karin-plugin-kkk 2.41.0 → 2.41.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [2.41.2](https://github.com/ikenxuan/karin-plugin-kkk/compare/v2.41.1...v2.41.2) (2026-08-02)
6
+
7
+
8
+ ### 🐛 错误修复
9
+
10
+ * ci ([b4611b2](https://github.com/ikenxuan/karin-plugin-kkk/commit/b4611b2d8d754271c23da96d51eb8f29592cb848))
11
+
12
+ ## [2.41.1](https://github.com/ikenxuan/karin-plugin-kkk/compare/v2.41.0...v2.41.1) (2026-08-01)
13
+
14
+
15
+ ### 🐛 错误修复
16
+
17
+ * **douyin:** 修复部分状态下会发送空合并转发消息的情况,兜底插入视频 fix [#343](https://github.com/ikenxuan/karin-plugin-kkk/issues/343) ([0e785c9](https://github.com/ikenxuan/karin-plugin-kkk/commit/0e785c9be1fc8caa84509ac2393d3b0e44263151))
18
+
19
+
20
+ ### 🧰 其他更新
21
+
22
+ * use pnpm v11 ([8587b7f](https://github.com/ikenxuan/karin-plugin-kkk/commit/8587b7fe660f7f3f987c2038f86a8432cf51d8c8))
23
+
5
24
  ## [2.41.0](https://github.com/ikenxuan/karin-plugin-kkk/compare/v2.40.3...v2.41.0) (2026-07-30)
6
25
 
7
26
 
@@ -1,10 +1,10 @@
1
1
  {
2
- "version": "2.41.0",
3
- "buildTime": "2026-07-30T12:51:42.229Z",
4
- "buildTimestamp": 1785415902229,
2
+ "version": "2.41.2",
3
+ "buildTime": "2026-08-02T03:48:44.956Z",
4
+ "buildTimestamp": 1785642524956,
5
5
  "name": "karin-plugin-kkk",
6
6
  "description": "Karin 的「抖音」「B 站」视频解析/动态推送插件",
7
7
  "homepage": "https://github.com/ikenxuan/karin-plugin-kkk",
8
- "commitHash": "96b43e4b61c746909eb8e00d0ad98e2fac8a6e08",
9
- "shortCommitHash": "96b43e4b"
8
+ "commitHash": "f55f3ee34be37df21d7543caf195ef311a19c5d4",
9
+ "shortCommitHash": "f55f3ee3"
10
10
  }
@@ -2,7 +2,7 @@ import { EventEmitter as EventEmitter$1 } from "node:events";
2
2
  import zod from "zod";
3
3
  import { AxiosRequestConfig, AxiosResponse, RawAxiosResponseHeaders } from "axios";
4
4
  import express from "express";
5
- //#region ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.d.ts
5
+ //#region ../../node_modules/.pnpm/chalk@6.0.0/node_modules/chalk/source/vendor/supports-color/index.d.ts
6
6
  /**
7
7
  Levels:
8
8
  - `0` - All colors disabled.
@@ -12,7 +12,7 @@ Levels:
12
12
  */
13
13
  type ColorSupportLevel = 0 | 1 | 2 | 3;
14
14
  //#endregion
15
- //#region ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/index.d.ts
15
+ //#region ../../node_modules/.pnpm/chalk@6.0.0/node_modules/chalk/source/index.d.ts
16
16
  interface ChalkInstance {
17
17
  (...text: unknown[]): string;
18
18
  /**
@@ -25,6 +25,8 @@ interface ChalkInstance {
25
25
  - `1` - Basic 16 colors support.
26
26
  - `2` - ANSI 256 colors support.
27
27
  - `3` - Truecolor 16 million colors support.
28
+
29
+ @throws If the assigned value is not an integer from 0 to 3.
28
30
  */
29
31
  level: ColorSupportLevel;
30
32
  /**
@@ -54,6 +56,8 @@ interface ChalkInstance {
54
56
  /**
55
57
  Use an [8-bit unsigned number](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) to set text color.
56
58
 
59
+ The value is downsampled to the 16-color palette on terminals that only support basic colors (level 1), so `chalk.ansi256(196)` becomes 91 (ANSI escape for bright red).
60
+
57
61
  @example
58
62
  ```
59
63
  import chalk from 'chalk';
@@ -87,7 +91,9 @@ interface ChalkInstance {
87
91
  */
88
92
  bgHex: (color: string) => this;
89
93
  /**
90
- Use a [8-bit unsigned number](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) to set background color.
94
+ Use an [8-bit unsigned number](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) to set background color.
95
+
96
+ The value is downsampled to the 16-color palette on terminals that only support basic colors (level 1), so `chalk.bgAnsi256(196)` becomes 101 (ANSI escape for bright red background).
91
97
 
92
98
  @example
93
99
  ```
@@ -97,6 +103,49 @@ interface ChalkInstance {
97
103
  ```
98
104
  */
99
105
  bgAnsi256: (index: number) => this;
106
+ /**
107
+ Use RGB values to set underline color.
108
+
109
+ The underline color is only visible when an underline style is also applied.
110
+
111
+ @example
112
+ ```
113
+ import chalk from 'chalk';
114
+
115
+ chalk.underlineRgb(222, 173, 237).underlineCurly('Hello, world!');
116
+ ```
117
+ */
118
+ underlineRgb: (red: number, green: number, blue: number) => this;
119
+ /**
120
+ Use HEX value to set underline color.
121
+
122
+ The underline color is only visible when an underline style is also applied.
123
+
124
+ @param color - Hexadecimal value representing the desired color.
125
+
126
+ @example
127
+ ```
128
+ import chalk from 'chalk';
129
+
130
+ chalk.underlineHex('#DEADED').underlineCurly('Hello, world!');
131
+ ```
132
+ */
133
+ underlineHex: (color: string) => this;
134
+ /**
135
+ Use an [8-bit unsigned number](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) to set underline color.
136
+
137
+ The underline color is only visible when an underline style is also applied.
138
+
139
+ The value is downsampled to the first 16 palette entries on terminals that only support basic colors (level 1), so `chalk.underlineAnsi256(196)` becomes 9 (the palette index for bright red).
140
+
141
+ @example
142
+ ```
143
+ import chalk from 'chalk';
144
+
145
+ chalk.underlineAnsi256(201).underlineCurly('Hello, world!');
146
+ ```
147
+ */
148
+ underlineAnsi256: (index: number) => this;
100
149
  /**
101
150
  Modifier: Reset the current style.
102
151
  */
@@ -117,6 +166,22 @@ interface ChalkInstance {
117
166
  Modifier: Put a horizontal line below the text. *(Not widely supported)*
118
167
  */
119
168
  readonly underline: this;
169
+ /**
170
+ Modifier: Put a double horizontal line below the text. *(Not widely supported)*
171
+ */
172
+ readonly underlineDouble: this;
173
+ /**
174
+ Modifier: Put a curly horizontal line below the text. *(Not widely supported)*
175
+ */
176
+ readonly underlineCurly: this;
177
+ /**
178
+ Modifier: Put a dotted horizontal line below the text. *(Not widely supported)*
179
+ */
180
+ readonly underlineDotted: this;
181
+ /**
182
+ Modifier: Put a dashed horizontal line below the text. *(Not widely supported)*
183
+ */
184
+ readonly underlineDashed: this;
120
185
  /**
121
186
  Modifier: Put a horizontal line above the text. *(Not widely supported)*
122
187
  */
@@ -147,11 +212,11 @@ interface ChalkInstance {
147
212
  readonly magenta: this;
148
213
  readonly cyan: this;
149
214
  readonly white: this;
150
- /*
215
+ /**
151
216
  Alias for `blackBright`.
152
217
  */
153
218
  readonly gray: this;
154
- /*
219
+ /**
155
220
  Alias for `blackBright`.
156
221
  */
157
222
  readonly grey: this;
@@ -171,11 +236,11 @@ interface ChalkInstance {
171
236
  readonly bgMagenta: this;
172
237
  readonly bgCyan: this;
173
238
  readonly bgWhite: this;
174
- /*
239
+ /**
175
240
  Alias for `bgBlackBright`.
176
241
  */
177
242
  readonly bgGray: this;
178
- /*
243
+ /**
179
244
  Alias for `bgBlackBright`.
180
245
  */
181
246
  readonly bgGrey: this;
@@ -187,6 +252,30 @@ interface ChalkInstance {
187
252
  readonly bgMagentaBright: this;
188
253
  readonly bgCyanBright: this;
189
254
  readonly bgWhiteBright: this;
255
+ readonly underlineBlack: this;
256
+ readonly underlineRed: this;
257
+ readonly underlineGreen: this;
258
+ readonly underlineYellow: this;
259
+ readonly underlineBlue: this;
260
+ readonly underlineMagenta: this;
261
+ readonly underlineCyan: this;
262
+ readonly underlineWhite: this;
263
+ /**
264
+ Alias for `underlineBlackBright`.
265
+ */
266
+ readonly underlineGray: this;
267
+ /**
268
+ Alias for `underlineBlackBright`.
269
+ */
270
+ readonly underlineGrey: this;
271
+ readonly underlineBlackBright: this;
272
+ readonly underlineRedBright: this;
273
+ readonly underlineGreenBright: this;
274
+ readonly underlineYellowBright: this;
275
+ readonly underlineBlueBright: this;
276
+ readonly underlineMagentaBright: this;
277
+ readonly underlineCyanBright: this;
278
+ readonly underlineWhiteBright: this;
190
279
  }
191
280
  //#endregion
192
281
  //#region ../amagi/packages/core/dist/default/index.d.ts
@@ -38873,6 +38873,18 @@ function getWorkTypeDisplayName(workTypeInfo) {
38873
38873
  if (workTypeInfo.isLive) return "直播";
38874
38874
  return "未知";
38875
38875
  }
38876
+ /**
38877
+ * 抖音图文里的 clip_type=5 才能生成系统实况图;clip_type=4 是短片。
38878
+ * 用户选择仅发送实况图时,短片仍需兜底成视频节点,避免生成空合并转发。
38879
+ */
38880
+ function getDouyinLiveImageSendPolicy(clipType, livePhotoMode = "video_and_livephoto") {
38881
+ const isStaticImage = clipType === 2 || clipType === void 0;
38882
+ const canGenerateLivePhoto = clipType === 5;
38883
+ return {
38884
+ shouldGenerateVideo: livePhotoMode === "video_and_livephoto" || livePhotoMode === "video_only" || livePhotoMode === "livephoto_only" && !isStaticImage && !canGenerateLivePhoto,
38885
+ shouldGenerateLivePhoto: canGenerateLivePhoto && (livePhotoMode === "video_and_livephoto" || livePhotoMode === "livephoto_only")
38886
+ };
38887
+ }
38876
38888
  //#endregion
38877
38889
  //#region src/platform/douyin/push/render.ts
38878
38890
  /**
@@ -39487,9 +39499,7 @@ var DouYin = class DouYin extends Base {
39487
39499
  });
39488
39500
  staticImgPath = staticImg.filepath ?? "";
39489
39501
  }
39490
- const livePhotoMode = Config.app.livePhotoMode ?? "video_and_livephoto";
39491
- const shouldGenerateVideo = livePhotoMode === "video_and_livephoto" || livePhotoMode === "video_only";
39492
- const shouldGenerateLivePhoto = livePhotoMode === "video_and_livephoto" || livePhotoMode === "livephoto_only";
39502
+ const { shouldGenerateVideo, shouldGenerateLivePhoto } = getDouyinLiveImageSendPolicy(imageItem.clip_type, Config.app.livePhotoMode ?? "video_and_livephoto");
39493
39503
  if (shouldGenerateVideo) {
39494
39504
  const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
39495
39505
  const safeStaticPath = staticImgPath || liveimg.filepath;
@@ -39551,14 +39561,17 @@ var DouYin = class DouYin extends Base {
39551
39561
  });
39552
39562
  processedImages.push(...tipImg);
39553
39563
  }
39554
- 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);
39555
39564
  try {
39556
- await this.e.bot.sendForwardMsg(this.e.contact, Element, {
39557
- source: "图集内容",
39558
- summary: `查看${Element.length}张图片/视频消息`,
39559
- prompt: "抖音图集解析结果",
39560
- news: [{ text: "点击查看解析结果" }]
39561
- });
39565
+ if (processedImages.length === 0) logger.warn(`抖音图集解析未生成可发送内容,aweme_id=${VideoData.data.aweme_detail.aweme_id}`);
39566
+ else {
39567
+ 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);
39568
+ await this.e.bot.sendForwardMsg(this.e.contact, Element, {
39569
+ source: "图集内容",
39570
+ summary: `查看${Element.length}张图片/视频消息`,
39571
+ prompt: "抖音图集解析结果",
39572
+ news: [{ text: "点击查看解析结果" }]
39573
+ });
39574
+ }
39562
39575
  } finally {
39563
39576
  for (const item of temp) await Common.removeFile(item.filepath, true);
39564
39577
  }
@@ -39642,9 +39655,7 @@ var DouYin = class DouYin extends Base {
39642
39655
  });
39643
39656
  staticImgPath = staticImg.filepath ?? "";
39644
39657
  }
39645
- const livePhotoMode = Config.app.livePhotoMode ?? "video_and_livephoto";
39646
- const shouldGenerateVideo = livePhotoMode === "video_and_livephoto" || livePhotoMode === "video_only";
39647
- const shouldGenerateLivePhoto = livePhotoMode === "video_and_livephoto" || livePhotoMode === "livephoto_only";
39658
+ const { shouldGenerateVideo, shouldGenerateLivePhoto } = getDouyinLiveImageSendPolicy(item.clip_type, Config.app.livePhotoMode ?? "video_and_livephoto");
39648
39659
  if (shouldGenerateVideo) {
39649
39660
  const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
39650
39661
  const safeStaticPath = staticImgPath || livePhoto.filepath;
@@ -39706,14 +39717,17 @@ var DouYin = class DouYin extends Base {
39706
39717
  });
39707
39718
  images.push(...tipImg);
39708
39719
  }
39709
- 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);
39710
39720
  try {
39711
- await this.e.bot.sendForwardMsg(this.e.contact, Element, {
39712
- source: "合辑内容",
39713
- summary: `查看${Element.length}张图片/视频消息`,
39714
- prompt: "抖音合辑解析结果",
39715
- news: [{ text: "点击查看解析结果" }]
39716
- });
39721
+ if (images.length === 0) logger.warn(`抖音合辑解析未生成可发送内容,aweme_id=${VideoData.data.aweme_detail.aweme_id}`);
39722
+ else {
39723
+ 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);
39724
+ await this.e.bot.sendForwardMsg(this.e.contact, Element, {
39725
+ source: "合辑内容",
39726
+ summary: `查看${Element.length}张图片/视频消息`,
39727
+ prompt: "抖音合辑解析结果",
39728
+ news: [{ text: "点击查看解析结果" }]
39729
+ });
39730
+ }
39717
39731
  } finally {
39718
39732
  for (const item of temp) await Common.removeFile(item.filepath, true);
39719
39733
  }
@@ -40857,9 +40871,7 @@ var DouYinpush = class extends Base {
40857
40871
  });
40858
40872
  staticImgPath = staticImg.filepath ?? "";
40859
40873
  }
40860
- const livePhotoMode = Config.app.livePhotoMode ?? "video_and_livephoto";
40861
- const shouldGenerateVideo = livePhotoMode === "video_and_livephoto" || livePhotoMode === "video_only";
40862
- const shouldGenerateLivePhoto = livePhotoMode === "video_and_livephoto" || livePhotoMode === "livephoto_only";
40874
+ const { shouldGenerateVideo, shouldGenerateLivePhoto } = getDouyinLiveImageSendPolicy(item.clip_type, Config.app.livePhotoMode ?? "video_and_livephoto");
40863
40875
  if (shouldGenerateVideo) {
40864
40876
  const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
40865
40877
  const safeStaticPath = staticImgPath || liveimg.filepath;
@@ -40922,14 +40934,17 @@ var DouYinpush = class extends Base {
40922
40934
  images.push(...tipImg);
40923
40935
  }
40924
40936
  const bot = karin.getBot(botId);
40925
- const Element = common.makeForward(images, botId, bot.account.name);
40926
40937
  try {
40927
- await bot.sendForwardMsg(Contact, Element, {
40928
- source: "合辑内容",
40929
- summary: `查看${Element.length}张图片/视频消息`,
40930
- prompt: "抖音合辑解析结果",
40931
- news: [{ text: "点击查看解析结果" }]
40932
- });
40938
+ if (images.length === 0) logger.warn(`抖音合辑推送解析未生成可发送内容,aweme_id=${Detail_Data.aweme_id}`);
40939
+ else {
40940
+ const Element = common.makeForward(images, botId, bot.account.name);
40941
+ await bot.sendForwardMsg(Contact, Element, {
40942
+ source: "合辑内容",
40943
+ summary: `查看${Element.length}张图片/视频消息`,
40944
+ prompt: "抖音合辑解析结果",
40945
+ news: [{ text: "点击查看解析结果" }]
40946
+ });
40947
+ }
40933
40948
  } catch (error) {
40934
40949
  logger.error(`发送合辑失败: ${error}`);
40935
40950
  } finally {
@@ -40980,9 +40995,7 @@ var DouYinpush = class extends Base {
40980
40995
  });
40981
40996
  staticImgPath = staticImg.filepath ?? "";
40982
40997
  }
40983
- const livePhotoMode = Config.app.livePhotoMode ?? "video_and_livephoto";
40984
- const shouldGenerateVideo = livePhotoMode === "video_and_livephoto" || livePhotoMode === "video_only";
40985
- const shouldGenerateLivePhoto = livePhotoMode === "video_and_livephoto" || livePhotoMode === "livephoto_only";
40998
+ const { shouldGenerateVideo, shouldGenerateLivePhoto } = getDouyinLiveImageSendPolicy(item.clip_type, Config.app.livePhotoMode ?? "video_and_livephoto");
40986
40999
  if (shouldGenerateVideo) {
40987
41000
  const transitionEnabled = loopCount > 1 && Boolean(staticImgPath);
40988
41001
  const safeStaticPath = staticImgPath || liveimg.filepath;
@@ -41045,14 +41058,17 @@ var DouYinpush = class extends Base {
41045
41058
  processedImages.push(...tipImg);
41046
41059
  }
41047
41060
  const bot = karin.getBot(botId);
41048
- const Element = common.makeForward(processedImages, botId, bot.account.name);
41049
41061
  try {
41050
- await bot.sendForwardMsg(Contact, Element, {
41051
- source: "图集内容",
41052
- summary: `查看${Element.length}张图片/视频消息`,
41053
- prompt: "抖音图集解析结果",
41054
- news: [{ text: "点击查看解析结果" }]
41055
- });
41062
+ if (processedImages.length === 0) logger.warn(`抖音图集推送解析未生成可发送内容,aweme_id=${Detail_Data.aweme_id}`);
41063
+ else {
41064
+ const Element = common.makeForward(processedImages, botId, bot.account.name);
41065
+ await bot.sendForwardMsg(Contact, Element, {
41066
+ source: "图集内容",
41067
+ summary: `查看${Element.length}张图片/视频消息`,
41068
+ prompt: "抖音图集解析结果",
41069
+ news: [{ text: "点击查看解析结果" }]
41070
+ });
41071
+ }
41056
41072
  } catch (error) {
41057
41073
  logger.error(`发送图集失败: ${error}`);
41058
41074
  } finally {
@@ -1771,7 +1771,7 @@ interface MenuGroup {
1771
1771
  }[];
1772
1772
  }
1773
1773
  //#endregion
1774
- //#region ../../node_modules/.pnpm/node-karin@1.16.3/node_modules/node-karin/dist/index.d.ts
1774
+ //#region ../../node_modules/.pnpm/node-karin@1.16.2/node_modules/node-karin/dist/index.d.ts
1775
1775
  //#endregion
1776
1776
  //#region src/types/adapter/info.d.ts
1777
1777
  /**