RSSHub/lib/routes/wordpress/index.ts

173 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Route } from '@/types';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import parser from '@/utils/rss-parser';
import { config } from '@/config';
import ConfigNotFoundError from '@/errors/types/config-not-found';
import { apiSlug, bakeFilterSearchParams, bakeFiltersWithPair, bakeUrl, fetchData, getFilterParamsForUrl, parseFilterStr } from './util';
async function handler(ctx) {
const { url = 'https://wordpress.org/news', filter } = ctx.req.param();
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 50;
if (!config.feature.allow_user_supply_unsafe_domain) {
throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
if (!/^(https?):\/\/[^\s#$./?].\S*$/i.test(url)) {
throw new Error('Invalid URL');
}
const cdn = config.wordpress.cdnUrl;
const rootUrl = url;
const filters = parseFilterStr(filter);
const filtersWithPair = await bakeFiltersWithPair(filters, rootUrl);
const searchParams = bakeFilterSearchParams(filters, 'name', false);
const apiSearchParams = bakeFilterSearchParams(filtersWithPair, 'id', true);
apiSearchParams.append('_embed', 'true');
apiSearchParams.append('per_page', String(limit));
const apiUrl = bakeUrl(`${apiSlug}/posts`, rootUrl, apiSearchParams);
const currentUrl = bakeUrl(getFilterParamsForUrl(filtersWithPair) ?? '', rootUrl, searchParams);
try {
const { data: response } = await got(apiUrl);
const items = (Array.isArray(response) ? response : JSON.parse(response.match(/(\[.*])$/)[1])).slice(0, limit).map((item) => {
const terminologies = item._embedded['wp:term'];
const guid = item.guid?.rendered ?? item.guid;
const $$ = load(item.content?.rendered ?? item.content);
$$('img').each((_, el) => {
el = $$(el);
const src = el.prop('src');
if (src.startsWith('/')) {
el.prop('src', `${cdn}${item.link}${src}`);
} else if (src.startsWith('http:')) {
el.prop('src', `${cdn}${src}`);
}
});
const description = $$.html();
return {
title: item.title?.rendered ?? item.title,
description,
pubDate: parseDate(item.date_gmt),
link: item.link,
category: [...new Set(terminologies.flat().map((c) => c.name))],
author: item._embedded.author.map((a) => a.name).join('/'),
guid,
id: guid,
content: {
html: description,
text: $$.text(),
},
updated: parseDate(item.modified_gmt),
};
});
const data = await fetchData(currentUrl, rootUrl);
return {
...data,
item: items,
};
} catch {
const feed = await parser.parseURL(`${rootUrl}/feed/`);
const items = feed.items.map((item) => {
const guid = item.guid;
const $$ = load(item['content:encoded']);
$$('img').each((_, el) => {
el = $$(el);
const src = el.prop('src');
if (src.startsWith('/')) {
el.prop('src', `${cdn}${item.link}${src}`);
} else if (src.startsWith('http:')) {
el.prop('src', `${cdn}${src}`);
}
});
const description = $$.html();
return {
title: item.title,
description,
pubDate: parseDate(item.pubDate ?? ''),
link: item.link,
category: item.categories,
author: item.creator,
guid,
id: guid,
content: {
html: description,
text: $$.text(),
},
};
});
return {
title: feed.title,
description: feed.description,
link: feed.link,
item: items,
allowEmpty: true,
image: feed.image?.url,
language: feed.language,
};
}
}
export const route: Route = {
path: '/:url?/:filter{.+}?',
name: 'WordPress',
url: 'wordpress.org',
maintainers: ['nczitzk'],
handler,
example: '/wordpress/https%3A%2F%2Fwordpress.org%2Fnews/category/Podcast',
parameters: { url: 'URL, <https://wordpress.org/news> by default', filter: 'Filter, see below' },
description: `If you subscribe to [WordPress News](https://wordpress.org/news/)where the URL is \`https://wordpress.org/news/\`, Encode the URL using \`encodeURIComponent()\` and then use it as the parameter. Therefore, the route will be [\`/wordpress/https%3A%2F%2Fwordpress.org%2Fnews\`](https://rsshub.app/wordpress/https%3A%2F%2Fwordpress.org%2Fnews).
:::tip
If you wish to subscribe to specific categories or tags, you can fill in the "filter" parameter in the route. \`/category/Podcast\` to subscribe to the Podcast category. In this case, the route would be [\`/wordpress/https%3A%2F%2Fwordpress.org%2Fnews/category/Podcast\`](https://rsshub.app/wordpress/https%3A%2F%2Fwordpress.org%2Fnews/category/Podcast).
You can also subscribe to multiple categories. \`/category/Podcast,Community\` to subscribe to both the Podcast and Community categories. In this case, the route would be [\`/wordpress/https%3A%2F%2Fwordpress.org%2Fnews/category/Podcast,Community\`](https://rsshub.app/wordpress/https%3A%2F%2Fwordpress.org%2Fnews/category/Podcast,Community).
Categories and tags can be combined as well. \`/category/Releases/tag/tagging\` to subscribe to the Releases category and the tagging tag. In this case, the route would be [\`/wordpress/https%3A%2F%2Fwordpress.org%2Fnews/category/Releases/tag/tagging\`](https://rsshub.app/wordpress/https%3A%2F%2Fwordpress.org%2Fnews/category/Releases/tag/tagging).
You can also search for keywords. \`/search/Blog\` to search for the keyword "Blog". In this case, the route would be [\`/wordpress/https%3A%2F%2Fwordpress.org%2Fnews/search/Blog\`](https://rsshub.app/wordpress/https%3A%2F%2Fwordpress.org%2Fnews/search/Blog).
:::`,
categories: ['blog'],
features: {
requireConfig: [
{
name: 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN',
description: `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`,
optional: false,
},
],
requirePuppeteer: false,
antiCrawler: false,
supportRadar: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [],
};