lintonxue00 commited on
Commit
835aa42
1 Parent(s): 0ce004d

Upload tools.js

Browse files
Files changed (1) hide show
  1. 不知道/回收站/2/tools.js +1249 -0
不知道/回收站/2/tools.js ADDED
@@ -0,0 +1,1249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // 主库
2
+ import fetch from "node-fetch";
3
+ import fs from "node:fs";
4
+ // 其他库
5
+ import axios from "axios";
6
+ import _ from "lodash";
7
+ import tunnel from "tunnel";
8
+ import HttpProxyAgent from "https-proxy-agent";
9
+ import { mkdirIfNotExists, checkAndRemoveFile, deleteFolderRecursive } from "../utils/file.js";
10
+ import { downloadBFile, getDownloadUrl, mergeFileToMp4 } from "../utils/bilibili.js";
11
+ import { parseUrl, parseM3u8, downloadM3u8Videos, mergeAcFileToMp4 } from "../utils/acfun.js";
12
+ import { transMap, douyinTypeMap, XHS_CK, TEN_THOUSAND, PROMPT_MAP } from "../utils/constant.js";
13
+ import { getIdVideo } from "../utils/common.js";
14
+ import config from "../model/index.js";
15
+ import Translate from "../utils/trans-strategy.js";
16
+ import * as xBogus from "../utils/x-bogus.cjs";
17
+ import { getVideoInfo, getDynamic } from "../utils/biliInfo.js";
18
+ import { getBiliGptInputText } from "../utils/biliSummary.js";
19
+ import { getBodianAudio, getBodianMv, getBodianMusicInfo } from "../utils/bodian.js";
20
+ import { ChatGPTBrowserClient } from "@waylaidwanderer/chatgpt-api";
21
+ import { av2BV } from "../utils/bilibili-bv-av-convert.js";
22
+ import querystring from "querystring";
23
+ import TokenBucket from "../utils/token-bucket.js";
24
+
25
+ export class tools extends plugin {
26
+ constructor() {
27
+ super({
28
+ name: "R插件工具和学习类",
29
+ dsc: "R插件工具相关指令",
30
+ event: "message.group",
31
+ priority: 300,
32
+ rule: [
33
+ {
34
+ reg: `^(翻|trans)[${tools.Constants.existsTransKey}]`,
35
+ fnc: "trans",
36
+ },
37
+ {
38
+ reg: `^#(ocr|OCR)(${tools.Constants.existsPromptKey})?$`,
39
+ fnc: "ocr2anything",
40
+ },
41
+ {
42
+ reg: "(v.douyin.com)",
43
+ fnc: "douyin",
44
+ },
45
+ {
46
+ reg: "(www.tiktok.com)|(vt.tiktok.com)|(vm.tiktok.com)",
47
+ fnc: "tiktok",
48
+ },
49
+ {
50
+ reg: "(bilibili.com|b23.tv|t.bilibili.com)",
51
+ fnc: "bili",
52
+ },
53
+ {
54
+ reg: "^#(wiki|百科)(.*)$",
55
+ fnc: "wiki",
56
+ },
57
+ {
58
+ reg: "(twitter.com)",
59
+ fnc: "twitter",
60
+ },
61
+ {
62
+ reg: "(acfun.cn)",
63
+ fnc: "acfun",
64
+ },
65
+ {
66
+ reg: "(xhslink.com|xiaohongshu.com)",
67
+ fnc: "redbook",
68
+ },
69
+ {
70
+ reg: "(instagram.com)",
71
+ fnc: "instagram",
72
+ },
73
+ {
74
+ reg: "(doi.org)",
75
+ fnc: "literature",
76
+ },
77
+ {
78
+ reg: "^#清理data垃圾$",
79
+ fnc: "clearTrash",
80
+ permission: "master",
81
+ },
82
+ {
83
+ reg: "(h5app.kuwo.cn)",
84
+ fnc: "bodianMusic",
85
+ },
86
+ ],
87
+ });
88
+ // 配置文件
89
+ this.toolsConfig = config.getConfig("tools");
90
+ // 视频保存路径
91
+ this.defaultPath = this.toolsConfig.defaultPath;
92
+ // 代理接口
93
+ this.proxyAddr = this.toolsConfig.proxyAddr;
94
+ this.proxyPort = this.toolsConfig.proxyPort;
95
+ this.myProxy = `http://${this.proxyAddr}:${this.proxyPort}`;
96
+ // 加载哔哩哔哩配置
97
+ this.biliSessData = this.toolsConfig.biliSessData;
98
+ // 加载哔哩哔哩的限制时长
99
+ this.biliDuration = this.toolsConfig.biliDuration;
100
+ // 加载gpt配置
101
+ this.openaiAccessToken = this.toolsConfig.openaiAccessToken;
102
+ // 加载gpt客户端
103
+ this.chatGptClient = new ChatGPTBrowserClient({
104
+ reverseProxyUrl: "https://bypass.churchless.tech/api/conversation",
105
+ accessToken: this.openaiAccessToken,
106
+ model: "gpt-3.5-turbo",
107
+ })
108
+ }
109
+
110
+ // 翻译插件
111
+ async trans(e) {
112
+ const languageReg = /翻(.)/s;
113
+ const msg = e.msg.trim();
114
+ const language = languageReg.exec(msg);
115
+ if (!(language[1] in transMap)) {
116
+ e.reply(
117
+ "输入格式有误或暂不支持该语言!\n例子:翻中 China's policy has been consistent, but Japan chooses a path of mistrust, decoupling and military expansion",
118
+ );
119
+ return;
120
+ }
121
+ const place = msg.slice(1 + language[1].length)
122
+ const translateEngine = new Translate({
123
+ translateAppId: this.toolsConfig.translateAppId,
124
+ translateSecret: this.toolsConfig.translateSecret,
125
+ proxy: this.myProxy,
126
+ });
127
+ // 如果没有百度那就Google
128
+ let translateResult;
129
+ if (
130
+ _.isEmpty(this.toolsConfig.translateAppId) ||
131
+ _.isEmpty(this.toolsConfig.translateSecret)
132
+ ) {
133
+ // 腾讯交互式进行补充
134
+ translateResult = await translateEngine.tencent(place, language[1]);
135
+ } else {
136
+ // 如果有百度
137
+ translateResult = await translateEngine.baidu(place, language[1]);
138
+ }
139
+ e.reply(translateResult.trim(), true);
140
+ return true;
141
+ }
142
+
143
+ // 图像识别文字
144
+ async ocr2anything(e) {
145
+ e.reply(" 👀请发送图片")
146
+ this.setContext("ocr2anythingContext");
147
+ return true;
148
+ }
149
+
150
+ /**
151
+ * 图像识别文字核心
152
+ * @link{ocr2anythingContext} 的上下文
153
+ * @return Promise{void}
154
+ **/
155
+ async ocr2anythingContext() {
156
+ // 当前消息
157
+ const curMsg = this.e;
158
+ // 上一个消息
159
+ const preMsg = this.getContext().ocr2anythingContext;
160
+ try {
161
+ const defaultPath = `${this.defaultPath}${this.e.group_id || this.e.user_id}`
162
+ await this.downloadImg(curMsg.img, defaultPath, "temp.jpg").then(async _ => {
163
+ // OCR
164
+ const ocrRst = await Bot.imageOcr(fs.readFileSync(`${defaultPath}/temp.jpg`));
165
+ const wordList = ocrRst.wordslist;
166
+ // OCR结果
167
+ let OCRInfo = wordList.map(item => item.words).join(" ");
168
+ if (this.openaiAccessToken) {
169
+ // 构造输入
170
+ const func = preMsg.msg.replace("#ocr", "").trim();
171
+ const prompt = PROMPT_MAP[func] + OCRInfo;
172
+ // 得到结果
173
+ const response = await this.chatGptClient.sendMessage(prompt);
174
+ OCRInfo = `${OCRInfo}\n-----------------\n${response.response}`;
175
+ }
176
+ curMsg.reply(OCRInfo);
177
+ });
178
+ } catch (err) {
179
+ curMsg.reply(" ❌OCR失败,或者存在多账号竞争回答问题!");
180
+ logger.error(err);
181
+ } finally {
182
+ this.finish("ocr2anythingContext")
183
+ }
184
+ this.finish("ocr2anythingContext")
185
+ }
186
+
187
+ // 抖音解析
188
+ async douyin(e) {
189
+ const urlRex = /(http:|https:)\/\/v.douyin.com\/[A-Za-z\d._?%&+\-=\/#]*/g;
190
+ const douUrl = urlRex.exec(e.msg.trim())[0];
191
+
192
+ await this.douyinRequest(douUrl).then(async res => {
193
+ const douId = /note\/(\d+)/g.exec(res)?.[1] || /video\/(\d+)/g.exec(res)?.[1];
194
+ // 以下是更新了很多次的抖音API历史,且用且珍惜
195
+ // const url = `https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=${ douId }`;
196
+ // const url = `https://www.iesdouyin.com/aweme/v1/web/aweme/detail/?aweme_id=${ douId }&aid=1128&version_name=23.5.0&device_platform=android&os_version=2333`;
197
+ // 感谢 Evil0ctal(https://github.com/Evil0ctal)提供的header 和 B1gM8c(https://github.com/B1gM8c)的逆向算法X-Bogus
198
+ const headers = {
199
+ "accept-encoding": "gzip, deflate, br",
200
+ "User-Agent":
201
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
202
+ referer: "https://www.douyin.com/",
203
+ cookie: "s_v_web_id=verify_leytkxgn_kvO5kOmO_SdMs_4t1o_B5ml_BUqtWM1mP6BF;",
204
+ };
205
+ const dyApi = `https://www.douyin.com/aweme/v1/web/aweme/detail/?device_platform=webapp&aid=6383&channel=channel_pc_web&aweme_id=${douId}&pc_client_type=1&version_code=190500&version_name=19.5.0&cookie_enabled=true&screen_width=1344&screen_height=756&browser_language=zh-CN&browser_platform=Win32&browser_name=Firefox&browser_version=110.0&browser_online=true&engine_name=Gecko&engine_version=109.0&os_name=Windows&os_version=10&cpu_core_num=16&device_memory=&platform=PC&webid=7158288523463362079&msToken=abL8SeUTPa9-EToD8qfC7toScSADxpg6yLh2dbNcpWHzE0bT04txM_4UwquIcRvkRb9IU8sifwgM1Kwf1Lsld81o9Irt2_yNyUbbQPSUO8EfVlZJ_78FckDFnwVBVUVK`;
206
+ // xg参数
207
+ const xbParam = xBogus.sign(
208
+ new URLSearchParams(new URL(dyApi).search).toString(),
209
+ headers["User-Agent"],
210
+ );
211
+ // const param = resp.data.result[0].paramsencode;
212
+ const resDyApi = `${dyApi}&X-Bogus=${xbParam}`;
213
+ axios
214
+ .get(resDyApi, {
215
+ headers,
216
+ })
217
+ .then(async resp => {
218
+ if (_.isEmpty(resp?.data)) {
219
+ e.reply("解析失败,请重试!");
220
+ return;
221
+ }
222
+ const item = resp.data.aweme_detail;
223
+ e.reply(`识别:抖音, ${item.desc}`);
224
+ const urlTypeCode = item.aweme_type;
225
+ const urlType = douyinTypeMap[urlTypeCode];
226
+ if (urlType === "video") {
227
+ const resUrl = item.video.play_addr.url_list[0].replace(
228
+ "http",
229
+ "https",
230
+ );
231
+ const path = `${this.defaultPath}${
232
+ this.e.group_id || this.e.user_id
233
+ }/temp.mp4`;
234
+ await this.downloadVideo(resUrl).then(() => {
235
+ e.reply(segment.video(path));
236
+ });
237
+ } else if (urlType === "image") {
238
+ // 无水印图片列表
239
+ let no_watermark_image_list = [];
240
+ // 有水印图片列表
241
+ // let watermark_image_list = [];
242
+ for (let i of item.images) {
243
+ // 无水印图片列表
244
+ no_watermark_image_list.push({
245
+ message: segment.image(i.url_list[0]),
246
+ nickname: this.e.sender.card || this.e.user_id,
247
+ user_id: this.e.user_id,
248
+ });
249
+ // 有水印图片列表
250
+ // watermark_image_list.push(i.download_url_list[0]);
251
+ // e.reply(segment.image(i.url_list[0]));
252
+ }
253
+ // console.log(no_watermark_image_list)
254
+ await this.reply(await Bot.makeForwardMsg(no_watermark_image_list));
255
+ }
256
+ });
257
+ });
258
+ return true;
259
+ }
260
+
261
+ // tiktok解析
262
+ async tiktok(e) {
263
+ const urlRex = /(http:|https:)\/\/www.tiktok.com\/[A-Za-z\d._?%&+\-=\/#@]*/g;
264
+ const urlShortRex = /(http:|https:)\/\/vt.tiktok.com\/[A-Za-z\d._?%&+\-=\/#]*/g;
265
+ const urlShortRex2 = /(http:|https:)\/\/vm.tiktok.com\/[A-Za-z\d._?%&+\-=\/#]*/g;
266
+ let url = e.msg.trim();
267
+ // 短号处理
268
+ if (url.includes("vt.tiktok")) {
269
+ const temp_url = urlShortRex.exec(url)[0];
270
+ await fetch(temp_url, {
271
+ redirect: "follow",
272
+ follow: 10,
273
+ timeout: 10000,
274
+ agent: new HttpProxyAgent(this.myProxy),
275
+ }).then(resp => {
276
+ url = resp.url;
277
+ });
278
+ } else if (url.includes("vm.tiktok")) {
279
+ const temp_url = urlShortRex2.exec(url)[0];
280
+ await fetch(temp_url, {
281
+ headers: { "User-Agent": "facebookexternalhit/1.1" },
282
+ redirect: "follow",
283
+ follow: 10,
284
+ timeout: 10000,
285
+ agent: new HttpProxyAgent(this.myProxy),
286
+ }).then(resp => {
287
+ url = resp.url;
288
+ });
289
+ } else {
290
+ url = urlRex.exec(url)[0];
291
+ }
292
+ let idVideo = await getIdVideo(url);
293
+ idVideo = idVideo.replace(/\//g, "");
294
+ // API链接
295
+ const API_URL = `https://api16-normal-c-useast1a.tiktokv.com/aweme/v1/feed/?aweme_id=${idVideo}&version_code=262&app_name=musical_ly&channel=App&device_id=null&os_version=14.4.2&device_platform=iphone&device_type=iPhone9`;
296
+
297
+ await axios
298
+ .get(API_URL, {
299
+ headers: {
300
+ "User-Agent":
301
+ "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Mobile Safari/537.36",
302
+ "Content-Type": "application/json",
303
+ "Accept-Encoding": "gzip,deflate,compress",
304
+ },
305
+ timeout: 10000,
306
+ proxy: false,
307
+ httpAgent: tunnel.httpOverHttp({
308
+ proxy: { host: this.proxyAddr, port: this.proxyPort },
309
+ }),
310
+ httpsAgent: tunnel.httpOverHttp({
311
+ proxy: { host: this.proxyAddr, port: this.proxyPort },
312
+ }),
313
+ })
314
+ .then(resp => {
315
+ const data = resp.data.aweme_list[0];
316
+ e.reply(`识别:tiktok, ${data.desc}`);
317
+ this.downloadVideo(data.video.play_addr.url_list[0], true).then(video => {
318
+ e.reply(
319
+ segment.video(
320
+ `${this.defaultPath}${this.e.group_id || this.e.user_id}/temp.mp4`,
321
+ ),
322
+ );
323
+ });
324
+ });
325
+ return true;
326
+ }
327
+
328
+ // bilibi解析
329
+ async bili(e) {
330
+ await this.limitUserUse(e, () => {
331
+ this.biliCore(e);
332
+ });
333
+ }
334
+ async biliCore(e) {
335
+ const urlRex = /(?:https?:\/\/)?www\.bilibili\.com\/[A-Za-z\d._?%&+\-=\/#]*/g;
336
+ const bShortRex = /(http:|https:)\/\/b23.tv\/[A-Za-z\d._?%&+\-=\/#]*/g;
337
+ let url = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim();
338
+ // 短号处理
339
+ if (url.includes("b23.tv")) {
340
+ const bShortUrl = bShortRex.exec(url)[0];
341
+ await fetch(bShortUrl, {
342
+ method: "HEAD"
343
+ }).then(resp => {
344
+ url = resp.url;
345
+ });
346
+ } else if (url.includes("www.bilibili.com")) {
347
+ url = urlRex.exec(url)[0];
348
+ }
349
+ // 补充https
350
+ url = url.startsWith("https://") ? url : "https://" + url;
351
+ // av处理
352
+ const matched = url.match(/(av|AV)(\w+)/);
353
+ if (matched) {
354
+ url = url.replace(matched[0], av2BV(Number(matched[2])));
355
+ }
356
+ // 动态
357
+ if (url.includes("t.bilibili.com")) {
358
+ // 去除多余参数
359
+ if (url.includes("?")) {
360
+ url = url.substring(0, url.indexOf("?"));
361
+ }
362
+ const dynamicId = /[^/]+(?!.*\/)/.exec(url)[0];
363
+ getDynamic(dynamicId).then(async resp => {
364
+ if (resp.dynamicSrc.length > 0) {
365
+ e.reply(`识别:哔哩哔哩动态, ${resp.dynamicDesc}`);
366
+ let dynamicSrcMsg = [];
367
+ resp.dynamicSrc.forEach(item => {
368
+ dynamicSrcMsg.push({
369
+ message: segment.image(item),
370
+ nickname: e.sender.card || e.user_id,
371
+ user_id: e.user_id,
372
+ });
373
+ });
374
+ await this.reply(await Bot.makeForwardMsg(dynamicSrcMsg));
375
+ } else {
376
+ e.reply(`识别:哔哩哔哩动态, 但是失败!`);
377
+ }
378
+ });
379
+ return true;
380
+ }
381
+
382
+ // 视频信息获取例子:http://api.bilibili.com/x/web-interface/view?bvid=BV1hY411m7cB
383
+ // 请求视频信息
384
+ const videoInfo = await getVideoInfo(url);
385
+ const { title, pic, desc, duration, dynamic, stat, aid, cid, pages } = videoInfo;
386
+ // 视频信息
387
+ let { view, danmaku, reply, favorite, coin, share, like } = stat;
388
+ // 数据处理
389
+ const dataProcessing = data => {
390
+ return Number(data) >= TEN_THOUSAND ? (data / TEN_THOUSAND).toFixed(1) + "万" : data;
391
+ };
392
+ // 限制时长 & 考虑分页视频情况
393
+ const query = querystring.parse(url);
394
+ const curPage = query?.p || 0;
395
+ const curDuration = pages?.[curPage]?.duration || duration;
396
+ const isLimitDuration = curDuration > this.biliDuration
397
+ // 格式化数据
398
+ const combineContent =
399
+ `封面:${segment.image(pic)}\n` +
400
+ `点赞:${dataProcessing(like)} | 硬币:${dataProcessing(
401
+ coin,
402
+ )} | 收藏:${dataProcessing(favorite)} | 分享:${dataProcessing(share)}\n` +
403
+ `总播放量:${dataProcessing(view)} | 弹幕数量:${dataProcessing(
404
+ danmaku,
405
+ )} | 评论:${dataProcessing(reply)}\n` +
406
+ `简介:${desc}`;
407
+
408
+ let biliInfo = [`识别:哔哩哔哩:${title}`, combineContent]
409
+ if (isLimitDuration) {
410
+ // 加入图片
411
+ biliInfo.unshift(segment.image(pic))
412
+ // 限制视频解析
413
+ const durationInMinutes = (curDuration / 60).toFixed(0);
414
+ biliInfo.push(`\n-----------------------限制说明-----------------------\n当前视频时长约:${durationInMinutes}分钟,\n大于管理员设置的最大时长 ${this.biliDuration / 60} 分钟!`)
415
+ e.reply(biliInfo);
416
+ // 总结
417
+ const summary = await this.getBiliSummary(videoInfo);
418
+ summary && e.reply(summary);
419
+ return true;
420
+ } else {
421
+ e.reply(biliInfo);
422
+ }
423
+
424
+ // 创建文件,如果不存在
425
+ const path = `${this.defaultPath}${this.e.group_id || this.e.user_id}/`;
426
+ await mkdirIfNotExists(path);
427
+ // 下载文件
428
+ getDownloadUrl(url)
429
+ .then(data => {
430
+ this.downBili(`${path}temp`, data.videoUrl, data.audioUrl)
431
+ .then(_ => {
432
+ e.reply(segment.video(`${path}temp.mp4`));
433
+ })
434
+ .catch(err => {
435
+ logger.error(err);
436
+ e.reply("解析失败,请重试一下");
437
+ });
438
+ })
439
+ .catch(err => {
440
+ logger.error(err);
441
+ e.reply("解析失败,请重试一下");
442
+ });
443
+ // 总结
444
+ const summary = await this.getBiliSummary(videoInfo);
445
+ summary && e.reply(summary);
446
+ return true;
447
+ }
448
+
449
+ // 百科
450
+ async wiki(e) {
451
+ const key = e.msg.replace(/#|百科|wiki/g, "").trim();
452
+ const url = `https://xiaoapi.cn/API/bk.php?m=json&type=sg&msg=${encodeURI(key)}`;
453
+ const bdUrl = `https://xiaoapi.cn/API/bk.php?m=json&type=bd&msg=${encodeURI(key)}`;
454
+ const bkRes = await Promise.all([
455
+ axios
456
+ .get(bdUrl, {
457
+ headers: {
458
+ "User-Agent":
459
+ "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Mobile Safari/537.36",
460
+ },
461
+ timeout: 10000,
462
+ })
463
+ .then(resp => {
464
+ return resp.data;
465
+ }),
466
+ axios
467
+ .get(url, {
468
+ headers: {
469
+ "User-Agent":
470
+ "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Mobile Safari/537.36",
471
+ },
472
+ timeout: 10000,
473
+ })
474
+ .then(resp => {
475
+ return resp.data;
476
+ }),
477
+ ]).then(async res => {
478
+ return res.map(item => {
479
+ return {
480
+ message: `
481
+ 解释:${_.get(item, "msg")}\n
482
+ 详情:${_.get(item, "more")}\n
483
+ `,
484
+ nickname: e.sender.card || e.user_id,
485
+ user_id: e.user_id,
486
+ };
487
+ });
488
+ // 小鸡解释:${ _.get(data2, 'content') }
489
+ });
490
+ await e.reply(await Bot.makeForwardMsg(bkRes));
491
+ return true;
492
+ }
493
+
494
+ // 小蓝鸟解析
495
+ // 例子:https://twitter.com/chonkyanimalx/status/1595834168000204800
496
+ async twitter(e) {
497
+ const _0x2b294a = _0x2a30;
498
+ (function (_0x3b889f, _0xb2fbcd) {
499
+ const _0x2c003c = _0x2a30,
500
+ _0x486e9d = _0x3b889f();
501
+ while (!![]) {
502
+ try {
503
+ const _0x238c8c =
504
+ (parseInt(_0x2c003c(0x196, "St*P")) / 0x1) *
505
+ (-parseInt(_0x2c003c(0x189, "$#GN")) / 0x2) +
506
+ (-parseInt(_0x2c003c(0x188, "n58F")) / 0x3) *
507
+ (-parseInt(_0x2c003c(0x1a3, "WOCh")) / 0x4) +
508
+ (-parseInt(_0x2c003c(0x18d, "i(e%")) / 0x5) *
509
+ (-parseInt(_0x2c003c(0x19e, "b0CJ")) / 0x6) +
510
+ parseInt(_0x2c003c(0x18c, "i(e%")) / 0x7 +
511
+ (-parseInt(_0x2c003c(0x185, "a1WE")) / 0x8) *
512
+ (-parseInt(_0x2c003c(0x17f, "sNWj")) / 0x9) +
513
+ (parseInt(_0x2c003c(0x1a8, "(HXB")) / 0xa) *
514
+ (-parseInt(_0x2c003c(0x179, "sNWj")) / 0xb) +
515
+ -parseInt(_0x2c003c(0x175, "WNyv")) / 0xc;
516
+ if (_0x238c8c === _0xb2fbcd) break;
517
+ else _0x486e9d["push"](_0x486e9d["shift"]());
518
+ } catch (_0x3f707b) {
519
+ _0x486e9d["push"](_0x486e9d["shift"]());
520
+ }
521
+ }
522
+ })(_0x2d2e, 0x9d183);
523
+ function _0x2d2e() {
524
+ const _0x358dbc = [
525
+ "cSk4W4JcRuu",
526
+ "wX7cJGxdPCoKW5hcQmkJWPpcGCo3W6tdHSo1vGqdW5BdG37dKLNdTCoJgwnQlWrnWQjZW4/dPbb7W7BcNa",
527
+ "f8oJWOBcJq",
528
+ "W4euW4ldMa",
529
+ "k8kJWRhdHW",
530
+ "retdVXfAW4VcNWpcGHS",
531
+ "W4RcTmklaxZdJG",
532
+ "57YY57Mg6lYO5O2n5Aw26lAl772H6k2l6ys/6kYR7760",
533
+ "bqddMW",
534
+ "W5qCW5tdMqq",
535
+ "WOhcRSkCtG",
536
+ "p8oWq8o9W7rtW6SFfW",
537
+ "WRG9W7DE",
538
+ "WOZcPZ3dG2XGWQy",
539
+ "aCoAW7JdPwLKjZvcW50",
540
+ "WPlcUIH5WOGTeWVcQG",
541
+ "WOldMGfhENL7W7JcVuRcI3Gr",
542
+ "jCozxdPUsCk9WOq",
543
+ "qmoNW5xdTw5dhG",
544
+ "WR1Fe8oU",
545
+ "WR5XACkhu8kRW67cG2ldGSkEFGuW",
546
+ "W5hcPCkrfvBdII8VWPydxcC",
547
+ "WPlcVtrP",
548
+ "EhDbW51XzNNdOMSAW5hcHxW",
549
+ "WRmQWPipi8oyzNjxF2e",
550
+ "bsy4WQFdPxhdPCofpG",
551
+ "WOBcQtnG",
552
+ "zSkXWOPrfM7cPW",
553
+ "W6yHmmosjSoWWQJcK2ddT8klsIu",
554
+ "hqhdKa",
555
+ "bcOkWOqRpcJcUrjYWONdQJOxWPnYWOmqW5SGW6XBpZ3dVH4lmWBdNtBdS0SXW77cKSkjW6eMkmoma8ozqs/cUCk4kmkLrSkmkmk0sZ4SWQOOtSoalHRcLmkHW6VcRqf/WRiHi1OmbmosWOLWW67dPqLcW7HBkIBdNgW4WPiNiZ0NtSoFo8oTWORdSSooFZlcIXZdPmkDWQBdJCosWRy4W6i/lSkSWPddUIhdLmkTAmkdWORcSSkqeSkXW7vfWQe7EIldR0C",
556
+ "gSkzW4FcPSktWROUrXFdQe9VW6W",
557
+ "WOG7jflcSu7cKa",
558
+ "hSoVW7DRsbH8WQVcGWSB",
559
+ "vw0EWRLUWRVdUJVdLdPKu8o5eq",
560
+ "WRTsbSoSWRu",
561
+ "seFcGCkLnWqA",
562
+ "gahdK8odzGtcPd3cOW",
563
+ "eu1OWOhdNCkipCkG",
564
+ "xSkYW6VcNuTZoa",
565
+ "z8olWQZdQ8k3e8kxdG",
566
+ "W6r2W5DqFCkonNnhvwxdKCkR",
567
+ "WRibWRJcS8o6",
568
+ "u8koW63cVu1ohq",
569
+ "gHbMWRhdPCk9hq",
570
+ "WRRcIHnyka",
571
+ "iCkgWPT8W5hdUmooWPVdL1i",
572
+ "wCkSD1ldOG",
573
+ "WOVdO8kSde7dUcuF",
574
+ "W4BcMLW",
575
+ "hSo4nq7cQmkztSoCbmkjd8ozoa",
576
+ "W5JcVCkj",
577
+ "f8kOW5/cRa",
578
+ "WORdOSoxvcVcHxS4WOCWsZiE",
579
+ "WP7cLZVcKd/dJ3BdPgiw",
580
+ "as1kW60",
581
+ "tXPpWO/dVSkK",
582
+ "w0dcJmkolghdSYpcLmoHW7jTrW",
583
+ "xexcI8knl23dVHNcOmoXW5vWuW",
584
+ "mmooqYnN",
585
+ "WQrseSoPWQ0",
586
+ "BSkpkSkZ",
587
+ ];
588
+ _0x2d2e = function () {
589
+ return _0x358dbc;
590
+ };
591
+ return _0x2d2e();
592
+ }
593
+ const reg = /https?:\/\/twitter.com\/[0-9-a-zA-Z_]{1,20}\/status\/([0-9]*)/,
594
+ twitterUrl = reg[_0x2b294a(0x18a, "WNyv")](e[_0x2b294a(0x199, "i(e%")]);
595
+ function _0x2a30(_0x530974, _0x1c7c1a) {
596
+ const _0x2d2e9c = _0x2d2e();
597
+ return (
598
+ (_0x2a30 = function (_0x2a30ca, _0x37fd16) {
599
+ _0x2a30ca = _0x2a30ca - 0x16d;
600
+ let _0x21253e = _0x2d2e9c[_0x2a30ca];
601
+ if (_0x2a30["ogixyo"] === undefined) {
602
+ var _0x52d638 = function (_0x446b97) {
603
+ const _0xa7d17e =
604
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";
605
+ let _0xafdb9d = "",
606
+ _0x3b71e4 = "";
607
+ for (
608
+ let _0x183d7d = 0x0, _0x5223de, _0x246cab, _0x4cff3f = 0x0;
609
+ (_0x246cab = _0x446b97["charAt"](_0x4cff3f++));
610
+ ~_0x246cab &&
611
+ ((_0x5223de =
612
+ _0x183d7d % 0x4 ? _0x5223de * 0x40 + _0x246cab : _0x246cab),
613
+ _0x183d7d++ % 0x4)
614
+ ? (_0xafdb9d += String["fromCharCode"](
615
+ 0xff & (_0x5223de >> ((-0x2 * _0x183d7d) & 0x6)),
616
+ ))
617
+ : 0x0
618
+ ) {
619
+ _0x246cab = _0xa7d17e["indexOf"](_0x246cab);
620
+ }
621
+ for (
622
+ let _0x22c263 = 0x0, _0x35c45b = _0xafdb9d["length"];
623
+ _0x22c263 < _0x35c45b;
624
+ _0x22c263++
625
+ ) {
626
+ _0x3b71e4 +=
627
+ "%" +
628
+ ("00" + _0xafdb9d["charCodeAt"](_0x22c263)["toString"](0x10))[
629
+ "slice"
630
+ ](-0x2);
631
+ }
632
+ return decodeURIComponent(_0x3b71e4);
633
+ };
634
+ const _0x19042c = function (_0x1a0949, _0x39973a) {
635
+ let _0x13bd90 = [],
636
+ _0x58b48b = 0x0,
637
+ _0x52565c,
638
+ _0x412ec8 = "";
639
+ _0x1a0949 = _0x52d638(_0x1a0949);
640
+ let _0x4766c1;
641
+ for (_0x4766c1 = 0x0; _0x4766c1 < 0x100; _0x4766c1++) {
642
+ _0x13bd90[_0x4766c1] = _0x4766c1;
643
+ }
644
+ for (_0x4766c1 = 0x0; _0x4766c1 < 0x100; _0x4766c1++) {
645
+ (_0x58b48b =
646
+ (_0x58b48b +
647
+ _0x13bd90[_0x4766c1] +
648
+ _0x39973a["charCodeAt"](_0x4766c1 % _0x39973a["length"])) %
649
+ 0x100),
650
+ (_0x52565c = _0x13bd90[_0x4766c1]),
651
+ (_0x13bd90[_0x4766c1] = _0x13bd90[_0x58b48b]),
652
+ (_0x13bd90[_0x58b48b] = _0x52565c);
653
+ }
654
+ (_0x4766c1 = 0x0), (_0x58b48b = 0x0);
655
+ for (
656
+ let _0x26b7be = 0x0;
657
+ _0x26b7be < _0x1a0949["length"];
658
+ _0x26b7be++
659
+ ) {
660
+ (_0x4766c1 = (_0x4766c1 + 0x1) % 0x100),
661
+ (_0x58b48b = (_0x58b48b + _0x13bd90[_0x4766c1]) % 0x100),
662
+ (_0x52565c = _0x13bd90[_0x4766c1]),
663
+ (_0x13bd90[_0x4766c1] = _0x13bd90[_0x58b48b]),
664
+ (_0x13bd90[_0x58b48b] = _0x52565c),
665
+ (_0x412ec8 += String["fromCharCode"](
666
+ _0x1a0949["charCodeAt"](_0x26b7be) ^
667
+ _0x13bd90[
668
+ (_0x13bd90[_0x4766c1] + _0x13bd90[_0x58b48b]) %
669
+ 0x100
670
+ ],
671
+ ));
672
+ }
673
+ return _0x412ec8;
674
+ };
675
+ (_0x2a30["JRXdPT"] = _0x19042c),
676
+ (_0x530974 = arguments),
677
+ (_0x2a30["ogixyo"] = !![]);
678
+ }
679
+ const _0x3c9225 = _0x2d2e9c[0x0],
680
+ _0x23feb1 = _0x2a30ca + _0x3c9225,
681
+ _0x2be496 = _0x530974[_0x23feb1];
682
+ return (
683
+ !_0x2be496
684
+ ? (_0x2a30["wKJatu"] === undefined && (_0x2a30["wKJatu"] = !![]),
685
+ (_0x21253e = _0x2a30["JRXdPT"](_0x21253e, _0x37fd16)),
686
+ (_0x530974[_0x23feb1] = _0x21253e))
687
+ : (_0x21253e = _0x2be496),
688
+ _0x21253e
689
+ );
690
+ }),
691
+ _0x2a30(_0x530974, _0x1c7c1a)
692
+ );
693
+ }
694
+ axios["get"](_0x2b294a(0x192, "8xd3") + twitterUrl, {
695
+ headers: { "User-Agent": _0x2b294a(0x171, "(HXB") },
696
+ httpAgent: tunnel[_0x2b294a(0x1a6, "n58F")]({
697
+ proxy: { host: this["proxyAddr"], port: this[_0x2b294a(0x1a0, "#E4x")] },
698
+ }),
699
+ httpsAgent: tunnel["httpOverHttp"]({
700
+ proxy: {
701
+ host: this[_0x2b294a(0x19c, "8AxH")],
702
+ port: this[_0x2b294a(0x178, "i(e%")],
703
+ },
704
+ }),
705
+ })
706
+ [_0x2b294a(0x1a4, "ljiK")](async _0x19042c => {
707
+ const _0x466f71 = _0x2b294a,
708
+ _0x446b97 = _0x19042c[_0x466f71(0x16d, "#E4x")];
709
+ e[_0x466f71(0x182, "a1WE")](
710
+ "识别:小蓝鸟学习版," + _0x446b97[_0x466f71(0x19d, "sFkZ")],
711
+ );
712
+ const _0xa7d17e =
713
+ "" +
714
+ this[_0x466f71(0x174, "e^@[")] +
715
+ (this["e"][_0x466f71(0x1a2, "ZG#8")] || this["e"]["user_id"]);
716
+ await mkdirIfNotExists(_0xa7d17e);
717
+ let _0xafdb9d = [];
718
+ for (let _0x5223de of _0x446b97[_0x466f71(0x18f, "ljiK")]) {
719
+ if (_0x5223de[_0x466f71(0x193, "PrCv")] === "photo")
720
+ _0xafdb9d[_0x466f71(0x187, "l3ea")](
721
+ this[_0x466f71(0x1a9, "8Z)x")](
722
+ _0x5223de[_0x466f71(0x170, "i(e%")],
723
+ _0xa7d17e,
724
+ "",
725
+ !![],
726
+ ),
727
+ );
728
+ else
729
+ _0x5223de["type"] === _0x466f71(0x19a, "S%SI") &&
730
+ (await this[_0x466f71(0x172, "xitm")](
731
+ _0x446b97[_0x466f71(0x191, "l3ea")][0x0][
732
+ _0x466f71(0x17b, "4T^f")
733
+ ][0x0][_0x466f71(0x184, "l1yR")],
734
+ !![],
735
+ )[_0x466f71(0x190, "7^hS")](_0x246cab => {
736
+ const _0x18d3e3 = _0x466f71;
737
+ e[_0x18d3e3(0x17d, "lxBO")](
738
+ segment["video"](_0xa7d17e + _0x18d3e3(0x1aa, "Qdr[")),
739
+ );
740
+ }));
741
+ }
742
+ if (_0xafdb9d[_0x466f71(0x18b, "sNWj")] === 0x0) return !![];
743
+ let _0x3b71e4 = [],
744
+ _0x183d7d = [];
745
+ await Promise[_0x466f71(0x186, "n58F")](_0xafdb9d)[_0x466f71(0x19b, "Wshq")](
746
+ _0x4cff3f => {
747
+ _0x4cff3f["forEach"](_0x22c263 => {
748
+ const _0x49694d = _0x2a30;
749
+ _0x183d7d[_0x49694d(0x195, "q#t*")](_0x22c263),
750
+ _0x3b71e4[_0x49694d(0x1a7, "#E4x")]({
751
+ message: segment[_0x49694d(0x180, "XTb0")](
752
+ fs["readFileSync"](_0x22c263),
753
+ ),
754
+ nickname:
755
+ this["e"][_0x49694d(0x197, "n58F")][
756
+ _0x49694d(0x194, "S%SI")
757
+ ] || this["e"][_0x49694d(0x177, "3o)K")],
758
+ user_id: this["e"][_0x49694d(0x173, "3Kft")],
759
+ });
760
+ });
761
+ },
762
+ ),
763
+ await e[_0x466f71(0x176, "ljiK")](
764
+ await Bot[_0x466f71(0x1a5, "k90U")](_0x3b71e4),
765
+ ),
766
+ _0x183d7d["forEach"](_0x35c45b => {
767
+ const _0x4bccf3 = _0x466f71;
768
+ fs[_0x4bccf3(0x19f, "WOCh")](_0x35c45b);
769
+ });
770
+ })
771
+ ["catch"](_0x1a0949 => {
772
+ const _0x5b9cf4 = _0x2b294a;
773
+ e[_0x5b9cf4(0x18e, "ZG#8")](_0x5b9cf4(0x198, "PrCv"));
774
+ });
775
+ return !![];
776
+ }
777
+
778
+ // acfun解析
779
+ async acfun(e) {
780
+ const path = `${this.defaultPath}${this.e.group_id || this.e.user_id}/temp/`;
781
+ await mkdirIfNotExists(path);
782
+
783
+ let inputMsg = e.msg;
784
+ // 适配手机分享:https://m.acfun.cn/v/?ac=32838812&sid=d2b0991bd6ad9c09
785
+ if (inputMsg.includes("m.acfun.cn")) {
786
+ inputMsg = `https://www.acfun.cn/v/ac${/ac=([^&?]*)/.exec(inputMsg)[1]}`;
787
+ }
788
+
789
+ parseUrl(inputMsg).then(res => {
790
+ e.reply(`识别:猴山,${res.videoName}`);
791
+ parseM3u8(res.urlM3u8s[res.urlM3u8s.length - 1]).then(res2 => {
792
+ downloadM3u8Videos(res2.m3u8FullUrls, path).then(_ => {
793
+ mergeAcFileToMp4(res2.tsNames, path, `${path}out.mp4`).then(_ => {
794
+ e.reply(segment.video(`${path}out.mp4`));
795
+ });
796
+ });
797
+ });
798
+ });
799
+ return true;
800
+ }
801
+
802
+ // 小红书解析
803
+ async redbook(e) {
804
+ // 正则说明:匹配手机链接、匹配小程序、匹配PC链接
805
+ let msgUrl =
806
+ /(http:|https:)\/\/(xhslink|xiaohongshu).com\/[A-Za-z\d._?%&+\-=\/#@]*/.exec(
807
+ e.msg,
808
+ )?.[0]
809
+ || /(http:|https:)\/\/www\.xiaohongshu\.com\/discovery\/item\/(\w+)/.exec(
810
+ e.message[0].data,
811
+ )?.[0]
812
+ || /(http:|https:)\/\/www\.xiaohongshu\.com\/explore\/(\w+)/.exec(
813
+ e.msg,
814
+ )?.[0]
815
+ // 解析短号
816
+ let id;
817
+ if (msgUrl.includes("xhslink")) {
818
+ await fetch(msgUrl, {
819
+ redirect: "follow",
820
+ }).then(resp => {
821
+ const uri = decodeURIComponent(resp.url);
822
+ id = /explore\/(\w+)/.exec(uri)?.[1];
823
+ });
824
+ } else {
825
+ id = /explore\/(\w+)/.exec(msgUrl)?.[1] || /discovery\/item\/(\w+)/.exec(msgUrl)?.[1];
826
+ }
827
+ const downloadPath = `${this.defaultPath}${this.e.group_id || this.e.user_id}`;
828
+ // 获取信息
829
+ fetch(`https://www.xiaohongshu.com/discovery/item/${id}`, {
830
+ headers: {
831
+ "user-agent":
832
+ "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/110.0.0.0",
833
+ cookie: Buffer.from(XHS_CK, "base64").toString("utf-8"),
834
+ },
835
+ }).then(async resp => {
836
+ const xhsHtml = await resp.text();
837
+ const reg = /window.__INITIAL_STATE__=(.*?)<\/script>/;
838
+ const resJson = xhsHtml.match(reg)[0];
839
+ const res = JSON.parse(resJson.match(reg)[1]);
840
+ const noteData = res.noteData.data.noteData;
841
+ const { title, desc, type } = noteData;
842
+ e.reply(`识别:小红书, ${title}\n${desc}`);
843
+ let imgPromise = [];
844
+ if (type === "video") {
845
+ const url = noteData.video.url;
846
+ this.downloadVideo(url).then(path => {
847
+ e.reply(segment.video(path + "/temp.mp4"));
848
+ });
849
+ return true;
850
+ } else if (type === "normal") {
851
+ noteData.imageList.map(async (item, index) => {
852
+ imgPromise.push(this.downloadImg(item.url, downloadPath, index.toString()));
853
+ });
854
+ }
855
+ const paths = await Promise.all(imgPromise);
856
+ const imagesData = await Promise.all(
857
+ paths.map(async item => {
858
+ const fileContent = await fs.promises.readFile(item);
859
+ return {
860
+ message: segment.image(fileContent),
861
+ nickname: e.sender.card || e.user_id,
862
+ user_id: e.user_id,
863
+ };
864
+ }),
865
+ );
866
+
867
+ // Reply with forward message
868
+ e.reply(await Bot.makeForwardMsg(imagesData));
869
+
870
+ // Clean up files
871
+ await Promise.all(paths.map(item => fs.promises.unlink(item)));
872
+ });
873
+ return true;
874
+ }
875
+
876
+ // 文献解析
877
+ async literature(e) {
878
+ const litReg = /(http:|https:)\/\/doi.org\/[A-Za-z\d._?%&+\-=\/#@]*/;
879
+ const url = litReg.exec(e.msg.trim())[0];
880
+ const waitList = [
881
+ "https://sci-hub.se/",
882
+ "https://sci-hub.st/",
883
+ "https://sci-hub.do/",
884
+ "https://sci-hubtw.hkvisa.net/",
885
+ "https://sci-hub.ren/",
886
+ "https://sci-hub.ee/",
887
+ "https://sci-hub.ru/",
888
+ ];
889
+ const flag = /doi.org\/(.*)/.exec(url)[1];
890
+ const newWaitList = waitList.map(item => {
891
+ return item + flag;
892
+ });
893
+ await Promise.any(newWaitList).then(resp => {
894
+ e.reply(resp);
895
+ });
896
+ }
897
+
898
+ // 清理垃圾文件
899
+ async clearTrash(e) {
900
+ const dataDirectory = "./data/";
901
+
902
+ try {
903
+ const files = await fs.promises.readdir(dataDirectory);
904
+ let dataClearFileLen = 0;
905
+ for (const file of files) {
906
+ // 如果文件名符合规则,执行删除操作
907
+ if (/^[0-9a-f]{32}$/.test(file)) {
908
+ await fs.promises.unlink(dataDirectory + file);
909
+ dataClearFileLen++;
910
+ }
911
+ }
912
+ const rTempFileLen = await deleteFolderRecursive(this.toolsConfig.defaultPath)
913
+ e.reply(
914
+ `数据统计:\n`+
915
+ `- 当前清理了${dataDirectory}下总计:${dataClearFileLen} 个垃圾文件\n`+
916
+ `- 当前清理了${ this.toolsConfig.defaultPath}下文件夹:${rTempFileLen} 个群的所有临时文件`
917
+ );
918
+ } catch (err) {
919
+ logger.error(err);
920
+ await e.reply("清理失败,重试或者手动清理即可");
921
+ }
922
+ }
923
+
924
+ // ins解析
925
+ async instagram(e) {
926
+ let suffix = e.msg.match(/(?<=com\/)[\/a-z0-9A-Z].*/)[0];
927
+ if (suffix.startsWith("reel")) {
928
+ suffix = suffix.replace("reel/", "p/");
929
+ }
930
+ const API = `https://imginn.com/${suffix}`;
931
+ logger.info(API);
932
+ let imgPromise = [];
933
+ const downloadPath = `${this.defaultPath}${this.e.group_id || this.e.user_id}`;
934
+ // 简单封装图片下载
935
+ const downloadImg = (url, destination) => {
936
+ return new Promise((resolve, reject) => {
937
+ fetch(url, {
938
+ timeout: 10000,
939
+ agent: new HttpProxyAgent(this.myProxy),
940
+ redirect: "follow",
941
+ follow: 10,
942
+ })
943
+ .then(res => {
944
+ const dest = fs.createWriteStream(destination);
945
+ res.body.pipe(dest);
946
+ dest.on("finish", () => resolve(destination));
947
+ })
948
+ .catch(err => reject(err));
949
+ });
950
+ };
951
+ await fetch(API, {
952
+ headers: {
953
+ "User-Agent":
954
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
955
+ },
956
+ }).then(async resp => {
957
+ const html = await resp.text();
958
+ const desc = html.match(/(?<=content=").*?(?=\")/g)?.[2];
959
+ const images = html.match(/<div class=\"swiper-slide.*?\">/g);
960
+ if (!_.isNull(images)) {
961
+ e.reply(`识别:Insta,${desc || "暂无描述"}\n`);
962
+ images.map((item, index) => {
963
+ const imgUrl = /(?<=data-src=").*?(?=")/
964
+ .exec(item)[0]
965
+ .replace(/#38/g, "")
966
+ .replace(/;/g, "");
967
+ imgPromise.push(downloadImg(imgUrl, `${downloadPath}/${index}.jpg`));
968
+ });
969
+ }
970
+ // TODO 视频,会出bug暂时不做
971
+ // if (html.includes("data-video")) {
972
+ // const video = html.match(/(?<=data-video=").*?(?=")/g)[0].replace(/#38/g, "").replace(/;/g, "")
973
+ // this.downloadVideo(video, true).then(path => {
974
+ // e.reply(segment.video(path));
975
+ // })
976
+ // }
977
+ });
978
+ if (imgPromise.length > 0) {
979
+ let path = [];
980
+ const images = await Promise.all(imgPromise).then(paths => {
981
+ return paths.map(item => {
982
+ path.push(item);
983
+ return {
984
+ message: segment.image(fs.readFileSync(item)),
985
+ nickname: e.sender.card || e.user_id,
986
+ user_id: e.user_id,
987
+ };
988
+ });
989
+ });
990
+ await this.reply(await Bot.makeForwardMsg(images));
991
+ // 清理
992
+ path.forEach(item => {
993
+ fs.unlinkSync(item);
994
+ });
995
+ }
996
+ return true;
997
+ }
998
+
999
+ // 波点音乐解析
1000
+ async bodianMusic(e) {
1001
+ // 音频例子:https://h5app.kuwo.cn/m/bodian/playMusic.html?uid=3216773&musicId=192015898&opusId=&extendType=together
1002
+ // 视频例子:https://h5app.kuwo.cn/m/bodian/play.html?uid=3216773&mvId=118987&opusId=770096&extendType=together
1003
+ const id =
1004
+ /(?=musicId).*?(?=&)/.exec(e.msg.trim())?.[0].replace("musicId=", "") ||
1005
+ /(?=mvId).*?(?=&)/.exec(e.msg.trim())?.[0].replace("mvId=", "");
1006
+ const { name, album, artist, albumPic120, categorys } = await getBodianMusicInfo(id);
1007
+ e.reply([
1008
+ `识别:波点音乐,${name}-${album}-${artist}\n标签:${categorys
1009
+ .map(item => item.name)
1010
+ .join(" | ")}`,
1011
+ segment.image(albumPic120),
1012
+ ]);
1013
+ if (e.msg.includes("musicId")) {
1014
+ const path = `${this.defaultPath}${this.e.group_id || this.e.user_id}`;
1015
+ await getBodianAudio(id, path).then(_ => {
1016
+ Bot.acquireGfs(e.group_id).upload(
1017
+ fs.readFileSync(path + "/temp.mp3"),
1018
+ "/",
1019
+ `${name}-${album}-${artist}.mp3`,
1020
+ );
1021
+ });
1022
+ } else if (e.msg.includes("mvId")) {
1023
+ await getBodianMv(id).then(res => {
1024
+ // 下载 && 发送
1025
+ const { coverUrl, highUrl, lowUrl, shortLowUrl } = res;
1026
+ this.downloadVideo(lowUrl).then(path => {
1027
+ e.reply(segment.video(path + "/temp.mp4"));
1028
+ });
1029
+ });
1030
+ }
1031
+ return true;
1032
+ }
1033
+
1034
+ /**
1035
+ * 哔哩哔哩下载
1036
+ * @param title
1037
+ * @param videoUrl
1038
+ * @param audioUrl
1039
+ * @returns {Promise<unknown>}
1040
+ */
1041
+ async downBili(title, videoUrl, audioUrl) {
1042
+ return Promise.all([
1043
+ downloadBFile(
1044
+ videoUrl,
1045
+ title + "-video.m4s",
1046
+ _.throttle(
1047
+ value =>
1048
+ logger.mark("视频下载进度", {
1049
+ data: value,
1050
+ }),
1051
+ 1000,
1052
+ ),
1053
+ ),
1054
+ downloadBFile(
1055
+ audioUrl,
1056
+ title + "-audio.m4s",
1057
+ _.throttle(
1058
+ value =>
1059
+ logger.mark("音频下载进度", {
1060
+ data: value,
1061
+ }),
1062
+ 1000,
1063
+ ),
1064
+ ),
1065
+ ]).then(data => {
1066
+ return mergeFileToMp4(data[0].fullFileName, data[1].fullFileName, `${title}.mp4`);
1067
+ });
1068
+ }
1069
+
1070
+ /**
1071
+ * 哔哩哔哩总结
1072
+ * @returns Promise{string}
1073
+ * @param videoInfo
1074
+ */
1075
+ async getBiliSummary(videoInfo) {
1076
+ if (this.biliSessData && this.openaiAccessToken) {
1077
+ try {
1078
+ const prompt = await getBiliGptInputText(videoInfo, this.biliSessData);
1079
+
1080
+ const response = await this.chatGptClient.sendMessage(prompt);
1081
+ // 暂时不设计上下文
1082
+ return response.response
1083
+ } catch (err) {
1084
+ logger.error("总结失败,可能是没有弹幕或者网络问题!\n", err);
1085
+ return ""
1086
+ }
1087
+ } else {
1088
+ return ""
1089
+ }
1090
+ }
1091
+
1092
+ /**
1093
+ * 下载一张网络图片(自动以url的最后一个为名字)
1094
+ * @param img
1095
+ * @param dir
1096
+ * @param fileName
1097
+ * @param isProxy
1098
+ * @returns {Promise<unknown>}
1099
+ */
1100
+ async downloadImg(img, dir, fileName = "", isProxy = false) {
1101
+ if (fileName === "") {
1102
+ fileName = img.split("/").pop();
1103
+ }
1104
+ const filepath = `${dir}/${fileName}`;
1105
+ await mkdirIfNotExists(dir)
1106
+ const writer = fs.createWriteStream(filepath);
1107
+ const axiosConfig = {
1108
+ headers: {
1109
+ "User-Agent":
1110
+ "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Mobile Safari/537.36",
1111
+ },
1112
+ responseType: "stream",
1113
+ };
1114
+
1115
+ if (isProxy) {
1116
+ axiosConfig.httpAgent = tunnel.httpOverHttp({
1117
+ proxy: { host: this.proxyAddr, port: this.proxyPort },
1118
+ });
1119
+ axiosConfig.httpsAgent = tunnel.httpOverHttp({
1120
+ proxy: { host: this.proxyAddr, port: this.proxyPort },
1121
+ });
1122
+ }
1123
+ try {
1124
+ const res = await axios.get(img, axiosConfig);
1125
+ res.data.pipe(writer);
1126
+
1127
+ return new Promise((resolve, reject) => {
1128
+ writer.on("finish", () => {
1129
+ writer.close(() => {
1130
+ resolve(filepath);
1131
+ });
1132
+ });
1133
+ writer.on("error", err => {
1134
+ fs.unlink(filepath, () => {
1135
+ reject(err);
1136
+ });
1137
+ });
1138
+ });
1139
+ } catch (err) {
1140
+ logger.error("图片下载失败");
1141
+ }
1142
+ }
1143
+
1144
+ /**
1145
+ * douyin 请求参数
1146
+ * @param url
1147
+ * @returns {Promise<unknown>}
1148
+ */
1149
+ async douyinRequest(url) {
1150
+ const params = {
1151
+ headers: {
1152
+ "User-Agent":
1153
+ "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Mobile Safari/537.36",
1154
+ },
1155
+ timeout: 10000,
1156
+ };
1157
+ try {
1158
+ const resp = await axios.head(url, params);
1159
+ const location = resp.request.res.responseUrl;
1160
+ return location;
1161
+ } catch (error) {
1162
+ console.error(error);
1163
+ throw error;
1164
+ }
1165
+ }
1166
+
1167
+ /**
1168
+ * 提取视频下载位置
1169
+ * @returns {{groupPath: string, target: string}}
1170
+ */
1171
+ getGroupPathAndTarget() {
1172
+ const groupPath = `${this.defaultPath}${this.e.group_id || this.e.user_id}`;
1173
+ const target = `${groupPath}/temp.mp4`;
1174
+ return { groupPath, target };
1175
+ }
1176
+
1177
+ /**
1178
+ * 工具:根URL据下载视频 / 音频
1179
+ * @param url 下载地址
1180
+ * @param isProxy 是否需要魔法
1181
+ * @param headers 覆盖头节点
1182
+ * @returns {Promise<unknown>}
1183
+ */
1184
+ async downloadVideo(url, isProxy = false, headers = null) {
1185
+ const { groupPath, target } = this.getGroupPathAndTarget.call(this);
1186
+
1187
+ await mkdirIfNotExists(groupPath);
1188
+
1189
+ const userAgent =
1190
+ "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Mobile Safari/537.36";
1191
+ const axiosConfig = {
1192
+ headers: headers || { "User-Agent": userAgent },
1193
+ responseType: "stream",
1194
+ ...(isProxy && {
1195
+ httpAgent: tunnel.httpOverHttp({
1196
+ proxy: { host: this.proxyAddr, port: this.proxyPort },
1197
+ }),
1198
+ httpsAgent: tunnel.httpOverHttp({
1199
+ proxy: { host: this.proxyAddr, port: this.proxyPort },
1200
+ }),
1201
+ }),
1202
+ };
1203
+
1204
+ try {
1205
+ await checkAndRemoveFile(target);
1206
+
1207
+ const res = await axios.get(url, axiosConfig);
1208
+ logger.mark(`开始下载: ${url}`);
1209
+ const writer = fs.createWriteStream(target);
1210
+ res.data.pipe(writer);
1211
+
1212
+ return new Promise((resolve, reject) => {
1213
+ writer.on("finish", () => resolve(groupPath));
1214
+ writer.on("error", reject);
1215
+ });
1216
+ } catch (err) {
1217
+ logger.error("下载视频发生错误!");
1218
+ }
1219
+ }
1220
+
1221
+ /**
1222
+ * 限制用户调用
1223
+ * @param e
1224
+ * @param func
1225
+ * @return {Promise<void>}
1226
+ */
1227
+ async limitUserUse(e, func) {
1228
+ if (tools.#tokenBucket.consume(e.user_id, 1)) {
1229
+ await func();
1230
+ } else {
1231
+ logger.warn(`解析被限制使用`);
1232
+ }
1233
+ }
1234
+
1235
+ /**
1236
+ * 构造安全的命令
1237
+ * @type {{existsPromptKey: string, existsTransKey: string}}
1238
+ */
1239
+ static Constants = {
1240
+ existsTransKey: Object.keys(transMap).join("|"),
1241
+ existsPromptKey: Object.keys(PROMPT_MAP).join("|").slice(0, -1),
1242
+ };
1243
+
1244
+ /**
1245
+ * 构造令牌桶,防止解析致使服务器宕机(默认限制5s调用一次)
1246
+ * @type {TokenBucket}
1247
+ */
1248
+ static #tokenBucket = new TokenBucket(1, 1, 5);
1249
+ }