const fs = require('fs'); const axios = require('axios'); 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 EVOLUTION_API_URL = process.env.EVOLUTION_API_URL; const EVOLUTION_API_KEY = process.env.EVOLUTION_API_KEY; const EVOLUTION_INSTANCE_NAME = process.env.EVOLUTION_INSTANCE_NAME; const CHAT_ID = '573134848203-1408546044@g.us'; 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; async function getCollectionIds() { const list = await databases.listCollections(DB_ID); const map = {}; list.collections.forEach(c => { map[c.name.toLowerCase()] = c.$id; }); return map; } function extractText(msg) { if (msg.messageType === 'conversation') return msg.message.conversation; if (msg.messageType === 'extendedTextMessage') return msg.message.extendedTextMessage?.text; return null; } function inferPublicationType(text, typeMap) { if (!text) return typeMap['NOTICIA']; const t = text.toLowerCase(); // Check for Evento const eventKeywords = ['invita', 'invitación', 'asiste', 'te esperamos', 'evento', 'jornada', 'foro', 'congreso', 'feria', 'festival', 'encuentro', 'conmemoramos', 'celebramos']; if (eventKeywords.some(kw => t.includes(kw))) { return typeMap['EVENTO'] || typeMap['NOTICIA']; } // Check for Clasificado (or Convocatoria) const classKeywords = ['oferta', 'empleo', 'vacante', 'convocatoria', 'se busca', 'clasificado', 'postúlate', 'inscríbete']; if (classKeywords.some(kw => t.includes(kw))) { return typeMap['CLASIFICADO'] || typeMap['NOTICIA']; } return typeMap['NOTICIA']; } async function run() { console.log("Starting publishing process..."); try { const collections = await getCollectionIds(); const postsCollectionId = collections['posts'] || 'posts'; const mediaCollectionId = collections['media'] || 'media'; const pubTypesCollectionId = collections['publication types'] || collections['publication_types'] || 'publication_types'; const pubTypesRes = await databases.listDocuments(DB_ID, pubTypesCollectionId); const typeMap = {}; pubTypesRes.documents.forEach(d => { typeMap[d.codigo] = d.$id; }); // 1. Delete old posts console.log("Deleting old script posts..."); const resOld = await databases.listDocuments(DB_ID, postsCollectionId, [Query.limit(500)]); const scriptPosts = resOld.documents.filter(p => p.slug && p.slug.startsWith('gobernacion-cauca-mes-')); for (const p of scriptPosts) { await databases.deleteDocument(DB_ID, postsCollectionId, p.$id); } // 2. Fetch all messages const allRecords = []; let currentPage = 1; let totalPages = 1; console.log("Fetching messages from Evolution API..."); do { const response = await axios.post( `${EVOLUTION_API_URL}/chat/findMessages/${EVOLUTION_INSTANCE_NAME}?page=${currentPage}&limit=100`, { where: { "key": { "remoteJid": CHAT_ID } } }, { headers: { 'apikey': EVOLUTION_API_KEY, 'Content-Type': 'application/json' } } ); const messagesObj = response.data.messages || {}; totalPages = messagesObj.pages || 1; allRecords.push(...(messagesObj.records || [])); if (currentPage > 40) break; currentPage++; } while (currentPage <= totalPages); console.log(`Total messages fetched: ${allRecords.length}`); // 3. Process images and find associated text const targetMonths = [3, 4, 5, 6]; const monthCounts = { 3:0, 4:0, 5:0, 6:0 }; for (let i = 0; i < allRecords.length; i++) { const msg = allRecords[i]; if (msg.messageType !== 'imageMessage') continue; const ts = msg.messageTimestamp; const date = new Date(ts * 1000); const month = date.getMonth() + 1; if (!targetMonths.includes(month)) continue; if (monthCounts[month] >= 8) continue; // max 8 per month // Find associated text let caption = msg.message?.imageMessage?.caption; if (!caption) { // Look backwards and forwards within 10 minutes (600 seconds) let bestText = null; for (let j = Math.max(0, i - 10); j < Math.min(allRecords.length, i + 10); j++) { const nearbyMsg = allRecords[j]; if (Math.abs(nearbyMsg.messageTimestamp - ts) > 600) continue; const text = extractText(nearbyMsg); if (text && text.length > 20 && !text.toLowerCase().includes("comparte en tus historias")) { bestText = text; if (j > i) break; } } if (bestText) caption = bestText; } let cleanCaption = caption || `Publicación gráfica de la Gobernación del Cauca para el mes de ${month}.`; let title = `Boletín Gobernación - Mes ${month} #${monthCounts[month] + 1}`; if (cleanCaption.length > 20) { const lines = cleanCaption.split('\n').map(l => l.replace(/^[\p{Emoji}\s*_*]+/gu, '').trim()).filter(l => l.length > 5); if (lines.length > 0) { title = lines[0]; const genericTitles = ['flash informativo', 'boletín', 'gobernación del cauca', 'comunicado', 'atención', 'apreciados periodistas', 'medios de comunicación']; const isGeneric = genericTitles.some(g => title.toLowerCase().includes(g)) || title.toUpperCase() === title || title.length < 25; if (isGeneric && lines.length > 1) { title = `${title} | ${lines[1]}`; } if (title.length > 100) title = title.substring(0, 100) + '...'; } } const inferredType = inferPublicationType(cleanCaption, typeMap); try { // Get base64 const base64Res = await axios.post( `${EVOLUTION_API_URL}/chat/getBase64FromMediaMessage/${EVOLUTION_INSTANCE_NAME}`, { message: msg }, { headers: { 'apikey': EVOLUTION_API_KEY, 'Content-Type': 'application/json' } } ); const base64Data = base64Res.data.base64; if (!base64Data) continue; const buffer = Buffer.from(base64Data, 'base64'); const filePath = `/tmp/image-${month}-${monthCounts[month]}.jpeg`; fs.writeFileSync(filePath, buffer); let fileParam; if (InputFile && InputFile.fromPath) fileParam = InputFile.fromPath(filePath, `image.jpeg`); else if (InputFile && InputFile.fromBuffer) fileParam = InputFile.fromBuffer(buffer, `image.jpeg`); 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: title, tipo: 'imagen' }); const slug = `gobernacion-cauca-mes-${month}-pub-${monthCounts[month]}-${Date.now()}`; await databases.createDocument(DB_ID, postsCollectionId, ID.unique(), { titulo: title, slug: slug, tipo_publicacion_id: inferredType, estado: 'borrador', fecha_publicacion: date.toISOString(), contenido: `

${cleanCaption.replace(/\n/g, '
')}

`, imagen_destacada_id: mediaDoc.$id }); console.log(`[Success] Published post ${monthCounts[month] + 1}/8 for Month ${month}: ${title} (${Object.keys(typeMap).find(k => typeMap[k] === inferredType)})`); monthCounts[month]++; } catch (err) { console.error(`Failed to publish image for month ${month}:`, err.message); } } console.log("All done!"); } catch (e) { console.error("Error:", e.message); } } run();