diff --git a/.github/workflows/scrape.yml b/.github/workflows/scrape.yml index f3ef22e85f..3c626c5144 100644 --- a/.github/workflows/scrape.yml +++ b/.github/workflows/scrape.yml @@ -7,15 +7,26 @@ on: paths: - '.github/workflows/scrape.yml' - 'docs/**' + workflow_dispatch: ~ jobs: scrape-docs: runs-on: ubuntu-latest steps: - - name: Wait for Netlify to finish - run: sleep 3m - name: Checkout uses: actions/checkout@v3 + - name: Use Node.js v16 + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'yarn' + - name: Install dependencies (yarn) + run: yarn + - name: Pull image + run: docker pull getmeili/docs-scraper + - name: Wait for Netlify to finish + run: sleep 3m + if: github.event_name == 'push' - name: Run docs-scraper env: HOST_URL: ${{ secrets.MEILISEARCH_HOST_URL }} @@ -27,3 +38,14 @@ jobs: -e MEILISEARCH_API_KEY=$API_KEY \ -v $CONFIG_FILE_PATH:/docs-scraper/config.json \ getmeili/docs-scraper pipenv run ./docs_scraper config.json + - name: Update meilisearch settings + uses: actions/github-script@v6 + env: + HOST_URL: ${{ secrets.MEILISEARCH_HOST_URL }} + API_KEY: ${{ secrets.MEILISEARCH_API_KEY }} + with: + script: | + const meilisearchHost = process.env.HOST_URL + const meilisearchApiKey = process.env.API_KEY + const script = require(`${process.env.GITHUB_WORKSPACE}/scripts/docs-scraper/settings.js`) + await script({ core }, meilisearchHost, meilisearchApiKey) diff --git a/assets/radar-rules.js b/assets/radar-rules.js index 08f995c041..6bd1c0680e 100644 --- a/assets/radar-rules.js +++ b/assets/radar-rules.js @@ -34,21 +34,6 @@ s: [{ title: '热搜榜', docs: 'https://docs.rsshub.app/social-media.html#wei-bo', source: '/top/summary', target: '/weibo/search/hot' }], }, 'weibo.cn': { _name: '微博', m: [{ title: '博主', docs: 'https://docs.rsshub.app/social-media.html#wei-bo', source: ['/u/:uid', '/profile/:uid'], target: '/weibo/user/:uid' }] }, - 'pixiv.net': { - _name: 'Pixiv', - www: [ - { title: '用户收藏', docs: 'https://docs.rsshub.app/social-media.html#pixiv', source: '/users/:id/bookmarks/artworks', target: '/pixiv/user/bookmarks/:id' }, - { title: '用户动态', docs: 'https://docs.rsshub.app/social-media.html#pixiv', source: '/users/:id', target: '/pixiv/user/:id' }, - { title: '排行榜', docs: 'https://docs.rsshub.app/social-media.html#pixiv', source: '/ranking.php', target: (params, url) => `/pixiv/ranking/${new URL(url).searchParams.get('mode') || 'daily'}` }, - { - title: '关键词', - docs: 'https://docs.rsshub.app/social-media.html#pixiv', - source: ['/tags/:keyword', '/tags/:keyword/:type?'], - target: (params, url) => `/pixiv/search/:keyword/${new URL(url).searchParams.get('order')}/${new URL(url).searchParams.get('mode')}`, - }, - { title: '关注的新作品', docs: 'https://docs.rsshub.app/social-media.html#pixiv', source: '/bookmark_new_illust.php', target: '/pixiv/user/illustfollows' }, - ], - }, 'github.com': { _name: 'GitHub', '.': [ @@ -322,17 +307,6 @@ ], sites: [{ title: 'Sites', docs: 'https://docs.rsshub.app/blog.html#google-sites', source: ['/site/:id/*', '/site/:id'], target: '/google/sites/:id' }], }, - 'qidian.com': { - _name: '起点', - book: [ - { title: '章节', docs: 'https://docs.rsshub.app/reading.html#qi-dian', source: '/info/:id', target: '/qidian/chapter/:id' }, - { title: '讨论区', docs: 'https://docs.rsshub.app/reading.html#qi-dian', source: '/info/:id', target: '/qidian/forum/:id' }, - ], - www: [ - { title: '限免', docs: 'https://docs.rsshub.app/reading.html#qi-dian', source: '/free', target: '/qidian/free' }, - { title: '女生限免', docs: 'https://docs.rsshub.app/reading.html#qi-dian', source: '/mm/free', target: '/qidian/free/mm' }, - ], - }, 'hackerone.com': { _name: 'HackerOne', '.': [{ title: 'HackerOne Hacker Activity', docs: 'https://docs.rsshub.app/other.html#hackerone-hacker-activity', source: '/hacktivity', target: '/hackerone/hacktivity' }] }, 'cowlevel.net': { _name: '奶牛关', '.': [{ title: '元素文章', docs: 'https://docs.rsshub.app/game.html#nai-niu-guan', source: ['/element/:id', '/element/:id/article'], target: '/cowlevel/element/:id' }] }, 'beijing.gov.cn': { wjw: [{ title: '北京卫生健康委员会', docs: 'https://docs.rsshub.app/government.html#bei-jing-shi-wei-sheng-jian-kang-wei-yuan-hui', source: '/xwzx_20031/:caty', target: '/gov/beijing/mhc/:caty' }] }, diff --git a/docs/anime.md b/docs/anime.md index ce8443c735..c7b9af176b 100644 --- a/docs/anime.md +++ b/docs/anime.md @@ -363,7 +363,21 @@ Sources ### Love Live 官网最新 News - + + +### Love Live 官网分类 Topics + + + +| 子企划名(非全称) | Lovelive! | Lovelive! Sunshine!! | Lovelive! Nijigasaki High School Idol Club | Lovelive! Superstar!! | +| --------- | ----------- | -------------------- | ------------------------------------------ | --------------------- | +| `abbr`参数 | otonokizaka | uranohoshi | nijigasaki | yuigaoka | + +| 分类名 | 全てのニュース | 音楽商品 | アニメ映像商品 | キャスト映像商品 | 劇場 | アニメ放送 / 配信 | キャスト配信 / ラジオ | ライブ / イベント | ブック | グッズ | ゲーム | メディア | ご当地情報 | その他 | キャンペーン | +| ------------ | ------------ | ----- | ----------- | ---------- | ------- | ---------- | ------------ | ---------- | ----- | ----- | ---- | ----- | ----- | ----- | -------- | +| `category`参数 | *无参数* | music | anime_movie | cast_movie | theater | onair | radio | event | books | goods | game | media | local | other | campaign | + + ## Mox.moe diff --git a/docs/en/anime.md b/docs/en/anime.md index 1e5c6d7952..57d57ef406 100644 --- a/docs/en/anime.md +++ b/docs/en/anime.md @@ -72,7 +72,22 @@ When `posts` is selected as the value of the parameter **source**, the parameter ### Love Live! Official Website Latest NEWS - + + +### Love Live Official Website Categories Topics + + + +| Sub-project name (not full name) | Lovelive! | Lovelive! Sunshine!! | Lovelive! Nijigasaki High School Idol Club | Lovelive! Superstar!! | +| -------------------------------- | ----------- | -------------------- | ------------------------------------------ | --------------------- | +| `abbr`parameter | otonokizaka | uranohoshi | nijigasaki | yuigaoka | + + +| category name | 全てのニュース | 音楽商品 | アニメ映像商品 | キャスト映像商品 | 劇場 | アニメ放送/配信 | キャスト配信/ラジオ | ライブ/イベント | ブック | グッズ | ゲーム | メディア | ご当地情報 | その他 | キャンペーン | +| -------------- | --------------- | -------- | -------------- | ---------------- | ------- | --------------- | ------------------- | --------------- | ------ | ------ | ------ | -------- | ---------- | ------ | ------------ | +| `category`parameter | *No parameter* | music | anime_movie | cast_movie | theater | onair | radio | event | books | goods | game | media | local | other | campaign | + + ## Touhougarakuta diff --git a/docs/en/program-update.md b/docs/en/program-update.md index c3ed1eba37..942c4c8853 100644 --- a/docs/en/program-update.md +++ b/docs/en/program-update.md @@ -236,7 +236,7 @@ Language ### Script Update - + ## Hugo diff --git a/docs/en/social-media.md b/docs/en/social-media.md index eb80af7181..3cea3b9fc7 100644 --- a/docs/en/social-media.md +++ b/docs/en/social-media.md @@ -214,6 +214,10 @@ Though, every Story expires after 24 hours, so it may be not so serious. +### User Novels + + + ### Rankings diff --git a/docs/program-update.md b/docs/program-update.md index 542ec10e90..455863da18 100644 --- a/docs/program-update.md +++ b/docs/program-update.md @@ -268,7 +268,7 @@ pageClass: routes ### 脚本更新 - + ## Hugo diff --git a/docs/reading.md b/docs/reading.md index 9ff701babd..e6d7ce4920 100644 --- a/docs/reading.md +++ b/docs/reading.md @@ -399,6 +399,10 @@ count 的取值范围为 1-12,为防止请求次数过多,推荐设置为 5 +### 作者 + + + ## 起点图 ### 首订 diff --git a/docs/social-media.md b/docs/social-media.md index c0ad806d3b..63046888f3 100644 --- a/docs/social-media.md +++ b/docs/social-media.md @@ -562,6 +562,10 @@ Instagram Stories 没有可靠的 guid,你的 RSS 阅读器可能将同一条 +### 用户小说 + + + ### 排行榜 diff --git a/docs/traditional-media.md b/docs/traditional-media.md index ab639f15f8..5dbcb95bf1 100644 --- a/docs/traditional-media.md +++ b/docs/traditional-media.md @@ -1152,6 +1152,18 @@ Type 栏目: +## 華視 + +### 新聞 + + + +| 即時 | 氣象 | 政治 | 國際 | 社會 | 運動 | 生活 | 財經 | 台語 | 地方 | 產業 | 綜合 | 藝文 | 娛樂 | +| ---- | ------- | -------- | ------------- | ------- | ------ | ---- | ----- | --------- | ----- | -- | ------- | ---- | --------- | +| real | weather | politics | international | society | sports | life | money | taiwanese | local | pr | general | arts | entertain | + + + ## 环球网 ### 分类 @@ -1364,6 +1376,18 @@ category 对应的关键词有 +## 聯合新聞網 + +### 即時新聞 + + + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 99 | +| -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | --- | +| 精選 | 要聞 | 社會 | 地方 | 兩岸 | 國際 | 財經 | 運動 | 娛樂 | 生活 | 股市 | 文教 | 數位 | 不分類 | + + + ## 路透社 ::: warning 迁移说明 diff --git a/docs/university.md b/docs/university.md index 83f266f9d5..eba3086735 100644 --- a/docs/university.md +++ b/docs/university.md @@ -1811,7 +1811,7 @@ jsjxy.hbut.edu.cn 证书链不全,自建 RSSHub 可设置环境变量 NODE_TLS ### 教务处 - + | 通知公告 | 教学服务 | 教学建设 | 学生培养 | 教学资源 | | ---- | ---- | ---- | ---- | ---- | @@ -1819,9 +1819,27 @@ jsjxy.hbut.edu.cn 证书链不全,自建 RSSHub 可设置环境变量 NODE_TLS +### 研究生院 + + + +| 通知公告 | 新闻动态 | 学术信息 | 师生风采 | +| ---- | ---- | ---- | ---- | +| tzgg | xwdt | xsxx | ssfc | + + + +### 自动化学院 + + + +| 综合新闻 | 党委行政 | 人事 / 合作 | 研究生培养 | 本科生培养 | 学生工作 | 通知公告 | 学术信息 | 答辩公告 | +| ---- | ---- | ------- | ----- | ----- | ---- | ---- | ---- | ---- | +| zhxw | dwxz | rshz | yjs | bks | xsgz | tzgg | xsxx | dbgg | + ### 计算机科学与技术学院 - + | 通知公告 | 热点新闻 | 学科科研 | 教学动态 | 本科生培养 | 研究生培养 | 学生工作 | | ---- | ---- | ---- | ---- | ----- | ----- | ---- | @@ -1829,16 +1847,6 @@ jsjxy.hbut.edu.cn 证书链不全,自建 RSSHub 可设置环境变量 NODE_TLS -### 研究生院 - - - -| 最近动态 | 研院新闻 | 上级文件 | 管理文件 | 信息服务 | -| ------ | ---- | ---- | ---- | ---- | -| latest | yyxw | sjwj | glwj | xxfw | - - - ## 南京理工大学 ### 教务处 diff --git a/lib/radar-rules.js b/lib/radar-rules.js index 5bc9ce5681..0453e4740d 100644 --- a/lib/radar-rules.js +++ b/lib/radar-rules.js @@ -45,41 +45,6 @@ module.exports = { }, ], }, - 'pixiv.net': { - _name: 'Pixiv', - www: [ - { - title: '用户收藏', - docs: 'https://docs.rsshub.app/social-media.html#pixiv', - source: '/users/:id/bookmarks/artworks', - target: '/pixiv/user/bookmarks/:id', - }, - { - title: '用户动态', - docs: 'https://docs.rsshub.app/social-media.html#pixiv', - source: '/users/:id', - target: '/pixiv/user/:id', - }, - { - title: '排行榜', - docs: 'https://docs.rsshub.app/social-media.html#pixiv', - source: '/ranking.php', - target: (params, url) => `/pixiv/ranking/${new URL(url).searchParams.get('mode') || 'daily'}`, - }, - { - title: '关键词', - docs: 'https://docs.rsshub.app/social-media.html#pixiv', - source: ['/tags/:keyword', '/tags/:keyword/:type?'], - target: (params, url) => `/pixiv/search/:keyword/${new URL(url).searchParams.get('order')}/${new URL(url).searchParams.get('mode')}`, - }, - { - title: '关注的新作品', - docs: 'https://docs.rsshub.app/social-media.html#pixiv', - source: '/bookmark_new_illust.php', - target: '/pixiv/user/illustfollows', - }, - ], - }, 'smzdm.com': { _name: '什么值得买', www: [ @@ -782,37 +747,6 @@ module.exports = { }, ], }, - 'qidian.com': { - _name: '起点', - book: [ - { - title: '章节', - docs: 'https://docs.rsshub.app/reading.html#qi-dian', - source: '/info/:id', - target: '/qidian/chapter/:id', - }, - { - title: '讨论区', - docs: 'https://docs.rsshub.app/reading.html#qi-dian', - source: '/info/:id', - target: '/qidian/forum/:id', - }, - ], - www: [ - { - title: '限免', - docs: 'https://docs.rsshub.app/reading.html#qi-dian', - source: '/free', - target: '/qidian/free', - }, - { - title: '女生限免', - docs: 'https://docs.rsshub.app/reading.html#qi-dian', - source: '/mm/free', - target: '/qidian/free/mm', - }, - ], - }, 'hackerone.com': { _name: 'HackerOne', '.': [ diff --git a/lib/router.js b/lib/router.js index 1f8b037a37..fa8f760775 100644 --- a/lib/router.js +++ b/lib/router.js @@ -116,12 +116,12 @@ router.get('/mzitu/category/:category', lazyloadRouteHandler('./routes/mzitu/cat router.get('/mzitu/post/:id', lazyloadRouteHandler('./routes/mzitu/post')); router.get('/mzitu/tag/:tag', lazyloadRouteHandler('./routes/mzitu/tag')); -// pixiv -router.get('/pixiv/user/bookmarks/:id', lazyloadRouteHandler('./routes/pixiv/bookmarks')); -router.get('/pixiv/user/illustfollows', lazyloadRouteHandler('./routes/pixiv/illustfollow')); -router.get('/pixiv/user/:id', lazyloadRouteHandler('./routes/pixiv/user')); -router.get('/pixiv/ranking/:mode/:date?', lazyloadRouteHandler('./routes/pixiv/ranking')); -router.get('/pixiv/search/:keyword/:order?/:mode?', lazyloadRouteHandler('./routes/pixiv/search')); +// pixiv migrated to v2 +// router.get('/pixiv/user/bookmarks/:id', lazyloadRouteHandler('./routes/pixiv/bookmarks')); +// router.get('/pixiv/user/illustfollows', lazyloadRouteHandler('./routes/pixiv/illustfollow')); +// router.get('/pixiv/user/:id', lazyloadRouteHandler('./routes/pixiv/user')); +// router.get('/pixiv/ranking/:mode/:date?', lazyloadRouteHandler('./routes/pixiv/ranking')); +// router.get('/pixiv/search/:keyword/:order?/:mode?', lazyloadRouteHandler('./routes/pixiv/search')); // pixiv-fanbox router.get('/fanbox/:user?', lazyloadRouteHandler('./routes/fanbox/main')); @@ -338,11 +338,11 @@ router.get('/mi/bbs/board/:boardId', lazyloadRouteHandler('./routes/mi/board')); // Keep // router.get('/keep/user/:id', lazyloadRouteHandler('./routes/keep/user')); -// 起点 -router.get('/qidian/chapter/:id', lazyloadRouteHandler('./routes/qidian/chapter')); -router.get('/qidian/forum/:id', lazyloadRouteHandler('./routes/qidian/forum')); -router.get('/qidian/free/:type?', lazyloadRouteHandler('./routes/qidian/free')); -router.get('/qidian/free-next/:type?', lazyloadRouteHandler('./routes/qidian/free-next')); +// 起点 migrated to v2 +// router.get('/qidian/chapter/:id', lazyloadRouteHandler('./routes/qidian/chapter')); +// router.get('/qidian/forum/:id', lazyloadRouteHandler('./routes/qidian/forum')); +// router.get('/qidian/free/:type?', lazyloadRouteHandler('./routes/qidian/free')); +// router.get('/qidian/free-next/:type?', lazyloadRouteHandler('./routes/qidian/free-next')); // 纵横 router.get('/zongheng/chapter/:id', lazyloadRouteHandler('./routes/zongheng/chapter')); @@ -387,8 +387,8 @@ router.get('/xuangubao/subject/:subject_id', lazyloadRouteHandler('./routes/xuan // router.get('/xueqiu/hots', lazyloadRouteHandler('./routes/xueqiu/hots')); // router.get('/xueqiu/stock_comments/:id/:titleLength?', lazyloadRouteHandler('./routes/xueqiu/stock_comments')); -// Greasy Fork -router.get('/greasyfork/:language/:domain?', lazyloadRouteHandler('./routes/greasyfork/scripts')); +// Greasy Fork migrated to v2 +// router.get('/greasyfork/:language/:domain?', lazyloadRouteHandler('./routes/greasyfork/scripts')); // Gwern Bran­wen router.get('/gwern/:category', lazyloadRouteHandler('./routes/gwern/category')); diff --git a/lib/routes/bbc/index.js b/lib/routes/bbc/index.js index bfd70b9653..71fb375aac 100644 --- a/lib/routes/bbc/index.js +++ b/lib/routes/bbc/index.js @@ -85,6 +85,7 @@ module.exports = async (ctx) => { ctx.state.data = { title, link, + image: 'https://www.bbc.com/favicon.ico', description: title, item: items, }; diff --git a/lib/routes/pixiv/token.js b/lib/routes/pixiv/token.js deleted file mode 100644 index f00067b6ce..0000000000 --- a/lib/routes/pixiv/token.js +++ /dev/null @@ -1,74 +0,0 @@ -const config = require('@/config').value; -const logger = require('@/utils/logger'); -const got = require('./pixiv-got'); -const { maskHeader } = require('./constants'); - -let token = null; - -const authorizationInfo = { - client_id: 'MOBrBDS8blbauoSck0ZfDbtuzpyT', - client_secret: 'lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj', - hash_secret: '28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c', -}; - -const refreshToken = () => - got({ - method: 'post', - url: 'https://oauth.secure.pixiv.net/auth/token', - form: { - ...authorizationInfo, - get_secure_url: 1, - grant_type: 'refresh_token', - refresh_token: config.pixiv.refreshToken, - }, - headers: { - ...maskHeader, - }, - responseType: 'json', - resolveBodyOnly: true, - }).catch((e) => { - logger.error('Pixiv refresh token failed.'); - logger.debug(e); - }); - -async function tickToken() { - const result = await refreshToken(); - let expireTime; - if (result && result.access_token) { - logger.debug('Pixiv refresh token success.'); - token = result.access_token; - expireTime = result.expires_in; - } else { - expireTime = 30; - logger.error(`Pixiv refresh token failed, retry in ${expireTime} seconds.`); - } - // If is package, the mock server should be closed after request, needn't to refresh - if (!config.isPackage) { - setTimeout(tickToken, expireTime); - } -} - -let tickTokenStarted = false; - -async function startTickToken() { - if (!tickTokenStarted) { - // 如果tickToken没启动 - tickTokenStarted = true; - await tickToken(); // 启动tickToken - } -} - -async function waitForToken() { - while (!token) { - // eslint-disable-next-line no-await-in-loop - await new Promise((resolve) => setTimeout(resolve, 0)); - } - return token; -} - -module.exports = async () => { - await startTickToken(); - return waitForToken(); -}; - -module.exports.tickToken = tickToken; diff --git a/lib/v2/baidu/tieba/forum.js b/lib/v2/baidu/tieba/forum.js index 1845339b9d..622e8df254 100644 --- a/lib/v2/baidu/tieba/forum.js +++ b/lib/v2/baidu/tieba/forum.js @@ -39,13 +39,12 @@ module.exports = async (ctx) => { .toArray() .map((element) => { const item = $(element); - return ``; + return ``; }) .join(''); return { title, - // description: `

