karin-plugin-kkk 2.25.0 → 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-9pKTNH6x.js";
3
- import { n as init_client, r as reactServerRender } from "./template-CsOboAFj.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;
@@ -7111,6 +7140,124 @@ var init_EmojiReaction = __esmMin(() => {
7111
7140
  }
7112
7141
  };
7113
7142
  });
7143
+ var xmpHeaderBuffer, oppoExifHex, xiaomiExifHex, huaweiHonorLiveIdFallback, isJpegBuffer, getJpegDimensions, hasExifApp1, buildExifSegment, getSystemExifHex, resolveMotionPhotoSystem, buildMotionPhotoXmp, injectXmpToJpeg, readOrConvertToJpeg, buildGoogleMotionPhoto;
7144
+ var init_MotionPhoto = __esmMin(async () => {
7145
+ await init_Common();
7146
+ await init_Config();
7147
+ xmpHeaderBuffer = Buffer.from("http://ns.adobe.com/xap/1.0/\0", "utf8");
7148
+ oppoExifHex = "FFE100724578696600004D4D002A0000000800040100000400000001000005A001010004000000010000043C87690004000000010000003E011200030000000100000000000000000002928600020000000E0000005C920800040000000100000000000000006F706C75735F3833383836303800";
7149
+ xiaomiExifHex = "FFE1007E4578696600004D4D002A0000000800040100000400000001000005A001010004000000010000043C01120003000000010000000087690004000000010000003E000000000003889700010000000101000000920800040000000100000000928600020000000E00000068000000006F706C75735F3833383836303800";
7150
+ huaweiHonorLiveIdFallback = 1915884;
7151
+ isJpegBuffer = (fileBuffer) => fileBuffer.length > 2 && fileBuffer[0] === 255 && fileBuffer[1] === 216;
7152
+ getJpegDimensions = (jpegBuffer) => {
7153
+ let offset = 2;
7154
+ while (offset + 9 < jpegBuffer.length) {
7155
+ if (jpegBuffer[offset] !== 255) {
7156
+ offset += 1;
7157
+ continue;
7158
+ }
7159
+ const marker = jpegBuffer[offset + 1];
7160
+ if (marker === 216 || marker === 217 || marker === 1 || marker >= 208 && marker <= 215) {
7161
+ offset += 2;
7162
+ continue;
7163
+ }
7164
+ if (offset + 3 >= jpegBuffer.length) return null;
7165
+ const segmentLength = jpegBuffer.readUInt16BE(offset + 2);
7166
+ if (segmentLength < 2 || offset + 2 + segmentLength > jpegBuffer.length) return null;
7167
+ if ((marker >= 192 && marker <= 195 || marker >= 197 && marker <= 199 || marker >= 201 && marker <= 203 || marker >= 205 && marker <= 207) && segmentLength >= 7) {
7168
+ const height = jpegBuffer.readUInt16BE(offset + 5);
7169
+ const width = jpegBuffer.readUInt16BE(offset + 7);
7170
+ if (width > 0 && height > 0) return {
7171
+ width,
7172
+ height
7173
+ };
7174
+ return null;
7175
+ }
7176
+ offset += 2 + segmentLength;
7177
+ }
7178
+ return null;
7179
+ };
7180
+ hasExifApp1 = (jpegBuffer) => jpegBuffer.includes(Buffer.from("Exif\0\0", "binary"));
7181
+ buildExifSegment = (hex, width, height) => {
7182
+ const exifBuffer = Buffer.from(hex, "hex");
7183
+ exifBuffer[28] = width >> 24 & 255;
7184
+ exifBuffer[29] = width >> 16 & 255;
7185
+ exifBuffer[30] = width >> 8 & 255;
7186
+ exifBuffer[31] = width & 255;
7187
+ exifBuffer[40] = height >> 24 & 255;
7188
+ exifBuffer[41] = height >> 16 & 255;
7189
+ exifBuffer[42] = height >> 8 & 255;
7190
+ exifBuffer[43] = height & 255;
7191
+ return exifBuffer;
7192
+ };
7193
+ getSystemExifHex = (system) => {
7194
+ if (system === "oppo") return oppoExifHex;
7195
+ if (system === "xiaomi") return xiaomiExifHex;
7196
+ return null;
7197
+ };
7198
+ resolveMotionPhotoSystem = () => {
7199
+ const system = Config.app.livePhotoSystem;
7200
+ if (system === "google" || system === "xiaomi" || system === "oppo" || system === "huawei_honor") return system;
7201
+ return "google";
7202
+ };
7203
+ buildMotionPhotoXmp = (videoLength, presentationTimestampUs, system) => {
7204
+ if (system === "oppo") return `<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.1.0-jc003"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/" xmlns:GCamera="http://ns.google.com/photos/1.0/camera/" xmlns:OpCamera="http://ns.oplus.com/photos/1.0/camera/" xmlns:Container="http://ns.google.com/photos/1.0/container/" xmlns:Item="http://ns.google.com/photos/1.0/container/item/" hdrgm:Version="1.0" GCamera:MotionPhoto="1" GCamera:MotionPhotoVersion="1" GCamera:MotionPhotoPresentationTimestampUs="${presentationTimestampUs}" OpCamera:MotionPhotoPrimaryPresentationTimestampUs="${presentationTimestampUs}" OpCamera:MotionPhotoOwner="oplus" OpCamera:OLivePhotoVersion="2" OpCamera:VideoLength="${videoLength}"><Container:Directory><rdf:Seq><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="image/jpeg" Item:Semantic="Primary" Item:Length="0" Item:Padding="0" /></rdf:li><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="video/mp4" Item:Semantic="MotionPhoto" Item:Length="${videoLength}" /></rdf:li></rdf:Seq></Container:Directory></rdf:Description></rdf:RDF></x:xmpmeta>`;
7205
+ if (system === "xiaomi") return `<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.1.0-jc003"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:GCamera="http://ns.google.com/photos/1.0/camera/" xmlns:MiCamera="http://ns.xiaomi.com/photos/1.0/camera/" xmlns:Container="http://ns.google.com/photos/1.0/container/" xmlns:Item="http://ns.google.com/photos/1.0/container/item/" GCamera:MotionPhoto="1" GCamera:MotionPhotoVersion="1" GCamera:MotionPhotoPresentationTimestampUs="${presentationTimestampUs}" GCamera:MicroVideo="1" GCamera:MicroVideoVersion="1" GCamera:MicroVideoOffset="${videoLength}" GCamera:MicroVideoPresentationTimestampUs="${presentationTimestampUs}" MiCamera:XMPMeta="&lt;?xml version=&apos;1.0&apos; encoding=&apos;UTF-8&apos; standalone=&apos;yes&apos; ?&gt;"><Container:Directory><rdf:Seq><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="image/jpeg" Item:Semantic="Primary" Item:Length="0" Item:Padding="0" /></rdf:li><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="video/mp4" Item:Semantic="MotionPhoto" Item:Length="${videoLength}" Item:Padding="0" /></rdf:li></rdf:Seq></Container:Directory></rdf:Description></rdf:RDF></x:xmpmeta>`;
7206
+ return `<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.1.0-jc003"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:GCamera="http://ns.google.com/photos/1.0/camera/" xmlns:Container="http://ns.google.com/photos/1.0/container/" xmlns:Item="http://ns.google.com/photos/1.0/container/item/" GCamera:MotionPhoto="1" GCamera:MotionPhotoVersion="1" GCamera:MotionPhotoPresentationTimestampUs="${presentationTimestampUs}"><Container:Directory><rdf:Seq><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="image/jpeg" Item:Semantic="Primary" Item:Length="0" Item:Padding="0" /></rdf:li><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="video/mp4" Item:Semantic="MotionPhoto" Item:Length="${videoLength}" Item:Padding="0" /></rdf:li></rdf:Seq></Container:Directory></rdf:Description></rdf:RDF></x:xmpmeta>`;
7207
+ };
7208
+ injectXmpToJpeg = (jpegBuffer, xmpPacket, system) => {
7209
+ if (!isJpegBuffer(jpegBuffer)) throw new Error("输入图片不是 JPEG 格式");
7210
+ const xmpPayload = Buffer.concat([xmpHeaderBuffer, Buffer.from(xmpPacket, "utf8")]);
7211
+ const app1Length = xmpPayload.length + 2;
7212
+ if (app1Length > 65535) throw new Error("XMP 数据过大,无法写入 JPEG APP1");
7213
+ const app1Segment = Buffer.alloc(4);
7214
+ app1Segment[0] = 255;
7215
+ app1Segment[1] = 225;
7216
+ app1Segment.writeUInt16BE(app1Length, 2);
7217
+ const dimensions = getJpegDimensions(jpegBuffer);
7218
+ const exifHex = getSystemExifHex(system);
7219
+ const exifSegment = !hasExifApp1(jpegBuffer) && dimensions !== null && exifHex !== null ? buildExifSegment(exifHex, dimensions.width, dimensions.height) : null;
7220
+ return Buffer.concat([
7221
+ jpegBuffer.subarray(0, 2),
7222
+ ...exifSegment ? [exifSegment] : [],
7223
+ app1Segment,
7224
+ xmpPayload,
7225
+ jpegBuffer.subarray(2)
7226
+ ]);
7227
+ };
7228
+ readOrConvertToJpeg = async (imagePath) => {
7229
+ const sourceBuffer = fs.readFileSync(imagePath);
7230
+ if (isJpegBuffer(sourceBuffer)) return sourceBuffer;
7231
+ const tempJpegPath = path.join(Common.tempDri.images, `MotionPhoto_${Date.now()}_${Math.random().toString(36).slice(2)}.jpg`);
7232
+ if (!(await ffmpeg(`-y -i "${imagePath}" -frames:v 1 -q:v 2 "${tempJpegPath}"`)).status) throw new Error(`图片转换 JPEG 失败: ${imagePath}`);
7233
+ try {
7234
+ return fs.readFileSync(tempJpegPath);
7235
+ } finally {
7236
+ fs.rmSync(tempJpegPath, { force: true });
7237
+ }
7238
+ };
7239
+ buildGoogleMotionPhoto = async (options) => {
7240
+ const { imagePath, videoPath, outputPath, presentationTimestampUs } = options;
7241
+ try {
7242
+ const system = resolveMotionPhotoSystem();
7243
+ const imageBuffer = await readOrConvertToJpeg(imagePath);
7244
+ const videoBuffer = fs.readFileSync(videoPath);
7245
+ const resolvedPresentationTimestampUs = presentationTimestampUs === void 0 || presentationTimestampUs < 0 ? 0 : presentationTimestampUs;
7246
+ const huaweiHonorFooter = Buffer.from(`v2_f35 409:1000 LIVE_${resolvedPresentationTimestampUs > 0 ? Math.floor(resolvedPresentationTimestampUs) : huaweiHonorLiveIdFallback}`, "utf8");
7247
+ const outputBuffer = system === "huawei_honor" ? Buffer.concat([imageBuffer, huaweiHonorFooter]) : (() => {
7248
+ const jpegWithXmp = injectXmpToJpeg(imageBuffer, buildMotionPhotoXmp(videoBuffer.length, resolvedPresentationTimestampUs, system), system);
7249
+ return Buffer.concat([jpegWithXmp, videoBuffer]);
7250
+ })();
7251
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
7252
+ fs.writeFileSync(outputPath, outputBuffer);
7253
+ logger.debug(`Motion Photo 封面生成成功(${system}): ${outputPath}`);
7254
+ return true;
7255
+ } catch (error) {
7256
+ logger.error("Motion Photo 封面生成失败", error);
7257
+ return false;
7258
+ }
7259
+ };
7260
+ });
7114
7261
  async function fixM4sFile(inputPath, outputPath) {
7115
7262
  const result = await ffmpeg(`-y -i "${inputPath}" -c copy -movflags +faststart "${outputPath}"`);
7116
7263
  if (result.status) logger.debug(`m4s 文件修复成功: ${outputPath}`);
@@ -7136,9 +7283,10 @@ async function compressVideo(options) {
7136
7283
  } else logger.error(`视频压缩失败: ${inputPath}`, result);
7137
7284
  return result.status;
7138
7285
  }
