blog_tech/plugins/docusaurus-plugin-rss-aggregator/index.js
Tellsanguis df63713055 Ajout page Veille avec agrégation RSS automatique
- Création d'un plugin Docusaurus pour agréger les flux RSS au build
  * Récupère 37 flux RSS depuis le fichier OPML
  * Filtre les articles des dernières 24h
  * Génère un fichier JSON statique pour chargement instantané

- Page Veille avec composant React
  * Affichage des articles groupés par catégorie
  * Menus dépliables (repliés par défaut)
  * Chargement ultra-rapide depuis JSON pré-généré
  * Support bilingue FR/EN

- GitHub Actions pour rebuild automatique quotidien
  * Workflow déclenché tous les jours à 6h UTC
  * Met à jour les flux RSS via l'API Cloudflare Pages
  * Déclenchement manuel possible

- Configuration Webpack pour compatibilité navigateur
  * Désactivation des polyfills Node.js côté client
  * Correction du warning onBrokenMarkdownLinks

- Icône RSS dans la navbar
  * Lien vers le flux Atom du blog
  * Style cohérent avec les autres icônes

125 articles trouvés dans les dernières 24h lors du dernier build.
2025-12-06 09:33:43 +01:00

133 lines
4.4 KiB
JavaScript

const Parser = require('rss-parser');
const { XMLParser } = require('fast-xml-parser');
const fs = require('fs');
const path = require('path');
const https = require('https');
const http = require('http');
module.exports = function (context, options) {
return {
name: 'docusaurus-plugin-rss-aggregator',
async loadContent() {
console.log('[RSS Aggregator] Récupération des flux RSS...');
const parser = new Parser({
timeout: 10000,
customFields: {
item: ['description', 'content:encoded']
}
});
// Lecture du fichier OPML
const opmlPath = path.join(context.siteDir, 'static', 'veille-tech.opml');
const opmlText = fs.readFileSync(opmlPath, 'utf-8');
const xmlParser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: ''
});
const opmlData = xmlParser.parse(opmlText);
// Extraction des flux depuis le fichier OPML
const opmlFeeds = [];
const outlines = opmlData.opml.body.outline;
outlines.forEach((categoryOutline) => {
const categoryName = categoryOutline.text;
const feedOutlines = Array.isArray(categoryOutline.outline)
? categoryOutline.outline
: [categoryOutline.outline];
feedOutlines.forEach((feed) => {
if (feed.xmlUrl) {
opmlFeeds.push({
title: feed.text || feed.title,
xmlUrl: feed.xmlUrl,
category: categoryName
});
}
});
});
// Récupération des flux RSS (articles des dernières 24h)
const allItems = [];
const now = Date.now();
const twentyFourHoursAgo = now - (24 * 60 * 60 * 1000);
console.log(`[RSS Aggregator] Récupération de ${opmlFeeds.length} flux RSS...`);
// Traitement par lots de 5 flux en parallèle pour ne pas surcharger
const batchSize = 5;
for (let i = 0; i < opmlFeeds.length; i += batchSize) {
const batch = opmlFeeds.slice(i, i + batchSize);
const batchPromises = batch.map(async (feedInfo) => {
try {
const feed = await parser.parseURL(feedInfo.xmlUrl);
// Filtrer les articles des dernières 24h
const recentItems = feed.items.filter((item) => {
const itemDate = new Date(item.pubDate || item.isoDate || '');
return itemDate.getTime() >= twentyFourHoursAgo;
});
return recentItems.map((item) => ({
title: item.title || 'Sans titre',
link: item.link || '#',
pubDate: item.pubDate || item.isoDate || new Date().toISOString(),
source: feedInfo.title,
category: feedInfo.category
}));
} catch (err) {
console.warn(`[RSS Aggregator] Échec ${feedInfo.title}:`, err.message);
return [];
}
});
const batchResults = await Promise.all(batchPromises);
allItems.push(...batchResults.flat());
}
// Grouper par catégorie et trier
const groupedByCategory = new Map();
allItems.forEach((item) => {
if (!groupedByCategory.has(item.category)) {
groupedByCategory.set(item.category, []);
}
groupedByCategory.get(item.category).push(item);
});
// Trier les articles de chaque catégorie par date (plus récent en premier)
const groups = Array.from(groupedByCategory.entries())
.map(([category, items]) => ({
category,
items: items.sort((a, b) =>
new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime()
)
}))
.sort((a, b) => a.category.localeCompare(b.category));
console.log(`[RSS Aggregator] ${allItems.length} articles trouvés dans les dernières 24h`);
return {
groups,
generatedAt: new Date().toISOString(),
totalArticles: allItems.length
};
},
async contentLoaded({ content, actions }) {
const { setGlobalData } = actions;
// Écrire les données dans un fichier JSON statique
const outputPath = path.join(context.siteDir, 'static', 'rss-feed-cache.json');
fs.writeFileSync(outputPath, JSON.stringify(content, null, 2));
console.log(`[RSS Aggregator] Données écrites dans ${outputPath}`);
// Rendre les données disponibles globalement
setGlobalData(content);
},
};
};