${details}

${medias}

作者:${author_name}

`, description: art(path.join(__dirname, '../templates/forum.art'), { details, medias, diff --git a/lib/v2/cts/maintainer.js b/lib/v2/cts/maintainer.js new file mode 100644 index 0000000000..5c4aa089da --- /dev/null +++ b/lib/v2/cts/maintainer.js @@ -0,0 +1,3 @@ +module.exports = { + '/:category': ['miles170'], +}; diff --git a/lib/v2/cts/news.js b/lib/v2/cts/news.js new file mode 100644 index 0000000000..e77d0ae898 --- /dev/null +++ b/lib/v2/cts/news.js @@ -0,0 +1,40 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); +const asyncPool = require('tiny-async-pool'); + +module.exports = async (ctx) => { + const category = ctx.params.category; + const currentUrl = `https://news.cts.com.tw/${category}/index.html`; + const response = await got(currentUrl); + const $ = cheerio.load(response.data); + const items = []; + for await (const data of asyncPool(5, $('#newslist-top a[title]').slice(0, ctx.query.limit ? parseInt(ctx.query.limit) : 20), (item) => { + item = $(item); + const link = item.attr('href'); + return ctx.cache.tryGet(link, async () => { + const response = await got(link); + const $ = cheerio.load(response.data); + const author = $('.artical-content p:eq(0)').text().trim(); + $('.artical-content p:eq(0), .artical-content .flexbox').remove(); + + return { + title: item.attr('title'), + author, + description: $('.artical-content').html(), + category: $('meta[property="article:section"]').attr('content'), + pubDate: parseDate($('meta[property="article:published_time"]').attr('content')), + link, + }; + }); + })) { + items.push(data); + } + + ctx.state.data = { + title: $('title').text(), + link: currentUrl, + description: $('meta[name="description"]').attr('content'), + item: items, + }; +}; diff --git a/lib/v2/cts/radar.js b/lib/v2/cts/radar.js new file mode 100644 index 0000000000..6dc1db9275 --- /dev/null +++ b/lib/v2/cts/radar.js @@ -0,0 +1,13 @@ +module.exports = { + 'cts.com.tw': { + _name: '華視', + news: [ + { + title: '新聞', + docs: 'https://docs.rsshub.app/traditional-media.html#hua-shi-xin-wen', + source: '/:category/index.html', + target: '/cts/:category', + }, + ], + }, +}; diff --git a/lib/v2/cts/router.js b/lib/v2/cts/router.js new file mode 100644 index 0000000000..b9311da838 --- /dev/null +++ b/lib/v2/cts/router.js @@ -0,0 +1,3 @@ +module.exports = function (router) { + router.get('/:category', require('./news')); +}; diff --git a/lib/v2/greasyfork/maintainer.js b/lib/v2/greasyfork/maintainer.js new file mode 100644 index 0000000000..0eb4f31b83 --- /dev/null +++ b/lib/v2/greasyfork/maintainer.js @@ -0,0 +1,3 @@ +module.exports = { + '/:language/:domain?': ['imlonghao', 'miles170'], +}; diff --git a/lib/v2/greasyfork/radar.js b/lib/v2/greasyfork/radar.js new file mode 100644 index 0000000000..ee48265de2 --- /dev/null +++ b/lib/v2/greasyfork/radar.js @@ -0,0 +1,13 @@ +module.exports = { + 'greasyfork.org': { + _name: 'Greasy Fork', + '.': [ + { + title: 'User scripts', + docs: 'https://docs.rsshub.app/program-update.html#greasy-fork', + source: ['/:language', '/:language/scripts/by-site/:domain?'], + target: '/greasyfork/:language/:domain?', + }, + ], + }, +}; diff --git a/lib/v2/greasyfork/router.js b/lib/v2/greasyfork/router.js new file mode 100644 index 0000000000..32144b2830 --- /dev/null +++ b/lib/v2/greasyfork/router.js @@ -0,0 +1,3 @@ +module.exports = (router) => { + router.get('/:language/:domain?', require('./scripts')); +}; diff --git a/lib/routes/greasyfork/scripts.js b/lib/v2/greasyfork/scripts.js similarity index 86% rename from lib/routes/greasyfork/scripts.js rename to lib/v2/greasyfork/scripts.js index cee531a42e..b3d0659265 100644 --- a/lib/routes/greasyfork/scripts.js +++ b/lib/v2/greasyfork/scripts.js @@ -7,10 +7,10 @@ module.exports = async (ctx) => { const domain = ctx.params.domain; const filter_locale = ctx.params.language === 'all' ? 0 : 1; const url = domain ? `by-site/${domain}` : ''; - + const currentUrl = `https://greasyfork.org/${language}/scripts/${url}`; const res = await got({ method: 'get', - url: `https://greasyfork.org/${language}/scripts/${url}`, + url: currentUrl, searchParams: queryString.stringify({ filter_locale, sort: 'updated', @@ -21,12 +21,12 @@ module.exports = async (ctx) => { ctx.state.data = { title: $('title').first().text(), - link: `https://greasyfork.org/${language}/scripts/${url}`, + link: currentUrl, description: $('meta[name=description]').attr('content'), item: list && list - .map((index, item) => { + .map((_, item) => { item = $(item); return { title: item.find('a').text(), diff --git a/lib/v2/lovelive-anime/maintainer.js b/lib/v2/lovelive-anime/maintainer.js index 4dc741ff71..1f883a6c90 100644 --- a/lib/v2/lovelive-anime/maintainer.js +++ b/lib/v2/lovelive-anime/maintainer.js @@ -1,3 +1,4 @@ module.exports = { '/news/:option?': ['axojhf'], + '/topics/:abbr/:category?': ['axojhf'], }; diff --git a/lib/v2/lovelive-anime/radar.js b/lib/v2/lovelive-anime/radar.js index fa57df0d35..e64c194838 100644 --- a/lib/v2/lovelive-anime/radar.js +++ b/lib/v2/lovelive-anime/radar.js @@ -3,11 +3,20 @@ module.exports = { _name: 'Love Live 官网', www: [ { - title: '最新NEWS', - docs: 'https://docs.rsshub.app/anime.html#lovelive-anime', - source: ['/'], + title: '最新 NEWS', + docs: 'https://docs.rsshub.app/anime.html#lovelive-anime-love-live-guan-wang-zui-xin-news', + source: ['/', '/news'], target: '/lovelive-anime/news', }, + { + title: '分类 Topics', + docs: 'https://docs.rsshub.app/anime.html#lovelive-anime-love-live-guan-wang-fen-lei-topics', + source: ['/:abbr/topics/', '/:abbr/topics.php'], + target: (params, url) => { + const cat = url.match(/\?cat=(.*)/); + return `/lovelive-anime/topics/${params.abbr}/${null !== cat && cat.length === 2 ? cat[1] : ''}`; + }, + }, ], }, }; diff --git a/lib/v2/lovelive-anime/router.js b/lib/v2/lovelive-anime/router.js index c7bbc2e167..0734739f7b 100644 --- a/lib/v2/lovelive-anime/router.js +++ b/lib/v2/lovelive-anime/router.js @@ -1,3 +1,4 @@ module.exports = (router) => { router.get('/news/:option?', require('./news')); + router.get('/topics/:abbr/:category?/:option?', require('./topics')); }; diff --git a/lib/v2/lovelive-anime/topics.js b/lib/v2/lovelive-anime/topics.js new file mode 100644 index 0000000000..01a65f9401 --- /dev/null +++ b/lib/v2/lovelive-anime/topics.js @@ -0,0 +1,84 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const path = require('path'); +const { art } = require('@/utils/render'); +const { parseDate } = require('@/utils/parse-date'); +const renderDescription = (desc) => art(path.join(__dirname, 'templates/description.art'), desc); + +module.exports = async (ctx) => { + const abbr = ctx.params.abbr; + const rootUrl = `https://www.lovelive-anime.jp/${abbr}`; + const topicsUrlPart = 'yuigaoka' === abbr ? 'topics/' : 'topics.php'; + const baseUrl = `${rootUrl}/${'yuigaoka' === abbr ? 'topics/' : ''}`; + const abbrDetail = { + otonokizaka: 'ラブライブ!', + uranohoshi: 'サンシャイン!!', + nijigasaki: '虹ヶ咲学園', + yuigaoka: 'スーパースター!!', + }; + let url; + + if (ctx.params.hasOwnProperty('category') && ctx.params.category !== 'detail') { + url = `${rootUrl}/${topicsUrlPart}?cat=${ctx.params.category}`; + } else { + url = `${rootUrl}/${topicsUrlPart}`; + } + + const response = await got(url); + + const $ = cheerio.load(response.data); + + const categoryName = 'uranohoshi' === abbr ? $('div.llbox > p').text() : $('div.category_title > h2').text(); + + let items = $('ul.listbox > li') + .map((_, item) => { + item = $(item); + + const link = `${baseUrl}${item.find('div > a').attr('href')}`; + const pubDate = parseDate(item.find('a > p.date').text(), 'YYYY/MM/DD'); + const title = item.find('a > p.title').text(); + const category = item.find('a > p.category').text(); + const imglink = `${baseUrl}${ + item + .find('a > img') + .attr('style') + .match(/background-image:url\((.*)\)/)[1] + }`; + + return { + link, + pubDate, + title, + category, + description: renderDescription({ + title, + imglink, + }), + }; + }) + .get(); + + if (ctx.params.option === 'detail' || ctx.params.category === 'detail') { + items = await Promise.all( + items.map((item) => + ctx.cache.tryGet(item.link, async () => { + const detailResp = await got(item.link); + const $ = cheerio.load(detailResp.data); + + const content = $('div.p-page__detail.p-article'); + for (const v of content.find('img')) { + v.attribs.src = `${baseUrl}${v.attribs.src}`; + } + item.description = content.html(); + return item; + }) + ) + ); + } + + ctx.state.data = { + title: `${categoryName} - ${abbrDetail[abbr]} - Love Live Official Website Topics`, + link: url, + item: items, + }; +}; diff --git a/lib/v2/nuaa/college/cae.js b/lib/v2/nuaa/college/cae.js new file mode 100644 index 0000000000..3f2dfa6c05 --- /dev/null +++ b/lib/v2/nuaa/college/cae.js @@ -0,0 +1,79 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); +const getCookie = require('../utils/pypasswaf'); +const host = 'https://cae.nuaa.edu.cn/'; + +const map = new Map([ + ['zhxw', { title: '综合新闻 | 南京航空航天大学自动化学院', suffix: '5399/list.htm' }], + ['dwxz', { title: '党委行政 | 南京航空航天大学自动化学院', suffix: '13266/list.htm' }], + ['rshz', { title: '人事/合作 | 南京航空航天大学自动化学院', suffix: '13267/list.htm' }], + ['yjs', { title: '研究生培养 | 南京航空航天大学自动化学院', suffix: '13271/list.htm' }], + ['bks', { title: '本科生培养 | 南京航空航天大学自动化学院', suffix: '13270/list.htm' }], + ['xsgz', { title: '学生工作 | 南京航空航天大学自动化学院', suffix: '13268/list.htm' }], + ['tzgg', { title: '通知公告 | 南京航空航天大学自动化学院', suffix: '13264/list.htm' }], + ['xsxx', { title: '学术信息 | 南京航空航天大学自动化学院', suffix: '13265/list.htm' }], + ['dbgg', { title: '答辩公告 | 南京航空航天大学自动化学院', suffix: 'dbgg/list.htm' }], +]); + +module.exports = async (ctx) => { + const type = ctx.params.type; + const suffix = map.get(type).suffix; + const getDescription = Boolean(ctx.params.getDescription) || false; + const link = new URL(suffix, host).href; + const cookie = await getCookie(host); + const gotConfig = { + headers: { + cookie, + }, + }; + const response = await got(link, gotConfig); + const $ = cheerio.load(response.data); + + const list = $('#wp_news_w6 ul li') + .slice(0, 10) + .map(function () { + const info = { + title: $(this).find('a').text(), + link: $(this).find('a').attr('href'), + date: $(this).find('span').text(), + }; + return info; + }) + .get(); + + const out = await Promise.all( + list.map(async (info) => { + const title = info.title || 'tzgg'; + const date = info.date; + const itemUrl = new URL(info.link, host).href; + let description = title + '
查看原文'; + + if (getDescription) { + description = await ctx.cache.tryGet(itemUrl, async () => { + const arr = itemUrl.split('.'); + const pageType = arr[arr.length - 1]; + if (pageType === 'htm' || pageType === 'html') { + const response = await got(itemUrl, gotConfig); + const $ = cheerio.load(response.data); + return $('.wp_articlecontent').html() + '