7139
- var getMediaFrameRate, loopVideoWithTransition, xmpHeaderBuffer, isJpegBuffer, buildMotionPhotoXmp, injectXmpToJpeg, readOrConvertToJpeg, buildGoogleMotionPhoto;
7286
+ var getMediaFrameRate, loopVideoWithTransition;
7140
7287
  var init_FFmpeg = __esmMin(async () => {
7141
7288
  await init_utils$1();
7289
+ await init_MotionPhoto();
7142
7290
  getMediaFrameRate = async (path$1) => {
7143
7291
  const { stdout } = await ffprobe(`-v error -select_streams v:0 -show_entries stream=avg_frame_rate -of default=noprint_wrappers=1:nokey=1 "${path$1}"`);
7144
7292
  const rate = stdout.trim();
@@ -7234,71 +7382,6 @@ var init_FFmpeg = __esmMin(async () => {
7234
7382
  else logger.error("Live Photo 效果视频重放失败", result);
7235
7383
  return { success: result.status };
7236
7384
  };
7237
- xmpHeaderBuffer = Buffer.from("http://ns.adobe.com/xap/1.0/\0", "utf8");
7238
- isJpegBuffer = (fileBuffer) => fileBuffer.length > 2 && fileBuffer[0] === 255 && fileBuffer[1] === 216;
7239
- buildMotionPhotoXmp = (videoLength, presentationTimestampUs) => `<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.1.0-jc003"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" ${[
7240
- "xmlns:GCamera=\"http://ns.google.com/photos/1.0/camera/\"",
7241
- "xmlns:MiCamera=\"http://ns.xiaomi.com/photos/1.0/camera/\"",
7242
- "xmlns:Container=\"http://ns.google.com/photos/1.0/container/\"",
7243
- "xmlns:Item=\"http://ns.google.com/photos/1.0/container/item/\"",
7244
- `GCamera:MotionPhoto="1" GCamera:MotionPhotoVersion="1" GCamera:MotionPhotoPresentationTimestampUs="${presentationTimestampUs}"`,
7245
- `GCamera:MicroVideo="1" GCamera:MicroVideoVersion="1" GCamera:MicroVideoOffset="${videoLength}"`,
7246
- `GCamera:MicroVideoPresentationTimestampUs="${presentationTimestampUs}"`,
7247
- "MiCamera:XMPMeta=\"&lt;?xml version=&apos;1.0&apos; encoding=&apos;UTF-8&apos; standalone=&apos;yes&apos; ?&gt;\"",
7248
- "xmlns:OpCamera=\"http://ns.oplus.com/photos/1.0/camera/\"",
7249
- `OpCamera:MotionPhotoPrimaryPresentationTimestampUs="${presentationTimestampUs}"`,
7250
- "OpCamera:MotionPhotoOwner=\"oplus\"",
7251
- "OpCamera:OLivePhotoVersion=\"2\"",
7252
- `OpCamera:VideoLength="${videoLength}"`
7253
- ].join(" ")}><Container:Directory><rdf:Seq><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="image/jpeg" Item:Semantic="Primary" Item:Length="0" Item:Padding="0" /></rdf:li><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="video/mp4" Item:Semantic="MotionPhoto" Item:Length="${videoLength}" Item:Padding="0" /></rdf:li></rdf:Seq></Container:Directory></rdf:Description></rdf:RDF></x:xmpmeta>`;
7254
- injectXmpToJpeg = (jpegBuffer, xmpPacket) => {
7255
- if (!isJpegBuffer(jpegBuffer)) throw new Error("输入图片不是 JPEG 格式");
7256
- const xmpPayload = Buffer.concat([xmpHeaderBuffer, Buffer.from(xmpPacket, "utf8")]);
7257
- const app1Length = xmpPayload.length + 2;
7258
- if (app1Length > 65535) throw new Error("XMP 数据过大,无法写入 JPEG APP1");
7259
- const app1Segment = Buffer.alloc(4);
7260
- app1Segment[0] = 255;
7261
- app1Segment[1] = 225;
7262
- app1Segment.writeUInt16BE(app1Length, 2);
7263
- return Buffer.concat([
7264
- jpegBuffer.subarray(0, 2),
7265
- app1Segment,
7266
- xmpPayload,
7267
- jpegBuffer.subarray(2)
7268
- ]);
7269
- };
7270
- readOrConvertToJpeg = async (imagePath) => {
7271
- const sourceBuffer = fs.readFileSync(imagePath);
7272
- if (isJpegBuffer(sourceBuffer)) return sourceBuffer;
7273
- const tempJpegPath = path.join(Common.tempDri.images, `MotionPhoto_${Date.now()}_${Math.random().toString(36).slice(2)}.jpg`);
7274
- if (!(await ffmpeg(`-y -i "${imagePath}" -frames:v 1 -q:v 2 "${tempJpegPath}"`)).status) throw new Error(`图片转换 JPEG 失败: ${imagePath}`);
7275
- try {
7276
- return fs.readFileSync(tempJpegPath);
7277
- } finally {
7278
- fs.rmSync(tempJpegPath, { force: true });
7279
- }
7280
- };
7281
- buildGoogleMotionPhoto = async (options) => {
7282
- const { imagePath, videoPath, outputPath, presentationTimestampUs } = options;
7283
- try {
7284
- const imageBuffer = await readOrConvertToJpeg(imagePath);
7285
- const videoBuffer = fs.readFileSync(videoPath);
7286
- let resolvedPresentationTimestampUs = presentationTimestampUs;
7287
- if (resolvedPresentationTimestampUs === void 0 || resolvedPresentationTimestampUs < 0) {
7288
- const videoDurationSeconds = await getMediaDuration(videoPath);
7289
- if (Number.isFinite(videoDurationSeconds) && videoDurationSeconds > 0) resolvedPresentationTimestampUs = Math.round(videoDurationSeconds * 5e5);
7290
- else resolvedPresentationTimestampUs = 15e5;
7291
- }
7292
- const jpegWithXmp = injectXmpToJpeg(imageBuffer, buildMotionPhotoXmp(videoBuffer.length, resolvedPresentationTimestampUs));
7293
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
7294
- fs.writeFileSync(outputPath, Buffer.concat([jpegWithXmp, videoBuffer]));
7295
- logger.debug(`Google Motion Photo 封面生成成功: ${outputPath}`);
7296
- return true;
7297
- } catch (error) {
7298
- logger.error("Google Motion Photo 封面生成失败", error);
7299
- return false;
7300
- }
7301
- };
7302
7385
  });
7303
7386
  var ERROR_CODE_MAP, RECOVERABLE_ERROR_CODES, RECOVERABLE_KEYWORDS, BASE_HEADERS;
7304
7387
  var init_constants = __esmMin(() => {
@@ -8223,6 +8306,7 @@ var init_utils$1 = __esmMin(async () => {
8223
8306
  init_EmojiReaction();
8224
8307
  init_FFmpeg();
8225
8308
  init_ImageHelper();
8309
+ init_MotionPhoto();
8226
8310
  init_Networks();
8227
8311
  init_QRCodeScanner();
8228
8312
  init_Render();
@@ -11116,6 +11200,64 @@ var init_app_schema = __esmMin(() => {
11116
11200
  error: "请输入一个范围在 1000 到 20000 之间的数字"
11117
11201
  }]
11118
11202
  },
11203
+ {
11204
+ type: "divider",
11205
+ title: "Live Photo 兼容设置"
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
+ },
11231
+ {
11232
+ key: "livePhotoSystem",
11233
+ type: "radio",
11234
+ label: "Live Photo 静态图兼容系统",
11235
+ description: "当解析到作品/动态包含 Live Photo 时,合并转发里发送的 Live Photo 静态图按所选系统生成。推荐 OPPO,兼容性最广",
11236
+ orientation: "horizontal",
11237
+ disabled: $ne("livePhotoMode", "livephoto_only"),
11238
+ options: [
11239
+ {
11240
+ label: "Google",
11241
+ value: "google",
11242
+ description: "Google Motion Photo 格式"
11243
+ },
11244
+ {
11245
+ label: "小米(HyperOS)",
11246
+ value: "xiaomi",
11247
+ description: "兼容小米(任何版本)和 Google,但无法被 OPPO 识别"
11248
+ },
11249
+ {
11250
+ label: "OPPO(ColorOS)",
11251
+ value: "oppo",
11252
+ description: "推荐,兼容 OPPO、小米(较新版本)和 Google"
11253
+ },
11254
+ {
11255
+ label: "华为/荣耀(HarmonyOS/MagicOS)",
11256
+ value: "huawei_honor",
11257
+ description: "理论可行但未实测"
11258
+ }
11259
+ ]
11260
+ },
11119
11261
  {
11120
11262
  type: "divider",
11121
11263
  title: "API服务配置"
@@ -12798,11 +12940,18 @@ var init_api = __esmMin(async () => {
12798
12940
  apiRouter.use("/platforms/douyin", ...authMiddlewares, router$1);
12799
12941
  apiRouter.use("/platforms/bilibili", ...authMiddlewares, router);
12800
12942
  });
12801
- var videoStreamRouter, getVideoRouter;
12943
+ var videoStreamRouter, getVideoRouter, videoPreviewEventsRouter;
12802
12944
  var init_router = __esmMin(async () => {
12945
+ await init_client();
12803
12946
  await init_utils$1();
12947
+ await init_Config();
12804
12948
  videoStreamRouter = (req, res) => {
12805
- 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
+ }
12806
12955
  const videoPath = Common.validateVideoRequest(filename, res);
12807
12956
  if (!videoPath) return;
12808
12957
  try {
@@ -12865,17 +13014,76 @@ var init_router = __esmMin(async () => {
12865
13014
  }
12866
13015
  };
12867
13016
  getVideoRouter = (req, res) => {
12868
- const filename = req.params.filename;
12869
- 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;
12870
13025
  const videoDataUrl = `/api/kkk/stream/${encodeURIComponent(filename)}`;
12871
- const resPath = path.join(Root.pluginPath, "/resources") + "/".replace(/\\/g, "/");
12872
- const htmlContent = template(path.join(resPath, "template", "videoView", "index.html"), {
12873
- videoDataUrl,
12874
- 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`
12875
13038
  });
13039
+ res.setHeader("Cache-Control", "no-cache");
12876
13040
  res.setHeader("Content-Type", "text/html; charset=utf-8");
12877
13041
  res.send(htmlContent);
12878
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
+ };
12879
13087
  });
12880
13088
  var import_lib, import_dist, server, proxyOptions, app$1;
12881
13089
  var init_Register = __esmMin(async () => {
@@ -12914,6 +13122,7 @@ var init_Register = __esmMin(async () => {
12914
13122
  } }).startServer(Config.app.APIServerPort);
12915
13123
  app$1.get("/stream/:filename", videoStreamRouter);
12916
13124
  app$1.get("/video/:filename", getVideoRouter);
13125
+ app$1.get("/video/:filename/events", videoPreviewEventsRouter);
12917
13126
  app$1.use("/v1", apiRouter);
12918
13127
  app.use("/api/kkk", app$1);
12919
13128
  });
@@ -13832,7 +14041,7 @@ const DouyinWeb = (all) => [components.accordion.create("douyin", {
13832
14041
  label: "合辑 Live 图 BGM 合并方式",
13833
14042
  orientation: "horizontal",
13834
14043
  defaultValue: all.douyin.liveImageMergeMode.toString(),
13835
- isDisabled: !all.douyin.switch,
14044
+ isDisabled: !all.douyin.switch || all.app.livePhotoMode === "livephoto_only",
13836
14045
  radio: [components.radio.create("liveImageMergeMode:radio-1", {
13837
14046
  label: "连续",
13838
14047
  value: "continuous",
@@ -14496,6 +14705,74 @@ const webConfig = defineConfig({
14496
14705
  error: "请输入一个范围在 1000 到 20000 之间的数字"
14497
14706
  }]
14498
14707
  }),
14708
+ components.divider.create("divider-app-live-photo", {
14709
+ description: "Live Photo 兼容设置",
14710
+ descPosition: 20
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
+ }),
14735
+ components.radio.group("livePhotoSystem", {
14736
+ label: "Live Photo 静态图兼容系统",
14737
+ description: "当解析到作品/动态包含 Live Photo 时,合并转发里发送的 Live Photo 静态图按所选系统生成。推荐 OPPO,兼容性最广",
14738
+ orientation: "horizontal",
14739
+ defaultValue: all.app.livePhotoSystem || "oppo",
14740
+ isDisabled: all.app.livePhotoMode === "video_only",
14741
+ radio: [
14742
+ components.radio.create("livePhotoSystem-google", {
14743
+ label: "Google",
14744
+ description: "Google Motion Photo 格式",
14745
+ value: "google"
14746
+ }),
14747
+ components.radio.create("livePhotoSystem-xiaomi", {
14748
+ label: "小米(HyperOS)",
14749
+ description: "兼容小米(任何版本)和 Google,但无法被 OPPO 识别",
14750
+ value: "xiaomi"
14751
+ }),
14752
+ components.radio.create("livePhotoSystem-oppo", {
14753
+ label: "OPPO(ColorOS)",
14754
+ description: "推荐,兼容 OPPO、小米(较新版本)和 Google",
14755
+ value: "oppo"
14756
+ }),
14757
+ components.radio.create("livePhotoSystem-huawei-honor", {
14758
+ label: "华为/荣耀(HarmonyOS/MagicOS)",
14759
+ description: "理论可行但未实测(作者无对应设备)",
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
14773
+ })
14774
+ ]
14775
+ }),
14499
14776
  components.divider.create("divider-app-api", {
14500
14777
  description: "API服务配置",
14501
14778
  descPosition: 20
@@ -15851,7 +16128,6 @@ var Bilibili = class extends Base {
15851
16128
  });
15852
16129
  if (livePhoto.filepath) {
15853
16130
  const outputPath = Common.tempDri.video + `Bilibili_Live_${Date.now()}_${index}.mp4`;
15854
- let success;
15855
16131
  const staticImg = await downloadFile(img$2.url, {
15856
16132
  title: `Bilibili_static_${Date.now()}_${index}.jpg`,
15857
16133
  headers: BASE_HEADERS,
@@ -15861,28 +16137,34 @@ var Bilibili = class extends Base {
15861
16137
  filepath: staticImg.filepath,
15862
16138
  totalBytes: 0
15863
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";
15864
16143
  const loopCount = 3;
15865
16144
  if (!staticImg.filepath) {
15866
16145
  await Common.removeFile(livePhoto.filepath, true);
15867
16146
  continue;
15868
16147
  }
15869
- success = (await loopVideoWithTransition({
15870
- inputPath: livePhoto.filepath,
15871
- outputPath,
15872
- loopCount,
15873
- staticImagePath: staticImg.filepath,
15874
- transitionEnabled: loopCount > 1
15875
- })).success;
15876
- if (success) {
15877
- const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
15878
- fs.renameSync(outputPath, filePath);
15879
- logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
15880
- temp.push({
15881
- filepath: filePath,
15882
- totalBytes: 0
15883
- });
15884
- const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
15885
- 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) {
15886
16168
  let hasPushedMotionPhotoCover = false;
15887
16169
  if (staticImg.filepath) {
15888
16170
  const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(/* @__PURE__ */ new Date(), "yyyyMMdd_HHmmss_SSS")}_${index}.jpg`;
@@ -15904,9 +16186,9 @@ var Bilibili = class extends Base {
15904
16186
  const imageUrl = await processImageUrl(img$2.url, title, index);
15905
16187
  imgArray.push(segment.image(imageUrl));
15906
16188
  }
15907
- logger.mark("正在尝试删除缓存文件");
15908
- await Common.removeFile(livePhoto.filepath, true);
15909
- } else await Common.removeFile(livePhoto.filepath, true);
16189
+ }
16190
+ logger.mark("正在尝试删除缓存文件");
16191
+ await Common.removeFile(livePhoto.filepath, true);
15910
16192
  }
15911
16193
  } else {
15912
16194
  const imageUrl = await processImageUrl(img$2.url, title, index);
@@ -17694,27 +17976,34 @@ var Bilibilipush = class extends Base {
17694
17976
  filepath: staticImg.filepath,
17695
17977
  totalBytes: 0
17696
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";
17697
17982
  const loopCount = 3;
17698
17983
  if (!staticImg.filepath) {
17699
17984
  await Common.removeFile(livePhoto.filepath, true);
17700
17985
  continue;
17701
17986
  }
17702
- if ((await loopVideoWithTransition({
17703
- inputPath: livePhoto.filepath,
17704
- outputPath,
17705
- loopCount,
17706
- staticImagePath: staticImg.filepath,
17707
- transitionEnabled: loopCount > 1
17708
- })).success) {
17709
- const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
17710
- fs.renameSync(outputPath, filePath);
17711
- logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
17712
- temp.push({
17713
- filepath: filePath,
17714
- totalBytes: 0
17715
- });
17716
- const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
17717
- 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) {
17718
18007
  let hasPushedMotionPhotoCover = false;
17719
18008
  if (staticImg.filepath) {
17720
18009
  const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${Date.now()}_${index}.jpg`;
@@ -17736,11 +18025,10 @@ var Bilibilipush = class extends Base {
17736
18025
  const imageUrl = await processImageUrl(imageSrc, title, index);
17737
18026
  imgArray.push(segment.image(imageUrl));
17738
18027
  }
17739
- logger.mark("正在尝试删除缓存文件");
17740
- await Common.removeFile(livePhoto.filepath, true);
17741
- continue;
17742
18028
  }
18029
+ logger.mark("正在尝试删除缓存文件");
17743
18030
  await Common.removeFile(livePhoto.filepath, true);
18031
+ continue;
17744
18032
  }
17745
18033
  }
17746
18034
  if (imageSrc) {
@@ -18621,56 +18909,61 @@ var DouYin = class extends Base {
18621
18909
  });
18622
18910
  staticImgPath = staticImg.filepath ?? "";
18623
18911
  }
18624
- const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
18625
- const safeStaticPath = staticImgPath || liveimg.filepath;
18626
- const result = await loopVideoWithTransition({
18627
- inputPath: liveimg.filepath,
18628
- outputPath,
18629
- loopCount,
18630
- staticImagePath: safeStaticPath,
18631
- transitionEnabled,
18632
- bgmPath: liveimgbgm?.filepath,
18633
- mergeMode,
18634
- context: bgmContext ?? void 0
18635
- });
18636
- const success = result.success;
18637
- if (mergeMode === "continuous" && result.context) bgmContext = result.context;
18638
- if (success) {
18639
- const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
18640
- fs.renameSync(outputPath, filePath);
18641
- logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
18642
- temp.push({
18643
- filepath: filePath,
18644
- 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
18645
18927
  });
18646
- const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
18647
- processedImages.push(segment.video(videoPath));
18648
- if (imageItem.clip_type === 5 && imageItem.url_list?.[0]) {
18649
- let hasPushedMotionPhotoCover = false;
18650
- if (staticImgPath) {
18651
- const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(/* @__PURE__ */ new Date(), "yyyyMMdd_HHmmss_SSS")}_${index}.jpg`;
18652
- if (await buildGoogleMotionPhoto({
18653
- imagePath: staticImgPath,
18654
- videoPath: liveimg.filepath,
18655
- outputPath: motionPhotoCoverPath
18656
- })) {
18657
- temp.push({
18658
- filepath: motionPhotoCoverPath,
18659
- totalBytes: 0
18660
- });
18661
- const motionPhotoCover = Config.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
18662
- processedImages.push(segment.image(motionPhotoCover));
18663
- hasPushedMotionPhotoCover = true;
18664
- }
18665
- }
18666
- if (!hasPushedMotionPhotoCover) {
18667
- const imageUrl = await processImageUrl(imageItem.url_list[0], g_title, index);
18668
- processedImages.push(segment.image(imageUrl));
18669
- }
18670
- }
18671
- logger.mark("正在尝试删除缓存文件");
18672
- await Common.removeFile(liveimg.filepath, true);
18673
- } else await Common.removeFile(liveimg.filepath, true);
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;
18958
+ }
18959
+ }
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);
18674
18967
  }
18675
18968
  }
18676
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);
@@ -18761,56 +19054,61 @@ var DouYin = class extends Base {
18761
19054
  });
18762
19055
  staticImgPath = staticImg.filepath ?? "";
18763
19056
  }
18764
- const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
18765
- const safeStaticPath = staticImgPath || livePhoto.filepath;
18766
- const result = await loopVideoWithTransition({
18767
- inputPath: livePhoto.filepath,
18768
- outputPath,
18769
- loopCount,
18770
- staticImagePath: safeStaticPath,
18771
- transitionEnabled,
18772
- bgmPath: liveimgbgm?.filepath,
18773
- mergeMode,
18774
- context: bgmContext ?? void 0
18775
- });
18776
- const success = result.success;
18777
- if (mergeMode === "continuous" && result.context) bgmContext = result.context;
18778
- if (success) {
18779
- const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
18780
- fs.renameSync(outputPath, filePath);
18781
- logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
18782
- temp.push({
18783
- filepath: filePath,
18784
- 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
18785
19072
  });
18786
- const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
18787
- images.push(segment.video(videoPath));
18788
- if (item.clip_type === 5 && item.url_list?.[0]) {
18789
- let hasPushedMotionPhotoCover = false;
18790
- if (staticImgPath) {
18791
- const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(/* @__PURE__ */ new Date(), "yyyyMMdd_HHmmss_SSS")}_${index}.jpg`;
18792
- if (await buildGoogleMotionPhoto({
18793
- imagePath: staticImgPath,
18794
- videoPath: livePhoto.filepath,
18795
- outputPath: motionPhotoCoverPath
18796
- })) {
18797
- temp.push({
18798
- filepath: motionPhotoCoverPath,
18799
- totalBytes: 0
18800
- });
18801
- const motionPhotoCover = Config.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
18802
- images.push(segment.image(motionPhotoCover));
18803
- hasPushedMotionPhotoCover = true;
18804
- }
18805
- }
18806
- if (!hasPushedMotionPhotoCover) {
18807
- const imageUrl = await processImageUrl(item.url_list[0], g_title, index);
18808
- 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;
18809
19103
  }
18810
19104
  }
18811
- logger.mark("正在尝试删除缓存文件");
18812
- await Common.removeFile(livePhoto.filepath, true);
18813
- } 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);
18814
19112
  }
18815
19113
  }
18816
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);
@@ -18833,11 +19131,12 @@ var DouYin = class extends Base {
18833
19131
  if (music.play_url.uri === "") music_url = JSON.parse(music.extra).original_song_url;
18834
19132
  else music_url = music.play_url.uri;
18835
19133
  if (!isVideo && Config.app.removeCache === false && music_url !== void 0) try {
18836
- const path$1 = Common.tempDri.images + `${g_title}/BGM.mp3`;
18837
- await new Network({
18838
- url: music_url,
18839
- type: "arraybuffer"
18840
- }).getData().then((data$2) => fs.promises.writeFile(path$1, Buffer.from(data$2)));
19134
+ const title = g_title ?? VideoData.data.aweme_detail.preview_title.substring(0, 50).replace(/[\\/:*?"<>|\r\n]/g, " ");
19135
+ const path$1 = Common.tempDri.images + `${title}.mp3`;
19136
+ await downloadFile(music_url, {
19137
+ title,
19138
+ filepath: path$1
19139
+ });
18841
19140
  } catch (error) {
18842
19141
  console.log(error);
18843
19142
  }
@@ -19914,56 +20213,61 @@ var DouYinpush = class extends Base {
19914
20213
  });
19915
20214
  staticImgPath = staticImg.filepath ?? "";
19916
20215
  }
19917
- const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
19918
- const safeStaticPath = staticImgPath || liveimg.filepath;
19919
- const result = await loopVideoWithTransition({
19920
- inputPath: liveimg.filepath,
19921
- outputPath,
19922
- loopCount,
19923
- staticImagePath: safeStaticPath,
19924
- transitionEnabled,
19925
- bgmPath: liveimgbgm?.filepath,
19926
- mergeMode,
19927
- context: bgmContext ?? void 0
19928
- });
19929
- const success = result.success;
19930
- if (mergeMode === "continuous" && result.context) bgmContext = result.context;
19931
- if (success) {
19932
- const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
19933
- fs.renameSync(outputPath, filePath);
19934
- logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
19935
- temp.push({
19936
- filepath: filePath,
19937
- 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
19938
20231
  });
19939
- const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
19940
- images.push(segment.video(videoPath));
19941
- if (item.clip_type === 5 && item.url_list?.[0]) {
19942
- let hasPushedMotionPhotoCover = false;
19943
- if (staticImgPath) {
19944
- const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(/* @__PURE__ */ new Date(), "yyyyMMdd_HHmmss_SSS")}_${index}.jpg`;
19945
- if (await buildGoogleMotionPhoto({
19946
- imagePath: staticImgPath,
19947
- videoPath: liveimg.filepath,
19948
- outputPath: motionPhotoCoverPath
19949
- })) {
19950
- temp.push({
19951
- filepath: motionPhotoCoverPath,
19952
- totalBytes: 0
19953
- });
19954
- const motionPhotoCover = Config.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
19955
- images.push(segment.image(motionPhotoCover));
19956
- hasPushedMotionPhotoCover = true;
19957
- }
19958
- }
19959
- if (!hasPushedMotionPhotoCover) {
19960
- const imageUrl = await processImageUrl(item.url_list[0], Detail_Data.desc, index);
19961
- 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;
19962
20262
  }
19963
20263
  }
19964
- logger.mark("正在尝试删除缓存文件");
19965
- await Common.removeFile(liveimg.filepath, true);
19966
- } 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);
19967
20271
  }
