diff --git a/lib/router.js b/lib/router.js index d68905006b0ae0..09b80aaeb2a61d 100644 --- a/lib/router.js +++ b/lib/router.js @@ -487,8 +487,8 @@ router.get('/gov/shuju/:caty/:item', lazyloadRouteHandler('./routes/gov/shuju')) router.get('/gov/xinwen/tujie/:caty', lazyloadRouteHandler('./routes/gov/xinwen/tujie')); // 苏州 -router.get('/gov/suzhou/news/:uid', lazyloadRouteHandler('./routes/gov/suzhou/news')); -router.get('/gov/suzhou/doc', lazyloadRouteHandler('./routes/gov/suzhou/doc')); +// router.get('/gov/suzhou/news/:uid', lazyloadRouteHandler('./routes/gov/suzhou/news')); +// router.get('/gov/suzhou/doc', lazyloadRouteHandler('./routes/gov/suzhou/doc')); // 山西 router.get('/gov/shanxi/rst/:category', lazyloadRouteHandler('./routes/gov/shanxi/rst')); diff --git a/lib/routes/gov/suzhou/doc.js b/lib/routes/gov/suzhou/doc.js deleted file mode 100644 index 4baa1bc3d13392..00000000000000 --- a/lib/routes/gov/suzhou/doc.js +++ /dev/null @@ -1,39 +0,0 @@ -const got = require('@/utils/got'); -const cheerio = require('cheerio'); -const util = require('./utils'); -module.exports = async (ctx) => { - const link = 'https://www.suzhou.gov.cn/szxxgk/front/xxgk_right.jsp'; - const response = await got.get(link); - const data = response.data; - const $ = cheerio.load(data); - const list = $('.tr_main_value_odd'); - - ctx.state.data = { - title: '苏州市政府 - 政策公开文件', - link, - item: await Promise.all( - list - .slice(0, 12) - .map(async (index, item) => { - item = $(item); - // 获取全文 - const contentUrl = 'https://www.suzhou.gov.cn' + item.find('a').attr('href'); - const arr = await ctx.cache.tryGet(contentUrl, async () => { - const fullText = await got.get(contentUrl); - const fullTextData = cheerio.load(fullText.data); - const content = util.content(fullText.data); - const title = fullTextData('h1').text().replace(/\s*/g, ''); - - return new Array(title, content); - }); - return { - title: arr[0], - description: arr[1], - pubDate: item.find('td:nth-child(4)').text(), - link: contentUrl, - }; - }) - .get() - ), - }; -}; diff --git a/lib/routes/gov/suzhou/news.js b/lib/routes/gov/suzhou/news.js deleted file mode 100644 index ee5e278e6b3685..00000000000000 --- a/lib/routes/gov/suzhou/news.js +++ /dev/null @@ -1,142 +0,0 @@ -const got = require('@/utils/got'); -const cheerio = require('cheerio'); -const logger = require('@/utils/logger'); -const liburl = require('url'); -const util = require('./utils'); -const root_url = 'https://www.suzhou.gov.cn/'; - -module.exports = async (ctx) => { - const uid = ctx.params.uid; - let url = ''; - let title = ''; - let urljs = ''; - switch (uid) { - case 'szyw': - case 'news': - urljs = 'https://www.suzhou.gov.cn/szinf/info/getInfoCommon/?pagesize=15&currpage=1&channelid=5057aeffb1a84a7e8aeded87728da48c'; - url = 'https://www.suzhou.gov.cn/szsrmzf/szyw/nav_list.shtml'; - title = '苏州市政府 - 苏州要闻'; - break; - case 'qxkx': - case 'district': - urljs = 'https://www.suzhou.gov.cn/szinf/info/getInfoCommon/?pagesize=15&currpage=1&channelid=75c636ea0efb487ea7e479e3cc0ff3e5'; - url = 'https://www.suzhou.gov.cn/szsrmzf/qxkx/nav_list.shtml'; - title = '苏州市政府 - 区县快讯'; - break; - case 'bmdt': - urljs = 'https://www.suzhou.gov.cn/szinf/info/getInfoCommon/?pagesize=15&currpage=1&channelid=b3d097e3eb79421f88439ea381ce33c3'; - url = 'https://www.suzhou.gov.cn/szsrmzf/bmdt/nav_list.shtml'; - title = '苏州市政府 - 部门动态'; - break; - case 'xwsp': - urljs = 'https://www.suzhou.gov.cn/szinf/info/getInfoCommon/?pagesize=15&currpage=1&channelid=507980d214c943ebb0a70853ec94b12e'; - url = 'https://www.suzhou.gov.cn/szsrmzf/xwsp/nav_list.shtml'; - title = '苏州市政府 - 新闻视频'; - break; - case 'rdzt': - url = 'https://www.suzhou.gov.cn/szsrmzf/rdzt/nav_list.shtml'; - title = '苏州市政府 - 热点专题'; - break; - case 'sbjzt': - url = 'https://www.suzhou.gov.cn/szsrmzf/sbjzt/nav_list.shtml'; - title = '苏州市政府 - 市本级专题'; - break; - case 'zxrdzt': - url = 'https://www.suzhou.gov.cn/szsrmzf/zxrdzt/nav_list.shtml'; - title = '苏州市政府 - 最新热点专题'; - break; - case 'wqzt': - url = 'https://www.suzhou.gov.cn/szsrmzf/wqzt/nav_list.shtml'; - title = '苏州市政府 - 往期专题'; - break; - case 'qxzt': - url = 'https://www.suzhou.gov.cn/szsrmzf/qxzt/nav_list.shtml'; - title = '苏州市政府 - 区县专题'; - break; - case 'zwgg': - urljs = 'https://www.suzhou.gov.cn/szinf/info/getInfoCommon/?pagesize=15&currpage=1&channelid=260915178a1f4c4fac44c4bf6378c9b0'; - url = 'https://www.suzhou.gov.cn/szsrmzf/zwgg/nav_list.shtml'; - title = '苏州市政府 - 政务公告'; - break; - case 'mszx': - urljs = 'https://www.suzhou.gov.cn/szinf/info/getInfoCommon/?pagesize=15&currpage=1&channelid=dc60acecb0be46b89d42272dcb8bd32b'; - url = 'https://www.suzhou.gov.cn/szsrmzf/mszx/nav_list.shtml'; - title = '苏州市政府 - 便民公告'; - break; - case 'bmzx': - urljs = 'https://www.suzhou.gov.cn/szinf/info/getInfoCommon/?pagesize=15&currpage=1&channelid=b015bfa5e5514cc9a26cd9f956ef8e69'; - url = 'https://www.suzhou.gov.cn/szsrmzf/bmzx/bmzx_list.shtml'; - title = '苏州市政府 - 民生资讯'; - break; - default: - logger.error('pattern not matched'); - } - if (urljs) { - const responsejs = await got({ - method: 'get', - url: urljs, - Host: 'www.suzhou.gov.cn', - }); - const jsdata = responsejs.data.infolist; - - ctx.state.data = { - title, - link: url, - item: await Promise.all( - jsdata.slice(0, 10).map(async (item) => { - // 获取全文 - const link = item.link.indexOf('http') === -1 ? liburl.resolve(root_url, item.link) : item.link; - const description = await ctx.cache.tryGet(link, async () => { - let responsehtml; - - try { - responsehtml = await got({ - method: 'get', - url: link, - }); - const content = util.content(responsehtml.data); - return content; - } catch (error) { - return ''; - } - }); - return { - title: item.title, - description, - link, - pubDate: item.pubtime, - }; - }) - ), - }; - } - if (!urljs) { - const response = await got({ - method: 'get', - url, - }); - - const $ = cheerio.load(response.data); - const list = $('div.pageList li'); - - ctx.state.data = { - title, - link: url, - item: await Promise.all( - list - .slice(0, 10) - .map((_, item) => { - item = $(item); - const a = item.find('a'); - const link = liburl.resolve(root_url, a.attr('href')); - return { - title: a.attr('title'), - link, - pubDate: new Date(item.find('.time').text()).toUTCString(), - }; - }) - .get() - ), - }; - } -}; diff --git a/lib/routes/gov/suzhou/utils.js b/lib/routes/gov/suzhou/utils.js deleted file mode 100644 index a687caa2783463..00000000000000 --- a/lib/routes/gov/suzhou/utils.js +++ /dev/null @@ -1,20 +0,0 @@ -const cheerio = require('cheerio'); - -const content = (response) => { - const e = cheerio.load(response); - e('.article-extended').remove(); - e('#shareNode').remove(); - e('.article-auxiliary').remove(); - const content = e('.contentShow').html() - ? e('.contentShow').html() - : e('div.detail') - .html() - .replace(/<.?ucaptitle>/g, '') - .replace(/<.?ucapcontent>/g, '') - .replace(/"\/szsrmzf/g, '"https://www.suzhou.gov.cn/szsrmzf') - .replace('publishtime', 'span'); - return content; -}; -module.exports = { - content, -}; diff --git a/lib/v2/gov/maintainer.js b/lib/v2/gov/maintainer.js index e02d783226edf2..ad148d8cd12ab5 100644 --- a/lib/v2/gov/maintainer.js +++ b/lib/v2/gov/maintainer.js @@ -101,6 +101,9 @@ module.exports = { '/shenzhen/zzb/:caty/:page?': ['zlasd'], '/sichuan/deyang/govpublicinfo/:countyName/:infoType?': ['zytomorrow'], '/sichuan/deyang/mztoday/:infoType?': ['zytomorrow'], + '/suzhou/doc': ['EsuRt'], + '/suzhou/fg/:category?': ['nczitzk'], + '/suzhou/news/:uid': ['EsuRt', 'luyuhuang'], '/taiyuan/rsj/:caty/:page?': ['2PoL'], '/wuhan/sy/whyw': ['nczitzk'], '/xinyi/:path+': ['ShuiHuo'], diff --git a/lib/v2/gov/radar.js b/lib/v2/gov/radar.js index 35da7bc7942777..37651a679cca03 100644 --- a/lib/v2/gov/radar.js +++ b/lib/v2/gov/radar.js @@ -2287,6 +2287,31 @@ module.exports = { }, ], }, + 'suzhou.gov.cn': { + _name: '苏州市人民政府', + fg: [ + { + title: '苏州市发展和改革委员会', + docs: 'https://docs.rsshub.app/routes/government#su-zhou-shi-ren-min-zheng-fu-su-zhou-shi-fa-zhan-he-gai-ge-wei-yuan-hui', + source: ['/:category*'], + target: (params) => `/gov/suzhou/fg/${params.replace(/\.shtml/, '')}`, + }, + ], + www: [ + { + title: '政府信息公开文件', + docs: 'https://docs.rsshub.app/routes/government#su-zhou-shi-ren-min-zheng-fu', + source: ['/szxxgk/front/xxgk_right.jsp', '/'], + target: '/gov/suzhou/doc', + }, + { + title: '政府新闻', + docs: 'https://docs.rsshub.app/routes/government#su-zhou-shi-ren-min-zheng-fu', + source: ['/szsrmzf/:uid/nav_list.shtml'], + target: '/gov/suzhou/news/:uid', + }, + ], + }, 'sz.gov.cn': { _name: '深圳政府在线移动门户', hrss: [ diff --git a/lib/v2/gov/router.js b/lib/v2/gov/router.js index 1164beb421f0ee..0d1ec6a01303ac 100644 --- a/lib/v2/gov/router.js +++ b/lib/v2/gov/router.js @@ -93,6 +93,9 @@ module.exports = function (router) { router.get('/shenzhen/zzb/:caty/:page?', require('./shenzhen/zzb/index')); router.get('/sichuan/deyang/govpublicinfo/:countyName/:infoType?', require('./sichuan/deyang/govpublicinfo')); router.get('/sichuan/deyang/mztoday/:infoType?', require('./sichuan/deyang/mztoday')); + router.get('/suzhou/doc', require('./suzhou/doc')); + router.get('/suzhou/fg/:category*', require('./suzhou/fg')); + router.get('/suzhou/news/:uid', require('./suzhou/news')); router.get('/taiyuan/rsj/:caty/:page?', require('./taiyuan/rsj')); router.get('/wuhan/sy/whyw', require('./wuhan/whyw')); router.get(/xinyi(\/[\w/-]+)?/, require('./xinyi/xinyi')); diff --git a/lib/v2/gov/suzhou/doc.js b/lib/v2/gov/suzhou/doc.js new file mode 100644 index 00000000000000..fddf232695fb37 --- /dev/null +++ b/lib/v2/gov/suzhou/doc.js @@ -0,0 +1,46 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); +const timezone = require('@/utils/timezone'); + +module.exports = async (ctx) => { + const link = 'https://www.suzhou.gov.cn/szxxgk/front/xxgk_right.jsp'; + + const { data: response } = await got(link); + const $ = cheerio.load(response); + const list = $('.tr_main_value_odd') + .toArray() + .map((item) => { + item = $(item); + const title = item.find('a'); + return { + title: title.attr('title'), + link: `https://www.suzhou.gov.cn${title.attr('href')}`, + pubDate: timezone(parseDate(item.find('td:nth-child(3)').text().trim()), 8), + }; + }); + + const items = await Promise.all( + list.map((item) => + ctx.cache.tryGet(item.link, async () => { + const { data: response } = await got(item.link); + const $ = cheerio.load(response); + + item.description = $('.article-content').html(); + item.author = $('dd.addWidth:nth-child(3) div').text().trim(); + item.pubDate = $('meta[name="PubDate"]').length ? timezone(parseDate($('meta[name="PubDate"]').attr('content'), 'YYYY-MM-DD HH:mm:ss'), 8) : item.pubDate; + item.category = $('.OwnerDept font') + .toArray() + .map((item) => $(item).text().trim()); + + return item; + }) + ) + ); + + ctx.state.data = { + title: '苏州市政府 - 政策公开文件', + link, + item: items, + }; +}; diff --git a/lib/v2/gov/suzhou/fg.js b/lib/v2/gov/suzhou/fg.js new file mode 100644 index 00000000000000..5e2d7b94173919 --- /dev/null +++ b/lib/v2/gov/suzhou/fg.js @@ -0,0 +1,62 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const timezone = require('@/utils/timezone'); +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + const { category = 'szfgw/ggl/nav_list' } = ctx.params; + const limit = ctx.query.limit ? parseInt(ctx.query.limit, 10) : 30; + + const rootUrl = 'https://fg.suzhou.gov.cn'; + const currentUrl = new URL(`${category}.shtml`, rootUrl).href; + + const { data: response } = await got(currentUrl); + + const $ = cheerio.load(response); + + let items = $('h4 a[title]') + .slice(0, limit) + .toArray() + .map((item) => { + item = $(item); + + return { + title: item.prop('title') || item.text(), + link: new URL(item.prop('href'), rootUrl).href, + author: item.find('.author').text(), + pubDate: parseDate(item.parent().find('span.time').text().trim()), + }; + }); + + 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); + + item.title = content('ucaptitle').text().trim(); + item.description = content('ucapcontent').html(); + item.author = content('span.ly b').text().trim(); + item.pubDate = timezone(parseDate(content('meta[name="PubDate"]').prop('content')), +8); + + return item; + }) + ) + ); + + const author = $('meta[name="SiteName"]').prop('content'); + const subtitle = $('meta[name="ColumnName"]').prop('content'); + const image = new URL($('div.logo img').prop('src'), rootUrl).href; + + ctx.state.data = { + item: items, + title: `${author} - ${subtitle}`, + link: currentUrl, + description: $('meta[name="ColumnDescription"]').prop('content'), + language: $('html').prop('lang'), + image, + subtitle, + author, + }; +}; diff --git a/lib/v2/gov/suzhou/news.js b/lib/v2/gov/suzhou/news.js new file mode 100644 index 00000000000000..2db427155543b9 --- /dev/null +++ b/lib/v2/gov/suzhou/news.js @@ -0,0 +1,116 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); +const timezone = require('@/utils/timezone'); + +module.exports = async (ctx) => { + const rootUrl = 'https://www.suzhou.gov.cn'; + const uid = ctx.params.uid; + let url = ''; + let title = ''; + let apiUrl = ''; + let items = []; + switch (uid) { + case 'szyw': + case 'news': + apiUrl = `${rootUrl}/szinf/info/getInfoCommon/?pagesize=15&currpage=1&channelid=5057aeffb1a84a7e8aeded87728da48c`; + url = `${rootUrl}/szsrmzf/szyw/nav_list.shtml`; + title = '苏州市政府 - 苏州要闻'; + break; + case 'qxkx': + case 'district': + apiUrl = `${rootUrl}/szinf/info/getInfoCommon/?pagesize=15&currpage=1&channelid=75c636ea0efb487ea7e479e3cc0ff3e5`; + url = `${rootUrl}/szsrmzf/qxkx/nav_list.shtml`; + title = '苏州市政府 - 区县快讯'; + break; + case 'bmdt': + apiUrl = `${rootUrl}/szinf/info/getInfoCommon/?pagesize=15&currpage=1&channelid=b3d097e3eb79421f88439ea381ce33c3`; + url = `${rootUrl}/szsrmzf/bmdt/nav_list.shtml`; + title = '苏州市政府 - 部门动态'; + break; + case 'xwsp': + apiUrl = `${rootUrl}/szinf/info/getInfoCommon/?pagesize=15&currpage=1&channelid=507980d214c943ebb0a70853ec94b12e`; + url = `${rootUrl}/szsrmzf/xwsp/nav_list.shtml`; + title = '苏州市政府 - 新闻视频'; + break; + case 'rdzt': + url = `${rootUrl}/szsrmzf/rdzt/nav_list.shtml`; + title = '苏州市政府 - 热点专题'; + break; + case 'sbjzt': + url = `${rootUrl}/szsrmzf/sbjzt/nav_list.shtml`; + title = '苏州市政府 - 市本级专题'; + break; + case 'zxrdzt': + url = `${rootUrl}/szsrmzf/zxrdzt/nav_list.shtml`; + title = '苏州市政府 - 最新热点专题'; + break; + case 'wqzt': + url = `${rootUrl}/szsrmzf/wqzt/nav_list.shtml`; + title = '苏州市政府 - 往期专题'; + break; + case 'qxzt': + url = `${rootUrl}/szsrmzf/qxzt/nav_list.shtml`; + title = '苏州市政府 - 区县专题'; + break; + case 'zwgg': + apiUrl = `${rootUrl}/szinf/info/getInfoCommon/?pagesize=15&currpage=1&channelid=260915178a1f4c4fac44c4bf6378c9b0`; + url = `${rootUrl}/szsrmzf/zwgg/nav_list.shtml`; + title = '苏州市政府 - 政务公告'; + break; + case 'mszx': + apiUrl = `${rootUrl}/szinf/info/getInfoCommon/?pagesize=15&currpage=1&channelid=dc60acecb0be46b89d42272dcb8bd32b`; + url = `${rootUrl}/szsrmzf/mszx/nav_list.shtml`; + title = '苏州市政府 - 便民公告'; + break; + case 'bmzx': + apiUrl = `${rootUrl}/szinf/info/getInfoCommon/?pagesize=15&currpage=1&channelid=b015bfa5e5514cc9a26cd9f956ef8e69`; + url = `${rootUrl}/szsrmzf/bmzx/bmzx_list.shtml`; + title = '苏州市政府 - 民生资讯'; + break; + default: + throw 'pattern not matched'; + } + if (apiUrl) { + const response = await got(apiUrl); + const infoList = response.data.infolist.map((item) => ({ + title: item.title, + link: item.link.startsWith('http') ? item.link : new URL(item.link, rootUrl).href, + pubDate: timezone(parseDate(item.pubtime, 'YYYY-MM-DD HH:mm:ss'), 8), + })); + + items = await Promise.all( + infoList.map((item) => + // 获取全文 + ctx.cache.tryGet(item.link, async () => { + const response = await got(item.link); + const $ = cheerio.load(response.data); + item.description = $('ucapcontent').html(); + + return item; + }) + ) + ); + } else { + const response = await got(url); + + const $ = cheerio.load(response.data); + items = $('ul.infolist li') + .toArray() + .map((item) => { + item = $(item); + const a = item.find('a'); + return { + title: a.attr('title'), + link: new URL(a.attr('href'), rootUrl).href, + pubDate: timezone(parseDate(item.find('.time').text(), 'YYYY-MM-DD'), 8), + }; + }); + } + + ctx.state.data = { + title, + link: url, + item: items, + }; +}; diff --git a/lib/v2/luogu/daily.js b/lib/v2/luogu/daily.js index 39b558b4faa91a..4e8afb5a3efc7c 100644 --- a/lib/v2/luogu/daily.js +++ b/lib/v2/luogu/daily.js @@ -9,19 +9,24 @@ module.exports = async (ctx) => { const $ = cheerio.load(response.data); const title = $('head title').text(); - const firstPost = $('.am-comment-main .am-comment-bd').first(); - const dailyLink = firstPost.find('a').first().attr('href'); - const issueHeading = firstPost.find('h1').text().trim(); + const injectionScript = $('head script:contains("window._feInjection")').text(); + const jsonRaw = injectionScript.match(/window\._feInjection = JSON\.parse\(decodeURIComponent\("(.*?)"\)\);/)[1]; + const jsonDecode = JSON.parse(decodeURIComponent(jsonRaw)); + + const mdRaw = jsonDecode.currentData.post.content; + + const dailyLink = mdRaw.match(/<([^>]*)>/)[1]; const { data: dailyResponse } = await got(dailyLink); const $daily = cheerio.load(dailyResponse); + const issueHeading = $daily('.am-article-title').first().text().trim(); const item = [ { title, description: $daily('#article-content').html(), link, - author: firstPost.find('p').eq(1).text(), + author: jsonDecode.currentData.post.author.name, guid: `${link}#${issueHeading}`, - pubDate: parseDate(issueHeading.match(/(\d{4} 年 \d{1,2} 月 \d{1,2} 日)/)[1], 'YYYY 年 M 月 D 日'), + pubDate: parseDate(jsonDecode.currentData.post.time), }, ]; diff --git a/lib/v2/pornhub/utils.js b/lib/v2/pornhub/utils.js index 10d4af2b4663c3..1dc21ff9a540d6 100644 --- a/lib/v2/pornhub/utils.js +++ b/lib/v2/pornhub/utils.js @@ -1,6 +1,7 @@ const { art } = require('@/utils/render'); const { join } = require('path'); const { parseRelativeDate } = require('@/utils/parse-date'); +const dayjs = require('dayjs'); const defaultDomain = 'https://www.pornhub.com'; @@ -10,6 +11,10 @@ const headers = { }; const renderDescription = (data) => art(join(__dirname, 'templates/description.art'), data); +const extractDateFromImageUrl = (imageUrl) => { + const matchResult = imageUrl.match(/(\d{6})\/(\d{2})/); + return matchResult ? matchResult.slice(1, 3).join('') : null; +}; const parseItems = (e) => ({ title: e.find('span.title a').text().trim(), @@ -19,7 +24,7 @@ const parseItems = (e) => ({ previewVideo: e.find('img').data('mediabook'), }), author: e.find('.usernameWrap a').text(), - pubDate: parseRelativeDate(e.find('.added').text()), + pubDate: dayjs(extractDateFromImageUrl(e.find('img').data('mediumthumb'))) || parseRelativeDate(e.find('.added').text()), }); module.exports = { diff --git a/lib/v2/tencent/news/author.js b/lib/v2/tencent/news/author.js index 6921419346e3b3..d5fb5bb1605665 100644 --- a/lib/v2/tencent/news/author.js +++ b/lib/v2/tencent/news/author.js @@ -1,8 +1,9 @@ const got = require('@/utils/got'); const cheerio = require('cheerio'); const { parseDate } = require('@/utils/parse-date'); -const timezone = require('@/utils/timezone'); const config = require('@/config').value; +const { art } = require('@/utils/render'); +const { join } = require('path'); module.exports = async (ctx) => { const mid = ctx.params.mid; @@ -18,7 +19,7 @@ module.exports = async (ctx) => { const items = await Promise.all( news.map((item) => { const title = item.title; - const pubDate = timezone(parseDate(item.time), +8); + const pubDate = parseDate(item.timestamp, 'X'); const itemUrl = item.url; const author = item.source; const abstract = item.abstract; @@ -26,11 +27,26 @@ module.exports = async (ctx) => { return ctx.cache.tryGet(itemUrl, async () => { const response = await got(itemUrl); const $ = cheerio.load(response.data); - const article = $('#ArticleContent'); + const data = JSON.parse( + $('script:contains("window.DATA")') + .text() + .match(/window\.DATA = (\{.+\});/)[1] + ); + const $data = cheerio.load(data.originContent.text, null, false); + + $data('*') + .contents() + .filter((_, elem) => elem.type === 'comment') + .replaceWith((_, elem) => + art(join(__dirname, '../templates/news/image.art'), { + attribute: elem.data.trim(), + originAttribute: data.originAttribute, + }) + ); return { title, - description: article.html() || abstract, + description: $data.html() || abstract, link: itemUrl, author, pubDate, diff --git a/lib/v2/tencent/templates/news/image.art b/lib/v2/tencent/templates/news/image.art new file mode 100644 index 00000000000000..04120bbbfe2694 --- /dev/null +++ b/lib/v2/tencent/templates/news/image.art @@ -0,0 +1,4 @@ +{{ if attribute?.startsWith('IMG') && originAttribute[attribute] }} + {{ set image = originAttribute[attribute] }} + +{{ /if }} diff --git a/lib/v2/tradingview/desktop.js b/lib/v2/tradingview/desktop.js new file mode 100644 index 00000000000000..e894fae40428a4 --- /dev/null +++ b/lib/v2/tradingview/desktop.js @@ -0,0 +1,61 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const timezone = require('@/utils/timezone'); +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + const limit = ctx.query.limit ? parseInt(ctx.query.limit, 10) : 50; + + const rootUrl = 'https://www.tradingview.com'; + const currentUrl = new URL('/support/solutions/43000673888-tradingview-desktop-releases-and-release-notes/', rootUrl).href; + + const { data: response } = await got(currentUrl); + + const $ = cheerio.load(response); + + $('h4[data-identifyelement]').each((_, el) => { + el = $(el); + + if (el.text().trim() === '') { + el.remove(); + } + }); + + const items = $('h4[data-identifyelement]') + .toArray() + .slice(0, limit) + .map((item) => { + item = $(item); + + const title = item.text(); + const description = $.html(item.nextUntil('h4')); + const content = cheerio.load(description); + + return { + title, + link: currentUrl, + description, + category: content('h5') + .toArray() + .map((c) => $(c).text()), + guid: `tradingview-desktop#${title.split(/versions?\s/).pop()}`, + pubDate: timezone(parseDate(title.split(/\./)[0], 'MMMM D, YYYY'), +8), + }; + }); + + const title = $('title').text(); + const titleSplits = title.split(/—/); + const icon = new URL($('link[rel="icon"]').prop('href'), rootUrl).href; + + ctx.state.data = { + item: items, + title, + link: currentUrl, + description: titleSplits[0], + language: $('html').prop('lang'), + icon, + logo: icon, + subtitle: titleSplits[0], + author: titleSplits.pop(), + }; +}; diff --git a/lib/v2/tradingview/maintainer.js b/lib/v2/tradingview/maintainer.js index cb9d37b3a3a949..10e854fa3281b6 100644 --- a/lib/v2/tradingview/maintainer.js +++ b/lib/v2/tradingview/maintainer.js @@ -1,3 +1,4 @@ module.exports = { '/blog/:language?/category/:category?': ['nczitzk'], + '/desktop': ['nczitzk'], }; diff --git a/lib/v2/tradingview/radar.js b/lib/v2/tradingview/radar.js index 642caa3f60af72..04884af51e58a4 100644 --- a/lib/v2/tradingview/radar.js +++ b/lib/v2/tradingview/radar.js @@ -92,6 +92,12 @@ module.exports = { source: ['/blog/:language/category/widgets/'], target: '/tradingview/blog/:language/category/widgets', }, + { + title: 'Desktop releases and release notes', + docs: 'https://docs.rsshub.app/routes/program-update#tradingview-desktop-releases-and-release-notes', + source: ['/support/solutions/43000673888-tradingview-desktop-releases-and-release-notes/'], + target: '/tradingview/desktop', + }, ], }, }; diff --git a/lib/v2/tradingview/router.js b/lib/v2/tradingview/router.js index 98bb22bf2c0d0a..a985e2dcc14ac0 100644 --- a/lib/v2/tradingview/router.js +++ b/lib/v2/tradingview/router.js @@ -1,3 +1,4 @@ module.exports = function (router) { router.get('/blog/:category*', require('./blog')); + router.get('/desktop', require('./desktop')); }; diff --git a/lib/v2/twitch/schedule.js b/lib/v2/twitch/schedule.js index 21345e196f5409..b393fc8fdeddc4 100644 --- a/lib/v2/twitch/schedule.js +++ b/lib/v2/twitch/schedule.js @@ -57,7 +57,8 @@ module.exports = async (ctx) => { const displayName = channelShellData.userOrError.displayName; - const out = streamScheduleData.user.channel.schedule.segments.map((item) => ({ + // schedule segments may be null + const out = streamScheduleData.user.channel.schedule.segments?.map((item) => ({ title: item.title, guid: item.id, link: `https://www.twitch.tv/${login}`, diff --git a/lib/v2/twitch/video.js b/lib/v2/twitch/video.js index d552077d98c815..a29c5443fefdc4 100644 --- a/lib/v2/twitch/video.js +++ b/lib/v2/twitch/video.js @@ -4,14 +4,18 @@ const { parseDate } = require('@/utils/parse-date'); // https://github.com/streamlink/streamlink/blob/master/src/streamlink/plugins/twitch.py#L286 const TWITCH_CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; -const FILTER_CURSOR_MAP = { - archive: 0, - highlights: 1, - all: 2, +const FILTER_NODE_TYPE_MAP = { + archive: 'LATEST_BROADCASTS', + highlights: 'LATEST_NON_BROADCASTS', + all: 'ALL_VIDEOS', }; module.exports = async (ctx) => { - const { login, filter = 'all' } = ctx.params; + const login = ctx.params.login; + const filter = ctx.params.filter?.toLowerCase() || 'all'; + if (!FILTER_NODE_TYPE_MAP[filter]) { + throw Error(`Unsupported filter type "${filter}", please choose from { ${Object.keys(FILTER_NODE_TYPE_MAP).join(', ')} }`); + } const response = await got({ method: 'post', @@ -45,7 +49,10 @@ module.exports = async (ctx) => { const displayName = channelVideoShelvesQueryData.user.displayName; - const videoShelvesEdge = channelVideoShelvesQueryData.user.videoShelves.edges[FILTER_CURSOR_MAP[filter] || FILTER_CURSOR_MAP.all]; + const videoShelvesEdge = channelVideoShelvesQueryData.user.videoShelves.edges.find((edge) => edge.node.type === FILTER_NODE_TYPE_MAP[filter]); + if (!videoShelvesEdge) { + throw Error(`No video under filter type "${filter}"`); + } const out = videoShelvesEdge.node.items.map((item) => ({ title: item.title, @@ -53,7 +60,7 @@ module.exports = async (ctx) => { author: displayName, pubDate: parseDate(item.publishedAt), description: `
`, - category: [item.game.displayName], + category: item.game && [item.game.displayName], // item.game may be null })); ctx.state.data = { diff --git a/website/docs/routes/government.mdx b/website/docs/routes/government.mdx index 9cd45d80a1b452..bb9ae8a4f0adb6 100644 --- a/website/docs/routes/government.mdx +++ b/website/docs/routes/government.mdx @@ -1080,7 +1080,7 @@ ### 政府新闻 {#su-zhou-shi-ren-min-zheng-fu-zheng-fu-xin-wen} - + | 新闻栏目名 | :uid | | :--------: | :--------------: | | 苏州要闻 | news 或 szyw | @@ -1110,7 +1110,15 @@ ### 政府信息公开文件 {#su-zhou-shi-ren-min-zheng-fu-zheng-fu-xin-xi-gong-kai-wen-jian} - + + +### 苏州市发展和改革委员会 {#su-zhou-shi-ren-min-zheng-fu-su-zhou-shi-fa-zhan-he-gai-ge-wei-yuan-hui} + + + | 通知公告 | 发改要闻 | + | ------------------- | -------------------- | + | szfgw/ggl/nav\_list | szfgw/gzdt/nav\_list | + ## 台湾行政院消费者保护会 {#tai-wan-xing-zheng-yuan-xiao-fei-zhe-bao-hu-hui} diff --git a/website/docs/routes/live.mdx b/website/docs/routes/live.mdx index 1e92b84d4f6ef7..7cf6de50cc588d 100644 --- a/website/docs/routes/live.mdx +++ b/website/docs/routes/live.mdx @@ -18,7 +18,7 @@ ### Channel Video {#twitch-channel-video} - + | archive | highlights | all | | ----------------- | ----------------------------- | ---------- | diff --git a/website/docs/routes/program-update.mdx b/website/docs/routes/program-update.mdx index ccf012212ff8e0..a03328077f5e5f 100644 --- a/website/docs/routes/program-update.mdx +++ b/website/docs/routes/program-update.mdx @@ -616,6 +616,10 @@ Logseq 开发团队已经放弃了 [旧网站](https://logseq.com/blog)。 | [Widgets](https://www.tradingview.com/blog/en/category/widgets/) | category/widgets | +### Desktop releases and release notes {#tradingview-desktop-releases-and-release-notes} + + + ## Typora {#typora} ### Changelog {#typora-changelog}