From 07819def986ba081751b9f3d7d439a67dff74f99 Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 29 Jun 2022 03:46:21 +0500 Subject: [PATCH] feat(route): douyin live (#10060) * feat(route): douyin live * fix: add guid * fix: typo * docs: fix path --- docs/live.md | 6 ++++ docs/social-media.md | 4 +++ lib/v2/douyin/hashtag.js | 44 +------------------------- lib/v2/douyin/live.js | 68 ++++++++++++++++++++++++++++++++++++++++ lib/v2/douyin/radar.js | 8 +++++ lib/v2/douyin/router.js | 1 + lib/v2/douyin/user.js | 44 +------------------------- lib/v2/douyin/utils.js | 50 +++++++++++++++++++++++++++++ 8 files changed, 139 insertions(+), 86 deletions(-) create mode 100644 lib/v2/douyin/live.js create mode 100644 lib/v2/douyin/utils.js diff --git a/docs/live.md b/docs/live.md index 2a289d5da..1738d1ad0 100644 --- a/docs/live.md +++ b/docs/live.md @@ -32,6 +32,12 @@ pageClass: routes +## 抖音直播 + +### 直播间开播 + + + ## 斗鱼直播 ### 直播间开播 diff --git a/docs/social-media.md b/docs/social-media.md index b62b654e1..5fd4d21c0 100644 --- a/docs/social-media.md +++ b/docs/social-media.md @@ -875,6 +875,10 @@ YouTube 官方亦有提供频道 RSS,形如 +### 直播 + +见 [#抖音直播](/live.html#dou-yin-zhi-bo) + ## 豆瓣 ### 正在上映的电影 diff --git a/lib/v2/douyin/hashtag.js b/lib/v2/douyin/hashtag.js index b3d325bca..576a8c08c 100644 --- a/lib/v2/douyin/hashtag.js +++ b/lib/v2/douyin/hashtag.js @@ -1,51 +1,9 @@ const cheerio = require('cheerio'); const { parseDate } = require('@/utils/parse-date'); const { art } = require('@/utils/render'); -const path = require('path'); const config = require('@/config').value; const { fallback, queryToBoolean } = require('@/utils/readable-social'); - -const templates = { - desc: path.join(__dirname, 'templates/desc.art'), - cover: path.join(__dirname, 'templates/cover.art'), - embed: path.join(__dirname, 'templates/embed.art'), - iframe: path.join(__dirname, 'templates/iframe.art'), -}; - -const resolveUrl = (url, tls = true, forceResolve = false) => { - if (!url) { - return ''; - } - if (url.startsWith('//')) { - return (tls ? 'https:' : 'http:') + url; - } - if (forceResolve && !url.match(/^https?:\/\//)) { - return (tls ? 'https://' : 'http://') + url; - } - return url; -}; - -const proxyVideo = (url, proxy) => { - if (!(url && proxy)) { - return url + ''; - } - if (proxy.includes('?')) { - if (!proxy.endsWith('=')) { - proxy += '='; - } - return proxy + encodeURIComponent(url); - } else { - if (!proxy.endsWith('/')) { - proxy += '/'; - } - return proxy + url; - } -}; - -const getOriginAvatar = (url) => - resolveUrl(url) - .replace(/^(.*\.douyinpic\.com\/).*(\/aweme-avatar\/)([^?]*)(\?.*)?$/, '$1origin$2$3') - .replace(/~\w+_\d+x\d+/g, ''); +const { templates, resolveUrl, proxyVideo, getOriginAvatar } = require('./utils'); module.exports = async (ctx) => { const { cid } = ctx.params; diff --git a/lib/v2/douyin/live.js b/lib/v2/douyin/live.js new file mode 100644 index 000000000..b3a139d7a --- /dev/null +++ b/lib/v2/douyin/live.js @@ -0,0 +1,68 @@ +const config = require('@/config').value; +const { getOriginAvatar } = require('./utils'); + +module.exports = async (ctx) => { + const { rid } = ctx.params; + if (isNaN(rid)) { + throw Error('Invalid room ID. Room ID should be a number.'); + } + + const pageUrl = `https://live.douyin.com/${rid}`; + + const renderData = await ctx.cache.tryGet( + `douyin:live:${rid}`, + async () => { + const browser = await require('@/utils/puppeteer')(); + const page = await browser.newPage(); + await page.setRequestInterception(true); + page.on('request', (request) => { + request.resourceType() === 'document' ? request.continue() : request.abort(); + }); + await page.goto(pageUrl, { + waitUntil: 'domcontentloaded', + }); + const data = await page.evaluate(() => renderData); + browser.close(); + + return data; + }, + config.cache.routeExpire, + false + ); + + if (renderData.routeInitialProps.status_code !== '0') { + throw `Status code ${renderData.routeInitialProps.status_code}: ${renderData.routeInitialProps.error.title}`; + } + + const roomInfo = renderData.initialState.roomStore.roomInfo; + const nickname = roomInfo.anchor.nickname; + const userAvatar = roomInfo.anchor.avatar_thumb.url_list[0]; + + const items = []; + if (roomInfo.roomId) { + if (roomInfo.room.status === 2) { + items.push({ + title: `开播:${roomInfo.room.title}`, + link: pageUrl, + author: nickname, + guid: roomInfo.roomId, // roomId is unique for each live event + }); + } else if (roomInfo.room.status === 4) { + items.push({ + title: `当前直播已结束,期待下一场:${roomInfo.room.title}`, + link: `https://www.douyin.com/user/${roomInfo.anchor.sec_uid}`, + author: nickname, + guid: roomInfo.roomId, + }); + } + } + + ctx.state.data = { + title: `${nickname}的抖音直播间 - 抖音直播`, + description: `欢迎来到${nickname}的抖音直播间,${nickname}与大家一起记录美好生活 - 抖音直播`, + image: getOriginAvatar(userAvatar), + link: pageUrl, + item: items, + allowEmpty: true, + }; +}; diff --git a/lib/v2/douyin/radar.js b/lib/v2/douyin/radar.js index 9f438c9d2..af92f6938 100644 --- a/lib/v2/douyin/radar.js +++ b/lib/v2/douyin/radar.js @@ -15,5 +15,13 @@ module.exports = { target: '/douyin/user/:uid', }, ], + live: [ + { + title: '直播间开播', + docs: 'https://docs.rsshub.app/live.html#dou-yin-zhi-bo', + source: '/:rid', + target: '/douyin/live/:rid', + }, + ], }, }; diff --git a/lib/v2/douyin/router.js b/lib/v2/douyin/router.js index 6faf82f43..c7cfd8837 100644 --- a/lib/v2/douyin/router.js +++ b/lib/v2/douyin/router.js @@ -1,4 +1,5 @@ module.exports = function (router) { router.get('/hashtag/:cid/:routeParams?', require('./hashtag')); + router.get('/live/:rid', require('./live')); router.get('/user/:uid/:routeParams?', require('./user')); }; diff --git a/lib/v2/douyin/user.js b/lib/v2/douyin/user.js index fccae1a84..6dd7d7fd5 100644 --- a/lib/v2/douyin/user.js +++ b/lib/v2/douyin/user.js @@ -1,51 +1,9 @@ const cheerio = require('cheerio'); const { parseDate } = require('@/utils/parse-date'); const { art } = require('@/utils/render'); -const path = require('path'); const config = require('@/config').value; const { fallback, queryToBoolean } = require('@/utils/readable-social'); - -const templates = { - desc: path.join(__dirname, 'templates/desc.art'), - cover: path.join(__dirname, 'templates/cover.art'), - embed: path.join(__dirname, 'templates/embed.art'), - iframe: path.join(__dirname, 'templates/iframe.art'), -}; - -const resolveUrl = (url, tls = true, forceResolve = false) => { - if (!url) { - return ''; - } - if (url.startsWith('//')) { - return (tls ? 'https:' : 'http:') + url; - } - if (forceResolve && !url.match(/^https?:\/\//)) { - return (tls ? 'https://' : 'http://') + url; - } - return url; -}; - -const proxyVideo = (url, proxy) => { - if (!(url && proxy)) { - return url + ''; - } - if (proxy.includes('?')) { - if (!proxy.endsWith('=')) { - proxy += '='; - } - return proxy + encodeURIComponent(url); - } else { - if (!proxy.endsWith('/')) { - proxy += '/'; - } - return proxy + url; - } -}; - -const getOriginAvatar = (url) => - resolveUrl(url) - .replace(/^(.*\.douyinpic\.com\/).*(\/aweme-avatar\/)([^?]*)(\?.*)?$/, '$1origin$2$3') - .replace(/~\w+_\d+x\d+/g, ''); +const { templates, resolveUrl, proxyVideo, getOriginAvatar } = require('./utils'); module.exports = async (ctx) => { const uid = ctx.params.uid; diff --git a/lib/v2/douyin/utils.js b/lib/v2/douyin/utils.js new file mode 100644 index 000000000..8725f7dbd --- /dev/null +++ b/lib/v2/douyin/utils.js @@ -0,0 +1,50 @@ +const path = require('path'); + +const templates = { + desc: path.join(__dirname, 'templates/desc.art'), + cover: path.join(__dirname, 'templates/cover.art'), + embed: path.join(__dirname, 'templates/embed.art'), + iframe: path.join(__dirname, 'templates/iframe.art'), +}; + +const resolveUrl = (url, tls = true, forceResolve = false) => { + if (!url) { + return ''; + } + if (url.startsWith('//')) { + return (tls ? 'https:' : 'http:') + url; + } + if (forceResolve && !url.match(/^https?:\/\//)) { + return (tls ? 'https://' : 'http://') + url; + } + return url; +}; + +const proxyVideo = (url, proxy) => { + if (!(url && proxy)) { + return url + ''; + } + if (proxy.includes('?')) { + if (!proxy.endsWith('=')) { + proxy += '='; + } + return proxy + encodeURIComponent(url); + } else { + if (!proxy.endsWith('/')) { + proxy += '/'; + } + return proxy + url; + } +}; + +const getOriginAvatar = (url) => + resolveUrl(url) + .replace(/^(.*\.douyinpic\.com\/).*(\/aweme-avatar\/)([^?]*)(\?.*)?$/, '$1origin$2$3') + .replace(/~\w+_\d+x\d+/g, ''); + +module.exports = { + templates, + resolveUrl, + proxyVideo, + getOriginAvatar, +};