19968
20272
  }
19969
20273
  const bot = karin$1.getBot(botId);
@@ -20022,56 +20326,61 @@ var DouYinpush = class extends Base {
20022
20326
  });
20023
20327
  staticImgPath = staticImg.filepath ?? "";
20024
20328
  }
20025
- const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
20026
- const safeStaticPath = staticImgPath || liveimg.filepath;
20027
- const result = await loopVideoWithTransition({
20028
- inputPath: liveimg.filepath,
20029
- outputPath,
20030
- loopCount,
20031
- staticImagePath: safeStaticPath,
20032
- transitionEnabled,
20033
- bgmPath: liveimgbgm?.filepath,
20034
- mergeMode,
20035
- context: bgmContext ?? void 0
20036
- });
20037
- const success = result.success;
20038
- if (mergeMode === "continuous" && result.context) bgmContext = result.context;
20039
- if (success) {
20040
- const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`;
20041
- fs.renameSync(outputPath, filePath);
20042
- logger.mark(`视频文件重命名完成: ${outputPath.split("/").pop()} -> ${filePath.split("/").pop()}`);
20043
- temp.push({
20044
- filepath: filePath,
20045
- 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
20046
20344
  });
20047
- const videoPath = Config.upload.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
20048
- processedImages.push(segment.video(videoPath));
20049
- if (item.clip_type === 5 && item.url_list?.[0]) {
20050
- let hasPushedMotionPhotoCover = false;
20051
- if (staticImgPath) {
20052
- const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(/* @__PURE__ */ new Date(), "yyyyMMdd_HHmmss_SSS")}_${index}.jpg`;
20053
- if (await buildGoogleMotionPhoto({
20054
- imagePath: staticImgPath,
20055
- videoPath: liveimg.filepath,
20056
- outputPath: motionPhotoCoverPath
20057
- })) {
20058
- temp.push({
20059
- filepath: motionPhotoCoverPath,
20060
- totalBytes: 0
20061
- });
20062
- const motionPhotoCover = Config.upload.imageSendMode === "base64" ? `base64://${fs.readFileSync(motionPhotoCoverPath).toString("base64")}` : `file://${motionPhotoCoverPath}`;
20063
- processedImages.push(segment.image(motionPhotoCover));
20064
- hasPushedMotionPhotoCover = true;
20065
- }
20066
- }
20067
- if (!hasPushedMotionPhotoCover) {
20068
- const imageUrl = await processImageUrl(item.url_list[0], Detail_Data.desc, index);
20069
- 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;
20070
20375
  }
