// ===== scanners/parliamentLives.js ===== // Version "costaud" avec parseurs spécifiques FR/BE + fallback générique. const axios = require("axios"); const cheerio = require("cheerio"); const { supaExists, askDeepSeek, extractDeepSeekResponse, sendToDiscord, logArticle, } = require("../utils/shared"); // ---------- ENV attendus ---------- // FR_PARL_SCHEDULE_URL : ex. https://www2.assemblee-nationale.fr/agendas/les-agendas // FR_PARL_WATCH_URL : ex. https://videos.assemblee-nationale.fr/direct.php // FR_PARL_ALT_SCHEDULE : (optionnel) Guide TV LCP ex. https://lcp.fr/guide-tv/100 // FR_PARL_ALT_WATCH : (optionnel) Direct LCP ex. https://lcp.fr/direct-lcp-5434 // // BE_PARL_SCHEDULE_URL : ex. https://lachambre.be/ (ou page Agenda) // BE_PARL_WATCH_URL : ex. https://media.lachambre.be/ // // Tu peux ajouter/retirer des sources ci-dessous. const SOURCES = [ { key: "FR", label: "🇫🇷 Assemblée nationale — Direct", scheduleUrl: process.env.FR_PARL_SCHEDULE_URL || "", watchUrl: process.env.FR_PARL_WATCH_URL || "", altScheduleUrl: process.env.FR_PARL_ALT_SCHEDULE || "", altWatchUrl: process.env.FR_PARL_ALT_WATCH || "", leadMinutes: 90, tz: "Europe/Paris", parser: parseFRAgenda, // parseur spécifique altParser: parseLCPGuideTV, // parseur de secours LCP keywords: [ "séance", "plénière", "questions au gouvernement", "qag", "hémicycle", "direct", "en direct", "live", ], }, { key: "BE", label: "🇧🇪 Chambre — Direct", scheduleUrl: process.env.BE_PARL_SCHEDULE_URL || "", watchUrl: process.env.BE_PARL_WATCH_URL || "", leadMinutes: 60, tz: "Europe/Brussels", parser: parseBEChambre, // parseur spécifique pour media.lachambre / page d’accueil vers Agenda keywords: ["séance", "plénière", "commission", "en direct", "live"], }, ]; // ---------- Utils heure & texte ---------- function toMinutes(hh, mm) { return hh * 60 + mm; } function parseHourString(s) { // 9h, 09h, 9h00, 09h30, 9:30, 09:30 const re = /(?:^|\s)(\d{1,2})[h:](\d{2})(?:\b|h|\s|$)/i; const m = re.exec(String(s || "")); if (!m) return null; const hh = parseInt(m[1], 10); const mm = parseInt(m[2], 10); if (Number.isNaN(hh) || Number.isNaN(mm) || hh > 23 || mm > 59) return null; return { hh, mm }; } function norm(s) { return String(s || "") .normalize("NFD") .replace(/[\u0300-\u036f]/g, "") .toLowerCase(); } function hasKeyword(s, keywords) { const t = norm(s); return keywords.some((k) => t.includes(norm(k))); } function nowMinutesLocal() { const d = new Date(); return d.getHours() * 60 + d.getMinutes(); } // ---------- Fallback générique (au cas où) ---------- function parseGeneric(html, { keywords }) { const $ = cheerio.load(html); const pick = "time, h1, h2, h3, h4, h5, a, li, div, p, span"; const events = []; $(pick).each((_, el) => { const text = $(el).text().replace(/\s+/g, " ").trim(); if (!text || text.length < 4) return; const tm = parseHourString(text); if (!tm) return; if (!hasKeyword(text, keywords || [])) return; const title = text; events.push({ title, hour: tm.hh, min: tm.mm, raw: text }); }); // dédoublonnage simple const seen = new Set(); const uniq = []; for (const e of events) { const k = `${e.hour}:${e.min}::${norm(e.title)}`; if (!seen.has(k)) { seen.add(k); uniq.push(e); } } return uniq; } // ---------- Parseur spécifique FR : https://www2.assemblee-nationale.fr/agendas/les-agendas ---------- function parseFRAgenda(html, { keywords }) { const $ = cheerio.load(html); const out = []; // Heuristiques spécifiques (mais robustes) : // - nombreuses pages listent des items d'agenda sous
,
  • , ou des blocs .agenda, avec