import '@dotenvx/dotenvx/config'; import { readFileSync, readdirSync, existsSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { google } from 'googleapis'; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = join(__dirname, '..'); const NEWS_DIR = join(ROOT, 'src', 'content', 'news'); const LANGUAGES = ['es', 'en', 'fr', 'pt', 'rw', 'he', 'uk', 'ru', 'kr']; function extractField(fm, field) { const re = new RegExp(`^${field}:\\s*(.*)$`, 'm'); const m = fm.match(re); if (!m) return ''; let val = m[1].trim(); if ((val.startsWith("'") && val.endsWith("'")) || (val.startsWith('"') && val.endsWith('"'))) { val = val.slice(1, -1); } return val; } const ROUTE_TRANSLATIONS = { es: "noticias", en: "news", fr: "informations", he: "\u05d7\u05d3\u05e9\u05d5\u05ea", uk: "noticias", pt: "noticias", ru: "\u043d\u043e\u0432\u043e\u0441\u0442\u0438", rw: "amakuru", kr: "nouvel", }; function articleUrl(locale, slug) { const route = ROUTE_TRANSLATIONS[locale] || 'news'; return `https://www.centrodelreinodepazyjusticia.com/${locale}/${route}/${slug}`; } async function clearSheet(sheetName) { const saEmail = process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL; const pk = process.env.GOOGLE_PRIVATE_KEY; const sid = process.env.GOOGLE_SHEET_ID; if (!saEmail || !pk || !sid) { console.log(`[send-to-n8n] Skipping sheet clear — missing Google credentials`); return; } const auth = new google.auth.GoogleAuth({ credentials: { client_email: saEmail, private_key: pk.replace(/\\n/g, '\n'), }, scopes: ['https://www.googleapis.com/auth/spreadsheets'], }); const sheets = google.sheets({ version: 'v4', auth }); await sheets.spreadsheets.values.clear({ spreadsheetId: sid, range: `${sheetName}!A:Z`, }); console.log(`[send-to-n8n] Cleared sheet: ${sheetName}`); } function parseMeta(filePath) { const raw = readFileSync(filePath, 'utf-8'); const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/); if (!m) return {}; const fm = m[1]; return { locale: extractField(fm, 'locale'), title: extractField(fm, 'title'), date: extractField(fm, 'date'), draft: extractField(fm, 'draft'), slug: extractField(fm, 'slug'), }; } function normFilename(fileName) { const name = fileName.replace(/\.md$/, ''); return name.replace(/^(\d{4}-\d{2}-\d{2})-0(\d)$/, '$1-$2'); } async function main() { const groups = {}; for (const lang of LANGUAGES) { const dir = join(NEWS_DIR, lang); if (!existsSync(dir)) continue; for (const file of readdirSync(dir).filter(f => f.endsWith('.md'))) { const meta = parseMeta(join(dir, file)); if (meta.draft === 'true') continue; const key = normFilename(file); if (!groups[key]) groups[key] = {}; groups[key][lang] = { file, title: meta.title || '', date: meta.date || '', slug: meta.slug || '', }; } } const allKeys = Object.keys(groups).sort(); const esKeys = allKeys.filter(k => groups[k].es); const summary = Object.fromEntries(LANGUAGES.map(l => { const matches = esKeys.filter(k => groups[k][l]).length; return [l, { total: allKeys.filter(k => groups[k][l]).length, translated: matches, missing: esKeys.length - matches, percent: esKeys.length > 0 ? Math.round((matches / esKeys.length) * 100) : 0, }]; })); const articles = esKeys.map(k => { const es = groups[k].es; const files = Object.fromEntries(LANGUAGES.map(l => [l, groups[k][l]?.file || null])); const slugs = Object.fromEntries(LANGUAGES.map(l => [l, groups[k][l]?.slug || null])); const urls = Object.fromEntries(LANGUAGES.map(l => { const entry = groups[k][l]; return [l, entry?.slug ? articleUrl(l, entry.slug) : null]; })); return { groupId: k, date: es?.date || '', spanishTitle: es?.title || '', spanishFile: es?.file || '', spanishSlug: es?.slug || '', spanishUrl: es?.slug ? articleUrl('es', es.slug) : '', files, slugs, urls, }; }); const orphaned = allKeys.filter(k => !groups[k].es).map(k => { const langs = Object.keys(groups[k]); const files = Object.fromEntries(langs.map(l => [l, groups[k][l].file])); const slugs = Object.fromEntries(langs.map(l => [l, groups[k][l].slug])); const urls = Object.fromEntries(langs.map(l => { const entry = groups[k][l]; return [l, entry?.slug ? articleUrl(l, entry.slug) : null]; })); return { groupId: k, languages: langs, files, slugs, urls, }; }); const payload = { timestamp: new Date().toISOString(), source: 'cdrdpyj-postbuild', site: 'centrodelreinodepazyjusticia.com', summary, articles, orphaned, totals: { spanishArticles: esKeys.length, totalGroups: allKeys.length, orphanedArticles: orphaned.length, languages: Object.fromEntries(LANGUAGES.map(l => { const c = allKeys.filter(k => groups[k][l]).length; return [l, c]; })), }, }; const orphanCount = orphaned.length; if (orphanCount > 0) { console.log(`[send-to-n8n] ⚠️ ${orphanCount} orphaned article(s) — missing Spanish version`); orphaned.forEach(o => { console.log(` ⚠️ ${o.groupId} solo en: ${o.languages.join(', ')}`); Object.entries(o.urls).forEach(([l, u]) => console.log(` ${l}: ${u}`)); }); } const url = process.env.N8N_WEBHOOK_URL; if (!url) { console.log('[send-to-n8n] N8N_WEBHOOK_URL not set — showing sample payload'); console.log('Summary:', JSON.stringify(payload.summary, null, 2)); console.log('Sample articles (first 5):'); payload.articles.slice(0, 5).forEach(a => { console.log(` ${a.groupId} | ${a.date} | ${a.spanishTitle.slice(0, 60)}...`); console.log(` → ES: ${a.spanishUrl}`); const langs = Object.entries(a.urls).filter(([_, v]) => v).map(([k]) => k).join(', '); console.log(` → Present in: ${langs || 'ES only'}`); }); if (orphanCount) { console.log('Orphaned articles:'); payload.orphaned.forEach(o => { console.log(` ${o.groupId}`); Object.entries(o.urls).forEach(([l, u]) => console.log(` ${l}: ${u}`)); }); } console.log(`[send-to-n8n] Total: ${articles.length} ES articles, ${orphanCount} orphans`); return; } if (process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL && process.env.GOOGLE_SHEET_ID) { try { await clearSheet('Resumen'); await clearSheet('Detalle'); } catch (e) { console.error(`[send-to-n8n] Warning: could not clear sheets (${e.message})`); } } const apiKey = process.env.N8N_API_KEY; const apiKeyHeader = process.env.N8N_API_KEY_HEADER || 'X-API-Key'; const headers = { 'Content-Type': 'application/json' }; if (apiKey) headers[apiKeyHeader] = apiKey; fetch(url, { method: 'POST', headers, body: JSON.stringify(payload), }) .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`); console.log(`[send-to-n8n] OK — ${articles.length} ES articles, ${orphanCount} orphans`); }) .catch(e => { console.error(`[send-to-n8n] Warning: n8n unreachable (${e.message}) — build continues`); }); } main().catch(e => { console.error(`[send-to-n8n] Fatal error: ${e.message}`); process.exit(1); });