查看原文'; + } + }); + } + + return { + title, + link: itemUrl, + description, + pubDate: parseDate(date), + }; + }) + ); + + ctx.state.data = { + title: map.get(type).title, + link, + description: '南京航空航天大学自动化学院RSS', + item: out, + }; +}; diff --git a/lib/v2/nuaa/college/cs.js b/lib/v2/nuaa/college/cs.js new file mode 100644 index 0000000000..672573432a --- /dev/null +++ b/lib/v2/nuaa/college/cs.js @@ -0,0 +1,78 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); +const getCookie = require('../utils/pypasswaf'); +const host = 'https://cs.nuaa.edu.cn/'; + +const map = new Map([ + ['tzgg', { title: '通知公告 | 南京航空航天大学计算机科学与技术学院', suffix: 'tzgg/list.htm' }], + ['rdxw', { title: '热点新闻 | 南京航空航天大学计算机科学与技术学院', suffix: '10846/list.htm' }], + ['xkky', { title: '学科科研 | 南京航空航天大学计算机科学与技术学院', suffix: '10849/list.htm' }], + ['be', { title: '本科生培养 | 南京航空航天大学计算机科学与技术学院', suffix: '10850/list.htm' }], + ['me', { title: '研究生培养 | 南京航空航天大学计算机科学与技术学院', suffix: '10851/list.htm' }], + ['jxdt', { title: '教学动态 | 南京航空航天大学计算机科学与技术学院', suffix: '1977/list.htm' }], + ['xsgz', { title: '学生工作 | 南京航空航天大学计算机科学与技术学院', suffix: '1959/list.htm' }], +]); + +module.exports = async (ctx) => { + const type = ctx.params.type; + const getDescription = Boolean(ctx.params.getDescription) || false; + const suffix = map.get(type).suffix; + + const link = new URL(suffix, host).href; + const cookie = await getCookie(host); + const gotConfig = { + headers: { + cookie, + }, + }; + const response = await got(link, gotConfig); + const $ = cheerio.load(response.data); + + const list = $('#news_list ul li') + .slice(0, Math.min(parseInt($('.per_count', '#wp_paging_w6').text()), parseInt($('.all_count', '#wp_paging_w6').slice(1).text()))) + .map(function () { + const info = { + title: $(this).find('a').attr('title'), + link: $(this).find('a').attr('href'), + date: $(this).find('span').text(), + }; + return info; + }) + .get(); + + const out = await Promise.all( + list.map(async (info) => { + const title = info.title || 'tzgg'; + const date = info.date; + const itemUrl = new URL(info.link, host).href; + let description = title + '
查看原文'; + + if (getDescription) { + description = await ctx.cache.tryGet(itemUrl, async () => { + const arr = itemUrl.split('.'); + const pageType = arr[arr.length - 1]; + if (pageType === 'htm' || pageType === 'html') { + const response = await got(itemUrl, gotConfig); + const $ = cheerio.load(response.data); + return $('.wp_articlecontent').html() + '

