diff --git a/docs/new-media.md b/docs/new-media.md index a42c29f9a6..921a6d858a 100644 --- a/docs/new-media.md +++ b/docs/new-media.md @@ -537,6 +537,20 @@ Supported sub-sites: +## 報道者 + +### 最新 + + + +### 摄影 + + + +### 分类 + + + ## 本地宝 ### 焦点资讯 diff --git a/lib/router.js b/lib/router.js index bea4b8a0f0..806b3a15f0 100644 --- a/lib/router.js +++ b/lib/router.js @@ -96,6 +96,11 @@ router.get('/bangumi/group/:id', require('./routes/bangumi/group/topic')); router.get('/bangumi/subject/:id', require('./routes/bangumi/subject')); router.get('/bangumi/user/blog/:id', require('./routes/bangumi/user/blog')); +// 報道者 +router.get('/twreporter/newest', require('./routes/twreporter/newest')); +router.get('/twreporter/photography', require('./routes/twreporter/photography')); +router.get('/twreporter/category/:cid', require('./routes/twreporter/category')); + // 微博 router.get('/weibo/user/:uid/:routeParams?', require('./routes/weibo/user')); router.get('/weibo/keyword/:keyword/:routeParams?', require('./routes/weibo/keyword')); diff --git a/lib/routes/twreporter/category.js b/lib/routes/twreporter/category.js new file mode 100644 index 0000000000..aef0e8a086 --- /dev/null +++ b/lib/routes/twreporter/category.js @@ -0,0 +1,36 @@ +const cheerio = require('cheerio'); +const got = require('@/utils/got'); + +const fetch = require('./fetch_article'); + +module.exports = async (ctx) => { + const baseURL = 'https://www.twreporter.org'; + const url = baseURL + `/categories/${ctx.params.cid}`; + const res = await got.get(url); + const $ = cheerio.load(res.data); + const list = $('.lnKPLr').get(); + const category = $('.kCfkTU').text(); + + const out = await Promise.all( + list.map(async (item) => { + const $ = cheerio.load(item); + const address = baseURL + $('a').attr('href'); + const title = $('.list-item__Title-sc-1dx5lew-5').text(); + const cache = await ctx.cache.get(address); + if (cache) { + return Promise.resolve(JSON.parse(cache)); + } + + const single = await fetch(address); + single.title = title; + + ctx.cache.set(address, JSON.stringify(single)); + return Promise.resolve(single); + }) + ); + ctx.state.data = { + title: `報道者 | ${category}`, + link: url, + item: out, + }; +}; diff --git a/lib/routes/twreporter/fetch_article.js b/lib/routes/twreporter/fetch_article.js new file mode 100644 index 0000000000..ff94f5ddc4 --- /dev/null +++ b/lib/routes/twreporter/fetch_article.js @@ -0,0 +1,66 @@ +const cheerio = require('cheerio'); +const got = require('@/utils/got'); + +module.exports = async function fetch(address) { + const res = await got.get(address); + const capture = cheerio.load(res.data); + capture('.gIMvvS').remove(); + + let metaInfoBox = capture('.iRdvCH') + .filter((index) => index === 0) + .get(); + + // For photography + if (metaInfoBox.length === 0) { + metaInfoBox = capture('.dPQBaW') + .filter((index) => index === 0) + .get(); + } + + const acquire = cheerio.load(metaInfoBox); + let time = acquire('.gimsRe').text(); + + // For `photography` + if (!time) { + time = acquire('.kHluJP').text(); + } + // # Author(s) of the article + // + // There exists two formats for this section. + // 1. 文字 /...` with `摄影 / ..` in separate line, or + // 2. just a simple line starts with `文/ ...` + // For the first condition, we use a array + // to record the list of author(s), and use + // `;` to connect two lines. + const authors = []; + + if (acquire('.loxoWO').text() === '') { + for (const item of acquire('.flciyI').get()) { + const $ = cheerio.load(item); + const job = $('.hGsNtm').text(); + // An article may have multiple authors + const name = []; + for (const item of $('.cidPTd > a').get()) { + const $ = cheerio.load(item); + name.push($('.fJSaZP').text()); + } + + const author = job + '/' + name.join(','); + authors.push(author); + } + } else { + authors.push(acquire('.loxoWO').text()); + } + const author = authors.join(';'); + + // contents = cover photo + italic intro + text + const contents = '' + capture('.hxFBKc').html() + '
' + capture('.irqyDp').html(); + + return { + author: author, + description: contents, + link: address, + guid: address, + pubDate: new Date(time).toUTCString(), + }; +}; diff --git a/lib/routes/twreporter/newest.js b/lib/routes/twreporter/newest.js new file mode 100644 index 0000000000..b9ab37cd68 --- /dev/null +++ b/lib/routes/twreporter/newest.js @@ -0,0 +1,34 @@ +const cheerio = require('cheerio'); +const got = require('@/utils/got'); + +const fetch = require('./fetch_article'); + +module.exports = async (ctx) => { + const url = 'https://www.twreporter.org'; + const res = await got.get(url); + const $ = cheerio.load(res.data); + const list = $('.gKMjSz').get(); + + const out = await Promise.all( + list.map(async (item) => { + const $ = cheerio.load(item); + const address = url + $('a').attr('href'); + const title = $('.dpNivU').text(); + const cache = await ctx.cache.get(address); + if (cache) { + return Promise.resolve(JSON.parse(cache)); + } + + const single = await fetch(address); + single.title = title; + + ctx.cache.set(address, JSON.stringify(single)); + return Promise.resolve(single); + }) + ); + ctx.state.data = { + title: `報道者 | 最新`, + link: url, + item: out, + }; +}; diff --git a/lib/routes/twreporter/photography.js b/lib/routes/twreporter/photography.js new file mode 100644 index 0000000000..bb6c3e5a89 --- /dev/null +++ b/lib/routes/twreporter/photography.js @@ -0,0 +1,57 @@ +const cheerio = require('cheerio'); +const got = require('@/utils/got'); + +const fetch = require('./fetch_article'); + +module.exports = async (ctx) => { + const baseURL = 'https://www.twreporter.org'; + const url = baseURL + `/photography`; + const res = await got.get(url); + const $ = cheerio.load(res.data); + const coverList = $('.WPJvn').get(); + const commonList = $('.eVNsZf').get(); + + const coverView = await Promise.all( + coverList.map(async (item) => { + const $ = cheerio.load(item); + const address = baseURL + $('li > a').attr('href'); + const title = $('.gRCDdm').text(); + + const cache = await ctx.cache.get(address); + if (cache) { + return Promise.resolve(JSON.parse(cache)); + } + + const single = await fetch(address); + single.title = title; + + ctx.cache.set(address, JSON.stringify(single)); + return Promise.resolve(single); + }) + ); + + const listView = await Promise.all( + commonList.map(async (item) => { + const $ = cheerio.load(item); + const address = baseURL + $('li > a').attr('href'); + const title = $('.etJLWI').text(); + + const cache = await ctx.cache.get(address); + if (cache) { + return Promise.resolve(JSON.parse(cache)); + } + + const single = await fetch(address); + single.title = title; + + ctx.cache.set(address, JSON.stringify(single)); + return Promise.resolve(single); + }) + ); + + ctx.state.data = { + title: `報道者 | 影像`, + link: url, + item: coverView.concat(listView), + }; +};