20071
20376
  }
20072
- logger.mark("正在尝试删除缓存文件");
20073
- await Common.removeFile(liveimg.filepath, true);
20074
- } 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);
20075
20384
  }
20076
20385
  }
20077
20386
  const bot = karin$1.getBot(botId);
@@ -20544,305 +20853,308 @@ var safeScreenshot = async (page, screenshotPath) => {
20544
20853
  };
20545
20854
  const douyinLogin = async (e) => {
20546
20855
  const msg_id = [];
20547
- try {
20548
- const puppeteer = await snapka.launch({
20549
- headless: "new",
20550
- protocolTimeout: 6e4,
20551
- args: [
20552
- "--disable-blink-features=AutomationControlled",
20553
- "--mute-audio",
20554
- "--window-size=800,600",
20555
- "--disable-gpu",
20556
- "--no-sandbox",
20557
- "--disable-setuid-sandbox",
20558
- "--no-zygote",
20559
- "--disable-extensions",
20560
- "--disable-dev-shm-usage",
20561
- "--disable-background-networking",
20562
- "--disable-sync",
20563
- "--disable-crash-reporter",
20564
- "--disable-translate",
20565
- "--disable-notifications",
20566
- "--disable-device-discovery-notifications",
20567
- "--disable-accelerated-2d-canvas",
20568
- "--autoplay-policy=user-gesture-required",
20569
- "--disable-web-security",
20570
- "--disable-features=IsolateOrigins,site-per-process",
20571
- "--disable-site-isolation-trials",
20572
- "--disable-features=VizDisplayCompositor",
20573
- "--js-flags=--max-old-space-size=128",
20574
- "--disable-software-rasterizer",
20575
- "--disable-webgl",
20576
- "--disable-webgl2",
20577
- "--disable-3d-apis",
20578
- "--disable-accelerated-video-decode",
20579
- "--disable-background-timer-throttling",
20580
- "--disable-backgrounding-occluded-windows",
20581
- "--disable-breakpad",
20582
- "--disable-component-extensions-with-background-pages",
20583
- "--disable-features=TranslateUI,BlinkGenPropertyTrees",
20584
- "--disable-ipc-flooding-protection",
20585
- "--disable-renderer-backgrounding"
20586
- ],
20587
- ignoreDefaultArgs: ["--enable-automation"]
20588
- });
20589
- const getOperatingSystem = () => {
20590
- const os$1 = platform();
20591
- if (os$1 === "win32") return "windows";
20592
- if (os$1 === "darwin") return "macos";
20593
- 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"));
20594
20920
  };
20595
- const page = await newInjectedPage(puppeteer.browser, { fingerprintOptions: {
20596
- devices: ["desktop"],
20597
- operatingSystems: [getOperatingSystem()]
20598
- } });
20599
- await page.setRequestInterception(true);
20600
- page.on("request", async (request) => {
20601
- const resourceType = request.resourceType();
20602
- const url = request.url();
20603
- if (url.includes("passport") || url.includes("login") || url.includes("qrconnect") || url.includes("qrcode")) logger.debug(`[请求] ${resourceType}: ${url}`);
20604
- 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"))) {
20605
- if (url.includes("passport") || url.includes("login") || url.includes("qrconnect")) logger.warn(`[拦截] 登录相关请求被拦截: ${url}`);
20606
- request.abort();
20607
- } else request.continue();
20608
- });
20609
- await page.evaluateOnNewDocument(() => {
20610
- HTMLMediaElement.prototype.play = function() {
20611
- return Promise.reject(/* @__PURE__ */ new Error("Video playback blocked"));
20612
- };
20613
- if (window.MediaSource) window.MediaSource = void 0;
20614
- window.IntersectionObserver = class {
20615
- observe() {}
20616
- unobserve() {}
20617
- disconnect() {}
20618
- };
20619
- });
20620
- await page.goto("https://www.douyin.com", {
20621
- timeout: 12e4,
20622
- waitUntil: "domcontentloaded"
20623
- });
20624
- let qrCodeData;
20625
- try {
20626
- logger.mark("开始等待二维码加载...");
20627
- qrCodeData = await Promise.race([waitQrcode(page), new Promise((_$1, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("QR_CODE_TIMEOUT")), 6e4))]);
20628
- logger.mark("二维码获取成功");
20629
- } catch (error) {
20630
- if (error.message === "QR_CODE_TIMEOUT") {
20631
- logger.warn("获取二维码超时");
20632
- await safeScreenshot(page, path.join(karinPathTemp, Root.pluginName, "DouyinLoginQrcodeError.png"));
20633
- await e.reply("获取二维码超时,请稍后重试", { reply: true });
20634
- } else {
20635
- logger.error("获取二维码失败:", error);
20636
- await e.reply("获取二维码失败,请查看控制台日志", { reply: true });
20637
- }
20638
- await puppeteer.browser.close();
20639
- 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 });
20640
20945
  }