查看原文'; + } + }); + } + + return { + title, + link: itemUrl, + description, + pubDate: parseDate(date), + }; + }) + ); + + ctx.state.data = { + title: map.get(type).title, + link, + description: '南京航空航天大学计算机科学与技术学院RSS', + item: out, + }; +}; diff --git a/lib/v2/nuaa/cs/index.js b/lib/v2/nuaa/cs/index.js deleted file mode 100644 index bc3cb3571d..0000000000 --- a/lib/v2/nuaa/cs/index.js +++ /dev/null @@ -1,87 +0,0 @@ -const got = require('@/utils/got'); -const cheerio = require('cheerio'); -const { parseDate } = require('@/utils/parse-date'); -const getCookie = require('../utils/pypasswaf'); -const host = 'http://cs.nuaa.edu.cn/'; - -const map = new Map([ - ['tzgg', { title: '南京航空航天大学计算机科学与技术学院 -- 通知公告', suffix: 'tzgg/list.htm' }], - ['rdxw', { title: '南京航空航天大学计算机科学与技术学院 -- 热点新闻', suffix: '10846/list.htm' }], - ['xkky', { title: '南京航空航天大学计算机科学与技术学院 -- 学科科研', suffix: '10849/list.htm' }], - ['be', { title: '南京航空航天大学计算机科学与技术学院 -- 本科生培养', suffix: '10850/list.htm' }], - ['me', { title: '南京航空航天大学计算机科学与技术学院 -- 研究生培养', suffix: '10851/list.htm' }], - ['jxdt', { title: '南京航空航天大学计算机科学与技术学院 -- 教学动态', suffix: '1977/list.htm' }], - ['xsgz', { title: '南京航空航天大学计算机科学与技术学院 -- 学生工作', suffix: '1959/list.htm' }], -]); - -module.exports = async (ctx) => { - const type = ctx.params.type; - const getDescription = Boolean(ctx.params.getDescription) || false; - const suffix = map.get(type).suffix; - - const link = new URL(suffix, host).href; - const cookie = await getCookie(); - const gotConfig = { - headers: { - cookie, - }, - }; - const response = await got(link, gotConfig); - const $ = cheerio.load(response.data); - - const list = $('#news_list ul li') - .slice(0, Math.min(parseInt($('.per_count', '#wp_paging_w6').text()), parseInt($('.all_count', '#wp_paging_w6').slice(1).text()))) - .map(function () { - const info = { - title: $(this).find('a').attr('title'), - link: $(this).find('a').attr('href'), - date: $(this).find('span').text(), - }; - return info; - }) - .get(); - - const out = await Promise.all( - list.map((info) => { - const title = info.title || 'tzgg'; - const date = info.date; - const itemUrl = new URL(info.link, host).href; - - return ctx.cache.tryGet(itemUrl, async () => { - const arr = itemUrl.split('.'); - const pageType = arr[arr.length - 1]; - - // 南航新 WAF 过于敏感 - // 目前 description 需要遍历页面,会被 WAF 拦截导致无法输出 - // 考虑换一种获取 description 的方式或者将标题当作 title。 - let description = title; - if (getDescription) { - description = itemUrl; - if (pageType === 'htm' || pageType === 'html') { - const response = await got.get(itemUrl, gotConfig); - const $ = cheerio.load(response.data); - description = $('.wp_articlecontent') - .html() - .replace(/src="\//g, `src="${new URL('.', host).href}`) - .trim(); - } - } - - const single = { - title, - link: itemUrl, - description, - pubDate: parseDate(date), - }; - return single; - }); - }) - ); - - ctx.state.data = { - title: map.get(type).title, - link, - description: '南京航空航天大学计算机科学与技术学院RSS', - item: out, - }; -}; diff --git a/lib/v2/nuaa/jwc/jwc.js b/lib/v2/nuaa/jwc/jwc.js index 8f199edb09..22c8b22294 100644 --- a/lib/v2/nuaa/jwc/jwc.js +++ b/lib/v2/nuaa/jwc/jwc.js @@ -5,11 +5,11 @@ const getCookie = require('../utils/pypasswaf'); const host = 'http://aao.nuaa.edu.cn/'; const map = new Map([ - ['tzgg', { title: '南京航空航天大学教务处 -- 通知公告', suffix: '8222/list.htm' }], - ['jxfw', { title: '南京航空航天大学教务处 -- 教学服务', suffix: '8230/list.htm' }], - ['xspy', { title: '南京航空航天大学教务处 -- 学生培养', suffix: '8231/list.htm' }], - ['jxjs', { title: '南京航空航天大学教务处 -- 教学建设', suffix: '8232/list.htm' }], - ['jxzy', { title: '南京航空航天大学教务处 -- 教学资源', suffix: '8233/list.htm' }], + ['tzgg', { title: '通知公告 | 南京航空航天大学教务处', suffix: '8222/list.htm' }], + ['jxfw', { title: '教学服务 | 南京航空航天大学教务处', suffix: '8230/list.htm' }], + ['xspy', { title: '学生培养 | 南京航空航天大学教务处', suffix: '8231/list.htm' }], + ['jxjs', { title: '教学建设 | 南京航空航天大学教务处', suffix: '8232/list.htm' }], + ['jxzy', { title: '教学资源 | 南京航空航天大学教务处', suffix: '8233/list.htm' }], ]); module.exports = async (ctx) => { @@ -18,7 +18,7 @@ module.exports = async (ctx) => { const getDescription = Boolean(ctx.params.getDescription) || false; const link = new URL(suffix, host).href; - const cookie = await getCookie(); + const cookie = await getCookie(host); const gotConfig = { headers: { cookie, @@ -40,36 +40,30 @@ module.exports = async (ctx) => { .get(); const out = await Promise.all( - list.map((info) => { + list.map(async (info) => { const title = info.title || 'tzgg'; const date = info.date; const itemUrl = new URL(info.link, host).href; + let description = title + '
查看原文'; - return ctx.cache.tryGet(itemUrl, async () => { - const arr = itemUrl.split('.'); - const pageType = arr[arr.length - 1]; - - // 南航新 WAF 过于敏感 - // 目前 description 需要遍历页面,会被 WAF 拦截导致无法输出 - // 考虑换一种获取 description 的方式或者将标题当作 title。 - let description = title; - if (getDescription) { - description = itemUrl; + if (getDescription) { + description = await ctx.cache.tryGet(itemUrl, async () => { + const arr = itemUrl.split('.'); + const pageType = arr[arr.length - 1]; if (pageType === 'htm' || pageType === 'html') { const response = await got(itemUrl, gotConfig); const $ = cheerio.load(response.data); - description = $('.wp_articlecontent').html(); + return $('.wp_articlecontent').html() + '

