From 2a6d5ac9f97edba60ab9fd0d3a15aaee87568f95 Mon Sep 17 00:00:00 2001 From: DIYgod Date: Sun, 7 Apr 2024 13:07:09 +0800 Subject: [PATCH 01/11] feat: better error message --- lib/app.tsx | 2 ++ lib/errors/index.test.ts | 4 +-- lib/errors/index.tsx | 44 +++++++++++++++---------------- lib/errors/not-found.ts | 4 ++- lib/errors/reject.ts | 4 ++- lib/errors/request-in-progress.ts | 4 ++- 6 files changed, 35 insertions(+), 27 deletions(-) diff --git a/lib/app.tsx b/lib/app.tsx index 53e85e2a56dee1..65feef3abfc31f 100644 --- a/lib/app.tsx +++ b/lib/app.tsx @@ -13,6 +13,7 @@ import header from '@/middleware/header'; import antiHotlink from '@/middleware/anti-hotlink'; import parameter from '@/middleware/parameter'; import { jsxRenderer } from 'hono/jsx-renderer'; +import { trimTrailingSlash } from 'hono/trailing-slash'; import logger from '@/utils/logger'; @@ -26,6 +27,7 @@ process.on('uncaughtException', (e) => { const app = new Hono(); +app.use(trimTrailingSlash()); app.use(compress()); app.use( diff --git a/lib/errors/index.test.ts b/lib/errors/index.test.ts index f76af62198a959..dc58186aa7696a 100644 --- a/lib/errors/index.test.ts +++ b/lib/errors/index.test.ts @@ -22,7 +22,7 @@ describe('httperror', () => { it(`httperror`, async () => { const response = await request.get('/test/httperror'); expect(response.status).toBe(503); - expect(response.text).toMatch('404 Not Found: target website might be blocking our access, you can host your own RSSHub instance for a better usability.'); + expect(response.text).toMatch('FetchError: [GET] "https://httpbingo.org/status/404": 404 Not Found'); }, 20000); }); @@ -31,7 +31,7 @@ describe('RequestInProgressError', () => { const responses = await Promise.all([request.get('/test/slow'), request.get('/test/slow')]); expect(new Set(responses.map((r) => r.status))).toEqual(new Set([200, 503])); expect(new Set(responses.map((r) => r.headers['cache-control']))).toEqual(new Set([`public, max-age=${config.cache.routeExpire}`, `public, max-age=${config.requestTimeout / 1000}`])); - expect(responses.filter((r) => r.text.includes('This path is currently fetching, please come back later!'))).toHaveLength(1); + expect(responses.filter((r) => r.text.includes('RequestInProgressError: This path is currently fetching, please come back later!'))).toHaveLength(1); }); }); diff --git a/lib/errors/index.tsx b/lib/errors/index.tsx index 2f16d0512d4d79..77a8f0831983d1 100644 --- a/lib/errors/index.tsx +++ b/lib/errors/index.tsx @@ -5,8 +5,6 @@ import Sentry from '@sentry/node'; import logger from '@/utils/logger'; import Error from '@/views/error'; -import RequestInProgressError from './request-in-progress'; -import RejectError from './reject'; import NotFoundError from './not-found'; export const errorHandler: ErrorHandler = (error, ctx) => { @@ -38,27 +36,29 @@ export const errorHandler: ErrorHandler = (error, ctx) => { }); } - let message = ''; - if (error.name && (error.name === 'HTTPError' || error.name === 'RequestError' || error.name === 'FetchError')) { - ctx.status(503); - message = `${error.message}: target website might be blocking our access, you can host your own RSSHub instance for a better usability.`; - } else if (error instanceof RequestInProgressError) { - ctx.header('Cache-Control', `public, max-age=${config.requestTimeout / 1000}`); - ctx.status(503); - message = error.message; - } else if (error instanceof RejectError) { - ctx.status(403); - message = error.message; - } else if (error instanceof NotFoundError) { - ctx.status(404); - message = 'wrong path'; - if (ctx.req.path.endsWith('/')) { - message += ', you can try removing the trailing slash in the path'; - } - } else { - ctx.status(503); - message = process.env.NODE_ENV === 'production' ? error.message : error.stack || error.message; + let errorMessage = process.env.NODE_ENV === 'production' ? error.message : error.stack || error.message; + switch (error.constructor.name) { + case 'HTTPError': + case 'RequestError': + case 'FetchError': + ctx.status(503); + break; + case 'RequestInProgressError': + ctx.header('Cache-Control', `public, max-age=${config.requestTimeout / 1000}`); + ctx.status(503); + break; + case 'RejectError': + ctx.status(403); + break; + case 'NotFoundError': + ctx.status(404); + errorMessage += 'The route does not exist or has been deleted.'; + break; + default: + ctx.status(503); + break; } + const message = `${error.name}: ${errorMessage}`; logger.error(`Error in ${requestPath}: ${message}`); diff --git a/lib/errors/not-found.ts b/lib/errors/not-found.ts index 2ba6e18b3e1945..9cba16b31e797a 100644 --- a/lib/errors/not-found.ts +++ b/lib/errors/not-found.ts @@ -1,3 +1,5 @@ -class NotFoundError extends Error {} +class NotFoundError extends Error { + name = 'NotFoundError'; +} export default NotFoundError; diff --git a/lib/errors/reject.ts b/lib/errors/reject.ts index 6296482265cae3..b6b91fe4967c4a 100644 --- a/lib/errors/reject.ts +++ b/lib/errors/reject.ts @@ -1,3 +1,5 @@ -class RejectError extends Error {} +class RejectError extends Error { + name = 'RejectError'; +} export default RejectError; diff --git a/lib/errors/request-in-progress.ts b/lib/errors/request-in-progress.ts index 99118977e8e77f..73ae4b5705d37c 100644 --- a/lib/errors/request-in-progress.ts +++ b/lib/errors/request-in-progress.ts @@ -1,3 +1,5 @@ -class RequestInProgressError extends Error {} +class RequestInProgressError extends Error { + name = 'RequestInProgressError'; +} export default RequestInProgressError; From efc2b2f5a4eca447f1e9bb7a9ab6fa4b25d68f7b Mon Sep 17 00:00:00 2001 From: DIYgod Date: Sun, 7 Apr 2024 14:06:32 +0800 Subject: [PATCH 02/11] test: fix parameter test text --- lib/middleware/parameter.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/middleware/parameter.test.ts b/lib/middleware/parameter.test.ts index 6fb8788bbf35b2..4166157238ef63 100644 --- a/lib/middleware/parameter.test.ts +++ b/lib/middleware/parameter.test.ts @@ -324,7 +324,7 @@ describe('wrong_path', () => { const response = await app.request('/wrong'); expect(response.status).toBe(404); expect(response.headers.get('cache-control')).toBe(`public, max-age=${config.cache.routeExpire}`); - expect(await response.text()).toMatch('wrong path'); + expect(await response.text()).toMatch('The route does not exist or has been deleted.'); }); }); From 2b59c835c4453f5749b678d68887f9dc4e25ff63 Mon Sep 17 00:00:00 2001 From: lyqluis <39592732+lyqluis@users.noreply.github.com> Date: Sun, 7 Apr 2024 16:35:07 +0800 Subject: [PATCH 03/11] fix(route): xueqiu user (#15140) --- lib/routes/xueqiu/user.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/routes/xueqiu/user.ts b/lib/routes/xueqiu/user.ts index c2d0f11108bb16..105ff90cae8cb3 100644 --- a/lib/routes/xueqiu/user.ts +++ b/lib/routes/xueqiu/user.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; +import ofetch from '@/utils/ofetch'; import queryString from 'query-string'; import { parseDate } from '@/utils/parse-date'; import sanitizeHtml from 'sanitize-html'; @@ -47,11 +48,11 @@ async function handler(ctx) { 11: '交易', }; - const res1 = await got({ + const res1 = await ofetch.raw(rootUrl, { method: 'get', - url: rootUrl, }); - const token = res1.headers['set-cookie'].find((s) => s.startsWith('xq_a_token=')).split(';')[0]; + const cookieArray = res1.headers.getSetCookie(); + const token = cookieArray.find((c) => c.startsWith('xq_a_token=')); const res2 = await got({ method: 'get', From cef1ed1eb61c18bf5c2c7d391267ac1ec58a1f30 Mon Sep 17 00:00:00 2001 From: DIYgod Date: Sun, 7 Apr 2024 16:37:54 +0800 Subject: [PATCH 04/11] refactor: error types --- lib/errors/index.tsx | 2 +- lib/errors/{ => types}/not-found.ts | 0 lib/errors/{ => types}/reject.ts | 0 lib/errors/{ => types}/request-in-progress.ts | 0 lib/middleware/access-control.ts | 2 +- lib/middleware/cache.ts | 2 +- 6 files changed, 3 insertions(+), 3 deletions(-) rename lib/errors/{ => types}/not-found.ts (100%) rename lib/errors/{ => types}/reject.ts (100%) rename lib/errors/{ => types}/request-in-progress.ts (100%) diff --git a/lib/errors/index.tsx b/lib/errors/index.tsx index 77a8f0831983d1..56ba079e455fc3 100644 --- a/lib/errors/index.tsx +++ b/lib/errors/index.tsx @@ -5,7 +5,7 @@ import Sentry from '@sentry/node'; import logger from '@/utils/logger'; import Error from '@/views/error'; -import NotFoundError from './not-found'; +import NotFoundError from './types/not-found'; export const errorHandler: ErrorHandler = (error, ctx) => { const requestPath = ctx.req.path; diff --git a/lib/errors/not-found.ts b/lib/errors/types/not-found.ts similarity index 100% rename from lib/errors/not-found.ts rename to lib/errors/types/not-found.ts diff --git a/lib/errors/reject.ts b/lib/errors/types/reject.ts similarity index 100% rename from lib/errors/reject.ts rename to lib/errors/types/reject.ts diff --git a/lib/errors/request-in-progress.ts b/lib/errors/types/request-in-progress.ts similarity index 100% rename from lib/errors/request-in-progress.ts rename to lib/errors/types/request-in-progress.ts diff --git a/lib/middleware/access-control.ts b/lib/middleware/access-control.ts index 24714869b8b65c..2ba1b237c764fd 100644 --- a/lib/middleware/access-control.ts +++ b/lib/middleware/access-control.ts @@ -1,7 +1,7 @@ import type { MiddlewareHandler } from 'hono'; import { config } from '@/config'; import md5 from '@/utils/md5'; -import RejectError from '@/errors/reject'; +import RejectError from '@/errors/types/reject'; const reject = () => { throw new RejectError('Authentication failed. Access denied.'); diff --git a/lib/middleware/cache.ts b/lib/middleware/cache.ts index a9e8c8a8f48368..5ceee99eadfd54 100644 --- a/lib/middleware/cache.ts +++ b/lib/middleware/cache.ts @@ -2,7 +2,7 @@ import xxhash from 'xxhash-wasm'; import type { MiddlewareHandler } from 'hono'; import { config } from '@/config'; -import RequestInProgressError from '@/errors/request-in-progress'; +import RequestInProgressError from '@/errors/types/request-in-progress'; import cacheModule from '@/utils/cache/index'; import { Data } from '@/types'; From 7fad84d9f6d2346af561c9e3b99c6a119c5767a0 Mon Sep 17 00:00:00 2001 From: Shima Rin <125703711+nashi23@users.noreply.github.com> Date: Sun, 7 Apr 2024 17:20:45 +0800 Subject: [PATCH 05/11] =?UTF-8?q?feat(route):=20add=20=E7=BA=BF=E6=8A=A5?= =?UTF-8?q?=E9=85=B7=20(#15144)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(route): add 线报酷 * Update lib/routes/xianbao/index.ts Co-authored-by: Tony * Update lib/routes/xianbao/index.ts Co-authored-by: Tony * Update lib/routes/xianbao/index.ts Co-authored-by: Tony --------- --- lib/routes/xianbao/index.ts | 134 ++++++++++++++++++++++++++++++++ lib/routes/xianbao/namespace.ts | 6 ++ 2 files changed, 140 insertions(+) create mode 100644 lib/routes/xianbao/index.ts create mode 100644 lib/routes/xianbao/namespace.ts diff --git a/lib/routes/xianbao/index.ts b/lib/routes/xianbao/index.ts new file mode 100644 index 00000000000000..209feab26945bb --- /dev/null +++ b/lib/routes/xianbao/index.ts @@ -0,0 +1,134 @@ +import { Route } from '@/types'; +import got from '@/utils/got'; +import { parseDate } from '@/utils/parse-date'; + +export const route: Route = { + path: '/:category?', + name: '线板酷', + url: 'new.xianbao.fun', + maintainers: ['nashi23'], + handler, + example: '/xianbao', + parameters: { category: '类别id,默认为:latest' }, + description: ` +| 分类 | id | +| ------------ | -------------- | +| 最新 | latest | +| 赚客吧 | zuankeba | +| 赚客吧热帖 | zuankeba-hot | +| 新赚吧 | xinzuanba | +| 新赚吧热帖 | xinzuanba-hot | +| 微博 | weibo | +| 微博热帖 | weibo-hot | +| 豆瓣线报 | douban | +| 豆瓣热帖 | douban-hot | +| 酷安 | kuan | +| 小嘀咕 | xiaodigu | +| 葫芦侠 | huluxia | +| 小刀娱乐网 | xiadao | +| 技术 QQ 网 | qqjishu | +| YYOK 大全 | yyok | +| 活动资讯网 | huodong | +| 免费赚钱中心 | maifei | +| 一小时 | yixiaoshi | +| 三小时 | sanxiaoshi | +| 六小时 | liuxiaoshi | +| 十二小时 | shierxiaoshi | +| 二十四小时 | ershisixiaoshi | +| 四十八小时 | sishibaxiaoshi | +| 今天 | jintian | +| 昨天 | zuotian | +| 前天 | qiantian | +| 三天 | santian | +| 五天 | wutian | +| 七天 | qitian | +| 十五天 | shiwutian |`, + categories: ['shopping'], + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportRadar: true, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['new.xianbao.fun'], + target: '/', + }, + ], +}; + +async function handler(ctx) { + const categoryParam = ctx.req.param() || { category: 'latest' }; + + let urlPath = ''; + + const category = categoryParam.category || 'latest'; + + const cat = CATEGORIES.find((cat) => cat.id === category) || CATEGORIES.find((cat) => cat.id === 'latest'); + const fullName = cat!.fullName; + const pushPath = cat!.pushPath; + + if (category.endsWith('xiaoshi') || category.endsWith('tian')) { + urlPath = `${category}-hot.html`; + } else if (category.endsWith('hot')) { + urlPath = `${category}.html`; + } else { + urlPath = category === 'latest' ? '' : `category-${category}`; + } + + const apiUrl = `http://new.xianbao.fun/plus/json/${pushPath}.json`; + const response = await got(apiUrl); + const responseData = JSON.parse(response.body); + + const items = responseData.map((item) => ({ + title: item.title, + link: `http://new.xianbao.fun${item.url}`, + description: item.content, + pubDate: parseDate(item.shijianchuo * 1000), + author: item.louzhu, + category: item.catename, + })); + + return { + title: `线板酷-${fullName}`, + link: `http://new.xianbao.fun/${urlPath}`, + item: items, + }; +} + +const CATEGORIES = [ + { id: 'latest', fullName: '最新', pushPath: 'push' }, + { id: 'yixiaoshi', fullName: '一小时', pushPath: 'rank/yixiaoshi' }, + { id: 'sanxiaoshi', fullName: '三小时', pushPath: 'rank/sanxiaoshi' }, + { id: 'liuxiaoshi', fullName: '六小时', pushPath: 'rank/liuxiaoshi' }, + { id: 'shierxiaoshi', fullName: '十二小时', pushPath: 'rank/shierxiaoshi' }, + { id: 'ershisixiaoshi', fullName: '二十四小时', pushPath: 'rank/ershisixiaoshi' }, + { id: 'sishibaxiaoshi', fullName: '四十八小时', pushPath: 'rank/sishibaxiaoshi' }, + { id: 'jintian', fullName: '今天', pushPath: 'rank/jintian' }, + { id: 'zuotian', fullName: '昨天', pushPath: 'rank/zuotian' }, + { id: 'qiantian', fullName: '前天', pushPath: 'rank/qiantian' }, + { id: 'santian', fullName: '三天', pushPath: 'rank/santian' }, + { id: 'wutian', fullName: '五天', pushPath: 'rank/wutian' }, + { id: 'qitian', fullName: '七天', pushPath: 'rank/qitian' }, + { id: 'shiwutian', fullName: '十五天', pushPath: 'rank/shiwutian' }, + { id: 'zuankeba', fullName: '赚客吧', pushPath: 'push_16' }, + { id: 'zuankeba-hot', fullName: '赚客吧热帖', pushPath: 'rank/zuankeba-hot' }, + { id: 'xinzuanba', fullName: '新赚吧', pushPath: 'push_18' }, + { id: 'xinzuanba-hot', fullName: '新赚吧热帖', pushPath: 'rank/xinzuanba-hot' }, + { id: 'weibo', fullName: '微博', pushPath: 'push_10' }, + { id: 'weibo-hot', fullName: '微博热帖', pushPath: 'rank/weibo-hot' }, + { id: 'douban', fullName: '豆瓣线报', pushPath: 'push_23' }, + { id: 'douban-hot', fullName: '豆瓣热帖', pushPath: 'rank/douban-hot' }, + { id: 'kuan', fullName: '酷安', pushPath: 'push_17' }, + { id: 'xiaodigu', fullName: '小嘀咕', pushPath: 'push_11' }, + { id: 'huluxia', fullName: '葫芦侠', pushPath: 'push_20' }, + { id: 'xiadao', fullName: '小刀娱乐网', pushPath: 'push_3' }, + { id: 'qqjishu', fullName: '技术QQ网', pushPath: 'push_6' }, + { id: 'yyok', fullName: 'YYOK大全', pushPath: 'push_7' }, + { id: 'huodong', fullName: '活动资讯网', pushPath: 'push_8' }, + { id: 'maifei', fullName: '免费赚钱中心', pushPath: 'push_9' }, +]; diff --git a/lib/routes/xianbao/namespace.ts b/lib/routes/xianbao/namespace.ts new file mode 100644 index 00000000000000..2491698de29dc6 --- /dev/null +++ b/lib/routes/xianbao/namespace.ts @@ -0,0 +1,6 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: '线报酷', + url: 'new.xianbao.fun', +}; From 79fcce27291862134b4b90c0cf666a6bc79948da Mon Sep 17 00:00:00 2001 From: DIYgod Date: Sun, 7 Apr 2024 17:36:18 +0800 Subject: [PATCH 06/11] feat: add ConfigNotFoundError and InvalidParameterError --- lib/errors/types/config-not-found.ts | 5 +++++ lib/errors/types/invalid-parameter.ts | 5 +++++ lib/routes/12306/index.ts | 3 ++- lib/routes/163/news/rank.ts | 5 +++-- lib/routes/163/news/special.ts | 3 ++- lib/routes/18comic/utils.ts | 3 ++- lib/routes/19lou/index.ts | 3 ++- lib/routes/4gamers/utils.ts | 3 ++- lib/routes/591/list.ts | 3 ++- lib/routes/91porn/utils.ts | 3 ++- lib/routes/acfun/article.ts | 7 ++++--- lib/routes/aip/journal-pupp.ts | 3 ++- lib/routes/aisixiang/thinktank.ts | 3 ++- lib/routes/bangumi/tv/subject/index.ts | 3 ++- lib/routes/bdys/index.ts | 3 ++- lib/routes/bendibao/news.ts | 3 ++- lib/routes/bilibili/followers.ts | 5 +++-- lib/routes/bilibili/followings-article.ts | 5 +++-- lib/routes/bilibili/followings-dynamic.ts | 5 +++-- lib/routes/bilibili/followings-video.ts | 5 +++-- lib/routes/bilibili/followings.ts | 8 +++++--- lib/routes/bilibili/manga-followings.ts | 5 +++-- lib/routes/bilibili/watchlater.ts | 5 +++-- lib/routes/biquge/index.ts | 3 ++- lib/routes/btzj/index.ts | 3 ++- lib/routes/caixin/blog.ts | 3 ++- lib/routes/caixin/category.ts | 3 ++- lib/routes/cls/depth.ts | 3 ++- lib/routes/cnjxol/index.ts | 3 ++- lib/routes/comicskingdom/index.ts | 3 ++- lib/routes/coolapk/dyh.ts | 3 ++- lib/routes/coolapk/huati.ts | 3 ++- lib/routes/coolapk/user-dynamic.ts | 5 +++-- lib/routes/discord/channel.ts | 3 ++- lib/routes/discourse/utils.ts | 3 ++- lib/routes/discuz/discuz.ts | 6 ++++-- lib/routes/dlsite/campaign.ts | 3 ++- lib/routes/dlsite/new.ts | 3 ++- lib/routes/domp4/utils.ts | 3 ++- lib/routes/douyin/hashtag.ts | 3 ++- lib/routes/douyin/live.ts | 3 ++- lib/routes/douyin/user.ts | 3 ++- lib/routes/dut/index.ts | 3 ++- lib/routes/eagle/blog.ts | 3 ++- lib/routes/ehentai/favorites.ts | 3 ++- lib/routes/eprice/rss.ts | 3 ++- lib/routes/fansly/utils.ts | 3 ++- lib/routes/ff14/ff14-global.ts | 3 ++- lib/routes/finviz/news.ts | 3 ++- lib/routes/gamme/category.ts | 3 ++- lib/routes/gamme/tag.ts | 3 ++- lib/routes/gcores/category.ts | 3 ++- lib/routes/gcores/tag.ts | 3 ++- lib/routes/github/follower.ts | 3 ++- lib/routes/github/notifications.ts | 3 ++- lib/routes/github/star.ts | 3 ++- lib/routes/github/trending.ts | 3 ++- lib/routes/google/fonts.ts | 3 ++- lib/routes/gov/shenzhen/xxgk/zfxxgj.ts | 3 ++- lib/routes/gov/shenzhen/zjj/index.ts | 3 ++- lib/routes/gov/suzhou/news.ts | 3 ++- lib/routes/gumroad/index.ts | 3 ++- lib/routes/guokr/channel.ts | 3 ++- lib/routes/huanqiu/index.ts | 3 ++- lib/routes/instagram/private-api/index.ts | 3 ++- lib/routes/instagram/private-api/utils.ts | 3 ++- lib/routes/instagram/web-api/index.ts | 6 ++++-- lib/routes/instagram/web-api/utils.ts | 7 ++++--- lib/routes/iqiyi/album.ts | 3 ++- lib/routes/itch/devlog.ts | 3 ++- lib/routes/ithome/index.ts | 3 ++- lib/routes/ithome/ranking.ts | 3 ++- lib/routes/iwara/subscriptions.ts | 3 ++- lib/routes/javbus/index.ts | 3 ++- lib/routes/javdb/utils.ts | 3 ++- lib/routes/kyodonews/index.ts | 6 ++++-- lib/routes/lemmy/index.ts | 6 ++++-- lib/routes/liveuamap/index.ts | 3 ++- lib/routes/lofter/user.ts | 3 ++- lib/routes/m4/index.ts | 3 ++- lib/routes/mail/imap.ts | 3 ++- lib/routes/manhuagui/subscribe.ts | 3 ++- lib/routes/mastodon/account-id.ts | 3 ++- lib/routes/mastodon/timeline-local.ts | 3 ++- lib/routes/mastodon/timeline-remote.ts | 3 ++- lib/routes/mastodon/utils.ts | 5 +++-- lib/routes/medium/following.ts | 5 +++-- lib/routes/medium/for-you.ts | 5 +++-- lib/routes/medium/list.ts | 6 ++++-- lib/routes/medium/tag.ts | 5 +++-- lib/routes/mihoyo/bbs/cache.ts | 3 ++- lib/routes/mihoyo/bbs/timeline.ts | 3 ++- lib/routes/miniflux/entry.ts | 3 ++- lib/routes/miniflux/subscription.ts | 3 ++- lib/routes/mirror/index.ts | 3 ++- lib/routes/misskey/featured-notes.ts | 3 ++- lib/routes/mixcloud/index.ts | 3 ++- lib/routes/myfigurecollection/activity.ts | 3 ++- lib/routes/myfigurecollection/index.ts | 3 ++- lib/routes/newrank/douyin.ts | 3 ++- lib/routes/newrank/wechat.ts | 3 ++- lib/routes/nhentai/other.ts | 3 ++- lib/routes/nhentai/util.ts | 5 +++-- lib/routes/nintendo/eshop-cn.ts | 3 ++- lib/routes/nintendo/news-china.ts | 3 ++- lib/routes/njust/cwc.ts | 3 ++- lib/routes/njust/dgxg.ts | 3 ++- lib/routes/njust/eoe.ts | 3 ++- lib/routes/njust/jwc.ts | 3 ++- lib/routes/notion/database.ts | 8 +++++--- lib/routes/nua/dc.ts | 3 ++- lib/routes/nua/lib.ts | 3 ++- lib/routes/oceanengine/arithmetic-index.ts | 5 +++-- lib/routes/people/index.ts | 3 ++- lib/routes/pianyuan/utils.ts | 5 +++-- lib/routes/pixiv/bookmarks.ts | 5 +++-- lib/routes/pixiv/illustfollow.ts | 5 +++-- lib/routes/pixiv/ranking.ts | 5 +++-- lib/routes/pixiv/search.ts | 5 +++-- lib/routes/pixiv/user.ts | 5 +++-- lib/routes/plurk/top.ts | 3 ++- lib/routes/pornhub/category-url.ts | 3 ++- lib/routes/pornhub/model.ts | 3 ++- lib/routes/pornhub/pornstar.ts | 3 ++- lib/routes/pornhub/users.ts | 3 ++- lib/routes/qweather/3days.ts | 3 ++- lib/routes/rsshub/transform/html.ts | 3 ++- lib/routes/rsshub/transform/json.ts | 3 ++- lib/routes/rsshub/transform/sitemap.ts | 3 ++- lib/routes/sehuatang/user.ts | 3 ++- lib/routes/shiep/index.ts | 5 +++-- lib/routes/solidot/main.ts | 3 ++- lib/routes/spotify/utils.ts | 5 +++-- lib/routes/sspai/author.ts | 3 ++- lib/routes/swjtu/xg.ts | 3 ++- lib/routes/telegram/stickerpack.ts | 3 ++- lib/routes/telegram/tglib/channel.ts | 3 ++- lib/routes/telegram/tglib/client.ts | 3 ++- lib/routes/tencent/news/coronavirus/data.ts | 3 ++- lib/routes/tencent/qq/sdk/changelog.ts | 3 ++- lib/routes/trending/all-trending.ts | 3 ++- lib/routes/twitch/schedule.ts | 3 ++- lib/routes/twitch/video.ts | 7 ++++--- lib/routes/twitter/api/index.ts | 3 ++- lib/routes/twitter/api/mobile-api/api.ts | 3 ++- lib/routes/twitter/api/mobile-api/token.ts | 3 ++- lib/routes/twitter/api/web-api/api.ts | 3 ++- lib/routes/twitter/api/web-api/utils.ts | 5 +++-- lib/routes/twitter/likes.ts | 3 ++- lib/routes/twitter/trends.ts | 3 ++- lib/routes/uestc/cqe.ts | 3 ++- lib/routes/uestc/jwc.ts | 3 ++- lib/routes/uestc/news.ts | 3 ++- lib/routes/uestc/sise.ts | 3 ++- lib/routes/uptimerobot/rss.ts | 7 ++++--- lib/routes/ustc/eeis.ts | 3 ++- lib/routes/ustc/gs.ts | 3 ++- lib/routes/ustc/math.ts | 3 ++- lib/routes/ustc/sist.ts | 3 ++- lib/routes/utgd/topic.ts | 3 ++- lib/routes/wechat/data258.ts | 5 +++-- lib/routes/wechat/feeddd.ts | 3 ++- lib/routes/weibo/friends.ts | 3 ++- lib/routes/weibo/group.ts | 3 ++- lib/routes/wnacg/index.ts | 3 ++- lib/routes/xiaohongshu/user.ts | 5 +++-- lib/routes/xsijishe/rank.ts | 3 ++- lib/routes/xueqiu/timeline.ts | 3 ++- lib/routes/yahoo/news/tw/index.ts | 3 ++- lib/routes/yahoo/news/tw/provider-helper.ts | 3 ++- lib/routes/yahoo/news/tw/provider.ts | 3 ++- lib/routes/yahoo/news/us/index.ts | 3 ++- lib/routes/youtube/channel.ts | 6 ++++-- lib/routes/youtube/custom.ts | 3 ++- lib/routes/youtube/live.ts | 3 ++- lib/routes/youtube/playlist.ts | 3 ++- lib/routes/youtube/subscriptions.ts | 3 ++- lib/routes/youtube/user.ts | 3 ++- lib/routes/zcool/user.ts | 3 ++- lib/routes/zhihu/timeline.ts | 3 ++- lib/routes/zhubai/index.ts | 3 ++- lib/routes/zjol/paper.ts | 3 ++- lib/routes/zodgame/forum.ts | 3 ++- 183 files changed, 423 insertions(+), 224 deletions(-) create mode 100644 lib/errors/types/config-not-found.ts create mode 100644 lib/errors/types/invalid-parameter.ts diff --git a/lib/errors/types/config-not-found.ts b/lib/errors/types/config-not-found.ts new file mode 100644 index 00000000000000..c96cea03c2b80e --- /dev/null +++ b/lib/errors/types/config-not-found.ts @@ -0,0 +1,5 @@ +class ConfigNotFoundError extends Error { + name = 'ConfigNotFoundError'; +} + +export default ConfigNotFoundError; diff --git a/lib/errors/types/invalid-parameter.ts b/lib/errors/types/invalid-parameter.ts new file mode 100644 index 00000000000000..8599ec4d2af4f3 --- /dev/null +++ b/lib/errors/types/invalid-parameter.ts @@ -0,0 +1,5 @@ +class InvalidParameterError extends Error { + name = 'InvalidParameterError'; +} + +export default InvalidParameterError; diff --git a/lib/routes/12306/index.ts b/lib/routes/12306/index.ts index 392af5db14769a..769af199a1d113 100644 --- a/lib/routes/12306/index.ts +++ b/lib/routes/12306/index.ts @@ -7,6 +7,7 @@ import got from '@/utils/got'; import { art } from '@/utils/render'; import path from 'node:path'; import { config } from '@/config'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const rootUrl = 'https://kyfw.12306.cn'; @@ -85,7 +86,7 @@ async function handler(ctx) { }, }); if (response.data.data === undefined || response.data.data.length === 0) { - throw new Error('没有找到相关车次,请检查参数是否正确'); + throw new InvalidParameterError('没有找到相关车次,请检查参数是否正确'); } const data = response.data.data.result; const map = response.data.data.map; diff --git a/lib/routes/163/news/rank.ts b/lib/routes/163/news/rank.ts index be2be096827399..2c83f7064e4f45 100644 --- a/lib/routes/163/news/rank.ts +++ b/lib/routes/163/news/rank.ts @@ -4,6 +4,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import iconv from 'iconv-lite'; import { parseDate } from '@/utils/parse-date'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const rootUrl = 'https://news.163.com'; @@ -119,9 +120,9 @@ async function handler(ctx) { const cfg = config[category]; if (!cfg) { - throw new Error('Bad category. See docs'); + throw new InvalidParameterError('Bad category. See docs'); } else if ((category !== 'whole' && type === 'click' && time === 'month') || (category === 'whole' && type === 'click' && time === 'hour') || (type === 'follow' && time === 'hour')) { - throw new Error('Bad timeRange range. See docs'); + throw new InvalidParameterError('Bad timeRange range. See docs'); } const currentUrl = category === 'money' ? cfg.link : `${rootUrl}${cfg.link}`; diff --git a/lib/routes/163/news/special.ts b/lib/routes/163/news/special.ts index bbf0d051b4a8e7..c80244353cfa99 100644 --- a/lib/routes/163/news/special.ts +++ b/lib/routes/163/news/special.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; @@ -43,7 +44,7 @@ export const route: Route = { async function handler(ctx) { if (!ctx.req.param('type')) { - throw new Error('Bad parameter. See https://docs.rsshub.app/routes/game#wang-yi-da-shen'); + throw new InvalidParameterError('Bad parameter. See https://docs.rsshub.app/routes/game#wang-yi-da-shen'); } const selectedType = Number.parseInt(ctx.req.param('type')); let type; diff --git a/lib/routes/18comic/utils.ts b/lib/routes/18comic/utils.ts index 9c8829e6a4e3b4..e4ea27d1b3746a 100644 --- a/lib/routes/18comic/utils.ts +++ b/lib/routes/18comic/utils.ts @@ -8,6 +8,7 @@ import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const defaultDomain = 'jmcomic1.me'; // list of address: https://jmcomic2.bet @@ -15,7 +16,7 @@ const allowDomain = new Set(['18comic.vip', '18comic.org', 'jmcomic.me', 'jmcomi const getRootUrl = (domain) => { if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.has(domain)) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } return `https://${domain}`; diff --git a/lib/routes/19lou/index.ts b/lib/routes/19lou/index.ts index ca36be62c2f7fd..25b092aca0f109 100644 --- a/lib/routes/19lou/index.ts +++ b/lib/routes/19lou/index.ts @@ -6,6 +6,7 @@ import timezone from '@/utils/timezone'; import { parseDate } from '@/utils/parse-date'; import iconv from 'iconv-lite'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const setCookie = function (cookieName, cookieValue, seconds, path, domain, secure) { let expires = null; @@ -52,7 +53,7 @@ export const route: Route = { async function handler(ctx) { const city = ctx.req.param('city') ?? 'www'; if (!isValidHost(city)) { - throw new Error('Invalid city'); + throw new InvalidParameterError('Invalid city'); } const rootUrl = `https://${city}.19lou.com`; diff --git a/lib/routes/4gamers/utils.ts b/lib/routes/4gamers/utils.ts index b9a2f2f5d35336..b4a9d06194e597 100644 --- a/lib/routes/4gamers/utils.ts +++ b/lib/routes/4gamers/utils.ts @@ -5,6 +5,7 @@ import got from '@/utils/got'; import path from 'node:path'; import { art } from '@/utils/render'; import { parseDate } from '@/utils/parse-date'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const getCategories = (tryGet) => tryGet('4gamers:categories', async () => { @@ -48,7 +49,7 @@ const parseItem = async (item) => { case 'ImageGroupSection': return renderImages(section.items); default: - throw new Error(`Unhandled section type: ${section['@type']} on ${item.link}`); + throw new InvalidParameterError(`Unhandled section type: ${section['@type']} on ${item.link}`); } }) .join('') diff --git a/lib/routes/591/list.ts b/lib/routes/591/list.ts index 4ba811bb8cb898..5b3502ba9e52ce 100644 --- a/lib/routes/591/list.ts +++ b/lib/routes/591/list.ts @@ -10,6 +10,7 @@ import { load } from 'cheerio'; import got from '@/utils/got'; import { art } from '@/utils/render'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const cookieJar = new CookieJar(); @@ -133,7 +134,7 @@ async function handler(ctx) { const country = ctx.req.param('country') ?? 'tw'; if (!isValidHost(country) && country !== 'tw') { - throw new Error('Invalid country codes. Only "tw" is supported now.'); + throw new InvalidParameterError('Invalid country codes. Only "tw" is supported now.'); } /** @type {House[]} */ diff --git a/lib/routes/91porn/utils.ts b/lib/routes/91porn/utils.ts index 40ef32d86d060f..36c6a2e6157b01 100644 --- a/lib/routes/91porn/utils.ts +++ b/lib/routes/91porn/utils.ts @@ -1,9 +1,10 @@ import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const allowDomain = new Set(['91porn.com', 'www.91porn.com', '0122.91p30.com', 'www.91zuixindizhi.com', 'w1218.91p46.com']); const domainValidation = (domain) => { if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.has(domain)) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } }; diff --git a/lib/routes/acfun/article.ts b/lib/routes/acfun/article.ts index 709ce97e755f3e..3c7b623e911944 100644 --- a/lib/routes/acfun/article.ts +++ b/lib/routes/acfun/article.ts @@ -3,6 +3,7 @@ import cache from '@/utils/cache'; import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const baseUrl = 'https://www.acfun.cn'; const categoryMap = { @@ -66,13 +67,13 @@ export const route: Route = { async function handler(ctx) { const { categoryId, sortType = 'createTime', timeRange = 'all' } = ctx.req.param(); if (!categoryMap[categoryId]) { - throw new Error(`Invalid category Id: ${categoryId}`); + throw new InvalidParameterError(`Invalid category Id: ${categoryId}`); } if (!sortTypeEnum.has(sortType)) { - throw new Error(`Invalid sort type: ${sortType}`); + throw new InvalidParameterError(`Invalid sort type: ${sortType}`); } if (!timeRangeEnum.has(timeRange)) { - throw new Error(`Invalid time range: ${timeRange}`); + throw new InvalidParameterError(`Invalid time range: ${timeRange}`); } const url = `${baseUrl}/v/list${categoryId}/index.htm`; diff --git a/lib/routes/aip/journal-pupp.ts b/lib/routes/aip/journal-pupp.ts index 9010c9182e3799..85779d01e874cb 100644 --- a/lib/routes/aip/journal-pupp.ts +++ b/lib/routes/aip/journal-pupp.ts @@ -4,6 +4,7 @@ import { puppeteerGet, renderDesc } from './utils'; import { config } from '@/config'; import { isValidHost } from '@/utils/valid-host'; import puppeteer from '@/utils/puppeteer'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const handler = async (ctx) => { const pub = ctx.req.param('pub'); @@ -11,7 +12,7 @@ const handler = async (ctx) => { const host = `https://pubs.aip.org`; const jrnlUrl = `${host}/${pub}/${jrn}/issue`; if (!isValidHost(pub)) { - throw new Error('Invalid pub'); + throw new InvalidParameterError('Invalid pub'); } // use Puppeteer due to the obstacle by cloudflare challenge diff --git a/lib/routes/aisixiang/thinktank.ts b/lib/routes/aisixiang/thinktank.ts index 861d0a8d38438f..12239370e75d55 100644 --- a/lib/routes/aisixiang/thinktank.ts +++ b/lib/routes/aisixiang/thinktank.ts @@ -4,6 +4,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { rootUrl, ossUrl, ProcessFeed } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/thinktank/:id/:type?', @@ -43,7 +44,7 @@ async function handler(ctx) { .toArray() .filter((h) => (type ? $(h).text() === type : true)); if (!targetList) { - throw new Error(`Not found ${type} in ${id}: ${currentUrl}`); + throw new InvalidParameterError(`Not found ${type} in ${id}: ${currentUrl}`); } for (const l of targetList) { diff --git a/lib/routes/bangumi/tv/subject/index.ts b/lib/routes/bangumi/tv/subject/index.ts index 0e31441948735e..83416b2d83528e 100644 --- a/lib/routes/bangumi/tv/subject/index.ts +++ b/lib/routes/bangumi/tv/subject/index.ts @@ -3,6 +3,7 @@ import getComments from './comments'; import getFromAPI from './offcial-subject-api'; import getEps from './ep'; import { queryToBoolean } from '@/utils/readable-social'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/tv/subject/:id/:type?/:showOriginalName?', @@ -50,7 +51,7 @@ async function handler(ctx) { response = await getFromAPI('topic')(id, showOriginalName); break; default: - throw new Error(`暂不支持对${type}的订阅`); + throw new InvalidParameterError(`暂不支持对${type}的订阅`); } return response; } diff --git a/lib/routes/bdys/index.ts b/lib/routes/bdys/index.ts index 981dbe0de3d38a..5c541d877cc8ab 100644 --- a/lib/routes/bdys/index.ts +++ b/lib/routes/bdys/index.ts @@ -11,6 +11,7 @@ import { art } from '@/utils/render'; import path from 'node:path'; import asyncPool from 'tiny-async-pool'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; // Visit https://www.bdys.me for the list of domains const allowDomains = new Set(['52bdys.com', 'bde4.icu', 'bdys01.com']); @@ -107,7 +108,7 @@ async function handler(ctx) { const site = ctx.req.query('domain') || 'bdys01.com'; if (!config.feature.allow_user_supply_unsafe_domain && !allowDomains.has(new URL(`https://${site}`).hostname)) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } const rootUrl = `https://www.${site}`; diff --git a/lib/routes/bendibao/news.ts b/lib/routes/bendibao/news.ts index 59969ba0903f39..bca4a37467f281 100644 --- a/lib/routes/bendibao/news.ts +++ b/lib/routes/bendibao/news.ts @@ -5,6 +5,7 @@ import { load } from 'cheerio'; import timezone from '@/utils/timezone'; import { parseDate } from '@/utils/parse-date'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/news/:city', @@ -43,7 +44,7 @@ export const route: Route = { async function handler(ctx) { const city = ctx.req.param('city'); if (!isValidHost(city)) { - throw new Error('Invalid city'); + throw new InvalidParameterError('Invalid city'); } const rootUrl = `http://${city}.bendibao.com`; diff --git a/lib/routes/bilibili/followers.ts b/lib/routes/bilibili/followers.ts index 962d860d03df9e..3cb11f00439dce 100644 --- a/lib/routes/bilibili/followers.ts +++ b/lib/routes/bilibili/followers.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import cache from './cache'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/user/followers/:uid/:loginUid', @@ -45,7 +46,7 @@ async function handler(ctx) { const cookie = config.bilibili.cookies[loginUid]; if (cookie === undefined) { - throw new Error('缺少对应 loginUid 的 Bilibili 用户登录后的 Cookie 值 bilibili 用户关注动态系列路由'); + throw new ConfigNotFoundError('缺少对应 loginUid 的 Bilibili 用户登录后的 Cookie 值 bilibili 用户关注动态系列路由'); } const name = await cache.getUsernameFromUID(uid); @@ -68,7 +69,7 @@ async function handler(ctx) { }, }); if (response.data.code === -6 || response.data.code === -101) { - throw new Error('对应 loginUid 的 Bilibili 用户的 Cookie 已过期'); + throw new ConfigNotFoundError('对应 loginUid 的 Bilibili 用户的 Cookie 已过期'); } const data = response.data.data.list; diff --git a/lib/routes/bilibili/followings-article.ts b/lib/routes/bilibili/followings-article.ts index 6eab9ceb6692d5..42ae47e72e0eeb 100644 --- a/lib/routes/bilibili/followings-article.ts +++ b/lib/routes/bilibili/followings-article.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import cache from './cache'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/followings/article/:uid', @@ -39,7 +40,7 @@ async function handler(ctx) { const cookie = config.bilibili.cookies[uid]; if (cookie === undefined) { - throw new Error('缺少对应 uid 的 Bilibili 用户登录后的 Cookie 值'); + throw new ConfigNotFoundError('缺少对应 uid 的 Bilibili 用户登录后的 Cookie 值'); } const response = await got({ @@ -51,7 +52,7 @@ async function handler(ctx) { }, }); if (response.data.code === -6) { - throw new Error('对应 uid 的 Bilibili 用户的 Cookie 已过期'); + throw new ConfigNotFoundError('对应 uid 的 Bilibili 用户的 Cookie 已过期'); } const cards = response.data.data.cards; diff --git a/lib/routes/bilibili/followings-dynamic.ts b/lib/routes/bilibili/followings-dynamic.ts index 5c637d98db3709..1e09fa53930c3c 100644 --- a/lib/routes/bilibili/followings-dynamic.ts +++ b/lib/routes/bilibili/followings-dynamic.ts @@ -6,6 +6,7 @@ import utils from './utils'; import JSONbig from 'json-bigint'; import { fallback, queryToBoolean } from '@/utils/readable-social'; import querystring from 'querystring'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/followings/dynamic/:uid/:routeParams?', @@ -49,7 +50,7 @@ async function handler(ctx) { const cookie = config.bilibili.cookies[uid]; if (cookie === undefined) { - throw new Error('缺少对应 uid 的 Bilibili 用户登录后的 Cookie 值'); + throw new ConfigNotFoundError('缺少对应 uid 的 Bilibili 用户登录后的 Cookie 值'); } const response = await got({ @@ -61,7 +62,7 @@ async function handler(ctx) { }, }); if (response.data.code === -6) { - throw new Error('对应 uid 的 Bilibili 用户的 Cookie 已过期'); + throw new ConfigNotFoundError('对应 uid 的 Bilibili 用户的 Cookie 已过期'); } const data = JSONbig.parse(response.body).data.cards; diff --git a/lib/routes/bilibili/followings-video.ts b/lib/routes/bilibili/followings-video.ts index 50d50943a38c56..6a112ed90a8b45 100644 --- a/lib/routes/bilibili/followings-video.ts +++ b/lib/routes/bilibili/followings-video.ts @@ -3,6 +3,7 @@ import got from '@/utils/got'; import cache from './cache'; import { config } from '@/config'; import utils from './utils'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/followings/video/:uid/:disableEmbed?', @@ -41,7 +42,7 @@ async function handler(ctx) { const cookie = config.bilibili.cookies[uid]; if (cookie === undefined) { - throw new Error('缺少对应 uid 的 Bilibili 用户登录后的 Cookie 值'); + throw new ConfigNotFoundError('缺少对应 uid 的 Bilibili 用户登录后的 Cookie 值'); } const response = await got({ @@ -53,7 +54,7 @@ async function handler(ctx) { }, }); if (response.data.code === -6) { - throw new Error('对应 uid 的 Bilibili 用户的 Cookie 已过期'); + throw new ConfigNotFoundError('对应 uid 的 Bilibili 用户的 Cookie 已过期'); } const cards = response.data.data.cards; diff --git a/lib/routes/bilibili/followings.ts b/lib/routes/bilibili/followings.ts index 82cce50d205e47..0304864a70f69c 100644 --- a/lib/routes/bilibili/followings.ts +++ b/lib/routes/bilibili/followings.ts @@ -2,6 +2,8 @@ import { Route } from '@/types'; import got from '@/utils/got'; import cache from './cache'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/user/followings/:uid/:loginUid', @@ -43,7 +45,7 @@ async function handler(ctx) { const loginUid = ctx.req.param('loginUid'); const cookie = config.bilibili.cookies[loginUid]; if (cookie === undefined) { - throw new Error('缺少对应 loginUid 的 Bilibili 用户登录后的 Cookie 值 bilibili 用户关注动态系列路由'); + throw new ConfigNotFoundError('缺少对应 loginUid 的 Bilibili 用户登录后的 Cookie 值 bilibili 用户关注动态系列路由'); } const uid = ctx.req.param('uid'); @@ -67,11 +69,11 @@ async function handler(ctx) { }, }); if (response.data.code === -6) { - throw new Error('对应 loginUid 的 Bilibili 用户的 Cookie 已过期'); + throw new ConfigNotFoundError('对应 loginUid 的 Bilibili 用户的 Cookie 已过期'); } // 22115 : 用户已设置隐私,无法查看 if (response.data.code === 22115) { - throw new Error(response.data.message); + throw new InvalidParameterError(response.data.message); } const data = response.data.data.list; diff --git a/lib/routes/bilibili/manga-followings.ts b/lib/routes/bilibili/manga-followings.ts index bd134bc784fa46..b992b3e4aa9988 100644 --- a/lib/routes/bilibili/manga-followings.ts +++ b/lib/routes/bilibili/manga-followings.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import cache from './cache'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/manga/followings/:uid/:limits?', @@ -39,7 +40,7 @@ async function handler(ctx) { const cookie = config.bilibili.cookies[uid]; if (cookie === undefined) { - throw new Error('缺少对应 uid 的 Bilibili 用户登录后的 Cookie 值'); + throw new ConfigNotFoundError('缺少对应 uid 的 Bilibili 用户登录后的 Cookie 值'); } const page_size = ctx.req.param('limits') || 10; const link = 'https://manga.bilibili.com/account-center'; @@ -53,7 +54,7 @@ async function handler(ctx) { }, }); if (response.data.code === -6) { - throw new Error('对应 uid 的 Bilibili 用户的 Cookie 已过期'); + throw new ConfigNotFoundError('对应 uid 的 Bilibili 用户的 Cookie 已过期'); } const comics = response.data.data; diff --git a/lib/routes/bilibili/watchlater.ts b/lib/routes/bilibili/watchlater.ts index fe43348eefec06..cb0cf9e31c7a59 100644 --- a/lib/routes/bilibili/watchlater.ts +++ b/lib/routes/bilibili/watchlater.ts @@ -4,6 +4,7 @@ import cache from './cache'; import { config } from '@/config'; import utils from './utils'; import { parseDate } from '@/utils/parse-date'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/watchlater/:uid/:disableEmbed?', @@ -42,7 +43,7 @@ async function handler(ctx) { const cookie = config.bilibili.cookies[uid]; if (cookie === undefined) { - throw new Error('缺少对应 uid 的 Bilibili 用户登录后的 Cookie 值'); + throw new ConfigNotFoundError('缺少对应 uid 的 Bilibili 用户登录后的 Cookie 值'); } const response = await got({ @@ -55,7 +56,7 @@ async function handler(ctx) { }); if (response.data.code) { const message = response.data.code === -6 ? '对应 uid 的 Bilibili 用户的 Cookie 已过期' : response.data.message; - throw new Error(`Error code ${response.data.code}: ${message}`); + throw new ConfigNotFoundError(`Error code ${response.data.code}: ${message}`); } const list = response.data.data.list || []; diff --git a/lib/routes/biquge/index.ts b/lib/routes/biquge/index.ts index 2f71468e6ffffa..581d94db85a465 100644 --- a/lib/routes/biquge/index.ts +++ b/lib/routes/biquge/index.ts @@ -7,6 +7,7 @@ import iconv from 'iconv-lite'; import timezone from '@/utils/timezone'; import { parseDate } from '@/utils/parse-date'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const allowHost = new Set([ 'www.xbiquwx.la', 'www.biqu5200.net', @@ -36,7 +37,7 @@ async function handler(ctx) { const rootUrl = getSubPath(ctx).split('/').slice(1, 4).join('/'); const currentUrl = getSubPath(ctx).slice(1); if (!config.feature.allow_user_supply_unsafe_domain && !allowHost.has(new URL(rootUrl).hostname)) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } const response = await got({ diff --git a/lib/routes/btzj/index.ts b/lib/routes/btzj/index.ts index 8d775f73cc430f..58687e1a240098 100644 --- a/lib/routes/btzj/index.ts +++ b/lib/routes/btzj/index.ts @@ -10,6 +10,7 @@ import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const allowDomain = new Set(['2btjia.com', '88btbtt.com', 'btbtt15.com', 'btbtt20.com']); export const route: Route = { @@ -71,7 +72,7 @@ async function handler(ctx) { let category = ctx.req.param('category') ?? ''; let domain = ctx.req.query('domain') ?? 'btbtt15.com'; if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.has(new URL(`http://${domain}/`).hostname)) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } if (category === 'base') { diff --git a/lib/routes/caixin/blog.ts b/lib/routes/caixin/blog.ts index dc648a63ae6d10..add66df27bb785 100644 --- a/lib/routes/caixin/blog.ts +++ b/lib/routes/caixin/blog.ts @@ -5,6 +5,7 @@ import { load } from 'cheerio'; import { isValidHost } from '@/utils/valid-host'; import { parseDate } from '@/utils/parse-date'; import { parseBlogArticle } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/blog/:column?', @@ -30,7 +31,7 @@ async function handler(ctx) { const { limit = 20 } = ctx.req.query(); if (column) { if (!isValidHost(column)) { - throw new Error('Invalid column'); + throw new InvalidParameterError('Invalid column'); } const link = `https://${column}.blog.caixin.com`; const { data: response } = await got(link); diff --git a/lib/routes/caixin/category.ts b/lib/routes/caixin/category.ts index 31732e09a35639..60f17b21aad69f 100644 --- a/lib/routes/caixin/category.ts +++ b/lib/routes/caixin/category.ts @@ -6,6 +6,7 @@ import { isValidHost } from '@/utils/valid-host'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; import { parseArticle } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:column/:category', @@ -47,7 +48,7 @@ async function handler(ctx) { const column = ctx.req.param('column'); const url = `https://${column}.caixin.com/${category}`; if (!isValidHost(column)) { - throw new Error('Invalid column'); + throw new InvalidParameterError('Invalid column'); } const response = await got(url); diff --git a/lib/routes/cls/depth.ts b/lib/routes/cls/depth.ts index c14f3d6aeda93d..3d81bb1dd2f19d 100644 --- a/lib/routes/cls/depth.ts +++ b/lib/routes/cls/depth.ts @@ -10,6 +10,7 @@ import { art } from '@/utils/render'; import path from 'node:path'; import { rootUrl, getSearchParams } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const categories = { 1000: '头条', @@ -57,7 +58,7 @@ async function handler(ctx) { const title = categories[category]; if (!title) { - throw new Error('Bad category. See docs'); + throw new InvalidParameterError('Bad category. See docs'); } const apiUrl = `${rootUrl}/v3/depth/home/assembled/${category}`; diff --git a/lib/routes/cnjxol/index.ts b/lib/routes/cnjxol/index.ts index ef2fe0a714ad7e..c28f69a1030c3d 100644 --- a/lib/routes/cnjxol/index.ts +++ b/lib/routes/cnjxol/index.ts @@ -8,6 +8,7 @@ import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const categories = { jxrb: '嘉兴日报', @@ -25,7 +26,7 @@ async function handler(ctx) { const category = ctx.req.param('category') ?? 'jxrb'; const id = ctx.req.param('id'); if (!Object.keys(categories).includes(category)) { - throw new Error('Invalid category'); + throw new InvalidParameterError('Invalid category'); } const rootUrl = `https://${category}.cnjxol.com`; diff --git a/lib/routes/comicskingdom/index.ts b/lib/routes/comicskingdom/index.ts index 4f89fc61cd4e35..ee4026017f397c 100644 --- a/lib/routes/comicskingdom/index.ts +++ b/lib/routes/comicskingdom/index.ts @@ -8,6 +8,7 @@ import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:name', @@ -50,7 +51,7 @@ async function handler(ctx) { .map((el) => $(el).find('a').first().attr('href')); if (links.length === 0) { - throw new Error(`Comic Not Found - ${name}`); + throw new InvalidParameterError(`Comic Not Found - ${name}`); } const items = await Promise.all( links.map((link) => diff --git a/lib/routes/coolapk/dyh.ts b/lib/routes/coolapk/dyh.ts index 4844cfd5195f6b..8b386a72505c26 100644 --- a/lib/routes/coolapk/dyh.ts +++ b/lib/routes/coolapk/dyh.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import utils from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/dyh/:dyhId', @@ -48,7 +49,7 @@ async function handler(ctx) { out = out.filter(Boolean); // 去除空值 if (out.length === 0) { - throw new Error('仅限于采集站内订阅的看看号的图文及动态内容。这个ID可能是站外订阅。'); + throw new InvalidParameterError('仅限于采集站内订阅的看看号的图文及动态内容。这个ID可能是站外订阅。'); } return { title: `酷安看看号-${targetTitle}`, diff --git a/lib/routes/coolapk/huati.ts b/lib/routes/coolapk/huati.ts index 5d8de470a8678a..7fc9820e895ffe 100644 --- a/lib/routes/coolapk/huati.ts +++ b/lib/routes/coolapk/huati.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import utils from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/huati/:tag', @@ -32,7 +33,7 @@ async function handler(ctx) { out = out.filter(Boolean); // 去除空值 if (out.length === 0) { - throw new Error('这个话题还没有被创建或现在没有图文及动态内容。'); + throw new InvalidParameterError('这个话题还没有被创建或现在没有图文及动态内容。'); } return { title: `酷安话题-${tag}`, diff --git a/lib/routes/coolapk/user-dynamic.ts b/lib/routes/coolapk/user-dynamic.ts index e380fe1f155639..965df483525cc9 100644 --- a/lib/routes/coolapk/user-dynamic.ts +++ b/lib/routes/coolapk/user-dynamic.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import utils from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/user/:uid/dynamic', @@ -29,7 +30,7 @@ async function handler(ctx) { }); const data = response.data.data; if (!data) { - throw new Error('这个人没有任何动态。'); + throw new InvalidParameterError('这个人没有任何动态。'); } let out = await Promise.all( data.map((item) => { @@ -43,7 +44,7 @@ async function handler(ctx) { out = out.filter(Boolean); // 去除空值 if (out.length === 0) { - throw new Error('这个人还没有图文或动态。'); + throw new InvalidParameterError('这个人还没有图文或动态。'); } return { title: `酷安个人动态-${username}`, diff --git a/lib/routes/discord/channel.ts b/lib/routes/discord/channel.ts index 8474b51a169146..74c6fa34c3e204 100644 --- a/lib/routes/discord/channel.ts +++ b/lib/routes/discord/channel.ts @@ -8,6 +8,7 @@ import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; import { baseUrl, getChannel, getChannelMessages, getGuild } from './discord-api'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/channel/:channelId', @@ -39,7 +40,7 @@ export const route: Route = { async function handler(ctx) { if (!config.discord || !config.discord.authorization) { - throw new Error('Discord RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('Discord RSS is disabled due to the lack of relevant config'); } const { authorization } = config.discord; const channelId = ctx.req.param('channelId'); diff --git a/lib/routes/discourse/utils.ts b/lib/routes/discourse/utils.ts index 897738ed72518a..8638a86ffe4fbf 100644 --- a/lib/routes/discourse/utils.ts +++ b/lib/routes/discourse/utils.ts @@ -1,8 +1,9 @@ import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; function getConfig(ctx) { if (!config.discourse.config[ctx.req.param('configId')]) { - throw new Error('Discourse RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('Discourse RSS is disabled due to the lack of relevant config'); } return config.discourse.config[ctx.req.param('configId')]; } diff --git a/lib/routes/discuz/discuz.ts b/lib/routes/discuz/discuz.ts index 864f09a72e35f2..e68a74485613fe 100644 --- a/lib/routes/discuz/discuz.ts +++ b/lib/routes/discuz/discuz.ts @@ -5,6 +5,8 @@ import { load } from 'cheerio'; import iconv from 'iconv-lite'; import { parseDate } from '@/utils/parse-date'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; function fixUrl(itemLink, baseUrl) { // 处理相对链接 @@ -68,7 +70,7 @@ async function handler(ctx) { const cookie = cid === undefined ? '' : config.discuz.cookies[cid]; if (cookie === undefined) { - throw new Error('缺少对应论坛的cookie.'); + throw new ConfigNotFoundError('缺少对应论坛的cookie.'); } const header = { @@ -149,7 +151,7 @@ async function handler(ctx) { ) ); } else { - throw new Error('不支持当前Discuz版本.'); + throw new InvalidParameterError('不支持当前Discuz版本.'); } return { diff --git a/lib/routes/dlsite/campaign.ts b/lib/routes/dlsite/campaign.ts index be219f3518628a..bcf575ce24b5ef 100644 --- a/lib/routes/dlsite/campaign.ts +++ b/lib/routes/dlsite/campaign.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import { Route } from '@/types'; import got from '@/utils/got'; import { load } from 'cheerio'; @@ -141,7 +142,7 @@ async function handler(ctx) { const info = infos[ctx.req.param('type')]; // 判断参数是否合理 if (info === undefined) { - throw new Error('不支持指定类型!'); + throw new InvalidParameterError('不支持指定类型!'); } if (ctx.req.param('free') !== undefined) { info.params.is_free = 1; diff --git a/lib/routes/dlsite/new.ts b/lib/routes/dlsite/new.ts index 90c5a7d84963d3..f4b4d3bb468b32 100644 --- a/lib/routes/dlsite/new.ts +++ b/lib/routes/dlsite/new.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const host = 'https://www.dlsite.com'; const infos = { @@ -75,7 +76,7 @@ async function handler(ctx) { const info = infos[ctx.req.param('type')]; // 判断参数是否合理 if (info === undefined) { - throw new Error('不支持指定类型!'); + throw new InvalidParameterError('不支持指定类型!'); } const link = info.url.slice(1); diff --git a/lib/routes/domp4/utils.ts b/lib/routes/domp4/utils.ts index d6e0f6bf10f121..15d045292b0a6c 100644 --- a/lib/routes/domp4/utils.ts +++ b/lib/routes/domp4/utils.ts @@ -1,4 +1,5 @@ import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const defaultDomain = 'mp4us.com'; @@ -87,7 +88,7 @@ function decodeCipherText(p, a, c, k, e, d) { function ensureDomain(ctx, domain = defaultDomain) { const origin = `https://${domain}`; if (!config.feature.allow_user_supply_unsafe_domain && !allowedDomains.has(new URL(origin).hostname)) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } return origin; } diff --git a/lib/routes/douyin/hashtag.ts b/lib/routes/douyin/hashtag.ts index 4cafd12d1dc7b8..d7ef5538c83115 100644 --- a/lib/routes/douyin/hashtag.ts +++ b/lib/routes/douyin/hashtag.ts @@ -6,6 +6,7 @@ import { config } from '@/config'; import { fallback, queryToBoolean } from '@/utils/readable-social'; import { templates, resolveUrl, proxyVideo, getOriginAvatar } from './utils'; import puppeteer from '@/utils/puppeteer'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/hashtag/:cid/:routeParams?', @@ -34,7 +35,7 @@ export const route: Route = { async function handler(ctx) { const cid = ctx.req.param('cid'); if (isNaN(cid)) { - throw new TypeError('Invalid tag ID. Tag ID should be a number.'); + throw new InvalidParameterError('Invalid tag ID. Tag ID should be a number.'); } const routeParams = Object.fromEntries(new URLSearchParams(ctx.req.param('routeParams'))); const embed = fallback(undefined, queryToBoolean(routeParams.embed), false); // embed video diff --git a/lib/routes/douyin/live.ts b/lib/routes/douyin/live.ts index bf640a007bc6d3..f4eaf5fb491cdb 100644 --- a/lib/routes/douyin/live.ts +++ b/lib/routes/douyin/live.ts @@ -4,6 +4,7 @@ import { config } from '@/config'; import { getOriginAvatar } from './utils'; import logger from '@/utils/logger'; import puppeteer from '@/utils/puppeteer'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/live/:rid', @@ -31,7 +32,7 @@ export const route: Route = { async function handler(ctx) { const rid = ctx.req.param('rid'); if (isNaN(rid)) { - throw new TypeError('Invalid room ID. Room ID should be a number.'); + throw new InvalidParameterError('Invalid room ID. Room ID should be a number.'); } const pageUrl = `https://live.douyin.com/${rid}`; diff --git a/lib/routes/douyin/user.ts b/lib/routes/douyin/user.ts index 72053101dca970..f4bf243df3888c 100644 --- a/lib/routes/douyin/user.ts +++ b/lib/routes/douyin/user.ts @@ -5,6 +5,7 @@ import { art } from '@/utils/render'; import { config } from '@/config'; import { fallback, queryToBoolean } from '@/utils/readable-social'; import { templates, resolveUrl, proxyVideo, getOriginAvatar, universalGet } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/user/:uid/:routeParams?', @@ -33,7 +34,7 @@ export const route: Route = { async function handler(ctx) { const uid = ctx.req.param('uid'); if (!uid.startsWith('MS4wLjABAAAA')) { - throw new Error('Invalid UID. UID should start with MS4wLjABAAAA.'); + throw new InvalidParameterError('Invalid UID. UID should start with MS4wLjABAAAA.'); } const routeParams = Object.fromEntries(new URLSearchParams(ctx.req.param('routeParams'))); const embed = fallback(undefined, queryToBoolean(routeParams.embed), false); // embed video diff --git a/lib/routes/dut/index.ts b/lib/routes/dut/index.ts index a671c82904076f..dbee2d986bb95a 100644 --- a/lib/routes/dut/index.ts +++ b/lib/routes/dut/index.ts @@ -6,6 +6,7 @@ import { parseDate } from '@/utils/parse-date'; import defaults from './defaults'; import shortcuts from './shortcuts'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: ['/*/*', '/:0?'], @@ -17,7 +18,7 @@ export const route: Route = { async function handler(ctx) { const site = ctx.params[0] ?? 'news'; if (!isValidHost(site)) { - throw new Error('Invalid site'); + throw new InvalidParameterError('Invalid site'); } let items; diff --git a/lib/routes/eagle/blog.ts b/lib/routes/eagle/blog.ts index 4bf28bb30d1dde..d14e6cf18dd56a 100644 --- a/lib/routes/eagle/blog.ts +++ b/lib/routes/eagle/blog.ts @@ -4,6 +4,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const cateList = new Set(['all', 'design-resources', 'learn-design', 'inside-eagle']); export const route: Route = { @@ -35,7 +36,7 @@ async function handler(ctx) { let cate = ctx.req.param('cate') ?? 'all'; let language = ctx.req.param('language') ?? 'cn'; if (!isValidHost(cate) || !isValidHost(language)) { - throw new Error('Invalid host'); + throw new InvalidParameterError('Invalid host'); } if (!cateList.has(cate)) { language = cate; diff --git a/lib/routes/ehentai/favorites.ts b/lib/routes/ehentai/favorites.ts index 2c0dac5a7566f1..5ff6a8a38d0549 100644 --- a/lib/routes/ehentai/favorites.ts +++ b/lib/routes/ehentai/favorites.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import cache from '@/utils/cache'; import EhAPI from './ehapi'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/favorites/:favcat?/:order?/:page?/:routeParams?', @@ -22,7 +23,7 @@ export const route: Route = { async function handler(ctx) { if (!EhAPI.has_cookie) { - throw new Error('Ehentai favorites RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('Ehentai favorites RSS is disabled due to the lack of relevant config'); } const favcat = ctx.req.param('favcat') ? Number.parseInt(ctx.req.param('favcat')) : 0; const page = ctx.req.param('page'); diff --git a/lib/routes/eprice/rss.ts b/lib/routes/eprice/rss.ts index 4facb99afad8fd..510eb66caf3714 100644 --- a/lib/routes/eprice/rss.ts +++ b/lib/routes/eprice/rss.ts @@ -9,6 +9,7 @@ import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const allowRegion = new Set(['tw', 'hk']); export const route: Route = { @@ -37,7 +38,7 @@ export const route: Route = { async function handler(ctx) { const region = ctx.req.param('region') ?? 'tw'; if (!allowRegion.has(region)) { - throw new Error('Invalid region'); + throw new InvalidParameterError('Invalid region'); } const feed = await parser.parseURL(`https://www.eprice.com.${region}/news/rss.xml`); diff --git a/lib/routes/fansly/utils.ts b/lib/routes/fansly/utils.ts index b95c937671f362..91f47ca2243e8c 100644 --- a/lib/routes/fansly/utils.ts +++ b/lib/routes/fansly/utils.ts @@ -4,6 +4,7 @@ const __dirname = getCurrentPath(import.meta.url); import got from '@/utils/got'; import path from 'node:path'; import { art } from '@/utils/render'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const apiBaseUrl = 'https://apiv3.fansly.com'; const baseUrl = 'https://fansly.com'; @@ -27,7 +28,7 @@ const getAccountByUsername = (username, tryGet) => }); if (!accountResponse.response.length) { - throw new Error('This profile or page does not exist.'); + throw new InvalidParameterError('This profile or page does not exist.'); } return accountResponse.response[0]; diff --git a/lib/routes/ff14/ff14-global.ts b/lib/routes/ff14/ff14-global.ts index 2d54646209a1ae..3ffb4561ab729e 100644 --- a/lib/routes/ff14/ff14-global.ts +++ b/lib/routes/ff14/ff14-global.ts @@ -7,6 +7,7 @@ import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: ['/global/:lang/:type?', '/ff14_global/:lang/:type?'], @@ -41,7 +42,7 @@ async function handler(ctx) { const type = ctx.req.param('type') ?? 'all'; if (!isValidHost(lang)) { - throw new Error('Invalid lang'); + throw new InvalidParameterError('Invalid lang'); } const response = await got({ diff --git a/lib/routes/finviz/news.ts b/lib/routes/finviz/news.ts index 93d541b285454f..8ef797301c5459 100644 --- a/lib/routes/finviz/news.ts +++ b/lib/routes/finviz/news.ts @@ -3,6 +3,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import timezone from '@/utils/timezone'; import { parseDate } from '@/utils/parse-date'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const categories = { news: 0, @@ -41,7 +42,7 @@ async function handler(ctx) { const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 200; if (!Object.hasOwn(categories, category.toLowerCase())) { - throw new Error(`No category '${category}'.`); + throw new InvalidParameterError(`No category '${category}'.`); } const rootUrl = 'https://finviz.com'; diff --git a/lib/routes/gamme/category.ts b/lib/routes/gamme/category.ts index fcca76ae4d6284..e60ab5a84f4eba 100644 --- a/lib/routes/gamme/category.ts +++ b/lib/routes/gamme/category.ts @@ -4,6 +4,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import parser from '@/utils/rss-parser'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:domain/:category?', @@ -15,7 +16,7 @@ export const route: Route = { async function handler(ctx) { const { domain = 'news', category } = ctx.req.param(); if (!isValidHost(domain)) { - throw new Error('Invalid domain'); + throw new InvalidParameterError('Invalid domain'); } const baseUrl = `https://${domain}.gamme.com.tw`; const feed = await parser.parseURL(`${baseUrl + (category ? `/category/${category}` : '')}/feed`); diff --git a/lib/routes/gamme/tag.ts b/lib/routes/gamme/tag.ts index 106faa8051afbd..b9dbb922e49648 100644 --- a/lib/routes/gamme/tag.ts +++ b/lib/routes/gamme/tag.ts @@ -4,6 +4,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:domain/tag/:tag', @@ -15,7 +16,7 @@ export const route: Route = { async function handler(ctx) { const { domain = 'news', tag } = ctx.req.param(); if (!isValidHost(domain)) { - throw new Error('Invalid domain'); + throw new InvalidParameterError('Invalid domain'); } const baseUrl = `https://${domain}.gamme.com.tw`; const pageUrl = `${baseUrl}/tag/${tag}`; diff --git a/lib/routes/gcores/category.ts b/lib/routes/gcores/category.ts index b02c4eb0b09a57..269a2a8d1a439d 100644 --- a/lib/routes/gcores/category.ts +++ b/lib/routes/gcores/category.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; @@ -60,7 +61,7 @@ async function handler(ctx) { list = list.get(); if (list.length > 0 && list.every((item) => item.url === undefined)) { - throw new Error('Article URL not found! Please submit an issue on GitHub.'); + throw new InvalidParameterError('Article URL not found! Please submit an issue on GitHub.'); } const out = await Promise.all( diff --git a/lib/routes/gcores/tag.ts b/lib/routes/gcores/tag.ts index c6490932efcaec..d32c63351e0a68 100644 --- a/lib/routes/gcores/tag.ts +++ b/lib/routes/gcores/tag.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; @@ -52,7 +53,7 @@ async function handler(ctx) { .get(); if (list.length > 0 && list.every((item) => item.url === undefined)) { - throw new Error('Article URL not found! Please submit an issue on GitHub.'); + throw new InvalidParameterError('Article URL not found! Please submit an issue on GitHub.'); } const out = await Promise.all( diff --git a/lib/routes/github/follower.ts b/lib/routes/github/follower.ts index 6a6dabb9a8160b..30363b8b29ad7b 100644 --- a/lib/routes/github/follower.ts +++ b/lib/routes/github/follower.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/user/followers/:user', @@ -27,7 +28,7 @@ export const route: Route = { async function handler(ctx) { if (!config.github || !config.github.access_token) { - throw new Error('GitHub follower RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('GitHub follower RSS is disabled due to the lack of relevant config'); } const user = ctx.req.param('user'); diff --git a/lib/routes/github/notifications.ts b/lib/routes/github/notifications.ts index e0594bd46ff566..9668c410e39dca 100644 --- a/lib/routes/github/notifications.ts +++ b/lib/routes/github/notifications.ts @@ -4,6 +4,7 @@ import { parseDate } from '@/utils/parse-date'; const apiUrl = 'https://api.github.com'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/notifications', @@ -36,7 +37,7 @@ export const route: Route = { async function handler(ctx) { if (!config.github || !config.github.access_token) { - throw new Error('GitHub trending RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('GitHub trending RSS is disabled due to the lack of relevant config'); } const headers = { Accept: 'application/vnd.github.v3+json', diff --git a/lib/routes/github/star.ts b/lib/routes/github/star.ts index 06e2b1b2bac557..d7597ee2336605 100644 --- a/lib/routes/github/star.ts +++ b/lib/routes/github/star.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/stars/:user/:repo', @@ -27,7 +28,7 @@ export const route: Route = { async function handler(ctx) { if (!config.github || !config.github.access_token) { - throw new Error('GitHub star RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('GitHub star RSS is disabled due to the lack of relevant config'); } const user = ctx.req.param('user'); const repo = ctx.req.param('repo'); diff --git a/lib/routes/github/trending.ts b/lib/routes/github/trending.ts index 4ec4313cb998dc..a7c31e9e93668c 100644 --- a/lib/routes/github/trending.ts +++ b/lib/routes/github/trending.ts @@ -7,6 +7,7 @@ import got from '@/utils/got'; import { art } from '@/utils/render'; import { load } from 'cheerio'; import path from 'node:path'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/trending/:since/:language/:spoken_language?', @@ -44,7 +45,7 @@ export const route: Route = { async function handler(ctx) { if (!config.github || !config.github.access_token) { - throw new Error('GitHub trending RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('GitHub trending RSS is disabled due to the lack of relevant config'); } const since = ctx.req.param('since'); const language = ctx.req.param('language') === 'any' ? '' : ctx.req.param('language'); diff --git a/lib/routes/google/fonts.ts b/lib/routes/google/fonts.ts index 4b32499540c279..623b5271df85e7 100644 --- a/lib/routes/google/fonts.ts +++ b/lib/routes/google/fonts.ts @@ -7,6 +7,7 @@ import { config } from '@/config'; import { art } from '@/utils/render'; import path from 'node:path'; import { parseDate } from '@/utils/parse-date'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const titleMap = { date: 'Newest', @@ -52,7 +53,7 @@ async function handler(ctx) { const API_KEY = config.google.fontsApiKey; if (!API_KEY) { - throw new Error('Google Fonts API key is required.'); + throw new ConfigNotFoundError('Google Fonts API key is required.'); } const googleFontsAPI = `https://www.googleapis.com/webfonts/v1/webfonts?sort=${sort}&key=${API_KEY}`; diff --git a/lib/routes/gov/shenzhen/xxgk/zfxxgj.ts b/lib/routes/gov/shenzhen/xxgk/zfxxgj.ts index ed648795221e50..0feb049a894efa 100644 --- a/lib/routes/gov/shenzhen/xxgk/zfxxgj.ts +++ b/lib/routes/gov/shenzhen/xxgk/zfxxgj.ts @@ -4,6 +4,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const rootUrl = 'http://www.sz.gov.cn/cn/xxgk/zfxxgj/'; const config = { @@ -49,7 +50,7 @@ export const route: Route = { async function handler(ctx) { const cfg = config[ctx.req.param('caty')]; if (!cfg) { - throw new Error('Bad category. See docs'); + throw new InvalidParameterError('Bad category. See docs'); } const currentUrl = new URL(cfg.link, rootUrl).href; diff --git a/lib/routes/gov/shenzhen/zjj/index.ts b/lib/routes/gov/shenzhen/zjj/index.ts index 0674eef013580a..8893e007e4c7f8 100644 --- a/lib/routes/gov/shenzhen/zjj/index.ts +++ b/lib/routes/gov/shenzhen/zjj/index.ts @@ -3,6 +3,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const config = { tzgg: { @@ -41,7 +42,7 @@ async function handler(ctx) { const baseUrl = 'http://zjj.sz.gov.cn/xxgk/'; const cfg = config[ctx.req.param('caty')]; if (!cfg) { - throw new Error('Bad category. See docs'); + throw new InvalidParameterError('Bad category. See docs'); } const currentUrl = new URL(cfg.link, baseUrl).href; diff --git a/lib/routes/gov/suzhou/news.ts b/lib/routes/gov/suzhou/news.ts index b69de4777dc821..074e3bf25f2184 100644 --- a/lib/routes/gov/suzhou/news.ts +++ b/lib/routes/gov/suzhou/news.ts @@ -4,6 +4,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/suzhou/news/:uid', @@ -119,7 +120,7 @@ async function handler(ctx) { title = '苏州市政府 - 民生资讯'; break; default: - throw new Error('pattern not matched'); + throw new InvalidParameterError('pattern not matched'); } if (apiUrl) { const response = await got(apiUrl); diff --git a/lib/routes/gumroad/index.ts b/lib/routes/gumroad/index.ts index 321cba95b3b1cf..052f15bb1eca19 100644 --- a/lib/routes/gumroad/index.ts +++ b/lib/routes/gumroad/index.ts @@ -7,6 +7,7 @@ import { load } from 'cheerio'; import { art } from '@/utils/render'; import path from 'node:path'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:username/:products', @@ -31,7 +32,7 @@ async function handler(ctx) { const username = ctx.req.param('username'); const products = ctx.req.param('products'); if (!isValidHost(username)) { - throw new Error('Invalid username'); + throw new InvalidParameterError('Invalid username'); } const url = `https://${username}.gumroad.com/l/${products}`; diff --git a/lib/routes/guokr/channel.ts b/lib/routes/guokr/channel.ts index ee52d5de173170..1d7c962bbf2749 100644 --- a/lib/routes/guokr/channel.ts +++ b/lib/routes/guokr/channel.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import { parseList, parseItem } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const channelMap = { calendar: 'pac', @@ -42,7 +43,7 @@ async function handler(ctx) { const result = parseList(response.result); if (result.length === 0) { - throw new Error('Unknown channel'); + throw new InvalidParameterError('Unknown channel'); } const channelName = result[0].channels[0].name; diff --git a/lib/routes/huanqiu/index.ts b/lib/routes/huanqiu/index.ts index a59014961b048b..2b70b76f7f436b 100644 --- a/lib/routes/huanqiu/index.ts +++ b/lib/routes/huanqiu/index.ts @@ -4,6 +4,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; function getKeysRecursive(dic, key, attr, array) { for (const v of Object.values(dic)) { @@ -46,7 +47,7 @@ export const route: Route = { async function handler(ctx) { const category = ctx.req.param('category') ?? 'china'; if (!isValidHost(category)) { - throw new Error('Invalid category'); + throw new InvalidParameterError('Invalid category'); } const host = `https://${category}.huanqiu.com`; diff --git a/lib/routes/instagram/private-api/index.ts b/lib/routes/instagram/private-api/index.ts index 92f1aaedad0354..b42a8beda7752c 100644 --- a/lib/routes/instagram/private-api/index.ts +++ b/lib/routes/instagram/private-api/index.ts @@ -4,6 +4,7 @@ import { ig, login } from './utils'; import logger from '@/utils/logger'; import { config } from '@/config'; import { renderItems } from '../common-utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; // loadContent pulls the desired user/tag/etc async function loadContent(category, nameOrId, tryGet) { @@ -93,7 +94,7 @@ async function handler(ctx) { // e.g. username for user feed const { category, key } = ctx.req.param(); if (!availableCategories.includes(category)) { - throw new Error('Such feed is not supported.'); + throw new InvalidParameterError('Such feed is not supported.'); } if (config.instagram && config.instagram.proxy) { diff --git a/lib/routes/instagram/private-api/utils.ts b/lib/routes/instagram/private-api/utils.ts index f6cdeac65810d6..db481f242174fe 100644 --- a/lib/routes/instagram/private-api/utils.ts +++ b/lib/routes/instagram/private-api/utils.ts @@ -1,12 +1,13 @@ import { IgApiClient } from 'instagram-private-api'; import logger from '@/utils/logger'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const ig = new IgApiClient(); async function login(ig, cache) { if (!config.instagram || !config.instagram.username || !config.instagram.password) { - throw new Error('Instagram RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('Instagram RSS is disabled due to the lack of relevant config'); } const LOGIN_CACHE_KEY = 'instagram:login'; const { username, password } = config.instagram; diff --git a/lib/routes/instagram/web-api/index.ts b/lib/routes/instagram/web-api/index.ts index f53da7ff0c2750..6acc1249b4cc38 100644 --- a/lib/routes/instagram/web-api/index.ts +++ b/lib/routes/instagram/web-api/index.ts @@ -4,6 +4,8 @@ import { CookieJar } from 'tough-cookie'; import { config } from '@/config'; import { renderItems } from '../common-utils'; import { baseUrl, COOKIE_URL, checkLogin, getUserInfo, getUserFeedItems, getTagsFeedItems, getLoggedOutTagsFeedItems, renderGuestItems } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/2/:category/:key', @@ -39,7 +41,7 @@ async function handler(ctx) { const { category, key } = ctx.req.param(); const { cookie } = config.instagram; if (!availableCategories.includes(category)) { - throw new Error('Such feed is not supported.'); + throw new InvalidParameterError('Such feed is not supported.'); } let cookieJar = await cache.get('instagram:cookieJar'); @@ -58,7 +60,7 @@ async function handler(ctx) { } if (!wwwClaimV2 && cookie && !(await checkLogin(cookieJar, cache))) { - throw new Error('Invalid cookie'); + throw new ConfigNotFoundError('Invalid cookie'); } let feedTitle, feedLink, feedDescription, feedLogo; diff --git a/lib/routes/instagram/web-api/utils.ts b/lib/routes/instagram/web-api/utils.ts index f2ddaa003badff..24a3a084579de9 100644 --- a/lib/routes/instagram/web-api/utils.ts +++ b/lib/routes/instagram/web-api/utils.ts @@ -6,6 +6,7 @@ import { parseDate } from '@/utils/parse-date'; import { config } from '@/config'; import { art } from '@/utils/render'; import path from 'node:path'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const baseUrl = 'https://www.instagram.com'; const COOKIE_URL = 'https://instagram.com'; @@ -59,7 +60,7 @@ const getUserInfo = async (username, cookieJar, cache) => { }, }); if (response.url.includes('/accounts/login/')) { - throw new Error('Invalid cookie'); + throw new ConfigNotFoundError('Invalid cookie'); } webProfileInfo = response.data.data.user; @@ -69,7 +70,7 @@ const getUserInfo = async (username, cookieJar, cache) => { await cache.set(`instagram:userInfo:${id}`, webProfileInfo); } catch (error) { if (error.message.includes("Cookie not in this host's domain")) { - throw new Error('Invalid cookie'); + throw new ConfigNotFoundError('Invalid cookie'); } throw error; } @@ -97,7 +98,7 @@ const getUserFeedItems = (id, username, cookieJar, cache) => }, }); if (response.url.includes('/accounts/login/')) { - throw new Error(`Invalid cookie. + throw new ConfigNotFoundError(`Invalid cookie. Please also check if your account is being blocked by Instagram.`); } diff --git a/lib/routes/iqiyi/album.ts b/lib/routes/iqiyi/album.ts index 460e25a2b4eee0..d2aed2de5be927 100644 --- a/lib/routes/iqiyi/album.ts +++ b/lib/routes/iqiyi/album.ts @@ -7,6 +7,7 @@ import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/album/:id', @@ -43,7 +44,7 @@ async function handler(ctx) { } = await got(`https://pcw-api.iqiyi.com/album/album/baseinfo/${album.videoAlbumInfo.albumId}`); if (Object.keys(album.cacheAlbumList).length === 0) { - throw new Error(`${baseInfo.name} is not available in this server region.`); + throw new InvalidParameterError(`${baseInfo.name} is not available in this server region.`); } let pos = 1; diff --git a/lib/routes/itch/devlog.ts b/lib/routes/itch/devlog.ts index 62c2dcb3c5ed48..7f2a931bc16617 100644 --- a/lib/routes/itch/devlog.ts +++ b/lib/routes/itch/devlog.ts @@ -10,6 +10,7 @@ import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/devlog/:user/:id', @@ -38,7 +39,7 @@ async function handler(ctx) { const user = ctx.req.param('user') ?? ''; const id = ctx.req.param('id') ?? ''; if (!isValidHost(user)) { - throw new Error('Invalid user'); + throw new InvalidParameterError('Invalid user'); } const rootUrl = `https://${user}.itch.io/${id}/devlog`; diff --git a/lib/routes/ithome/index.ts b/lib/routes/ithome/index.ts index e80ba7c3c96ecb..840e9ab1bcd440 100644 --- a/lib/routes/ithome/index.ts +++ b/lib/routes/ithome/index.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; @@ -59,7 +60,7 @@ export const route: Route = { async function handler(ctx) { const cfg = config[ctx.req.param('caty')]; if (!cfg) { - throw new Error('Bad category. See https://docs.rsshub.app/routes/new-media#it-zhi-jia'); + throw new InvalidParameterError('Bad category. See https://docs.rsshub.app/routes/new-media#it-zhi-jia'); } const current_url = get_url(ctx.req.param('caty')); diff --git a/lib/routes/ithome/ranking.ts b/lib/routes/ithome/ranking.ts index cfd4cbd77dc12c..b95630cdceea9c 100644 --- a/lib/routes/ithome/ranking.ts +++ b/lib/routes/ithome/ranking.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; @@ -49,7 +50,7 @@ async function handler(ctx) { const id = type2id[option]; if (!id) { - throw new Error('Bad type. See https://docs.rsshub.app/routes/new-media#it-zhi-jia'); + throw new InvalidParameterError('Bad type. See https://docs.rsshub.app/routes/new-media#it-zhi-jia'); } const list = $(`#${id} > li`) diff --git a/lib/routes/iwara/subscriptions.ts b/lib/routes/iwara/subscriptions.ts index 629d6fec4ac048..c7a94b687b0b29 100644 --- a/lib/routes/iwara/subscriptions.ts +++ b/lib/routes/iwara/subscriptions.ts @@ -9,6 +9,7 @@ import { art } from '@/utils/render'; import { parseDate } from '@/utils/parse-date'; import path from 'node:path'; import MarkdownIt from 'markdown-it'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const md = MarkdownIt({ html: true, }); @@ -51,7 +52,7 @@ export const route: Route = { async function handler() { if (!config.iwara || !config.iwara.username || !config.iwara.password) { - throw new Error('Iwara subscription RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('Iwara subscription RSS is disabled due to the lack of relevant config'); } const rootUrl = `https://www.iwara.tv`; diff --git a/lib/routes/javbus/index.ts b/lib/routes/javbus/index.ts index 9567e6fe739dd1..37ed73efa21bd7 100644 --- a/lib/routes/javbus/index.ts +++ b/lib/routes/javbus/index.ts @@ -10,6 +10,7 @@ import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const toSize = (raw) => { const matches = raw.match(/(\d+(\.\d+)?)(\w+)/); @@ -41,7 +42,7 @@ async function handler(ctx) { const westernUrl = `https://www.${westernDomain}`; if (!config.feature.allow_user_supply_unsafe_domain && (!allowDomain.has(new URL(`https://${domain}/`).hostname) || !allowDomain.has(new URL(`https://${westernDomain}/`).hostname))) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } const currentUrl = `${isWestern ? westernUrl : rootUrl}${getSubPath(ctx) diff --git a/lib/routes/javdb/utils.ts b/lib/routes/javdb/utils.ts index f13b4484557cca..0e9016f760c339 100644 --- a/lib/routes/javdb/utils.ts +++ b/lib/routes/javdb/utils.ts @@ -3,13 +3,14 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const allowDomain = new Set(['javdb.com', 'javdb36.com', 'javdb007.com', 'javdb521.com']); const ProcessItems = async (ctx, currentUrl, title) => { const domain = ctx.req.query('domain') ?? 'javdb.com'; const url = new URL(currentUrl, `https://${domain}`); if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.has(url.hostname)) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } const rootUrl = `https://${domain}`; diff --git a/lib/routes/kyodonews/index.ts b/lib/routes/kyodonews/index.ts index 5e71ddee8b4b38..a3f7cd84cc9da8 100644 --- a/lib/routes/kyodonews/index.ts +++ b/lib/routes/kyodonews/index.ts @@ -9,6 +9,8 @@ import timezone from '@/utils/timezone'; import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const resolveRelativeLink = (link, baseUrl) => (link.startsWith('http') ? link : `${baseUrl}${link}`); @@ -37,7 +39,7 @@ async function handler(ctx) { // raise error for invalid languages if (!['china', 'tchina'].includes(language)) { - throw new Error('Invalid language'); + throw new ConfigNotFoundError('Invalid language'); } const rootUrl = `https://${language}.kyodonews.net`; @@ -47,7 +49,7 @@ async function handler(ctx) { try { response = await got(currentUrl); } catch (error) { - throw error.response && error.response.statusCode === 404 ? new Error('Invalid keyword') : error; + throw error.response && error.response.statusCode === 404 ? new InvalidParameterError('Invalid keyword') : error; } const $ = load(response.data, { xmlMode: keyword === 'rss' }); diff --git a/lib/routes/lemmy/index.ts b/lib/routes/lemmy/index.ts index 256991c92b8e7e..a8bc1fd4b642c9 100644 --- a/lib/routes/lemmy/index.ts +++ b/lib/routes/lemmy/index.ts @@ -5,6 +5,8 @@ import { parseDate } from '@/utils/parse-date'; import MarkdownIt from 'markdown-it'; const md = MarkdownIt({ html: true }); import { config } from '@/config'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/:community/:sort?', @@ -34,12 +36,12 @@ async function handler(ctx) { const community = ctx.req.param('community'); const communitySlices = community.split('@'); if (communitySlices.length !== 2) { - throw new Error(`Invalid community: ${community}`); + throw new InvalidParameterError(`Invalid community: ${community}`); } const instance = community.split('@')[1]; const allowedDomain = ['lemmy.world', 'lemm.ee', 'lemmy.ml', 'sh.itjust.works', 'feddit.de', 'hexbear.net', 'beehaw.org', 'lemmynsfw.com', 'lemmy.ca', 'programming.dev']; if (!config.feature.allow_user_supply_unsafe_domain && !allowedDomain.includes(new URL(`http://${instance}/`).hostname)) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } const communityUrl = `https://${instance}/api/v3/community?name=${community}`; diff --git a/lib/routes/liveuamap/index.ts b/lib/routes/liveuamap/index.ts index 030bb2ec24906d..cfa1ea48bdcd7b 100644 --- a/lib/routes/liveuamap/index.ts +++ b/lib/routes/liveuamap/index.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import { load } from 'cheerio'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:region?', @@ -31,7 +32,7 @@ async function handler(ctx) { const region = ctx.req.param('region') ?? 'ukraine'; const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 50; if (!isValidHost(region)) { - throw new Error('Invalid region'); + throw new InvalidParameterError('Invalid region'); } const url = `https://${region}.liveuamap.com/`; diff --git a/lib/routes/lofter/user.ts b/lib/routes/lofter/user.ts index 7ccdfd3eece76b..f3531b7f582721 100644 --- a/lib/routes/lofter/user.ts +++ b/lib/routes/lofter/user.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; @@ -25,7 +26,7 @@ async function handler(ctx) { const name = ctx.req.param('name') ?? 'i'; const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : '50'; if (!isValidHost(name)) { - throw new Error('Invalid name'); + throw new InvalidParameterError('Invalid name'); } const rootUrl = `${name}.lofter.com`; diff --git a/lib/routes/m4/index.ts b/lib/routes/m4/index.ts index fe684e5fad1da7..32c3d62a7e3028 100644 --- a/lib/routes/m4/index.ts +++ b/lib/routes/m4/index.ts @@ -10,6 +10,7 @@ import timezone from '@/utils/timezone'; import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:id?/:category{.+}?', @@ -21,7 +22,7 @@ export const route: Route = { async function handler(ctx) { const { id = 'news', category = 'china' } = ctx.req.param(); if (!isValidHost(id)) { - throw new Error('Invalid id'); + throw new InvalidParameterError('Invalid id'); } const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30; diff --git a/lib/routes/mail/imap.ts b/lib/routes/mail/imap.ts index fa9be620c1bc4f..4f476198bb8875 100644 --- a/lib/routes/mail/imap.ts +++ b/lib/routes/mail/imap.ts @@ -5,6 +5,7 @@ import { config } from '@/config'; import { simpleParser } from 'mailparser'; import logger from '@/utils/logger'; import { parseDate } from '@/utils/parse-date'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/imap/:email/:folder{.+}?', @@ -23,7 +24,7 @@ async function handler(ctx) { }; if (!mailConfig.username || !mailConfig.password || !mailConfig.host || !mailConfig.port) { - throw new Error('Email Inbox RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('Email Inbox RSS is disabled due to the lack of relevant config'); } const client = new ImapFlow({ diff --git a/lib/routes/manhuagui/subscribe.ts b/lib/routes/manhuagui/subscribe.ts index e0679b29dbef53..34455022d0d366 100644 --- a/lib/routes/manhuagui/subscribe.ts +++ b/lib/routes/manhuagui/subscribe.ts @@ -8,6 +8,7 @@ import { load } from 'cheerio'; import { art } from '@/utils/render'; import path from 'node:path'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const web_url = 'https://www.manhuagui.com/user/book/shelf/1'; export const route: Route = { @@ -45,7 +46,7 @@ export const route: Route = { async function handler() { if (!config.manhuagui || !config.manhuagui.cookie) { - throw new Error('manhuagui RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('manhuagui RSS is disabled due to the lack of relevant config'); } const cookie = config.manhuagui.cookie; const response = await got({ diff --git a/lib/routes/mastodon/account-id.ts b/lib/routes/mastodon/account-id.ts index bd553918875290..65a54cc2aa6197 100644 --- a/lib/routes/mastodon/account-id.ts +++ b/lib/routes/mastodon/account-id.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import utils from './utils'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/account_id/:site/:account_id/statuses/:only_media?', @@ -14,7 +15,7 @@ async function handler(ctx) { const account_id = ctx.req.param('account_id'); const only_media = ctx.req.param('only_media') ? 'true' : 'false'; if (!config.feature.allow_user_supply_unsafe_domain && !utils.allowSiteList.includes(site)) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } const { account_data, data } = await utils.getAccountStatuses(site, account_id, only_media); diff --git a/lib/routes/mastodon/timeline-local.ts b/lib/routes/mastodon/timeline-local.ts index 43773041e01f25..c88541cb58f0d8 100644 --- a/lib/routes/mastodon/timeline-local.ts +++ b/lib/routes/mastodon/timeline-local.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import utils from './utils'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/timeline/:site/:only_media?', @@ -26,7 +27,7 @@ async function handler(ctx) { const site = ctx.req.param('site'); const only_media = ctx.req.param('only_media') ? 'true' : 'false'; if (!config.feature.allow_user_supply_unsafe_domain && !utils.allowSiteList.includes(site)) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } const url = `http://${site}/api/v1/timelines/public?local=true&only_media=${only_media}`; diff --git a/lib/routes/mastodon/timeline-remote.ts b/lib/routes/mastodon/timeline-remote.ts index 6b17f620aaf026..bb85812af7c693 100644 --- a/lib/routes/mastodon/timeline-remote.ts +++ b/lib/routes/mastodon/timeline-remote.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import utils from './utils'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/remote/:site/:only_media?', @@ -26,7 +27,7 @@ async function handler(ctx) { const site = ctx.req.param('site'); const only_media = ctx.req.param('only_media') ? 'true' : 'false'; if (!config.feature.allow_user_supply_unsafe_domain && !utils.allowSiteList.includes(site)) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } const url = `http://${site}/api/v1/timelines/public?remote=true&only_media=${only_media}`; diff --git a/lib/routes/mastodon/utils.ts b/lib/routes/mastodon/utils.ts index f25cda95e517bb..40b4401ade10f5 100644 --- a/lib/routes/mastodon/utils.ts +++ b/lib/routes/mastodon/utils.ts @@ -2,6 +2,7 @@ import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const allowSiteList = ['mastodon.social', 'pawoo.net', config.mastodon.apiHost].filter(Boolean); @@ -93,10 +94,10 @@ async function getAccountIdByAcct(acct) { const site = mastodonConfig.apiHost || acctHost; const acctDomain = mastodonConfig.acctDomain || acctHost; if (!(site && acctDomain)) { - throw new Error('Mastodon RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('Mastodon RSS is disabled due to the lack of relevant config'); } if (!config.feature.allow_user_supply_unsafe_domain && !allowSiteList.includes(site)) { - throw new Error(`RSS for this domain is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true' or 'MASTODON_API_HOST' is set.`); + throw new ConfigNotFoundError(`RSS for this domain is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true' or 'MASTODON_API_HOST' is set.`); } const search_url = `https://${site}/api/v2/search`; diff --git a/lib/routes/medium/following.ts b/lib/routes/medium/following.ts index bcaded1aa1e673..1f34e24533fc0b 100644 --- a/lib/routes/medium/following.ts +++ b/lib/routes/medium/following.ts @@ -3,6 +3,7 @@ import { config } from '@/config'; import parseArticle from './parse-article.js'; import { getFollowingFeedQuery } from './graphql.js'; +import ConfigNotFoundError from '@/errors/types/config-not-found.js'; export const route: Route = { path: '/following/:user', @@ -35,7 +36,7 @@ async function handler(ctx) { const cookie = config.medium.cookies[user]; if (cookie === undefined) { - throw new Error(`缺少 Medium 用户 ${user} 登录后的 Cookie 值`); + throw new ConfigNotFoundError(`缺少 Medium 用户 ${user} 登录后的 Cookie 值`); } const posts = await getFollowingFeedQuery(user, cookie); @@ -43,7 +44,7 @@ async function handler(ctx) { if (!posts) { // login failed - throw new Error(`Medium 用户 ${user} 的 Cookie 无效或已过期`); + throw new ConfigNotFoundError(`Medium 用户 ${user} 的 Cookie 无效或已过期`); } const urls = posts.items.map((data) => data.post.mediumUrl); diff --git a/lib/routes/medium/for-you.ts b/lib/routes/medium/for-you.ts index 320100c14b417a..48b7a204f96754 100644 --- a/lib/routes/medium/for-you.ts +++ b/lib/routes/medium/for-you.ts @@ -3,6 +3,7 @@ import { config } from '@/config'; import parseArticle from './parse-article.js'; import { getWebInlineRecommendedFeedQuery } from './graphql.js'; +import ConfigNotFoundError from '@/errors/types/config-not-found.js'; export const route: Route = { path: '/for-you/:user', @@ -35,7 +36,7 @@ async function handler(ctx) { const cookie = config.medium.cookies[user]; if (cookie === undefined) { - throw new Error(`缺少 Medium 用户 ${user} 登录后的 Cookie 值`); + throw new ConfigNotFoundError(`缺少 Medium 用户 ${user} 登录后的 Cookie 值`); } const posts = await getWebInlineRecommendedFeedQuery(user, cookie); @@ -43,7 +44,7 @@ async function handler(ctx) { if (!posts) { // login failed - throw new Error(`Medium 用户 ${user} 的 Cookie 无效或已过期`); + throw new ConfigNotFoundError(`Medium 用户 ${user} 的 Cookie 无效或已过期`); } const urls = posts.items.map((data) => data.post.mediumUrl); diff --git a/lib/routes/medium/list.ts b/lib/routes/medium/list.ts index 4b447baafc8057..d628e232cf3f9f 100644 --- a/lib/routes/medium/list.ts +++ b/lib/routes/medium/list.ts @@ -3,6 +3,8 @@ import { config } from '@/config'; import parseArticle from './parse-article.js'; import { getUserCatalogMainContentQuery } from './graphql.js'; +import ConfigNotFoundError from '@/errors/types/config-not-found.js'; +import InvalidParameterError from '@/errors/types/invalid-parameter.js'; export const route: Route = { path: '/list/:user/:catalogId', @@ -37,10 +39,10 @@ async function handler(ctx) { ctx.set('json', catalog); if (catalog && catalog.__typename === 'Forbidden') { - throw new Error(`无权访问 id 为 ${catalogId} 的 List(可能是未设置 Cookie 或 Cookie 已过期)`); + throw new ConfigNotFoundError(`无权访问 id 为 ${catalogId} 的 List(可能是未设置 Cookie 或 Cookie 已过期)`); } if (!catalog || !catalog.itemsConnection) { - throw new Error(`id 为 ${catalogId} 的 List 不存在`); + throw new InvalidParameterError(`id 为 ${catalogId} 的 List 不存在`); } const name = catalog.name; diff --git a/lib/routes/medium/tag.ts b/lib/routes/medium/tag.ts index 07ac433b5cb891..15ef2e54d00ca6 100644 --- a/lib/routes/medium/tag.ts +++ b/lib/routes/medium/tag.ts @@ -3,6 +3,7 @@ import { config } from '@/config'; import parseArticle from './parse-article.js'; import { getWebInlineTopicFeedQuery } from './graphql.js'; +import ConfigNotFoundError from '@/errors/types/config-not-found.js'; export const route: Route = { path: '/tag/:user/:tag', @@ -38,7 +39,7 @@ async function handler(ctx) { const cookie = config.medium.cookies[user]; if (cookie === undefined) { - throw new Error(`缺少 Medium 用户 ${user} 登录后的 Cookie 值`); + throw new ConfigNotFoundError(`缺少 Medium 用户 ${user} 登录后的 Cookie 值`); } const posts = await getWebInlineTopicFeedQuery(user, tag, cookie); @@ -46,7 +47,7 @@ async function handler(ctx) { if (!posts) { // login failed - throw new Error(`Medium 用户 ${user} 的 Cookie 无效或已过期`); + throw new ConfigNotFoundError(`Medium 用户 ${user} 的 Cookie 无效或已过期`); } const urls = posts.items.map((data) => data.post.mediumUrl); diff --git a/lib/routes/mihoyo/bbs/cache.ts b/lib/routes/mihoyo/bbs/cache.ts index 9e56eafa7401ff..7c986a673c344c 100644 --- a/lib/routes/mihoyo/bbs/cache.ts +++ b/lib/routes/mihoyo/bbs/cache.ts @@ -1,10 +1,11 @@ import cache from '@/utils/cache'; import got from '@/utils/got'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const getUserFullInfo = (ctx, uid) => { if (!uid && !config.mihoyo.cookie) { - throw new Error('GetUserFullInfo is not available due to the absense of [Miyoushe Cookie]. Check relevant config tutorial'); + throw new ConfigNotFoundError('GetUserFullInfo is not available due to the absense of [Miyoushe Cookie]. Check relevant config tutorial'); } uid ||= ''; const key = 'mihoyo:user-full-info-uid-' + uid; diff --git a/lib/routes/mihoyo/bbs/timeline.ts b/lib/routes/mihoyo/bbs/timeline.ts index 04795c48070370..af60ba5f8a70ac 100644 --- a/lib/routes/mihoyo/bbs/timeline.ts +++ b/lib/routes/mihoyo/bbs/timeline.ts @@ -3,6 +3,7 @@ import got from '@/utils/got'; import cache from './cache'; import { config } from '@/config'; import { post2item } from './utils'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/bbs/timeline', @@ -37,7 +38,7 @@ export const route: Route = { async function handler(ctx) { if (!config.mihoyo.cookie) { - throw new Error('Miyoushe Timeline is not available due to the absense of [Miyoushe Cookie]. Check relevant config tutorial'); + throw new ConfigNotFoundError('Miyoushe Timeline is not available due to the absense of [Miyoushe Cookie]. Check relevant config tutorial'); } const page_size = ctx.req.query('limit') || '20'; diff --git a/lib/routes/miniflux/entry.ts b/lib/routes/miniflux/entry.ts index ed88b660ef0397..3867c8136ae758 100644 --- a/lib/routes/miniflux/entry.ts +++ b/lib/routes/miniflux/entry.ts @@ -1,6 +1,7 @@ import { Route, Data } from '@/types'; import got from '@/utils/got'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/entry/:feeds/:parameters?', @@ -63,7 +64,7 @@ async function handler(ctx) { const token = config.miniflux.token; if (!token) { - throw new Error('This RSS feed is disabled due to its incorrect configuration: the token is missing.'); + throw new ConfigNotFoundError('This RSS feed is disabled due to its incorrect configuration: the token is missing.'); } // In this function, var`mark`, `link`, and `limit`, `addFeedName` diff --git a/lib/routes/miniflux/subscription.ts b/lib/routes/miniflux/subscription.ts index 0aa91c42a4faeb..fa89d50fb89866 100644 --- a/lib/routes/miniflux/subscription.ts +++ b/lib/routes/miniflux/subscription.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/subscription/:parameters?', @@ -43,7 +44,7 @@ async function handler(ctx) { const token = config.miniflux.token; if (!token) { - throw new Error('This RSS feed is disabled due to its incorrect configuration: the token is missing.'); + throw new ConfigNotFoundError('This RSS feed is disabled due to its incorrect configuration: the token is missing.'); } function set(item) { diff --git a/lib/routes/mirror/index.ts b/lib/routes/mirror/index.ts index 8dc33bc21fa827..9025647795a577 100644 --- a/lib/routes/mirror/index.ts +++ b/lib/routes/mirror/index.ts @@ -7,6 +7,7 @@ const md = MarkdownIt({ linkify: true, }); import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:id', @@ -29,7 +30,7 @@ export const route: Route = { async function handler(ctx) { const id = ctx.req.param('id'); if (!id.endsWith('.eth') && !isValidHost(id)) { - throw new Error('Invalid id'); + throw new InvalidParameterError('Invalid id'); } const rootUrl = 'https://mirror.xyz'; const currentUrl = id.endsWith('.eth') ? `${rootUrl}/${id}` : `https://${id}.mirror.xyz`; diff --git a/lib/routes/misskey/featured-notes.ts b/lib/routes/misskey/featured-notes.ts index 5fa9d8c11fc3ca..2891809315cb68 100644 --- a/lib/routes/misskey/featured-notes.ts +++ b/lib/routes/misskey/featured-notes.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import utils from './utils'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/notes/featured/:site', @@ -24,7 +25,7 @@ export const route: Route = { async function handler(ctx) { const site = ctx.req.param('site'); if (!config.feature.allow_user_supply_unsafe_domain && !utils.allowSiteList.includes(site)) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } // docs on: https://misskey-hub.net/docs/api/endpoints/notes/featured.html diff --git a/lib/routes/mixcloud/index.ts b/lib/routes/mixcloud/index.ts index 5c8ef282decbb5..d907ec4b98d186 100644 --- a/lib/routes/mixcloud/index.ts +++ b/lib/routes/mixcloud/index.ts @@ -3,6 +3,7 @@ import got from '@/utils/got'; import CryptoJS from 'crypto-js'; import { parseDate } from '@/utils/parse-date'; import { queries } from './queries'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:username/:type?', @@ -37,7 +38,7 @@ async function handler(ctx) { const type = ctx.req.param('type') ?? 'uploads'; if (!['stream', 'uploads', 'favorites', 'listens'].includes(type)) { - throw new Error(`Invalid type: ${type}`); + throw new InvalidParameterError(`Invalid type: ${type}`); } const username = ctx.req.param('username'); diff --git a/lib/routes/myfigurecollection/activity.ts b/lib/routes/myfigurecollection/activity.ts index 30d45321f7de5e..8bd33065f062df 100644 --- a/lib/routes/myfigurecollection/activity.ts +++ b/lib/routes/myfigurecollection/activity.ts @@ -9,6 +9,7 @@ import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/activity/:category?/:language?/:latestAdditions?/:latestEdits?/:latestAlerts?/:latestPictures?', @@ -75,7 +76,7 @@ async function handler(ctx) { const latestPictures = ctx.req.param('latestPictures') ?? '1'; if (language && !isValidHost(language)) { - throw new Error('Invalid language'); + throw new InvalidParameterError('Invalid language'); } const rootUrl = `https://${language === 'en' || language === '' ? '' : `${language}.`}myfigurecollection.net`; diff --git a/lib/routes/myfigurecollection/index.ts b/lib/routes/myfigurecollection/index.ts index 034a2052834e37..d0201640397719 100644 --- a/lib/routes/myfigurecollection/index.ts +++ b/lib/routes/myfigurecollection/index.ts @@ -8,6 +8,7 @@ import { load } from 'cheerio'; import { art } from '@/utils/render'; import path from 'node:path'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const shortcuts = { potd: 'picture/browse/potd/', @@ -46,7 +47,7 @@ async function handler(ctx) { const language = ctx.req.param('language') ?? ''; const category = ctx.req.param('category') ?? 'figure'; if (language && !isValidHost(language)) { - throw new Error('Invalid language'); + throw new InvalidParameterError('Invalid language'); } const rootUrl = `https://${language === 'en' || language === '' ? '' : `${language}.`}myfigurecollection.net`; diff --git a/lib/routes/newrank/douyin.ts b/lib/routes/newrank/douyin.ts index 4a77715a6acf43..ed2f38a65af7b5 100644 --- a/lib/routes/newrank/douyin.ts +++ b/lib/routes/newrank/douyin.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import utils from './utils'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/douyin/:dyid', @@ -31,7 +32,7 @@ export const route: Route = { async function handler(ctx) { if (!config.newrank || !config.newrank.cookie) { - throw new Error('newrank RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('newrank RSS is disabled due to the lack of relevant config'); } const uid = ctx.req.param('dyid'); const nonce = utils.random_nonce(9); diff --git a/lib/routes/newrank/wechat.ts b/lib/routes/newrank/wechat.ts index 592d83a06c327a..dd7db0c947f52d 100644 --- a/lib/routes/newrank/wechat.ts +++ b/lib/routes/newrank/wechat.ts @@ -4,6 +4,7 @@ import { finishArticleItem } from '@/utils/wechat-mp'; import { load } from 'cheerio'; import utils from './utils'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/wechat/:wxid', @@ -30,7 +31,7 @@ export const route: Route = { async function handler(ctx) { if (!config.newrank || !config.newrank.cookie) { - throw new Error('newrank RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('newrank RSS is disabled due to the lack of relevant config'); } const uid = ctx.req.param('wxid'); const nonce = utils.random_nonce(9); diff --git a/lib/routes/nhentai/other.ts b/lib/routes/nhentai/other.ts index 191a140c3cc0c8..d7fe5273b3fe75 100644 --- a/lib/routes/nhentai/other.ts +++ b/lib/routes/nhentai/other.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import cache from '@/utils/cache'; import { getSimple, getDetails, getTorrents } from './util'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const supportedKeys = new Set(['parody', 'character', 'tag', 'artist', 'group', 'language', 'category']); @@ -36,7 +37,7 @@ async function handler(ctx) { const { key, keyword, mode } = ctx.req.param(); if (!supportedKeys.has(key)) { - throw new Error('Unsupported key'); + throw new InvalidParameterError('Unsupported key'); } const url = `https://nhentai.net/${key}/${keyword.toLowerCase().replace(' ', '-')}/`; diff --git a/lib/routes/nhentai/util.ts b/lib/routes/nhentai/util.ts index bd1b3f0e7c5031..9783450b1010d7 100644 --- a/lib/routes/nhentai/util.ts +++ b/lib/routes/nhentai/util.ts @@ -7,6 +7,7 @@ import { config } from '@/config'; import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const baseUrl = 'https://nhentai.net'; @@ -88,11 +89,11 @@ const getDetails = (cache, simples, limit) => Promise.all(simples.slice(0, limit const getTorrents = async (cache, simples, limit) => { if (!config.nhentai || !config.nhentai.username || !config.nhentai.password) { - throw new Error('nhentai RSS with torrents is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('nhentai RSS with torrents is disabled due to the lack of relevant config'); } const cookie = await getCookie(config.nhentai.username, config.nhentai.password, cache); if (!cookie) { - throw new Error('Invalid username (or email) or password for nhentai torrent download'); + throw new ConfigNotFoundError('Invalid username (or email) or password for nhentai torrent download'); } return getTorrentWithCookie(cache, simples, cookie, limit); }; diff --git a/lib/routes/nintendo/eshop-cn.ts b/lib/routes/nintendo/eshop-cn.ts index f8b3a8eb1f0229..1ad97ada38addd 100644 --- a/lib/routes/nintendo/eshop-cn.ts +++ b/lib/routes/nintendo/eshop-cn.ts @@ -4,6 +4,7 @@ import got from '@/utils/got'; import util from './utils'; const software_url = 'https://www.nintendoswitch.com.cn/software/'; import { parseDate } from '@/utils/parse-date'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/eshop/cn', @@ -38,7 +39,7 @@ async function handler() { title: "附带导航!一做就上手 第一次的游戏程序设计" */ if (!result.recentSoftwareList) { - throw new Error('软件信息不存在,请报告这个问题'); + throw new InvalidParameterError('软件信息不存在,请报告这个问题'); } let data = result.recentSoftwareList.map((item) => ({ diff --git a/lib/routes/nintendo/news-china.ts b/lib/routes/nintendo/news-china.ts index a6b3be7ac80680..ba30267ffe60c7 100644 --- a/lib/routes/nintendo/news-china.ts +++ b/lib/routes/nintendo/news-china.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import util from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const news_url = 'https://www.nintendoswitch.com.cn'; export const route: Route = { @@ -41,7 +42,7 @@ async function handler() { title: "8款新品开启预约:超级马力欧系列官方周边" */ if (!result.newsList) { - throw new Error('新闻信息不存在,请报告这个问题'); + throw new InvalidParameterError('新闻信息不存在,请报告这个问题'); } let data = result.newsList.map((item) => ({ diff --git a/lib/routes/njust/cwc.ts b/lib/routes/njust/cwc.ts index 4f37a672a34f5a..6d211c8bbf52cb 100644 --- a/lib/routes/njust/cwc.ts +++ b/lib/routes/njust/cwc.ts @@ -3,6 +3,7 @@ import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; import { getContent } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const map = new Map([ ['tzgg', { title: '南京理工大学财务处 -- 通知公告', id: '/12432' }], @@ -36,7 +37,7 @@ async function handler(ctx) { const type = ctx.req.param('type') ?? 'tzgg'; const info = map.get(type); if (!info) { - throw new Error('invalid type'); + throw new InvalidParameterError('invalid type'); } const id = info.id; const siteUrl = host + id + '/list.htm'; diff --git a/lib/routes/njust/dgxg.ts b/lib/routes/njust/dgxg.ts index 1be3db32b17a80..c568ba9877cf2e 100644 --- a/lib/routes/njust/dgxg.ts +++ b/lib/routes/njust/dgxg.ts @@ -3,6 +3,7 @@ import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; import { getContent } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const map = new Map([ ['gstz', { title: '南京理工大学电光学院研学网 -- 公示通知', id: '/6509' }], @@ -37,7 +38,7 @@ async function handler(ctx) { const type = ctx.req.param('type') ?? 'gstz'; const info = map.get(type); if (!info) { - throw new Error('invalid type'); + throw new InvalidParameterError('invalid type'); } const id = info.id; const siteUrl = host + id + '/list.htm'; diff --git a/lib/routes/njust/eoe.ts b/lib/routes/njust/eoe.ts index 96562cdc05cb3e..231f92263e84ac 100644 --- a/lib/routes/njust/eoe.ts +++ b/lib/routes/njust/eoe.ts @@ -3,6 +3,7 @@ import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; import { getContent } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const map = new Map([ ['tzgg', { title: '南京理工大学电子工程与光电技术学院 -- 通知公告', id: '/1920' }], @@ -36,7 +37,7 @@ async function handler(ctx) { const type = ctx.req.param('type') ?? 'tzgg'; const info = map.get(type); if (!info) { - throw new Error('invalid type'); + throw new InvalidParameterError('invalid type'); } const id = info.id; const siteUrl = host + id + '/list.htm'; diff --git a/lib/routes/njust/jwc.ts b/lib/routes/njust/jwc.ts index 44b9b366301b72..bbe175fe0631b8 100644 --- a/lib/routes/njust/jwc.ts +++ b/lib/routes/njust/jwc.ts @@ -3,6 +3,7 @@ import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; import { getContent } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const map = new Map([ ['jstz', { title: '南京理工大学教务处 -- 教师通知', id: '/1216' }], @@ -38,7 +39,7 @@ async function handler(ctx) { const type = ctx.req.param('type') ?? 'xstz'; const info = map.get(type); if (!info) { - throw new Error('invalid type'); + throw new InvalidParameterError('invalid type'); } const id = info.id; const siteUrl = host + id + '/list.htm'; diff --git a/lib/routes/notion/database.ts b/lib/routes/notion/database.ts index 833f4b185514a0..cd83c0753aa3e2 100644 --- a/lib/routes/notion/database.ts +++ b/lib/routes/notion/database.ts @@ -8,6 +8,8 @@ import got from '@/utils/got'; import { NotionToMarkdown } from 'notion-to-md'; import { load } from 'cheerio'; import MarkdownIt from 'markdown-it'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const md = MarkdownIt({ html: true, linkify: true, @@ -55,7 +57,7 @@ export const route: Route = { async function handler(ctx) { if (!config.notion.key) { - throw new Error('Notion RSS is disabled due to the lack of NOTION_TOKEN(relevant config)'); + throw new ConfigNotFoundError('Notion RSS is disabled due to the lack of NOTION_TOKEN(relevant config)'); } const databaseId = ctx.req.param('databaseId'); @@ -161,9 +163,9 @@ async function handler(ctx) { if (isNotionClientError(error)) { if (error.statusCode === APIErrorCode.ObjectNotFound) { - throw new Error('The database is not exist'); + throw new InvalidParameterError('The database is not exist'); } else if (error.statusCode === APIErrorCode.Unauthorized) { - throw new Error('Please check the config of NOTION_TOKEN'); + throw new ConfigNotFoundError('Please check the config of NOTION_TOKEN'); } else { ctx.throw(error.statusCode, 'Notion API Error'); } diff --git a/lib/routes/nua/dc.ts b/lib/routes/nua/dc.ts index 381a4276c3144d..37614bbc9a5536 100644 --- a/lib/routes/nua/dc.ts +++ b/lib/routes/nua/dc.ts @@ -1,5 +1,6 @@ import { Route } from '@/types'; import util from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/dc/:type', @@ -80,7 +81,7 @@ async function handler(ctx) { webPageName = 'ul.screen_4 .big_title'; break; default: - throw new Error(`暂不支持对${type}的订阅`); + throw new InvalidParameterError(`暂不支持对${type}的订阅`); } const items = await util.ProcessList(baseUrl, baseUrl, listName, listDate, webPageName); diff --git a/lib/routes/nua/lib.ts b/lib/routes/nua/lib.ts index 2eb74a1335b793..8c6254ff9df9e4 100644 --- a/lib/routes/nua/lib.ts +++ b/lib/routes/nua/lib.ts @@ -1,5 +1,6 @@ import { Route } from '@/types'; import util from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const baseUrl = 'https://lib.nua.edu.cn'; export const route: Route = { @@ -49,7 +50,7 @@ async function handler(ctx) { webPageName = '.wp_column.column-4.selected'; break; default: - throw new Error(`暂不支持对${type}的订阅`); + throw new InvalidParameterError(`暂不支持对${type}的订阅`); } const newsUrl = `${baseUrl}/${type}/list.htm`; diff --git a/lib/routes/oceanengine/arithmetic-index.ts b/lib/routes/oceanengine/arithmetic-index.ts index 2f082f00af7197..b40e52ad7fecdd 100644 --- a/lib/routes/oceanengine/arithmetic-index.ts +++ b/lib/routes/oceanengine/arithmetic-index.ts @@ -11,6 +11,7 @@ import path from 'node:path'; import { config } from '@/config'; import puppeteer from '@/utils/puppeteer'; import { createDecipheriv } from 'node:crypto'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; // Parameters const CACHE_MAX_AGE = config.cache.contentExpire; @@ -95,10 +96,10 @@ async function handler(ctx) { const end_date = now.format('YYYYMMDD'); const keyword = ctx.req.param('keyword'); if (!keyword) { - throw new Error('Invalid keyword'); + throw new InvalidParameterError('Invalid keyword'); } if (ctx.req.param('channel') && !['douyin', 'toutiao'].includes(ctx.req.param('channel'))) { - throw new Error('Invalid channel。 Only support `douyin` or `toutiao`'); + throw new InvalidParameterError('Invalid channel。 Only support `douyin` or `toutiao`'); } const channel = ctx.req.param('channel') === 'toutiao' ? 'toutiao' : 'aweme'; // default channel is `douyin` diff --git a/lib/routes/people/index.ts b/lib/routes/people/index.ts index 67bb30fa5bd394..0970a07c011a7c 100644 --- a/lib/routes/people/index.ts +++ b/lib/routes/people/index.ts @@ -6,6 +6,7 @@ import iconv from 'iconv-lite'; import timezone from '@/utils/timezone'; import { parseDate } from '@/utils/parse-date'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:site?/:category{.+}?', @@ -22,7 +23,7 @@ async function handler(ctx) { const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30; if (!isValidHost(site)) { - throw new Error('Invalid site'); + throw new InvalidParameterError('Invalid site'); } const rootUrl = `http://${site}.people.com.cn`; const currentUrl = new URL(`GB/${category}`, rootUrl).href; diff --git a/lib/routes/pianyuan/utils.ts b/lib/routes/pianyuan/utils.ts index 7948eac5c33c65..de18c2fcd8f056 100644 --- a/lib/routes/pianyuan/utils.ts +++ b/lib/routes/pianyuan/utils.ts @@ -1,6 +1,7 @@ import { load } from 'cheerio'; import got from '@/utils/got'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const security_key = 'pianyuan-security_session_verify'; const PHPSESSID_key = 'pianyuan-PHPSESSID'; const loginauth_key = 'pianyuan-py_loginauth'; @@ -47,7 +48,7 @@ async function getCookie(cache) { let py_loginauth = await cache.get(loginauth_key); if (!py_loginauth) { if (!config.pianyuan || !config.pianyuan.cookie) { - throw new Error('pianyuan is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('pianyuan is disabled due to the lack of relevant config'); } py_loginauth = config.pianyuan.cookie; } @@ -77,7 +78,7 @@ async function request(link, cache) { } } if (response.data.includes('会员登录后才能访问')) { - throw new Error('pianyuan Cookie已失效'); + throw new ConfigNotFoundError('pianyuan Cookie已失效'); } return response; } diff --git a/lib/routes/pixiv/bookmarks.ts b/lib/routes/pixiv/bookmarks.ts index ea5fab4bae1618..10b009faab5730 100644 --- a/lib/routes/pixiv/bookmarks.ts +++ b/lib/routes/pixiv/bookmarks.ts @@ -6,6 +6,7 @@ import getUserDetail from './api/get-user-detail'; import { config } from '@/config'; import pixivUtils from './utils'; import { parseDate } from '@/utils/parse-date'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/user/bookmarks/:id', @@ -32,14 +33,14 @@ export const route: Route = { async function handler(ctx) { if (!config.pixiv || !config.pixiv.refreshToken) { - throw new Error('pixiv RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('pixiv RSS is disabled due to the lack of relevant config'); } const id = ctx.req.param('id'); const token = await getToken(cache.tryGet); if (!token) { - throw new Error('pixiv not login'); + throw new ConfigNotFoundError('pixiv not login'); } const [bookmarksResponse, userDetailResponse] = await Promise.all([getBookmarks(id, token), getUserDetail(id, token)]); diff --git a/lib/routes/pixiv/illustfollow.ts b/lib/routes/pixiv/illustfollow.ts index 569499898e086f..4fb4e6dc7a2855 100644 --- a/lib/routes/pixiv/illustfollow.ts +++ b/lib/routes/pixiv/illustfollow.ts @@ -5,6 +5,7 @@ import getIllustFollows from './api/get-illust-follows'; import { config } from '@/config'; import pixivUtils from './utils'; import { parseDate } from '@/utils/parse-date'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/user/illustfollows', @@ -40,12 +41,12 @@ export const route: Route = { async function handler() { if (!config.pixiv || !config.pixiv.refreshToken) { - throw new Error('pixiv RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('pixiv RSS is disabled due to the lack of relevant config'); } const token = await getToken(cache.tryGet); if (!token) { - throw new Error('pixiv not login'); + throw new ConfigNotFoundError('pixiv not login'); } const response = await getIllustFollows(token); diff --git a/lib/routes/pixiv/ranking.ts b/lib/routes/pixiv/ranking.ts index 3f3576941705e2..a1fd17eb8242ba 100644 --- a/lib/routes/pixiv/ranking.ts +++ b/lib/routes/pixiv/ranking.ts @@ -5,6 +5,7 @@ import getRanking from './api/get-ranking'; import { config } from '@/config'; import pixivUtils from './utils'; import { parseDate } from '@/utils/parse-date'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const titles = { day: 'pixiv 日排行', @@ -84,7 +85,7 @@ export const route: Route = { async function handler(ctx) { if (!config.pixiv || !config.pixiv.refreshToken) { - throw new Error('pixiv RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('pixiv RSS is disabled due to the lack of relevant config'); } const mode = alias[ctx.req.param('mode')] ?? ctx.req.param('mode'); @@ -92,7 +93,7 @@ async function handler(ctx) { const token = await getToken(cache.tryGet); if (!token) { - throw new Error('pixiv not login'); + throw new ConfigNotFoundError('pixiv not login'); } const response = await getRanking(mode, ctx.req.param('date') && date, token); diff --git a/lib/routes/pixiv/search.ts b/lib/routes/pixiv/search.ts index 30c2a04561a74c..e4eb0f76dff914 100644 --- a/lib/routes/pixiv/search.ts +++ b/lib/routes/pixiv/search.ts @@ -6,6 +6,7 @@ import searchIllust from './api/search-illust'; import { config } from '@/config'; import pixivUtils from './utils'; import { parseDate } from '@/utils/parse-date'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/search/:keyword/:order?/:mode?', @@ -30,7 +31,7 @@ export const route: Route = { async function handler(ctx) { if (!config.pixiv || !config.pixiv.refreshToken) { - throw new Error('pixiv RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('pixiv RSS is disabled due to the lack of relevant config'); } const keyword = ctx.req.param('keyword'); @@ -39,7 +40,7 @@ async function handler(ctx) { const token = await getToken(cache.tryGet); if (!token) { - throw new Error('pixiv not login'); + throw new ConfigNotFoundError('pixiv not login'); } const response = await (order === 'popular' ? searchPopularIllust(keyword, token) : searchIllust(keyword, token)); diff --git a/lib/routes/pixiv/user.ts b/lib/routes/pixiv/user.ts index 47a1bc3ccda8bb..08243ad2e38c33 100644 --- a/lib/routes/pixiv/user.ts +++ b/lib/routes/pixiv/user.ts @@ -5,6 +5,7 @@ import getIllusts from './api/get-illusts'; import { config } from '@/config'; import pixivUtils from './utils'; import { parseDate } from '@/utils/parse-date'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/user/:id', @@ -31,13 +32,13 @@ export const route: Route = { async function handler(ctx) { if (!config.pixiv || !config.pixiv.refreshToken) { - throw new Error('pixiv RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('pixiv RSS is disabled due to the lack of relevant config'); } const id = ctx.req.param('id'); const token = await getToken(cache.tryGet); if (!token) { - throw new Error('pixiv not login'); + throw new ConfigNotFoundError('pixiv not login'); } const response = await getIllusts(id, token); diff --git a/lib/routes/plurk/top.ts b/lib/routes/plurk/top.ts index 07a8939d984a3b..1ce1924a1774d1 100644 --- a/lib/routes/plurk/top.ts +++ b/lib/routes/plurk/top.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { baseUrl, getPlurk } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const categoryList = new Set(['topReplurks', 'topFavorites', 'topResponded']); @@ -33,7 +34,7 @@ export const route: Route = { async function handler(ctx) { const { category = 'topReplurks', lang = 'en' } = ctx.req.param(); if (!categoryList.has(category)) { - throw new Error(`Invalid category: ${category}`); + throw new InvalidParameterError(`Invalid category: ${category}`); } const { data: apiResponse } = await got(`${baseUrl}/Stats/${category}`, { diff --git a/lib/routes/pornhub/category-url.ts b/lib/routes/pornhub/category-url.ts index cbb09cd1da19d8..5a2af089439e8a 100644 --- a/lib/routes/pornhub/category-url.ts +++ b/lib/routes/pornhub/category-url.ts @@ -3,6 +3,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { isValidHost } from '@/utils/valid-host'; import { headers, parseItems } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:language?/category_url/:url?', @@ -33,7 +34,7 @@ async function handler(ctx) { const { language = 'www', url = 'video' } = ctx.req.param(); const link = `https://${language}.pornhub.com/${url}`; if (!isValidHost(language)) { - throw new Error('Invalid language'); + throw new InvalidParameterError('Invalid language'); } const { data: response } = await got(link, { headers }); diff --git a/lib/routes/pornhub/model.ts b/lib/routes/pornhub/model.ts index e9f9ae788686a9..da5f44b01deda0 100644 --- a/lib/routes/pornhub/model.ts +++ b/lib/routes/pornhub/model.ts @@ -3,6 +3,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { isValidHost } from '@/utils/valid-host'; import { headers, parseItems } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:language?/model/:username/:sort?', @@ -32,7 +33,7 @@ async function handler(ctx) { const { language = 'www', username, sort = '' } = ctx.req.param(); const link = `https://${language}.pornhub.com/model/${username}/videos${sort ? `?o=${sort}` : ''}`; if (!isValidHost(language)) { - throw new Error('Invalid language'); + throw new InvalidParameterError('Invalid language'); } const { data: response } = await got(link, { headers }); diff --git a/lib/routes/pornhub/pornstar.ts b/lib/routes/pornhub/pornstar.ts index 58faaa440b4047..9815004bb2c3c1 100644 --- a/lib/routes/pornhub/pornstar.ts +++ b/lib/routes/pornhub/pornstar.ts @@ -3,6 +3,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { isValidHost } from '@/utils/valid-host'; import { headers, parseItems } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:language?/pornstar/:username/:sort?', @@ -37,7 +38,7 @@ async function handler(ctx) { const { language = 'www', username, sort = 'mr' } = ctx.req.param(); const link = `https://${language}.pornhub.com/pornstar/${username}/videos?o=${sort}`; if (!isValidHost(language)) { - throw new Error('Invalid language'); + throw new InvalidParameterError('Invalid language'); } const { data: response } = await got(link, { headers }); diff --git a/lib/routes/pornhub/users.ts b/lib/routes/pornhub/users.ts index a28314a092f158..e3137b60199309 100644 --- a/lib/routes/pornhub/users.ts +++ b/lib/routes/pornhub/users.ts @@ -3,6 +3,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { isValidHost } from '@/utils/valid-host'; import { headers, parseItems } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:language?/users/:username', @@ -32,7 +33,7 @@ async function handler(ctx) { const { language = 'www', username } = ctx.req.param(); const link = `https://${language}.pornhub.com/users/${username}/videos`; if (!isValidHost(language)) { - throw new Error('Invalid language'); + throw new InvalidParameterError('Invalid language'); } const { data: response } = await got(link, { headers }); diff --git a/lib/routes/qweather/3days.ts b/lib/routes/qweather/3days.ts index 31f674fabfeb37..d077dde84f7d76 100644 --- a/lib/routes/qweather/3days.ts +++ b/lib/routes/qweather/3days.ts @@ -7,6 +7,7 @@ import got from '@/utils/got'; import { art } from '@/utils/render'; import path from 'node:path'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const WEATHER_API = 'https://devapi.qweather.com/v7/weather/3d'; const AIR_QUALITY_API = 'https://devapi.qweather.com/v7/air/5d'; @@ -39,7 +40,7 @@ export const route: Route = { async function handler(ctx) { if (!config.hefeng.key) { - throw new Error('QWeather RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('QWeather RSS is disabled due to the lack of relevant config'); } const id = await cache.tryGet(ctx.req.param('location') + '_id', async () => { const response = await got(`${CIRY_LOOKUP_API}?location=${ctx.req.param('location')}&key=${config.hefeng.key}`); diff --git a/lib/routes/rsshub/transform/html.ts b/lib/routes/rsshub/transform/html.ts index 91889d4d87f336..d63c73782c24b1 100644 --- a/lib/routes/rsshub/transform/html.ts +++ b/lib/routes/rsshub/transform/html.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import { load } from 'cheerio'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/transform/html/:url/:routeParams', @@ -57,7 +58,7 @@ Specify options (in the format of query string) in parameter \`routeParams\` par async function handler(ctx) { if (!config.feature.allow_user_supply_unsafe_domain) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } const url = ctx.req.param('url'); const response = await got({ diff --git a/lib/routes/rsshub/transform/json.ts b/lib/routes/rsshub/transform/json.ts index c61bde64b0dbc3..ec4ba9601d8982 100644 --- a/lib/routes/rsshub/transform/json.ts +++ b/lib/routes/rsshub/transform/json.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import { load } from 'cheerio'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; function jsonGet(obj, attr) { if (typeof attr !== 'string') { @@ -70,7 +71,7 @@ JSON Path only supports format like \`a.b.c\`. if you need to access arrays, lik async function handler(ctx) { if (!config.feature.allow_user_supply_unsafe_domain) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } const url = ctx.req.param('url'); const response = await got({ diff --git a/lib/routes/rsshub/transform/sitemap.ts b/lib/routes/rsshub/transform/sitemap.ts index 19f5d8d321d2d1..f013e943af0241 100644 --- a/lib/routes/rsshub/transform/sitemap.ts +++ b/lib/routes/rsshub/transform/sitemap.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import { load } from 'cheerio'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/transform/sitemap/:url/:routeParams?', @@ -12,7 +13,7 @@ export const route: Route = { async function handler(ctx) { if (!config.feature.allow_user_supply_unsafe_domain) { - throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); + throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`); } const url = ctx.req.param('url'); const response = await got({ diff --git a/lib/routes/sehuatang/user.ts b/lib/routes/sehuatang/user.ts index 01fbbb4cac65ec..7046f2f933e64a 100644 --- a/lib/routes/sehuatang/user.ts +++ b/lib/routes/sehuatang/user.ts @@ -5,6 +5,7 @@ import got from '@/utils/got'; // 自订的 got import { load } from 'cheerio'; // 可以使用类似 jQuery 的 API HTML 解析器 import { parseDate } from '@/utils/parse-date'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const baseUrl = 'https://sehuatang.org/'; @@ -33,7 +34,7 @@ export const route: Route = { async function handler(ctx) { if (!config.sehuatang.cookie) { - throw new Error('Sehuatang RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('Sehuatang RSS is disabled due to the lack of relevant config'); } // 从Url参数中获取uid const uid = ctx.req.param('uid'); diff --git a/lib/routes/shiep/index.ts b/lib/routes/shiep/index.ts index 4c783ee548d00a..ed49dc73e25fa2 100644 --- a/lib/routes/shiep/index.ts +++ b/lib/routes/shiep/index.ts @@ -11,6 +11,7 @@ import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import { config } from './config'; import { radar } from './radar'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:type/:id?', @@ -58,13 +59,13 @@ async function handler(ctx) { const type = ctx.req.param('type'); if (!Object.keys(config).includes(type)) { - throw new Error(`Invalid type: ${type}`); + throw new InvalidParameterError(`Invalid type: ${type}`); } const { listSelector = '.list_item', pubDateSelector = '.Article_PublishDate', descriptionSelector = '.wp_articlecontent', title } = config[type]; if (!title) { - throw new Error(`Invalid type: ${type}`); + throw new InvalidParameterError(`Invalid type: ${type}`); } const host = `https://${type}.shiep.edu.cn`; diff --git a/lib/routes/solidot/main.ts b/lib/routes/solidot/main.ts index 34a61afec433df..18f1d5bcba03bb 100644 --- a/lib/routes/solidot/main.ts +++ b/lib/routes/solidot/main.ts @@ -9,6 +9,7 @@ import got from '@/utils/got'; // get web content import { load } from 'cheerio'; // html parser import get_article from './_article'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/:type?', @@ -40,7 +41,7 @@ export const route: Route = { async function handler(ctx) { const type = ctx.req.param('type') ?? 'www'; if (!isValidHost(type)) { - throw new Error('Invalid type'); + throw new InvalidParameterError('Invalid type'); } const base_url = `https://${type}.solidot.org`; diff --git a/lib/routes/spotify/utils.ts b/lib/routes/spotify/utils.ts index 6103efa730e2dc..ea460fba9fce45 100644 --- a/lib/routes/spotify/utils.ts +++ b/lib/routes/spotify/utils.ts @@ -1,10 +1,11 @@ import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; import got from '@/utils/got'; // Token used to retrieve public information. async function getPublicToken() { if (!config.spotify || !config.spotify.clientId || !config.spotify.clientSecret) { - throw new Error('Spotify public RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('Spotify public RSS is disabled due to the lack of relevant config'); } const { clientId, clientSecret } = config.spotify; @@ -26,7 +27,7 @@ async function getPublicToken() { // Note that we don't use PKCE since the client secret shall be safe on the server. async function getPrivateToken() { if (!config.spotify || !config.spotify.clientId || !config.spotify.clientSecret || !config.spotify.refreshToken) { - throw new Error('Spotify private RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('Spotify private RSS is disabled due to the lack of relevant config'); } const { clientId, clientSecret, refreshToken } = config.spotify; diff --git a/lib/routes/sspai/author.ts b/lib/routes/sspai/author.ts index 120785b32096d3..fced3b0cfee885 100644 --- a/lib/routes/sspai/author.ts +++ b/lib/routes/sspai/author.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; @@ -13,7 +14,7 @@ async function getUserId(slug) { }); if (response.data.error !== 0) { - throw new Error('User Not Found'); + throw new InvalidParameterError('User Not Found'); } return response.data.data.id; diff --git a/lib/routes/swjtu/xg.ts b/lib/routes/swjtu/xg.ts index 8103faa59722bc..06e734110d67eb 100644 --- a/lib/routes/swjtu/xg.ts +++ b/lib/routes/swjtu/xg.ts @@ -3,6 +3,7 @@ import cache from '@/utils/cache'; import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const rootURL = 'http://xg.swjtu.edu.cn'; const listURL = { @@ -84,7 +85,7 @@ async function handler(ctx) { const pageURL = listURL[code]; if (!pageURL) { - throw new Error('code not supported'); + throw new InvalidParameterError('code not supported'); } const resp = await got({ diff --git a/lib/routes/telegram/stickerpack.ts b/lib/routes/telegram/stickerpack.ts index 66338509104b07..cd5822117638ac 100644 --- a/lib/routes/telegram/stickerpack.ts +++ b/lib/routes/telegram/stickerpack.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/stickerpack/:name', @@ -22,7 +23,7 @@ export const route: Route = { async function handler(ctx) { if (!config.telegram || !config.telegram.token) { - throw new Error('Telegram Sticker Pack RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('Telegram Sticker Pack RSS is disabled due to the lack of relevant config'); } const name = ctx.req.param('name'); diff --git a/lib/routes/telegram/tglib/channel.ts b/lib/routes/telegram/tglib/channel.ts index 1c4f0947332ef2..73864bf364b6f0 100644 --- a/lib/routes/telegram/tglib/channel.ts +++ b/lib/routes/telegram/tglib/channel.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import { client, decodeMedia, getClient, getFilename, getMediaLink, streamDocument, streamThumbnail } from './client'; import { returnBigInt as bigInt } from 'telegram/Helpers'; import { HTMLParser } from 'telegram/extensions/html'; @@ -8,7 +9,7 @@ function parseRange(range, length) { } const [typ, segstr] = range.split('='); if (typ !== 'bytes') { - throw `unsupported range: ${typ}`; + throw new InvalidParameterError(`unsupported range: ${typ}`); } const segs = segstr.split(',').map((s) => s.trim()); const parsedSegs = []; diff --git a/lib/routes/telegram/tglib/client.ts b/lib/routes/telegram/tglib/client.ts index 6d01710f96e3f9..855c5f887d01db 100644 --- a/lib/routes/telegram/tglib/client.ts +++ b/lib/routes/telegram/tglib/client.ts @@ -4,11 +4,12 @@ import { StringSession } from 'telegram/sessions'; import { getAppropriatedPartSize } from 'telegram/Utils'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; let client: TelegramClient | undefined; export async function getClient(authParams?: UserAuthParams, session?: string) { if (!config.telegram.session && session === undefined) { - throw new Error('TELEGRAM_SESSION is not configured'); + throw new ConfigNotFoundError('TELEGRAM_SESSION is not configured'); } if (client) { return client; diff --git a/lib/routes/tencent/news/coronavirus/data.ts b/lib/routes/tencent/news/coronavirus/data.ts index 8b08bb410f6497..bff3ed95f0e189 100644 --- a/lib/routes/tencent/news/coronavirus/data.ts +++ b/lib/routes/tencent/news/coronavirus/data.ts @@ -6,6 +6,7 @@ import { getData } from './utils'; import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/news/coronavirus/data/:province?/:city?', @@ -50,7 +51,7 @@ async function handler(ctx) { } } if (!coronavirusData) { - throw new Error(`未找到 ${placeName} 的疫情数据,请检查输入的省市名称是否正确`); + throw new InvalidParameterError(`未找到 ${placeName} 的疫情数据,请检查输入的省市名称是否正确`); } todayConfirm = coronavirusData.today?.confirm; totalNowConfirm = coronavirusData.total?.nowConfirm; diff --git a/lib/routes/tencent/qq/sdk/changelog.ts b/lib/routes/tencent/qq/sdk/changelog.ts index 3b7d92762c3bc7..6f58b7c8c9474c 100644 --- a/lib/routes/tencent/qq/sdk/changelog.ts +++ b/lib/routes/tencent/qq/sdk/changelog.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import { Route } from '@/types'; import got from '@/utils/got'; import { load } from 'cheerio'; @@ -32,7 +33,7 @@ async function handler(ctx) { title = 'Android SDK 历史变更'; link = 'https://wiki.connect.qq.com/android_sdk历史变更'; } else { - throw new Error('not support platform'); + throw new InvalidParameterError('not support platform'); } const response = await got.get(link); diff --git a/lib/routes/trending/all-trending.ts b/lib/routes/trending/all-trending.ts index 31d15f3cfd8480..1233cdeaf281df 100644 --- a/lib/routes/trending/all-trending.ts +++ b/lib/routes/trending/all-trending.ts @@ -13,6 +13,7 @@ import { art } from '@/utils/render'; import path from 'node:path'; import { config } from '@/config'; import md5 from '@/utils/md5'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; // Constants const CACHE_KEY = 'trending-all-in-one'; @@ -187,7 +188,7 @@ export const route: Route = { async function handler(ctx) { // Prevent making over 100 requests per invocation if (ctx.req.param('numberOfDays') > 14) { - throw new Error('days must be less than 14'); + throw new InvalidParameterError('days must be less than 14'); } const numberOfDays = ctx.req.param('numberOfDays') || 3; const currentShanghaiDateTime = dayjs(toShanghaiTimezone(new Date())); diff --git a/lib/routes/twitch/schedule.ts b/lib/routes/twitch/schedule.ts index ce72407b3b917f..1f5af2c5e9191e 100644 --- a/lib/routes/twitch/schedule.ts +++ b/lib/routes/twitch/schedule.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import { Route } from '@/types'; import got from '@/utils/got'; @@ -76,7 +77,7 @@ async function handler(ctx) { const streamScheduleData = response.data[1].data; if (!streamScheduleData.user.id) { - throw new Error(`Username does not exist`); + throw new InvalidParameterError(`Username does not exist`); } const displayName = channelShellData.userOrError.displayName; diff --git a/lib/routes/twitch/video.ts b/lib/routes/twitch/video.ts index dd85d9e097b2a2..3e3ed59a8493f3 100644 --- a/lib/routes/twitch/video.ts +++ b/lib/routes/twitch/video.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; @@ -42,7 +43,7 @@ async function handler(ctx) { const login = ctx.req.param('login'); const filter = ctx.req.param('filter')?.toLowerCase() || 'all'; if (!FILTER_NODE_TYPE_MAP[filter]) { - throw new Error(`Unsupported filter type "${filter}", please choose from { ${Object.keys(FILTER_NODE_TYPE_MAP).join(', ')} }`); + throw new InvalidParameterError(`Unsupported filter type "${filter}", please choose from { ${Object.keys(FILTER_NODE_TYPE_MAP).join(', ')} }`); } const response = await got({ @@ -72,14 +73,14 @@ async function handler(ctx) { const channelVideoShelvesQueryData = response.data[0].data; if (!channelVideoShelvesQueryData.user.id) { - throw new Error(`Username does not exist`); + throw new InvalidParameterError(`Username does not exist`); } const displayName = channelVideoShelvesQueryData.user.displayName; const videoShelvesEdge = channelVideoShelvesQueryData.user.videoShelves.edges.find((edge) => edge.node.type === FILTER_NODE_TYPE_MAP[filter]); if (!videoShelvesEdge) { - throw new Error(`No video under filter type "${filter}"`); + throw new InvalidParameterError(`No video under filter type "${filter}"`); } const out = videoShelvesEdge.node.items.map((item) => ({ diff --git a/lib/routes/twitter/api/index.ts b/lib/routes/twitter/api/index.ts index 10d721d092197d..64e861cae56987 100644 --- a/lib/routes/twitter/api/index.ts +++ b/lib/routes/twitter/api/index.ts @@ -1,3 +1,4 @@ +import ConfigNotFoundError from '@/errors/types/config-not-found'; import mobileApi from './mobile-api/api'; import webApi from './web-api/api'; import { config } from '@/config'; @@ -19,7 +20,7 @@ let api: { getHomeTimeline: ApiItem; } = { init: () => { - throw new Error('Twitter API is not configured'); + throw new ConfigNotFoundError('Twitter API is not configured'); }, getUser: () => null, getUserTweets: () => null, diff --git a/lib/routes/twitter/api/mobile-api/api.ts b/lib/routes/twitter/api/mobile-api/api.ts index aa8bc978326087..199f21c6e217a8 100644 --- a/lib/routes/twitter/api/mobile-api/api.ts +++ b/lib/routes/twitter/api/mobile-api/api.ts @@ -7,6 +7,7 @@ import CryptoJS from 'crypto-js'; import queryString from 'query-string'; import { initToken, getToken } from './token'; import cache from '@/utils/cache'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const twitterGot = async (url, params) => { const token = await getToken(); @@ -209,7 +210,7 @@ const getUser = async (id) => { const cacheTryGet = async (_id, params, func) => { const id = await getUserID(_id); if (id === undefined) { - throw new Error('User not found'); + throw new InvalidParameterError('User not found'); } const funcName = func.name; const paramsString = JSON.stringify(params); diff --git a/lib/routes/twitter/api/mobile-api/token.ts b/lib/routes/twitter/api/mobile-api/token.ts index 2d49d7303fe9b5..cba2174cfe7f5a 100644 --- a/lib/routes/twitter/api/mobile-api/token.ts +++ b/lib/routes/twitter/api/mobile-api/token.ts @@ -1,5 +1,6 @@ import { config } from '@/config'; import login from './login'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; let tokenIndex = 0; let authentication = null; @@ -32,7 +33,7 @@ function getToken() { tokenIndex = 0; } } else { - throw new Error('Invalid twitter configs'); + throw new ConfigNotFoundError('Invalid twitter configs'); } return token; diff --git a/lib/routes/twitter/api/web-api/api.ts b/lib/routes/twitter/api/web-api/api.ts index 2b684806195ab7..0431b632a8b46b 100644 --- a/lib/routes/twitter/api/web-api/api.ts +++ b/lib/routes/twitter/api/web-api/api.ts @@ -2,6 +2,7 @@ import { baseUrl, gqlMap, gqlFeatures } from './constants'; import { config } from '@/config'; import cache from '@/utils/cache'; import { twitterGot, paginationTweets, gatherLegacyFromData } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const getUserData = (id) => cache.tryGet(`twitter-userdata-${id}`, () => { @@ -33,7 +34,7 @@ const cacheTryGet = async (_id, params, func) => { const userData: any = await getUserData(_id); const id = (userData.data?.user || userData.data?.user_result)?.result?.rest_id; if (id === undefined) { - throw new Error('User not found'); + throw new InvalidParameterError('User not found'); } const funcName = func.name; const paramsString = JSON.stringify(params); diff --git a/lib/routes/twitter/api/web-api/utils.ts b/lib/routes/twitter/api/web-api/utils.ts index dce2192370ed38..b76647de1bab5f 100644 --- a/lib/routes/twitter/api/web-api/utils.ts +++ b/lib/routes/twitter/api/web-api/utils.ts @@ -1,3 +1,4 @@ +import ConfigNotFoundError from '@/errors/types/config-not-found'; import { baseUrl, gqlFeatures, bearerToken, gqlMap } from './constants'; import { config } from '@/config'; import got from '@/utils/got'; @@ -6,7 +7,7 @@ import { Cookie } from 'tough-cookie'; export const twitterGot = async (url, params) => { if (!config.twitter.cookie) { - throw new Error('Twitter cookie is not configured'); + throw new ConfigNotFoundError('Twitter cookie is not configured'); } const jsonCookie = Object.fromEntries( config.twitter.cookie @@ -15,7 +16,7 @@ export const twitterGot = async (url, params) => { .map((c) => [c?.key, c?.value]) ); if (!jsonCookie || !jsonCookie.auth_token || !jsonCookie.ct0) { - throw new Error('Twitter cookie is not valid'); + throw new ConfigNotFoundError('Twitter cookie is not valid'); } const requestData = { diff --git a/lib/routes/twitter/likes.ts b/lib/routes/twitter/likes.ts index 1c62479b965026..4f69bbe21a0f2d 100644 --- a/lib/routes/twitter/likes.ts +++ b/lib/routes/twitter/likes.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import utils from './utils'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/likes/:id/:routeParams?', @@ -22,7 +23,7 @@ export const route: Route = { async function handler(ctx) { if (!config.twitter || !config.twitter.consumer_key || !config.twitter.consumer_secret) { - throw new Error('Twitter RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('Twitter RSS is disabled due to the lack of relevant config'); } const id = ctx.req.param('id'); const client = await utils.getAppClient(); diff --git a/lib/routes/twitter/trends.ts b/lib/routes/twitter/trends.ts index 19ea43b55ac4f2..37d28812d45731 100644 --- a/lib/routes/twitter/trends.ts +++ b/lib/routes/twitter/trends.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import utils from './utils'; import { config } from '@/config'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/trends/:woeid?', @@ -22,7 +23,7 @@ export const route: Route = { async function handler(ctx) { if (!config.twitter || !config.twitter.consumer_key || !config.twitter.consumer_secret) { - throw new Error('Twitter RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('Twitter RSS is disabled due to the lack of relevant config'); } const woeid = ctx.req.param('woeid') ?? 1; // Global information is available by using 1 as the WOEID const client = await utils.getAppClient(); diff --git a/lib/routes/uestc/cqe.ts b/lib/routes/uestc/cqe.ts index a06de72b0f2480..9d3db276117b4f 100644 --- a/lib/routes/uestc/cqe.ts +++ b/lib/routes/uestc/cqe.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import puppeteer from '@/utils/puppeteer'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const baseUrl = 'https://cqe.uestc.edu.cn/'; @@ -47,7 +48,7 @@ async function handler(ctx) { const type = ctx.req.param('type') || 'tzgg'; const pageUrl = mapUrl[type]; if (!pageUrl) { - throw new Error('type not supported'); + throw new InvalidParameterError('type not supported'); } const browser = await puppeteer({ stealth: true }); diff --git a/lib/routes/uestc/jwc.ts b/lib/routes/uestc/jwc.ts index 02fe7b33ddfc44..324e0f34c8c505 100644 --- a/lib/routes/uestc/jwc.ts +++ b/lib/routes/uestc/jwc.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const dateRegex = /(20\d{2})\/(\d{2})\/(\d{2})/; @@ -48,7 +49,7 @@ async function handler(ctx) { const type = ctx.req.param('type') || 'important'; const pageUrl = map[type]; if (!pageUrl) { - throw new Error('type not supported'); + throw new InvalidParameterError('type not supported'); } const response = await got.get(baseUrl + pageUrl); diff --git a/lib/routes/uestc/news.ts b/lib/routes/uestc/news.ts index 6eb4cd98fcf14e..a602a1b1409fec 100644 --- a/lib/routes/uestc/news.ts +++ b/lib/routes/uestc/news.ts @@ -2,6 +2,7 @@ import { Route } from '@/types'; import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const dateRegex = /(20\d{2}).(\d{2})-(\d{2})/; @@ -45,7 +46,7 @@ async function handler(ctx) { const type = ctx.req.param('type') || 'announcement'; const pageUrl = map[type]; if (!pageUrl) { - throw new Error('type not supported'); + throw new InvalidParameterError('type not supported'); } const response = await got.get(baseUrl + pageUrl); diff --git a/lib/routes/uestc/sise.ts b/lib/routes/uestc/sise.ts index 522114338e6478..de987645646bc8 100644 --- a/lib/routes/uestc/sise.ts +++ b/lib/routes/uestc/sise.ts @@ -3,6 +3,7 @@ import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import dayjs from 'dayjs'; import puppeteer from '@/utils/puppeteer'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const baseUrl = 'https://sise.uestc.edu.cn/'; @@ -62,7 +63,7 @@ async function handler(ctx) { const type = ctx.req.param('type') || 1; const divId = mapId[type]; if (!divId) { - throw new Error('type not supported'); + throw new InvalidParameterError('type not supported'); } const browser = await puppeteer({ stealth: true }); diff --git a/lib/routes/uptimerobot/rss.ts b/lib/routes/uptimerobot/rss.ts index 5bb7bc23b09bfa..46f174f5d74b49 100644 --- a/lib/routes/uptimerobot/rss.ts +++ b/lib/routes/uptimerobot/rss.ts @@ -7,6 +7,7 @@ import { art } from '@/utils/render'; import path from 'node:path'; import dayjs from 'dayjs'; import { fallback, queryToBoolean } from '@/utils/readable-social'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const titleRegex = /(.+)\s+is\s+([A-Z]+)\s+\((.+)\)/; @@ -101,12 +102,12 @@ async function handler(ctx) { const items = rss.items.reverse().map((item) => { const titleMatch = item.title.match(titleRegex); if (!titleMatch) { - throw new Error('Unexpected title, please open an issue.'); + throw new InvalidParameterError('Unexpected title, please open an issue.'); } const [monitorName, status, id] = titleMatch.slice(1); if (id !== item.link) { - throw new Error('Monitor ID mismatch, please open an issue.'); + throw new InvalidParameterError('Monitor ID mismatch, please open an issue.'); } // id could be a URL, a domain, an IP address, or a hex string. fix it @@ -125,7 +126,7 @@ async function handler(ctx) { } else if (status === 'DOWN') { monitor.down(duration); } else { - throw new Error('Unexpected status, please open an issue.'); + throw new InvalidParameterError('Unexpected status, please open an issue.'); } const desc = art(path.join(__dirname, 'templates/rss.art'), { diff --git a/lib/routes/ustc/eeis.ts b/lib/routes/ustc/eeis.ts index e32d2aed94e108..85d532d5ba7077 100644 --- a/lib/routes/ustc/eeis.ts +++ b/lib/routes/ustc/eeis.ts @@ -4,6 +4,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const map = new Map([ ['tzgg', { title: '中国科学技术大学电子工程与信息科学系 - 通知公告', id: '2702' }], @@ -44,7 +45,7 @@ async function handler(ctx) { const type = ctx.req.param('type') ?? 'tzgg'; const info = map.get(type); if (!info) { - throw new Error('invalid type'); + throw new InvalidParameterError('invalid type'); } const id = info.id; diff --git a/lib/routes/ustc/gs.ts b/lib/routes/ustc/gs.ts index ff5b61fa121576..b5cfb55b0200d1 100644 --- a/lib/routes/ustc/gs.ts +++ b/lib/routes/ustc/gs.ts @@ -4,6 +4,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const map = new Map([ ['tzgg', { title: '中国科学技术大学研究生院 - 通知公告', id: '9' }], @@ -44,7 +45,7 @@ async function handler(ctx) { const type = ctx.req.param('type') ?? 'tzgg'; const info = map.get(type); if (!info) { - throw new Error('invalid type'); + throw new InvalidParameterError('invalid type'); } const id = info.id; diff --git a/lib/routes/ustc/math.ts b/lib/routes/ustc/math.ts index d19fb67980dc72..cccdd7e245053b 100644 --- a/lib/routes/ustc/math.ts +++ b/lib/routes/ustc/math.ts @@ -4,6 +4,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const map = new Map([ ['xyxw', { title: '中国科学技术大学数学科学学院 - 学院新闻', id: 'xyxw' }], @@ -46,7 +47,7 @@ async function handler(ctx) { const type = ctx.req.param('type') ?? 'tzgg'; const info = map.get(type); if (!info) { - throw new Error('invalid type'); + throw new InvalidParameterError('invalid type'); } const id = info.id; diff --git a/lib/routes/ustc/sist.ts b/lib/routes/ustc/sist.ts index dd49cc0cd36dfd..3b0a2953929af5 100644 --- a/lib/routes/ustc/sist.ts +++ b/lib/routes/ustc/sist.ts @@ -4,6 +4,7 @@ import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const map = new Map([ ['tzgg', { title: '中国科学技术大学信息科学技术学院 - 通知公告', id: '5142' }], @@ -44,7 +45,7 @@ async function handler(ctx) { const type = ctx.req.param('type') ?? 'tzgg'; const info = map.get(type); if (!info) { - throw new Error('invalid type'); + throw new InvalidParameterError('invalid type'); } const id = info.id; diff --git a/lib/routes/utgd/topic.ts b/lib/routes/utgd/topic.ts index 17ce8d3ebbfdcd..e01fa5147c272d 100644 --- a/lib/routes/utgd/topic.ts +++ b/lib/routes/utgd/topic.ts @@ -9,6 +9,7 @@ import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; import MarkdownIt from 'markdown-it'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const md = MarkdownIt({ html: true, }); @@ -58,7 +59,7 @@ async function handler(ctx) { const topicItems = response.data.filter((i) => i.title === topic); if (!topicItems) { - throw new Error(`No topic named ${topic}`); + throw new InvalidParameterError(`No topic named ${topic}`); } const topicItem = topicItems[0]; diff --git a/lib/routes/wechat/data258.ts b/lib/routes/wechat/data258.ts index a4babfa3a5b567..51142a9a7ae4cf 100644 --- a/lib/routes/wechat/data258.ts +++ b/lib/routes/wechat/data258.ts @@ -6,6 +6,7 @@ import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; import { finishArticleItem } from '@/utils/wechat-mp'; import wait from '@/utils/wait'; +import RequestInProgressError from '@/errors/types/request-in-progress'; const parsePage = ($item, hyperlinkSelector, timeSelector) => { const hyperlink = $item.find(hyperlinkSelector); @@ -35,7 +36,7 @@ export const route: Route = { async function handler(ctx) { // !!! here we must use a lock to prevent other requests to break the anti-anti-crawler workarounds !!! if ((await cache.get('data258:lock', false)) === '1') { - throw new Error('Another request is in progress, please try again later.'); + throw new RequestInProgressError('Another request is in progress, please try again later.'); } // !!! here no need to acquire the lock, because the MP/category page has no crawler detection !!! @@ -69,7 +70,7 @@ async function handler(ctx) { // !!! double-check !!! if ((await cache.get('data258:lock', false)) === '1') { - throw new Error('Another request is in progress, please try again later.'); + throw new RequestInProgressError('Another request is in progress, please try again later.'); } else { // !!! here we acquire the lock because the jump page has crawler detection !!! await cache.set('data258:lock', '1', 60); diff --git a/lib/routes/wechat/feeddd.ts b/lib/routes/wechat/feeddd.ts index f9dede4d29cd34..21b1b6e16e0adb 100644 --- a/lib/routes/wechat/feeddd.ts +++ b/lib/routes/wechat/feeddd.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import { finishArticleItem } from '@/utils/wechat-mp'; @@ -14,7 +15,7 @@ const handler = async (ctx) => { response = await got(apiUrl); } catch (error) { if ((error.name === 'HTTPError' || error.name === 'FetchError') && error.response.statusCode === 404) { - throw new Error('该公众号不存在,有关如何获取公众号 id,详见 https://docs.rsshub.app/routes/new-media#wei-xin-gong-zhong-hao-feeddd-lai-yuan'); + throw new InvalidParameterError('该公众号不存在,有关如何获取公众号 id,详见 https://docs.rsshub.app/routes/new-media#wei-xin-gong-zhong-hao-feeddd-lai-yuan'); } throw error; } diff --git a/lib/routes/weibo/friends.ts b/lib/routes/weibo/friends.ts index 4aeb9d1980022d..3cb5cbe34752ba 100644 --- a/lib/routes/weibo/friends.ts +++ b/lib/routes/weibo/friends.ts @@ -5,6 +5,7 @@ import got from '@/utils/got'; import { config } from '@/config'; import weiboUtils from './utils'; import { fallback, queryToBoolean } from '@/utils/readable-social'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/friends/:routeParams?', @@ -45,7 +46,7 @@ export const route: Route = { async function handler(ctx) { if (!config.weibo.cookies) { - throw new Error('Weibo Friends Timeline is not available due to the absense of [Weibo Cookies]. Check relevant config tutorial'); + throw new ConfigNotFoundError('Weibo Friends Timeline is not available due to the absense of [Weibo Cookies]. Check relevant config tutorial'); } let displayVideo = '1'; diff --git a/lib/routes/weibo/group.ts b/lib/routes/weibo/group.ts index c3850822c5d262..ad00e817d07b88 100644 --- a/lib/routes/weibo/group.ts +++ b/lib/routes/weibo/group.ts @@ -5,6 +5,7 @@ import got from '@/utils/got'; import { config } from '@/config'; import weiboUtils from './utils'; import { fallback, queryToBoolean } from '@/utils/readable-social'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/group/:gid/:gname?/:routeParams?', @@ -38,7 +39,7 @@ export const route: Route = { async function handler(ctx) { if (!config.weibo.cookies) { - throw new Error('Weibo Group Timeline is not available due to the absense of [Weibo Cookies]. Check relevant config tutorial'); + throw new ConfigNotFoundError('Weibo Group Timeline is not available due to the absense of [Weibo Cookies]. Check relevant config tutorial'); } const gid = ctx.req.param('gid'); diff --git a/lib/routes/wnacg/index.ts b/lib/routes/wnacg/index.ts index 6ab4a78c8d5c78..bb877eabcfc9b7 100644 --- a/lib/routes/wnacg/index.ts +++ b/lib/routes/wnacg/index.ts @@ -8,6 +8,7 @@ import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; const categories = { 1: '同人誌 漢化', @@ -49,7 +50,7 @@ export const route: Route = { async function handler(ctx) { const { cid, tag } = ctx.req.param(); if (cid && !Object.keys(categories).includes(cid)) { - throw new Error('此分类不存在'); + throw new InvalidParameterError('此分类不存在'); } const url = `${baseUrl}/albums${cid ? `-index-cate-${cid}` : ''}${tag ? `-index-tag-${tag}` : ''}.html`; diff --git a/lib/routes/xiaohongshu/user.ts b/lib/routes/xiaohongshu/user.ts index dc60ae9168a305..96b03405fb6bb4 100644 --- a/lib/routes/xiaohongshu/user.ts +++ b/lib/routes/xiaohongshu/user.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import cache from '@/utils/cache'; import { getUser } from './util'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/user/:user_id/:category', @@ -36,13 +37,13 @@ async function handler(ctx) { ); const renderCollect = (collect) => { if (!collect) { - throw new Error('该用户已设置收藏内容不可见'); + throw new InvalidParameterError('该用户已设置收藏内容不可见'); } if (collect.code !== 0) { throw new Error(JSON.stringify(collect)); } if (!collect.data.notes.length) { - throw new Error('该用户已设置收藏内容不可见'); + throw new InvalidParameterError('该用户已设置收藏内容不可见'); } return collect.data.notes.map((item) => ({ title: item.display_title, diff --git a/lib/routes/xsijishe/rank.ts b/lib/routes/xsijishe/rank.ts index fa3304e0bc9ec0..fd95af2be76980 100644 --- a/lib/routes/xsijishe/rank.ts +++ b/lib/routes/xsijishe/rank.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; @@ -33,7 +34,7 @@ async function handler(ctx) { title = '司机社综合月排行榜'; rankId = 'nex_recons_demens1'; } else { - throw new Error('Invalid rank type'); + throw new InvalidParameterError('Invalid rank type'); } const url = `${baseUrl}/portal.php`; const resp = await got(url); diff --git a/lib/routes/xueqiu/timeline.ts b/lib/routes/xueqiu/timeline.ts index 46d649ba930aec..1b47e38dfa1261 100644 --- a/lib/routes/xueqiu/timeline.ts +++ b/lib/routes/xueqiu/timeline.ts @@ -3,6 +3,7 @@ import got from '@/utils/got'; import { config } from '@/config'; import { parseDate } from '@/utils/parse-date'; import cache from '@/utils/cache'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const rootUrl = 'https://xueqiu.com'; export const route: Route = { path: '/timeline/:usergroup_id?', @@ -39,7 +40,7 @@ async function handler(ctx) { const limit = ctx.req.query('limit') || 15; const usergroup_id = ctx.req.param('usergroup_id') ?? -1; if (cookie === undefined) { - throw new Error('缺少雪球用户登录后的 Cookie 值'); + throw new ConfigNotFoundError('缺少雪球用户登录后的 Cookie 值'); } let out: DataItem[] = []; let max_id = -1; diff --git a/lib/routes/yahoo/news/tw/index.ts b/lib/routes/yahoo/news/tw/index.ts index ec681e779d1fc9..30fb4a70f4f9c4 100644 --- a/lib/routes/yahoo/news/tw/index.ts +++ b/lib/routes/yahoo/news/tw/index.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import cache from '@/utils/cache'; import { getArchive, getCategories, parseList, parseItem } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/news/:region/:category?', @@ -58,7 +59,7 @@ export const route: Route = { async function handler(ctx) { const { region, category } = ctx.req.param(); if (!['hk', 'tw'].includes(region)) { - throw new Error(`Unknown region: ${region}`); + throw new InvalidParameterError(`Unknown region: ${region}`); } const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; diff --git a/lib/routes/yahoo/news/tw/provider-helper.ts b/lib/routes/yahoo/news/tw/provider-helper.ts index d2e9a4d325358a..e7c9345f342b20 100644 --- a/lib/routes/yahoo/news/tw/provider-helper.ts +++ b/lib/routes/yahoo/news/tw/provider-helper.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import cache from '@/utils/cache'; import { getProviderList } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/news/providers/:region', @@ -23,7 +24,7 @@ export const route: Route = { async function handler(ctx) { const region = ctx.req.param('region'); if (!['hk', 'tw'].includes(region)) { - throw new Error(`Unknown region: ${region}`); + throw new InvalidParameterError(`Unknown region: ${region}`); } const providerList = await getProviderList(region, cache.tryGet); diff --git a/lib/routes/yahoo/news/tw/provider.ts b/lib/routes/yahoo/news/tw/provider.ts index a5fffc0bebdf34..934ef11501dc9a 100644 --- a/lib/routes/yahoo/news/tw/provider.ts +++ b/lib/routes/yahoo/news/tw/provider.ts @@ -1,6 +1,7 @@ import { Route } from '@/types'; import cache from '@/utils/cache'; import { getArchive, getProviderList, parseList, parseItem } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/news/provider/:region/:providerId', @@ -26,7 +27,7 @@ export const route: Route = { async function handler(ctx) { const { region, providerId } = ctx.req.param(); if (!['hk', 'tw'].includes(region)) { - throw new Error(`Unknown region: ${region}`); + throw new InvalidParameterError(`Unknown region: ${region}`); } const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; diff --git a/lib/routes/yahoo/news/us/index.ts b/lib/routes/yahoo/news/us/index.ts index 9d8e568470ef75..3daf5d415cf662 100644 --- a/lib/routes/yahoo/news/us/index.ts +++ b/lib/routes/yahoo/news/us/index.ts @@ -4,6 +4,7 @@ import got from '@/utils/got'; import parser from '@/utils/rss-parser'; import { load } from 'cheerio'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/news/en/:category?', @@ -15,7 +16,7 @@ export const route: Route = { async function handler(ctx) { const region = ctx.req.param('region') === 'en' ? '' : ctx.req.param('region').toLowerCase(); if (!isValidHost(region) && region !== '') { - throw new Error('Invalid region'); + throw new InvalidParameterError('Invalid region'); } const category = ctx.req.param('category') ? ctx.req.param('category').toLowerCase() : ''; const rssUrl = `https://${region ? `${region}.` : ''}news.yahoo.com/rss/${category}`; diff --git a/lib/routes/youtube/channel.ts b/lib/routes/youtube/channel.ts index d5c557ffcfcedf..abd64ec0433435 100644 --- a/lib/routes/youtube/channel.ts +++ b/lib/routes/youtube/channel.ts @@ -3,6 +3,8 @@ import cache from '@/utils/cache'; import utils from './utils'; import { config } from '@/config'; import { parseDate } from '@/utils/parse-date'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/channel/:id/:embed?', @@ -38,13 +40,13 @@ YouTube provides official RSS feeds for channels, for instance [https://www.yout async function handler(ctx) { if (!config.youtube || !config.youtube.key) { - throw new Error('YouTube RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('YouTube RSS is disabled due to the lack of relevant config'); } const id = ctx.req.param('id'); const embed = !ctx.req.param('embed'); if (!utils.isYouTubeChannelId(id)) { - throw new Error(`Invalid YouTube channel ID. \nYou may want to use /youtube/user/:id instead.`); + throw new InvalidParameterError(`Invalid YouTube channel ID. \nYou may want to use /youtube/user/:id instead.`); } const playlistId = (await utils.getChannelWithId(id, 'contentDetails', cache)).data.items[0].contentDetails.relatedPlaylists.uploads; diff --git a/lib/routes/youtube/custom.ts b/lib/routes/youtube/custom.ts index 7267f635f5be15..2f7f0453d2e403 100644 --- a/lib/routes/youtube/custom.ts +++ b/lib/routes/youtube/custom.ts @@ -5,6 +5,7 @@ import { config } from '@/config'; import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/c/:username/:embed?', @@ -24,7 +25,7 @@ export const route: Route = { async function handler(ctx) { if (!config.youtube || !config.youtube.key) { - throw new Error('YouTube RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('YouTube RSS is disabled due to the lack of relevant config'); } const username = ctx.req.param('username'); const embed = !ctx.req.param('embed'); diff --git a/lib/routes/youtube/live.ts b/lib/routes/youtube/live.ts index 680c66582ec0ec..6e2f10cced8a68 100644 --- a/lib/routes/youtube/live.ts +++ b/lib/routes/youtube/live.ts @@ -5,6 +5,7 @@ import { config } from '@/config'; import { parseDate } from '@/utils/parse-date'; import got from '@/utils/got'; import { load } from 'cheerio'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/live/:username/:embed?', @@ -26,7 +27,7 @@ export const route: Route = { async function handler(ctx) { if (!config.youtube || !config.youtube.key) { - throw new Error('YouTube RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('YouTube RSS is disabled due to the lack of relevant config'); } const username = ctx.req.param('username'); const embed = !ctx.req.param('embed'); diff --git a/lib/routes/youtube/playlist.ts b/lib/routes/youtube/playlist.ts index 70602d066fb89a..e62717e991d7bd 100644 --- a/lib/routes/youtube/playlist.ts +++ b/lib/routes/youtube/playlist.ts @@ -3,6 +3,7 @@ import cache from '@/utils/cache'; import utils from './utils'; import { config } from '@/config'; import { parseDate } from '@/utils/parse-date'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/playlist/:id/:embed?', @@ -24,7 +25,7 @@ export const route: Route = { async function handler(ctx) { if (!config.youtube || !config.youtube.key) { - throw new Error('YouTube RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('YouTube RSS is disabled due to the lack of relevant config'); } const id = ctx.req.param('id'); const embed = !ctx.req.param('embed'); diff --git a/lib/routes/youtube/subscriptions.ts b/lib/routes/youtube/subscriptions.ts index f19492ae1d85fb..0fec4e22342bae 100644 --- a/lib/routes/youtube/subscriptions.ts +++ b/lib/routes/youtube/subscriptions.ts @@ -4,6 +4,7 @@ import { config } from '@/config'; import utils from './utils'; import { parseDate } from '@/utils/parse-date'; import asyncPool from 'tiny-async-pool'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/subscriptions/:embed?', @@ -44,7 +45,7 @@ export const route: Route = { async function handler(ctx) { if (!config.youtube || !config.youtube.key || !config.youtube.clientId || !config.youtube.clientSecret || !config.youtube.refreshToken) { - throw new Error('YouTube RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('YouTube RSS is disabled due to the lack of relevant config'); } const embed = !ctx.req.param('embed'); diff --git a/lib/routes/youtube/user.ts b/lib/routes/youtube/user.ts index bc910c34418f7f..bd1d1e6ecd52ae 100644 --- a/lib/routes/youtube/user.ts +++ b/lib/routes/youtube/user.ts @@ -5,6 +5,7 @@ import { config } from '@/config'; import { parseDate } from '@/utils/parse-date'; import got from '@/utils/got'; import { load } from 'cheerio'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/user/:username/:embed?', @@ -32,7 +33,7 @@ export const route: Route = { async function handler(ctx) { if (!config.youtube || !config.youtube.key) { - throw new Error('YouTube RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('YouTube RSS is disabled due to the lack of relevant config'); } const username = ctx.req.param('username'); const embed = !ctx.req.param('embed'); diff --git a/lib/routes/zcool/user.ts b/lib/routes/zcool/user.ts index abdc135b3f6c8a..beac412846ad91 100644 --- a/lib/routes/zcool/user.ts +++ b/lib/routes/zcool/user.ts @@ -5,6 +5,7 @@ import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; import { extractArticle, extractWork } from './utils'; import { isValidHost } from '@/utils/valid-host'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/user/:uid', @@ -40,7 +41,7 @@ async function handler(ctx) { let pageUrl = `https://www.zcool.com.cn/u/${uid}`; if (isNaN(uid)) { if (!isValidHost(uid)) { - throw new Error('Invalid uid'); + throw new InvalidParameterError('Invalid uid'); } pageUrl = `https://${uid}.zcool.com.cn`; } diff --git a/lib/routes/zhihu/timeline.ts b/lib/routes/zhihu/timeline.ts index 1c3aaab4e6ccdf..cdd7f28a40f742 100644 --- a/lib/routes/zhihu/timeline.ts +++ b/lib/routes/zhihu/timeline.ts @@ -3,6 +3,7 @@ import got from '@/utils/got'; import { config } from '@/config'; import utils from './utils'; import { parseDate } from '@/utils/parse-date'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; export const route: Route = { path: '/timeline', @@ -33,7 +34,7 @@ export const route: Route = { async function handler(ctx) { const cookie = config.zhihu.cookies; if (cookie === undefined) { - throw new Error('缺少知乎用户登录后的 Cookie 值'); + throw new ConfigNotFoundError('缺少知乎用户登录后的 Cookie 值'); } const response = await got({ method: 'get', diff --git a/lib/routes/zhubai/index.ts b/lib/routes/zhubai/index.ts index 64df930d372317..13db57d4377a1f 100644 --- a/lib/routes/zhubai/index.ts +++ b/lib/routes/zhubai/index.ts @@ -1,3 +1,4 @@ +import InvalidParameterError from '@/errors/types/invalid-parameter'; import { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; @@ -28,7 +29,7 @@ async function handler(ctx) { const id = ctx.req.param('id'); const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 20; if (!isValidHost(id)) { - throw new Error('Invalid id'); + throw new InvalidParameterError('Invalid id'); } const response = await got({ diff --git a/lib/routes/zjol/paper.ts b/lib/routes/zjol/paper.ts index 11b3b0eacc3ee3..d1d988312eb83d 100644 --- a/lib/routes/zjol/paper.ts +++ b/lib/routes/zjol/paper.ts @@ -3,6 +3,7 @@ import cache from '@/utils/cache'; import got from '@/utils/got'; import { load } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; export const route: Route = { path: '/paper/:id?', @@ -31,7 +32,7 @@ async function handler(ctx) { const allowedId = ['zjrb', 'qjwb', 'msb', 'zjlnb', 'zjfzb', 'jnyb']; if (!allowedId.includes(id)) { - throw new Error('id not allowed'); + throw new InvalidParameterError('id not allowed'); } const query = id === 'jnyb' ? 'map[name="PagePicMap"] area' : 'ul.main-ed-articlenav-list li a'; diff --git a/lib/routes/zodgame/forum.ts b/lib/routes/zodgame/forum.ts index 164945729371bd..4a49c33700dda1 100644 --- a/lib/routes/zodgame/forum.ts +++ b/lib/routes/zodgame/forum.ts @@ -8,6 +8,7 @@ import { config } from '@/config'; import { parseDate } from '@/utils/parse-date'; import { art } from '@/utils/render'; import path from 'node:path'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; const rootUrl = 'https://zodgame.xyz'; @@ -40,7 +41,7 @@ async function handler(ctx) { const cookie = config.zodgame.cookie; if (cookie === undefined) { - throw new Error('Zodgame RSS is disabled due to the lack of relevant config'); + throw new ConfigNotFoundError('Zodgame RSS is disabled due to the lack of relevant config'); } const response = await got({ From c145a725909fe4b33ca1874390e43c3c6b46ffe1 Mon Sep 17 00:00:00 2001 From: DIYgod Date: Sun, 7 Apr 2024 17:46:46 +0800 Subject: [PATCH 07/11] test: add invalid-parameter-error and config-not-found-error --- lib/errors/index.test.ts | 26 +++++++++++++++++++++----- lib/routes/test/index.ts | 8 ++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/errors/index.test.ts b/lib/errors/index.test.ts index dc58186aa7696a..e1af5105878618 100644 --- a/lib/errors/index.test.ts +++ b/lib/errors/index.test.ts @@ -35,6 +35,22 @@ describe('RequestInProgressError', () => { }); }); +describe('config-not-found-error', () => { + it(`config-not-found-error`, async () => { + const response = await request.get('/test/config-not-found-error'); + expect(response.status).toBe(503); + expect(response.text).toMatch('ConfigNotFoundError: Test config not found error'); + }, 20000); +}); + +describe('invalid-parameter-error', () => { + it(`invalid-parameter-error`, async () => { + const response = await request.get('/test/invalid-parameter-error'); + expect(response.status).toBe(503); + expect(response.text).toMatch('InvalidParameterError: Test invalid parameter error'); + }, 20000); +}); + describe('route throws an error', () => { it('route path error should have path mounted', async () => { await request.get('/test/error'); @@ -47,19 +63,19 @@ describe('route throws an error', () => { const value = $(item).find('.debug-value').html()?.trim(); switch (key) { case 'Request Amount:': - expect(value).toBe('7'); + expect(value).toBe('9'); break; case 'Hot Routes:': - expect(value).toBe('4 /test/:id
'); + expect(value).toBe('6 /test/:id
'); break; case 'Hot Paths:': - expect(value).toBe('2 /test/error
2 /test/slow
1 /test/httperror
1 /thisDoesNotExist
1 /
'); + expect(value).toBe('2 /test/error
2 /test/slow
1 /test/httperror
1 /test/config-not-found-error
1 /test/invalid-parameter-error
1 /thisDoesNotExist
1 /
'); break; case 'Hot Error Routes:': - expect(value).toBe('3 /test/:id
'); + expect(value).toBe('5 /test/:id
'); break; case 'Hot Error Paths:': - expect(value).toBe('2 /test/error
1 /test/httperror
1 /test/slow
1 /thisDoesNotExist
'); + expect(value).toBe('2 /test/error
1 /test/httperror
1 /test/slow
1 /test/config-not-found-error
1 /test/invalid-parameter-error
1 /thisDoesNotExist
'); break; default: } diff --git a/lib/routes/test/index.ts b/lib/routes/test/index.ts index 752fad595758f0..138e3691b4896c 100644 --- a/lib/routes/test/index.ts +++ b/lib/routes/test/index.ts @@ -3,6 +3,8 @@ import { config } from '@/config'; import got from '@/utils/got'; import wait from '@/utils/wait'; import cache from '@/utils/cache'; +import ConfigNotFoundError from '@/errors/types/config-not-found'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; let cacheIndex = 0; @@ -23,6 +25,12 @@ async function handler(ctx) { url: 'https://httpbingo.org/status/404', }); } + if (ctx.req.param('id') === 'config-not-found-error') { + throw new ConfigNotFoundError('Test config not found error'); + } + if (ctx.req.param('id') === 'invalid-parameter-error') { + throw new InvalidParameterError('Test invalid parameter error'); + } let item: DataItem[] = []; switch (ctx.req.param('id')) { case 'filter': From ccbe3990023fab2d3de11d0fe728bccce72099b6 Mon Sep 17 00:00:00 2001 From: Felix Hsu Date: Sun, 7 Apr 2024 19:35:01 +0800 Subject: [PATCH 08/11] fix(route): the Bloomberg RSS doesn't work after using the new ofetch as the 'got' lib (#15112) * Fix the Bloomberg RSS by using the old/deprecated 'got' because the 'redirectUrls' feature is not supported in the new 'got' library. * Use the redirected prop in RawResponse * Use the ofetch.raw * Update lib/routes/bloomberg/index.ts Reformat the desc of Route Co-authored-by: Tony --------- --- lib/routes/bloomberg/index.ts | 35 ++++++++++++++++++++++--- lib/routes/bloomberg/utils.ts | 48 ++++++++++++++++++++++------------- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/lib/routes/bloomberg/index.ts b/lib/routes/bloomberg/index.ts index d6477af221c703..9a0b8d43a39c6c 100644 --- a/lib/routes/bloomberg/index.ts +++ b/lib/routes/bloomberg/index.ts @@ -16,9 +16,38 @@ const site_title_mapping = { }; export const route: Route = { - path: ['/:site', '/'], - name: 'Unknown', - maintainers: [], + path: '/:site?', + categories: ['finance'], + example: '/bloomberg/bbiz', + parameters: { + site: 'Site ID, can be found below', + }, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: true, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + name: 'Bloomberg Site', + maintainers: ['bigfei'], + description: ` + | Site ID | Title | + | ------------ | ------------ | + | / | News | + | bpol | Politics | + | bbiz | Business | + | markets | Markets | + | technology | Technology | + | green | Green | + | wealth | Wealth | + | pursuits | Pursuits | + | bview | Opinion | + | equality | Equality | + | businessweek | Businessweek | + | citylab | CityLab | + `, handler, }; diff --git a/lib/routes/bloomberg/utils.ts b/lib/routes/bloomberg/utils.ts index 377eef2219e54c..6a0ef7cd47cc4a 100644 --- a/lib/routes/bloomberg/utils.ts +++ b/lib/routes/bloomberg/utils.ts @@ -5,9 +5,11 @@ import cache from '@/utils/cache'; import { load } from 'cheerio'; import path from 'node:path'; import asyncPool from 'tiny-async-pool'; +import { destr } from 'destr'; import { parseDate } from '@/utils/parse-date'; import got from '@/utils/got'; +import ofetch from '@/utils/ofetch'; import { art } from '@/utils/render'; const rootUrl = 'https://www.bloomberg.com/feeds'; @@ -60,6 +62,15 @@ const regex = [pageTypeRegex1, pageTypeRegex2]; const capRegex = /

|<\/p>/g; const emptyRegex = /]*>( |\s)<\/p>/g; +const redirectGot = (url) => + ofetch.raw(url, { + headers, + parseResponse: (responseText) => ({ + data: destr(responseText), + body: responseText, + }), + }); + const parseNewsList = async (url, ctx) => { const resp = await got(url); const $ = load(resp.data, { @@ -96,12 +107,12 @@ const parseArticle = (item) => try { const apiUrl = `${api.url}${link}`; - res = await got(apiUrl, { headers }); + res = await redirectGot(apiUrl); } catch (error) { // fallback if (error.name && (error.name === 'HTTPError' || error.name === 'RequestError' || error.name === 'FetchError')) { try { - res = await got(item.link, { headers }); + res = await redirectGot(item.link); } catch { // return the default one return { @@ -114,8 +125,7 @@ const parseArticle = (item) => } // Blocked by PX3, or 404 by both api and direct link, return the default - const redirectUrls = res.redirectUrls.map(String); - if (redirectUrls.some((r) => new URL(r).pathname === '/tosv2.html') || res.statusCode === 404) { + if ((res.redirected && new URL(res.url).pathname === '/tosv2.html') || res.status === 404) { return { title: item.title, link: item.link, @@ -125,15 +135,15 @@ const parseArticle = (item) => switch (page) { case 'audio': - return parseAudioPage(res, api, item); + return parseAudioPage(res._data, api, item); case 'videos': - return parseVideoPage(res, api, item); + return parseVideoPage(res._data, api, item); case 'photo-essays': - return parsePhotoEssaysPage(res, api, item); + return parsePhotoEssaysPage(res._data, api, item); case 'features/': // single features page - return parseReactRendererPage(res, api, item); + return parseReactRendererPage(res._data, api, item); default: // use story api to get json - return parseStoryJson(res.data, item); + return parseStoryJson(res._data.data, item); } } } @@ -210,8 +220,8 @@ const parseReactRendererPage = async (res, api, item) => { const json = load(res.data)(api.sel).text().trim(); const story_id = JSON.parse(json)[api.prop]; try { - const res = await got(`${idUrl}${story_id}`, { headers }); - return await parseStoryJson(res.data, item); + const res = await redirectGot(`${idUrl}${story_id}`); + return await parseStoryJson(res._data, item); } catch (error) { // fallback if (error.name && (error.name === 'HTTPError' || error.name === 'RequestError' || error.name === 'FetchError')) { @@ -364,11 +374,10 @@ const processBody = async (body_html, story_json) => { const processVideo = async (bmmrId, summary) => { const api = `https://www.bloomberg.com/multimedia/api/embed?id=${bmmrId}`; - const res = await got(api, { headers }); + const res = await redirectGot(api); // Blocked by PX3, return the default - const redirectUrls = res.redirectUrls.map(String); - if (redirectUrls.some((r) => new URL(r).pathname === '/tosv2.html')) { + if ((res.redirected && new URL(res.url).pathname === '/tosv2.html') || res.status === 404) { return { stream: '', mp4: '', @@ -377,8 +386,8 @@ const processVideo = async (bmmrId, summary) => { }; } - if (res.data) { - const video_json = res.data; + if (res._data.data) { + const video_json = res._data.data; return { stream: video_json.streams ? video_json.streams[0]?.url : '', mp4: video_json.downloadURLs ? video_json.downloadURLs['600'] : '', @@ -386,7 +395,12 @@ const processVideo = async (bmmrId, summary) => { caption: video_json.description || video_json.title || summary, }; } - return {}; + return { + stream: '', + mp4: '', + coverUrl: '', + caption: summary, + }; }; const nodeRenderers = { From b26a7044f7163cb13883910b1e7810d419d54151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luna=20Mikaelsd=C3=B3ttir?= <73906201+LM1207@users.noreply.github.com> Date: Sun, 7 Apr 2024 21:19:47 +0800 Subject: [PATCH 09/11] feat(route): add atptour (#15006) * feat(route): add atptour * fix: radix * fix: remove puppeteer --- lib/routes/atptour/namespace.ts | 7 +++++ lib/routes/atptour/news.ts | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 lib/routes/atptour/namespace.ts create mode 100644 lib/routes/atptour/news.ts diff --git a/lib/routes/atptour/namespace.ts b/lib/routes/atptour/namespace.ts new file mode 100644 index 00000000000000..5a0805bd67a7d5 --- /dev/null +++ b/lib/routes/atptour/namespace.ts @@ -0,0 +1,7 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'ATP Tour', + url: 'www.atptour.com', + description: "News from the official site of men's professional tennis.", +}; diff --git a/lib/routes/atptour/news.ts b/lib/routes/atptour/news.ts new file mode 100644 index 00000000000000..a5698bdd3412f4 --- /dev/null +++ b/lib/routes/atptour/news.ts @@ -0,0 +1,52 @@ +import { Route } from '@/types'; +import { parseDate } from '@/utils/parse-date'; +import got from '@/utils/got'; +import { config } from '@/config'; + +export const route: Route = { + path: '/news/:lang?', + categories: ['other'], + example: '/atptour/news/en', + parameters: { lang: 'en or es.' }, + radar: [ + { + source: ['atptour.com'], + }, + ], + name: 'News', + maintainers: ['LM1207'], + handler, +}; + +async function handler(ctx) { + const baseUrl = 'https://www.atptour.com'; + const favIcon = `${baseUrl}/assets/atptour/assets/favicon.ico`; + const { lang = 'en' } = ctx.req.param(); + const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 15; + + const link = `${baseUrl}/${lang}/-/tour/news/latest-filtered-results/0/${limit}`; + const { data } = await got(link, { + headers: { + 'user-agent': config.trueUA, + }, + }); + + return { + title: lang === 'en' ? 'News' : 'Noticias', + link: `${baseUrl}/${lang}/news/news-filter-results`, + description: lang === 'en' ? "News from the official site of men's professional tennis." : 'Noticias del sitio oficial del tenis profesional masculino.', + language: lang, + icon: favIcon, + logo: favIcon, + author: 'ATP', + item: data.content.map((item) => ({ + title: item.title, + link: baseUrl + item.url, + description: item.description, + author: item.byline, + category: item.category, + pubDate: parseDate(item.authoredDate), + image: baseUrl + item.image, + })), + }; +} From d7c23dcff5c906052544bb68c5ed2185f8304c29 Mon Sep 17 00:00:00 2001 From: DIYgod Date: Sun, 7 Apr 2024 22:15:17 +0800 Subject: [PATCH 10/11] fix(bilibili): vsearch, close #15105 --- lib/routes/bilibili/vsearch.ts | 57 ++++++++++++++++------------------ 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/lib/routes/bilibili/vsearch.ts b/lib/routes/bilibili/vsearch.ts index 76fcba44b8b3bf..63b601cdceb6fd 100644 --- a/lib/routes/bilibili/vsearch.ts +++ b/lib/routes/bilibili/vsearch.ts @@ -1,12 +1,9 @@ import { Route } from '@/types'; -import cache from '@/utils/cache'; -import { config } from '@/config'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import utils from './utils'; -import { CookieJar } from 'tough-cookie'; -const cookieJar = new CookieJar(); import { queryToBoolean } from '@/utils/readable-social'; +import cacheIn from './cache'; export const route: Route = { path: '/vsearch/:kw/:order?/:disableEmbed?/:tid?', @@ -14,7 +11,17 @@ export const route: Route = { example: '/bilibili/vsearch/RSSHub', parameters: { kw: '检索关键字', order: '排序方式, 综合:totalrank 最多点击:click 最新发布:pubdate(缺省) 最多弹幕:dm 最多收藏:stow', disableEmbed: '默认为开启内嵌视频, 任意值为关闭', tid: '分区 id' }, features: { - requireConfig: false, + requireConfig: [ + { + name: 'BILIBILI_COOKIE_*', + optional: true, + description: `如果没有此配置,那么必须开启 puppeteer 支持;BILIBILI_COOKIE_{uid}: 用于用户关注动态系列路由,对应 uid 的 b 站用户登录后的 Cookie 值,\`{uid}\` 替换为 uid,如 \`BILIBILI_COOKIE_2267573\`,获取方式: +1. 打开 [https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid=0&type=8](https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid=0&type=8) +2. 打开控制台,切换到 Network 面板,刷新 +3. 点击 dynamic_new 请求,找到 Cookie +4. 视频和专栏,UP 主粉丝及关注只要求 \`SESSDATA\` 字段,动态需复制整段 Cookie`, + }, + ], requirePuppeteer: false, antiCrawler: false, supportBT: false, @@ -22,7 +29,7 @@ export const route: Route = { supportScihub: false, }, name: '视频搜索', - maintainers: ['Symty'], + maintainers: ['Symty', 'DIYgod'], handler, description: `分区 id 的取值请参考下表: @@ -37,32 +44,22 @@ async function handler(ctx) { const disableEmbed = queryToBoolean(ctx.req.param('disableEmbed')); const kw_url = encodeURIComponent(kw); const tids = ctx.req.param('tid') ?? 0; + const cookie = await cacheIn.getCookie(); - const data = await cache.tryGet( - `bilibili:vsearch:${tids}:${kw}:${order}`, - async () => { - await got('https://passport.bilibili.com/login', { - cookieJar, - }); - - const response = await got('https://api.bilibili.com/x/web-interface/search/type', { - headers: { - Referer: `https://search.bilibili.com/all?keyword=${kw_url}`, - }, - cookieJar, - searchParams: { - search_type: 'video', - highlight: 1, - keyword: kw, - order, - tids, - }, - }); - return response.data.data.result; + const response = await got('https://api.bilibili.com/x/web-interface/search/type', { + headers: { + Referer: `https://search.bilibili.com/all?keyword=${kw_url}`, + Cookie: cookie, + }, + searchParams: { + search_type: 'video', + highlight: 1, + keyword: kw, + order, + tids, }, - config.cache.routeExpire, - false - ); + }); + const data = response.data.data.result; return { title: `${kw} - bilibili`, From b3a7186a1e6ea8301daf17aa3310b93585792bf3 Mon Sep 17 00:00:00 2001 From: Tony Date: Sun, 7 Apr 2024 22:21:23 +0800 Subject: [PATCH 11/11] docs: fix author github id #1911, [skip ci] --- lib/routes/bilibili/vsearch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/routes/bilibili/vsearch.ts b/lib/routes/bilibili/vsearch.ts index 63b601cdceb6fd..e76126756bd065 100644 --- a/lib/routes/bilibili/vsearch.ts +++ b/lib/routes/bilibili/vsearch.ts @@ -29,7 +29,7 @@ export const route: Route = { supportScihub: false, }, name: '视频搜索', - maintainers: ['Symty', 'DIYgod'], + maintainers: ['pcrtool', 'DIYgod'], handler, description: `分区 id 的取值请参考下表: