mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-10-24 23:42:40 +08:00
feat(route): add Le Monde & Le Monde in English (#22175)
* feat(route): add Le Monde route * feat(route): add Le Monde in English route Add English version of Le Monde RSS route with 40+ categories covering World, Europe, France, Economy, Culture, Sports, etc. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(route/le-monde): remove unnecessary cache.tryGet wrapper cache.tryGet was wrapping a synchronous callback with no async work, making it a no-op. The items are already fully parsed from the RSS feed, so direct assignment is sufficient. * fix: address review comments for Le Monde route - Rename namespace from le-monde/le-monde-en to lemonde - Remove unnecessary .first() calls on RSS elements - Remove default ofetch responseType option - Use static cheerio import instead of dynamic import - Extract <category> tags into category field Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: sort imports to satisfy oxlint Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: update example paths to match lemonde namespace Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: trigger CI to re-test with updated route paths Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: remove non-existent <content> element fallback from image URL Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: mlkgrnt <mlkgrnt@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
mlkgrnt
parent
cae03809c0
commit
6886265f1b
@@ -0,0 +1,217 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import type { Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
|
||||
const ROOT_URL = 'https://www.lemonde.fr/en';
|
||||
|
||||
const feedMap: Record<string, string> = {
|
||||
'': `${ROOT_URL}/rss/une.xml`,
|
||||
// World
|
||||
international: `${ROOT_URL}/international/rss_full.xml`,
|
||||
americas: `${ROOT_URL}/americas/rss_full.xml`,
|
||||
'united-kingdom': `${ROOT_URL}/united-kingdom/rss_full.xml`,
|
||||
'united-states': `${ROOT_URL}/united-states/rss_full.xml`,
|
||||
africa: `${ROOT_URL}/le-monde-africa/rss_full.xml`,
|
||||
'asia-pacific': `${ROOT_URL}/asia-and-pacific/rss_full.xml`,
|
||||
'middle-east': `${ROOT_URL}/middle-east/rss_full.xml`,
|
||||
// Europe
|
||||
europe: `${ROOT_URL}/europe/rss_full.xml`,
|
||||
// France
|
||||
politics: `${ROOT_URL}/politics/rss_full.xml`,
|
||||
'police-and-justice': `${ROOT_URL}/police-and-justice/rss_full.xml`,
|
||||
education: `${ROOT_URL}/education/rss_full.xml`,
|
||||
'french-delights': `${ROOT_URL}/french-delights/rss_full.xml`,
|
||||
// Environment
|
||||
environment: `${ROOT_URL}/environment/rss_full.xml`,
|
||||
// Economy
|
||||
economy: `${ROOT_URL}/economy/rss_full.xml`,
|
||||
'world-economy': `${ROOT_URL}/world-economy/rss_full.xml`,
|
||||
'french-economy': `${ROOT_URL}/french-economy/rss_full.xml`,
|
||||
// M Magazine
|
||||
'm-le-mag': `${ROOT_URL}/m-le-mag/rss_full.xml`,
|
||||
lifestyle: `${ROOT_URL}/lifestyle/rss_full.xml`,
|
||||
fashion: `${ROOT_URL}/fashion/rss_full.xml`,
|
||||
food: `${ROOT_URL}/food/rss_full.xml`,
|
||||
travel: `${ROOT_URL}/travel/rss_full.xml`,
|
||||
// Culture
|
||||
culture: `${ROOT_URL}/culture/rss_full.xml`,
|
||||
arts: `${ROOT_URL}/arts/rss_full.xml`,
|
||||
cinema: `${ROOT_URL}/cinema/rss_full.xml`,
|
||||
music: `${ROOT_URL}/music/rss_full.xml`,
|
||||
books: `${ROOT_URL}/books/rss_full.xml`,
|
||||
// Global Issues
|
||||
'global-issues': `${ROOT_URL}/global-issues/rss_full.xml`,
|
||||
// Pixels
|
||||
pixels: `${ROOT_URL}/pixels/rss_full.xml`,
|
||||
'artificial-intelligence': `${ROOT_URL}/artificial-intelligence/rss_full.xml`,
|
||||
'social-media': `${ROOT_URL}/social-media/rss_full.xml`,
|
||||
// Sports
|
||||
sports: `${ROOT_URL}/sports/rss_full.xml`,
|
||||
football: `${ROOT_URL}/football/rss_full.xml`,
|
||||
rugby: `${ROOT_URL}/rugby/rss_full.xml`,
|
||||
tennis: `${ROOT_URL}/tennis/rss_full.xml`,
|
||||
cycling: `${ROOT_URL}/cycling/rss_full.xml`,
|
||||
basketball: `${ROOT_URL}/basketball/rss_full.xml`,
|
||||
// Science & Health
|
||||
science: `${ROOT_URL}/science/rss_full.xml`,
|
||||
health: `${ROOT_URL}/health/rss_full.xml`,
|
||||
intimacy: `${ROOT_URL}/intimacy/rss_full.xml`,
|
||||
// Others
|
||||
'les-decodeurs': `${ROOT_URL}/les-decodeurs/rss_full.xml`,
|
||||
'our-times': `${ROOT_URL}/our-times/rss_full.xml`,
|
||||
obituaries: `${ROOT_URL}/obituaries/rss_full.xml`,
|
||||
religion: `${ROOT_URL}/religion/rss_full.xml`,
|
||||
// Opinion
|
||||
opinion: `${ROOT_URL}/opinion/rss_full.xml`,
|
||||
editorials: `${ROOT_URL}/editorials/rss_full.xml`,
|
||||
columns: `${ROOT_URL}/columns/rss_full.xml`,
|
||||
'op-eds': `${ROOT_URL}/op-eds/rss_full.xml`,
|
||||
};
|
||||
|
||||
export const route: Route = {
|
||||
path: '/en/:category?',
|
||||
categories: ['traditional-media'],
|
||||
example: '/lemonde/en',
|
||||
view: ViewType.Articles,
|
||||
parameters: {
|
||||
category: {
|
||||
description: 'Category slug, see table below. Defaults to homepage.',
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
source: ['lemonde.fr/en/:category'],
|
||||
target: '/en/:category',
|
||||
},
|
||||
],
|
||||
name: 'News (English)',
|
||||
maintainers: ['mlkgrnt'],
|
||||
handler,
|
||||
description: `| Category | Description |
|
||||
| ----------------------- | ------------------------- |
|
||||
| (empty) | Homepage / Top stories |
|
||||
| international | World – International |
|
||||
| americas | World – Americas |
|
||||
| united-kingdom | World – United Kingdom |
|
||||
| united-states | World – United States |
|
||||
| africa | World – Africa |
|
||||
| asia-pacific | World – Asia Pacific |
|
||||
| middle-east | World – Middle East |
|
||||
| europe | Europe |
|
||||
| politics | France – French Politics |
|
||||
| police-and-justice | France – French Justice |
|
||||
| education | France – French Education |
|
||||
| french-delights | France – French Delights |
|
||||
| environment | Environment |
|
||||
| economy | Economy |
|
||||
| world-economy | Economy – World Economy |
|
||||
| french-economy | Economy – French Economy |
|
||||
| m-le-mag | M Magazine |
|
||||
| lifestyle | M Magazine – Lifestyle |
|
||||
| fashion | M Magazine – Fashion |
|
||||
| food | M Magazine – Food |
|
||||
| travel | M Magazine – Travel |
|
||||
| culture | Culture |
|
||||
| arts | Culture – Art |
|
||||
| cinema | Culture – Cinema |
|
||||
| music | Culture – Music |
|
||||
| books | Culture – Books |
|
||||
| global-issues | Global Issues |
|
||||
| pixels | Pixels |
|
||||
| artificial-intelligence | Pixels – AI |
|
||||
| social-media | Pixels – Social Media |
|
||||
| sports | Sports |
|
||||
| football | Sports – Football |
|
||||
| rugby | Sports – Rugby |
|
||||
| tennis | Sports – Tennis |
|
||||
| cycling | Sports – Cycling |
|
||||
| basketball | Sports – Basketball |
|
||||
| science | Science |
|
||||
| health | Health |
|
||||
| intimacy | Intimacy |
|
||||
| les-decodeurs | Les Décodeurs |
|
||||
| our-times | Our Times |
|
||||
| obituaries | Obituaries |
|
||||
| religion | Religion |
|
||||
| opinion | Opinion |
|
||||
| editorials | Opinion – Editorials |
|
||||
| columns | Opinion – Columns |
|
||||
| op-eds | Opinion – Op-Eds |`,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const category = ctx.req.param('category') ?? '';
|
||||
const feedUrl = feedMap[category];
|
||||
|
||||
if (!feedUrl) {
|
||||
throw new Error(`Invalid category: ${category}`);
|
||||
}
|
||||
|
||||
const xml = await ofetch(feedUrl);
|
||||
const $ = load(xml, { xml: true });
|
||||
|
||||
const channel = $('channel');
|
||||
const feedTitle = channel.children('title').text();
|
||||
const feedLink = channel.children('link').text() || ROOT_URL;
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20;
|
||||
|
||||
const items = $('item')
|
||||
.toArray()
|
||||
.slice(0, limit)
|
||||
.map((el) => {
|
||||
const item = $(el);
|
||||
const link = item.children('link').text() || item.children('guid').text();
|
||||
const title = item.children('title').text();
|
||||
const pubDateText = item.children('pubDate').text();
|
||||
const summary = item.children('description').text();
|
||||
const categoryList = item
|
||||
.children('category')
|
||||
.toArray()
|
||||
.map((c) => $(c).text())
|
||||
.filter(Boolean);
|
||||
const imageUrl = item.find(String.raw`media\:content`).attr('url');
|
||||
const imageCredit = item.find(String.raw`media\:credit`).text();
|
||||
|
||||
let description = '';
|
||||
if (imageUrl) {
|
||||
description += `<img src="${imageUrl}" />`;
|
||||
}
|
||||
if (imageCredit) {
|
||||
description += `<p><em>${imageCredit}</em></p>`;
|
||||
}
|
||||
if (summary) {
|
||||
description += summary;
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
link,
|
||||
pubDate: pubDateText ? parseDate(pubDateText) : undefined,
|
||||
description,
|
||||
category: categoryList,
|
||||
guid: link,
|
||||
};
|
||||
})
|
||||
.filter((item) => item.link);
|
||||
|
||||
return {
|
||||
title: feedTitle || 'Le Monde in English',
|
||||
link: feedLink,
|
||||
description: feedTitle || 'Le Monde in English',
|
||||
language: 'en',
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import type { Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
|
||||
const ROOT_URL = 'https://www.lemonde.fr';
|
||||
|
||||
const feedMap: Record<string, string> = {
|
||||
'': `${ROOT_URL}/rss/une.xml`,
|
||||
international: `${ROOT_URL}/international/rss_full.xml`,
|
||||
politique: `${ROOT_URL}/politique/rss_full.xml`,
|
||||
economie: `${ROOT_URL}/economie/rss_full.xml`,
|
||||
societe: `${ROOT_URL}/societe/rss_full.xml`,
|
||||
culture: `${ROOT_URL}/culture/rss_full.xml`,
|
||||
sport: `${ROOT_URL}/sport/rss_full.xml`,
|
||||
planete: `${ROOT_URL}/planete/rss_full.xml`,
|
||||
pixels: `${ROOT_URL}/pixels/rss_full.xml`,
|
||||
sciences: `${ROOT_URL}/sciences/rss_full.xml`,
|
||||
idees: `${ROOT_URL}/idees/rss_full.xml`,
|
||||
sante: `${ROOT_URL}/sante/rss_full.xml`,
|
||||
em: `${ROOT_URL}/em/rss_full.xml`,
|
||||
'en-continu': `${ROOT_URL}/en-continu/rss_full.xml`,
|
||||
decodeurs: `${ROOT_URL}/decodeurs/rss_full.xml`,
|
||||
};
|
||||
|
||||
export const route: Route = {
|
||||
path: '/:category?',
|
||||
categories: ['traditional-media'],
|
||||
example: '/lemonde',
|
||||
view: ViewType.Articles,
|
||||
parameters: {
|
||||
category: {
|
||||
description: 'Category slug, see table below. Defaults to homepage.',
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
source: ['lemonde.fr/:category'],
|
||||
target: '/:category',
|
||||
},
|
||||
],
|
||||
name: 'News',
|
||||
maintainers: ['mlkgrnt'],
|
||||
handler,
|
||||
description: `| Category | Description |
|
||||
| ------------- | ---------------------- |
|
||||
| (empty) | Homepage / Top stories |
|
||||
| international | International |
|
||||
| politique | Politics |
|
||||
| economie | Economy |
|
||||
| societe | Society |
|
||||
| culture | Culture |
|
||||
| sport | Sports |
|
||||
| planete | Environment |
|
||||
| pixels | Tech / Digital |
|
||||
| sciences | Sciences |
|
||||
| idees | Opinions |
|
||||
| sante | Health |
|
||||
| em | M le mag |
|
||||
| en-continu | Live / Breaking |
|
||||
| decodeurs | Fact-checking |`,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const category = ctx.req.param('category') ?? '';
|
||||
const feedUrl = feedMap[category];
|
||||
|
||||
if (!feedUrl) {
|
||||
throw new Error(`Invalid category: ${category}`);
|
||||
}
|
||||
|
||||
const xml = await ofetch(feedUrl);
|
||||
const $ = load(xml, { xml: true });
|
||||
|
||||
const channel = $('channel');
|
||||
const feedTitle = channel.children('title').text();
|
||||
const feedLink = channel.children('link').text() || ROOT_URL;
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20;
|
||||
|
||||
const items = $('item')
|
||||
.toArray()
|
||||
.slice(0, limit)
|
||||
.map((el) => {
|
||||
const item = $(el);
|
||||
const link = item.children('link').text() || item.children('guid').text();
|
||||
const title = item.children('title').text();
|
||||
const pubDateText = item.children('pubDate').text();
|
||||
const summary = item.children('description').text();
|
||||
const categoryList = item
|
||||
.children('category')
|
||||
.toArray()
|
||||
.map((c) => $(c).text())
|
||||
.filter(Boolean);
|
||||
const imageUrl = item.find(String.raw`media\:content`).attr('url');
|
||||
const imageCredit = item.find(String.raw`media\:credit`).text();
|
||||
|
||||
let description = '';
|
||||
if (imageUrl) {
|
||||
description += `<img src="${imageUrl}" />`;
|
||||
}
|
||||
if (imageCredit) {
|
||||
description += `<p><em>${imageCredit}</em></p>`;
|
||||
}
|
||||
if (summary) {
|
||||
description += summary;
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
link,
|
||||
pubDate: pubDateText ? parseDate(pubDateText) : undefined,
|
||||
description,
|
||||
category: categoryList,
|
||||
guid: link,
|
||||
};
|
||||
})
|
||||
.filter((item) => item.link);
|
||||
|
||||
return {
|
||||
title: feedTitle || 'Le Monde',
|
||||
link: feedLink,
|
||||
description: feedTitle || 'Le Monde',
|
||||
language: 'fr',
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: 'Le Monde',
|
||||
url: 'lemonde.fr',
|
||||
lang: 'fr',
|
||||
};
|
||||
Reference in New Issue
Block a user