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.
Files changed (33) hide show
  1. package/CHANGELOG.md +842 -1166
  2. package/README.md +4 -5
  3. package/config/default_config/app.yaml +1 -1
  4. package/config/default_config/bilibili.yaml +2 -2
  5. package/config/default_config/config.json +136 -0
  6. package/config/default_config/cookies.yaml +2 -2
  7. package/config/default_config/douyin.yaml +3 -3
  8. package/config/default_config/kuaishou.yaml +1 -1
  9. package/config/default_config/pushlist.yaml +6 -6
  10. package/config/default_config/request.yaml +1 -1
  11. package/config/default_config/xiaohongshu.yaml +2 -2
  12. package/lib/build-metadata.json +5 -5
  13. package/lib/core_chunk/amagi.d.mts +18 -18
  14. package/lib/core_chunk/amagi.js +1 -1
  15. package/lib/core_chunk/amagiClient.js +1 -1
  16. package/lib/core_chunk/main.js +618 -1833
  17. package/lib/core_chunk/vendor.js +4564 -2166
  18. package/lib/root.js +1 -1
  19. package/lib/web/assets/CronEditor-Cg5OQ1zE.js +1 -0
  20. package/lib/web/assets/DesktopLayout-R64EJDVu.js +1 -0
  21. package/lib/web/assets/MobileLayout-D45Q2A-w.js +2 -0
  22. package/lib/web/assets/ThemeSwitch-M2yTf191.js +1 -0
  23. package/lib/web/assets/index-Bz9RVG7l.js +28 -0
  24. package/lib/web/assets/index-C9k3Jf0p.css +2 -0
  25. package/lib/web/index.html +3 -3
  26. package/package.json +28 -28
  27. package/resources/font/bilifont/font.css +3 -3
  28. package/resources/font/fansmedal-num/font.css +1 -1
  29. package/lib/web/assets/AboutPanel-DFjZYu60.js +0 -1
  30. package/lib/web/assets/DesktopLayout-Cr5AitGP.js +0 -1
  31. package/lib/web/assets/MobileLayout-BW-vN-VU.js +0 -2
  32. package/lib/web/assets/index-8KaTMCj2.css +0 -2
  33. package/lib/web/assets/index-CQhoJUBv.js +0 -28
