mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-10-24 02:08:55 +08:00
feat(aeon): enhance category and type routes with detailed parameters and improved data fetching (#17745)
This commit is contained in:
+34
-17
@@ -1,13 +1,24 @@
|
||||
import { Route } from '@/types';
|
||||
import { load } from 'cheerio';
|
||||
import got from '@/utils/got';
|
||||
import { getData } from './utils';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { getBuildId, getData } from './utils';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/category/:category',
|
||||
categories: ['new-media', 'popular'],
|
||||
example: '/aeon/category/philosophy',
|
||||
parameters: { category: 'Category' },
|
||||
parameters: {
|
||||
category: {
|
||||
description: 'Category',
|
||||
options: [
|
||||
{ value: 'philosophy', label: 'Philosophy' },
|
||||
{ value: 'science', label: 'Science' },
|
||||
{ value: 'psychology', label: 'Psychology' },
|
||||
{ value: 'society', label: 'Society' },
|
||||
{ value: 'culture', label: 'Culture' },
|
||||
],
|
||||
},
|
||||
},
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
@@ -18,34 +29,40 @@ export const route: Route = {
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
source: ['aeon.aeon.co/:category'],
|
||||
source: ['aeon.co/:category'],
|
||||
},
|
||||
],
|
||||
name: 'Categories',
|
||||
maintainers: ['emdoe'],
|
||||
handler,
|
||||
description: `Supported categories: Philosophy, Science, Psychology, Society, and Culture.`,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const url = `https://aeon.co/${ctx.req.param('category')}`;
|
||||
const { data: response } = await got(url);
|
||||
const $ = load(response);
|
||||
const category = ctx.req.param('category').toLowerCase();
|
||||
const url = `https://aeon.co/category/${category}`;
|
||||
const buildId = await getBuildId();
|
||||
const response = await ofetch(`https://aeon.co/_next/data/${buildId}/${category}.json`);
|
||||
|
||||
const data = JSON.parse($('script#__NEXT_DATA__').text());
|
||||
const section = response.pageProps.section;
|
||||
|
||||
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}`,
|
||||
const list = section.articles.edges.map(({ node }) => ({
|
||||
title: node.title,
|
||||
description: node.standfirstLong,
|
||||
author: node.authors.map((author) => author.displayName).join(', '),
|
||||
link: `https://aeon.co/${node.type}s/${node.slug}`,
|
||||
pubDate: parseDate(node.createdAt),
|
||||
category: [node.section.title, ...node.topics.map((topic) => topic.title)],
|
||||
image: node.image.url,
|
||||
type: node.type,
|
||||
slug: node.slug,
|
||||
}));
|
||||
|
||||
const items = await getData(ctx, list);
|
||||
const items = await getData(list);
|
||||
|
||||
return {
|
||||
title: `AEON | ${data.props.pageProps.section.title}`,
|
||||
title: `AEON | ${section.title}`,
|
||||
link: url,
|
||||
description: data.props.pageProps.section.metaDescription,
|
||||
description: section.metaDescription,
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
<img src="{{ banner }}" alt="">
|
||||
{{ if banner.url }}
|
||||
<figure>
|
||||
<img src="{{ banner.url }}" alt="{{ banner.alt }}">
|
||||
{{ if banner.caption }}
|
||||
<figcaption>{{ banner.caption }}</figcaption>
|
||||
{{ /if }}
|
||||
{{ /if }}
|
||||
{{@ authorsBio }}
|
||||
{{@ content}}
|
||||
{{@ content }}
|
||||
|
||||
@@ -1,10 +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://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}}
|
||||
{{@ article.credits }}
|
||||
{{@ article.description }}
|
||||
|
||||
+28
-17
@@ -1,13 +1,22 @@
|
||||
import { Route } from '@/types';
|
||||
import { load } from 'cheerio';
|
||||
import got from '@/utils/got';
|
||||
import { getData } from './utils';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { getBuildId, getData } from './utils';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/:type',
|
||||
categories: ['new-media', 'popular'],
|
||||
example: '/aeon/essays',
|
||||
parameters: { type: 'Type' },
|
||||
parameters: {
|
||||
type: {
|
||||
description: 'Type',
|
||||
options: [
|
||||
{ value: 'essays', label: 'Essays' },
|
||||
{ value: 'videos', label: 'Videos' },
|
||||
{ value: 'audio', label: 'Audio' },
|
||||
],
|
||||
},
|
||||
},
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
@@ -18,7 +27,7 @@ export const route: Route = {
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
source: ['aeon.aeon.co/:type'],
|
||||
source: ['aeon.co/:type'],
|
||||
},
|
||||
],
|
||||
name: 'Types',
|
||||
@@ -26,28 +35,30 @@ export const route: Route = {
|
||||
handler,
|
||||
description: `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.`,
|
||||
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.`,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const type = ctx.req.param('type');
|
||||
const binaryType = type === 'videos' ? 'videos' : 'essays';
|
||||
const capitalizedType = type.charAt(0).toUpperCase() + type.slice(1);
|
||||
|
||||
const buildId = await getBuildId();
|
||||
const url = `https://aeon.co/${type}`;
|
||||
const { data: response } = await got(url);
|
||||
const $ = load(response);
|
||||
const response = await ofetch(`https://aeon.co/_next/data/${buildId}/${type}.json`);
|
||||
|
||||
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}`,
|
||||
const list = response.pageProps.articles.map((node) => ({
|
||||
title: node.title,
|
||||
description: node.standfirstLong,
|
||||
author: node.authors.map((author) => author.displayName).join(', '),
|
||||
link: `https://aeon.co/${node.type}s/${node.slug}`,
|
||||
pubDate: parseDate(node.createdAt),
|
||||
category: [node.section.title, ...node.topics.map((topic) => topic.title)],
|
||||
image: node.image.url,
|
||||
type: node.type,
|
||||
slug: node.slug,
|
||||
}));
|
||||
|
||||
const items = await getData(ctx, list);
|
||||
const items = await getData(list);
|
||||
|
||||
return {
|
||||
title: `AEON | ${capitalizedType}`,
|
||||
|
||||
+47
-17
@@ -3,28 +3,61 @@ const __dirname = getCurrentPath(import.meta.url);
|
||||
|
||||
import cache from '@/utils/cache';
|
||||
import { load } from 'cheerio';
|
||||
import got from '@/utils/got';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { art } from '@/utils/render';
|
||||
import path from 'node:path';
|
||||
import { config } from '@/config';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
|
||||
const getData = async (ctx, list) => {
|
||||
export const getBuildId = () =>
|
||||
cache.tryGet(
|
||||
'aeon:buildId',
|
||||
async () => {
|
||||
const response = await ofetch('https://aeon.co');
|
||||
const $ = load(response);
|
||||
const nextData = JSON.parse($('script#__NEXT_DATA__').text());
|
||||
return nextData.buildId;
|
||||
},
|
||||
config.cache.routeExpire,
|
||||
false
|
||||
);
|
||||
|
||||
const getData = async (list) => {
|
||||
const items = await Promise.all(
|
||||
list.map((item) =>
|
||||
cache.tryGet(item.link, async () => {
|
||||
const { data: response } = await got(item.link);
|
||||
const $ = load(response);
|
||||
const buildId = await getBuildId();
|
||||
const response = await ofetch(`https://aeon.co/_next/data/${buildId}/${item.type}s/${item.slug}.json?id=${item.slug}`);
|
||||
|
||||
const data = JSON.parse($('script#__NEXT_DATA__').text());
|
||||
const type = data.props.pageProps.article.type.toLowerCase();
|
||||
const data = response.pageProps.article;
|
||||
const type = data.type.toLowerCase();
|
||||
|
||||
item.pubDate = new Date(data.props.pageProps.article.publishedAt).toUTCString();
|
||||
item.pubDate = parseDate(data.publishedAt);
|
||||
|
||||
if (type === 'video') {
|
||||
item.description = art(path.join(__dirname, 'templates/video.art'), { article: data.props.pageProps.article });
|
||||
item.description = art(path.join(__dirname, 'templates/video.art'), { article: data });
|
||||
} else {
|
||||
// Essay or Audio
|
||||
// But unfortunately, the method based on __NEXT_DATA__
|
||||
// does not include the information of the audio link.
|
||||
if (data.audio?.id) {
|
||||
const response = await ofetch('https://api.aeonmedia.co/graphql', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
query: `query getAudio($audioId: ID!) {
|
||||
audio(id: $audioId) {
|
||||
id
|
||||
streamUrl
|
||||
}
|
||||
}`,
|
||||
variables: {
|
||||
audioId: data.audio.id,
|
||||
},
|
||||
operationName: 'getAudio',
|
||||
},
|
||||
});
|
||||
|
||||
delete item.image;
|
||||
item.enclosure_url = response.data.audio.streamUrl;
|
||||
item.enclosure_type = 'audio/mpeg';
|
||||
}
|
||||
|
||||
// Besides, it seems that the method based on __NEXT_DATA__
|
||||
// does not include the information of the two-column
|
||||
@@ -32,14 +65,11 @@ const getData = async (ctx, list) => {
|
||||
// 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.name).join(', ');
|
||||
|
||||
const article = data.props.pageProps.article;
|
||||
const capture = load(article.body);
|
||||
const banner = article.image?.url;
|
||||
const capture = load(data.body, null, false);
|
||||
const banner = data.image;
|
||||
capture('p.pullquote').remove();
|
||||
|
||||
const authorsBio = article.authors.map((author) => '<p>' + author.name + author.authorBio.replaceAll(/^<p>/g, ' ')).join('');
|
||||
const authorsBio = data.authors.map((author) => '<p>' + author.name + author.authorBio.replaceAll(/^<p>/g, ' ')).join('');
|
||||
|
||||
item.description = art(path.join(__dirname, 'templates/essay.art'), { banner, authorsBio, content: capture.html() });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user