karin-plugin-kkk 2.25.1 → 2.25.2

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.
@@ -1,6 +1,6 @@
1
1
  import { n as __esmMin, o as __toESM, r as __export } from "./rolldown-runtime-BMXAG3ag.js";
2
- import { A as init_locale, An as init_zod, Cn as Chalk, Dn as axios_default, En as init_axios, On as Xhshow, Sn as require_protobufjs, Tn as AxiosError$1, _n as require_png, a as Window, bn as require_heic_decode, dt as init_date_fns, ft as fromUnixTime, ht as differenceInSeconds, i as init_lib, j as zhCN, jn as zod_default, kn as init_dist, mt as format, n as require_lib, pt as formatDistanceToNow, r as require_qr_code_styling, t as require_dist, vn as require_jsQR, wn as init_source, xn as require_express, yn as require_jpeg_js } from "./vendor-DxfKHvj-.js";
3
- import { n as init_client, r as reactServerRender } from "./template-DekmxKd7.js";
2
+ import { A as init_locale, An as init_zod, Cn as Chalk, Dn as axios_default, En as init_axios, On as Xhshow, Sn as require_protobufjs, Tn as AxiosError$1, _n as require_png, a as Window, bn as require_heic_decode, dt as init_date_fns, ft as fromUnixTime, ht as differenceInSeconds, i as init_lib, j as zhCN, jn as zod_default, kn as init_dist, mt as format, n as require_lib, pt as formatDistanceToNow, r as require_qr_code_styling, t as require_dist, vn as require_jsQR, wn as init_source, xn as require_express, yn as require_jpeg_js } from "./vendor-rZ5rQnq1.js";
3
+ import { i as renderVideoPreviewPage, n as init_client, r as reactServerRender } from "./template-B7RenK2I.js";
4
4
  import { createRequire } from "node:module";
5
5
  import karin$1, { BOT_CONNECT, app, authMiddleware, checkPkgUpdate, checkPort, common, components, config, copyConfigSync, createBadRequestResponse, createNotFoundResponse, createServerErrorResponse, createSuccessResponse, db, defineConfig, ffmpeg, ffprobe, filesByExt, getBot, hooks, karin, karinPathHtml, karinPathTemp, logger, logs, mkdirSync, range, render, requireFileSync, restart, segment, updatePkg, watch } from "node-karin";
6
6
  import fs from "node:fs";
@@ -17,7 +17,6 @@ import axios, { AxiosError } from "node-karin/axios";
17
17
  import { Transform } from "node:stream";
18
18
  import { pipeline } from "node:stream/promises";
19
19
  import express from "node-karin/express";
20
- import template from "node-karin/template";
21
20
  import _ from "node-karin/lodash";
22
21
  import { snapka } from "@snapka/puppeteer";
23
22
  import { newInjectedPage } from "fingerprint-injector";
