From 532ef65992c445ad25a68293af4da8b2000a5b86 Mon Sep 17 00:00:00 2001 From: minty_frankie <77310871+mintyfrankie@users.noreply.github.com> Date: Sun, 2 Jun 2024 09:59:23 +0200 Subject: [PATCH] feat(route): add route SustainabilityMagazine articles (#15666) * feat(susmag): add namespace * fix(sustamag): update namespace * feat(sustamag): add articles route * fix(sustamag): fix bugs - fix items' title parsing - fix items' link parsing * feat(sustamag): add full-text support * fix(doc): set requirePuppeteer * refactor(route): remove puppeteer requirement * refactor(route): change article list fectching method to GraphQL * fix: apply suggestions from code review Co-authored-by: Tony * refactor: change article items fetching selectors * fix: article description --------- --- lib/routes/sustainabilitymag/articles.ts | 144 ++++++++++++++++++++++ lib/routes/sustainabilitymag/namespace.ts | 6 + 2 files changed, 150 insertions(+) create mode 100644 lib/routes/sustainabilitymag/articles.ts create mode 100644 lib/routes/sustainabilitymag/namespace.ts diff --git a/lib/routes/sustainabilitymag/articles.ts b/lib/routes/sustainabilitymag/articles.ts new file mode 100644 index 000000000..8be6ed653 --- /dev/null +++ b/lib/routes/sustainabilitymag/articles.ts @@ -0,0 +1,144 @@ +import { Route } from '@/types'; +import cache from '@/utils/cache'; +import oftech from '@/utils/ofetch'; +import { parseDate } from '@/utils/parse-date'; +import { load } from 'cheerio'; + +export const route: Route = { + path: '/articles', + name: 'Articles', + url: 'sustainabilitymag.com/articles', + maintainers: ['mintyfrankie'], + categories: ['other'], + example: '/sustainabilitymag/articles', + radar: [ + { + source: ['https://sustainabilitymag.com/articles'], + target: '/sustainabilitymag/articles', + }, + ], + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + handler, +}; + +const findLargestImgKey = (images) => + Object.keys(images) + .filter((key) => key.startsWith('inline_free_') || key.startsWith('hero_landscape_')) + .sort((a, b) => Number.parseInt(b.split('_')[2]) - Number.parseInt(a.split('_')[2]))[0]; + +const renderFigure = (url, caption) => `
${caption}
${caption}
`; + +const render = (widgets) => + widgets + .map((w) => { + switch (w.type) { + case 'text': + return w.html; + case 'keyFacts': + return `
`; + case 'inlineVideo': + return w.provider === 'youtube' + ? `` + : new Error(`Unhandled inlineVideo provider: ${w.provider}`); + case 'inlineImage': + return w.inlineImageImages + .map((image) => { + const i = image.images[findLargestImgKey(image.images)][0]; + return renderFigure(i.url, i.caption); + }) + .join(''); + default: + throw new Error(`Unhandled widget type: ${w.type}`); + } + }) + .join(''); + +async function handler() { + const baseURL = `https://sustainabilitymag.com`; + const feedURL = `${baseURL}/articles`; + const feedLang = 'en'; + const feedDescription = 'Sustainability Magazine Articles'; + + const requestEndpoint = `${baseURL}/graphql`; + const requestBody = JSON.stringify({ + query: `query PaginatedQuery($url: String!, $page: Int = 1, $widgetType: String!) { + paginatedWidget(url: $url, widgetType: $widgetType) { + ... on SimpleArticleGridWidget { + articles(page: $page) { + results { + _id + headline + fullUrlPath + featured + category + contentType + tags { + tag + } + attribution + subAttribution + sell + images { + thumbnail_widescreen_553 { + url + } + } + } + } + } + } + }`, + operationName: 'PaginatedQuery', + variables: { + widgetType: 'simpleArticleGrid', + page: 1, + url: 'https://sustainabilitymag.com/articles', + }, + }); + + const results = await oftech(requestEndpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: requestBody, + }); + + const list = results.data.paginatedWidget.articles.results.map((item) => ({ + title: item.headline, + link: `${baseURL}${item.fullUrlPath}`, + image: item.images?.thumbnail_widescreen_553?.url, + category: item.category, + })); + + const items = await Promise.all( + list.map((item) => + cache.tryGet(item.link, async () => { + const response = await oftech(item.link); + const $ = load(response); + + const nextData = JSON.parse($('script#__NEXT_DATA__').text()); + const article = nextData.props.pageProps.article; + item.pubDate = parseDate(article.displayDate); + item.author = article.author.name; + const heroImage = article.images[findLargestImgKey(article.images)][0]; + item.description = renderFigure(heroImage.url, heroImage.caption) + render(article.body.widgets); + + return item; + }) + ) + ); + + return { + title: 'Sustainability Magazine Articles', + language: feedLang, + description: feedDescription, + link: `https://${feedURL}`, + item: items, + }; +} diff --git a/lib/routes/sustainabilitymag/namespace.ts b/lib/routes/sustainabilitymag/namespace.ts new file mode 100644 index 000000000..b43a42250 --- /dev/null +++ b/lib/routes/sustainabilitymag/namespace.ts @@ -0,0 +1,6 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'Sustainability Magazine', + url: 'https://sustainabilitymag.com', +};