karin-plugin-kkk 2.37.1 → 2.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [2.38.0](https://github.com/ikenxuan/karin-plugin-kkk/compare/v2.37.1...v2.38.0) (2026-07-10)
6
+
7
+
8
+ ### ✨ 新功能
9
+
10
+ * **douyin:** 优化作品分享链接生成逻辑,支持不同类型作品的短链接 ([206927b](https://github.com/ikenxuan/karin-plugin-kkk/commit/206927bb68d3a3c7f24224c7fea2d76f5d067486))
11
+ * 替换二维码生成逻辑,支持带头像的二维码组件,并更新相关依赖 ([5428123](https://github.com/ikenxuan/karin-plugin-kkk/commit/5428123b71e33c5109714f3f0cc16d5dd438ecb8))
12
+
13
+
14
+ ### 💄 UI 优化
15
+
16
+ * **dy:** 增强噪点层视觉效果 ([37b2e1c](https://github.com/ikenxuan/karin-plugin-kkk/commit/37b2e1cb38f9243109533caabe7f462dd2d9a852))
17
+
5
18
  ## [2.37.1](https://github.com/ikenxuan/karin-plugin-kkk/compare/v2.37.0...v2.37.1) (2026-07-10)
6
19
 
7
20
 
@@ -1,10 +1,10 @@
1
1
  {
2
- "version": "2.37.1",
3
- "buildTime": "2026-07-10T10:55:31.473Z",
4
- "buildTimestamp": 1783680931473,
2
+ "version": "2.38.0",
3
+ "buildTime": "2026-07-10T20:22:38.928Z",
4
+ "buildTimestamp": 1783714958929,
5
5
  "name": "karin-plugin-kkk",
6
6
  "description": "Karin 的「抖音」「B 站」视频解析/动态推送插件",
7
7
  "homepage": "https://github.com/ikenxuan/karin-plugin-kkk",
8
- "commitHash": "02f47fc0b64b44b5bed99dc5846142408de6c9e0",
9
- "shortCommitHash": "02f47fc0"
8
+ "commitHash": "e556efcc0efce2a40e3e0df46b2a599440b0df25",
9
+ "shortCommitHash": "e556efcc"
10
10
  }
@@ -15545,9 +15545,11 @@ var renderRichTextToReact = (document, options = {}) => {
15545
15545
  *
15546
15546
  * @param {string} text - The text to encode in the QR code.
15547
15547
  * @param {boolean} [useDarkTheme=false] - Whether to use a dark theme for the QR code.
15548
+ * @param {Uint8Array} [image] - Optional binary logo image embedded in the center of the QR code.
15548
15549
  * @return {string} The base64-encoded QR code image.
15549
15550
  */
15550
- var generateQRCode = (text, useDarkTheme = false) => {
15551
+ var generateQRCode = (text, useDarkTheme = false, image) => {
15552
+ const hasImage = Boolean(image?.byteLength);
15551
15553
  return `data:image/webp;base64,${generateSync({
15552
15554
  data: text,
15553
15555
  size: 1e3,
@@ -15563,7 +15565,14 @@ var generateQRCode = (text, useDarkTheme = false) => {
15563
15565
  cornerType: "dot",
15564
15566
  color: useDarkTheme ? "rgba(255, 255, 255, 0.9)" : "rgba(0, 0, 0, 0.8)"
15565
15567
  },
15566
- backgroundOptions: { transparent: true }
15568
+ backgroundOptions: { transparent: true },
15569
+ image: hasImage ? image : void 0,
15570
+ imageOptions: hasImage ? {
15571
+ imageSize: .2,
15572
+ margin: 24,
15573
+ round: .2,
15574
+ hideBackgroundDots: true
15575
+ } : void 0
15567
15576
  }, "webp", "base64")}`;
15568
15577
  };
15569
15578
  //#endregion
@@ -16368,6 +16377,93 @@ var BilibiliComment = import_react.memo((props) => {
16368
16377
  });
16369
16378
  });
16370
16379
  //#endregion
16380
+ //#region ../template/src/utils/QRCodeAvatar.ts
16381
+ var MAX_QRCODE_AVATAR_BYTES = 2 * 1024 * 1024;
16382
+ var QRCODE_AVATAR_TIMEOUT_MS = 5e3;
16383
+ var QRCODE_AVATAR_CACHE_SIZE = 128;
16384
+ var SUPPORTED_QRCODE_AVATAR_TYPES = /* @__PURE__ */ new Set([
16385
+ "image/png",
16386
+ "image/jpeg",
16387
+ "image/webp"
16388
+ ]);
16389
+ var avatarRequestCache = /* @__PURE__ */ new Map();
16390
+ var isSupportedImageBytes = (image) => {
16391
+ const isPng = image.byteLength >= 8 && image[0] === 137 && image[1] === 80 && image[2] === 78 && image[3] === 71 && image[4] === 13 && image[5] === 10 && image[6] === 26 && image[7] === 10;
16392
+ const isJpeg = image.byteLength >= 3 && image[0] === 255 && image[1] === 216 && image[2] === 255;
16393
+ const isWebp = image.byteLength >= 12 && image[0] === 82 && image[1] === 73 && image[2] === 70 && image[3] === 70 && image[8] === 87 && image[9] === 69 && image[10] === 66 && image[11] === 80;
16394
+ return isPng || isJpeg || isWebp;
16395
+ };
16396
+ var fetchQRCodeAvatar = async (url) => {
16397
+ try {
16398
+ const response = await fetch(url, {
16399
+ headers: { accept: "image/png,image/jpeg,image/webp,image/*;q=0.8,*/*;q=0.5" },
16400
+ signal: AbortSignal.timeout(QRCODE_AVATAR_TIMEOUT_MS)
16401
+ });
16402
+ if (!response.ok) return void 0;
16403
+ if (Number(response.headers.get("content-length") ?? 0) > MAX_QRCODE_AVATAR_BYTES) return void 0;
16404
+ const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
16405
+ const image = new Uint8Array(await response.arrayBuffer());
16406
+ if (image.byteLength === 0 || image.byteLength > MAX_QRCODE_AVATAR_BYTES) return void 0;
16407
+ if (contentType && SUPPORTED_QRCODE_AVATAR_TYPES.has(contentType)) return image;
16408
+ return isSupportedImageBytes(image) ? image : void 0;
16409
+ } catch {
16410
+ return;
16411
+ }
16412
+ };
16413
+ /**
16414
+ * 加载并缓存二维码头像。同一头像的并发渲染会复用请求,失败结果不会进入缓存。
16415
+ */
16416
+ var loadQRCodeAvatar = (url) => {
16417
+ if (!url || !/^https?:\/\//i.test(url)) return Promise.resolve(void 0);
16418
+ const cached = avatarRequestCache.get(url);
16419
+ if (cached) return cached;
16420
+ if (avatarRequestCache.size >= QRCODE_AVATAR_CACHE_SIZE) {
16421
+ const oldestKey = avatarRequestCache.keys().next().value;
16422
+ if (oldestKey) avatarRequestCache.delete(oldestKey);
16423
+ }
16424
+ const request = fetchQRCodeAvatar(url);
16425
+ avatarRequestCache.set(url, request);
16426
+ request.then((image) => {
16427
+ if (!image) avatarRequestCache.delete(url);
16428
+ });
16429
+ return request;
16430
+ };
16431
+ //#endregion
16432
+ //#region ../template/src/components/common/QRCodeWithAvatar.tsx
16433
+ var QRCodeImage = ({ value, avatarUrl: _, useDarkTheme = false, avatar, alt = "二维码", ...imageProps }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
16434
+ ...imageProps,
16435
+ src: generateQRCode(value, useDarkTheme, avatar),
16436
+ alt
16437
+ });
16438
+ /** Node SSR 版本:在服务端流式渲染期间等待头像二进制。 */
16439
+ var QRCodeWithAvatarServer = async (props) => {
16440
+ const avatar = await loadQRCodeAvatar(props.avatarUrl);
16441
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeImage, {
16442
+ ...props,
16443
+ avatar
16444
+ });
16445
+ };
16446
+ /** 浏览器版本:保持同步组件语义,通过 effect 异步补充头像二维码。 */
16447
+ var QRCodeWithAvatarClient = (props) => {
16448
+ const [avatar, setAvatar] = import_react.useState();
16449
+ import_react.useEffect(() => {
16450
+ let active = true;
16451
+ setAvatar(void 0);
16452
+ loadQRCodeAvatar(props.avatarUrl).then((image) => {
16453
+ if (active) setAvatar(image);
16454
+ });
16455
+ return () => {
16456
+ active = false;
16457
+ };
16458
+ }, [props.avatarUrl]);
16459
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeImage, {
16460
+ ...props,
16461
+ avatar
16462
+ });
16463
+ };
16464
+ var QRCodeWithAvatar = typeof window === "undefined" ? import_react.memo(QRCodeWithAvatarServer) : import_react.memo(QRCodeWithAvatarClient);
16465
+ QRCodeWithAvatar.displayName = "QRCodeWithAvatar";
16466
+ //#endregion
16371
16467
  //#region ../template/src/components/platforms/bilibili/dynamic/DYNAMIC_TYPE_ARTICLE.tsx
16372
16468
  /**
16373
16469
  * B站专栏动态用户信息组件
@@ -16649,8 +16745,10 @@ var BilibiliArticleFooter = import_react.memo((props) => {
16649
16745
  })]
16650
16746
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
16651
16747
  className: "flex flex-col items-center gap-4",
16652
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
16653
- src: generateQRCode(props.data.share_url, props.data.useDarkTheme),
16748
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
16749
+ value: props.data.share_url,
16750
+ avatarUrl: props.data.avatar_url,
16751
+ useDarkTheme: props.data.useDarkTheme,
16654
16752
  alt: "二维码",
16655
16753
  className: "h-auto w-75 rounded-2xl"
16656
16754
  })
@@ -16876,8 +16974,10 @@ var BilibiliDynamicFooter = (props) => {
16876
16974
  })]
16877
16975
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
16878
16976
  className: "flex flex-col items-center gap-4",
16879
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
16880
- src: generateQRCode(props.share_url, props.useDarkTheme),
16977
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
16978
+ value: props.share_url,
16979
+ avatarUrl: props.avatar_url,
16980
+ useDarkTheme: props.useDarkTheme,
16881
16981
  alt: "二维码",
16882
16982
  className: "h-auto w-75 rounded-2xl"
16883
16983
  })
@@ -18303,8 +18403,10 @@ var BilibiliLiveDynamic = import_react.memo((props) => {
18303
18403
  className: "absolute right-22 top-1/2 h-52 w-52 -translate-y-1/2 rounded-full blur-[48px]",
18304
18404
  style: { backgroundColor: withAlphaFromCss(accentColor, isDark ? .16 : .1) }
18305
18405
  }),
18306
- data.share_url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
18307
- src: generateQRCode(data.share_url, isDark),
18406
+ data.share_url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
18407
+ value: data.share_url,
18408
+ avatarUrl: data.avatar_url,
18409
+ useDarkTheme: isDark,
18308
18410
  alt: "二维码",
18309
18411
  className: "relative h-100 w-100 object-contain drop-shadow-[0_20px_38px_rgba(0,0,0,0.18)]"
18310
18412
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -19693,8 +19795,10 @@ var DouyinPosterFooter$2 = ({ data }) => {
19693
19795
  className: "shrink-0 text-center",
19694
19796
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19695
19797
  className: "drop-shadow-2xl",
19696
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
19697
- src: generateQRCode(share_url, useDarkTheme),
19798
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
19799
+ value: share_url,
19800
+ avatarUrl: avater_url,
19801
+ useDarkTheme,
19698
19802
  alt: "二维码",
19699
19803
  className: "h-[300px] w-[300px]"
19700
19804
  })
@@ -19764,8 +19868,10 @@ var QRCodeSection = (props) => {
19764
19868
  className: "flex flex-col items-center",
19765
19869
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19766
19870
  className: "flex justify-center items-center w-100 h-100 p-4",
19767
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
19768
- src: generateQRCode(props.share_url, props.useDarkTheme),
19871
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
19872
+ value: props.share_url,
19873
+ avatarUrl: props.AuthorAvatar,
19874
+ useDarkTheme: props.useDarkTheme,
19769
19875
  alt: "二维码",
19770
19876
  className: "object-contain w-full h-full rounded-lg"
19771
19877
  })
@@ -20570,8 +20676,10 @@ var DouyinDynamic = (props) => {
20570
20676
  className: "flex justify-between items-start px-20 pb-20",
20571
20677
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(UserInfoSection, { ...props }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20572
20678
  className: "flex flex-col items-center gap-4",
20573
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
20574
- src: generateQRCode(props.data.share_url, props.data.useDarkTheme),
20679
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
20680
+ value: props.data.share_url,
20681
+ avatarUrl: props.data.avater_url,
20682
+ useDarkTheme: props.data.useDarkTheme,
20575
20683
  alt: "二维码",
20576
20684
  className: "h-auto w-75 rounded-xl"
20577
20685
  })
@@ -20726,8 +20834,10 @@ var DouyinFavoriteList = (props) => {
20726
20834
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-px w-full bg-linear-to-r from-surface-secondary via-border to-transparent" }),
20727
20835
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20728
20836
  className: "flex items-end gap-6",
20729
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
20730
- src: generateQRCode(props.data.share_url, props.data.useDarkTheme),
20837
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
20838
+ value: props.data.share_url,
20839
+ avatarUrl: props.data.author_avatar,
20840
+ useDarkTheme: props.data.useDarkTheme,
20731
20841
  className: "w-65 h-auto rounded-2xl mix-blend-multiply",
20732
20842
  alt: "QR"
20733
20843
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -20924,18 +21034,19 @@ var DouyinDiffuseBackground$1 = ({ data }) => {
20924
21034
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
20925
21035
  className: "h-full w-full",
20926
21036
  xmlns: "http://www.w3.org/2000/svg",
20927
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("defs", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("filter", {
21037
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("filter", {
20928
21038
  id: "douyinImageWorkNoise",
20929
21039
  children: [
20930
21040
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feTurbulence", {
20931
21041
  type: "fractalNoise",
20932
- baseFrequency: "1.2",
20933
- numOctaves: "3",
21042
+ baseFrequency: "0.8",
21043
+ numOctaves: "2",
20934
21044
  stitchTiles: "stitch"
20935
21045
  }),
20936
21046
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feColorMatrix", {
20937
21047
  type: "saturate",
20938
- values: "0"
21048
+ values: "0",
21049
+ result: "gray"
20939
21050
  }),
20940
21051
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("feComponentTransfer", { children: [
20941
21052
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncR", {
@@ -20957,47 +21068,7 @@ var DouyinDiffuseBackground$1 = ({ data }) => {
20957
21068
  intercept: "-0.5"
20958
21069
  }) })
20959
21070
  ]
20960
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("mask", {
20961
- id: "douyinImageWorkNoiseMask",
20962
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("linearGradient", {
20963
- id: "douyinImageWorkNoiseGradient",
20964
- x1: "0%",
20965
- y1: "0%",
20966
- x2: "0%",
20967
- y2: "100%",
20968
- children: [
20969
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", {
20970
- offset: "0%",
20971
- stopColor: "white",
20972
- stopOpacity: "1"
20973
- }),
20974
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", {
20975
- offset: "15%",
20976
- stopColor: "white",
20977
- stopOpacity: "0.6"
20978
- }),
20979
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", {
20980
- offset: "50%",
20981
- stopColor: "white",
20982
- stopOpacity: "0.15"
20983
- }),
20984
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", {
20985
- offset: "85%",
20986
- stopColor: "white",
20987
- stopOpacity: "0.6"
20988
- }),
20989
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", {
20990
- offset: "100%",
20991
- stopColor: "white",
20992
- stopOpacity: "1"
20993
- })
20994
- ]
20995
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", {
20996
- width: "100%",
20997
- height: "100%",
20998
- fill: "url(#douyinImageWorkNoiseGradient)"
20999
- })]
21000
- })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", {
21071
+ }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", {
21001
21072
  width: "100%",
21002
21073
  height: "100%",
21003
21074
  filter: "url(#douyinImageWorkNoise)",
@@ -21063,7 +21134,7 @@ var DouyinPosterTitle$1 = ({ data }) => {
21063
21134
  const hasDesc = Boolean(desc?.nodes.length);
21064
21135
  const titleClassName = getTitleClassName$1(title ? extractRichTextPlainText(title).length : 0);
21065
21136
  const richTextOptions = {
21066
- hashtag: { className: "text-inherit opacity-60" },
21137
+ hashtag: { className: cn$1("text-inherit opacity-60", !hasTitle && "text-6xl") },
21067
21138
  mention: { className: "text-inherit" }
21068
21139
  };
21069
21140
  if (!hasTitle && !hasDesc) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
@@ -21080,7 +21151,7 @@ var DouyinPosterTitle$1 = ({ data }) => {
21080
21151
  },
21081
21152
  children: renderRichTextToReact(title, richTextOptions)
21082
21153
  }), hasDesc && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21083
- className: "mt-7 whitespace-pre-wrap text-[42px] font-medium leading-[1.48] select-text",
21154
+ className: cn$1("mt-7 whitespace-pre-wrap select-text", !hasTitle ? "text-6xl font-bold leading-tight" : "text-5xl font-medium"),
21084
21155
  style: {
21085
21156
  wordBreak: "break-word",
21086
21157
  overflowWrap: "break-word"
@@ -21164,21 +21235,19 @@ var DouyinImageCover = ({ data }) => {
21164
21235
  }),
21165
21236
  music && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21166
21237
  className: "absolute bottom-12 left-24 z-30 flex max-w-[850px] items-center gap-5 text-white drop-shadow-xl",
21167
- children: [music.cover ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21238
+ children: [music.cover ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21168
21239
  className: "relative h-20 w-20 shrink-0",
21169
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
21170
- src: music.cover,
21171
- alt: "",
21172
- className: "absolute inset-0 h-full w-full scale-110 rounded-2xl object-cover opacity-65 blur-md",
21173
- referrerPolicy: "no-referrer",
21174
- crossOrigin: "anonymous"
21175
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
21176
- src: music.cover,
21177
- alt: "BGM封面",
21178
- className: "relative z-10 h-full w-full rounded-2xl object-cover",
21179
- referrerPolicy: "no-referrer",
21180
- crossOrigin: "anonymous"
21181
- })]
21240
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlowImage, {
21241
+ glowStrength: 1,
21242
+ blurRadius: 20,
21243
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
21244
+ src: music.cover,
21245
+ alt: "BGM封面",
21246
+ className: "relative z-10 h-full w-full rounded-2xl object-cover",
21247
+ referrerPolicy: "no-referrer",
21248
+ crossOrigin: "anonymous"
21249
+ })
21250
+ })
21182
21251
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(e, {
21183
21252
  size: 44,
21184
21253
  weight: "fill",
@@ -21381,8 +21450,10 @@ var DouyinPosterFooter$1 = ({ data }) => {
21381
21450
  className: "shrink-0 text-center",
21382
21451
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21383
21452
  className: "drop-shadow-2xl",
21384
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
21385
- src: generateQRCode(share_url, useDarkTheme),
21453
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
21454
+ value: share_url,
21455
+ avatarUrl: avater_url,
21456
+ useDarkTheme,
21386
21457
  alt: "二维码",
21387
21458
  className: "h-[300px] w-[300px]"
21388
21459
  })
@@ -21731,8 +21802,10 @@ var BottomSection = ({ data }) => {
21731
21802
  alt: "抖音",
21732
21803
  className: "w-60 h-auto opacity-80 dark:opacity-70"
21733
21804
  }),
21734
- generateQRCode(data.share_url, data.useDarkTheme) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
21735
- src: generateQRCode(data.share_url, data.useDarkTheme),
21805
+ data.share_url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
21806
+ value: data.share_url,
21807
+ avatarUrl: data.avater_url,
21808
+ useDarkTheme: data.useDarkTheme,
21736
21809
  alt: "二维码",
21737
21810
  className: "h-auto w-75"
21738
21811
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -21893,7 +21966,7 @@ var MusicAuthorInfoSection = ({ avatarUrl, username, userShortId, totalFavorited
21893
21966
  * @param props 组件属性
21894
21967
  * @returns JSX元素
21895
21968
  */
21896
- var MusicQRCodeSection = ({ share_url, useDarkTheme }) => {
21969
+ var MusicQRCodeSection = ({ share_url, avatarUrl, useDarkTheme }) => {
21897
21970
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
21898
21971
  className: "flex flex-col-reverse items-center -mb-12 mr-18",
21899
21972
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -21901,8 +21974,10 @@ var MusicQRCodeSection = ({ share_url, useDarkTheme }) => {
21901
21974
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(QrCode, { className: "w-11 h-11" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "文件直链:永久有效" })]
21902
21975
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
21903
21976
  className: "p-2.5 rounded-sm border-[7px] border-dashed border-border",
21904
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
21905
- src: generateQRCode(share_url, useDarkTheme),
21977
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
21978
+ value: share_url,
21979
+ avatarUrl,
21980
+ useDarkTheme,
21906
21981
  alt: "二维码",
21907
21982
  className: "w-87.5 h-87.5 select-text"
21908
21983
  })
@@ -21950,6 +22025,7 @@ var DouyinMusicInfo = (props) => {
21950
22025
  useDarkTheme: data.useDarkTheme
21951
22026
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MusicQRCodeSection, {
21952
22027
  share_url: data.share_url,
22028
+ avatarUrl: data.avater_url,
21953
22029
  useDarkTheme: data.useDarkTheme
21954
22030
  })]
21955
22031
  })
@@ -22319,8 +22395,10 @@ var DouyinRecommendList = (props) => {
22319
22395
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-px w-full bg-linear-to-r from-surface-secondary via-border to-transparent" }),
22320
22396
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
22321
22397
  className: "flex items-end gap-6",
22322
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
22323
- src: generateQRCode(props.data.share_url, props.data.useDarkTheme),
22398
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
22399
+ value: props.data.share_url,
22400
+ avatarUrl: props.data.author_avatar,
22401
+ useDarkTheme: props.data.useDarkTheme,
22324
22402
  className: "w-65 h-auto rounded-2xl mix-blend-multiply",
22325
22403
  alt: "QR"
22326
22404
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -23039,7 +23117,7 @@ var getTitleClassName = (titleLength) => {
23039
23117
  if (titleLength > 96) return "text-[42px] leading-[1.34]";
23040
23118
  if (titleLength > 72) return "text-[48px] leading-[1.3]";
23041
23119
  if (titleLength > 48) return "text-[54px] leading-[1.26]";
23042
- return "text-[62px] leading-[1.18]";
23120
+ return "text-[62px] leading-tight";
23043
23121
  };
23044
23122
  function formatDuration(duration) {
23045
23123
  if (typeof duration !== "number" || !Number.isFinite(duration) || duration < 0) return void 0;
@@ -23072,18 +23150,19 @@ var DouyinDiffuseBackground = ({ data }) => /* @__PURE__ */ (0, import_jsx_runti
23072
23150
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
23073
23151
  className: "h-full w-full",
23074
23152
  xmlns: "http://www.w3.org/2000/svg",
23075
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("defs", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("filter", {
23153
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("filter", {
23076
23154
  id: "douyinVideoWorkNoise",
23077
23155
  children: [
23078
23156
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feTurbulence", {
23079
23157
  type: "fractalNoise",
23080
- baseFrequency: "1.2",
23081
- numOctaves: "3",
23158
+ baseFrequency: "0.8",
23159
+ numOctaves: "2",
23082
23160
  stitchTiles: "stitch"
23083
23161
  }),
23084
23162
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feColorMatrix", {
23085
23163
  type: "saturate",
23086
- values: "0"
23164
+ values: "0",
23165
+ result: "gray"
23087
23166
  }),
23088
23167
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("feComponentTransfer", { children: [
23089
23168
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncR", {
@@ -23105,47 +23184,7 @@ var DouyinDiffuseBackground = ({ data }) => /* @__PURE__ */ (0, import_jsx_runti
23105
23184
  intercept: "-0.5"
23106
23185
  }) })
23107
23186
  ]
23108
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("mask", {
23109
- id: "douyinVideoWorkNoiseMask",
23110
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("linearGradient", {
23111
- id: "douyinVideoWorkNoiseGradient",
23112
- x1: "0%",
23113
- y1: "0%",
23114
- x2: "0%",
23115
- y2: "100%",
23116
- children: [
23117
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", {
23118
- offset: "0%",
23119
- stopColor: "white",
23120
- stopOpacity: "1"
23121
- }),
23122
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", {
23123
- offset: "15%",
23124
- stopColor: "white",
23125
- stopOpacity: "0.6"
23126
- }),
23127
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", {
23128
- offset: "50%",
23129
- stopColor: "white",
23130
- stopOpacity: "0.15"
23131
- }),
23132
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", {
23133
- offset: "85%",
23134
- stopColor: "white",
23135
- stopOpacity: "0.6"
23136
- }),
23137
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", {
23138
- offset: "100%",
23139
- stopColor: "white",
23140
- stopOpacity: "1"
23141
- })
23142
- ]
23143
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", {
23144
- width: "100%",
23145
- height: "100%",
23146
- fill: "url(#douyinVideoWorkNoiseGradient)"
23147
- })]
23148
- })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", {
23187
+ }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", {
23149
23188
  width: "100%",
