diff --git a/lib/routes/web/articles.ts b/lib/routes/web/articles.ts new file mode 100644 index 000000000..73a3f1445 --- /dev/null +++ b/lib/routes/web/articles.ts @@ -0,0 +1,23 @@ +import type { Data, Route } from '@/types'; +import { fetchItems } from './utils'; + +export const route: Route = { + path: '/articles', + categories: ['programming'], + example: '/web/articles', + radar: [{ + source: ['web.dev/articles'], + }], + name: 'Articles', + maintainers: ['KarasuShin'], + handler, +}; + +async function handler(): Promise { + return { + title: 'Articles', + link: 'https://web.dev/articles', + image: 'https://web.dev/_pwa/web/icons/icon-144x144.png', + item: await fetchItems('family_url:/articles'), + }; +} diff --git a/lib/routes/web/blog.ts b/lib/routes/web/blog.ts new file mode 100644 index 000000000..cbb31aff0 --- /dev/null +++ b/lib/routes/web/blog.ts @@ -0,0 +1,23 @@ +import type { Data, Route } from '@/types'; +import { fetchItems } from './utils'; + +export const route: Route = { + path: '/blog', + categories: ['programming'], + example: '/web/blog', + radar: [{ + source: ['web.dev/blog'], + }], + name: 'Blog', + maintainers: ['KarasuShin'], + handler, +}; + +async function handler(): Promise { + return { + title: 'Blog', + link: 'https://web.dev/blog', + image: 'https://web.dev/_pwa/web/icons/icon-144x144.png', + item: await fetchItems('type:blog'), + }; +} diff --git a/lib/routes/web/namespace.ts b/lib/routes/web/namespace.ts new file mode 100644 index 000000000..97a7dd7cb --- /dev/null +++ b/lib/routes/web/namespace.ts @@ -0,0 +1,6 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'web.dev', + url: 'web.dev', +}; diff --git a/lib/routes/web/series.ts b/lib/routes/web/series.ts new file mode 100644 index 000000000..91a222e24 --- /dev/null +++ b/lib/routes/web/series.ts @@ -0,0 +1,30 @@ +import type { Data, Route } from '@/types'; +import { fetchItems, hyphen2Pascal } from './utils'; + +export const route: Route = { + path: '/series/:seriesName', + parameters: { seriesName: 'topic name in the series section' }, + categories: ['programming'], + example: '/web/series/new-to-the-web', + radar: [{ + source: ['web.dev/series/:seriesName'], + target: '/series/:seriesName', + }], + name: 'Series', + maintainers: ['KarasuShin'], + handler, + description: `:::tip + The \`seriesName\` can be extracted from the Series page URL: \`https://web.dev/series/:seriesName\` + :::`, +}; + +async function handler(ctx): Promise { + const seriesName = ctx.req.param('seriesName'); + + return { + title: seriesName, + link: `https://web.dev/series/${seriesName}`, + image: 'https://web.dev/_pwa/web/icons/icon-144x144.png', + item: await fetchItems(`category:${hyphen2Pascal(seriesName)}`), + }; +} diff --git a/lib/routes/web/utils.ts b/lib/routes/web/utils.ts new file mode 100644 index 000000000..b7eb24dfa --- /dev/null +++ b/lib/routes/web/utils.ts @@ -0,0 +1,68 @@ +import type { DataItem } from '@/types'; +import cache from '@/utils/cache'; +import got from '@/utils/got'; +import { load } from 'cheerio'; + +type ArticleData = [ + string, + string, + unknown, + string, + string, + number[], + string, + string, + unknown, + unknown, + string[], + string, + string[], + string, + string, + unknown, + string[], + string, + string, + number, + string[], + unknown, + number, + string[], + number, + number, +]; + +export async function fetchItems(queryParam: string): Promise { + const res = await got('https://web.dev/_d/dynamic_content', { + body: `[null,null,null, "${queryParam}",null,null,null,null,31,null,null,null,2]`, + method: 'post', + }); + const data = JSON.parse(res.data.replace(/^[^[]*/, '')) as [ArticleData[], number]; + const items = await Promise.all( + data[0].map((item) => { + const link = item[6]; + return cache.tryGet(link, async () => { + const { data: articleHtml } = await got.get(link); + const $ = load(articleHtml); + const articleBody = $('.devsite-article-body'); + articleBody.find('.wd-authors').remove(); + + return { + title: item[0], + pubDate: new Date(item[5][0] * 1e3), + description: articleBody.html(), + link, + }; + }) as unknown as DataItem; + }) + ); + + return items; +} + +export function hyphen2Pascal(value: string) { + return value + .split('-') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(''); +}