diff --git a/lib/v2/bilibili/watchlater.js b/lib/v2/bilibili/watchlater.js
index 04f803094..f6454eb14 100644
--- a/lib/v2/bilibili/watchlater.js
+++ b/lib/v2/bilibili/watchlater.js
@@ -26,7 +26,7 @@ module.exports = async (ctx) => {
const message = response.data.code === -6 ? '对应 uid 的 Bilibili 用户的 Cookie 已过期' : response.data.message;
throw new Error(`Error code ${response.data.code}: ${message}`);
}
- const list = response.data.data.list;
+ const list = response.data.data.list || [];
const out = list.map((item) => ({
title: item.title,
diff --git a/lib/v2/lala/maintainer.js b/lib/v2/lala/maintainer.js
new file mode 100644
index 000000000..387d961bf
--- /dev/null
+++ b/lib/v2/lala/maintainer.js
@@ -0,0 +1,3 @@
+module.exports = {
+ '/': ['cnkmmk'],
+};
diff --git a/lib/v2/lala/radar.js b/lib/v2/lala/radar.js
new file mode 100644
index 000000000..de35d68e8
--- /dev/null
+++ b/lib/v2/lala/radar.js
@@ -0,0 +1,13 @@
+module.exports = {
+ 'lala.im': {
+ _name: '荒岛',
+ '.': [
+ {
+ title: '博客',
+ docs: 'https://docs.rsshub.app/routes/blog#huang-dao',
+ source: ['/'],
+ target: '/lala',
+ },
+ ],
+ },
+};
diff --git a/lib/v2/lala/router.js b/lib/v2/lala/router.js
new file mode 100644
index 000000000..574929e02
--- /dev/null
+++ b/lib/v2/lala/router.js
@@ -0,0 +1,3 @@
+module.exports = function (router) {
+ router.get('/', require('./rss'));
+};
diff --git a/lib/v2/lala/rss.js b/lib/v2/lala/rss.js
new file mode 100644
index 000000000..82eba69a7
--- /dev/null
+++ b/lib/v2/lala/rss.js
@@ -0,0 +1,32 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+
+module.exports = async (ctx) => {
+ const currentUrl = 'https://lala.im';
+ const response = await got(`${currentUrl}/feed`);
+ const $ = cheerio.load(response.data, { xmlMode: true });
+ const titleMain = $('channel > title').text();
+ const descriptionMain = $('channel > description').text();
+ const items = $('channel > item')
+ .map((_, item) => {
+ const $item = $(item);
+ const link = $item.find('link').text();
+ const title = $item.find('title').text();
+ const description = $item.find('description').text();
+ const pubDate = $item.find('pubDate').text();
+ return {
+ link,
+ pubDate, // no need to normalize because it's from a valid RSS feed
+ title,
+ description,
+ };
+ })
+ .get();
+
+ ctx.state.data = {
+ title: titleMain,
+ description: descriptionMain,
+ link: currentUrl,
+ item: items,
+ };
+};
diff --git a/lib/v2/m4/index.js b/lib/v2/m4/index.js
new file mode 100644
index 000000000..ec63c6946
--- /dev/null
+++ b/lib/v2/m4/index.js
@@ -0,0 +1,82 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+const timezone = require('@/utils/timezone');
+const { parseDate } = require('@/utils/parse-date');
+const { art } = require('@/utils/render');
+const path = require('path');
+
+module.exports = async (ctx) => {
+ const { id = 'news', category = 'china' } = ctx.params;
+ const limit = ctx.query.limit ? Number.parseInt(ctx.query.limit, 10) : 30;
+
+ const rootUrl = `http://${id}.m4.cn`;
+ const currentUrl = new URL(category ? `/${category.replace(/\/$/, '')}/` : '/', rootUrl).href;
+
+ const { data: response } = await got(currentUrl);
+
+ const $ = cheerio.load(response);
+
+ let items = $('div.articleitem0 div.aheader0')
+ .slice(0, limit)
+ .toArray()
+ .map((item) => {
+ item = $(item);
+
+ const a = item.find('a').first();
+
+ return {
+ title: a.text(),
+ link: a.prop('href'),
+ description: art(path.join(__dirname, 'templates/description.art'), {
+ images: [
+ {
+ src: item.parent().find('div.aimg0 a img').prop('src'),
+ alt: a.text(),
+ },
+ ],
+ }),
+ category: item
+ .find('a.aclass')
+ .toArray()
+ .map((c) => $(c).text().replaceAll('[]', '').trim()),
+ pubDate: timezone(parseDate(item.find('span.atime').text()), +8),
+ };
+ });
+
+ items = await Promise.all(
+ items.map((item) =>
+ ctx.cache.tryGet(item.link, async () => {
+ const { data: detailResponse } = await got(item.link);
+
+ const content = cheerio.load(detailResponse);
+
+ item.title = content('h1').first().text();
+ item.description = art(path.join(__dirname, 'templates/description.art'), {
+ intro: content('div.aintro1, p.cont-summary').text(),
+ description: content('div.content0, div.cont-detail').html(),
+ });
+ item.category = content('span.dd0 a, a[rel="category"]')
+ .toArray()
+ .map((c) => content(c).text())
+ .slice(1);
+ item.pubDate = timezone(parseDate(content('span.atime1, span.post-time').text()), +8);
+
+ return item;
+ })
+ )
+ );
+
+ const image = $('a.logo0_b img').prop('src');
+
+ ctx.state.data = {
+ item: items,
+ title: $('title').text(),
+ link: currentUrl,
+ description: $('meta[name="description"]').prop('content'),
+ language: 'zh',
+ image,
+ subtitle: $('meta[name="keywords"]').prop('content'),
+ author: $('meta[name="author"]').prop('content'),
+ allowEmpty: true,
+ };
+};
diff --git a/lib/v2/m4/maintainer.js b/lib/v2/m4/maintainer.js
new file mode 100644
index 000000000..cc5628051
--- /dev/null
+++ b/lib/v2/m4/maintainer.js
@@ -0,0 +1,4 @@
+module.exports = {
+ '/mil/:category?': ['nczitzk'],
+ '/news/:category?': ['nczitzk'],
+};
diff --git a/lib/v2/m4/radar.js b/lib/v2/m4/radar.js
new file mode 100644
index 000000000..097604a24
--- /dev/null
+++ b/lib/v2/m4/radar.js
@@ -0,0 +1,87 @@
+module.exports = {
+ 'm4.cn': {
+ _name: '四月网',
+ news: [
+ {
+ title: '要闻 - 国内新闻',
+ docs: 'https://docs.rsshub.app/routes/new-media#si-yue-wang-yao-wen',
+ source: ['/china/'],
+ target: '/m4/news/china',
+ },
+ {
+ title: '要闻 - 国际新闻',
+ docs: 'https://docs.rsshub.app/routes/new-media#si-yue-wang-yao-wen',
+ source: ['/world/'],
+ target: '/m4/news/world',
+ },
+ {
+ title: '要闻 - 民生',
+ docs: 'https://docs.rsshub.app/routes/new-media#si-yue-wang-yao-wen',
+ source: ['/livelihood/'],
+ target: '/m4/news/livelihood',
+ },
+ {
+ title: '要闻 - 社会',
+ docs: 'https://docs.rsshub.app/routes/new-media#si-yue-wang-yao-wen',
+ source: ['/society/'],
+ target: '/m4/news/society',
+ },
+ {
+ title: '要闻 - 财经',
+ docs: 'https://docs.rsshub.app/routes/new-media#si-yue-wang-yao-wen',
+ source: ['/finance/'],
+ target: '/m4/news/finance',
+ },
+ {
+ title: '要闻 - 科技',
+ docs: 'https://docs.rsshub.app/routes/new-media#si-yue-wang-yao-wen',
+ source: ['/tech/'],
+ target: '/m4/news/tech',
+ },
+ ],
+ mil: [
+ {
+ title: '军事 - 中国军情',
+ docs: 'https://docs.rsshub.app/routes/new-media#si-yue-wang-jun-shi',
+ source: ['/china/'],
+ target: '/m4/mil/china',
+ },
+ {
+ title: '军事 - 国际军情',
+ docs: 'https://docs.rsshub.app/routes/new-media#si-yue-wang-jun-shi',
+ source: ['/world/'],
+ target: '/m4/mil/world',
+ },
+ {
+ title: '军事 - 军事评论',
+ docs: 'https://docs.rsshub.app/routes/new-media#si-yue-wang-jun-shi',
+ source: ['/views/'],
+ target: '/m4/mil/views',
+ },
+ {
+ title: '军事 - 军事历史',
+ docs: 'https://docs.rsshub.app/routes/new-media#si-yue-wang-jun-shi',
+ source: ['/history/'],
+ target: '/m4/mil/history',
+ },
+ {
+ title: '军事 - 军迷说',
+ docs: 'https://docs.rsshub.app/routes/new-media#si-yue-wang-jun-shi',
+ source: ['/talk/'],
+ target: '/m4/mil/talk',
+ },
+ {
+ title: '军事 - 图说军事',
+ docs: 'https://docs.rsshub.app/routes/new-media#si-yue-wang-jun-shi',
+ source: ['/photo/'],
+ target: '/m4/mil/photo',
+ },
+ {
+ title: '军事 - 武器库',
+ docs: 'https://docs.rsshub.app/routes/new-media#si-yue-wang-jun-shi',
+ source: ['/arms/'],
+ target: '/m4/mil/arms',
+ },
+ ],
+ },
+};
diff --git a/lib/v2/m4/router.js b/lib/v2/m4/router.js
new file mode 100644
index 000000000..6e412f06b
--- /dev/null
+++ b/lib/v2/m4/router.js
@@ -0,0 +1,3 @@
+module.exports = (router) => {
+ router.get('/:id?/:category*', require('./'));
+};
diff --git a/lib/v2/m4/templates/description.art b/lib/v2/m4/templates/description.art
new file mode 100644
index 000000000..249654e7e
--- /dev/null
+++ b/lib/v2/m4/templates/description.art
@@ -0,0 +1,21 @@
+{{ if images }}
+ {{ each images image }}
+ {{ if image?.src }}
+
+
{{ intro }}+{{ /if }} + +{{ if description }} + {{@ description }} +{{ /if }} \ No newline at end of file diff --git a/lib/v2/shisu/maintainer.js b/lib/v2/shisu/maintainer.js new file mode 100644 index 000000000..6c1396234 --- /dev/null +++ b/lib/v2/shisu/maintainer.js @@ -0,0 +1,3 @@ +module.exports = { + '/news/:category': ['Duuckjing'], +}; diff --git a/lib/v2/shisu/news.js b/lib/v2/shisu/news.js new file mode 100644 index 000000000..bc58b68c5 --- /dev/null +++ b/lib/v2/shisu/news.js @@ -0,0 +1,64 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); +const timezone = require('@/utils/timezone'); + +const url = 'https://news.shisu.edu.cn'; +const banner = 'https://news.shisu.edu.cn/news/index/39adf3d9ae414bc39c6d3b9316ae531f.png'; + +module.exports = async (ctx) => { + const { section = 'news' } = ctx.params; + const { data: r } = await got(`${url}/${section}/index.html`); + const $ = cheerio.load(r); + let itemsoup; + switch (section) { + case 'news': + itemsoup = $('#gallery-wrapper > article') + .toArray() + .map((i0) => { + const i = $(i0); + const img = i.find('img').attr('src'); + return { + title: i.find('a').text().trim(), + link: `${url}${i.find('a').attr('href')}`, + category: i.find('.in-con02 > span:nth-child(1)').text(), + itunes_item_image: `${url}${img}`, + }; + }); + break; + default: + itemsoup = $('li.clear') + .toArray() + .map((i0) => { + const i = $(i0); + return { + title: i.find('h3>a').attr('title').trim(), + link: `${url}${i.find('h3>a').attr('href')}`, + category: i.find('p>span:nth-child(1)').text(), + }; + }); + } + const items = await Promise.all( + itemsoup.map((j) => + ctx.cache.tryGet(j.link, async () => { + const { data: r } = await got(j.link); + const $ = cheerio.load(r); + const img = $('.tempWrap > ul > li:nth-child(1)> img').attr('src'); + j.description = $('.ot_main_r .content').html(); + j.author = $('.math_time_l > span:nth-child(3)').text().trim(); + j.pubDate = timezone(parseDate($('.math_time_l > span:nth-child(2)').text(), 'YYYY-MM-DD'), +8); + if (!j.itunes_item_image) { + j.itunes_item_image = img ? `${url}${img}` : banner; + } + return j; + }) + ) + ); + + ctx.state.data = { + title: `上外新闻|SISU TODAY -${section.charAt(0).toUpperCase() + section.slice(1)}`, + image: 'https://bkimg.cdn.bcebos.com/pic/8d5494eef01f3a296b70affa9825bc315c607c4d?x-bce-process=image/resize,m_lfit,w_536,limit_1/quality,Q_70', + link: `${url}/${section}/index.html`, + item: items, + }; +}; diff --git a/lib/v2/shisu/radar.js b/lib/v2/shisu/radar.js new file mode 100644 index 000000000..2afb8b858 --- /dev/null +++ b/lib/v2/shisu/radar.js @@ -0,0 +1,13 @@ +module.exports = { + 'shisu.edu.cn': { + _name: '上海外国语大学', + news: [ + { + title: '上外新闻', + docs: 'https://docs.rsshub.app/routes/university#shang-hai-wai-guo-yu-da-xue', + source: ['/:category/index.html'], + target: '/shisu/news/:category', + }, + ], + }, +}; diff --git a/lib/v2/shisu/router.js b/lib/v2/shisu/router.js new file mode 100644 index 000000000..37ca55cd6 --- /dev/null +++ b/lib/v2/shisu/router.js @@ -0,0 +1,3 @@ +module.exports = (router) => { + router.get('/news/:category', require('./news')); +}; diff --git a/lib/v2/u3c3/index.js b/lib/v2/u3c3/index.js index e35ae9d41..428357d49 100644 --- a/lib/v2/u3c3/index.js +++ b/lib/v2/u3c3/index.js @@ -2,7 +2,7 @@ const got = require('@/utils/got'); const cheerio = require('cheerio'); module.exports = async (ctx) => { - const type = ctx.params.type; + const { type, keywoard, preview } = ctx.params; // should be: // undefined // U3C3 @@ -15,13 +15,12 @@ module.exports = async (ctx) => { const rootURL = 'https://www.u3c3.com'; let currentURL; let title; - if (type === undefined) { + if (keywoard) { + currentURL = `${rootURL}/?search=${keywoard}`; + title = `search ${keywoard} - u3c3`; + } else if (type === undefined) { currentURL = rootURL; title = 'home - u3c3'; - } else if (type === 'search') { - const keyword = ctx.params.keyword === undefined ? '' : ctx.params.keyword; - currentURL = `${rootURL}/?search=${keyword}`; - title = `search ${keyword} - u3c3`; } else { currentURL = `${rootURL}/?type=${type}&p=1`; title = `${type} - u3c3`; @@ -30,7 +29,7 @@ module.exports = async (ctx) => { const response = await got(currentURL); const $ = cheerio.load(response.data); - const items = $('body > div.container > div.table-responsive > table > tbody > tr') + const list = $('body > div.container > div.table-responsive > table > tbody > tr') .toArray() .map((item) => { item = $(item); @@ -50,6 +49,21 @@ module.exports = async (ctx) => { }; }); + const items = preview + ? await Promise.all( + list.map((item) => + ctx.cache.tryGet(item.link, async () => { + const { data: response } = await got(item.link); + const $ = cheerio.load(response); + + item.description = $('div.panel-footer > img:first-child').parent().html(); + + return item; + }) + ) + ) + : list; + ctx.state.data = { title, description: title, diff --git a/lib/v2/u3c3/maintainer.js b/lib/v2/u3c3/maintainer.js index c7fbc179a..725114f6b 100644 --- a/lib/v2/u3c3/maintainer.js +++ b/lib/v2/u3c3/maintainer.js @@ -1,3 +1,4 @@ module.exports = { - '/:type?/:keyword?': ['noname1897'], + '/search/:keyword/:preview?': ['storytellerF'], + '/:type?/:preview?': ['noname1897', 'storytellerF'], }; diff --git a/lib/v2/u3c3/router.js b/lib/v2/u3c3/router.js index 1b78935d6..c86f95d51 100644 --- a/lib/v2/u3c3/router.js +++ b/lib/v2/u3c3/router.js @@ -1,3 +1,4 @@ module.exports = function (router) { - router.get('/:type?/:keyword?', require('./index')); + router.get('/search/:keyword/:preview?', require('./index')); + router.get('/:type?/:preview?', require('./index')); }; diff --git a/lib/v2/unraid/community-apps.js b/lib/v2/unraid/community-apps.js new file mode 100644 index 000000000..c19834469 --- /dev/null +++ b/lib/v2/unraid/community-apps.js @@ -0,0 +1,35 @@ +const got = require('@/utils/got'); +const { parseDate } = require('@/utils/parse-date'); + +const appFeedUrl = 'https://raw.githubusercontent.com/Squidly271/AppFeed/master/applicationFeed.json'; +const defaultLimit = 20; + +module.exports = async (ctx) => { + const { data } = await got(appFeedUrl); + const limit = ctx.query.limit ? Number.parseInt(ctx.query.limit) : defaultLimit; + + let appList = data.applist; + appList = appList.map((app) => { + const _pubDate = app.LastUpdate ?? app.FirstSeen ?? 0; + return { + ...app, + _pubDate, + }; + }); + appList.sort((a, b) => b._pubDate - a._pubDate); + const returnedAppList = appList.slice(0, limit); + + ctx.state.data = { + title: 'Unraid Community Apps', + link: 'https://unraid.net/community/apps', + image: 'https://craftassets.unraid.net/static/favicon/favicon.ico?v=1.0', + item: returnedAppList.map((app) => ({ + title: `${app.Name} (${app.Repository ?? 'Unknown repository'})`, + link: app.Registry ?? `https://unraid.net/community/apps?q=${app.Name}`, + description: app.Overview.replaceAll('\r\n', '