mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-10-13 16:57:27 +08:00
fix(route): add input validations (#23304)
* fix(route): add input validation * fix(route): more input validation * fix(route): improve handling of publication date and category extraction
This commit is contained in:
@@ -41,6 +41,15 @@ const fakeDirectories = {
|
||||
handler: (ctx: Context) => ({ title: String(ctx.get('fromOuter')), link: 'https://example.com', item: [], allowEmpty: true }),
|
||||
},
|
||||
},
|
||||
'/redirect.ts': {
|
||||
route: {
|
||||
path: '/redirect',
|
||||
name: 'Redirect',
|
||||
handler: (ctx: Context) => {
|
||||
ctx.set('redirect', '/flat/single');
|
||||
},
|
||||
},
|
||||
},
|
||||
'/boom.ts': {
|
||||
route: {
|
||||
path: '/boom',
|
||||
@@ -90,7 +99,7 @@ afterAll(() => {
|
||||
const buildApp = () => {
|
||||
const namespaces: NamespacesType = {};
|
||||
const dev = createDevRegistry({ routesDirectory, namespaces });
|
||||
const app = new Hono<{ Variables: { fromOuter: string; data: Data; apiData: { ok: boolean } } }>();
|
||||
const app = new Hono<{ Variables: { fromOuter: string; data: Data; apiData: { ok: boolean }; redirect: string } }>();
|
||||
app.use(async (ctx, next) => {
|
||||
ctx.set('fromOuter', 'bridged');
|
||||
await next();
|
||||
@@ -98,6 +107,10 @@ const buildApp = () => {
|
||||
if (apiData) {
|
||||
return ctx.json(apiData);
|
||||
}
|
||||
const redirect = ctx.get('redirect');
|
||||
if (redirect) {
|
||||
return ctx.redirect(redirect, 301);
|
||||
}
|
||||
const data = ctx.get('data');
|
||||
if (data) {
|
||||
return ctx.json(data);
|
||||
@@ -143,6 +156,13 @@ describe('createDevRegistry', () => {
|
||||
expect(body.title).toBe('bridged');
|
||||
});
|
||||
|
||||
it('finalizes handlers that only set a redirect', async () => {
|
||||
const { app } = buildApp();
|
||||
const response = await app.request('/flat/redirect');
|
||||
expect(response.status).toBe(301);
|
||||
expect(response.headers.get('location')).toBe('/flat/single');
|
||||
});
|
||||
|
||||
it('serves nested namespaces', async () => {
|
||||
const { app } = buildApp();
|
||||
const response = await app.request('/github/enterprise/news');
|
||||
|
||||
+2
-2
@@ -46,7 +46,7 @@ export function createDevRegistry({ routesDirectory, namespaces }: { routesDirec
|
||||
outer.set(key as never, value as never);
|
||||
}
|
||||
}
|
||||
if (!ctx.finalized && (ctx.get('data') || ctx.get('apiData'))) {
|
||||
if (!ctx.finalized && (ctx.get('data') || ctx.get('apiData') || ctx.get('redirect') || ctx.get('no-content'))) {
|
||||
// Data-producing handlers return undefined (the outer template middleware renders the
|
||||
// bridged vars); finalize so Hono does not raise "Context is not finalized".
|
||||
ctx.res = new Response(null, { status: 204 });
|
||||
@@ -104,7 +104,7 @@ export function createDevRegistry({ routesDirectory, namespaces }: { routesDirec
|
||||
outerContexts.set(ctx.req.raw, ctx);
|
||||
try {
|
||||
const response = await subApp.fetch(ctx.req.raw);
|
||||
if (ctx.get('data') || ctx.get('apiData')) {
|
||||
if (ctx.get('data') || ctx.get('apiData') || ctx.get('redirect') || ctx.get('no-content')) {
|
||||
// The upstream template middleware renders from the bridged vars
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,16 +3,22 @@ import { load } from 'cheerio';
|
||||
import type { Element } from 'domhandler';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Data, DataItem, Language, Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate, parseRelativeDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const { category = 'newslists', id } = ctx.req.param();
|
||||
if (!isValidHost(category)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const limit = Number(ctx.req.query('limit') ?? '30');
|
||||
|
||||
const baseUrl = 'https://info.10000link.com';
|
||||
|
||||
@@ -15,7 +15,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const limit = Number(ctx.req.query('limit') ?? '30');
|
||||
|
||||
const baseUrl = 'https://www.199it.com';
|
||||
const targetUrl: string = new URL(category, baseUrl).href;
|
||||
const targetUrl = `${baseUrl}/${category}`;
|
||||
|
||||
const response = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(response);
|
||||
|
||||
@@ -18,7 +18,7 @@ export const handler = async (ctx: Context) => {
|
||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||||
.join('&');
|
||||
|
||||
const currentUrl = new URL(`${params && params.endsWith('.htm') ? params : `${params}.htm`}${queryString ? `?${queryString}` : ''}`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${params && params.endsWith('.htm') ? params : `${params}.htm`}${queryString ? `?${queryString}` : ''}`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export const handler = async (ctx) => {
|
||||
|
||||
const domain = '423down.com';
|
||||
const rootUrl = `https://www.${domain}`;
|
||||
const currentUrl = new URL(category, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category}`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -2,12 +2,14 @@ import { load } from 'cheerio';
|
||||
import { raw } from 'hono/html';
|
||||
import { renderToString } from 'hono/jsx/dom/server';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { DataItem, Language, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import md5 from '@/utils/md5';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
const renderDescription = ({ images, title, keys, details, description, info, links }) =>
|
||||
renderToString(
|
||||
@@ -79,6 +81,10 @@ function stringtoHex(acSTR) {
|
||||
|
||||
async function handler(ctx) {
|
||||
const { id = '4k-uhd-1' } = ctx.req.param();
|
||||
if (!isValidHost(id)) {
|
||||
throw new InvalidParameterError('Invalid id');
|
||||
}
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 25;
|
||||
|
||||
const rootUrl = 'https://www.4ksj.com';
|
||||
|
||||
@@ -17,7 +17,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const encoding = 'gb2312';
|
||||
|
||||
const baseUrl = 'https://www.hao6v.me';
|
||||
const targetUrl: string = new URL(category.startsWith('gvod') ? `${category}.html` : category, baseUrl).href;
|
||||
const targetUrl = `${baseUrl}/${category.startsWith('gvod') ? `${category}.html` : category}`;
|
||||
|
||||
const response = await ofetch(targetUrl, {
|
||||
responseType: 'arrayBuffer',
|
||||
|
||||
@@ -42,8 +42,8 @@ async function handler(ctx) {
|
||||
let currentUrl: string;
|
||||
let documentId;
|
||||
|
||||
if (Number.isNaN(category)) {
|
||||
currentUrl = new URL(category, rootUrl).href;
|
||||
if (Number.isNaN(Number(category))) {
|
||||
currentUrl = `${rootUrl}/${category}`;
|
||||
} else {
|
||||
documentId = category;
|
||||
const feedUrl = new URL(`news/feed/${documentId}/rss.xml`, rootUrl).href;
|
||||
|
||||
@@ -12,7 +12,7 @@ export const handler = async (ctx) => {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
|
||||
|
||||
const rootUrl = 'https://www.accessbriefing.com';
|
||||
const currentUrl = new URL(category, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category}`;
|
||||
const apiUrl = new URL('Ajax/GetPagedArticles', rootUrl).href;
|
||||
|
||||
const { data: currentResponse } = await got(currentUrl);
|
||||
|
||||
@@ -3,16 +3,22 @@ import { load } from 'cheerio';
|
||||
import type { Element } from 'domhandler';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Data, DataItem, Language, Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const { device, sort, searchParams } = ctx.req.param();
|
||||
if (!isValidHost(device)) {
|
||||
throw new InvalidParameterError('Invalid device');
|
||||
}
|
||||
|
||||
const limit = Number(ctx.req.query('limit') ?? '30');
|
||||
|
||||
const baseUrl = 'https://amazfitwatchfaces.com';
|
||||
|
||||
@@ -2,14 +2,20 @@ import type { CheerioAPI } from 'cheerio';
|
||||
import { load } from 'cheerio';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Data, DataItem, Language, Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { baseUrl, fetchItems } from './util';
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const { category = 'highlights', country = 'us' } = ctx.req.param();
|
||||
if (!isValidHost(category)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const limit = Number(ctx.req.query('limit') ?? '100');
|
||||
|
||||
const targetUrl: string = new URL(category.endsWith('/') ? category : `${category}/`, baseUrl).href;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { DataItem, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
const host = 'https://support.apple.com/';
|
||||
|
||||
@@ -33,6 +35,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
const country = ctx.req.param('country') ?? '';
|
||||
if (country && !isValidHost(country)) {
|
||||
throw new InvalidParameterError('Invalid country');
|
||||
}
|
||||
|
||||
const link = new URL(`${country}/service-programs`, host).href;
|
||||
|
||||
const response = await got(link);
|
||||
|
||||
@@ -3,16 +3,22 @@ import { load } from 'cheerio';
|
||||
import type { Element } from 'domhandler';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Data, DataItem, Language, Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/security-releases';
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const { language: languageParam = 'en-us' } = ctx.req.param();
|
||||
if (!isValidHost(languageParam)) {
|
||||
throw new InvalidParameterError('Invalid language');
|
||||
}
|
||||
|
||||
const language = languageParam as Language;
|
||||
const limit = Number(ctx.req.query('limit') ?? '30');
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ async function handler(ctx) {
|
||||
const filterRegion = filterMatches[1];
|
||||
const filterType = filterMatches[2] === 'tag' ? 'tags' : filterMatches[2] === 'category' ? 'categories' : filterMatches[2];
|
||||
const filterKeyword = decodeURI(filterMatches[3].split('/').pop());
|
||||
const filterApiUrl = new URL(`${filterRegion}/${apiSlug}/${filterType}?search=${filterKeyword}`, rootUrl).href;
|
||||
const filterApiUrl = `${rootUrl}/${filterRegion}/${apiSlug}/${filterType}?search=${filterKeyword}`;
|
||||
|
||||
const { data: filterResponse } = await got(filterApiUrl);
|
||||
|
||||
@@ -47,7 +47,7 @@ async function handler(ctx) {
|
||||
if (filter?.id ?? undefined) {
|
||||
filterName = filter.name ?? filterKeyword;
|
||||
currentUrl = filter.link ?? currentUrl;
|
||||
apiUrl = new URL(`${filterRegion}/${apiSlug}/posts?_embed=true&per_page=${limit}&${filterType}=${filter.id}`, rootUrl).href;
|
||||
apiUrl = `${rootUrl}/${filterRegion}/${apiSlug}/posts?_embed=true&per_page=${limit}&${filterType}=${filter.id}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { load } from 'cheerio';
|
||||
import iconv from 'iconv-lite';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { DataItem, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/:category?',
|
||||
@@ -30,6 +32,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
const { category = 'xxkd' } = ctx.req.param();
|
||||
if (!isValidHost(category)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
|
||||
|
||||
const rootUrl = 'http://www.auto-stats.org.cn';
|
||||
|
||||
@@ -10,7 +10,7 @@ export const handler = async (ctx) => {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20;
|
||||
|
||||
const rootUrl = 'https://mba.bnu.edu.cn';
|
||||
const currentUrl = new URL(`${category.replace(/\/$/, '')}/`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category.replace(/\/$/, '')}/`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ async function handler(ctx) {
|
||||
let filterName;
|
||||
|
||||
const path = ctx.req.param('path') ?? '';
|
||||
const currentUrl = new URL(`/${path}`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${path}`;
|
||||
let apiUrl = new URL(`${apiSlug}/posts?_embed=true&per_page=${limit}`, rootUrl).href;
|
||||
|
||||
const filterMatches = path.match(/^post-(tag|category)\/(.*)$/);
|
||||
|
||||
@@ -15,7 +15,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const limit = Number(ctx.req.query('limit') ?? '10');
|
||||
|
||||
const baseUrl = 'http://www.ccagm.org.cn';
|
||||
const targetUrl: string = new URL(category, baseUrl).href;
|
||||
const targetUrl = `${baseUrl}/${category}`;
|
||||
|
||||
const response = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(response);
|
||||
|
||||
@@ -14,7 +14,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const limit = Number(ctx.req.query('limit') ?? '15');
|
||||
|
||||
const baseUrl = 'https://www.cccmc.org.cn';
|
||||
const targetUrl: string = new URL(category.endsWith('/') ? category : `${category}/`, baseUrl).href;
|
||||
const targetUrl = `${baseUrl}/${category.endsWith('/') ? category : `${category}/`}`;
|
||||
|
||||
const response = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(response);
|
||||
|
||||
@@ -3,16 +3,22 @@ import { load } from 'cheerio';
|
||||
import type { Element } from 'domhandler';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Data, DataItem, Language, Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const { category = 'news' } = ctx.req.param();
|
||||
if (!isValidHost(category)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const limit = Number(ctx.req.query('limit') ?? '7');
|
||||
|
||||
const baseUrl = 'http://www.ccg.org.cn';
|
||||
|
||||
@@ -16,7 +16,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const limit = Number(ctx.req.query('limit') ?? '11');
|
||||
|
||||
const rootUrl = 'https://www.chinacdc.cn';
|
||||
const targetUrl: string = new URL(category.endsWith('/') ? category : `${category}/`, rootUrl).href;
|
||||
const targetUrl = `${rootUrl}/${category.endsWith('/') ? category : `${category}/`}`;
|
||||
|
||||
const response = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(response);
|
||||
|
||||
@@ -17,7 +17,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const limit = Number(ctx.req.query('limit') ?? '30');
|
||||
|
||||
const baseUrl = 'https://language.chinadaily.com.cn';
|
||||
const targetUrl: string = new URL(category, baseUrl).href;
|
||||
const targetUrl = `${baseUrl}/${category}`;
|
||||
|
||||
const response = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(response);
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { DataItem, Language, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
export const handler = async (ctx) => {
|
||||
const { category = 'news-325' } = ctx.req.param();
|
||||
if (!isValidHost(category)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 12;
|
||||
|
||||
const rootUrl = 'https://www.cngold.org.cn';
|
||||
|
||||
@@ -3,14 +3,20 @@ import { load } from 'cheerio';
|
||||
import type { Element } from 'domhandler';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Data, DataItem, Language, Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const { category = 'news', id = '10' } = ctx.req.param();
|
||||
if (!isValidHost(category)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const limit = Number(ctx.req.query('limit') ?? '20');
|
||||
|
||||
const baseUrl = 'https://www.cnljxh.org.cn';
|
||||
|
||||
@@ -37,7 +37,7 @@ export const route: Route = {
|
||||
async function handler(ctx) {
|
||||
const { path = 'index/tzgg' } = ctx.req.param();
|
||||
const baseUrl = 'http://jwc.cqu.edu.cn';
|
||||
const url = new URL(`${path}.htm`, baseUrl).href;
|
||||
const url = `${baseUrl}/${path}.htm`;
|
||||
|
||||
const response = await ofetch(url);
|
||||
const $ = load(response);
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import { load } from 'cheerio';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Data, Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const { topic, locale } = ctx.req.param();
|
||||
if (locale && !isValidHost(locale)) {
|
||||
throw new InvalidParameterError('Invalid locale');
|
||||
}
|
||||
|
||||
const limit = Number(ctx.req.query('limit') ?? '10');
|
||||
|
||||
const baseUrl = 'https://cursor.com';
|
||||
|
||||
@@ -3,13 +3,19 @@ import { load } from 'cheerio';
|
||||
import type { Element } from 'domhandler';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Data, DataItem, Language, Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const locale = ctx.req.param('locale');
|
||||
if (locale && !isValidHost(locale)) {
|
||||
throw new InvalidParameterError('Invalid locale');
|
||||
}
|
||||
|
||||
const limit = Number(ctx.req.query('limit') ?? '100');
|
||||
|
||||
const baseUrl = 'https://cursor.com';
|
||||
|
||||
@@ -3,16 +3,22 @@ import { load } from 'cheerio';
|
||||
import type { Element } from 'domhandler';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Data, DataItem, Language, Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const { lang = 'en' } = ctx.req.param();
|
||||
if (!isValidHost(lang)) {
|
||||
throw new InvalidParameterError('Invalid lang');
|
||||
}
|
||||
|
||||
const limit = Number(ctx.req.query('limit') ?? '30');
|
||||
|
||||
const baseUrl = 'https://www.deepl.com';
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { DataItem, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
export const handler = async (ctx) => {
|
||||
const { language = 'CN', category = 'paper' } = ctx.req.param();
|
||||
if (!isValidHost(language)) {
|
||||
throw new InvalidParameterError('Invalid language');
|
||||
}
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 6;
|
||||
|
||||
const rootUrl = 'https://www.dehenglaw.com';
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { load } from 'cheerio';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Route } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/:type',
|
||||
@@ -16,6 +18,9 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx: Context) {
|
||||
const { type } = ctx.req.param();
|
||||
if (!isValidHost(type)) {
|
||||
throw new InvalidParameterError('Invalid type');
|
||||
}
|
||||
|
||||
const host = 'https://www.dekudeals.com/';
|
||||
const link = new URL(type, host).href;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { DataItem, Language, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
@@ -42,6 +44,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
const { language, category = '' } = ctx.req.param();
|
||||
if (!isValidHost(language)) {
|
||||
throw new InvalidParameterError('Invalid language');
|
||||
}
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 10;
|
||||
|
||||
const rootUrl = 'https://dn.com';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Route } from '@/types';
|
||||
import got from '@/utils/got';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
const host = 'https://www.douban.com/explore/column/';
|
||||
export const route: Route = {
|
||||
@@ -16,6 +18,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
const id = ctx.req.param('id');
|
||||
if (!isValidHost(id)) {
|
||||
throw new InvalidParameterError('Invalid id');
|
||||
}
|
||||
|
||||
const link = new URL(id, host).href;
|
||||
const response = await got.get(link);
|
||||
const $ = load(response.data);
|
||||
|
||||
@@ -2,9 +2,11 @@ import { load } from 'cheerio';
|
||||
import { raw } from 'hono/html';
|
||||
import { renderToString } from 'hono/jsx/dom/server';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
const baseURL = 'https://freecomputerbooks.com/';
|
||||
|
||||
@@ -43,6 +45,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
const categoryId = ctx.req.param('category')?.trim();
|
||||
if (categoryId && !isValidHost(categoryId)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const requestURL = categoryId ? new URL(`${categoryId}.html`, baseURL).href : baseURL;
|
||||
const $ = await cheerioLoad(requestURL);
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@ import type { Element } from 'domhandler';
|
||||
import type { Context } from 'hono';
|
||||
import { renderToString } from 'hono/jsx/dom/server';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Data, DataItem, Language, Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
const renderDescription = ({ images, stargazersCount, language, description }) =>
|
||||
renderToString(
|
||||
@@ -49,6 +51,10 @@ const renderDescription = ({ images, stargazersCount, language, description }) =
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const { category = 'repositories' } = ctx.req.param();
|
||||
if (!isValidHost(category)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const limit = Number(ctx.req.query('limit') ?? '100');
|
||||
|
||||
const baseUrl = 'https://gitstar-ranking.com';
|
||||
|
||||
@@ -8,7 +8,7 @@ export const handler = async (ctx) => {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
|
||||
|
||||
const rootUrl = 'https://www.mhlw.go.jp';
|
||||
const currentUrl = new URL(category.endsWith('.html') ? category : `${category}.html`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category.endsWith('.html') ? category : `${category}.html`}`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export const handler = async (ctx) => {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20;
|
||||
|
||||
const rootUrl = 'https://kjt.ah.gov.cn';
|
||||
const currentUrl = new URL(`${category.replace(/\/$/, '').replace(/\/index\.html$/, '')}/`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category.replace(/\/$/, '').replace(/\/index\.html$/, '')}/`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ async function handler(ctx) {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 15;
|
||||
|
||||
const rootUrl = 'https://gzw.cq.gov.cn';
|
||||
const currentUrl = new URL(category, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category}`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { DataItem, Language, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
@@ -35,6 +37,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
const { category = 'gjlckjdjt' } = ctx.req.param();
|
||||
if (!isValidHost(category)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
|
||||
|
||||
const rootUrl = 'http://www.forestry.gov.cn';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import cache from '@/utils/cache';
|
||||
import { getSubPath } from '@/utils/common-utils';
|
||||
// 来人拯救一下啊( >﹏<。)
|
||||
@@ -40,6 +41,7 @@ import { getSubPath } from '@/utils/common-utils';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
import { finishArticleItem } from '@/utils/wechat-mp';
|
||||
|
||||
import { renderZcjdpt } from './templates/zcjdpt';
|
||||
@@ -49,6 +51,9 @@ const gdgov = async (info, ctx) => {
|
||||
.split('/')
|
||||
.filter((item) => item !== '');
|
||||
const [site, branch] = path;
|
||||
if (!isValidHost(site) || !isValidHost(branch)) {
|
||||
throw new InvalidParameterError('Invalid site or branch');
|
||||
}
|
||||
|
||||
// 网站
|
||||
const pathstartat = info.pathstartat === undefined ? 0 : info.pathstartat;
|
||||
|
||||
@@ -11,7 +11,7 @@ export const handler = async (ctx) => {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 25;
|
||||
|
||||
const rootUrl = 'https://www.lswz.gov.cn';
|
||||
const currentUrl = new URL(`${category}.shtml`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category}.shtml`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const limit = Number(ctx.req.query('limit') ?? '30');
|
||||
|
||||
const baseUrl = 'http://www.gjs.moa.gov.cn';
|
||||
const targetUrl: string = new URL(category.endsWith('/') ? category : `${category}/`, baseUrl).href;
|
||||
const targetUrl = `${baseUrl}/${category.endsWith('/') ? category : `${category}/`}`;
|
||||
|
||||
const response = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(response);
|
||||
|
||||
@@ -67,7 +67,7 @@ async function handler(ctx) {
|
||||
}
|
||||
|
||||
const rootUrl = 'https://www.nsfc.gov.cn';
|
||||
const currentUrl = new URL((thePath.endsWith('/more') ? `${thePath}.htm` : thePath) || 'publish/portal0/tab442/', rootUrl).href;
|
||||
const currentUrl = `${rootUrl}${(thePath.endsWith('/more') ? `${thePath}.htm` : thePath) || '/publish/portal0/tab442/'}`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { load } from 'cheerio';
|
||||
import { renderToString } from 'hono/jsx/dom/server';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Language } from '@/types';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
const rootUrl = 'https://www.safe.gov.cn';
|
||||
|
||||
@@ -16,6 +18,10 @@ const zxfkCategoryApis = {
|
||||
};
|
||||
|
||||
const processZxfkItems = async (site = 'beijing', category = 'ywzx', limit = 3) => {
|
||||
if (!isValidHost(site)) {
|
||||
throw new InvalidParameterError('Invalid site');
|
||||
}
|
||||
|
||||
const apiUrl = new URL(`${zxfkCategoryApis[category]}${site}`, rootUrl).href;
|
||||
const currentUrl = new URL(`${site}/${category}/index.html`, rootUrl).href;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export const handler = async (ctx) => {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20;
|
||||
|
||||
const rootUrl = 'https://fgw.sh.gov.cn';
|
||||
const currentUrl = new URL(`${category}/index.html`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category}/index.html`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Route } from '@/types';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
const rootURL = 'http://www.zzb.sz.gov.cn/';
|
||||
|
||||
@@ -36,6 +38,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
const categoryID = ctx.req.param('caty');
|
||||
if (!isValidHost(categoryID)) {
|
||||
throw new InvalidParameterError('Invalid caty');
|
||||
}
|
||||
|
||||
const page = ctx.req.param('page') ?? '1';
|
||||
|
||||
const pageParam = Number.parseInt(page) > 1 ? `_${page}` : '';
|
||||
|
||||
@@ -30,7 +30,7 @@ async function handler(ctx) {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
|
||||
|
||||
const rootUrl = 'https://fg.suzhou.gov.cn';
|
||||
const currentUrl = new URL(`${categoryPath}.shtml`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${categoryPath}.shtml`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -3,15 +3,21 @@ import { load } from 'cheerio';
|
||||
import type { Element } from 'domhandler';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Data, DataItem, Language, Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const { category, id } = ctx.req.param();
|
||||
if (!isValidHost(category)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const limit = Number(ctx.req.query('limit') ?? '30');
|
||||
|
||||
const baseUrl = 'http://load.grainoil.com.cn';
|
||||
|
||||
@@ -3,16 +3,22 @@ import { load } from 'cheerio';
|
||||
import type { Element } from 'domhandler';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Data, DataItem, Language, Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const { id = 'tzgg' } = ctx.req.param();
|
||||
if (!isValidHost(id)) {
|
||||
throw new InvalidParameterError('Invalid id');
|
||||
}
|
||||
|
||||
const limit = Number(ctx.req.query('limit') ?? '10');
|
||||
|
||||
const baseUrl = 'https://hitgs.hit.edu.cn';
|
||||
|
||||
@@ -10,7 +10,7 @@ export const handler = async (ctx) => {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20;
|
||||
|
||||
const rootUrl = 'https://www.hubu.edu.cn';
|
||||
const currentUrl = new URL(`${category}.htm`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category}.htm`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export const handler = async (ctx) => {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20;
|
||||
|
||||
const rootUrl = 'https://zhxy.hubu.edu.cn';
|
||||
const currentUrl = new URL(`${category}.htm`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category}.htm`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export const handler = async (ctx) => {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 16;
|
||||
|
||||
const rootUrl = 'https://gs.hust.edu.cn';
|
||||
const currentUrl = new URL(`${category}.htm`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category}.htm`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export const handler = async (ctx) => {
|
||||
|
||||
const domain = 'mse.hust.edu.cn';
|
||||
const rootUrl = `https://${domain}`;
|
||||
const currentUrl = new URL(`${category}.htm`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category}.htm`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Language, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
@@ -30,6 +32,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
const { category = 'portraits' } = ctx.req.param();
|
||||
if (!isValidHost(category)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
|
||||
|
||||
const author = 'Ian Spriggs';
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Language, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
export const handler = async (ctx) => {
|
||||
const { conference } = ctx.req.param();
|
||||
if (conference && !isValidHost(conference)) {
|
||||
throw new InvalidParameterError('Invalid conference');
|
||||
}
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 12;
|
||||
|
||||
const rootUrl = 'https://www.infoq.com';
|
||||
|
||||
@@ -14,7 +14,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const limit = Number(ctx.req.query('limit') ?? '20');
|
||||
|
||||
const baseUrl = 'https://www.investor.org.cn';
|
||||
const targetUrl: string = new URL(id.endsWith('/') ? id : `${id}/`, baseUrl).href;
|
||||
const targetUrl = `${baseUrl}/${id.endsWith('/') ? id : `${id}/`}`;
|
||||
|
||||
const response = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(response);
|
||||
|
||||
@@ -44,7 +44,7 @@ async function handler(ctx) {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
|
||||
|
||||
const rootUrl = 'http://v.iqilu.com';
|
||||
const currentUrl = new URL(category, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category}`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
@@ -40,6 +42,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
const language = ctx.req.param('language');
|
||||
if (!isValidHost(language)) {
|
||||
throw new InvalidParameterError('Invalid language');
|
||||
}
|
||||
|
||||
const baseUrl = 'https://www.jimmyspa.com';
|
||||
const booksListUrl = new URL(`/${language}/Books/Ajax/changeList?year=&keyword=&categoryId=0&page=1`, baseUrl).href;
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
@@ -39,6 +41,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
const language = ctx.req.param('language');
|
||||
if (!isValidHost(language)) {
|
||||
throw new InvalidParameterError('Invalid language');
|
||||
}
|
||||
|
||||
const rootUrl = 'https://www.jimmyspa.com';
|
||||
|
||||
const currentUrl = new URL(`/${language}/News/Ajax/changeList?year=&keyword=&categoryId=0&page=1`, rootUrl).href;
|
||||
|
||||
@@ -92,7 +92,7 @@ async function handler(ctx) {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
|
||||
|
||||
const rootUrl = 'https://www.kantarworldpanel.com/';
|
||||
const currentUrl = new URL(`${region}/${category}`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}${region}/${category}`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ async function handler(ctx) {
|
||||
const category = ctx.req.param('category') ?? 'National';
|
||||
const baseUrl = 'https://www.koreaherald.com/';
|
||||
|
||||
const response = await got(new URL(category, baseUrl).href);
|
||||
const response = await got(`${baseUrl}${category}`);
|
||||
const $ = load(response.data);
|
||||
const title = $('ul.gnb').find('[class="on"]').length > 0 ? $('ul.gnb').find('[class="on"]').text() : $('div.nav_area > a.category').text();
|
||||
const list = $('article.recent_news > ul.news_list > li')
|
||||
@@ -63,7 +63,7 @@ async function handler(ctx) {
|
||||
);
|
||||
return {
|
||||
title: `The Korea Herald - ${title}`,
|
||||
link: new URL(category, baseUrl).href,
|
||||
link: `${baseUrl}${category}`,
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { load } from 'cheerio';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Data, DataItem, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/xwzx/:name?',
|
||||
@@ -31,6 +33,10 @@ const nameProps = {
|
||||
|
||||
async function handler(ctx: Context): Promise<Data> {
|
||||
const { name = 'all' } = ctx.req.param();
|
||||
if (!isValidHost(name)) {
|
||||
throw new InvalidParameterError('Invalid name');
|
||||
}
|
||||
|
||||
const categories = name === 'all' ? Object.keys(nameProps) : [name];
|
||||
|
||||
const lists = await Promise.all(
|
||||
|
||||
@@ -50,7 +50,7 @@ export async function handler(ctx) {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20;
|
||||
|
||||
const rootUrl = 'https://www.liulinblog.com';
|
||||
const currentUrl = subPath === '/' ? rootUrl : new URL(subPath, rootUrl).href;
|
||||
const currentUrl = subPath === '/' ? rootUrl : `${rootUrl}${subPath}`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { DataItem, Language, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
export const handler = async (ctx) => {
|
||||
const { category = '' } = ctx.req.param();
|
||||
if (category && !isValidHost(category)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 12;
|
||||
|
||||
const rootUrl = 'https://lrepacks.net';
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import { getSubPath } from '@/utils/common-utils';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
@@ -36,6 +38,10 @@ export const route: Route = {
|
||||
|
||||
export async function handler(ctx) {
|
||||
const [id, category = 'china'] = getSubPath(ctx).split('/').filter(Boolean);
|
||||
if (!isValidHost(category)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
|
||||
|
||||
const rootUrl = `http://${id}.m4.cn`;
|
||||
|
||||
@@ -3,12 +3,18 @@ import { load } from 'cheerio';
|
||||
import type { Element } from 'domhandler';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Data, DataItem, Route } from '@/types';
|
||||
import { ViewType } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const { language = '' } = ctx.req.param();
|
||||
if (language && !isValidHost(language)) {
|
||||
throw new InvalidParameterError('Invalid language');
|
||||
}
|
||||
|
||||
const limit = Number(ctx.req.query('limit') ?? '30');
|
||||
|
||||
const baseUrl = 'https://musify.club';
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Language, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { ProcessFeed, rootUrl } from './utils';
|
||||
|
||||
@@ -23,6 +25,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
let sectionName: string = ctx.req.param('section');
|
||||
if (!isValidHost(sectionName)) {
|
||||
throw new InvalidParameterError('Invalid section');
|
||||
}
|
||||
|
||||
sectionName = sectionName.replace('-', '/');
|
||||
sectionName += '/';
|
||||
const sectionUrl = new URL(sectionName, rootUrl).href;
|
||||
|
||||
@@ -16,7 +16,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const limit = Number(ctx.req.query('limit') ?? '50');
|
||||
|
||||
const baseUrl = 'https://papers.cool';
|
||||
const targetUrl: string = new URL(`${id}?show=${limit}`, baseUrl).href;
|
||||
const targetUrl = `${baseUrl}/${id}?show=${limit}`;
|
||||
|
||||
const response = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(response);
|
||||
|
||||
@@ -16,7 +16,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const limit = Number(ctx.req.query('limit') ?? '10');
|
||||
|
||||
const baseUrl = 'https://www.producereport.com';
|
||||
const targetUrl: string = new URL(category, baseUrl).href;
|
||||
const targetUrl = `${baseUrl}/${category}`;
|
||||
|
||||
const response = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(response);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { DataItem, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/:category?',
|
||||
@@ -34,6 +36,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
const { category = '' } = ctx.req.param();
|
||||
if (category && !isValidHost(category)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 10;
|
||||
|
||||
const rootUrl = 'https://qqorw.cn';
|
||||
|
||||
@@ -14,7 +14,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const limit = Number(ctx.req.query('limit') ?? '17');
|
||||
|
||||
const baseUrl = 'https://www.samrdprc.org.cn';
|
||||
const targetUrl: string = new URL(id.endsWith('/') ? id : `${id}/`, baseUrl).href;
|
||||
const targetUrl = `${baseUrl}/${id.endsWith('/') ? id : `${id}/`}`;
|
||||
|
||||
const response = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(response);
|
||||
|
||||
@@ -16,7 +16,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const limit = Number(ctx.req.query('limit') ?? '12');
|
||||
|
||||
const baseUrl = 'https://www.semiconductors.org';
|
||||
const targetUrl: string = new URL(category.endsWith('/') ? category : `${category}/`, baseUrl).href;
|
||||
const targetUrl = `${baseUrl}/${category.endsWith('/') ? category : `${category}/`}`;
|
||||
|
||||
const response = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(response);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { DataItem, Language, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/pe/:id?',
|
||||
@@ -67,6 +69,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
const { id = '342' } = ctx.req.param();
|
||||
if (!isValidHost(id)) {
|
||||
throw new InvalidParameterError('Invalid id');
|
||||
}
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
|
||||
|
||||
const rootUrl = 'https://pe2016.sspu.edu.cn';
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { DataItem, Language, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
@@ -323,6 +325,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
const { category = '' } = ctx.req.param();
|
||||
if (category && !isValidHost(category)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 50;
|
||||
|
||||
const rootUrl = 'https://www.thoughtco.com';
|
||||
|
||||
@@ -22,7 +22,8 @@ export const route: Route = {
|
||||
},
|
||||
{
|
||||
name: 'WEIBO_REDIRECT_URL',
|
||||
description: '',
|
||||
optional: true,
|
||||
description: "OAuth callback URL. Defaults to `<request origin>/weibo/timeline/0`. Set it when the auto-composed URL doesn't work",
|
||||
},
|
||||
],
|
||||
requirePuppeteer: false,
|
||||
@@ -93,7 +94,7 @@ async function handler(ctx) {
|
||||
);
|
||||
// 检查token失效
|
||||
if (response.error !== undefined) {
|
||||
const { app_key = '', redirect_url = ctx.req.origin + '/weibo/timeline/0' } = config.weibo;
|
||||
const { app_key = '', redirect_url = `${new URL(ctx.req.url).origin}/weibo/timeline/0` } = config.weibo;
|
||||
|
||||
ctx.status = 302;
|
||||
ctx.set({
|
||||
@@ -164,8 +165,8 @@ async function handler(ctx) {
|
||||
item: resultItem,
|
||||
});
|
||||
}
|
||||
if (uid === '0' || ctx.req.query()) {
|
||||
const { app_key = '', redirect_url = ctx.req.origin + '/weibo/timeline/0', app_secret = '' } = config.weibo;
|
||||
if (uid === '0' || ctx.req.query('code')) {
|
||||
const { app_key = '', redirect_url = `${new URL(ctx.req.url).origin}/weibo/timeline/0`, app_secret = '' } = config.weibo;
|
||||
|
||||
const code = ctx.req.query('code');
|
||||
const routeParams = ctx.req.query('state');
|
||||
@@ -176,14 +177,11 @@ async function handler(ctx) {
|
||||
const expires_in = rep.data.expires_in;
|
||||
await cache.set('weibotimelineuid' + uid, token, expires_in);
|
||||
|
||||
ctx.set({
|
||||
'Content-Type': 'text/html; charset=UTF-8',
|
||||
'Cache-Control': 'no-cache',
|
||||
});
|
||||
ctx.html(`<script>window.location = '/weibo/timeline/${uid}${routeParams ? `/${routeParams}` : ''}'</script>`);
|
||||
ctx.header('Cache-Control', 'no-cache');
|
||||
return ctx.redirect(`/weibo/timeline/${uid}${routeParams ? `/${routeParams}` : ''}`);
|
||||
}
|
||||
} else {
|
||||
const { app_key = '', redirect_url = ctx.req.origin + '/weibo/timeline/0' } = config.weibo;
|
||||
const { app_key = '', redirect_url = `${new URL(ctx.req.url).origin}/weibo/timeline/0` } = config.weibo;
|
||||
|
||||
ctx.status = 302;
|
||||
ctx.set({
|
||||
|
||||
@@ -61,7 +61,7 @@ async function handler(ctx) {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
|
||||
|
||||
const rootUrl = `https://hyxt.${domain}`;
|
||||
const currentUrl = new URL(`${category}.htm`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category}.htm`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const limit = Number(ctx.req.query('limit') ?? '30');
|
||||
|
||||
const baseUrl = 'https://zs.xjtu.edu.cn';
|
||||
const targetUrl: string = new URL(`${category}.htm`, baseUrl).href;
|
||||
const targetUrl = `${baseUrl}/${category}.htm`;
|
||||
|
||||
const response = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(response);
|
||||
|
||||
@@ -46,7 +46,7 @@ async function handler(ctx) {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
|
||||
|
||||
const rootUrl = 'https://news.xmnn.cn';
|
||||
const currentUrl = new URL(`${category}/`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${category}/`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ async function handler(ctx): Promise<Data> {
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 10;
|
||||
|
||||
const rootUrl = 'https://dongke.yangtzeu.edu.cn';
|
||||
const currentUrl = new URL(`/${ctx.req.param('path') ?? 'yqzl/xyxw'}.htm`, rootUrl).href;
|
||||
const currentUrl = `${rootUrl}/${ctx.req.param('path') ?? 'yqzl/xyxw'}.htm`;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
@@ -52,15 +52,11 @@ async function handler(ctx): Promise<Data> {
|
||||
|
||||
item.title = content('title').text();
|
||||
item.description = content('div.v_news_content').html();
|
||||
item.category = content('meta[name="keywords"]').prop('content').split(',');
|
||||
item.pubDate = timezone(
|
||||
parseDate(
|
||||
content('p.content-info')
|
||||
.text()
|
||||
.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)![0]
|
||||
),
|
||||
8
|
||||
);
|
||||
item.category = content('meta[name="keywords"]').prop('content')?.split(',');
|
||||
const pubDate = content('p.content-info')
|
||||
.text()
|
||||
.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)?.[0];
|
||||
item.pubDate = pubDate ? timezone(parseDate(pubDate), 8) : undefined;
|
||||
|
||||
return item;
|
||||
})
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import type { Language, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
import { renderDescription } from './templates/description';
|
||||
|
||||
@@ -74,6 +76,10 @@ export const route: Route = {
|
||||
|
||||
async function handler(ctx) {
|
||||
const { column = 'article', category = '0' } = ctx.req.param();
|
||||
if (!isValidHost(column)) {
|
||||
throw new InvalidParameterError('Invalid column');
|
||||
}
|
||||
|
||||
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
|
||||
|
||||
const rootUrl = 'https://dt.yicai.com';
|
||||
|
||||
@@ -7,6 +7,7 @@ import got from '@/utils/got';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import parser from '@/utils/rss-parser';
|
||||
import timezone from '@/utils/timezone';
|
||||
import { isValidHost } from '@/utils/valid-host';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/:lang?/:channel?',
|
||||
@@ -48,17 +49,12 @@ For example, the path for the RSS feed url <https://www.yna.co.kr/rss/economy.xm
|
||||
:::`,
|
||||
};
|
||||
|
||||
// The language becomes the leading label of the requested host below, so it is
|
||||
// checked against the codes the route documents. 'sp' is kept as well because
|
||||
// the Spanish feed page linked in the description lives on sp.yna.co.kr.
|
||||
const languages = new Set(['ko', 'en', 'cn', 'jp', 'ar', 'es', 'sp', 'fr']);
|
||||
|
||||
async function handler(ctx) {
|
||||
const lang = ctx.req.param('lang') ?? 'ko';
|
||||
const channel = ctx.req.param('channel') ?? 'news';
|
||||
|
||||
if (!languages.has(lang)) {
|
||||
throw new InvalidParameterError('Unsupported language');
|
||||
if (!isValidHost(lang)) {
|
||||
throw new InvalidParameterError('Invalid lang');
|
||||
}
|
||||
|
||||
let url;
|
||||
|
||||
@@ -43,7 +43,7 @@ export const route: Route = {
|
||||
async function handler(ctx) {
|
||||
const uid = ctx.req.param('uid');
|
||||
let pageUrl = `https://www.zcool.com.cn/u/${uid}`;
|
||||
if (Number.isNaN(uid)) {
|
||||
if (Number.isNaN(Number(uid))) {
|
||||
if (!isValidHost(uid)) {
|
||||
throw new InvalidParameterError('Invalid uid');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user