diff --git a/docs/traditional-media.md b/docs/traditional-media.md index 7e3c313d19..c47546cc0b 100644 --- a/docs/traditional-media.md +++ b/docs/traditional-media.md @@ -1795,6 +1795,38 @@ category 对应的关键词有 +## 天下雜誌 + +### 最新上線 + + + +### 主頻道 + + + +| 主頻道名稱 | 主頻道 ID | +| ----- | ------ | +| 財經 | 8 | +| 產業 | 7 | +| 國際 | 9 | +| 管理 | 10 | +| 環境 | 12 | +| 教育 | 13 | +| 人物 | 14 | +| 政治社會 | 77 | +| 調查排行 | 15 | +| 健康關係 | 79 | +| 時尚品味 | 11 | +| 運動生活 | 103 | +| 重磅外媒 | 16 | + + + +### 子頻道 + + + ## 卫报 The Guardian 通过提取文章全文,以提供比官方源更佳的阅读体验。 diff --git a/lib/v2/cw/maintainer.js b/lib/v2/cw/maintainer.js new file mode 100644 index 0000000000..51a7e83af2 --- /dev/null +++ b/lib/v2/cw/maintainer.js @@ -0,0 +1,5 @@ +module.exports = { + '/master/:channel': ['TonyRL'], + '/sub/:channel': ['TonyRL'], + '/today': ['TonyRL'], +}; diff --git a/lib/v2/cw/master.js b/lib/v2/cw/master.js new file mode 100644 index 0000000000..6530807d96 --- /dev/null +++ b/lib/v2/cw/master.js @@ -0,0 +1,28 @@ +const cheerio = require('cheerio'); +const { baseUrl, cookieJar, got, parseList, parseItems } = require('./utils'); + +module.exports = async (ctx) => { + const { channel } = ctx.params; + const pageUrl = `${baseUrl}/masterChannel.action`; + const { data: response } = await got(pageUrl, { + headers: { + Referer: baseUrl, + }, + cookieJar, + searchParams: { + idMasterChannel: channel, + }, + }); + const $ = cheerio.load(response); + + const list = parseList($, ctx.query.limit ? Number(ctx.query.limit) : 100); + const items = await parseItems(list, ctx.cache.tryGet); + + ctx.state.data = { + title: $('head title').text(), + description: $('meta[name=description]').attr('content'), + image: `${baseUrl}/assets_new/img/fbshare.jpg`, + language: $('meta[property="og:locale"]').attr('content'), + item: items, + }; +}; diff --git a/lib/v2/cw/radar.js b/lib/v2/cw/radar.js new file mode 100644 index 0000000000..9fa3a27e46 --- /dev/null +++ b/lib/v2/cw/radar.js @@ -0,0 +1,25 @@ +module.exports = { + 'cw.com.tw': { + _name: '天下雜誌', + '.': [ + { + title: '最新上線', + docs: 'https://docs.rsshub.app/traditional-media.html#tian-xia-za-zhi', + source: ['/today', '/'], + target: '/cw/today', + }, + { + title: '主頻道', + docs: 'https://docs.rsshub.app/traditional-media.html#tian-xia-za-zhi', + source: ['/masterChannel.action'], + target: (_, url) => `/cw/master/${new URL(url).searchParams.get('idMasterChannel')}`, + }, + { + title: '子頻道', + docs: 'https://docs.rsshub.app/traditional-media.html#tian-xia-za-zhi', + source: ['/subchannel.action'], + target: (_, url) => `/cw/sub/${new URL(url).searchParams.get('idSubChannel')}`, + }, + ], + }, +}; diff --git a/lib/v2/cw/router.js b/lib/v2/cw/router.js new file mode 100644 index 0000000000..6d7edc690f --- /dev/null +++ b/lib/v2/cw/router.js @@ -0,0 +1,5 @@ +module.exports = (router) => { + router.get('/master/:channel', require('./master')); + router.get('/sub/:channel', require('./sub')); + router.get('/today', require('./today')); +}; diff --git a/lib/v2/cw/sub.js b/lib/v2/cw/sub.js new file mode 100644 index 0000000000..7d4d9b0ad2 --- /dev/null +++ b/lib/v2/cw/sub.js @@ -0,0 +1,28 @@ +const cheerio = require('cheerio'); +const { baseUrl, cookieJar, got, parseList, parseItems } = require('./utils'); + +module.exports = async (ctx) => { + const { channel } = ctx.params; + const pageUrl = `${baseUrl}/subchannel.action`; + const { data: response } = await got(pageUrl, { + headers: { + Referer: baseUrl, + }, + cookieJar, + searchParams: { + idSubChannel: channel, + }, + }); + const $ = cheerio.load(response); + + const list = parseList($, ctx.query.limit ? Number(ctx.query.limit) : 100); + const items = await parseItems(list, ctx.cache.tryGet); + + ctx.state.data = { + title: $('head title').text(), + description: $('meta[name=description]').attr('content'), + image: `${baseUrl}/assets_new/img/fbshare.jpg`, + language: $('meta[property="og:locale"]').attr('content'), + item: items, + }; +}; diff --git a/lib/v2/cw/today.js b/lib/v2/cw/today.js new file mode 100644 index 0000000000..02fd199b97 --- /dev/null +++ b/lib/v2/cw/today.js @@ -0,0 +1,24 @@ +const cheerio = require('cheerio'); +const { baseUrl, cookieJar, got, parseList, parseItems } = require('./utils'); + +module.exports = async (ctx) => { + const pageUrl = `${baseUrl}/today`; + const { data: response } = await got(pageUrl, { + headers: { + Referer: baseUrl, + }, + cookieJar, + }); + const $ = cheerio.load(response); + + const list = parseList(response, ctx.query.limit ? Number(ctx.query.limit) : 100); + const items = await parseItems(list, ctx.cache.tryGet); + + ctx.state.data = { + title: $('head title').text(), + description: $('meta[name=description]').attr('content'), + image: `${baseUrl}/assets_new/img/fbshare.jpg`, + language: $('meta[property="og:locale"]').attr('content'), + item: items, + }; +}; diff --git a/lib/v2/cw/utils.js b/lib/v2/cw/utils.js new file mode 100644 index 0000000000..e3263c870d --- /dev/null +++ b/lib/v2/cw/utils.js @@ -0,0 +1,63 @@ +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); +const { CookieJar } = require('tough-cookie'); +const cookieJar = new CookieJar(); +const config = require('@/config').value; + +const baseUrl = 'https://www.cw.com.tw'; + +const got = require('@/utils/got').extend({ + headers: { + 'User-Agent': config.trueUA, + }, +}); + +const parseList = ($, limit) => + $('.caption') + .toArray() + .map((item) => { + item = $(item); + return { + title: item.find('h3').text(), + link: item.find('h3 a').attr('href'), + pubDate: parseDate(item.find('time').text()), + }; + }) + .slice(0, limit); + +const parseItems = (list, tryGet) => + Promise.all( + list.map((item) => + tryGet(item.link, async () => { + const { data: response } = await got(item.link, { + cookieJar, + }); + + const $ = cheerio.load(response); + const meta = JSON.parse($('head script[type="application/ld+json"]').eq(0).text()); + $('.article__head .breadcrumb, .article__head h1, .article__provideViews, .ad').remove(); + $('img.lazyload').each((_, img) => { + if (img.attribs['data-src']) { + img.attribs.src = img.attribs['data-src']; + delete img.attribs['data-src']; + } + }); + + item.title = $('head title').text(); + item.category = $('meta[name=keywords]').attr('content').split(','); + item.pubDate = parseDate(meta.datePublished); + item.author = meta.author.name.replace(',', ' ') || meta.publisher.name; + item.description = $('.article__head .container').html() + $('.article__content').html(); + + return item; + }) + ) + ); + +module.exports = { + baseUrl, + cookieJar, + got, + parseList, + parseItems, +};