查看原文'; } - } + }); + } - const single = { - title, - link: itemUrl, - description, - pubDate: parseDate(date), - }; - return single; - }); + return { + title, + link: itemUrl, + description, + pubDate: parseDate(date), + }; }) ); diff --git a/lib/v2/nuaa/maintainer.js b/lib/v2/nuaa/maintainer.js index 1861cbdab9..bd7b7fc5ee 100644 --- a/lib/v2/nuaa/maintainer.js +++ b/lib/v2/nuaa/maintainer.js @@ -1,5 +1,6 @@ module.exports = { - '/cs/:type/:getDescription?': ['LogicJake', 'Seiry', 'qrzbing'], - '/jwc/:type/:getDescription?': ['arcosx', 'Seiry', 'qrzbing'], - '/yjsy/:type?': ['junfengP', 'Seiry'], + '/cae/:type/:getDescription?': ['Xm798'], + '/cs/:type/:getDescription?': ['LogicJake', 'Seiry', 'qrzbing', 'Xm798'], + '/jwc/:type/:getDescription?': ['arcosx', 'Seiry', 'qrzbing', 'Xm798'], + '/yjsy/:type/:getDescription?': ['junfengP', 'Seiry', 'Xm798'], }; diff --git a/lib/v2/nuaa/radar.js b/lib/v2/nuaa/radar.js index df521f4734..d6045101db 100644 --- a/lib/v2/nuaa/radar.js +++ b/lib/v2/nuaa/radar.js @@ -7,6 +7,12 @@ module.exports = { docs: 'https://docs.rsshub.app/university.html#nan-jing-hang-kong-hang-tian-da-xue', }, ], + cae: [ + { + title: '自动化学院', + docs: 'https://docs.rsshub.app/university.html#nan-jing-hang-kong-hang-tian-da-xue', + }, + ], cs: [ { title: '计算机科学与技术学院', diff --git a/lib/v2/nuaa/router.js b/lib/v2/nuaa/router.js index c906a0548a..cf57ad6b5c 100644 --- a/lib/v2/nuaa/router.js +++ b/lib/v2/nuaa/router.js @@ -1,5 +1,6 @@ module.exports = (router) => { - router.get('/cs/:type/:getDescription?', require('./cs/index')); + router.get('/cae/:type/:getDescription?', require('./college/cae')); + router.get('/cs/:type/:getDescription?', require('./college/cs')); router.get('/jwc/:type/:getDescription?', require('./jwc/jwc')); - router.get('/yjsy/:type?', require('./yjsy/yjsy')); + router.get('/yjsy/:type/:getDescription?', require('./yjsy/yjsy')); }; diff --git a/lib/v2/nuaa/utils/pypasswaf.js b/lib/v2/nuaa/utils/pypasswaf.js index 1135d53490..cec40e60a2 100644 --- a/lib/v2/nuaa/utils/pypasswaf.js +++ b/lib/v2/nuaa/utils/pypasswaf.js @@ -1,10 +1,8 @@ -const host = 'http://aao.nuaa.edu.cn/'; - /** * async function 获取cookie * @desc 返回一个可用的cookie,使用 `got` 发起请求的时候,传入到`options.headers.cookie`即可 */ -module.exports = async function getCookie() { +module.exports = async function getCookie(host) { const browser = await require('@/utils/puppeteer')({ stealth: true }); const page = await browser.newPage(); await page.setRequestInterception(true); diff --git a/lib/v2/nuaa/yjsy/yjsy.js b/lib/v2/nuaa/yjsy/yjsy.js index 6f21060fcf..c36bb1c29e 100644 --- a/lib/v2/nuaa/yjsy/yjsy.js +++ b/lib/v2/nuaa/yjsy/yjsy.js @@ -1,55 +1,74 @@ const got = require('@/utils/got'); const cheerio = require('cheerio'); -const host = 'http://www.graduate.nuaa.edu.cn/'; const { parseDate } = require('@/utils/parse-date'); +const getCookie = require('../utils/pypasswaf'); +const host = 'http://www.graduate.nuaa.edu.cn/'; -const map = { - latest: '2146/list.htm', - yyxw: 'yyxw/list.htm', - sjwj: 'sjwj/list.htm', - glwj: '2017/list.htm', - xxfw: '2147/list.htm', -}; - -const load = (link, ctx) => - ctx.cache.tryGet(link, async () => { - const response = await got(host + link); - const $ = cheerio.load(response.data); - const images = $('img'); - for (let k = 0; k < images.length; k++) { - $(images[k]).replaceWith(``); - } - const description = $('.wp_articlecontent').html(); - return { description }; - }); +const map = new Map([ + ['tzgg', { title: '通知公告 | 南京航空航天大学研究生院', suffix: '2145/list.htm' }], + ['xwdt', { title: '新闻动态 | 南京航空航天大学研究生院', suffix: '13276/list.htm' }], + ['xsxx', { title: '学术信息 | 南京航空航天大学研究生院', suffix: '13277/list.htm' }], + ['ssfc', { title: '师生风采 | 南京航空航天大学研究生院', suffix: '13278/list.htm' }], +]); module.exports = async (ctx) => { - const type = ctx.params.type ?? 'latest'; - const response = await got({ - method: 'get', - url: host + map[type], - }); + const type = ctx.params.type; + const suffix = map.get(type).suffix; + const getDescription = Boolean(ctx.params.getDescription) || false; + const link = new URL(suffix, host).href; + const cookie = await getCookie(host); + const gotConfig = { + headers: { + cookie, + }, + }; + const response = await got(link, gotConfig); const $ = cheerio.load(response.data); - const list = $('#news_list ul.news_ul > li').slice(0, 10).get(); - const process = await Promise.all( - list.map(async (item) => { - const a = $(item).find('a'); - const t = $(item).find('span'); - const itemUrl = a.attr('href'); - const single = { - title: a.text(), - link: new URL(itemUrl, host).href, - pubDate: parseDate(t.text(), 'YYYY-MM-DD'), + + const list = $('#wp_news_w6 ul li') + .slice(0, 10) + .map(function () { + const info = { + title: $(this).find('a').text(), + link: $(this).find('a').attr('href'), + date: $(this).find('span').text(), + }; + return info; + }) + .get(); + + const out = await Promise.all( + list.map(async (info) => { + const title = info.title || 'tzgg'; + const date = info.date; + const itemUrl = new URL(info.link, host).href; + let description = title + '
查看原文'; + + if (getDescription) { + description = await ctx.cache.tryGet(itemUrl, async () => { + const arr = itemUrl.split('.'); + const pageType = arr[arr.length - 1]; + if (pageType === 'htm' || pageType === 'html') { + const response = await got(itemUrl, gotConfig); + const $ = cheerio.load(response.data); + return $('.wp_articlecontent').html() + '

