diff --git a/docs/other.md b/docs/other.md index 595e1921a9..2732fb6df7 100644 --- a/docs/other.md +++ b/docs/other.md @@ -270,6 +270,26 @@ type 为 all 时,category 参数不支持 cost 和 free +## wikiHow + +### 首页 + + + +### 分类目录 + + + +顶级目录分类可在目录分类页[查看](https://zh.wikihow.com/Special:CategoryListing), 支持二级目录 + +类型 + +| 所有 | 推荐 | +| ---- | ---- | +| all | rec | + + + ## ZAKER ### source @@ -1041,6 +1061,24 @@ type 为 all 时,category 参数不支持 cost 和 free +## 正版中国 + +### 分类列表 + + + +类型 + +| 全部文章 | 永久免费 | 限时折扣 | 限时免费 | PC | Mac | Android | UWP | +| -------- | -------- | -------- | -------- | --- | --- | ------- | --- | +| all | 311 | 309 | 310 | 8 | 50 | 17 | 312 | + + + +### 搜索 + + + ## 中国大学 MOOC(慕课) ### 最新 diff --git a/lib/router.js b/lib/router.js index b89cbddee7..7971bc588e 100644 --- a/lib/router.js +++ b/lib/router.js @@ -1545,4 +1545,12 @@ router.get('/ff14/ff14_zh/:type', require('./routes/ff14/ff14_zh')); router.get('/xuetangx/course/:cid/:type', require('./routes/xuetangx/course_info')); router.get('/xuetangx/course/list/:mode/:credential/:status/:type?', require('./routes/xuetangx/course_list')); +// wikihow +router.get('/wikihow/index', require('./routes/wikihow/index.js')); +router.get('/wikihow/category/:category/:type', require('./routes/wikihow/category.js')); + +// 正版中国 +router.get('/getitfree/category/:category?', require('./routes/getitfree/category.js')); +router.get('/getitfree/search/:keyword?', require('./routes/getitfree/search.js')); + module.exports = router; diff --git a/lib/routes/getitfree/category.js b/lib/routes/getitfree/category.js new file mode 100644 index 0000000000..9ecb18b1c0 --- /dev/null +++ b/lib/routes/getitfree/category.js @@ -0,0 +1,59 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const utils = require('./utils'); + +module.exports = async (ctx) => { + const { category = 'all' } = ctx.params; + const baseUrl = `https://getitfree.cn`; + const categoryToLabel = { + '8': 'PC', + '17': 'Android', + '50': 'Mac', + '309': '限时折扣', + '310': '限时免费', + '311': '永久免费', + '312': 'UWP', + all: '全部文章', + }; + + let item = []; + if (category === 'all') { + const res = await got(baseUrl); + const $ = cheerio.load(res.data); + $('#page-content .top-content .top-slide > .item') + .toArray() + .forEach((ele) => { + const $item = cheerio.load(ele); + const infoNode = $item('.slider-image > a'); + const title = infoNode.attr('title'); + const link = infoNode.attr('href'); + const thumbnail = infoNode.attr('style').replace(/background-image:url\(|\)/g, ''); + const deadlineStr = utils.getDeadlineStr($item, '.countDownCont'); + const description = [title, deadlineStr, ``].filter((str) => !!str).join('
'); + item.push({ + title, + link, + description, + }); + }); + item = item.concat(utils.parseListItem($, '.main-content')); + } else { + const res = await got(baseUrl, { + query: { + action: 'fa_load_postlist', + paged: 1, + category, + }, + }); + const $ = cheerio.load(res.data); + item = utils.parseListItem($, '.ajax-load-con'); + } + + const categoryLabel = categoryToLabel[category]; + ctx.state.data = { + title: `正版中国 - ${categoryLabel}`, + description: `正版中国 - ${categoryLabel}`, + link: baseUrl, + item, + }; +}; diff --git a/lib/routes/getitfree/search.js b/lib/routes/getitfree/search.js new file mode 100644 index 0000000000..a419195cda --- /dev/null +++ b/lib/routes/getitfree/search.js @@ -0,0 +1,25 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const qs = require('query-string'); +const utils = require('./utils'); + +module.exports = async (ctx) => { + const { keyword } = ctx.params; + const link = 'https://getitfree.cn'; + const query = { + s: keyword, + }; + const res = await got(link, { + query, + }); + const $ = cheerio.load(res.data); + + const item = utils.parseListItem($, '#page-content'); + + ctx.state.data = { + title: `正版中国搜索 - ${keyword}`, + description: `正版中国搜索 - ${keyword}`, + link: `${link}?${qs.stringify(query)}`, + item, + }; +}; diff --git a/lib/routes/getitfree/utils.js b/lib/routes/getitfree/utils.js new file mode 100644 index 0000000000..3273141e17 --- /dev/null +++ b/lib/routes/getitfree/utils.js @@ -0,0 +1,47 @@ +const cheerio = require('cheerio'); + +const getDeadlineStr = ($, countDownNodeSelector) => { + const countDownNode = $(countDownNodeSelector); + let deadlineStr = ''; + if (countDownNode.text().includes('活动已经结束')) { + return '截至日期: 活动已经结束'; + } + const countdownCodeStr = countDownNode.children('script').html() || ''; + const arr = /ShowCountDown\("\d*"\s*,\s*([\d,]+)\s*\)/.exec(countdownCodeStr.trim()); + if (arr) { + const dateStr = arr[1].replace(/,/g, (...args) => { + const index = args[args.length - 2]; + return index === 10 ? ' ' : index < 10 ? '-' : ':'; + }); + deadlineStr = `截至日期: ${new Date(dateStr).toUTCString()}`; + } + return deadlineStr; +}; + +const parseListItem = ($, listSelector) => + $(`${listSelector} .content-box`) + .map((_, ele) => { + const $item = cheerio.load(ele); + const infoNode = $item('.posts-default-title > h2 > a'); + const title = infoNode.attr('title'); + const link = infoNode.attr('href'); + const pubDateStr = $item('.posts-default-info .icon-time').text(); + const pubDate = new Date(pubDateStr).toUTCString(); + const thumbnail = $item('.posts-default-img img').attr('data-original'); + const deadlineStr = getDeadlineStr($item, '.countDownCont'); + const digest = $item('.posts-default-content > .posts-text') + .text() + .trim(); + return { + title, + link, + pubDate, + description: [title, deadlineStr, digest, ``].filter((str) => !!str).join('
'), + }; + }) + .get(); + +module.exports = { + getDeadlineStr, + parseListItem, +}; diff --git a/lib/routes/wikihow/category.js b/lib/routes/wikihow/category.js new file mode 100644 index 0000000000..2da08f83ae --- /dev/null +++ b/lib/routes/wikihow/category.js @@ -0,0 +1,34 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); + +module.exports = async (ctx) => { + const { category, type = 'all' } = ctx.params; + const link = encodeURI(`https://zh.wikihow.com/Category:${category}`); + const res = await got(link); + const $ = cheerio.load(res.data); + + const itemsSelector = type === 'all' ? '#cat_featured .thumbnail' : '#cat_all .thumbnail'; + const item = $(itemsSelector) + .map((_, ele) => { + const $item = cheerio.load(ele); + const title = '如何' + $item('.text span').text(); + const link = $item('a').attr('href'); + const thumbnail = $item('img').attr('data-src'); + return { + title, + description: ` + ${title}
+ + `, + link, + }; + }) + .get(); + + ctx.state.data = { + title: `wikiHow - ${category}`, + description: `wikiHow - ${category}`, + link, + item, + }; +}; diff --git a/lib/routes/wikihow/index.js b/lib/routes/wikihow/index.js new file mode 100644 index 0000000000..4af318427d --- /dev/null +++ b/lib/routes/wikihow/index.js @@ -0,0 +1,32 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); + +module.exports = async (ctx) => { + const link = `https://zh.wikihow.com/${encodeURIComponent('首页')}`; + const res = await got(link); + const $ = cheerio.load(res.data); + + const item = $('#fa_container td.image_map') + .map((_, ele) => { + const $item = cheerio.load(ele); + const title = '如何' + $item('.text span').text(); + const link = $item('.thumbnail > a').attr('href'); + const thumbnail = $item('.thumbnail img').attr('data-src'); + return { + title, + description: ` + ${title}
+ + `, + link, + }; + }) + .get(); + + ctx.state.data = { + title: 'wikiHow - 首页', + description: 'wikiHow - 首页', + link, + item, + }; +};