feat(route): douyin live (#10060)
* feat(route): douyin live * fix: add guid * fix: typo * docs: fix path
This commit is contained in:
parent
a4e677d488
commit
07819def98
|
|
@ -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"/>
|
||||
|
||||
## 斗鱼直播
|
||||
|
||||
### 直播间开播
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
## 豆瓣
|
||||
|
||||
### 正在上映的电影
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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'));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
Loading…
Reference in New Issue