@@ -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, copyConfigSync, createBadRequestResponse, createNotFoundResponse, createServerErrorResponse, createSuccessResponse, db, defineConfig, ffmpeg, ffprobe, filesByExt, getBot, hooks, karin, karinPathHtml, karinPathTemp, logger, logger as logger$1, logs, mkdirSync, parseChangelog, range, render, requireFileSync, restart, segment, updatePkg, watch } from "node-karin";
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(data) {
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\.]+/g, "");
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 ? this.chunk = a.slice(f, Math.min(f + 64, a.length)) : this.chunk = [];
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++) r[t] = e[4 * t] << 24, r[t] |= e[4 * t + 1] << 16, r[t] |= e[4 * t + 2] << 8, r[t] |= e[4 * t + 3], r[t] >>>= 0;
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), r[n] = (a ^ this.le(r[n - 13], 7) ^ r[n - 6]) >>> 0;
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, i[3] = i[2], i[2] = this.le(i[1], 9), i[1] = i[0], i[0] = u, i[7] = i[6], i[6] = this.le(i[5], 19), i[5] = i[4], i[4] = (b ^ this.le(b, 9) ^ this.le(b, 17)) >>> 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\.]+/g, "");
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$1(options, cookie, requestConfig) {
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$1(options, cookie, reqConfig ?? requestConfig),
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: parseWork$1,
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\.]+/g, "");
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
- let isvip;
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.default.Root.fromJSON(DANMAKU_PROTO_JSON).lookupType("bilibili.community.service.dm.v1.DmSegMobileReply");
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
- copyConfigSync(this.defCfgPath, this.dirCfgPath);
10210
- const files = filesByExt(this.dirCfgPath, ".yaml", "name");
10211
- for (const file of files) {
10212
- const config = YAML.parseDocument(fs.readFileSync(`${this.dirCfgPath}/${file}`, "utf8"));
10213
- const defConfig = YAML.parseDocument(fs.readFileSync(`${this.defCfgPath}/${file}`, "utf8"));
10214
- const { differences, result } = this.mergeObjectsWithPriority(config, defConfig);
10215
- if (differences) fs.writeFileSync(`${this.dirCfgPath}/${file}`, result.toString({ lineWidth: -1 }));
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
- filesByExt(this.dirCfgPath, ".yaml", "abs").forEach((file) => watch(file, (_old, _now) => {
10222
- const fileName = path.basename(file, ".yaml");
10223
- if (fileName === "cookies" || fileName === "request") {
10224
- logger.debug(`[Config] 检测到 ${fileName} 配置变化,正在重载 Amagi Client...`);
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
- const def = this.getYaml("default_config", name);
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
- * 获取 YAML 文件内容
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 filePath = type === "config" ? `${this.dirCfgPath}/${name}.yaml` : `${this.defCfgPath}/${name}.yaml`;
10315
- try {
10316
- const existingContent = fs.readFileSync(filePath, "utf8");
10317
- const doc = YAML.parseDocument(existingContent);
10318
- let filterCfg = config;
10319
- if (name === "pushlist" && ("douyin" in config || "bilibili" in config)) {
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.getYaml("config", "pushlist");
10423
- if (pushCfg.bilibili) await bilibiliDB.syncConfigSubscriptions(pushCfg.bilibili);
10424
- if (pushCfg.douyin) await douyinDB.syncConfigSubscriptions(pushCfg.douyin);
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) configInstance = new Proxy(new Cfg().initCfg(), { get(target, prop) {
10441
- if (prop in target) return target[prop];
10442
- return target.getDefOrConfig(prop);
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: Config.request.timeout,
10488
- headers: { "User-Agent": Config.request["User-Agent"] },
10489
- proxy: Config.request.proxy?.switch ? Config.request.proxy : false
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
- /** 导出 Amagi Client 实例 */
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.upload.compress && file.totalBytes > Config.upload.compresstrigger) {
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.upload.compresstrigger} MB),正在进行压缩至${Config.upload.compressvalue} MB...`));
10725
- const message = [segment.text(`视频大小 (${file.totalBytes} MB) 触发压缩条件(设定值:${Config.upload.compresstrigger} MB),正在进行压缩至${Config.upload.compressvalue} MB...`), options?.message_id ? segment.reply(options.message_id) : segment.text("")];
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.upload.compresstrigger, Duration) * .75;
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.upload.usegroupfile && newFileSize > Config.upload.groupfilevalue;
10743
- if (Config.upload.videoSendMode === "base64" && !options?.useGroupFile) {
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 ? sendStatus = true : sendStatus = false;
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 ? sendStatus = true : sendStatus = false;
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}/api/kkk/video/${encodeURIComponent(filePath.split("/").pop() ?? "")}`);
10772
- Config.app.removeCache && logger.info(`文件 ${filePath} 将在 30 分钟后删除`);
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.upload.usefilelimit && fileSize > Config.upload.filelimit) {
10793
- const message = segment.text(`视频:「${downloadOpt.title.originTitle ?? "Error: 文件名获取失败"}」大小 (${fileSizeInMB} MB) 超出最大限制(设定值:${Config.upload.filelimit} MB),已取消上传`);
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.upload;
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(/[<>:"/\\|?*\x00-\x1f]/g, "_").replace(/^\.+/, "").replace(/\.+$/, "").replace(/\s+/g, "_").substring(0, 200);
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.upload.imageSendMode) {
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$5 = (ms) => {
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$5(state.remainingMs);
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, "![$1]($2)"),
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, "![$1]($2)")
19172
19161
  })
19173
19162
  })
19174
19163
  });
@@ -22388,7 +22377,7 @@ var DouyinUserList = (props) => {
22388
22377
  /**
22389
22378
  * 格式化数字显示 (使用中文单位:万、亿)
22390
22379
  */
22391
- var formatCount$2 = (count) => {
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$4 = (milliseconds) => {
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$4(video.duration)
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$2(video.statistics.like_count) })]
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$2(video.statistics.comment_count) })]
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$2(video.statistics.collect_count) })]
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$2(video.statistics.share_count) })]
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$2(prpos.data.user.following_count)]
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$2(prpos.data.user.follower_count)]
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$2(prpos.data.user.total_favorited)]
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
- " 之间的数字解析对应作品。例如发送“1”解析第一个作品"
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$3 = (ms) => {
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$3(props.data.video.duration) : null, [props.data.video]);
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$2(duration) {
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$2(duration);
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 = /\x1b\[([0-9;]+)m/g;
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
- "TRAC": {
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
- "DEBU": {
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
- "MARK": {
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
- "INFO": {
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
- "WARN": {
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
- "ERRO": {
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
- "FATA": {
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 && await this.e.reply("检测到B站链接,开始解析");
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
- const danmakuCid = iddata.p ? infoData.data.data.pages[iddata.p - 1]?.cid ?? infoData.data.data.cid : infoData.data.data.cid;
34041
- const danmakuDuration = iddata.p ? infoData.data.data.pages[iddata.p - 1]?.duration ?? infoData.data.data.duration : infoData.data.data.duration;
34042
- const hotDanmaku = getHotDanmaku(await this.fetchVideoDanmakuList(danmakuCid, danmakuDuration), 20);
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.upload.usefilelimit && Number(videoSize) > Number(Config.upload.filelimit) && !Config.upload.compress) this.e.reply(`设定的最大上传大小为 ${Config.upload.filelimit}MB\n当前解析到的视频大小为 ${Number(videoSize)}MB\n视频太大了,还是去B站看吧~`, { reply: true });
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.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
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.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
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.upload.groupfilevalue) await uploadFile(this.e, {
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.upload.groupfilevalue) await uploadFile(this.e, {
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
- let isvip;
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 loginInfo = await bilibiliFetcher.fetchLoginStatus({ typeMode: "strict" });
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("cookies", "bilibili", cookieString);
35738
- await e.reply("登录成功!用户登录凭证已保存至cookies.yaml", { reply: true });
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 && logger.warn(`动态 https://t.bilibili.com/${dynamicId} 已被处理,跳过`);
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.upload.usefilelimit && Number(videoSize) > Number(Config.upload.filelimit) && !Config.upload.compress) {
36324
- await karin$1.sendMsg(botId, Contact, [segment.text(`设定的最大上传大小为 ${Config.upload.filelimit}MB\n当前解析到的视频大小为 ${Number(videoSize)}MB\n视频太大了,还是去B站看吧~`), segment.reply(status.messageId)]);
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.upload.groupfilevalue) await uploadFile(this.e, {
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.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
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.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
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 && this.e.reply("检测到抖音链接,开始解析");
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.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
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.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
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.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
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.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
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") for (const v of item.words) v.word && suggest.push(v.word);
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.upload.groupfilevalue) await uploadFile(this.e, {
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.upload.filelimit) * 1024 * 1024;
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 && console.log(result);
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 && logger.warn(`作品 https://www.douyin.com/video/${actualAwemeId} 已被处理,跳过`);
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.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
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.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
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.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
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.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
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 === "" ? user_shortid = UserInfoData.data.user.short_id : user_shortid = 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 && console.log(result);
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 && await this.e.reply("检测到快手链接,开始解析");
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 && console.log(result);
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 && await this.e.reply("检测到小红书链接,开始解析");
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.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
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.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
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.upload.filelimit) * 1024 * 1024;
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("cookies", "douyin", cookieString);
41008
+ Config.Modify("amagi", "cookies.douyin", cookieString);
40990
41009
  logger.debug("cookies 保存完成");
40991
- await e.reply("登录成功!用户登录凭证已保存至cookies.yaml", { reply: true });
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.upload.imageSendMode === "file") {
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/platform/bilibili/api/contents.ts
42411
- var import_lib = /* @__PURE__ */ __toESM(require_lib(), 1);
42412
- /**
42413
- * B站内容管理 API
42414
- */
42415
- /**
42416
- * 获取B站内容列表
42417
- * GET /api/v1/platforms/bilibili/contents?groupId=xxx
42418
- */
42419
- var getContents$1 = async (req, res) => {
42420
- try {
42421
- const { groupId } = req.query;
42422
- if (!groupId || typeof groupId !== "string") return createBadRequestResponse(res, "请提供群组ID");
42423
- const caches = await (await getBilibiliDB()).dynamicCacheRepository.find({
42424
- where: { groupId },
42425
- relations: ["bilibiliUser"],
42426
- order: { createdAt: "DESC" },
42427
- take: 100
42428
- });
42429
- const uniqueMids = [...new Set(caches.map((c) => c.host_mid))];
42430
- const avatarCache = /* @__PURE__ */ new Map();
42431
- let hitRiskControl = false;
42432
- for (const mid of uniqueMids) {
42433
- if (hitRiskControl) {
42434
- avatarCache.set(mid, "");
42435
- continue;
42436
- }
42437
- try {
42438
- const userProfile = await bilibiliFetcher.fetchUserCard({
42439
- host_mid: mid,
42440
- typeMode: "strict"
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/api/bots.ts
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 { botId } = req.params;
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 { botId } = req.params;
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 { botId, groupId } = req.params;
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/api/config.ts
42651
+ //#region src/module/server/controllers/config.ts
43683
42652
  /**
43684
- * 获取所有配置
43685
- * GET /api/kkk/v1/config
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
- * PUT/POST /api/kkk/v1/config/:module
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 patchConfigItem = async (req, res) => {
42660
+ var getAllConfig = async (_req, res) => {
43776
42661
  try {
43777
- const { module } = req.params;
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.status(500).json({
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
- * PUT /api/kkk/v1/config
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.status(400).json({
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 && (moduleName === "cookies" || moduleName === "request")) needReloadAmagi = true;
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
- originalUrl: link,
44006
- finalUrl,
44007
- platform,
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
- logger.error("[LinkAPI] 链接解析失败:", error);
44013
- return createServerErrorResponse(res, `链接解析失败: ${error.message}`);
42710
+ return createServerErrorResponse(res, `配置更新失败: ${error.message}`);
44014
42711
  }
44015
42712
  };
44016
42713
  //#endregion
44017
- //#region src/module/server/api/index.ts
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 videoStreamRouter = (req, res) => {
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("Requested range not satisfiable");
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 getVideoRouter = (req, res) => {
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 = `/api/kkk/stream/${encodeURIComponent(filename)}`;
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: `/api/kkk/video/${encodeURIComponent(filename)}/events`
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 videoPreviewEventsRouter = (req, res) => {
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/Register.ts
44224
- var server = express();
44225
- var proxyOptions = {
44226
- target: "https://developer.huawei.com",
44227
- changeOrigin: true
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
- const message = `[karin-plugin-kkk] Failed to read Web UI entry: ${webIndexPath}`;
44238
- logger.error(error instanceof Error ? `${message}\n${error.stack ?? error.message}` : `${message}\n${String(error)}`);
44239
- res.status(500).type("text/plain").send(message);
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.app.APIServer && Config.app.APIServerMount) {
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.app.APIServer) new Client({ cookies: {
44263
- bilibili: Config.cookies.bilibili,
44264
- douyin: Config.cookies.douyin,
44265
- kuaishou: Config.cookies.kuaishou,
44266
- xiaohongshu: Config.cookies.xiaohongshu
44267
- } }).startServer(Config.app.APIServerPort);
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
- var staticRouter = express.Router();
44273
- var webStatic = express.static(webDistPath, {
44274
- redirect: false,
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 $, xiaohongshuApiUrls as $n, emitLog as $r, MajorType as $t, template_default as A, fetchResponse as An, BilibiliBangumiStreamParamsSchema as Ar, DouyinMethodMapping as At, DouyinDBBase as B, createBoundDouyinFetcher as Bn, BilibiliMethodRoutes as Br, DouyinFetcherMethods as Bt, help as C, bv2av as Cn, DouyinWorkParamsSchema as Cr, softFetch as Ct, removeOldFiles as D, logMiddleware as Dn, BilibiliArticleParamsSchema as Dr, BilibiliApiRoutes as Dt, dylogin as E, initLogger as En, BilibiliArticleInfoParamsSchema as Er, amagiClient$1 as Et, getBilibiliDB as F, createBoundKuaishouFetcher as Fn, BilibiliDanmakuParamsSchema as Fr, getApiRoute as Ft, createAtNode as G, createErrorResponse as Gn, BilibiliValidationSchemas as Gr, KuaishouMethodToFetcher as Gt, reactServerRender as H, douyinSign as Hn, BilibiliQrcodeStatusParamsSchema as Hr, DouyinMethodToFetcher as Ht, getDouyinDB as I, kuaishouFetcher$1 as In, BilibiliDynamicParamsSchema as Ir, getEnglishMethodName as It, createEmojiNode as J, validateDouyinParams as Jn, amagiEvents as Jr, XiaohongshuInternalMethods as Jt, createBlockquoteNode as K, createSuccessResponse$1 as Kn, BilibiliVideoDownloadParamsSchema as Kr, MethodMaps as Kt, getStatisticsDB as L, kuaishouSign as Ln, BilibiliEmojiParamsSchema as Lr, BilibiliFetcherMethods as Lt, bilibiliDBInstance as M, isNetworkErrorResult as Mn, BilibiliColumnInfoParamsSchema as Mr, KuaishouMethodMapping as Mt, cleanOldDynamicCache as N, createBoundXiaohongshuFetcher as Nn, BilibiliCommentParamsSchema as Nr, XiaohongshuApiRoutes as Nt, task as O, logger$2 as On, BilibiliAv2BvParamsSchema as Or, BilibiliMethodMapping as Ot, douyinDBInstance as P, xiaohongshuFetcher$1 as Pn, BilibiliCommentReplyParamsSchema as Pr, XiaohongshuMethodMapping as Pt, createImageNode as Q, XiaohongshuValidationSchemas as Qn, emitHttpResponse as Qr, AdditionalType as Qt, initAllDatabases as R, kuaishouApiUrls as Rn, BilibiliLiveParamsSchema as Rr, BilibiliInternalMethods as Rt, setdyPush as S, av2bv as Sn, DouyinValidationSchemas as Sr, reloadAmagiConfig as St, biLogin as T, httpLogger as Tn, BilibiliArticleCardParamsSchema as Tr, amagi as Tt, renderVideoPreviewPage as U, bilibiliFetcher$1 as Un, BilibiliUserParamsSchema as Ur, KuaishouFetcherMethods as Ut, BilibiliDBBase as V, douyinApiUrls as Vn, BilibiliQrcodeParamsSchema as Vr, DouyinInternalMethods as Vt, renderRichTextToReact as W, createBoundBilibiliFetcher as Wn, BilibiliValidateCaptchaParamsSchema as Wr, KuaishouInternalMethods as Wt, createHeadingNode as X, validateXiaohongshuParams as Xn, emitApiSuccess as Xr, toFetcherMethod as Xt, createHashtagNode as Y, validateKuaishouParams as Yn, emitApiError as Yr, XiaohongshuMethodToFetcher as Yt, createHorizontalRuleNode as Z, XiaohongshuMethodRoutes as Zn, emitHttpRequest as Zr, DynamicType as Zt, douyinPush as _, ValidationError as _n, DouyinMusicParamsSchema as _r, SOFT_ERROR_CODES as _t, bilibiliAPP as a, emitNetworkError as ai, xiaohongshu$1 as an, KuaishouUserProfileParamsSchema as ar, createParagraphNode as at, globalIgnore as b, wbi_sign as bn, DouyinUserListParamsSchema as br, douyinFetcher as bt, prefix as c, createBoundBilibiliApi as ci, createBoundKuaishouApi as cn, KuaishouVideoParamsSchema as cr, createTextNode as ct, globalStatistics as d, getDouyinData as di, createDouyinRoutes as dn, DouyinDanmakuParamsSchema as dr, createVoteNode as dt, emitLogDebug as ei, CommentType as en, xiaohongshuSign as er, createLinkCardNode as et, groupStatistics as f, getKuaishouData as fi, createBoundDouyinApi as fn, DouyinEmojiListParamsSchema as fr, createWebLinkNode as ft, changeBotID as g, ApiError as gn, DouyinMethodRoutes as gr, AmagiError as gt, bilibiliPushList as h, createBilibiliRoutes as hn, DouyinLiveRoomParamsSchema as hr, AmagiBase as ht, update as i, emitLogWarn as ii, createBoundXiaohongshuApi as in, KuaishouMethodRoutes as ir, createMentionNode as it, webConfig as j, getHeadersAndData as jn, BilibiliBv2AvParamsSchema as jr, KuaishouApiRoutes as jt, testWrapWithErrorHandler as k, fetchData as kn, BilibiliBangumiInfoParamsSchema as kr, DouyinApiRoutes as kt, xiaohongshuAPP as l, bilibiliApiUrls as li, kuaishou$1 as ln, DouyinCommentParamsSchema as lr, createTopicNode as lt, bilibiliPush as m, bilibiliUtils as mn, DouyinHotWordsParamsSchema as mr, normalizeRichTextNodes as mt, kkkUpdateCommand as n, emitLogInfo as ni, xiaohongshuUtils as nn, KuaishouEmojiParamsSchema as nr, createListNode as nt, douyinAPP as o, emitNetworkRetry as oi, kuaishouUtils as on, KuaishouUserWorkListParamsSchema as or, createRichTextDocument as ot, qrLogin as p, Root as pi, douyin$1 as pn, DouyinEmojiProParamsSchema as pr, extractRichTextPlainText as pt, createCodeBlockNode as q, validateBilibiliParams as qn, BilibiliVideoParamsSchema as qr, XiaohongshuFetcherMethods as qt, kkkUpdateTest as r, emitLogMark as ri, createXiaohongshuRoutes as rn, KuaishouLiveRoomInfoParamsSchema as rr, createLotteryNode as rt, kuaishouAPP as s, bilibili$1 as si, createKuaishouRoutes as sn, KuaishouValidationSchemas as sr, createSearchKeywordNode as st, kkkUpdate as t, emitLogError as ti, createAmagiClient as tn, KuaishouCommentParamsSchema as tr, createListItemNode as tt, testPush as u, getBilibiliData as ui, douyinUtils as un, DouyinCommentReplyParamsSchema as ur, createViewPictureNode as ut, douyinPushList as v, handleError as vn, DouyinQrcodeParamsSchema as vr, amagiClient as vt, version as w, qtparam as wn, BilibiliApplyCaptchaParamsSchema as wr, CreateApp as wt, setbiliPush as x, parseDmSegMobileReply as xn, DouyinUserParamsSchema as xr, kuaishouFetcher as xt, forcePush as y, bilibiliErrorCodeMap as yn, DouyinSearchParamsSchema as yr, bilibiliFetcher as yt, StatisticsDBBase as z, douyinFetcher$1 as zn, BilibiliLoginParamsSchema as zr, BilibiliMethodToFetcher as zt };
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 };