├── logs └── .gitkeep ├── .nvmrc ├── lib ├── config │ └── .gitkeep ├── favicon.png ├── routes │ ├── javbus │ │ ├── home.js │ │ ├── western │ │ │ ├── home.js │ │ │ ├── star.js │ │ │ ├── genre.js │ │ │ └── series.js │ │ ├── uncensored │ │ │ ├── home.js │ │ │ ├── genre.js │ │ │ ├── star.js │ │ │ └── series.js │ │ ├── star.js │ │ ├── genre.js │ │ └── series.js │ ├── titsguru │ │ ├── home.js │ │ ├── daily.js │ │ ├── category.js │ │ └── model.js │ ├── dapenti │ │ ├── tugua.js │ │ └── subject.js │ ├── xiachufang │ │ ├── popular.js │ │ └── user │ │ │ ├── cooked.js │ │ │ └── created.js │ ├── dribbble │ │ ├── user.js │ │ ├── keyword.js │ │ └── popular.js │ ├── pixiv │ │ ├── constants.js │ │ └── api │ │ │ ├── getBookmarks.js │ │ │ ├── getIllusts.js │ │ │ ├── searchPopularIllust.js │ │ │ ├── searchIllust.js │ │ │ ├── getUserDetail.js │ │ │ └── getRanking.js │ ├── ft │ │ └── channel.js │ ├── thepaper │ │ ├── featured.js │ │ └── channel.js │ ├── dongqiudi │ │ ├── player_news.js │ │ └── team_news.js │ ├── cctv │ │ └── category.js │ ├── mzitu │ │ ├── util.js │ │ ├── tags.js │ │ └── post.js │ ├── zhihu │ │ ├── pin │ │ │ ├── hotlist.js │ │ │ ├── daily.js │ │ │ └── people.js │ │ ├── bookstore │ │ │ └── newest.js │ │ └── utils.js │ ├── mi │ │ ├── youpin │ │ │ ├── new.js │ │ │ ├── crowdfunding.js │ │ │ └── utils.js │ │ └── crowdfunding.js │ ├── nhentai │ │ ├── search.js │ │ └── other.js │ ├── patchwork.kernel.org │ │ ├── cache.js │ │ └── comments.js │ ├── twitter │ │ ├── likes.js │ │ ├── list.js │ │ └── user.js │ ├── wikipedia │ │ └── utils.js │ ├── jike │ │ ├── daily.js │ │ └── topicSquare.js │ ├── donews │ │ └── utils.js │ ├── wegene │ │ ├── newest.js │ │ └── column.js │ ├── vgtime │ │ └── release.js │ ├── zimuzu │ │ └── resource.js │ ├── anigamer │ │ ├── anime.js │ │ └── new_anime.js │ ├── universities │ │ ├── bit │ │ │ ├── jwc │ │ │ │ └── jwc.js │ │ │ └── cs │ │ │ │ └── cs.js │ │ ├── zjgsu │ │ │ └── tzgg │ │ │ │ └── scripts.js │ │ ├── sjtu │ │ │ └── gs │ │ │ │ └── tzgg.js │ │ ├── njust │ │ │ ├── cwc │ │ │ │ └── index.js │ │ │ └── jwc │ │ │ │ └── index.js │ │ ├── tju │ │ │ └── sse │ │ │ │ └── _article.js │ │ ├── cuc │ │ │ └── yz.js │ │ ├── scnu │ │ │ ├── library.js │ │ │ └── jw.js │ │ ├── henu │ │ │ └── news.js │ │ ├── seu │ │ │ └── yzb │ │ │ │ └── index.js │ │ ├── uestc │ │ │ └── news.js │ │ └── kmust │ │ │ └── job │ │ │ └── jobfairs.js │ ├── gov │ │ ├── city │ │ │ ├── index.js │ │ │ └── nanjing │ │ │ │ └── index.js │ │ └── province │ │ │ └── index.js │ ├── guokr │ │ └── scientific.js │ ├── douban │ │ ├── later.js │ │ ├── latest_book.js │ │ ├── ustop.js │ │ ├── bookstore.js │ │ ├── playing.js │ │ ├── event │ │ │ └── hot.js │ │ └── book │ │ │ └── rank.js │ ├── aqk │ │ └── category.js │ ├── nvidia │ │ └── webdriverupdate.js │ ├── sspai │ │ ├── series.js │ │ └── shortcutsGallery.js │ ├── jianshu │ │ ├── home.js │ │ ├── trending.js │ │ ├── user.js │ │ └── collection.js │ ├── infoq │ │ └── recommend.js │ ├── guanzhi │ │ └── guanzhi.js │ ├── uraaka-joshi │ │ ├── uraaka-joshi.js │ │ └── uraaka-joshi-user.js │ ├── gaoqing │ │ └── latest.js │ ├── laosiji │ │ ├── hot.js │ │ ├── feed.js │ │ └── hotshow.js │ ├── sogou │ │ └── doodles.js │ ├── facebook │ │ └── article.js │ ├── ciweimao │ │ └── chapter.js │ ├── coolbuy │ │ └── newest.js │ ├── eztv │ │ └── imdb.js │ ├── galgame │ │ └── zdfx.js │ ├── weatheralarm │ │ └── index.js │ ├── ebb │ │ └── index.js │ ├── huxiu │ │ ├── tag.js │ │ ├── search.js │ │ └── author.js │ ├── tencent │ │ ├── qcloud │ │ │ └── mlvb │ │ │ │ └── changelog.js │ │ ├── video │ │ │ └── playlist.js │ │ └── wechat │ │ │ └── announce.js │ ├── express │ │ └── express.js │ ├── geekpark │ │ └── breakingnews.js │ ├── bilibili │ │ ├── page.js │ │ ├── mallIP.js │ │ ├── coin.js │ │ ├── reply.js │ │ ├── mallNew.js │ │ ├── blackboard.js │ │ ├── partion.js │ │ ├── video.js │ │ ├── article.js │ │ └── userFav.js │ ├── baidu │ │ ├── doodles.js │ │ └── topwords.js │ ├── bangumi │ │ ├── subject │ │ │ ├── index.js │ │ │ ├── ep.js │ │ │ └── offcial-subject-api.js │ │ └── group │ │ │ └── topic.js │ ├── juejin │ │ ├── books.js │ │ └── posts.js │ ├── douyu │ │ └── room.js │ ├── github │ │ ├── repos.js │ │ ├── pulls.js │ │ └── issue.js │ ├── kingkong │ │ └── room.js │ ├── huya │ │ └── live.js │ ├── tophub │ │ └── index.js │ ├── xiaoheihe │ │ └── discount.js │ ├── blogs │ │ └── jingwei_link.js │ ├── mobdata │ │ └── report.js │ ├── douyin │ │ └── utils.js │ ├── fir │ │ └── update.js │ ├── steam │ │ └── news.js │ ├── 9to5 │ │ └── utils.js │ ├── natgeo │ │ └── dailyphoto.js │ ├── testerhome │ │ └── newest.js │ ├── vocus │ │ └── publication.js │ ├── dockerhub │ │ └── build.js │ ├── luogu │ │ └── daily.js │ ├── blogread │ │ └── newest.js │ ├── kirara │ │ └── news.js │ ├── wenku8 │ │ └── chapter.js │ ├── anime1 │ │ ├── search.js │ │ └── anime.js │ ├── v2ex │ │ └── topics.js │ ├── appstore │ │ ├── xianmian.js │ │ └── update.js │ ├── pigtails │ │ └── index.js │ ├── tingshuitz │ │ ├── xian.js │ │ ├── nanjing.js │ │ ├── dalian.js │ │ └── hangzhou.js │ ├── hexo │ │ └── yilia.js │ ├── instapaper │ │ └── person.js │ ├── jpmorganchase │ │ └── research.js │ ├── itjuzi │ │ ├── merge.js │ │ └── invest.js │ ├── jinritoutiao │ │ └── keyword.js │ ├── tanwu │ │ └── products.js │ ├── fitchratings │ │ └── site.js │ ├── chouti │ │ └── index.js │ ├── meipai │ │ ├── utils.js │ │ └── user.js │ ├── xueqiu │ │ └── favorite.js │ ├── ncm │ │ └── artist.js │ ├── pediy │ │ └── utils.js │ ├── thunderbird │ │ └── release.js │ ├── tingdiantz │ │ └── nanjing.js │ ├── aisixiang │ │ ├── ranking.js │ │ └── column.js │ ├── dysfz │ │ └── index.js │ ├── zongheng │ │ └── chapter.js │ ├── gitlab │ │ └── explore.js │ ├── psnine │ │ ├── news.js │ │ └── index.js │ └── solidot │ │ └── main.js ├── utils │ ├── wait.js │ ├── md5.js │ ├── puppeteer.js │ └── logger.js ├── middleware │ ├── api-template.js │ ├── utf8.js │ ├── debug.js │ ├── header.js │ └── onerror.js ├── protected_router.js └── api_router.js ├── .eslintignore ├── Procfile ├── test ├── .eslintrc ├── utils │ ├── md5.js │ ├── wait.js │ ├── puppeteer.js │ └── common-config.js └── middleware │ ├── error.js │ └── header.js ├── docs ├── .vuepress │ ├── styles │ │ ├── palette.styl │ │ └── index.styl │ └── components │ │ └── Author.vue ├── support │ └── README.md └── en │ └── support │ └── README.md ├── .prettierignore ├── renovate.json ├── CONTRIBUTING.md ├── .prettierrc ├── .dockerignore ├── .codecov.yml ├── .gitignore ├── .github └── ISSUE_TEMPLATE │ ├── feature_request_zh.md │ ├── rss_request_zh.md │ ├── feature_request_en.md │ ├── bug_report_zh.md │ ├── rss_request_en.md │ └── bug_report_en.md ├── process.json ├── .travis.yml ├── .editorconfig ├── docker-compose.yml ├── LICENSE └── app.json /logs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 10.13.0 2 | -------------------------------------------------------------------------------- /lib/config/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | .vscode 3 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node lib/index.js 2 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | env: 2 | jest: true 3 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/palette.styl: -------------------------------------------------------------------------------- 1 | $accentColor = #F5712C 2 | -------------------------------------------------------------------------------- /lib/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/RSSHub/master/lib/favicon.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | docs/.vuepress/dist 3 | package-lock.json 4 | .github/ 5 | renovate.json 6 | coverage 7 | .vscode/ 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "prHourlyLimit": 0, 6 | "separateMajorMinor": false 7 | } 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## 请参见[参与我们](https://docs.rsshub.app/joinus/) 2 | 3 | ## Please refer to [Join Us](https://docs.rsshub.app/en/joinus/) 4 | -------------------------------------------------------------------------------- /lib/routes/javbus/home.js: -------------------------------------------------------------------------------- 1 | const { createHandler } = require('./util'); 2 | 3 | module.exports = createHandler('https://www.javbus.com/'); 4 | -------------------------------------------------------------------------------- /lib/routes/titsguru/home.js: -------------------------------------------------------------------------------- 1 | const { createHandler } = require('./util'); 2 | 3 | module.exports = createHandler('https://tits-guru.com/'); 4 | -------------------------------------------------------------------------------- /lib/routes/javbus/western/home.js: -------------------------------------------------------------------------------- 1 | const { createHandler } = require('../util'); 2 | 3 | module.exports = createHandler('https://www.javbus.work/'); 4 | -------------------------------------------------------------------------------- /lib/routes/titsguru/daily.js: -------------------------------------------------------------------------------- 1 | const { createHandler } = require('./util'); 2 | 3 | module.exports = createHandler('https://tits-guru.com/thebest/perDay'); 4 | -------------------------------------------------------------------------------- /lib/utils/wait.js: -------------------------------------------------------------------------------- 1 | module.exports = function wait(ms) { 2 | return new Promise((resolve) => { 3 | setTimeout(resolve, ms); 4 | }); 5 | }; 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 233, 3 | "tabWidth": 4, 4 | "singleQuote": true, 5 | "trailingComma": "es5", 6 | "arrowParens": "always" 7 | } 8 | -------------------------------------------------------------------------------- /lib/routes/javbus/uncensored/home.js: -------------------------------------------------------------------------------- 1 | const { createHandler } = require('../util'); 2 | 3 | module.exports = createHandler('https://www.javbus.com/uncensored'); 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | Dockerfile* 4 | docker-compose* 5 | .dockerignore 6 | .git 7 | .gitignore 8 | README.md 9 | LICENSE 10 | .vscode -------------------------------------------------------------------------------- /lib/routes/dapenti/tugua.js: -------------------------------------------------------------------------------- 1 | const utils = require('./utils'); 2 | 3 | module.exports = async (ctx) => { 4 | ctx.state.data = await utils.parseFeed({ subjectid: 70 }); 5 | }; 6 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | coverage: 3 | status: 4 | patch: 5 | default: 6 | enabled: no 7 | project: 8 | default: 9 | threshold: 2 10 | -------------------------------------------------------------------------------- /lib/routes/dapenti/subject.js: -------------------------------------------------------------------------------- 1 | const utils = require('./utils'); 2 | 3 | module.exports = async (ctx) => { 4 | ctx.state.data = await utils.parseFeed({ subjectid: ctx.params.id }); 5 | }; 6 | -------------------------------------------------------------------------------- /lib/routes/xiachufang/popular.js: -------------------------------------------------------------------------------- 1 | const { generatePopularData } = require('./utils'); 2 | 3 | module.exports = async (ctx) => { 4 | ctx.state.data = await generatePopularData(ctx.params.timeframe); 5 | }; 6 | -------------------------------------------------------------------------------- /lib/utils/md5.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | 3 | module.exports = function md5(date) { 4 | return crypto 5 | .createHash('md5') 6 | .update(date) 7 | .digest('hex'); 8 | }; 9 | -------------------------------------------------------------------------------- /test/utils/md5.js: -------------------------------------------------------------------------------- 1 | const md5 = require('../../lib/utils/md5'); 2 | 3 | describe('md5', () => { 4 | it('md5 RSSHub', async () => { 5 | expect(md5('RSSHub')).toBe('3187d745ec5983413e4f0dce3900d92d'); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /lib/routes/dribbble/user.js: -------------------------------------------------------------------------------- 1 | const utils = require('./utils'); 2 | 3 | module.exports = async (ctx) => { 4 | const name = ctx.params.name; 5 | 6 | ctx.state.data = await utils.getData(name, `https://dribbble.com/${name}`); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/routes/javbus/star.js: -------------------------------------------------------------------------------- 1 | const { getPage } = require('./util'); 2 | 3 | module.exports = async (ctx) => { 4 | const { sid } = ctx.params; 5 | 6 | ctx.state.data = await getPage(`https://www.javbus.com/star/${sid}`, ctx); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/routes/javbus/genre.js: -------------------------------------------------------------------------------- 1 | const { getPage } = require('./util'); 2 | 3 | module.exports = async (ctx) => { 4 | const { gid } = ctx.params; 5 | 6 | ctx.state.data = await getPage(`https://www.javbus.com/genre/${gid}`, ctx); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/routes/javbus/western/star.js: -------------------------------------------------------------------------------- 1 | const { getPage } = require('../util'); 2 | 3 | module.exports = async (ctx) => { 4 | const { sid } = ctx.params; 5 | 6 | ctx.state.data = await getPage(`https://www.javbus.work/star/${sid}`, ctx); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/routes/javbus/series.js: -------------------------------------------------------------------------------- 1 | const { getPage } = require('./util'); 2 | 3 | module.exports = async (ctx) => { 4 | const { seriesid } = ctx.params; 5 | 6 | ctx.state.data = await getPage(`https://www.javbus.com/series/${seriesid}`, ctx); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/routes/javbus/western/genre.js: -------------------------------------------------------------------------------- 1 | const { getPage } = require('../util'); 2 | 3 | module.exports = async (ctx) => { 4 | const { gid } = ctx.params; 5 | 6 | ctx.state.data = await getPage(`https://www.javbus.work/genre/${gid}`, ctx); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/routes/javbus/uncensored/genre.js: -------------------------------------------------------------------------------- 1 | const { getPage } = require('../util'); 2 | 3 | module.exports = async (ctx) => { 4 | const { gid } = ctx.params; 5 | ctx.state.data = await getPage(`https://www.javbus.com/uncensored/genre/${gid}`, ctx); 6 | }; 7 | -------------------------------------------------------------------------------- /lib/routes/xiachufang/user/cooked.js: -------------------------------------------------------------------------------- 1 | const { generateUserData } = require('../utils'); 2 | 3 | module.exports = async (ctx) => { 4 | ctx.state.data = await generateUserData({ 5 | id: ctx.params.id, 6 | path: 'cooked', 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /lib/routes/xiachufang/user/created.js: -------------------------------------------------------------------------------- 1 | const { generateUserData } = require('../utils'); 2 | 3 | module.exports = async (ctx) => { 4 | ctx.state.data = await generateUserData({ 5 | id: ctx.params.id, 6 | path: 'created', 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /lib/routes/javbus/uncensored/star.js: -------------------------------------------------------------------------------- 1 | const { getPage } = require('../util'); 2 | 3 | module.exports = async (ctx) => { 4 | const { sid } = ctx.params; 5 | 6 | ctx.state.data = await getPage(`https://www.javbus.com/uncensored/star/${sid}`, ctx); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/routes/javbus/western/series.js: -------------------------------------------------------------------------------- 1 | const { getPage } = require('../util'); 2 | 3 | module.exports = async (ctx) => { 4 | const { seriesid } = ctx.params; 5 | 6 | ctx.state.data = await getPage(`https://www.javbus.work/series/${seriesid}`, ctx); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/routes/pixiv/constants.js: -------------------------------------------------------------------------------- 1 | exports.modules = { 2 | maskHeader: { 3 | 'App-OS': 'ios', 4 | 'App-OS-Version': '10.3.1', 5 | 'App-Version': '6.7.1', 6 | 'User-Agent': 'PixivIOSApp/6.7.1 (iOS 10.3.1; iPhone8,1)', 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /lib/routes/ft/channel.js: -------------------------------------------------------------------------------- 1 | const utils = require('./utils'); 2 | 3 | module.exports = async (ctx) => { 4 | ctx.state.data = await utils.getData({ 5 | site: ctx.params.language === 'chinese' ? 'www' : 'big5', 6 | channel: ctx.params.channel, 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /lib/routes/javbus/uncensored/series.js: -------------------------------------------------------------------------------- 1 | const { getPage } = require('../util'); 2 | 3 | module.exports = async (ctx) => { 4 | const { seriesid } = ctx.params; 5 | 6 | ctx.state.data = await getPage(`https://www.javbus.com/uncensored/series/${seriesid}`, ctx); 7 | }; 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | error.log 4 | combined.log 5 | package-lock.json 6 | .vscode 7 | .idea 8 | .DS_Store 9 | docs/.vuepress/dist 10 | lib/config/app.json 11 | lib/config/config.js 12 | yarn-error.log 13 | tmp 14 | *.swp 15 | *.iml 16 | coverage 17 | -------------------------------------------------------------------------------- /lib/routes/dribbble/keyword.js: -------------------------------------------------------------------------------- 1 | const utils = require('./utils'); 2 | 3 | module.exports = async (ctx) => { 4 | const keyword = ctx.params.keyword; 5 | 6 | ctx.state.data = await utils.getData(`Keyword ${keyword}`, `https://dribbble.com/search?q=${keyword}&s=latest`); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/routes/titsguru/category.js: -------------------------------------------------------------------------------- 1 | const { getPage, normalizeKeyword } = require('./util'); 2 | 3 | module.exports = async (ctx) => { 4 | const { type } = ctx.params; 5 | 6 | ctx.state.data = await getPage(`https://tits-guru.com/category/${normalizeKeyword(type)}/date`); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/routes/dribbble/popular.js: -------------------------------------------------------------------------------- 1 | const utils = require('./utils'); 2 | 3 | module.exports = async (ctx) => { 4 | const timeframe = ctx.params.timeframe; 5 | 6 | ctx.state.data = await utils.getData('Popular Shots', `https://dribbble.com/shots${timeframe ? `?timeframe=${timeframe}` : ''}`); 7 | }; 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🍭 功能需求 3 | about: 提交新的功能需求 4 | --- 5 | 6 | 10 | 11 | ### 这是一个什么样的功能? 12 | 13 | ### 这个功能可以解决什么问题? 14 | 15 | ### 额外描述 16 | -------------------------------------------------------------------------------- /process.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps": [ 3 | { 4 | "name": "rsshub", 5 | "script": "lib/index.js", 6 | "instances": "max", 7 | "exec_mode": "cluster", 8 | "env": { 9 | "NODE_ENV": "production" 10 | } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: stable 4 | 5 | services: 6 | - redis-server 7 | 8 | install: 9 | - yarn 10 | 11 | script: 12 | - npm run test 13 | 14 | after_script: 15 | - npm install codecov 16 | - ./node_modules/.bin/codecov 17 | 18 | cache: 19 | yarn: true 20 | directories: 21 | - node_modules 22 | -------------------------------------------------------------------------------- /lib/routes/thepaper/featured.js: -------------------------------------------------------------------------------- 1 | const utils = require('./utils'); 2 | 3 | module.exports = async (ctx) => { 4 | const link = 'https://m.thepaper.cn/'; 5 | const items = await utils.ProcessFeed(link, ctx); 6 | 7 | ctx.state.data = { 8 | title: '澎湃新闻 - 首页头条', 9 | link, 10 | item: items, 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /lib/middleware/api-template.js: -------------------------------------------------------------------------------- 1 | module.exports = async (ctx, next) => { 2 | await next(); 3 | if (ctx.request.path.startsWith('/api/')) { 4 | return ctx.res.ok({ 5 | message: `request returned ${ctx.body.counter} ${ctx.body.counter > 1 ? 'routes' : 'route'}`, 6 | data: ctx.body.result, 7 | }); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /lib/protected_router.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | const router = new Router(); 3 | const auth = require('koa-basic-auth'); 4 | const config = require('./config'); 5 | 6 | router.use('/(.*)', auth(config.authentication)); 7 | 8 | // RSSHub 9 | router.get('/rsshub/rss', require('./routes/rsshub/rss')); 10 | 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /lib/routes/dongqiudi/player_news.js: -------------------------------------------------------------------------------- 1 | const utils = require('./utils'); 2 | 3 | module.exports = async (ctx) => { 4 | const id = ctx.params.id; 5 | const link = `https://www.dongqiudi.com/player/${id}.html`; 6 | const api = `https://www.dongqiudi.com/data/person/archive?person=${id}`; 7 | 8 | await utils.ProcessFeed(ctx, link, api); 9 | }; 10 | -------------------------------------------------------------------------------- /lib/routes/dongqiudi/team_news.js: -------------------------------------------------------------------------------- 1 | const utils = require('./utils'); 2 | 3 | module.exports = async (ctx) => { 4 | const team = ctx.params.team; 5 | const link = `https://www.dongqiudi.com/team/${team}.html`; 6 | const api = `https://www.dongqiudi.com/data/team/archive?team=${team}`; 7 | 8 | await utils.ProcessFeed(ctx, link, api); 9 | }; 10 | -------------------------------------------------------------------------------- /lib/routes/thepaper/channel.js: -------------------------------------------------------------------------------- 1 | const utils = require('./utils'); 2 | 3 | module.exports = async (ctx) => { 4 | const { id } = ctx.params; 5 | const link = `https://m.thepaper.cn/list_${id}`; 6 | const items = await utils.ProcessFeed(link, ctx); 7 | 8 | ctx.state.data = { 9 | title: `澎湃新闻频道 - ${id}`, 10 | link, 11 | item: items, 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /test/utils/wait.js: -------------------------------------------------------------------------------- 1 | const wait = require('../../lib/utils/wait'); 2 | 3 | describe('wait', () => { 4 | it('wait 0.1 second', async () => { 5 | const startDate = new Date(); 6 | 7 | await wait(0.1 * 1000); 8 | 9 | const endDate = new Date(); 10 | expect(endDate - startDate).toBeGreaterThan(90); 11 | expect(endDate - startDate).toBeLessThan(110); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/rss_request_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🍰 RSS 需求 3 | about: 提交新的 RSS 需求 4 | --- 5 | 6 | 12 | 13 | ### 源站地址 14 | 15 | ### 需要生成什么内容? 16 | 17 | - [ ] 内容一 18 | - [ ] 内容二 19 | 20 | ### 额外描述 21 | -------------------------------------------------------------------------------- /lib/middleware/utf8.js: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/questions/2507608/error-input-is-not-proper-utf-8-indicate-encoding-using-phps-simplexml-lo/40552083#40552083 2 | // https://stackoverflow.com/questions/1497885/remove-control-characters-from-php-string/1497928#1497928 3 | module.exports = async (ctx, next) => { 4 | await next(); 5 | ctx.body = typeof ctx.body !== 'object' ? ctx.body.replace(/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/g, '') : ctx.body; 6 | }; 7 | -------------------------------------------------------------------------------- /test/middleware/error.js: -------------------------------------------------------------------------------- 1 | const supertest = require('supertest'); 2 | const { server } = require('../../lib/index'); 3 | const request = supertest(server); 4 | 5 | afterAll(() => { 6 | server.close(); 7 | }); 8 | 9 | describe('error', () => { 10 | it(`error`, async () => { 11 | const response = await request.get('/test/0'); 12 | expect(response.text).toMatch(/RSSHub 发生了一些意外:
Error: Error test/);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/lib/routes/cctv/category.js:
--------------------------------------------------------------------------------
1 | module.exports = async (ctx) => {
2 | const { category } = ctx.params;
3 | let responseData;
4 |
5 | if (category === 'mzzlbg') {
6 | // 每周质量报告
7 | const getMzzlbg = require('./utils/mzzlbg');
8 | responseData = await getMzzlbg();
9 | } else {
10 | // 央视新闻
11 | const getNews = require('./utils/news');
12 | responseData = await getNews(category, ctx);
13 | }
14 |
15 | ctx.state.data = responseData;
16 | };
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request_en.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🍭 Feature Request
3 | about: Submit a new feature request
4 | ---
5 |
6 |
10 |
11 | ### What feature is it?
12 |
13 | ### What problem does this feature solve?
14 |
15 | ### Additional description
16 |
--------------------------------------------------------------------------------
/lib/routes/mzitu/util.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | exports.getList = async (id) => {
4 | const contentUrl = `http://adr.meizitu.net/wp-json/wp/v2/i?id=${id}`;
5 | const contentResponse = await axios({
6 | method: 'get',
7 | url: contentUrl,
8 | });
9 | const content = contentResponse.data.content.split(',');
10 | return content.map((url) => {
11 | url = url.replace(/"/g, '');
12 | return `
`;
13 | });
14 | };
15 |
--------------------------------------------------------------------------------
/lib/routes/zhihu/pin/hotlist.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const { generateData } = require('./utils');
3 |
4 | module.exports = async (ctx) => {
5 | const {
6 | data: { data },
7 | } = await axios({
8 | method: 'get',
9 | url: 'https://api.zhihu.com/pins/hot_list?reverse_order=0',
10 | });
11 |
12 | ctx.state.data = {
13 | title: '知乎想法热榜',
14 | link: 'https://www.zhihu.com/',
15 | description: '整点更新',
16 | item: generateData(data),
17 | };
18 | };
19 |
--------------------------------------------------------------------------------
/docs/support/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar: auto
3 | ---
4 |
5 | # 支持 RSSHub
6 |
7 | RSSHub 是采用 MIT 许可的开源项目, 使用完全免费. 但是随着项目规模的增长, 也需要有相应的资金支持才能持续项目的维护与开发.
8 |
9 | 你可以通过下列的方法来赞助 RSSHub 的开发.
10 |
11 | ## 周期性赞助
12 |
13 | 周期性赞助可以获得额外的回报, 比如更快的 GitHub 响应或者你的名字会出现在 RSSHub 的 GitHub 仓库和现在我们的官网中.
14 |
15 | - 通过 [Patreon](https://www.patreon.com/DIYgod) 赞助
16 | - 给我们发邮件联系赞助事宜: i#diygod.me
17 |
18 | ## 一次性赞助
19 |
20 | 我们通过以下方式接受赞助 :
21 |
22 | - [微信支付](https://i.loli.net/2019/03/23/5c950ebbc373e.png)
23 | - [支付宝](https://i.loli.net/2019/03/23/5c950ebbc980e.png)
24 |
--------------------------------------------------------------------------------
/lib/routes/mi/youpin/new.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const utils = require('./utils');
3 |
4 | module.exports = async (ctx) => {
5 | const response = await axios({
6 | method: 'get',
7 | url: 'https://home.mi.com/lasagne/page/4',
8 | });
9 |
10 | const data = response.data.floors;
11 |
12 | ctx.state.data = {
13 | title: '小米有品每日上新',
14 | link: 'https://home.mi.com/newproduct?pageid=4',
15 | description: '小米有品每日上新',
16 | item: utils.generateData(data),
17 | };
18 | };
19 |
--------------------------------------------------------------------------------
/lib/routes/nhentai/search.js:
--------------------------------------------------------------------------------
1 | const { getSimple, getDetails } = require('./util');
2 |
3 | module.exports = async (ctx) => {
4 | const { keyword, mode } = ctx.params;
5 | const isSimple = mode !== 'detail';
6 |
7 | const simples = await getSimple(`https://nhentai.net/search/?q=${keyword}`);
8 |
9 | ctx.state.data = {
10 | title: `nHentai - search - ${keyword}`,
11 | link: 'https://nhentai.net',
12 | description: 'hentai',
13 | item: isSimple ? simples : await getDetails(ctx.cache, simples),
14 | };
15 | };
16 |
--------------------------------------------------------------------------------
/lib/routes/mi/youpin/crowdfunding.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const utils = require('./utils');
3 |
4 | module.exports = async (ctx) => {
5 | const response = await axios({
6 | method: 'get',
7 | url: 'https://home.mi.com/lasagne/page/5',
8 | });
9 |
10 | const data = response.data.floors;
11 |
12 | ctx.state.data = {
13 | title: '小米有品众筹',
14 | link: 'https://home.mi.com/crowdfunding?&pageid=5',
15 | description: '小米有品众筹',
16 | item: utils.generateData(data),
17 | };
18 | };
19 |
--------------------------------------------------------------------------------
/lib/middleware/debug.js:
--------------------------------------------------------------------------------
1 | module.exports = async (ctx, next) => {
2 | if (!ctx.debug.routes[ctx.request.path]) {
3 | ctx.debug.routes[ctx.request.path] = 0;
4 | }
5 | ctx.debug.routes[ctx.request.path]++;
6 |
7 | const ip = ctx.ips[0] || ctx.ip;
8 | if (!ctx.debug.ips[ip]) {
9 | ctx.debug.ips[ip] = 0;
10 | }
11 | ctx.debug.ips[ip]++;
12 | ctx.debug.request++;
13 |
14 | await next();
15 |
16 | if (ctx.response.get('X-Koa-Redis-Cache') || ctx.response.get('X-Koa-Memory-Cache')) {
17 | ctx.debug.hitCache++;
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lib/routes/titsguru/model.js:
--------------------------------------------------------------------------------
1 | const { getPage, normalizeKeyword } = require('./util');
2 |
3 | module.exports = async (ctx) => {
4 | const { name } = ctx.params;
5 |
6 | const formalName = normalizeKeyword(name)
7 | .split('-')
8 | .map((word) => `${word[0].toUpperCase()}${word.substr(1)}`)
9 | .join(' ');
10 |
11 | ctx.state.data = {
12 | ...(await getPage(`https://tits-guru.com/boobs-model/${normalizeKeyword(name)}/date`)),
13 | title: `TitsGuru - ${formalName}`,
14 | description: `TitsGuru - ${formalName}`,
15 | };
16 | };
17 |
--------------------------------------------------------------------------------
/docs/.vuepress/styles/index.styl:
--------------------------------------------------------------------------------
1 | .navbar .home-link .site-name {
2 | color: #F5712C;
3 | }
4 |
5 | .page .custom-block.tip {
6 | border-color: #F5712C;
7 | }
8 |
9 | .theme-container .page .content .logo-img {
10 | margin-top: 3.6rem;
11 | }
12 |
13 | .page .content .logo-text {
14 | padding-top: 2.6rem;
15 | padding-bottom: 2rem;
16 | }
17 |
18 | .icon.outbound {
19 | display: none;
20 | }
21 |
22 | a {
23 | word-break: break-all;
24 | }
25 |
26 | #关于 {
27 | display: none;
28 | }
29 |
30 | #app .global-ui .sw-update-popup {
31 | border: 1px solid #F5712C;
32 | }
33 |
--------------------------------------------------------------------------------
/lib/middleware/header.js:
--------------------------------------------------------------------------------
1 | const logger = require('../utils/logger');
2 | const config = require('../config');
3 | const headers = {
4 | 'Access-Control-Allow-Origin': '*',
5 | 'Access-Control-Allow-Headers': 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With',
6 | 'Access-Control-Allow-Methods': 'GET',
7 | 'Content-Type': 'application/xml; charset=utf-8',
8 | 'Cache-Control': `max-age=${config.cacheExpire / 2}`,
9 | };
10 |
11 | module.exports = async (ctx, next) => {
12 | logger.info(`${ctx.url}, user IP: ${ctx.ips[0] || ctx.ip}`);
13 | ctx.set(headers);
14 | await next();
15 | };
16 |
--------------------------------------------------------------------------------
/lib/middleware/onerror.js:
--------------------------------------------------------------------------------
1 | const logger = require('../utils/logger');
2 |
3 | module.exports = async (ctx, next) => {
4 | try {
5 | await next();
6 | } catch (err) {
7 | logger.error(`Error in ${ctx.request.path}: ${err instanceof Error ? err.stack : err}`);
8 | ctx.set({
9 | 'Content-Type': 'text/html; charset=UTF-8',
10 | });
11 | ctx.body = `RSSHub 发生了一些意外: ${err instanceof Error ? err.stack : err}`;
12 | if (err.status === 401) {
13 | ctx.status = 401;
14 | } else {
15 | ctx.status = 404;
16 | }
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/test/utils/puppeteer.js:
--------------------------------------------------------------------------------
1 | const puppeteer = require('../../lib/utils/puppeteer');
2 |
3 | describe('puppeteer', () => {
4 | it('puppeteer run', async () => {
5 | const browser = await puppeteer();
6 | const page = await browser.newPage();
7 | await page.goto('https://github.com/DIYgod/RSSHub', {
8 | waitUntil: 'domcontentloaded',
9 | });
10 |
11 | // eslint-disable-next-line no-undef
12 | const html = await page.evaluate(() => document.body.innerHTML);
13 | expect(html.length).toBeGreaterThan(0);
14 |
15 | await browser.close();
16 | }, 10000);
17 | });
18 |
--------------------------------------------------------------------------------
/lib/routes/patchwork.kernel.org/cache.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = {
4 | getPatchnameFromID: async (ctx, id) => {
5 | const key = 'patchwork-kernel-org-patchname-from-id-' + id;
6 | let name = await ctx.cache.get(key);
7 | if (!name) {
8 | const patchDetail = await axios({
9 | method: 'get',
10 | url: `https://patchwork.kernel.org/api/patches/${id}/`,
11 | });
12 | name = patchDetail.data.name;
13 | ctx.cache.set(key, name, 24 * 60 * 60);
14 | }
15 | return name;
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/lib/routes/twitter/likes.js:
--------------------------------------------------------------------------------
1 | const Twit = require('twit');
2 | const config = require('../../config');
3 | const utils = require('./utils');
4 |
5 | const T = new Twit(config.twitter);
6 |
7 | module.exports = async (ctx) => {
8 | const id = ctx.params.id;
9 | const result = await T.get('favorites/list', {
10 | screen_name: id,
11 | tweet_mode: 'extended',
12 | });
13 | const data = result.data;
14 |
15 | ctx.state.data = {
16 | title: `Twitter Likes - ${id}`,
17 | link: `https://twitter.com/${id}/likes`,
18 | item: utils.ProcessFeed({
19 | data,
20 | }),
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/lib/routes/zhihu/pin/daily.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const { generateData } = require('./utils');
3 |
4 | module.exports = async (ctx) => {
5 | const {
6 | data: { data },
7 | } = await axios({
8 | method: 'get',
9 | url: 'https://api.zhihu.com/pins/special/972884951192113152/moments?order_by=newest&reverse_order=0&limit=20',
10 | });
11 |
12 | ctx.state.data = {
13 | title: '知乎想法-24小时新闻汇总',
14 | link: 'https://www.zhihu.com/pin/special/972884951192113152',
15 | description: '汇集每天的社会大事、行业资讯,让你用最简单的方式获得想法里的新闻',
16 | item: generateData(data),
17 | };
18 | };
19 |
--------------------------------------------------------------------------------
/lib/routes/wikipedia/utils.js:
--------------------------------------------------------------------------------
1 | const ProcessLink = ($, lang) => {
2 | lang = !lang ? 'en' : lang;
3 |
4 | $.find('a').each((i, e) => {
5 | if (e.attribs.href.startsWith('/wiki/')) {
6 | e.attribs.href = `https://${lang}.wikipedia.org${e.attribs.href}`;
7 | }
8 | });
9 |
10 | // img links
11 | $.find('img').each((i, e) => {
12 | if (e.attribs.src.startsWith('//upload.wikimedia.org/')) {
13 | e.attribs.src = `https:${e.attribs.src}`;
14 | e.attribs.srcset = '';
15 | }
16 | });
17 |
18 | return $.html();
19 | };
20 |
21 | module.exports = {
22 | ProcessLink,
23 | };
24 |
--------------------------------------------------------------------------------
/lib/routes/zhihu/pin/people.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const { generateData } = require('./utils');
3 |
4 | module.exports = async (ctx) => {
5 | const { id } = ctx.params;
6 |
7 | const {
8 | data: { data },
9 | } = await axios({
10 | method: 'get',
11 | url: `https://www.zhihu.com/api/v4/members/${id}/pins?offset=0&limit=20&includes=data%5B*%5D.upvoted_followees%2Cadmin_closed_comment`,
12 | });
13 |
14 | ctx.state.data = {
15 | title: `${data[0].author.name}的知乎想法`,
16 | link: `https://www.zhihu.com/people/${id}/pins`,
17 | item: generateData(data),
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/lib/routes/jike/daily.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const {
5 | data: { data },
6 | } = await axios.get('https://app.jike.ruguoapp.com/1.0/dailies/list');
7 |
8 | ctx.state.data = {
9 | title: '即刻小报',
10 | link: 'https://jike.app/',
11 | description: '不定期呈现的即刻内容精选',
12 | item: data.map((item) => ({
13 | title: item.title,
14 | description: `
`,
15 | pubDate: new Date(item.date).toUTCString(),
16 | link: `https://m.okjike.com/dailies/${item.id}`,
17 | })),
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/lib/routes/donews/utils.js:
--------------------------------------------------------------------------------
1 | const cheerio = require('cheerio');
2 | const axios = require('../../utils/axios');
3 | const date = require('../../utils/date');
4 |
5 | const ProcessFeed = async (link) => {
6 | const response = await axios.get(link);
7 |
8 | const $ = cheerio.load(response.data);
9 |
10 | const meta = $($('#main .fl')[0]);
11 |
12 | return {
13 | title: $('h1').text() || $($('h2')[1]).text(),
14 | author: meta.find('span:nth-child(1)').text(),
15 | description: $('.article-con').html(),
16 | pubDate: date(meta.find('span:nth-child(2)').text(), 8),
17 | link,
18 | };
19 | };
20 |
21 | module.exports = {
22 | ProcessFeed,
23 | };
24 |
--------------------------------------------------------------------------------
/lib/routes/twitter/list.js:
--------------------------------------------------------------------------------
1 | const Twit = require('twit');
2 | const config = require('../../config');
3 | const utils = require('./utils');
4 |
5 | const T = new Twit(config.twitter);
6 |
7 | module.exports = async (ctx) => {
8 | const { id, name } = ctx.params;
9 | const result = await T.get('lists/statuses', {
10 | owner_screen_name: id,
11 | slug: name,
12 | tweet_mode: 'extended',
13 | });
14 | const data = result.data;
15 |
16 | ctx.state.data = {
17 | title: `Twitter List - ${id}/${name}`,
18 | link: `https://twitter.com/${id}/lists/${name}`,
19 | item: utils.ProcessFeed({
20 | data,
21 | }),
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/lib/routes/twitter/user.js:
--------------------------------------------------------------------------------
1 | const Twit = require('twit');
2 | const config = require('../../config');
3 | const utils = require('./utils');
4 |
5 | const T = new Twit(config.twitter);
6 |
7 | module.exports = async (ctx) => {
8 | const id = ctx.params.id;
9 | const result = await T.get('statuses/user_timeline', {
10 | screen_name: id,
11 | tweet_mode: 'extended',
12 | });
13 | const data = result.data;
14 |
15 | ctx.state.data = {
16 | title: `${data[0].user.name} 的 Twitter`,
17 | link: `https://twitter.com/${id}/`,
18 | description: data[0].user.description,
19 | item: utils.ProcessFeed({
20 | data,
21 | }),
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/lib/routes/wegene/newest.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const link = 'https://www.wegene.com/';
5 | const api = 'https://www.wegene.com/contents/ajax/update/get_list/?htmlspecialchars_decode=true&page=1';
6 |
7 | const response = await axios.get(api);
8 | const data = response.data.rsm;
9 |
10 | ctx.state.data = {
11 | title: '最近更新-WeGene',
12 | link: link,
13 | item: data.map((item) => ({
14 | title: item.title,
15 | description: item.description,
16 | pubDate: new Date(item.add_time * 1000).toUTCString(),
17 | link: item.url,
18 | })),
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report_zh.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🐛 Bug 报告
3 | about: 早起的小可爱有虫抓
4 | ---
5 |
6 |
11 |
12 | ### 路由地址
13 |
14 | ### 预期是什么?
15 |
16 | ### 实际发生了什么?
17 |
18 | ### 部署相关信息
19 |
20 |
24 |
25 | | Env | Value |
26 | | ------------------ | ------------- |
27 | | OS | |
28 | | Node version | |
29 | | if Docker, version | |
30 |
31 | ### 额外信息(日志、报错等)
32 |
--------------------------------------------------------------------------------
/lib/routes/vgtime/release.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: `http://app02.vgtime.com:8080/vgtime-app/api/v2/game/last/sales`,
7 | });
8 | const data = response.data.data.gameList;
9 |
10 | ctx.state.data = {
11 | title: `游戏时光游戏发售表`,
12 | link: `https://www.vgtime.com/game/release.jhtml`,
13 | item: data.map((item) => ({
14 | title: item.title,
15 | description: `平台:${item.platformsText};${item.introduction}`,
16 | pubDate: item.publishDate,
17 | link: item.shareUrl,
18 | })),
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/lib/routes/zimuzu/resource.js:
--------------------------------------------------------------------------------
1 | const Parser = require('rss-parser');
2 | const parser = new Parser({
3 | customFields: {
4 | item: ['magnet'],
5 | },
6 | });
7 |
8 | module.exports = async (ctx) => {
9 | const { id = 0 } = ctx.params;
10 | const feed = await parser.parseURL(`http://diaodiaode.me/rss/feed/${id}`);
11 | feed.items.map((item) => {
12 | item.link = null;
13 | item.enclosure_url = item.magnet;
14 | item.enclosure_type = 'application/x-bittorrent';
15 | return item;
16 | });
17 |
18 | ctx.state.data = {
19 | title: feed.title,
20 | link: feed.link,
21 | description: feed.description,
22 | item: feed.items,
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/lib/routes/anigamer/anime.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const { anime } = await axios.get(`https://api.gamer.com.tw/mobile_app/anime/v1/video.php?sn=0&anime_sn=${ctx.params.sn}`).then((r) => r.data);
5 | ctx.state.data = {
6 | title: anime.title,
7 | link: `https://ani.gamer.com.tw/animeRef.php?sn=${anime.anime_sn}`,
8 | description: `
` + anime.content.trim(),
9 | item: anime.volumes[0].map((item) => ({
10 | title: `${anime.title} 第 ${item.volume} 集`,
11 | link: `https://ani.gamer.com.tw/animeVideo.php?sn=${item.video_sn}`,
12 | })),
13 | };
14 | };
15 |
--------------------------------------------------------------------------------
/lib/routes/universities/bit/jwc/jwc.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const util = require('./utils');
4 |
5 | module.exports = async (ctx) => {
6 | const response = await axios({
7 | method: 'get',
8 | url: 'http://jwc.bit.edu.cn/tzgg',
9 | });
10 |
11 | const $ = cheerio.load(response.data);
12 |
13 | const list = $('.crules div')
14 | .slice(0, 10)
15 | .get();
16 |
17 | const result = await util.ProcessFeed(list, ctx.cache);
18 |
19 | ctx.state.data = {
20 | title: $('title').text(),
21 | link: 'http://jwc.bit.edu.cn/tzgg',
22 | description: '北京理工大学教务部',
23 | item: result,
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/lib/routes/universities/zjgsu/tzgg/scripts.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const util = require('./utils');
4 |
5 | module.exports = async (ctx) => {
6 | const response = await axios({
7 | method: 'get',
8 | url: 'http://news.zjgsu.edu.cn/18/',
9 | });
10 |
11 | const data = response.data;
12 | const $ = cheerio.load(data);
13 | const list = $('ul.list-1 li').get();
14 |
15 | const result = await util.ProcessFeed(list, ctx.cache);
16 |
17 | ctx.state.data = {
18 | title: '浙江工商大学新闻网-通知公告',
19 | link: 'http://news.zjgsu.edu.cn/18/',
20 | description: '浙江工商大学新闻网-通知公告',
21 | item: result,
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/lib/routes/gov/city/index.js:
--------------------------------------------------------------------------------
1 | module.exports = async (ctx) => {
2 | const { name, category } = ctx.params;
3 | let url = '';
4 |
5 | switch (name) {
6 | case 'nanjing':
7 | url = 'http://www.nanjing.gov.cn';
8 | break;
9 | default:
10 | console.log('URL pattern not matched');
11 | }
12 |
13 | if (url === '') {
14 | ctx.throw(404, 'Cannot find page');
15 | return;
16 | }
17 |
18 | try {
19 | const getRSS = require(`./${name}`);
20 | const responseData = await getRSS(url, category);
21 | ctx.state.data = responseData;
22 | } catch (error) {
23 | console.error(error);
24 | ctx.throw(404, 'Cannot find page');
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/lib/routes/guokr/scientific.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios.get('https://www.guokr.com/apis/minisite/article.json?retrieve_type=by_subject&limit=20&offset=0');
5 |
6 | const result = response.data.result;
7 |
8 | ctx.state.data = {
9 | title: '果壳网 科学人',
10 | link: 'https://www.guokr.com/scientific',
11 | description: '果壳网 科学人',
12 | item: result.map((item) => ({
13 | title: item.title,
14 | description: `${item.summary}
`,
15 | pubDate: item.date_published,
16 | link: item.url,
17 | })),
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/lib/routes/mzitu/tags.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const url = 'http://adr.meizitu.net/wp-json/wp/v2/tags?orderby=count&order=desc&per_page=100';
5 |
6 | const response = await axios({
7 | method: 'get',
8 | url: url,
9 | });
10 | const data = response.data;
11 |
12 | ctx.state.data = {
13 | title: '妹子图专题',
14 | link: 'http://www.mzitu.com/zhuanti',
15 | description: '妹子图美女专题栏目,为您精心准备各种美女图片专题,包括名站美女写真,妹子特点分类,美女大全等专题。',
16 | item: data.map((item) => ({
17 | title: item.name,
18 | description: `${item.name}`,
19 | link: `http://www.mzitu.com/tag/${item.slug}`,
20 | })),
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/lib/routes/gov/province/index.js:
--------------------------------------------------------------------------------
1 | module.exports = async (ctx) => {
2 | const { name, category } = ctx.params;
3 | let url = '';
4 |
5 | switch (name) {
6 | case 'jiangsu':
7 | url = 'http://www.jiangsu.gov.cn';
8 | break;
9 | default:
10 | console.log('URL pattern not matched');
11 | }
12 |
13 | if (url === '') {
14 | ctx.throw(404, 'Cannot find page');
15 | return;
16 | }
17 |
18 | try {
19 | const getRSS = require(`./${name}`);
20 | const responseData = await getRSS(url, category);
21 | ctx.state.data = responseData;
22 | } catch (error) {
23 | console.error(error);
24 | ctx.throw(404, 'Cannot find page');
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/lib/routes/pixiv/api/getBookmarks.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const maskHeader = require('../constants').maskHeader;
3 |
4 | /**
5 | * 获取用户的收藏
6 | *
7 | * @param {string} user_id 目标用户id
8 | * @param {string} token pixiv oauth token
9 | * @returns {Promise>}
10 | */
11 | module.exports = async function getBookmarks(user_id, token) {
12 | return await axios({
13 | method: 'get',
14 | url: 'https://app-api.pixiv.net/v1/user/bookmarks/illust',
15 | headers: {
16 | ...maskHeader,
17 | Authorization: 'Bearer ' + token,
18 | },
19 | params: {
20 | user_id: user_id,
21 | restrict: 'public',
22 | },
23 | });
24 | };
25 |
--------------------------------------------------------------------------------
/lib/routes/universities/bit/cs/cs.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const util = require('./utils');
4 |
5 | module.exports = async (ctx) => {
6 | const response = await axios({
7 | method: 'get',
8 | url: 'http://cs.bit.edu.cn/tzgg',
9 | });
10 |
11 | const $ = cheerio.load(response.data);
12 |
13 | const list = $('.box_list01 li')
14 | .slice(0, 10)
15 | .get();
16 |
17 | const result = await util.ProcessFeed(list, ctx.cache);
18 |
19 | ctx.state.data = {
20 | title: $('title').text(),
21 | link: 'http://cs.bit.edu.cn/tzgg',
22 | description: $('meta[name="description"]').attr('content'),
23 | item: result,
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/lib/routes/douban/later.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: 'https://api.douban.com/v2/movie/coming_soon',
7 | });
8 | const movieList = response.data.subjects;
9 |
10 | ctx.state.data = {
11 | title: '即将上映的电影',
12 | link: 'https://movie.douban.com/cinema/later/',
13 | item: movieList.map((item) => ({
14 | title: item.title,
15 | description: `标题:${item.title}
影片类型:${item.genres.join(' | ')}
评分:${item.rating.stars === '00' ? '无' : item.rating.average}
`,
16 | link: item.alt,
17 | })),
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/lib/routes/pixiv/api/getIllusts.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const maskHeader = require('../constants').maskHeader;
3 |
4 | /**
5 | * 获取用户插画作品
6 | * @param {string} user_id 目标用户id
7 | * @param {string} token pixiv oauth token
8 | * @returns {Promise>}
9 | */
10 | module.exports = async function getIllusts(user_id, token) {
11 | return await axios({
12 | method: 'get',
13 | url: 'https://app-api.pixiv.net/v1/user/illusts',
14 | headers: {
15 | ...maskHeader,
16 | Authorization: 'Bearer ' + token,
17 | },
18 | params: {
19 | user_id: user_id,
20 | filter: 'for_ios',
21 | type: 'illust',
22 | },
23 | });
24 | };
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/rss_request_en.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🍰 RSS Request
3 | about: Submit a new RSS request
4 | ---
5 |
6 |
12 |
13 | ### Website URL
14 |
15 | ### What content should be included?
16 |
17 | - [ ] content one
18 | - [ ] content two
19 |
20 | ### Additional description
21 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/Author.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
22 |
23 |
38 |
--------------------------------------------------------------------------------
/lib/routes/aqk/category.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const api = 'https://api.anquanke.com/data/v1/posts?size=10&page=1&category=';
5 | const type = ctx.params.category;
6 | const host = 'https://www.anquanke.com';
7 | const res = await axios.get(`${api}${type}`);
8 | const dataArray = res.data.data;
9 |
10 | const items = dataArray.map((item) => ({
11 | title: item.title,
12 | description: item.desc,
13 | pubDate: item.date,
14 | link: `${host}/${type === 'week' ? 'week' : 'post'}/id/${item.id}`,
15 | }));
16 |
17 | ctx.state.data = {
18 | title: `安全客-${dataArray[0].category_name}`,
19 | link: 'https://www.anquanke.com',
20 | item: items,
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/lib/routes/nvidia/webdriverupdate.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const plist = require('plist');
3 |
4 | module.exports = async (ctx) => {
5 | const url = 'https://gfe.nvidia.com/mac-update';
6 |
7 | const res = await axios({
8 | method: 'get',
9 | url: url,
10 | });
11 |
12 | const list = plist.parse(res.data).updates;
13 |
14 | const resultItem = list.map(function(i) {
15 | const item = {};
16 | item.title = i.version;
17 |
18 | item.description = i.version;
19 | item.guid = i.checksum;
20 | item.link = i.downloadURL;
21 | return item;
22 | });
23 |
24 | ctx.state.data = {
25 | title: 'Nvidia WebDriver Update',
26 | link: url,
27 | item: resultItem,
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/lib/api_router.js:
--------------------------------------------------------------------------------
1 | const Router = require('koa-router');
2 | const router = new Router();
3 | const routes = require('./router');
4 |
5 | router.get('/routes/:name?', (ctx) => {
6 | const allRoutes = Array.from(routes.stack);
7 | allRoutes.shift();
8 | const result = {};
9 | let counter = 0;
10 |
11 | allRoutes.forEach((i) => {
12 | const path = i.path;
13 | const top = path.split('/')[1];
14 |
15 | if (!ctx.params.name || top === ctx.params.name) {
16 | if (result[top]) {
17 | result[top].routes.push(path);
18 | } else {
19 | result[top] = { routes: [path] };
20 | }
21 | counter++;
22 | }
23 | });
24 |
25 | ctx.body = { counter, result };
26 | });
27 |
28 | module.exports = router;
29 |
--------------------------------------------------------------------------------
/lib/routes/sspai/series.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: `https://sspai.com/api/v1/series/page/get?limit=5&sort=weight&offset=0&released_at=0`,
7 | headers: {
8 | Host: 'sspai.com',
9 | },
10 | });
11 | const data = response.data.data;
12 |
13 | ctx.state.data = {
14 | title: '少数派 -- 最新上架付费专栏',
15 | link: 'https://sspai.com/series',
16 | description: '少数派 -- 最新上架付费专栏',
17 | item: data.map((item) => ({
18 | title: item.title,
19 | description: item.summary,
20 | link: `https://sspai.com/series/${item.id}`,
21 | author: item.nickname,
22 | })),
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/lib/routes/jianshu/home.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const util = require('./utils');
4 |
5 | module.exports = async (ctx) => {
6 | const response = await axios({
7 | method: 'get',
8 | url: 'https://www.jianshu.com',
9 | headers: {
10 | Referer: 'https://www.jianshu.com',
11 | },
12 | });
13 |
14 | const data = response.data;
15 |
16 | const $ = cheerio.load(data);
17 | const list = $('.note-list li').get();
18 |
19 | const result = await util.ProcessFeed(list, ctx.cache);
20 |
21 | ctx.state.data = {
22 | title: '简书首页',
23 | link: 'https://www.jianshu.com',
24 | description: $('meta[name="description"]').attr('content'),
25 | item: result,
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/lib/routes/infoq/recommend.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const utils = require('./utils');
3 |
4 | module.exports = async (ctx) => {
5 | const apiUrl = 'https://www.infoq.cn/public/v1/my/recommond';
6 | const pageUrl = 'https://www.infoq.cn';
7 |
8 | const resp = await axios({
9 | method: 'post',
10 | url: apiUrl,
11 | headers: {
12 | Referer: pageUrl,
13 | 'Content-Type': 'application/json',
14 | },
15 | data: {
16 | size: 5,
17 | },
18 | });
19 |
20 | const data = resp.data.data;
21 | const items = await utils.ProcessFeed(data, ctx.cache);
22 |
23 | ctx.state.data = {
24 | title: 'InfoQ推荐',
25 | link: pageUrl,
26 | description: 'InfoQ推荐',
27 | item: items,
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/lib/routes/pixiv/api/searchPopularIllust.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const maskHeader = require('../constants').maskHeader;
3 |
4 | /**
5 | * 按热门排序搜索内容
6 | * @param {string} keyword 关键词
7 | * @param {string} token pixiv oauth token
8 | * @returns {Promise>}
9 | */
10 | module.exports = async function searchPopularIllust(keyword, token) {
11 | return await axios({
12 | method: 'get',
13 | url: 'https://app-api.pixiv.net/v1/search/popular-preview/illust',
14 | headers: {
15 | ...maskHeader,
16 | Authorization: 'Bearer ' + token,
17 | },
18 | params: {
19 | word: keyword,
20 | search_target: 'partial_match_for_tags',
21 | filter: 'for_ios',
22 | },
23 | });
24 | };
25 |
--------------------------------------------------------------------------------
/lib/routes/guanzhi/guanzhi.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const {
5 | data: { data },
6 | } = await axios.get('https://interface.meiriyiwen.com/article/today');
7 |
8 | ctx.state.data = {
9 | title: '观止',
10 | link: 'https://meiriyiwen.com/',
11 | description: '每天一篇精选优质短篇',
12 | item: [
13 | {
14 | title: data.title,
15 | description: data.content,
16 | author: data.author,
17 | pubDate: new Date(`${data.date.curr.substr(0, 4)}-${data.date.curr.substr(4, 2)}-${data.date.curr.substr(6)} 00:00:01`).toUTCString(),
18 | guid: data.date.curr,
19 | link: 'https://meiriyiwen.com/',
20 | },
21 | ],
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/lib/routes/uraaka-joshi/uraaka-joshi.js:
--------------------------------------------------------------------------------
1 | const buildData = require('../../utils/common-config');
2 |
3 | module.exports = async (ctx) => {
4 | const link = `https://www.uraaka-joshi.com/`;
5 | ctx.state.data = await buildData({
6 | link,
7 | url: link,
8 | title: `%title%`,
9 | params: {
10 | title: '裏垢女子まとめ',
11 | },
12 | item: {
13 | item: '.content-main .stream .stream-item',
14 | title: `$('.post-account-group').text() + ' - %title%'`,
15 | link: `$('.post-account-group').attr('href')`,
16 | description: `$('.post .context').html()`,
17 | pubDate: `new Date($('.post-time').attr('datetime')).toUTCString()`,
18 | guid: `new Date($('.post-time').attr('datetime')).getTime()`,
19 | },
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/lib/routes/gaoqing/latest.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const util = require('./utils');
4 |
5 | module.exports = async (ctx) => {
6 | const response = await axios({
7 | method: 'get',
8 | url: 'https://gaoqing.fm/',
9 | headers: {
10 | Referer: 'https://gaoqing.fm/',
11 | },
12 | });
13 |
14 | const data = response.data;
15 |
16 | const $ = cheerio.load(data);
17 | const list = $('#result1 > li').get();
18 |
19 | let result = await util.ProcessFeed(list, ctx.cache);
20 | result = result.slice(0, 10);
21 |
22 | ctx.state.data = {
23 | title: '高清电台',
24 | link: 'https://gaoqing.fm',
25 | description: $('meta[name="description"]').attr('content'),
26 | item: result,
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/lib/routes/pixiv/api/searchIllust.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const maskHeader = require('../constants').maskHeader;
3 |
4 | /**
5 | * 按时间排序搜索内容
6 | * @param {string} keyword 关键词
7 | * @param {string} token pixiv oauth token
8 | * @returns {Promise>}
9 | */
10 | module.exports = async function searchIllust(keyword, token) {
11 | return await axios({
12 | method: 'get',
13 | url: 'https://app-api.pixiv.net/v1/search/illust',
14 | headers: {
15 | ...maskHeader,
16 | Authorization: 'Bearer ' + token,
17 | },
18 | params: {
19 | word: keyword,
20 | search_target: 'partial_match_for_tags',
21 | sort: 'date_desc',
22 | filter: 'for_ios',
23 | },
24 | });
25 | };
26 |
--------------------------------------------------------------------------------
/lib/routes/laosiji/hot.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: 'http://www.laosiji.com/thread/hotList',
7 | });
8 |
9 | const data = response.data;
10 |
11 | ctx.state.data = {
12 | title: '老司机-24小时热门',
13 | link: 'http://www.laosiji.com/new_web/index.html',
14 | description: '老司机-24小时热门',
15 | item: data.map(({ title, description, id, imageInfo, createtime }) => ({
16 | title: title === '' ? description : title,
17 | link: `http://www.laosiji.com/thread/${id}.html`,
18 | description: `
${description}`,
19 | pubDate: new Date(createtime).toUTCString(),
20 | })),
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/lib/routes/laosiji/feed.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const parseDate = require('../../utils/date');
3 |
4 | module.exports = async (ctx) => {
5 | const response = await axios({
6 | method: 'post',
7 | url: 'http://www.laosiji.com/api/topic/feed/list',
8 | });
9 |
10 | const data = response.data.body.sns.list;
11 |
12 | ctx.state.data = {
13 | title: '老司机-首页',
14 | link: 'http://www.laosiji.com/new_web/index.html',
15 | description: '老司机-首页',
16 | item: data.map(({ title, resourceid, image, publishtime }) => ({
17 | title,
18 | link: `http://www.laosiji.com/thread/${resourceid}.html`,
19 | description: `
`,
20 | pubDate: parseDate(publishtime, 8),
21 | })),
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/lib/routes/douban/latest_book.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | const url = 'https://book.douban.com/latest';
5 |
6 | module.exports = async (ctx) => {
7 | const res = await axios.get(url);
8 | const $ = cheerio.load(res.data);
9 | const list = $('#content')
10 | .find('li')
11 | .get();
12 | ctx.state.data = {
13 | title: '豆瓣新书速递',
14 | link: url,
15 | item: list.map((item, index) => ({
16 | title: `${index < 20 ? '[虚构类]' : '[非虚构类]'}${$(item)
17 | .find('h2')
18 | .text()
19 | .trim()}`,
20 | link: $(item)
21 | .find('a')
22 | .first()
23 | .attr('href'),
24 | description: $(item).html(),
25 | })),
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/lib/routes/douban/ustop.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: 'https://api.douban.com/v2/movie/us_box',
7 | });
8 | const movieList = response.data.subjects;
9 |
10 | ctx.state.data = {
11 | title: '豆瓣电影北美票房榜',
12 | link: 'https://movie.douban.com/chart',
13 | item: movieList.map((item) => {
14 | item = item.subject;
15 | return {
16 | title: item.title,
17 | description: `标题:${item.title}
影片类型:${item.genres.join(' | ')}
评分:${item.rating.stars === '00' ? '无' : item.rating.average}
`,
18 | link: item.alt,
19 | };
20 | }),
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/lib/routes/sogou/doodles.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: 'http://help.sogou.com/logo/doodle_logo_list.html',
7 | });
8 |
9 | const data = response.data.split(/\r\n/).slice(1);
10 |
11 | ctx.state.data = {
12 | title: '搜狗特色LOGO',
13 | link: 'http://help.sogou.com/logo/',
14 | item: data.map((item) => {
15 | item = item.split(',');
16 |
17 | return {
18 | title: `${item[2]}-${item[5]}`,
19 | description: `
`,
20 | pubDate: new Date(item[5]).toUTCString(),
21 | link: item[7],
22 | guid: item[4],
23 | };
24 | }),
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/lib/routes/nhentai/other.js:
--------------------------------------------------------------------------------
1 | const { getSimple, getDetails } = require('./util');
2 |
3 | const supportedKeys = ['parody', 'character', 'tag', 'artist', 'group', 'language', 'category'];
4 |
5 | module.exports = async (ctx) => {
6 | const { keyword, key, mode } = ctx.params;
7 | const isSimple = mode !== 'detail';
8 |
9 | if (supportedKeys.indexOf(key) === -1) {
10 | ctx.state.data = {
11 | title: 'nHentai - unsupported',
12 | };
13 | return;
14 | }
15 |
16 | const simples = await getSimple(`https://nhentai.net/${key}/${keyword.toLowerCase().replace(' ', '-')}`);
17 |
18 | ctx.state.data = {
19 | title: `nHentai - ${key} - ${keyword}`,
20 | link: 'https://nhentai.net',
21 | description: 'hentai',
22 | item: isSimple ? simples : await getDetails(ctx.cache, simples),
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/lib/routes/facebook/article.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (url) => {
5 | const { data: html } = await axios.get(url);
6 | const $ = cheerio.load(html);
7 | const $ct = $($('#m_story_permalink_view').get(0)).find('div>div>div>div>p');
8 | $ct.find('br').replaceWith('\n');
9 | const content = $ct
10 | .map((i, p) => $(p).text())
11 | .toArray()
12 | .join('\n');
13 | const imgs = $ct
14 | .parent()
15 | .next()
16 | .find('img')
17 | .toArray()
18 | .map((img) => $(img).attr('src'));
19 | const { searchParams: q } = new URL(url);
20 | return { url: `https://www.facebook.com/story.php?story_fbid=${q.get('story_fbid')}&id=${q.get('id')}`, html, title: $($('h3 strong a').get(0)).text(), content, imgs };
21 | };
22 |
--------------------------------------------------------------------------------
/lib/routes/ciweimao/chapter.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const id = ctx.params.id;
6 |
7 | const response = await axios({
8 | method: 'get',
9 | url: `http://www.ciweimao.com/chapter-list/${id}/book_detail`,
10 | });
11 | const $ = cheerio.load(response.data);
12 |
13 | const name = $('.book-catalog>.hd>h3').text();
14 |
15 | const chapter_item = [];
16 |
17 | $('.book-chapter>.book-chapter-box>ul>li>a').each(function() {
18 | chapter_item.push({
19 | title: $(this).text(),
20 | link: $(this).attr('href'),
21 | });
22 | });
23 |
24 | ctx.state.data = {
25 | title: `刺猬猫 ${name}`,
26 | link: `http://www.ciweimao.com/book/${id}`,
27 | item: chapter_item,
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/lib/routes/coolbuy/newest.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: 'https://coolbuy.com/api/v1.4/product_preview/?order_by=-id&limit=20&page=0&offset=0',
7 | });
8 |
9 | const data = response.data.objects;
10 |
11 | ctx.state.data = {
12 | title: '玩物志-最新',
13 | link: 'https://coolbuy.com/',
14 | description: '值得买的未来生活',
15 | item: data.map((item) => ({
16 | title: item.title,
17 | link: item.visit_url,
18 | description: `
19 | 
20 | 
21 | ${item.summary}
22 | 价格: ${item.price}元
23 | `,
24 | })),
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/lib/routes/gov/city/nanjing/index.js:
--------------------------------------------------------------------------------
1 | const getContent = require('./getContent');
2 |
3 | const TOTAL_PAGE = 3;
4 |
5 | module.exports = async (homePage, category) => {
6 | let url = '';
7 |
8 | switch (category) {
9 | case 'news':
10 | url = `${homePage}/njxx/`;
11 | break;
12 | case 'department':
13 | url = `${homePage}/bmdt/`;
14 | break;
15 | case 'district':
16 | url = `${homePage}/gqdt/`;
17 | break;
18 | case 'livelihood':
19 | url = `${homePage}/mszx/`;
20 | break;
21 | default:
22 | console.log('URL pattern not matched');
23 | }
24 |
25 | if (url === '') {
26 | throw new Error('Nanjing, Cannot find page');
27 | }
28 |
29 | const responseData = await getContent(url, TOTAL_PAGE);
30 | return responseData;
31 | };
32 |
--------------------------------------------------------------------------------
/lib/routes/pixiv/api/getUserDetail.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const maskHeader = require('../constants').maskHeader;
3 |
4 | /**
5 | * pixiv 用户
6 | * @typedef {{user: {id: number, name: string, account: string, profile_image_urls: any, comment: string}, profile: any} userDetail
7 | */
8 |
9 | /**
10 | * 获取用户信息
11 | *
12 | * @param {string} user_id 目标用户id
13 | * @param {string} token pixiv oauth token
14 | * @returns {Promise>}
15 | */
16 | module.exports = async function getUserDetail(user_id, token) {
17 | return await axios({
18 | method: 'get',
19 | url: 'https://app-api.pixiv.net/v1/user/detail',
20 | headers: {
21 | ...maskHeader,
22 | Authorization: 'Bearer ' + token,
23 | },
24 | params: {
25 | user_id: user_id,
26 | },
27 | });
28 | };
29 |
--------------------------------------------------------------------------------
/docs/en/support/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar: auto
3 | ---
4 |
5 | # Support RSSHub
6 |
7 | RSSHub is open source and completely free under the MIT license. However, just like any other open source project, as the project grows, the hosting, development and maintenance requires funding support.
8 |
9 | You can support RSSHub via donations.
10 |
11 | ## Recurring Donation
12 |
13 | Recurring donors will be rewarded via express issue response, or even have your name displayed on our GitHub page and website.
14 |
15 | - Become a Backer or a Sponser on [Patreon](https://www.patreon.com/DIYgod)
16 | - Contact us directly: i#diygod.me
17 |
18 | ## One-time Donation
19 |
20 | We accept donations via the following ways:
21 |
22 | - [WeChat Pay](https://i.loli.net/2019/03/23/5c950ebbc373e.png)
23 | - [Alipay](https://i.loli.net/2019/03/23/5c950ebbc980e.png)
24 | - [Paypal](https://www.paypal.me/DIYgod)
25 |
--------------------------------------------------------------------------------
/lib/routes/jianshu/trending.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const util = require('./utils');
4 |
5 | module.exports = async (ctx) => {
6 | const link = `https://www.jianshu.com/trending/${ctx.params.timeframe}`;
7 | const response = await axios({
8 | method: 'get',
9 | url: link,
10 | headers: {
11 | Referer: link,
12 | },
13 | });
14 |
15 | const data = response.data;
16 |
17 | const $ = cheerio.load(data);
18 | const list = $('.note-list li').get();
19 |
20 | const result = await util.ProcessFeed(list, ctx.cache);
21 |
22 | ctx.state.data = {
23 | title: `简书 ${ctx.params.timeframe === 'weekly' ? '7' : '30'} 日热门`,
24 | link,
25 | description: `简书 ${ctx.params.timeframe === 'weekly' ? '7' : '30'} 日热门`,
26 | item: result,
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/lib/routes/uraaka-joshi/uraaka-joshi-user.js:
--------------------------------------------------------------------------------
1 | const buildData = require('../../utils/common-config');
2 |
3 | module.exports = async (ctx) => {
4 | const params = ctx.params;
5 | const link = `https://www.uraaka-joshi.com/users/${params.id}`;
6 | ctx.state.data = await buildData({
7 | link,
8 | url: link,
9 | title: `$('.top-profile-card-name-link').text() + '@${params.id} - 裏垢女子まとめ'`,
10 | item: {
11 | item: '.content-main .stream .stream-item',
12 | title: `$('.post-name').text() + '@${params.id} - 裏垢女子まとめ'`,
13 | link: `https://www.uraaka-joshi.com/users/${params.id}`,
14 | description: `$('.post .context').html()`,
15 | pubDate: `new Date($('.post-time').attr('datetime')).toUTCString()`,
16 | guid: `new Date($('.post-time').attr('datetime')).getTime()`,
17 | },
18 | });
19 | };
20 |
--------------------------------------------------------------------------------
/test/middleware/header.js:
--------------------------------------------------------------------------------
1 | const supertest = require('supertest');
2 | const { server } = require('../../lib/index');
3 | const request = supertest(server);
4 | const config = require('../../lib/config');
5 |
6 | afterAll(() => {
7 | server.close();
8 | });
9 |
10 | describe('header', () => {
11 | it(`header`, async () => {
12 | const response = await request.get('/test/1');
13 | expect(response.headers['access-control-allow-origin']).toBe('*');
14 | expect(response.headers['access-control-allow-headers']).toBe('Content-Type, Content-Length, Authorization, Accept, X-Requested-With');
15 | expect(response.headers['access-control-allow-methods']).toBe('GET');
16 | expect(response.headers['content-type']).toBe('application/xml; charset=utf-8');
17 | expect(response.headers['cache-control']).toBe(`max-age=${config.cacheExpire / 2}`);
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/lib/routes/eztv/imdb.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const { imdb_id } = ctx.params;
5 |
6 | const response = await axios({
7 | method: 'get',
8 | url: `https://eztv.ag/api/get-torrents?imdb_id=${imdb_id}`,
9 | headers: {
10 | Referer: 'https://eztv.ag',
11 | },
12 | });
13 |
14 | const { torrents } = response.data;
15 |
16 | ctx.state.data = {
17 | title: `EZTV's Torrents of IMBD ID: ${imdb_id}`,
18 | link: 'https://eztv.ag',
19 | description: `EZTV's Torrents of IMBD ID: ${imdb_id}`,
20 | item: torrents.map((item) => ({
21 | title: item.title,
22 | description: item.magnet_url,
23 | pubDate: new Date(item.date_released_unix * 1000).toUTCString(),
24 | link: item.torrent_url,
25 | })),
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/lib/routes/galgame/zdfx.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const host = 'https://bbs.zdfx.net/';
4 |
5 | module.exports = async (ctx) => {
6 | const response = await axios({
7 | method: 'get',
8 | url: host,
9 | });
10 | const $ = cheerio.load(response.data);
11 | const list = $('.slideother a');
12 |
13 | const process = list.map((index, item) => {
14 | const a = $(item);
15 | const img_tag = '.slideshow a:nth-child(' + (index + 1).toString() + ')';
16 |
17 | return {
18 | title: a.text(),
19 | description: $(img_tag).html(),
20 | link: host + a.attr('href'),
21 | };
22 | });
23 |
24 | ctx.state.data = {
25 | title: '终点分享',
26 | link: host,
27 | description: '终点分享最新汉化通知',
28 | item: process.get(),
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/lib/routes/weatheralarm/index.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const date = require('../../utils/date');
4 |
5 | module.exports = async (ctx) => {
6 | const alarmInfoURL = 'http://www.nmc.cn/f/alarm.html';
7 | const html = (await axios.get(alarmInfoURL)).data;
8 | const $ = cheerio.load(html);
9 | const alarmElements = $('.alarmlist > div:not(.pagination)').toArray();
10 |
11 | ctx.state.data = {
12 | title: '中央气象台全国气象预警',
13 | link: alarmInfoURL,
14 | item: alarmElements.map((el) => {
15 | const $el = $(el);
16 | const $aEl = $el.find('a');
17 | return {
18 | title: $aEl.text(),
19 | link: `http://www.nmc.cn${$aEl.attr('href')}`,
20 | pubDate: date($el.find('.date').text(), 8),
21 | };
22 | }),
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/lib/routes/ebb/index.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const lzstring = require('lz-string');
3 | module.exports = async (ctx) => {
4 | const url = 'https://ebb.io/_/anime_list';
5 | const response = await axios({
6 | method: 'get',
7 | url: url,
8 | headers: {
9 | Referer: url,
10 | },
11 | return: 'string',
12 | });
13 | const responseData = JSON.parse(lzstring.decompressFromUTF16(response.data));
14 | const result = responseData.map((item) => ({
15 | title: item.name_chi,
16 | link: `https://ebb.io/anime/${item.anime_id}x${item.season_id}`,
17 | description: `${item.season_title} - ${item.episode_title}`,
18 | guid: `${item.anime_id}-${item.season_id}-${item.episode_title}`,
19 | }));
20 | ctx.state.data = { title: 'ebb.io', link: 'https://ebb.io', description: '最新連載', item: result };
21 | };
22 |
--------------------------------------------------------------------------------
/lib/routes/jianshu/user.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const util = require('./utils');
4 |
5 | module.exports = async (ctx) => {
6 | const id = ctx.params.id;
7 |
8 | const response = await axios({
9 | method: 'get',
10 | url: `https://www.jianshu.com/u/${id}`,
11 | headers: {
12 | Referer: `https://www.jianshu.com/u/${id}`,
13 | },
14 | });
15 |
16 | const data = response.data;
17 |
18 | const $ = cheerio.load(data);
19 | const list = $('.note-list li').get();
20 |
21 | const result = await util.ProcessFeed(list, ctx.cache);
22 |
23 | ctx.state.data = {
24 | title: $('title').text(),
25 | link: `https://www.jianshu.com/u/${id}`,
26 | description: $('meta[name="description"]').attr('content') || $('title').text(),
27 | item: result,
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | # Use 4 spaces for the Python files
13 | [*.py]
14 | indent_size = 4
15 | max_line_length = 80
16 |
17 | # The JSON files contain newlines inconsistently
18 | [*.json]
19 | insert_final_newline = ignore
20 |
21 | # Minified JavaScript files shouldn't be changed
22 | [**.min.js]
23 | indent_style = ignore
24 | insert_final_newline = ignore
25 |
26 | # Makefiles always use tabs for indentation
27 | [Makefile]
28 | indent_style = tab
29 |
30 | # Batch files use tabs for indentation
31 | [*.bat]
32 | indent_style = tab
33 |
34 | [*.md]
35 | trim_trailing_whitespace = false
36 |
37 | # Matches the exact files either package.json or .travis.yml
38 | [{package.json,.travis.yml}]
39 | indent_size = 2
40 |
--------------------------------------------------------------------------------
/lib/routes/huxiu/tag.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const utils = require('./utils');
4 |
5 | module.exports = async (ctx) => {
6 | const { id } = ctx.params;
7 | const link = `https://www.huxiu.com/tags/${id}.html`;
8 | const response = await axios({
9 | method: 'get',
10 | url: link,
11 | headers: {
12 | Referer: link,
13 | },
14 | });
15 |
16 | const data = response.data;
17 | const $ = cheerio.load(data);
18 | const list = $('.related-article li a')
19 | .get()
20 | .map((e) => $(e).attr('href'));
21 |
22 | const items = await utils.ProcessFeed(list, ctx.cache);
23 |
24 | const info = `虎嗅 - ${$('.tag-title').text()}`;
25 | ctx.state.data = {
26 | title: info,
27 | link,
28 | description: info,
29 | item: items,
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/lib/routes/jianshu/collection.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const util = require('./utils');
4 |
5 | module.exports = async (ctx) => {
6 | const id = ctx.params.id;
7 |
8 | const response = await axios({
9 | method: 'get',
10 | url: `https://www.jianshu.com/c/${id}`,
11 | headers: {
12 | Referer: `https://www.jianshu.com/c/${id}`,
13 | },
14 | });
15 |
16 | const data = response.data;
17 |
18 | const $ = cheerio.load(data);
19 | const list = $('.note-list li').get();
20 |
21 | const result = await util.ProcessFeed(list, ctx.cache);
22 |
23 | ctx.state.data = {
24 | title: $('title').text(),
25 | link: `https://www.jianshu.com/c/${id}`,
26 | description: $('meta[name="description"]').attr('content') || $('title').text(),
27 | item: result,
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/lib/routes/tencent/qcloud/mlvb/changelog.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const url = 'https://cloud.tencent.com/document/product/454/7878';
6 |
7 | const res = await axios({
8 | method: 'get',
9 | url: url,
10 | });
11 | const $ = cheerio.load(res.data);
12 |
13 | const resultItem = [];
14 | $('.J-mainDetail #docArticleContent h3').each(function() {
15 | const item = {};
16 | item.title = $(this).text();
17 | item.description = $(this)
18 | .nextUntil('h3')
19 | .text();
20 | item.link = url;
21 | item.guid = $(this).text();
22 | resultItem.push(item);
23 | });
24 |
25 | ctx.state.data = {
26 | title: '腾讯移动直播 SDK 更新日志',
27 | link: url,
28 | item: resultItem,
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/lib/routes/universities/sjtu/gs/tzgg.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const util = require('./utils');
4 |
5 | module.exports = async (ctx) => {
6 | const type = ctx.params.type;
7 | let url = '';
8 | if (type) {
9 | url = `http://www.gs.sjtu.edu.cn/index/tzgg/${type}.htm`;
10 | } else {
11 | url = 'http://www.gs.sjtu.edu.cn/index/tzgg.htm';
12 | }
13 |
14 | const response = await axios({
15 | method: 'get',
16 | url: url,
17 | });
18 |
19 | const data = response.data;
20 |
21 | const $ = cheerio.load(data);
22 | const list = $('.comm li')
23 | .slice(0, 10)
24 | .get();
25 |
26 | const result = await util.ProcessFeed(list, ctx.cache, url);
27 |
28 | ctx.state.data = {
29 | title: $('title').text(),
30 | link: url,
31 | item: result,
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/lib/utils/puppeteer.js:
--------------------------------------------------------------------------------
1 | const config = require('../config');
2 | const puppeteer = require('puppeteer');
3 |
4 | const options = {
5 | args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-infobars', '--window-position=0,0', '--ignore-certifcate-errors', '--ignore-certifcate-errors-spki-list', `--user-agent=${config.ua}`],
6 | headless: true,
7 | ignoreHTTPSErrors: true,
8 | userDataDir: './tmp',
9 | };
10 |
11 | module.exports = async () => {
12 | let browser;
13 | if (config.puppeteerWSEndpoint) {
14 | browser = await puppeteer.connect({
15 | browserWSEndpoint: config.puppeteerWSEndpoint,
16 | });
17 | } else {
18 | browser = await puppeteer.launch(options);
19 | }
20 | setTimeout(async () => {
21 | if ((await browser.process()).signalCode) {
22 | browser.close();
23 | }
24 | }, 5000);
25 |
26 | return browser;
27 | };
28 |
--------------------------------------------------------------------------------
/lib/routes/express/express.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const company = ctx.params.company;
5 | const number = ctx.params.number;
6 |
7 | const response = await axios({
8 | method: 'get',
9 | url: `https://www.kuaidi100.com/query?type=${company}&postid=${number}`,
10 | headers: {
11 | Referer: 'https://www.kuaidi100.com',
12 | },
13 | });
14 |
15 | const data = response.data.data;
16 |
17 | ctx.state.data = {
18 | title: `快递 ${company}-${number}`,
19 | link: 'https://www.kuaidi100.com',
20 | description: `快递 ${company}-${number}`,
21 | item: data.map((item) => ({
22 | title: item.context,
23 | description: item.context,
24 | pubDate: new Date(item.time || item.ftime).toUTCString(),
25 | link: item.context,
26 | })),
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/lib/routes/geekpark/breakingnews.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const url = 'https://digestapit.geeks.vc/v1/articles';
5 | const link = 'https://www.geekpark.net/breakingnews';
6 |
7 | const response = await axios({
8 | method: 'get',
9 | url,
10 | });
11 | const data = response.data.data.reverse();
12 |
13 | ctx.state.data = {
14 | title: '全球快讯 | 极客公园',
15 | description:
16 | '极客公园聚焦互联网领域,跟踪最新的科技新闻动态,关注极具创新精神的科技产品。目前涵盖前沿科技、游戏、手机评测、硬件测评、出行方式、共享经济、人工智能等全方位的科技生活内容。现有前沿社、挖App、深度报道、极客养成指南等多个内容栏目。',
17 | link,
18 | item: data.map(({ url, edited_title, summary, published, _id }) => ({
19 | title: edited_title,
20 | description: summary,
21 | link: url,
22 | pubDate: new Date(published).toUTCString(),
23 | guid: _id,
24 | })),
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/lib/routes/bilibili/page.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const aid = ctx.params.aid;
5 | const response = await axios({
6 | method: 'get',
7 | url: `https://api.bilibili.com/x/web-interface/view?aid=${aid}`,
8 | headers: {
9 | Referer: `https://www.bilibili.com/video/av${aid}`,
10 | },
11 | });
12 |
13 | const { title: name, pages: data } = response.data.data;
14 |
15 | ctx.state.data = {
16 | title: `视频 ${name} 的选集列表`,
17 | link: `https://www.bilibili.com/video/av${aid}`,
18 | description: `视频 ${name} 的视频选集列表`,
19 | item: data.map((item) => ({
20 | title: item.part,
21 | description: `${item.part} - ${name}`,
22 | pubDate: new Date().toUTCString(),
23 | link: `https://www.bilibili.com/video/av${aid}?p=${item.page}`,
24 | })),
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/lib/routes/baidu/doodles.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const response = await axios({
6 | method: 'get',
7 | url: 'http://logo.baidu.com/main/show_data/0/0/0/0',
8 | });
9 |
10 | const $ = cheerio.load(response.data);
11 |
12 | ctx.state.data = {
13 | title: '百度趣画',
14 | link: 'http://logo.baidu.com/',
15 | item: $('.col')
16 | .map((index, item) => {
17 | item = $(item);
18 |
19 | return {
20 | title: `${item.find('.title').text()}-${item.find('.date').text()}`,
21 | description: `
`,
22 | link: item.find('.more a').attr('href'),
23 | };
24 | })
25 | .get(),
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/lib/routes/bangumi/subject/index.js:
--------------------------------------------------------------------------------
1 | const getComments = require('./comments.js');
2 | const getFromAPI = require('./offcial-subject-api.js');
3 |
4 | const getEps = require('./ep.js');
5 |
6 | module.exports = async (ctx) => {
7 | const id = ctx.params.id;
8 | let response;
9 | if (ctx.params.type) {
10 | switch (ctx.params.type) {
11 | case 'comments':
12 | response = await getComments(id, Number(ctx.request.query.minLength) || 0);
13 | break;
14 | case 'blogs':
15 | response = await getFromAPI('blog')(id);
16 | break;
17 | case 'topics':
18 | response = await getFromAPI('topic')(id);
19 | break;
20 | default:
21 | throw Error(`暂不支持对${ctx.params.type}的订阅`);
22 | }
23 | } else {
24 | response = await getEps(id);
25 | }
26 | ctx.state.data = response;
27 | };
28 |
--------------------------------------------------------------------------------
/lib/routes/juejin/books.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: 'https://xiaoce-timeline-api-ms.juejin.im/v1/getListByLastTime?uid=&client_id=&token=&src=web&alias=&pageNum=1',
7 | });
8 |
9 | const data = response.data.d;
10 |
11 | ctx.state.data = {
12 | title: '掘金小册',
13 | link: 'https://juejin.im/books',
14 | item: data.map(({ title, id, img, desc, createdAt, price }) => ({
15 | title,
16 | link: `https://juejin.im/book/${id}`,
17 | description: `
18 | 
19 | ${title}
20 | ${desc}
21 | 价格: ${price}元
22 | `,
23 | pubDate: new Date(createdAt).toUTCString(),
24 | guid: id,
25 | })),
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/lib/routes/douyu/room.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const id = ctx.params.id;
5 |
6 | const response = await axios({
7 | method: 'get',
8 | url: `http://open.douyucdn.cn/api/RoomApi/room/${id}`,
9 | headers: {
10 | Referer: `https://www.douyu.com/${id}`,
11 | },
12 | });
13 |
14 | const data = response.data.data;
15 |
16 | let item;
17 | if (data.online !== 0) {
18 | item = [
19 | {
20 | title: `开播: ${data.room_name}`,
21 | pubDate: new Date(data.start_time).toUTCString(),
22 | guid: data.start_time,
23 | link: `https://www.douyu.com/${id}`,
24 | },
25 | ];
26 | }
27 |
28 | ctx.state.data = {
29 | title: `${data.owner_name}的斗鱼直播间`,
30 | link: `https://www.douyu.com/${id}`,
31 | item: item,
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/lib/routes/github/repos.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const config = require('../../config');
3 |
4 | module.exports = async (ctx) => {
5 | const user = ctx.params.user;
6 |
7 | const response = await axios({
8 | method: 'get',
9 | url: `https://api.github.com/users/${user}/repos`,
10 | params: {
11 | sort: 'created',
12 | access_token: config.github.access_token,
13 | },
14 | });
15 | const data = response.data;
16 | ctx.state.data = {
17 | title: `${user}'s GitHub repositories`,
18 | link: `https://github.com/${user}`,
19 | item:
20 | data &&
21 | data.map((item) => ({
22 | title: item.name,
23 | description: item.description || 'No description',
24 | pubDate: new Date(item.created_at).toUTCString(),
25 | link: item.html_url,
26 | })),
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/lib/routes/kingkong/room.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const id = ctx.params.id;
5 | const url = `https://www.kingkong.com.tw/${id}`;
6 |
7 | const api = `https://api-kk.lv-play.com/webapi/v1/room/info?room_id=${id}`;
8 | const response = await axios({
9 | method: 'get',
10 | url: api,
11 | });
12 |
13 | let item;
14 | const name = response.data.data.live_info.nickname;
15 | if (response.data.data.live_info.live_status === 1) {
16 | item = [
17 | {
18 | title: name + '开播了',
19 | link: url,
20 | guid: response.data.data.live_info.live_id,
21 | description: response.data.data.live_info.describe,
22 | },
23 | ];
24 | }
25 |
26 | ctx.state.data = {
27 | title: `${name}的kingkong直播`,
28 | link: url,
29 | item: item,
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/lib/routes/huxiu/search.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const utils = require('./utils');
4 |
5 | module.exports = async (ctx) => {
6 | const { keyword } = ctx.params;
7 | const link = `https://www.huxiu.com/search.html?s=${encodeURIComponent(keyword)}&sort=dateline:desc`;
8 |
9 | const response = await axios({
10 | method: 'get',
11 | url: link,
12 | headers: {
13 | Referer: link,
14 | },
15 | });
16 |
17 | const data = response.data;
18 | const $ = cheerio.load(data);
19 |
20 | const list = $('.search-wrap-list-ul > li > h2 > a')
21 | .get()
22 | .map((e) => $(e).attr('href'));
23 |
24 | const items = await utils.ProcessFeed(list, ctx.cache);
25 |
26 | const info = `虎嗅网 - ${keyword}`;
27 | ctx.state.data = {
28 | title: info,
29 | link,
30 | description: info,
31 | item: items,
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/lib/routes/huya/live.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const id = ctx.params.id;
6 | const url = `https://www.huya.com/${id}`;
7 | const response = await axios({
8 | method: 'get',
9 | url: url,
10 | });
11 |
12 | const $ = cheerio.load(response.data);
13 | const timestamp = parseInt(response.data.match(/"startTime":"?(\d+)?/)[1]) * 1000;
14 |
15 | let item;
16 | if (response.data.match(/"isOn":(\w{4})/)[1] === 'true') {
17 | item = [
18 | {
19 | title: $('#J_roomTitle').text(),
20 | guid: timestamp,
21 | pubDate: new Date(timestamp).toUTCString(),
22 | link: url,
23 | },
24 | ];
25 | }
26 |
27 | ctx.state.data = {
28 | title: `${$('.host-name').text()}的虎牙直播`,
29 | link: url,
30 | item: item,
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/lib/routes/tophub/index.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const id = ctx.params.id;
6 |
7 | const link = `https://tophub.today/n/${id}`;
8 | const response = await axios.get(link);
9 | const $ = cheerio.load(response.data);
10 |
11 | const title = $('div.Xc-ec-L.b-L')
12 | .text()
13 | .trim();
14 |
15 | const out = $('div.Zd-p-Sc > div:nth-child(1) tr')
16 | .map(function() {
17 | const info = {
18 | title: $(this)
19 | .find('td.al a')
20 | .text(),
21 | link: $(this)
22 | .find('td.al a')
23 | .attr('href'),
24 | };
25 | return info;
26 | })
27 | .get();
28 |
29 | ctx.state.data = {
30 | title: title,
31 | link: link,
32 | item: out,
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/lib/routes/xiaoheihe/discount.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: `https://api.xiaoheihe.cn/game/all_recommend/games/?show_type=discount&offset=0&limit=30&heybox_id=12777814&imei=867252032615972&os_type=Android&os_version=9&version=1.1.55&_time=1551803757&hkey=6977cde54ab859fbb98757d3e6b953d9`,
7 | });
8 | const data = response.data.result.list;
9 |
10 | ctx.state.data = {
11 | title: `小黑盒游戏打折情况`,
12 | link: `https://xiaoheihe.cn/games/index`,
13 | item: data.map((item) => ({
14 | title: item.game_name,
15 | description: `原价¥${item.heybox_price.original_coin / 1000}元,现价¥${item.heybox_price.cost_coin / 1000}元,折扣力度${item.heybox_price.discount}%OFF`,
16 | pubDate: ('' + item.price.deadline_date).replace(' 截止', ''),
17 | link: item.game_img,
18 | })),
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/lib/routes/blogs/jingwei_link.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const url = 'https://jingwei.link/';
6 | const response = await axios({
7 | method: 'get',
8 | url,
9 | });
10 | const $ = cheerio.load(response.data);
11 | const resultItem = $("article[class='article-item']")
12 | .map((index, elem) => {
13 | elem = $(elem);
14 |
15 | const $esction = elem.find('section[class="post-preview"]');
16 |
17 | return {
18 | title: $esction.find('h2').text(),
19 | description: $esction.find('h3').text(),
20 | link: $esction.find('a').attr('href'),
21 | author: '敬维',
22 | };
23 | })
24 | .get();
25 |
26 | ctx.state.data = {
27 | title: '敬维-以认真的态度做完美的事情',
28 | link: url,
29 | item: resultItem,
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/lib/routes/mobdata/report.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const date = require('../../utils/date');
3 |
4 | module.exports = async (ctx) => {
5 | const apiUrl = 'http://admin.mob.com/api/mobdata/report/list';
6 | const pageUrl = 'http://mobdata.mob.com/mobdata/report';
7 |
8 | const resp = await axios({
9 | method: 'post',
10 | url: apiUrl,
11 | headers: {
12 | Referer: pageUrl,
13 | 'Content-Type': 'application/json',
14 | },
15 | });
16 |
17 | const list = resp.data.list;
18 | const items = list.map((item) => ({
19 | title: item.title,
20 | description: `${item.desc}
查看报告`,
21 | pubDate: date(item.created_at),
22 | link: item.report_path,
23 | }));
24 | ctx.state.data = {
25 | title: 'MobData分析报告',
26 | link: pageUrl,
27 | description: 'MobData分析报告',
28 | item: items,
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/lib/routes/douyin/utils.js:
--------------------------------------------------------------------------------
1 | const formatVideoUrl = (url) => url.replace('/play/', '/playwm/');
2 |
3 | const formatItem = (item) => {
4 | const originVideoUrl = item.video.play_addr.url_list[0];
5 | const formatedVideoUrl = formatVideoUrl(originVideoUrl);
6 |
7 | return {
8 | title: item.desc,
9 | description: `
10 | ${item.desc}
11 |
12 |
13 |
`,
21 | link: formatedVideoUrl,
22 | guid: originVideoUrl,
23 | };
24 | };
25 |
26 | module.exports = {
27 | formatItem,
28 | };
29 |
--------------------------------------------------------------------------------
/lib/routes/douban/bookstore.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const link = 'https://market.douban.com/book/';
5 | const response = await axios({
6 | method: 'get',
7 | url: 'https://market.douban.com/api/freyr/books?page=1&page_size=20&type=book',
8 | headers: {
9 | Referer: link,
10 | },
11 | });
12 |
13 | const data = response.data.data;
14 |
15 | ctx.state.data = {
16 | title: '豆瓣书店',
17 | link,
18 | description: '在豆瓣书店,遇见美好·書生活',
19 | item: data.map(({ title, url, price, square_pic, rectangle_pic, desc }) => ({
20 | title,
21 | link: url,
22 | description: `
23 | 
24 | 
25 | ${desc}
26 | 价格: ${price}元
27 | `,
28 | })),
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/lib/routes/douban/playing.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const city = ctx.params.city;
5 | const score = parseFloat(ctx.params.score, 10);
6 | const response = await axios({
7 | method: 'get',
8 | url: 'https://api.douban.com/v2/movie/in_theaters',
9 | params: { city },
10 | });
11 | const movieList = score ? response.data.subjects.filter((item) => item.rating.average >= score) : response.data.subjects;
12 |
13 | ctx.state.data = {
14 | title: `${city ? city : ''}正在上映的${score ? `超过 ${score} 分的` : ''}电影`,
15 | link: 'https://movie.douban.com/cinema/nowplaying/',
16 | item: movieList.map((item) => ({
17 | title: item.title,
18 | description: `标题:${item.title}
影片类型:${item.genres.join(' | ')}
评分:${item.rating.average}
`,
19 | link: item.alt,
20 | })),
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/lib/routes/juejin/posts.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const util = require('./utils');
3 |
4 | module.exports = async (ctx) => {
5 | const id = ctx.params.id;
6 |
7 | const response = await axios({
8 | method: 'get',
9 | url: `https://timeline-merger-ms.juejin.im/v1/get_entry_by_self?src=web&targetUid=${id}&type=post&order=createdAt`,
10 | headers: {
11 | Host: 'timeline-merger-ms.juejin.im',
12 | Origin: 'https://juejin.im',
13 | Referer: `https://juejin.im/user/${id}/posts`,
14 | },
15 | });
16 | const data = response.data.d.entrylist;
17 | const username = data && data[0] && data[0].user && data[0].user.username;
18 | const resultItems = await util.ProcessFeed(data, ctx.cache);
19 |
20 | ctx.state.data = {
21 | title: `掘金专栏-${username}`,
22 | link: `https://juejin.im/user/${id}/posts`,
23 | description: `掘金专栏-${username}`,
24 | item: resultItems,
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/lib/routes/bangumi/subject/ep.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 |
3 | module.exports = async (subjectID) => {
4 | const url = `https://api.bgm.tv/subject/${subjectID}?responseGroup=large`;
5 | const epsInfo = (await axios.get(url)).data;
6 | const activeEps = [];
7 |
8 | epsInfo.eps.forEach((e) => {
9 | if (e.status === 'Air') {
10 | activeEps.push(e);
11 | }
12 | });
13 |
14 | return {
15 | title: `${epsInfo.name_cn}`,
16 | link: `https://bgm.tv/subject/${subjectID}`,
17 | description: epsInfo.summary,
18 | item: activeEps.reverse().map((e) => ({
19 | title: `ep.${e.sort} ${e.name_cn}`,
20 | description: `
${e.desc.replace(/\n+/g, '
')}
`,
21 | pubDate: new Date(e.airdate).toUTCString(),
22 | guid: e.id,
23 | link: e.url,
24 | })),
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/lib/routes/huxiu/author.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const utils = require('./utils');
4 |
5 | module.exports = async (ctx) => {
6 | const { id } = ctx.params;
7 | const link = `https://www.huxiu.com/member/${id}.html`;
8 | const response = await axios({
9 | method: 'get',
10 | url: link,
11 | headers: {
12 | Referer: link,
13 | },
14 | });
15 |
16 | const $ = cheerio.load(response.data);
17 | const author = $('.user-name')
18 | .text()
19 | .trim();
20 |
21 | const list = $('.message-box > .mod-art > a')
22 | .slice(0, 10)
23 | .get()
24 | .map((e) => $(e).attr('href'));
25 |
26 | const items = await utils.ProcessFeed(list, ctx.cache);
27 |
28 | const authorInfo = `虎嗅网 - ${author}`;
29 | ctx.state.data = {
30 | title: authorInfo,
31 | link,
32 | description: authorInfo,
33 | item: items,
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/lib/routes/fir/update.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const id = ctx.params.id;
5 | const APIUrl = `https://download.fir.im/${id}`;
6 |
7 | const webUrl = `https://fir.im/${id}`;
8 |
9 | const res = await axios({
10 | method: 'get',
11 | url: APIUrl,
12 | headers: {
13 | Referer: webUrl,
14 | },
15 | });
16 |
17 | const data = res.data;
18 |
19 | const title = data.app.name + ' 更新';
20 |
21 | const item = {};
22 | item.title = data.app.name + ' ' + data.app.releases.master.version + '(' + data.app.releases.master.build + ')';
23 | item.description = data.app.releases.master.changelog;
24 | item.link = `https://fir.im/x9zu?release_id=${data.app.releases.master.id}`;
25 | item.pubDate = new Date(data.app.releases.master.created_at * 1000).toUTCString();
26 | ctx.state.data = {
27 | title: title,
28 | link: webUrl,
29 | item: [item],
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/lib/routes/steam/news.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const { appids } = ctx.params;
6 | const { data: html } = await axios.get(`https://store.steampowered.com/news/`, {
7 | params: { appids },
8 | });
9 | const $ = cheerio.load(html);
10 |
11 | ctx.state.data = {
12 | title: $('.pageheader').text(),
13 | link: `https://store.steampowered.com/news/?appids=${appids}`,
14 | item: $('#news div[id]')
15 | .toArray()
16 | .map((a) => {
17 | const $el = $(a);
18 | return {
19 | title: $el.find('.posttitle').text(),
20 | link: $el.find('.posttitle a').attr('href'),
21 | author: $el.find('.feed').text(),
22 | description: $el.find('.body').html(),
23 | };
24 | })
25 | .filter((it) => it.title),
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/lib/routes/9to5/utils.js:
--------------------------------------------------------------------------------
1 | const cheerio = require('cheerio');
2 |
3 | const ProcessFeed = (data) => {
4 | const $ = cheerio.load(data);
5 | const content = $('div[itemprop="articleBody"]');
6 |
7 | const cover = $('meta[property="og:image"]');
8 | if (cover.length > 0) {
9 | $(`
`).insertBefore(content[0].firstChild);
10 | }
11 |
12 | // remove useless DOMs
13 | content
14 | .find('hr')
15 | .nextAll()
16 | .remove();
17 |
18 | content.find('hr, ins.adsbygoogle, script').each((i, e) => {
19 | $(e).remove();
20 | });
21 |
22 | content.find('div').each((i, e) => {
23 | if ($(e)[0].attribs.class) {
24 | const classes = $(e)[0].attribs.class;
25 | if (classes.match(/st\w{8}\sst\w{8}/g)) {
26 | $(e).remove();
27 | }
28 | }
29 | });
30 |
31 | return content.html();
32 | };
33 |
34 | module.exports = {
35 | ProcessFeed,
36 | };
37 |
--------------------------------------------------------------------------------
/lib/routes/natgeo/dailyphoto.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const today = new Date();
5 | const year = today.getFullYear();
6 | const month = today.getMonth() + 1;
7 |
8 | const api = `https://www.nationalgeographic.com/content/photography/en_US/photo-of-the-day/_jcr_content/.gallery.${year}-${month}.json`;
9 | const response = await axios.get(api);
10 | const items = response.data.items;
11 |
12 | const out = items.slice(0, 10).map((item) => {
13 | const info = {
14 | title: item.altText,
15 | author: item.credit,
16 | link: item['full-path-url'],
17 | description: `
` + item.caption,
18 | };
19 | return info;
20 | });
21 |
22 | ctx.state.data = {
23 | title: 'Photo of the Day',
24 | link: 'https://www.nationalgeographic.com/photography/photo-of-the-day/',
25 | item: out,
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/lib/routes/testerhome/newest.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const response = await axios({
6 | method: 'get',
7 | url: 'https://testerhome.com/topics/last',
8 | });
9 |
10 | const $ = cheerio.load(response.data);
11 | const resultItem = $('.item-list .topic')
12 | .map((index, elem) => {
13 | elem = $(elem);
14 | const $link = elem.find('.title a');
15 | const title = $link.attr('title');
16 |
17 | return {
18 | title,
19 | link: `https://testerhome.com${$link.attr('href')}`,
20 | description: title,
21 | };
22 | })
23 | .get();
24 |
25 | ctx.state.data = {
26 | title: 'TesterHome-最新发布',
27 | link: 'https://testerhome.com/topics/last',
28 | description: 'TesterHome软件测试社区,人气最旺的软件测试技术门户,提供软件测试社区交流,测试沙龙。',
29 | item: resultItem,
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/lib/routes/vocus/publication.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const { id } = ctx.params;
5 |
6 | const { _id, title, abstract } = (await axios.get(`https://api.sosreader.com/api/publication/${id}`)).data;
7 | const { articles } = (await axios.get(`https://api.sosreader.com/api/articles?publicationId=${_id}&status=2&num=10&page=1`)).data;
8 | const items = await Promise.all(
9 | articles.map(async (item) => ({
10 | title: item.title,
11 | author: item.user.fullname,
12 | description: (await axios.get(`https://api.sosreader.com/api/article/${item._id}`)).data.article.content,
13 | pubDate: new Date(item.updatedAt).toUTCString(),
14 | link: `https://vocus.cc/${id}/${item._id}`,
15 | }))
16 | );
17 |
18 | ctx.state.data = {
19 | title: `${title} - 方格子`,
20 | link: `https://vocus.cc/${id}/home`,
21 | description: abstract,
22 | item: items,
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/lib/routes/zhihu/bookstore/newest.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: 'https://api.zhihu.com/books/features/new',
7 | });
8 |
9 | const data = response.data.data;
10 |
11 | ctx.state.data = {
12 | title: '知乎书店-新书抢鲜',
13 | link: 'https://www.zhihu.com/pub/features/new',
14 | item: data.map((item) => {
15 | const authors = item.authors.map((author) => author.name).join('、');
16 |
17 | return {
18 | title: item.title,
19 | link: item.url,
20 | description: `
21 | })
22 | ${item.title}
23 | 作者: ${authors}
24 | ${item.description}
25 | 价格: ${item.promotion.price / 100}元
26 | `,
27 | };
28 | }),
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/lib/routes/anigamer/new_anime.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const { new_anime } = await axios.get('https://api.gamer.com.tw/mobile_app/anime/v1/index.php').then((r) => r.data);
5 | ctx.state.data = {
6 | title: '動畫瘋最後更新',
7 | link: 'https://ani.gamer.com.tw/',
8 | item: new_anime.date.map((item) => {
9 | const date = new Date();
10 | const month = item.info.split('/')[0] - 1;
11 | const day = item.info.split(' ')[0].split('/')[1];
12 | const pubdatetemp = new Date(date.getFullYear(), month, day);
13 | const pubdate = pubdatetemp > date ? new Date(date.getFullYear() - 1, month, day) : pubdatetemp;
14 |
15 | return {
16 | title: item.title,
17 | description: item.info,
18 | link: `https://ani.gamer.com.tw/animeRef.php?sn=${item.anime_sn}`,
19 | pubDate: pubdate.toUTCString(),
20 | };
21 | }),
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/lib/routes/bilibili/mallIP.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const id = ctx.params.id;
5 |
6 | const response = await axios({
7 | method: 'get',
8 | url: `https://mall.bilibili.com/mall-c/search/category?keyword=&filters=&priceFlow=&priceCeil=&sortType=recommend&sortOrder=&pageIndex=1&state=&type=ip&id=${id}`,
9 | headers: {
10 | Referer: `https://mall.bilibili.com/list.html?ip=${id}`,
11 | },
12 | });
13 |
14 | const data = response.data.data;
15 |
16 | ctx.state.data = {
17 | title: `${data.pageTitle} - 会员购`,
18 | link: `https://mall.bilibili.com/list.html?ip=${id}`,
19 | item: data.list.map((item) => ({
20 | title: item.name,
21 | description: `${item.name}
${item.brief}
¥${item.price}
`,
22 | link: `https://mall.bilibili.com/detail.html?itemsId=${item.itemsId}`,
23 | })),
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/lib/routes/patchwork.kernel.org/comments.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cache = require('./cache');
3 | const he = require('he');
4 |
5 | module.exports = async (ctx) => {
6 | const id = ctx.params.id;
7 |
8 | const name = await cache.getPatchnameFromID(ctx, id);
9 |
10 | const host = `https://patchwork.kernel.org/patch/${id}/`;
11 | const url = `https://patchwork.kernel.org/api/patches/${id}/comments/`;
12 |
13 | const response = await axios({
14 | method: 'get',
15 | url,
16 | params: {
17 | order: '-date',
18 | },
19 | });
20 | const data = response.data;
21 |
22 | ctx.state.data = {
23 | title: `${name} - Comments`,
24 | link: host,
25 | item: data.map((item) => ({
26 | title: item.subject,
27 | description: he.escape(he.escape(item.content)).replace(/\n/g, '
'),
28 | pubDate: new Date(item.date).toUTCString(),
29 | link: item.web_url,
30 | })),
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report_en.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🐛 Bug Report
3 | about: Submit discovered bugs
4 | ---
5 |
6 |
11 |
12 | ### Involved route
13 |
14 | ### What is expected?
15 |
16 | ### What is actually happening?
17 |
18 | ### Self-deployed information
19 |
20 |
24 |
25 | | Env | Value |
26 | | ------------------ | ------------- |
27 | | OS | |
28 | | Node version | |
29 | | if Docker, version | |
30 |
31 | ### Additional info (logs errors etc)
32 |
--------------------------------------------------------------------------------
/lib/utils/logger.js:
--------------------------------------------------------------------------------
1 | const winston = require('winston');
2 | const config = require('../config');
3 |
4 | const logger = winston.createLogger({
5 | level: config.loggerLevel,
6 | format: winston.format.json(),
7 | transports: [
8 | //
9 | // - Write to all logs with level `info` and below to `combined.log`
10 | // - Write all logs error (and below) to `error.log`.
11 | //
12 | new winston.transports.File({
13 | filename: 'logs/error.log',
14 | level: 'error',
15 | }),
16 | new winston.transports.File({ filename: 'logs/combined.log' }),
17 | ],
18 | });
19 |
20 | //
21 | // If we're not in production then log to the `console` with the format:
22 | // `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
23 | //
24 | logger.add(
25 | new winston.transports.Console({
26 | format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
27 | silent: process.env.NODE_ENV === 'test',
28 | })
29 | );
30 |
31 | module.exports = logger;
32 |
--------------------------------------------------------------------------------
/lib/routes/mi/crowdfunding.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'post',
6 | url: 'http://api.m.mi.com/v1/microwd/home',
7 | headers: {
8 | 'Mishop-Client-Id': '180100031055',
9 | 'User-Agent': 'MiShop/4.3.68 (iPhone; iOS 12.0.1; Scale/3.00)',
10 | 'IOS-App-Version': '4.3.68',
11 | 'IOS-Version': 'system=12.0.1&device=iPhone10,3',
12 | },
13 | });
14 | let list = [];
15 | response.data.data.list &&
16 | response.data.data.list.forEach((item) => {
17 | list = list.concat(item.items);
18 | });
19 |
20 | ctx.state.data = {
21 | title: '小米众筹',
22 | link: '',
23 | item:
24 | list &&
25 | list.map((item) => ({
26 | title: item.product_name,
27 | description: `
价格:${item.product_price}元`,
28 | })),
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 |
5 | service.rsshub:
6 | build:
7 | dockerfile: ./Dockerfile
8 | context: .
9 | args:
10 | # 是否在构建过程中使用淘宝NPM源进行加速
11 | USE_CHINA_NPM_REGISTRY: 0
12 | # 是否在构建过程中跳过安装 Chromium, 参见 https://docs.rsshub.app/install/#docker-compose-deployment
13 | # whether to skip puppeteer Chromium installation or not, refer to https://docs.rsshub.app/en/install/#docker-compose-deployment
14 | PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
15 | restart: always
16 | image: rsshub
17 | ports:
18 | - "1200:1200"
19 | environment:
20 | NODE_ENV: production
21 | CACHE_TYPE: redis
22 | REDIS_URL: 'redis://db.redis:6379/'
23 | volumes:
24 | - ./lib/config/app.json:/app/lib/app.json
25 | - ./lib/config/config.js:/app/lib/config.js
26 | depends_on:
27 | - db.redis
28 |
29 | db.redis:
30 | image: redis:5.0.4-alpine
31 | volumes:
32 | - redis-data:/data
33 | expose:
34 | - "6379"
35 |
36 | volumes:
37 | redis-data:
38 |
--------------------------------------------------------------------------------
/lib/routes/dockerhub/build.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const { owner, image, tag = 'latest' } = ctx.params;
5 |
6 | const namespace = `${owner}/${image}`;
7 |
8 | const link = `https://hub.docker.com/r/${namespace}`;
9 |
10 | const data = await axios.get(`https://hub.docker.com/v2/repositories/${namespace}/tags/?page_size=5`);
11 | const metadata = await axios.get(`https://hub.docker.com/v2/repositories/${namespace}`);
12 |
13 | const list = data.data.results.filter((a) => a.name === tag);
14 |
15 | const out = list.map((item) => ({
16 | title: `${namespace}:${tag} was built. ${(item.images[0].size / 1000000).toFixed(2)} MB`,
17 | link,
18 | author: owner,
19 | pubDate: new Date(item.last_updated).toUTCString(),
20 | guid: item.last_updated,
21 | }));
22 |
23 | ctx.state.data = {
24 | title: `${namespace}:${tag} build history`,
25 | description: metadata.description,
26 | link,
27 | item: out,
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/lib/routes/luogu/daily.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const id = ctx.params.id || 92685;
6 |
7 | const link = `https://www.luogu.org/discuss/show/${id}`;
8 | const response = await axios.get(link);
9 | const $ = cheerio.load(response.data);
10 | const title = $('#app-body-new div h1').text();
11 |
12 | const out = $('div.am-comment-main > div > p')
13 | .slice(0, 10)
14 | .map(function() {
15 | const info = {
16 | title:
17 | $(this)
18 | .find('strong')
19 | .text() || $(this).text(),
20 | description: $(this).html(),
21 | link: $(this)
22 | .find('a')
23 | .attr('href'),
24 | };
25 | return info;
26 | })
27 | .get();
28 |
29 | ctx.state.data = {
30 | title: title,
31 | link: link,
32 | item: out,
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/lib/routes/blogread/newest.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const url = 'http://blogread.cn/news/newest.php';
6 | const response = await axios({
7 | method: 'get',
8 | url,
9 | });
10 | const $ = cheerio.load(response.data);
11 | const resultItem = $('.media')
12 | .map((index, elem) => {
13 | elem = $(elem);
14 | const $link = elem.find('dt a');
15 |
16 | return {
17 | title: $link.text(),
18 | description: elem
19 | .find('dd')
20 | .eq(0)
21 | .text(),
22 | link: $link.attr('href'),
23 | author: elem
24 | .find('.small a')
25 | .eq(0)
26 | .text(),
27 | };
28 | })
29 | .get();
30 |
31 | ctx.state.data = {
32 | title: '技术头条',
33 | link: url,
34 | item: resultItem,
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/lib/routes/kirara/news.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios.get('https://kirarafantasia.com/wp-json/wp/v2/posts');
5 |
6 | const posts = response.data || [];
7 |
8 | ctx.state.data = {
9 | title: 'NEWS|きららファンタジア 公式サイト',
10 | link: 'https://kirarafantasia.com/news/',
11 | description: '「まんがタイムきらら」の人気キャラクターたちが、RPGの世界に大集合!あなたの毎日が「きらら」でいっぱいに! #きらファン',
12 | item: posts.map((post) => {
13 | const image = `
`;
14 |
15 | const html = image;
16 |
17 | const [y, m, d] = post.date.match(/\d+/g);
18 | const date = new Date(y, m - 1, d);
19 |
20 | return {
21 | title: post.title,
22 | link: post.url,
23 | pubDate: date.toUTCString(),
24 | published: date.toISOString(),
25 | description: html,
26 | content: { html },
27 | };
28 | }),
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/lib/routes/sspai/shortcutsGallery.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const {
5 | data: { data: categories },
6 | } = await axios.get('https://shortcuts.sspai.com/api/v1/user/workflow/all/get');
7 |
8 | const items = [];
9 |
10 | for (const category of categories) {
11 | for (const shortcut of category.data || []) {
12 | items.push({
13 | title: shortcut.name,
14 | description: `作者:${shortcut.author_id}
${decodeURIComponent((shortcut.description || '').replace(/\+/g, '%20'))}`,
15 | pubDate: new Date(shortcut.utime * 1000).toUTCString(),
16 | guid: shortcut.id,
17 | link: shortcut.url,
18 | });
19 | }
20 | }
21 |
22 | ctx.state.data = {
23 | title: 'Shortcuts Gallery - 少数派',
24 | link: 'https://shortcuts.sspai.com/#/main/workflow',
25 | description: 'Shortcuts Gallery - 少数派',
26 | item: items,
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/lib/routes/wenku8/chapter.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const iconv = require('iconv-lite');
4 |
5 | module.exports = async (ctx) => {
6 | const id = ctx.params.id;
7 | const index = parseInt(id / 1000);
8 |
9 | const response = await axios({
10 | method: 'get',
11 | url: `https://www.wenku8.net/novel/${index}/${id}/index.htm`,
12 | responseType: 'arraybuffer',
13 | });
14 |
15 | const responseHtml = iconv.decode(response.data, 'gbk');
16 |
17 | const $ = cheerio.load(responseHtml);
18 |
19 | const name = $('#title').text();
20 |
21 | const chapter_item = [];
22 |
23 | $('.ccss>a').each(function() {
24 | chapter_item.push({
25 | title: $(this).text(),
26 | link: `https://www.wenku8.net/novel/${index}/${id}/` + $(this).attr('href'),
27 | });
28 | });
29 |
30 | ctx.state.data = {
31 | title: `轻小说文库 ${name}`,
32 | link: `https://www.wenku8.net/book/${id}.htm`,
33 | item: chapter_item,
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/lib/routes/bilibili/coin.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cache = require('./cache');
3 |
4 | module.exports = async (ctx) => {
5 | const uid = ctx.params.uid;
6 |
7 | const name = await cache.getUsernameFromUID(ctx, uid);
8 |
9 | const response = await axios({
10 | method: 'get',
11 | url: `https://api.bilibili.com/x/space/coin/video?vmid=${uid}&jsonp=jsonp`,
12 | headers: {
13 | Referer: `https://space.bilibili.com/${uid}/`,
14 | },
15 | });
16 | const data = response.data.data;
17 |
18 | ctx.state.data = {
19 | title: `${name} 的 bilibili 投币视频`,
20 | link: `https://space.bilibili.com/${uid}`,
21 | description: `${name} 的 bilibili 投币视频`,
22 | item: data.map((item) => ({
23 | title: item.title,
24 | description: `${item.desc}
`,
25 | pubDate: new Date(item.time * 1000).toUTCString(),
26 | link: `https://www.bilibili.com/video/av${item.aid}`,
27 | })),
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/lib/routes/anime1/search.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const { keyword } = ctx.params;
6 | const $ = await axios.get(`https://anime1.me/?s=${encodeURIComponent(keyword)}`).then((r) => cheerio.load(r.data));
7 | const title = $('.page-title')
8 | .text()
9 | .trim();
10 | ctx.state.data = {
11 | title,
12 | link: `https://anime1.me/?s=${keyword}`,
13 | description: title,
14 | item: $('article:has(.cat-links)')
15 | .toArray()
16 | .map((art) => {
17 | const $el = $(art);
18 | const title = $el.find('.entry-title a').text();
19 | return {
20 | title: $el.find('.entry-title a').text(),
21 | link: $el.find('.entry-title a').attr('href'),
22 | description: title,
23 | pubDate: new Date($el.find('time').attr('datetime')).toUTCString(),
24 | };
25 | }),
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/lib/routes/bangumi/group/topic.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const resolve_url = require('url').resolve;
4 |
5 | const base_url = 'https://bgm.tv/';
6 |
7 | module.exports = async (ctx) => {
8 | const groupID = ctx.params.id;
9 | const link = 'https://bgm.tv/group/' + groupID + '/forum';
10 | const html = (await axios.get(link)).data;
11 | const $ = cheerio.load(html);
12 | const title = 'Bangumi - ' + $('.SecondaryNavTitle').text();
13 |
14 | ctx.state.data = {
15 | title: `${title}`,
16 | link: link,
17 | item: $('.topic_list .topic')
18 | .map((_, elem) => ({
19 | link: resolve_url(base_url, $('.subject a', elem).attr('href')),
20 | title: $('.subject a', elem).attr('title'),
21 | pubDate: new Date($('.lastpost .time', elem).text()).toUTCString(),
22 | description: 'Author: ' + $('.author a', elem).text() + '
' + 'Replay: ' + $('.posts', elem).text(),
23 | }))
24 | .get(),
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/lib/routes/v2ex/topics.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const type = ctx.params.type;
5 |
6 | const response = await axios({
7 | method: 'get',
8 | url: `https://www.v2ex.com/api/topics/${type}.json`,
9 | });
10 |
11 | const data = response.data;
12 |
13 | let title;
14 | if (type === 'hot') {
15 | title = '最热主题';
16 | } else if (type === 'latest') {
17 | title = '最新主题';
18 | }
19 |
20 | ctx.state.data = {
21 | title: `V2EX-${title}`,
22 | link: 'https://www.v2ex.com/',
23 | description: `V2EX-${title}`,
24 | item: data.map((item) => ({
25 | title: item.title,
26 | description: `${item.member.username}: ${item.content_rendered}`,
27 | content: { text: item.content, html: item.content_rendered },
28 | pubDate: new Date(item.created * 1000).toUTCString(),
29 | guid: item.id,
30 | link: item.url,
31 | author: item.member.username,
32 | })),
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/lib/routes/wegene/column.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const type = ctx.params.type;
5 | let name;
6 | if (type === 'all') {
7 | name = '全部项目';
8 | } else {
9 | name = '专业版';
10 | }
11 | const category = ctx.params.category;
12 |
13 | const link = 'https://www.wegene.com/crowdsourcing';
14 | const api = `https://www.wegene.com/crowdsourcing/ajax/get_list/?&htmlspecialchars_decode=true&type=${type}&page=1&limit=10&category=${category}&sort=NEW`;
15 |
16 | const response = await axios.get(api);
17 | const data = response.data.rsm.list;
18 |
19 | ctx.state.data = {
20 | title: `${name}的${category}最近更新-WeGene`,
21 | link: link,
22 | item: data.map((item) => ({
23 | title: item.title,
24 | link: `https://www.wegene.com/crowdsourcing/details/${item.id}`,
25 | date: new Date(item.add_time * 1000).toUTCString(),
26 | author: item.user.user_name,
27 | description: item.description,
28 | })),
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/lib/routes/appstore/xianmian.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const {
5 | data: { objects: data },
6 | } = await axios.get('http://app.so/api/v5/appso/discount/?platform=web&limit=10');
7 |
8 | ctx.state.data = {
9 | title: '每日精品限免 / 促销应用',
10 | link: 'http://app.so/xianmian/',
11 | description: '鲜面连线 by AppSo:每日精品限免 / 促销应用',
12 | item: data.map((item) => ({
13 | title: `「${item.discount_info[0].discounted_price === '0.00' ? '免费' : '降价'}」${item.app.name}`,
14 | description: `
15 |
16 |
17 | 原价:¥${item.discount_info[0].original_price} -> 现价:¥${item.discount_info[0].discounted_price}
18 |
19 | 平台:${item.app.download_link[0].device}
20 |
21 | ${item.content}
22 | `,
23 | pubDate: new Date(item.updated_at * 1000).toUTCString(),
24 | link: item.app.download_link[0].link,
25 | })),
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/lib/routes/github/pulls.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const config = require('../../config');
3 | const md = require('markdown-it')({
4 | html: true,
5 | });
6 |
7 | module.exports = async (ctx) => {
8 | const user = ctx.params.user;
9 | const repo = ctx.params.repo;
10 |
11 | const host = `https://github.com/${user}/${repo}/pulls`;
12 | const url = `https://api.github.com/repos/${user}/${repo}/pulls`;
13 |
14 | const response = await axios({
15 | method: 'get',
16 | url,
17 | params: {
18 | sort: 'created',
19 | access_token: config.github.access_token,
20 | },
21 | });
22 | const data = response.data;
23 |
24 | ctx.state.data = {
25 | title: `${user}/${repo} Pull requests`,
26 | link: host,
27 | item: data.map((item) => ({
28 | title: item.title,
29 | description: md.render(item.body) || 'No description',
30 | pubDate: new Date(item.created_at).toUTCString(),
31 | link: `${host}/${item.number}`,
32 | })),
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/lib/routes/mzitu/post.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const { id } = ctx.params;
5 |
6 | const contentUrl = `http://adr.meizitu.net/wp-json/wp/v2/i?id=${id}`;
7 | const postUrl = `http://adr.meizitu.net/wp-json/wp/v2/posts/${id}`;
8 | const link = `http://www.mzitu.com/${id}`;
9 |
10 | const contentResponse = await axios({
11 | method: 'get',
12 | url: contentUrl,
13 | });
14 | const content = contentResponse.data.content.split(',');
15 |
16 | const postResponse = await axios({
17 | method: 'get',
18 | url: postUrl,
19 | });
20 | const { title } = postResponse.data;
21 |
22 | ctx.state.data = {
23 | title: title,
24 | link,
25 | item: content.map((url, index) => {
26 | url = url.replace(/"/g, '');
27 |
28 | return {
29 | title: `${title}(${index + 1})`,
30 | description: `
`,
31 | link: url,
32 | };
33 | }),
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/lib/routes/pigtails/index.js:
--------------------------------------------------------------------------------
1 | const cheerio = require('cheerio');
2 | const axios = require('../../utils/axios');
3 |
4 | const base_url = 'https://pigtails.moe';
5 | module.exports = async (ctx) => {
6 | const response = await axios({
7 | method: 'get',
8 | url: base_url,
9 | headers: {
10 | Referer: base_url,
11 | },
12 | });
13 |
14 | const $ = cheerio.load(response.data);
15 |
16 | const items = [];
17 | $('.posts > .post > a').each((idx, item) => {
18 | const $item = $(item);
19 | const img_url = $('.thumb', item)
20 | .attr('style')
21 | .split(')')[0]
22 | .split('(')[1];
23 | items.push({
24 | title: $item.text(),
25 | description: `
`,
26 | link: `${base_url}${$item.attr('href')}`,
27 | });
28 | });
29 |
30 | ctx.state.data = {
31 | title: 'Awesome Pigtails',
32 | description: 'Share awesome pigtails.',
33 | link: base_url,
34 | item: items,
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/lib/routes/tingshuitz/xian.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const iconv = require('iconv-lite');
4 |
5 | module.exports = async (ctx) => {
6 | const url = 'http://www.xazls.com/tsgg/index.htm';
7 | const response = await axios.get(url, {
8 | responseType: 'arraybuffer',
9 | });
10 |
11 | const data = response.data;
12 | const $ = cheerio.load(iconv.decode(data, 'gb2312'));
13 | const list = $('#body > div.M > div.AboutUsDetail > ul > li');
14 |
15 | ctx.state.data = {
16 | title: $('title').text() || '停水通知 - 西安市自来水有限公司',
17 | link: 'http://www.xazls.com/tsgg/index.htm',
18 | item: list
19 | .map((_, el) => {
20 | const item = $(el);
21 |
22 | const a = item.find('a');
23 | return {
24 | title: a.text().trim(),
25 | description: item.text().trim(),
26 | link: 'http://www.xazls.com' + a.attr('href'),
27 | };
28 | })
29 | .get(),
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/lib/routes/hexo/yilia.js:
--------------------------------------------------------------------------------
1 | const cheerio = require('cheerio');
2 | const axios = require('../../utils/axios');
3 |
4 | module.exports = async (ctx) => {
5 | const url = `http://${ctx.params.url}`;
6 | const res = await axios.get(url);
7 | const $ = cheerio.load(res.data);
8 | const authorInfo = $('.left-col').find('#header');
9 | const title = authorInfo.find('.header-author').text();
10 | const subtitle = authorInfo.find('.header-subtitle').text();
11 | const articleNodeList = $('article');
12 | const articles = Array.from(articleNodeList).map((article) => {
13 | const each = $(article);
14 | const titleEl = each.find('.article-title');
15 |
16 | return {
17 | title: titleEl.text(),
18 | link: encodeURI(`${url}${titleEl.attr('href')}`),
19 | description: each.find('.article-entry').text(),
20 | pubDate: each.find('time').attr('datetime'),
21 | };
22 | });
23 |
24 | ctx.state.data = {
25 | title,
26 | link: url,
27 | description: subtitle,
28 | item: articles,
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/lib/routes/anime1/anime.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const { time, name } = ctx.params;
6 | const $ = await axios.get(`https://anime1.me/category/${encodeURIComponent(time)}/${encodeURIComponent(name)}`).then((r) => cheerio.load(r.data));
7 | const title = $('.page-title')
8 | .text()
9 | .trim();
10 | ctx.state.data = {
11 | title,
12 | link: `https://anime1.me/category/${time}/${name}`,
13 | description: title,
14 | item: $('article')
15 | .toArray()
16 | .map((art) => {
17 | const $el = $(art);
18 | const title = $el.find('.entry-title a').text();
19 | return {
20 | title: $el.find('.entry-title a').text(),
21 | link: $el.find('.entry-title a').attr('href'),
22 | description: title,
23 | pubDate: new Date($el.find('time').attr('datetime')).toUTCString(),
24 | };
25 | }),
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/lib/routes/instapaper/person.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const name = ctx.params.name;
6 | const link = `https://www.instapaper.com/p/${name}`;
7 |
8 | const response = await axios.get(link);
9 | const $ = cheerio.load(response.data);
10 |
11 | const out = $('article.article_item.article_browse')
12 | .slice(0, 10)
13 | .map(function() {
14 | const info = {
15 | title: $(this)
16 | .find('div.js_title_row.title_row a')
17 | .attr('title'),
18 | link: $(this)
19 | .find('div.js_title_row.title_row a')
20 | .attr('href'),
21 | description: $(this)
22 | .find('div.article_preview')
23 | .text(),
24 | };
25 | return info;
26 | })
27 | .get();
28 |
29 | ctx.state.data = {
30 | title: `${name}-Instapaper分享`,
31 | link: link,
32 | item: out,
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 DIYgod
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/routes/bilibili/reply.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cache = require('./cache');
3 |
4 | module.exports = async (ctx) => {
5 | const aid = ctx.params.aid;
6 | const name = await cache.getVideoNameFromAid(ctx, aid);
7 |
8 | const response = await axios({
9 | method: 'get',
10 | url: `https://api.bilibili.com/x/v2/reply?type=1&oid=${aid}&sort=0`,
11 | headers: {
12 | Referer: `https://www.bilibili.com/video/av${aid}`,
13 | },
14 | });
15 |
16 | const data = response.data.data.replies;
17 |
18 | ctx.state.data = {
19 | title: `${name} 的 评论`,
20 | link: `https://www.bilibili.com/video/av${aid}`,
21 | description: `${name} 的评论`,
22 | item: data.map((item) => ({
23 | title: `${item.member.uname} : ${item.content.message}`,
24 | description: `#${item.floor}
${item.member.uname} : ${item.content.message}`,
25 | pubDate: new Date(item.ctime * 1000).toUTCString(),
26 | link: `https://www.bilibili.com/video/av${aid}/#reply${item.rpid}`,
27 | })),
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/lib/routes/jpmorganchase/research.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | const url = 'https://www.jpmorganchase.com';
5 | module.exports = async (ctx) => {
6 | const response = await axios({
7 | method: 'get',
8 | url: `${url}/corporate/institute/research.htm`,
9 | });
10 |
11 | const title = 'All Reports';
12 | const $ = cheerio.load(response.data);
13 |
14 | const items = $('.globalphilsect')
15 | .map((index, item) => {
16 | item = $(item);
17 | return {
18 | title: item.find('.chaseanalytics-track-link').text(),
19 | link: `${url}${item.find('.chaseanalytics-track-link').attr('href')}`,
20 | description: item.find('.lead-in + p').text(),
21 | pubDate: item.find('.lead-in').text(),
22 | };
23 | })
24 | .get();
25 | ctx.state.data = {
26 | title: `${title} - JPMorgan Chase Institute`,
27 | link: url,
28 | description: `${title} - JPMorgan Chase Institute`,
29 | item: items,
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/lib/routes/itjuzi/merge.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: 'https://www.itjuzi.com/api/index/merge',
7 | });
8 |
9 | const data = response.data.data;
10 |
11 | ctx.state.data = {
12 | title: 'IT桔子-并购事件',
13 | link: 'https://www.itjuzi.com/',
14 | item: data.map((item) => {
15 | const party = item.party.map((item) => item.name || item.invst_name).join('、');
16 |
17 | return {
18 | title: `${item.name}-${item.slogan}`,
19 | link: `https://www.itjuzi.com/merger/${item.id}`,
20 | description: `
21 | 
22 | ${item.name}
23 | ${item.slogan}
24 | 股权占比: ${item.ratio} / 金额: ${item.money} / ${item.time}
25 | 并购方: ${party}
26 | `,
27 | pubDate: new Date(item.time).toUTCString(),
28 | guid: item.id,
29 | };
30 | }),
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/lib/routes/itjuzi/invest.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: 'https://www.itjuzi.com/api/index/invse',
7 | });
8 |
9 | const data = response.data.data;
10 |
11 | ctx.state.data = {
12 | title: 'IT桔子-投融资事件',
13 | link: 'https://www.itjuzi.com/',
14 | item: data.map((item) => {
15 | const invest = item.invst.map((item) => item.name).join('、');
16 |
17 | return {
18 | title: `${item.name} / ${item.round} / ${item.money}`,
19 | link: `https://www.itjuzi.com/company/${item.invse_com_id}`,
20 | description: `
21 | 
22 | ${item.name}
23 | ${item.slogan}
24 | ${item.round} / ${item.money} / ${item.time}
25 | 投资方: ${invest}
26 | `,
27 | pubDate: new Date(item.time).toUTCString(),
28 | guid: item.id,
29 | };
30 | }),
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/lib/routes/jike/topicSquare.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const common = require('./common');
3 |
4 | module.exports = async (ctx) => {
5 | const id = ctx.params.id;
6 |
7 | const response = await axios({
8 | method: 'post',
9 | url: 'https://app.jike.ruguoapp.com/1.0/squarePosts/list',
10 | headers: {
11 | Referer: `https://m.okjike.com/topics/${id}`,
12 | 'App-Version': '4.1.0',
13 | },
14 | data: {
15 | loadMoreKey: null,
16 | topicId: id,
17 | limit: 20,
18 | },
19 | });
20 |
21 | const data = response.data.data;
22 |
23 | if (common.emptyResponseCheck(ctx, data)) {
24 | return;
25 | }
26 |
27 | const topic = data[0].topic;
28 |
29 | ctx.state.data = {
30 | title: `${topic.content} - 即刻主题广场`,
31 | link: `https://web.okjike.com/topic/${id}/user`,
32 | description: topic.content,
33 | image: topic.squarePicture.picUrl || topic.squarePicture.middlePicUrl || topic.squarePicture.thumbnailUrl,
34 | item: common.topicDataHanding(data),
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/lib/routes/jinritoutiao/keyword.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios');
2 |
3 | module.exports = async (ctx) => {
4 | const keyword = ctx.params.keyword;
5 |
6 | const response = await axios({
7 | method: 'get',
8 | url: `https://www.toutiao.com/api/search/content/?offset=0&format=json&keyword=${encodeURIComponent(keyword)}&autoload=true&count=20&cur_tab=1&from=search_tab`,
9 | headers: {
10 | Referer: `https://www.toutiao.com/search/?keyword=${encodeURIComponent(keyword)}`,
11 | },
12 | });
13 | let data = response.data.data;
14 | data = data.filter(function(item) {
15 | return !item.cell_type;
16 | });
17 |
18 | ctx.state.data = {
19 | title: `今日头条: ${keyword}`,
20 | link: `https://www.toutiao.com/search/?keyword=${keyword}`,
21 | description: `${keyword}`,
22 | item: data.map((item) => ({
23 | title: `${item.media_name}: ${item.title}`,
24 | description: `${item.abstract}`,
25 | pubDate: `${new Date(parseInt(item.create_time) * 1000)}`,
26 | link: `${item.article_url}`,
27 | })),
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/lib/routes/tanwu/products.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: 'https://api.tanwuapp.com/iOS/2.0.0/products?rentType=0&pageSize=1000&pageNum=1&clientType=web:h5',
7 | });
8 |
9 | const data = response.data.details.allProductList.data;
10 |
11 | ctx.state.data = {
12 | title: '探物',
13 | link: 'https://m.tanwuapp.com',
14 | description: '探物是一个科技数码产品租赁平台,旨在把市面上最新、最酷、最实用的科技产品带去给用户,传播科技便捷生活、共享丰富生活的价值理念。',
15 | item: data.map(({ productName, productImageSrc, productRent, rentUnit, productId, appraiseAvgScore }) => ({
16 | title: productName,
17 | link: `https://m.tanwuapp.com/?source=mobile#/productDetails?productId=${productId}`,
18 | description: `
19 | 
20 | ${productName}
21 | 价格: ¥${productRent} / ${rentUnit}
22 | 评分: ${appraiseAvgScore}
23 | `,
24 | guid: productId,
25 | })),
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/lib/routes/tencent/video/playlist.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const { id } = ctx.params;
6 | const link = `https://v.qq.com/detail/${id[0]}/${id}.html`;
7 | const page = await axios.get(link);
8 | const $ = cheerio.load(page.data);
9 | const episodes = await axios.get(`https://s.video.qq.com/get_playsource?id=${id}&plat=2&type=4&data_type=2&video_type=3&range=1&plname=qq&otype=json&num_mod_cnt=20&callback=a&_t=${Date.now()}`);
10 | const playList = JSON.parse(episodes.data.slice(2, -1)).PlaylistItem.videoPlayList;
11 | const items = playList.map((video) => ({
12 | title: video.title,
13 | link: video.playUrl,
14 | description: `
15 |
16 | `,
17 | }));
18 |
19 | ctx.state.data = {
20 | title: $('title').text(),
21 | link,
22 | description: $('meta[name=description]').attr('content'),
23 | item: items,
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/lib/routes/fitchratings/site.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const type = ctx.params.type;
6 |
7 | const link = `https://www.fitchratings.com/site/${type}`;
8 | const listData = await axios.get(link);
9 | const $list = cheerio.load(listData.data);
10 | ctx.state.data = {
11 | title: `${type} - 惠誉评级`,
12 | link,
13 | item: await Promise.all(
14 | $list('div.card-text-container')
15 | .slice(0, 10)
16 | .map(async (_, el) => {
17 | const $el = $list(el);
18 | const $a = $el.find('h4 a');
19 |
20 | const href = $a.attr('href');
21 | const title = $a.text();
22 | const description = $el.find('div p').text();
23 |
24 | return {
25 | title: title,
26 | description,
27 | link: href,
28 | };
29 | })
30 | .get()
31 | ),
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/lib/routes/mi/youpin/utils.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | generateData: (data) =>
3 | data.reduce((acc, item) => {
4 | let goodsList = [];
5 |
6 | if (item.data.result) {
7 | goodsList = item.data.result.goods_list;
8 | } else if (item.data.tabs) {
9 | goodsList = item.data.tabs.reduce((acc, tab) => {
10 | if (tab.result) {
11 | tab.result.goods_list.forEach((goods) => goods);
12 | }
13 |
14 | return acc;
15 | }, []);
16 | }
17 |
18 | goodsList.forEach((goods) => {
19 | const img = goods.pic_url;
20 | acc.push({
21 | title: goods.name,
22 | description: `
${goods.summary}
价格:${goods.market_price / 100}元`,
23 | link: goods.jump_url,
24 | pubDate: new Date(goods.first_release_time * 1000).toUTCString(),
25 | });
26 | });
27 |
28 | return acc;
29 | }, []),
30 | };
31 |
--------------------------------------------------------------------------------
/lib/routes/universities/njust/cwc/index.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const url = require('url');
4 |
5 | const host = 'http://cwc.njust.edu.cn/';
6 |
7 | const map = new Map([[1, { title: '南京理工大学财务处 -- 新闻及通知', id: 'wp_news_w2' }], [2, { title: '南京理工大学财务处 -- 办事指南', id: 'wp_news_w3' }]]);
8 |
9 | module.exports = async (ctx) => {
10 | const type = Number.parseInt(ctx.params.type);
11 | const response = await axios.get(host);
12 |
13 | const $ = cheerio.load(response.data);
14 |
15 | const id = map.get(type).id;
16 |
17 | const items = $(`#${id} tr tr`)
18 | .slice(0, 10)
19 | .map((_, elem) => {
20 | const a = $('td:first-child > a', elem);
21 | return {
22 | link: url.resolve(host, a.attr('href')),
23 | title: a.attr('title'),
24 | pubDate: new Date($('td:nth-child(2)', elem).text()).toUTCString(),
25 | };
26 | })
27 | .get();
28 |
29 | ctx.state.data = {
30 | link: host,
31 | title: map.get(type).title,
32 | item: items,
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/lib/routes/chouti/index.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | let { subject = '' } = ctx.params;
5 | const host = 'https://m.chouti.com';
6 |
7 | if (subject === 'hot') {
8 | subject = '';
9 | }
10 |
11 | const response = await axios({
12 | method: 'get',
13 | url: `${host}/m/link/more.do?type=hot&subject=${subject}&limit=recent`,
14 | headers: {
15 | Referer: host,
16 | },
17 | });
18 |
19 | const resultItem = response.data.result.data.items.map((item) => ({
20 | title: item.title,
21 | author: item.nick,
22 | description: `${item.title}

评论`,
23 | link: item.url,
24 | pubDate: new Date(item.createtime / 1000).toUTCString(),
25 | }));
26 |
27 | ctx.state.data = {
28 | title: '抽屉新热榜',
29 | description: '抽屉新热榜,汇聚每日搞笑段子、热门图片、有趣新闻。它将微博、门户、社区、bbs、社交网站等海量内容聚合在一起,通过用户推荐生成最热榜单。看抽屉新热榜,每日热门、有趣资讯尽收眼底。',
30 | link: host,
31 | item: resultItem,
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/lib/routes/meipai/utils.js:
--------------------------------------------------------------------------------
1 | const cheerio = require('cheerio');
2 | const url = require('url');
3 |
4 | const ProcessFeed = async (list) => {
5 | const host = 'https://www.meipai.com';
6 |
7 | return await Promise.all(
8 | list.map(async (item) => {
9 | const $ = cheerio.load(item);
10 |
11 | const $title = $('.detail-cover-title');
12 | const $desciption = $('.feed-description');
13 |
14 | // 详情页面的地址(视频页面地址)
15 | const itemUrl = url.resolve(host, $desciption.attr('href'));
16 |
17 | // RSS内容(美拍提供了友好的网页版视频展示)
18 | const imgSrc = $('.feed-v-wrap img').attr('src');
19 | const text = $desciption.text() + `
`;
20 |
21 | // 列表上提取到的信息
22 | return {
23 | title: $title.text(),
24 | description: text,
25 | link: itemUrl,
26 | author: $('.feed-name').text(),
27 | guid: itemUrl,
28 | };
29 | })
30 | );
31 | };
32 |
33 | module.exports = {
34 | ProcessFeed,
35 | };
36 |
--------------------------------------------------------------------------------
/lib/routes/universities/tju/sse/_article.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../../utils/axios'); // get web content
2 | const cheerio = require('cheerio'); // html parser
3 |
4 | const domain = 'http://sse.tongji.edu.cn';
5 |
6 | module.exports = async function get_article(url) {
7 | if (/^\/.*$/.test(url)) {
8 | url = domain + url;
9 | }
10 | const response = await axios({
11 | method: 'get',
12 | url: url,
13 | });
14 | const data = response.data;
15 |
16 | const $ = cheerio.load(data);
17 | const title = $('div.view-title').text();
18 | const pub_date_raw = $('div.view-info').text();
19 | const pub_date_parse = /\d{2,4}-\d{1,2}-\d{1,2}/.exec(pub_date_raw);
20 | const pub_date = new Date(pub_date_parse[0]).toUTCString();
21 | const content = $('div.view-cnt')
22 | .html()
23 | .replace(//g, '-->')
25 | .replace(/href="\//g, 'href="' + domain + '/');
26 |
27 | const item = {
28 | title: title,
29 | pubDate: pub_date,
30 | link: url,
31 | description: content,
32 | };
33 | return item;
34 | };
35 |
--------------------------------------------------------------------------------
/lib/routes/xueqiu/favorite.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const id = ctx.params.id;
5 |
6 | const res1 = await axios({
7 | method: 'get',
8 | url: 'https://xueqiu.com/',
9 | });
10 | const token = res1.headers['set-cookie'].find((s) => s.startsWith('xq_a_token=')).split(';')[0];
11 |
12 | const res2 = await axios({
13 | method: 'get',
14 | url: 'https://xueqiu.com/favorites.json',
15 | params: {
16 | userid: id,
17 | },
18 | headers: {
19 | Cookie: token,
20 | Referer: `https://xueqiu.com/u/${id}`,
21 | },
22 | });
23 | const data = res2.data.list;
24 |
25 | ctx.state.data = {
26 | title: `ID: ${id} 的雪球收藏动态`,
27 | link: `https://xueqiu.com/u/${id}`,
28 | description: `ID: ${id} 的雪球收藏动态`,
29 | item: data.map((item) => ({
30 | title: item.title,
31 | description: item.description,
32 | pubDate: new Date(item.created_at).toUTCString(),
33 | link: `https://xueqiu.com${item.target}`,
34 | })),
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/lib/routes/universities/cuc/yz.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const url = require('url');
4 | const iconv = require('iconv-lite');
5 |
6 | const host = 'http://yz.cuc.edu.cn/';
7 |
8 | /* 研究生招生网通知公告*/
9 |
10 | module.exports = async (ctx) => {
11 | const response = await axios({
12 | method: 'get',
13 | url: host,
14 | responseType: 'arraybuffer',
15 | });
16 |
17 | const responseHtml = iconv.decode(response.data, 'gbk');
18 | const $ = cheerio.load(responseHtml);
19 |
20 | const notice = $('#notice_area').children('.notice_block_top');
21 | const content = notice.children('.notice_content1').children();
22 |
23 | const items = content
24 | .map((_, elem) => {
25 | const a = $('a', elem);
26 | return {
27 | link: url.resolve(host, a.attr('href')),
28 | title: a.text(),
29 | };
30 | })
31 | .get();
32 |
33 | ctx.state.data = {
34 | title: $('title').text(),
35 | link: host,
36 | description: '中国传媒大学研究生招生网 通知公告',
37 | item: items,
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/lib/routes/bilibili/mallNew.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: 'https://mall.bilibili.com/mall-c/home/calendar/list?page=new&startWeekNO=0&limitWeekSize=3',
7 | headers: {
8 | Referer: 'https://mall.bilibili.com/date.html?page=new',
9 | },
10 | });
11 |
12 | const data = response.data.data.vo.weeks;
13 | const days = [...data[0].days, ...data[1].days];
14 | const items = [];
15 | days.forEach((day) => {
16 | items.push(...day.presaleItems);
17 | });
18 |
19 | ctx.state.data = {
20 | title: '会员购新品上架',
21 | link: 'https://mall.bilibili.com/date.html?page=new',
22 | item: items.map((item) => ({
23 | title: item.name,
24 | description: `${item.name}
${item.priceDesc ? `${item.pricePrefix}${item.priceSymbol}${item.priceDesc[0]}` : ''}

APP 内打开`,
27 | link: item.itemUrlForH5,
28 | })),
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/lib/routes/tencent/wechat/announce.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const { data: htmlString } = await axios({
6 | method: 'get',
7 | url: 'https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncementlist&lang=zh_CN',
8 | });
9 |
10 | const $ = cheerio.load(htmlString);
11 | const announceList = [];
12 |
13 | $('.mp_news_list > .mp_news_item').each(function() {
14 | const $item = $(this);
15 | const $link = $item.find('a');
16 | const time = $item.find('.read_more').text();
17 | const title = $item.find('strong').text();
18 |
19 | announceList.push({
20 | title: `${time} ${title}`,
21 | link: `https://mp.weixin.qq.com${$link.attr('href')}`,
22 | description: title,
23 | pubDate: new Date(time).toUTCString(),
24 | });
25 | });
26 |
27 | ctx.state.data = {
28 | title: '微信公众平台-系统公告栏目',
29 | link: 'https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncementlist&lang=zh_CN&token=',
30 | item: announceList,
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/lib/routes/universities/scnu/library.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const res = await axios({
6 | method: 'get',
7 | url: 'https://lib.scnu.edu.cn/news/zuixingonggao',
8 | headers: {
9 | Referer: 'https://lib.scnu.edu.cn',
10 | },
11 | });
12 | const $ = cheerio.load(res.data);
13 | const list = $('.article-list')
14 | .find('li')
15 | .slice(0, 10);
16 |
17 | ctx.state.data = {
18 | title: $('title').text(),
19 | link: 'https://lib.scnu.edu.cn/news/zuixingonggao',
20 | description: '华南师范大学图书馆 - 通知公告',
21 | item:
22 | list &&
23 | list
24 | .map((index, item) => {
25 | item = $(item);
26 | return {
27 | title: item.find('a').text(),
28 | pubDate: new Date(item.find('.clock').text()).toUTCString(),
29 | link: item.find('a').attr('href'),
30 | };
31 | })
32 | .get(),
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RSSHub",
3 | "description": "万物皆可 RSS",
4 | "repository": "https://github.com/DIYgod/RSSHub",
5 | "website": "https://docs.rsshub.app/",
6 | "logo": "https://i.imgur.com/NZpRScX.png",
7 | "keywords": ["RSS"],
8 | "env": {
9 | "NODE_MODULES_CACHE": {
10 | "value": "false",
11 | "required": true
12 | },
13 | "PORT": {
14 | "value": "80",
15 | "required": false
16 | },
17 | "PIXIV_USERNAME": {
18 | "required": false
19 | },
20 | "PIXIV_PASSWORD": {
21 | "required": false
22 | },
23 | "DISQUS_API_KEY": {
24 | "required": false
25 | },
26 | "TWITTER_CONSUMER_KEY": {
27 | "required": false
28 | },
29 | "TWITTER_CONSUMER_SECRET": {
30 | "required": false
31 | },
32 | "TWITTER_ACCESS_TOKEN": {
33 | "required": false
34 | },
35 | "TWITTER_ACCESS_TOKEN_SECRET": {
36 | "required": false
37 | },
38 | "YOUTUBE_KEY": {
39 | "required": false
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/routes/bilibili/blackboard.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const response = await axios({
5 | method: 'get',
6 | url: 'https://www.bilibili.com/activity/page/list?plat=1,2,3&mold=1&http=3&page=1&tid=0',
7 | headers: {
8 | Referer: 'https://www.bilibili.com/blackboard/topic_list.html',
9 | },
10 | });
11 |
12 | const data = response.data.data.list;
13 |
14 | ctx.state.data = {
15 | title: 'bilibili 话题列表',
16 | link: 'https://www.bilibili.com/blackboard/topic_list.html#/',
17 | description: 'bilibili 话题列表',
18 | item: data
19 | .filter(function(item, index, array) {
20 | // 由于某些话题在不同平台上是同时分开发布的,会产生重复,在这里去除
21 | return !index || item.name !== array[index - 1].name;
22 | })
23 | .map((item) => ({
24 | title: `${item.name}`,
25 | description: `${item.name}
${item.desc}`,
26 | pubDate: new Date(item.ctime.replace(' ', 'T') + '+08:00').toUTCString(),
27 | link: `${item.pc_url}`,
28 | })),
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/lib/routes/pixiv/api/getRanking.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const maskHeader = require('../constants').maskHeader;
3 | const assert = require('assert');
4 |
5 | const allowMode = ['day', 'week', 'month', 'day_male', 'day_female', 'week_original', 'week_rookie', 'day_r18', 'day_male_r18', 'day_female_r18', 'week_r18', 'week_r18g'];
6 |
7 | /**
8 | * 获取某天的排行榜
9 | * @param {string} mode 模式
10 | * @param {Date} date 日期
11 | * @param {string} token pixiv oauth token
12 | * @returns {Promise>}
13 | */
14 | module.exports = async function getRanking(mode, date, token) {
15 | assert(allowMode.includes(mode), 'Mode not allow.');
16 | return await axios({
17 | method: 'get',
18 | url: 'https://app-api.pixiv.net/v1/illust/ranking',
19 | headers: {
20 | ...maskHeader,
21 | Authorization: 'Bearer ' + token,
22 | },
23 | params: {
24 | mode: mode,
25 | filter: 'for_ios',
26 | ...(date && {
27 | date: `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`,
28 | }),
29 | },
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/test/utils/common-config.js:
--------------------------------------------------------------------------------
1 | const configUtils = require('../../lib/utils/common-config');
2 |
3 | describe('index', () => {
4 | it('transElemText', async () => {
5 | const $ = () => 'RSSHub';
6 | expect(configUtils.transElemText($, '$()')).toBe('RSSHub');
7 | });
8 |
9 | it('replaceParams', async () => {
10 | const $ = () => 'RSSHub';
11 | const data = {
12 | params: {
13 | title: 'RSSHub',
14 | },
15 | title: '%title%',
16 | };
17 | expect(configUtils.replaceParams(data, data.title, $)).toBe('RSSHub');
18 | });
19 |
20 | it('getProp', async () => {
21 | const $ = () => 'RSSHub';
22 | const data = {
23 | title: 'RSSHub',
24 | };
25 | expect(configUtils.getProp(data, ['title'], $)).toBe('RSSHub');
26 | });
27 |
28 | it('all', async () => {
29 | const $ = () => 'RSSHub';
30 | const data = {
31 | params: {
32 | title: '$()',
33 | },
34 | title: '%title%',
35 | };
36 | expect(configUtils.getProp(data, ['title'], $)).toBe('RSSHub');
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/lib/routes/bilibili/partion.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const tid = ctx.params.tid;
5 |
6 | const response = await axios({
7 | method: 'get',
8 | url: `https://api.bilibili.com/x/web-interface/newlist?ps=15&rid=${tid}&_=${+new Date()}`,
9 | headers: {
10 | Referer: 'https://www.bilibili.com/',
11 | },
12 | });
13 |
14 | const list = response.data.data.archives;
15 | let name = '未知';
16 | if (list && list[0] && list[0].tname) {
17 | name = list[0].tname;
18 | }
19 |
20 | ctx.state.data = {
21 | title: `bilibili ${name}分区`,
22 | link: 'https://www.bilibili.com',
23 | description: `bilibili ${name}分区`,
24 | item:
25 | list &&
26 | list.map((item) => ({
27 | title: `${item.title} - ${item.owner.name}`,
28 | description: `${item.desc}
`,
29 | pubDate: new Date(item.pubdate * 1000).toUTCString(),
30 | link: `https://www.bilibili.com/video/av${item.aid}`,
31 | })),
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/lib/routes/bilibili/video.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cache = require('./cache');
3 |
4 | module.exports = async (ctx) => {
5 | const uid = ctx.params.uid;
6 | const name = await cache.getUsernameFromUID(ctx, uid);
7 |
8 | const response = await axios({
9 | method: 'get',
10 | url: `https://space.bilibili.com/ajax/member/getSubmitVideos?mid=${uid}`,
11 | headers: {
12 | Referer: `https://space.bilibili.com/${uid}/`,
13 | },
14 | });
15 | const data = response.data;
16 |
17 | ctx.state.data = {
18 | title: `${name} 的 bilibili 空间`,
19 | link: `https://space.bilibili.com/${uid}`,
20 | description: `${name} 的 bilibili 空间`,
21 | item:
22 | data.data &&
23 | data.data.vlist &&
24 | data.data.vlist.map((item) => ({
25 | title: item.title,
26 | description: `${item.description}
`,
27 | pubDate: new Date(item.created * 1000).toUTCString(),
28 | link: `https://www.bilibili.com/video/av${item.aid}`,
29 | })),
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/lib/routes/douban/event/hot.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const { locationId = 0 } = ctx.params;
5 | const referer = 'https://m.douban.com/app_topic/event_hot';
6 |
7 | const response = await axios({
8 | method: 'get',
9 | url: `https://m.douban.com/rexxar/api/v2/subject_collection/event_hot/items?os=ios&for_mobile=1&callback=&start=0&count=20&loc_id=${locationId}`,
10 | headers: {
11 | Referer: referer,
12 | },
13 | });
14 |
15 | ctx.state.data = {
16 | title: `豆瓣同城-热门活动-${locationId}`,
17 | link: referer,
18 | item: response.data.subject_collection_items.map(({ title, url, date, cover, subtype, info, price_range }) => {
19 | const pubDate = new Date(date).toUTCString();
20 | const description = `
21 | ${info}/${subtype}/${price_range}
22 | `;
23 |
24 | return {
25 | title,
26 | description,
27 | pubDate,
28 | link: url,
29 | };
30 | }),
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/lib/routes/github/issue.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const config = require('../../config');
3 | const md = require('markdown-it')({
4 | html: true,
5 | });
6 |
7 | module.exports = async (ctx) => {
8 | const user = ctx.params.user;
9 | const repo = ctx.params.repo;
10 |
11 | const host = `https://github.com/${user}/${repo}/issues`;
12 | const url = `https://api.github.com/repos/${user}/${repo}/issues`;
13 |
14 | const response = await axios({
15 | method: 'get',
16 | url,
17 | params: {
18 | sort: 'created',
19 | access_token: config.github.access_token,
20 | },
21 | });
22 | const data = response.data;
23 |
24 | ctx.state.data = {
25 | title: `${user}/${repo} Issues`,
26 | link: host,
27 | item: data
28 | .filter((item) => item.pull_request === undefined)
29 | .map((item) => ({
30 | title: item.title,
31 | description: md.render(item.body) || 'No description',
32 | pubDate: new Date(item.created_at).toUTCString(),
33 | link: `${host}/${item.number}`,
34 | })),
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/lib/routes/ncm/artist.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const id = ctx.params.id;
5 |
6 | const response = await axios({
7 | method: 'get',
8 | url: `https://music.163.com/api/artist/albums/${id}`,
9 | headers: {
10 | Referer: 'https://music.163.com/',
11 | },
12 | });
13 |
14 | const data = response.data;
15 |
16 | ctx.state.data = {
17 | title: data.artist.name,
18 | link: `https://music.163.com/#/artist/album?id=${id}`,
19 | description: `网易云音乐歌手专辑 - ${data.artist.name}`,
20 | item: data.hotAlbums.map((item) => {
21 | const singer = item.artists.length === 1 ? item.artists[0].name : item.artists.reduce((prev, cur) => (prev.name || prev) + '/' + cur.name);
22 | return {
23 | title: `${item.name} - ${singer}`,
24 | description: `歌手:${singer}
专辑:${item.name}
日期:${new Date(item.publishTime).toLocaleDateString()}
`,
25 | link: `https://music.163.com/#/album?id=${item.id}`,
26 | };
27 | }),
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/lib/routes/universities/scnu/jw.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const res = await axios({
6 | method: 'get',
7 | url: 'https://jw.scnu.edu.cn/ann/index.html',
8 | headers: {
9 | Referer: 'https://jw.scnu.edu.cn',
10 | },
11 | });
12 | const $ = cheerio.load(res.data);
13 | const list = $('.notice_01')
14 | .find('li')
15 | .slice(0, 10);
16 |
17 | ctx.state.data = {
18 | title: $('title')
19 | .first()
20 | .text(),
21 | link: 'https://jw.scnu.edu.cn/ann/index.html',
22 | description: '华南师范大学教务处 - 通知公告',
23 | item:
24 | list &&
25 | list
26 | .map((index, item) => {
27 | item = $(item);
28 | return {
29 | title: item.find('a').text(),
30 | pubDate: new Date(item.find('.time').text()).toUTCString(),
31 | link: item.find('a').attr('href'),
32 | };
33 | })
34 | .get(),
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/lib/routes/bilibili/article.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cache = require('./cache');
3 |
4 | module.exports = async (ctx) => {
5 | const uid = ctx.params.uid;
6 | const name = await cache.getUsernameFromUID(ctx, uid);
7 |
8 | const response = await axios({
9 | method: 'get',
10 | url: `https://api.bilibili.com/x/space/article?mid=${uid}&pn=1&ps=10&sort=publish_time&jsonp=jsonp`,
11 | headers: {
12 | Referer: `https://space.bilibili.com/${uid}/`,
13 | },
14 | });
15 | const data = response.data.data;
16 |
17 | ctx.state.data = {
18 | title: `${name} 的 bilibili 专栏`,
19 | link: `https://space.bilibili.com/${uid}/#/article`,
20 | description: `${name} 的 bilibili 专栏`,
21 | item:
22 | data.articles &&
23 | data.articles.map((item) => ({
24 | title: item.title,
25 | description: `${item.summary}
`,
26 | pubDate: new Date(item.publish_time * 1000).toUTCString(),
27 | link: `https://www.bilibili.com/read/cv${item.id}`,
28 | })),
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/lib/routes/laosiji/hotshow.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const parseDate = require('../../utils/date');
3 | const qs = require('querystring');
4 |
5 | module.exports = async (ctx) => {
6 | const { id } = ctx.params;
7 | const link = `http://www.laosiji.com/hotshow/detail/${id}`;
8 | const response = await axios({
9 | method: 'post',
10 | url: 'http://www.laosiji.com/api/hotShow/program',
11 | headers: {
12 | Referer: link,
13 | 'Content-Type': 'application/x-www-form-urlencoded',
14 | },
15 | data: qs.stringify({
16 | hotShowId: id,
17 | sort: 1,
18 | pageNo: 1,
19 | }),
20 | });
21 |
22 | const data = response.data.body.hotshow;
23 |
24 | ctx.state.data = {
25 | title: `老司机-${data.name}`,
26 | link,
27 | item: data.sns.list.map(({ title, resourceid, image, publishtime }) => ({
28 | title,
29 | link: `http://www.laosiji.com/thread/${resourceid}.html`,
30 | description: `
`,
31 | pubDate: parseDate(publishtime, 8),
32 | })),
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/lib/routes/pediy/utils.js:
--------------------------------------------------------------------------------
1 | const pediyUtils = {
2 | dateParser: (html, timeZone) => {
3 | let math;
4 | let date = new Date();
5 | if (/(\d+)分钟前/.exec(html)) {
6 | math = /(\d+)分钟前/.exec(html);
7 | date.setMinutes(date.getMinutes() - math[1]);
8 | return date.toUTCString();
9 | } else if (/(\d+)小时前/.exec(html)) {
10 | math = /(\d+)小时前/.exec(html);
11 | date.setHours(date.getHours() - math[1]);
12 | return date.toUTCString();
13 | } else if (/(\d+)天前/.exec(html)) {
14 | math = /(\d+)天前/.exec(html);
15 | date.setDate(date.getDate() - math[1]);
16 | return date.toUTCString();
17 | } else if (/(\d+)-(\d+)-(\d+) (\d+):(\d+)/.exec(html)) {
18 | math = /(\d+)-(\d+)-(\d+) (\d+):(\d+)/.exec(html);
19 | date = new Date(math[1], parseInt(math[2]) - 1, math[3], math[4], math[5]);
20 | const serverOffset = new Date().getTimezoneOffset() / 60;
21 | return new Date(date.getTime() - 60 * 60 * 1000 * (timeZone + serverOffset)).toUTCString();
22 | }
23 | return html;
24 | },
25 | };
26 |
27 | module.exports = pediyUtils;
28 |
--------------------------------------------------------------------------------
/lib/routes/thunderbird/release.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | let response = await axios.get('https://www.thunderbird.net/en-US/thunderbird/releases/');
6 | let data = response.data;
7 | let $ = cheerio.load(data);
8 |
9 | const version = $('#main-content')
10 | .find('ol')
11 | .find('li')
12 | .first()
13 | .find('a')
14 | .last()
15 | .text();
16 |
17 | response = await axios.get(`https://www.thunderbird.net/en-US/thunderbird/${version}/releasenotes/`);
18 | data = response.data;
19 | $ = cheerio.load(data);
20 |
21 | const content = $('.main-column').html();
22 |
23 | ctx.state.data = {
24 | title: 'Thunderbird release note',
25 | link: 'https://www.thunderbird.net/',
26 | item: [
27 | {
28 | title: ['Thunderbird', version, 'release'].join(' '),
29 | guid: version,
30 | link: ['https://www.thunderbird.net/en-US/thunderbird/', version, '/releasenotes'].join(''),
31 | description: content,
32 | },
33 | ],
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/lib/routes/tingdiantz/nanjing.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | const HOME_PAGE = 'http://www.js.sgcc.com.cn';
5 |
6 | module.exports = async (ctx) => {
7 | const url = `${HOME_PAGE}/html/njgdgs/col152/column_152_1.html`;
8 | const response = await axios.get(url);
9 |
10 | const data = response.data;
11 | const $ = cheerio.load(data);
12 | const list = $('.rightarea ul li');
13 |
14 | ctx.state.data = {
15 | title: $('head title').text(),
16 | link: url,
17 | item: list
18 | .map((index, item) => {
19 | const $item = $(item);
20 | const $aTag = $item.find('a');
21 | const link = $aTag.attr('href');
22 | const title = $aTag.text();
23 |
24 | let pubDate = $item.find('span').text();
25 | pubDate = new Date(pubDate).toUTCString();
26 |
27 | return {
28 | title,
29 | description: '南京市停电通知',
30 | link: `${HOME_PAGE}${link}`,
31 | pubDate,
32 | };
33 | })
34 | .get(),
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/lib/routes/aisixiang/ranking.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const iconv = require('iconv-lite');
4 |
5 | const util = require('./utils');
6 |
7 | module.exports = async (ctx) => {
8 | const { type = 1, range = 1 } = ctx.params;
9 |
10 | const host = `http://www.aisixiang.com/toplist/index.php?id=${type}&period=${range}`;
11 |
12 | const response = await axios.get(host, {
13 | responseType: 'arraybuffer',
14 | });
15 |
16 | response.data = iconv.decode(response.data, 'gbk');
17 |
18 | const $ = cheerio.load(response.data);
19 |
20 | const list = $('.tops_list > .tips > a')
21 | .slice(0, 10)
22 | .get();
23 |
24 | const columnName = $('.tops_text > h3')[0].firstChild.nodeValue;
25 |
26 | const items = await Promise.all(
27 | list.map(async (e) => {
28 | const link = $(e).attr('href');
29 | return await ctx.cache.tryGet(link, async () => await util.ProcessFeed(link));
30 | })
31 | );
32 |
33 | ctx.state.data = {
34 | title: `爱思想 - ${columnName}`,
35 | link: host,
36 | description: `爱思想 - ${columnName}`,
37 | item: items,
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/lib/routes/aisixiang/column.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const iconv = require('iconv-lite');
4 |
5 | const util = require('./utils');
6 |
7 | module.exports = async (ctx) => {
8 | const { id } = ctx.params;
9 |
10 | const host = `http://www.aisixiang.com/data/search.php?lanmu=${id}`;
11 |
12 | const response = await axios.get(host, {
13 | responseType: 'arraybuffer',
14 | });
15 |
16 | response.data = iconv.decode(response.data, 'gbk');
17 |
18 | const $ = cheerio.load(response.data);
19 |
20 | const list = $('.search_list > ul > li > a:nth-child(2)')
21 | .slice(0, 10)
22 | .get();
23 |
24 | const columnName = $('.search_list > ul > li:nth-child(1) > a:nth-child(1)').text();
25 |
26 | const items = await Promise.all(
27 | list.map(async (e) => {
28 | const link = $(e).attr('href');
29 | return await ctx.cache.tryGet(link, async () => await util.ProcessFeed(link));
30 | })
31 | );
32 |
33 | ctx.state.data = {
34 | title: `爱思想栏目 - ${columnName}`,
35 | link: host,
36 | description: `爱思想栏目 - ${columnName}`,
37 | item: items,
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/lib/routes/tingshuitz/nanjing.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | const HOME_PAGE = 'http://www.jlwater.com/';
5 |
6 | module.exports = async (ctx) => {
7 | const url = `${HOME_PAGE}portal.do?method=news&subjectchildid=8`;
8 | const response = await axios.get(url);
9 |
10 | const data = response.data;
11 | const $ = cheerio.load(data);
12 | const list = $('.maincol-list ul li');
13 |
14 | ctx.state.data = {
15 | title: $('head title').text(),
16 | link: url,
17 | item: list
18 | .map((index, item) => {
19 | const $item = $(item);
20 | const $title = $item.find('.rtitle');
21 | const link = $title.find('a').attr('href');
22 |
23 | let pubDate = $item.find('.rtime').text();
24 | pubDate = new Date(pubDate.substring(1, pubDate.length - 1)).toUTCString();
25 |
26 | return {
27 | title: $title.text(),
28 | description: '南京市停水通知',
29 | link: `${HOME_PAGE}${link}`,
30 | pubDate,
31 | };
32 | })
33 | .get(),
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/lib/routes/universities/henu/news.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const resolve_url = require('url').resolve;
4 |
5 | const base_url = 'http://jwc.henu.edu.cn';
6 |
7 | const map = {
8 | all: '/',
9 | xszl: '/jwzl/xszl.htm',
10 | jszl: '/jwzl/jszl.htm',
11 | xwgg: '/jwzl/xwgg.htm',
12 | ybdt: '/jwzl/ybdt.htm',
13 | gjqy: '/jwzl/gjqy.htm',
14 | };
15 |
16 | module.exports = async (ctx) => {
17 | const type = ctx.params.type || 'all';
18 | const link = `${base_url}${map[type]}`;
19 | const response = await axios({
20 | method: 'get',
21 | url: link,
22 | headers: {
23 | Referer: link,
24 | },
25 | });
26 | const $ = cheerio.load(response.data);
27 | ctx.state.data = {
28 | link: link,
29 | title: $('title').text(),
30 | item: $('.list>tr')
31 | .slice(0, 10)
32 | .map((_, elem) => ({
33 | link: resolve_url(link, $('a', elem).attr('href')),
34 | title: $('tit1', elem).text(),
35 | pubDate: new Date($('time1.span', elem).text()).toUTCString(),
36 | }))
37 | .get(),
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/lib/routes/appstore/update.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const id = ctx.params.id;
6 | const country = ctx.params.country;
7 | const url = `https://itunes.apple.com/${country}/app/${id}`;
8 |
9 | const res = await axios.get(url);
10 | const $ = cheerio.load(res.data);
11 |
12 | const titleTags = $('h1').attr('class', 'product-header__title');
13 | titleTags.find('span').remove();
14 | const platform = $('.we-localnav__title__product').text();
15 |
16 | const title = `${titleTags.text().trim()} for ${platform === 'App Store' ? 'iOS' : 'macOS'} ${country === 'cn' ? '更新' : 'Update'} `;
17 |
18 | const item = {};
19 | $('.whats-new').each(function() {
20 | const version = $('.whats-new__latest__version')
21 | .text()
22 | .split(' ')[1];
23 | item.title = `${titleTags.text().trim()} ${version}`;
24 | item.description = $('.whats-new__content .we-truncate').html();
25 | item.link = url;
26 | item.guid = id + version;
27 | });
28 |
29 | ctx.state.data = {
30 | title,
31 | link: url,
32 | item: [item],
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/lib/routes/bangumi/subject/offcial-subject-api.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const DateTime = require('luxon').DateTime;
3 |
4 | module.exports = (type) => {
5 | const mapping = {
6 | blog: {
7 | en: 'reviews',
8 | cn: '评论',
9 | },
10 | topic: {
11 | en: 'board',
12 | cn: '讨论',
13 | },
14 | };
15 |
16 | return async (subjectID) => {
17 | // 官方提供的条目API文档见https://github.com/bangumi/api/blob/master/docs-raw/Subject-API.md
18 | const url = `https://api.bgm.tv/subject/${subjectID}?responseGroup=large`;
19 | const subjectInfo = (await axios.get(url)).data;
20 | return {
21 | title: `${subjectInfo.name_cn || subjectInfo.name}的Bangumi${mapping[type].cn}`,
22 | link: `https://bgm.tv/subject/${subjectInfo.id}/${mapping[type].en}`,
23 | item: subjectInfo[type].map((article) => ({
24 | title: `${article.user.nickname}:${article.title}`,
25 | description: article.summary || '',
26 | link: article.url,
27 | pubDate: DateTime.fromMillis(article.timestamp * 1000).toRFC2822(),
28 | })),
29 | };
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/lib/routes/bilibili/userFav.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cache = require('./cache');
3 |
4 | module.exports = async (ctx) => {
5 | const uid = ctx.params.uid;
6 | const name = await cache.getUsernameFromUID(ctx, uid);
7 |
8 | const response = await axios({
9 | method: 'get',
10 | url: `https://api.bilibili.com/x/v2/fav/video?vmid=${uid}&ps=30&tid=0&keyword=&pn=1&order=fav_time`,
11 | headers: {
12 | Referer: `https://space.bilibili.com/${uid}/#/favlist`,
13 | },
14 | });
15 | const data = response.data;
16 |
17 | ctx.state.data = {
18 | title: `${name} 的 bilibili 收藏夹`,
19 | link: `https://space.bilibili.com/${uid}/#/favlist`,
20 | description: `${name} 的 bilibili 收藏夹`,
21 |
22 | item:
23 | data.data &&
24 | data.data.archives &&
25 | data.data.archives.map((item) => ({
26 | title: item.title,
27 | description: `${item.desc}
`,
28 | pubDate: new Date(item.fav_at * 1000).toUTCString(),
29 | link: `https://www.bilibili.com/video/av${item.aid}`,
30 | })),
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/lib/routes/dysfz/index.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | let responseData = '';
6 | for (let index = 1; index <= 5; index++) {
7 | responseData =
8 | responseData +
9 | (await axios.get(`http://www.wuhaozhan.net/movie/list/?p=${index}`, {
10 | params: { p: index },
11 | })).data;
12 | }
13 | const $ = cheerio.load(responseData);
14 | const list = $('.pure-u-16-24').get();
15 | const data = {
16 | title: '电影首发站',
17 | link: 'http://www.wuhaozhan.net/movie/list/',
18 | description: '高清电影',
19 | item: list.map((item) => ({
20 | title: $(item)
21 | .find('h2')
22 | .text(),
23 | description: $(item)
24 | .find('.l-des')
25 | .text(),
26 | pubDate: new Date(
27 | $(item)
28 | .find('.dt')
29 | .text()
30 | ).toUTCString(),
31 | link: $(item)
32 | .find('.l-a')
33 | .attr('href'),
34 | })),
35 | };
36 | ctx.state.data = data;
37 | };
38 |
--------------------------------------------------------------------------------
/lib/routes/douban/book/rank.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const { type = 'fiction' } = ctx.params;
5 | const referer = `https://m.douban.com/subject_collection/book_${type}_hot_weekly`;
6 |
7 | const response = await axios({
8 | method: 'get',
9 | url: `https://m.douban.com/rexxar/api/v2/subject_collection/book_${type}_hot_weekly/items?start=0&count=10`,
10 | headers: {
11 | Referer: referer,
12 | },
13 | });
14 |
15 | ctx.state.data = {
16 | title: `豆瓣热门图书-${type === 'fiction' ? '虚构类' : '非虚构类'}`,
17 | link: referer,
18 | description: '每周一更新',
19 | item: response.data.subject_collection_items.map(({ title, url, cover, info, rating, null_rating_reason }) => {
20 | const rate = rating ? `${rating.value.toFixed(1)}分` : null_rating_reason;
21 | const description = `
22 | ${title}/${info}/${rate}
23 | `;
24 |
25 | return {
26 | title: `${title}-${info}`,
27 | description,
28 | link: url,
29 | };
30 | }),
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/lib/routes/universities/seu/yzb/index.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const url = require('url');
4 |
5 | const host = 'http://yzb.seu.edu.cn/';
6 |
7 | const map = new Map([
8 | [1, { title: '东南大学研究生招生网 -- 硕士招生', id: 'wp_news_w11' }],
9 | [2, { title: '东南大学研究生招生网 -- 博士招生', id: 'wp_news_w12' }],
10 | [3, { title: '东南大学研究生招生网 -- 港澳台及中外合作办学', id: 'wp_news_w103' }],
11 | ]);
12 |
13 | module.exports = async (ctx) => {
14 | const type = Number.parseInt(ctx.params.type);
15 | const response = await axios.get(host);
16 |
17 | const $ = cheerio.load(response.data);
18 |
19 | const id = map.get(type).id;
20 |
21 | const items = $(`#${id} tr tr`)
22 | .slice(0, 10)
23 | .map((_, elem) => {
24 | const a = $('td:first-child > a', elem);
25 | return {
26 | link: url.resolve(host, a.attr('href')),
27 | title: a.attr('title'),
28 | pubDate: new Date($('td:nth-child(2)', elem).text()).toUTCString(),
29 | };
30 | })
31 | .get();
32 |
33 | ctx.state.data = {
34 | link: host,
35 | title: map.get(type).title,
36 | item: items,
37 | };
38 | };
39 |
--------------------------------------------------------------------------------
/lib/routes/zongheng/chapter.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const id = ctx.params.id;
6 |
7 | const [$, $home] = (await Promise.all([axios.get(`http://book.zongheng.com/showchapter/${id}.html`), axios.get(`http://book.zongheng.com/book/${id}.html`)])).map((res) => cheerio.load(res.data));
8 | const date_re = /更新时间:(.*)$/;
9 | const cover_url = $home('.book-img img').attr('src');
10 | const description = $home('.book-dec').text();
11 | const name = $('.book-meta h1').text();
12 |
13 | const chapters = $('.volume-list li a');
14 | const items = [];
15 | for (let i = 0; i < chapters.length; i++) {
16 | const chapter = chapters.eq(i);
17 | const date_string = date_re.exec(chapter.attr('title'))[1];
18 |
19 | items.push({
20 | title: chapter.text(),
21 | pubDate: new Date(date_string).toUTCString(),
22 | link: chapter.attr('href'),
23 | });
24 | }
25 |
26 | ctx.state.data = {
27 | title: `纵横 ${name}`,
28 | link: `http://book.zongheng.com/book/${id}.html`,
29 | description,
30 | image: cover_url,
31 | item: items,
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/lib/routes/gitlab/explore.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | const type = ctx.params.type === 'all' ? '' : ctx.params.type;
6 | const typename = {
7 | trending: 'Trending',
8 | starred: 'Most stars',
9 | all: 'All',
10 | };
11 |
12 | const res = await axios({
13 | method: 'get',
14 | url: `https://gitlab.com/explore/projects/${type}`,
15 | });
16 | const $ = cheerio.load(res.data);
17 | const list = $('ul.projects-list').find('li');
18 |
19 | ctx.state.data = {
20 | title: `${typename[ctx.params.type]} - Explore - Gitlab`,
21 | link: `https://gitlab.com/explore/projects/${type}`,
22 | item:
23 | list &&
24 | list
25 | .map((index, item) => {
26 | item = $(item);
27 | return {
28 | title: item.find('.project-full-name').text(),
29 | description: item.find('.description').text(),
30 | link: `https://gitlab.com${item.find('a.text-plain').attr('href')}`,
31 | };
32 | })
33 | .get(),
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/lib/routes/psnine/news.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const date = require('../../utils/date');
4 |
5 | module.exports = async (ctx) => {
6 | const url = 'https://www.psnine.com/news';
7 | const response = await axios({
8 | method: 'get',
9 | url: url,
10 | });
11 |
12 | const data = response.data;
13 | const $ = cheerio.load(data);
14 |
15 | const out = $('.box li')
16 | .map(function() {
17 | const info = {
18 | title: $(this)
19 | .find('.content')
20 | .text(),
21 | link: $(this)
22 | .find('.touch')
23 | .attr('href'),
24 | pubDate: date(
25 | $(this)
26 | .find('.meta')
27 | .text()
28 | ),
29 | author: $(this)
30 | .find('.meta a')
31 | .text(),
32 | };
33 | return info;
34 | })
35 | .get();
36 |
37 | ctx.state.data = {
38 | title: 'psnine-' + $('title').text(),
39 | link: 'https://www.psnine.com/',
40 | item: out,
41 | };
42 | };
43 |
--------------------------------------------------------------------------------
/lib/routes/baidu/topwords.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 |
3 | module.exports = async (ctx) => {
4 | const { boardId = 1 } = ctx.params;
5 | const response = await axios({
6 | method: 'get',
7 | url: `http://top.baidu.com/mobile_v2/buzz?b=${boardId}`,
8 | });
9 |
10 | const { board, topwords, descs } = response.data.result;
11 | const items = topwords.map((item, index) => {
12 | const title = item.keyword;
13 | const content = descs[index].content;
14 | const desc = content
15 | ? content.data[0]
16 | : {
17 | originlink: `https://www.baidu.com/s?ie=utf-8&wd=${encodeURIComponent(title)}`,
18 | title,
19 | description: title,
20 | pubDate: Date.now(),
21 | };
22 |
23 | return {
24 | title,
25 | description: `
26 | ${desc.title}
27 | ${desc.description || ''}
28 | `,
29 | link: desc.originlink,
30 | pubDate: new Date(desc.pubDate).toUTCString(),
31 | };
32 | });
33 |
34 | ctx.state.data = {
35 | title: `百度搜索风云榜-${board.boardname}`,
36 | item: items,
37 | };
38 | };
39 |
--------------------------------------------------------------------------------
/lib/routes/universities/uestc/news.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | const listUrl = 'http://www.new1.uestc.edu.cn//?n=UestcNews.Front.Category.Page&CatId=';
5 | const baseUrl = 'http://www.new1.uestc.edu.cn';
6 | const map = {
7 | academy: '66',
8 | culture: '67',
9 | announcement: '68',
10 | notification: '72',
11 | };
12 | module.exports = async (ctx) => {
13 | const type = ctx.params.type || 'announcement';
14 | const response = await axios({
15 | method: 'get',
16 | url: listUrl + map[type],
17 | });
18 |
19 | const data = response.data;
20 | const $ = cheerio.load(data);
21 | ctx.state.data = {
22 | title: '电子科技大学新闻中心',
23 | link: baseUrl,
24 | item: $('div[id="Degas_news_list"] ul li h3 a')
25 | .slice(0, 10)
26 | .map((_, elem) => ({
27 | link: baseUrl + elem.attribs.href,
28 | title: $(elem).text(),
29 | pubDate: new Date(
30 | $(elem)
31 | .parent('h3')
32 | .next('span')
33 | .text()
34 | ).toUTCString(),
35 | }))
36 | .get(),
37 | };
38 | };
39 |
--------------------------------------------------------------------------------
/lib/routes/tingshuitz/dalian.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | // const area = ctx.params.area;
6 | const url = 'http://www.swj.dl.gov.cn/html/tstz/';
7 | const response = await axios({
8 | method: 'get',
9 | url: url,
10 | });
11 |
12 | const data = response.data;
13 | const $ = cheerio.load(data);
14 | const list = $('.listBox li');
15 |
16 | ctx.state.data = {
17 | title: $('title').text() || '停水通知 - 大连市水务局',
18 | link: 'http://www.swj.dl.gov.cn/html/tstz/',
19 | description: $('meta[name="description"]').attr('content') || $('title').text() || '停水通知 - 大连市水务局',
20 | item:
21 | list &&
22 | list
23 | .map((index, item) => {
24 | item = $(item);
25 | return {
26 | title: item.find('a').text(),
27 | description: `大连市停水通知:${item.find('a').text()}`,
28 | pubDate: new Date(item.find('span').text()).toUTCString(),
29 | link: `${item.find('a').attr('href')}`,
30 | };
31 | })
32 | .get(),
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/lib/routes/psnine/index.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const date = require('../../utils/date');
4 |
5 | module.exports = async (ctx) => {
6 | const url = 'https://www.psnine.com/';
7 | const response = await axios({
8 | method: 'get',
9 | url: url,
10 | });
11 |
12 | const data = response.data;
13 | const $ = cheerio.load(data);
14 |
15 | const out = $('.list li')
16 | .slice(0, 20)
17 | .map(function() {
18 | const info = {
19 | title: $(this)
20 | .find('.title')
21 | .text(),
22 | link: $(this)
23 | .find('.title a')
24 | .attr('href'),
25 | pubDate: date(
26 | $(this)
27 | .find('.meta')
28 | .text()
29 | ),
30 | author: $(this)
31 | .find('.meta a')
32 | .text(),
33 | };
34 | return info;
35 | })
36 | .get();
37 |
38 | ctx.state.data = {
39 | title: 'psnine-' + $('title').text(),
40 | link: 'https://www.psnine.com/',
41 | item: out,
42 | };
43 | };
44 |
--------------------------------------------------------------------------------
/lib/routes/universities/kmust/job/jobfairs.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../../utils/axios');
2 | const url = require('url');
3 |
4 | const baseUrl = 'http://job.kmust.edu.cn';
5 |
6 | module.exports = async (ctx) => {
7 | const title = '双选会-昆明理工大学就业网';
8 | const pageUrl = url.resolve(baseUrl, `/module/getjobfairs?start_page=1&keyword=&count=20&start=1&_=${+new Date()}`);
9 | const response = await axios({
10 | method: 'get',
11 | url: pageUrl,
12 | headers: {
13 | Referer: baseUrl,
14 | },
15 | });
16 | const data = response.data || {};
17 |
18 | ctx.state.data = {
19 | title,
20 | link: url.resolve(baseUrl, '/module/jobfairs'),
21 | item:
22 | data.data &&
23 | data.data.slice(0, 10).map((item) => {
24 | const { meet_day = '', meet_time = '', title = '', school_name = '', address = '', fair_id = '' } = item;
25 | return {
26 | title: `【${meet_day} ${meet_time}】${title}`,
27 | description: `时间:${meet_day} ${meet_time}
地点:${school_name ? `${school_name}-${address}` : address}`,
28 | link: url.resolve(baseUrl, `/detail/jobfair?id=${fair_id}`),
29 | };
30 | }),
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/lib/routes/zhihu/utils.js:
--------------------------------------------------------------------------------
1 | const cheerio = require('cheerio');
2 |
3 | module.exports = {
4 | header: {
5 | 'x-api-version': '3.0.40',
6 | 'x-udid': 'AMAiMrPqqQ2PTnOxAr5M71LCh-dIQ8kkYvw=',
7 | },
8 | ProcessImage: function(content) {
9 | const $ = cheerio.load(content, { xmlMode: true });
10 |
11 | $('noscript').remove();
12 |
13 | $('img.content_image, img.origin_image, img.content-image, img.data-actualsrc').each((i, e) => {
14 | if (e.attribs['data-original']) {
15 | $(e).attr({
16 | src: e.attribs['data-original'],
17 | width: null,
18 | height: null,
19 | });
20 | } else if (!e.attribs['data-original'] && e.attribs['data-actualsrc']) {
21 | $(e).attr({
22 | src: e.attribs['data-actualsrc'],
23 | width: null,
24 | height: null,
25 | });
26 | } else {
27 | $(e).attr({
28 | src: e.attribs.src.replace('_b.jpg', '_r.jpg'),
29 | width: null,
30 | height: null,
31 | });
32 | }
33 | });
34 |
35 | return $.html();
36 | },
37 | };
38 |
--------------------------------------------------------------------------------
/lib/routes/meipai/user.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const util = require('./utils');
4 |
5 | // const date = require('../../utils/date');
6 |
7 | module.exports = async (ctx) => {
8 | const uid = ctx.params.uid;
9 |
10 | const response = await axios({
11 | method: 'get',
12 | url: `https://www.meipai.com/user/${uid}`,
13 | headers: {
14 | Referer: 'https://www.meipai.com/',
15 | },
16 | });
17 | const data = response.data;
18 |
19 | const $ = cheerio.load(data); // 使用 cheerio 加载返回的 HTML
20 | const list = $('#mediasList')
21 | .find('li')
22 | .get();
23 | const name = $('.feed-left .content-l-h2').text();
24 |
25 | const result = await util.ProcessFeed(list, ctx.cache);
26 |
27 | // 使用 cheerio 选择器,选择 class="note-list" 下的所有 "li"元素,返回 cheerio node 对象数组
28 | // cheerio get() 方法将 cheerio node 对象数组转换为 node 对象数组
29 |
30 | // 注:每一个 cheerio node 对应一个 HTML DOM
31 | // 注:cheerio 选择器与 jquery 选择器几乎相同
32 | // 参考 cheerio 文档:https://cheerio.js.org/
33 |
34 | ctx.state.data = {
35 | title: `${name}又有更新了`,
36 | link: `https://www.meipai.com/user/${uid}/`,
37 | description: `${name}`,
38 | item: result,
39 | };
40 | };
41 |
--------------------------------------------------------------------------------
/lib/routes/solidot/main.js:
--------------------------------------------------------------------------------
1 | // Warning: The author still knows nothing about javascript!
2 |
3 | // params:
4 | // type: subject type
5 |
6 | const axios = require('../../utils/axios'); // get web content
7 | const cheerio = require('cheerio'); // html parser
8 | const get_article = require('./_article');
9 |
10 | const base_url = 'https://$type$.solidot.org';
11 | module.exports = async (ctx) => {
12 | const type = ctx.params.type || 'www';
13 |
14 | const list_url = base_url.replace('$type$', type);
15 | const response = await axios({
16 | method: 'get',
17 | url: list_url,
18 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0',
19 | });
20 | const data = response.data; // content is html format
21 | const $ = cheerio.load(data);
22 |
23 | // get urls
24 | const a = $('div.block_m').find('div.bg_htit > h2 > a');
25 | const urls = [];
26 | for (let i = 0; i < a.length; ++i) {
27 | urls.push($(a[i]).attr('href'));
28 | }
29 |
30 | // get articles
31 | const msg_list = await Promise.all(urls.map((u) => get_article(u)));
32 |
33 | // feed the data
34 | ctx.state.data = {
35 | title: '奇客的资讯,重要的东西',
36 | link: list_url,
37 | item: msg_list,
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/lib/routes/universities/njust/jwc/index.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../../../utils/axios');
2 | const cheerio = require('cheerio');
3 | const url = require('url');
4 |
5 | const host = 'http://jwc.njust.edu.cn/';
6 |
7 | const map = new Map([
8 | [1, { title: '南京理工大学教务处 -- 教师通知', id: 'wp_news_w12' }],
9 | [2, { title: '南京理工大学教务处 -- 学生通知', id: 'wp_news_w13' }],
10 | [3, { title: '南京理工大学教务处 -- 新闻', id: 'wp_news_w14' }],
11 | [4, { title: '南京理工大学教务处 -- 学院动态', id: 'wp_news_w15' }],
12 | ]);
13 |
14 | module.exports = async (ctx) => {
15 | const type = Number.parseInt(ctx.params.type);
16 | const response = await axios.get(host);
17 |
18 | const $ = cheerio.load(response.data);
19 |
20 | const id = map.get(type).id;
21 |
22 | const items = $(`#${id} tr tr`)
23 | .slice(0, 10)
24 | .map((_, elem) => {
25 | const a = $('td:first-child > a', elem);
26 | return {
27 | link: url.resolve(host, a.attr('href')),
28 | title: a.attr('title'),
29 | pubDate: new Date($('td:nth-child(2)', elem).text()).toUTCString(),
30 | };
31 | })
32 | .get();
33 |
34 | ctx.state.data = {
35 | link: host,
36 | title: map.get(type).title,
37 | item: items,
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/lib/routes/tingshuitz/hangzhou.js:
--------------------------------------------------------------------------------
1 | const axios = require('../../utils/axios');
2 | const cheerio = require('cheerio');
3 |
4 | module.exports = async (ctx) => {
5 | // const area = ctx.params.area;
6 | const url = 'http://www.hzwgc.com/public/stop_the_water/';
7 | const response = await axios({
8 | method: 'get',
9 | url: url,
10 | });
11 |
12 | const data = response.data;
13 | const $ = cheerio.load(data);
14 | const list = $('.datalist li');
15 |
16 | ctx.state.data = {
17 | title: $('title').text(),
18 | link: 'http://www.hzwgc.com/public/stop_the_water/',
19 | description: $('meta[name="description"]').attr('content') || $('title').text(),
20 | item:
21 | list &&
22 | list
23 | .map((index, item) => {
24 | item = $(item);
25 | return {
26 | title: item.find('.title').text(),
27 | description: `杭州市停水通知:${item.find('.title').text()}`,
28 | pubDate: new Date(item.find('.published').text()).toUTCString(),
29 | link: `http://www.hzwgc.com${item.find('.btn-read').attr('href')}`,
30 | };
31 | })
32 | .get(),
33 | };
34 | };
35 |
--------------------------------------------------------------------------------