查看原文'; + } + }); + } + + return { + title, + link: itemUrl, + description, + pubDate: parseDate(date), }; - const other = await load(itemUrl, ctx); - return { ...single, ...other }; }) ); ctx.state.data = { - title: '南航研究生院', - link: host, - description: '南航研究生院RSS', - item: process, + title: map.get(type).title, + link, + description: '南京航空航天大学研究生院RSS', + item: out, }; }; diff --git a/lib/routes/pixiv/api/getBookmarks.js b/lib/v2/pixiv/api/getBookmarks.js similarity index 84% rename from lib/routes/pixiv/api/getBookmarks.js rename to lib/v2/pixiv/api/getBookmarks.js index eafc5e7b32..07b5f5be43 100644 --- a/lib/routes/pixiv/api/getBookmarks.js +++ b/lib/v2/pixiv/api/getBookmarks.js @@ -10,9 +10,7 @@ const queryString = require('query-string'); * @returns {Promise>} */ module.exports = function getBookmarks(user_id, token) { - return got({ - method: 'get', - url: 'https://app-api.pixiv.net/v1/user/bookmarks/illust', + return got('https://app-api.pixiv.net/v1/user/bookmarks/illust', { headers: { ...maskHeader, Authorization: 'Bearer ' + token, diff --git a/lib/routes/pixiv/api/getIllustFollows.js b/lib/v2/pixiv/api/getIllustFollows.js similarity index 84% rename from lib/routes/pixiv/api/getIllustFollows.js rename to lib/v2/pixiv/api/getIllustFollows.js index ea6d436a08..6c492edcdb 100644 --- a/lib/routes/pixiv/api/getIllustFollows.js +++ b/lib/v2/pixiv/api/getIllustFollows.js @@ -8,9 +8,7 @@ const queryString = require('query-string'); * @returns {Promise>} */ module.exports = function getUserIllustFollows(token) { - return got({ - method: 'get', - url: 'https://app-api.pixiv.net/v2/illust/follow', + return got('https://app-api.pixiv.net/v2/illust/follow', { headers: { ...maskHeader, Authorization: 'Bearer ' + token, diff --git a/lib/routes/pixiv/api/getIllusts.js b/lib/v2/pixiv/api/getIllusts.js similarity index 85% rename from lib/routes/pixiv/api/getIllusts.js rename to lib/v2/pixiv/api/getIllusts.js index 33f62e2c10..d27e29c314 100644 --- a/lib/routes/pixiv/api/getIllusts.js +++ b/lib/v2/pixiv/api/getIllusts.js @@ -9,9 +9,7 @@ const queryString = require('query-string'); * @returns {Promise>} */ module.exports = function getIllusts(user_id, token) { - return got({ - method: 'get', - url: 'https://app-api.pixiv.net/v1/user/illusts', + return got('https://app-api.pixiv.net/v1/user/illusts', { headers: { ...maskHeader, Authorization: 'Bearer ' + token, diff --git a/lib/routes/pixiv/api/getRanking.js b/lib/v2/pixiv/api/getRanking.js similarity index 91% rename from lib/routes/pixiv/api/getRanking.js rename to lib/v2/pixiv/api/getRanking.js index fe94389eaa..5bbc0bbef5 100644 --- a/lib/routes/pixiv/api/getRanking.js +++ b/lib/v2/pixiv/api/getRanking.js @@ -14,9 +14,7 @@ const allowMode = ['day', 'week', 'month', 'day_male', 'day_female', 'week_origi */ module.exports = function getRanking(mode, date, token) { assert(allowMode.includes(mode), 'Mode not allow.'); - return got({ - method: 'get', - url: 'https://app-api.pixiv.net/v1/illust/ranking', + return got('https://app-api.pixiv.net/v1/illust/ranking', { headers: { ...maskHeader, Authorization: 'Bearer ' + token, diff --git a/lib/routes/pixiv/api/getUserDetail.js b/lib/v2/pixiv/api/getUserDetail.js similarity index 88% rename from lib/routes/pixiv/api/getUserDetail.js rename to lib/v2/pixiv/api/getUserDetail.js index 5a1d1c25bb..abc51ad47b 100644 --- a/lib/routes/pixiv/api/getUserDetail.js +++ b/lib/v2/pixiv/api/getUserDetail.js @@ -15,9 +15,7 @@ const queryString = require('query-string'); * @returns {Promise>} */ module.exports = function getUserDetail(user_id, token) { - return got({ - method: 'get', - url: 'https://app-api.pixiv.net/v1/user/detail', + return got('https://app-api.pixiv.net/v1/user/detail', { headers: { ...maskHeader, Authorization: 'Bearer ' + token, diff --git a/lib/routes/pixiv/api/searchIllust.js b/lib/v2/pixiv/api/searchIllust.js similarity index 87% rename from lib/routes/pixiv/api/searchIllust.js rename to lib/v2/pixiv/api/searchIllust.js index 3b7277327c..fcc839402b 100644 --- a/lib/routes/pixiv/api/searchIllust.js +++ b/lib/v2/pixiv/api/searchIllust.js @@ -9,9 +9,7 @@ const queryString = require('query-string'); * @returns {Promise>} */ module.exports = function searchIllust(keyword, token) { - return got({ - method: 'get', - url: 'https://app-api.pixiv.net/v1/search/illust', + return got('https://app-api.pixiv.net/v1/search/illust', { headers: { ...maskHeader, Authorization: 'Bearer ' + token, diff --git a/lib/routes/pixiv/api/searchPopularIllust.js b/lib/v2/pixiv/api/searchPopularIllust.js similarity index 85% rename from lib/routes/pixiv/api/searchPopularIllust.js rename to lib/v2/pixiv/api/searchPopularIllust.js index 1756424411..a13dab4661 100644 --- a/lib/routes/pixiv/api/searchPopularIllust.js +++ b/lib/v2/pixiv/api/searchPopularIllust.js @@ -9,9 +9,7 @@ const queryString = require('query-string'); * @returns {Promise>} */ module.exports = function searchPopularIllust(keyword, token) { - return got({ - method: 'get', - url: 'https://app-api.pixiv.net/v1/search/popular-preview/illust', + return got('https://app-api.pixiv.net/v1/search/popular-preview/illust', { headers: { ...maskHeader, Authorization: 'Bearer ' + token, diff --git a/lib/routes/pixiv/bookmarks.js b/lib/v2/pixiv/bookmarks.js similarity index 87% rename from lib/routes/pixiv/bookmarks.js rename to lib/v2/pixiv/bookmarks.js index 89b7e88371..61112a7e75 100644 --- a/lib/routes/pixiv/bookmarks.js +++ b/lib/v2/pixiv/bookmarks.js @@ -1,8 +1,9 @@ -const getToken = require('./token'); +const { getToken } = require('./token'); const getBookmarks = require('./api/getBookmarks'); const getUserDetail = require('./api/getUserDetail'); const config = require('@/config').value; const pixivUtils = require('./utils'); +const { parseDate } = require('@/utils/parse-date'); module.exports = async (ctx) => { if (!config.pixiv || !config.pixiv.refreshToken) { @@ -11,7 +12,7 @@ module.exports = async (ctx) => { const id = ctx.params.id; - const token = await getToken(); + const token = await getToken(ctx.cache.tryGet); if (!token) { throw 'pixiv not login'; } @@ -30,7 +31,7 @@ module.exports = async (ctx) => { return { title: illust.title, author: illust.user.name, - pubDate: new Date(illust.create_date).toUTCString(), + pubDate: parseDate(illust.create_date), description: `

画师:${illust.user.name} - 阅览数:${illust.total_view} - 收藏数:${illust.total_bookmarks}

${images.join('')}`, link: `https://www.pixiv.net/artworks/${illust.id}`, }; diff --git a/lib/routes/pixiv/constants.js b/lib/v2/pixiv/constants.js similarity index 100% rename from lib/routes/pixiv/constants.js rename to lib/v2/pixiv/constants.js diff --git a/lib/routes/pixiv/illustfollow.js b/lib/v2/pixiv/illustfollow.js similarity index 84% rename from lib/routes/pixiv/illustfollow.js rename to lib/v2/pixiv/illustfollow.js index 0c37c0dba4..a40b73586d 100644 --- a/lib/routes/pixiv/illustfollow.js +++ b/lib/v2/pixiv/illustfollow.js @@ -1,15 +1,16 @@ -const getToken = require('./token'); +const { getToken } = require('./token'); const getIllustFollows = require('./api/getIllustFollows'); const config = require('@/config').value; const pixivUtils = require('./utils'); +const { parseDate } = require('@/utils/parse-date'); module.exports = async (ctx) => { if (!config.pixiv || !config.pixiv.refreshToken) { throw 'pixiv RSS is disabled due to the lack of relevant config'; } - const token = await getToken(); - if (!(await getToken())) { + const token = await getToken(ctx.cache.tryGet); + if (!token) { throw 'pixiv not login'; } @@ -24,7 +25,7 @@ module.exports = async (ctx) => { return { title: illust.title, author: illust.user.name, - pubDate: new Date(illust.create_date).toUTCString(), + pubDate: parseDate(illust.create_date), description: `

画师:${illust.user.name} - 阅览数:${illust.total_view} - 收藏数:${illust.total_bookmarks}

