diff --git a/lib/middleware/sentry.ts b/lib/middleware/sentry.ts
index 39818a3064..04d59e95e1 100644
--- a/lib/middleware/sentry.ts
+++ b/lib/middleware/sentry.ts
@@ -1,7 +1,7 @@
import { MiddlewareHandler } from 'hono';
import logger from '@/utils/logger';
import { config } from '@/config';
-import Sentry from '@sentry/node';
+import * as Sentry from '@sentry/node';
import { getRouteNameFromPath } from '@/utils/helpers';
if (config.sentry.dsn) {
diff --git a/lib/router.js b/lib/router.js
index b1a0e80697..80d0cfa6fc 100644
--- a/lib/router.js
+++ b/lib/router.js
@@ -155,7 +155,7 @@ router.get('/mafengwo/ziyouxing/:code', lazyloadRouteHandler('./routes/mafengwo/
router.get('/novel/uukanshu/:uid', lazyloadRouteHandler('./routes/novel/uukanshu'));
// 中国气象网
-router.get('/weatheralarm/:province?', lazyloadRouteHandler('./routes/weatheralarm'));
+// router.get('/weatheralarm/:province?', lazyloadRouteHandler('./routes/weatheralarm'));
// Gitlab
router.get('/gitlab/explore/:type/:host?', lazyloadRouteHandler('./routes/gitlab/explore'));
diff --git a/lib/routes-deprecated/weatheralarm/index.js b/lib/routes-deprecated/weatheralarm/index.js
deleted file mode 100644
index 88d0595378..0000000000
--- a/lib/routes-deprecated/weatheralarm/index.js
+++ /dev/null
@@ -1,26 +0,0 @@
-const got = require('@/utils/got');
-
-module.exports = async (ctx) => {
- const province = ctx.params.province || '&';
- const alarmInfoURL = `http://www.nmc.cn/rest/findAlarm?pageNo=1&pageSize=20&signaltype=&signallevel=&province=${province}`;
- const response = await got({
- method: 'get',
- url: alarmInfoURL,
- });
- const data = response.data.data;
- const list = data.page.list;
-
- ctx.state.data = {
- title: '中央气象台全国气象预警',
- link: `http://www.nmc.cn/publish/alarm.html`,
- item: list.map((item) => {
- const title = item.title;
- const url = item.url;
- return {
- title,
- link: `http://www.nmc.cn${url}`,
- pubDate: item.issuetime,
- };
- }),
- };
-};
diff --git a/lib/routes/baidu/radar.ts b/lib/routes/baidu/radar.ts
index 5d91512855..13e02d4527 100644
--- a/lib/routes/baidu/radar.ts
+++ b/lib/routes/baidu/radar.ts
@@ -75,7 +75,7 @@ module.exports = {
const qw = searchParams.get('qw');
const kw = searchParams.get('kw');
if (qw) {
- const route = `/baidu/tieba/user/${qw}`;
+ const route = `/baidu/tieba/search/${qw}`;
return kw ? `${route}/kw=${kw}` : route;
}
},
diff --git a/lib/routes/baidu/tieba/forum.ts b/lib/routes/baidu/tieba/forum.ts
index 27f81aca55..7c21caa648 100644
--- a/lib/routes/baidu/tieba/forum.ts
+++ b/lib/routes/baidu/tieba/forum.ts
@@ -16,7 +16,7 @@ export default async (ctx) => {
// PC端:https://tieba.baidu.com/f?kw=${encodeURIComponent(kw)}
// 移动端接口:https://tieba.baidu.com/mo/q/m?kw=${encodeURIComponent(kw)}&lp=5024&forum_recommend=1&lm=0&cid=0&has_url_param=1&pn=0&is_ajax=1
const params = { kw: encodeURIComponent(kw) };
- ctx._matchedRoute.includes('good') && (params.tab = 'good');
+ ctx.req.path.includes('good') && (params.tab = 'good');
cid && (params.cid = cid);
const { data } = await got(`https://tieba.baidu.com/f`, {
headers: {
diff --git a/lib/routes/baidu/tieba/post.ts b/lib/routes/baidu/tieba/post.ts
index 9e5080e916..421418dd1a 100644
--- a/lib/routes/baidu/tieba/post.ts
+++ b/lib/routes/baidu/tieba/post.ts
@@ -34,7 +34,7 @@ async function getPost(id, lz = 0, pn = 7e6) {
export default async (ctx) => {
const id = ctx.req.param('id');
- const lz = ctx._matchedRoute.includes('lz') ? 1 : 0;
+ const lz = ctx.req.path.includes('lz') ? 1 : 0;
const html = await getPost(id, lz);
const $ = load(html);
const title = $('.core_title_txt').attr('title');
diff --git a/lib/routes/bilibili/like.js b/lib/routes/bilibili/like.js
new file mode 100644
index 0000000000..2b628aec41
--- /dev/null
+++ b/lib/routes/bilibili/like.js
@@ -0,0 +1,35 @@
+import got from '@/utils/got';
+const cache = require('./cache');
+const utils = require('./utils');
+import { parseDate } from '@/utils/parse-date';
+
+module.exports = async (ctx) => {
+ const uid = ctx.req.param('uid');
+ const disableEmbed = ctx.req.param('disableEmbed');
+
+ const name = await cache.getUsernameFromUID(ctx, uid);
+
+ const response = await got({
+ url: `https://api.bilibili.com/x/space/like/video?vmid=${uid}`,
+ headers: {
+ Referer: `https://space.bilibili.com/${uid}/`,
+ },
+ });
+ const { data, code, message } = response.data;
+ if (code) {
+ throw new Error(message ?? code);
+ }
+
+ ctx.set('data', {
+ title: `${name} 的 bilibili 点赞视频`,
+ link: `https://space.bilibili.com/${uid}`,
+ description: `${name} 的 bilibili 点赞视频`,
+ item: data.list.map((item) => ({
+ title: item.title,
+ description: `${item.desc}${disableEmbed ? '' : `
${utils.iframe(item.aid)}`}`,
+ pubDate: parseDate(item.pubdate * 1000),
+ link: item.pubdate > utils.bvidTime && item.bvid ? `https://www.bilibili.com/video/${item.bvid}` : `https://www.bilibili.com/video/av${item.aid}`,
+ author: item.owner.name,
+ })),
+ });
+};
diff --git a/lib/routes/bilibili/maintainer.ts b/lib/routes/bilibili/maintainer.ts
index 2cbb05bfe8..bfab17167d 100644
--- a/lib/routes/bilibili/maintainer.ts
+++ b/lib/routes/bilibili/maintainer.ts
@@ -31,6 +31,7 @@ module.exports = {
'/user/fav/:uid/:disableEmbed?': ['DIYgod'],
'/user/followers/:uid/:loginUid': ['Qixingchen'],
'/user/followings/:uid/:loginUid': ['Qixingchen'],
+ '/user/like/:uid/:disableEmbed?': ['ygguorun'],
'/user/video/:uid/:disableEmbed?': ['DIYgod'],
'/video/danmaku/:bvid/:pid?': ['Qixingchen'],
'/video/page/:bvid/:disableEmbed?': ['sxzz'],
diff --git a/lib/routes/bilibili/radar.ts b/lib/routes/bilibili/radar.ts
index 24fa549a60..91df8b0b90 100644
--- a/lib/routes/bilibili/radar.ts
+++ b/lib/routes/bilibili/radar.ts
@@ -102,6 +102,12 @@ module.exports = {
source: '/:uid',
target: '/bilibili/user/coin/:uid',
},
+ {
+ title: 'UP 主点赞视频',
+ docs: 'https://docs.rsshub.app/routes/social-media#bilibili',
+ source: '/:uid',
+ target: '/bilibili/user/like/:uid',
+ },
{
title: 'UP 主粉丝',
docs: 'https://docs.rsshub.app/routes/social-media#bilibili',
diff --git a/lib/routes/bilibili/router.ts b/lib/routes/bilibili/router.ts
index 739d7fa2ea..01c61b004a 100644
--- a/lib/routes/bilibili/router.ts
+++ b/lib/routes/bilibili/router.ts
@@ -31,6 +31,7 @@ export default (router) => {
router.get('/user/fav/:uid/:disableEmbed?', './user-fav');
router.get('/user/followers/:uid/:loginUid', './followers');
router.get('/user/followings/:uid/:loginUid', './followings');
+ router.get('/user/like/:uid/:disableEmbed?', './like');
router.get('/user/video/:uid/:disableEmbed?', './video');
router.get('/user/video-all/:uid/:disableEmbed?', './video-all');
router.get('/video/danmaku/:bvid/:pid?', './danmaku');
diff --git a/lib/routes/github/notifications.ts b/lib/routes/github/notifications.ts
index 3d1ab7623b..36982f510c 100644
--- a/lib/routes/github/notifications.ts
+++ b/lib/routes/github/notifications.ts
@@ -20,13 +20,19 @@ export default async (ctx) => {
});
const notifications = response.data;
- const items = notifications.map((item) => ({
- title: item.subject.title,
- description: item.subject.title,
- pubDate: parseDate(item.updated_at), // item.updated_at follows ISO 8601.
- guid: item.id,
- link: item.subject.url,
- }));
+ const items = notifications.map((item) => {
+ let originUrl = item.subject.url ? item.subject.url.replace('https://api.github.com/repos/', 'https://github.com/') : 'https://github.com/notifications';
+ if (originUrl.includes('/releases/')) {
+ originUrl = originUrl.replace(/\/releases\/\d+$/, '/releases');
+ }
+ return {
+ title: item.subject.title,
+ description: item.subject.title,
+ pubDate: parseDate(item.updated_at), // item.updated_at follows ISO 8601.
+ guid: item.id,
+ link: originUrl,
+ };
+ });
ctx.set('data', {
title: 'Github Notifications',
diff --git a/lib/routes/mastodon/timeline-local.ts b/lib/routes/mastodon/timeline-local.ts
index 8beb5f6fb3..5af1a3b0aa 100644
--- a/lib/routes/mastodon/timeline-local.ts
+++ b/lib/routes/mastodon/timeline-local.ts
@@ -12,7 +12,7 @@ export default async (ctx) => {
const url = `http://${site}/api/v1/timelines/public?local=true&only_media=${only_media}`;
- const response = await got.get(url);
+ const response = await got.get(url, { headers: utils.apiHeaders() });
const list = response.data;
ctx.set('data', {
diff --git a/lib/routes/mastodon/timeline-remote.ts b/lib/routes/mastodon/timeline-remote.ts
index 4755f131bb..ac570da1ce 100644
--- a/lib/routes/mastodon/timeline-remote.ts
+++ b/lib/routes/mastodon/timeline-remote.ts
@@ -12,7 +12,7 @@ export default async (ctx) => {
const url = `http://${site}/api/v1/timelines/public?remote=true&only_media=${only_media}`;
- const response = await got.get(url);
+ const response = await got.get(url, { headers: utils.apiHeaders() });
const list = response.data;
ctx.set('data', {
diff --git a/lib/routes/mastodon/utils.ts b/lib/routes/mastodon/utils.ts
index 836379d76d..8383b93845 100644
--- a/lib/routes/mastodon/utils.ts
+++ b/lib/routes/mastodon/utils.ts
@@ -6,6 +6,12 @@ import { config } from '@/config';
const allowSiteList = ['mastodon.social', 'pawoo.net'];
+const apiHeaders = (site) => {
+ const { accessToken, apiHost } = config.mastodon;
+ // avoid sending API token to other sites
+ return accessToken && site === apiHost ? { Authorization: `Bearer ${accessToken}` } : {};
+};
+
const mediaParse = (media_attachments) =>
media_attachments
.map((item) => {
@@ -60,6 +66,7 @@ async function getAccountStatuses(site, account_id, only_media) {
const statuses_response = await got({
method: 'get',
url: statuses_url,
+ headers: apiHeaders(site),
});
const data = statuses_response.data;
@@ -71,6 +78,7 @@ async function getAccountStatuses(site, account_id, only_media) {
const account_response = await got({
method: 'get',
url: account_url,
+ headers: apiHeaders(site),
});
account_data = account_response.data;
}
@@ -96,9 +104,7 @@ async function getAccountIdByAcct(acct) {
const search_response = await got({
method: 'get',
url: search_url,
- headers: {
- ...(mastodonConfig.accessToken ? { Authorization: `Bearer ${mastodonConfig.accessToken}` } : {}),
- },
+ headers: apiHeaders(site),
searchParams: {
q: acct,
type: 'accounts',
@@ -124,6 +130,7 @@ async function getAccountIdByAcct(acct) {
}
module.exports = {
+ apiHeaders,
parseStatuses,
getAccountStatuses,
getAccountIdByAcct,
diff --git a/lib/routes/nlc/maintainer.js b/lib/routes/nlc/maintainer.js
new file mode 100644
index 0000000000..2a5a4f02d7
--- /dev/null
+++ b/lib/routes/nlc/maintainer.js
@@ -0,0 +1,3 @@
+module.exports = {
+ '/read/:type?': ['nczitzk'],
+};
diff --git a/lib/routes/nlc/radar.js b/lib/routes/nlc/radar.js
new file mode 100644
index 0000000000..607df4da50
--- /dev/null
+++ b/lib/routes/nlc/radar.js
@@ -0,0 +1,66 @@
+module.exports = {
+ 'nlc.cn': {
+ _name: '中国国家图书馆',
+ read: [
+ {
+ title: '读者云平台',
+ docs: 'https://docs.rsshub.app/routes/other#zhong-guo-guo-jia-tu-shu-guan-du-zhe-yun-ping-tai',
+ source: ['/outRes/outResList'],
+ target: (_, url) => {
+ url = new URL(url);
+ const type = url.searchParams.get('type');
+
+ return `/nlc/read/${type ? `/${type}` : ''}`;
+ },
+ },
+ {
+ title: '电子图书',
+ docs: 'https://docs.rsshub.app/routes/other#zhong-guo-guo-jia-tu-shu-guan-du-zhe-yun-ping-tai',
+ source: ['/outRes/outResList'],
+ target: '/nlc/read/电子图书',
+ },
+ {
+ title: '电子期刊',
+ docs: 'https://docs.rsshub.app/routes/other#zhong-guo-guo-jia-tu-shu-guan-du-zhe-yun-ping-tai',
+ source: ['/outRes/outResList'],
+ target: '/nlc/read/电子期刊',
+ },
+ {
+ title: '电子论文',
+ docs: 'https://docs.rsshub.app/routes/other#zhong-guo-guo-jia-tu-shu-guan-du-zhe-yun-ping-tai',
+ source: ['/outRes/outResList'],
+ target: '/nlc/read/电子论文',
+ },
+ {
+ title: '电子报纸',
+ docs: 'https://docs.rsshub.app/routes/other#zhong-guo-guo-jia-tu-shu-guan-du-zhe-yun-ping-tai',
+ source: ['/outRes/outResList'],
+ target: '/nlc/read/电子报纸',
+ },
+ {
+ title: '音视频',
+ docs: 'https://docs.rsshub.app/routes/other#zhong-guo-guo-jia-tu-shu-guan-du-zhe-yun-ping-tai',
+ source: ['/outRes/outResList'],
+ target: '/nlc/read/音视频',
+ },
+ {
+ title: '标准专利',
+ docs: 'https://docs.rsshub.app/routes/other#zhong-guo-guo-jia-tu-shu-guan-du-zhe-yun-ping-tai',
+ source: ['/outRes/outResList'],
+ target: '/nlc/read/标准专利',
+ },
+ {
+ title: '工具书',
+ docs: 'https://docs.rsshub.app/routes/other#zhong-guo-guo-jia-tu-shu-guan-du-zhe-yun-ping-tai',
+ source: ['/outRes/outResList'],
+ target: '/nlc/read/工具书',
+ },
+ {
+ title: '少儿资源',
+ docs: 'https://docs.rsshub.app/routes/other#zhong-guo-guo-jia-tu-shu-guan-du-zhe-yun-ping-tai',
+ source: ['/outRes/outResList'],
+ target: '/nlc/read/少儿资源',
+ },
+ ],
+ },
+};
diff --git a/lib/routes/nlc/read.js b/lib/routes/nlc/read.js
new file mode 100644
index 0000000000..f9afd64681
--- /dev/null
+++ b/lib/routes/nlc/read.js
@@ -0,0 +1,71 @@
+import got from '@/utils/got';
+import { load } from 'cheerio';
+import { art } from '@/utils/render';
+import * as path from 'node:path';
+
+export default async (ctx) => {
+ const { type = '电子图书' } = ctx.req.param();
+ const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 15;
+
+ const author = '中国国家图书馆';
+
+ const rootUrl = 'http://read.nlc.cn';
+ const currentUrl = new URL(`/outRes/outResList?type=${type}`, rootUrl).href;
+
+ const { data: response } = await got(currentUrl);
+
+ const $ = load(response);
+
+ const items = $('ul.YMH2019_New_GRZX_list7 li a.aa')
+ .slice(0, limit)
+ .toArray()
+ .map((item) => {
+ item = $(item);
+
+ const title = item.find('span').first().text();
+
+ return {
+ title,
+ link: item.prop('onclick').match(/openOutRes\('1','(.*?)','1',/)[1],
+ description: art(path.join(__dirname, 'templates/description.art'), {
+ images: item
+ .find('div.pic img')
+ .toArray()
+ .map((i) => {
+ i = $(i);
+
+ return {
+ src: i.prop('src'),
+ alt: title,
+ };
+ }),
+ intro: item.find('div.txt').prop('title'),
+ }),
+ author,
+ category: [type],
+ guid: `nlc-read#${
+ item
+ .prev()
+ .prop('onclick')
+ .match(/\('(\d+)'\)/)[1]
+ }`,
+ };
+ });
+
+ const image = new URL('static/style/css/images/YMH_home_main_logo.png', rootUrl).href;
+ const icon = new URL($('link[rel="shortcut icon"]').prop('href'), rootUrl).href;
+
+ ctx.set('data', {
+ item: items,
+ title: `${$('title').text()} - ${type}`,
+ link: currentUrl,
+ description: type,
+ language: 'zh',
+ image,
+ icon,
+ logo: icon,
+ subtitle: type,
+ author,
+ allowEmpty: true,
+ });
+};
diff --git a/lib/routes/nlc/router.js b/lib/routes/nlc/router.js
new file mode 100644
index 0000000000..87a34ad553
--- /dev/null
+++ b/lib/routes/nlc/router.js
@@ -0,0 +1,3 @@
+export default (router) => {
+ router.get('/read/:type?', './read');
+};
diff --git a/lib/routes/nlc/templates/description.art b/lib/routes/nlc/templates/description.art
new file mode 100644
index 0000000000..a3f88c9b4d
--- /dev/null
+++ b/lib/routes/nlc/templates/description.art
@@ -0,0 +1,17 @@
+{{ if images }}
+ {{ each images image }}
+ {{ if image?.src }}
+
+
{{ intro }}
+{{ /if }} \ No newline at end of file diff --git a/lib/routes/nmc/maintainer.js b/lib/routes/nmc/maintainer.js new file mode 100644 index 0000000000..42f2f53ab7 --- /dev/null +++ b/lib/routes/nmc/maintainer.js @@ -0,0 +1,3 @@ +module.exports = { + '/weatheralarm/:province?': ['ylc395'], +}; diff --git a/lib/routes/nmc/radar.js b/lib/routes/nmc/radar.js new file mode 100644 index 0000000000..0e77a18f3e --- /dev/null +++ b/lib/routes/nmc/radar.js @@ -0,0 +1,13 @@ +module.exports = { + 'nmc.cn': { + _name: '中央气象台', + '.': [ + { + title: '全国气象预警', + docs: 'https://docs.rsshub.app/routes/forecast#zhong-yang-qi-xiang-tai', + source: ['/publish/alarm.html', '/'], + target: '/nmc/weatheralarm', + }, + ], + }, +}; diff --git a/lib/routes/nmc/router.js b/lib/routes/nmc/router.js new file mode 100644 index 0000000000..fde453364b --- /dev/null +++ b/lib/routes/nmc/router.js @@ -0,0 +1,3 @@ +export default (router) => { + router.get('/weatheralarm/:province?', './weatheralarm'); +}; diff --git a/lib/routes/nmc/weatheralarm.js b/lib/routes/nmc/weatheralarm.js new file mode 100644 index 0000000000..825d35650f --- /dev/null +++ b/lib/routes/nmc/weatheralarm.js @@ -0,0 +1,49 @@ +import cache from '@/utils/cache'; +import got from '@/utils/got'; +import { parseDate } from '@/utils/parse-date'; +import timezone from '@/utils/timezone'; +import * as cheerio from 'cheerio'; + +export default async (ctx) => { + const { province } = ctx.req.param(); + const alarmInfoURL = `http://www.nmc.cn/rest/findAlarm`; + const { data: response } = await got(alarmInfoURL, { + searchParams: { + pageNo: 1, + pageSize: 20, + signaltype: '', + signallevel: '', + province, + }, + }); + + const list = response.data.page.list.map((item) => ({ + title: item.title, + link: `http://www.nmc.cn${item.url}`, + pubDate: timezone(parseDate(item.issuetime), 8), + })); + + const items = await Promise.all( + list.map((item) => + cache.tryGet(item.link, async () => { + const { data: response } = await got(item.link); + const $ = cheerio.load(response); + + item.description = + $('#icon').html() + + $('#alarmtext') + .toArray() + .map((i) => $(i).html()) + .join(''); + + return item; + }) + ) + ); + + ctx.set('data', { + title: '中央气象台全国气象预警', + link: 'http://www.nmc.cn/publish/alarm.html', + item: items, + }); +}; diff --git a/lib/routes/npm/package.ts b/lib/routes/npm/package.ts index 58ab3ebf12..33ed1d6ba8 100644 --- a/lib/routes/npm/package.ts +++ b/lib/routes/npm/package.ts @@ -7,7 +7,7 @@ import { art } from '@/utils/render'; import * as path from 'node:path'; export default async (ctx) => { - const name = ctx.req.url.split('package/')[1]; + const name = ctx.req.param('name'); const packageDownloadLastMonthAPI = `https://api.npmjs.org/downloads/point/last-month/${name}`; // 按月统计 const packageDownloadLastWeekAPI = `https://api.npmjs.org/downloads/point/last-week/${name}`; // 按周统计 const packageDownloadLastDayAPI = `https://api.npmjs.org/downloads/point/last-day/${name}`; // 按天统计 @@ -19,14 +19,12 @@ export default async (ctx) => { const packageVersionRes = await got(packageVersionAPI).json(); const packageVersion = packageVersionRes.time; - const packageVersionList = []; - for (const key in packageVersion) { - packageVersionList.push({ + const packageVersionList = Object.keys(packageVersion) + .map((key) => ({ version: key, time: packageVersion[key], - }); - } - packageVersionList.reverse(); + })) + .toReversed(); ctx.set('data', { title: `${name} - npm`, diff --git a/lib/routes/npm/router.ts b/lib/routes/npm/router.ts index 03da709285..0b04076bb8 100644 --- a/lib/routes/npm/router.ts +++ b/lib/routes/npm/router.ts @@ -1,3 +1,4 @@ export default (router) => { - router.get('package/*', './package'); + // https://github.com/dword-design/package-name-regex/blob/master/src/index.js + router.get('package/:name{(@[a-z0-9-~][a-z0-9-._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*}', './package'); }; diff --git a/lib/routes/radio-canada/latest.ts b/lib/routes/radio-canada/latest.ts index e0bdf987c8..c402a49d18 100644 --- a/lib/routes/radio-canada/latest.ts +++ b/lib/routes/radio-canada/latest.ts @@ -32,10 +32,10 @@ export default async (ctx) => { }); const $ = load(detailResponse.data); - const rcState = $('script:contains("window._rcState_ = JSON.parse")') + const rcState = $('script:contains("window._rcState_ = ")') .text() - .match(/JSON\.parse\((".*")\);/)[1]; - const rcStateJson = JSON.parse(JSON.parse(rcState)); + .match(/window\._rcState_ = (.*);/)[1]; + const rcStateJson = JSON.parse(rcState); const news = Object.values(rcStateJson.pagesV2.pages)[0]; item.description = news.data.newsStory.body.html.replaceAll('\\n', '