20641
- let gcInterval;
20642
- try {
20643
- const loginQRcode = await Render("douyin/qrcodeImg", qrCodeData.url ? { share_url: qrCodeData.url } : { qrCodeDataUrl: qrCodeData.originalImage });
20644
- const base64Data = loginQRcode[0]?.file;
20645
- if (!base64Data) throw new Error("生成二维码图片失败");
20646
- const cleanBase64 = base64Data.replace(/^base64:\/\//, "");
20647
- const buffer = Buffer.from(cleanBase64, "base64");
20648
- fs.writeFileSync(`${Common.tempDri.default}DouyinLoginQrcode.png`, buffer);
20649
- const message2 = await e.reply(loginQRcode, { reply: true });
20650
- logger.debug("二维码图片消息ID:", message2.messageId);
20651
- msg_id.push(message2.messageId);
20652
- gcInterval = setInterval(async () => {
20653
- try {
20654
- await page.evaluate(() => {
20655
- if (window.gc) window.gc();
20656
- });
20657
- } catch {}
20658
- }, 1e4);
20659
- logger.mark("开始等待用户扫码登录...");
20660
- const loginResult = await Promise.race([new Promise((resolve$1) => {
20661
- const timer = setTimeout(async () => {
20662
- logger.warn("扫码登录超时(2分钟),撤回二维码消息");
20663
- await Promise.all(msg_id.map(async (id) => {
20664
- await e.bot.recallMsg(e.contact, id);
20665
- }));
20666
- resolve$1(false);
20667
- }, 120 * 1e3);
20668
- let secondVerifyHandled = false;
20669
- let scannedHandled = false;
20670
- let responseCount = 0;
20671
- page.on("response", async (response) => {
20672
- responseCount++;
20673
- const url = response.url();
20674
- if (responseCount % 10 === 0) logger.debug(`[心跳] 已收到 ${responseCount} 个响应`);
20675
- 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();
20676
20964
  });
20677
- logger.mark("响应监听器已注册");
20678
- page.on("response", async (response) => {
20679
- try {
20680
- if (response.url().includes("check_qrconnect")) {
20681
- logger.debug(`收到登录轮询响应: ${response.url()}`);
20682
- const hasSidGuard = (response.headers()["set-cookie"] || "").includes("sid_guard");
20683
- logger.debug(`响应头包含 sid_guard: ${hasSidGuard}`);
20684
- if (hasSidGuard) {
20685
- clearTimeout(timer);
20686
- logger.mark("检测到 sid_guard,登录成功");
20687
- logger.debug("开始获取 cookies...");
20688
- const cookies = await puppeteer.browser.cookies();
20689
- logger.debug(`获取到 ${cookies.length} cookies`);
20690
- const cookieString = cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
20691
- logger.debug("开始保存 cookies...");
20692
- Config.Modify("cookies", "douyin", cookieString);
20693
- logger.debug("cookies 保存完成");
20694
- await e.reply("登录成功!用户登录凭证已保存至cookies.yaml", { reply: true });
20695
- await Promise.all(msg_id.map(async (id) => {
20696
- await e.bot.recallMsg(e.contact, id);
20697
- }));
20698
- logger.mark("关闭浏览器...");
20699
- await puppeteer.browser.close();
20700
- logger.mark("浏览器已关闭");
20701
- resolve$1(true);
20702
- return;
20703
- }
20704
- const responseBody = await response.text();
20705
- const jsonResponse = JSON.parse(responseBody);
20706
- logger.debug(`二维码状态:${jsonResponse.data?.status}, error_code: ${jsonResponse.data?.error_code}`);
20707
- if (jsonResponse.data?.status === "scanned" && !scannedHandled) {
20708
- scannedHandled = true;
20709
- logger.mark("检测到二维码已被扫描");
20710
- await Promise.all(msg_id.map(async (id) => {
20711
- await e.bot.recallMsg(e.contact, id);
20712
- }));
20713
- msg_id.length = 0;
20714
- const authMsg = await e.reply("此二维码已被扫描,请在手机上授权以登录", { reply: true });
20715
- 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");
20716
21006
  }
20717
- if (jsonResponse.data?.error_code === 2046 && !secondVerifyHandled) {
20718
- secondVerifyHandled = true;
20719
- logger.mark("检测到需要二次验证");
20720
- clearTimeout(timer);
20721
- (async () => {
20722
- try {
20723
- await page.waitForSelector("#uc-second-verify", { timeout: 5e3 });
20724
- if (!await page.evaluate(() => {
20725
- const element = document.evaluate("//text()[contains(., '接收短信验证码')]/ancestor::*[1]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
20726
- if (element) {
20727
- element.click();
20728
- return true;
20729
- }
20730
- return false;
20731
- })) logger.warn("未找到\"接收短信验证码\"按钮");
20732
- await new Promise((resolve$2) => setTimeout(resolve$2, 2e3));
20733
- const inputSelector = "#uc-second-verify input";
20734
- await page.waitForSelector(inputSelector, { timeout: 1e4 });
20735
- const tipMsg = await e.reply("此次验证需要进行 2FA\n6 位数的验证码已发送至扫码设备绑定的手机号\n请在 60 秒内发送此验证码以通过 2FA", { reply: true });
20736
- msg_id.push(tipMsg.messageId);
20737
- let verifyAttempts = 0;
20738
- const maxAttempts = 2;
20739
- let verifySuccess = false;
20740
- while (verifyAttempts < maxAttempts && !verifySuccess) {
20741
- verifyAttempts++;
20742
- logger.debug(`验证码输入尝试 ${verifyAttempts}/${maxAttempts}`);
20743
- 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) => {
20744
- if (error.message === "2FA_TIMEOUT") {
20745
- logger.warn("2FA验证码输入超时");
20746
- clearTimeout(timer);
20747
- if (gcInterval) clearInterval(gcInterval);
20748
- await puppeteer.browser.close();
20749
- await Promise.all(msg_id.map(async (id) => {
20750
- await e.bot.recallMsg(e.contact, id);
20751
- }));
20752
- await e.reply("验证码验证码输入超时,登录失败", { reply: true });
20753
- resolve$1(true);
20754
- }
20755
- throw error;
20756
- });
20757
- if (!ctx) return;
20758
- await page.evaluate((selector) => {
20759
- const input = document.querySelector(selector);
20760
- if (input) input.value = "";
20761
- }, inputSelector);
20762
- await page.type(inputSelector, ctx.msg);
20763
- const validatePromise = new Promise((resolveValidate) => {
20764
- const validateHandler = async (resp) => {
20765
- if (resp.url().includes("validate_code")) try {
20766
- const validateBody = await resp.text();
20767
- const validateJson = JSON.parse(validateBody);
20768
- logger.debug("验证码验证响应:", validateJson);
20769
- if (validateJson.data?.error_code === 1202) {
20770
- logger.warn("验证码错误");
20771
- page.off("response", validateHandler);
20772
- resolveValidate(false);
20773
- } else if (validateJson.message === "success" || !validateJson.data?.error_code) {
20774
- logger.mark("验证码验证成功");
20775
- page.off("response", validateHandler);
20776
- resolveValidate(true);
20777
- }
20778
- } catch (err) {
20779
- logger.debug("解析验证响应失败:", err);
20780
- }
20781
- };
20782
- page.on("response", validateHandler);
20783
- setTimeout(() => {
20784
- page.off("response", validateHandler);
20785
- resolveValidate(false);
20786
- }, 5e3);
20787
- });
20788
- await page.evaluate(() => {
20789
- const verifyBtn = Array.from(document.querySelectorAll("*")).find((el) => el.textContent?.trim() === "验证");
20790
- if (verifyBtn) verifyBtn.click();
20791
- });
20792
- logger.mark("已提交验证码,等待验证结果...");
20793
- verifySuccess = await validatePromise;
20794
- if (!verifySuccess && verifyAttempts < maxAttempts) {
20795
- const retryMsg = await e.reply("验证码错误,请重新发送正确的 6 位数验证码(剩余机会:1次)", { reply: true });
20796
- msg_id.push(retryMsg.messageId);
20797
- } else if (!verifySuccess && verifyAttempts >= maxAttempts) {
20798
- 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验证码输入超时");
20799
21062
  clearTimeout(timer);
20800
21063
  if (gcInterval) clearInterval(gcInterval);
20801
21064
  await puppeteer.browser.close();
20802
21065
  await Promise.all(msg_id.map(async (id) => {
20803
21066
  await e.bot.recallMsg(e.contact, id);
20804
21067
  }));
20805
- await e.reply("验证码错误,登录失败", { reply: true });
21068
+ await e.reply("验证码验证码输入超时,登录失败", { reply: true });
20806
21069
  resolve$1(true);
20807
- return;
20808
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;
20809
21124
  }
20810
- if (verifySuccess) logger.mark("2FA验证通过,等待最终登录确认...");
20811
- } catch (err) {
20812
- logger.error("二次验证处理失败:", err);
20813
- clearTimeout(timer);
20814
- if (gcInterval) clearInterval(gcInterval);
20815
- await puppeteer.browser.close();
20816
- await Promise.all(msg_id.map(async (id) => {
20817
- await e.bot.recallMsg(e.contact, id);
20818
- }));
20819
- await e.reply("二次验证处理失败,登录失败", { reply: true });
20820
- resolve$1(true);
20821
21125
  }
20822
- })();
20823
- }
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
+ })();
20824
21139
  }
20825
- } catch (error) {
20826
- logger.error("处理响应时出错:", error);
20827
21140
  }
20828
- });
20829
- })]);
20830
- if (gcInterval) clearInterval(gcInterval);
20831
- if (!loginResult) {
20832
- logger.warn("登录超时或失败");
20833
- await puppeteer.browser.close();
20834
- await e.reply("登录超时!二维码已失效!", { reply: true });
20835
- return true;
20836
- }
20837
- } catch (err) {
20838
- logger.error("登录流程出错:", err);
20839
- 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("登录超时或失败");
20840
21149
  await puppeteer.browser.close();
20841
- await e.reply("登录过程出错,请查看控制台日志", { reply: true });
21150
+ await e.reply("登录超时!二维码已失效!", { reply: true });
21151
+ return true;
20842
21152
  }