@@ -5767,7 +5766,7 @@ var init_src = __esmMin(() => {
5767
5766
  init_server();
5768
5767
  init_types$1();
5769
5768
  init_api_spec();
5770
- getVersion = () => "6.0.0-beta.3";
5769
+ getVersion = () => "6.0.0";
5771
5770
  VERSION = getVersion();
5772
5771
  Object.defineProperty(CreateAmagiApp, "version", {
5773
5772
  value: VERSION,
@@ -6228,11 +6227,12 @@ var init_Base = __esmMin(() => {
6228
6227
  throw error;
6229
6228
  } finally {
6230
6229
  const filePath = file.filepath;
6230
+ Common.registerVideoPreview(filePath, Config.app.removeCache, 1800 * 1e3);
6231
6231
  logger.mark(`临时预览地址:http://localhost:${process.env.HTTP_PORT}/api/kkk/video/${encodeURIComponent(filePath.split("/").pop() ?? "")}`);
6232
- Config.app.removeCache && logger.info(`文件 ${filePath} 将在 10 分钟后删除`);
6232
+ Config.app.removeCache && logger.info(`文件 ${filePath} 将在 30 分钟后删除`);
6233
6233
  setTimeout(async () => {
6234
- await Common.removeFile(filePath);
6235
- }, 600 * 1e3);
6234
+ if (await Common.removeFile(filePath)) Common.markVideoPreviewRemoved(filePath);
6235
+ }, 1800 * 1e3);
6236
6236
  }
6237
6237
  };
6238
6238
  downloadVideo = async (event, downloadOpt, uploadOpt) => {
@@ -6842,12 +6842,14 @@ var init_Common = __esmMin(async () => {
6842
6842
  await init_module();
6843
6843
  Tools = class {
6844
6844
  tempDri;
6845
+ videoPreviewState;
6845
6846
  constructor() {
6846
6847
  this.tempDri = {
6847
6848
  default: `${karinPathTemp$1}/${Root.pluginName}/`.replace(/\\/g, "/"),
6848
6849
  video: `${karinPathTemp$1}/${Root.pluginName}/kkkdownload/video/`.replace(/\\/g, "/"),
6849
6850
  images: `${karinPathTemp$1}/${Root.pluginName}/kkkdownload/images/`.replace(/\\/g, "/")
6850
6851
  };
6852
+ this.videoPreviewState = /* @__PURE__ */ new Map();
6851
6853
  }
6852
6854
  async getReplyMessage(e) {
6853
6855
  if (e.replyId) {
@@ -6948,6 +6950,33 @@ var init_Common = __esmMin(async () => {
6948
6950
  }
6949
6951
  return true;
6950
6952
  }
6953
+ registerVideoPreview(filePath, removeCache, ttlMs) {
6954
+ const filename = path.basename(filePath);
6955
+ const createdAt = Date.now();
6956
+ const info = {
6957
+ filename,
6958
+ filePath,
6959
+ createdAt,
6960
+ expireAt: removeCache ? createdAt + ttlMs : void 0,
6961
+ removeCache
6962
+ };
6963
+ this.videoPreviewState.set(filename, info);
6964
+ return info;
6965
+ }
6966
+ getVideoPreview(filename) {
6967
+ return this.videoPreviewState.get(filename) ?? null;
6968
+ }
6969
+ markVideoPreviewRemoved(filePathOrFilename) {
6970
+ const filename = filePathOrFilename.includes("/") || filePathOrFilename.includes("\\") ? path.basename(filePathOrFilename) : filePathOrFilename;
6971
+ const info = this.videoPreviewState.get(filename);
6972
+ if (!info) return null;
6973
+ const updated = {
6974
+ ...info,
6975
+ removedAt: Date.now()
6976
+ };
6977
+ this.videoPreviewState.set(filename, updated);
6978
+ return updated;
6979
+ }
6951
6980
  useDarkTheme() {
6952
6981
  let dark = true;
6953
6982
  const configTheme = Config.app.Theme;
@@ -11175,28 +11204,57 @@ var init_app_schema = __esmMin(() => {
11175
11204
  type: "divider",
11176
11205
  title: "Live Photo 兼容设置"
11177
11206
  },
11207
+ {
11208
+ key: "livePhotoMode",
11209
+ type: "radio",
11210
+ label: "Live Photo 处理和发送方式",
11211
+ description: "解析遇到实况图时的处理和发送方式。注意:生成视频性能开销大,2C2G 服务器单张约需 20 秒",
11212
+ orientation: "horizontal",
11213
+ options: [
11214
+ {
11215
+ label: "视频 + 实况图",
11216
+ value: "video_and_livephoto",
11217
+ description: "生成并发送仿 iPhone Live Photo 播放效果的视频(播放三次)+ 对应系统的实况图"
11218
+ },
11219
+ {
11220
+ label: "仅视频",
11221
+ value: "video_only",
11222
+ description: "仅生成并发送仿 iPhone Live Photo 播放效果的视频(播放三次)"
11223
+ },
11224
+ {
11225
+ label: "仅实况图",
11226
+ value: "livephoto_only",
11227
+ description: "仅生成并发送对应系统的实况图,性能开销小"
11228
+ }
11229
+ ]
11230
+ },
11178
11231
  {
11179
11232
  key: "livePhotoSystem",
11180
11233
  type: "radio",
11181
11234
  label: "Live Photo 静态图兼容系统",
11182
- description: "当解析到作品/动态包含 Live Photo 时,合并转发里发送的 Live Photo 静态图按所选系统生成",
11235
+ description: "当解析到作品/动态包含 Live Photo 时,合并转发里发送的 Live Photo 静态图按所选系统生成。推荐 OPPO,兼容性最广",
11183
11236
  orientation: "horizontal",
11237
+ disabled: $ne("livePhotoMode", "livephoto_only"),
11184
11238
  options: [
11185
11239
  {
11186
11240
  label: "Google",
11187
- value: "google"
11241
+ value: "google",
11242
+ description: "Google Motion Photo 格式"
11188
11243
  },
11189
11244
  {
11190
11245
  label: "小米(HyperOS)",
11191
- value: "xiaomi"
11246
+ value: "xiaomi",
11247
+ description: "兼容小米(任何版本)和 Google,但无法被 OPPO 识别"
11192
11248
  },
11193
11249
  {
11194
11250
  label: "OPPO(ColorOS)",
11195
- value: "oppo"
11251
+ value: "oppo",
11252
+ description: "推荐,兼容 OPPO、小米(较新版本)和 Google"
11196
11253
  },
11197
11254
  {
11198
11255
  label: "华为/荣耀(HarmonyOS/MagicOS)",
11199
- value: "huawei_honor"
11256
+ value: "huawei_honor",
11257
+ description: "理论可行但未实测"
11200
11258
  }
11201
11259
  ]
11202
11260
  },
@@ -12882,11 +12940,18 @@ var init_api = __esmMin(async () => {
12882
12940
  apiRouter.use("/platforms/douyin", ...authMiddlewares, router$1);
12883
12941
  apiRouter.use("/platforms/bilibili", ...authMiddlewares, router);
12884
12942
  });
12885
- var videoStreamRouter, getVideoRouter;
12943
+ var videoStreamRouter, getVideoRouter, videoPreviewEventsRouter;
12886
12944
  var init_router = __esmMin(async () => {
12945
+ await init_client();
12887
12946
  await init_utils$1();
12947
+ await init_Config();
12888
12948
  videoStreamRouter = (req, res) => {
12889
- const filename = req.params.filename;
12949
+ const filenameParam = req.params.filename;
12950
+ const filename = Array.isArray(filenameParam) ? filenameParam[0] : filenameParam;
12951
+ if (!filename) {
12952
+ createNotFoundResponse(res, "无效的文件名");
12953
+ return;
12954
+ }
12890
12955
  const videoPath = Common.validateVideoRequest(filename, res);
12891
12956
  if (!videoPath) return;
12892
12957
  try {
@@ -12949,17 +13014,76 @@ var init_router = __esmMin(async () => {
12949
13014
  }
12950
13015
  };
12951
13016
  getVideoRouter = (req, res) => {
12952
- const filename = req.params.filename;
12953
- if (!Common.validateVideoRequest(filename, res)) return;
13017
+ const filenameParam = req.params.filename;
13018
+ const filename = Array.isArray(filenameParam) ? filenameParam[0] : filenameParam;
13019
+ if (!filename) {
13020
+ createNotFoundResponse(res, "无效的文件名");
13021
+ return;
13022
+ }
13023
+ const videoPath = Common.validateVideoRequest(filename, res);
13024
+ if (!videoPath) return;
12954
13025
  const videoDataUrl = `/api/kkk/stream/${encodeURIComponent(filename)}`;
12955
- const resPath = path.join(Root.pluginPath, "/resources") + "/".replace(/\\/g, "/");
12956
- const htmlContent = template(path.join(resPath, "template", "videoView", "index.html"), {
12957
- videoDataUrl,
12958
- filename
13026
+ const previewInfo = Common.getVideoPreview(filename);
13027
+ const removeCache = previewInfo?.removeCache ?? Config.app.removeCache;
13028
+ const createdAt = previewInfo?.createdAt ?? Date.now();
13029
+ const expireAt = previewInfo?.expireAt ?? (removeCache ? createdAt + 600 * 1e3 : void 0);
13030
+ const htmlContent = renderVideoPreviewPage({
13031
+ filename,
13032
+ filePath: previewInfo?.filePath ?? videoPath,
13033
+ videoUrl: videoDataUrl,
13034
+ removeCache,
13035
+ createdAt,
13036
+ expireAt,
13037
+ eventsUrl: `/api/kkk/video/${encodeURIComponent(filename)}/events`
12959
13038
  });
13039
+ res.setHeader("Cache-Control", "no-cache");
12960
13040
  res.setHeader("Content-Type", "text/html; charset=utf-8");
12961
13041
  res.send(htmlContent);
12962
13042
  };
13043
+ videoPreviewEventsRouter = (req, res) => {
13044
+ const filenameParam = req.params.filename;
13045
+ const filename = Array.isArray(filenameParam) ? filenameParam[0] : filenameParam;
13046
+ if (!filename) {
13047
+ createNotFoundResponse(res, "无效的文件名");
13048
+ return;
13049
+ }
13050
+ if (path.basename(filename) !== filename || filename.includes("/") || filename.includes("\\")) {
13051
+ createNotFoundResponse(res, "无效的文件名");
13052
+ return;
13053
+ }
13054
+ const previewInfo = Common.getVideoPreview(filename);
13055
+ if (!previewInfo) {
13056
+ createNotFoundResponse(res, "预览信息不存在");
13057
+ return;
13058
+ }
13059
+ res.setHeader("Content-Type", "text/event-stream");
13060
+ res.setHeader("Cache-Control", "no-cache");
13061
+ res.setHeader("Connection", "keep-alive");
13062
+ res.flushHeaders?.();
13063
+ const sendPayload = () => {
13064
+ const now = Date.now();
13065
+ const remainingMs = previewInfo.expireAt ? Math.max(previewInfo.expireAt - now, 0) : null;
13066
+ const fileMissing = previewInfo.filePath ? !fs.existsSync(previewInfo.filePath) : false;
13067
+ const removed = Boolean(previewInfo.removedAt) || previewInfo.removeCache && remainingMs === 0 && fileMissing;
13068
+ if (removed && !previewInfo.removedAt) Common.markVideoPreviewRemoved(previewInfo.filename);
13069
+ const payload = {
13070
+ filename: previewInfo.filename,
13071
+ filePath: previewInfo.filePath,
13072
+ removeCache: previewInfo.removeCache,
13073
+ createdAt: previewInfo.createdAt,
13074
+ expireAt: previewInfo.expireAt,
13075
+ remainingMs,
13076
+ removed,
13077
+ serverNow: now
13078
+ };
13079
+ res.write(`data: ${JSON.stringify(payload)}\n\n`);
13080
+ };
13081
+ sendPayload();
13082
+ const timer = setInterval(sendPayload, 1e3);
13083
+ res.on("close", () => {
13084
+ clearInterval(timer);
13085
+ });
13086
+ };
12963
13087
  });
12964
13088
  var import_lib, import_dist, server, proxyOptions, app$1;
12965
13089
  var init_Register = __esmMin(async () => {
@@ -12998,6 +13122,7 @@ var init_Register = __esmMin(async () => {
12998
13122
  } }).startServer(Config.app.APIServerPort);
12999
13123
  app$1.get("/stream/:filename", videoStreamRouter);
13000
13124
  app$1.get("/video/:filename", getVideoRouter);
13125
+ app$1.get("/video/:filename/events", videoPreviewEventsRouter);
13001
13126
  app$1.use("/v1", apiRouter);
13002
13127
  app.use("/api/kkk", app$1);
13003
13128
  });
@@ -13916,7 +14041,7 @@ const DouyinWeb = (all) => [components.accordion.create("douyin", {
13916
14041
  label: "合辑 Live 图 BGM 合并方式",
13917
14042
  orientation: "horizontal",
13918
14043
  defaultValue: all.douyin.liveImageMergeMode.toString(),
13919
- isDisabled: !all.douyin.switch,
14044
+ isDisabled: !all.douyin.switch || all.app.livePhotoMode === "livephoto_only",
13920
14045
  radio: [components.radio.create("liveImageMergeMode:radio-1", {
13921
14046
  label: "连续",
13922
14047
  value: "continuous",
@@ -14584,27 +14709,67 @@ const webConfig = defineConfig({
14584
14709
  description: "Live Photo 兼容设置",
14585
14710
  descPosition: 20
14586
14711
  }),
14712
+ components.radio.group("livePhotoMode", {
14713
+ label: "Live Photo 处理和发送方式",
14714
+ description: "解析遇到实况图时的处理和发送方式。注意:生成视频性能开销大,2C2G 服务器单张约需 20 秒",
14715
+ orientation: "horizontal",
14716
+ defaultValue: all.app.livePhotoMode || "video_and_livephoto",
14717
+ radio: [
14718
+ components.radio.create("livePhotoMode-video-and-livephoto", {
14719
+ label: "视频 + 实况图",
14720
+ description: "生成并发送仿 iPhone Live Photo 播放效果的视频(播放三次)+ 对应系统的实况图",
14721
+ value: "video_and_livephoto"
14722
+ }),
14723
+ components.radio.create("livePhotoMode-video-only", {
14724
+ label: "仅视频",
14725
+ description: "仅生成并发送仿 iPhone Live Photo 播放效果的视频(播放三次)",
14726
+ value: "video_only"
14727
+ }),
14728
+ components.radio.create("livePhotoMode-livephoto-only", {
14729
+ label: "仅实况图",
14730
+ description: "仅生成并发送对应系统的实况图,性能开销小",
14731
+ value: "livephoto_only"
14732
+ })
14733
+ ]
14734
+ }),
14587
14735
  components.radio.group("livePhotoSystem", {
14588
14736
  label: "Live Photo 静态图兼容系统",
14589
- description: "当解析到作品/动态包含 Live Photo 时,合并转发里发送的 Live Photo 静态图按所选系统生成",
14737
+ description: "当解析到作品/动态包含 Live Photo 时,合并转发里发送的 Live Photo 静态图按所选系统生成。推荐 OPPO,兼容性最广",
14590
14738
  orientation: "horizontal",
14591
- defaultValue: all.app.livePhotoSystem || "google",
14739
+ defaultValue: all.app.livePhotoSystem || "oppo",
14740
+ isDisabled: all.app.livePhotoMode === "video_only",
14592
14741
  radio: [
14593
14742
  components.radio.create("livePhotoSystem-google", {
14594
14743
  label: "Google",
14744
+ description: "Google Motion Photo 格式",
14595
14745
  value: "google"
14596
14746
  }),
14597
14747
  components.radio.create("livePhotoSystem-xiaomi", {
14598
14748
  label: "小米(HyperOS)",
14749
+ description: "兼容小米(任何版本)和 Google,但无法被 OPPO 识别",
14599
14750
  value: "xiaomi"
14600
14751
  }),
14601
14752
  components.radio.create("livePhotoSystem-oppo", {
14602
14753
  label: "OPPO(ColorOS)",
14754
+ description: "推荐,兼容 OPPO、小米(较新版本)和 Google",
14603
14755
  value: "oppo"
14604
14756
  }),
14605
14757
  components.radio.create("livePhotoSystem-huawei-honor", {
14606
14758
  label: "华为/荣耀(HarmonyOS/MagicOS)",
14759
+ description: "理论可行但未实测(作者无对应设备)",
14607
14760
  value: "huawei_honor"
14761
+ }),
14762
+ components.radio.create("livePhotoSystem-vivo", {
14763
+ label: "vivo(Origin OS)",
14764
+ description: "需要独立的图片和同名视频文件,暂不支持",
14765
+ value: "vivo",
14766
+ isDisabled: true
14767
+ }),
14768
+ components.radio.create("livePhotoSystem-iphone", {
14769
+ label: "iPhone(iOS)",
14770
+ description: "需要独立的图片和同名视频文件,暂不支持",
14771
+ value: "iphone",
14772
+ isDisabled: true
14608
14773
  })
14609
14774
  ]
14610
14775
  }),
@@ -15963,7 +16128,6 @@ var Bilibili = class extends Base {
15963
16128
  });
15964
16129
  if (livePhoto.filepath) {
15965
16130
  const outputPath = Common.tempDri.video + `Bilibili_Live_${Date.now()}_${index}.mp4`;
15966
- let success;
15967
16131
  const staticImg = await downloadFile(img$2.url, {
15968
16132
  title: `Bilibili_static_${Date.now()}_${index}.jpg`,
15969
16133
  headers: BASE_HEADERS,
@@ -15973,28 +16137,34 @@ var Bilibili = class extends Base {
15973
16137
  filepath: staticImg.filepath,
15974
16138
  totalBytes: 0
15975
16139
  });
16140
+ const livePhotoMode = Config.app.livePhotoMode ?? "video_and_livephoto";
16141
+ const shouldGenerateVideo = livePhotoMode === "video_and_livephoto" || livePhotoMode === "video_only";
16142
+ const shouldGenerateLivePhoto = livePhotoMode === "video_and_livephoto" || livePhotoMode === "livephoto_only";
15976
16143
  const loopCount = 3;
15977
16144
  if (!staticImg.filepath) {
15978
16145
  await Common.removeFile(livePhoto.filepath, true);
15979
16146
  continue;
15980
16147
  }
15981
- success = (await loopVideoWithTransition({
15982
- inputPath: livePhoto.filepath,
15983
- outputPath,
15984
- loopCount,
15985
- staticImagePath: staticImg.filepath,
15986
- transitionEnabled: loopCount > 1
15987
- })).success;
15988
- if (success) {
15989
- const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
15990
- fs.renameSync(outputPath, filePath);
15991
- logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
15992
- temp.push({
15993
- filepath: filePath,
15994
- totalBytes: 0
15995
- });
15996
- const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
15997
- imgArray.push(segment.video(videoPath));
16148
+ if (shouldGenerateVideo) {
16149
+ if ((await loopVideoWithTransition({
16150
+ inputPath: livePhoto.filepath,
16151
+ outputPath,
16152
+ loopCount,
16153
+ staticImagePath: staticImg.filepath,
16154
+ transitionEnabled: loopCount > 1
16155
+ })).success) {
16156
+ const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
16157
+ fs.renameSync(outputPath, filePath);
16158
+ logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
16159
+ temp.push({
16160
+ filepath: filePath,
16161
+ totalBytes: 0
16162
+ });
16163
+ const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
16164
+ imgArray.push(segment.video(videoPath));
16165
+ }
16166
+ }
16167
+ if (shouldGenerateLivePhoto) {
15998
16168
  let hasPushedMotionPhotoCover = false;
15999
16169
  if (staticImg.filepath) {
16000
16170
  const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(/* @__PURE__ */ new Date(), "yyyyMMdd_HHmmss_SSS")}_${index}.jpg`;
@@ -16016,9 +16186,9 @@ var Bilibili = class extends Base {
16016
16186
  const imageUrl = await processImageUrl(img$2.url, title, index);
16017
16187
  imgArray.push(segment.image(imageUrl));
16018
16188
  }
16019
- logger.mark("正在尝试删除缓存文件");
16020
- await Common.removeFile(livePhoto.filepath, true);
16021
- } else await Common.removeFile(livePhoto.filepath, true);
16189
+ }
16190
+ logger.mark("正在尝试删除缓存文件");
16191
+ await Common.removeFile(livePhoto.filepath, true);
16022
16192
  }
16023
16193
  } else {
16024
16194
  const imageUrl = await processImageUrl(img$2.url, title, index);
@@ -17806,27 +17976,34 @@ var Bilibilipush = class extends Base {
17806
17976
  filepath: staticImg.filepath,
17807
17977
  totalBytes: 0
17808
17978
  });
17979
+ const livePhotoMode = Config.app.livePhotoMode ?? "video_and_livephoto";
17980
+ const shouldGenerateVideo = livePhotoMode === "video_and_livephoto" || livePhotoMode === "video_only";
17981
+ const shouldGenerateLivePhoto = livePhotoMode === "video_and_livephoto" || livePhotoMode === "livephoto_only";
17809
17982
  const loopCount = 3;
17810
17983
  if (!staticImg.filepath) {
17811
17984
  await Common.removeFile(livePhoto.filepath, true);
17812
17985
  continue;
17813
17986
  }
17814
- if ((await loopVideoWithTransition({
17815
- inputPath: livePhoto.filepath,
17816
- outputPath,
17817
- loopCount,
17818
- staticImagePath: staticImg.filepath,
17819
- transitionEnabled: loopCount > 1
17820
- })).success) {
17821
- const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
17822
- fs.renameSync(outputPath, filePath);
17823
- logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
17824
- temp.push({
17825
- filepath: filePath,
17826
- totalBytes: 0
17827
- });
17828
- const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
17829
- imgArray.push(segment.video(videoPath));
17987
+ if (shouldGenerateVideo) {
17988
+ if ((await loopVideoWithTransition({
17989
+ inputPath: livePhoto.filepath,
17990
+ outputPath,
17991
+ loopCount,
17992
+ staticImagePath: staticImg.filepath,
17993
+ transitionEnabled: loopCount > 1
17994
+ })).success) {
17995
+ const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
17996
+ fs.renameSync(outputPath, filePath);
17997
+ logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
17998
+ temp.push({
17999
+ filepath: filePath,
18000
+ totalBytes: 0
18001
+ });
18002
+ const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
18003
+ imgArray.push(segment.video(videoPath));
18004
+ }
18005
+ }
18006
+ if (shouldGenerateLivePhoto) {
17830
18007
  let hasPushedMotionPhotoCover = false;
17831
18008
  if (staticImg.filepath) {
17832
18009
  const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${Date.now()}_${index}.jpg`;
@@ -17848,11 +18025,10 @@ var Bilibilipush = class extends Base {
17848
18025
  const imageUrl = await processImageUrl(imageSrc, title, index);
17849
18026
  imgArray.push(segment.image(imageUrl));
17850
18027
  }
17851
- logger.mark("正在尝试删除缓存文件");
17852
- await Common.removeFile(livePhoto.filepath, true);
17853
- continue;
17854
18028
  }
18029
+ logger.mark("正在尝试删除缓存文件");
17855
18030
  await Common.removeFile(livePhoto.filepath, true);
18031
+ continue;
17856
18032
  }
17857
18033
  }
17858
18034
  if (imageSrc) {
@@ -18733,56 +18909,61 @@ var DouYin = class extends Base {
18733
18909
  });
18734
18910
  staticImgPath = staticImg.filepath ?? "";
18735
18911
  }
18736
- const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
18737
- const safeStaticPath = staticImgPath || liveimg.filepath;
18738
- const result = await loopVideoWithTransition({
18739
- inputPath: liveimg.filepath,
18740
- outputPath,
18741
- loopCount,
18742
- staticImagePath: safeStaticPath,
18743
- transitionEnabled,
18744
- bgmPath: liveimgbgm?.filepath,
18745
- mergeMode,
18746
- context: bgmContext ?? void 0
18747
- });
18748
- const success = result.success;
18749
- if (mergeMode === "continuous" && result.context) bgmContext = result.context;
18750
- if (success) {
18751
- const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
18752
- fs.renameSync(outputPath, filePath);
18753
- logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
18754
- temp.push({
18755
- filepath: filePath,
18756
- totalBytes: 0
18912
+ const livePhotoMode = Config.app.livePhotoMode ?? "video_and_livephoto";
18913
+ const shouldGenerateVideo = livePhotoMode === "video_and_livephoto" || livePhotoMode === "video_only";
18914
+ const shouldGenerateLivePhoto = livePhotoMode === "video_and_livephoto" || livePhotoMode === "livephoto_only";
18915
+ if (shouldGenerateVideo) {
18916
+ const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
18917
+ const safeStaticPath = staticImgPath || liveimg.filepath;
18918
+ const result = await loopVideoWithTransition({
18919
+ inputPath: liveimg.filepath,
18920
+ outputPath,
18921
+ loopCount,
18922
+ staticImagePath: safeStaticPath,
18923
+ transitionEnabled,
18924
+ bgmPath: liveimgbgm?.filepath,
18925
+ mergeMode,
18926
+ context: bgmContext ?? void 0
18757
18927
  });
18758
- const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
18759
- processedImages.push(segment.video(videoPath));
18760
- if (imageItem.clip_type === 5 && imageItem.url_list?.[0]) {
18761
- let hasPushedMotionPhotoCover = false;
18762
- if (staticImgPath) {
18763
- const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(/* @__PURE__ */ new Date(), "yyyyMMdd_HHmmss_SSS")}_${index}.jpg`;
18764
- if (await buildGoogleMotionPhoto({
18765
- imagePath: staticImgPath,
18766
- videoPath: liveimg.filepath,
18767
- outputPath: motionPhotoCoverPath
18768
- })) {
18769
- temp.push({
18770
- filepath: motionPhotoCoverPath,
18771
- totalBytes: 0
18772
- });
18773
- const motionPhotoCover = Config.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
18774
- processedImages.push(segment.image(motionPhotoCover));
18775
- hasPushedMotionPhotoCover = true;
18776
- }
18777
- }
18778
- if (!hasPushedMotionPhotoCover) {
18779
- const imageUrl = await processImageUrl(imageItem.url_list[0], g_title, index);
18780
- processedImages.push(segment.image(imageUrl));
18928
+ const success = result.success;
18929
+ if (mergeMode === "continuous" && result.context) bgmContext = result.context;
18930
+ if (success) {
18931
+ const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
18932
+ fs.renameSync(outputPath, filePath);
18933
+ logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
18934
+ temp.push({
18935
+ filepath: filePath,
18936
+ totalBytes: 0
18937
+ });
18938
+ const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
18939
+ processedImages.push(segment.video(videoPath));
18940
+ }
18941
+ }
18942
+ if (shouldGenerateLivePhoto && imageItem.clip_type === 5 && imageItem.url_list?.[0]) {
18943
+ let hasPushedMotionPhotoCover = false;
18944
+ if (staticImgPath) {
18945
+ const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(/* @__PURE__ */ new Date(), "yyyyMMdd_HHmmss_SSS")}_${index}.jpg`;
18946
+ if (await buildGoogleMotionPhoto({
18947
+ imagePath: staticImgPath,
18948
+ videoPath: liveimg.filepath,
18949
+ outputPath: motionPhotoCoverPath
18950
+ })) {
18951
+ temp.push({
18952
+ filepath: motionPhotoCoverPath,
18953
+ totalBytes: 0
18954
+ });
18955
+ const motionPhotoCover = Config.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
18956
+ processedImages.push(segment.image(motionPhotoCover));
18957
+ hasPushedMotionPhotoCover = true;
18781
18958
  }
18782
18959
  }
18783
- logger.mark("正在尝试删除缓存文件");
18784
- await Common.removeFile(liveimg.filepath, true);
18785
- } else await Common.removeFile(liveimg.filepath, true);
18960
+ if (!hasPushedMotionPhotoCover) {
18961
+ const imageUrl = await processImageUrl(imageItem.url_list[0], g_title, index);
18962
+ processedImages.push(segment.image(imageUrl));
18963
+ }
18964
+ }
18965
+ logger.mark("正在尝试删除缓存文件");
18966
+ await Common.removeFile(liveimg.filepath, true);
18786
18967
  }
18787
18968
  }
18788
18969
  const Element = common.makeForward(processedImages, Config.app.fakeForward ? this.e.sender.userId : this.e.bot.account.selfId, Config.app.fakeForward ? this.e.sender.nick : this.e.bot.account.name);
@@ -18873,56 +19054,61 @@ var DouYin = class extends Base {
18873
19054
  });
18874
19055
  staticImgPath = staticImg.filepath ?? "";
18875
19056
  }
18876
- const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
18877
- const safeStaticPath = staticImgPath || livePhoto.filepath;
18878
- const result = await loopVideoWithTransition({
18879
- inputPath: livePhoto.filepath,
18880
- outputPath,
18881
- loopCount,
18882
- staticImagePath: safeStaticPath,
18883
- transitionEnabled,
18884
- bgmPath: liveimgbgm?.filepath,
18885
- mergeMode,
18886
- context: bgmContext ?? void 0
18887
- });
18888
- const success = result.success;
18889
- if (mergeMode === "continuous" && result.context) bgmContext = result.context;
18890
- if (success) {
18891
- const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
18892
- fs.renameSync(outputPath, filePath);
18893
- logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
18894
- temp.push({
18895
- filepath: filePath,
18896
- totalBytes: 0
19057
+ const livePhotoMode = Config.app.livePhotoMode ?? "video_and_livephoto";
19058
+ const shouldGenerateVideo = livePhotoMode === "video_and_livephoto" || livePhotoMode === "video_only";
19059
+ const shouldGenerateLivePhoto = livePhotoMode === "video_and_livephoto" || livePhotoMode === "livephoto_only";
19060
+ if (shouldGenerateVideo) {
19061
+ const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
19062
+ const safeStaticPath = staticImgPath || livePhoto.filepath;
19063
+ const result = await loopVideoWithTransition({
19064
+ inputPath: livePhoto.filepath,
19065
+ outputPath,
19066
+ loopCount,
19067
+ staticImagePath: safeStaticPath,
19068
+ transitionEnabled,
19069
+ bgmPath: liveimgbgm?.filepath,
19070
+ mergeMode,
19071
+ context: bgmContext ?? void 0
18897
19072
  });
18898
- const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
18899
- images.push(segment.video(videoPath));
18900
- if (item.clip_type === 5 && item.url_list?.[0]) {
18901
- let hasPushedMotionPhotoCover = false;
18902
- if (staticImgPath) {
18903
- const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(/* @__PURE__ */ new Date(), "yyyyMMdd_HHmmss_SSS")}_${index}.jpg`;
18904
- if (await buildGoogleMotionPhoto({
18905
- imagePath: staticImgPath,
18906
- videoPath: livePhoto.filepath,
18907
- outputPath: motionPhotoCoverPath
18908
- })) {
18909
- temp.push({
18910
- filepath: motionPhotoCoverPath,
18911
- totalBytes: 0
18912
- });
18913
- const motionPhotoCover = Config.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
18914
- images.push(segment.image(motionPhotoCover));
18915
- hasPushedMotionPhotoCover = true;
18916
- }
18917
- }
18918
- if (!hasPushedMotionPhotoCover) {
18919
- const imageUrl = await processImageUrl(item.url_list[0], g_title, index);
18920
- images.push(segment.image(imageUrl));
19073
+ const success = result.success;
19074
+ if (mergeMode === "continuous" && result.context) bgmContext = result.context;
19075
+ if (success) {
19076
+ const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
19077
+ fs.renameSync(outputPath, filePath);
19078
+ logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
19079
+ temp.push({
19080
+ filepath: filePath,
19081
+ totalBytes: 0
19082
+ });
19083
+ const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
19084
+ images.push(segment.video(videoPath));
19085
+ }
19086
+ }
19087
+ if (shouldGenerateLivePhoto && item.clip_type === 5 && item.url_list?.[0]) {
19088
+ let hasPushedMotionPhotoCover = false;
19089
+ if (staticImgPath) {
19090
+ const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(/* @__PURE__ */ new Date(), "yyyyMMdd_HHmmss_SSS")}_${index}.jpg`;
19091
+ if (await buildGoogleMotionPhoto({
19092
+ imagePath: staticImgPath,
19093
+ videoPath: livePhoto.filepath,
19094
+ outputPath: motionPhotoCoverPath
19095
+ })) {
19096
+ temp.push({
19097
+ filepath: motionPhotoCoverPath,
19098
+ totalBytes: 0
19099
+ });
19100
+ const motionPhotoCover = Config.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
19101
+ images.push(segment.image(motionPhotoCover));
19102
+ hasPushedMotionPhotoCover = true;
18921
19103
  }
18922
19104
  }
18923
- logger.mark("正在尝试删除缓存文件");
18924
- await Common.removeFile(livePhoto.filepath, true);
18925
- } else await Common.removeFile(livePhoto.filepath, true);
19105
+ if (!hasPushedMotionPhotoCover) {
19106
+ const imageUrl = await processImageUrl(item.url_list[0], g_title, index);
19107
+ images.push(segment.image(imageUrl));
19108
+ }
19109
+ }
19110
+ logger.mark("正在尝试删除缓存文件");
19111
+ await Common.removeFile(livePhoto.filepath, true);
18926
19112
  }
18927
19113
  }
18928
19114
  const Element = common.makeForward(images, Config.app.fakeForward ? this.e.sender.userId : this.e.bot.account.selfId, Config.app.fakeForward ? this.e.sender.nick : this.e.bot.account.name);
@@ -20027,56 +20213,61 @@ var DouYinpush = class extends Base {
20027
20213
  });
20028
20214
  staticImgPath = staticImg.filepath ?? "";
20029
20215
  }
20030
- const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
20031
- const safeStaticPath = staticImgPath || liveimg.filepath;
20032
- const result = await loopVideoWithTransition({
20033
- inputPath: liveimg.filepath,
20034
- outputPath,
20035
- loopCount,
20036
- staticImagePath: safeStaticPath,
20037
- transitionEnabled,
20038
- bgmPath: liveimgbgm?.filepath,
20039
- mergeMode,
20040
- context: bgmContext ?? void 0
20041
- });
20042
- const success = result.success;
20043
- if (mergeMode === "continuous" && result.context) bgmContext = result.context;
20044
- if (success) {
20045
- const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
20046
- fs.renameSync(outputPath, filePath);
20047
- logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
20048
- temp.push({
20049
- filepath: filePath,
20050
- totalBytes: 0
20216
+ const livePhotoMode = Config.app.livePhotoMode ?? "video_and_livephoto";
20217
+ const shouldGenerateVideo = livePhotoMode === "video_and_livephoto" || livePhotoMode === "video_only";
20218
+ const shouldGenerateLivePhoto = livePhotoMode === "video_and_livephoto" || livePhotoMode === "livephoto_only";
20219
+ if (shouldGenerateVideo) {
20220
+ const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
20221
+ const safeStaticPath = staticImgPath || liveimg.filepath;
20222
+ const result = await loopVideoWithTransition({
20223
+ inputPath: liveimg.filepath,
20224
+ outputPath,
20225
+ loopCount,
20226
+ staticImagePath: safeStaticPath,
20227
+ transitionEnabled,
20228
+ bgmPath: liveimgbgm?.filepath,
20229
+ mergeMode,
20230
+ context: bgmContext ?? void 0
20051
20231
  });
20052
- const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
20053
- images.push(segment.video(videoPath));
20054
- if (item.clip_type === 5 && item.url_list?.[0]) {
20055
- let hasPushedMotionPhotoCover = false;
20056
- if (staticImgPath) {
20057
- const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(/* @__PURE__ */ new Date(), "yyyyMMdd_HHmmss_SSS")}_${index}.jpg`;
20058
- if (await buildGoogleMotionPhoto({
20059
- imagePath: staticImgPath,
20060
- videoPath: liveimg.filepath,
20061
- outputPath: motionPhotoCoverPath
20062
- })) {
20063
- temp.push({
20064
- filepath: motionPhotoCoverPath,
20065
- totalBytes: 0
20066
- });
20067
- const motionPhotoCover = Config.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
20068
- images.push(segment.image(motionPhotoCover));
20069
- hasPushedMotionPhotoCover = true;
20070
- }
20071
- }
20072
- if (!hasPushedMotionPhotoCover) {
20073
- const imageUrl = await processImageUrl(item.url_list[0], Detail_Data.desc, index);
20074
- images.push(segment.image(imageUrl));
20232
+ const success = result.success;
20233
+ if (mergeMode === "continuous" && result.context) bgmContext = result.context;
20234
+ if (success) {
20235
+ const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
20236
+ fs.renameSync(outputPath, filePath);
20237
+ logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
20238
+ temp.push({
20239
+ filepath: filePath,
20240
+ totalBytes: 0
20241
+ });
20242
+ const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
20243
+ images.push(segment.video(videoPath));
20244
+ }
20245
+ }
20246
+ if (shouldGenerateLivePhoto && item.clip_type === 5 && item.url_list?.[0]) {
20247
+ let hasPushedMotionPhotoCover = false;
20248
+ if (staticImgPath) {
20249
+ const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(/* @__PURE__ */ new Date(), "yyyyMMdd_HHmmss_SSS")}_${index}.jpg`;
20250
+ if (await buildGoogleMotionPhoto({
20251
+ imagePath: staticImgPath,
20252
+ videoPath: liveimg.filepath,
20253
+ outputPath: motionPhotoCoverPath
20254
+ })) {
20255
+ temp.push({
20256
+ filepath: motionPhotoCoverPath,
20257
+ totalBytes: 0
20258
+ });
20259
+ const motionPhotoCover = Config.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
20260
+ images.push(segment.image(motionPhotoCover));
20261
+ hasPushedMotionPhotoCover = true;
20075
20262
  }
20076
20263
  }
20077
- logger.mark("正在尝试删除缓存文件");
20078
- await Common.removeFile(liveimg.filepath, true);
20079
- } else await Common.removeFile(liveimg.filepath, true);
20264
+ if (!hasPushedMotionPhotoCover) {
20265
+ const imageUrl = await processImageUrl(item.url_list[0], Detail_Data.desc, index);
20266
+ images.push(segment.image(imageUrl));
20267
+ }
20268
+ }
20269
+ logger.mark("正在尝试删除缓存文件");
20270
+ await Common.removeFile(liveimg.filepath, true);
20080
20271
  }
20081
20272
  }
20082
20273
  const bot = karin$1.getBot(botId);
@@ -20135,56 +20326,61 @@ var DouYinpush = class extends Base {
20135
20326
  });
20136
20327
  staticImgPath = staticImg.filepath ?? "";
20137
20328
  }
20138
- const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
20139
- const safeStaticPath = staticImgPath || liveimg.filepath;
20140
- const result = await loopVideoWithTransition({
20141
- inputPath: liveimg.filepath,
20142
- outputPath,
20143
- loopCount,
20144
- staticImagePath: safeStaticPath,
20145
- transitionEnabled,
20146
- bgmPath: liveimgbgm?.filepath,
20147
- mergeMode,
20148
- context: bgmContext ?? void 0
20149
- });
20150
- const success = result.success;
20151
- if (mergeMode === "continuous" && result.context) bgmContext = result.context;
20152
- if (success) {
20153
- const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
20154
- fs.renameSync(outputPath, filePath);
20155
- logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
20156
- temp.push({
20157
- filepath: filePath,
20158
- totalBytes: 0
20329
+ const livePhotoMode = Config.app.livePhotoMode ?? "video_and_livephoto";
20330
+ const shouldGenerateVideo = livePhotoMode === "video_and_livephoto" || livePhotoMode === "video_only";
20331
+ const shouldGenerateLivePhoto = livePhotoMode === "video_and_livephoto" || livePhotoMode === "livephoto_only";
20332
+ if (shouldGenerateVideo) {
20333
+ const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
20334
+ const safeStaticPath = staticImgPath || liveimg.filepath;
20335
+ const result = await loopVideoWithTransition({
20336
+ inputPath: liveimg.filepath,
20337
+ outputPath,
20338
+ loopCount,
20339
+ staticImagePath: safeStaticPath,
20340
+ transitionEnabled,
20341
+ bgmPath: liveimgbgm?.filepath,
20342
+ mergeMode,
20343
+ context: bgmContext ?? void 0
20159
20344
  });
20160
- const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
20161
- processedImages.push(segment.video(videoPath));
20162
- if (item.clip_type === 5 && item.url_list?.[0]) {
20163
- let hasPushedMotionPhotoCover = false;
20164
- if (staticImgPath) {
20165
- const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(/* @__PURE__ */ new Date(), "yyyyMMdd_HHmmss_SSS")}_${index}.jpg`;
20166
- if (await buildGoogleMotionPhoto({
20167
- imagePath: staticImgPath,
20168
- videoPath: liveimg.filepath,
20169
- outputPath: motionPhotoCoverPath
20170
- })) {
20171
- temp.push({
20172
- filepath: motionPhotoCoverPath,
20173
- totalBytes: 0
20174
- });
20175
- const motionPhotoCover = Config.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
20176
- processedImages.push(segment.image(motionPhotoCover));
20177
- hasPushedMotionPhotoCover = true;
20178
- }
20179
- }
20180
- if (!hasPushedMotionPhotoCover) {
20181
- const imageUrl = await processImageUrl(item.url_list[0], Detail_Data.desc, index);
20182
- processedImages.push(segment.image(imageUrl));
20345
+ const success = result.success;
20346
+ if (mergeMode === "continuous" && result.context) bgmContext = result.context;
20347
+ if (success) {
20348
+ const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
20349
+ fs.renameSync(outputPath, filePath);
20350
+ logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
20351
+ temp.push({
20352
+ filepath: filePath,
20353
+ totalBytes: 0
20354
+ });
20355
+ const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
20356
+ processedImages.push(segment.video(videoPath));
20357
+ }
20358
+ }
20359
+ if (shouldGenerateLivePhoto && item.clip_type === 5 && item.url_list?.[0]) {
20360
+ let hasPushedMotionPhotoCover = false;
20361
+ if (staticImgPath) {
20362
+ const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(/* @__PURE__ */ new Date(), "yyyyMMdd_HHmmss_SSS")}_${index}.jpg`;
20363
+ if (await buildGoogleMotionPhoto({
20364
+ imagePath: staticImgPath,
20365
+ videoPath: liveimg.filepath,
20366
+ outputPath: motionPhotoCoverPath
20367
+ })) {
20368
+ temp.push({
20369
+ filepath: motionPhotoCoverPath,
20370
+ totalBytes: 0
20371
+ });
20372
+ const motionPhotoCover = Config.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
20373
+ processedImages.push(segment.image(motionPhotoCover));
20374
+ hasPushedMotionPhotoCover = true;
20183
20375
  }
20184
20376
  }
20185
- logger.mark("正在尝试删除缓存文件");
20186
- await Common.removeFile(liveimg.filepath, true);
20187
- } else await Common.removeFile(liveimg.filepath, true);
20377
+ if (!hasPushedMotionPhotoCover) {
20378
+ const imageUrl = await processImageUrl(item.url_list[0], Detail_Data.desc, index);
20379
+ processedImages.push(segment.image(imageUrl));
20380
+ }
20381
+ }
20382
+ logger.mark("正在尝试删除缓存文件");
20383
+ await Common.removeFile(liveimg.filepath, true);
20188
20384
  }
20189
20385
  }
20190
20386
  const bot = karin$1.getBot(botId);
@@ -20657,305 +20853,308 @@ var safeScreenshot = async (page, screenshotPath) => {
20657
20853
  };
20658
20854
  const douyinLogin = async (e) => {
20659
20855
  const msg_id = [];
20660
- try {
20661
- const puppeteer = await snapka.launch({
20662
- headless: "new",
20663
- protocolTimeout: 6e4,
20664
- args: [
20665
- "--disable-blink-features=AutomationControlled",
20666
- "--mute-audio",
20667
- "--window-size=800,600",
20668
- "--disable-gpu",
20669
- "--no-sandbox",
20670
- "--disable-setuid-sandbox",
20671
- "--no-zygote",
20672
- "--disable-extensions",
20673
- "--disable-dev-shm-usage",
20674
- "--disable-background-networking",
20675
- "--disable-sync",
20676
- "--disable-crash-reporter",
20677
- "--disable-translate",
20678
- "--disable-notifications",
20679
- "--disable-device-discovery-notifications",
20680
- "--disable-accelerated-2d-canvas",
20681
- "--autoplay-policy=user-gesture-required",
20682
- "--disable-web-security",
20683
- "--disable-features=IsolateOrigins,site-per-process",
20684
- "--disable-site-isolation-trials",
20685
- "--disable-features=VizDisplayCompositor",
20686
- "--js-flags=--max-old-space-size=128",
20687
- "--disable-software-rasterizer",
20688
- "--disable-webgl",
20689
- "--disable-webgl2",
20690
- "--disable-3d-apis",
20691
- "--disable-accelerated-video-decode",
20692
- "--disable-background-timer-throttling",
20693
- "--disable-backgrounding-occluded-windows",
20694
- "--disable-breakpad",
20695
- "--disable-component-extensions-with-background-pages",
20696
- "--disable-features=TranslateUI,BlinkGenPropertyTrees",
20697
- "--disable-ipc-flooding-protection",
20698
- "--disable-renderer-backgrounding"
20699
- ],
20700
- ignoreDefaultArgs: ["--enable-automation"]
20701
- });
20702
- const getOperatingSystem = () => {
20703
- const os$1 = platform();
20704
- if (os$1 === "win32") return "windows";
20705
- if (os$1 === "darwin") return "macos";
20706
- return "linux";
20856
+ const puppeteer = await snapka.launch({
20857
+ headless: "new",
20858
+ protocolTimeout: 6e4,
20859
+ args: [
20860
+ "--disable-blink-features=AutomationControlled",
20861
+ "--mute-audio",
20862
+ "--window-size=800,600",
20863
+ "--disable-gpu",
20864
+ "--no-sandbox",
20865
+ "--disable-setuid-sandbox",
20866
+ "--no-zygote",
20867
+ "--disable-extensions",
20868
+ "--disable-dev-shm-usage",
20869
+ "--disable-background-networking",
20870
+ "--disable-sync",
20871
+ "--disable-crash-reporter",
20872
+ "--disable-translate",
20873
+ "--disable-notifications",
20874
+ "--disable-device-discovery-notifications",
20875
+ "--disable-accelerated-2d-canvas",
20876
+ "--autoplay-policy=user-gesture-required",
20877
+ "--disable-web-security",
20878
+ "--disable-features=IsolateOrigins,site-per-process",
20879
+ "--disable-site-isolation-trials",
20880
+ "--disable-features=VizDisplayCompositor",
20881
+ "--js-flags=--max-old-space-size=128",
20882
+ "--disable-software-rasterizer",
20883
+ "--disable-webgl",
20884
+ "--disable-webgl2",
20885
+ "--disable-3d-apis",
20886
+ "--disable-accelerated-video-decode",
20887
+ "--disable-background-timer-throttling",
20888
+ "--disable-backgrounding-occluded-windows",
20889
+ "--disable-breakpad",
20890
+ "--disable-component-extensions-with-background-pages",
20891
+ "--disable-features=TranslateUI,BlinkGenPropertyTrees",
20892
+ "--disable-ipc-flooding-protection",
20893
+ "--disable-renderer-backgrounding"
20894
+ ],
20895
+ ignoreDefaultArgs: ["--enable-automation"]
20896
+ });
20897
+ const getOperatingSystem = () => {
20898
+ const os$1 = platform();
20899
+ if (os$1 === "win32") return "windows";
20900
+ if (os$1 === "darwin") return "macos";
20901
+ return "linux";
20902
+ };
20903
+ const page = await newInjectedPage(puppeteer.browser, { fingerprintOptions: {
20904
+ devices: ["desktop"],
20905
+ operatingSystems: [getOperatingSystem()]
20906
+ } });
20907
+ await page.setRequestInterception(true);
20908
+ page.on("request", async (request) => {
20909
+ const resourceType = request.resourceType();
20910
+ const url = request.url();
20911
+ if (url.includes("passport") || url.includes("login") || url.includes("qrconnect") || url.includes("qrcode")) logger.debug(`[请求] ${resourceType}: ${url}`);
20912
+ if (resourceType === "media" || resourceType === "font" || resourceType === "stylesheet" || /\.(mp4|webm|m3u8|flv|avi|mov|wmv|mkv)(\?|$)/i.test(url) || url.includes("/aweme/") || url.includes("/video/") || url.includes("v.douyin.com") || resourceType === "image" && !url.includes("qrcode") && !url.includes("data:image") && (url.includes(".jpg") || url.includes(".jpeg") || url.includes(".webp"))) {
20913
+ if (url.includes("passport") || url.includes("login") || url.includes("qrconnect")) logger.warn(`[拦截] 登录相关请求被拦截: ${url}`);
20914
+ request.abort();
20915
+ } else request.continue();
20916
+ });
20917
+ await page.evaluateOnNewDocument(() => {
20918
+ HTMLMediaElement.prototype.play = function() {
20919
+ return Promise.reject(/* @__PURE__ */ new Error("Video playback blocked"));
20707
20920
  };
20708
- const page = await newInjectedPage(puppeteer.browser, { fingerprintOptions: {
20709
- devices: ["desktop"],
20710
- operatingSystems: [getOperatingSystem()]
20711
- } });
20712
- await page.setRequestInterception(true);
20713
- page.on("request", async (request) => {
20714
- const resourceType = request.resourceType();
20715
- const url = request.url();
20716
- if (url.includes("passport") || url.includes("login") || url.includes("qrconnect") || url.includes("qrcode")) logger.debug(`[请求] ${resourceType}: ${url}`);
20717
- if (resourceType === "media" || resourceType === "font" || resourceType === "stylesheet" || /\.(mp4|webm|m3u8|flv|avi|mov|wmv|mkv)(\?|$)/i.test(url) || url.includes("/aweme/") || url.includes("/video/") || url.includes("v.douyin.com") || resourceType === "image" && !url.includes("qrcode") && !url.includes("data:image") && (url.includes(".jpg") || url.includes(".jpeg") || url.includes(".webp"))) {
20718
- if (url.includes("passport") || url.includes("login") || url.includes("qrconnect")) logger.warn(`[拦截] 登录相关请求被拦截: ${url}`);
20719
- request.abort();
20720
- } else request.continue();
20721
- });
20722
- await page.evaluateOnNewDocument(() => {
20723
- HTMLMediaElement.prototype.play = function() {
20724
- return Promise.reject(/* @__PURE__ */ new Error("Video playback blocked"));
20725
- };
20726
- if (window.MediaSource) window.MediaSource = void 0;
20727
- window.IntersectionObserver = class {
20728
- observe() {}
20729
- unobserve() {}
20730
- disconnect() {}
20731
- };
20732
- });
20733
- await page.goto("https://www.douyin.com", {
20734
- timeout: 12e4,
20735
- waitUntil: "domcontentloaded"
20736
- });
20737
- let qrCodeData;
20738
- try {
20739
- logger.mark("开始等待二维码加载...");
20740
- qrCodeData = await Promise.race([waitQrcode(page), new Promise((_$1, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("QR_CODE_TIMEOUT")), 6e4))]);
20741
- logger.mark("二维码获取成功");
20742
- } catch (error) {
20743
- if (error.message === "QR_CODE_TIMEOUT") {
20744
- logger.warn("获取二维码超时");
20745
- await safeScreenshot(page, path.join(karinPathTemp, Root.pluginName, "DouyinLoginQrcodeError.png"));
20746
- await e.reply("获取二维码超时,请稍后重试", { reply: true });
20747
- } else {
20748
- logger.error("获取二维码失败:", error);
20749
- await e.reply("获取二维码失败,请查看控制台日志", { reply: true });
20750
- }
20751
- await puppeteer.browser.close();
20752
- return true;
20921
+ if (window.MediaSource) window.MediaSource = void 0;
20922
+ window.IntersectionObserver = class {
20923
+ observe() {}
20924
+ unobserve() {}
20925
+ disconnect() {}
20926
+ };
20927
+ });
20928
+ await page.goto("https://www.douyin.com", {
20929
+ timeout: 12e4,
20930
+ waitUntil: "domcontentloaded"
20931
+ });
20932
+ let qrCodeData;
20933
+ try {
20934
+ logger.mark("开始等待二维码加载...");
20935
+ qrCodeData = await Promise.race([waitQrcode(page), new Promise((_$1, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("QR_CODE_TIMEOUT")), 6e4))]);
20936
+ logger.mark("二维码获取成功");
20937
+ } catch (error) {
20938
+ if (error.message === "QR_CODE_TIMEOUT") {
20939
+ logger.warn("获取二维码超时");
20940
+ await safeScreenshot(page, path.join(karinPathTemp, Root.pluginName, "DouyinLoginQrcodeError.png"));
20941
+ await e.reply("获取二维码超时,请稍后重试", { reply: true });
20942
+ } else {
20943
+ logger.error("获取二维码失败:", error);
20944
+ await e.reply("获取二维码失败,请查看控制台日志", { reply: true });
20753
20945
  }
20754
- let gcInterval;
20755
- try {
20756
- const loginQRcode = await Render("douyin/qrcodeImg", qrCodeData.url ? { share_url: qrCodeData.url } : { qrCodeDataUrl: qrCodeData.originalImage });
20757
- const base64Data = loginQRcode[0]?.file;
20758
- if (!base64Data) throw new Error("生成二维码图片失败");
20759
- const cleanBase64 = base64Data.replace(/^base64:\/\//, "");
20760
- const buffer = Buffer.from(cleanBase64, "base64");
20761
- fs.writeFileSync(`${Common.tempDri.default}DouyinLoginQrcode.png`, buffer);
20762
- const message2 = await e.reply(loginQRcode, { reply: true });
20763
- logger.debug("二维码图片消息ID:", message2.messageId);
20764
- msg_id.push(message2.messageId);
20765
- gcInterval = setInterval(async () => {
20766
- try {
20767
- await page.evaluate(() => {
20768
- if (window.gc) window.gc();
20769
- });
20770
- } catch {}
20771
- }, 1e4);
20772
- logger.mark("开始等待用户扫码登录...");
20773
- const loginResult = await Promise.race([new Promise((resolve$1) => {
20774
- const timer = setTimeout(async () => {
20775
- logger.warn("扫码登录超时(2分钟),撤回二维码消息");
20776
- await Promise.all(msg_id.map(async (id) => {
20777
- await e.bot.recallMsg(e.contact, id);
20778
- }));
20779
- resolve$1(false);
20780
- }, 120 * 1e3);
20781
- let secondVerifyHandled = false;
20782
- let scannedHandled = false;
20783
- let responseCount = 0;
20784
- page.on("response", async (response) => {
20785
- responseCount++;
20786
- const url = response.url();
20787
- if (responseCount % 10 === 0) logger.debug(`[心跳] 已收到 ${responseCount} 个响应`);
20788
- if (url.includes("passport") || url.includes("login") || url.includes("qrconnect") || url.includes("qrcode")) logger.debug(`[响应] 登录相关请求: ${url}, status: ${response.status()}`);
20946
+ await puppeteer.browser.close();
20947
+ return true;
20948
+ }
20949
+ let gcInterval;
20950
+ try {
20951
+ const loginQRcode = await Render("douyin/qrcodeImg", qrCodeData.url ? { share_url: qrCodeData.url } : { qrCodeDataUrl: qrCodeData.originalImage });
20952
+ const base64Data = loginQRcode[0]?.file;
20953
+ if (!base64Data) throw new Error("生成二维码图片失败");
20954
+ const cleanBase64 = base64Data.replace(/^base64:\/\//, "");
20955
+ const buffer = Buffer.from(cleanBase64, "base64");
20956
+ fs.writeFileSync(`${Common.tempDri.default}DouyinLoginQrcode.png`, buffer);
20957
+ const message2 = await e.reply(loginQRcode, { reply: true });
20958
+ logger.debug("二维码图片消息ID:", message2.messageId);
20959
+ msg_id.push(message2.messageId);
20960
+ gcInterval = setInterval(async () => {
20961
+ try {
20962
+ await page.evaluate(() => {
20963
+ if (window.gc) window.gc();
20789
20964
  });
20790
- logger.mark("响应监听器已注册");
20791
- page.on("response", async (response) => {
20792
- try {
20793
- if (response.url().includes("check_qrconnect")) {
20794
- logger.debug(`收到登录轮询响应: ${response.url()}`);
20795
- const hasSidGuard = (response.headers()["set-cookie"] || "").includes("sid_guard");
20796
- logger.debug(`响应头包含 sid_guard: ${hasSidGuard}`);
20797
- if (hasSidGuard) {
20798
- clearTimeout(timer);
20799
- logger.mark("检测到 sid_guard,登录成功");
20800
- logger.debug("开始获取 cookies...");
20801
- const cookies = await puppeteer.browser.cookies();
20802
- logger.debug(`获取到 ${cookies.length} cookies`);
20803
- const cookieString = cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
20804
- logger.debug("开始保存 cookies...");
20805
- Config.Modify("cookies", "douyin", cookieString);
20806
- logger.debug("cookies 保存完成");
20807
- await e.reply("登录成功!用户登录凭证已保存至cookies.yaml", { reply: true });
20808
- await Promise.all(msg_id.map(async (id) => {
20809
- await e.bot.recallMsg(e.contact, id);
20810
- }));
20811
- logger.mark("关闭浏览器...");
20812
- await puppeteer.browser.close();
20813
- logger.mark("浏览器已关闭");
20814
- resolve$1(true);
20815
- return;
20816
- }
20817
- const responseBody = await response.text();
20818
- const jsonResponse = JSON.parse(responseBody);
20819
- logger.debug(`二维码状态:${jsonResponse.data?.status}, error_code: ${jsonResponse.data?.error_code}`);
20820
- if (jsonResponse.data?.status === "scanned" && !scannedHandled) {
20821
- scannedHandled = true;
20822
- logger.mark("检测到二维码已被扫描");
20823
- await Promise.all(msg_id.map(async (id) => {
20824
- await e.bot.recallMsg(e.contact, id);
20825
- }));
20826
- msg_id.length = 0;
20827
- const authMsg = await e.reply("此二维码已被扫描,请在手机上授权以登录", { reply: true });
20828
- msg_id.push(authMsg.messageId);
20965
+ } catch {}
20966
+ }, 1e4);
20967
+ logger.mark("开始等待用户扫码登录...");
20968
+ const loginResult = await Promise.race([new Promise((resolve$1) => {
20969
+ const timer = setTimeout(async () => {
20970
+ logger.warn("扫码登录超时(2分钟),撤回二维码消息");
20971
+ await Promise.all(msg_id.map(async (id) => {
20972
+ await e.bot.recallMsg(e.contact, id);
20973
+ }));
20974
+ resolve$1(false);
20975
+ }, 120 * 1e3);
20976
+ let secondVerifyHandled = false;
20977
+ let scannedHandled = false;
20978
+ let responseCount = 0;
20979
+ page.on("response", async (response) => {
20980
+ responseCount++;
20981
+ const url = response.url();
20982
+ if (responseCount % 10 === 0) logger.debug(`[心跳] 已收到 ${responseCount} 个响应`);
20983
+ if (url.includes("passport") || url.includes("login") || url.includes("qrconnect") || url.includes("qrcode")) logger.debug(`[响应] 登录相关请求: ${url}, status: ${response.status()}`);
20984
+ });
20985
+ logger.mark("响应监听器已注册");
20986
+ page.on("response", async (response) => {
20987
+ try {
20988
+ if (response.url().includes("check_qrconnect")) {
20989
+ logger.debug(`收到登录轮询响应: ${response.url()}`);
20990
+ const hasSidGuard = (response.headers()["set-cookie"] || "").includes("sid_guard");
20991
+ logger.debug(`响应头包含 sid_guard: ${hasSidGuard}`);
20992
+ if (hasSidGuard) {
20993
+ clearTimeout(timer);
20994
+ logger.mark("检测到 sid_guard,登录成功");
20995
+ logger.debug("开始获取 cookies...");
20996
+ const cookies = await puppeteer.browser.cookies();
20997
+ logger.debug(`获取到 ${cookies.length} 个 cookies`);
20998
+ const cookieString = cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
20999
+ const hasSessionidSs = cookies.some((cookie) => cookie.name === "sessionid_ss");
21000
+ const hasTtwid = cookies.some((cookie) => cookie.name === "ttwid");
21001
+ logger.mark(`Cookie 参数检测: sessionid_ss=${hasSessionidSs}, ttwid=${hasTtwid}`);
21002
+ if (!hasSessionidSs || !hasTtwid) {
21003
+ logger.warn("警告:缺少关键 cookie 参数!");
21004
+ if (!hasSessionidSs) logger.warn(" - 缺少 sessionid_ss");
21005
+ if (!hasTtwid) logger.warn(" - 缺少 ttwid");
20829
21006
  }
20830
- if (jsonResponse.data?.error_code === 2046 && !secondVerifyHandled) {
20831
- secondVerifyHandled = true;
20832
- logger.mark("检测到需要二次验证");
20833
- clearTimeout(timer);
20834
- (async () => {
20835
- try {
20836
- await page.waitForSelector("#uc-second-verify", { timeout: 5e3 });
20837
- if (!await page.evaluate(() => {
20838
- const element = document.evaluate("//text()[contains(., '接收短信验证码')]/ancestor::*[1]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
20839
- if (element) {
20840
- element.click();
20841
- return true;
20842
- }
20843
- return false;
20844
- })) logger.warn("未找到\"接收短信验证码\"按钮");
20845
- await new Promise((resolve$2) => setTimeout(resolve$2, 2e3));
20846
- const inputSelector = "#uc-second-verify input";
20847
- await page.waitForSelector(inputSelector, { timeout: 1e4 });
20848
- const tipMsg = await e.reply("此次验证需要进行 2FA\n6 位数的验证码已发送至扫码设备绑定的手机号\n请在 60 秒内发送此验证码以通过 2FA", { reply: true });
20849
- msg_id.push(tipMsg.messageId);
20850
- let verifyAttempts = 0;
20851
- const maxAttempts = 2;
20852
- let verifySuccess = false;
20853
- while (verifyAttempts < maxAttempts && !verifySuccess) {
20854
- verifyAttempts++;
20855
- logger.debug(`验证码输入尝试 ${verifyAttempts}/${maxAttempts}`);
20856
- const ctx = await Promise.race([karin.ctx(e, { reply: true }), new Promise((_$1, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("2FA_TIMEOUT")), 6e4))]).catch(async (error) => {
20857
- if (error.message === "2FA_TIMEOUT") {
20858
- logger.warn("2FA验证码输入超时");
20859
- clearTimeout(timer);
20860
- if (gcInterval) clearInterval(gcInterval);
20861
- await puppeteer.browser.close();
20862
- await Promise.all(msg_id.map(async (id) => {
20863
- await e.bot.recallMsg(e.contact, id);
20864
- }));
20865
- await e.reply("验证码验证码输入超时,登录失败", { reply: true });
20866
- resolve$1(true);
20867
- }
20868
- throw error;
20869
- });
20870
- if (!ctx) return;
20871
- await page.evaluate((selector) => {
20872
- const input = document.querySelector(selector);
20873
- if (input) input.value = "";
20874
- }, inputSelector);
20875
- await page.type(inputSelector, ctx.msg);
20876
- const validatePromise = new Promise((resolveValidate) => {
20877
- const validateHandler = async (resp) => {
20878
- if (resp.url().includes("validate_code")) try {
20879
- const validateBody = await resp.text();
20880
- const validateJson = JSON.parse(validateBody);
20881
- logger.debug("验证码验证响应:", validateJson);
20882
- if (validateJson.data?.error_code === 1202) {
20883
- logger.warn("验证码错误");
20884
- page.off("response", validateHandler);
20885
- resolveValidate(false);
20886
- } else if (validateJson.message === "success" || !validateJson.data?.error_code) {
20887
- logger.mark("验证码验证成功");
20888
- page.off("response", validateHandler);
20889
- resolveValidate(true);
20890
- }
20891
- } catch (err) {
20892
- logger.debug("解析验证响应失败:", err);
20893
- }
20894
- };
20895
- page.on("response", validateHandler);
20896
- setTimeout(() => {
20897
- page.off("response", validateHandler);
20898
- resolveValidate(false);
20899
- }, 5e3);
20900
- });
20901
- await page.evaluate(() => {
20902
- const verifyBtn = Array.from(document.querySelectorAll("*")).find((el) => el.textContent?.trim() === "验证");
20903
- if (verifyBtn) verifyBtn.click();
20904
- });
20905
- logger.mark("已提交验证码,等待验证结果...");
20906
- verifySuccess = await validatePromise;
20907
- if (!verifySuccess && verifyAttempts < maxAttempts) {
20908
- const retryMsg = await e.reply("验证码错误,请重新发送正确的 6 位数验证码(剩余机会:1次)", { reply: true });
20909
- msg_id.push(retryMsg.messageId);
20910
- } else if (!verifySuccess && verifyAttempts >= maxAttempts) {
20911
- logger.warn("验证码错误次数过多,登录失败");
21007
+ logger.debug("开始保存 cookies...");
21008
+ Config.Modify("cookies", "douyin", cookieString);
21009
+ logger.debug("cookies 保存完成");
21010
+ await e.reply("登录成功!用户登录凭证已保存至cookies.yaml", { reply: true });
21011
+ await Promise.all(msg_id.map(async (id) => {
21012
+ await e.bot.recallMsg(e.contact, id);
21013
+ }));
21014
+ logger.mark("关闭浏览器...");
21015
+ await puppeteer.browser.close();
21016
+ logger.mark("浏览器已关闭");
21017
+ resolve$1(true);
21018
+ return;
21019
+ }
21020
+ const responseBody = await response.text();
21021
+ const jsonResponse = JSON.parse(responseBody);
21022
+ logger.debug(`二维码状态:${jsonResponse.data?.status}, error_code: ${jsonResponse.data?.error_code}`);
21023
+ if (jsonResponse.data?.status === "scanned" && !scannedHandled) {
21024
+ scannedHandled = true;
21025
+ logger.mark("检测到二维码已被扫描");
21026
+ await Promise.all(msg_id.map(async (id) => {
21027
+ await e.bot.recallMsg(e.contact, id);
21028
+ }));
21029
+ msg_id.length = 0;
21030
+ const authMsg = await e.reply("此二维码已被扫描,请在手机上授权以登录", { reply: true });
21031
+ msg_id.push(authMsg.messageId);
21032
+ }
21033
+ if (jsonResponse.data?.error_code === 2046 && !secondVerifyHandled) {
21034
+ secondVerifyHandled = true;
21035
+ logger.mark("检测到需要二次验证");
21036
+ clearTimeout(timer);
21037
+ (async () => {
21038
+ try {
21039
+ await page.waitForSelector("#uc-second-verify", { timeout: 5e3 });
21040
+ if (!await page.evaluate(() => {
21041
+ const element = document.evaluate("//text()[contains(., '接收短信验证码')]/ancestor::*[1]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
21042
+ if (element) {
21043
+ element.click();
21044
+ return true;
21045
+ }
21046
+ return false;
21047
+ })) logger.warn("未找到\"接收短信验证码\"按钮");
21048
+ await new Promise((resolve$2) => setTimeout(resolve$2, 2e3));
21049
+ const inputSelector = "#uc-second-verify input";
21050
+ await page.waitForSelector(inputSelector, { timeout: 1e4 });
21051
+ const tipMsg = await e.reply("此次验证需要进行 2FA\n6 位数的验证码已发送至扫码设备绑定的手机号\n请在 60 秒内发送此验证码以通过 2FA", { reply: true });
21052
+ msg_id.push(tipMsg.messageId);
21053
+ let verifyAttempts = 0;
21054
+ const maxAttempts = 2;
21055
+ let verifySuccess = false;
21056
+ while (verifyAttempts < maxAttempts && !verifySuccess) {
21057
+ verifyAttempts++;
21058
+ logger.debug(`验证码输入尝试 ${verifyAttempts}/${maxAttempts}`);
21059
+ const ctx = await Promise.race([karin.ctx(e, { reply: true }), new Promise((_$1, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("2FA_TIMEOUT")), 6e4))]).catch(async (error) => {
21060
+ if (error.message === "2FA_TIMEOUT") {
21061
+ logger.warn("2FA验证码输入超时");
20912
21062
  clearTimeout(timer);
20913
21063
  if (gcInterval) clearInterval(gcInterval);
20914
21064
  await puppeteer.browser.close();
20915
21065
  await Promise.all(msg_id.map(async (id) => {
20916
21066
  await e.bot.recallMsg(e.contact, id);
20917
21067
  }));
20918
- await e.reply("验证码错误,登录失败", { reply: true });
21068
+ await e.reply("验证码验证码输入超时,登录失败", { reply: true });
20919
21069
  resolve$1(true);
20920
- return;
20921
21070
  }
21071
+ throw error;
21072
+ });
21073
+ if (!ctx) return;
21074
+ await page.evaluate((selector) => {
21075
+ const input = document.querySelector(selector);
21076
+ if (input) input.value = "";
21077
+ }, inputSelector);
21078
+ await page.type(inputSelector, ctx.msg);
21079
+ const validatePromise = new Promise((resolveValidate) => {
21080
+ const validateHandler = async (resp) => {
21081
+ if (resp.url().includes("validate_code")) try {
21082
+ const validateBody = await resp.text();
21083
+ const validateJson = JSON.parse(validateBody);
21084
+ logger.debug("验证码验证响应:", validateJson);
21085
+ if (validateJson.data?.error_code === 1202) {
21086
+ logger.warn("验证码错误");
21087
+ page.off("response", validateHandler);
21088
+ resolveValidate(false);
21089
+ } else if (validateJson.message === "success" || !validateJson.data?.error_code) {
21090
+ logger.mark("验证码验证成功");
21091
+ page.off("response", validateHandler);
21092
+ resolveValidate(true);
21093
+ }
21094
+ } catch (err) {
21095
+ logger.debug("解析验证响应失败:", err);
21096
+ }
21097
+ };
21098
+ page.on("response", validateHandler);
21099
+ setTimeout(() => {
21100
+ page.off("response", validateHandler);
21101
+ resolveValidate(false);
21102
+ }, 5e3);
21103
+ });
21104
+ await page.evaluate(() => {
21105
+ const verifyBtn = Array.from(document.querySelectorAll("*")).find((el) => el.textContent?.trim() === "验证");
21106
+ if (verifyBtn) verifyBtn.click();
21107
+ });
21108
+ logger.mark("已提交验证码,等待验证结果...");
21109
+ verifySuccess = await validatePromise;
21110
+ if (!verifySuccess && verifyAttempts < maxAttempts) {
21111
+ const retryMsg = await e.reply("验证码错误,请重新发送正确的 6 位数验证码(剩余机会:1次)", { reply: true });
21112
+ msg_id.push(retryMsg.messageId);
21113
+ } else if (!verifySuccess && verifyAttempts >= maxAttempts) {
21114
+ logger.warn("验证码错误次数过多,登录失败");
21115
+ clearTimeout(timer);
21116
+ if (gcInterval) clearInterval(gcInterval);
21117
+ await puppeteer.browser.close();
21118
+ await Promise.all(msg_id.map(async (id) => {
21119
+ await e.bot.recallMsg(e.contact, id);
21120
+ }));
21121
+ await e.reply("验证码错误,登录失败", { reply: true });
21122
+ resolve$1(true);
21123
+ return;
20922
21124
  }
20923
- if (verifySuccess) logger.mark("2FA验证通过,等待最终登录确认...");
20924
- } catch (err) {
20925
- logger.error("二次验证处理失败:", err);
20926
- clearTimeout(timer);
20927
- if (gcInterval) clearInterval(gcInterval);
20928
- await puppeteer.browser.close();
20929
- await Promise.all(msg_id.map(async (id) => {
20930
- await e.bot.recallMsg(e.contact, id);
20931
- }));
20932
- await e.reply("二次验证处理失败,登录失败", { reply: true });
20933
- resolve$1(true);
20934
21125
  }
20935
- })();
20936
- }
21126
+ if (verifySuccess) logger.mark("2FA验证通过,等待最终登录确认...");
21127
+ } catch (err) {
21128
+ logger.error("二次验证处理失败:", err);
21129
+ clearTimeout(timer);
21130
+ if (gcInterval) clearInterval(gcInterval);
21131
+ await puppeteer.browser.close();
21132
+ await Promise.all(msg_id.map(async (id) => {
21133
+ await e.bot.recallMsg(e.contact, id);
21134
+ }));
21135
+ await e.reply("二次验证处理失败,登录失败", { reply: true });
21136
+ resolve$1(true);
21137
+ }
21138
+ })();
20937
21139
  }
20938
- } catch (error) {
20939
- logger.error("处理响应时出错:", error);
20940
21140
  }
20941
- });
20942
- })]);
20943
- if (gcInterval) clearInterval(gcInterval);
20944
- if (!loginResult) {
20945
- logger.warn("登录超时或失败");
20946
- await puppeteer.browser.close();
20947
- await e.reply("登录超时!二维码已失效!", { reply: true });
20948
- return true;
20949
- }
20950
- } catch (err) {
20951
- logger.error("登录流程出错:", err);
20952
- if (gcInterval) clearInterval(gcInterval);
21141
+ } catch (error) {
21142
+ logger.error("处理响应时出错:", error);
21143
+ }
21144
+ });
21145
+ })]);
21146
+ if (gcInterval) clearInterval(gcInterval);
21147
+ if (!loginResult) {
21148
+ logger.warn("登录超时或失败");
20953
21149
  await puppeteer.browser.close();
20954
- await e.reply("登录过程出错,请查看控制台日志", { reply: true });
21150
+ await e.reply("登录超时!二维码已失效!", { reply: true });
21151
+ return true;
20955
21152
  }
20956
- } catch (error) {
20957
- logger.error(error);
20958
- await e.reply("浏览器环境初始化失败,请查看控制台错误日志", { reply: true });
21153
+ } catch (err) {
21154
+ logger.error("登录流程出错:", err);
21155
+ if (gcInterval) clearInterval(gcInterval);
21156
+ await puppeteer.browser.close();
21157
+ await e.reply("登录过程出错,请查看控制台日志", { reply: true });
20959
21158
  }
20960
21159
  return true;
20961
21160
  };
@@ -21003,55 +21202,52 @@ var waitQrcode = async (page) => {
21003
21202
  };
21004
21203
  await init_module();
21005
21204
  await init_Config();
21006
- const task = Config.app.removeCache && karin$1.task("[kkk-缓存自动删除]", "0 */4 * * *", async () => {
21007
- try {
21008
- const twoHoursAgo = Date.now() - 7200 * 1e3;
21009
- const videoDeleted = removeOldFiles(Common.tempDri.video, twoHoursAgo);
21010
- logger.mark(`${Common.tempDri.video} 目录下已删除 ${videoDeleted} 个文件`);
21011
- if (Config.upload.imageSendMode === "file") {
21012
- const imageDeleted = removeOldFiles(Common.tempDri.images, twoHoursAgo);
21013
- logger.mark(`${Common.tempDri.images} 目录下已删除 ${imageDeleted} 个文件`);
21014
- }
21015
- } catch (err) {
21016
- console.error("删除文件时出错:", err);
21205
+ await init_ErrorHandler();
21206
+ var handleCacheCleanup = wrapWithErrorHandler(async () => {
21207
+ const twoHoursAgo = Date.now() - 7200 * 1e3;
21208
+ const videoDeleted = removeOldFiles(Common.tempDri.video, twoHoursAgo);
21209
+ logger.mark(`${Common.tempDri.video} 目录下已删除 ${videoDeleted} 个文件`);
21210
+ if (Config.upload.imageSendMode === "file") {
21211
+ const imageDeleted = removeOldFiles(Common.tempDri.images, twoHoursAgo);
21212
+ logger.mark(`${Common.tempDri.images} 目录下已删除 ${imageDeleted} 个文件`);
21017
21213
  }
21018
- });
21019
- const biLogin = karin$1.command(/^#?(kkk)?\s*B站\s*(扫码)?\s*登录$/i, async (e) => {
21214
+ }, { businessName: "缓存自动删除" });
21215
+ const task = Config.app.removeCache && karin$1.task("[kkk-缓存自动删除]", "0 */4 * * *", handleCacheCleanup);
21216
+ var handleBilibiliLogin = wrapWithErrorHandler(async (e) => {
21020
21217
  await bilibiliLogin(e);
21021
21218
  return true;
21022
- }, {
21219
+ }, { businessName: "B站登录" });
21220
+ var handleDouyinLogin = wrapWithErrorHandler(async (e) => {
21221
+ await douyinLogin(e);
21222
+ return true;
21223
+ }, { businessName: "抖音登录" });
21224
+ const biLogin = karin$1.command(/^#?(kkk)?\s*B站\s*(扫码)?\s*登录$/i, handleBilibiliLogin, {
21023
21225
  perm: Config.bilibili.loginPerm,
21024
21226
  name: "kkk-ck管理"
21025
21227
  });
21026
- const dylogin = karin$1.command(/^#?(kkk)?抖音(扫码)?登录$/, async (e) => {
21027
- await douyinLogin(e);
21028
- return true;
21029
- }, {
21228
+ const dylogin = karin$1.command(/^#?(kkk)?抖音(扫码)?登录$/, handleDouyinLogin, {
21030
21229
  perm: Config.douyin.loginPerm,
21031
21230
  name: "kkk-ck管理"
21032
21231
  });
21033
21232
  const removeOldFiles = (dir, beforeTimestamp) => {
21034
21233
  let deletedCount = 0;
21035
- try {
21036
- const files = fs.readdirSync(dir);
21037
- for (const file of files) {
21038
- const filePath = path.join(dir, file);
21039
- const stats = fs.statSync(filePath);
21040
- if (stats.isDirectory()) {
21041
- deletedCount += removeOldFiles(filePath, beforeTimestamp);
21042
- if (fs.readdirSync(filePath).length === 0) fs.rmdirSync(filePath);
21043
- } else if (stats.mtimeMs < beforeTimestamp) {
21044
- fs.unlinkSync(filePath);
21045
- deletedCount++;
21046
- }
21234
+ const files = fs.readdirSync(dir);
21235
+ for (const file of files) {
21236
+ const filePath = path.join(dir, file);
21237
+ const stats = fs.statSync(filePath);
21238
+ if (stats.isDirectory()) {
21239
+ deletedCount += removeOldFiles(filePath, beforeTimestamp);
21240
+ if (fs.readdirSync(filePath).length === 0) fs.rmdirSync(filePath);
21241
+ } else if (stats.mtimeMs < beforeTimestamp) {
21242
+ fs.unlinkSync(filePath);
21243
+ deletedCount++;
21047
21244
  }
21048
- } catch (err) {
21049
- logger.error(`处理目录 ${dir} 时出错:`, err);
21050
21245
  }
21051
21246
  return deletedCount;
21052
21247
  };
21053
21248
  await init_module();
21054
21249
  await init_Config();
21250
+ await init_ErrorHandler();
21055
21251
  var HELP_MENU_CONFIG = [
21056
21252
  {
21057
21253
  title: "常用功能",
@@ -21160,7 +21356,7 @@ var buildMenuForRole = (role) => {
21160
21356
  };
21161
21357
  }).filter((g) => g.items.length > 0 || g.subGroups && g.subGroups.length > 0);
21162
21358
  };
21163
- const help = karin$1.command(/^#?kkk帮助$/, async (e) => {
21359
+ var handleHelp = wrapWithErrorHandler(async (e) => {
21164
21360
  const masters = config.master().filter((id) => id !== "console");
21165
21361
  const role = !!e.sender && masters.includes(e.sender.userId) ? "master" : "member";
21166
21362
  const menu = buildMenuForRole(role);
@@ -21182,8 +21378,8 @@ const help = karin$1.command(/^#?kkk帮助$/, async (e) => {
21182
21378
  });
21183
21379
  await e.reply(img$2);
21184
21380
  return true;
21185
- }, { name: "kkk-帮助" });
21186
- const version = karin$1.command(/^#?kkk(版本|更新日志)$/, async (e) => {
21381
+ }, { businessName: "KKK帮助" });
21382
+ var handleVersion = wrapWithErrorHandler(async (e) => {
21187
21383
  const changelogContent = fs.readFileSync(Root.pluginPath + "/CHANGELOG.md", "utf8");
21188
21384
  const img$2 = await Render("other/changelog", {
21189
21385
  markdown: logs({
@@ -21197,7 +21393,9 @@ const version = karin$1.command(/^#?kkk(版本|更新日志)$/, async (e) => {
21197
21393
  });
21198
21394
  e.reply(img$2);
21199
21395
  return true;
21200
- }, { name: "kkk-版本" });
21396
+ }, { businessName: "KKK版本" });
21397
+ const help = karin$1.command(/^#?kkk帮助$/, handleHelp, { name: "kkk-帮助" });
21398
+ const version = karin$1.command(/^#?kkk(版本|更新日志)$/, handleVersion, { name: "kkk-版本" });
21201
21399
  await init_date_fns();
21202
21400
  await init_module();
21203
21401
  await init_db();
@@ -21430,6 +21628,7 @@ const testDouyinPush = karin$1.command(/^#测试抖音推送\s*(https?:\/\/[^\s]
21430
21628
  priority: -Infinity
21431
21629
  });
21432
21630
  await init_Config();
21631
+ await init_ErrorHandler();
21433
21632
  await init_Render();
21434
21633
  function getLocalIP() {
21435
21634
  const interfaces = os.networkInterfaces();
@@ -21483,7 +21682,7 @@ async function getHostByConfig() {
21483
21682
  }
21484
21683
  return getLocalIP();
21485
21684
  }
21486
- const qrLogin = karin$1.command(/^#?(kkk)?登录$/i, async (e) => {
21685
+ var handleQrLogin = wrapWithErrorHandler(async (e) => {
21487
21686
  const bot = karin$1.getBot(e.selfId);
21488
21687
  const userId = e.userId;
21489
21688
  if (e.isGroup) {
@@ -21500,105 +21699,93 @@ const qrLogin = karin$1.command(/^#?(kkk)?登录$/i, async (e) => {
21500
21699
  await e.reply("未配置 HTTP_AUTH_KEY 环境变量,无法生成登录二维码");
21501
21700
  return true;
21502
21701
  }
21503
- const qrData = JSON.stringify({
21504
- protocol,
21505
- host,
21506
- port,
21507
- authKey
21702
+ const images = await Render("other/qrlogin", {
21703
+ share_url: JSON.stringify({
21704
+ protocol,
21705
+ host,
21706
+ port,
21707
+ authKey
21708
+ }),
21709
+ serverUrl: `${protocol}://${host}:${port}`
21508
21710
  });
21509
- const serverUrl = `${protocol}://${host}:${port}`;
21510
- try {
21511
- const images = await Render("other/qrlogin", {
21512
- share_url: qrData,
21513
- serverUrl
21514
- });
21515
- await karin$1.sendMaster(e.selfId, userId, images);
21516
- if (e.isGroup) await e.reply("登录二维码已私聊发送,请查收~");
21517
- } catch (error) {
21518
- await e.reply("生成二维码失败: " + (error instanceof Error ? error.message : String(error)));
21519
- }
21711
+ await karin$1.sendMaster(e.selfId, userId, images);
21712
+ if (e.isGroup) await e.reply("登录二维码已私聊发送,请查收~");
21520
21713
  return true;
21521
- }, {
21714
+ }, { businessName: "APP扫码登录" });
21715
+ const qrLogin = karin$1.command(/^#?(kkk)?登录$/i, handleQrLogin, {
21522
21716
  perm: "master",
21523
21717
  name: "kkk-APP扫码登录"
21524
21718
  });
21525
21719
  await init_module();
21526
21720
  await init_db();
21527
- const groupStatistics = karin$1.command(/^#?kkk解析统计$/, async (e) => {
21528
- try {
21529
- const groupId = e.isGroup ? e.contact?.peer || "" : "";
21530
- if (!groupId) {
21531
- await e.reply("此命令仅支持在群聊中使用");
21532
- return true;
21533
- }
21534
- let groupName = "";
21535
- let groupMemberCount;
21536
- let groupAvatar;
21537
- try {
21538
- const groupInfo = await e.bot.getGroupInfo(groupId);
21539
- groupName = groupInfo?.groupName || "";
21540
- groupMemberCount = groupInfo?.memberCount;
21541
- groupAvatar = groupInfo?.avatar;
21542
- } catch (error) {
21543
- logger.debug("[统计] 获取群组信息失败:", error);
21544
- }
21545
- const statisticsDB$1 = await getStatisticsDB();
21546
- const groupStats = await statisticsDB$1.getGroupStatistics(groupId);
21547
- const groupUniqueUsers = await statisticsDB$1.getGroupUniqueUsers(groupId);
21548
- const globalSummary = await statisticsDB$1.getGlobalSummary();
21549
- const groupTotalParses = groupStats.reduce((sum, stat) => sum + stat.parseCount, 0);
21550
- const platformData = {
21551
- douyin: groupStats.filter((s) => s.platform === "douyin").reduce((sum, s) => sum + s.parseCount, 0),
21552
- bilibili: groupStats.filter((s) => s.platform === "bilibili").reduce((sum, s) => sum + s.parseCount, 0),
21553
- kuaishou: groupStats.filter((s) => s.platform === "kuaishou").reduce((sum, s) => sum + s.parseCount, 0),
21554
- xiaohongshu: groupStats.filter((s) => s.platform === "xiaohongshu").reduce((sum, s) => sum + s.parseCount, 0)
21555
- };
21556
- const img$2 = await Render("statistics/group", {
21557
- groupId,
21558
- groupName,
21559
- groupMemberCount,
21560
- groupAvatar,
21561
- groupTotalParses,
21562
- groupUniqueUsers,
21563
- platformData,
21564
- globalTotalGroups: globalSummary.totalGroups,
21565
- globalTotalParses: globalSummary.totalParses
21566
- });
21567
- await e.reply(img$2);
21568
- return true;
21569
- } catch (error) {
21570
- await e.reply(`获取解析统计失败:${error}`);
21721
+ await init_ErrorHandler();
21722
+ var handleGroupStatistics = wrapWithErrorHandler(async (e) => {
21723
+ const groupId = e.isGroup ? e.contact?.peer || "" : "";
21724
+ if (!groupId) {
21725
+ await e.reply("此命令仅支持在群聊中使用");
21571
21726
  return true;
21572
21727
  }
21573
- }, { name: "kkk-解析统计" });
21574
- const globalStatistics = karin$1.command(/^#?kkk全局解析统计$/, async (e) => {
21728
+ let groupName = "";
21729
+ let groupMemberCount;
21730
+ let groupAvatar;
21575
21731
  try {
21576
- const statisticsDB$1 = await getStatisticsDB();
21577
- const allStats = await statisticsDB$1.getAllStatistics();
21578
- const historyData = await statisticsDB$1.getRecentHistory(30);
21579
- const groupIds = [...new Set(allStats.map((s) => s.groupId))];
21580
- const groupInfoMap = /* @__PURE__ */ new Map();
21581
- for (const groupId of groupIds) try {
21582
- const groupInfo = await e.bot.getGroupInfo(groupId);
21583
- groupInfoMap.set(groupId, {
21584
- groupName: groupInfo?.groupName,
21585
- groupAvatar: groupInfo?.avatar
21586
- });
21587
- } catch (error) {
21588
- logger.debug(`[统计] 获取群组 ${groupId} 信息失败:`, error);
21589
- }
21590
- const img$2 = await Render("statistics/global", {
21591
- allStats,
21592
- historyData: historyData.reverse(),
21593
- groupInfoMap: Object.fromEntries(groupInfoMap)
21732
+ const groupInfo = await e.bot.getGroupInfo(groupId);
21733
+ groupName = groupInfo?.groupName || "";
21734
+ groupMemberCount = groupInfo?.memberCount;
21735
+ groupAvatar = groupInfo?.avatar;
21736
+ } catch (error) {
21737
+ logger.debug("[统计] 获取群组信息失败:", error);
21738
+ }
21739
+ const statisticsDB$1 = await getStatisticsDB();
21740
+ const groupStats = await statisticsDB$1.getGroupStatistics(groupId);
21741
+ const groupUniqueUsers = await statisticsDB$1.getGroupUniqueUsers(groupId);
21742
+ const globalSummary = await statisticsDB$1.getGlobalSummary();
21743
+ const groupTotalParses = groupStats.reduce((sum, stat) => sum + stat.parseCount, 0);
21744
+ const platformData = {
21745
+ douyin: groupStats.filter((s) => s.platform === "douyin").reduce((sum, s) => sum + s.parseCount, 0),
21746
+ bilibili: groupStats.filter((s) => s.platform === "bilibili").reduce((sum, s) => sum + s.parseCount, 0),
21747
+ kuaishou: groupStats.filter((s) => s.platform === "kuaishou").reduce((sum, s) => sum + s.parseCount, 0),
21748
+ xiaohongshu: groupStats.filter((s) => s.platform === "xiaohongshu").reduce((sum, s) => sum + s.parseCount, 0)
21749
+ };
21750
+ const img$2 = await Render("statistics/group", {
21751
+ groupId,
21752
+ groupName,
21753
+ groupMemberCount,
21754
+ groupAvatar,
21755
+ groupTotalParses,
21756
+ groupUniqueUsers,
21757
+ platformData,
21758
+ globalTotalGroups: globalSummary.totalGroups,
21759
+ globalTotalParses: globalSummary.totalParses
21760
+ });
21761
+ await e.reply(img$2);
21762
+ return true;
21763
+ }, { businessName: "群组解析统计" });
21764
+ const groupStatistics = karin$1.command(/^#?kkk解析统计$/, handleGroupStatistics, { name: "kkk-解析统计" });
21765
+ var handleGlobalStatistics = wrapWithErrorHandler(async (e) => {
21766
+ const statisticsDB$1 = await getStatisticsDB();
21767
+ const allStats = await statisticsDB$1.getAllStatistics();
21768
+ const historyData = await statisticsDB$1.getRecentHistory(30);
21769
+ const groupIds = [...new Set(allStats.map((s) => s.groupId))];
21770
+ const groupInfoMap = /* @__PURE__ */ new Map();
21771
+ for (const groupId of groupIds) try {
21772
+ const groupInfo = await e.bot.getGroupInfo(groupId);
21773
+ groupInfoMap.set(groupId, {
21774
+ groupName: groupInfo?.groupName,
21775
+ groupAvatar: groupInfo?.avatar
21594
21776
  });
21595
- await e.reply(img$2);
21596
- return true;
21597
21777
  } catch (error) {
21598
- await e.reply(`获取全局解析统计失败:${error}`);
21599
- return true;
21778
+ logger.debug(`[统计] 获取群组 ${groupId} 信息失败:`, error);
21600
21779
  }
21601
- }, {
21780
+ const img$2 = await Render("statistics/global", {
21781
+ allStats,
21782
+ historyData: historyData.reverse(),
21783
+ groupInfoMap: Object.fromEntries(groupInfoMap)
21784
+ });
21785
+ await e.reply(img$2);
21786
+ return true;
21787
+ }, { businessName: "全局解析统计" });
21788
+ const globalStatistics = karin$1.command(/^#?kkk全局解析统计$/, handleGlobalStatistics, {
21602
21789
  name: "kkk-全局解析统计",
21603
21790
  perm: "master"
21604
21791
  });
@@ -22216,6 +22403,7 @@ const getChangelogImage = async (props) => {
22216
22403
  }) || null;
22217
22404
  };
22218
22405
  await init_module();
22406
+ await init_ErrorHandler();
22219
22407
  await init_semver();
22220
22408
  var UPDATE_LOCK_KEY = "kkk:update:lock";
22221
22409
  var UPDATE_MSGID_KEY = "kkk:update:msgId";
@@ -22257,30 +22445,29 @@ var Handler = async (e) => {
22257
22445
  }
22258
22446
  return true;
22259
22447
  };
22448
+ var handleUpdateHook = wrapWithErrorHandler(async (e) => {
22449
+ e.reply("开始更新 karin-plugin-kkk ...", { reply: true });
22450
+ const upd = await checkPkgUpdate(Root.pluginName, { compare: "semver" });
22451
+ if (upd.status === "yes") {
22452
+ const result = await updatePkg(Root.pluginName);
22453
+ if (result.status === "ok") {
22454
+ if ((await e.reply(`${Root.pluginName} 更新成功!\n${result.local} -> ${result.remote}\n开始执行重启......`)).messageId) try {
22455
+ await db.del(UPDATE_MSGID_KEY);
22456
+ await db.del(UPDATE_LOCK_KEY);
22457
+ } catch {}
22458
+ await restart(e.selfId, e.contact, e.messageId);
22459
+ } else e.reply(`${Root.pluginName} 更新失败: ${result.data ?? "更新执行失败"}`);
22460
+ } else if (upd.status === "no") e.reply("未检测到可更新版本。");
22461
+ else e.reply(`${Root.pluginName} 更新失败: ${upd.error?.message ?? String(upd.error)}`);
22462
+ }, { businessName: "更新Hook" });
22260
22463
  const kkkUpdate = hooks.message.friend(async (e, next) => {
22261
22464
  if (e.msg.includes("更新")) {
22262
22465
  const msgId = await db.get(UPDATE_MSGID_KEY);
22263
- if (e.replyId === msgId) try {
22264
- e.reply("开始更新 karin-plugin-kkk ...", { reply: true });
22265
- const upd = await checkPkgUpdate(Root.pluginName, { compare: "semver" });
22266
- if (upd.status === "yes") {
22267
- const result = await updatePkg(Root.pluginName);
22268
- if (result.status === "ok") {
22269
- if ((await e.reply(`${Root.pluginName} 更新成功!\n${result.local} -> ${result.remote}\n开始执行重启......`)).messageId) try {
22270
- await db.del(UPDATE_MSGID_KEY);
22271
- await db.del(UPDATE_LOCK_KEY);
22272
- } catch {}
22273
- await restart(e.selfId, e.contact, e.messageId);
22274
- } else e.reply(`${Root.pluginName} 更新失败: ${result.data ?? "更新执行失败"}`);
22275
- } else if (upd.status === "no") e.reply("未检测到可更新版本。");
22276
- else e.reply(`${Root.pluginName} 更新失败: ${upd.error?.message ?? String(upd.error)}`);
22277
- } catch (error) {
22278
- e.reply(`${Root.pluginName} 更新失败: ${error.message}`);
22279
- }
22466
+ if (e.replyId === msgId) await handleUpdateHook(e);
22280
22467
  }
22281
22468
  next();
22282
22469
  }, { priority: 100 });
22283
- const kkkUpdateCommand = karin$1.command(/^#?kkk更新$/, async (e) => {
22470
+ var handleKkkUpdate = wrapWithErrorHandler(async (e) => {
22284
22471
  const upd = await checkPkgUpdate(Root.pluginName, { compare: "semver" });
22285
22472
  if (upd.status === "error") {
22286
22473
  e.reply(`获取远程版本失败:${upd.error?.message ?? String(upd.error)}`);
@@ -22302,20 +22489,17 @@ const kkkUpdateCommand = karin$1.command(/^#?kkk更新$/, async (e) => {
22302
22489
  });
22303
22490
  if (ChangeLogImg) e.reply([segment.text(`${Root.pluginName} 的更新日志:`), ...ChangeLogImg], { reply: true });
22304
22491
  else e.reply("获取更新日志失败,更新进程继续......", { reply: true });
22305
- try {
22306
- const result = await updatePkg(Root.pluginName);
22307
- if (result.status === "ok") {
22308
- const msgResult = await e.reply(`${Root.pluginName} 更新成功!\n${result.local} -> ${result.remote}\n开始执行重启......`);
22309
- if (msgResult.messageId) try {
22310
- await db.del(UPDATE_MSGID_KEY);
22311
- await db.del(UPDATE_LOCK_KEY);
22312
- } catch {}
22313
- await restart(e.selfId, e.contact, msgResult.messageId);
22314
- } else e.reply(`${Root.pluginName} 更新失败: ${result.data ?? "更新执行失败"}`);
22315
- } catch (error) {
22316
- e.reply(`${Root.pluginName} 更新失败: ${error.message}`);
22317
- }
22318
- }, { name: "kkk-更新" });
22492
+ const result = await updatePkg(Root.pluginName);
22493
+ if (result.status === "ok") {
22494
+ const msgResult = await e.reply(`${Root.pluginName} 更新成功!\n${result.local} -> ${result.remote}\n开始执行重启......`);
22495
+ if (msgResult.messageId) try {
22496
+ await db.del(UPDATE_MSGID_KEY);
22497
+ await db.del(UPDATE_LOCK_KEY);
22498
+ } catch {}
22499
+ await restart(e.selfId, e.contact, msgResult.messageId);
22500
+ } else e.reply(`${Root.pluginName} 更新失败: ${result.data ?? "更新执行失败"}`);
22501
+ }, { businessName: "KKK更新" });
22502
+ const kkkUpdateCommand = karin$1.command(/^#?kkk更新$/, handleKkkUpdate, { name: "kkk-更新" });
22319
22503
  const update = karin$1.task("kkk-更新检测", "*/5 * * * *", Handler, {
22320
22504
  name: "kkk-更新检测",
22321
22505
  log: false