const axios = require('axios'); const cheerio = require('cheerio'); const fs = require('fs'); const { Client, Databases, Storage, ID, Query } = require('node-appwrite'); require('dotenv').config({ path: '.env.local' }); let InputFile; try { InputFile = require('node-appwrite/file').InputFile; } catch (e) { InputFile = null; } const client = new Client() .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT) .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY); const databases = new Databases(client); const storage = new Storage(client); const DB_ID = process.env.APPWRITE_DATABASE_ID; const BUCKET_ID = process.env.NEXT_PUBLIC_APPWRITE_MEDIA_BUCKET_ID; const scrapedNews = [ { title: "Gobernación del Cauca lideró mesa técnica para fortalecer la respuesta ante la actividad del volcán Puracé", dateStr: "jueves, 20 de agosto de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobernación-del-Cauca-lideró-mesa-técnica-para-fortalecer-la-respuesta-ante-la-actividad-del-volcán-Puracé.aspx" }, { title: "¡Gracias Cauca! Ayudas humanitarias recolectadas superan las 80 toneladas", dateStr: "miércoles, 19 de agosto de 2026", link: "/Prensa/SaladePrensa/Paginas/-¡Gracias-Cauca!-Ayudas-humanitarias-recolectadas-superan-las-80-toneladas.aspx" }, { title: "Gobernación del Cauca fortalece la conectividad vial en la vereda El Túnel, Cajibío", dateStr: "martes, 18 de agosto de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobernación-del-Cauca-fortalece-la-conectividad-vial-en-la-vereda-El-Túnel,-Cajibío.aspx" }, { title: "Gobierno Departamental mantiene seguimiento a afectaciones ocasionadas por el sismo del 10 de agosto", dateStr: "martes, 18 de agosto de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobierno-Departamental-mantiene-seguimiento-a-afectaciones-ocasionadas-por-el-sismo-del-10-de-agosto-.aspx" }, { title: "Más de 40 toneladas de ayudas serán entregadas en las regiones afectadas del país", dateStr: "viernes, 14 de agosto de 2026", link: "/Prensa/SaladePrensa/Paginas/Más-de-40-toneladas-de-ayudas-serán-entregadas-en-las-regiones-afectadas-del-país.aspx" }, { title: "Gobierno departamental hace llamado vehemente al Gobierno Nacional frente a la situación de orden público que atenta contra...", dateStr: "jueves, 13 de agosto de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobierno-departamental-hace-llamado-vehemente-al-Gobierno-Nacional-frente-a-la-situación-de-orden-público-que-atenta-contra.aspx" }, { title: "Gobernación del Cauca sigue consolidando afectaciones tras el sismo del 10 de agosto", dateStr: "jueves, 13 de agosto de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobernación-del-Cauca-sigue-consolidando-afectaciones-tras-el-sismo-del-10-de-agosto.aspx" }, { title: "Gobernación del Cauca sustentó proyecto para reducir hasta el 80 % de intereses y sanciones en impuestos departamentales", dateStr: "jueves, 13 de agosto de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobernación-del-Cauca-sustentó-proyecto-para-reducir-hasta-el-80--de-intereses-y-sanciones-en-impuestos-departamentales.aspx" }, { title: "Gobernación del Cauca activa Plan de Evaluación Integral tras sismo de 7.4 registrado en el país", dateStr: "lunes, 10 de agosto de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobernación-del-Cauca-activa-Plan-de-Evaluación-Integral-tras-sismo-de-7-4-registrado-en-el-país.aspx" }, { title: "Gobernación del Cauca está en permanente monitoreo y atención a municipios afectados tras el sismo de 7.4", dateStr: "lunes, 10 de agosto de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobernación-del-Cauca-está-en-permanente-monitoreo-y-atención-a-municipios-afectados-tras-el-sismo-de-7-4.aspx" }, { title: "Gobernación del Cauca desplegó acciones simultáneas frente a la alerta volcánica del Puracé, la protección de la vía Panamericana...", dateStr: "domingo, 9 de agosto de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobernación-del-Cauca-desplegó-acciones-simultáneas-frente-a-la-alerta-volcánica-del-Puracé,-la-protección-de-la-vía-Paname.aspx" }, { title: "Gobernación del Cauca entregó ayudas humanitarias ante la emergencia generada por la actividad del volcán Puracé", dateStr: "miércoles, 5 de agosto de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobernación-del-Cauca-entregó-ayudas-humanitarias-ante-la-emergencia-generada-por-la-actividad-del-volcán-Puracé.aspx" }, { title: "Gobernación del Cauca intensifica acciones para proteger la salud de las comunidades tras la alerta naranja del volcán Puracé", dateStr: "sábado, 1 de agosto de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobernación-del-Cauca-intensifica-acciones-para-proteger-la-salud-de-las-comunidades-tras-la-alerta-naranja-del-volcán-Pura.aspx" }, { title: "Gobernación del Cauca unifica sus trámites tributarios en el portal web institucional en cumplimiento de la Ley de Transparencia", dateStr: "martes, 28 de julio de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobernación-del-Cauca-unifica-sus-trámites-tributarios-en-el-portal-web-institucional-en-cumplimiento-de-la-Ley-de-Transpar.aspx" }, { title: "Invitación rueda de prensa sobre el estado actual de la Industria Licorera del Cauca", dateStr: "martes, 28 de julio de 2026", link: "/Prensa/SaladePrensa/Paginas/Invitación-rueda-de-prensa-sobre-el-estado-actual-de-la-Industria-Licorera-del-Cauca.aspx" }, { title: "Gobierno Departamental fortalece la defensa de los Derechos Humanos con la Mesa Territorial de Garantías", dateStr: "lunes, 27 de julio de 2026", link: "/Prensa/SaladePrensa/Paginas/-Gobierno-Departamental-fortalece-la-defensa-de-los-Derechos-Humanos-con-la-Mesa-Territorial-de-Garantías--En-el-marco-del-.aspx" }, { title: "La Afrocolombianidad se vive y se fortalece en el Cauca con La Fuerza del Pueblo", dateStr: "viernes, 24 de julio de 2026", link: "/Prensa/SaladePrensa/Paginas/La-Afrocolombianidad-se-vive-y-se-fortalece-en-el-Cauca-con-La-Fuerza-del-Pueblo.aspx" }, { title: "Gobernación del Cauca hace un llamado urgente a entidades competentes para garantizar atención en salud a más de 400.000 afiliados de Asmet Salud", dateStr: "jueves, 23 de julio de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobernación-del-Cauca-hace-un-llamado-urgente-a-entidades-competentes-para-garantizar-atención-en-salud-a-más-de-400-000-af.aspx" }, { title: "Golpe al contrabando en el Norte del Cauca: decomisadas 586 unidades de licor", dateStr: "martes, 21 de julio de 2026", link: "/Prensa/SaladePrensa/Paginas/-Golpe-al-contrabando-en-el-Norte-del-Cauca-decomisadas-586-unidades-de-licor.aspx" }, { title: "Con Escuela y Café, la Gobernación del Cauca fortalece la formación de los jóvenes del departamento", dateStr: "martes, 21 de julio de 2026", link: "/Prensa/SaladePrensa/Paginas/Con-Escuela-y-Café,-la-Gobernación-del-Cauca-fortalece-la-formación-de-los-jóvenes-del-departamento.aspx" }, { title: "Vías para la Vida: el sector rural de Popayán, una prioridad para el Gobierno Departamental", dateStr: "sábado, 11 de julio de 2026", link: "/Prensa/SaladePrensa/Paginas/Vías-para-la-Vida-el-sector-rural-de-Popayán,-una-prioridad-para-el-Gobierno-Departamental.aspx" }, { title: "La paz se construye con acciones en los territorios: Gobernación del Cauca entrega ayudas integrales en El Carmelo, Cajibío", dateStr: "miércoles, 8 de julio de 2026", link: "/Prensa/SaladePrensa/Paginas/La-paz-se-construye-con-acciones-en-los-territorios-Gobernación-del-Cauca-entrega-ayudas-integrales-en-El-Carmelo,-Cajibío.aspx" }, { title: "El Cauca se destaca por su gestión administrativa a nivel nacional", dateStr: "lunes, 6 de julio de 2026", link: "/Prensa/SaladePrensa/Paginas/El-Cauca-se-destaca-por-su-gestión-administrative-a-nivel-nacional.aspx" }, { title: "Gobernación del Cauca alerta sobre enlace fraudulento para el pago del impuesto vehicular", dateStr: "lunes, 6 de julio de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobernación-del-Cauca-alerta-sobre-enlace-fraudulento-para-el-pago-del-impuesto-vehicular.aspx" }, { title: "Cauca se consolida como referente mundial del café con la VI Feria \"Cauca Sabe a Café 2026\"", dateStr: "viernes, 26 de junio de 2026", link: "/Prensa/SaladePrensa/Paginas/-Cauca-se-consolida-como-referente-mundial-del-café-con-la-VI-Feria-Cauca-Sabe-a-Café-2026.aspx" }, { title: "Este 26 de junio Indeportes Cauca rinde cuentas a la ciudadanía", dateStr: "martes, 23 de junio de 2026", link: "/Prensa/SaladePrensa/Paginas/-Este-26-de-junio-Indeportes-Cauca-rinde-cuentas-a-la-ciudadanía.aspx" }, { title: "Participación histórica fortalece la democracia en el Cauca", dateStr: "domingo, 21 de junio de 2026", link: "/Prensa/SaladePrensa/Paginas/-Participación-histórica-fortalece-la-democracia-en-el-Cauca.aspx" }, { title: "Con normalidad y garantías, se realiza la segunda vuelta presidencial en los 42 municipios del Cauca", dateStr: "domingo, 21 de junio de 2026", link: "/Prensa/SaladePrensa/Paginas/Con-normalidad-y-garantías,-se-realiza-la-segunda-vuelta-presidencial-en-los-42-municipios-del-Cauca.aspx" }, { title: "Cauca fortalece garantías para un proceso electoral seguro, transparente y participativo en la segunda vuelta presidencial", dateStr: "miércoles, 10 de junio de 2026", link: "/Prensa/SaladePrensa/Paginas/Cauca-fortalece-garantías-para-un-proceso-electoral-seguro,-transparente-y-participativo-en-la-segunda-vuelta-presidencial.aspx" }, { title: "La Industria Licorera del Cauca se vistió de oro en París", dateStr: "martes, 9 de junio de 2026", link: "/Prensa/SaladePrensa/Paginas/La-Industria-Licorera-del-Cauca-se-vistió-de-oro-en-París.aspx" }, { title: "Gobernación del Cauca fortalece acciones de vigilancia sanitaria en establecimientos de estética", dateStr: "sábado, 6 de junio de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobernación-del-Cauca-fortalece-acciones-de-vigilancia-sanitaria-en-establecimientos-de-estética.aspx" }, { title: "Gobernación del Cauca fortalece el bienestar animal, la movilidad rural and la productividad campesina en Timbío", dateStr: "viernes, 5 de junio de 2026", link: "/Prensa/SaladePrensa/Paginas/Gobernación-del-Cauca-fortalece-el-bienestar-animal,-la-movilidad-rural-y-la-productividad-campesina-en-Timbío.aspx" } ]; function slugify(text) { return text .toString() .toLowerCase() .normalize('NFD') .replace(/[\u0300-\u036f]/g, '') .replace(/[^a-z0-9 -]/g, '') .replace(/\s+/g, '-') .replace(/-+/g, '-') .trim(); } async function run() { try { const collections = await databases.listCollections(DB_ID); const map = {}; collections.collections.forEach(c => { map[c.name.toLowerCase()] = c.$id; }); const postsCollectionId = map['posts'] || 'posts'; const mediaCollectionId = map['media'] || 'media'; console.log(`Starting self-healing scan on ${scrapedNews.length} articles...`); for (let i = 0; i < scrapedNews.length; i++) { const item = scrapedNews[i]; const slug = `gobernacion-web-${slugify(item.title)}`; console.log(`\nChecking [${i+1}/${scrapedNews.length}]: "${item.title}"`); const postsRes = await databases.listDocuments(DB_ID, postsCollectionId, [ Query.equal('slug', slug), Query.limit(1) ]); if (postsRes.documents.length === 0) { console.log(`-> Post not found in database. Skipping.`); continue; } const post = postsRes.documents[0]; let needsHealing = false; let oldMediaId = post.imagen_destacada_id; let oldFileId = null; if (!oldMediaId) { console.log("-> Post has no featured image ID. Needs healing!"); needsHealing = true; } else { try { const mediaDoc = await databases.getDocument(DB_ID, mediaCollectionId, oldMediaId); oldFileId = mediaDoc.file_id; const file = await storage.getFile(BUCKET_ID, oldFileId); if (file.mimeType.includes('html') || file.mimeType.includes('text')) { console.log(`-> Found corrupted HTML file payload (ID: ${oldFileId}, Mime: ${file.mimeType}). Needs healing!`); needsHealing = true; } else { console.log(`-> Image is valid (Mime: ${file.mimeType}).`); } } catch (e) { console.log(`-> Missing file or media document in Appwrite: ${e.message}. Needs healing!`); needsHealing = true; } } if (!needsHealing) continue; const url = 'https://cauca.gov.co' + item.link; console.log(`-> Healing: Fetching webpage to scrape image... ${url}`); try { const res = await axios.get(encodeURI(decodeURI(url)), { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36' }, timeout: 10000 }); const $ = cheerio.load(res.data); let image = null; // 1. Check body content first $('.ms-rtestate-field img').each((idx, el) => { const src = $(el).attr('src'); if (src && !src.includes('logo') && !src.includes('icon')) { image = src.startsWith('http') ? src : 'https://cauca.gov.co' + src; return false; } }); // 2. Try old path if (!image) { $('img').each((idx, el) => { const src = $(el).attr('src'); if (src && src.includes('/Prensa/PublishingImages/')) { image = src.startsWith('http') ? src : 'https://cauca.gov.co' + src; return false; } }); } // 3. Try general image fallback if (!image) { $('img').each((idx, el) => { const src = $(el).attr('src'); if (src && !src.includes('logo') && !src.includes('icon') && !src.includes('menu') && src.length > 10) { image = src.startsWith('http') ? src : 'https://cauca.gov.co' + src; return false; } }); } if (!image) { console.log("-> Could not find any valid image on this article page."); continue; } console.log(`-> Found image URL: ${image}`); console.log("-> Downloading binary image with correct User-Agent..."); const imgRes = await axios.get(encodeURI(decodeURI(image)), { responseType: 'arraybuffer', timeout: 10000, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36' } }); const buffer = Buffer.from(imgRes.data, 'binary'); // Verify that the response is actually binary image and not HTML const isHtml = buffer.toString('utf-8', 0, 50).trim().toLowerCase().startsWith(' Error: Image request returned HTML instead of binary image data!"); continue; } const urlParts = image.split('/'); const filename = urlParts[urlParts.length - 1] || 'image.jpg'; const filePath = `/tmp/heal_${ID.unique()}_${filename}`; fs.writeFileSync(filePath, buffer); let fileParam; if (InputFile && InputFile.fromPath) fileParam = InputFile.fromPath(filePath, filename); else fileParam = fs.createReadStream(filePath); console.log("-> Uploading new binary image file to Appwrite..."); const newFile = await storage.createFile(BUCKET_ID, ID.unique(), fileParam); console.log("-> Creating new media document in database..."); const newMediaDoc = await databases.createDocument(DB_ID, mediaCollectionId, ID.unique(), { file_id: newFile.$id, bucket_id: BUCKET_ID, titulo: item.title, tipo: 'imagen' }); console.log(`-> Updating post document "${post.titulo}" to link new image...`); await databases.updateDocument(DB_ID, postsCollectionId, post.$id, { imagen_destacada_id: newMediaDoc.$id }); // Cleanup temp file try { fs.unlinkSync(filePath); } catch (e) {} // Delete old corrupted file and media document if they existed if (oldFileId) { console.log(`-> Cleaning up old corrupted file ID ${oldFileId} from storage...`); try { await storage.deleteFile(BUCKET_ID, oldFileId); } catch (e) { console.log(` (Failed to delete file: ${e.message})`); } } if (oldMediaId) { console.log(`-> Cleaning up old corrupted media document ID ${oldMediaId} from database...`); try { await databases.deleteDocument(DB_ID, mediaCollectionId, oldMediaId); } catch (e) { console.log(` (Failed to delete media document: ${e.message})`); } } console.log(`[HEALED SUCCESS] Post "${post.titulo}" now has its correct featured image!`); } catch (err) { console.error(`-> Error healing post:`, err.message); } } console.log("\nSelf-healing scan complete!"); } catch (e) { console.error("Fatal Error:", e.message); } } run();