Skip to content

Commit

Permalink
Merge pull request #129 from DIYgod/master
Browse files Browse the repository at this point in the history
[pull] master from diygod:master
  • Loading branch information
pull[bot] authored Mar 5, 2024
2 parents 1fafa1d + 066ee90 commit 31e47eb
Show file tree
Hide file tree
Showing 2,429 changed files with 638 additions and 2,682 deletions.
51 changes: 26 additions & 25 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"extends": ["eslint:recommended", "plugin:n/recommended", "plugin:unicorn/recommended", "plugin:prettier/recommended", "plugin:yml/recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"root": true,
"plugins": ["prettier", "@stylistic/js", "unicorn", "@typescript-eslint"],
"plugins": ["prettier", "@stylistic", "unicorn", "@typescript-eslint"],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
Expand All @@ -18,6 +18,7 @@
"no-await-in-loop": 2,
"no-control-regex": 0,
"no-duplicate-imports": 2,
"no-prototype-builtins": 0,
// suggestions
"arrow-body-style": 2,
"block-scoped-var": 2,
Expand Down Expand Up @@ -47,6 +48,10 @@
"prefer-object-has-own": 2,
"prefer-regex-literals": ["error", { "disallowRedundantWrapping": true }],
"require-await": 2,
// typescript
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-var-requires": 0,
// plugin specific
"unicorn/consistent-destructuring": 1,
"unicorn/consistent-function-scoping": 1,
Expand Down Expand Up @@ -89,37 +94,33 @@
"unicorn/switch-case-braces": ["error", "avoid"],
"unicorn/text-encoding-identifier-case": 0,
// previous eslint formatting rules
"@stylistic/js/arrow-parens": 2,
"@stylistic/js/arrow-spacing": 2,
"@stylistic/js/comma-spacing": 2,
"@stylistic/js/comma-style": 2,
"@stylistic/js/function-call-spacing": 2,
"@stylistic/js/keyword-spacing": 2,
"@stylistic/js/linebreak-style": 2,
"@stylistic/js/lines-around-comment": ["error", { "beforeBlockComment": false }],
"@stylistic/js/no-multiple-empty-lines": 2,
"@stylistic/js/no-trailing-spaces": 2,
"@stylistic/js/rest-spread-spacing": 2,
"@stylistic/js/semi": 2,
"@stylistic/js/space-before-blocks": 2,
"@stylistic/js/space-in-parens": 2,
"@stylistic/js/space-infix-ops": 2,
"@stylistic/js/space-unary-ops": 2,
"@stylistic/js/spaced-comment": 2,
"@stylistic/arrow-parens": 2,
"@stylistic/arrow-spacing": 2,
"@stylistic/comma-spacing": 2,
"@stylistic/comma-style": 2,
"@stylistic/function-call-spacing": 2,
"@stylistic/keyword-spacing": 2,
"@stylistic/linebreak-style": 2,
"@stylistic/lines-around-comment": ["error", { "beforeBlockComment": false }],
"@stylistic/no-multiple-empty-lines": 2,
"@stylistic/no-trailing-spaces": 2,
"@stylistic/rest-spread-spacing": 2,
"@stylistic/semi": 2,
"@stylistic/space-before-blocks": 2,
"@stylistic/space-in-parens": 2,
"@stylistic/space-infix-ops": 2,
"@stylistic/space-unary-ops": 2,
"@stylistic/spaced-comment": 2,
// https://github.com/eslint-community/eslint-plugin-n
"n/no-extraneous-require": ["error", { "allowModules": ["puppeteer-extra-plugin-user-preferences", "puppeteer-extra-plugin-user-data-dir"] }],
"n/no-deprecated-api": 1,
"n/no-missing-import": 0,
"n/no-missing-require": 0,
"n/no-process-exit": 0,
"n/no-unpublished-import": 0,
"n/no-unpublished-require": ["error", { "allowModules": ["tosource"] }],
"prettier/prettier": 0,
"yml/quotes": ["error", { "prefer": "single" }],
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/no-explicit-any": 0,
"n/no-missing-import": 0,
"n/no-unpublished-import": 0,
"no-prototype-builtins": 0,
"@typescript-eslint/ban-ts-comment": 0
"yml/quotes": ["error", { "prefer": "single" }]
},
"overrides": [
{
Expand Down
2 changes: 1 addition & 1 deletion .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

'Route':
- changed-files:
- any-glob-to-any-file: ['lib/routes/**/*.js']
- any-glob-to-any-file: ['lib/routes/**/*.ts']

core enhancement:
- changed-files:
Expand Down
8 changes: 3 additions & 5 deletions lib/errors/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,16 @@ afterAll(() => {
describe('error', () => {
it(`error`, async () => {
const response = await request.get('/test/error');
expect(response.status).toBe(404);
expect(response.status).toBe(503);
expect(response.text).toMatch(/Error: Error test/);
});
});

describe('httperror', () => {
it(`httperror`, async () => {
const response = await request.get('/test/httperror');
expect(response.status).toBe(404);
expect(response.text).toMatch(
/Response code 404 \(Not Found\): target website might be blocking our access, you can <a href="https:\/\/docs\.rsshub\.app\/install\/">host your own RSSHub instance<\/a> for a better usability\./
);
expect(response.status).toBe(503);
expect(response.text).toMatch('Response code 404 (Not Found): target website might be blocking our access, you can host your own RSSHub instance for a better usability.');
}, 20000);
});

Expand Down
78 changes: 35 additions & 43 deletions lib/errors/index.ts → lib/errors/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,16 @@ import { getDebugInfo, setDebugInfo } from '@/utils/debug-info';
import { config } from '@/config';
import Sentry from '@sentry/node';
import logger from '@/utils/logger';
import art from 'art-template';
import * as path from 'node:path';
import { gitHash } from '@/utils/git-hash';
import Error from '@/views/error';

import RequestInProgressError from './request-in-progress';
import RejectError from './reject';
import NotFoundError from './not-found';

import { getCurrentPath } from '@/utils/helpers';
const __dirname = getCurrentPath(import.meta.url);

export const errorHandler: ErrorHandler = (error, ctx) => {
const requestPath = ctx.req.path;
const matchedRoute = ctx.req.routePath;
const hasMatchedRoute = matchedRoute !== '/*';
let message = '';
if (error.name && (error.name === 'HTTPError' || error.name === 'RequestError')) {
message = `${error.message}: target website might be blocking our access, you can <a href="https://docs.rsshub.app/install/">host your own RSSHub instance</a> for a better usability.`;
} else if (error instanceof Error) {
message = process.env.NODE_ENV === 'production' ? error.message : error.stack || error.message;
}

const debug = getDebugInfo();
if (ctx.res.headers.get('RSSHub-Cache-Status')) {
Expand All @@ -49,39 +38,42 @@ export const errorHandler: ErrorHandler = (error, ctx) => {
});
}

logger.error(`Error in ${requestPath}: ${message}`);

if (config.isPackage) {
return ctx.json({
error: {
message: error.message ?? error,
},
});
} 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';
} else {
ctx.status(404);
let message = '';
if (error.name && (error.name === 'HTTPError' || error.name === 'RequestError')) {
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';
}

return ctx.html(
art(path.resolve(__dirname, '../views/error.art'), {
requestPath,
message,
errorRoute: hasMatchedRoute ? matchedRoute : requestPath,
nodeVersion: process.version,
gitHash,
})
);
} else {
ctx.status(503);
message = process.env.NODE_ENV === 'production' ? error.message : error.stack || error.message;
}

logger.error(`Error in ${requestPath}: ${message}`);

return config.isPackage ? ctx.json({
error: {
message: error.message ?? error,
},
}) : ctx.html((
<Error
requestPath={requestPath}
message={message}
errorRoute={hasMatchedRoute ? matchedRoute : requestPath}
nodeVersion={process.version}
/>
));
};

export const notFoundHandler: NotFoundHandler = (ctx) => errorHandler(new NotFoundError(), ctx);
9 changes: 6 additions & 3 deletions lib/middleware/anti-hotlink.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const expects = {
<img data-mock="/DIYgod/RSSHub.png" src="https://mock.com/DIYgod/RSSHub.png" referrerpolicy="no-referrer">
<img mock="/DIYgod/RSSHub.gif" src="https://mock.com/DIYgod/RSSHub.gif" referrerpolicy="no-referrer">
<img src="http://mock.com/DIYgod/DIYgod/RSSHub" referrerpolicy="no-referrer">
<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">`,
<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" referrerpolicy="no-referrer">`,
`<a href="https://mock.com/DIYgod/RSSHub"></a>
<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">`,
],
Expand All @@ -48,7 +49,8 @@ const expects = {
<img data-mock="/DIYgod/RSSHub.png" src="https://i3.wp.com/mock.com/DIYgod/RSSHub.png" referrerpolicy="no-referrer">
<img mock="/DIYgod/RSSHub.gif" src="https://i3.wp.com/mock.com/DIYgod/RSSHub.gif" referrerpolicy="no-referrer">
<img src="https://i3.wp.com/mock.com/DIYgod/DIYgod/RSSHub" referrerpolicy="no-referrer">
<img src="https://i3.wp.com/mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">`,
<img src="https://i3.wp.com/mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" referrerpolicy="no-referrer">`,
`<a href="https://mock.com/DIYgod/RSSHub"></a>
<img src="https://i3.wp.com/mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">`,
],
Expand All @@ -65,7 +67,8 @@ const expects = {
<img data-mock="/DIYgod/RSSHub.png" src="https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.png" referrerpolicy="no-referrer">
<img mock="/DIYgod/RSSHub.gif" src="https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.gif" referrerpolicy="no-referrer">
<img src="https://images.weserv.nl?url=http%3A%2F%2Fmock.com%2FDIYgod%2FDIYgod%2FRSSHub" referrerpolicy="no-referrer">
<img src="https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.jpg" referrerpolicy="no-referrer">`,
<img src="https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.jpg" referrerpolicy="no-referrer">
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" referrerpolicy="no-referrer">`,
`<a href="https://mock.com/DIYgod/RSSHub"></a>
<img src="https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.jpg" referrerpolicy="no-referrer">`,
],
Expand Down
28 changes: 14 additions & 14 deletions lib/middleware/anti-hotlink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ const parseUrl = (str: string) => {
};
const replaceUrls = ($: CheerioAPI, selector: string, template: string, attribute = 'src') => {
$(selector).each(function () {
const old_src = $(this).attr(attribute);
if (old_src) {
const url = parseUrl(old_src);
if (url) {
const oldSrc = $(this).attr(attribute);
if (oldSrc) {
const url = parseUrl(oldSrc);
if (url && url.protocol !== 'data:') {
// Cheerio will do the right thing to prohibit XSS.
$(this).attr(attribute, interpolate(template, url));
}
Expand Down Expand Up @@ -88,8 +88,8 @@ const validateTemplate = (template?: string) => {
const middleware: MiddlewareHandler = async (ctx, next) => {
await next();

let image_hotlink_template;
let multimedia_hotlink_template;
let imageHotlinkTemplate: string | undefined;
let multimediaHotlinkTemplate: string | undefined;

// Read params if enabled
if (config.feature.allow_user_hotlink_template) {
Expand All @@ -98,21 +98,21 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
// A risk is that the media URLs will be replaced by user-supplied templates,
// so a user could literally take the control of "where are the media from",
// but only in their personal-use feed URL.
multimedia_hotlink_template = ctx.req.query('multimedia_hotlink_template');
image_hotlink_template = ctx.req.query('image_hotlink_template');
multimediaHotlinkTemplate = ctx.req.query('multimedia_hotlink_template');
imageHotlinkTemplate = ctx.req.query('image_hotlink_template');
}

// Force config hotlink template on conflict
if (config.hotlink.template) {
image_hotlink_template = filterPath(ctx.req.path) ? config.hotlink.template : undefined;
imageHotlinkTemplate = filterPath(ctx.req.path) ? config.hotlink.template : undefined;
}

if (!image_hotlink_template && !multimedia_hotlink_template) {
if (!imageHotlinkTemplate && !multimediaHotlinkTemplate) {
return;
}

validateTemplate(image_hotlink_template);
validateTemplate(multimedia_hotlink_template);
validateTemplate(imageHotlinkTemplate);
validateTemplate(multimediaHotlinkTemplate);

// Assume that only description include image link
// and here we will only check them in description.
Expand All @@ -121,13 +121,13 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
const data: Data = ctx.get('data');
if (data) {
if (data.description) {
data.description = process(data.description, image_hotlink_template, multimedia_hotlink_template);
data.description = process(data.description, imageHotlinkTemplate, multimediaHotlinkTemplate);
}

if (data.item) {
for (const item of data.item) {
if (item.description) {
item.description = process(item.description, image_hotlink_template, multimedia_hotlink_template);
item.description = process(item.description, imageHotlinkTemplate, multimediaHotlinkTemplate);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions lib/middleware/filter-engine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('filter-engine', () => {
const app = (await import('@/app')).default;

const response = await app.request('/test/1?filter=abc(%3F%3Ddef)');
expect(response.status).toBe(404);
expect(response.status).toBe(503);
expect(await response.text()).toMatch(/RE2JSSyntaxException/);
});

Expand All @@ -33,7 +33,7 @@ describe('filter-engine', () => {
const app = (await import('@/app')).default;

const response = await app.request('/test/1?filter=abc(%3F%3Ddef)');
expect(response.status).toBe(404);
expect(response.status).toBe(503);
expect(await response.text()).toMatch(/somethingelse/);
});
});
7 changes: 4 additions & 3 deletions lib/middleware/parameter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ describe('tgiv', () => {
describe('empty', () => {
it(`empty`, async () => {
const response1 = await app.request('/test/empty');
expect(response1.status).toBe(404);
expect(response1.status).toBe(503);
expect(await response1.text()).toMatch(/Error: this route is empty/);

const response2 = await app.request('/test/1?limit=0');
Expand All @@ -320,7 +320,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(/Error message: wrong path/);
expect(await response.text()).toMatch('wrong path');
});
});

Expand All @@ -347,7 +347,8 @@ describe('complicated_description', () => {
<img data-mock="/DIYgod/RSSHub.png" src="https://mock.com/DIYgod/RSSHub.png" referrerpolicy="no-referrer">
<img mock="/DIYgod/RSSHub.gif" src="https://mock.com/DIYgod/RSSHub.gif" referrerpolicy="no-referrer">
<img src="http://mock.com/DIYgod/DIYgod/RSSHub" referrerpolicy="no-referrer">
<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">`);
<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" referrerpolicy="no-referrer">`);
expect(parsed.items[1].content).toBe(`<a href="https://mock.com/DIYgod/RSSHub"></a>
<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">`);
});
Expand Down
6 changes: 3 additions & 3 deletions lib/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ router.get('/huya/live/:id', lazyloadRouteHandler('./routes/huya/live'));
// router.get('/v2ex/tab/:tabid', lazyloadRouteHandler('./routes/v2ex/tab'));

// f-droid
router.get('/fdroid/apprelease/:app', lazyloadRouteHandler('./routes/fdroid/apprelease'));
// router.get('/fdroid/apprelease/:app', lazyloadRouteHandler('./routes/fdroid/apprelease'));

// konachan
router.get('/konachan/post/popular_recent', lazyloadRouteHandler('./routes/konachan/post-popular-recent'));
Expand Down Expand Up @@ -928,7 +928,7 @@ router.get('/mcbbs/forum/:type', lazyloadRouteHandler('./routes/mcbbs/forum'));
router.get('/mcbbs/post/:tid/:authorid?', lazyloadRouteHandler('./routes/mcbbs/post'));

// 每日猪价
router.get('/pork-price', lazyloadRouteHandler('./routes/pork-price'));
// router.get('/pork-price', lazyloadRouteHandler('./routes/pork-price'));

// NOI 全国青少年信息学奥林匹克竞赛
router.get('/noi', lazyloadRouteHandler('./routes/noi'));
Expand Down Expand Up @@ -1591,7 +1591,7 @@ router.get('/dida365/habit/checkins', lazyloadRouteHandler('./routes/dida365/hab
router.get('/ditto/changes/:type?', lazyloadRouteHandler('./routes/ditto/changes'));

// iDaily 每日环球视野
router.get('/idaily/today', lazyloadRouteHandler('./routes/idaily/index'));
// router.get('/idaily/today', lazyloadRouteHandler('./routes/idaily/index'));

// Oak Ridge National Laboratory
router.get('/ornl/news', lazyloadRouteHandler('./routes/ornl/news'));
Expand Down
Loading

0 comments on commit 31e47eb

Please sign in to comment.