karin-plugin-kkk 2.34.0 → 2.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +842 -1166
- package/README.md +4 -5
- package/config/default_config/app.yaml +1 -1
- package/config/default_config/bilibili.yaml +2 -2
- package/config/default_config/config.json +136 -0
- package/config/default_config/cookies.yaml +2 -2
- package/config/default_config/douyin.yaml +3 -3
- package/config/default_config/kuaishou.yaml +1 -1
- package/config/default_config/pushlist.yaml +6 -6
- package/config/default_config/request.yaml +1 -1
- package/config/default_config/xiaohongshu.yaml +2 -2
- package/lib/build-metadata.json +5 -5
- package/lib/core_chunk/amagi.d.mts +18 -18
- package/lib/core_chunk/amagi.js +1 -1
- package/lib/core_chunk/amagiClient.js +1 -1
- package/lib/core_chunk/main.js +618 -1833
- package/lib/core_chunk/vendor.js +4564 -2166
- package/lib/root.js +1 -1
- package/lib/web/assets/CronEditor-Cg5OQ1zE.js +1 -0
- package/lib/web/assets/DesktopLayout-R64EJDVu.js +1 -0
- package/lib/web/assets/MobileLayout-D45Q2A-w.js +2 -0
- package/lib/web/assets/ThemeSwitch-M2yTf191.js +1 -0
- package/lib/web/assets/index-Bz9RVG7l.js +28 -0
- package/lib/web/assets/index-C9k3Jf0p.css +2 -0
- package/lib/web/index.html +3 -3
- package/package.json +28 -28
- package/resources/font/bilifont/font.css +3 -3
- package/resources/font/fansmedal-num/font.css +1 -1
- package/lib/web/assets/AboutPanel-DFjZYu60.js +0 -1
- package/lib/web/assets/DesktopLayout-Cr5AitGP.js +0 -1
- package/lib/web/assets/MobileLayout-BW-vN-VU.js +0 -2
- package/lib/web/assets/index-8KaTMCj2.css +0 -2
- package/lib/web/assets/index-CQhoJUBv.js +0 -28
package/lib/core_chunk/main.js
CHANGED
|
@@ -4,7 +4,7 @@ import "node:module";
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path, { resolve } from "node:path";
|
|
6
6
|
import URL$2, { fileURLToPath } from "node:url";
|
|
7
|
-
import karin$1, { BOT_CONNECT, app, authMiddleware, checkPkgUpdate, checkPort, common, config,
|
|
7
|
+
import karin$1, { BOT_CONNECT, app, authMiddleware, checkPkgUpdate, checkPort, common, config, createBadRequestResponse, createNotFoundResponse, createServerErrorResponse, createSuccessResponse, db, defineConfig, ffmpeg, ffprobe, hooks, karin, karinPathHtml, karinPathTemp, logger, logger as logger$1, logs, mkdirSync, parseChangelog, range, render, restart, segment, updatePkg, watch } from "node-karin";
|
|
8
8
|
import { EventEmitter } from "node:events";
|
|
9
9
|
import crypto from "node:crypto";
|
|
10
10
|
import axios, { AxiosError } from "node-karin/axios";
|
|
@@ -1597,7 +1597,7 @@ var xiaohongshuApiUrls = {
|
|
|
1597
1597
|
* @param data - 请求参数
|
|
1598
1598
|
* @returns 完整的接口URL
|
|
1599
1599
|
*/
|
|
1600
|
-
emojiList(
|
|
1600
|
+
emojiList(_data) {
|
|
1601
1601
|
return {
|
|
1602
1602
|
apiPath: "/api/im/redmoji/detail",
|
|
1603
1603
|
Url: "https://edith.xiaohongshu.com/api/im/redmoji/detail"
|
|
@@ -2503,7 +2503,7 @@ var generateSecChUa = (userAgent) => {
|
|
|
2503
2503
|
*/
|
|
2504
2504
|
var getDouyinDefaultConfig = (cookie, requestConfig) => {
|
|
2505
2505
|
let finalUserAgent = requestConfig?.headers?.["User-Agent"] ?? "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
|
|
2506
|
-
finalUserAgent = finalUserAgent.replace(/\s+Edg\/[\d
|
|
2506
|
+
finalUserAgent = finalUserAgent.replace(/\s+Edg\/[\d.]+/g, "");
|
|
2507
2507
|
const defHeaders = {
|
|
2508
2508
|
Accept: "application/json, text/plain, */*",
|
|
2509
2509
|
"Accept-Encoding": "gzip, deflate, br, zstd",
|
|
@@ -2800,7 +2800,8 @@ var SM3 = class {
|
|
|
2800
2800
|
this.chunk = this.chunk.concat(a.slice(0, f));
|
|
2801
2801
|
while (this.chunk.length >= 64) {
|
|
2802
2802
|
this._compress(this.chunk);
|
|
2803
|
-
f < a.length
|
|
2803
|
+
if (f < a.length) this.chunk = a.slice(f, Math.min(f + 64, a.length));
|
|
2804
|
+
else this.chunk = [];
|
|
2804
2805
|
f += 64;
|
|
2805
2806
|
}
|
|
2806
2807
|
}
|
|
@@ -2836,10 +2837,17 @@ var SM3 = class {
|
|
|
2836
2837
|
if (t.length < 64) console.error("compress error: not enough data");
|
|
2837
2838
|
else {
|
|
2838
2839
|
for (var f = ((e) => {
|
|
2839
|
-
for (var r = new Array(132), t = 0; t < 16; t++)
|
|
2840
|
+
for (var r = new Array(132), t = 0; t < 16; t++) {
|
|
2841
|
+
r[t] = e[4 * t] << 24;
|
|
2842
|
+
r[t] |= e[4 * t + 1] << 16;
|
|
2843
|
+
r[t] |= e[4 * t + 2] << 8;
|
|
2844
|
+
r[t] |= e[4 * t + 3];
|
|
2845
|
+
r[t] >>>= 0;
|
|
2846
|
+
}
|
|
2840
2847
|
for (var n = 16; n < 68; n++) {
|
|
2841
2848
|
let a = r[n - 16] ^ r[n - 9] ^ this.le(r[n - 3], 15);
|
|
2842
|
-
a = a ^ this.le(a, 15) ^ this.le(a, 23)
|
|
2849
|
+
a = a ^ this.le(a, 15) ^ this.le(a, 23);
|
|
2850
|
+
r[n] = (a ^ this.le(r[n - 13], 7) ^ r[n - 6]) >>> 0;
|
|
2843
2851
|
}
|
|
2844
2852
|
for (n = 0; n < 64; n++) r[n + 68] = (r[n] ^ r[n + 4]) >>> 0;
|
|
2845
2853
|
return r;
|
|
@@ -2849,7 +2857,15 @@ var SM3 = class {
|
|
|
2849
2857
|
let u = this.pe(c, i[0], i[1], i[2]);
|
|
2850
2858
|
u = (4294967295 & (u = u + i[3] + s + f[c + 68])) >>> 0;
|
|
2851
2859
|
let b = this.he(c, i[4], i[5], i[6]);
|
|
2852
|
-
b = (4294967295 & (b = b + i[7] + o + f[c])) >>> 0
|
|
2860
|
+
b = (4294967295 & (b = b + i[7] + o + f[c])) >>> 0;
|
|
2861
|
+
i[3] = i[2];
|
|
2862
|
+
i[2] = this.le(i[1], 9);
|
|
2863
|
+
i[1] = i[0];
|
|
2864
|
+
i[0] = u;
|
|
2865
|
+
i[7] = i[6];
|
|
2866
|
+
i[6] = this.le(i[5], 19);
|
|
2867
|
+
i[5] = i[4];
|
|
2868
|
+
i[4] = (b ^ this.le(b, 9) ^ this.le(b, 17)) >>> 0;
|
|
2853
2869
|
}
|
|
2854
2870
|
for (let l = 0; l < 8; l++) this.reg[l] = (this.reg[l] ^ i[l]) >>> 0;
|
|
2855
2871
|
}
|
|
@@ -3160,7 +3176,7 @@ function generate_random_str() {
|
|
|
3160
3176
|
* @returns 清理后的User-Agent字符串
|
|
3161
3177
|
*/
|
|
3162
3178
|
var cleanUserAgentForSigning = (userAgent) => {
|
|
3163
|
-
return userAgent.replace(/\s+Edg\/[\d
|
|
3179
|
+
return userAgent.replace(/\s+Edg\/[\d.]+/g, "");
|
|
3164
3180
|
};
|
|
3165
3181
|
/**
|
|
3166
3182
|
* 抖音a_bogus签名算法
|
|
@@ -4957,7 +4973,7 @@ async function fetchTextWork(options, cookie, requestConfig) {
|
|
|
4957
4973
|
* console.log(result.data) // 自动解析的作品数据
|
|
4958
4974
|
* ```
|
|
4959
4975
|
*/
|
|
4960
|
-
async function parseWork
|
|
4976
|
+
async function parseWork(options, cookie, requestConfig) {
|
|
4961
4977
|
return fetchDouyinInternal("parseWork", options, {
|
|
4962
4978
|
cookie,
|
|
4963
4979
|
requestConfig
|
|
@@ -5003,7 +5019,7 @@ function createBoundDouyinFetcher(cookie, requestConfig) {
|
|
|
5003
5019
|
fetchImageAlbumWork: (options, reqConfig) => fetchImageAlbumWork(options, cookie, reqConfig ?? requestConfig),
|
|
5004
5020
|
fetchSlidesWork: (options, reqConfig) => fetchSlidesWork(options, cookie, reqConfig ?? requestConfig),
|
|
5005
5021
|
fetchTextWork: (options, reqConfig) => fetchTextWork(options, cookie, reqConfig ?? requestConfig),
|
|
5006
|
-
parseWork: (options, reqConfig) => parseWork
|
|
5022
|
+
parseWork: (options, reqConfig) => parseWork(options, cookie, reqConfig ?? requestConfig),
|
|
5007
5023
|
fetchDanmakuList: (options, reqConfig) => fetchDanmakuList(options, cookie, reqConfig ?? requestConfig),
|
|
5008
5024
|
fetchWorkComments: (options, reqConfig) => fetchWorkComments$1(options, cookie, reqConfig ?? requestConfig),
|
|
5009
5025
|
fetchCommentReplies: (options, reqConfig) => fetchCommentReplies(options, cookie, reqConfig ?? requestConfig),
|
|
@@ -5041,7 +5057,7 @@ var douyinFetcher$1 = {
|
|
|
5041
5057
|
fetchImageAlbumWork,
|
|
5042
5058
|
fetchSlidesWork,
|
|
5043
5059
|
fetchTextWork,
|
|
5044
|
-
parseWork
|
|
5060
|
+
parseWork,
|
|
5045
5061
|
fetchDanmakuList,
|
|
5046
5062
|
fetchWorkComments: fetchWorkComments$1,
|
|
5047
5063
|
fetchCommentReplies,
|
|
@@ -7919,7 +7935,7 @@ var createNetworkErrorResult = (error, retries) => {
|
|
|
7919
7935
|
* @returns 清理后的User-Agent字符串
|
|
7920
7936
|
*/
|
|
7921
7937
|
var cleanUserAgent = (userAgent) => {
|
|
7922
|
-
return userAgent.replace(/\s+Edg\/[\d
|
|
7938
|
+
return userAgent.replace(/\s+Edg\/[\d.]+/g, "");
|
|
7923
7939
|
};
|
|
7924
7940
|
/**
|
|
7925
7941
|
* 执行网络请求并返回数据(带自动重试)
|
|
@@ -8171,8 +8187,7 @@ var qtparam = async (BASEURL, cookie) => {
|
|
|
8171
8187
|
126,
|
|
8172
8188
|
127
|
|
8173
8189
|
];
|
|
8174
|
-
|
|
8175
|
-
logininfo.data.vipStatus === 1 ? isvip = true : isvip = false;
|
|
8190
|
+
const isvip = logininfo.data.vipStatus === 1;
|
|
8176
8191
|
if (isvip) return {
|
|
8177
8192
|
QUERY: `&fnval=4048&fourk=1&${sign}`,
|
|
8178
8193
|
STATUS: "isLogin",
|
|
@@ -8313,7 +8328,7 @@ var DmSegMobileReplyType = null;
|
|
|
8313
8328
|
*/
|
|
8314
8329
|
function getProtoType() {
|
|
8315
8330
|
if (DmSegMobileReplyType) return DmSegMobileReplyType;
|
|
8316
|
-
DmSegMobileReplyType = import_protobufjs.
|
|
8331
|
+
DmSegMobileReplyType = import_protobufjs.Root.fromJSON(DANMAKU_PROTO_JSON).lookupType("bilibili.community.service.dm.v1.DmSegMobileReply");
|
|
8317
8332
|
return DmSegMobileReplyType;
|
|
8318
8333
|
}
|
|
8319
8334
|
/**
|
|
@@ -9330,15 +9345,6 @@ var kuaishouUtils = {
|
|
|
9330
9345
|
//#endregion
|
|
9331
9346
|
//#region ../amagi/packages/core/src/platform/xiaohongshu/XiaohongshuApi.ts
|
|
9332
9347
|
/**
|
|
9333
|
-
* 小红书 API 模块 (已废弃)
|
|
9334
|
-
*
|
|
9335
|
-
* 此模块中的 API 已在 v6 版本废弃
|
|
9336
|
-
* 请使用 xiaohongshuFetcher 或 client.xiaohongshu.fetcher 替代
|
|
9337
|
-
*
|
|
9338
|
-
* @module platform/xiaohongshu/XiaohongshuApi
|
|
9339
|
-
* @deprecated v6 已废弃,请使用 fetcher API 替代
|
|
9340
|
-
*/
|
|
9341
|
-
/**
|
|
9342
9348
|
* 创建废弃的 API 存根函数
|
|
9343
9349
|
*/
|
|
9344
9350
|
var createDeprecatedStub = (methodName) => {
|
|
@@ -10200,62 +10206,79 @@ var Cfg = class {
|
|
|
10200
10206
|
dirCfgPath;
|
|
10201
10207
|
/** 默认配置文件路径 */
|
|
10202
10208
|
defCfgPath;
|
|
10209
|
+
/** JSON 配置文件路径 */
|
|
10210
|
+
get jsonConfigPath() {
|
|
10211
|
+
return `${this.dirCfgPath}/config.json`;
|
|
10212
|
+
}
|
|
10213
|
+
get defJsonConfigPath() {
|
|
10214
|
+
return `${this.defCfgPath}/config.json`;
|
|
10215
|
+
}
|
|
10203
10216
|
constructor() {
|
|
10204
10217
|
this.dirCfgPath = `${karinPathBase}/${Root.pluginName}/config`;
|
|
10205
10218
|
this.defCfgPath = `${Root.pluginPath}/config/default_config/`;
|
|
10206
10219
|
}
|
|
10207
10220
|
/** 初始化配置 */
|
|
10208
10221
|
initCfg() {
|
|
10209
|
-
|
|
10210
|
-
const
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10222
|
+
if (!fs.existsSync(this.dirCfgPath)) fs.mkdirSync(this.dirCfgPath, { recursive: true });
|
|
10223
|
+
const hasJson = fs.existsSync(this.jsonConfigPath);
|
|
10224
|
+
const hasYaml = [
|
|
10225
|
+
"app",
|
|
10226
|
+
"bilibili",
|
|
10227
|
+
"cookies",
|
|
10228
|
+
"douyin",
|
|
10229
|
+
"kuaishou",
|
|
10230
|
+
"pushlist",
|
|
10231
|
+
"request",
|
|
10232
|
+
"upload",
|
|
10233
|
+
"xiaohongshu"
|
|
10234
|
+
].some((name) => fs.existsSync(`${this.dirCfgPath}/${name}.yaml`));
|
|
10235
|
+
if (!hasJson && !hasYaml && fs.existsSync(this.defJsonConfigPath)) fs.copyFileSync(this.defJsonConfigPath, this.jsonConfigPath);
|
|
10236
|
+
if (fs.existsSync(this.jsonConfigPath) && fs.existsSync(this.defJsonConfigPath)) {
|
|
10237
|
+
const userConfig = this.getJson();
|
|
10238
|
+
const defConfig = JSON.parse(fs.readFileSync(this.defJsonConfigPath, "utf8"));
|
|
10239
|
+
const merged = this.mergeJsonConfigs(defConfig, userConfig);
|
|
10240
|
+
if (JSON.stringify(merged) !== JSON.stringify(userConfig)) this.setJson(merged);
|
|
10216
10241
|
}
|
|
10217
|
-
/**
|
|
10218
|
-
* @description 监听配置文件
|
|
10219
|
-
*/
|
|
10220
10242
|
setTimeout(() => {
|
|
10221
|
-
|
|
10222
|
-
|
|
10223
|
-
|
|
10224
|
-
logger.
|
|
10225
|
-
import("./amagiClient.js").then(({ reloadAmagiConfig }) => {
|
|
10226
|
-
reloadAmagiConfig();
|
|
10227
|
-
}).catch((error) => {
|
|
10228
|
-
logger.error(`[Config] 重载 Amagi Client 失败: ${error}`);
|
|
10229
|
-
});
|
|
10243
|
+
if (fs.existsSync(this.jsonConfigPath)) watch(this.jsonConfigPath, (old, now) => {
|
|
10244
|
+
if (old?.amagi !== now?.amagi) {
|
|
10245
|
+
logger.debug("[karin-plugin-kkk][Config] 检测到 amagi 配置变化,正在重载 Amagi Client...");
|
|
10246
|
+
import("./amagiClient.js").then(({ reloadAmagiConfig }) => reloadAmagiConfig()).catch((error) => logger.error(`[karin-plugin-kkk][Config] 重载 Amagi Client 失败: ${error}`));
|
|
10230
10247
|
}
|
|
10231
|
-
})
|
|
10248
|
+
});
|
|
10232
10249
|
}, 2e3);
|
|
10233
10250
|
return this;
|
|
10234
10251
|
}
|
|
10252
|
+
/** 读取 JSON 配置 */
|
|
10253
|
+
getJson() {
|
|
10254
|
+
return JSON.parse(fs.readFileSync(this.jsonConfigPath, "utf8"));
|
|
10255
|
+
}
|
|
10256
|
+
/** 写入 JSON 配置 */
|
|
10257
|
+
setJson(config) {
|
|
10258
|
+
fs.writeFileSync(this.jsonConfigPath, JSON.stringify(config, null, 2), "utf8");
|
|
10259
|
+
}
|
|
10260
|
+
/** 深度合并 JSON 配置(保留用户值,补充默认值) */
|
|
10261
|
+
mergeJsonConfigs(def, user) {
|
|
10262
|
+
if (!def || typeof def !== "object" || Array.isArray(def)) return user ?? def;
|
|
10263
|
+
if (!user || typeof user !== "object" || Array.isArray(user)) return user ?? def;
|
|
10264
|
+
const result = { ...def };
|
|
10265
|
+
for (const key in user) if (typeof user[key] === "object" && !Array.isArray(user[key]) && user[key] !== null) result[key] = this.mergeJsonConfigs(def[key], user[key]);
|
|
10266
|
+
else result[key] = user[key];
|
|
10267
|
+
return result;
|
|
10268
|
+
}
|
|
10235
10269
|
/**
|
|
10236
|
-
*
|
|
10237
|
-
* @param name
|
|
10238
|
-
* @returns 返回合并后的配置
|
|
10270
|
+
* 获取配置
|
|
10271
|
+
* @param name 配置键名
|
|
10239
10272
|
*/
|
|
10240
10273
|
getDefOrConfig(name) {
|
|
10241
|
-
|
|
10242
|
-
const config = this.getYaml("config", name);
|
|
10243
|
-
return {
|
|
10244
|
-
...def,
|
|
10245
|
-
...config
|
|
10246
|
-
};
|
|
10274
|
+
return this.getJson()[name] || {};
|
|
10247
10275
|
}
|
|
10248
10276
|
/** 获取所有配置文件 */
|
|
10249
10277
|
async All() {
|
|
10250
10278
|
const { getDouyinDB, getBilibiliDB } = await import("./db.js");
|
|
10251
10279
|
const douyinDB = await getDouyinDB();
|
|
10252
10280
|
const bilibiliDB = await getBilibiliDB();
|
|
10253
|
-
const allConfig =
|
|
10254
|
-
const files = fs.readdirSync(this.defCfgPath);
|
|
10255
|
-
for (const file of files) {
|
|
10256
|
-
const fileName = path.basename(file, ".yaml");
|
|
10257
|
-
allConfig[fileName] = this.getDefOrConfig(fileName) || {};
|
|
10258
|
-
}
|
|
10281
|
+
const allConfig = this.getJson();
|
|
10259
10282
|
if (allConfig.pushlist) try {
|
|
10260
10283
|
if (allConfig.pushlist.douyin) for (const item of allConfig.pushlist.douyin) {
|
|
10261
10284
|
const filterWords = await douyinDB.getFilterWords(item.sec_uid);
|
|
@@ -10279,71 +10302,62 @@ var Cfg = class {
|
|
|
10279
10302
|
return allConfig;
|
|
10280
10303
|
}
|
|
10281
10304
|
/**
|
|
10282
|
-
*
|
|
10283
|
-
* @param type 配置文件类型
|
|
10284
|
-
* @param name 配置文件名
|
|
10285
|
-
* @returns 返回 YAML 文件内容
|
|
10286
|
-
*/
|
|
10287
|
-
getYaml(type, name) {
|
|
10288
|
-
return requireFileSync(type === "config" ? `${this.dirCfgPath}/${name}.yaml` : `${this.defCfgPath}/${name}.yaml`, { force: true });
|
|
10289
|
-
}
|
|
10290
|
-
/**
|
|
10291
|
-
* 修改配置文件
|
|
10292
|
-
* @param name 文件名
|
|
10293
|
-
* @param key 键
|
|
10294
|
-
* @param value 值
|
|
10295
|
-
* @param type 配置文件类型,默认为用户配置文件 `config`
|
|
10296
|
-
*/
|
|
10297
|
-
Modify(name, key, value, type = "config") {
|
|
10298
|
-
const path = type === "config" ? `${this.dirCfgPath}/${name}.yaml` : `${this.defCfgPath}/${name}.yaml`;
|
|
10299
|
-
const yamlData = YAML.parseDocument(fs.readFileSync(path, "utf8"));
|
|
10300
|
-
const keys = key.split(".");
|
|
10301
|
-
yamlData.setIn(keys, value);
|
|
10302
|
-
fs.writeFileSync(path, yamlData.toString({ lineWidth: -1 }), "utf8");
|
|
10303
|
-
}
|
|
10304
|
-
/**
|
|
10305
|
-
* 修改整个配置文件,保留注释
|
|
10305
|
+
* 修改整个配置文件
|
|
10306
10306
|
* @param name 文件名
|
|
10307
10307
|
* @param config 完整的配置对象
|
|
10308
|
-
* @param type 配置文件类型,默认为用户配置文件 `config`
|
|
10309
10308
|
*/
|
|
10310
10309
|
async ModifyPro(name, config, type = "config") {
|
|
10310
|
+
if (type !== "config") return false;
|
|
10311
10311
|
const { getDouyinDB, getBilibiliDB } = await import("./db.js");
|
|
10312
10312
|
const douyinDB = await getDouyinDB();
|
|
10313
10313
|
const bilibiliDB = await getBilibiliDB();
|
|
10314
|
-
const
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
const cleanedConfig = { ...config };
|
|
10321
|
-
if ("douyin" in cleanedConfig) cleanedConfig.douyin = cleanedConfig.douyin.map((item) => {
|
|
10322
|
-
const { Keywords, Tags, filterMode, ...rest } = item;
|
|
10323
|
-
return rest;
|
|
10324
|
-
});
|
|
10325
|
-
if ("bilibili" in cleanedConfig) cleanedConfig.bilibili = cleanedConfig.bilibili.map((item) => {
|
|
10326
|
-
const { Keywords, Tags, filterMode, ...rest } = item;
|
|
10327
|
-
return rest;
|
|
10328
|
-
});
|
|
10329
|
-
filterCfg = cleanedConfig;
|
|
10330
|
-
}
|
|
10331
|
-
const newConfigNode = YAML.parseDocument(YAML.stringify(filterCfg)).contents;
|
|
10332
|
-
this.deepMergeYaml(doc.contents, newConfigNode);
|
|
10333
|
-
fs.writeFileSync(filePath, doc.toString({ lineWidth: -1 }), "utf8");
|
|
10334
|
-
if ("douyin" in config) {
|
|
10335
|
-
await this.syncFilterConfigToDb(config.douyin, douyinDB, "sec_uid");
|
|
10336
|
-
logger.debug("已同步抖音过滤配置到数据库");
|
|
10337
|
-
}
|
|
10338
|
-
if ("bilibili" in config) {
|
|
10339
|
-
await this.syncFilterConfigToDb(config.bilibili, bilibiliDB, "host_mid");
|
|
10340
|
-
logger.debug("已同步B站过滤配置到数据库");
|
|
10341
|
-
}
|
|
10342
|
-
return true;
|
|
10343
|
-
} catch (error) {
|
|
10344
|
-
logger.error(`修改配置文件时发生错误:${error}`);
|
|
10345
|
-
return false;
|
|
10314
|
+
const jsonConfig = this.getJson();
|
|
10315
|
+
jsonConfig[name] = config;
|
|
10316
|
+
this.setJson(jsonConfig);
|
|
10317
|
+
if ("douyin" in config) {
|
|
10318
|
+
await this.syncFilterConfigToDb(config.douyin, douyinDB, "sec_uid");
|
|
10319
|
+
logger.debug("[karin-plugin-kkk][Config] 已同步抖音过滤配置到数据库");
|
|
10346
10320
|
}
|
|
10321
|
+
if ("bilibili" in config) {
|
|
10322
|
+
await this.syncFilterConfigToDb(config.bilibili, bilibiliDB, "host_mid");
|
|
10323
|
+
logger.debug("[karin-plugin-kkk][Config] 已同步B站过滤配置到数据库");
|
|
10324
|
+
}
|
|
10325
|
+
return true;
|
|
10326
|
+
}
|
|
10327
|
+
/**
|
|
10328
|
+
* 修改配置字段(支持深层嵌套路径,包括数组索引)
|
|
10329
|
+
* @param moduleName 模块名
|
|
10330
|
+
* @param path 字段路径,如 'push.switch' 或 'cookies.douyin' 或 'list[0].name'
|
|
10331
|
+
* @param value 新值
|
|
10332
|
+
*/
|
|
10333
|
+
async Modify(moduleName, path, value) {
|
|
10334
|
+
const jsonConfig = this.getJson();
|
|
10335
|
+
const pathKeys = this.parsePath(path);
|
|
10336
|
+
let target = jsonConfig[moduleName];
|
|
10337
|
+
for (let i = 0; i < pathKeys.length - 1; i++) {
|
|
10338
|
+
const key = pathKeys[i];
|
|
10339
|
+
if (!(key in target)) target[key] = {};
|
|
10340
|
+
target = target[key];
|
|
10341
|
+
}
|
|
10342
|
+
const lastKey = pathKeys[pathKeys.length - 1];
|
|
10343
|
+
target[lastKey] = value;
|
|
10344
|
+
this.setJson(jsonConfig);
|
|
10345
|
+
if (moduleName === "pushlist") {
|
|
10346
|
+
const { getDouyinDB, getBilibiliDB } = await import("./db.js");
|
|
10347
|
+
const douyinDB = await getDouyinDB();
|
|
10348
|
+
const bilibiliDB = await getBilibiliDB();
|
|
10349
|
+
if ("douyin" in jsonConfig[moduleName]) await this.syncFilterConfigToDb(jsonConfig[moduleName].douyin, douyinDB, "sec_uid");
|
|
10350
|
+
if ("bilibili" in jsonConfig[moduleName]) await this.syncFilterConfigToDb(jsonConfig[moduleName].bilibili, bilibiliDB, "host_mid");
|
|
10351
|
+
}
|
|
10352
|
+
return true;
|
|
10353
|
+
}
|
|
10354
|
+
/**
|
|
10355
|
+
* 解析路径字符串,支持点号和数组索引
|
|
10356
|
+
* 'a.b.c' => ['a', 'b', 'c']
|
|
10357
|
+
* 'a[0].b' => ['a', '0', 'b']
|
|
10358
|
+
*/
|
|
10359
|
+
parsePath(path) {
|
|
10360
|
+
return path.replace(/\[(\d+)\]/g, ".$1").split(".");
|
|
10347
10361
|
}
|
|
10348
10362
|
/**
|
|
10349
10363
|
* 同步过滤配置到数据库
|
|
@@ -10369,86 +10383,115 @@ var Cfg = class {
|
|
|
10369
10383
|
}
|
|
10370
10384
|
}
|
|
10371
10385
|
/**
|
|
10372
|
-
* 深度合并YAML节点(保留目标注释)
|
|
10373
|
-
* @param target 目标节点(保留注释的原始节点)
|
|
10374
|
-
* @param source 源节点(提供新值的节点)
|
|
10375
|
-
*/
|
|
10376
|
-
deepMergeYaml(target, source) {
|
|
10377
|
-
if (YAML.isMap(target) && YAML.isMap(source)) for (const pair of source.items) {
|
|
10378
|
-
const key = pair.key;
|
|
10379
|
-
const sourceVal = pair.value;
|
|
10380
|
-
const targetVal = target.get(key);
|
|
10381
|
-
if (targetVal === void 0) target.set(key, sourceVal);
|
|
10382
|
-
else if (YAML.isMap(targetVal) && YAML.isMap(sourceVal)) this.deepMergeYaml(targetVal, sourceVal);
|
|
10383
|
-
else if (YAML.isSeq(targetVal) && YAML.isSeq(sourceVal)) {
|
|
10384
|
-
targetVal.items = sourceVal.items;
|
|
10385
|
-
targetVal.flow = sourceVal.flow;
|
|
10386
|
-
} else target.set(key, sourceVal);
|
|
10387
|
-
}
|
|
10388
|
-
}
|
|
10389
|
-
mergeObjectsWithPriority(userDoc, defaultDoc) {
|
|
10390
|
-
let differences = false;
|
|
10391
|
-
/** 合并 YAML 对象,确保注释保留 */
|
|
10392
|
-
const mergeYamlNodes = (target, source) => {
|
|
10393
|
-
if (YAML.isMap(target) && YAML.isMap(source)) for (const pair of source.items) {
|
|
10394
|
-
const key = pair.key;
|
|
10395
|
-
const value = pair.value;
|
|
10396
|
-
const existing = target.get(key);
|
|
10397
|
-
if (existing === void 0) {
|
|
10398
|
-
differences = true;
|
|
10399
|
-
target.set(key, value);
|
|
10400
|
-
} else if (YAML.isMap(value) && YAML.isMap(existing)) mergeYamlNodes(existing, value);
|
|
10401
|
-
else if (existing !== value) {
|
|
10402
|
-
differences = true;
|
|
10403
|
-
target.set(key, value);
|
|
10404
|
-
}
|
|
10405
|
-
}
|
|
10406
|
-
};
|
|
10407
|
-
mergeYamlNodes(defaultDoc.contents, userDoc.contents);
|
|
10408
|
-
return {
|
|
10409
|
-
differences,
|
|
10410
|
-
result: defaultDoc
|
|
10411
|
-
};
|
|
10412
|
-
}
|
|
10413
|
-
/**
|
|
10414
10386
|
* 同步配置到数据库
|
|
10415
|
-
* 这个方法应该在所有模块都初始化完成后调用
|
|
10416
10387
|
*/
|
|
10417
10388
|
async syncConfigToDatabase() {
|
|
10418
10389
|
try {
|
|
10419
10390
|
const { getDouyinDB, getBilibiliDB } = await import("./db.js");
|
|
10420
10391
|
const douyinDB = await getDouyinDB();
|
|
10421
10392
|
const bilibiliDB = await getBilibiliDB();
|
|
10422
|
-
const pushCfg = this.
|
|
10423
|
-
if (pushCfg
|
|
10424
|
-
if (pushCfg
|
|
10425
|
-
logger.debug("[BilibiliDB] + [DouyinDB] 配置已同步到数据库");
|
|
10393
|
+
const pushCfg = this.getJson().pushlist;
|
|
10394
|
+
if (pushCfg?.bilibili) await bilibiliDB.syncConfigSubscriptions(pushCfg.bilibili);
|
|
10395
|
+
if (pushCfg?.douyin) await douyinDB.syncConfigSubscriptions(pushCfg.douyin);
|
|
10396
|
+
logger.debug("[karin-plugin-kkk][BilibiliDB] + [DouyinDB] 配置已同步到数据库");
|
|
10426
10397
|
} catch (error) {
|
|
10427
|
-
logger.error("同步配置到数据库失败:", error);
|
|
10398
|
+
logger.error("[karin-plugin-kkk]同步配置到数据库失败:", error);
|
|
10428
10399
|
}
|
|
10429
10400
|
}
|
|
10430
10401
|
};
|
|
10431
|
-
/**
|
|
10432
|
-
* 配置实例缓存
|
|
10433
|
-
*/
|
|
10434
10402
|
var configInstance = null;
|
|
10435
|
-
|
|
10436
|
-
* 获取配置实例(延迟初始化)
|
|
10437
|
-
* @returns 配置实例
|
|
10438
|
-
*/
|
|
10403
|
+
var migrationExecuted = false;
|
|
10439
10404
|
var getConfigInstance = () => {
|
|
10440
|
-
if (!configInstance)
|
|
10441
|
-
if (
|
|
10442
|
-
|
|
10443
|
-
|
|
10405
|
+
if (!configInstance) {
|
|
10406
|
+
if (!migrationExecuted) {
|
|
10407
|
+
migrateConfigFromYaml();
|
|
10408
|
+
migrationExecuted = true;
|
|
10409
|
+
}
|
|
10410
|
+
configInstance = new Proxy(new Cfg().initCfg(), { get(target, prop) {
|
|
10411
|
+
if (prop in target) return target[prop];
|
|
10412
|
+
return target.getDefOrConfig(prop);
|
|
10413
|
+
} });
|
|
10414
|
+
}
|
|
10444
10415
|
return configInstance;
|
|
10445
10416
|
};
|
|
10446
|
-
/**
|
|
10447
|
-
* 配置对象代理
|
|
10448
|
-
*/
|
|
10449
10417
|
var Config = new Proxy({}, { get(target, prop) {
|
|
10450
10418
|
return getConfigInstance()[prop];
|
|
10451
10419
|
} });
|
|
10420
|
+
/**
|
|
10421
|
+
* 从 YAML 迁移到 JSON
|
|
10422
|
+
*/
|
|
10423
|
+
var migrateConfigFromYaml = () => {
|
|
10424
|
+
const dirCfgPath = `${karinPathBase}/${Root.pluginName}/config`;
|
|
10425
|
+
const defCfgPath = `${Root.pluginPath}/config/default_config/`;
|
|
10426
|
+
const jsonConfigPath = `${dirCfgPath}/config.json`;
|
|
10427
|
+
if (fs.existsSync(jsonConfigPath)) return;
|
|
10428
|
+
const yamlFiles = [
|
|
10429
|
+
"app",
|
|
10430
|
+
"bilibili",
|
|
10431
|
+
"cookies",
|
|
10432
|
+
"douyin",
|
|
10433
|
+
"kuaishou",
|
|
10434
|
+
"pushlist",
|
|
10435
|
+
"request",
|
|
10436
|
+
"upload",
|
|
10437
|
+
"xiaohongshu"
|
|
10438
|
+
];
|
|
10439
|
+
if (!yamlFiles.some((name) => fs.existsSync(`${dirCfgPath}/${name}.yaml`))) return;
|
|
10440
|
+
try {
|
|
10441
|
+
const defYaml = {};
|
|
10442
|
+
for (const name of yamlFiles) {
|
|
10443
|
+
const file = `${defCfgPath}/${name}.yaml`;
|
|
10444
|
+
if (fs.existsSync(file)) defYaml[name] = YAML.parse(fs.readFileSync(file, "utf8"));
|
|
10445
|
+
}
|
|
10446
|
+
const userYaml = {};
|
|
10447
|
+
for (const name of yamlFiles) {
|
|
10448
|
+
const file = `${dirCfgPath}/${name}.yaml`;
|
|
10449
|
+
if (fs.existsSync(file)) userYaml[name] = filterKeys(YAML.parse(fs.readFileSync(file, "utf8")), defYaml[name]);
|
|
10450
|
+
}
|
|
10451
|
+
const jsonConfig = {
|
|
10452
|
+
amagi: {
|
|
10453
|
+
timeout: userYaml.request?.timeout ?? defYaml.request?.timeout,
|
|
10454
|
+
"User-Agent": userYaml.request?.["User-Agent"] ?? defYaml.request?.["User-Agent"],
|
|
10455
|
+
proxy: userYaml.request?.proxy ?? defYaml.request?.proxy,
|
|
10456
|
+
cookies: userYaml.cookies || defYaml.cookies || {},
|
|
10457
|
+
APIServer: userYaml.app?.APIServer ?? defYaml.app?.APIServer,
|
|
10458
|
+
APIServerMount: userYaml.app?.APIServerMount ?? defYaml.app?.APIServerMount,
|
|
10459
|
+
APIServerPort: userYaml.app?.APIServerPort ?? defYaml.app?.APIServerPort
|
|
10460
|
+
},
|
|
10461
|
+
app: {
|
|
10462
|
+
...defYaml.app,
|
|
10463
|
+
...defYaml.upload,
|
|
10464
|
+
...userYaml.app,
|
|
10465
|
+
...userYaml.upload
|
|
10466
|
+
},
|
|
10467
|
+
douyin: userYaml.douyin || defYaml.douyin,
|
|
10468
|
+
bilibili: userYaml.bilibili || defYaml.bilibili,
|
|
10469
|
+
kuaishou: userYaml.kuaishou || defYaml.kuaishou,
|
|
10470
|
+
xiaohongshu: userYaml.xiaohongshu || defYaml.xiaohongshu,
|
|
10471
|
+
pushlist: userYaml.pushlist || defYaml.pushlist
|
|
10472
|
+
};
|
|
10473
|
+
fs.writeFileSync(jsonConfigPath, JSON.stringify(jsonConfig, null, 2), "utf8");
|
|
10474
|
+
const backupDir = `${dirCfgPath}/yaml_backup_${Date.now()}`;
|
|
10475
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
10476
|
+
for (const name of yamlFiles) {
|
|
10477
|
+
const file = `${dirCfgPath}/${name}.yaml`;
|
|
10478
|
+
if (fs.existsSync(file)) {
|
|
10479
|
+
fs.copyFileSync(file, `${backupDir}/${name}.yaml`);
|
|
10480
|
+
fs.unlinkSync(file);
|
|
10481
|
+
}
|
|
10482
|
+
}
|
|
10483
|
+
logger.info(`[karin-plugin-kkk][Config] YAML 配置已迁移到 config.json,备份保存在 ${backupDir}`);
|
|
10484
|
+
} catch (error) {
|
|
10485
|
+
logger.error(`[karin-plugin-kkk][Config] YAML 迁移失败: ${error}`);
|
|
10486
|
+
}
|
|
10487
|
+
};
|
|
10488
|
+
var filterKeys = (user, template) => {
|
|
10489
|
+
if (!template || typeof template !== "object") return user;
|
|
10490
|
+
if (Array.isArray(template)) return user;
|
|
10491
|
+
const result = {};
|
|
10492
|
+
for (const key in template) if (key in user) result[key] = typeof template[key] === "object" && !Array.isArray(template[key]) ? filterKeys(user[key], template[key]) : user[key];
|
|
10493
|
+
return result;
|
|
10494
|
+
};
|
|
10452
10495
|
//#endregion
|
|
10453
10496
|
//#region src/module/utils/amagiClient.ts
|
|
10454
10497
|
/**
|
|
@@ -10476,48 +10519,24 @@ var AmagiBase = class {
|
|
|
10476
10519
|
}
|
|
10477
10520
|
/** 创建解析库实例 */
|
|
10478
10521
|
createAmagiClient = () => {
|
|
10522
|
+
const amagi = Config.amagi;
|
|
10479
10523
|
return Client({
|
|
10480
|
-
cookies: {
|
|
10481
|
-
douyin: Config.cookies.douyin,
|
|
10482
|
-
bilibili: Config.cookies.bilibili,
|
|
10483
|
-
kuaishou: Config.cookies.kuaishou,
|
|
10484
|
-
xiaohongshu: Config.cookies.xiaohongshu
|
|
10485
|
-
},
|
|
10524
|
+
cookies: amagi.cookies || {},
|
|
10486
10525
|
request: {
|
|
10487
|
-
timeout:
|
|
10488
|
-
headers: { "User-Agent":
|
|
10489
|
-
proxy:
|
|
10526
|
+
timeout: amagi.timeout,
|
|
10527
|
+
headers: { "User-Agent": amagi["User-Agent"] },
|
|
10528
|
+
proxy: amagi.proxy?.switch ? amagi.proxy : false
|
|
10490
10529
|
}
|
|
10491
10530
|
});
|
|
10492
10531
|
};
|
|
10493
10532
|
/**
|
|
10494
10533
|
* 重载配置 - 重新创建 Amagi Client 实例
|
|
10495
|
-
* 当配置文件中的 cookies 或 request 配置更新后,调用此方法使新配置生效
|
|
10496
10534
|
*/
|
|
10497
10535
|
reloadConfig() {
|
|
10498
10536
|
logger.debug("[AmagiClient] 检测到配置变化,正在重载...");
|
|
10499
|
-
const oldCookies = {
|
|
10500
|
-
douyin: Config.cookies.douyin?.substring(0, 20) + "...",
|
|
10501
|
-
bilibili: Config.cookies.bilibili?.substring(0, 20) + "...",
|
|
10502
|
-
kuaishou: Config.cookies.kuaishou?.substring(0, 20) + "...",
|
|
10503
|
-
xiaohongshu: Config.cookies.xiaohongshu?.substring(0, 20) + "..."
|
|
10504
|
-
};
|
|
10505
10537
|
const client = this.createAmagiClient();
|
|
10506
10538
|
this.amagi = this.wrapAmagiClient(client);
|
|
10507
|
-
const newCookies = {
|
|
10508
|
-
douyin: Config.cookies.douyin?.substring(0, 20) + "...",
|
|
10509
|
-
bilibili: Config.cookies.bilibili?.substring(0, 20) + "...",
|
|
10510
|
-
kuaishou: Config.cookies.kuaishou?.substring(0, 20) + "...",
|
|
10511
|
-
xiaohongshu: Config.cookies.xiaohongshu?.substring(0, 20) + "..."
|
|
10512
|
-
};
|
|
10513
10539
|
logger.debug("[AmagiClient] 配置重载完成");
|
|
10514
|
-
logger.debug(`[AmagiClient] Cookie 变化对比:\n${util.inspect({
|
|
10515
|
-
旧配置: oldCookies,
|
|
10516
|
-
新配置: newCookies
|
|
10517
|
-
}, {
|
|
10518
|
-
colors: true,
|
|
10519
|
-
depth: 2
|
|
10520
|
-
})}`);
|
|
10521
10540
|
}
|
|
10522
10541
|
/** 包装解析库实例,递归代理所有嵌套对象的方法 */
|
|
10523
10542
|
wrapAmagiClient = (client) => {
|
|
@@ -10559,20 +10578,14 @@ var AmagiBase = class {
|
|
|
10559
10578
|
};
|
|
10560
10579
|
};
|
|
10561
10580
|
/**
|
|
10562
|
-
*
|
|
10563
|
-
* 在 softFetch 中配置后,对应接口调用不会抛出异常,而是原样返回 Result,
|
|
10564
|
-
* 由业务代码根据 code 决定后续处理逻辑。
|
|
10565
|
-
*
|
|
10581
|
+
* 软错误码常量
|
|
10566
10582
|
* Bilibili:
|
|
10567
10583
|
* 12061 - UP主已关闭评论区
|
|
10568
10584
|
*/
|
|
10569
10585
|
var SOFT_ERROR_CODES = { BILIBILI_COMMENTS_DISABLED: 12061 };
|
|
10570
10586
|
/**
|
|
10571
|
-
* 调用 amagi fetcher 方法,允许特定错误码不抛出异常而是以 Result
|
|
10572
|
-
*
|
|
10573
|
-
* 业务代码收到返回值后,通过判断 result.code 决定继续解析还是返回提示。
|
|
10574
|
-
*
|
|
10575
|
-
* @param fn - 经过代理包装的 amagi 方法调用
|
|
10587
|
+
* 调用 amagi fetcher 方法,允许特定错误码不抛出异常而是以 Result 形式返回
|
|
10588
|
+
* @param fn - 经过代理包装的 amagi 方法调用
|
|
10576
10589
|
* @param allowedCodes - 不应抛出异常的错误码列表
|
|
10577
10590
|
*/
|
|
10578
10591
|
var softFetch = async (fn, allowedCodes) => {
|
|
@@ -10589,37 +10602,13 @@ var softFetch = async (fn, allowedCodes) => {
|
|
|
10589
10602
|
throw err;
|
|
10590
10603
|
}
|
|
10591
10604
|
};
|
|
10592
|
-
/** 获取已初始化的解析库实例(单例) */
|
|
10593
|
-
var createLiveProxy = (getter) => {
|
|
10594
|
-
return new Proxy({}, { get(_target, prop) {
|
|
10595
|
-
const current = getter();
|
|
10596
|
-
const value = Reflect.get(current, prop);
|
|
10597
|
-
if (typeof value === "function") return value.bind(current);
|
|
10598
|
-
if (value && typeof value === "object") return createLiveProxy(() => Reflect.get(getter(), prop));
|
|
10599
|
-
return value;
|
|
10600
|
-
} });
|
|
10601
|
-
};
|
|
10602
10605
|
var amagiClientInstance = new AmagiBase();
|
|
10603
|
-
|
|
10604
|
-
var amagiClient = createLiveProxy(() => amagiClientInstance.amagi);
|
|
10605
|
-
/**
|
|
10606
|
-
* 重载 Amagi 配置
|
|
10607
|
-
* 当 cookies 或 request 配置更新后调用此方法,使新配置立即生效
|
|
10608
|
-
* @example
|
|
10609
|
-
* ```typescript
|
|
10610
|
-
* // 更新配置后
|
|
10611
|
-
* await Config.Modify('cookies', 'douyin', newCookie)
|
|
10612
|
-
* reloadAmagiConfig() // 重载配置
|
|
10613
|
-
* ```
|
|
10614
|
-
*/
|
|
10606
|
+
var amagiClient = amagiClientInstance.amagi;
|
|
10615
10607
|
var reloadAmagiConfig = () => {
|
|
10616
10608
|
amagiClientInstance.reloadConfig();
|
|
10617
10609
|
};
|
|
10618
|
-
/** B站 Fetcher 实例 */
|
|
10619
10610
|
var bilibiliFetcher = amagiClient.bilibili.fetcher;
|
|
10620
|
-
/** 抖音 Fetcher 实例 */
|
|
10621
10611
|
var douyinFetcher = amagiClient.douyin.fetcher;
|
|
10622
|
-
/** 快手 Fetcher 实例 */
|
|
10623
10612
|
var kuaishouFetcher = amagiClient.kuaishou.fetcher;
|
|
10624
10613
|
amagiClient.xiaohongshu.fetcher;
|
|
10625
10614
|
//#endregion
|
|
@@ -10646,7 +10635,7 @@ var Base = class extends AmagiBase {
|
|
|
10646
10635
|
/**
|
|
10647
10636
|
* 统计每个平台使用最多的机器人ID和使用次数
|
|
10648
10637
|
* @param pushList - 推送列表配置
|
|
10649
|
-
* @returns
|
|
10638
|
+
* @returns
|
|
10650
10639
|
*/
|
|
10651
10640
|
var statBotId$1 = (pushList) => {
|
|
10652
10641
|
const platformBotCount = {
|
|
@@ -10719,12 +10708,12 @@ var uploadFile = async (event, file, videoUrl, options) => {
|
|
|
10719
10708
|
selfId = event.selfId;
|
|
10720
10709
|
contact = event.contact;
|
|
10721
10710
|
}
|
|
10722
|
-
if (Config.
|
|
10711
|
+
if (Config.app.compress && file.totalBytes > Config.app.compresstrigger) {
|
|
10723
10712
|
const Duration = await getMediaDuration(file.filepath);
|
|
10724
|
-
logger.warn(logger.yellow(`视频大小 (${file.totalBytes} MB) 触发压缩条件(设定值:${Config.
|
|
10725
|
-
const message = [segment.text(`视频大小 (${file.totalBytes} MB) 触发压缩条件(设定值:${Config.
|
|
10713
|
+
logger.warn(logger.yellow(`视频大小 (${file.totalBytes} MB) 触发压缩条件(设定值:${Config.app.compresstrigger} MB),正在进行压缩至${Config.app.compressvalue} MB...`));
|
|
10714
|
+
const message = [segment.text(`视频大小 (${file.totalBytes} MB) 触发压缩条件(设定值:${Config.app.compresstrigger} MB),正在进行压缩至${Config.app.compressvalue} MB...`), options?.message_id ? segment.reply(options.message_id) : segment.text("")];
|
|
10726
10715
|
const msg1 = await karin$1.sendMsg(selfId, contact, message);
|
|
10727
|
-
const targetBitrate = Common.calculateBitrate(Config.
|
|
10716
|
+
const targetBitrate = Common.calculateBitrate(Config.app.compresstrigger, Duration) * .75;
|
|
10728
10717
|
const startTime = Date.now();
|
|
10729
10718
|
const outputPath = `${Common.tempDri.video}tmp_${Date.now()}.mp4`;
|
|
10730
10719
|
await compressVideo({
|
|
@@ -10739,8 +10728,8 @@ var uploadFile = async (event, file, videoUrl, options) => {
|
|
|
10739
10728
|
const message2 = [segment.text(`压缩后最终视频大小为: ${newFileSize.toFixed(1)} MB,压缩耗时:${((endTime - startTime) / 1e3).toFixed(1)} 秒`), segment.reply(msg1.messageId)];
|
|
10740
10729
|
await karin$1.sendMsg(selfId, contact, message2);
|
|
10741
10730
|
}
|
|
10742
|
-
if (options) options.useGroupFile = Config.
|
|
10743
|
-
if (Config.
|
|
10731
|
+
if (options) options.useGroupFile = Config.app.usegroupfile && newFileSize > Config.app.groupfilevalue;
|
|
10732
|
+
if (Config.app.videoSendMode === "base64" && !options?.useGroupFile) {
|
|
10744
10733
|
File = `base64://${fs.readFileSync(file.filepath).toString("base64")}`;
|
|
10745
10734
|
logger.mark(`已开启视频文件 base64转换 正在进行${logger.yellow("base64转换中")}...`);
|
|
10746
10735
|
} else File = options?.useGroupFile ? file.filepath : `file://${file.filepath}`;
|
|
@@ -10751,14 +10740,14 @@ var uploadFile = async (event, file, videoUrl, options) => {
|
|
|
10751
10740
|
await bot.uploadFile(contact, File, file.originTitle ? `${file.originTitle}.mp4` : `${File.split("/").pop()}`);
|
|
10752
10741
|
} else {
|
|
10753
10742
|
logger.mark(`${logger.blue("主动消息:")} 视频大小: ${newFileSize.toFixed(1)}MB 正在通过${logger.yellow("karin.sendMsg")}回复...`);
|
|
10754
|
-
(await karin$1.sendMsg(selfId, contact, [segment.video(File)])).messageId ?
|
|
10743
|
+
sendStatus = (await karin$1.sendMsg(selfId, contact, [segment.video(File)])).messageId ? true : false;
|
|
10755
10744
|
}
|
|
10756
10745
|
else if (options?.useGroupFile) {
|
|
10757
10746
|
logger.mark(`${logger.blue("被动消息:")} 视频大小: ${newFileSize.toFixed(1)}MB 正在通过${logger.yellow("e.bot.uploadFile")}回复...`);
|
|
10758
10747
|
await event.bot.uploadFile(event.contact, File, file.originTitle ? `${file.originTitle}.mp4` : `${File.split("/").pop()}`);
|
|
10759
10748
|
} else {
|
|
10760
10749
|
logger.mark(`${logger.blue("被动消息:")} 视频大小: ${newFileSize.toFixed(1)}MB 正在通过${logger.yellow("e.reply")}回复...`);
|
|
10761
|
-
(await event.reply(segment.video(File) || videoUrl)).messageId ?
|
|
10750
|
+
sendStatus = (await event.reply(segment.video(File) || videoUrl)).messageId ? true : false;
|
|
10762
10751
|
}
|
|
10763
10752
|
return sendStatus;
|
|
10764
10753
|
} catch (error) {
|
|
@@ -10768,8 +10757,8 @@ var uploadFile = async (event, file, videoUrl, options) => {
|
|
|
10768
10757
|
} finally {
|
|
10769
10758
|
const filePath = file.filepath;
|
|
10770
10759
|
Common.registerVideoPreview(filePath, Config.app.removeCache, 1800 * 1e3);
|
|
10771
|
-
logger.mark(`临时预览地址:http://localhost:${process.env.HTTP_PORT}/
|
|
10772
|
-
Config.app.removeCache
|
|
10760
|
+
logger.mark(`临时预览地址:http://localhost:${process.env.HTTP_PORT}/kkk/ssr/video/${encodeURIComponent(filePath.split("/").pop() ?? "")}`);
|
|
10761
|
+
if (Config.app.removeCache) logger.info(`文件 ${filePath} 将在 30 分钟后删除`);
|
|
10773
10762
|
setTimeout(async () => {
|
|
10774
10763
|
if (await Common.removeFile(filePath)) Common.markVideoPreviewRemoved(filePath);
|
|
10775
10764
|
}, 1800 * 1e3);
|
|
@@ -10789,8 +10778,8 @@ var downloadVideo = async (event, downloadOpt, uploadOpt) => {
|
|
|
10789
10778
|
}).getHeaders());
|
|
10790
10779
|
const fileSizeInMB = (fileSizeContent / (1024 * 1024)).toFixed(2);
|
|
10791
10780
|
const fileSize = parseInt(parseFloat(fileSizeInMB).toFixed(2));
|
|
10792
|
-
if (fileSizeContent > 0 && Config.
|
|
10793
|
-
const message = segment.text(`视频:「${downloadOpt.title.originTitle ?? "Error: 文件名获取失败"}」大小 (${fileSizeInMB} MB) 超出最大限制(设定值:${Config.
|
|
10781
|
+
if (fileSizeContent > 0 && Config.app.usefilelimit && fileSize > Config.app.filelimit) {
|
|
10782
|
+
const message = segment.text(`视频:「${downloadOpt.title.originTitle ?? "Error: 文件名获取失败"}」大小 (${fileSizeInMB} MB) 超出最大限制(设定值:${Config.app.filelimit} MB),已取消上传`);
|
|
10794
10783
|
const selfId = event.selfId || uploadOpt?.activeOption?.uin;
|
|
10795
10784
|
const contact = event.contact || karin$1.contactGroup(uploadOpt?.activeOption?.group_id) || karin$1.contactFriend(selfId);
|
|
10796
10785
|
await karin$1.sendMsg(selfId, contact, message);
|
|
@@ -10816,7 +10805,7 @@ var downloadVideo = async (event, downloadOpt, uploadOpt) => {
|
|
|
10816
10805
|
*/
|
|
10817
10806
|
var downloadFile = async (videoUrl, opt) => {
|
|
10818
10807
|
const startTime = Date.now();
|
|
10819
|
-
const uploadConfig = Config.
|
|
10808
|
+
const uploadConfig = Config.app;
|
|
10820
10809
|
const throttleConfig = {
|
|
10821
10810
|
enabled: uploadConfig.downloadThrottle ?? false,
|
|
10822
10811
|
maxSpeed: (uploadConfig.downloadMaxSpeed ?? 10) * 1024 * 1024,
|
|
@@ -11583,7 +11572,7 @@ var buildGoogleMotionPhoto = async (options) => {
|
|
|
11583
11572
|
};
|
|
11584
11573
|
//#endregion
|
|
11585
11574
|
//#region src/module/utils/FFmpeg.ts
|
|
11586
|
-
/**
|
|
11575
|
+
/**
|
|
11587
11576
|
* 修复 m4s 文件为标准 MP4 格式
|
|
11588
11577
|
* B站的 DASH 流使用 m4s 格式,缺少 moov atom,需要转换
|
|
11589
11578
|
*/
|
|
@@ -11980,7 +11969,7 @@ var extractTotalBytesFromHeaders = (headers) => {
|
|
|
11980
11969
|
* @returns 安全的文件名
|
|
11981
11970
|
*/
|
|
11982
11971
|
var sanitizeFilename = (filename) => {
|
|
11983
|
-
return filename.replace(/[<>:"/\\|?*\
|
|
11972
|
+
return filename.replace(/[<>:"/\\|?*\p{Cc}]/gu, "_").replace(/^\.+/, "").replace(/\.+$/, "").replace(/\s+/g, "_").substring(0, 200);
|
|
11984
11973
|
};
|
|
11985
11974
|
//#endregion
|
|
11986
11975
|
//#region src/module/utils/Network/ThrottleStream.ts
|
|
@@ -12341,7 +12330,7 @@ var ImageDownloader = class {
|
|
|
12341
12330
|
* @returns 处理后的图片路径(HTTP URL / file:// 协议 / base64://)
|
|
12342
12331
|
*/
|
|
12343
12332
|
async processImage(imageUrl, title, index) {
|
|
12344
|
-
switch (Config.
|
|
12333
|
+
switch (Config.app.imageSendMode) {
|
|
12345
12334
|
case "base64": try {
|
|
12346
12335
|
return await this.downloadAndConvertToBase64(imageUrl);
|
|
12347
12336
|
} catch (error) {
|
|
@@ -12766,18 +12755,18 @@ function getImageDownloader() {
|
|
|
12766
12755
|
/**
|
|
12767
12756
|
* 处理图片 URL,根据配置决定是否本地下载
|
|
12768
12757
|
* 这是一个便捷函数,用于快速处理单个图片
|
|
12769
|
-
*
|
|
12758
|
+
*
|
|
12770
12759
|
* @param imageUrl - 图片 URL
|
|
12771
12760
|
* @param title - 作品标题(用于文件命名)
|
|
12772
12761
|
* @param index - 图片索引(用于多图场景)
|
|
12773
12762
|
* @returns 处理后的图片路径(可能是 file:// 协议或原始 HTTP URL)
|
|
12774
|
-
*
|
|
12763
|
+
*
|
|
12775
12764
|
* @example
|
|
12776
12765
|
* ```ts
|
|
12777
12766
|
* // 单张图片
|
|
12778
12767
|
* const imagePath = await processImageUrl('https://example.com/image.jpg', '作品标题')
|
|
12779
12768
|
* await e.reply(segment.image(imagePath))
|
|
12780
|
-
*
|
|
12769
|
+
*
|
|
12781
12770
|
* // 多张图片
|
|
12782
12771
|
* const imagePaths = await Promise.all(
|
|
12783
12772
|
* imageUrls.map((url, i) => processImageUrl(url, '作品标题', i))
|
|
@@ -12791,7 +12780,7 @@ async function processImageUrl(imageUrl, title, index) {
|
|
|
12791
12780
|
//#region ../template/src/dev/preview/utils/time.ts
|
|
12792
12781
|
var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
|
|
12793
12782
|
var import_server_node = require_server_node();
|
|
12794
|
-
var formatDuration$
|
|
12783
|
+
var formatDuration$3 = (ms) => {
|
|
12795
12784
|
const totalSeconds = Math.max(Math.floor(ms / 1e3), 0);
|
|
12796
12785
|
const hours = Math.floor(totalSeconds / 3600);
|
|
12797
12786
|
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
@@ -12815,7 +12804,7 @@ var buildStatus = (state) => {
|
|
|
12815
12804
|
statusText: `文件 ${filePath} 将在未知时间后删除`,
|
|
12816
12805
|
countdownText: "--:--"
|
|
12817
12806
|
};
|
|
12818
|
-
const countdownText = formatDuration$
|
|
12807
|
+
const countdownText = formatDuration$3(state.remainingMs);
|
|
12819
12808
|
return {
|
|
12820
12809
|
statusText: `文件 ${filePath} 将在 ${countdownText} 后删除`,
|
|
12821
12810
|
countdownText
|
|
@@ -17345,7 +17334,7 @@ var OriginalAVContent = ({ content }) => {
|
|
|
17345
17334
|
children: content.duration_text
|
|
17346
17335
|
}),
|
|
17347
17336
|
content.play,
|
|
17348
|
-
"观看
|
|
17337
|
+
"观看 ",
|
|
17349
17338
|
content.danmaku,
|
|
17350
17339
|
"弹幕"
|
|
17351
17340
|
]
|
|
@@ -19105,7 +19094,6 @@ var ContentSection = ({ markdown, images }) => {
|
|
|
19105
19094
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
19106
19095
|
className: "prose prose-lg max-w-none text-foreground select-text",
|
|
19107
19096
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Markdown, {
|
|
19108
|
-
children: markdown.replace(/!\[([^\]]*)\]\(([^\s)]+)(?:\s+width=\d+)?(?:\s+height=\d+)?\)/g, ""),
|
|
19109
19097
|
components: {
|
|
19110
19098
|
h1: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
|
|
19111
19099
|
className: "text-8xl font-bold mb-8 mt-12 text-foreground",
|
|
@@ -19168,7 +19156,8 @@ var ContentSection = ({ markdown, images }) => {
|
|
|
19168
19156
|
className: "bg-surface px-3 py-1 rounded text-5xl text-accent font-mono",
|
|
19169
19157
|
children
|
|
19170
19158
|
})
|
|
19171
|
-
}
|
|
19159
|
+
},
|
|
19160
|
+
children: markdown.replace(/!\[([^\]]*)\]\(([^\s)]+)(?:\s+width=\d+)?(?:\s+height=\d+)?\)/g, "")
|
|
19172
19161
|
})
|
|
19173
19162
|
})
|
|
19174
19163
|
});
|
|
@@ -22388,7 +22377,7 @@ var DouyinUserList = (props) => {
|
|
|
22388
22377
|
/**
|
|
22389
22378
|
* 格式化数字显示 (使用中文单位:万、亿)
|
|
22390
22379
|
*/
|
|
22391
|
-
var formatCount
|
|
22380
|
+
var formatCount = (count) => {
|
|
22392
22381
|
if (count >= 1e8) return (count / 1e8).toFixed(1) + "亿";
|
|
22393
22382
|
if (count >= 1e4) return (count / 1e4).toFixed(1) + "万";
|
|
22394
22383
|
return count.toString();
|
|
@@ -22397,7 +22386,7 @@ var formatCount$2 = (count) => {
|
|
|
22397
22386
|
* 格式化视频时长显示 (如: 6:20)
|
|
22398
22387
|
* @param milliseconds 毫秒数
|
|
22399
22388
|
*/
|
|
22400
|
-
var formatDuration$
|
|
22389
|
+
var formatDuration$2 = (milliseconds) => {
|
|
22401
22390
|
const seconds = Math.floor(milliseconds / 1e3);
|
|
22402
22391
|
return `${Math.floor(seconds / 60)}:${(seconds % 60).toString().padStart(2, "0")}`;
|
|
22403
22392
|
};
|
|
@@ -22433,7 +22422,7 @@ var VideoCard = ({ video }) => {
|
|
|
22433
22422
|
}),
|
|
22434
22423
|
video.is_video && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
22435
22424
|
className: "absolute bottom-4 right-4 px-8 py-3 rounded-2xl text-4xl bg-white/50 text-black backdrop-blur-xs shadow-lg",
|
|
22436
|
-
children: formatDuration$
|
|
22425
|
+
children: formatDuration$2(video.duration)
|
|
22437
22426
|
}),
|
|
22438
22427
|
video.music && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
22439
22428
|
className: "absolute bottom-4 left-4 flex items-center gap-2 px-6 py-3 rounded-2xl text-xl bg-white/50 text-black backdrop-blur-xs shadow-lg",
|
|
@@ -22465,19 +22454,19 @@ var VideoCard = ({ video }) => {
|
|
|
22465
22454
|
children: [
|
|
22466
22455
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
22467
22456
|
className: "flex items-center gap-2 text-foreground/70",
|
|
22468
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AiFillHeart, { size: 34 }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatCount
|
|
22457
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AiFillHeart, { size: 34 }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatCount(video.statistics.like_count) })]
|
|
22469
22458
|
}),
|
|
22470
22459
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
22471
22460
|
className: "flex items-center gap-2 text-foreground/70",
|
|
22472
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FaCommentDots, { size: 34 }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatCount
|
|
22461
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FaCommentDots, { size: 34 }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatCount(video.statistics.comment_count) })]
|
|
22473
22462
|
}),
|
|
22474
22463
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
22475
22464
|
className: "flex items-center gap-2 text-foreground/70",
|
|
22476
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AiFillStar, { size: 34 }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatCount
|
|
22465
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AiFillStar, { size: 34 }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatCount(video.statistics.collect_count) })]
|
|
22477
22466
|
}),
|
|
22478
22467
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
22479
22468
|
className: "flex items-center gap-2 text-foreground/70",
|
|
22480
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RiShareForwardFill, { size: 34 }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatCount
|
|
22469
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RiShareForwardFill, { size: 34 }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatCount(video.statistics.share_count) })]
|
|
22481
22470
|
})
|
|
22482
22471
|
]
|
|
22483
22472
|
})
|
|
@@ -22559,7 +22548,7 @@ var DouyinUserVideoList = (prpos) => {
|
|
|
22559
22548
|
}),
|
|
22560
22549
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
22561
22550
|
className: "font-medium text-4xl text-foreground",
|
|
22562
|
-
children: [" ", formatCount
|
|
22551
|
+
children: [" ", formatCount(prpos.data.user.following_count)]
|
|
22563
22552
|
})
|
|
22564
22553
|
]
|
|
22565
22554
|
}),
|
|
@@ -22576,7 +22565,7 @@ var DouyinUserVideoList = (prpos) => {
|
|
|
22576
22565
|
}),
|
|
22577
22566
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
22578
22567
|
className: "font-medium text-4xl text-foreground",
|
|
22579
|
-
children: [" ", formatCount
|
|
22568
|
+
children: [" ", formatCount(prpos.data.user.follower_count)]
|
|
22580
22569
|
})
|
|
22581
22570
|
]
|
|
22582
22571
|
}),
|
|
@@ -22593,7 +22582,7 @@ var DouyinUserVideoList = (prpos) => {
|
|
|
22593
22582
|
}),
|
|
22594
22583
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
22595
22584
|
className: "font-medium text-4xl text-foreground",
|
|
22596
|
-
children: [" ", formatCount
|
|
22585
|
+
children: [" ", formatCount(prpos.data.user.total_favorited)]
|
|
22597
22586
|
})
|
|
22598
22587
|
]
|
|
22599
22588
|
})
|
|
@@ -22621,7 +22610,8 @@ var DouyinUserVideoList = (prpos) => {
|
|
|
22621
22610
|
className: "text-accent font-bold",
|
|
22622
22611
|
children: ["1~", prpos.data.videos.length]
|
|
22623
22612
|
}),
|
|
22624
|
-
"
|
|
22613
|
+
" ",
|
|
22614
|
+
"之间的数字解析对应作品。例如发送“1”解析第一个作品"
|
|
22625
22615
|
] })]
|
|
22626
22616
|
})
|
|
22627
22617
|
}),
|
|
@@ -22647,12 +22637,12 @@ var formatNumber$1 = (num) => {
|
|
|
22647
22637
|
if (num >= 1e4) return `${(num / 1e4).toFixed(1)}万`;
|
|
22648
22638
|
return num.toLocaleString();
|
|
22649
22639
|
};
|
|
22650
|
-
var formatDuration$
|
|
22640
|
+
var formatDuration$1 = (ms) => {
|
|
22651
22641
|
const seconds = Math.floor(ms / 1e3);
|
|
22652
22642
|
return `${Math.floor(seconds / 60)}:${(seconds % 60).toString().padStart(2, "0")}`;
|
|
22653
22643
|
};
|
|
22654
22644
|
var DouyinVideoInfo = import_react.memo((props) => {
|
|
22655
|
-
const duration = (0, import_react.useMemo)(() => props.data.video ? formatDuration$
|
|
22645
|
+
const duration = (0, import_react.useMemo)(() => props.data.video ? formatDuration$1(props.data.video.duration) : null, [props.data.video]);
|
|
22656
22646
|
const coverMaskStyle = (0, import_react.useMemo)(() => ({
|
|
22657
22647
|
maskImage: `linear-gradient(to bottom,
|
|
22658
22648
|
transparent 0%,
|
|
@@ -22883,7 +22873,7 @@ var StatItem$1 = ({ icon, value }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx
|
|
|
22883
22873
|
DouyinVideoInfo.displayName = "DouyinVideoInfo";
|
|
22884
22874
|
//#endregion
|
|
22885
22875
|
//#region ../template/src/components/platforms/douyin/VideoWork.tsx
|
|
22886
|
-
function formatDuration
|
|
22876
|
+
function formatDuration(duration) {
|
|
22887
22877
|
if (typeof duration !== "number" || !Number.isFinite(duration) || duration < 0) return void 0;
|
|
22888
22878
|
const totalSeconds = Math.floor(duration / 1e3);
|
|
22889
22879
|
const hours = Math.floor(totalSeconds / 3600);
|
|
@@ -22948,7 +22938,7 @@ var DouyinAvatarUserInfo = (props) => {
|
|
|
22948
22938
|
};
|
|
22949
22939
|
var DouyinVideoCover = (props) => {
|
|
22950
22940
|
const { image_url, music, duration } = props.data;
|
|
22951
|
-
const durationText = formatDuration
|
|
22941
|
+
const durationText = formatDuration(duration);
|
|
22952
22942
|
const musicBadge = music && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
22953
22943
|
className: "absolute left-7 bottom-7 z-20 flex items-center gap-4 max-w-[72%] p-3 rounded-3xl bg-black/45 backdrop-blur-2xl border border-white/20 shadow-large overflow-hidden",
|
|
22954
22944
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
@@ -24987,7 +24977,8 @@ var GroupStatistics = (props) => {
|
|
|
24987
24977
|
className: "font-black text-foreground",
|
|
24988
24978
|
children: props.data.globalTotalGroups
|
|
24989
24979
|
}),
|
|
24990
|
-
" 个群组 · 解析
|
|
24980
|
+
" 个群组 · 解析",
|
|
24981
|
+
" ",
|
|
24991
24982
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
24992
24983
|
className: "font-black text-foreground",
|
|
24993
24984
|
children: props.data.globalTotalParses
|
|
@@ -25270,7 +25261,7 @@ var ansi256ToColor = (colorCode) => {
|
|
|
25270
25261
|
return `#${hex}${hex}${hex}`;
|
|
25271
25262
|
};
|
|
25272
25263
|
var convertAnsiToHtml = (text) => {
|
|
25273
|
-
const ansiRegex =
|
|
25264
|
+
const ansiRegex = /* @__PURE__ */ new RegExp("\x1B\\[([0-9;]+)m", "g");
|
|
25274
25265
|
let result = "", lastIndex = 0;
|
|
25275
25266
|
let currentStyles = { classes: [] };
|
|
25276
25267
|
let match;
|
|
@@ -25318,7 +25309,7 @@ var convertAnsiToHtml = (text) => {
|
|
|
25318
25309
|
};
|
|
25319
25310
|
var getLogLevelTheme = (level, isDark) => {
|
|
25320
25311
|
const themeMap = {
|
|
25321
|
-
|
|
25312
|
+
TRAC: {
|
|
25322
25313
|
bgClass: isDark ? "bg-muted/10" : "bg-muted/5",
|
|
25323
25314
|
borderClass: "border-muted/20",
|
|
25324
25315
|
textClass: "text-muted",
|
|
@@ -25326,7 +25317,7 @@ var getLogLevelTheme = (level, isDark) => {
|
|
|
25326
25317
|
levelClass: isDark ? "text-muted/10" : "text-muted/10",
|
|
25327
25318
|
dotClass: "bg-muted/40"
|
|
25328
25319
|
},
|
|
25329
|
-
|
|
25320
|
+
DEBU: {
|
|
25330
25321
|
bgClass: isDark ? "bg-cyan-400/10" : "bg-cyan-500/5",
|
|
25331
25322
|
borderClass: isDark ? "border-cyan-400/20" : "border-cyan-500/20",
|
|
25332
25323
|
textClass: isDark ? "text-cyan-400" : "text-cyan-600",
|
|
@@ -25334,7 +25325,7 @@ var getLogLevelTheme = (level, isDark) => {
|
|
|
25334
25325
|
levelClass: isDark ? "text-cyan-400/10" : "text-cyan-600/10",
|
|
25335
25326
|
dotClass: isDark ? "bg-cyan-400/40" : "bg-cyan-500/40"
|
|
25336
25327
|
},
|
|
25337
|
-
|
|
25328
|
+
MARK: {
|
|
25338
25329
|
bgClass: isDark ? "bg-muted/10" : "bg-muted/5",
|
|
25339
25330
|
borderClass: "border-muted/20",
|
|
25340
25331
|
textClass: "text-muted",
|
|
@@ -25342,7 +25333,7 @@ var getLogLevelTheme = (level, isDark) => {
|
|
|
25342
25333
|
levelClass: isDark ? "text-muted/10" : "text-muted/10",
|
|
25343
25334
|
dotClass: "bg-muted/40"
|
|
25344
25335
|
},
|
|
25345
|
-
|
|
25336
|
+
INFO: {
|
|
25346
25337
|
bgClass: "bg-success-soft",
|
|
25347
25338
|
borderClass: "border-success/25",
|
|
25348
25339
|
textClass: "text-success",
|
|
@@ -25350,7 +25341,7 @@ var getLogLevelTheme = (level, isDark) => {
|
|
|
25350
25341
|
levelClass: isDark ? "text-success/10" : "text-success/10",
|
|
25351
25342
|
dotClass: "bg-success/40"
|
|
25352
25343
|
},
|
|
25353
|
-
|
|
25344
|
+
WARN: {
|
|
25354
25345
|
bgClass: "bg-warning-soft",
|
|
25355
25346
|
borderClass: "border-warning/25",
|
|
25356
25347
|
textClass: "text-warning",
|
|
@@ -25358,7 +25349,7 @@ var getLogLevelTheme = (level, isDark) => {
|
|
|
25358
25349
|
levelClass: isDark ? "text-warning/10" : "text-warning-soft",
|
|
25359
25350
|
dotClass: "bg-warning/40"
|
|
25360
25351
|
},
|
|
25361
|
-
|
|
25352
|
+
ERRO: {
|
|
25362
25353
|
bgClass: "bg-danger-soft",
|
|
25363
25354
|
borderClass: "border-danger/25",
|
|
25364
25355
|
textClass: "text-danger",
|
|
@@ -25366,7 +25357,7 @@ var getLogLevelTheme = (level, isDark) => {
|
|
|
25366
25357
|
levelClass: isDark ? "text-danger/10" : "text-danger/10",
|
|
25367
25358
|
dotClass: "bg-danger/40"
|
|
25368
25359
|
},
|
|
25369
|
-
|
|
25360
|
+
FATA: {
|
|
25370
25361
|
bgClass: isDark ? "bg-pink-400/10" : "bg-pink-500/5",
|
|
25371
25362
|
borderClass: isDark ? "border-pink-400/25" : "border-pink-500/25",
|
|
25372
25363
|
textClass: isDark ? "text-pink-400" : "text-pink-500",
|
|
@@ -26024,7 +26015,8 @@ var handlerError = (props) => {
|
|
|
26024
26015
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MdSchedule, { size: 24 }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
|
|
26025
26016
|
"Built Time: ",
|
|
26026
26017
|
data.buildTime,
|
|
26027
|
-
" 于
|
|
26018
|
+
" 于",
|
|
26019
|
+
" ",
|
|
26028
26020
|
formatDistanceToNow(parse(data.buildTime, "yyyy年MM月dd日 HH:mm", /* @__PURE__ */ new Date()), { locale: zhCN }),
|
|
26029
26021
|
"前"
|
|
26030
26022
|
] })]
|
|
@@ -27904,13 +27896,15 @@ var VersionWarning = (props) => {
|
|
|
27904
27896
|
className: "text-[26px] leading-relaxed",
|
|
27905
27897
|
style: { color: secondaryColor },
|
|
27906
27898
|
children: [
|
|
27907
|
-
"当前插件版本基于
|
|
27899
|
+
"当前插件版本基于",
|
|
27900
|
+
" ",
|
|
27908
27901
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
27909
27902
|
className: "font-bold font-mono",
|
|
27910
27903
|
style: { color: accentColor },
|
|
27911
27904
|
children: ["node-karin v", props.data.requireVersion]
|
|
27912
27905
|
}),
|
|
27913
|
-
"
|
|
27906
|
+
" ",
|
|
27907
|
+
"开发,低版本运行时可能出现功能异常或兼容性问题,请及时更新以获得最佳体验"
|
|
27914
27908
|
]
|
|
27915
27909
|
})]
|
|
27916
27910
|
})]
|
|
@@ -29829,11 +29823,11 @@ var SSRRender = class {
|
|
|
29829
29823
|
};
|
|
29830
29824
|
/**
|
|
29831
29825
|
* SSR 预渲染组件为 HTML 的具体实现
|
|
29832
|
-
*
|
|
29826
|
+
*
|
|
29833
29827
|
* @template K 模板类型键,用于类型推断
|
|
29834
29828
|
* @param options 渲染配置选项
|
|
29835
29829
|
* @returns 渲染结果 Promise
|
|
29836
|
-
*
|
|
29830
|
+
*
|
|
29837
29831
|
* # Example
|
|
29838
29832
|
* ```typescript
|
|
29839
29833
|
* // 基础使用
|
|
@@ -29845,7 +29839,7 @@ var SSRRender = class {
|
|
|
29845
29839
|
* },
|
|
29846
29840
|
* outputDir: './output'
|
|
29847
29841
|
* })
|
|
29848
|
-
*
|
|
29842
|
+
*
|
|
29849
29843
|
* // 使用插件
|
|
29850
29844
|
* const result = await reactServerRender({
|
|
29851
29845
|
* request: renderRequest,
|
|
@@ -30313,7 +30307,7 @@ var embedWatermark = (pngBytes, watermarkText) => {
|
|
|
30313
30307
|
/**
|
|
30314
30308
|
* 渲染函数
|
|
30315
30309
|
* 将指定路径的模板渲染为图片元素数组
|
|
30316
|
-
*
|
|
30310
|
+
*
|
|
30317
30311
|
* @param event 消息事件对象,用于获取机器人账号信息
|
|
30318
30312
|
* @template P 渲染路径,必须是有效的动态路径
|
|
30319
30313
|
* @param path 渲染路径,格式为 "平台/组件ID" 或 "平台/分类/组件ID"
|
|
@@ -32598,7 +32592,7 @@ var webConfig = defineConfig({
|
|
|
32598
32592
|
}]
|
|
32599
32593
|
},
|
|
32600
32594
|
page: {
|
|
32601
|
-
url: process.env.NODE_ENV === "development" ? "http://192.168.1.8:5176/kkk/karin-config" : "/kkk/karin-config",
|
|
32595
|
+
url: process.env.NODE_ENV === "development" ? "http://192.168.1.8:5176/kkk/assets/karin-config" : "/kkk/assets/karin-config",
|
|
32602
32596
|
title: "kkk插件配置管理",
|
|
32603
32597
|
description: "使用 kkk 插件自带的配置管理页面"
|
|
32604
32598
|
}
|
|
@@ -32707,7 +32701,7 @@ var renderErrorImage = async (ctx, opts = {}) => {
|
|
|
32707
32701
|
//#region src/module/utils/bot.ts
|
|
32708
32702
|
/**
|
|
32709
32703
|
* 获取候选机器人
|
|
32710
|
-
* @returns
|
|
32704
|
+
* @returns
|
|
32711
32705
|
*/
|
|
32712
32706
|
var getCandidateBots = () => {
|
|
32713
32707
|
return karin$1.getAllBotList().map((item) => item.bot).filter((bot) => bot.account.name !== "console");
|
|
@@ -32715,7 +32709,7 @@ var getCandidateBots = () => {
|
|
|
32715
32709
|
/**
|
|
32716
32710
|
* 获取非console主机器人ID列表
|
|
32717
32711
|
* @param masters - 主机器人ID列表
|
|
32718
|
-
* @returns
|
|
32712
|
+
* @returns
|
|
32719
32713
|
*/
|
|
32720
32714
|
var getNonConsoleMasters = (masters = config.master()) => {
|
|
32721
32715
|
return masters.filter((id) => id !== "console");
|
|
@@ -32723,7 +32717,7 @@ var getNonConsoleMasters = (masters = config.master()) => {
|
|
|
32723
32717
|
/**
|
|
32724
32718
|
* 获取可访问的主机器人
|
|
32725
32719
|
* @param masters - 主机器人ID列表
|
|
32726
|
-
* @returns
|
|
32720
|
+
* @returns
|
|
32727
32721
|
*/
|
|
32728
32722
|
var getReachableMasterBots = async (masters = config.master()) => {
|
|
32729
32723
|
const owners = getNonConsoleMasters(masters);
|
|
@@ -32754,7 +32748,7 @@ var getReachableMasterBots = async (masters = config.master()) => {
|
|
|
32754
32748
|
/**
|
|
32755
32749
|
* 获取一个最少能用的机器人实例,优先级:1. selfId 参数指定的机器人 2. 可访问主人机器人中的第一个 3. pushlist 中活跃的机器人 4. 任意一个在线机器人
|
|
32756
32750
|
* @param selfId - 机器人ID
|
|
32757
|
-
* @returns
|
|
32751
|
+
* @returns
|
|
32758
32752
|
*/
|
|
32759
32753
|
var resolveUsableBot = async (selfId) => {
|
|
32760
32754
|
if (selfId) {
|
|
@@ -32959,7 +32953,7 @@ var sendErrorToAllMasters = async (ctx, img, customPrefix) => {
|
|
|
32959
32953
|
* @param event - 错误事件上下文
|
|
32960
32954
|
* @param isPush - 是否为推送任务
|
|
32961
32955
|
*
|
|
32962
|
-
* @returns
|
|
32956
|
+
* @returns
|
|
32963
32957
|
*/
|
|
32964
32958
|
var resolveSingleMasterTarget = async (event, isPush) => {
|
|
32965
32959
|
if (isPush) {
|
|
@@ -33987,10 +33981,10 @@ var Bilibili = class extends Base {
|
|
|
33987
33981
|
this.downloadfilename = "";
|
|
33988
33982
|
this.forceBurnDanmaku = options?.forceBurnDanmaku ?? false;
|
|
33989
33983
|
this.headers.Referer = "https://www.bilibili.com/";
|
|
33990
|
-
this.headers.Cookie = Config.cookies.bilibili;
|
|
33984
|
+
this.headers.Cookie = Config.amagi.cookies.bilibili;
|
|
33991
33985
|
}
|
|
33992
33986
|
async BilibiliHandler(iddata) {
|
|
33993
|
-
Config.app.parseTip
|
|
33987
|
+
if (Config.app.parseTip) this.e.reply("检测到B站链接,开始解析");
|
|
33994
33988
|
switch (this.Type) {
|
|
33995
33989
|
case "one_video": {
|
|
33996
33990
|
const infoData = await this.amagi.bilibili.fetcher.fetchVideoInfo({
|
|
@@ -34037,9 +34031,12 @@ var Bilibili = class extends Base {
|
|
|
34037
34031
|
host_mid: infoData.data.data.owner.mid,
|
|
34038
34032
|
typeMode: "strict"
|
|
34039
34033
|
});
|
|
34040
|
-
|
|
34041
|
-
|
|
34042
|
-
|
|
34034
|
+
let hotDanmaku;
|
|
34035
|
+
if (Config.bilibili.showDanmakuInVideoInfo) {
|
|
34036
|
+
const danmakuCid = iddata.p ? infoData.data.data.pages[iddata.p - 1]?.cid ?? infoData.data.data.cid : infoData.data.data.cid;
|
|
34037
|
+
const danmakuDuration = iddata.p ? infoData.data.data.pages[iddata.p - 1]?.duration ?? infoData.data.data.duration : infoData.data.data.duration;
|
|
34038
|
+
hotDanmaku = getHotDanmaku(await this.fetchVideoDanmakuList(danmakuCid, danmakuDuration), 20);
|
|
34039
|
+
}
|
|
34043
34040
|
const img = await Render(this.e, "bilibili/videoInfo", {
|
|
34044
34041
|
share_url: "https://b23.tv/" + infoData.data.data.bvid,
|
|
34045
34042
|
title: infoData.data.data.title,
|
|
@@ -34120,7 +34117,7 @@ var Bilibili = class extends Base {
|
|
|
34120
34117
|
}
|
|
34121
34118
|
}
|
|
34122
34119
|
}
|
|
34123
|
-
if (Config.bilibili.sendContent.some((content) => content === "video")) if (Config.
|
|
34120
|
+
if (Config.bilibili.sendContent.some((content) => content === "video")) if (Config.app.usefilelimit && Number(videoSize) > Number(Config.app.filelimit) && !Config.app.compress) this.e.reply(`设定的最大上传大小为 ${Config.app.filelimit}MB\n当前解析到的视频大小为 ${Number(videoSize)}MB\n视频太大了,还是去B站看吧~`, { reply: true });
|
|
34124
34121
|
else {
|
|
34125
34122
|
if (Config.bilibili.videoQuality !== 0 && Config.bilibili.videoQuality < 64) this.islogin = false;
|
|
34126
34123
|
let danmakuList = [];
|
|
@@ -34284,7 +34281,7 @@ var Bilibili = class extends Base {
|
|
|
34284
34281
|
filepath: filePath,
|
|
34285
34282
|
totalBytes: 0
|
|
34286
34283
|
});
|
|
34287
|
-
const videoPath = Config.
|
|
34284
|
+
const videoPath = Config.app.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
|
|
34288
34285
|
imgArray.push(segment.video(videoPath));
|
|
34289
34286
|
}
|
|
34290
34287
|
}
|
|
@@ -34301,7 +34298,7 @@ var Bilibili = class extends Base {
|
|
|
34301
34298
|
filepath: motionPhotoCoverPath,
|
|
34302
34299
|
totalBytes: 0
|
|
34303
34300
|
});
|
|
34304
|
-
const motionPhotoCover = Config.
|
|
34301
|
+
const motionPhotoCover = Config.app.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
|
|
34305
34302
|
imgArray.push(segment.image(motionPhotoCover));
|
|
34306
34303
|
hasPushedMotionPhotoCover = true;
|
|
34307
34304
|
}
|
|
@@ -34880,7 +34877,7 @@ var Bilibili = class extends Base {
|
|
|
34880
34877
|
await Common.removeFile(bmp3.filepath, true);
|
|
34881
34878
|
const stats = fs.statSync(filePath);
|
|
34882
34879
|
const fileSizeInMB = Number((stats.size / (1024 * 1024)).toFixed(2));
|
|
34883
|
-
if (fileSizeInMB > Config.
|
|
34880
|
+
if (fileSizeInMB > Config.app.groupfilevalue) await uploadFile(this.e, {
|
|
34884
34881
|
filepath: filePath,
|
|
34885
34882
|
totalBytes: fileSizeInMB,
|
|
34886
34883
|
originTitle: this.downloadfilename
|
|
@@ -34922,7 +34919,7 @@ var Bilibili = class extends Base {
|
|
|
34922
34919
|
await Common.removeFile(videoFile.filepath, true);
|
|
34923
34920
|
const stats = fs.statSync(filePath);
|
|
34924
34921
|
const fileSizeInMB = Number((stats.size / (1024 * 1024)).toFixed(2));
|
|
34925
|
-
if (fileSizeInMB > Config.
|
|
34922
|
+
if (fileSizeInMB > Config.app.groupfilevalue) await uploadFile(this.e, {
|
|
34926
34923
|
filepath: filePath,
|
|
34927
34924
|
totalBytes: fileSizeInMB,
|
|
34928
34925
|
originTitle: this.downloadfilename
|
|
@@ -35123,7 +35120,7 @@ var mapping_table = (type) => {
|
|
|
35123
35120
|
* 根据动态类型获取对应的oid(对象ID),用于后续评论接口调用
|
|
35124
35121
|
* @param dynamicType 动态类型
|
|
35125
35122
|
* @param dynamicData 动态数据
|
|
35126
|
-
* @returns
|
|
35123
|
+
* @returns
|
|
35127
35124
|
*/
|
|
35128
35125
|
var oid = (dynamicType, dynamicData) => {
|
|
35129
35126
|
switch (dynamicType) {
|
|
@@ -35204,7 +35201,7 @@ var getvideosize = async (videourl, audiourl, bvid) => {
|
|
|
35204
35201
|
headers: {
|
|
35205
35202
|
...BASE_HEADERS,
|
|
35206
35203
|
Referer: `https://www.bilibili.com/video/${bvid}`,
|
|
35207
|
-
Cookie: Config.cookies.bilibili
|
|
35204
|
+
Cookie: Config.amagi.cookies.bilibili
|
|
35208
35205
|
}
|
|
35209
35206
|
}).getHeaders();
|
|
35210
35207
|
const audioheaders = await new Network({
|
|
@@ -35212,7 +35209,7 @@ var getvideosize = async (videourl, audiourl, bvid) => {
|
|
|
35212
35209
|
headers: {
|
|
35213
35210
|
...BASE_HEADERS,
|
|
35214
35211
|
Referer: `https://www.bilibili.com/video/${bvid}`,
|
|
35215
|
-
Cookie: Config.cookies.bilibili
|
|
35212
|
+
Cookie: Config.amagi.cookies.bilibili
|
|
35216
35213
|
}
|
|
35217
35214
|
}).getHeaders();
|
|
35218
35215
|
const videoSize = extractTotalBytesFromHeaders(videoheaders);
|
|
@@ -35269,8 +35266,8 @@ var getStringDisplayWidth$1 = (str) => {
|
|
|
35269
35266
|
};
|
|
35270
35267
|
/**
|
|
35271
35268
|
* 提取专栏中的所有图片URL
|
|
35272
|
-
* @param content
|
|
35273
|
-
* @returns
|
|
35269
|
+
* @param content
|
|
35270
|
+
* @returns
|
|
35274
35271
|
*/
|
|
35275
35272
|
var extractArticleImages = (content) => {
|
|
35276
35273
|
const images = [];
|
|
@@ -35544,9 +35541,9 @@ var extractFansDetail = (member) => {
|
|
|
35544
35541
|
* @returns
|
|
35545
35542
|
*/
|
|
35546
35543
|
var genParams = async (apiURL) => {
|
|
35547
|
-
if (Config.cookies.bilibili === "" || Config.cookies.bilibili === null) return "&platform=html5";
|
|
35544
|
+
if (Config.amagi.cookies.bilibili === "" || Config.amagi.cookies.bilibili === null) return "&platform=html5";
|
|
35548
35545
|
const loginInfo = await bilibiliFetcher.fetchLoginStatus({ typeMode: "strict" });
|
|
35549
|
-
const genSign = await wbi_sign(apiURL, Config.cookies.bilibili);
|
|
35546
|
+
const genSign = await wbi_sign(apiURL, Config.amagi.cookies.bilibili);
|
|
35550
35547
|
const qn = [
|
|
35551
35548
|
6,
|
|
35552
35549
|
16,
|
|
@@ -35561,19 +35558,15 @@ var genParams = async (apiURL) => {
|
|
|
35561
35558
|
126,
|
|
35562
35559
|
127
|
|
35563
35560
|
];
|
|
35564
|
-
|
|
35565
|
-
loginInfo.data.data.vipStatus === 1 ? isvip = true : isvip = false;
|
|
35566
|
-
if (isvip) return `&fnval=16&fourk=1&${genSign}`;
|
|
35561
|
+
if (loginInfo.data.data.vipStatus === 1) return `&fnval=16&fourk=1&${genSign}`;
|
|
35567
35562
|
else return `&qn=${qn[3]}&fnval=16`;
|
|
35568
35563
|
};
|
|
35569
35564
|
var checkCk = async () => {
|
|
35570
|
-
if (Config.cookies.bilibili === "" || Config.cookies.bilibili === null) return {
|
|
35565
|
+
if (Config.amagi.cookies.bilibili === "" || Config.amagi.cookies.bilibili === null) return {
|
|
35571
35566
|
Status: "!isLogin",
|
|
35572
35567
|
isVIP: false
|
|
35573
35568
|
};
|
|
35574
|
-
const
|
|
35575
|
-
let isVIP;
|
|
35576
|
-
loginInfo.data.data.vipStatus === 1 ? isVIP = true : isVIP = false;
|
|
35569
|
+
const isVIP = (await bilibiliFetcher.fetchLoginStatus({ typeMode: "strict" })).data.data.vipStatus === 1;
|
|
35577
35570
|
if (isVIP) return {
|
|
35578
35571
|
Status: "isLogin",
|
|
35579
35572
|
isVIP
|
|
@@ -35734,8 +35727,8 @@ var bilibiliLogin = async (e) => {
|
|
|
35734
35727
|
let cookieString;
|
|
35735
35728
|
if (Array.isArray(setCookieHeader)) cookieString = setCookieHeader.join("; ");
|
|
35736
35729
|
else cookieString = setCookieHeader || "";
|
|
35737
|
-
Config.Modify("
|
|
35738
|
-
await e.reply("
|
|
35730
|
+
Config.Modify("amagi", "cookies.bilibili", cookieString);
|
|
35731
|
+
await e.reply("登录成功!用户登录凭证已保存至配置", { reply: true });
|
|
35739
35732
|
await recallMessages();
|
|
35740
35733
|
};
|
|
35741
35734
|
/**
|
|
@@ -35810,7 +35803,7 @@ var allBilibiliPushTypes = [
|
|
|
35810
35803
|
var bilibiliBaseHeaders = {
|
|
35811
35804
|
...BASE_HEADERS,
|
|
35812
35805
|
Referer: "https://www.bilibili.com",
|
|
35813
|
-
Cookie: Config.cookies.bilibili
|
|
35806
|
+
Cookie: Config.amagi.cookies.bilibili
|
|
35814
35807
|
};
|
|
35815
35808
|
var Bilibilipush = class extends Base {
|
|
35816
35809
|
force = false;
|
|
@@ -35914,7 +35907,7 @@ var Bilibilipush = class extends Base {
|
|
|
35914
35907
|
${logger.cyan("动态id")}:${logger.yellow(dynamicId)}
|
|
35915
35908
|
${logger.cyan("访问地址")}:${logger.green("https://t.bilibili.com/" + dynamicId)}`);
|
|
35916
35909
|
let skip = await skipDynamic$1(data[dynamicId]);
|
|
35917
|
-
skip
|
|
35910
|
+
if (skip) logger.warn(`动态 https://t.bilibili.com/${dynamicId} 已被处理,跳过`);
|
|
35918
35911
|
let send_video = true;
|
|
35919
35912
|
let img = [];
|
|
35920
35913
|
this.injectBotToEventForRender(data[dynamicId].targets);
|
|
@@ -36320,8 +36313,8 @@ var Bilibilipush = class extends Base {
|
|
|
36320
36313
|
playUrlData.data.data.accept_description = correctList.accept_description;
|
|
36321
36314
|
/** 获取第一个视频流的大小 */
|
|
36322
36315
|
videoSize = await getvideosize(correctList.videoList[0].base_url, playUrlData.data.data.dash.audio[0].base_url, data[dynamicId].Dynamic_Data.modules.module_dynamic.major.archive.bvid);
|
|
36323
|
-
if (Config.
|
|
36324
|
-
await karin$1.sendMsg(botId, Contact, [segment.text(`设定的最大上传大小为 ${Config.
|
|
36316
|
+
if (Config.app.usefilelimit && Number(videoSize) > Number(Config.app.filelimit) && !Config.app.compress) {
|
|
36317
|
+
await karin$1.sendMsg(botId, Contact, [segment.text(`设定的最大上传大小为 ${Config.app.filelimit}MB\n当前解析到的视频大小为 ${Number(videoSize)}MB\n视频太大了,还是去B站看吧~`), segment.reply(status.messageId)]);
|
|
36325
36318
|
break;
|
|
36326
36319
|
}
|
|
36327
36320
|
logger.mark(`当前处于自动推送状态,解析到的视频大小为 ${logger.yellow(Number(videoSize))} MB`);
|
|
@@ -36348,7 +36341,7 @@ var Bilibilipush = class extends Base {
|
|
|
36348
36341
|
await Common.removeFile(mp3File.filepath, true);
|
|
36349
36342
|
const stats = fs.statSync(filePath);
|
|
36350
36343
|
const fileSizeInMB = Number((stats.size / (1024 * 1024)).toFixed(2));
|
|
36351
|
-
if (fileSizeInMB > Config.
|
|
36344
|
+
if (fileSizeInMB > Config.app.groupfilevalue) await uploadFile(this.e, {
|
|
36352
36345
|
filepath: filePath,
|
|
36353
36346
|
totalBytes: fileSizeInMB,
|
|
36354
36347
|
originTitle: `${infoData.data.data.desc.substring(0, 50).replace(/[\\/:\\*\\?"<>\\|\r\n\s]/g, " ")}`
|
|
@@ -36427,7 +36420,7 @@ var Bilibilipush = class extends Base {
|
|
|
36427
36420
|
filepath: filePath,
|
|
36428
36421
|
totalBytes: 0
|
|
36429
36422
|
});
|
|
36430
|
-
const videoPath = Config.
|
|
36423
|
+
const videoPath = Config.app.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
|
|
36431
36424
|
imgArray.push(segment.video(videoPath));
|
|
36432
36425
|
}
|
|
36433
36426
|
}
|
|
@@ -36444,7 +36437,7 @@ var Bilibilipush = class extends Base {
|
|
|
36444
36437
|
filepath: motionPhotoCoverPath,
|
|
36445
36438
|
totalBytes: 0
|
|
36446
36439
|
});
|
|
36447
|
-
const motionPhotoCover = Config.
|
|
36440
|
+
const motionPhotoCover = Config.app.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
|
|
36448
36441
|
imgArray.push(segment.image(motionPhotoCover));
|
|
36449
36442
|
hasPushedMotionPhotoCover = true;
|
|
36450
36443
|
}
|
|
@@ -37505,7 +37498,7 @@ var DouYin = class DouYin extends Base {
|
|
|
37505
37498
|
return true;
|
|
37506
37499
|
}
|
|
37507
37500
|
async DouyinHandler(data) {
|
|
37508
|
-
Config.app.parseTip
|
|
37501
|
+
if (Config.app.parseTip) this.e.reply("检测到抖音链接,开始解析");
|
|
37509
37502
|
switch (this.type) {
|
|
37510
37503
|
case "one_work": {
|
|
37511
37504
|
const VideoData = await this.amagi.douyin.fetcher.parseWork({
|
|
@@ -37615,7 +37608,7 @@ var DouYin = class DouYin extends Base {
|
|
|
37615
37608
|
filepath: filePath,
|
|
37616
37609
|
totalBytes: 0
|
|
37617
37610
|
});
|
|
37618
|
-
const videoPath = Config.
|
|
37611
|
+
const videoPath = Config.app.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
|
|
37619
37612
|
processedImages.push(segment.video(videoPath));
|
|
37620
37613
|
}
|
|
37621
37614
|
}
|
|
@@ -37632,7 +37625,7 @@ var DouYin = class DouYin extends Base {
|
|
|
37632
37625
|
filepath: motionPhotoCoverPath,
|
|
37633
37626
|
totalBytes: 0
|
|
37634
37627
|
});
|
|
37635
|
-
const motionPhotoCover = Config.
|
|
37628
|
+
const motionPhotoCover = Config.app.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
|
|
37636
37629
|
processedImages.push(segment.image(motionPhotoCover));
|
|
37637
37630
|
hasPushedMotionPhotoCover = true;
|
|
37638
37631
|
}
|
|
@@ -37770,7 +37763,7 @@ var DouYin = class DouYin extends Base {
|
|
|
37770
37763
|
filepath: filePath,
|
|
37771
37764
|
totalBytes: 0
|
|
37772
37765
|
});
|
|
37773
|
-
const videoPath = Config.
|
|
37766
|
+
const videoPath = Config.app.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
|
|
37774
37767
|
images.push(segment.video(videoPath));
|
|
37775
37768
|
}
|
|
37776
37769
|
}
|
|
@@ -37787,7 +37780,7 @@ var DouYin = class DouYin extends Base {
|
|
|
37787
37780
|
filepath: motionPhotoCoverPath,
|
|
37788
37781
|
totalBytes: 0
|
|
37789
37782
|
});
|
|
37790
|
-
const motionPhotoCover = Config.
|
|
37783
|
+
const motionPhotoCover = Config.app.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
|
|
37791
37784
|
images.push(segment.image(motionPhotoCover));
|
|
37792
37785
|
hasPushedMotionPhotoCover = true;
|
|
37793
37786
|
}
|
|
@@ -37941,7 +37934,9 @@ var DouYin = class DouYin extends Base {
|
|
|
37941
37934
|
else {
|
|
37942
37935
|
const suggest = [];
|
|
37943
37936
|
if (VideoData.data.aweme_detail?.suggest_words?.suggest_words) {
|
|
37944
|
-
for (const item of VideoData.data.aweme_detail.suggest_words.suggest_words) if (item.words && item.scene === "comment_top_rec")
|
|
37937
|
+
for (const item of VideoData.data.aweme_detail.suggest_words.suggest_words) if (item.words && item.scene === "comment_top_rec") {
|
|
37938
|
+
for (const v of item.words) if (v.word) suggest.push(v.word);
|
|
37939
|
+
}
|
|
37945
37940
|
}
|
|
37946
37941
|
const aweme = VideoData.data.aweme_detail;
|
|
37947
37942
|
const img = await Render(this.e, "douyin/comment", {
|
|
@@ -38024,7 +38019,7 @@ var DouYin = class DouYin extends Base {
|
|
|
38024
38019
|
await Common.removeFile(videoFile.filepath, true);
|
|
38025
38020
|
const stats = fs.statSync(filePath);
|
|
38026
38021
|
const fileSizeInMB = Number((stats.size / (1024 * 1024)).toFixed(2));
|
|
38027
|
-
if (fileSizeInMB > Config.
|
|
38022
|
+
if (fileSizeInMB > Config.app.groupfilevalue) await uploadFile(this.e, {
|
|
38028
38023
|
filepath: filePath,
|
|
38029
38024
|
totalBytes: fileSizeInMB,
|
|
38030
38025
|
originTitle: g_title || ""
|
|
@@ -38258,7 +38253,7 @@ var douyinProcessVideos = (videos, videoQuality, maxAutoVideoSize) => {
|
|
|
38258
38253
|
videos.sort((a, b) => b.play_addr.data_size - a.play_addr.data_size);
|
|
38259
38254
|
});
|
|
38260
38255
|
if (videoQuality === "adapt") {
|
|
38261
|
-
const sizeLimitBytes = (maxAutoVideoSize || Config.
|
|
38256
|
+
const sizeLimitBytes = (maxAutoVideoSize || Config.app.filelimit) * 1024 * 1024;
|
|
38262
38257
|
for (const quality of [
|
|
38263
38258
|
"4k",
|
|
38264
38259
|
"2k",
|
|
@@ -38538,7 +38533,7 @@ var getDouyinID = async (event, url, log = true) => {
|
|
|
38538
38533
|
logger.warn("无法获取作品ID");
|
|
38539
38534
|
break;
|
|
38540
38535
|
}
|
|
38541
|
-
log
|
|
38536
|
+
if (log) console.log(result);
|
|
38542
38537
|
return result;
|
|
38543
38538
|
};
|
|
38544
38539
|
//#endregion
|
|
@@ -39265,7 +39260,7 @@ async function renderLiveImage(options) {
|
|
|
39265
39260
|
var douyinBaseHeaders = {
|
|
39266
39261
|
...BASE_HEADERS,
|
|
39267
39262
|
Referer: "https://www.douyin.com",
|
|
39268
|
-
Cookie: Config.cookies.douyin
|
|
39263
|
+
Cookie: Config.amagi.cookies.douyin
|
|
39269
39264
|
};
|
|
39270
39265
|
var DouYinpush = class extends Base {
|
|
39271
39266
|
force = false;
|
|
@@ -39279,7 +39274,7 @@ var DouYinpush = class extends Base {
|
|
|
39279
39274
|
constructor(e = {}, force = false) {
|
|
39280
39275
|
super(e);
|
|
39281
39276
|
this.headers.Referer = "https://www.douyin.com";
|
|
39282
|
-
this.headers.Cookie = Config.cookies.douyin;
|
|
39277
|
+
this.headers.Cookie = Config.amagi.cookies.douyin;
|
|
39283
39278
|
this.force = force;
|
|
39284
39279
|
}
|
|
39285
39280
|
injectBotToEventForRender(targets) {
|
|
@@ -39299,7 +39294,7 @@ var DouYinpush = class extends Base {
|
|
|
39299
39294
|
const deletedCount = await cleanOldDynamicCache("douyin");
|
|
39300
39295
|
if (deletedCount > 0) logger.info(`已清理 ${deletedCount} 条过期的抖音作品缓存记录`);
|
|
39301
39296
|
if (await this.checkremark()) return true;
|
|
39302
|
-
this.ensureConfigFields(Config.pushlist.douyin);
|
|
39297
|
+
await this.ensureConfigFields(Config.pushlist.douyin);
|
|
39303
39298
|
const registeredBotIds = karin$1.getAllBotID();
|
|
39304
39299
|
const filteredPushList = this.filterPushListByRegisteredBots(Config.pushlist.douyin, registeredBotIds);
|
|
39305
39300
|
if (filteredPushList.length === 0) {
|
|
@@ -39315,10 +39310,30 @@ var DouYinpush = class extends Base {
|
|
|
39315
39310
|
* 检查并补全配置文件中缺失的字段
|
|
39316
39311
|
* @param pushList 推送配置列表
|
|
39317
39312
|
*/
|
|
39318
|
-
ensureConfigFields(pushList) {
|
|
39313
|
+
async ensureConfigFields(pushList) {
|
|
39319
39314
|
if (!pushList || pushList.length === 0) return;
|
|
39320
39315
|
let hasChanges = false;
|
|
39321
39316
|
for (const item of pushList) {
|
|
39317
|
+
if (!item.sec_uid && item.short_id) try {
|
|
39318
|
+
logger.info(`自动获取用户 ${item.remark || item.short_id} 的 sec_uid`);
|
|
39319
|
+
const searchResult = await this.amagi.douyin.fetcher.searchContent({
|
|
39320
|
+
query: item.short_id,
|
|
39321
|
+
type: "user",
|
|
39322
|
+
typeMode: "strict"
|
|
39323
|
+
});
|
|
39324
|
+
let matchedUser = null;
|
|
39325
|
+
for (const userItem of searchResult.data.user_list) if ((userItem.user_info.unique_id || userItem.user_info.short_id) === item.short_id) {
|
|
39326
|
+
matchedUser = userItem.user_info;
|
|
39327
|
+
break;
|
|
39328
|
+
}
|
|
39329
|
+
if (matchedUser?.sec_uid) {
|
|
39330
|
+
item.sec_uid = matchedUser.sec_uid;
|
|
39331
|
+
hasChanges = true;
|
|
39332
|
+
logger.info(`已为 ${item.remark || item.short_id} 补全 sec_uid: ${item.sec_uid}`);
|
|
39333
|
+
} else logger.warn(`无法获取用户 ${item.short_id} 的 sec_uid`);
|
|
39334
|
+
} catch (error) {
|
|
39335
|
+
logger.error(`获取 ${item.short_id} 的 sec_uid 失败: ${error}`);
|
|
39336
|
+
}
|
|
39322
39337
|
if (!item.pushTypes || item.pushTypes.length === 0) {
|
|
39323
39338
|
item.pushTypes = [
|
|
39324
39339
|
"post",
|
|
@@ -39402,7 +39417,7 @@ var DouYinpush = class extends Base {
|
|
|
39402
39417
|
`);
|
|
39403
39418
|
const Detail_Data = pushItem.Detail_Data;
|
|
39404
39419
|
const skip = await skipDynamic(pushItem);
|
|
39405
|
-
skip
|
|
39420
|
+
if (skip) logger.warn(`作品 https://www.douyin.com/video/${actualAwemeId} 已被处理,跳过`);
|
|
39406
39421
|
let img = [];
|
|
39407
39422
|
let iddata = { type: "one_work" };
|
|
39408
39423
|
this.injectBotToEventForRender(pushItem.targets);
|
|
@@ -39583,7 +39598,7 @@ var DouYinpush = class extends Base {
|
|
|
39583
39598
|
filepath: filePath,
|
|
39584
39599
|
totalBytes: 0
|
|
39585
39600
|
});
|
|
39586
|
-
const videoPath = Config.
|
|
39601
|
+
const videoPath = Config.app.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
|
|
39587
39602
|
images.push(segment.video(videoPath));
|
|
39588
39603
|
}
|
|
39589
39604
|
}
|
|
@@ -39600,7 +39615,7 @@ var DouYinpush = class extends Base {
|
|
|
39600
39615
|
filepath: motionPhotoCoverPath,
|
|
39601
39616
|
totalBytes: 0
|
|
39602
39617
|
});
|
|
39603
|
-
const motionPhotoCover = Config.
|
|
39618
|
+
const motionPhotoCover = Config.app.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
|
|
39604
39619
|
images.push(segment.image(motionPhotoCover));
|
|
39605
39620
|
hasPushedMotionPhotoCover = true;
|
|
39606
39621
|
}
|
|
@@ -39706,7 +39721,7 @@ var DouYinpush = class extends Base {
|
|
|
39706
39721
|
filepath: filePath,
|
|
39707
39722
|
totalBytes: 0
|
|
39708
39723
|
});
|
|
39709
|
-
const videoPath = Config.
|
|
39724
|
+
const videoPath = Config.app.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
|
|
39710
39725
|
processedImages.push(segment.video(videoPath));
|
|
39711
39726
|
}
|
|
39712
39727
|
}
|
|
@@ -39723,7 +39738,7 @@ var DouYinpush = class extends Base {
|
|
|
39723
39738
|
filepath: motionPhotoCoverPath,
|
|
39724
39739
|
totalBytes: 0
|
|
39725
39740
|
});
|
|
39726
|
-
const motionPhotoCover = Config.
|
|
39741
|
+
const motionPhotoCover = Config.app.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
|
|
39727
39742
|
processedImages.push(segment.image(motionPhotoCover));
|
|
39728
39743
|
hasPushedMotionPhotoCover = true;
|
|
39729
39744
|
}
|
|
@@ -39799,6 +39814,10 @@ var DouYinpush = class extends Base {
|
|
|
39799
39814
|
const filteredUserList = userList.filter((item) => item.switch !== false);
|
|
39800
39815
|
for (const item of filteredUserList) {
|
|
39801
39816
|
await common.sleep(2e3);
|
|
39817
|
+
if (!item.sec_uid) {
|
|
39818
|
+
logger.warn(`用户 ${item.remark || item.short_id} 缺少 sec_uid,跳过`);
|
|
39819
|
+
continue;
|
|
39820
|
+
}
|
|
39802
39821
|
const sec_uid = item.sec_uid;
|
|
39803
39822
|
const pushTypes = item.pushTypes || ["post"];
|
|
39804
39823
|
logger.debug(`开始获取用户:${item.remark}(${sec_uid})的内容,推送类型:${pushTypes.join(", ")}`);
|
|
@@ -39920,7 +39939,7 @@ var DouYinpush = class extends Base {
|
|
|
39920
39939
|
});
|
|
39921
39940
|
/** 处理抖音号 */
|
|
39922
39941
|
let user_shortid;
|
|
39923
|
-
UserInfoData.data.user.unique_id === "" ?
|
|
39942
|
+
user_shortid = UserInfoData.data.user.unique_id === "" ? UserInfoData.data.user.short_id : UserInfoData.data.user.unique_id;
|
|
39924
39943
|
config.douyin ??= [];
|
|
39925
39944
|
const existingItem = config.douyin.find((item) => item.sec_uid === sec_uid);
|
|
39926
39945
|
const isSubscribed = await douyinDBInstance.isSubscribed(sec_uid, groupId);
|
|
@@ -40210,7 +40229,7 @@ var getKuaishouID = async (url, log = true) => {
|
|
|
40210
40229
|
logger.warn("无法获取作品ID");
|
|
40211
40230
|
break;
|
|
40212
40231
|
}
|
|
40213
|
-
log
|
|
40232
|
+
if (log) console.log(result);
|
|
40214
40233
|
return result;
|
|
40215
40234
|
};
|
|
40216
40235
|
//#endregion
|
|
@@ -40229,7 +40248,7 @@ var Kuaishou = class extends Base {
|
|
|
40229
40248
|
await this.e.reply("不支持解析的视频");
|
|
40230
40249
|
return true;
|
|
40231
40250
|
}
|
|
40232
|
-
Config.app.parseTip
|
|
40251
|
+
if (Config.app.parseTip) this.e.reply("检测到快手链接,开始解析");
|
|
40233
40252
|
const video_url = data.VideoData.data.data.visionVideoDetail.photo.photoUrl;
|
|
40234
40253
|
const transformedData = Object.entries(data.EmojiData.data.data.visionBaseEmoticons.iconUrls).map(([name, path]) => {
|
|
40235
40254
|
return {
|
|
@@ -40346,7 +40365,7 @@ var getXiaohongshuID = async (url, log = true) => {
|
|
|
40346
40365
|
break;
|
|
40347
40366
|
}
|
|
40348
40367
|
if (result.type === "unknown") throw new Error("无法从链接中提取小红书笔记ID");
|
|
40349
|
-
log
|
|
40368
|
+
if (log) console.log(result);
|
|
40350
40369
|
return result;
|
|
40351
40370
|
};
|
|
40352
40371
|
//#endregion
|
|
@@ -40493,8 +40512,8 @@ var Xiaohongshu = class extends Base {
|
|
|
40493
40512
|
this.type = iddata?.type;
|
|
40494
40513
|
}
|
|
40495
40514
|
async XiaohongshuHandler(data) {
|
|
40496
|
-
if (Config.cookies.xiaohongshu === "") throw new Error("我还没有小红书的 Cookies,暂时无法解析呢 ~");
|
|
40497
|
-
Config.app.parseTip
|
|
40515
|
+
if (Config.amagi.cookies.xiaohongshu === "") throw new Error("我还没有小红书的 Cookies,暂时无法解析呢 ~");
|
|
40516
|
+
if (Config.app.parseTip) await this.e.reply("检测到小红书链接,开始解析");
|
|
40498
40517
|
const NoteData = await this.amagi.xiaohongshu.fetcher.fetchNoteDetail({
|
|
40499
40518
|
typeMode: "strict",
|
|
40500
40519
|
note_id: data.note_id,
|
|
@@ -40553,7 +40572,7 @@ var Xiaohongshu = class extends Base {
|
|
|
40553
40572
|
headers: {
|
|
40554
40573
|
...BASE_HEADERS,
|
|
40555
40574
|
Referer: "https://www.xiaohongshu.com",
|
|
40556
|
-
Cookie: Config.cookies.xiaohongshu
|
|
40575
|
+
Cookie: Config.amagi.cookies.xiaohongshu
|
|
40557
40576
|
}
|
|
40558
40577
|
});
|
|
40559
40578
|
let staticImgPath = "";
|
|
@@ -40567,7 +40586,7 @@ var Xiaohongshu = class extends Base {
|
|
|
40567
40586
|
headers: {
|
|
40568
40587
|
...BASE_HEADERS,
|
|
40569
40588
|
Referer: "https://www.xiaohongshu.com",
|
|
40570
|
-
Cookie: Config.cookies.xiaohongshu
|
|
40589
|
+
Cookie: Config.amagi.cookies.xiaohongshu
|
|
40571
40590
|
}
|
|
40572
40591
|
});
|
|
40573
40592
|
if (livePhoto.filepath) {
|
|
@@ -40594,7 +40613,7 @@ var Xiaohongshu = class extends Base {
|
|
|
40594
40613
|
filepath: filePath,
|
|
40595
40614
|
totalBytes: 0
|
|
40596
40615
|
});
|
|
40597
|
-
const videoPath = Config.
|
|
40616
|
+
const videoPath = Config.app.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
|
|
40598
40617
|
processedImages.push(segment.video(videoPath));
|
|
40599
40618
|
}
|
|
40600
40619
|
}
|
|
@@ -40611,7 +40630,7 @@ var Xiaohongshu = class extends Base {
|
|
|
40611
40630
|
filepath: motionPhotoCoverPath,
|
|
40612
40631
|
totalBytes: 0
|
|
40613
40632
|
});
|
|
40614
|
-
const motionPhotoCover = Config.
|
|
40633
|
+
const motionPhotoCover = Config.app.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
|
|
40615
40634
|
processedImages.push(segment.image(motionPhotoCover));
|
|
40616
40635
|
hasPushedMotionPhotoCover = true;
|
|
40617
40636
|
hasGeneratedLivePhoto = true;
|
|
@@ -40667,7 +40686,7 @@ var Xiaohongshu = class extends Base {
|
|
|
40667
40686
|
headers: {
|
|
40668
40687
|
...BASE_HEADERS,
|
|
40669
40688
|
Referer: "https://www.xiaohongshu.com",
|
|
40670
|
-
Cookie: Config.cookies.xiaohongshu
|
|
40689
|
+
Cookie: Config.amagi.cookies.xiaohongshu
|
|
40671
40690
|
}
|
|
40672
40691
|
}, { message_id: this.e.messageId });
|
|
40673
40692
|
else await this.e.reply(segment.video(video.url_default));
|
|
@@ -40741,7 +40760,7 @@ var xiaohongshuProcessVideos = (streamData, videoQuality, maxAutoVideoSize) => {
|
|
|
40741
40760
|
videos.sort((a, b) => b.size - a.size);
|
|
40742
40761
|
});
|
|
40743
40762
|
if (videoQuality === "adapt") {
|
|
40744
|
-
const sizeLimitBytes = (maxAutoVideoSize || Config.
|
|
40763
|
+
const sizeLimitBytes = (maxAutoVideoSize || Config.app.filelimit) * 1024 * 1024;
|
|
40745
40764
|
for (const quality of [
|
|
40746
40765
|
"4k",
|
|
40747
40766
|
"2k",
|
|
@@ -40986,9 +41005,9 @@ var douyinLogin = async (e) => {
|
|
|
40986
41005
|
if (!hasTtwid) logger.warn(" - 缺少 ttwid");
|
|
40987
41006
|
}
|
|
40988
41007
|
logger.debug("开始保存 cookies...");
|
|
40989
|
-
Config.Modify("
|
|
41008
|
+
Config.Modify("amagi", "cookies.douyin", cookieString);
|
|
40990
41009
|
logger.debug("cookies 保存完成");
|
|
40991
|
-
await e.reply("
|
|
41010
|
+
await e.reply("登录成功!用户登录凭证已保存至配置", { reply: true });
|
|
40992
41011
|
await Promise.all(msg_id.map(async (id) => {
|
|
40993
41012
|
await e.bot.recallMsg(e.contact, id);
|
|
40994
41013
|
}));
|
|
@@ -41194,7 +41213,7 @@ var handleCacheCleanup = wrapWithErrorHandler(async () => {
|
|
|
41194
41213
|
const twoHoursAgo = Date.now() - 7200 * 1e3;
|
|
41195
41214
|
const videoDeleted = removeOldFiles(Common.tempDri.video, twoHoursAgo);
|
|
41196
41215
|
logger.debug(`${Common.tempDri.video} 目录下已删除 ${videoDeleted} 个文件`);
|
|
41197
|
-
if (Config.
|
|
41216
|
+
if (Config.app.imageSendMode === "file") {
|
|
41198
41217
|
const imageDeleted = removeOldFiles(Common.tempDri.images, twoHoursAgo);
|
|
41199
41218
|
logger.debug(`${Common.tempDri.images} 目录下已删除 ${imageDeleted} 个文件`);
|
|
41200
41219
|
}
|
|
@@ -41463,7 +41482,7 @@ var handleSetBilibiliPush = wrapWithErrorHandler(async (e) => {
|
|
|
41463
41482
|
logger.info(`B站推送已${enable ? "开启" : "关闭"}`);
|
|
41464
41483
|
return true;
|
|
41465
41484
|
}
|
|
41466
|
-
if (!Config.cookies.bilibili) {
|
|
41485
|
+
if (!Config.amagi.cookies.bilibili) {
|
|
41467
41486
|
await e.reply("\n请先配置B站Cookie", { at: true });
|
|
41468
41487
|
return true;
|
|
41469
41488
|
}
|
|
@@ -42045,7 +42064,7 @@ var handleDouyin = wrapWithErrorHandler(async (e, next) => {
|
|
|
42045
42064
|
var handleBilibili = wrapWithErrorHandler(async (e, next) => {
|
|
42046
42065
|
e.msg = e.msg.replace(/\\/g, "");
|
|
42047
42066
|
const forceBurnDanmaku = /^#?弹幕解析/.test(e.msg);
|
|
42048
|
-
const urlRegex = /(https?:\/\/(?:(?:www\.|m\.|t\.)?bilibili\.com|b23\.tv|bili2233\.cn)\/[a-zA-Z0-9_
|
|
42067
|
+
const urlRegex = /(https?:\/\/(?:(?:www\.|m\.|t\.)?bilibili\.com|b23\.tv|bili2233\.cn)\/[a-zA-Z0-9_\-.~:/?#[\]@!$&'()*+,;=]+)/;
|
|
42049
42068
|
const bvRegex = /^BV[1-9a-zA-Z]{10}$/;
|
|
42050
42069
|
const avRegex = /^av\d+$/i;
|
|
42051
42070
|
let url = null;
|
|
@@ -42407,1091 +42426,40 @@ var update = karin$1.task("kkk-更新检测", "*/3 * * * *", Handler, {
|
|
|
42407
42426
|
log: false
|
|
42408
42427
|
});
|
|
42409
42428
|
//#endregion
|
|
42410
|
-
//#region src/
|
|
42411
|
-
|
|
42412
|
-
|
|
42413
|
-
|
|
42414
|
-
|
|
42415
|
-
/**
|
|
42416
|
-
|
|
42417
|
-
|
|
42418
|
-
|
|
42419
|
-
|
|
42420
|
-
|
|
42421
|
-
|
|
42422
|
-
|
|
42423
|
-
|
|
42424
|
-
|
|
42425
|
-
|
|
42426
|
-
|
|
42427
|
-
|
|
42428
|
-
|
|
42429
|
-
|
|
42430
|
-
|
|
42431
|
-
|
|
42432
|
-
|
|
42433
|
-
|
|
42434
|
-
|
|
42435
|
-
|
|
42436
|
-
|
|
42437
|
-
|
|
42438
|
-
|
|
42439
|
-
|
|
42440
|
-
|
|
42441
|
-
});
|
|
42442
|
-
avatarCache.set(mid, userProfile.data?.data?.card?.face || "");
|
|
42443
|
-
} catch (error) {
|
|
42444
|
-
if (error instanceof AmagiError && (error.code === -352 || error.code === -412)) {
|
|
42445
|
-
logger.warn(`[BilibiliAPI] 获取头像时遇到风控(${error.code})`);
|
|
42446
|
-
hitRiskControl = true;
|
|
42447
|
-
}
|
|
42448
|
-
avatarCache.set(mid, "");
|
|
42449
|
-
}
|
|
42450
|
-
}
|
|
42451
|
-
return createSuccessResponse(res, caches.map((cache) => {
|
|
42452
|
-
const authorName = cache.bilibiliUser?.remark || cache.host_mid.toString();
|
|
42453
|
-
return {
|
|
42454
|
-
id: cache.dynamic_id,
|
|
42455
|
-
platform: "bilibili",
|
|
42456
|
-
title: `B站动态 ${cache.dynamic_id}`,
|
|
42457
|
-
author: authorName,
|
|
42458
|
-
authorId: cache.host_mid.toString(),
|
|
42459
|
-
avatar: avatarCache.get(cache.host_mid) || "",
|
|
42460
|
-
thumbnail: "",
|
|
42461
|
-
type: "dynamic",
|
|
42462
|
-
dynamicType: cache.dynamic_type,
|
|
42463
|
-
createdAt: cache.createdAt.getTime()
|
|
42464
|
-
};
|
|
42465
|
-
}));
|
|
42466
|
-
} catch (error) {
|
|
42467
|
-
logger.error("[BilibiliAPI] 获取内容列表失败:", error);
|
|
42468
|
-
return createServerErrorResponse(res, "获取内容列表失败");
|
|
42469
|
-
}
|
|
42470
|
-
};
|
|
42471
|
-
/**
|
|
42472
|
-
* 添加B站内容
|
|
42473
|
-
* POST /api/v1/platforms/bilibili/contents
|
|
42474
|
-
* Body: { contentId, groupId, authorId }
|
|
42475
|
-
*/
|
|
42476
|
-
var addContent$1 = async (req, res) => {
|
|
42477
|
-
try {
|
|
42478
|
-
const { contentId, groupId, authorId } = req.body;
|
|
42479
|
-
if (!contentId || !groupId || !authorId) return createBadRequestResponse(res, "请提供 contentId、groupId 和 authorId");
|
|
42480
|
-
const bilibiliDB = await getBilibiliDB();
|
|
42481
|
-
const hostMid = parseInt(authorId);
|
|
42482
|
-
if (!await bilibiliDB.getBilibiliUser(hostMid)) return createBadRequestResponse(res, "该UP主未在订阅列表中,请先添加订阅");
|
|
42483
|
-
await bilibiliDB.addDynamicCache(contentId, hostMid, groupId, "manual");
|
|
42484
|
-
return createSuccessResponse(res, { message: "添加成功" });
|
|
42485
|
-
} catch (error) {
|
|
42486
|
-
logger.error("[BilibiliAPI] 添加内容失败:", error);
|
|
42487
|
-
return createServerErrorResponse(res, "添加内容失败");
|
|
42488
|
-
}
|
|
42489
|
-
};
|
|
42490
|
-
/**
|
|
42491
|
-
* 删除B站内容
|
|
42492
|
-
* POST /api/kkk/v1/platforms/bilibili/contents/:id/delete
|
|
42493
|
-
* Body: { groupId: string }
|
|
42494
|
-
*/
|
|
42495
|
-
var deleteContent$1 = async (req, res) => {
|
|
42496
|
-
try {
|
|
42497
|
-
const { id } = req.params;
|
|
42498
|
-
const { groupId } = req.body;
|
|
42499
|
-
if (!id || Array.isArray(id) && !id[0] || !groupId) return createBadRequestResponse(res, "请提供内容ID和群组ID");
|
|
42500
|
-
const result = await (await getBilibiliDB()).dynamicCacheRepository.delete({
|
|
42501
|
-
dynamic_id: Array.isArray(id) ? id[0] : id,
|
|
42502
|
-
groupId
|
|
42503
|
-
});
|
|
42504
|
-
if (result.affected === 0) return createBadRequestResponse(res, "未找到要删除的内容");
|
|
42505
|
-
return createSuccessResponse(res, {
|
|
42506
|
-
message: "删除成功",
|
|
42507
|
-
affected: result.affected
|
|
42508
|
-
});
|
|
42509
|
-
} catch (error) {
|
|
42510
|
-
logger.error("[BilibiliAPI] 删除内容失败:", error);
|
|
42511
|
-
return createServerErrorResponse(res, "删除内容失败");
|
|
42512
|
-
}
|
|
42513
|
-
};
|
|
42514
|
-
//#endregion
|
|
42515
|
-
//#region src/platform/bilibili/api/risk-control.ts
|
|
42516
|
-
/**
|
|
42517
|
-
* B站风控处理 API
|
|
42518
|
-
*/
|
|
42519
|
-
/**
|
|
42520
|
-
* 创建风控错误响应
|
|
42521
|
-
*/
|
|
42522
|
-
var createRiskControlResponse = (res, geetest, token, v_voucher) => {
|
|
42523
|
-
return res.status(452).json({
|
|
42524
|
-
message: "B站风控验证",
|
|
42525
|
-
code: -352,
|
|
42526
|
-
data: {
|
|
42527
|
-
type: "bilibili_risk_control",
|
|
42528
|
-
geetest,
|
|
42529
|
-
token,
|
|
42530
|
-
v_voucher
|
|
42531
|
-
}
|
|
42532
|
-
});
|
|
42533
|
-
};
|
|
42534
|
-
/**
|
|
42535
|
-
* 创建无法验证的风控错误响应
|
|
42536
|
-
*/
|
|
42537
|
-
var createRiskControlNoVoucherResponse = (res, code) => {
|
|
42538
|
-
return res.status(452).json({
|
|
42539
|
-
message: "B站风控",
|
|
42540
|
-
code,
|
|
42541
|
-
data: {
|
|
42542
|
-
type: "bilibili_risk_control_no_voucher",
|
|
42543
|
-
message: code === -352 ? "B站风控校验失败,请稍后重试或更换 Cookie" : "当前IP被B站风控,请稍后重试或更换网络"
|
|
42544
|
-
}
|
|
42545
|
-
});
|
|
42546
|
-
};
|
|
42547
|
-
/**
|
|
42548
|
-
* 处理 B站风控错误
|
|
42549
|
-
* 检测 -352 和 -412 错误并申请验证码
|
|
42550
|
-
*/
|
|
42551
|
-
var handleBilibiliRiskControl = async (error, res) => {
|
|
42552
|
-
if (!(error instanceof AmagiError)) return false;
|
|
42553
|
-
if (error.code !== -352 && error.code !== -412) return false;
|
|
42554
|
-
const v_voucher = error.data?.data?.v_voucher;
|
|
42555
|
-
if (!v_voucher) {
|
|
42556
|
-
logger.info(`[BilibiliAPI] 检测到风控(${error.code}),但无 v_voucher`);
|
|
42557
|
-
createRiskControlNoVoucherResponse(res, error.code);
|
|
42558
|
-
return true;
|
|
42559
|
-
}
|
|
42560
|
-
try {
|
|
42561
|
-
logger.info(`[BilibiliAPI] 检测到风控(${error.code}),申请验证码...`);
|
|
42562
|
-
const verification = await bilibiliFetcher.requestCaptchaFromVoucher({
|
|
42563
|
-
v_voucher,
|
|
42564
|
-
typeMode: "strict"
|
|
42565
|
-
});
|
|
42566
|
-
if (!verification.data?.data?.geetest) {
|
|
42567
|
-
logger.error("[BilibiliAPI] 申请验证码失败");
|
|
42568
|
-
createRiskControlNoVoucherResponse(res, error.code);
|
|
42569
|
-
return true;
|
|
42570
|
-
}
|
|
42571
|
-
const geetest = verification.data.data.geetest;
|
|
42572
|
-
const token = verification.data.data.token;
|
|
42573
|
-
createRiskControlResponse(res, {
|
|
42574
|
-
gt: geetest.gt,
|
|
42575
|
-
challenge: geetest.challenge
|
|
42576
|
-
}, token, v_voucher);
|
|
42577
|
-
return true;
|
|
42578
|
-
} catch (err) {
|
|
42579
|
-
logger.error("[BilibiliAPI] 申请验证码异常:", err);
|
|
42580
|
-
createRiskControlNoVoucherResponse(res, error.code);
|
|
42581
|
-
return true;
|
|
42582
|
-
}
|
|
42583
|
-
};
|
|
42584
|
-
/**
|
|
42585
|
-
* B站风控验证结果提交
|
|
42586
|
-
* POST /api/v1/platforms/bilibili/verify
|
|
42587
|
-
* Body: { challenge, token, validate, seccode }
|
|
42588
|
-
*/
|
|
42589
|
-
var verifyCaptcha = async (req, res) => {
|
|
42590
|
-
try {
|
|
42591
|
-
const { challenge, token, validate, seccode } = req.body;
|
|
42592
|
-
if (!challenge || !token || !validate || !seccode) return createBadRequestResponse(res, "验证参数不完整");
|
|
42593
|
-
logger.info("[BilibiliAPI] 提交风控验证结果...");
|
|
42594
|
-
const verifyResult = await bilibiliFetcher.validateCaptchaResult({
|
|
42595
|
-
challenge,
|
|
42596
|
-
token,
|
|
42597
|
-
validate,
|
|
42598
|
-
seccode,
|
|
42599
|
-
typeMode: "strict"
|
|
42600
|
-
});
|
|
42601
|
-
if (verifyResult.success && verifyResult.data?.data?.grisk_id) {
|
|
42602
|
-
logger.info(`[BilibiliAPI] 验证成功,grisk_id: ${verifyResult.data.data.grisk_id}`);
|
|
42603
|
-
return createSuccessResponse(res, {
|
|
42604
|
-
success: true,
|
|
42605
|
-
message: "验证成功",
|
|
42606
|
-
grisk_id: verifyResult.data.data.grisk_id
|
|
42607
|
-
});
|
|
42608
|
-
}
|
|
42609
|
-
return createSuccessResponse(res, {
|
|
42610
|
-
success: false,
|
|
42611
|
-
message: "验证失败,请重试"
|
|
42612
|
-
});
|
|
42613
|
-
} catch (error) {
|
|
42614
|
-
logger.error("[BilibiliAPI] 验证请求失败:", error);
|
|
42615
|
-
if (error instanceof AmagiError) {
|
|
42616
|
-
if (error.code === -111) return createSuccessResponse(res, {
|
|
42617
|
-
success: false,
|
|
42618
|
-
message: "验证失败,建议重新配置 B站 Cookie"
|
|
42619
|
-
});
|
|
42620
|
-
return createSuccessResponse(res, {
|
|
42621
|
-
success: false,
|
|
42622
|
-
message: error.rawError?.errorDescription || "验证失败"
|
|
42623
|
-
});
|
|
42624
|
-
}
|
|
42625
|
-
return createServerErrorResponse(res, "验证请求失败");
|
|
42626
|
-
}
|
|
42627
|
-
};
|
|
42628
|
-
//#endregion
|
|
42629
|
-
//#region src/platform/bilibili/api/parse.ts
|
|
42630
|
-
/**
|
|
42631
|
-
* 判断是否为图文动态
|
|
42632
|
-
*/
|
|
42633
|
-
function isDynamicTypeDraw(data) {
|
|
42634
|
-
return data.data.item.type === DynamicType.DRAW;
|
|
42635
|
-
}
|
|
42636
|
-
/**
|
|
42637
|
-
* 判断是否为文字动态
|
|
42638
|
-
*/
|
|
42639
|
-
function isDynamicTypeWord(data) {
|
|
42640
|
-
return data.data.item.type === DynamicType.WORD;
|
|
42641
|
-
}
|
|
42642
|
-
/**
|
|
42643
|
-
* 判断是否为视频动态
|
|
42644
|
-
*/
|
|
42645
|
-
function isDynamicTypeAV(data) {
|
|
42646
|
-
return data.data.item.type === DynamicType.AV;
|
|
42647
|
-
}
|
|
42648
|
-
/**
|
|
42649
|
-
* 判断是否为转发动态
|
|
42650
|
-
*/
|
|
42651
|
-
function isDynamicTypeForward(data) {
|
|
42652
|
-
return data.data.item.type === DynamicType.FORWARD;
|
|
42653
|
-
}
|
|
42654
|
-
/**
|
|
42655
|
-
* 判断是否为专栏文章动态
|
|
42656
|
-
*/
|
|
42657
|
-
function isDynamicTypeArticle(data) {
|
|
42658
|
-
return data.data.item.type === DynamicType.ARTICLE;
|
|
42659
|
-
}
|
|
42660
|
-
/**
|
|
42661
|
-
* 判断是否为直播推荐动态
|
|
42662
|
-
*/
|
|
42663
|
-
function isDynamicTypeLiveRcmd(data) {
|
|
42664
|
-
return data.data.item.type === DynamicType.LIVE_RCMD;
|
|
42665
|
-
}
|
|
42666
|
-
/**
|
|
42667
|
-
* 格式化数字
|
|
42668
|
-
*/
|
|
42669
|
-
var formatCount$1 = (count) => {
|
|
42670
|
-
if (count >= 1e8) return `${(count / 1e8).toFixed(1)}亿`;
|
|
42671
|
-
if (count >= 1e4) return `${(count / 1e4).toFixed(1)}万`;
|
|
42672
|
-
return count.toString();
|
|
42673
|
-
};
|
|
42674
|
-
/**
|
|
42675
|
-
* 格式化时长
|
|
42676
|
-
*/
|
|
42677
|
-
var formatDuration$1 = (seconds) => {
|
|
42678
|
-
return `${Math.floor(seconds / 60)}:${Math.floor(seconds % 60).toString().padStart(2, "0")}`;
|
|
42679
|
-
};
|
|
42680
|
-
/**
|
|
42681
|
-
* 格式化时间戳
|
|
42682
|
-
*/
|
|
42683
|
-
var formatTimestamp$1 = (timestamp) => {
|
|
42684
|
-
const date = /* @__PURE__ */ new Date(timestamp * 1e3);
|
|
42685
|
-
const diff = (/* @__PURE__ */ new Date()).getTime() - date.getTime();
|
|
42686
|
-
if (diff < 6e4) return "刚刚";
|
|
42687
|
-
if (diff < 36e5) return `${Math.floor(diff / 6e4)}分钟前`;
|
|
42688
|
-
if (diff < 864e5) return `${Math.floor(diff / 36e5)}小时前`;
|
|
42689
|
-
if (diff < 2592e6) return `${Math.floor(diff / 864e5)}天前`;
|
|
42690
|
-
return `${date.getMonth() + 1}月${date.getDate()}日`;
|
|
42691
|
-
};
|
|
42692
|
-
/**
|
|
42693
|
-
* 处理评论中的表情
|
|
42694
|
-
*/
|
|
42695
|
-
var processCommentEmojis$1 = (text, emojiData) => {
|
|
42696
|
-
if (!text || !emojiData?.data?.packages) return text;
|
|
42697
|
-
const emojiMap = /* @__PURE__ */ new Map();
|
|
42698
|
-
emojiData.data.packages.forEach((pkg) => {
|
|
42699
|
-
pkg.emote.forEach((emote) => {
|
|
42700
|
-
emojiMap.set(emote.text, emote.url);
|
|
42701
|
-
});
|
|
42702
|
-
});
|
|
42703
|
-
let processedText = text;
|
|
42704
|
-
processedText = processedText.replace(/\[([^\]]+)\]/g, (match, emojiName) => {
|
|
42705
|
-
const emojiUrl = emojiMap.get(match) || emojiMap.get(emojiName);
|
|
42706
|
-
if (emojiUrl) return `<img src="${emojiUrl}" alt="${emojiName}" class="emoji" />`;
|
|
42707
|
-
return match;
|
|
42708
|
-
});
|
|
42709
|
-
return processedText.split(/(<img[^>]*>)/).map((part) => {
|
|
42710
|
-
if (part.startsWith("<img")) return part;
|
|
42711
|
-
if (part.trim()) return `<span>${part}</span>`;
|
|
42712
|
-
return part;
|
|
42713
|
-
}).join("");
|
|
42714
|
-
};
|
|
42715
|
-
/**
|
|
42716
|
-
* 解析评论数据
|
|
42717
|
-
*/
|
|
42718
|
-
var parseComments$1 = (commentsData, emojiData) => {
|
|
42719
|
-
if (!commentsData || !Array.isArray(commentsData)) return [];
|
|
42720
|
-
return commentsData.map((comment, index) => {
|
|
42721
|
-
let processedContent = comment.content?.message || "";
|
|
42722
|
-
if (emojiData && processedContent) processedContent = processCommentEmojis$1(processedContent, emojiData);
|
|
42723
|
-
const pictures = comment.content?.pictures;
|
|
42724
|
-
return {
|
|
42725
|
-
id: comment.rpid?.toString() || index.toString(),
|
|
42726
|
-
author: comment.member?.uname || "匿名用户",
|
|
42727
|
-
avatar: comment.member?.avatar || "",
|
|
42728
|
-
content: processedContent,
|
|
42729
|
-
images: pictures?.map((pic) => pic.img_src) || [],
|
|
42730
|
-
likes: comment.like || 0,
|
|
42731
|
-
timestamp: formatTimestamp$1(comment.ctime || 0)
|
|
42732
|
-
};
|
|
42733
|
-
});
|
|
42734
|
-
};
|
|
42735
|
-
/**
|
|
42736
|
-
* 获取动态评论类型
|
|
42737
|
-
* 参考 mapping_table 函数的映射关系
|
|
42738
|
-
*/
|
|
42739
|
-
var getDynamicCommentType = (dynamicType) => {
|
|
42740
|
-
switch (dynamicType) {
|
|
42741
|
-
case DynamicType.AV: return 1;
|
|
42742
|
-
case DynamicType.DRAW: return 11;
|
|
42743
|
-
case DynamicType.ARTICLE: return 12;
|
|
42744
|
-
case DynamicType.LIVE_RCMD:
|
|
42745
|
-
case DynamicType.FORWARD:
|
|
42746
|
-
case DynamicType.WORD:
|
|
42747
|
-
default: return 17;
|
|
42748
|
-
}
|
|
42749
|
-
};
|
|
42750
|
-
/**
|
|
42751
|
-
* 获取动态 OID(用于评论接口)
|
|
42752
|
-
*/
|
|
42753
|
-
var getDynamicOid = (dynamicInfo, dynamicCard) => {
|
|
42754
|
-
const item = dynamicInfo.data.item;
|
|
42755
|
-
if (isDynamicTypeAV(dynamicInfo)) return (dynamicInfo.data.item.modules.module_dynamic.major?.archive)?.aid?.toString() || item.id_str;
|
|
42756
|
-
if (isDynamicTypeWord(dynamicInfo) || isDynamicTypeForward(dynamicInfo)) return item.id_str;
|
|
42757
|
-
return dynamicCard.card?.desc?.rid?.toString() || item.id_str;
|
|
42758
|
-
};
|
|
42759
|
-
/**
|
|
42760
|
-
* 解析图文动态内容
|
|
42761
|
-
*/
|
|
42762
|
-
var parseDrawDynamic = (data) => {
|
|
42763
|
-
const moduleDynamic = data.data.item.modules.module_dynamic;
|
|
42764
|
-
const description = moduleDynamic.major?.opus?.summary?.text || "";
|
|
42765
|
-
const images = (moduleDynamic.major?.opus?.pics || []).map((pic) => pic.url).filter((url) => typeof url === "string");
|
|
42766
|
-
return {
|
|
42767
|
-
title: "图文动态",
|
|
42768
|
-
description,
|
|
42769
|
-
thumbnail: images[0] || "",
|
|
42770
|
-
images,
|
|
42771
|
-
type: "note"
|
|
42772
|
-
};
|
|
42773
|
-
};
|
|
42774
|
-
/**
|
|
42775
|
-
* 解析文字动态内容
|
|
42776
|
-
*/
|
|
42777
|
-
var parseWordDynamic = (data) => {
|
|
42778
|
-
return {
|
|
42779
|
-
title: "文字动态",
|
|
42780
|
-
description: data.data.item.modules.module_dynamic.major?.opus?.summary?.text || "",
|
|
42781
|
-
thumbnail: "",
|
|
42782
|
-
images: [],
|
|
42783
|
-
type: "note"
|
|
42784
|
-
};
|
|
42785
|
-
};
|
|
42786
|
-
/**
|
|
42787
|
-
* 解析视频动态内容
|
|
42788
|
-
*/
|
|
42789
|
-
var parseAVDynamic = (data) => {
|
|
42790
|
-
const archive = data.data.item.modules.module_dynamic.major?.archive;
|
|
42791
|
-
return {
|
|
42792
|
-
title: archive?.title || "视频动态",
|
|
42793
|
-
description: archive?.desc || "",
|
|
42794
|
-
thumbnail: archive?.cover || "",
|
|
42795
|
-
images: [],
|
|
42796
|
-
type: "video"
|
|
42797
|
-
};
|
|
42798
|
-
};
|
|
42799
|
-
/**
|
|
42800
|
-
* 解析转发动态内容
|
|
42801
|
-
*/
|
|
42802
|
-
var parseForwardDynamic = (data) => {
|
|
42803
|
-
return {
|
|
42804
|
-
title: "转发动态",
|
|
42805
|
-
description: data.data.item.modules.module_dynamic.desc?.text || "",
|
|
42806
|
-
thumbnail: "",
|
|
42807
|
-
images: [],
|
|
42808
|
-
type: "forward"
|
|
42809
|
-
};
|
|
42810
|
-
};
|
|
42811
|
-
/**
|
|
42812
|
-
* 解析专栏文章动态内容
|
|
42813
|
-
*/
|
|
42814
|
-
var parseArticleDynamic = (data) => {
|
|
42815
|
-
const opus = data.data.item.modules.module_dynamic.major?.opus;
|
|
42816
|
-
return {
|
|
42817
|
-
title: opus?.title || "专栏文章",
|
|
42818
|
-
description: opus?.summary?.text || "",
|
|
42819
|
-
thumbnail: "",
|
|
42820
|
-
images: [],
|
|
42821
|
-
type: "article"
|
|
42822
|
-
};
|
|
42823
|
-
};
|
|
42824
|
-
/**
|
|
42825
|
-
* 解析直播推荐动态内容
|
|
42826
|
-
*/
|
|
42827
|
-
var parseLiveRcmdDynamic = (data) => {
|
|
42828
|
-
const liveRcmd = data.data.item.modules.module_dynamic.major?.live_rcmd;
|
|
42829
|
-
let title = "直播推荐";
|
|
42830
|
-
let thumbnail = "";
|
|
42831
|
-
if (liveRcmd?.content) try {
|
|
42832
|
-
const liveInfo = JSON.parse(liveRcmd.content);
|
|
42833
|
-
title = liveInfo.live_play_info?.title || "直播推荐";
|
|
42834
|
-
thumbnail = liveInfo.live_play_info?.cover || "";
|
|
42835
|
-
} catch {}
|
|
42836
|
-
return {
|
|
42837
|
-
title,
|
|
42838
|
-
description: "",
|
|
42839
|
-
thumbnail,
|
|
42840
|
-
images: [],
|
|
42841
|
-
type: "live"
|
|
42842
|
-
};
|
|
42843
|
-
};
|
|
42844
|
-
/**
|
|
42845
|
-
* 解析动态内容(主入口)
|
|
42846
|
-
* 使用类型守卫进行类型安全的分发
|
|
42847
|
-
*/
|
|
42848
|
-
var parseDynamicContent = (dynamicInfo) => {
|
|
42849
|
-
if (isDynamicTypeDraw(dynamicInfo)) return parseDrawDynamic(dynamicInfo);
|
|
42850
|
-
if (isDynamicTypeWord(dynamicInfo)) return parseWordDynamic(dynamicInfo);
|
|
42851
|
-
if (isDynamicTypeAV(dynamicInfo)) return parseAVDynamic(dynamicInfo);
|
|
42852
|
-
if (isDynamicTypeForward(dynamicInfo)) return parseForwardDynamic(dynamicInfo);
|
|
42853
|
-
if (isDynamicTypeArticle(dynamicInfo)) return parseArticleDynamic(dynamicInfo);
|
|
42854
|
-
if (isDynamicTypeLiveRcmd(dynamicInfo)) return parseLiveRcmdDynamic(dynamicInfo);
|
|
42855
|
-
return {
|
|
42856
|
-
title: "动态内容",
|
|
42857
|
-
description: "",
|
|
42858
|
-
thumbnail: "",
|
|
42859
|
-
images: [],
|
|
42860
|
-
type: "dynamic"
|
|
42861
|
-
};
|
|
42862
|
-
};
|
|
42863
|
-
/**
|
|
42864
|
-
* 获取动态作者信息
|
|
42865
|
-
*/
|
|
42866
|
-
var getDynamicAuthor = (dynamicInfo) => {
|
|
42867
|
-
const moduleAuthor = dynamicInfo.data.item.modules.module_author;
|
|
42868
|
-
return {
|
|
42869
|
-
name: moduleAuthor.name || "未知用户",
|
|
42870
|
-
avatar: moduleAuthor.face || "",
|
|
42871
|
-
id: moduleAuthor.mid?.toString() || ""
|
|
42872
|
-
};
|
|
42873
|
-
};
|
|
42874
|
-
/**
|
|
42875
|
-
* 获取动态统计信息
|
|
42876
|
-
*/
|
|
42877
|
-
var getDynamicStats = (dynamicInfo) => {
|
|
42878
|
-
const moduleStat = dynamicInfo.data.item.modules.module_stat;
|
|
42879
|
-
return {
|
|
42880
|
-
views: formatCount$1(moduleStat.forward?.count || 0),
|
|
42881
|
-
likes: formatCount$1(moduleStat.like?.count || 0),
|
|
42882
|
-
commentCount: moduleStat.comment?.count || 0
|
|
42883
|
-
};
|
|
42884
|
-
};
|
|
42885
|
-
/**
|
|
42886
|
-
* 解析B站视频
|
|
42887
|
-
* POST /api/kkk/v1/platforms/bilibili/parse/video
|
|
42888
|
-
* Body: { bvid?: string, aid?: string }
|
|
42889
|
-
*/
|
|
42890
|
-
var parseVideo = async (req, res) => {
|
|
42891
|
-
try {
|
|
42892
|
-
const { bvid, aid } = req.body;
|
|
42893
|
-
if (!bvid && !aid) return createBadRequestResponse(res, "请提供 bvid 或 aid");
|
|
42894
|
-
const videoDetail = (await bilibiliFetcher.fetchVideoInfo({
|
|
42895
|
-
bvid: bvid || "",
|
|
42896
|
-
typeMode: "strict"
|
|
42897
|
-
})).data.data;
|
|
42898
|
-
const streamData = (await bilibiliFetcher.fetchVideoStreamUrl({
|
|
42899
|
-
avid: videoDetail.aid,
|
|
42900
|
-
cid: videoDetail.cid,
|
|
42901
|
-
typeMode: "strict"
|
|
42902
|
-
})).data.data;
|
|
42903
|
-
const [commentsResponse, emojiResponse] = await Promise.all([bilibiliFetcher.fetchComments({
|
|
42904
|
-
oid: videoDetail.aid.toString(),
|
|
42905
|
-
type: 1,
|
|
42906
|
-
number: 50,
|
|
42907
|
-
typeMode: "strict"
|
|
42908
|
-
}), bilibiliFetcher.fetchEmojiList({ typeMode: "strict" })]);
|
|
42909
|
-
const commentsData = commentsResponse.data.data;
|
|
42910
|
-
const emojiData = emojiResponse.data;
|
|
42911
|
-
const comments = parseComments$1(commentsData.replies || [], emojiData);
|
|
42912
|
-
return createSuccessResponse(res, {
|
|
42913
|
-
id: videoDetail.bvid,
|
|
42914
|
-
title: videoDetail.title || "无标题",
|
|
42915
|
-
description: videoDetail.desc || "",
|
|
42916
|
-
thumbnail: videoDetail.pic || "",
|
|
42917
|
-
duration: formatDuration$1(videoDetail.duration || 0),
|
|
42918
|
-
views: formatCount$1(videoDetail.stat?.view || 0),
|
|
42919
|
-
likes: formatCount$1(videoDetail.stat?.like || 0),
|
|
42920
|
-
author: {
|
|
42921
|
-
name: videoDetail.owner?.name || "未知用户",
|
|
42922
|
-
avatar: videoDetail.owner?.face || "",
|
|
42923
|
-
id: videoDetail.owner?.mid?.toString() || ""
|
|
42924
|
-
},
|
|
42925
|
-
type: "video",
|
|
42926
|
-
downloadUrl: {
|
|
42927
|
-
video: streamData.dash?.video?.[0]?.baseUrl,
|
|
42928
|
-
audio: streamData.dash?.audio?.[0]?.baseUrl
|
|
42929
|
-
},
|
|
42930
|
-
tags: videoDetail.tname ? [videoDetail.tname] : [],
|
|
42931
|
-
comments,
|
|
42932
|
-
commentCount: videoDetail.stat?.reply || 0
|
|
42933
|
-
});
|
|
42934
|
-
} catch (error) {
|
|
42935
|
-
const err = error;
|
|
42936
|
-
if (await handleBilibiliRiskControl(err, res)) return;
|
|
42937
|
-
logger.error("[BilibiliAPI] 解析视频失败:", err);
|
|
42938
|
-
return createServerErrorResponse(res, `解析失败: ${err.message}`);
|
|
42939
|
-
}
|
|
42940
|
-
};
|
|
42941
|
-
/**
|
|
42942
|
-
* 解析B站动态(返回原始数据)
|
|
42943
|
-
* POST /api/kkk/v1/platforms/bilibili/parse/dynamic/raw
|
|
42944
|
-
* Body: { dynamic_id: string }
|
|
42945
|
-
*
|
|
42946
|
-
* 返回原始 API 数据,前端自行处理不同动态类型
|
|
42947
|
-
* 注意:不获取评论数据,减少请求开销
|
|
42948
|
-
*/
|
|
42949
|
-
var parseDynamicRaw = async (req, res) => {
|
|
42950
|
-
try {
|
|
42951
|
-
const { dynamic_id } = req.body;
|
|
42952
|
-
if (!dynamic_id) return createBadRequestResponse(res, "请提供动态ID (dynamic_id)");
|
|
42953
|
-
const dynamicInfo = (await bilibiliFetcher.fetchDynamicDetail({
|
|
42954
|
-
dynamic_id,
|
|
42955
|
-
typeMode: "strict"
|
|
42956
|
-
})).data;
|
|
42957
|
-
const [dynamicCardResponse, userProfileResponse] = await Promise.all([bilibiliFetcher.fetchDynamicCard({
|
|
42958
|
-
dynamic_id: dynamicInfo.data.item.id_str,
|
|
42959
|
-
typeMode: "strict"
|
|
42960
|
-
}), bilibiliFetcher.fetchUserCard({
|
|
42961
|
-
host_mid: dynamicInfo.data.item.modules.module_author.mid,
|
|
42962
|
-
typeMode: "strict"
|
|
42963
|
-
})]);
|
|
42964
|
-
const dynamicCard = dynamicCardResponse.data.data;
|
|
42965
|
-
return createSuccessResponse(res, {
|
|
42966
|
-
dynamicInfo: dynamicInfo.data,
|
|
42967
|
-
dynamicCard,
|
|
42968
|
-
userProfile: userProfileResponse.data.data,
|
|
42969
|
-
comments: null,
|
|
42970
|
-
emoji: null
|
|
42971
|
-
});
|
|
42972
|
-
} catch (error) {
|
|
42973
|
-
const err = error;
|
|
42974
|
-
if (await handleBilibiliRiskControl(err, res)) return;
|
|
42975
|
-
logger.error("[BilibiliAPI] 解析动态失败:", err);
|
|
42976
|
-
return createServerErrorResponse(res, `解析失败: ${err.message}`);
|
|
42977
|
-
}
|
|
42978
|
-
};
|
|
42979
|
-
/**
|
|
42980
|
-
* 解析B站动态
|
|
42981
|
-
* POST /api/kkk/v1/platforms/bilibili/parse/dynamic
|
|
42982
|
-
* Body: { dynamic_id: string }
|
|
42983
|
-
*/
|
|
42984
|
-
var parseDynamic = async (req, res) => {
|
|
42985
|
-
try {
|
|
42986
|
-
const { dynamic_id } = req.body;
|
|
42987
|
-
if (!dynamic_id) return createBadRequestResponse(res, "请提供动态ID (dynamic_id)");
|
|
42988
|
-
const dynamicInfo = (await bilibiliFetcher.fetchDynamicDetail({
|
|
42989
|
-
dynamic_id,
|
|
42990
|
-
typeMode: "strict"
|
|
42991
|
-
})).data;
|
|
42992
|
-
const dynamicCard = (await bilibiliFetcher.fetchDynamicCard({
|
|
42993
|
-
dynamic_id: dynamicInfo.data.item.id_str,
|
|
42994
|
-
typeMode: "strict"
|
|
42995
|
-
})).data.data;
|
|
42996
|
-
const dynamicContent = parseDynamicContent(dynamicInfo);
|
|
42997
|
-
const author = getDynamicAuthor(dynamicInfo);
|
|
42998
|
-
const stats = getDynamicStats(dynamicInfo);
|
|
42999
|
-
let comments = [];
|
|
43000
|
-
const itemType = dynamicInfo.data.item.type;
|
|
43001
|
-
if (itemType !== DynamicType.LIVE_RCMD) try {
|
|
43002
|
-
const [commentsResponse, emojiResponse] = await Promise.all([bilibiliFetcher.fetchComments({
|
|
43003
|
-
type: getDynamicCommentType(itemType),
|
|
43004
|
-
oid: getDynamicOid(dynamicInfo, dynamicCard),
|
|
43005
|
-
typeMode: "strict"
|
|
43006
|
-
}), bilibiliFetcher.fetchEmojiList({ typeMode: "strict" })]);
|
|
43007
|
-
const commentsData = commentsResponse.data.data;
|
|
43008
|
-
const emojiData = emojiResponse.data;
|
|
43009
|
-
comments = parseComments$1(commentsData.replies || [], emojiData);
|
|
43010
|
-
} catch (error) {
|
|
43011
|
-
logger.warn("[BilibiliAPI] 获取动态评论失败:", error);
|
|
43012
|
-
}
|
|
43013
|
-
return createSuccessResponse(res, {
|
|
43014
|
-
id: dynamicInfo.data.item.id_str,
|
|
43015
|
-
title: dynamicContent.title,
|
|
43016
|
-
description: dynamicContent.description,
|
|
43017
|
-
thumbnail: dynamicContent.thumbnail || author.avatar,
|
|
43018
|
-
duration: "0:00",
|
|
43019
|
-
views: stats.views,
|
|
43020
|
-
likes: stats.likes,
|
|
43021
|
-
author,
|
|
43022
|
-
type: dynamicContent.type,
|
|
43023
|
-
dynamicType: itemType,
|
|
43024
|
-
images: dynamicContent.images,
|
|
43025
|
-
tags: [],
|
|
43026
|
-
comments,
|
|
43027
|
-
commentCount: stats.commentCount
|
|
43028
|
-
});
|
|
43029
|
-
} catch (error) {
|
|
43030
|
-
const err = error;
|
|
43031
|
-
if (await handleBilibiliRiskControl(err, res)) return;
|
|
43032
|
-
logger.error("[BilibiliAPI] 解析动态失败:", err);
|
|
43033
|
-
return createServerErrorResponse(res, `解析失败: ${err.message}`);
|
|
43034
|
-
}
|
|
43035
|
-
};
|
|
43036
|
-
//#endregion
|
|
43037
|
-
//#region src/platform/bilibili/api/video.ts
|
|
43038
|
-
var QUALITY_MAP = {
|
|
43039
|
-
6: "240P 极速",
|
|
43040
|
-
16: "360P 流畅",
|
|
43041
|
-
32: "480P 清晰",
|
|
43042
|
-
64: "720P 高清",
|
|
43043
|
-
74: "720P60 高帧率",
|
|
43044
|
-
80: "1080P 高清",
|
|
43045
|
-
112: "1080P+ 高码率",
|
|
43046
|
-
116: "1080P60 高帧率",
|
|
43047
|
-
120: "4K 超清",
|
|
43048
|
-
125: "HDR 真彩色",
|
|
43049
|
-
126: "杜比视界",
|
|
43050
|
-
127: "8K 超高清"
|
|
43051
|
-
};
|
|
43052
|
-
/**
|
|
43053
|
-
* 获取视频播放地址
|
|
43054
|
-
* GET /api/kkk/v1/platforms/bilibili/video/playurl?bvid=xxx&p=1
|
|
43055
|
-
*
|
|
43056
|
-
* 返回视频播放地址,前端直接使用这些 URL 播放
|
|
43057
|
-
* - DASH 格式:返回视频流和音频流列表,前端使用支持 DASH 的播放器
|
|
43058
|
-
* - DURL 格式:返回合并好的单流地址,可直接播放
|
|
43059
|
-
*/
|
|
43060
|
-
var getVideoPlayUrl = async (req, res) => {
|
|
43061
|
-
try {
|
|
43062
|
-
const { bvid, p } = req.query;
|
|
43063
|
-
if (!bvid) return createBadRequestResponse(res, "请提供视频 BV 号 (bvid)");
|
|
43064
|
-
const pageNum = p ? parseInt(p, 10) : 1;
|
|
43065
|
-
const videoInfo = (await bilibiliFetcher.fetchVideoInfo({
|
|
43066
|
-
bvid,
|
|
43067
|
-
typeMode: "strict"
|
|
43068
|
-
})).data.data;
|
|
43069
|
-
const cid = pageNum > 1 && videoInfo.pages[pageNum - 1] ? videoInfo.pages[pageNum - 1].cid : videoInfo.cid;
|
|
43070
|
-
const playUrlData = (await bilibiliFetcher.fetchVideoStreamUrl({
|
|
43071
|
-
avid: videoInfo.aid,
|
|
43072
|
-
cid,
|
|
43073
|
-
typeMode: "strict"
|
|
43074
|
-
})).data.data;
|
|
43075
|
-
const response = {
|
|
43076
|
-
bvid: videoInfo.bvid,
|
|
43077
|
-
aid: videoInfo.aid,
|
|
43078
|
-
cid,
|
|
43079
|
-
title: videoInfo.title,
|
|
43080
|
-
duration: videoInfo.duration,
|
|
43081
|
-
cover: videoInfo.pic
|
|
43082
|
-
};
|
|
43083
|
-
if (playUrlData.dash) {
|
|
43084
|
-
response.streamType = "dash";
|
|
43085
|
-
const videoMap = /* @__PURE__ */ new Map();
|
|
43086
|
-
for (const video of playUrlData.dash.video) if (!videoMap.has(video.id)) videoMap.set(video.id, {
|
|
43087
|
-
url: video.baseUrl || video.base_url,
|
|
43088
|
-
quality: video.id,
|
|
43089
|
-
qualityDesc: QUALITY_MAP[video.id] || `${video.id}P`,
|
|
43090
|
-
codecs: video.codecs,
|
|
43091
|
-
width: video.width,
|
|
43092
|
-
height: video.height,
|
|
43093
|
-
bandwidth: video.bandwidth
|
|
43094
|
-
});
|
|
43095
|
-
response.videoStreams = Array.from(videoMap.values()).sort((a, b) => b.quality - a.quality);
|
|
43096
|
-
response.audioStreams = playUrlData.dash.audio.map((audio) => ({
|
|
43097
|
-
url: audio.baseUrl || audio.base_url,
|
|
43098
|
-
quality: audio.id,
|
|
43099
|
-
bandwidth: audio.bandwidth
|
|
43100
|
-
})).sort((a, b) => b.bandwidth - a.bandwidth);
|
|
43101
|
-
} else if (playUrlData.durl && playUrlData.durl.length > 0) {
|
|
43102
|
-
response.streamType = "durl";
|
|
43103
|
-
response.durlUrl = playUrlData.durl[0].url;
|
|
43104
|
-
response.durlQuality = playUrlData.accept_description?.[0] || "默认清晰度";
|
|
43105
|
-
} else return createServerErrorResponse(res, "无法获取视频播放地址");
|
|
43106
|
-
return createSuccessResponse(res, response);
|
|
43107
|
-
} catch (error) {
|
|
43108
|
-
const err = error;
|
|
43109
|
-
if (await handleBilibiliRiskControl(err, res)) return;
|
|
43110
|
-
logger.error("[BilibiliAPI] 获取视频播放地址失败:", err);
|
|
43111
|
-
return createServerErrorResponse(res, `获取播放地址失败: ${err.message}`);
|
|
43112
|
-
}
|
|
43113
|
-
};
|
|
43114
|
-
//#endregion
|
|
43115
|
-
//#region src/platform/bilibili/api/index.ts
|
|
43116
|
-
/**
|
|
43117
|
-
* B站平台 API 路由
|
|
43118
|
-
*/
|
|
43119
|
-
var router$1 = express.Router();
|
|
43120
|
-
router$1.get("/contents", getContents$1);
|
|
43121
|
-
router$1.post("/contents", addContent$1);
|
|
43122
|
-
router$1.post("/contents/:id/delete", deleteContent$1);
|
|
43123
|
-
router$1.post("/parse/video", parseVideo);
|
|
43124
|
-
router$1.post("/parse/dynamic", parseDynamic);
|
|
43125
|
-
router$1.post("/parse/dynamic/raw", parseDynamicRaw);
|
|
43126
|
-
router$1.get("/video/playurl", getVideoPlayUrl);
|
|
43127
|
-
router$1.post("/verify", verifyCaptcha);
|
|
43128
|
-
//#endregion
|
|
43129
|
-
//#region src/platform/douyin/api/contents.ts
|
|
43130
|
-
/**
|
|
43131
|
-
* 抖音内容管理 API
|
|
43132
|
-
*/
|
|
43133
|
-
/**
|
|
43134
|
-
* 获取抖音内容列表
|
|
43135
|
-
* GET /api/v1/platforms/douyin/contents?groupId=xxx
|
|
43136
|
-
*/
|
|
43137
|
-
var getContents = async (req, res) => {
|
|
43138
|
-
try {
|
|
43139
|
-
const { groupId } = req.query;
|
|
43140
|
-
if (!groupId || typeof groupId !== "string") return createBadRequestResponse(res, "请提供群组ID");
|
|
43141
|
-
const caches = await (await getDouyinDB()).awemeCacheRepository.find({
|
|
43142
|
-
where: { groupId },
|
|
43143
|
-
relations: ["douyinUser"],
|
|
43144
|
-
order: { createdAt: "DESC" },
|
|
43145
|
-
take: 100
|
|
43146
|
-
});
|
|
43147
|
-
const userAvatarMap = /* @__PURE__ */ new Map();
|
|
43148
|
-
const uniqueSecUids = [...new Set(caches.map((c) => c.sec_uid))];
|
|
43149
|
-
for (let i = 0; i < uniqueSecUids.length; i += 5) {
|
|
43150
|
-
const batch = uniqueSecUids.slice(i, i + 5);
|
|
43151
|
-
await Promise.all(batch.map(async (secUid) => {
|
|
43152
|
-
try {
|
|
43153
|
-
const userProfile = await douyinFetcher.fetchUserProfile({
|
|
43154
|
-
sec_uid: secUid,
|
|
43155
|
-
typeMode: "strict"
|
|
43156
|
-
});
|
|
43157
|
-
userAvatarMap.set(secUid, userProfile.data?.user?.avatar_larger?.url_list[0] || "");
|
|
43158
|
-
} catch {
|
|
43159
|
-
userAvatarMap.set(secUid, "");
|
|
43160
|
-
}
|
|
43161
|
-
}));
|
|
43162
|
-
}
|
|
43163
|
-
return createSuccessResponse(res, caches.map((cache) => {
|
|
43164
|
-
const cacheWithUser = cache;
|
|
43165
|
-
const authorName = cacheWithUser.douyinUser?.remark || cacheWithUser.douyinUser?.short_id || cache.sec_uid;
|
|
43166
|
-
return {
|
|
43167
|
-
id: cache.aweme_id,
|
|
43168
|
-
platform: "douyin",
|
|
43169
|
-
title: `抖音作品 ${cache.aweme_id}`,
|
|
43170
|
-
author: authorName,
|
|
43171
|
-
authorId: cache.sec_uid,
|
|
43172
|
-
avatar: userAvatarMap.get(cache.sec_uid) || "",
|
|
43173
|
-
thumbnail: "",
|
|
43174
|
-
type: "video",
|
|
43175
|
-
createdAt: cache.createdAt.getTime()
|
|
43176
|
-
};
|
|
43177
|
-
}));
|
|
43178
|
-
} catch (error) {
|
|
43179
|
-
logger.error("[DouyinAPI] 获取内容列表失败:", error);
|
|
43180
|
-
return createServerErrorResponse(res, "获取内容列表失败");
|
|
43181
|
-
}
|
|
43182
|
-
};
|
|
43183
|
-
/**
|
|
43184
|
-
* 添加抖音内容
|
|
43185
|
-
* POST /api/v1/platforms/douyin/contents
|
|
43186
|
-
* Body: { contentId, groupId, authorId }
|
|
43187
|
-
*/
|
|
43188
|
-
var addContent = async (req, res) => {
|
|
43189
|
-
try {
|
|
43190
|
-
const { contentId, groupId, authorId } = req.body;
|
|
43191
|
-
if (!contentId || !groupId || !authorId) return createBadRequestResponse(res, "请提供 contentId、groupId 和 authorId");
|
|
43192
|
-
const douyinDB = await getDouyinDB();
|
|
43193
|
-
if (!await douyinDB.getDouyinUser(authorId)) return createBadRequestResponse(res, "该作者未在订阅列表中,请先添加订阅");
|
|
43194
|
-
await douyinDB.addAwemeCache(contentId, authorId, groupId);
|
|
43195
|
-
return createSuccessResponse(res, { message: "添加成功" });
|
|
43196
|
-
} catch (error) {
|
|
43197
|
-
logger.error("[DouyinAPI] 添加内容失败:", error);
|
|
43198
|
-
return createServerErrorResponse(res, "添加内容失败");
|
|
43199
|
-
}
|
|
43200
|
-
};
|
|
43201
|
-
/**
|
|
43202
|
-
* 删除抖音内容
|
|
43203
|
-
* POST /api/kkk/v1/platforms/douyin/contents/:id/delete
|
|
43204
|
-
* Body: { groupId: string }
|
|
43205
|
-
*/
|
|
43206
|
-
var deleteContent = async (req, res) => {
|
|
43207
|
-
try {
|
|
43208
|
-
const { id } = req.params;
|
|
43209
|
-
const { groupId } = req.body;
|
|
43210
|
-
if (!id || Array.isArray(id) && !id[0] || !groupId) return createBadRequestResponse(res, "请提供内容ID和群组ID");
|
|
43211
|
-
const result = await (await getDouyinDB()).awemeCacheRepository.delete({
|
|
43212
|
-
aweme_id: Array.isArray(id) ? id[0] : id,
|
|
43213
|
-
groupId
|
|
43214
|
-
});
|
|
43215
|
-
if (result.affected === 0) return createBadRequestResponse(res, "未找到要删除的内容");
|
|
43216
|
-
return createSuccessResponse(res, {
|
|
43217
|
-
message: "删除成功",
|
|
43218
|
-
affected: result.affected
|
|
43219
|
-
});
|
|
43220
|
-
} catch (error) {
|
|
43221
|
-
logger.error("[DouyinAPI] 删除内容失败:", error);
|
|
43222
|
-
return createServerErrorResponse(res, "删除内容失败");
|
|
43223
|
-
}
|
|
43224
|
-
};
|
|
43225
|
-
//#endregion
|
|
43226
|
-
//#region src/platform/douyin/api/parse.ts
|
|
43227
|
-
/**
|
|
43228
|
-
* 格式化数字
|
|
43229
|
-
*/
|
|
43230
|
-
var formatCount = (count) => {
|
|
43231
|
-
if (count >= 1e8) return `${(count / 1e8).toFixed(1)}亿`;
|
|
43232
|
-
if (count >= 1e4) return `${(count / 1e4).toFixed(1)}万`;
|
|
43233
|
-
return count.toString();
|
|
43234
|
-
};
|
|
43235
|
-
/**
|
|
43236
|
-
* 格式化时长
|
|
43237
|
-
*/
|
|
43238
|
-
var formatDuration = (seconds) => {
|
|
43239
|
-
return `${Math.floor(seconds / 60)}:${Math.floor(seconds % 60).toString().padStart(2, "0")}`;
|
|
43240
|
-
};
|
|
43241
|
-
/**
|
|
43242
|
-
* 格式化时间戳
|
|
43243
|
-
*/
|
|
43244
|
-
var formatTimestamp = (timestamp) => {
|
|
43245
|
-
const date = /* @__PURE__ */ new Date(timestamp * 1e3);
|
|
43246
|
-
const diff = (/* @__PURE__ */ new Date()).getTime() - date.getTime();
|
|
43247
|
-
if (diff < 6e4) return "刚刚";
|
|
43248
|
-
if (diff < 36e5) return `${Math.floor(diff / 6e4)}分钟前`;
|
|
43249
|
-
if (diff < 864e5) return `${Math.floor(diff / 36e5)}小时前`;
|
|
43250
|
-
if (diff < 2592e6) return `${Math.floor(diff / 864e5)}天前`;
|
|
43251
|
-
return `${date.getMonth() + 1}月${date.getDate()}日`;
|
|
43252
|
-
};
|
|
43253
|
-
/**
|
|
43254
|
-
* 从标题中移除标签
|
|
43255
|
-
*/
|
|
43256
|
-
var removeTags = (title, tags) => {
|
|
43257
|
-
if (!title || !tags || tags.length === 0) return title;
|
|
43258
|
-
let cleanTitle = title;
|
|
43259
|
-
tags.forEach((tag) => {
|
|
43260
|
-
if (tag) {
|
|
43261
|
-
const hashtagPattern = new RegExp(`#${tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|$)`, "gi");
|
|
43262
|
-
cleanTitle = cleanTitle.replace(hashtagPattern, "");
|
|
43263
|
-
const atPattern = new RegExp(`@${tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|$)`, "gi");
|
|
43264
|
-
cleanTitle = cleanTitle.replace(atPattern, "");
|
|
43265
|
-
}
|
|
43266
|
-
});
|
|
43267
|
-
return cleanTitle.replace(/\s+/g, " ").trim();
|
|
43268
|
-
};
|
|
43269
|
-
/**
|
|
43270
|
-
* 处理评论中的表情
|
|
43271
|
-
*/
|
|
43272
|
-
var processCommentEmojis = (text, emojiData) => {
|
|
43273
|
-
if (!text || !emojiData?.emoji_list) return text;
|
|
43274
|
-
let processedText = text;
|
|
43275
|
-
processedText = processedText.replace(/\[([^\]]+)\]/g, (match, emojiName) => {
|
|
43276
|
-
const emojiInfo = emojiData.emoji_list.find((emoji) => emoji.display_name === `[${emojiName}]`);
|
|
43277
|
-
if (emojiInfo && emojiInfo.emoji_url?.url_list?.[0]) return `<img src="${emojiInfo.emoji_url.url_list[0]}" alt="${emojiName}" class="emoji" />`;
|
|
43278
|
-
return match;
|
|
43279
|
-
});
|
|
43280
|
-
return processedText.split(/(<img[^>]*>)/).map((part) => {
|
|
43281
|
-
if (part.startsWith("<img")) return part;
|
|
43282
|
-
if (part.trim()) return `<span>${part}</span>`;
|
|
43283
|
-
return part;
|
|
43284
|
-
}).join("");
|
|
43285
|
-
};
|
|
43286
|
-
/**
|
|
43287
|
-
* 解析评论数据
|
|
43288
|
-
*/
|
|
43289
|
-
var parseComments = (commentsData, emojiData) => {
|
|
43290
|
-
if (!commentsData || !Array.isArray(commentsData)) return [];
|
|
43291
|
-
return commentsData.map((comment, index) => {
|
|
43292
|
-
let processedText = comment.text || "";
|
|
43293
|
-
if (emojiData && comment.text) processedText = processCommentEmojis(comment.text, emojiData);
|
|
43294
|
-
return {
|
|
43295
|
-
id: comment.cid?.toString() || index.toString(),
|
|
43296
|
-
author: comment.user?.nickname || "匿名用户",
|
|
43297
|
-
avatar: comment.user?.avatar_thumb?.url_list?.[0] || "",
|
|
43298
|
-
content: processedText,
|
|
43299
|
-
images: comment.image_list?.map((img) => img.origin_url?.url_list?.[3]).filter(Boolean) || [],
|
|
43300
|
-
likes: comment.digg_count || 0,
|
|
43301
|
-
timestamp: formatTimestamp(comment.create_time || 0)
|
|
43302
|
-
};
|
|
43303
|
-
});
|
|
43304
|
-
};
|
|
43305
|
-
/**
|
|
43306
|
-
* 解析抖音作品
|
|
43307
|
-
* POST /api/v1/platforms/douyin/parse
|
|
43308
|
-
* Body: { aweme_id: string }
|
|
43309
|
-
*/
|
|
43310
|
-
var parseWork = async (req, res) => {
|
|
43311
|
-
try {
|
|
43312
|
-
const { aweme_id } = req.body;
|
|
43313
|
-
if (!aweme_id) return createBadRequestResponse(res, "请提供作品ID (aweme_id)");
|
|
43314
|
-
const [workResponse, commentsResponse, emojiResponse] = await Promise.all([
|
|
43315
|
-
douyinFetcher.parseWork({
|
|
43316
|
-
aweme_id,
|
|
43317
|
-
typeMode: "strict"
|
|
43318
|
-
}),
|
|
43319
|
-
douyinFetcher.fetchWorkComments({
|
|
43320
|
-
aweme_id,
|
|
43321
|
-
typeMode: "strict"
|
|
43322
|
-
}),
|
|
43323
|
-
douyinFetcher.fetchEmojiList({ typeMode: "strict" })
|
|
43324
|
-
]);
|
|
43325
|
-
const awemeDetail = workResponse.data.aweme_detail;
|
|
43326
|
-
const commentsData = commentsResponse.data.comments;
|
|
43327
|
-
const emojiData = emojiResponse.data;
|
|
43328
|
-
const isSlides = awemeDetail.is_slides === true && awemeDetail.images !== null;
|
|
43329
|
-
const isVideo = !awemeDetail.images && !isSlides;
|
|
43330
|
-
const workType = isSlides ? "slides" : isVideo ? "video" : "note";
|
|
43331
|
-
const comments = parseComments(commentsData, emojiData);
|
|
43332
|
-
const tags = Array.isArray(awemeDetail.text_extra) ? awemeDetail.text_extra.map((tag) => typeof tag === "string" ? tag : tag.hashtag_name).filter(Boolean) : [];
|
|
43333
|
-
const cleanTitle = removeTags(awemeDetail.desc || "无标题", tags);
|
|
43334
|
-
let slides;
|
|
43335
|
-
if (isSlides && awemeDetail.images) slides = awemeDetail.images.map((item) => {
|
|
43336
|
-
if (item.clip_type === 2) return {
|
|
43337
|
-
type: "image",
|
|
43338
|
-
url: item.url_list[2],
|
|
43339
|
-
thumbnail: item.url_list[2]
|
|
43340
|
-
};
|
|
43341
|
-
else if (item.clip_type === 3) {
|
|
43342
|
-
const videoUri = item.video?.play_addr_h264?.uri;
|
|
43343
|
-
const videoUrl = videoUri ? `https://aweme.snssdk.com/aweme/v1/play/?video_id=${videoUri}&ratio=1080p&line=0` : item.video?.play_addr_h264?.url_list?.[0];
|
|
43344
|
-
return {
|
|
43345
|
-
type: "livephoto",
|
|
43346
|
-
url: item.url_list[2],
|
|
43347
|
-
videoUrl,
|
|
43348
|
-
thumbnail: item.url_list[2],
|
|
43349
|
-
duration: formatDuration(item.video?.duration / 1e3 || 0)
|
|
43350
|
-
};
|
|
43351
|
-
} else return {
|
|
43352
|
-
type: "video",
|
|
43353
|
-
url: item.video.play_addr_h264.url_list[0],
|
|
43354
|
-
thumbnail: item.url_list[2],
|
|
43355
|
-
duration: formatDuration(item.video?.duration / 1e3 || 0)
|
|
43356
|
-
};
|
|
43357
|
-
});
|
|
43358
|
-
return createSuccessResponse(res, {
|
|
43359
|
-
id: awemeDetail.aweme_id,
|
|
43360
|
-
title: cleanTitle,
|
|
43361
|
-
description: awemeDetail.desc || "",
|
|
43362
|
-
thumbnail: isVideo ? awemeDetail.video?.cover?.url_list?.[0] : awemeDetail.images?.[0]?.url_list?.[0] || "",
|
|
43363
|
-
duration: isVideo ? formatDuration(awemeDetail.video?.duration / 1e3 || 0) : "0:00",
|
|
43364
|
-
views: formatCount(awemeDetail.statistics?.play_count || 0),
|
|
43365
|
-
likes: formatCount(awemeDetail.statistics?.digg_count || 0),
|
|
43366
|
-
author: {
|
|
43367
|
-
name: awemeDetail.author?.nickname || "未知用户",
|
|
43368
|
-
avatar: awemeDetail.author?.avatar_thumb?.url_list?.[0] || "",
|
|
43369
|
-
id: awemeDetail.author?.sec_uid || ""
|
|
43370
|
-
},
|
|
43371
|
-
type: workType,
|
|
43372
|
-
downloadUrl: {
|
|
43373
|
-
video: isVideo ? awemeDetail.video?.play_addr?.url_list?.[0] : isSlides && slides ? slides.find((slide) => slide.type === "video")?.url : void 0,
|
|
43374
|
-
audio: isSlides && awemeDetail.video ? awemeDetail.video?.play_addr?.url_list?.[0] : awemeDetail.music?.play_url?.uri
|
|
43375
|
-
},
|
|
43376
|
-
images: workType === "note" ? awemeDetail.images?.map((img) => img.url_list?.[2]).filter(Boolean) : void 0,
|
|
43377
|
-
slides,
|
|
43378
|
-
tags,
|
|
43379
|
-
comments,
|
|
43380
|
-
commentCount: awemeDetail.statistics?.comment_count || 0
|
|
43381
|
-
});
|
|
43382
|
-
} catch (error) {
|
|
43383
|
-
logger.error("[DouyinAPI] 解析作品失败:", error);
|
|
43384
|
-
return createServerErrorResponse(res, `解析失败: ${error.message}`);
|
|
43385
|
-
}
|
|
43386
|
-
};
|
|
43387
|
-
//#endregion
|
|
43388
|
-
//#region src/platform/douyin/api/index.ts
|
|
43389
|
-
/**
|
|
43390
|
-
* 抖音平台 API 路由
|
|
43391
|
-
*/
|
|
43392
|
-
var router = express.Router();
|
|
43393
|
-
router.get("/contents", getContents);
|
|
43394
|
-
router.post("/contents", addContent);
|
|
43395
|
-
router.post("/contents/:id/delete", deleteContent);
|
|
43396
|
-
router.post("/parse", parseWork);
|
|
43397
|
-
//#endregion
|
|
43398
|
-
//#region src/module/server/auth.ts
|
|
43399
|
-
/**
|
|
43400
|
-
* Base64解码
|
|
43401
|
-
* @param str Base64编码的字符串
|
|
43402
|
-
* @returns 解码后的字符串
|
|
43403
|
-
*/
|
|
43404
|
-
var base64Decode = (str) => {
|
|
43405
|
-
return Buffer.from(str, "base64").toString("utf8");
|
|
43406
|
-
};
|
|
43407
|
-
/**
|
|
43408
|
-
* URL解码
|
|
43409
|
-
* @param str URL编码的字符串
|
|
43410
|
-
* @returns 解码后的字符串
|
|
43411
|
-
*/
|
|
43412
|
-
var urlDecode = (str) => {
|
|
43413
|
-
return decodeURIComponent(str);
|
|
43414
|
-
};
|
|
43415
|
-
/**
|
|
43416
|
-
* 十六进制解码
|
|
43417
|
-
* @param str 十六进制编码的字符串
|
|
43418
|
-
* @returns 解码后的字符串
|
|
43419
|
-
*/
|
|
43420
|
-
var hexDecode = (str) => {
|
|
43421
|
-
return Buffer.from(str, "hex").toString("utf8");
|
|
43422
|
-
};
|
|
43423
|
-
/**
|
|
43424
|
-
* 反转字符串
|
|
43425
|
-
* @param str 待反转字符串
|
|
43426
|
-
* @returns 反转后的字符串
|
|
43427
|
-
*/
|
|
43428
|
-
var reverseString = (str) => {
|
|
43429
|
-
return str.split("").reverse().join("");
|
|
43430
|
-
};
|
|
43431
|
-
/**
|
|
43432
|
-
* 字符偏移解码
|
|
43433
|
-
* @param str 编码的字符串
|
|
43434
|
-
* @param offset 偏移量
|
|
43435
|
-
* @returns 解码后的字符串
|
|
43436
|
-
*/
|
|
43437
|
-
var charOffsetDecode = (str, offset = 5) => {
|
|
43438
|
-
return str.split("").map((char) => {
|
|
43439
|
-
const code = char.charCodeAt(0);
|
|
43440
|
-
return String.fromCharCode(code - offset);
|
|
43441
|
-
}).join("");
|
|
43442
|
-
};
|
|
43443
|
-
/**
|
|
43444
|
-
* 多层解码解密
|
|
43445
|
-
* @param str 多层编码的字符串
|
|
43446
|
-
* @returns 解码后的原始字符串
|
|
43447
|
-
*/
|
|
43448
|
-
var multiLayerDecode = (str) => {
|
|
43449
|
-
try {
|
|
43450
|
-
let decoded = base64Decode(str);
|
|
43451
|
-
decoded = urlDecode(decoded);
|
|
43452
|
-
decoded = base64Decode(decoded);
|
|
43453
|
-
decoded = reverseString(decoded);
|
|
43454
|
-
decoded = hexDecode(decoded);
|
|
43455
|
-
decoded = charOffsetDecode(decoded, 5);
|
|
43456
|
-
return decoded;
|
|
43457
|
-
} catch (error) {
|
|
43458
|
-
throw new Error("多层解码失败:" + error);
|
|
43459
|
-
}
|
|
43460
|
-
};
|
|
43461
|
-
/**
|
|
43462
|
-
* HMAC-SHA256签名验证中间件
|
|
43463
|
-
* @param req 请求对象
|
|
43464
|
-
* @param res 响应对象
|
|
43465
|
-
* @param next 下一个中间件函数
|
|
43466
|
-
*/
|
|
43467
|
-
var signatureVerificationMiddleware = (req, res, next) => {
|
|
43468
|
-
try {
|
|
43469
|
-
const encodedSignature = req.headers["x-signature"];
|
|
43470
|
-
const timestamp = req.headers["x-timestamp"];
|
|
43471
|
-
const nonce = req.headers["x-nonce"];
|
|
43472
|
-
const token = req.headers["authorization"]?.replace("Bearer ", "") || "";
|
|
43473
|
-
if (!encodedSignature || !timestamp || !nonce) return createBadRequestResponse(res, "缺少必要的签名参数");
|
|
43474
|
-
if (Math.abs(Date.now() - parseInt(timestamp)) > 300 * 1e3) return createBadRequestResponse(res, "请求时间戳已过期");
|
|
43475
|
-
let decodedSignature;
|
|
43476
|
-
try {
|
|
43477
|
-
decodedSignature = multiLayerDecode(encodedSignature);
|
|
43478
|
-
} catch (error) {
|
|
43479
|
-
return createBadRequestResponse(res, "签名格式错误:" + error);
|
|
43480
|
-
}
|
|
43481
|
-
const signatureString = `${req.method.toUpperCase()}|${req.headers["x-original-url"] || req.originalUrl}|${req.method === "GET" ? "" : JSON.stringify(req.body || {})}|${timestamp}|${nonce}`;
|
|
43482
|
-
const expectedSignature = crypto.createHmac("sha256", token).update(signatureString).digest("hex");
|
|
43483
|
-
if (decodedSignature !== expectedSignature) {
|
|
43484
|
-
logger.warn(`签名验证失败: 期望=${expectedSignature}, 解码后实际=${decodedSignature}, 签名字符串=${signatureString}`);
|
|
43485
|
-
return createBadRequestResponse(res, "签名验证失败");
|
|
43486
|
-
}
|
|
43487
|
-
next();
|
|
43488
|
-
} catch (error) {
|
|
43489
|
-
logger.error("签名验证中间件错误:", error);
|
|
43490
|
-
return createServerErrorResponse(res, "签名验证失败");
|
|
43491
|
-
}
|
|
42429
|
+
//#region src/module/server/constants/routes.ts
|
|
42430
|
+
/**
|
|
42431
|
+
* 路由常量定义
|
|
42432
|
+
*/
|
|
42433
|
+
var KKK_PREFIX = "/kkk";
|
|
42434
|
+
/** 静态文件 */
|
|
42435
|
+
var ASSETS_PREFIX = "/assets";
|
|
42436
|
+
/** SSR 页面 */
|
|
42437
|
+
var SSR_PREFIX = "/ssr";
|
|
42438
|
+
`${KKK_PREFIX}${ASSETS_PREFIX}`;
|
|
42439
|
+
`${KKK_PREFIX}${SSR_PREFIX}`;
|
|
42440
|
+
`${KKK_PREFIX}`;
|
|
42441
|
+
var ROUTES = {
|
|
42442
|
+
/** 获取所有 Bot */
|
|
42443
|
+
BOTS: "/bots",
|
|
42444
|
+
/** 获取指定 Bot 信息 */
|
|
42445
|
+
BOT_INFO: "/bots/:botId",
|
|
42446
|
+
/** 获取指定 Bot 的群列表 */
|
|
42447
|
+
BOT_GROUPS: "/bots/:botId/groups",
|
|
42448
|
+
/** 获取指定 Bot 群信息 */
|
|
42449
|
+
BOT_GROUP_INFO: "/bots/:botId/groups/:groupId",
|
|
42450
|
+
/** 获取所有群组 */
|
|
42451
|
+
GROUPS_BATCH: "/groups/batch",
|
|
42452
|
+
/** 获取插件所有配置 */
|
|
42453
|
+
CONFIG: "/config",
|
|
42454
|
+
/** 获取视频流 */
|
|
42455
|
+
VIDEO_STREAM: "/stream/:filename",
|
|
42456
|
+
/** 获取视频事件 */
|
|
42457
|
+
VIDEO_EVENTS: "/video/:filename/events",
|
|
42458
|
+
/** 视频播放页面 */
|
|
42459
|
+
VIDEO_PAGE: "/video/:filename"
|
|
43492
42460
|
};
|
|
43493
42461
|
//#endregion
|
|
43494
|
-
//#region src/module/server/
|
|
42462
|
+
//#region src/module/server/controllers/bots.ts
|
|
43495
42463
|
/**
|
|
43496
42464
|
* Bot 管理 API
|
|
43497
42465
|
*/
|
|
@@ -43543,7 +42511,7 @@ var getBots = async (_req, res) => {
|
|
|
43543
42511
|
*/
|
|
43544
42512
|
var getBotInfo = async (req, res) => {
|
|
43545
42513
|
try {
|
|
43546
|
-
const
|
|
42514
|
+
const botId = Array.isArray(req.params.botId) ? req.params.botId[0] : req.params.botId;
|
|
43547
42515
|
if (!botId) return createServerErrorResponse(res, "缺少 botId 参数");
|
|
43548
42516
|
const bot = getOnlineBotById(botId);
|
|
43549
42517
|
if (!bot || bot.account.name === "console") return createServerErrorResponse(res, "Bot 不存在或不在线");
|
|
@@ -43564,7 +42532,7 @@ var getBotInfo = async (req, res) => {
|
|
|
43564
42532
|
*/
|
|
43565
42533
|
var getBotGroups = async (req, res) => {
|
|
43566
42534
|
try {
|
|
43567
|
-
const
|
|
42535
|
+
const botId = Array.isArray(req.params.botId) ? req.params.botId[0] : req.params.botId;
|
|
43568
42536
|
if (!botId) return createServerErrorResponse(res, "缺少 botId 参数");
|
|
43569
42537
|
const botItem = karin.getAllBotList().find((item) => item.bot.account.selfId === botId);
|
|
43570
42538
|
if (!botItem) return createServerErrorResponse(res, "Bot 不存在或不在线");
|
|
@@ -43594,7 +42562,8 @@ var getBotGroups = async (req, res) => {
|
|
|
43594
42562
|
*/
|
|
43595
42563
|
var getBotGroupInfo = async (req, res) => {
|
|
43596
42564
|
try {
|
|
43597
|
-
const
|
|
42565
|
+
const botId = Array.isArray(req.params.botId) ? req.params.botId[0] : req.params.botId;
|
|
42566
|
+
const groupId = Array.isArray(req.params.groupId) ? req.params.groupId[0] : req.params.groupId;
|
|
43598
42567
|
if (!botId || !groupId) return createServerErrorResponse(res, "缺少 botId 或 groupId 参数");
|
|
43599
42568
|
const bot = getOnlineBotById(botId);
|
|
43600
42569
|
if (!bot) return createServerErrorResponse(res, "Bot 不存在或不在线");
|
|
@@ -43679,141 +42648,31 @@ var getGroupsBatch = async (req, res) => {
|
|
|
43679
42648
|
}
|
|
43680
42649
|
};
|
|
43681
42650
|
//#endregion
|
|
43682
|
-
//#region src/module/server/
|
|
42651
|
+
//#region src/module/server/controllers/config.ts
|
|
43683
42652
|
/**
|
|
43684
|
-
*
|
|
43685
|
-
*
|
|
42653
|
+
* 配置管理 API
|
|
42654
|
+
* 提供配置的增删改查接口,供 APP 端使用
|
|
43686
42655
|
*/
|
|
43687
|
-
var getAllConfig = async (_req, res) => {
|
|
43688
|
-
try {
|
|
43689
|
-
const config = await Config.All();
|
|
43690
|
-
res.json({
|
|
43691
|
-
success: true,
|
|
43692
|
-
message: "获取配置成功",
|
|
43693
|
-
data: config
|
|
43694
|
-
});
|
|
43695
|
-
} catch (error) {
|
|
43696
|
-
res.status(500).json({
|
|
43697
|
-
success: false,
|
|
43698
|
-
message: `获取配置失败: ${error.message}`,
|
|
43699
|
-
data: null
|
|
43700
|
-
});
|
|
43701
|
-
}
|
|
43702
|
-
};
|
|
43703
|
-
/**
|
|
43704
|
-
* 获取指定配置模块
|
|
43705
|
-
* GET /api/kkk/v1/config/:module
|
|
43706
|
-
*/
|
|
43707
|
-
var getConfigModule = async (req, res) => {
|
|
43708
|
-
try {
|
|
43709
|
-
const { module } = req.params;
|
|
43710
|
-
const allConfig = await Config.All();
|
|
43711
|
-
if (!(module in allConfig)) return res.status(400).json({
|
|
43712
|
-
success: false,
|
|
43713
|
-
message: `配置模块 "${module}" 不存在`,
|
|
43714
|
-
data: null
|
|
43715
|
-
});
|
|
43716
|
-
res.json({
|
|
43717
|
-
success: true,
|
|
43718
|
-
message: "获取配置成功",
|
|
43719
|
-
data: allConfig[module]
|
|
43720
|
-
});
|
|
43721
|
-
} catch (error) {
|
|
43722
|
-
res.status(500).json({
|
|
43723
|
-
success: false,
|
|
43724
|
-
message: `获取配置失败: ${error.message}`,
|
|
43725
|
-
data: null
|
|
43726
|
-
});
|
|
43727
|
-
}
|
|
43728
|
-
};
|
|
43729
42656
|
/**
|
|
43730
|
-
*
|
|
43731
|
-
*
|
|
43732
|
-
*/
|
|
43733
|
-
var updateConfigModule = async (req, res) => {
|
|
43734
|
-
try {
|
|
43735
|
-
const { module } = req.params;
|
|
43736
|
-
const newConfig = req.body?.config || req.body;
|
|
43737
|
-
if (!newConfig || typeof newConfig !== "object") return res.status(400).json({
|
|
43738
|
-
success: false,
|
|
43739
|
-
message: "请求体必须是有效的配置对象",
|
|
43740
|
-
data: null
|
|
43741
|
-
});
|
|
43742
|
-
if ("_method" in newConfig) delete newConfig._method;
|
|
43743
|
-
if (!(module in await Config.All())) return res.status(400).json({
|
|
43744
|
-
success: false,
|
|
43745
|
-
message: `配置模块 "${module}" 不存在`,
|
|
43746
|
-
data: null
|
|
43747
|
-
});
|
|
43748
|
-
if (await Config.ModifyPro(module, newConfig)) {
|
|
43749
|
-
if (module === "pushlist") await Config.syncConfigToDatabase();
|
|
43750
|
-
if (module === "cookies" || module === "request") reloadAmagiConfig();
|
|
43751
|
-
const updatedConfig = await Config.All();
|
|
43752
|
-
res.json({
|
|
43753
|
-
success: true,
|
|
43754
|
-
message: "配置更新成功",
|
|
43755
|
-
data: updatedConfig[module]
|
|
43756
|
-
});
|
|
43757
|
-
} else res.status(500).json({
|
|
43758
|
-
success: false,
|
|
43759
|
-
message: "配置更新失败",
|
|
43760
|
-
data: null
|
|
43761
|
-
});
|
|
43762
|
-
} catch (error) {
|
|
43763
|
-
res.status(500).json({
|
|
43764
|
-
success: false,
|
|
43765
|
-
message: `配置更新失败: ${error.message}`,
|
|
43766
|
-
data: null
|
|
43767
|
-
});
|
|
43768
|
-
}
|
|
43769
|
-
};
|
|
43770
|
-
/**
|
|
43771
|
-
* 更新单个配置项
|
|
43772
|
-
* PATCH /api/kkk/v1/config/:module
|
|
43773
|
-
* Body: { key: string, value: any }
|
|
42657
|
+
* 获取所有配置
|
|
42658
|
+
* GET /kkk/web/v1/config
|
|
43774
42659
|
*/
|
|
43775
|
-
var
|
|
42660
|
+
var getAllConfig = async (_req, res) => {
|
|
43776
42661
|
try {
|
|
43777
|
-
|
|
43778
|
-
const { key, value } = req.body;
|
|
43779
|
-
if (!key) return res.status(400).json({
|
|
43780
|
-
success: false,
|
|
43781
|
-
message: "缺少配置项 key",
|
|
43782
|
-
data: null
|
|
43783
|
-
});
|
|
43784
|
-
if (!(module in await Config.All())) return res.status(400).json({
|
|
43785
|
-
success: false,
|
|
43786
|
-
message: `配置模块 "${module}" 不存在`,
|
|
43787
|
-
data: null
|
|
43788
|
-
});
|
|
43789
|
-
Config.Modify(module, key, value);
|
|
43790
|
-
const updatedConfig = await Config.All();
|
|
43791
|
-
res.json({
|
|
43792
|
-
success: true,
|
|
43793
|
-
message: "配置项更新成功",
|
|
43794
|
-
data: updatedConfig[module]
|
|
43795
|
-
});
|
|
42662
|
+
return createSuccessResponse(res, await Config.All());
|
|
43796
42663
|
} catch (error) {
|
|
43797
|
-
res.
|
|
43798
|
-
success: false,
|
|
43799
|
-
message: `配置项更新失败: ${error.message}`,
|
|
43800
|
-
data: null
|
|
43801
|
-
});
|
|
42664
|
+
return createServerErrorResponse(res, `获取配置失败: ${error.message}`);
|
|
43802
42665
|
}
|
|
43803
42666
|
};
|
|
43804
42667
|
/**
|
|
43805
42668
|
* 批量更新配置
|
|
43806
|
-
*
|
|
42669
|
+
* POST /kkk/web/v1/config
|
|
43807
42670
|
* Body: Partial<ConfigType>
|
|
43808
42671
|
*/
|
|
43809
42672
|
var updateAllConfig = async (req, res) => {
|
|
43810
42673
|
try {
|
|
43811
42674
|
const newConfig = req.body;
|
|
43812
|
-
if (!newConfig || typeof newConfig !== "object") return res
|
|
43813
|
-
success: false,
|
|
43814
|
-
message: "请求体必须是有效的配置对象",
|
|
43815
|
-
data: null
|
|
43816
|
-
});
|
|
42675
|
+
if (!newConfig || typeof newConfig !== "object") return createBadRequestResponse(res, "请求体必须是有效的配置对象");
|
|
43817
42676
|
const oldConfig = await Config.All();
|
|
43818
42677
|
const results = [];
|
|
43819
42678
|
let needReloadAmagi = false;
|
|
@@ -43832,7 +42691,7 @@ var updateAllConfig = async (req, res) => {
|
|
|
43832
42691
|
module,
|
|
43833
42692
|
success
|
|
43834
42693
|
});
|
|
43835
|
-
if (success &&
|
|
42694
|
+
if (success && moduleName === "amagi") needReloadAmagi = true;
|
|
43836
42695
|
} catch (error) {
|
|
43837
42696
|
results.push({
|
|
43838
42697
|
module,
|
|
@@ -43843,211 +42702,23 @@ var updateAllConfig = async (req, res) => {
|
|
|
43843
42702
|
if ("pushlist" in newConfig) await Config.syncConfigToDatabase();
|
|
43844
42703
|
if (needReloadAmagi) reloadAmagiConfig();
|
|
43845
42704
|
const allSuccess = results.every((r) => r.success);
|
|
43846
|
-
const updatedConfig = await Config.All();
|
|
43847
|
-
res.json({
|
|
43848
|
-
success: allSuccess,
|
|
43849
|
-
message: allSuccess ? "所有配置更新成功" : "部分配置更新失败",
|
|
43850
|
-
data: {
|
|
43851
|
-
config: updatedConfig,
|
|
43852
|
-
results
|
|
43853
|
-
}
|
|
43854
|
-
});
|
|
43855
|
-
} catch (error) {
|
|
43856
|
-
res.status(500).json({
|
|
43857
|
-
success: false,
|
|
43858
|
-
message: `配置更新失败: ${error.message}`,
|
|
43859
|
-
data: null
|
|
43860
|
-
});
|
|
43861
|
-
}
|
|
43862
|
-
};
|
|
43863
|
-
//#endregion
|
|
43864
|
-
//#region src/module/server/api/groups.ts
|
|
43865
|
-
/**
|
|
43866
|
-
* 群组管理 API
|
|
43867
|
-
*/
|
|
43868
|
-
/**
|
|
43869
|
-
* 获取所有已订阅推送功能的群组列表
|
|
43870
|
-
* GET /api/v1/groups
|
|
43871
|
-
*/
|
|
43872
|
-
var getGroups = async (_req, res) => {
|
|
43873
|
-
try {
|
|
43874
|
-
const douyinDB = await getDouyinDB();
|
|
43875
|
-
const bilibiliDB = await getBilibiliDB();
|
|
43876
|
-
const [douyinGroups, bilibiliGroups] = await Promise.all([douyinDB.groupRepository.find(), bilibiliDB.groupRepository.find()]);
|
|
43877
|
-
const allGroupsMap = /* @__PURE__ */ new Map();
|
|
43878
|
-
douyinGroups.forEach((group) => {
|
|
43879
|
-
allGroupsMap.set(group.id, {
|
|
43880
|
-
id: group.id,
|
|
43881
|
-
botId: group.botId
|
|
43882
|
-
});
|
|
43883
|
-
});
|
|
43884
|
-
bilibiliGroups.forEach((group) => {
|
|
43885
|
-
if (!allGroupsMap.has(group.id)) allGroupsMap.set(group.id, {
|
|
43886
|
-
id: group.id,
|
|
43887
|
-
botId: group.botId
|
|
43888
|
-
});
|
|
43889
|
-
});
|
|
43890
|
-
const groupList = [];
|
|
43891
|
-
for (const group of allGroupsMap.values()) {
|
|
43892
|
-
const [douyinSubscriptions, bilibiliSubscriptions] = await Promise.all([douyinDB.getGroupSubscriptions(group.id), bilibiliDB.getGroupSubscriptions(group.id)]);
|
|
43893
|
-
if (douyinSubscriptions.length > 0 || bilibiliSubscriptions.length > 0) {
|
|
43894
|
-
const bot = getBot(group.botId);
|
|
43895
|
-
let groupName = group.id;
|
|
43896
|
-
let groupAvatarUrl = "";
|
|
43897
|
-
let botAvatarUrl = "";
|
|
43898
|
-
let isOnline = true;
|
|
43899
|
-
if (!bot) isOnline = false;
|
|
43900
|
-
else try {
|
|
43901
|
-
const groupInfo = await bot.getGroupInfo(group.id);
|
|
43902
|
-
if (groupInfo) groupName = groupInfo.groupName || groupName;
|
|
43903
|
-
groupAvatarUrl = await bot.getGroupAvatarUrl(group.id) || "";
|
|
43904
|
-
botAvatarUrl = await bot.getAvatarUrl(group.botId) || "";
|
|
43905
|
-
} catch (e) {
|
|
43906
|
-
logger.warn(`[GroupsAPI] 获取群组信息失败 ${group.id}:`, e);
|
|
43907
|
-
}
|
|
43908
|
-
groupList.push({
|
|
43909
|
-
id: group.id,
|
|
43910
|
-
name: groupName,
|
|
43911
|
-
avatar: groupAvatarUrl,
|
|
43912
|
-
botId: group.botId,
|
|
43913
|
-
botAvatar: botAvatarUrl,
|
|
43914
|
-
isOnline,
|
|
43915
|
-
subscriptionCount: {
|
|
43916
|
-
douyin: douyinSubscriptions.length,
|
|
43917
|
-
bilibili: bilibiliSubscriptions.length
|
|
43918
|
-
}
|
|
43919
|
-
});
|
|
43920
|
-
}
|
|
43921
|
-
}
|
|
43922
|
-
return createSuccessResponse(res, groupList);
|
|
43923
|
-
} catch (error) {
|
|
43924
|
-
logger.error("[GroupsAPI] 获取群组列表失败:", error);
|
|
43925
|
-
return createServerErrorResponse(res, "获取群组列表失败");
|
|
43926
|
-
}
|
|
43927
|
-
};
|
|
43928
|
-
//#endregion
|
|
43929
|
-
//#region src/module/server/api/link.ts
|
|
43930
|
-
/**
|
|
43931
|
-
* 链接解析 API
|
|
43932
|
-
*/
|
|
43933
|
-
/**
|
|
43934
|
-
* 识别平台类型
|
|
43935
|
-
*/
|
|
43936
|
-
var detectPlatform = (url) => {
|
|
43937
|
-
if (url.includes("douyin.com") || url.includes("iesdouyin.com") || url.includes("webcast.amemv.com") || url.includes("live.douyin.com")) return "douyin";
|
|
43938
|
-
if (url.includes("bilibili.com") || url.includes("b23.tv")) return "bilibili";
|
|
43939
|
-
return "unknown";
|
|
43940
|
-
};
|
|
43941
|
-
/**
|
|
43942
|
-
* 从 URL 中提取作品 ID
|
|
43943
|
-
*/
|
|
43944
|
-
var extractWorkId = (url, platform) => {
|
|
43945
|
-
if (platform === "douyin") {
|
|
43946
|
-
const videoMatch = /video\/(\d+)/.exec(url);
|
|
43947
|
-
if (videoMatch) return {
|
|
43948
|
-
type: "video",
|
|
43949
|
-
id: videoMatch[1]
|
|
43950
|
-
};
|
|
43951
|
-
const noteMatch = /note\/(\d+)/.exec(url);
|
|
43952
|
-
if (noteMatch) return {
|
|
43953
|
-
type: "note",
|
|
43954
|
-
id: noteMatch[1]
|
|
43955
|
-
};
|
|
43956
|
-
const modalMatch = /modal_id=(\d+)/.exec(url);
|
|
43957
|
-
if (modalMatch) return {
|
|
43958
|
-
type: "video",
|
|
43959
|
-
id: modalMatch[1]
|
|
43960
|
-
};
|
|
43961
|
-
}
|
|
43962
|
-
if (platform === "bilibili") {
|
|
43963
|
-
const bvidMatch = /\/video\/(BV[a-zA-Z0-9]+)/.exec(url);
|
|
43964
|
-
if (bvidMatch) return {
|
|
43965
|
-
type: "video",
|
|
43966
|
-
id: bvidMatch[1]
|
|
43967
|
-
};
|
|
43968
|
-
const aidMatch = /\/video\/av(\d+)/.exec(url);
|
|
43969
|
-
if (aidMatch) return {
|
|
43970
|
-
type: "video",
|
|
43971
|
-
id: aidMatch[1]
|
|
43972
|
-
};
|
|
43973
|
-
const tMatch = /^https:\/\/t\.bilibili\.com\/(\d+)/.exec(url);
|
|
43974
|
-
if (tMatch) return {
|
|
43975
|
-
type: "dynamic",
|
|
43976
|
-
id: tMatch[1]
|
|
43977
|
-
};
|
|
43978
|
-
const opusMatch = /\/opus\/(\d+)/.exec(url);
|
|
43979
|
-
if (opusMatch) return {
|
|
43980
|
-
type: "dynamic",
|
|
43981
|
-
id: opusMatch[1]
|
|
43982
|
-
};
|
|
43983
|
-
}
|
|
43984
|
-
return null;
|
|
43985
|
-
};
|
|
43986
|
-
/**
|
|
43987
|
-
* 解析短链接并获取最终 URL
|
|
43988
|
-
* POST /api/v1/link/resolve
|
|
43989
|
-
* Body: { link: string }
|
|
43990
|
-
*/
|
|
43991
|
-
var resolveLink = async (req, res) => {
|
|
43992
|
-
try {
|
|
43993
|
-
const { link } = req.body;
|
|
43994
|
-
if (!link || typeof link !== "string") return createBadRequestResponse(res, "请提供有效的链接");
|
|
43995
|
-
const finalUrl = (await axios.get(link, {
|
|
43996
|
-
headers: { "User-Agent": "Apifox/1.0.0 (https://apifox.com)" },
|
|
43997
|
-
maxRedirects: 10,
|
|
43998
|
-
validateStatus: () => true
|
|
43999
|
-
})).request.res?.responseUrl || link;
|
|
44000
|
-
if (finalUrl.includes("403 Forbidden")) return createServerErrorResponse(res, "无法获取链接的重定向地址");
|
|
44001
|
-
const platform = detectPlatform(finalUrl);
|
|
44002
|
-
const workInfo = extractWorkId(finalUrl, platform);
|
|
44003
|
-
logger.debug(`[LinkAPI] 链接解析: ${link} -> ${platform} (${workInfo?.type}: ${workInfo?.id})`);
|
|
44004
42705
|
return createSuccessResponse(res, {
|
|
44005
|
-
|
|
44006
|
-
|
|
44007
|
-
|
|
44008
|
-
workType: workInfo?.type || null,
|
|
44009
|
-
workId: workInfo?.id || null
|
|
44010
|
-
});
|
|
42706
|
+
config: await Config.All(),
|
|
42707
|
+
results
|
|
42708
|
+
}, allSuccess ? "所有配置更新成功" : "部分配置更新失败");
|
|
44011
42709
|
} catch (error) {
|
|
44012
|
-
|
|
44013
|
-
return createServerErrorResponse(res, `链接解析失败: ${error.message}`);
|
|
42710
|
+
return createServerErrorResponse(res, `配置更新失败: ${error.message}`);
|
|
44014
42711
|
}
|
|
44015
42712
|
};
|
|
44016
42713
|
//#endregion
|
|
44017
|
-
//#region src/module/server/
|
|
44018
|
-
/**
|
|
44019
|
-
* API 路由聚合
|
|
44020
|
-
*/
|
|
44021
|
-
var apiRouter = express.Router();
|
|
44022
|
-
var authMiddlewares = [authMiddleware, signatureVerificationMiddleware];
|
|
44023
|
-
apiRouter.get("/bots", ...authMiddlewares, getBots);
|
|
44024
|
-
apiRouter.get("/bots/:botId", ...authMiddlewares, getBotInfo);
|
|
44025
|
-
apiRouter.get("/bots/:botId/groups", ...authMiddlewares, getBotGroups);
|
|
44026
|
-
apiRouter.get("/bots/:botId/groups/:groupId", ...authMiddlewares, getBotGroupInfo);
|
|
44027
|
-
apiRouter.get("/groups", ...authMiddlewares, getGroups);
|
|
44028
|
-
apiRouter.post("/groups/batch", ...authMiddlewares, getGroupsBatch);
|
|
44029
|
-
apiRouter.post("/link/resolve", ...authMiddlewares, resolveLink);
|
|
44030
|
-
apiRouter.get("/config", ...authMiddlewares, getAllConfig);
|
|
44031
|
-
apiRouter.put("/config", ...authMiddlewares, updateAllConfig);
|
|
44032
|
-
apiRouter.post("/config", ...authMiddlewares, updateAllConfig);
|
|
44033
|
-
apiRouter.get("/config/:module", ...authMiddlewares, getConfigModule);
|
|
44034
|
-
apiRouter.put("/config/:module", ...authMiddlewares, updateConfigModule);
|
|
44035
|
-
apiRouter.post("/config/:module", ...authMiddlewares, updateConfigModule);
|
|
44036
|
-
apiRouter.patch("/config/:module", ...authMiddlewares, patchConfigItem);
|
|
44037
|
-
apiRouter.use("/platforms/douyin", ...authMiddlewares, router);
|
|
44038
|
-
apiRouter.use("/platforms/bilibili", ...authMiddlewares, router$1);
|
|
44039
|
-
//#endregion
|
|
44040
|
-
//#region src/module/server/router.ts
|
|
42714
|
+
//#region src/module/server/controllers/video.ts
|
|
44041
42715
|
/**
|
|
44042
|
-
*
|
|
42716
|
+
* 视频控制器
|
|
44043
42717
|
*/
|
|
44044
42718
|
/**
|
|
44045
42719
|
* 视频文件流传输
|
|
44046
|
-
* GET /api/kkk/stream/:filename
|
|
44047
|
-
* @param req 请求对象。
|
|
44048
|
-
* @param res 响应对象。
|
|
44049
42720
|
*/
|
|
44050
|
-
var
|
|
42721
|
+
var getVideoStream = (req, res) => {
|
|
44051
42722
|
const filenameParam = req.params.filename;
|
|
44052
42723
|
const filename = Array.isArray(filenameParam) ? filenameParam[0] : filenameParam;
|
|
44053
42724
|
if (!filename) {
|
|
@@ -44064,7 +42735,7 @@ var videoStreamRouter = (req, res) => {
|
|
|
44064
42735
|
const start = parseInt(parts[0], 10);
|
|
44065
42736
|
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
|
|
44066
42737
|
if (start >= fileSize || end >= fileSize || start > end) {
|
|
44067
|
-
res.status(416).send("
|
|
42738
|
+
res.status(416).send("请求范围不满足");
|
|
44068
42739
|
return;
|
|
44069
42740
|
}
|
|
44070
42741
|
const chunksize = end - start + 1;
|
|
@@ -44096,7 +42767,7 @@ var videoStreamRouter = (req, res) => {
|
|
|
44096
42767
|
file.on("error", (err) => {
|
|
44097
42768
|
logger.error(`读取视频文件流时出错 (Full): ${err.message}`);
|
|
44098
42769
|
if (!res.headersSent) try {
|
|
44099
|
-
createNotFoundResponse(res, "
|
|
42770
|
+
createNotFoundResponse(res, "读取视频文件失败");
|
|
44100
42771
|
} catch (e) {
|
|
44101
42772
|
logger.error("发送读取错误响应失败:", e);
|
|
44102
42773
|
if (!res.writableEnded) res.end();
|
|
@@ -44110,18 +42781,15 @@ var videoStreamRouter = (req, res) => {
|
|
|
44110
42781
|
else if (!res.writableEnded) res.end();
|
|
44111
42782
|
} else {
|
|
44112
42783
|
logger.error(`处理视频数据请求时发生错误: ${error.message}`);
|
|
44113
|
-
if (!res.headersSent) createNotFoundResponse(res, "
|
|
42784
|
+
if (!res.headersSent) createNotFoundResponse(res, "服务器错误");
|
|
44114
42785
|
else if (!res.writableEnded) res.end();
|
|
44115
42786
|
}
|
|
44116
42787
|
}
|
|
44117
42788
|
};
|
|
44118
42789
|
/**
|
|
44119
|
-
*
|
|
44120
|
-
* GET /api/kkk/video/:filename
|
|
44121
|
-
* @param req 请求对象。
|
|
44122
|
-
* @param res 响应对象。
|
|
42790
|
+
* 视频播放页面(SSR)
|
|
44123
42791
|
*/
|
|
44124
|
-
var
|
|
42792
|
+
var getVideoPage = (req, res) => {
|
|
44125
42793
|
const filenameParam = req.params.filename;
|
|
44126
42794
|
const filename = Array.isArray(filenameParam) ? filenameParam[0] : filenameParam;
|
|
44127
42795
|
if (!filename) {
|
|
@@ -44130,7 +42798,7 @@ var getVideoRouter = (req, res) => {
|
|
|
44130
42798
|
}
|
|
44131
42799
|
const videoPath = Common.validateVideoRequest(filename, res);
|
|
44132
42800
|
if (!videoPath) return;
|
|
44133
|
-
const videoDataUrl = `/
|
|
42801
|
+
const videoDataUrl = `/kkk/v1/stream/${encodeURIComponent(filename)}`;
|
|
44134
42802
|
const previewInfo = Common.getVideoPreview(filename);
|
|
44135
42803
|
const removeCache = previewInfo?.removeCache ?? Config.app.removeCache;
|
|
44136
42804
|
const createdAt = previewInfo?.createdAt ?? Date.now();
|
|
@@ -44142,19 +42810,16 @@ var getVideoRouter = (req, res) => {
|
|
|
44142
42810
|
removeCache,
|
|
44143
42811
|
createdAt,
|
|
44144
42812
|
expireAt,
|
|
44145
|
-
eventsUrl: `/
|
|
42813
|
+
eventsUrl: `/kkk/v1/video/${encodeURIComponent(filename)}/events`
|
|
44146
42814
|
});
|
|
44147
42815
|
res.setHeader("Cache-Control", "no-cache");
|
|
44148
42816
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
44149
42817
|
res.send(htmlContent);
|
|
44150
42818
|
};
|
|
44151
42819
|
/**
|
|
44152
|
-
*
|
|
44153
|
-
* 持续向前端推送剩余有效期与删除状态,并在预览失效后自动结束连接。
|
|
44154
|
-
* @param req 请求对象。
|
|
44155
|
-
* @param res 响应对象。
|
|
42820
|
+
* 视频预览状态事件流(SSE)
|
|
44156
42821
|
*/
|
|
44157
|
-
var
|
|
42822
|
+
var getVideoEvents = (req, res) => {
|
|
44158
42823
|
const filenameParam = req.params.filename;
|
|
44159
42824
|
const filename = Array.isArray(filenameParam) ? filenameParam[0] : filenameParam;
|
|
44160
42825
|
if (!filename) {
|
|
@@ -44175,10 +42840,6 @@ var videoPreviewEventsRouter = (req, res) => {
|
|
|
44175
42840
|
res.setHeader("Connection", "keep-alive");
|
|
44176
42841
|
res.flushHeaders?.();
|
|
44177
42842
|
let timer = null;
|
|
44178
|
-
/**
|
|
44179
|
-
* 推送一次最新的预览状态到客户端。
|
|
44180
|
-
* @returns 当前预览是否已被删除或失效。
|
|
44181
|
-
*/
|
|
44182
42843
|
const sendPayload = () => {
|
|
44183
42844
|
const currentInfo = Common.getVideoPreview(filename) ?? previewInfo;
|
|
44184
42845
|
const now = Date.now();
|
|
@@ -44220,12 +42881,130 @@ var videoPreviewEventsRouter = (req, res) => {
|
|
|
44220
42881
|
});
|
|
44221
42882
|
};
|
|
44222
42883
|
//#endregion
|
|
44223
|
-
//#region src/module/server/
|
|
44224
|
-
|
|
44225
|
-
|
|
44226
|
-
|
|
44227
|
-
|
|
42884
|
+
//#region src/module/server/middlewares/auth.ts
|
|
42885
|
+
/**
|
|
42886
|
+
* Base64解码
|
|
42887
|
+
* @param str Base64编码的字符串
|
|
42888
|
+
* @returns 解码后的字符串
|
|
42889
|
+
*/
|
|
42890
|
+
var base64Decode = (str) => {
|
|
42891
|
+
return Buffer.from(str, "base64").toString("utf8");
|
|
44228
42892
|
};
|
|
42893
|
+
/**
|
|
42894
|
+
* URL解码
|
|
42895
|
+
* @param str URL编码的字符串
|
|
42896
|
+
* @returns 解码后的字符串
|
|
42897
|
+
*/
|
|
42898
|
+
var urlDecode = (str) => {
|
|
42899
|
+
return decodeURIComponent(str);
|
|
42900
|
+
};
|
|
42901
|
+
/**
|
|
42902
|
+
* 十六进制解码
|
|
42903
|
+
* @param str 十六进制编码的字符串
|
|
42904
|
+
* @returns 解码后的字符串
|
|
42905
|
+
*/
|
|
42906
|
+
var hexDecode = (str) => {
|
|
42907
|
+
return Buffer.from(str, "hex").toString("utf8");
|
|
42908
|
+
};
|
|
42909
|
+
/**
|
|
42910
|
+
* 反转字符串
|
|
42911
|
+
* @param str 待反转字符串
|
|
42912
|
+
* @returns 反转后的字符串
|
|
42913
|
+
*/
|
|
42914
|
+
var reverseString = (str) => {
|
|
42915
|
+
return str.split("").reverse().join("");
|
|
42916
|
+
};
|
|
42917
|
+
/**
|
|
42918
|
+
* 字符偏移解码
|
|
42919
|
+
* @param str 编码的字符串
|
|
42920
|
+
* @param offset 偏移量
|
|
42921
|
+
* @returns 解码后的字符串
|
|
42922
|
+
*/
|
|
42923
|
+
var charOffsetDecode = (str, offset = 5) => {
|
|
42924
|
+
return str.split("").map((char) => {
|
|
42925
|
+
const code = char.charCodeAt(0);
|
|
42926
|
+
return String.fromCharCode(code - offset);
|
|
42927
|
+
}).join("");
|
|
42928
|
+
};
|
|
42929
|
+
/**
|
|
42930
|
+
* 多层解码解密
|
|
42931
|
+
* @param str 多层编码的字符串
|
|
42932
|
+
* @returns 解码后的原始字符串
|
|
42933
|
+
*/
|
|
42934
|
+
var multiLayerDecode = (str) => {
|
|
42935
|
+
try {
|
|
42936
|
+
let decoded = base64Decode(str);
|
|
42937
|
+
decoded = urlDecode(decoded);
|
|
42938
|
+
decoded = base64Decode(decoded);
|
|
42939
|
+
decoded = reverseString(decoded);
|
|
42940
|
+
decoded = hexDecode(decoded);
|
|
42941
|
+
decoded = charOffsetDecode(decoded, 5);
|
|
42942
|
+
return decoded;
|
|
42943
|
+
} catch (error) {
|
|
42944
|
+
throw new Error("多层解码失败:" + error);
|
|
42945
|
+
}
|
|
42946
|
+
};
|
|
42947
|
+
/**
|
|
42948
|
+
* HMAC-SHA256签名验证中间件
|
|
42949
|
+
* @param req 请求对象
|
|
42950
|
+
* @param res 响应对象
|
|
42951
|
+
* @param next 下一个中间件函数
|
|
42952
|
+
*/
|
|
42953
|
+
var signatureVerificationMiddleware = (req, res, next) => {
|
|
42954
|
+
try {
|
|
42955
|
+
const encodedSignature = req.headers["x-signature"];
|
|
42956
|
+
const timestamp = req.headers["x-timestamp"];
|
|
42957
|
+
const nonce = req.headers["x-nonce"];
|
|
42958
|
+
const token = req.headers["authorization"]?.replace("Bearer ", "") || "";
|
|
42959
|
+
if (!encodedSignature || !timestamp || !nonce) return createBadRequestResponse(res, "缺少必要的签名参数");
|
|
42960
|
+
if (Math.abs(Date.now() - parseInt(timestamp)) > 300 * 1e3) return createBadRequestResponse(res, "请求时间戳已过期");
|
|
42961
|
+
let decodedSignature;
|
|
42962
|
+
try {
|
|
42963
|
+
decodedSignature = multiLayerDecode(encodedSignature);
|
|
42964
|
+
} catch (error) {
|
|
42965
|
+
return createBadRequestResponse(res, "签名格式错误:" + error);
|
|
42966
|
+
}
|
|
42967
|
+
const signatureString = `${req.method.toUpperCase()}|${req.headers["x-original-url"] || req.originalUrl}|${req.method === "GET" ? "" : JSON.stringify(req.body || {})}|${timestamp}|${nonce}`;
|
|
42968
|
+
const expectedSignature = crypto.createHmac("sha256", token).update(signatureString).digest("hex");
|
|
42969
|
+
if (decodedSignature !== expectedSignature) {
|
|
42970
|
+
logger.warn(`签名验证失败: 期望=${expectedSignature}, 解码后实际=${decodedSignature}, 签名字符串=${signatureString}`);
|
|
42971
|
+
return createBadRequestResponse(res, "签名验证失败");
|
|
42972
|
+
}
|
|
42973
|
+
next();
|
|
42974
|
+
} catch (error) {
|
|
42975
|
+
logger.error("签名验证中间件错误:", error);
|
|
42976
|
+
return createServerErrorResponse(res, "签名验证失败");
|
|
42977
|
+
}
|
|
42978
|
+
};
|
|
42979
|
+
//#endregion
|
|
42980
|
+
//#region src/module/server/routes/api.ts
|
|
42981
|
+
var import_lib = /* @__PURE__ */ __toESM(require_lib(), 1);
|
|
42982
|
+
/**
|
|
42983
|
+
* API 路由注册
|
|
42984
|
+
*/
|
|
42985
|
+
var apiRouter = express.Router();
|
|
42986
|
+
var authMiddlewares = [authMiddleware, signatureVerificationMiddleware];
|
|
42987
|
+
apiRouter.get(ROUTES.BOTS, ...authMiddlewares, getBots);
|
|
42988
|
+
apiRouter.get(ROUTES.BOT_INFO, ...authMiddlewares, getBotInfo);
|
|
42989
|
+
apiRouter.get(ROUTES.BOT_GROUPS, ...authMiddlewares, getBotGroups);
|
|
42990
|
+
apiRouter.get(ROUTES.BOT_GROUP_INFO, ...authMiddlewares, getBotGroupInfo);
|
|
42991
|
+
apiRouter.post(ROUTES.GROUPS_BATCH, ...authMiddlewares, getGroupsBatch);
|
|
42992
|
+
apiRouter.get(ROUTES.CONFIG, ...authMiddlewares, getAllConfig);
|
|
42993
|
+
apiRouter.post(ROUTES.CONFIG, ...authMiddlewares, updateAllConfig);
|
|
42994
|
+
apiRouter.get(ROUTES.VIDEO_STREAM, getVideoStream);
|
|
42995
|
+
apiRouter.get(ROUTES.VIDEO_EVENTS, getVideoEvents);
|
|
42996
|
+
//#endregion
|
|
42997
|
+
//#region src/module/server/routes/ssr.ts
|
|
42998
|
+
/**
|
|
42999
|
+
* SSR 路由注册
|
|
43000
|
+
*/
|
|
43001
|
+
var ssrRouter = express.Router();
|
|
43002
|
+
ssrRouter.get(ROUTES.VIDEO_PAGE, getVideoPage);
|
|
43003
|
+
//#endregion
|
|
43004
|
+
//#region src/module/server/routes/static.ts
|
|
43005
|
+
/**
|
|
43006
|
+
* 静态文件路由注册
|
|
43007
|
+
*/
|
|
44229
43008
|
var webDistPath = path.join(Root.pluginPath, "lib", "web");
|
|
44230
43009
|
var webIndexPath = path.join(webDistPath, "index.html");
|
|
44231
43010
|
var sendWebIndex = (res) => {
|
|
@@ -44234,10 +43013,34 @@ var sendWebIndex = (res) => {
|
|
|
44234
43013
|
res.setHeader("Cache-Control", "no-cache");
|
|
44235
43014
|
res.type("html").send(html);
|
|
44236
43015
|
} catch (error) {
|
|
44237
|
-
|
|
44238
|
-
|
|
44239
|
-
|
|
43016
|
+
logger.error("[karin-plugin-kkk] 读取 Web UI 入口文件失败:", error);
|
|
43017
|
+
createServerErrorResponse(res, "加载 Web UI 失败");
|
|
43018
|
+
}
|
|
43019
|
+
};
|
|
43020
|
+
var staticRouter = express.Router();
|
|
43021
|
+
var webStatic = express.static(webDistPath, {
|
|
43022
|
+
redirect: false,
|
|
43023
|
+
setHeaders: (res, filePath) => {
|
|
43024
|
+
if (filePath.endsWith(".html")) {
|
|
43025
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
43026
|
+
return;
|
|
43027
|
+
}
|
|
43028
|
+
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
44240
43029
|
}
|
|
43030
|
+
});
|
|
43031
|
+
staticRouter.use(webStatic);
|
|
43032
|
+
staticRouter.use((_req, res) => {
|
|
43033
|
+
sendWebIndex(res);
|
|
43034
|
+
});
|
|
43035
|
+
//#endregion
|
|
43036
|
+
//#region src/module/server/routes/index.ts
|
|
43037
|
+
/**
|
|
43038
|
+
* 主路由注册
|
|
43039
|
+
*/
|
|
43040
|
+
var server = express();
|
|
43041
|
+
var proxyOptions = {
|
|
43042
|
+
target: "https://developer.huawei.com",
|
|
43043
|
+
changeOrigin: true
|
|
44241
43044
|
};
|
|
44242
43045
|
server.use(import_lib.default());
|
|
44243
43046
|
server.use("/", createProxyMiddleware(proxyOptions));
|
|
@@ -44254,39 +43057,21 @@ if (process.env.NODE_ENV !== "test") checkPort(3780).then((isOpen) => {
|
|
|
44254
43057
|
var app$1 = express.Router();
|
|
44255
43058
|
app$1.use(express.json());
|
|
44256
43059
|
app$1.use(express.urlencoded({ extended: true }));
|
|
44257
|
-
if (Config.
|
|
44258
|
-
app$1.use("/amagi/api/bilibili", createBilibiliRoutes(Config.cookies.bilibili));
|
|
44259
|
-
app$1.use("/amagi/api/douyin", createDouyinRoutes(Config.cookies.douyin));
|
|
44260
|
-
app$1.use("/amagi/api/kuaishou", createKuaishouRoutes(Config.cookies.kuaishou));
|
|
44261
|
-
app$1.use("/amagi/api/xiaohongshu", createXiaohongshuRoutes(Config.cookies.xiaohongshu));
|
|
44262
|
-
} else if (Config.
|
|
44263
|
-
bilibili: Config.cookies.bilibili,
|
|
44264
|
-
douyin: Config.cookies.douyin,
|
|
44265
|
-
kuaishou: Config.cookies.kuaishou,
|
|
44266
|
-
xiaohongshu: Config.cookies.xiaohongshu
|
|
44267
|
-
} }).startServer(Config.
|
|
44268
|
-
app$1.get("/stream/:filename", videoStreamRouter);
|
|
44269
|
-
app$1.get("/video/:filename", getVideoRouter);
|
|
44270
|
-
app$1.get("/video/:filename/events", videoPreviewEventsRouter);
|
|
43060
|
+
if (Config.amagi.APIServer && Config.amagi.APIServerMount) {
|
|
43061
|
+
app$1.use("/amagi/api/bilibili", createBilibiliRoutes(Config.amagi.cookies.bilibili));
|
|
43062
|
+
app$1.use("/amagi/api/douyin", createDouyinRoutes(Config.amagi.cookies.douyin));
|
|
43063
|
+
app$1.use("/amagi/api/kuaishou", createKuaishouRoutes(Config.amagi.cookies.kuaishou));
|
|
43064
|
+
app$1.use("/amagi/api/xiaohongshu", createXiaohongshuRoutes(Config.amagi.cookies.xiaohongshu));
|
|
43065
|
+
} else if (Config.amagi.APIServer) new Client({ cookies: {
|
|
43066
|
+
bilibili: Config.amagi.cookies.bilibili,
|
|
43067
|
+
douyin: Config.amagi.cookies.douyin,
|
|
43068
|
+
kuaishou: Config.amagi.cookies.kuaishou,
|
|
43069
|
+
xiaohongshu: Config.amagi.cookies.xiaohongshu
|
|
43070
|
+
} }).startServer(Config.amagi.APIServerPort);
|
|
44271
43071
|
app$1.use("/v1", apiRouter);
|
|
44272
|
-
|
|
44273
|
-
|
|
44274
|
-
|
|
44275
|
-
setHeaders: (res, filePath) => {
|
|
44276
|
-
if (filePath.endsWith(".html")) {
|
|
44277
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
44278
|
-
return;
|
|
44279
|
-
}
|
|
44280
|
-
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
44281
|
-
}
|
|
44282
|
-
});
|
|
44283
|
-
staticRouter.use(webStatic);
|
|
44284
|
-
staticRouter.use((_req, res) => {
|
|
44285
|
-
sendWebIndex(res);
|
|
44286
|
-
});
|
|
44287
|
-
/** 将子路由挂载到主路由上 */
|
|
44288
|
-
app.use("/kkk", staticRouter);
|
|
44289
|
-
app.use("/api/kkk", app$1);
|
|
43072
|
+
app$1.use(SSR_PREFIX, ssrRouter);
|
|
43073
|
+
app$1.use(ASSETS_PREFIX, staticRouter);
|
|
43074
|
+
app.use(KKK_PREFIX, app$1);
|
|
44290
43075
|
//#endregion
|
|
44291
43076
|
//#region src/setup.ts
|
|
44292
43077
|
var requireVersion = "1.16.1";
|
|
@@ -44333,4 +43118,4 @@ mkdirSync(`${karinPathBase}/${Root.pluginName}/data`);
|
|
|
44333
43118
|
mkdirSync(Common.tempDri.images);
|
|
44334
43119
|
mkdirSync(Common.tempDri.video);
|
|
44335
43120
|
//#endregion
|
|
44336
|
-
export { createLineBreakNode as $,
|
|
43121
|
+
export { createLineBreakNode as $, xiaohongshuSign as $n, emitLogDebug as $r, CommentType as $t, template_default as A, getHeadersAndData as An, BilibiliBv2AvParamsSchema as Ar, KuaishouApiRoutes as At, DouyinDBBase as B, douyinApiUrls as Bn, BilibiliQrcodeParamsSchema as Br, DouyinInternalMethods as Bt, help as C, qtparam as Cn, BilibiliApplyCaptchaParamsSchema as Cr, CreateApp as Ct, removeOldFiles as D, logger$2 as Dn, BilibiliAv2BvParamsSchema as Dr, BilibiliMethodMapping as Dt, dylogin as E, logMiddleware as En, BilibiliArticleParamsSchema as Er, BilibiliApiRoutes as Et, getBilibiliDB as F, kuaishouFetcher$1 as Fn, BilibiliDynamicParamsSchema as Fr, getEnglishMethodName as Ft, createAtNode as G, createSuccessResponse$1 as Gn, BilibiliVideoDownloadParamsSchema as Gr, MethodMaps as Gt, reactServerRender as H, bilibiliFetcher$1 as Hn, BilibiliUserParamsSchema as Hr, KuaishouFetcherMethods as Ht, getDouyinDB as I, kuaishouSign as In, BilibiliEmojiParamsSchema as Ir, BilibiliFetcherMethods as It, createEmojiNode as J, validateKuaishouParams as Jn, emitApiError as Jr, XiaohongshuMethodToFetcher as Jt, createBlockquoteNode as K, validateBilibiliParams as Kn, BilibiliVideoParamsSchema as Kr, XiaohongshuFetcherMethods as Kt, getStatisticsDB as L, kuaishouApiUrls as Ln, BilibiliLiveParamsSchema as Lr, BilibiliInternalMethods as Lt, bilibiliDBInstance as M, createBoundXiaohongshuFetcher as Mn, BilibiliCommentParamsSchema as Mr, XiaohongshuApiRoutes as Mt, cleanOldDynamicCache as N, xiaohongshuFetcher$1 as Nn, BilibiliCommentReplyParamsSchema as Nr, XiaohongshuMethodMapping as Nt, task as O, fetchData as On, BilibiliBangumiInfoParamsSchema as Or, DouyinApiRoutes as Ot, douyinDBInstance as P, createBoundKuaishouFetcher as Pn, BilibiliDanmakuParamsSchema as Pr, getApiRoute as Pt, createImageNode as Q, xiaohongshuApiUrls as Qn, emitLog as Qr, MajorType as Qt, initAllDatabases as R, douyinFetcher$1 as Rn, BilibiliLoginParamsSchema as Rr, BilibiliMethodToFetcher as Rt, setdyPush as S, bv2av as Sn, DouyinWorkParamsSchema as Sr, softFetch as St, biLogin as T, initLogger as Tn, BilibiliArticleInfoParamsSchema as Tr, amagiClient$1 as Tt, renderVideoPreviewPage as U, createBoundBilibiliFetcher as Un, BilibiliValidateCaptchaParamsSchema as Ur, KuaishouInternalMethods as Ut, BilibiliDBBase as V, douyinSign as Vn, BilibiliQrcodeStatusParamsSchema as Vr, DouyinMethodToFetcher as Vt, renderRichTextToReact as W, createErrorResponse as Wn, BilibiliValidationSchemas as Wr, KuaishouMethodToFetcher as Wt, createHeadingNode as X, XiaohongshuMethodRoutes as Xn, emitHttpRequest as Xr, DynamicType as Xt, createHashtagNode as Y, validateXiaohongshuParams as Yn, emitApiSuccess as Yr, toFetcherMethod as Yt, createHorizontalRuleNode as Z, XiaohongshuValidationSchemas as Zn, emitHttpResponse as Zr, AdditionalType as Zt, douyinPush as _, handleError as _n, DouyinQrcodeParamsSchema as _r, SOFT_ERROR_CODES as _t, bilibiliAPP as a, emitNetworkRetry as ai, kuaishouUtils as an, KuaishouUserWorkListParamsSchema as ar, createParagraphNode as at, globalIgnore as b, parseDmSegMobileReply as bn, DouyinUserParamsSchema as br, kuaishouFetcher as bt, prefix as c, bilibiliApiUrls as ci, kuaishou$1 as cn, DouyinCommentParamsSchema as cr, createTextNode as ct, globalStatistics as d, getKuaishouData as di, createBoundDouyinApi as dn, DouyinEmojiListParamsSchema as dr, createVoteNode as dt, emitLogError as ei, createAmagiClient as en, KuaishouCommentParamsSchema as er, createLinkCardNode as et, groupStatistics as f, Root as fi, douyin$1 as fn, DouyinEmojiProParamsSchema as fr, createWebLinkNode as ft, changeBotID as g, ValidationError as gn, DouyinMusicParamsSchema as gr, AmagiError as gt, bilibiliPushList as h, ApiError as hn, DouyinMethodRoutes as hr, AmagiBase as ht, update as i, emitNetworkError as ii, xiaohongshu$1 as in, KuaishouUserProfileParamsSchema as ir, createMentionNode as it, webConfig as j, isNetworkErrorResult as jn, BilibiliColumnInfoParamsSchema as jr, KuaishouMethodMapping as jt, testWrapWithErrorHandler as k, fetchResponse as kn, BilibiliBangumiStreamParamsSchema as kr, DouyinMethodMapping as kt, xiaohongshuAPP as l, getBilibiliData as li, douyinUtils as ln, DouyinCommentReplyParamsSchema as lr, createTopicNode as lt, bilibiliPush as m, createBilibiliRoutes as mn, DouyinLiveRoomParamsSchema as mr, normalizeRichTextNodes as mt, kkkUpdateCommand as n, emitLogMark as ni, createXiaohongshuRoutes as nn, KuaishouLiveRoomInfoParamsSchema as nr, createListNode as nt, douyinAPP as o, bilibili$1 as oi, createKuaishouRoutes as on, KuaishouValidationSchemas as or, createRichTextDocument as ot, qrLogin as p, bilibiliUtils as pn, DouyinHotWordsParamsSchema as pr, extractRichTextPlainText as pt, createCodeBlockNode as q, validateDouyinParams as qn, amagiEvents as qr, XiaohongshuInternalMethods as qt, kkkUpdateTest as r, emitLogWarn as ri, createBoundXiaohongshuApi as rn, KuaishouMethodRoutes as rr, createLotteryNode as rt, kuaishouAPP as s, createBoundBilibiliApi as si, createBoundKuaishouApi as sn, KuaishouVideoParamsSchema as sr, createSearchKeywordNode as st, kkkUpdate as t, emitLogInfo as ti, xiaohongshuUtils as tn, KuaishouEmojiParamsSchema as tr, createListItemNode as tt, testPush as u, getDouyinData as ui, createDouyinRoutes as un, DouyinDanmakuParamsSchema as ur, createViewPictureNode as ut, douyinPushList as v, bilibiliErrorCodeMap as vn, DouyinSearchParamsSchema as vr, bilibiliFetcher as vt, version as w, httpLogger as wn, BilibiliArticleCardParamsSchema as wr, amagi as wt, setbiliPush as x, av2bv as xn, DouyinValidationSchemas as xr, reloadAmagiConfig as xt, forcePush as y, wbi_sign as yn, DouyinUserListParamsSchema as yr, douyinFetcher as yt, StatisticsDBBase as z, createBoundDouyinFetcher as zn, BilibiliMethodRoutes as zr, DouyinFetcherMethods as zt };
|