feat: 适配 dy 短链接直播 && 增加直播切片自定义时长 [beta]

This commit is contained in:
zhiyu1998 2024-10-01 08:27:36 +08:00
parent 82b80c5a09
commit 6e1fada26a
6 changed files with 114 additions and 15 deletions

View File

@ -36,7 +36,7 @@ import {
BILI_SUMMARY, BILI_SUMMARY,
DY_COMMENT, DY_COMMENT,
DY_INFO, DY_INFO,
DY_LIVE_INFO, DY_LIVE_INFO, DY_LIVE_INFO_2,
DY_TOUTIAO_INFO, DY_TOUTIAO_INFO,
GENERAL_REQ_LINK, GENERAL_REQ_LINK,
HIBI_API_SERVICE, HIBI_API_SERVICE,
@ -77,7 +77,7 @@ import {
downloadImg, downloadImg,
estimateReadingTime, estimateReadingTime,
formatBiliInfo, formatBiliInfo,
retryAxiosReq, retryAxiosReq, saveJsonToFile,
secondsToTime, secondsToTime,
testProxy, testProxy,
truncateString, truncateString,
@ -99,6 +99,7 @@ import { getDS } from "../utils/mihoyo.js";
import { OpenaiBuilder } from "../utils/openai-builder.js"; import { OpenaiBuilder } from "../utils/openai-builder.js";
import { redisExistKey, redisGetKey, redisSetKey } from "../utils/redis-util.js"; import { redisExistKey, redisGetKey, redisSetKey } from "../utils/redis-util.js";
import { saveTDL, startTDL } from "../utils/tdl-util.js"; import { saveTDL, startTDL } from "../utils/tdl-util.js";
import { genVerifyFp } from "../utils/tiktok.js";
import Translate from "../utils/trans-strategy.js"; import Translate from "../utils/trans-strategy.js";
import { mid2id } from "../utils/weibo.js"; import { mid2id } from "../utils/weibo.js";
import { ytDlpGetTilt, ytDlpHelper } from "../utils/yt-dlp-util.js"; import { ytDlpGetTilt, ytDlpHelper } from "../utils/yt-dlp-util.js";
@ -232,6 +233,8 @@ export class tools extends plugin {
this.myProxy = `http://${ this.proxyAddr }:${ this.proxyPort }`; this.myProxy = `http://${ this.proxyAddr }:${ this.proxyPort }`;
// 加载识别前缀 // 加载识别前缀
this.identifyPrefix = this.toolsConfig.identifyPrefix; this.identifyPrefix = this.toolsConfig.identifyPrefix;
// 加载直播录制时长
this.streamDuration = this.toolsConfig.streamDuration;
// 加载哔哩哔哩配置 // 加载哔哩哔哩配置
this.biliSessData = this.toolsConfig.biliSessData; this.biliSessData = this.toolsConfig.biliSessData;
// 加载哔哩哔哩的限制时长 // 加载哔哩哔哩的限制时长
@ -317,14 +320,18 @@ export class tools extends plugin {
} }
// 获取链接 // 获取链接
let douUrl = urlRex.exec(e.msg.trim())[0]; let douUrl = urlRex.exec(e.msg.trim())[0];
let ttwid = '';
if (douUrl.includes("v.douyin.com")) { if (douUrl.includes("v.douyin.com")) {
douUrl = await this.douyinRequest(douUrl) const { location, ttwidValue } = await this.douyinRequest(douUrl);
ttwid = ttwidValue;
douUrl = location
} }
// 获取 ID // 获取 ID
const douId = /note\/(\d+)/g.exec(douUrl)?.[1] || const douId = /note\/(\d+)/g.exec(douUrl)?.[1] ||
/video\/(\d+)/g.exec(douUrl)?.[1] || /video\/(\d+)/g.exec(douUrl)?.[1] ||
/live.douyin.com\/(\d+)/.exec(douUrl)?.[1] || /live.douyin.com\/(\d+)/.exec(douUrl)?.[1] ||
/live\/(\d+)/.exec(douUrl)?.[1]; /live\/(\d+)/.exec(douUrl)?.[1] ||
/webcast.amemv.com\/douyin\/webcast\/reflow\/(\d+)/.exec(douUrl)?.[1];
// 当前版本需要填入cookie // 当前版本需要填入cookie
if (_.isEmpty(this.douyinCookie) || _.isEmpty(douId)) { if (_.isEmpty(this.douyinCookie) || _.isEmpty(douId)) {
e.reply(`检测到没有Cookie 或者 这是一个无效链接,无法解析抖音${ HELP_DOC }`); e.reply(`检测到没有Cookie 或者 这是一个无效链接,无法解析抖音${ HELP_DOC }`);
@ -340,7 +347,28 @@ export class tools extends plugin {
Referer: "https://www.douyin.com/", Referer: "https://www.douyin.com/",
cookie: this.douyinCookie, cookie: this.douyinCookie,
}; };
const dyApi = douUrl.includes("live") ? DY_LIVE_INFO.replaceAll("{}", douId) : DY_INFO.replace("{}", douId); let dyApi;
if (douUrl.includes("live.douyin.com")) {
// 第一类直播类型
dyApi = DY_LIVE_INFO.replaceAll("{}", douId)
} else if (douUrl.includes("webcast.amemv.com")) {
// 第二类直播类型,这里必须使用客户端的 fetch 请求
dyApi = DY_LIVE_INFO_2.replace("{}", douId) + `&verifyFp=${ genVerifyFp() }` + `&msToken=${ ttwid }`;
const webcastResp = await fetch(dyApi);
const webcastData = await webcastResp.json();
const item = webcastData.data.room;
logger.info(item);
const { title, cover, user_count, stream_url } = item;
const dySendContent = `${ this.identifyPrefix }识别:抖音直播,${ title }`
e.reply([segment.image(cover?.url_list?.[0]), dySendContent, `\n🏄‍♂️在线人数:${ user_count }人正在观看`]);
// 下载10s的直播流
await this.sendStreamSegment(e, stream_url?.flv_pull_url?.HD1 || stream_url?.flv_pull_url?.FULL_HD1 || stream_url?.flv_pull_url?.SD1 || stream_url?.flv_pull_url?.SD2);
return;
} else {
// 普通类型
dyApi = DY_INFO.replace("{}", douId);
}
logger.info(dyApi);
// a-bogus参数 // a-bogus参数
const abParam = aBogus.generate_a_bogus( const abParam = aBogus.generate_a_bogus(
new URLSearchParams(new URL(dyApi).search).toString(), new URLSearchParams(new URL(dyApi).search).toString(),
@ -355,7 +383,7 @@ export class tools extends plugin {
}); });
// 如果失败进行3次重试 // 如果失败进行3次重试
try { try {
const data = await retryAxiosReq(dyResponse) const data = await retryAxiosReq(dyResponse);
// saveJsonToFile(data); // saveJsonToFile(data);
// 直播数据逻辑 // 直播数据逻辑
if (douUrl.includes("live")) { if (douUrl.includes("live")) {
@ -364,7 +392,7 @@ export class tools extends plugin {
const dySendContent = `${ this.identifyPrefix }识别:抖音直播,${ title }` const dySendContent = `${ this.identifyPrefix }识别:抖音直播,${ title }`
e.reply([segment.image(cover?.url_list?.[0]), dySendContent, `\n🏄‍♂️在线人数:${ user_count_str }人正在观看`]); e.reply([segment.image(cover?.url_list?.[0]), dySendContent, `\n🏄‍♂️在线人数:${ user_count_str }人正在观看`]);
// 下载10s的直播流 // 下载10s的直播流
await this.sendStreamSegment(e, stream_url?.flv_pull_url?.HD1); await this.sendStreamSegment(e, stream_url?.flv_pull_url?.HD1 || stream_url?.flv_pull_url?.FULL_HD1 || stream_url?.flv_pull_url?.SD1 || stream_url?.flv_pull_url?.SD2);
return; return;
} }
const item = await data.aweme_detail; const item = await data.aweme_detail;
@ -458,7 +486,7 @@ export class tools extends plugin {
* @param stream_url * @param stream_url
* @param second * @param second
*/ */
async sendStreamSegment(e, stream_url, second = 10) { async sendStreamSegment(e, stream_url, second = this.streamDuration) {
const outputFilePath = `${ this.getCurDownloadPath(e) }/stream_10s.flv`; const outputFilePath = `${ this.getCurDownloadPath(e) }/stream_10s.flv`;
await checkAndRemoveFile(outputFilePath); await checkAndRemoveFile(outputFilePath);
const file = fs.createWriteStream(outputFilePath); const file = fs.createWriteStream(outputFilePath);
@ -471,13 +499,13 @@ export class tools extends plugin {
// 设置 10 秒后停止下载 // 设置 10 秒后停止下载
setTimeout(() => { setTimeout(() => {
logger.info('[R插件][发送直播流] 直播下载10秒钟到停止下载'); logger.info(`[R插件][发送直播流] 直播下载 ${this.streamDuration} 秒钟到,停止下载!`);
response.data.destroy(); // 销毁流 response.data.destroy(); // 销毁流
e.reply(segment.video(outputFilePath)); e.reply(segment.video(outputFilePath));
file.close(); // 关闭文件流 file.close(); // 关闭文件流
}, second * 1000); // 10秒 = 10000毫秒 }, second * 1000); // 10秒 = 10000毫秒
}).catch(error => { }).catch(error => {
console.error('下载失败:', error.message); logger.error(`下载失败:${ error.message }`);
fs.unlink(outputFilePath, () => { fs.unlink(outputFilePath, () => {
}); // 下载失败时删除文件 }); // 下载失败时删除文件
}); });
@ -2160,11 +2188,28 @@ export class tools extends plugin {
timeout: 10000, timeout: 10000,
}; };
try { try {
const resp = await axios.head(url, params); const resp = await axios.get(url, params);
const location = resp.request.res.responseUrl; const location = resp.request.res.responseUrl;
const setCookieHeaders = resp.headers['set-cookie']
let ttwidValue;
if (setCookieHeaders) {
setCookieHeaders.forEach(cookie => {
// 使用正则表达式提取 ttwid 的值
const ttwidMatch = cookie.match(/ttwid=([^;]+)/);
if (ttwidMatch) {
ttwidValue = ttwidMatch[1];
}
});
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (location != null) { if (location != null) {
return resolve(location); return resolve({
location: location,
ttwidValue: ttwidValue
});
} else { } else {
return reject("获取失败"); return reject("获取失败");
} }

View File

@ -6,6 +6,8 @@ identifyPrefix: '' # 识别前缀,比如你识别哔哩哔哩,那么就有
deeplApiUrls: 'http://www.gptspt.cn/translate,http://gptspt.top/translate,http://8.134.135.4:1188/translate,http://120.76.141.173:1188/translate,http://bit.x7ys.com:1188/translate,http://deeplxapi.x7ys.com:1188/translate' deeplApiUrls: 'http://www.gptspt.cn/translate,http://gptspt.top/translate,http://8.134.135.4:1188/translate,http://120.76.141.173:1188/translate,http://bit.x7ys.com:1188/translate,http://deeplxapi.x7ys.com:1188/translate'
streamDuration: 10 # 视频最大时长(单位秒)
biliSessData: '' # 哔哩哔哩的SESSDATA biliSessData: '' # 哔哩哔哩的SESSDATA
biliIntroLenLimit: 50 # 哔哩哔哩简介长度限制,填 0 或者 -1 可以不做任何限制,显示完整简介 biliIntroLenLimit: 50 # 哔哩哔哩简介长度限制,填 0 或者 -1 可以不做任何限制,显示完整简介
biliDuration: 480 # 哔哩哔哩限制的最大视频时长默认8分钟单位 biliDuration: 480 # 哔哩哔哩限制的最大视频时长默认8分钟单位

View File

@ -1,11 +1,10 @@
- { - {
version: 1.9.0, version: 1.9.1,
data: data:
[ [
新增<span class="cmd">rso搜索</span>功能, 新增<span class="cmd">直播切片</span>功能,
优化<span class="cmd">队列下载和GPT</span>功能, 优化<span class="cmd">队列下载和GPT</span>功能,
新增<span class="cmd">哔哩哔哩下载分辨率设置</span>功能, 新增<span class="cmd">哔哩哔哩下载分辨率设置</span>功能,
新增<span class="cmd">自定义识别</span>功能,
支持<span class="cmd">锅巴</span>插件,方便查看和修改配置, 支持<span class="cmd">锅巴</span>插件,方便查看和修改配置,
输入<span class="cmd">#R帮助</span>获取插件帮助, 输入<span class="cmd">#R帮助</span>获取插件帮助,
输入<span class="cmd">#R更新</span>更新插件, 输入<span class="cmd">#R更新</span>更新插件,

View File

@ -127,6 +127,12 @@ export const DY_TOUTIAO_INFO = "https://aweme.snssdk.com/aweme/v1/play/?video_id
*/ */
export const DY_LIVE_INFO = "https://live.douyin.com/webcast/room/web/enter/?device_platform=webapp&aid=6383&channel=channel_pc_web&pc_client_type=1&version_code=190500&version_name=19.5.0&cookie_enabled=true&screen_width=1920&screen_height=1080&browser_language=zh-CN&browser_platform=Win32&browser_name=Firefox&browser_version=124.0&browser_online=true&engine_name=Gecko&engine_version=122.0.0.0&os_name=Windows&os_version=10&cpu_core_num=12&device_memory=8&platform=PC&web_rid={}&room_id_str={}"; export const DY_LIVE_INFO = "https://live.douyin.com/webcast/room/web/enter/?device_platform=webapp&aid=6383&channel=channel_pc_web&pc_client_type=1&version_code=190500&version_name=19.5.0&cookie_enabled=true&screen_width=1920&screen_height=1080&browser_language=zh-CN&browser_platform=Win32&browser_name=Firefox&browser_version=124.0&browser_online=true&engine_name=Gecko&engine_version=122.0.0.0&os_name=Windows&os_version=10&cpu_core_num=12&device_memory=8&platform=PC&web_rid={}&room_id_str={}";
/**
* DY 直播信息 二类型
* @type {string}
*/
export const DY_LIVE_INFO_2 = "https://webcast.amemv.com/webcast/room/reflow/info/?type_id=0&live_id=1&sec_user_id=&version_code=99.99.99&app_id=1128&room_id={}";
/** /**
* X API * X API
* @type {string} * @type {string}

View File

@ -69,6 +69,17 @@ export function supportGuoba() {
placeholder: "请输入DeeplX API地址集合", placeholder: "请输入DeeplX API地址集合",
}, },
}, },
{
field: "tools.streamDuration",
label: "解析直播时长",
bottomHelpMessage:
"解析直播目前涉及哔哩哔哩、抖音时长单位默认10秒建议时间为10~60不然也没人看",
component: "InputNumber",
required: false,
componentProps: {
placeholder: "请输入最大解析直播时长",
},
},
{ {
field: "tools.defaultPath", field: "tools.defaultPath",
label: "视频暂存位置", label: "视频暂存位置",

36
utils/tiktok.js Normal file
View File

@ -0,0 +1,36 @@
export function genVerifyFp() {
const baseStr = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const t = baseStr.length;
let milliseconds = Date.now(); // 获取当前的时间戳(毫秒)
let base36 = "";
// 将时间戳转换为base36
while (milliseconds > 0) {
let remainder = milliseconds % 36;
if (remainder < 10) {
base36 = remainder.toString() + base36;
} else {
base36 = String.fromCharCode('a'.charCodeAt(0) + remainder - 10) + base36;
}
milliseconds = Math.floor(milliseconds / 36);
}
const r = base36;
let o = new Array(36).fill("");
o[8] = o[13] = o[18] = o[23] = "_";
o[14] = "4";
// 生成随机字符
for (let i = 0; i < 36; i++) {
if (!o[i]) {
let n = Math.floor(Math.random() * t);
if (i === 19) {
n = (3 & n) | 8;
}
o[i] = baseStr[n];
}
}
return "verify_" + r + "_" + o.join("");
}