import { Readability } from '@mozilla/readability' import { JSDOM } from 'jsdom' import DOMPurify from 'isomorphic-dompurify' import { applyRtlToHtmlBlocks, readPageLocaleFromHtml, resolveClipLocale, wrapClipArticleHtml, type ClipLocaleHint, } from '@/lib/clip/rtl-content' export interface ExtractedArticle { title: string content: string textContent: string excerpt: string locale: ClipLocaleHint } const META_PATTERNS = [ // Dates/heures avec "mis à jour / updated / actualizado / aktualisiert" /\b(mis à jour|updated at|actualizado|aktualisiert am|aggiornato alle)\b[\s:|-]*.*/gi, // Temps de lecture /\b(temps de lecture|reading time|lesedauer|tiempo de lectura|tempo di lettura|czas czytania|время чтения|阅读时间|読了時間)\b[\s:|-]*.*/gi, // Lignes de type "Source - Aujourd'hui à 09:15 | mis à jour à 11:07" /^(.*?\s[-–|]\s)(aujourd['’]?hui|today|hoy|heute|oggi|vandaag|dzisiaj|сегодня|今日)\b.*$/gim, // Heures seules type "09:15" ou "09:15 | mis à jour..." /^\s*\d{1,2}:\d{2}(\s*[-|]\s*.*)?$/gm, ] export function cleanClipText(text: string): string { let cleaned = text.replace(/\r\n/g, '\n').replace(/\n{3,}/g, '\n\n') for (const pattern of META_PATTERNS) { cleaned = cleaned.replace(pattern, '') } return cleaned .split('\n') .map((line) => line.trim()) .filter((line) => line.length > 0) .join('\n') .replace(/\n{3,}/g, '\n\n') .trim() } export function makeExcerpt(text: string, maxLen = 500): string { if (text.length <= maxLen) return text // Préférer couper à la fin d'une phrase. const sentenceEnd = text.lastIndexOf('.', maxLen) if (sentenceEnd > maxLen * 0.6) return text.slice(0, sentenceEnd + 1).trim() // Sinon couper au dernier espace. const space = text.lastIndexOf(' ', maxLen) if (space > maxLen * 0.6) return text.slice(0, space).trim() + '…' return text.slice(0, maxLen).trim() + '…' } export function extractArticleFromHtml(html: string, pageUrl: string): ExtractedArticle | null { const dom = new JSDOM(html, { url: pageUrl }) const reader = new Readability(dom.window.document) const article = reader.parse() if (!article) return null const pageLocale = readPageLocaleFromHtml(html) const readabilityDir = article.dir?.toLowerCase() === 'rtl' ? 'rtl' : 'ltr' const readabilityLang = article.lang?.split('-')[0]?.toLowerCase() const locale = resolveClipLocale( pageUrl, article.title || '', article.textContent || '', ) const mergedLocale: ClipLocaleHint = { direction: readabilityDir === 'rtl' || pageLocale.direction === 'rtl' || locale.direction === 'rtl' ? 'rtl' : 'ltr', lang: (readabilityLang === 'fa' || readabilityLang === 'ar' || readabilityLang === 'he' ? readabilityLang : undefined) || locale.lang || pageLocale.lang, } const sanitized = DOMPurify.sanitize(article.content || '') const rtlBlocks = applyRtlToHtmlBlocks(sanitized, mergedLocale) const content = wrapClipArticleHtml(rtlBlocks, mergedLocale) const cleanText = cleanClipText(article.textContent || '') return { title: (article.title || '').trim(), content, textContent: cleanText, excerpt: makeExcerpt(cleanText, 500), locale: mergedLocale, } } export function clipFooterLocaleTag(lang?: string): string { if (lang === 'fa') return 'fa-IR' if (lang === 'ar') return 'ar' if (lang === 'he') return 'he-IL' return 'fr-FR' } export function buildClipSourceFooter(domain: string, date: Date, localeTag = 'fr-FR'): string { const formatted = date.toLocaleDateString(localeTag, { day: 'numeric', month: 'long', year: 'numeric' }) const isRtl = localeTag.startsWith('fa') || localeTag.startsWith('ar') || localeTag.startsWith('he') const label = localeTag.startsWith('fa') ? `برگرفته از ${domain} — ${formatted}` : localeTag.startsWith('ar') ? `مقتبس من ${domain} — ${formatted}` : `Extrait de ${domain} le ${formatted}` const dirAttr = isRtl ? ' dir="rtl"' : '' return `
${DOMPurify.sanitize(label)}
` }