${images.join('')}`, link: `https://www.pixiv.net/artworks/${illust.id}`, }; diff --git a/lib/v2/pixiv/maintainer.js b/lib/v2/pixiv/maintainer.js new file mode 100644 index 0000000000..c609eeb9a0 --- /dev/null +++ b/lib/v2/pixiv/maintainer.js @@ -0,0 +1,8 @@ +module.exports = { + '/ranking/:mode/:date?': ['EYHN'], + '/search/:keyword/:order?/:mode?': ['DIYgod'], + '/user/bookmarks/:id': ['EYHN'], + '/user/illustfollows': ['ClarkeCheng'], + '/user/novels/:id': ['TonyRL'], + '/user/:id': ['DIYgod'], +}; diff --git a/lib/v2/pixiv/novels.js b/lib/v2/pixiv/novels.js new file mode 100644 index 0000000000..44567f3b2c --- /dev/null +++ b/lib/v2/pixiv/novels.js @@ -0,0 +1,35 @@ +const got = require('@/utils/got'); +const { parseDate } = require('@/utils/parse-date'); +const baseUrl = 'https://www.pixiv.net'; + +module.exports = async (ctx) => { + const { id } = ctx.params; + const { data } = await got(`${baseUrl}/ajax/user/${id}/profile/all`); + + const items = [ + ...data.body.novelSeries.map((item) => ({ + title: item.title, + description: item.caption, + link: `https://www.pixiv.net/novel/series/${item.id}`, + author: item.userName, + pubDate: parseDate(item.createDate), + updated: parseDate(item.updateDate), + category: item.tags, + })), + ...data.body.pickup.map((item) => ({ + title: item.title, + description: item.description, + link: item.contentUrl, + author: item.userName, + pubDate: parseDate(item.createDate), + updated: parseDate(item.updateDate), + category: item.tags, + })), + ]; + + ctx.state.data = { + title: `${data.body.novelSeries[0].userName || data.body.pickup[0].userName} 的小说`, + link: `${baseUrl}/users/${id}/novels`, + item: items, + }; +}; diff --git a/lib/routes/pixiv/pixiv-got.js b/lib/v2/pixiv/pixiv-got.js similarity index 97% rename from lib/routes/pixiv/pixiv-got.js rename to lib/v2/pixiv/pixiv-got.js index 0fd73f9e9f..f562bc17fb 100644 --- a/lib/routes/pixiv/pixiv-got.js +++ b/lib/v2/pixiv/pixiv-got.js @@ -6,7 +6,7 @@ const pixivConfig = require('@/config').value.pixiv; async function dohResolve(name, jsonDohEndpoint) { try { - const response = await got.get(jsonDohEndpoint, { + const response = await got(jsonDohEndpoint, { searchParams: { name, type: 'A', diff --git a/lib/v2/pixiv/radar.js b/lib/v2/pixiv/radar.js new file mode 100644 index 0000000000..6b2229adbf --- /dev/null +++ b/lib/v2/pixiv/radar.js @@ -0,0 +1,43 @@ +module.exports = { + 'pixiv.net': { + _name: 'Pixiv', + www: [ + { + title: '用户收藏', + docs: 'https://docs.rsshub.app/social-media.html#pixiv', + source: '/users/:id/bookmarks/artworks', + target: '/pixiv/user/bookmarks/:id', + }, + { + title: '用户动态', + docs: 'https://docs.rsshub.app/social-media.html#pixiv', + source: '/users/:id', + target: '/pixiv/user/:id', + }, + { + title: '用户小说', + docs: 'https://docs.rsshub.app/social-media.html#pixiv', + source: '/users/:id/novels', + target: '/pixiv/user/novels/:id', + }, + { + title: '排行榜', + docs: 'https://docs.rsshub.app/social-media.html#pixiv', + source: '/ranking.php', + target: (params, url) => `/pixiv/ranking/${new URL(url).searchParams.get('mode') || 'daily'}`, + }, + { + title: '关键词', + docs: 'https://docs.rsshub.app/social-media.html#pixiv', + source: ['/tags/:keyword', '/tags/:keyword/:type?'], + target: (params, url) => `/pixiv/search/:keyword/${new URL(url).searchParams.get('order')}/${new URL(url).searchParams.get('mode')}`, + }, + { + title: '关注的新作品', + docs: 'https://docs.rsshub.app/social-media.html#pixiv', + source: '/bookmark_new_illust.php', + target: '/pixiv/user/illustfollows', + }, + ], + }, +}; diff --git a/lib/routes/pixiv/ranking.js b/lib/v2/pixiv/ranking.js similarity index 93% rename from lib/routes/pixiv/ranking.js rename to lib/v2/pixiv/ranking.js index b35b2bd592..ab1f139a70 100644 --- a/lib/routes/pixiv/ranking.js +++ b/lib/v2/pixiv/ranking.js @@ -1,7 +1,8 @@ -const getToken = require('./token'); +const { getToken } = require('./token'); const getRanking = require('./api/getRanking'); const config = require('@/config').value; const pixivUtils = require('./utils'); +const { parseDate } = require('@/utils/parse-date'); const titles = { day: 'pixiv 日排行', @@ -56,7 +57,7 @@ module.exports = async (ctx) => { const mode = alias[ctx.params.mode] ? alias[ctx.params.mode] : ctx.params.mode; const date = ctx.params.date ? new Date(ctx.params.date) : new Date(); - const token = await getToken(); + const token = await getToken(ctx.cache.tryGet); if (!token) { throw 'pixiv not login'; } @@ -75,7 +76,7 @@ module.exports = async (ctx) => { const images = pixivUtils.getImgs(illust); return { title: `#${index + 1} ${illust.title}`, - pubDate: new Date(illust.create_date).toUTCString(), + pubDate: parseDate(illust.create_date), description: `

画师:${illust.user.name} - 阅览数:${illust.total_view} - 收藏数:${illust.total_bookmarks}


${images.join('')}`, link: `https://www.pixiv.net/artworks/${illust.id}`, author: illust.user.name, diff --git a/lib/v2/pixiv/router.js b/lib/v2/pixiv/router.js new file mode 100644 index 0000000000..99263ffb9d --- /dev/null +++ b/lib/v2/pixiv/router.js @@ -0,0 +1,8 @@ +module.exports = (router) => { + router.get('/ranking/:mode/:date?', require('./ranking')); + router.get('/search/:keyword/:order?/:mode?', require('./search')); + router.get('/user/bookmarks/:id', require('./bookmarks')); + router.get('/user/illustfollows', require('./illustfollow')); + router.get('/user/novels/:id', require('./novels')); + router.get('/user/:id', require('./user')); +}; diff --git a/lib/routes/pixiv/search.js b/lib/v2/pixiv/search.js similarity index 89% rename from lib/routes/pixiv/search.js rename to lib/v2/pixiv/search.js index 8fcb5d93df..f6fada5f5b 100644 --- a/lib/routes/pixiv/search.js +++ b/lib/v2/pixiv/search.js @@ -1,8 +1,9 @@ -const getToken = require('./token'); +const { getToken } = require('./token'); const searchPopularIllust = require('./api/searchPopularIllust'); const searchIllust = require('./api/searchIllust'); const config = require('@/config').value; const pixivUtils = require('./utils'); +const { parseDate } = require('@/utils/parse-date'); module.exports = async (ctx) => { if (!config.pixiv || !config.pixiv.refreshToken) { @@ -13,7 +14,7 @@ module.exports = async (ctx) => { const order = ctx.params.order || 'date'; const mode = ctx.params.mode; - const token = await getToken(); + const token = await getToken(ctx.cache.tryGet); if (!token) { throw 'pixiv not login'; } @@ -40,7 +41,7 @@ module.exports = async (ctx) => { return { title: illust.title, author: illust.user.name, - pubDate: new Date(illust.create_date).toUTCString(), + pubDate: parseDate(illust.create_date), description: `

画师:${illust.user.name} - 阅览数:${illust.total_view} - 收藏数:${illust.total_bookmarks}

${images.join('')}`, link: `https://www.pixiv.net/artworks/${illust.id}`, }; diff --git a/lib/v2/pixiv/token.js b/lib/v2/pixiv/token.js new file mode 100644 index 0000000000..06911904ba --- /dev/null +++ b/lib/v2/pixiv/token.js @@ -0,0 +1,86 @@ +const config = require('@/config').value; +const logger = require('@/utils/logger'); +const got = require('./pixiv-got'); +const { maskHeader } = require('./constants'); + +let token = null; + +const authorizationInfo = { + client_id: 'MOBrBDS8blbauoSck0ZfDbtuzpyT', + client_secret: 'lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj', + hash_secret: '28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c', +}; + +const refreshToken = (tryGet) => + tryGet( + 'pixiv:accessToken', + () => + got + .post('https://oauth.secure.pixiv.net/auth/token', { + form: { + ...authorizationInfo, + get_secure_url: 1, + grant_type: 'refresh_token', + refresh_token: config.pixiv.refreshToken, + }, + headers: { + ...maskHeader, + }, + responseType: 'json', + resolveBodyOnly: true, + }) + .catch((e) => { + logger.error('Pixiv refresh token failed.'); + logger.error(e); + }), + 3600, + false + ); + +async function getToken(tryGet) { + const result = await refreshToken(tryGet); + // let expireTime; + if (result && result.access_token) { + logger.debug('Pixiv refresh token success.'); + token = result.access_token; + // expireTime = result.expires_in; + } + // } else { + // expireTime = 30; + // logger.error(`Pixiv refresh token failed, retry in ${expireTime} seconds.`); + // } + // If is package, the mock server should be closed after request, needn't to refresh + // if (!config.isPackage) { + // setTimeout(tickToken, expireTime * 1000); + // } + return token; +} + +module.exports = { + getToken, +}; + +// let tickTokenStarted = false; + +// async function startTickToken(tryGet) { +// if (!tickTokenStarted) { +// // 如果tickToken没启动 +// tickTokenStarted = true; +// await tickToken(tryGet); // 启动tickToken +// } +// } + +// async function waitForToken() { +// while (!token) { +// // eslint-disable-next-line no-await-in-loop +// await new Promise((resolve) => setTimeout(resolve, 0)); +// } +// return token; +// } + +// module.exports = async (tryGet) => { +// await startTickToken(tryGet); +// return waitForToken(); +// }; + +// module.exports.tickToken = tickToken; diff --git a/lib/routes/pixiv/user.js b/lib/v2/pixiv/user.js similarity index 85% rename from lib/routes/pixiv/user.js rename to lib/v2/pixiv/user.js index e9ff62b09f..e3893ec8cf 100644 --- a/lib/routes/pixiv/user.js +++ b/lib/v2/pixiv/user.js @@ -1,7 +1,8 @@ -const getToken = require('./token'); +const { getToken } = require('./token'); const getIllusts = require('./api/getIllusts'); const config = require('@/config').value; const pixivUtils = require('./utils'); +const { parseDate } = require('@/utils/parse-date'); module.exports = async (ctx) => { if (!config.pixiv || !config.pixiv.refreshToken) { @@ -9,7 +10,7 @@ module.exports = async (ctx) => { } const id = ctx.params.id; - const token = await getToken(); + const token = await getToken(ctx.cache.tryGet); if (!token) { throw 'pixiv not login'; } @@ -28,7 +29,7 @@ module.exports = async (ctx) => { return { title: illust.title, author: username, - pubDate: new Date(illust.create_date).toUTCString(), + pubDate: parseDate(illust.create_date), description: `

