/ src / lib / utils.ts
utils.ts
  1  import type { SearchType } from './types';
  2  import { external } from './config';
  3  
  4  /**
  5   * Determina el tipo de contenido basado en la URL y metadatos del ítem
  6   * @param link URL del recurso
  7   * @param defaultType Tipo por defecto a usar si no se determina otro
  8   * @returns El tipo de contenido determinado
  9   */
 10  export function determineContentType(link: string, defaultType: SearchType = 'web'): SearchType {
 11    if (!link) return defaultType;
 12    
 13    // Determinar tipo por extensión del archivo
 14    if (/\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff|ico)$/i.test(link)) {
 15      return 'images';
 16    }
 17    
 18    if (/\.(mp4|webm|avi|mov|wmv|flv|mkv)$/i.test(link)) {
 19      return 'videos';
 20    }
 21    
 22    if (/\.(mp3|wav|ogg|flac|aac)$/i.test(link)) {
 23      return 'audio';
 24    }
 25    
 26    if (/\.(pdf|doc|docx|xls|xlsx|ppt|pptx|odt|ods|odp|txt|rtf)$/i.test(link)) {
 27      return 'document';
 28    }
 29    
 30    if (/\.(exe|msi|apk|app|dmg|deb|rpm|zip|rar|tar|gz)$/i.test(link)) {
 31      return 'app';
 32    }
 33    
 34    return defaultType;
 35  }
 36  
 37  /**
 38   * Extrae el dominio de una URL
 39   * @param url URL a procesar
 40   * @returns Dominio extraído o cadena vacía si no es válido
 41   */
 42  export function extractDomain(url: string): string {
 43    if (!url) return '';
 44    
 45    try {
 46      const urlObj = new URL(url);
 47      return urlObj.hostname;
 48    } catch (error) {
 49      // Fallback con regex si la URL no es válida
 50      const domainMatch = url.match(/^(?:https?:\/\/)?([^\/]+)/i);
 51      return domainMatch ? domainMatch[1] : '';
 52    }
 53  }
 54  
 55  /**
 56   * Formatea una URL para visualización
 57   * @param url URL a formatear
 58   * @param maxLength Longitud máxima permitida
 59   * @returns URL formateada
 60   */
 61  export function formatUrl(url: string, maxLength: number = 50): string {
 62    if (!url) return '';
 63    
 64    try {
 65      const urlObj = new URL(url);
 66      const pathname = urlObj.pathname === '/' ? '' : urlObj.pathname;
 67      let formatted = urlObj.hostname + pathname;
 68      
 69      // Truncar si es muy largo
 70      if (formatted.length > maxLength) {
 71        formatted = formatted.substring(0, maxLength - 3) + '...';
 72      }
 73      return formatted;
 74    } catch (error) {
 75      return url.length > maxLength ? url.substring(0, maxLength - 3) + '...' : url;
 76    }
 77  }
 78  
 79  /**
 80   * Decodifica entidades HTML en un texto
 81   * @param html Texto con posibles entidades HTML
 82   * @returns Texto decodificado
 83   */
 84  export function decodeHtml(html: string): string {
 85    if (!html) return '';
 86    
 87    try {
 88      const txt = document.createElement('textarea');
 89      txt.innerHTML = html;
 90      return txt.value;
 91    } catch (error) {
 92      return html;
 93    }
 94  }
 95  
 96  /**
 97   * Trunca un texto a una longitud máxima
 98   * @param text Texto a truncar
 99   * @param maxLength Longitud máxima
100   * @returns Texto truncado
101   */
102  export function truncateText(text: string, maxLength: number): string {
103    if (!text) return '';
104    
105    return text.length > maxLength ? text.substring(0, maxLength) + '...' : text;
106  }
107  
108  /**
109   * Formatea un tamaño en bytes a formato legible
110   * @param bytes Tamaño en bytes
111   * @returns Tamaño formateado
112   */
113  export function formatSize(bytes: number): string {
114    if (isNaN(bytes) || bytes < 0) return '';
115    
116    if (bytes < 1024) return bytes + ' B';
117    if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
118    if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
119    
120    return (bytes / (1024 * 1024 * 1024)).toFixed(1) + ' GB';
121  }
122  
123  /**
124   * Obtiene la URL del favicon para un dominio
125   * @param domain Dominio del sitio
126   * @param size Tamaño del favicon (16, 32, 48, etc.)
127   * @returns URL del favicon
128   */
129  export function getFaviconUrl(domain: string, size: number = 32): string {
130    if (!domain) return '';
131    return `${external.faviconApi}?domain=${encodeURIComponent(domain)}&sz=${size}`;
132  }
133  
134  /**
135   * Genera una URL de imagen placeholder aleatoria
136   * @param width Ancho de la imagen
137   * @param height Alto de la imagen
138   * @param seed Semilla para la aleatorización (opcional)
139   * @returns URL de imagen placeholder
140   */
141  export function getPlaceholderImage(width: number = 800, height: number = 600, seed?: number): string {
142    const randomSeed = seed || Math.floor(Math.random() * 10000);
143    return `${external.placeholderImage}/${width}/${height}?random=${randomSeed}`;
144  }
145  
146  /**
147   * Obtiene una miniatura para un elemento basado en su tipo
148   * @param item Elemento del que obtener la miniatura
149   * @param type Tipo de contenido
150   * @returns URL de miniatura
151   */
152  export function getThumbnailUrl(item: any, type: SearchType): string {
153    // Para búsqueda de imágenes
154    if (type === 'images') {
155      // Verificar propiedades comunes para imágenes
156      if (item.image) return item.image;
157      if (item.thumbnail) return item.thumbnail;
158      if (item.imageurl) return item.imageurl;
159      if (item.iconlink) return item.iconlink;
160      
161      // Verificar propiedades en atributos
162      if (item.attr) {
163        if (item.attr.image) return item.attr.image;
164        if (item.attr.thumbnail) return item.attr.thumbnail;
165        if (item.attr.src) return item.attr.src;
166      }
167      
168      // Verificar si el link es una imagen
169      if (item.link && determineContentType(item.link) === 'images') {
170        return item.link;
171      }
172      
173      // Placeholder para imágenes
174      return getPlaceholderImage();
175    }
176    
177    // Para otros tipos de contenido
178    if (item.thumbnail) return item.thumbnail;
179    if (item.image) return item.image;
180    if (item.favicon) return item.favicon;
181    
182    // Usar favicon para contenido web
183    if (item.link) {
184      const domain = extractDomain(item.link);
185      if (domain) {
186        return getFaviconUrl(domain);
187      }
188    }
189    
190    return '';
191  }
192  
193  /**
194   * Debounce una función para evitar llamadas repetidas
195   * @param func Función a ejecutar
196   * @param wait Tiempo de espera en ms
197   * @returns Función con debounce
198   */
199  export function debounce<T extends (...args: any[]) => any>(
200    func: T, 
201    wait: number
202  ): (...args: Parameters<T>) => void {
203    let timeout: ReturnType<typeof setTimeout> | null = null;
204    
205    return function(...args: Parameters<T>): void {
206      const later = () => {
207        timeout = null;
208        func(...args);
209      };
210      
211      if (timeout) clearTimeout(timeout);
212      timeout = setTimeout(later, wait);
213    };
214  }
215  
216  /**
217   * Throttle una función para limitar la frecuencia de ejecución
218   * @param func Función a ejecutar
219   * @param limit Límite de tiempo en ms
220   * @returns Función con throttle
221   */
222  export function throttle<T extends (...args: any[]) => any>(
223    func: T, 
224    limit: number
225  ): (...args: Parameters<T>) => void {
226    let inThrottle = false;
227    
228    return function(...args: Parameters<T>): void {
229      if (!inThrottle) {
230        func(...args);
231        inThrottle = true;
232        setTimeout(() => (inThrottle = false), limit);
233      }
234    };
235  }