23150
23189
  height: "100%",
23151
23190
  filter: "url(#douyinVideoWorkNoise)",
@@ -23251,21 +23290,19 @@ var DouyinVideoCover = ({ data }) => {
23251
23290
  }),
23252
23291
  music && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
23253
23292
  className: "absolute bottom-12 left-24 z-30 flex max-w-[850px] items-center gap-5 text-white drop-shadow-xl",
23254
- children: [music.cover ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
23293
+ children: [music.cover ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
23255
23294
  className: "relative h-20 w-20 shrink-0",
23256
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
23257
- src: music.cover,
23258
- alt: "",
23259
- className: "absolute inset-0 h-full w-full scale-110 rounded-2xl object-cover opacity-65 blur-md",
23260
- referrerPolicy: "no-referrer",
23261
- crossOrigin: "anonymous"
23262
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
23263
- src: music.cover,
23264
- alt: "BGM封面",
23265
- className: "relative z-10 h-full w-full rounded-2xl object-cover",
23266
- referrerPolicy: "no-referrer",
23267
- crossOrigin: "anonymous"
23268
- })]
23295
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlowImage, {
23296
+ glowStrength: 1,
23297
+ blurRadius: 20,
23298
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
23299
+ src: music.cover,
23300
+ alt: "BGM封面",
23301
+ className: "relative z-10 h-full w-full rounded-2xl object-cover",
23302
+ referrerPolicy: "no-referrer",
23303
+ crossOrigin: "anonymous"
23304
+ })
23305
+ })
23269
23306
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(e, {
23270
23307
  size: 44,
23271
23308
  weight: "fill",
@@ -23499,8 +23536,10 @@ var DouyinPosterFooter = ({ data }) => {
23499
23536
  className: "shrink-0 text-center",
23500
23537
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
23501
23538
  className: "drop-shadow-2xl",
23502
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
23503
- src: generateQRCode(share_url, useDarkTheme),
23539
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
23540
+ value: share_url,
23541
+ avatarUrl: avater_url,
23542
+ useDarkTheme,
23504
23543
  alt: "二维码",
23505
23544
  className: "h-[300px] w-[300px]"
23506
23545
  })
@@ -30091,7 +30130,9 @@ var SSRRender = class {
30091
30130
  let component = await ComponentRendererFactory.createComponent(request, ctx.state.props);
30092
30131
  ctx.state.component = component;
30093
30132
  await this.pluginContainer.runDuring(ctx);
30094
- const htmlContent = (0, import_server_node.renderToString)(ctx.state.component ?? component);
30133
+ const stream = await (0, import_server_node.renderToReadableStream)(ctx.state.component ?? component);
30134
+ await stream.allReady;
30135
+ const htmlContent = await new Response(stream).text();
30095
30136
  ctx.state.html = htmlContent;
30096
30137
  await this.pluginContainer.runAfter(ctx);
30097
30138
  const safeTemplateName = request.templateName.replace(/\//g, "_");
@@ -38986,7 +39027,7 @@ var DouYin = class DouYin extends Base {
38986
39027
  sec_uid: aweme.author.sec_uid,
38987
39028
  typeMode: "strict"
38988
39029
  });
38989
- const shareLink = isVideo ? `https://aweme.snssdk.com/aweme/v1/play/?video_id=${aweme.video.play_addr.uri}&ratio=1080p&line=0` : aweme.share_url;
39030
+ const shareLink = isVideo ? `https://aweme.snssdk.com/aweme/v1/play/?video_id=${aweme.video.play_addr.uri}&ratio=1080p&line=0` : `https://www.douyin.com/${isArticle ? "article" : "note"}/${aweme.aweme_id}`;
38990
39031
  const workInfoImg = await renderWorkImage({
38991
39032
  e: this.e,
38992
39033
  Detail_Data: {
@@ -39893,6 +39934,7 @@ var DouYinpush = class extends Base {
39893
39934
  ${logger.cyan("分享链接")}: ${logger.green(shareUrl)}
39894
39935
  `);
39895
39936
  const Detail_Data = pushItem.Detail_Data;
39937
+ const workTypeInfo = getWorkTypeInfo(Detail_Data);
39896
39938
  const skip = await skipDynamic(pushItem);
39897
39939
  if (skip) logger.warn(`作品 https://www.douyin.com/video/${actualAwemeId} 已被处理,跳过`);
39898
39940
  let img = [];
@@ -39900,7 +39942,7 @@ var DouYinpush = class extends Base {
39900
39942
  this.injectBotToEventForRender(pushItem.targets);
39901
39943
  if (!skip) iddata = await getDouyinID(this.e, Detail_Data.share_url ?? "https://live.douyin.com/" + Detail_Data.room_data?.owner.web_rid, false);
39902
39944
  if (!skip) {
39903
- const realUrl = pushItem.pushType !== "live" && Config.douyin.push.shareType === "web" && await new Network({
39945
+ const realUrl = pushItem.pushType !== "live" && workTypeInfo.isVideo && Config.douyin.push.shareType === "web" && await new Network({
39904
39946
  url: Detail_Data.share_url,
39905
39947
  headers: {
39906
39948
  "User-Agent": "Apifox/1.0.0 (https://apifox.com)",
@@ -39909,6 +39951,11 @@ var DouYinpush = class extends Base {
39909
39951
  Connection: "keep-alive"
39910
39952
  }
39911
39953
  }).getLocation();
39954
+ let workShareLink;
39955
+ if (pushItem.pushType !== "live") if (workTypeInfo.isArticle) workShareLink = `https://www.douyin.com/article/${actualAwemeId}`;
39956
+ else if (workTypeInfo.isImage) workShareLink = `https://www.douyin.com/note/${actualAwemeId}`;
39957
+ else if (Config.douyin.push.shareType === "web") workShareLink = realUrl || `https://www.douyin.com/video/${actualAwemeId}`;
39958
+ else workShareLink = Detail_Data.video?.play_addr?.uri ? `https://aweme.snssdk.com/aweme/v1/play/?video_id=${Detail_Data.video.play_addr.uri}&ratio=1080p&line=0` : `https://www.douyin.com/video/${actualAwemeId}`;
39912
39959
  switch (pushItem.pushType) {
39913
39960
  case "live":
39914
39961
  if (!("room_data" in pushItem.Detail_Data && Detail_Data.live_data)) break;
@@ -39919,41 +39966,35 @@ var DouYinpush = class extends Base {
39919
39966
  dynamicTypeLabel: "直播动态推送"
39920
39967
  });
39921
39968
  break;
39922
- case "favorite": {
39923
- const shareLink = Config.douyin.push.shareType === "web" ? realUrl : `https://aweme.snssdk.com/aweme/v1/play/?video_id=${Detail_Data.video.play_addr.uri}&ratio=1080p&line=0`;
39969
+ case "favorite":
39924
39970
  img = await renderFavoriteImage({
39925
39971
  e: this.e,
39926
39972
  Detail_Data,
39927
39973
  create_time: pushItem.create_time,
39928
- shareLink,
39974
+ shareLink: workShareLink,
39929
39975
  remark: pushItem.remark,
39930
39976
  skipWatermark: true
39931
39977
  });
39932
39978
  break;
39933
- }
39934
- case "recommend": {
39935
- const shareLink = Config.douyin.push.shareType === "web" ? realUrl : `https://aweme.snssdk.com/aweme/v1/play/?video_id=${Detail_Data.video.play_addr.uri}&ratio=1080p&line=0`;
39979
+ case "recommend":
39936
39980
  img = await renderRecommendImage({
39937
39981
  e: this.e,
39938
39982
  Detail_Data,
39939
39983
  create_time: pushItem.create_time,
39940
- shareLink,
39984
+ shareLink: workShareLink,
39941
39985
  remark: pushItem.remark,
39942
39986
  skipWatermark: true
39943
39987
  });
39944
39988
  break;
39945
- }
39946
- default: {
39947
- const shareLink = Config.douyin.push.shareType === "web" ? realUrl : `https://aweme.snssdk.com/aweme/v1/play/?video_id=${Detail_Data.video.play_addr.uri}&ratio=1080p&line=0`;
39989
+ default:
39948
39990
  img = await renderWorkImage({
39949
39991
  e: this.e,
39950
39992
  Detail_Data,
39951
39993
  create_time: pushItem.create_time,
39952
- shareLink,
39994
+ shareLink: workShareLink,
39953
39995
  skipWatermark: true
39954
39996
  });
39955
39997
  break;
39956
- }
39957
39998
  }
39958
39999
  }
39959
40000
  for (const target of pushItem.targets) {
@@ -39978,7 +40019,6 @@ var DouYinpush = class extends Base {
39978
40019
  status = await karin.sendMsg(botId, Contact, [...watermarkedImg, ...parseButton]);
39979
40020
  if (pushItem.pushType === "live" && "room_data" in pushItem.Detail_Data && status.message_id) await douyinDBInstance.updateLiveStatus(pushItem.sec_uid, true);
39980
40021
  if (Config.douyin.push.parsedynamic && status.message_id) {
39981
- const workTypeInfo = getWorkTypeInfo(Detail_Data);
39982
40022
  logger.debug(`开始解析作品,类型为:${getWorkTypeDisplayName(workTypeInfo)}`);
39983
40023
  if (workTypeInfo.isVideo) {
39984
40024
  /** 默认视频下载地址 */
@@ -42341,6 +42381,13 @@ var globalStatistics = karin.command(/^#?kkk全局解析统计$/, handleGlobalSt
42341
42381
  });
42342
42382
  //#endregion
42343
42383
  //#region src/apps/testPush.ts
42384
+ /** 构建与生产推送一致的作品二维码链接。 */
42385
+ function buildWorkShareLink(aweme) {
42386
+ const workTypeInfo = getWorkTypeInfo(aweme);
42387
+ if (workTypeInfo.isArticle) return `https://www.douyin.com/article/${aweme.aweme_id}`;
42388
+ if (workTypeInfo.isImage) return `https://www.douyin.com/note/${aweme.aweme_id}`;
42389
+ return aweme.video?.play_addr?.uri ? `https://aweme.snssdk.com/aweme/v1/play/?video_id=${aweme.video.play_addr.uri}&ratio=1080p&line=0` : `https://www.douyin.com/video/${aweme.aweme_id}`;
42390
+ }
42344
42391
  /**
42345
42392
  * 测试抖音推送命令处理器
42346
42393
  * 支持四种推送类型的预览渲染,无数据库交互,仅用于调试和验证推送卡片效果
@@ -42392,7 +42439,7 @@ var handleTestPush = wrapWithErrorHandler(async (e) => {
42392
42439
  ...aweme,
42393
42440
  user_info: userinfo
42394
42441
  };
42395
- const shareLink = Detail_Data.video?.play_addr?.uri ? `https://aweme.snssdk.com/aweme/v1/play/?video_id=${Detail_Data.video.play_addr.uri}&ratio=1080p&line=0` : Detail_Data.share_url || url;
42442
+ const shareLink = buildWorkShareLink(Detail_Data);
42396
42443
  images = await renderWorkImage({
42397
42444
  e,
42398
42445
  Detail_Data,
@@ -42438,7 +42485,7 @@ var handleTestPush = wrapWithErrorHandler(async (e) => {
42438
42485
  user_info: userinfo,
42439
42486
  author_user_info: authorUserInfo
42440
42487
  };
42441
- const shareLink = Detail_Data.video?.play_addr?.uri ? `https://aweme.snssdk.com/aweme/v1/play/?video_id=${Detail_Data.video.play_addr.uri}&ratio=1080p&line=0` : aweme.share_url;
42488
+ const shareLink = buildWorkShareLink(Detail_Data);
42442
42489
  images = await renderFavoriteImage({
42443
42490
  e,
42444
42491
  Detail_Data,
@@ -42485,7 +42532,7 @@ var handleTestPush = wrapWithErrorHandler(async (e) => {
42485
42532
  user_info: userinfo,
42486
42533
  author_user_info: authorUserInfo
42487
42534
  };
42488
- const shareLink = Detail_Data.video?.play_addr?.uri ? `https://aweme.snssdk.com/aweme/v1/play/?video_id=${Detail_Data.video.play_addr.uri}&ratio=1080p&line=0` : aweme.share_url;
42535
+ const shareLink = buildWorkShareLink(Detail_Data);
42489
42536
  images = await renderRecommendImage({
42490
42537
  e,
42491
42538
  Detail_Data,
@@ -18907,10 +18907,6 @@
18907
18907
  opacity: .62;
18908
18908
  }
18909
18909
 
18910
- .opacity-65 {
18911
- opacity: .65;
18912
- }
18913
-
18914
18910
  .opacity-70 {
18915
18911
  opacity: .7;
18916
18912
  }
@@ -19168,11 +19164,6 @@
19168
19164
  filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
19169
19165
  }
19170
19166
 
19171
- .blur-md {
19172
- --tw-blur: blur(var(--blur-md));
19173
- filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
19174
- }
19175
-
19176
19167
  .blur-xl {
19177
19168
  --tw-blur: blur(var(--blur-xl));
19178
19169
  filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karin-plugin-kkk",
3
- "version": "2.37.1",
3
+ "version": "2.38.0",
4
4
  "description": "Karin 的「抖音」「B 站」视频解析/动态推送插件",
5
5
  "keywords": [
6
6
  "karin",
@@ -58,7 +58,7 @@
58
58
  "registry": "https://registry.npmjs.org"
59
59
  },
60
60
  "dependencies": {
61
- "@ikenxuan/qrcode": "1.4.0",
61
+ "@ikenxuan/qrcode": "1.5.0",
62
62
  "@ikenxuan/watermark": "1.3.0",
63
63
  "fingerprint-injector": "^2.1.82"
64
64
  },
@@ -89,5 +89,5 @@
89
89
  "web": "./lib/web.config.js",
90
90
  "ts-web": "./src/web.config.ts"
91
91
  },
92
- "timestamp": "2026-07-10T10:39:04.827Z"
92
+ "timestamp": "2026-07-10T20:15:26.597Z"
93
93
  }