画师:${username} - 阅览数:${illust.total_view} - 收藏数:${illust.total_bookmarks}

${images.join('')}`, link: `https://www.pixiv.net/artworks/${illust.id}`, }; diff --git a/lib/routes/pixiv/utils.js b/lib/v2/pixiv/utils.js similarity index 100% rename from lib/routes/pixiv/utils.js rename to lib/v2/pixiv/utils.js diff --git a/lib/v2/qidian/author.js b/lib/v2/qidian/author.js new file mode 100644 index 0000000000..8b515f0da2 --- /dev/null +++ b/lib/v2/qidian/author.js @@ -0,0 +1,41 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate, parseRelativeDate } = require('@/utils/parse-date'); +const { art } = require('@/utils/render'); +const path = require('path'); +const timezone = require('@/utils/timezone'); + +module.exports = async (ctx) => { + const id = ctx.params.id; + const rootUrl = 'https://my.qidian.com'; + const currentUrl = `${rootUrl}/author/${id}/`; + const response = await got(currentUrl); + const $ = cheerio.load(response.data); + const authorName = $('.header-msg h1').contents().first().text().trim(); + const items = $('.author-work .author-item') + .toArray() + .map((item) => { + item = $(item); + const messageItem = item.find('.author-item-msg'); + const updatedDate = messageItem.find('.author-item-update span').text().replace('·', '').trim(); + + return { + title: messageItem.find('.author-item-title').text().trim(), + author: authorName, + category: messageItem.find('.author-item-exp a').first().text().trim(), + description: art(path.join(__dirname, 'templates/description.art'), { + description: messageItem.find('.author-item-update a').attr('title'), + image: item.find('a img').attr('src'), + }), + pubDate: timezone(/(今|昨)/.test(updatedDate) ? parseRelativeDate(updatedDate) : parseDate(updatedDate, 'YYYY-MM-DD HH:mm'), +8), + link: messageItem.find('.author-item-update a').attr('href'), + }; + }); + + ctx.state.data = { + title: `${authorName} - 起点中文网`, + description: $('.header-msg-desc').text().trim(), + link: currentUrl, + item: items, + }; +}; diff --git a/lib/routes/qidian/chapter.js b/lib/v2/qidian/chapter.js similarity index 75% rename from lib/routes/qidian/chapter.js rename to lib/v2/qidian/chapter.js index a84cebbce9..f1f806386a 100644 --- a/lib/routes/qidian/chapter.js +++ b/lib/v2/qidian/chapter.js @@ -1,13 +1,11 @@ const got = require('@/utils/got'); const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); module.exports = async (ctx) => { const id = ctx.params.id; - const response = await got({ - method: 'get', - url: `https://book.qidian.com/info/${id}#Catalog`, - }); + const response = await got(`https://book.qidian.com/info/${id}/#Catalog`); const $ = cheerio.load(response.data); const name = $('.book-info>h1>em').text(); @@ -15,10 +13,7 @@ module.exports = async (ctx) => { const csrfToken = response.headers['set-cookie'].find((s) => s.startsWith('_csrfToken=')).split(';')[0]; - const chapters_response = await got({ - method: 'get', - url: `https://book.qidian.com/ajax/book/category?${csrfToken}&bookId=${id}`, - }); + const chapters_response = await got(`https://book.qidian.com/ajax/book/category?${csrfToken}&bookId=${id}`); const chapter_item = []; for (let i = 0; i < chapters_response.data.data.vs.length; i++) { @@ -27,7 +22,7 @@ module.exports = async (ctx) => { const chapter = chapters[j]; chapter_item.push({ title: chapter.cN, - pubDate: new Date(chapter.uT).toUTCString(), + pubDate: parseDate(chapter.uT), link: `https://vipreader.qidian.com/chapter/${id}/${chapter.id}`, }); } diff --git a/lib/routes/qidian/forum.js b/lib/v2/qidian/forum.js similarity index 93% rename from lib/routes/qidian/forum.js rename to lib/v2/qidian/forum.js index 177119e46b..8911e247a8 100644 --- a/lib/routes/qidian/forum.js +++ b/lib/v2/qidian/forum.js @@ -7,9 +7,7 @@ module.exports = async (ctx) => { const url = `https://forum.qidian.com/NewForum/List.aspx?BookId=${id}`; - const forum_response = await got({ - method: 'get', - url, + const forum_response = await got(url, { headers: { Referer: `https://book.qidian.com/info/${id}`, }, diff --git a/lib/routes/qidian/free-next.js b/lib/v2/qidian/free-next.js similarity index 93% rename from lib/routes/qidian/free-next.js rename to lib/v2/qidian/free-next.js index cbcb1abaa4..5643035c4c 100644 --- a/lib/routes/qidian/free-next.js +++ b/lib/v2/qidian/free-next.js @@ -13,10 +13,7 @@ module.exports = async (ctx) => { title = '起点中文网'; } - const response = await got({ - method: 'get', - url: link, - }); + const response = await got(link); const $ = cheerio.load(response.data); const list = $('div.other-rec-wrap li'); diff --git a/lib/routes/qidian/free.js b/lib/v2/qidian/free.js similarity index 88% rename from lib/routes/qidian/free.js rename to lib/v2/qidian/free.js index 01fe3d0d47..33253d3dca 100644 --- a/lib/routes/qidian/free.js +++ b/lib/v2/qidian/free.js @@ -1,5 +1,6 @@ const got = require('@/utils/got'); const cheerio = require('cheerio'); +const parseDate = require('@/utils/date'); module.exports = async (ctx) => { const type = ctx.params.type; @@ -13,10 +14,7 @@ module.exports = async (ctx) => { title = '起点中文网'; } - const response = await got({ - method: 'get', - url: link, - }); + const response = await got(link); const $ = cheerio.load(response.data); const list = $('#limit-list li'); @@ -31,7 +29,7 @@ module.exports = async (ctx) => { return { title: item.find('.book-mid-info h4 a').text(), description: img + rank + update + '
' + item.find('p.intro').html(), - pubDate: new Date(item.find('p.update span').text()).toUTCString(), + pubDate: parseDate(item.find('p.update span').text()), link: 'https:' + item.find('.book-mid-info h4 a').attr('href'), author: item.find('p.author a.name').text(), }; diff --git a/lib/v2/qidian/maintainer.js b/lib/v2/qidian/maintainer.js new file mode 100644 index 0000000000..a74b4404f0 --- /dev/null +++ b/lib/v2/qidian/maintainer.js @@ -0,0 +1,7 @@ +module.exports = { + '/author/:id': ['miles170'], + '/chapter/:id': ['Chingyat'], + '/forum/:id': ['Chingyat'], + '/free/:type?': ['LogicJake'], + '/free-next/:type?': ['LogicJake'], +}; diff --git a/lib/v2/qidian/radar.js b/lib/v2/qidian/radar.js new file mode 100644 index 0000000000..c823d302fc --- /dev/null +++ b/lib/v2/qidian/radar.js @@ -0,0 +1,41 @@ +module.exports = { + 'qidian.com': { + _name: '起点', + book: [ + { + title: '章节', + docs: 'https://docs.rsshub.app/reading.html#qi-dian', + source: '/info/:id', + target: '/qidian/chapter/:id', + }, + { + title: '讨论区', + docs: 'https://docs.rsshub.app/reading.html#qi-dian', + source: '/info/:id', + target: '/qidian/forum/:id', + }, + ], + my: [ + { + title: '作者', + docs: 'https://docs.rsshub.app/reading.html#qi-dian', + source: '/author/:id', + target: '/qidian/author/:id', + }, + ], + www: [ + { + title: '限免', + docs: 'https://docs.rsshub.app/reading.html#qi-dian', + source: '/free', + target: '/qidian/free', + }, + { + title: '女生限免', + docs: 'https://docs.rsshub.app/reading.html#qi-dian', + source: '/mm/free', + target: '/qidian/free/mm', + }, + ], + }, +}; diff --git a/lib/v2/qidian/router.js b/lib/v2/qidian/router.js new file mode 100644 index 0000000000..c809d5784e --- /dev/null +++ b/lib/v2/qidian/router.js @@ -0,0 +1,7 @@ +module.exports = (router) => { + router.get('/author/:id', require('./author')); + router.get('/chapter/:id', require('./chapter')); + router.get('/forum/:id', require('./forum')); + router.get('/free/:type?', require('./free')); + router.get('/free-next/:type?', require('./free-next')); +}; diff --git a/lib/v2/qidian/templates/description.art b/lib/v2/qidian/templates/description.art new file mode 100644 index 0000000000..33a6ef7e6b --- /dev/null +++ b/lib/v2/qidian/templates/description.art @@ -0,0 +1,5 @@ +

{{ description }}

+{{ if image }} + +{{ /if }} +{{ author }} \ No newline at end of file diff --git a/lib/v2/udn/breaking-news.js b/lib/v2/udn/breaking-news.js new file mode 100644 index 0000000000..9f3ee3c94c --- /dev/null +++ b/lib/v2/udn/breaking-news.js @@ -0,0 +1,85 @@ +const got = require('@/utils/got'); +const { parseDate } = require('@/utils/parse-date'); +const timezone = require('@/utils/timezone'); +const cheerio = require('cheerio'); +const { art } = require('@/utils/render'); +const path = require('path'); + +module.exports = async (ctx) => { + const id = ctx.params.id; + const link = `/news/breaknews/1/${id}#breaknews`; + const name = await getLinkName(ctx, link); + const rootUrl = 'https://udn.com'; + const response = await got(`${rootUrl}/api/more?page=1&channelId=1&cate_id=${id}&type=breaknews`); + const items = await Promise.all( + response.data.lists.map((item) => { + let link = item.titleLink.indexOf('http://') === 0 || item.titleLink.indexOf('https://') === 0 ? item.titleLink : `${rootUrl}${item.titleLink}`; + const linkUrl = new URL(link); + // cleanup query paramter + linkUrl.query = linkUrl.search = ''; + link = linkUrl.toString(); + return ctx.cache.tryGet(link, async () => { + let result = await got(link); + // VIP article requires redirection + // e.g. https://udn.com/news/story/7331/6576320 + const vip = result.data.match(/