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..e1af5105878618 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,10 +31,26 @@ 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); }); }); +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/errors/index.tsx b/lib/errors/index.tsx index 2f16d0512d4d79..56ba079e455fc3 100644 --- a/lib/errors/index.tsx +++ b/lib/errors/index.tsx @@ -5,9 +5,7 @@ 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'; +import NotFoundError from './types/not-found'; export const errorHandler: ErrorHandler = (error, ctx) => { const requestPath = ctx.req.path; @@ -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 deleted file mode 100644 index 2ba6e18b3e1945..00000000000000 --- a/lib/errors/not-found.ts +++ /dev/null @@ -1,3 +0,0 @@ -class NotFoundError extends Error {} - -export default NotFoundError; diff --git a/lib/errors/reject.ts b/lib/errors/reject.ts deleted file mode 100644 index 6296482265cae3..00000000000000 --- a/lib/errors/reject.ts +++ /dev/null @@ -1,3 +0,0 @@ -class RejectError extends Error {} - -export default RejectError; diff --git a/lib/errors/request-in-progress.ts b/lib/errors/request-in-progress.ts deleted file mode 100644 index 99118977e8e77f..00000000000000 --- a/lib/errors/request-in-progress.ts +++ /dev/null @@ -1,3 +0,0 @@ -class RequestInProgressError extends Error {} - -export default RequestInProgressError; 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/errors/types/not-found.ts b/lib/errors/types/not-found.ts new file mode 100644 index 00000000000000..9cba16b31e797a --- /dev/null +++ b/lib/errors/types/not-found.ts @@ -0,0 +1,5 @@ +class NotFoundError extends Error { + name = 'NotFoundError'; +} + +export default NotFoundError; diff --git a/lib/errors/types/reject.ts b/lib/errors/types/reject.ts new file mode 100644 index 00000000000000..b6b91fe4967c4a --- /dev/null +++ b/lib/errors/types/reject.ts @@ -0,0 +1,5 @@ +class RejectError extends Error { + name = 'RejectError'; +} + +export default RejectError; diff --git a/lib/errors/types/request-in-progress.ts b/lib/errors/types/request-in-progress.ts new file mode 100644 index 00000000000000..73ae4b5705d37c --- /dev/null +++ b/lib/errors/types/request-in-progress.ts @@ -0,0 +1,5 @@ +class RequestInProgressError extends Error { + name = 'RequestInProgressError'; +} + +export default RequestInProgressError; 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'; 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.'); }); }); 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/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': 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({