20843
- } catch (error) {
20844
- logger.error(error);
20845
- 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 });
20846
21158
  }
20847
21159
  return true;
20848
21160
  };
@@ -20890,55 +21202,52 @@ var waitQrcode = async (page) => {
20890
21202
  };
20891
21203
  await init_module();
20892
21204
  await init_Config();
20893
- const task = Config.app.removeCache && karin$1.task("[kkk-缓存自动删除]", "0 */4 * * *", async () => {
20894
- try {
20895
- const twoHoursAgo = Date.now() - 7200 * 1e3;
20896
- const videoDeleted = removeOldFiles(Common.tempDri.video, twoHoursAgo);
20897
- logger.mark(`${Common.tempDri.video} 目录下已删除 ${videoDeleted} 个文件`);
20898
- if (Config.upload.imageSendMode === "file") {
20899
- const imageDeleted = removeOldFiles(Common.tempDri.images, twoHoursAgo);
20900
- logger.mark(`${Common.tempDri.images} 目录下已删除 ${imageDeleted} 个文件`);
20901
- }
20902
- } catch (err) {
20903
- 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} 个文件`);
20904
21213
  }
20905
- });
20906
- 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) => {
20907
21217
  await bilibiliLogin(e);
20908
21218
  return true;
20909
- }, {
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, {
20910
21225
  perm: Config.bilibili.loginPerm,
20911
21226
  name: "kkk-ck管理"
20912
21227
  });
20913
- const dylogin = karin$1.command(/^#?(kkk)?抖音(扫码)?登录$/, async (e) => {
20914
- await douyinLogin(e);
20915
- return true;
20916
- }, {
21228
+ const dylogin = karin$1.command(/^#?(kkk)?抖音(扫码)?登录$/, handleDouyinLogin, {
20917
21229
  perm: Config.douyin.loginPerm,
20918
21230
  name: "kkk-ck管理"
20919
21231
  });
20920
21232
  const removeOldFiles = (dir, beforeTimestamp) => {
20921
21233
  let deletedCount = 0;
20922
- try {
20923
- const files = fs.readdirSync(dir);
20924
- for (const file of files) {
20925
- const filePath = path.join(dir, file);
20926
- const stats = fs.statSync(filePath);
20927
- if (stats.isDirectory()) {
20928
- deletedCount += removeOldFiles(filePath, beforeTimestamp);
20929
- if (fs.readdirSync(filePath).length === 0) fs.rmdirSync(filePath);
20930
- } else if (stats.mtimeMs < beforeTimestamp) {
20931
- fs.unlinkSync(filePath);
20932
- deletedCount++;
20933
- }
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++;
20934
21244
  }
20935
- } catch (err) {
20936
- logger.error(`处理目录 ${dir} 时出错:`, err);
20937
21245
  }
20938
21246
  return deletedCount;
20939
21247
  };
20940
21248
  await init_module();
20941
21249
  await init_Config();
21250
+ await init_ErrorHandler();
20942
21251
  var HELP_MENU_CONFIG = [
20943
21252
  {
20944
21253
  title: "常用功能",
@@ -21047,7 +21356,7 @@ var buildMenuForRole = (role) => {
21047
21356
  };
21048
21357
  }).filter((g) => g.items.length > 0 || g.subGroups && g.subGroups.length > 0);
21049
21358
  };
21050
- const help = karin$1.command(/^#?kkk帮助$/, async (e) => {
21359
+ var handleHelp = wrapWithErrorHandler(async (e) => {
21051
21360
  const masters = config.master().filter((id) => id !== "console");
21052
21361
  const role = !!e.sender && masters.includes(e.sender.userId) ? "master" : "member";
21053
21362
  const menu = buildMenuForRole(role);
@@ -21069,8 +21378,8 @@ const help = karin$1.command(/^#?kkk帮助$/, async (e) => {
21069
21378
  });
21070
21379
  await e.reply(img$2);
21071
21380
  return true;
21072
- }, { name: "kkk-帮助" });
21073
- const version = karin$1.command(/^#?kkk(版本|更新日志)$/, async (e) => {
21381
+ }, { businessName: "KKK帮助" });
21382
+ var handleVersion = wrapWithErrorHandler(async (e) => {
21074
21383
  const changelogContent = fs.readFileSync(Root.pluginPath + "/CHANGELOG.md", "utf8");
21075
21384
  const img$2 = await Render("other/changelog", {
21076
21385
  markdown: logs({
@@ -21084,7 +21393,9 @@ const version = karin$1.command(/^#?kkk(版本|更新日志)$/, async (e) => {
21084
21393
  });
21085
21394
  e.reply(img$2);
21086
21395
  return true;
21087
- }, { 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-版本" });
21088
21399
  await init_date_fns();
21089
21400
  await init_module();
21090
21401
  await init_db();
@@ -21317,6 +21628,7 @@ const testDouyinPush = karin$1.command(/^#测试抖音推送\s*(https?:\/\/[^\s]
21317
21628
  priority: -Infinity
21318
21629
  });
21319
21630
  await init_Config();
21631
+ await init_ErrorHandler();
21320
21632
  await init_Render();
21321
21633
  function getLocalIP() {
21322
21634
  const interfaces = os.networkInterfaces();
@@ -21370,7 +21682,7 @@ async function getHostByConfig() {
21370
21682
  }
21371
21683
  return getLocalIP();
21372
21684
  }
21373
- const qrLogin = karin$1.command(/^#?(kkk)?登录$/i, async (e) => {
21685
+ var handleQrLogin = wrapWithErrorHandler(async (e) => {
21374
21686
  const bot = karin$1.getBot(e.selfId);
21375
21687
  const userId = e.userId;
21376
21688
  if (e.isGroup) {
@@ -21387,105 +21699,93 @@ const qrLogin = karin$1.command(/^#?(kkk)?登录$/i, async (e) => {
21387
21699
  await e.reply("未配置 HTTP_AUTH_KEY 环境变量,无法生成登录二维码");
21388
21700
  return true;
21389
21701
  }
21390
- const qrData = JSON.stringify({
21391
- protocol,
21392
- host,
21393
- port,
21394
- 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}`
21395
21710
  });
