feat: Add script to distribute publications across past months and publish them directly
This commit is contained in:
parent
a0eaad5315
commit
404314a54d
202
publish-cauca-all-months.js
Normal file
202
publish-cauca-all-months.js
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
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];
|
||||||
|
let allImages = allRecords.filter(m => m.messageType === 'imageMessage');
|
||||||
|
console.log(`Found ${allImages.length} images in history.`);
|
||||||
|
|
||||||
|
let imageIndex = 0;
|
||||||
|
|
||||||
|
for (const month of targetMonths) {
|
||||||
|
let publishedCount = 0;
|
||||||
|
console.log(`\n--- Publishing for Month ${month} ---`);
|
||||||
|
|
||||||
|
while (publishedCount < 8 && imageIndex < allImages.length) {
|
||||||
|
const msg = allImages[imageIndex];
|
||||||
|
imageIndex++;
|
||||||
|
|
||||||
|
// Find associated text in the original allRecords array
|
||||||
|
let caption = msg.message?.imageMessage?.caption;
|
||||||
|
if (!caption) {
|
||||||
|
const idx = allRecords.indexOf(msg);
|
||||||
|
let bestText = null;
|
||||||
|
for (let j = Math.max(0, idx - 10); j < Math.min(allRecords.length, idx + 10); j++) {
|
||||||
|
const nearbyMsg = allRecords[j];
|
||||||
|
if (Math.abs(nearbyMsg.messageTimestamp - msg.messageTimestamp) > 600) continue;
|
||||||
|
const text = extractText(nearbyMsg);
|
||||||
|
if (text && text.length > 20 && !text.toLowerCase().includes("comparte en tus historias")) {
|
||||||
|
bestText = text;
|
||||||
|
if (j > idx) 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} #${publishedCount + 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 {
|
||||||
|
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}-${publishedCount}.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-${publishedCount}-${Date.now()}`;
|
||||||
|
|
||||||
|
// Artificial date corresponding to the target month
|
||||||
|
const artificialDate = new Date(2026, month - 1, 15 + publishedCount, 12, 0, 0).toISOString();
|
||||||
|
|
||||||
|
await databases.createDocument(DB_ID, postsCollectionId, ID.unique(), {
|
||||||
|
titulo: title,
|
||||||
|
slug: slug,
|
||||||
|
tipo_publicacion_id: inferredType,
|
||||||
|
estado: 'Publicado',
|
||||||
|
fecha_publicacion: artificialDate,
|
||||||
|
contenido: `<p>${cleanCaption.replace(/\n/g, '<br/>')}</p>`,
|
||||||
|
imagen_destacada_id: mediaDoc.$id
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`[Success] Published post ${publishedCount + 1}/8 for Month ${month}: ${title}`);
|
||||||
|
publishedCount++;
|
||||||
|
} 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();
|
||||||
Loading…
Reference in New Issue
Block a user