From 7bbf9710ed705f6aa49af80c80e8b7f764f5d829 Mon Sep 17 00:00:00 2001 From: Ethan Shen <42264778+nczitzk@users.noreply.github.com> Date: Wed, 24 Sep 2025 07:57:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(route):=20add=20dbaplus=E8=B5=84=E8=AE=AF?= =?UTF-8?q?=20(#20134)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(route): add dbaplus资讯 * fix typo --- lib/routes/dbaplus/new.ts | 393 +++++++++++++++++++ lib/routes/dbaplus/templates/description.art | 21 + 2 files changed, 414 insertions(+) create mode 100644 lib/routes/dbaplus/new.ts create mode 100644 lib/routes/dbaplus/templates/description.art diff --git a/lib/routes/dbaplus/new.ts b/lib/routes/dbaplus/new.ts new file mode 100644 index 000000000..16c5c1062 --- /dev/null +++ b/lib/routes/dbaplus/new.ts @@ -0,0 +1,393 @@ +import { type Data, type DataItem, type Route, ViewType } from '@/types'; + +import { art } from '@/utils/render'; +import cache from '@/utils/cache'; +import ofetch from '@/utils/ofetch'; +import { parseDate } from '@/utils/parse-date'; +import timezone from '@/utils/timezone'; + +import { type CheerioAPI, type Cheerio, load } from 'cheerio'; +import type { Element } from 'domhandler'; +import { type Context } from 'hono'; +import path from 'node:path'; + +export const handler = async (ctx: Context): Promise => { + const { id = '9' } = ctx.req.param(); + const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); + + const baseUrl: string = 'https://dbaplus.cn'; + const targetUrl: string = new URL(`news-${id}-1.html`, baseUrl).href; + + const response = await ofetch(targetUrl); + const $: CheerioAPI = load(response); + const language = $('html').attr('lang') ?? 'zh'; + + let items: DataItem[] = []; + + items = $('ul.media-list li.media') + .slice(0, limit) + .toArray() + .map((el): Element => { + const $el: Cheerio = $(el); + const $aEl: Cheerio = $el.find('h3.media-heading a'); + + const title: string = $aEl.text(); + const image: string | undefined = $el.find('img.media-object').attr('src'); + const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + images: image + ? [ + { + src: image, + alt: title, + }, + ] + : undefined, + intro: $el.find('div.mt10').html(), + }); + const pubDateStr: string | undefined = $el + .find('span.time') + .text() + .replaceAll(/(年|月)/g, '-') + .replace('日', ''); + const linkUrl: string | undefined = $aEl.attr('href'); + const authorEls: Element[] = $el.find('span.user').toArray(); + const authors: DataItem['author'] = authorEls.map((authorEl) => { + const $authorEl: Cheerio = $(authorEl); + + return { + name: $authorEl.text(), + url: undefined, + avatar: undefined, + }; + }); + const upDatedStr: string | undefined = pubDateStr; + + const processedItem: DataItem = { + title, + description, + pubDate: pubDateStr ? parseDate(pubDateStr) : undefined, + link: linkUrl, + author: authors, + content: { + html: description, + text: description, + }, + image, + banner: image, + updated: upDatedStr ? parseDate(upDatedStr) : undefined, + language, + }; + + return processedItem; + }); + + items = await Promise.all( + items.map((item) => { + if (!item.link) { + return item; + } + + return cache.tryGet(item.link, async (): Promise => { + const detailResponse = await ofetch(item.link); + const $$: CheerioAPI = load(detailResponse); + + const title: string = $$('h2.title').text(); + const description: string | undefined = + item.description + + art(path.join(__dirname, 'templates/description.art'), { + description: $$('div.new-detailed').html(), + }); + const pubDateStr: string | undefined = $$('span.time').first().text(); + const categories: string[] = $$('meta[name="keywords"]').attr('content')?.split(',') ?? []; + const authorEls: Element[] = $$('span.user').toArray(); + const authors: DataItem['author'] = authorEls.map((authorEl) => { + const $$authorEl: Cheerio = $$(authorEl); + + return { + name: $$authorEl.text(), + url: undefined, + avatar: undefined, + }; + }); + const upDatedStr: string | undefined = pubDateStr; + + const processedItem: DataItem = { + title, + description, + pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : item.pubDate, + category: categories, + author: authors, + content: { + html: description, + text: description, + }, + updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : item.updated, + language, + }; + + return { + ...item, + ...processedItem, + }; + }); + }) + ); + + const description: string = $('meta[name="description"]').attr('content') ?? ''; + + return { + title: $('title').text().split(/:/)[0], + description, + link: targetUrl, + item: items, + allowEmpty: true, + image: $('div.navbar-header img').attr('src'), + author: description.split(/:/)[0], + language, + id: targetUrl, + }; +}; + +export const route: Route = { + path: '/news/:id?', + name: '资讯', + url: 'dbaplus.cn', + maintainers: ['nczitzk'], + handler, + example: '/dbaplus/news/9', + parameters: { + category: { + description: '分类,默认为 `9`,即全部,可在对应分类页 URL 中找到', + options: [ + { + label: '全部', + value: '9', + }, + { + label: '数据库', + value: '153', + }, + { + label: '国产数据库', + value: '217', + }, + { + label: 'ORACLE', + value: '10', + }, + { + label: 'MySQL', + value: '11', + }, + { + label: 'SQL优化', + value: '155', + }, + { + label: 'Newsletter', + value: '156', + }, + { + label: '其它', + value: '154', + }, + { + label: '运维', + value: '134', + }, + { + label: '大数据', + value: '73', + }, + { + label: '架构', + value: '141', + }, + { + label: 'PaaS云', + value: '72', + }, + { + label: '职场生涯', + value: '149', + }, + { + label: '标准评估', + value: '248', + }, + { + label: '这里有毒', + value: '21', + }, + { + label: '最新活动', + value: '152', + }, + { + label: '往期干货', + value: '148', + }, + { + label: '特别策划', + value: '150', + }, + { + label: '荐书', + value: '151', + }, + ], + }, + }, + description: `:::tip +订阅 [资讯](https://dbaplus.cn/news-9-1.html),其源网址为 \`https://dbaplus.cn/news-9-1.html\`,请参考该 URL 指定部分构成参数,此时路由为 [\`/dbaplus/news/9\`](https://rsshub.app/dbaplus/news/9)。 + +::: + +
+ 更多分类 + + | [全部](https://dbaplus.cn/news-9-1.html) | [数据库](https://dbaplus.cn/news-153-1.html) | [运维](https://dbaplus.cn/news-134-1.html) | [大数据](https://dbaplus.cn/news-73-1.html) | [架构](https://dbaplus.cn/news-141-1.html) | + | ---------------------------------------- | -------------------------------------------- | ------------------------------------------ | ------------------------------------------- | ------------------------------------------ | + | [9](https://rsshub.app/dbaplus/news/9) | [153](https://rsshub.app/dbaplus/news/153) | [134](https://rsshub.app/dbaplus/news/134) | [73](https://rsshub.app/dbaplus/news/73) | [141](https://rsshub.app/dbaplus/news/141) | + + | [PaaS云](https://dbaplus.cn/news-72-1.html) | [职场生涯](https://dbaplus.cn/news-149-1.html) | [标准评估](https://dbaplus.cn/news-248-1.html) | [这里有毒](https://dbaplus.cn/news-21-1.html) | + | ------------------------------------------- | ---------------------------------------------- | ---------------------------------------------- | --------------------------------------------- | + | [72](https://rsshub.app/dbaplus/news/72) | [149](https://rsshub.app/dbaplus/news/149) | [248](https://rsshub.app/dbaplus/news/248) | [21](https://rsshub.app/dbaplus/news/21) | + + #### [数据库](https://dbaplus.cn/news-153-1.html) + + | [国产数据库](https://dbaplus.cn/news-217-1.html) | [ORACLE](https://dbaplus.cn/news-10-1.html) | [MySQL](https://dbaplus.cn/news-11-1.html) | [SQL优化](https://dbaplus.cn/news-155-1.html) | [Newsletter](https://dbaplus.cn/news-156-1.html) | + | ------------------------------------------------ | ------------------------------------------- | ------------------------------------------ | --------------------------------------------- | ------------------------------------------------ | + | [217](https://rsshub.app/dbaplus/news/217) | [10](https://rsshub.app/dbaplus/news/10) | [11](https://rsshub.app/dbaplus/news/11) | [155](https://rsshub.app/dbaplus/news/155) | [156](https://rsshub.app/dbaplus/news/156) | + + | [其它](https://dbaplus.cn/news-154-1.html) | + | ------------------------------------------ | + | [154](https://rsshub.app/dbaplus/news/154) | + + #### [这里有毒](https://dbaplus.cn/news-21-1.html) + + | [最新活动](https://dbaplus.cn/news-152-1.html) | [往期干货](https://dbaplus.cn/news-148-1.html) | [特别策划](https://dbaplus.cn/news-150-1.html) | [荐书](https://dbaplus.cn/news-151-1.html) | + | ---------------------------------------------- | ---------------------------------------------- | ---------------------------------------------- | ------------------------------------------ | + | [152](https://rsshub.app/dbaplus/news/152) | [148](https://rsshub.app/dbaplus/news/148) | [150](https://rsshub.app/dbaplus/news/150) | [151](https://rsshub.app/dbaplus/news/151) | + +
+`, + categories: ['programming'], + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportRadar: true, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['dbaplus.cn/news*'], + target: (_, url) => { + const urlObj: URL = new URL(url); + const href: string = urlObj.href; + const id: string | undefined = href.match(/-(\d+)-\.html/)?.[1]; + + return `/dbaplus/news${id ? `/${id}` : ''}`; + }, + }, + { + title: '全部', + source: ['dbaplus.cn/news-9-1.html'], + target: '/news/9', + }, + { + title: '数据库', + source: ['dbaplus.cn/news-153-1.html'], + target: '/news/153', + }, + { + title: '国产数据库', + source: ['dbaplus.cn/news-217-1.html'], + target: '/news/217', + }, + { + title: 'ORACLE', + source: ['dbaplus.cn/news-10-1.html'], + target: '/news/10', + }, + { + title: 'MySQL', + source: ['dbaplus.cn/news-11-1.html'], + target: '/news/11', + }, + { + title: 'SQL优化', + source: ['dbaplus.cn/news-155-1.html'], + target: '/news/155', + }, + { + title: 'Newsletter', + source: ['dbaplus.cn/news-156-1.html'], + target: '/news/156', + }, + { + title: '其它', + source: ['dbaplus.cn/news-154-1.html'], + target: '/news/154', + }, + { + title: '运维', + source: ['dbaplus.cn/news-134-1.html'], + target: '/news/134', + }, + { + title: '大数据', + source: ['dbaplus.cn/news-73-1.html'], + target: '/news/73', + }, + { + title: '架构', + source: ['dbaplus.cn/news-141-1.html'], + target: '/news/141', + }, + { + title: 'PaaS云', + source: ['dbaplus.cn/news-72-1.html'], + target: '/news/72', + }, + { + title: '职场生涯', + source: ['dbaplus.cn/news-149-1.html'], + target: '/news/149', + }, + { + title: '标准评估', + source: ['dbaplus.cn/news-248-1.html'], + target: '/news/248', + }, + { + title: '这里有毒', + source: ['dbaplus.cn/news-21-1.html'], + target: '/news/21', + }, + { + title: '最新活动', + source: ['dbaplus.cn/news-152-1.html'], + target: '/news/152', + }, + { + title: '往期干货', + source: ['dbaplus.cn/news-148-1.html'], + target: '/news/148', + }, + { + title: '特别策划', + source: ['dbaplus.cn/news-150-1.html'], + target: '/news/150', + }, + { + title: '荐书', + source: ['dbaplus.cn/news-151-1.html'], + target: '/news/151', + }, + ], + view: ViewType.Articles, +}; diff --git a/lib/routes/dbaplus/templates/description.art b/lib/routes/dbaplus/templates/description.art new file mode 100644 index 000000000..249654e7e --- /dev/null +++ b/lib/routes/dbaplus/templates/description.art @@ -0,0 +1,21 @@ +{{ if images }} + {{ each images image }} + {{ if image?.src }} +
+ {{ image.alt }} +
+ {{ /if }} + {{ /each }} +{{ /if }} + +{{ if intro }} +
{{ intro }}
+{{ /if }} + +{{ if description }} + {{@ description }} +{{ /if }} \ No newline at end of file