21396
- const serverUrl = `${protocol}://${host}:${port}`;
21397
- try {
21398
- const images = await Render("other/qrlogin", {
21399
- share_url: qrData,
21400
- serverUrl
21401
- });
21402
- await karin$1.sendMaster(e.selfId, userId, images);
21403
- if (e.isGroup) await e.reply("登录二维码已私聊发送,请查收~");
21404
- } catch (error) {
21405
- await e.reply("生成二维码失败: " + (error instanceof Error ? error.message : String(error)));
21406
- }
21711
+ await karin$1.sendMaster(e.selfId, userId, images);
21712
+ if (e.isGroup) await e.reply("登录二维码已私聊发送,请查收~");
21407
21713
  return true;
21408
- }, {
21714
+ }, { businessName: "APP扫码登录" });
21715
+ const qrLogin = karin$1.command(/^#?(kkk)?登录$/i, handleQrLogin, {
21409
21716
  perm: "master",
21410
21717
  name: "kkk-APP扫码登录"
21411
21718
  });
21412
21719
  await init_module();
21413
21720
  await init_db();
21414
- const groupStatistics = karin$1.command(/^#?kkk解析统计$/, async (e) => {
21415
- try {
21416
- const groupId = e.isGroup ? e.contact?.peer || "" : "";
21417
- if (!groupId) {
21418
- await e.reply("此命令仅支持在群聊中使用");
21419
- return true;
21420
- }
21421
- let groupName = "";
21422
- let groupMemberCount;
21423
- let groupAvatar;
21424
- try {
21425
- const groupInfo = await e.bot.getGroupInfo(groupId);
21426
- groupName = groupInfo?.groupName || "";
21427
- groupMemberCount = groupInfo?.memberCount;
21428
- groupAvatar = groupInfo?.avatar;
21429
- } catch (error) {
21430
- logger.debug("[统计] 获取群组信息失败:", error);
21431
- }
21432
- const statisticsDB$1 = await getStatisticsDB();
21433
- const groupStats = await statisticsDB$1.getGroupStatistics(groupId);
21434
- const groupUniqueUsers = await statisticsDB$1.getGroupUniqueUsers(groupId);
21435
- const globalSummary = await statisticsDB$1.getGlobalSummary();
21436
- const groupTotalParses = groupStats.reduce((sum, stat) => sum + stat.parseCount, 0);
21437
- const platformData = {
21438
- douyin: groupStats.filter((s) => s.platform === "douyin").reduce((sum, s) => sum + s.parseCount, 0),
21439
- bilibili: groupStats.filter((s) => s.platform === "bilibili").reduce((sum, s) => sum + s.parseCount, 0),
21440
- kuaishou: groupStats.filter((s) => s.platform === "kuaishou").reduce((sum, s) => sum + s.parseCount, 0),
21441
- xiaohongshu: groupStats.filter((s) => s.platform === "xiaohongshu").reduce((sum, s) => sum + s.parseCount, 0)
21442
- };
21443
- const img$2 = await Render("statistics/group", {
21444
- groupId,
21445
- groupName,
21446
- groupMemberCount,
21447
- groupAvatar,
21448
- groupTotalParses,
21449
- groupUniqueUsers,
21450
- platformData,
21451
- globalTotalGroups: globalSummary.totalGroups,
21452
- globalTotalParses: globalSummary.totalParses
21453
- });
21454
- await e.reply(img$2);
21455
- return true;
21456
- } catch (error) {
21457
- 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("此命令仅支持在群聊中使用");
21458
21726
  return true;
21459
21727
  }
21460
- }, { name: "kkk-解析统计" });
21461
- const globalStatistics = karin$1.command(/^#?kkk全局解析统计$/, async (e) => {
21728
+ let groupName = "";
21729
+ let groupMemberCount;
21730
+ let groupAvatar;
21462
21731
  try {
21463
- const statisticsDB$1 = await getStatisticsDB();
21464
- const allStats = await statisticsDB$1.getAllStatistics();
21465
- const historyData = await statisticsDB$1.getRecentHistory(30);
21466
- const groupIds = [...new Set(allStats.map((s) => s.groupId))];
21467
- const groupInfoMap = /* @__PURE__ */ new Map();
21468
- for (const groupId of groupIds) try {
21469
- const groupInfo = await e.bot.getGroupInfo(groupId);
21470
- groupInfoMap.set(groupId, {
21471
- groupName: groupInfo?.groupName,
21472
- groupAvatar: groupInfo?.avatar
21473
- });
21474
- } catch (error) {
21475
- logger.debug(`[统计] 获取群组 ${groupId} 信息失败:`, error);
21476
- }
21477
- const img$2 = await Render("statistics/global", {
21478
- allStats,
21479
- historyData: historyData.reverse(),
21480
- 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
21481
21776
  });
21482
- await e.reply(img$2);
21483
- return true;
21484
21777
  } catch (error) {
21485
- await e.reply(`获取全局解析统计失败:${error}`);
21486
- return true;
21778
+ logger.debug(`[统计] 获取群组 ${groupId} 信息失败:`, error);
21487
21779
  }
21488
- }, {
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, {
21489
21789
  name: "kkk-全局解析统计",
21490
21790
  perm: "master"
21491
21791
  });
