From 8f4392d32a8601bcdcec8d7807f03a2bef3767ae Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 30 Jan 2026 10:28:21 +0800 Subject: [PATCH] feat(route/nea): add nea bureaux (#21013) * feat(route/nea): add nea bureaux * Update lib/routes/gov/nea/bureau.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/routes/gov/nea/bureau.ts | 131 ++++++++++++++++++++++++++++++++ lib/routes/gov/nea/ghs.ts | 117 ---------------------------- lib/routes/gov/nea/namespace.ts | 7 ++ 3 files changed, 138 insertions(+), 117 deletions(-) create mode 100644 lib/routes/gov/nea/bureau.ts delete mode 100644 lib/routes/gov/nea/ghs.ts create mode 100644 lib/routes/gov/nea/namespace.ts diff --git a/lib/routes/gov/nea/bureau.ts b/lib/routes/gov/nea/bureau.ts new file mode 100644 index 000000000..258422c30 --- /dev/null +++ b/lib/routes/gov/nea/bureau.ts @@ -0,0 +1,131 @@ +import { load } from 'cheerio'; +import sanitizeHtml from 'sanitize-html'; + +import type { DataItem, Route } from '@/types'; +import cache from '@/utils/cache'; +import ofetch from '@/utils/ofetch'; +import { parseDate } from '@/utils/parse-date'; +import timezone from '@/utils/timezone'; + +export const route: Route = { + path: '/nea/sjzz/:bureau', + categories: ['government'], + example: '/gov/nea/sjzz/ghs', + parameters: { + bureau: { + description: '司局', + options: [ + { value: 'zhs', label: '综合司' }, + { value: 'fgs', label: '法改司' }, + { value: 'ghs', label: '规划司' }, + { value: 'kjs', label: '科技司' }, + { value: 'dls', label: '电力司' }, + { value: 'hds', label: '核电司' }, + { value: 'mts', label: '煤炭司' }, + { value: 'yqs', label: '油气司' }, + { value: 'xny', label: '新能源司' }, + { value: 'jgs', label: '监管司' }, + { value: 'aqs', label: '安全司' }, + { value: 'gjs', label: '国际司' }, + { value: 'jgdw', label: '机关党委(人事司)' }, + ], + }, + }, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['nea.gov.cn/sjzz/:bureau/index.htm'], + target: '/nea/sjzz/:bureau', + }, + ], + name: '司工作进展', + maintainers: ['nczitzk', 'pseudoyu'], + handler, + url: 'www.nea.gov.cn/', +}; + +async function handler(ctx) { + const { bureau } = ctx.req.param(); + const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 35; + + const rootUrl = 'https://www.nea.gov.cn'; + const link = `${rootUrl}/sjzz/${bureau}/index.htm`; + const response = await ofetch(link); + const $ = load(response); + + const dataSourceId: string | undefined = $('ul#showData0').attr('data')?.split(/:/).pop(); + if (!dataSourceId) { + throw new Error('Data source ID not found'); + } + + const jsonUrl = new URL(`ds_${dataSourceId}.json`, link).href; + const jsonData: NeaResponse = await ofetch(jsonUrl); + + const list: DataItem[] = jsonData.datasource.slice(0, limit).map((item) => { + const title = sanitizeHtml(item.title, { allowedTags: [], allowedAttributes: {} }); + + return { + title, + link: new URL(item.publishUrl, rootUrl).href, + pubDate: item.publishTime ? timezone(parseDate(item.publishTime), +8) : undefined, + description: item.summary?.trim() || title, + author: [...new Set([item.sourceText, item.author, item.editor, item.responsibleEditor].filter(Boolean))].map((author) => ({ + name: author, + })), + category: item.keywords.split(/,/), + }; + }); + + const items = await Promise.all( + list.map((item: DataItem) => + cache.tryGet(item.link, async () => { + try { + const response = await ofetch(item.link); + const $ = load(response); + + item.title = $('meta[name="ArticleTitle"]').prop('content') || item.title; + item.description = $('#detailContent').html() || $('div.article-content').html() || item.description; + item.category = [...new Set([...(item.category ?? []), ...($('meta[name="keywords"]').attr('content')?.split(/,/) ?? [])])]; + const detailPubDate = $('meta[name="PubDate"]').prop('content'); + item.pubDate = detailPubDate ? timezone(parseDate(detailPubDate), +8) : item.pubDate; + } catch { + // + } + return item; + }) + ) + ); + + return { + title: `${$('head title').text()}${$('#tab03').text()}`, + description: $('head meta[name="ColumnDescription"]').attr('content'), + item: items, + link, + }; +} + +interface NeaItem { + title: string; + showTitle: string; + publishUrl: string; + publishTime: string; + summary?: string; + sourceText?: string; + author?: string; + editor?: string; + responsibleEditor?: string; + keywords: string; +} + +interface NeaResponse { + categoryName?: string; + categoryDesc?: string; + datasource: NeaItem[]; +} diff --git a/lib/routes/gov/nea/ghs.ts b/lib/routes/gov/nea/ghs.ts deleted file mode 100644 index e94ce83f7..000000000 --- a/lib/routes/gov/nea/ghs.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { load } from 'cheerio'; - -import type { DataItem, Route } from '@/types'; -import cache from '@/utils/cache'; -import ofetch from '@/utils/ofetch'; -import { parseDate } from '@/utils/parse-date'; -import timezone from '@/utils/timezone'; - -export const route: Route = { - path: '/nea/sjzz/ghs', - categories: ['government'], - example: '/gov/nea/sjzz/ghs', - parameters: {}, - features: { - requireConfig: false, - requirePuppeteer: false, - antiCrawler: false, - supportBT: false, - supportPodcast: false, - supportScihub: false, - }, - radar: [ - { - source: ['nea.gov.cn/sjzz/ghs/'], - target: '/nea/sjzz/ghs', - }, - ], - name: '发展规划司', - maintainers: ['nczitzk', 'pseudoyu'], - handler, - url: 'www.nea.gov.cn/sjzz/ghs/', -}; - -async function handler(ctx) { - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 35; - - const rootUrl = 'https://www.nea.gov.cn'; - const targetUrl: string = new URL('sjzz/ghs/', rootUrl).href; - - const response = await ofetch(targetUrl); - const $ = load(response); - - const dataSourceId: string | undefined = $('ul#showData0').attr('data')?.split(/:/).pop(); - - if (!dataSourceId) { - throw new Error('Data source ID not found'); - } - - const jsonUrl = new URL(`ds_${dataSourceId}.json`, targetUrl).href; - - const jsonData: NeaGhsResponse = await ofetch(jsonUrl); - - const list: DataItem[] = jsonData.datasource.slice(0, limit).map((item) => { - const itemLink = new URL(item.publishUrl, rootUrl).href; - - const $title = load(item.showTitle); - const titleText = $title.text(); - - return { - title: titleText, - link: itemLink, - pubDate: item.publishTime ? timezone(parseDate(item.publishTime), +8) : undefined, - description: item.summary?.trim() || titleText, - author: [...new Set([item.sourceText, item.author, item.editor, item.responsibleEditor].filter(Boolean))].map((author) => ({ - name: author, - })), - category: item.keywords.split(/,/), - }; - }); - - const items = await Promise.all( - list.map((item: DataItem) => - cache.tryGet(item.link, async () => { - try { - const detailResponse = await ofetch(item.link); - const content = load(detailResponse); - - item.title = content('meta[name="ArticleTitle"]').prop('content') || item.title; - item.description = content('td.detail').html() || content('div.article-content').html() || item.description; - item.category = [...new Set([...(item.category ?? []), ...(content('meta[name="keywords"]').attr('conetnt')?.split(/,/) ?? [])])]; - const detailPubDate = content('meta[name="PubDate"]').prop('content'); - item.pubDate = detailPubDate ? timezone(parseDate(detailPubDate), +8) : item.pubDate; - } catch { - // logger.error(`Failed to fetch detail for ${item.link}`); - } - return item; - }) - ) - ); - - const filteredItems: DataItem[] = items.filter(Boolean) as DataItem[]; - - return { - item: filteredItems, - title: '国家能源局 - 发展规划司工作进展', - link: targetUrl, - description: '国家能源局 - 发展规划司工作进展', - }; -} - -interface NeaGhsItem { - showTitle: string; - publishUrl: string; - publishTime: string; - summary?: string; - sourceText?: string; - author?: string; - editor?: string; - responsibleEditor?: string; - keywords: string; -} - -interface NeaGhsResponse { - categoryName?: string; - categoryDesc?: string; - datasource: NeaGhsItem[]; -} diff --git a/lib/routes/gov/nea/namespace.ts b/lib/routes/gov/nea/namespace.ts new file mode 100644 index 000000000..99d654b0b --- /dev/null +++ b/lib/routes/gov/nea/namespace.ts @@ -0,0 +1,7 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: '国家能源局', + url: 'www.nea.gov.cn', + categories: ['government'], +};