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 }} +
+ {{ image.alt }} +
+ {{ /if }} + {{ /each }} +{{ /if }} + +{{ if intro }} +
{{ 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', '
').replaceAll('\n', '
').replaceAll('[br]', '
'), + pubDate: parseDate(app._pubDate * 1000), + category: app.CategoryList, + upvotes: app.stars, + })), + }; +}; diff --git a/lib/v2/unraid/maintainer.js b/lib/v2/unraid/maintainer.js new file mode 100644 index 000000000..159db7249 --- /dev/null +++ b/lib/v2/unraid/maintainer.js @@ -0,0 +1,3 @@ +module.exports = { + '/community-apps': ['KTachibanaM'], +}; diff --git a/lib/v2/unraid/radar.js b/lib/v2/unraid/radar.js new file mode 100644 index 000000000..58de116d8 --- /dev/null +++ b/lib/v2/unraid/radar.js @@ -0,0 +1,13 @@ +module.exports = { + 'unraid.net': { + _name: 'Unraid', + '.': [ + { + title: 'Community Apps', + docs: 'https://docs.rsshub.app/routes/program-update#unraid', + source: ['/community/apps'], + target: '/unraid/community-apps', + }, + ], + }, +}; diff --git a/lib/v2/unraid/router.js b/lib/v2/unraid/router.js new file mode 100644 index 000000000..6aa87f760 --- /dev/null +++ b/lib/v2/unraid/router.js @@ -0,0 +1,3 @@ +module.exports = (router) => { + router.get('/community-apps', require('./community-apps')); +}; diff --git a/website/docs/routes/blog.mdx b/website/docs/routes/blog.mdx index 3fad78967..157ffada8 100644 --- a/website/docs/routes/blog.mdx +++ b/website/docs/routes/blog.mdx @@ -340,6 +340,12 @@ +## 荒岛 {#huang-dao} + +### 博客 {#huang-dao-bo-ke} + + + ## 劍心.回憶 {#jian-xin-hui-yi} ### 分类 {#jian-xin-hui-yi-fen-lei} diff --git a/website/docs/routes/multimedia.mdx b/website/docs/routes/multimedia.mdx index 10a4cf967..bdb4e747b 100644 --- a/website/docs/routes/multimedia.mdx +++ b/website/docs/routes/multimedia.mdx @@ -1198,11 +1198,11 @@ Refer to [https://developers.themoviedb.org/3/getting-started/languages](https:/ ### Keyword Search {#u3c3-keyword-search} - + ### Type {#u3c3-type} - + ## U9A9 {#u9a9} diff --git a/website/docs/routes/new-media.mdx b/website/docs/routes/new-media.mdx index d95d8d02a..121f12743 100644 --- a/website/docs/routes/new-media.mdx +++ b/website/docs/routes/new-media.mdx @@ -4328,6 +4328,34 @@ +## 四月网 {#si-yue-wang} + +### 要闻 {#si-yue-wang-yao-wen} + + + | 分类 | ID | + | ------------------------------------- | ---------- | + | [国内新闻](http://news.m4.cn/china/) | china | + | [国际新闻](http://news.m4.cn/world/) | world | + | [民生](http://news.m4.cn/livelihood/) | livelihood | + | [社会](http://news.m4.cn/society/) | society | + | [财经](http://news.m4.cn/finance/) | finance | + | [科技](http://news.m4.cn/tech/) | tech | + + +### 军事 {#si-yue-wang-jun-shi} + + + | 分类 | ID | + | ------------------------------------- | ------- | + | [中国军情](http://mil.m4.cn/china/) | china | + | [国际军情](http://mil.m4.cn/world/) | world | + | [军事评论](http://mil.m4.cn/views/) | views | + | [军事历史](http://mil.m4.cn/history/) | history | + | [军迷说](http://mil.m4.cn/talk/) | talk | + | [武器库](http://mil.m4.cn/arms/) | arms | + + ## 搜狐号 {#sou-hu-hao} ### 更新 {#sou-hu-hao-geng-xin} diff --git a/website/docs/routes/program-update.mdx b/website/docs/routes/program-update.mdx index f67d994fd..bbfe64f4f 100644 --- a/website/docs/routes/program-update.mdx +++ b/website/docs/routes/program-update.mdx @@ -643,6 +643,12 @@ Logseq 开发团队已经放弃了 [旧网站](https://logseq.com/blog)。 +## Unraid {#unraid} + +### Community Apps {#unraid-community-apps} + + + ## Western Digital {#western-digital} ### Download {#western-digital-download} diff --git a/website/docs/routes/university.mdx b/website/docs/routes/university.mdx index 6b855591d..239f5e13e 100644 --- a/website/docs/routes/university.mdx +++ b/website/docs/routes/university.mdx @@ -2625,6 +2625,16 @@ jsjxy.hbut.edu.cn 证书链不全,自建 RSSHub 可设置环境变量 NODE\_TL | 1793 | sstmzs | +## 上海外国语大学 {#shang-hai-wai-guo-yu-da-xue} + +### 上外新闻 {#shang-hai-wai-guo-yu-da-xue-shang-wai-xin-wen} + + + | 首页 | 特稿 | 学术 | 教学 | 国际 | 校园 | 人物 | 视讯 | 公告 | + | ---- | ------- | --------- | ---------- | ------------- | ------ | ------ | ---------- | ------ | + | news | gazette | research- | academics- | international | campus | people | multimedia | notice | + + ## 深圳大学 {#shen-zhen-da-xue} ### 研究生招生网 {#shen-zhen-da-xue-yan-jiu-sheng-zhao-sheng-wang}