@@ -22103,6 +22403,7 @@ const getChangelogImage = async (props) => {
22103
22403
  }) || null;
22104
22404
  };
22105
22405
  await init_module();
22406
+ await init_ErrorHandler();
22106
22407
  await init_semver();
22107
22408
  var UPDATE_LOCK_KEY = "kkk:update:lock";
22108
22409
  var UPDATE_MSGID_KEY = "kkk:update:msgId";
@@ -22144,30 +22445,29 @@ var Handler = async (e) => {
22144
22445
  }
22145
22446
  return true;
22146
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" });
22147
22463
  const kkkUpdate = hooks.message.friend(async (e, next) => {
22148
22464
  if (e.msg.includes("更新")) {
22149
22465
  const msgId = await db.get(UPDATE_MSGID_KEY);
22150
- if (e.replyId === msgId) try {
22151
- e.reply("开始更新 karin-plugin-kkk ...", { reply: true });
22152
- const upd = await checkPkgUpdate(Root.pluginName, { compare: "semver" });
22153
- if (upd.status === "yes") {
22154
- const result = await updatePkg(Root.pluginName);
22155
- if (result.status === "ok") {
22156
- if ((await e.reply(`${Root.pluginName} 更新成功!\n${result.local} -> ${result.remote}\n开始执行重启......`)).messageId) try {
22157
- await db.del(UPDATE_MSGID_KEY);
22158
- await db.del(UPDATE_LOCK_KEY);
22159
- } catch {}
22160
- await restart(e.selfId, e.contact, e.messageId);
22161
- } else e.reply(`${Root.pluginName} 更新失败: ${result.data ?? "更新执行失败"}`);
22162
- } else if (upd.status === "no") e.reply("未检测到可更新版本。");
22163
- else e.reply(`${Root.pluginName} 更新失败: ${upd.error?.message ?? String(upd.error)}`);
22164
- } catch (error) {
22165
- e.reply(`${Root.pluginName} 更新失败: ${error.message}`);
22166
- }
22466
+ if (e.replyId === msgId) await handleUpdateHook(e);
22167
22467
  }
22168
22468
  next();
22169
22469
  }, { priority: 100 });
22170
- const kkkUpdateCommand = karin$1.command(/^#?kkk更新$/, async (e) => {
22470
+ var handleKkkUpdate = wrapWithErrorHandler(async (e) => {
22171
22471
  const upd = await checkPkgUpdate(Root.pluginName, { compare: "semver" });
22172
22472
  if (upd.status === "error") {
22173
22473
  e.reply(`获取远程版本失败:${upd.error?.message ?? String(upd.error)}`);
@@ -22189,20 +22489,17 @@ const kkkUpdateCommand = karin$1.command(/^#?kkk更新$/, async (e) => {
22189
22489
  });
22190
22490
  if (ChangeLogImg) e.reply([segment.text(`${Root.pluginName} 的更新日志:`), ...ChangeLogImg], { reply: true });
22191
22491
  else e.reply("获取更新日志失败,更新进程继续......", { reply: true });
22192
- try {
22193
- const result = await updatePkg(Root.pluginName);
22194
- if (result.status === "ok") {
22195
- const msgResult = await e.reply(`${Root.pluginName} 更新成功!\n${result.local} -> ${result.remote}\n开始执行重启......`);
22196
- if (msgResult.messageId) try {
22197
- await db.del(UPDATE_MSGID_KEY);
22198
- await db.del(UPDATE_LOCK_KEY);
22199
- } catch {}
22200
- await restart(e.selfId, e.contact, msgResult.messageId);
22201
- } else e.reply(`${Root.pluginName} 更新失败: ${result.data ?? "更新执行失败"}`);
22202
- } catch (error) {
22203
- e.reply(`${Root.pluginName} 更新失败: ${error.message}`);
22204
- }
22205
- }, { 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-更新" });
22206
22503
  const update = karin$1.task("kkk-更新检测", "*/5 * * * *", Handler, {
22207
22504
  name: "kkk-更新检测",
22208
22505
  log: false