From 676e70436fce3018407acb9f72a4a730272de928 Mon Sep 17 00:00:00 2001 From: Ethan Shen <42264778+nczitzk@users.noreply.github.com> Date: Wed, 8 Nov 2023 00:31:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(route):=20add=20=E7=BD=97=E6=88=88?= =?UTF-8?q?=E7=BD=91=20(#13704)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(route): add 罗戈网 * fix typo * fix typo --- lib/v2/logclub/index.js | 134 ++++++++++++ lib/v2/logclub/maintainer.js | 9 + lib/v2/logclub/radar.js | 139 ++++++++++++ lib/v2/logclub/report.js | 98 +++++++++ lib/v2/logclub/router.js | 5 + lib/v2/logclub/templates/description.art | 33 +++ website/docs/routes/new-media.mdx | 262 +++++++++++++++++++++++ 7 files changed, 680 insertions(+) create mode 100644 lib/v2/logclub/index.js create mode 100644 lib/v2/logclub/maintainer.js create mode 100644 lib/v2/logclub/radar.js create mode 100644 lib/v2/logclub/report.js create mode 100644 lib/v2/logclub/router.js create mode 100644 lib/v2/logclub/templates/description.art diff --git a/lib/v2/logclub/index.js b/lib/v2/logclub/index.js new file mode 100644 index 00000000000000..01d0ca5c752e8e --- /dev/null +++ b/lib/v2/logclub/index.js @@ -0,0 +1,134 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); +const { art } = require('@/utils/render'); +const path = require('path'); + +module.exports = async (ctx) => { + const { category = 'news' } = ctx.params; + const limit = ctx.query.limit ? parseInt(ctx.query.limit, 10) : 11; + + const rootUrl = 'https://www.logclub.com'; + const currentUrl = new URL(category, rootUrl).href; + + const { data: response } = await got(currentUrl); + + const $ = cheerio.load(response); + + let items = $('li.layui-row, li.layui-timeline-item') + .slice(0, limit) + .toArray() + .map((item) => { + item = $(item); + + const a = item.find('div.newslist-txt h3 a, a.article_title').first(); + const image = item.find('img.img-hover').prop('src')?.split(/\?/)[0] ?? undefined; + + return { + title: a.text(), + link: new URL(a.prop('href'), rootUrl).href, + description: art(path.join(__dirname, 'templates/description.art'), { + image: { + src: image, + alt: a.text(), + }, + intro: item.find('p.newslist-intro, div.newslist-info-intro').text(), + }), + itunes_item_image: image, + }; + }); + + items = await Promise.all( + items.map((item) => + ctx.cache.tryGet(item.link, async () => { + const { data: detailResponse } = await got(item.link); + + const content = cheerio.load(detailResponse); + + content('a.dl_file').each((_, el) => { + el = content(el); + el.parent().remove(); + }); + content('img').each((_, el) => { + el = content(el); + el.replaceWith( + art(path.join(__dirname, 'templates/description.art'), { + image: { + src: el.prop('src')?.split(/\?/)[0] ?? undefined, + alt: el.prop('title'), + }, + }) + ); + }); + + item.title = content('h1, div.current_video_title').first().text(); + + item.enclosure_url = content('video#ref_video').prop('src'); + if (item.enclosure_url) { + item.enclosure_type = `video/${item.enclosure_url.split(/\./).pop()}`; + } + + item.description += art(path.join(__dirname, 'templates/description.art'), { + video: { + poster: item.itunes_item_image, + src: item.enclosure_url, + type: item.enclosure_type, + }, + description: content('div.article-cont').html(), + }); + item.author = content('div.article-info-r a') + .toArray() + .map((a) => content(a).text()) + .join('/'); + item.category = [ + ...new Set([ + ...content('div.article-label-r a.label') + .toArray() + .map((c) => content(c).text()), + ...(content('meta[name="keywords"]') + .prop('content') + ?.split(/\s?,\s?/) ?? []), + ]), + ].filter((c) => c); + + if (content('span.aritlceIn-time').length === 0) { + item.pubDate = parseDate( + content( + content('div.video_info_item, div.lc-infos div') + .toArray() + .filter((i) => /\d{4}-\d{2}-\d{2}/.test(content(i).text())) + .pop() + ) + .text() + .split(/:/) + .pop() + .trim() + ); + } else { + item.pubDate = parseDate(content('span.aritlceIn-time').text().trim()); + } + + return item; + }) + ) + ); + + const icon = new URL($('link[rel="shortcut icon"]').prop('href'), rootUrl).href; + const subtitle = $('meta[name="keywords"]').prop('content'); + const author = subtitle.split(/,/)[0]; + + ctx.state.data = { + item: items, + title: $('title').text().split(/-/)[0].trim(), + link: currentUrl, + description: $('meta[name="description"]').prop('content'), + language: 'zh', + image: new URL($('div.logo_img img').prop('src'), rootUrl).href, + icon, + logo: icon, + subtitle: subtitle.replace(/,/g, ''), + author, + itunes_author: author, + itunes_category: 'News', + }; +}; diff --git a/lib/v2/logclub/maintainer.js b/lib/v2/logclub/maintainer.js new file mode 100644 index 00000000000000..68c46e20c708f0 --- /dev/null +++ b/lib/v2/logclub/maintainer.js @@ -0,0 +1,9 @@ +module.exports = { + '/company/:id': ['nczitzk'], + '/columnist/articleList/:id': ['nczitzk'], + '/lc_report/:id?': ['nczitzk'], + '/news/:id?': ['nczitzk'], + '/original': ['nczitzk'], + '/recruit': ['nczitzk'], + '/tender': ['nczitzk'], +}; diff --git a/lib/v2/logclub/radar.js b/lib/v2/logclub/radar.js new file mode 100644 index 00000000000000..675254d7cc9cf0 --- /dev/null +++ b/lib/v2/logclub/radar.js @@ -0,0 +1,139 @@ +module.exports = { + 'logclub.com': { + _name: '罗戈网', + '.': [ + { + title: '资讯', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zi-xun', + source: ['/news/:id', '/news'], + target: (params) => { + const id = params.id; + + return `/logclub/news${id ? `/${id}` : ''}`; + }, + }, + { + title: '资讯 - 供应链', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zi-xun', + source: ['/news/10-16'], + target: '/logclub/news/10-16', + }, + { + title: '资讯 - 快递', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zi-xun', + source: ['/news/11'], + target: '/logclub/news/11', + }, + { + title: '资讯 - 快运/运输', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zi-xun', + source: ['/news/30'], + target: '/logclub/news/30', + }, + { + title: '资讯 - 仓储/地产', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zi-xun', + source: ['/news/9'], + target: '/logclub/news/9', + }, + { + title: '资讯 - 物流综合', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zi-xun', + source: ['/news/32'], + target: '/logclub/news/32', + }, + { + title: '资讯 - 国际与跨境物流', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zi-xun', + source: ['/news/114'], + target: '/logclub/news/114', + }, + { + title: '资讯 - 科技创新', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zi-xun', + source: ['/news/107'], + target: '/logclub/news/107', + }, + { + title: '资讯 - 绿色供应链', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zi-xun', + source: ['/news/213'], + target: '/logclub/news/213', + }, + { + title: '资讯 - 低碳物流', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zi-xun', + source: ['/news/214'], + target: '/logclub/news/214', + }, + { + title: '资讯 - 碳中和碳达峰', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zi-xun', + source: ['/news/215'], + target: '/logclub/news/215', + }, + { + title: '招聘', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zhao-pin', + source: ['/recruit'], + target: '/logclub/recruit', + }, + { + title: '报告', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-bao-gao', + source: ['/lc_report'], + target: (params, url, document) => { + const id = document + ?.querySelector('li.layui-this[id]') + ?.id?.replace(/_/g, ' ') + .replace(/\b\w/g, (c) => c.toUpperCase()) + .replace(/\s/g, ''); + + return `/logclub/lc_report${id ? `/${id}` : ''}`; + }, + }, + { + title: '报告 - 罗戈研究出品', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-bao-gao', + source: ['/lc_report'], + target: '/logclub/lc_report/Report', + }, + { + title: '报告 - 物流报告', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-bao-gao', + source: ['/lc_report'], + target: '/logclub/lc_report/IndustryReport', + }, + { + title: '报告 - 绿色双碳报告', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-bao-gao', + source: ['/lc_report'], + target: '/logclub/lc_report/GreenDualCarbonReport', + }, + { + title: '招投标', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zhao-tou-biao', + source: ['/tender'], + target: '/logclub/tender', + }, + { + title: '原创', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zhao-yuan-chuang', + source: ['/original'], + target: '/logclub/original', + }, + { + title: '大企业', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zhao-da-qi-ye', + source: ['/company/:id'], + target: '/logclub/company/:id', + }, + { + title: '专家说', + docs: 'https://docs.rsshub.app/routes/new-media#luo-ge-wang-zhao-zhuan-jia-shuo', + source: ['/columnist/articleList/:id'], + target: '/logclub/columnist/articleList/:id', + }, + ], + }, +}; diff --git a/lib/v2/logclub/report.js b/lib/v2/logclub/report.js new file mode 100644 index 00000000000000..1abd9b1b97f938 --- /dev/null +++ b/lib/v2/logclub/report.js @@ -0,0 +1,98 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const timezone = require('@/utils/timezone'); +const { parseDate } = require('@/utils/parse-date'); +const { art } = require('@/utils/render'); +const path = require('path'); + +module.exports = async (ctx) => { + const { id = 'Report' } = ctx.params; + const limit = ctx.query.limit ? parseInt(ctx.query.limit, 10) : 11; + + const rootUrl = 'https://www.logclub.com'; + const currentUrl = new URL('lc_report', rootUrl).href; + const apiUrl = new URL(`front/lc_report/load${id}List`, rootUrl).href; + + const { data: response } = await got.post(apiUrl, { + json: { + page: 1, + }, + }); + + let items = response.list.slice(0, limit).map((item) => ({ + title: item.title, + link: new URL(`front/lc_report/get_report_info/${item.id}`, rootUrl).href, + description: art(path.join(__dirname, 'templates/description.art'), { + image: { + src: item.img_url?.split(/\?/)[0] ?? undefined, + alt: item.title, + }, + }), + author: item.author, + category: [item.channel_name], + guid: `logclub-report-${item.id}`, + pubDate: timezone(parseDate(item.release_time), +8), + })); + + items = await Promise.all( + items.map((item) => + ctx.cache.tryGet(item.link, async () => { + const { data: detailResponse } = await got(item.link); + + const content = cheerio.load(detailResponse); + + content('img').each((_, el) => { + el = content(el); + el.replaceWith( + art(path.join(__dirname, 'templates/description.art'), { + image: { + src: el.prop('src')?.split(/\?/)[0] ?? undefined, + alt: el.prop('title'), + }, + }) + ); + }); + + item.title = content('h1').first().text(); + item.description += art(path.join(__dirname, 'templates/description.art'), { + description: content('div.article-cont').html(), + }); + item.author = content('div.lc-infos a') + .toArray() + .map((a) => content(a).text()) + .join('/'); + item.category = [ + ...new Set([ + ...(item.category ?? []), + ...content('div.article-label-r a.label') + .toArray() + .map((c) => content(c).text()), + ]), + ].filter((c) => c); + + return item; + }) + ) + ); + + const { data: currentResponse } = await got(currentUrl); + + const $ = cheerio.load(currentResponse); + + const title = $('div.this_nav').text().trim(); + const icon = new URL($('link[rel="shortcut icon"]').prop('href'), rootUrl).href; + const subtitle = $('meta[name="keywords"]').prop('content'); + + ctx.state.data = { + item: items, + title: `${$('title').text()}${title}`, + link: currentUrl, + description: $('meta[name="description"]').prop('content'), + language: 'zh', + image: new URL($('div.logo_img img').prop('src'), rootUrl).href, + icon, + logo: icon, + subtitle: subtitle.replace(/,/g, ''), + author: subtitle.split(/,/)[0], + }; +}; diff --git a/lib/v2/logclub/router.js b/lib/v2/logclub/router.js new file mode 100644 index 00000000000000..fa03f16902fffb --- /dev/null +++ b/lib/v2/logclub/router.js @@ -0,0 +1,5 @@ +module.exports = (router) => { + router.get('/lc_report/:id?', require('./report')); + router.get('/report/:id?', require('./report')); + router.get('/:category*', require('./')); +}; diff --git a/lib/v2/logclub/templates/description.art b/lib/v2/logclub/templates/description.art new file mode 100644 index 00000000000000..eedb4179b066ae --- /dev/null +++ b/lib/v2/logclub/templates/description.art @@ -0,0 +1,33 @@ +{{ if image?.src }} + +{{ /if }} + +{{ if intro }} +
{{ intro }}
+{{ /if }} + +{{ if video?.src }} + +{{ /if }} + +{{ if description }} + {{@ description }} +{{ /if }} \ No newline at end of file diff --git a/website/docs/routes/new-media.mdx b/website/docs/routes/new-media.mdx index 8eda702dc6baac..c94ca05d0fe11a 100644 --- a/website/docs/routes/new-media.mdx +++ b/website/docs/routes/new-media.mdx @@ -4300,6 +4300,268 @@ column 为 third 时可选的 category: +## 罗戈网 {#luo-ge-wang} + +### 资讯 {#luo-ge-wang-zi-xun} + +