const axios = require('axios'); const fs = require('fs'); const cheerio = require('cheerio'); const stringSimilarity = require('string-similarity'); 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 excludedNodes = ['962', '963', '895', '894', '893', '964']; async function scrapeNode(url) { try { const res = await axios.get(url); const $ = cheerio.load(res.data); const title = $('h1').first().text().replace(/\|.*/, '').trim() || $('title').text().replace(/\|.*/, '').trim(); // Extract date let dateObj = null; const timeText = $('span').filter((i, el) => $(el).text().match(/\d{2}\/\d{2}\/\d{4} - \d{2}:\d{2}/)).first().text(); if (timeText) { const match = timeText.match(/(\d{2})\/(\d{2})\/(\d{4}) - (\d{2}:\d{2})/); if (match) { dateObj = new Date(`${match[3]}-${match[2]}-${match[1]}T${match[4]}:00.000Z`); } } if (!dateObj) return null; // We only want April (3), May (4), June (5) 2026 const validMonths = [3, 4, 5]; if (!validMonths.includes(dateObj.getMonth()) || dateObj.getFullYear() !== 2026) { return { skipped: true, reason: 'Not in April, May, or June 2026' }; } // Extract image let image = null; $('img').each((i, el) => { const src = $(el).attr('src'); if (src && (src.includes('public/imagenes') || src.includes('styles/')) && !src.includes('logo')) { image = src.startsWith('http') ? src : 'https://www.multivisiondecolombia.com' + src; return false; } }); // Extract content let contentHtml = ''; const bodyField = $('.field--name-body'); if (bodyField.length > 0) { contentHtml = bodyField.html(); } else { contentHtml = $('article').html() || ''; } const $content = cheerio.load(contentHtml || ''); $content('*').each((i, el) => { if (!['p', 'ul', 'li', 'strong', 'em', 'b', 'i'].includes(el.tagName.toLowerCase())) { if (el.tagName.toLowerCase() === 'br') { $content(el).replaceWith('
'); } else { $content(el).replaceWith($content(el).text()); } } }); const finalContent = $content.html() || '

'; return { title, image, date: dateObj.toISOString(), content: finalContent }; } catch (e) { console.error('Error scraping node', url, e.message); return null; } } 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'; const pubTypesCollectionId = map['publication types'] || map['publication_types'] || 'publication_types'; const typeRes = await databases.listDocuments(DB_ID, pubTypesCollectionId, [Query.equal('codigo', 'NOTICIA')]); const typeId = typeRes.documents.length > 0 ? typeRes.documents[0].$id : null; console.log('Fetching existing titles from Appwrite to prevent duplicates...'); let allExistingTitles = []; let cursor = null; while (true) { const queries = [Query.limit(100), Query.select(['titulo'])]; if (cursor) queries.push(Query.cursorAfter(cursor)); const existingRes = await databases.listDocuments(DB_ID, postsCollectionId, queries); existingRes.documents.forEach(d => allExistingTitles.push(d.titulo)); if (existingRes.documents.length < 100) break; cursor = existingRes.documents[existingRes.documents.length - 1].$id; } console.log(`Found ${allExistingTitles.length} existing posts.`); console.log('Fetching news list from Multivision (Pages 0-15)...'); const allNodes = new Set(); for (let page = 0; page <= 15; page++) { try { const listUrl = `https://www.multivisiondecolombia.com/noticias?page=${page}`; const res = await axios.get(listUrl); const $ = cheerio.load(res.data); $('a').each((i, el) => { const href = $(el).attr('href'); if (href && href.startsWith('/node/')) { const nodeId = href.split('/')[2]; if (nodeId && !excludedNodes.includes(nodeId)) { allNodes.add(`https://www.multivisiondecolombia.com/node/${nodeId}`); } } }); } catch(e) { console.log(`Failed to fetch page ${page}`); } } console.log(`Found ${allNodes.size} valid nodes to process.`); let count = 0; for (const url of allNodes) { console.log(`\nProcessing: ${url}`); const data = await scrapeNode(url); if (!data) { console.log('No data extracted.'); continue; } if (data.skipped) { console.log('Skipped:', data.reason); continue; } // Check similarity let isDuplicate = false; let highestSimilarity = 0; let mostSimilarTitle = ''; for (const exTitle of allExistingTitles) { if (!exTitle) continue; const sim = stringSimilarity.compareTwoStrings(data.title.toLowerCase(), exTitle.toLowerCase()); if (sim > highestSimilarity) { highestSimilarity = sim; mostSimilarTitle = exTitle; } } if (highestSimilarity > 0.60) { console.log(`Skipped due to similarity (${(highestSimilarity*100).toFixed(1)}%) with: "${mostSimilarTitle}"`); continue; } console.log('Extracted Title:', data.title); console.log('Extracted Date:', data.date); let mediaId = null; if (data.image) { try { const imgRes = await axios.get(data.image, { responseType: 'arraybuffer' }); const buffer = Buffer.from(imgRes.data, 'binary'); const filePath = `/tmp/multivision_${count}.jpg`; fs.writeFileSync(filePath, buffer); let fileParam; if (InputFile && InputFile.fromPath) fileParam = InputFile.fromPath(filePath, `image${count}.jpg`); else fileParam = fs.createReadStream(filePath); const file = await storage.createFile(BUCKET_ID, ID.unique(), fileParam); const mediaDoc = await databases.createDocument(DB_ID, mediaCollectionId, ID.unique(), { file_id: file.$id, bucket_id: BUCKET_ID, titulo: data.title, tipo: 'imagen' }); mediaId = mediaDoc.$id; } catch(err) { console.error('Error uploading image:', err.message); } } const doc = { titulo: data.title.substring(0, 100), slug: `multivision-amj-${Date.now()}-${count}`, tipo_publicacion_id: typeId, estado: 'Publicado', fecha_publicacion: data.date, contenido: data.content, imagen_destacada_id: mediaId }; const res = await databases.createDocument(DB_ID, postsCollectionId, ID.unique(), doc); console.log('Published! ID:', res.$id); // Add to existing to prevent duplicates within this run allExistingTitles.push(data.title); count++; } console.log(`\nAll done! Successfully published ${count} unique news articles from April, May, and June.`); } catch(e) { console.error(e); } } run();