fix(route): aeon - support for other categories (#12614)

* aeon: hotfix & migrate to v2

* rewrite & focus on essays

* fix banner

* better support for aeon

* update the doc

* credits before content

* using templates & fix doc
This commit is contained in:
Enoch Ma 2023-06-05 17:50:00 +02:00 committed by GitHub
parent b53d343bc1
commit 051a3efcbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 156 additions and 57 deletions

View File

@ -19,14 +19,24 @@ Supported sub-sites
## AEON
### Essays
### Types
<RouteEn author="emdoe" example="/aeon/essays" path="/aeon/essays" :paramsDesc="['Category']">
<RouteEn author="emdoe" example="/aeon/essays" path="/aeon/:type" :paramsDesc="['Type']">
Supported categories: Essays.
Supported types: Essays, Videos, and Audio.
Compared to the official one, the RSS feed generated by RSSHub not only has more fine-grained options, but also eliminates pull quotes, which can't be easily distinguished from other paragraphs by any RSS reader, but only disrupt the reading flow. This feed also provides users with a bio of the author at the top.
However, The content generated under `audio` does not contain links to audio files.
</RouteEn>
### Categories
<RouteEn author="emdoe" example="/aeon/category/philosophy" path="/aeon/category/:category" :paramsDesc="['Category']">
Supported categories: Philosophy, Science, Psychology, Society, and Culture.
</RouteEn>
## American Federation of Labor and Congress of Industrial Organizations

View File

@ -135,11 +135,19 @@ pageClass: routes
## AEON
### Essays
### 类型
<Route author="emdoe" example="/aeon/essays" path="/aeon/essays" :paramsDesc="['类别']">
<Route author="emdoe" example="/aeon/essays" path="/aeon/:type" :paramsDesc="['类别']">
支持获取 Essays.
支持获取 Essays, Videos, 以及 Audio. 但 Audio 仅输出正文内容,并不包括音轨链接。
</Route>
### 分类
<Route author="emdoe" example="/aeon/category/philosophy" path="/aeon/category/:category" :paramsDesc="['分类']">
支持获取的分类包括: Philosophy, Science, Psychology, Society, 以及 Culture.
</Route>

27
lib/v2/aeon/category.js Normal file
View File

@ -0,0 +1,27 @@
const cheerio = require('cheerio');
const got = require('@/utils/got');
const { getData } = require('./utils');
module.exports = async (ctx) => {
const url = `https://aeon.co/${ctx.params.category}`;
const { data: response } = await got(url);
const $ = cheerio.load(response);
const data = JSON.parse($('script#__NEXT_DATA__').text());
const list = data.props.pageProps.section.articles.edges.map((item) => ({
title: item.node.title,
author: item.node.authors.map((author) => author.displayName).join(', '),
link: `https://aeon.co/${item.node.type.toLowerCase()}s/${item.node.slug}`,
pubDate: item.node.createdAt,
}));
const items = await getData(ctx, list);
ctx.state.data = {
title: `AEON | ${data.props.pageProps.section.title}`,
link: url,
description: data.props.pageProps.section.metaDescription,
item: items,
};
};

View File

@ -1,46 +0,0 @@
const cheerio = require('cheerio');
const got = require('@/utils/got');
module.exports = async (ctx) => {
const url = `https://aeon.co/essays`;
const { data: response } = await got(url);
const $ = cheerio.load(response);
const data = JSON.parse($('script#__NEXT_DATA__').text());
const list = data.props.pageProps.articles.map((item) => ({
title: item.title,
author: item.authors.map((author) => author.displayName).join(', '),
link: `https://aeon.co/essays/${item.slug}`,
pubDate: item.createdAt,
}));
const items = await Promise.all(
list.map((item) =>
ctx.cache.tryGet(item.link, async () => {
const { data: response } = await got(item.link);
const $ = cheerio.load(response);
// It seems that the method based on __NEXT_DATA__
// does not include the information of the two-column
// images in the article body,
// e.g. https://aeon.co/essays/how-to-mourn-a-forest-a-lesson-from-west-papua .
// But that's very rare.
const data = JSON.parse($('script#__NEXT_DATA__').text());
const banner = `<img src="${data.props.pageProps.article.thumbnail.urls.header}">`;
const authorsBio = data.props.pageProps.article.authors.map((author) => '<p>' + author.displayName + author.authorBio.replace(/^<p>/g, ' ')).join('');
const capture = cheerio.load(data.props.pageProps.article.body);
capture('p.pullquote').remove();
item.description = banner + authorsBio + capture.html();
return item;
})
)
);
ctx.state.data = {
title: `AEON | Essays`,
link: url,
item: items,
};
};

View File

@ -1,3 +1,4 @@
module.exports = {
'/essays': ['emdoe'],
'/category/:category': ['emdoe'],
'/:type': ['emdoe'],
};

View File

@ -3,10 +3,16 @@ module.exports = {
_name: 'AEON',
aeon: [
{
title: 'Essays',
title: 'Types (Essays, Videos, or Audio)',
docs: 'https://docs.rsshub.app/new-media.html##aeon',
source: ['/'],
target: '/aeon/essays',
source: ['/:type'],
target: '/aeon/:type',
},
{
title: 'Category',
docs: 'https://docs.rsshub.app/new-media.html##aeon',
source: ['/:category'],
target: '/aeon/category/:category',
},
],
},

View File

@ -1,3 +1,4 @@
module.exports = function (router) {
router.get('/essays', require('./essays'));
router.get('/category/:category', require('./category'));
router.get('/:type', require('./type'));
};

View File

@ -0,0 +1,3 @@
<img src="{{ banner }}" alt="">
{{@ authorsBio }}
{{@ content}}

View File

@ -0,0 +1,10 @@
{{ set video = article.hosterId }}
{{ if article.hoster === 'vimeo' }}
{{ set video = "https://player.vimeo.com/video/" + video + "?dnt=1"}}
{{ else if article.hoster == 'youtube' }}
{{ set video = "https://www.youtube-nocookie.com/embed/" + video }}
{{ /if }}
<iframe width="672" height="377" src="{{ video }}" frameborder="0" allowfullscreen></iframe>
{{@ article.credits}}
{{@ article.description}}

29
lib/v2/aeon/type.js Normal file
View File

@ -0,0 +1,29 @@
const cheerio = require('cheerio');
const got = require('@/utils/got');
const { getData } = require('./utils');
module.exports = async (ctx) => {
const type = ctx.params.type;
const binaryType = type === 'videos' ? 'videos' : 'essays';
const capitalizedType = type.charAt(0).toUpperCase() + type.slice(1);
const url = `https://aeon.co/${type}`;
const { data: response } = await got(url);
const $ = cheerio.load(response);
const data = JSON.parse($('script#__NEXT_DATA__').text());
const list = data.props.pageProps.articles.map((item) => ({
title: item.title,
link: `https://aeon.co/${binaryType}/${item.slug}`,
pubDate: item.createdAt,
}));
const items = await getData(ctx, list);
ctx.state.data = {
title: `AEON | ${capitalizedType}`,
link: url,
item: items,
};
};

50
lib/v2/aeon/utils.js Normal file
View File

@ -0,0 +1,50 @@
const cheerio = require('cheerio');
const got = require('@/utils/got');
const { art } = require('@/utils/render');
const path = require('path');
const getData = async (ctx, list) => {
const items = await Promise.all(
list.map((item) =>
ctx.cache.tryGet(item.link, async () => {
const { data: response } = await got(item.link);
const $ = cheerio.load(response);
const data = JSON.parse($('script#__NEXT_DATA__').text());
const type = data.props.pageProps.article.type.toLowerCase();
if (type === 'video') {
item.description = art(path.join(__dirname, 'templates/video.art'), { article: data.props.pageProps.article });
} else {
// Essay or Audio
// But unfortunately, the method based on __NEXT_DATA__
// does not include the information of the audio link.
// Besides, it seems that the method based on __NEXT_DATA__
// does not include the information of the two-column
// images in the article body,
// e.g. https://aeon.co/essays/how-to-mourn-a-forest-a-lesson-from-west-papua .
// But that's very rare.
item.author = data.props.pageProps.article.authors.map((author) => author.displayName).join(', ');
const article = data.props.pageProps.article;
const capture = cheerio.load(article.body);
const banner = article.thumbnail.urls.header;
capture('p.pullquote').remove();
const authorsBio = article.authors.map((author) => '<p>' + author.displayName + author.authorBio.replace(/^<p>/g, ' ')).join('');
item.description = art(path.join(__dirname, 'templates/essay.art'), { banner, authorsBio, content: capture.html() });
}
return item;
})
)
);
return items;
};
module.exports = {
getData,
};