mirror of
https://github.com/Jerryplusy/rc-plugin.git
synced 2025-10-14 08:09:19 +00:00
✨ feat: 适配 dy 短链接直播 && 增加直播切片自定义时长 [beta]
This commit is contained in:
parent
82b80c5a09
commit
6e1fada26a
@ -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("获取失败");
|
||||||
}
|
}
|
||||||
|
@ -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分钟),单位:秒
|
||||||
|
@ -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>更新插件,
|
||||||
|
@ -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}
|
||||||
|
@ -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
36
utils/tiktok.js
Normal 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("");
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user