From 47cae995cef4c95216f69be02be83d9b65614f0d Mon Sep 17 00:00:00 2001 From: Yuiki Saito Date: Mon, 16 Jun 2025 00:12:36 +0900 Subject: [PATCH] feat(route): add Sankei Shimbun (#19381) * feat(route): add Sankei Shimbun * fix: fix wrong topic path * fix: fixed code as pointed out in review * fix: cleanup --- lib/routes/sankei/namespace.ts | 7 ++++ lib/routes/sankei/news.ts | 68 ++++++++++++++++++++++++++++++++ lib/routes/sankei/topics.ts | 71 ++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 lib/routes/sankei/namespace.ts create mode 100644 lib/routes/sankei/news.ts create mode 100644 lib/routes/sankei/topics.ts diff --git a/lib/routes/sankei/namespace.ts b/lib/routes/sankei/namespace.ts new file mode 100644 index 000000000..4550561ce --- /dev/null +++ b/lib/routes/sankei/namespace.ts @@ -0,0 +1,7 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'Sankei Shimbun 産経新聞', + url: 'sankei.com', + lang: 'ja', +}; diff --git a/lib/routes/sankei/news.ts b/lib/routes/sankei/news.ts new file mode 100644 index 000000000..cbd723e6f --- /dev/null +++ b/lib/routes/sankei/news.ts @@ -0,0 +1,68 @@ +import { Route, Data, DataItem } from '@/types'; + +import cache from '@/utils/cache'; +import got from '@/utils/got'; +import { load } from 'cheerio'; +import { parseDate } from '@/utils/parse-date'; +import { Context } from 'hono'; + +export const route: Route = { + path: '/news/:category', + categories: ['traditional-media'], + example: '/sankei/news/flash', + parameters: { category: 'Category name (as it will appear in URLs). For example, for "Breaking News" https://www.sankei.com/flash/, the category name would be "flash".' }, + radar: [ + { + source: ['www.sankei.com/:category'], + target: '/news/:category', + }, + ], + name: 'News', + maintainers: ['yuikisaito'], + handler, +}; + +async function handler(ctx: Context): Promise { + const baseUrl = 'https://www.sankei.com'; + const { category } = ctx.req.param(); + const url = `${baseUrl}/${category}/`; + + const response = await got(url); + const $ = load(response.body); + const categoryName = $('header.story-card-header:first-of-type > h1').text(); + const listSelector = $('div.story-card-feed.grid.hide_on_mobile > div > article'); + + const items: DataItem[] = await Promise.all( + listSelector.toArray().map((el) => { + const item = $(el); + item.find('p a').remove(); + + const title = item.find('div.story-card-flex > h2.headline > a').text(); + const link = baseUrl + (item.find('div.story-card-flex > h2.headline > a').attr('href') || ''); + const pubDate = parseDate(item.find('div.story-card-flex > div > time').attr('datetime') || ''); + + return cache.tryGet(link, async () => { + const detail = await got(link); + const $ = load(detail.body); + $('.inline-gptAd, .figure_image_sizer').remove(); + const articleHTML = $('div.article-body').html() || ''; + + return { + title, + link, + pubDate, + description: articleHTML, + }; + }); + }) + ); + + return { + title: '産経ニュース - ' + categoryName, + description: $('meta[name="description"]').attr('content'), + link: url, + image: $('meta[property="og:image"]').attr('content'), + language: 'ja', + item: items, + }; +} diff --git a/lib/routes/sankei/topics.ts b/lib/routes/sankei/topics.ts new file mode 100644 index 000000000..852031847 --- /dev/null +++ b/lib/routes/sankei/topics.ts @@ -0,0 +1,71 @@ +import { Route, Data, DataItem } from '@/types'; + +import cache from '@/utils/cache'; +import got from '@/utils/got'; +import { load } from 'cheerio'; +import { parseDate } from '@/utils/parse-date'; +import { Context } from 'hono'; + +export const route: Route = { + path: ['/topics/:topic'], + categories: ['traditional-media'], + example: '/sankei/topics/etc_100', + parameters: { + topic: 'Topic name (format included in URL). For example, for "Expo 2025 Osaka, Kansai, Japan Special Feature" https://www.sankei.com/tag/topic/etc_100, the value would be etc_100.', + }, + radar: [ + { + source: ['www.sankei.com/tag/topic/:topic'], + target: '/topics/:topic', + }, + ], + name: 'Topic', + maintainers: ['yuikisaito'], + handler, +}; + +async function handler(ctx: Context): Promise { + const baseUrl = 'https://www.sankei.com'; + const { topic } = ctx.req.param(); + const url = `${baseUrl}/tag/topic/${topic}/`; + + const response = await got(url); + const $ = load(response.body); + const categoryName = $('li.breadcrumb-list-item:nth-of-type(3) > a').text(); + + const listSelector = $('div.story-card-feed.grid.hide_on_mobile > div > article'); + + const items: DataItem[] = await Promise.all( + listSelector.toArray().map((el) => { + const item = $(el); + item.find('p a').remove(); + + const title = item.find('div.story-card-flex > h3.headline > a').text(); + const link = baseUrl + (item.find('div.story-card-flex > h3.headline > a').attr('href') || ''); + const pubDate = parseDate(item.find('div.story-card-flex > div > time').attr('datetime') || ''); + + return cache.tryGet(link, async () => { + const detail = await got(link); + const $ = load(detail.body); + $('.inline-gptAd, .figure_image_sizer').remove(); + const articleHTML = $('div.article-body').html() || ''; + + return { + title, + link, + pubDate, + description: articleHTML, + }; + }); + }) + ); + + return { + title: '産経ニュース - ' + categoryName, + description: $('meta[name="description"]').attr('content'), + link: url, + image: $('meta[property="og:image"]').attr('content'), + language: 'ja', + item: items, + }; +}