diff --git a/apps/tools.js b/apps/tools.js
index 2503bf9..b624a64 100644
--- a/apps/tools.js
+++ b/apps/tools.js
@@ -36,7 +36,7 @@ import {
BILI_SUMMARY,
DY_COMMENT,
DY_INFO,
- DY_LIVE_INFO,
+ DY_LIVE_INFO, DY_LIVE_INFO_2,
DY_TOUTIAO_INFO,
GENERAL_REQ_LINK,
HIBI_API_SERVICE,
@@ -77,7 +77,7 @@ import {
downloadImg,
estimateReadingTime,
formatBiliInfo,
- retryAxiosReq,
+ retryAxiosReq, saveJsonToFile,
secondsToTime,
testProxy,
truncateString,
@@ -99,6 +99,7 @@ import { getDS } from "../utils/mihoyo.js";
import { OpenaiBuilder } from "../utils/openai-builder.js";
import { redisExistKey, redisGetKey, redisSetKey } from "../utils/redis-util.js";
import { saveTDL, startTDL } from "../utils/tdl-util.js";
+import { genVerifyFp } from "../utils/tiktok.js";
import Translate from "../utils/trans-strategy.js";
import { mid2id } from "../utils/weibo.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.identifyPrefix = this.toolsConfig.identifyPrefix;
+ // 加载直播录制时长
+ this.streamDuration = this.toolsConfig.streamDuration;
// 加载哔哩哔哩配置
this.biliSessData = this.toolsConfig.biliSessData;
// 加载哔哩哔哩的限制时长
@@ -317,14 +320,18 @@ export class tools extends plugin {
}
// 获取链接
let douUrl = urlRex.exec(e.msg.trim())[0];
+ let ttwid = '';
if (douUrl.includes("v.douyin.com")) {
- douUrl = await this.douyinRequest(douUrl)
+ const { location, ttwidValue } = await this.douyinRequest(douUrl);
+ ttwid = ttwidValue;
+ douUrl = location
}
// 获取 ID
const douId = /note\/(\d+)/g.exec(douUrl)?.[1] ||
/video\/(\d+)/g.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
if (_.isEmpty(this.douyinCookie) || _.isEmpty(douId)) {
e.reply(`检测到没有Cookie 或者 这是一个无效链接,无法解析抖音${ HELP_DOC }`);
@@ -340,7 +347,28 @@ export class tools extends plugin {
Referer: "https://www.douyin.com/",
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参数
const abParam = aBogus.generate_a_bogus(
new URLSearchParams(new URL(dyApi).search).toString(),
@@ -355,7 +383,7 @@ export class tools extends plugin {
});
// 如果失败进行3次重试
try {
- const data = await retryAxiosReq(dyResponse)
+ const data = await retryAxiosReq(dyResponse);
// saveJsonToFile(data);
// 直播数据逻辑
if (douUrl.includes("live")) {
@@ -364,7 +392,7 @@ export class tools extends plugin {
const dySendContent = `${ this.identifyPrefix }识别:抖音直播,${ title }`
e.reply([segment.image(cover?.url_list?.[0]), dySendContent, `\n🏄♂️在线人数:${ user_count_str }人正在观看`]);
// 下载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;
}
const item = await data.aweme_detail;
@@ -458,7 +486,7 @@ export class tools extends plugin {
* @param stream_url
* @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`;
await checkAndRemoveFile(outputFilePath);
const file = fs.createWriteStream(outputFilePath);
@@ -471,13 +499,13 @@ export class tools extends plugin {
// 设置 10 秒后停止下载
setTimeout(() => {
- logger.info('[R插件][发送直播流] 直播下载10秒钟到,停止下载!');
+ logger.info(`[R插件][发送直播流] 直播下载 ${this.streamDuration} 秒钟到,停止下载!`);
response.data.destroy(); // 销毁流
e.reply(segment.video(outputFilePath));
file.close(); // 关闭文件流
}, second * 1000); // 10秒 = 10000毫秒
}).catch(error => {
- console.error('下载失败:', error.message);
+ logger.error(`下载失败:${ error.message }`);
fs.unlink(outputFilePath, () => {
}); // 下载失败时删除文件
});
@@ -2160,11 +2188,28 @@ export class tools extends plugin {
timeout: 10000,
};
try {
- const resp = await axios.head(url, params);
+ const resp = await axios.get(url, params);
+
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) => {
if (location != null) {
- return resolve(location);
+ return resolve({
+ location: location,
+ ttwidValue: ttwidValue
+ });
} else {
return reject("获取失败");
}
diff --git a/config/tools.yaml b/config/tools.yaml
index 9891ba7..cc46cfe 100644
--- a/config/tools.yaml
+++ b/config/tools.yaml
@@ -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'
+streamDuration: 10 # 视频最大时长(单位秒)
+
biliSessData: '' # 哔哩哔哩的SESSDATA
biliIntroLenLimit: 50 # 哔哩哔哩简介长度限制,填 0 或者 -1 可以不做任何限制,显示完整简介
biliDuration: 480 # 哔哩哔哩限制的最大视频时长(默认8分钟),单位:秒
diff --git a/config/version.yaml b/config/version.yaml
index f8e4610..db6d4b3 100644
--- a/config/version.yaml
+++ b/config/version.yaml
@@ -1,11 +1,10 @@
- {
- version: 1.9.0,
+ version: 1.9.1,
data:
[
- 新增rso搜索功能,
+ 新增直播切片功能,
优化队列下载和GPT功能,
新增哔哩哔哩下载分辨率设置功能,
- 新增自定义识别功能,
支持锅巴插件,方便查看和修改配置,
输入#R帮助获取插件帮助,
输入#R更新更新插件,
diff --git a/constants/tools.js b/constants/tools.js
index 91035a4..2f41a7e 100644
--- a/constants/tools.js
+++ b/constants/tools.js
@@ -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={}";
+/**
+ * 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
* @type {string}
diff --git a/guoba.support.js b/guoba.support.js
index 0ef076c..d87e661 100644
--- a/guoba.support.js
+++ b/guoba.support.js
@@ -69,6 +69,17 @@ export function supportGuoba() {
placeholder: "请输入DeeplX API地址集合",
},
},
+ {
+ field: "tools.streamDuration",
+ label: "解析直播时长",
+ bottomHelpMessage:
+ "解析直播(目前涉及哔哩哔哩、抖音)时长,单位:秒(默认:10秒),建议时间为10~60,不然也没人看",
+ component: "InputNumber",
+ required: false,
+ componentProps: {
+ placeholder: "请输入最大解析直播时长",
+ },
+ },
{
field: "tools.defaultPath",
label: "视频暂存位置",
diff --git a/utils/tiktok.js b/utils/tiktok.js
new file mode 100644
index 0000000..7f86e51
--- /dev/null
+++ b/utils/tiktok.js
@@ -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("");
+}
+