From ca98581d8fe19bc45ef42a6f5fac80df7869c8ec Mon Sep 17 00:00:00 2001 From: Ethan Shen <42264778+nczitzk@users.noreply.github.com> Date: Tue, 3 Jun 2025 00:22:36 +0800 Subject: [PATCH] feat(route): add Bandisoft History (#19247) * feat(route): add Bandisoft History * fix: add verification --- lib/routes/bandisoft/history.ts | 329 ++++++++++++++++++++++++++++++ lib/routes/bandisoft/namespace.ts | 9 + 2 files changed, 338 insertions(+) create mode 100644 lib/routes/bandisoft/history.ts create mode 100644 lib/routes/bandisoft/namespace.ts diff --git a/lib/routes/bandisoft/history.ts b/lib/routes/bandisoft/history.ts new file mode 100644 index 000000000..217a1de4e --- /dev/null +++ b/lib/routes/bandisoft/history.ts @@ -0,0 +1,329 @@ +import { type Data, type DataItem, type Route, ViewType } from '@/types'; + +import ofetch from '@/utils/ofetch'; +import { parseDate } from '@/utils/parse-date'; + +import { type CheerioAPI, type Cheerio, load } from 'cheerio'; +import type { Element } from 'domhandler'; +import { type Context } from 'hono'; + +const idOptions = [ + { + label: 'Bandizip', + value: 'bandizip', + }, + { + label: 'Bandizip for Mac', + value: 'bandizip.mac', + }, + { + label: 'BandiView', + value: 'bandiview', + }, + { + label: 'Honeycam', + value: 'honeycam', + }, +]; + +const languageOptions = [ + { + label: 'English', + value: 'en', + }, + { + label: '中文(简体)', + value: 'cn', + }, + { + label: '中文(繁體)', + value: 'tw', + }, + { + label: '日本語', + value: 'jp', + }, + { + label: 'Русский', + value: 'ru', + }, + { + label: 'Español', + value: 'es', + }, + { + label: 'Français', + value: 'fr', + }, + { + label: 'Deutsch', + value: 'de', + }, + { + label: 'Italiano', + value: 'it', + }, + { + label: 'Slovenčina', + value: 'sk', + }, + { + label: 'Українська', + value: 'uk', + }, + { + label: 'Беларуская', + value: 'be', + }, + { + label: 'Dansk', + value: 'da', + }, + { + label: 'Polski', + value: 'pl', + }, + { + label: 'Português Brasileiro', + value: 'br', + }, + { + label: 'Čeština', + value: 'cs', + }, + { + label: 'Nederlands', + value: 'nl', + }, + { + label: 'Slovenščina', + value: 'sl', + }, + { + label: 'Türkçe', + value: 'tr', + }, + { + label: 'ภาษาไทย', + value: 'th', + }, + { + label: 'Ελληνικά', + value: 'gr', + }, + { + label: "O'zbek", + value: 'uz', + }, + { + label: 'Romanian', + value: 'ro', + }, + { + label: '한국어', + value: 'kr', + }, +]; + +export const handler = async (ctx: Context): Promise => { + const { id = 'bandizip', language = 'en' } = ctx.req.param(); + const limit: number = Number.parseInt(ctx.req.query('limit') ?? '500', 10); + + const validIds: Set = new Set(idOptions.map((option) => option.value)); + + if (!validIds.has(id)) { + throw new Error(`Invalid id: ${id}. Allowed values are: ${[...validIds].join(', ')}`); + } + + const validLanguages: Set = new Set(languageOptions.map((option) => option.value)); + + if (!validLanguages.has(language)) { + throw new Error(`Invalid language: ${language}. Allowed values are: ${[...validLanguages].join(', ')}`); + } + + const baseUrl: string = `https://${language}.bandisoft.com`; + const targetUrl: string = new URL(`${id}/history/`, baseUrl).href; + + const response = await ofetch(targetUrl); + const $: CheerioAPI = load(response); + const lang = $('html').attr('lang') ?? 'en'; + const author: string | undefined = $('meta[name="author"]').attr('content'); + + const items: DataItem[] = $('div.row') + .slice(0, limit) + .toArray() + .map((el): Element => { + const $el: Cheerio = $(el); + + const version: string | undefined = $el.find('div.cell1').text(); + const pubDateStr: string | undefined = $el.find('div.cell2').text(); + + const title: string = version; + const description: string | undefined = $el.find('ul.cell3').html() ?? undefined; + + const linkUrl: string = targetUrl; + const guid: string = `bandisoft-${id}-${language}-${version}`; + const upDatedStr: string | undefined = pubDateStr; + + const processedItem: DataItem = { + title, + description, + pubDate: pubDateStr ? parseDate(pubDateStr) : undefined, + link: linkUrl, + author, + guid, + id: guid, + content: { + html: description, + text: description, + }, + updated: upDatedStr ? parseDate(upDatedStr) : undefined, + language: lang, + }; + + return processedItem; + }); + + return { + title: $('title').text(), + description: $('meta[name="description"]').attr('content'), + link: targetUrl, + item: items, + allowEmpty: true, + image: $('img#logo_light').attr('src'), + author, + language: lang, + id: targetUrl, + }; +}; + +export const route: Route = { + path: '/history/:id?/:language?', + name: 'History', + url: 'www.bandisoft.com', + maintainers: ['nczitzk'], + handler, + example: '/bandisoft/history/bandizip', + parameters: { + id: { + description: 'ID, `bandizip` by default', + options: idOptions, + }, + language: { + description: 'Language, `en` by default', + options: languageOptions, + }, + }, + description: `:::tip +To subscribe to [Bandizip Version History](https://www.bandisoft.com/bandizip/history/), where the source URL is \`https://www.bandisoft.com/bandizip/history/\`, extract the certain parts from this URL to be used as parameters, resulting in the route as [\`/bandisoft/history/bandizip\`](https://rsshub.app/bandisoft/history/bandizip). +::: + +
+ More languages + +| Language | ID | +| -------------------- | --- | +| English | en | +| 中文(简体) | cn | +| 中文(繁體) | tw | +| 日本語 | jp | +| Русский | ru | +| Español | es | +| Français | fr | +| Deutsch | de | +| Italiano | it | +| Slovenčina | sk | +| Українська | uk | +| Беларуская | be | +| Dansk | da | +| Polski | pl | +| Português Brasileiro | br | +| Čeština | cs | +| Nederlands | nl | +| Slovenščina | sl | +| Türkçe | tr | +| ภาษาไทย | th | +| Ελληνικά | gr | +| Oʻzbek | uz | +| Romanian | ro | +| 한국어 | kr | + +
+`, + categories: ['program-update'], + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportRadar: true, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['www.bandisoft.com/:id/history'], + target: (params) => { + const id: string = params.id; + + return `/bandisoft/history${id ? `/${id}` : ''}`; + }, + }, + ], + view: ViewType.Articles, + + zh: { + path: '/history/:id?/:language?', + name: '更新记录', + url: 'www.bandisoft.com', + maintainers: ['nczitzk'], + handler, + example: '/bandisoft/history/bandizip', + parameters: { + id: { + description: 'ID, 默认为 `bandizip`,可在对应产品页 URL 中找到', + options: idOptions, + }, + language: { + description: '地区, 默认为 `en`', + options: languageOptions, + }, + }, + description: `:::tip +若订阅 [Bandizip 更新记录](https://cn.bandisoft.com/bandizip/history/),网址为 \`https://cn.bandisoft.com/bandizip/history/\`,请截取 \`cn\` 作为 \`category\` 参数填入,此时目标路由为 [\`/bandisoft/:language?/:id?\`](https://rsshub.app/bandisoft/:language?/:id?)。 +::: + +
+ 更多语言 + +| Language | ID | +| -------------------- | --- | +| English | en | +| 中文(简体) | cn | +| 中文(繁體) | tw | +| 日本語 | jp | +| Русский | ru | +| Español | es | +| Français | fr | +| Deutsch | de | +| Italiano | it | +| Slovenčina | sk | +| Українська | uk | +| Беларуская | be | +| Dansk | da | +| Polski | pl | +| Português Brasileiro | br | +| Čeština | cs | +| Nederlands | nl | +| Slovenščina | sl | +| Türkçe | tr | +| ภาษาไทย | th | +| Ελληνικά | gr | +| Oʻzbek | uz | +| Romanian | ro | +| 한국어 | kr | + +
+`, + }, +}; diff --git a/lib/routes/bandisoft/namespace.ts b/lib/routes/bandisoft/namespace.ts new file mode 100644 index 000000000..194b2abff --- /dev/null +++ b/lib/routes/bandisoft/namespace.ts @@ -0,0 +1,9 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'Bandisoft', + url: 'bandisoft.com', + categories: ['program-update'], + description: '', + lang: 'en', +};