feat(route): support psyche.co (#15772)
* support psyche * Update lib/routes/psyche/topic.ts Co-authored-by: Tony <TonyRL@users.noreply.github.com> * Update lib/routes/psyche/topic.ts Co-authored-by: Tony <TonyRL@users.noreply.github.com> * Update lib/routes/psyche/topic.ts Co-authored-by: Tony <TonyRL@users.noreply.github.com> * graphql + _next/data/buildID * add link * delete json * cache link * hotfix: redundent 's' ---------
This commit is contained in:
parent
6a24c14974
commit
ec4a03e221
|
|
@ -0,0 +1,6 @@
|
|||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: 'Psyche',
|
||||
url: 'psyche.co',
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<img src="{{ banner }}" alt="">
|
||||
{{@ authorsBio }}
|
||||
{{@ content}}
|
||||
|
|
@ -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}}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { Route } from '@/types';
|
||||
import { load } from 'cheerio';
|
||||
import { ofetch } from 'ofetch';
|
||||
import { getData } from './utils';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/topic/:topic',
|
||||
categories: ['new-media'],
|
||||
example: '/psyche/topic/therapeia',
|
||||
parameters: { topic: 'Topic' },
|
||||
radar: [
|
||||
{
|
||||
source: ['psyche.co/:topic'],
|
||||
},
|
||||
],
|
||||
name: 'Topics',
|
||||
maintainers: ['emdoe'],
|
||||
handler,
|
||||
description: 'Supported categories: Therapeia, Eudaimonia, and Poiesis.',
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const url = `https://psyche.co/${ctx.req.param('topic')}`;
|
||||
const response = await ofetch(url);
|
||||
const $ = load(response);
|
||||
|
||||
const data = JSON.parse($('script#__NEXT_DATA__').text());
|
||||
const articles = data.props.pageProps.articles;
|
||||
const prefix = `https://psyche.co/_next/data/${data.buildId}`;
|
||||
const list = Object.keys(articles).flatMap((type) =>
|
||||
articles[type].edges.map((item) => ({
|
||||
title: item.node.title,
|
||||
link: `https://psyche.co/${type}/${item.node.slug}`,
|
||||
json: `${prefix}/${type}/${item.node.slug}.json`,
|
||||
}))
|
||||
);
|
||||
|
||||
const items = await getData(list);
|
||||
|
||||
return {
|
||||
title: `Psyche | ${data.props.pageProps.section.title}`,
|
||||
link: url,
|
||||
description: data.props.pageProps.section.metaDescription,
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import { Route } from '@/types';
|
||||
import { load } from 'cheerio';
|
||||
import { getData } from './utils';
|
||||
import { ofetch } from 'ofetch';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/type/:type',
|
||||
categories: ['new-media'],
|
||||
example: '/psyche/type/ideas',
|
||||
parameters: { type: 'Type' },
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
source: ['psyche.co/:type'],
|
||||
},
|
||||
],
|
||||
name: 'Types',
|
||||
maintainers: ['emdoe'],
|
||||
handler,
|
||||
description: `Supported types: Ideas, Guides, and Films.`,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const type = ctx.req.param('type');
|
||||
const capitalizedType = type.charAt(0).toUpperCase() + type.slice(1);
|
||||
|
||||
const url = `https://psyche.co/${type}`;
|
||||
const response = await ofetch(url);
|
||||
const $ = load(response);
|
||||
|
||||
const data = JSON.parse($('script#__NEXT_DATA__').text());
|
||||
const prefix = `https://psyche.co/_next/data/${data.buildId}`;
|
||||
const list = data.props.pageProps.articles.map((item) => ({
|
||||
title: item.title,
|
||||
link: `${url}/${item.slug}`,
|
||||
json: `${prefix}/${type}/${item.slug}.json`,
|
||||
}));
|
||||
|
||||
const items = await getData(list);
|
||||
|
||||
return {
|
||||
title: `Psyche | ${capitalizedType}`,
|
||||
link: url,
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
import { getCurrentPath } from '@/utils/helpers';
|
||||
const __dirname = getCurrentPath(import.meta.url);
|
||||
|
||||
import cache from '@/utils/cache';
|
||||
import { ofetch } from 'ofetch';
|
||||
import { load } from 'cheerio';
|
||||
import { art } from '@/utils/render';
|
||||
import path from 'node:path';
|
||||
|
||||
const getImageById = async (id) => {
|
||||
const response = await ofetch('https://api.aeonmedia.co/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: 'query getImageById($id: ID!) { image(id: $id) { id url alt caption width height } }',
|
||||
variables: { id, site: 'Aeon' },
|
||||
operationName: 'getImageById',
|
||||
}),
|
||||
});
|
||||
|
||||
return response.data.image.url;
|
||||
};
|
||||
|
||||
function format(article) {
|
||||
const type = article.type.toLowerCase();
|
||||
|
||||
let block = '';
|
||||
let banner = '';
|
||||
let authorsBio = '';
|
||||
|
||||
switch (type) {
|
||||
case 'film':
|
||||
block = art(path.join(__dirname, 'templates/video.art'), { article });
|
||||
|
||||
break;
|
||||
|
||||
case 'guide': {
|
||||
banner = article.imageSquare?.url;
|
||||
authorsBio = article.authors.map((author) => author.bio).join(' ');
|
||||
|
||||
const sectionNames = ['Need To Know', 'What To Do', 'Key Points', 'Learn More', 'Links & Books'];
|
||||
const sections = Object.keys(article).filter((key) => key.startsWith('section') && key !== 'section');
|
||||
const content = sections
|
||||
.map((section) => {
|
||||
const capture = load(article[section]);
|
||||
capture('p.pullquote').remove();
|
||||
const sectionName = sectionNames.shift();
|
||||
return `<h2>${sectionName}</h2>` + capture.html();
|
||||
})
|
||||
.join('');
|
||||
|
||||
block = art(path.join(__dirname, 'templates/essay.art'), { banner, authorsBio, content });
|
||||
|
||||
break;
|
||||
}
|
||||
case 'idea': {
|
||||
banner = article.imageLandscape?.url;
|
||||
authorsBio = article.authors.map((author) => author.bio).join(' ');
|
||||
|
||||
const capture = load(article.body);
|
||||
capture('p.pullquote').remove();
|
||||
block = art(path.join(__dirname, 'templates/essay.art'), { banner, authorsBio, content: capture.html() });
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
const getData = async (list) => {
|
||||
const items = await Promise.all(
|
||||
list.map((item) =>
|
||||
cache.tryGet(item.link, async () => {
|
||||
const data = await ofetch(item.json);
|
||||
const article = data.pageProps.article;
|
||||
item.pubDate = new Date(article.publishedAt).toUTCString();
|
||||
const content = format(article);
|
||||
const capture = load(content);
|
||||
await Promise.all(
|
||||
capture('dl > dt')
|
||||
.toArray()
|
||||
.map(async (item) => {
|
||||
const id = capture(item).text();
|
||||
const image = await getImageById(id);
|
||||
capture(item).replaceWith(`<img src="${image}" alt="${id}">`);
|
||||
})
|
||||
);
|
||||
|
||||
let authors = '';
|
||||
authors = article.type === 'film' ? article.creditsShort : article.authors.map((author) => author.name).join(', ');
|
||||
|
||||
item.description = capture.html();
|
||||
item.author = authors;
|
||||
|
||||
return item;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
export { getData };
|
||||
Loading…
Reference in New Issue