feat(route): douyin live (#10060)

* feat(route): douyin live

* fix: add guid

* fix: typo

* docs: fix path
This commit is contained in:
Tony 2022-06-29 03:46:21 +05:00 committed by GitHub
parent a4e677d488
commit 07819def98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 139 additions and 86 deletions

View File

@ -32,6 +32,12 @@ pageClass: routes
</Route>
## 抖音直播
### 直播间开播
<Route author="TonyRL" example="/douyin/live/685317364746" path="/douyin/live/:rid" :paramsDesc="['直播间 id, 可在主播直播间页 URL 中找到']" anticrawler="1" radar="1" rssbud="1" puppeteer="1"/>
## 斗鱼直播
### 直播间开播

View File

@ -875,6 +875,10 @@ YouTube 官方亦有提供频道 RSS形如 <https://www.youtube.com/feeds/vid
<Route author="TonyRL" example="/douyin/hashtag/1592824105719812" path="/douyin/hashtag/:cid/:routeParams?" :paramsDesc="['标签 ID可在标签页面 URL 中找到', '额外参数query string 格式,请参阅上面的表格']" anticrawler="1" radar="1" rssbud="1" puppeteer="1" />
### 直播
见 [#抖音直播](/live.html#dou-yin-zhi-bo)
## 豆瓣
### 正在上映的电影

View File

@ -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;

68
lib/v2/douyin/live.js Normal file
View File

@ -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,
};
};

View File

@ -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',
},
],
},
};

View File

@ -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'));
};

View File

@ -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;

50
lib/v2/douyin/utils.js Normal file
View File

@ -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,
};