karin-plugin-kkk 2.45.1 → 2.45.2-beta.1

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.
@@ -1113,6 +1113,7 @@ var Common = new class Tools {
1113
1113
  unit = 1;
1114
1114
  }
1115
1115
  }
1116
+ if (unit > 1) temp += unit;
1116
1117
  return result + temp;
1117
1118
  }
1118
1119
  /**
@@ -7644,6 +7645,7 @@ var templateFonts = (() => {
7644
7645
  var SMART_THEME_PATHS = /* @__PURE__ */ new Set([
7645
7646
  "douyin/video-work",
7646
7647
  "douyin/image-work",
7648
+ "douyin/mix",
7647
7649
  "bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD"
7648
7650
  ]);
7649
7651
  /**
@@ -7653,7 +7655,7 @@ var SMART_THEME_PATHS = /* @__PURE__ */ new Set([
7653
7655
  */
7654
7656
  var getCoverUrl$1 = (path, data) => {
7655
7657
  if (!data || typeof data !== "object") return "";
7656
- if (path === "douyin/video-work" || path === "bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD") {
7658
+ if (path === "douyin/video-work" || path === "douyin/mix" || path === "bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD") {
7657
7659
  const imageUrl = data.image_url;
7658
7660
  return typeof imageUrl === "string" ? imageUrl : "";
7659
7661
  }
@@ -16112,7 +16114,7 @@ var DouyinMoreIcon = ({ size = 36, ...props }) => /* @__PURE__ */ (0, import_jsx
16112
16114
  });
16113
16115
  //#endregion
16114
16116
  //#region ktr/template/douyin/article-work/components/ArticleWork.tsx
16115
- var getTitleClassName$2 = (titleLength) => {
16117
+ var getTitleClassName$3 = (titleLength) => {
16116
16118
  if (titleLength > 96) return "text-[42px] leading-[1.34]";
16117
16119
  if (titleLength > 72) return "text-[48px] leading-[1.3]";
16118
16120
  if (titleLength > 48) return "text-[54px] leading-[1.26]";
@@ -16271,7 +16273,7 @@ var DouyinPosterHeader$2 = ({ data, ctx }) => {
16271
16273
  var DouyinArticleTitle = ({ data }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("section", {
16272
16274
  className: "mt-13",
16273
16275
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
16274
- className: `${getTitleClassName$2(data.title.length)} font-bold text-foreground select-text`,
16276
+ className: `${getTitleClassName$3(data.title.length)} font-bold text-foreground select-text`,
16275
16277
  style: {
16276
16278
  wordBreak: "break-word",
16277
16279
  overflowWrap: "break-word"
@@ -17749,7 +17751,7 @@ var imageMediaTypeMeta = {
17749
17751
  icon: o$3
17750
17752
  }
17751
17753
  };
17752
- var getTitleClassName$1 = (titleLength) => {
17754
+ var getTitleClassName$2 = (titleLength) => {
17753
17755
  if (titleLength > 96) return "text-[42px] leading-[1.34]";
17754
17756
  if (titleLength > 72) return "text-[48px] leading-[1.3]";
17755
17757
  if (titleLength > 48) return "text-[54px] leading-[1.26]";
@@ -17871,7 +17873,7 @@ var DouyinPosterTitle$1 = ({ data }) => {
17871
17873
  const { title, desc } = data;
17872
17874
  const hasTitle = Boolean(title?.nodes.length);
17873
17875
  const hasDesc = Boolean(desc?.nodes.length);
17874
- const titleClassName = getTitleClassName$1(title ? extractRichTextPlainText(title).length : 0);
17876
+ const titleClassName = getTitleClassName$2(title ? extractRichTextPlainText(title).length : 0);
17875
17877
  const richTextOptions = {
17876
17878
  hashtag: { className: cn("text-inherit opacity-60", !hasTitle && "text-6xl") },
17877
17879
  mention: { className: "text-inherit" }
@@ -18597,6 +18599,415 @@ var live_default = defineTemplate({
18597
18599
  validate: (data) => typeof data === "object" && data !== null && typeof data.share_url === "string"
18598
18600
  });
18599
18601
  //#endregion
18602
+ //#region ktr/template/douyin/mix/components/Mix.tsx
18603
+ /** 万/亿 计数,数值与单位分开返回:数字用大号等宽,单位用小号 */
18604
+ var splitCount = (count) => {
18605
+ if (count >= 1e8) return {
18606
+ value: (count / 1e8).toFixed(1),
18607
+ unit: "亿"
18608
+ };
18609
+ if (count >= 1e4) return {
18610
+ value: (count / 1e4).toFixed(1),
18611
+ unit: "万"
18612
+ };
18613
+ return {
18614
+ value: count.toString(),
18615
+ unit: ""
18616
+ };
18617
+ };
18618
+ /** 总时长:不到一小时按分钟,否则按小时保留一位 */
18619
+ var splitRuntime = (milliseconds) => {
18620
+ const minutes = Math.round(milliseconds / 6e4);
18621
+ if (minutes < 60) return {
18622
+ value: minutes.toString(),
18623
+ unit: "分钟"
18624
+ };
18625
+ return {
18626
+ value: (minutes / 60).toFixed(1),
18627
+ unit: "小时"
18628
+ };
18629
+ };
18630
+ /** 单集时长 mm:ss */
18631
+ var formatDuration$4 = (milliseconds) => {
18632
+ const totalSeconds = Math.round(milliseconds / 1e3);
18633
+ const minutes = Math.floor(totalSeconds / 60);
18634
+ const seconds = totalSeconds % 60;
18635
+ return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
18636
+ };
18637
+ /** 标题按字数分档:短标题撑满,长标题收小并最多三行 */
18638
+ var getTitleClassName$1 = (titleLength) => {
18639
+ if (titleLength <= 8) return "text-[112px] leading-[1.05]";
18640
+ if (titleLength <= 14) return "text-[96px] leading-[1.08]";
18641
+ if (titleLength <= 22) return "text-[76px] leading-[1.12]";
18642
+ return "text-[60px] leading-[1.18]";
18643
+ };
18644
+ /** 弥散背景:海报模糊铺底 + 单色离散杂色 */
18645
+ var MixBackground = ({ data, ctx }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18646
+ className: "pointer-events-none absolute inset-0 overflow-hidden select-none",
18647
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AmbientCover, {
18648
+ src: data.image_url,
18649
+ ctx
18650
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18651
+ className: "absolute inset-0 opacity-[0.35] mix-blend-overlay dark:mix-blend-soft-light",
18652
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
18653
+ className: "h-full w-full",
18654
+ xmlns: "http://www.w3.org/2000/svg",
18655
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("filter", {
18656
+ id: "douyinMixNoise",
18657
+ children: [
18658
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feTurbulence", {
18659
+ type: "fractalNoise",
18660
+ baseFrequency: "0.8",
18661
+ numOctaves: "2",
18662
+ stitchTiles: "stitch"
18663
+ }),
18664
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feColorMatrix", {
18665
+ type: "saturate",
18666
+ values: "0"
18667
+ }),
18668
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("feComponentTransfer", { children: [
18669
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncR", {
18670
+ type: "discrete",
18671
+ tableValues: "0 1"
18672
+ }),
18673
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncG", {
18674
+ type: "discrete",
18675
+ tableValues: "0 1"
18676
+ }),
18677
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncB", {
18678
+ type: "discrete",
18679
+ tableValues: "0 1"
18680
+ })
18681
+ ] }),
18682
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feComponentTransfer", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncA", {
18683
+ type: "linear",
18684
+ slope: "2",
18685
+ intercept: "-0.5"
18686
+ }) })
18687
+ ]
18688
+ }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", {
18689
+ width: "100%",
18690
+ height: "100%",
18691
+ filter: "url(#douyinMixNoise)",
18692
+ fill: "white"
18693
+ })]
18694
+ })
18695
+ })]
18696
+ });
18697
+ var MixHeader = ({ ctx }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
18698
+ className: "flex items-center justify-between",
18699
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18700
+ className: "flex items-center gap-4",
18701
+ children: [
18702
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "h-4 w-4 rounded-full bg-foreground/80" }),
18703
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18704
+ className: "text-[26px] font-bold uppercase tracking-[0.24em] text-muted",
18705
+ children: "Douyin Mix"
18706
+ }),
18707
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18708
+ className: "text-[26px] font-bold text-muted",
18709
+ children: "· 合集选集"
18710
+ })
18711
+ ]
18712
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
18713
+ src: isDark(ctx) ? "/image/douyin/dylogo-light.svg" : "/image/douyin/dylogo-dark.svg",
18714
+ alt: "抖音",
18715
+ className: "h-17 w-auto shrink-0 object-contain opacity-90"
18716
+ })]
18717
+ });
18718
+ var MixHero = ({ data }) => {
18719
+ const { kind, image_url, mix_name, author, statistics, episodes, total_episode, updated_to_episode } = data;
18720
+ const countUnit = kind === "collection" ? "个视频" : "集";
18721
+ const runtime = splitRuntime(episodes.reduce((sum, item) => sum + item.duration, 0));
18722
+ const stats = [
18723
+ {
18724
+ icon: a,
18725
+ label: "总播放",
18726
+ ...splitCount(statistics.play_count)
18727
+ },
18728
+ {
18729
+ icon: DouyinLikeIcon,
18730
+ label: "总点赞",
18731
+ ...splitCount(statistics.digg_count)
18732
+ },
18733
+ {
18734
+ icon: DouyinFavoriteIcon,
18735
+ label: "总收藏",
18736
+ ...splitCount(statistics.collect_count)
18737
+ },
18738
+ {
18739
+ icon: c,
18740
+ label: "总时长",
18741
+ ...runtime
18742
+ }
18743
+ ];
18744
+ const follower = splitCount(author.follower_count);
18745
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
18746
+ className: "mt-14 flex gap-14",
18747
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18748
+ className: `relative ${kind === "collection" ? "h-120" : "h-180"} w-120 shrink-0`,
18749
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
18750
+ src: image_url,
18751
+ alt: "",
18752
+ "aria-hidden": "true",
18753
+ className: "absolute inset-0 h-full w-full scale-[1.06] rounded-[40px] object-cover opacity-55 blur-[36px] saturate-[1.2]",
18754
+ referrerPolicy: "no-referrer",
18755
+ crossOrigin: "anonymous"
18756
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
18757
+ src: image_url,
18758
+ alt: "合集海报",
18759
+ className: "relative h-full w-full rounded-[40px] object-cover shadow-2xl",
18760
+ referrerPolicy: "no-referrer",
18761
+ crossOrigin: "anonymous"
18762
+ })]
18763
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18764
+ className: "flex min-w-0 flex-1 flex-col justify-between gap-10",
18765
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
18766
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
18767
+ className: `${getTitleClassName$1(mix_name.length)} line-clamp-3 font-bold text-balance text-foreground select-text`,
18768
+ style: {
18769
+ wordBreak: "break-word",
18770
+ overflowWrap: "break-word"
18771
+ },
18772
+ children: mix_name
18773
+ }),
18774
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18775
+ className: "mt-8 flex flex-wrap items-center gap-4",
18776
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18777
+ className: "rounded-full bg-foreground px-7 py-3 text-[30px] font-bold leading-none text-background",
18778
+ children: [
18779
+ "共 ",
18780
+ total_episode,
18781
+ " ",
18782
+ countUnit
18783
+ ]
18784
+ }), updated_to_episode < total_episode && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18785
+ className: "rounded-full border border-foreground/20 px-7 py-3 text-[30px] font-semibold leading-none text-foreground/80",
18786
+ children: [
18787
+ "更新至第 ",
18788
+ updated_to_episode,
18789
+ " 集"
18790
+ ]
18791
+ })]
18792
+ }),
18793
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18794
+ className: "mt-10 flex min-w-0 items-center gap-5",
18795
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
18796
+ src: author.avatar,
18797
+ alt: "头像",
18798
+ className: "h-20 w-20 shrink-0 rounded-full object-cover shadow-xl",
18799
+ referrerPolicy: "no-referrer",
18800
+ crossOrigin: "anonymous"
18801
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18802
+ className: "min-w-0",
18803
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18804
+ className: "flex min-w-0 items-center gap-2",
18805
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18806
+ className: "truncate text-[38px] font-bold leading-tight text-foreground select-text",
18807
+ children: author.nickname
18808
+ }), author.verified && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RiVerifiedBadgeFill, {
18809
+ size: 34,
18810
+ className: "shrink-0 text-foreground/70"
18811
+ })]
18812
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18813
+ className: "mt-1 truncate text-[26px] text-muted select-text",
18814
+ children: [
18815
+ "抖音号 ",
18816
+ author.short_id,
18817
+ " · 粉丝 ",
18818
+ follower.value,
18819
+ follower.unit
18820
+ ]
18821
+ })]
18822
+ })]
18823
+ })
18824
+ ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18825
+ className: "grid grid-cols-2 gap-x-10 gap-y-8",
18826
+ children: stats.map((stat) => {
18827
+ const Icon = stat.icon;
18828
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18829
+ className: "min-w-0",
18830
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18831
+ className: "flex items-center gap-3 text-[28px] font-semibold text-muted",
18832
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, {
18833
+ size: 32,
18834
+ weight: "fill",
18835
+ className: "text-foreground/80"
18836
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: stat.label })]
18837
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18838
+ className: "mt-2 flex items-baseline gap-2 text-foreground select-text",
18839
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18840
+ className: "text-[64px] font-semibold leading-none tabular-nums",
18841
+ children: stat.value
18842
+ }), stat.unit && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18843
+ className: "text-[28px] font-bold",
18844
+ children: stat.unit
18845
+ })]
18846
+ })]
18847
+ }, stat.label);
18848
+ })
18849
+ })]
18850
+ })]
18851
+ });
18852
+ };
18853
+ /** 区块标题:竖条 + 标题 */
18854
+ var SectionTitle$1 = ({ children, extra }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18855
+ className: "flex items-end justify-between gap-8",
18856
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18857
+ className: "flex items-center gap-4",
18858
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "h-10 w-2 rounded-full bg-foreground/80" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
18859
+ className: "text-[44px] font-bold leading-none text-foreground",
18860
+ children
18861
+ })]
18862
+ }), extra]
18863
+ });
18864
+ var MixSynopsis = ({ data }) => {
18865
+ if (!data.desc) return null;
18866
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
18867
+ className: "mt-16",
18868
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle$1, { children: "简介" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
18869
+ className: "mt-6 line-clamp-5 text-[40px] leading-[1.6] text-foreground/85 select-text",
18870
+ style: { overflowWrap: "break-word" },
18871
+ children: data.desc
18872
+ })]
18873
+ });
18874
+ };
18875
+ var MixEpisodes = ({ data }) => {
18876
+ const { kind, episodes, total_episode, updated_to_episode, timeoutSeconds } = data;
18877
+ const episodeMap = new Map(episodes.map((item) => [item.episode, item]));
18878
+ const lastEpisode = episodes.reduce((max, item) => Math.max(max, item.episode), 0);
18879
+ const cells = Array.from({ length: lastEpisode }, (_, index) => ({
18880
+ episode: index + 1,
18881
+ item: episodeMap.get(index + 1)
18882
+ }));
18883
+ const missingCount = cells.filter((cell) => cell.item === void 0).length;
18884
+ const padLength = String(lastEpisode).length;
18885
+ const listedRange = lastEpisode < updated_to_episode;
18886
+ const unit = kind === "collection" ? "视频" : "剧集";
18887
+ const countUnit = kind === "collection" ? "个视频" : "集";
18888
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
18889
+ className: "mt-16",
18890
+ children: [
18891
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle$1, {
18892
+ extra: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18893
+ className: "text-[30px] font-semibold text-muted",
18894
+ children: [
18895
+ "已列出 ",
18896
+ episodes.length,
18897
+ " / ",
18898
+ total_episode,
18899
+ " ",
18900
+ countUnit
18901
+ ]
18902
+ }),
18903
+ children: "选集"
18904
+ }),
18905
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18906
+ className: "mt-8 flex items-center justify-between gap-10 rounded-4xl border border-foreground/10 bg-foreground/6 px-10 py-8",
18907
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18908
+ className: "flex min-w-0 flex-wrap items-center gap-x-4 gap-y-3 text-[36px] font-semibold leading-tight text-foreground",
18909
+ children: [
18910
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "发送" }),
18911
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18912
+ className: "rounded-2xl bg-foreground px-5 py-2 font-bold text-background",
18913
+ children: "第?集"
18914
+ }),
18915
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: ["或直接发送集数,解析对应", unit] })
18916
+ ]
18917
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18918
+ className: "shrink-0 text-right",
18919
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18920
+ className: "text-[64px] font-semibold leading-none text-foreground tabular-nums",
18921
+ children: [timeoutSeconds, "s"]
18922
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18923
+ className: "mt-2 text-[24px] font-semibold text-muted",
18924
+ children: "内有效"
18925
+ })]
18926
+ })]
18927
+ }),
18928
+ kind === "collection" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18929
+ className: "mt-8 grid grid-cols-2 gap-4",
18930
+ children: cells.map((cell) => cell.item === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18931
+ className: "flex items-center gap-6 rounded-3xl border-2 border-dashed border-foreground/20 px-7 py-6",
18932
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18933
+ className: "shrink-0 text-[40px] font-semibold leading-none text-foreground/25 tabular-nums",
18934
+ children: String(cell.episode).padStart(padLength, "0")
18935
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18936
+ className: "text-[30px] font-semibold text-foreground/25",
18937
+ children: "缺失"
18938
+ })]
18939
+ }, cell.episode) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18940
+ className: "flex items-center gap-6 rounded-3xl border border-foreground/10 bg-foreground/6 px-7 py-6",
18941
+ children: [
18942
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18943
+ className: "shrink-0 text-[40px] font-semibold leading-none text-foreground tabular-nums select-text",
18944
+ children: String(cell.episode).padStart(padLength, "0")
18945
+ }),
18946
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18947
+ className: "line-clamp-2 min-w-0 flex-1 text-[30px] font-semibold leading-[1.35] text-foreground select-text",
18948
+ style: { overflowWrap: "break-word" },
18949
+ children: cell.item.title || "无标题"
18950
+ }),
18951
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18952
+ className: "shrink-0 text-[24px] text-muted tabular-nums",
18953
+ children: formatDuration$4(cell.item.duration)
18954
+ })
18955
+ ]
18956
+ }, cell.episode))
18957
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18958
+ className: "mt-8 grid grid-cols-10 gap-4",
18959
+ children: cells.map((cell) => cell.item === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18960
+ className: "flex aspect-square flex-col items-center justify-center rounded-3xl border-2 border-dashed border-foreground/20",
18961
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18962
+ className: "text-[44px] font-semibold leading-none text-foreground/25 tabular-nums",
18963
+ children: String(cell.episode).padStart(padLength, "0")
18964
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18965
+ className: "mt-2 text-[22px] font-semibold text-foreground/25",
18966
+ children: "缺失"
18967
+ })]
18968
+ }, cell.episode) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18969
+ className: "flex aspect-square flex-col items-center justify-center rounded-3xl border border-foreground/10 bg-foreground/6",
18970
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18971
+ className: "text-[44px] font-semibold leading-none text-foreground tabular-nums select-text",
18972
+ children: String(cell.episode).padStart(padLength, "0")
18973
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18974
+ className: "mt-2 text-[22px] text-muted tabular-nums",
18975
+ children: formatDuration$4(cell.item.duration)
18976
+ })]
18977
+ }, cell.episode))
18978
+ }),
18979
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18980
+ className: "mt-8 flex flex-wrap items-center justify-between gap-6 text-[26px] text-muted",
18981
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [missingCount > 0 && `虚线格为接口未返回的${unit}(${missingCount} ${kind === "collection" ? "个" : "集"}),暂不能解析。`, listedRange && `已列出第 1–${lastEpisode} 集,共更新 ${updated_to_episode} 集。`] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18982
+ className: "select-text",
18983
+ children: ["图片生成于: ", format(/* @__PURE__ */ new Date(), "yyyy-MM-dd HH:mm:ss")]
18984
+ })]
18985
+ })
18986
+ ]
18987
+ });
18988
+ };
18989
+ var DouyinMix = import_react.memo((props) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DefaultLayout, {
18990
+ ...props,
18991
+ className: "relative overflow-hidden",
18992
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MixBackground, { ...props }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
18993
+ className: "relative z-10 px-20 pt-18",
18994
+ children: [
18995
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MixHeader, { ...props }),
18996
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MixHero, { ...props }),
18997
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MixSynopsis, { ...props }),
18998
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MixEpisodes, { ...props })
18999
+ ]
19000
+ })]
19001
+ }));
19002
+ DouyinMix.displayName = "DouyinMix";
19003
+ //#endregion
19004
+ //#region ktr/template/douyin/mix/index.tsx
19005
+ var mix_default = defineTemplate({
19006
+ name: "合集选集",
19007
+ description: "抖音合集(短剧 / 普通合集)选集列表模板",
19008
+ component: DouyinMix
19009
+ });
19010
+ //#endregion
18600
19011
  //#region ktr/template/douyin/musicinfo/components/MusicInfo.tsx
18601
19012
  /**
18602
19013
  * 抖音Logo头部组件
@@ -29428,6 +29839,7 @@ var templates = {
29428
29839
  "douyin/favorite-list": favorite_list_default,
29429
29840
  "douyin/image-work": image_work_default,
29430
29841
  "douyin/live": live_default,
29842
+ "douyin/mix": mix_default,
29431
29843
  "douyin/musicinfo": musicinfo_default,
29432
29844
  "douyin/qrcodeImg": qrcodeImg_default$2,
29433
29845
  "douyin/recommend-list": recommend_list_default,
@@ -31253,6 +31665,41 @@ var buildBilibiliRichTextForwardMessage = async (document, options = {}) => {
31253
31665
  //#endregion
31254
31666
  //#region src/platform/bilibili/bilibili.ts
31255
31667
  var img$1;
31668
+ /**
31669
+ * B站 PCDN 线路的域名特征。
31670
+ * 这类域名常被代理/去广告规则的 hosts 指向 0.0.0.0,直接请求会 ECONNREFUSED。
31671
+ */
31672
+ var BILIBILI_PCDN_HOST_RE = /(?:^|\.)(?:mcdn|pcdn)\.bilivideo\.(?:cn|com)$|(?:^|\.)szbdyd\.com$/i;
31673
+ /** B站自有 CDN 域名,PCDN 之外的线路可作为稳定备选 */
31674
+ var BILIBILI_CDN_HOST_RE = /(?:^|\.)bilivideo\.(?:cn|com)$/i;
31675
+ /** 取 URL 的 hostname,非法 URL 返回空串 */
31676
+ var getUrlHostname = (url) => {
31677
+ try {
31678
+ return new URL(url).hostname;
31679
+ } catch {
31680
+ return "";
31681
+ }
31682
+ };
31683
+ /** 判断一条 B站直链是否走 PCDN 线路 */
31684
+ var isBilibiliPcdnUrl = (url) => BILIBILI_PCDN_HOST_RE.test(getUrlHostname(url));
31685
+ /** 判断一条 B站直链是否走 B站自有 CDN */
31686
+ var isBilibiliCdnUrl = (url) => BILIBILI_CDN_HOST_RE.test(getUrlHostname(url));
31687
+ /**
31688
+ * 从播放接口返回的流信息里选一条可用直链。
31689
+ *
31690
+ * `base_url` 可能落在 PCDN 线路上,而 `backup_url` 里通常还有常规 CDN;
31691
+ * 主直链不是 PCDN 时保持原行为,是 PCDN 时优先换到 B站自有 CDN 的备用直链,
31692
+ * 全部不可用时退回 `base_url`。
31693
+ */
31694
+ var getBilibiliStreamUrl = (stream) => {
31695
+ if (!stream) return void 0;
31696
+ const baseUrl = stream.base_url ?? stream.baseUrl;
31697
+ if (baseUrl && !isBilibiliPcdnUrl(baseUrl)) return baseUrl;
31698
+ const backups = (stream.backup_url ?? stream.backupUrl ?? []).filter((url) => Boolean(url));
31699
+ const officialBackup = backups.find((url) => isBilibiliCdnUrl(url) && !isBilibiliPcdnUrl(url));
31700
+ const normalBackup = backups.find((url) => !isBilibiliPcdnUrl(url));
31701
+ return officialBackup ?? normalBackup ?? baseUrl ?? backups[0];
31702
+ };
31256
31703
  /** 评论请求统一使用匿名态,避免账号 Cookie 改变评论热度池结果。 */
31257
31704
  var bilibiliAnonymousRequestConfig = { headers: { Cookie: "" } };
31258
31705
  /**
@@ -31375,7 +31822,7 @@ var Bilibili = class extends Base {
31375
31822
  /** 替换原始的视频信息对象 */
31376
31823
  playUrlData.data.data.dash.video = simplify;
31377
31824
  /** 没有音频流(如纯视频稿件)时拿不到音频地址,按只统计视频流大小处理 */
31378
- const audioUrl = playUrlData.data.data.dash.audio?.[0]?.base_url;
31825
+ const audioUrl = getBilibiliStreamUrl(playUrlData.data.data.dash.audio?.[0]);
31379
31826
  /** 给视频信息对象删除不符合条件的视频流 */
31380
31827
  correctList = await bilibiliProcessVideos({
31381
31828
  accept_description: playUrlData.data.data.accept_description,
@@ -31385,7 +31832,7 @@ var Bilibili = class extends Base {
31385
31832
  playUrlData.data.data.dash.video = correctList.videoList;
31386
31833
  playUrlData.data.data.accept_description = correctList.accept_description;
31387
31834
  /** 获取第一个视频流的大小 */
31388
- videoSize = await getvideosize(correctList.videoList[0].base_url, audioUrl, infoData.data.data.bvid);
31835
+ videoSize = await getvideosize(getBilibiliStreamUrl(correctList.videoList[0]), audioUrl, infoData.data.data.bvid);
31389
31836
  } else videoSize = (nockData.data.durl[0].size / 1048576).toFixed(2);
31390
31837
  if (Config.bilibili.sendContent.some((content) => content === "comment")) {
31391
31838
  const commentsData = await softFetch(() => this.amagi.bilibili.fetcher.fetchComments({
@@ -31526,7 +31973,7 @@ var Bilibili = class extends Base {
31526
31973
  accept_description: playUrlData.result.accept_description,
31527
31974
  bvid: videoInfo.data.result.season_id.toString(),
31528
31975
  qn: Config.bilibili.videoQuality
31529
- }, simplify, playUrlData.result.dash.audio?.[0]?.base_url);
31976
+ }, simplify, getBilibiliStreamUrl(playUrlData.result.dash.audio?.[0]));
31530
31977
  playUrlData.result.dash.video = correctList.videoList;
31531
31978
  playUrlData.result.cept_description = correctList.accept_description;
31532
31979
  }
@@ -32105,12 +32552,12 @@ var Bilibili = class extends Base {
32105
32552
  logger$1.debug("是否登录:", this.islogin);
32106
32553
  switch (this.islogin) {
32107
32554
  case true: {
32108
- logger$1.debug("视频 URL:", this.Type === "one_video" ? playUrlData.data?.dash?.video[0].base_url : playUrlData.result.dash.video[0].base_url);
32555
+ logger$1.debug("视频 URL:", this.Type === "one_video" ? getBilibiliStreamUrl(playUrlData.data?.dash?.video[0]) : getBilibiliStreamUrl(playUrlData.result.dash.video[0]));
32109
32556
  const downloadHeaders = {
32110
32557
  ...this.headers,
32111
32558
  Referer: "https://www.bilibili.com"
32112
32559
  };
32113
- const bmp4Raw = await downloadFile(this.Type === "one_video" ? playUrlData.data?.dash?.video[0].base_url : playUrlData.result.dash.video[0].base_url, {
32560
+ const bmp4Raw = await downloadFile(this.Type === "one_video" ? getBilibiliStreamUrl(playUrlData.data?.dash?.video[0]) : getBilibiliStreamUrl(playUrlData.result.dash.video[0]), {
32114
32561
  title: `Bil_V_${this.Type === "one_video" ? infoData && infoData.data.bvid : infoData && infoData.result.season_id}.m4s`,
32115
32562
  headers: downloadHeaders
32116
32563
  });
@@ -32120,7 +32567,7 @@ var Bilibili = class extends Base {
32120
32567
  return false;
32121
32568
  }
32122
32569
  await Common.removeFile(bmp4Raw.filepath, true);
32123
- const audioUrl = this.Type === "one_video" ? playUrlData.data?.dash?.audio?.[0]?.base_url : playUrlData.result.dash.audio?.[0]?.base_url;
32570
+ const audioUrl = this.Type === "one_video" ? getBilibiliStreamUrl(playUrlData.data?.dash?.audio?.[0]) : getBilibiliStreamUrl(playUrlData.result.dash.audio?.[0]);
32124
32571
  logger$1.debug("音频 URL:", audioUrl);
32125
32572
  /** 没有音频流(如纯视频稿件)时为 undefined,此时无从合成,直接发视频流 */
32126
32573
  let bmp3;
@@ -32465,7 +32912,7 @@ var bilibiliProcessVideos = async (qualityOptions, videoList, audioUrl) => {
32465
32912
  }
32466
32913
  const results = {};
32467
32914
  for (const video of videoList) {
32468
- const size = await getvideosize(video.base_url, audioUrl, qualityOptions.bvid);
32915
+ const size = await getvideosize(getBilibiliStreamUrl(video), audioUrl, qualityOptions.bvid);
32469
32916
  results[video.id] = size;
32470
32917
  }
32471
32918
  const sizes = Object.values(results).map((size) => parseFloat(size.replace("MB", "")));
@@ -33778,7 +34225,7 @@ var Bilibilipush = class extends Base {
33778
34225
  /** 替换原始的视频信息对象 */
33779
34226
  playUrlData.data.data.dash.video = simplify;
33780
34227
  /** 没有音频流(如纯视频稿件)时拿不到音频地址,按只统计视频流大小处理 */
33781
- const audioUrl = playUrlData.data.data.dash.audio?.[0]?.base_url;
34228
+ const audioUrl = getBilibiliStreamUrl(playUrlData.data.data.dash.audio?.[0]);
33782
34229
  /** 给视频信息对象删除不符合条件的视频流 */
33783
34230
  correctList = await bilibiliProcessVideos({
33784
34231
  accept_description: playUrlData.data.data.accept_description,
@@ -33789,14 +34236,14 @@ var Bilibilipush = class extends Base {
33789
34236
  playUrlData.data.data.dash.video = correctList.videoList;
33790
34237
  playUrlData.data.data.accept_description = correctList.accept_description;
33791
34238
  /** 获取第一个视频流的大小 */
33792
- videoSize = await getvideosize(correctList.videoList[0].base_url, audioUrl, data[dynamicId].Dynamic_Data.modules.module_dynamic.major.archive.bvid);
34239
+ videoSize = await getvideosize(getBilibiliStreamUrl(correctList.videoList[0]), audioUrl, data[dynamicId].Dynamic_Data.modules.module_dynamic.major.archive.bvid);
33793
34240
  if (Config.app.usefilelimit && Number(videoSize) > Number(Config.app.filelimit) && !Config.app.compress) {
33794
34241
  await karin.sendMsg(botId, Contact, [segment.text(`设定的最大上传大小为 ${Config.app.filelimit}MB\n当前解析到的视频大小为 ${Number(videoSize)}MB\n视频太大了,还是去B站看吧~`), segment.reply(status.messageId)]);
33795
34242
  break;
33796
34243
  }
33797
34244
  logger$1.mark(`当前处于自动推送状态,解析到的视频大小为 ${logger$1.yellow(Number(videoSize))} MB`);
33798
34245
  const infoData = await this.amagi.bilibili.fetcher.fetchVideoInfo({ bvid: data[dynamicId].Dynamic_Data.modules.module_dynamic.major.archive.bvid });
33799
- const mp4File = await downloadFile(playUrlData.data?.data?.dash?.video[0].base_url, {
34246
+ const mp4File = await downloadFile(getBilibiliStreamUrl(playUrlData.data?.data?.dash?.video[0]), {
33800
34247
  title: `Bil_V_${infoData.data.data.bvid}.mp4`,
33801
34248
  headers: bilibiliBaseHeaders
33802
34249
  });
@@ -35495,6 +35942,24 @@ async function burnDouyinDanmaku(videoPath, danmakuList, outputPath, options = {
35495
35942
  return result.status;
35496
35943
  }
35497
35944
  //#endregion
35945
+ //#region src/platform/douyin/mix.ts
35946
+ /** 句中的「第N集」,集数可以是阿拉伯数字或中文数字,数字两侧允许空格(模板里写的就是「第 N 集」) */
35947
+ var EPISODE_IN_TEXT = /第\s*([0-9]+|[零一二三四五六七八九十百千]+)\s*集/;
35948
+ /** 整条回复只有集数:「12」「12集」「十二」 */
35949
+ var EPISODE_ONLY = /^([0-9]+|[零一二三四五六七八九十百千]+)\s*集?$/;
35950
+ /**
35951
+ * 从用户回复里解析要看的集数。
35952
+ * @param msg 用户回复的文本
35953
+ * @returns 集数(从 1 开始);没解析出来返回 null
35954
+ */
35955
+ var parseMixEpisode = (msg) => {
35956
+ const text = msg.trim();
35957
+ const raw = (EPISODE_IN_TEXT.exec(text) ?? EPISODE_ONLY.exec(text))?.[1];
35958
+ if (!raw) return null;
35959
+ const episode = /^[0-9]+$/.test(raw) ? Number(raw) : Common.chineseToArabic(raw);
35960
+ return Number.isInteger(episode) && episode > 0 ? episode : null;
35961
+ };
35962
+ //#endregion
35498
35963
  //#region src/platform/douyin/videoQuality.ts
35499
35964
  /** 档位优先级,从高到低 */
35500
35965
  var QUALITY_PRIORITY = [
@@ -36984,6 +37449,69 @@ var DouYin = class DouYin extends Base {
36984
37449
  }
36985
37450
  return true;
36986
37451
  }
37452
+ case "mix_work": {
37453
+ this.workType = "bangumi";
37454
+ /** 单次最多拉取的集数,超出部分不展示 */
37455
+ const episodeLimit = 200;
37456
+ const timeoutSeconds = 120;
37457
+ const awemeList = (await this.amagi.douyin.fetcher.fetchMixAweme({
37458
+ mix_id: data.mix_id,
37459
+ number: episodeLimit
37460
+ })).data.aweme_list;
37461
+ const first = awemeList[0];
37462
+ if (!first) throw new Error("获取合集作品列表失败,可能是因为该合集已被删除或设置为私密。");
37463
+ const mixInfo = first.mix_info;
37464
+ const author = first.author;
37465
+ const isPlaylet = typeof mixInfo.statis.total_episode === "number";
37466
+ const episodes = awemeList.map((aweme) => ({
37467
+ episode: aweme.mix_info.statis.current_episode,
37468
+ aweme_id: aweme.aweme_id,
37469
+ title: aweme.desc.replace(/#\S+/g, "").trim(),
37470
+ duration: aweme.video.duration
37471
+ }));
37472
+ const img = await Render(this.e, "douyin/mix", {
37473
+ kind: isPlaylet ? "playlet" : "collection",
37474
+ mix_name: mixInfo.mix_name,
37475
+ image_url: mixInfo.cover_url.url_list[0],
37476
+ desc: isPlaylet ? episodes[0].title : "",
37477
+ total_episode: isPlaylet ? mixInfo.statis.total_episode : mixInfo.statis.updated_to_episode,
37478
+ updated_to_episode: mixInfo.statis.updated_to_episode,
37479
+ author: {
37480
+ nickname: author.nickname,
37481
+ avatar: author.avatar_thumb.url_list[0],
37482
+ short_id: author.unique_id === "" ? author.short_id : author.unique_id,
37483
+ follower_count: author.follower_count,
37484
+ verified: !!author.custom_verify || !!author.enterprise_verify_reason
37485
+ },
37486
+ statistics: {
37487
+ play_count: awemeList.reduce((sum, aweme) => sum + aweme.statistics.play_count, 0),
37488
+ digg_count: awemeList.reduce((sum, aweme) => sum + aweme.statistics.digg_count, 0),
37489
+ collect_count: awemeList.reduce((sum, aweme) => sum + aweme.statistics.collect_count, 0)
37490
+ },
37491
+ episodes,
37492
+ timeoutSeconds
37493
+ });
37494
+ await this.e.reply([...img, segment.text(`请在${timeoutSeconds}秒内输入 第?集 选择集数`)]);
37495
+ const context = await karin.ctx(this.e, {
37496
+ reply: true,
37497
+ throwOnTimeout: false,
37498
+ time: timeoutSeconds
37499
+ });
37500
+ if (!context) return true;
37501
+ const episode = parseMixEpisode(context.msg);
37502
+ const target = episode === null ? void 0 : episodes.find((item) => item.episode === episode);
37503
+ if (!target) {
37504
+ await this.e.reply(episode === null ? "匹配内容失败,请重新发送链接再次解析" : `第${episode}集不在可解析的剧集里,请重新发送链接再次解析`);
37505
+ return true;
37506
+ }
37507
+ await this.e.reply(`收到请求,第${episode}集\n${mixInfo.mix_name}\n正在解析中`);
37508
+ const targetData = {
37509
+ type: "one_work",
37510
+ aweme_id: target.aweme_id
37511
+ };
37512
+ await new DouYin(context, targetData, { forceBurnDanmaku: this.forceBurnDanmaku }).DouyinHandler(targetData);
37513
+ return true;
37514
+ }
36987
37515
  case "music_work": {
36988
37516
  this.workType = "music";
36989
37517
  const musicInfo = (await this.amagi.douyin.fetcher.fetchMusicInfo({ music_id: data.music_id })).data.music_info;
@@ -37179,6 +37707,14 @@ var getDouyinID = async (event, url, log = true) => {
37179
37707
  room_id: longLink.split("/").pop()
37180
37708
  };
37181
37709
  break;
37710
+ case /\/(?:share\/(?:playlet|mix)\/detail|collection)\/(\d+)/.test(longLink): {
37711
+ const mixMatch = /\/(?:share\/(?:playlet|mix)\/detail|collection)\/(\d+)/.exec(longLink);
37712
+ result = {
37713
+ type: "mix_work",
37714
+ mix_id: mixMatch ? mixMatch[1] : void 0
37715
+ };
37716
+ break;
37717
+ }
37182
37718
  case /video\/(\d+)/.test(longLink):
37183
37719
  case /article\/(\d+)/.test(longLink):
37184
37720
  case /note\/(\d+)/.test(longLink): {
@@ -37433,6 +37969,26 @@ async function processRecommendList(contentList, sec_uid, userinfo, item, target
37433
37969
  return result;
37434
37970
  }
37435
37971
  //#endregion
37972
+ //#region src/platform/douyin/shortUrl.ts
37973
+ /**
37974
+ * 把分享链接换成抖音签发的 `v.douyin.com` 短链(二维码专用)。
37975
+ *
37976
+ * 短链由 amagi 的 `douyin.shortUrl` 端点签发:同一 target 恒得同一短码,
37977
+ * 内容比任何规范链接都短,能明显降低二维码密度。该端点免登录但仍要 `a_bogus` 签名,
37978
+ * 可能因签名或平台策略失败 —— 失败不阻塞推送,回退原链接即可。
37979
+ * @param target - 待缩短的分享链接(规范链接或视频直链均可)
37980
+ * @returns 短链;签发失败时原样返回 target
37981
+ */
37982
+ var toDouyinShortUrl = async (target) => {
37983
+ try {
37984
+ const shortUrl = (await douyinFetcher.fetchShortUrl({ target })).data?.data;
37985
+ return typeof shortUrl === "string" && shortUrl.length > 0 ? shortUrl : target;
37986
+ } catch (error) {
37987
+ logger$1.debug(`[抖音短链] 签发失败,二维码回退原链接:${error instanceof Error ? error.message : String(error)}`);
37988
+ return target;
37989
+ }
37990
+ };
37991
+ //#endregion
37436
37992
  //#region src/platform/douyin/push.ts
37437
37993
  var douyinBaseHeaders = {
37438
37994
  ...BASE_HEADERS,
@@ -37610,6 +38166,7 @@ var DouYinpush = class extends Base {
37610
38166
  const playAddr = selectedVideo?.play_addr ?? pushItem.Detail_Data.video?.play_addr;
37611
38167
  workShareLink = playAddr ? buildDouyinPlayUrl(playAddr) : `https://www.douyin.com/video/${actualAwemeId}`;
37612
38168
  }
38169
+ workShareLink = await toDouyinShortUrl(workShareLink);
37613
38170
  }
37614
38171
  switch (pushItem.pushType) {
37615
38172
  case "live":
@@ -38108,23 +38665,10 @@ var DouYinpush = class extends Base {
38108
38665
  const botId = this.e.selfId;
38109
38666
  try {
38110
38667
  const inputDouyinId = this.e.msg.replace(/^#设置抖音推送/, "").trim();
38111
- /**
38112
- * 搜索结果按响应形态自述(判别式 `__search_type`,由 amagi 的 normalize 按响应结构 +
38113
- * 请求类型写入)。订阅只认「用户搜索」那一支:抖音把 user 请求回成别的形态时在这里说清楚,
38114
- * 而不是拿一个 undefined 去遍历。
38115
- */
38116
- if (data.__search_type !== "user") throw new Error(`抖音没有返回用户搜索结果(返回形态:${data.__search_type ?? "未知"})`);
38117
- let matchedUser = null;
38118
- for (const userItem of data.user_list) if ((userItem.user_info.unique_id === "" ? userItem.user_info.short_id : userItem.user_info.unique_id) === inputDouyinId) {
38119
- matchedUser = userItem.user_info;
38120
- break;
38121
- }
38122
- if (!matchedUser) throw new Error(`未找到抖音号为 ${inputDouyinId} 的用户`);
38123
- const sec_uid = matchedUser.sec_uid;
38124
- const UserInfoData = await this.amagi.douyin.fetcher.fetchUserProfile({ sec_uid });
38125
38668
  /** 处理抖音号 */
38126
- let user_shortid;
38127
- user_shortid = UserInfoData.data.user.unique_id === "" ? UserInfoData.data.user.short_id : UserInfoData.data.user.unique_id;
38669
+ const user_shortid = data.user_info.unique_id === "" ? data.user_info.short_id : data.user_info.unique_id;
38670
+ const sec_uid = data.user_info.sec_uid;
38671
+ if (inputDouyinId !== user_shortid) throw new Error(`未找到抖音号为 ${inputDouyinId} 的用户`);
38128
38672
  config.douyin ??= [];
38129
38673
  const existingItem = config.douyin.find((item) => item.sec_uid === sec_uid);
38130
38674
  const isSubscribed = await douyinDBInstance.isSubscribed(sec_uid, groupId);
@@ -38144,31 +38688,31 @@ var DouYinpush = class extends Base {
38144
38688
  config.douyin.splice(index, 1);
38145
38689
  }
38146
38690
  Config.Modify("pushlist", "douyin", config.douyin);
38147
- await this.e.reply(`群:${groupInfo.groupName}(${groupId})\n删除成功!${UserInfoData.data.user.nickname}\n抖音号:${user_shortid}`);
38148
- logger$1.info(`\n删除成功!${UserInfoData.data.user.nickname}\n抖音号:${user_shortid}\nsec_uid${UserInfoData.data.user.sec_uid}`);
38691
+ await this.e.reply(`群:${groupInfo.groupName}(${groupId})\n删除成功!${data.user_info.nickname}\n抖音号:${user_shortid}`);
38692
+ logger$1.info(`\n删除成功!${data.user_info.nickname}\n抖音号:${user_shortid}\nsec_uid${data.user_info.sec_uid}`);
38149
38693
  } else {
38150
38694
  existingItem.group_id.push(`${groupId}:${botId}`);
38151
38695
  if (!existingItem.pushTypes || existingItem.pushTypes.length === 0) existingItem.pushTypes = ["post", "live"];
38152
- if (!isSubscribed) await douyinDBInstance.subscribeDouyinUser(groupId, botId, sec_uid, user_shortid, UserInfoData.data.user.nickname);
38696
+ if (!isSubscribed) await douyinDBInstance.subscribeDouyinUser(groupId, botId, sec_uid, user_shortid, data.user_info.nickname);
38153
38697
  Config.Modify("pushlist", "douyin", config.douyin);
38154
- await this.e.reply(`群:${groupInfo.groupName}(${groupId})\n添加成功!${UserInfoData.data.user.nickname}\n抖音号:${user_shortid}`);
38698
+ await this.e.reply(`群:${groupInfo.groupName}(${groupId})\n添加成功!${data.user_info.nickname}\n抖音号:${user_shortid}`);
38155
38699
  if (Config.douyin.push.switch === false) await this.e.reply("请发送「#设置抖音推送开启」以进行推送");
38156
- logger$1.info(`\n设置成功!${UserInfoData.data.user.nickname}\n抖音号:${user_shortid}\nsec_uid${UserInfoData.data.user.sec_uid}`);
38700
+ logger$1.info(`\n设置成功!${data.user_info.nickname}\n抖音号:${user_shortid}\nsec_uid${data.user_info.sec_uid}`);
38157
38701
  }
38158
38702
  } else {
38159
38703
  config.douyin.push({
38160
38704
  switch: true,
38161
38705
  sec_uid,
38162
38706
  group_id: [`${groupId}:${botId}`],
38163
- remark: UserInfoData.data.user.nickname,
38707
+ remark: data.user_info.nickname,
38164
38708
  short_id: user_shortid,
38165
38709
  pushTypes: ["post", "live"]
38166
38710
  });
38167
- if (!isSubscribed) await douyinDBInstance.subscribeDouyinUser(groupId, botId, sec_uid, user_shortid, UserInfoData.data.user.nickname);
38711
+ if (!isSubscribed) await douyinDBInstance.subscribeDouyinUser(groupId, botId, sec_uid, user_shortid, data.user_info.nickname);
38168
38712
  Config.Modify("pushlist", "douyin", config.douyin);
38169
- await this.e.reply(`群:${groupInfo.groupName}(${groupId})\n添加成功!${UserInfoData.data.user.nickname}\n抖音号:${user_shortid}`);
38713
+ await this.e.reply(`群:${groupInfo.groupName}(${groupId})\n添加成功!${data.user_info.nickname}\n抖音号:${user_shortid}`);
38170
38714
  if (Config.douyin.push.switch === false) await this.e.reply("请发送「#设置抖音推送开启」以进行推送");
38171
- logger$1.info(`\n设置成功!${UserInfoData.data.user.nickname}\n抖音号:${user_shortid}\nsec_uid${UserInfoData.data.user.sec_uid}`);
38715
+ logger$1.info(`\n设置成功!${data.user_info.nickname}\n抖音号:${user_shortid}\nsec_uid${data.user_info.sec_uid}`);
38172
38716
  }
38173
38717
  await this.renderPushList();
38174
38718
  } catch (error) {
@@ -39280,6 +39824,8 @@ var Xiaohongshu = class extends Base {
39280
39824
  let staticImgPath = "";
39281
39825
  if (staticImg.filepath) staticImgPath = staticImg.filepath;
39282
39826
  const livePhotoVideo = xiaohongshuGetLivePhotoVideo(item.stream);
39827
+ /** 这条动图是否已产出至少一个待发送元素(视频 / 实况图 / 静态兜底) */
39828
+ let hasPushedItem = false;
39283
39829
  if (livePhotoVideo) {
39284
39830
  const livePhotoPath = Common.tempDri.video + `livephoto_${Date.now()}_${index}.mp4`;
39285
39831
  const livePhoto = await downloadFile(livePhotoVideo.master_url, {
@@ -39317,6 +39863,7 @@ var Xiaohongshu = class extends Base {
39317
39863
  });
39318
39864
  const videoPath = Config.app.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
39319
39865
  processedImages.push(segment.video(videoPath));
39866
+ hasPushedItem = true;
39320
39867
  }
39321
39868
  }
39322
39869
  if (shouldGenerateLivePhoto) {
@@ -39334,6 +39881,7 @@ var Xiaohongshu = class extends Base {
39334
39881
  });
39335
39882
  const motionPhotoCover = processLocalImageFile(motionPhotoCoverPath);
39336
39883
  processedImages.push(segment.image(motionPhotoCover));
39884
+ hasPushedItem = true;
39337
39885
  hasPushedMotionPhotoCover = true;
39338
39886
  hasGeneratedLivePhoto = true;
39339
39887
  logger.debug(`小红书实况图生成成功: ${motionPhotoCoverPath}`);
@@ -39342,12 +39890,17 @@ var Xiaohongshu = class extends Base {
39342
39890
  if (!hasPushedMotionPhotoCover) {
39343
39891
  const imageUrl = await processImageUrl(item.url_default, title, index);
39344
39892
  processedImages.push(segment.image(imageUrl));
39893
+ hasPushedItem = true;
39345
39894
  }
39346
39895
  }
39347
39896
  logger.mark("正在尝试删除缓存文件");
39348
39897
  await Common.removeFile(livePhoto.filepath, true);
39349
39898
  }
39350
39899
  }
39900
+ if (!hasPushedItem) {
39901
+ const fallbackImage = staticImgPath ? processLocalImageFile(staticImgPath) : await processImageUrl(item.url_default, title, index);
39902
+ processedImages.push(segment.image(fallbackImage));
39903
+ }
39351
39904
  if (staticImgPath) temp.push({
39352
39905
  filepath: staticImgPath,
39353
39906
  totalBytes: 0
@@ -39375,6 +39928,10 @@ var Xiaohongshu = class extends Base {
39375
39928
  } finally {
39376
39929
  for (const item of temp) await Common.removeFile(item.filepath, true);
39377
39930
  }
39931
+ else {
39932
+ logger.warn(`小红书图集解析未生成可发送内容,note_id=${data.note_id}`);
39933
+ for (const item of temp) await Common.removeFile(item.filepath, true);
39934
+ }
39378
39935
  }
39379
39936
  if (NoteData.data.data.items[0].note_card.video && Config.xiaohongshu.sendContent.includes("video")) {
39380
39937
  const video = NoteData.data.data.items[0].note_card.video;
@@ -40166,10 +40723,7 @@ var handleSetDouyinPush = wrapWithErrorHandler(async (e) => {
40166
40723
  logger$1.info(`抖音推送已${enable ? "开启" : "关闭"}`);
40167
40724
  return true;
40168
40725
  }
40169
- const data = await douyinFetcher.searchContent({
40170
- query,
40171
- type: "user"
40172
- });
40726
+ const data = await douyinFetcher.fetchGuestUserInfo({ unique_id: query });
40173
40727
  await new DouYinpush(e).setting(data.data);
40174
40728
  return true;
40175
40729
  }, { businessName: "设置抖音推送" });
@@ -41216,13 +41770,17 @@ var globalStatistics = karin.command(/^#?kkk全局解析统计$/, handleGlobalSt
41216
41770
  });
41217
41771
  //#endregion
41218
41772
  //#region src/apps/testPush.ts
41219
- /** 构建与生产推送一致的作品二维码链接。 */
41220
- function buildWorkShareLink(aweme, selectedVideo) {
41773
+ /** 构建与生产推送一致的作品二维码短链(签发失败回退原链接)。 */
41774
+ async function buildWorkShareLink(aweme, selectedVideo) {
41221
41775
  const workTypeInfo = getWorkTypeInfo(aweme);
41222
- if (workTypeInfo.isArticle) return `https://www.douyin.com/article/${aweme.aweme_id}`;
41223
- if (workTypeInfo.isImage) return `https://www.douyin.com/note/${aweme.aweme_id}`;
41224
- const playAddr = selectedVideo?.play_addr ?? aweme.video?.play_addr;
41225
- return playAddr ? buildDouyinPlayUrl(playAddr) : `https://www.douyin.com/video/${aweme.aweme_id}`;
41776
+ let target;
41777
+ if (workTypeInfo.isArticle) target = `https://www.douyin.com/article/${aweme.aweme_id}`;
41778
+ else if (workTypeInfo.isImage) target = `https://www.douyin.com/note/${aweme.aweme_id}`;
41779
+ else {
41780
+ const playAddr = selectedVideo?.play_addr ?? aweme.video?.play_addr;
41781
+ target = playAddr ? buildDouyinPlayUrl(playAddr) : `https://www.douyin.com/video/${aweme.aweme_id}`;
41782
+ }
41783
+ return await toDouyinShortUrl(target);
41226
41784
  }
41227
41785
  /**
41228
41786
  * 测试抖音推送命令处理器
@@ -41266,7 +41824,7 @@ var handleTestPush = wrapWithErrorHandler(async (e) => {
41266
41824
  const aweme = workData.data.aweme_detail;
41267
41825
  const Detail_Data = buildDouyinWorkDetail(aweme, { user_info: (await douyinFetcher.fetchUserProfile({ sec_uid: aweme.author.sec_uid })).data });
41268
41826
  const selectedVideo = aweme.video?.bit_rate?.length ? douyinProcessVideos(aweme.video.bit_rate, Config.douyin.videoQuality)[0] : null;
41269
- const shareLink = buildWorkShareLink(Detail_Data, selectedVideo);
41827
+ const shareLink = await buildWorkShareLink(Detail_Data, selectedVideo);
41270
41828
  images = await renderWorkImage({
41271
41829
  e,
41272
41830
  Detail_Data,
@@ -41305,7 +41863,7 @@ var handleTestPush = wrapWithErrorHandler(async (e) => {
41305
41863
  user_info: userinfo.data,
41306
41864
  author_user_info: authorUserInfo
41307
41865
  });
41308
- const shareLink = buildWorkShareLink(Detail_Data, aweme.video?.bit_rate?.length ? douyinProcessVideos(aweme.video.bit_rate, Config.douyin.videoQuality)[0] : null);
41866
+ const shareLink = await buildWorkShareLink(Detail_Data, aweme.video?.bit_rate?.length ? douyinProcessVideos(aweme.video.bit_rate, Config.douyin.videoQuality)[0] : null);
41309
41867
  images = await renderFavoriteImage({
41310
41868
  e,
41311
41869
  Detail_Data,
@@ -41344,7 +41902,7 @@ var handleTestPush = wrapWithErrorHandler(async (e) => {
41344
41902
  user_info: userinfo.data,
41345
41903
  author_user_info: authorUserInfo
41346
41904
  });
41347
- const shareLink = buildWorkShareLink(Detail_Data, aweme.video?.bit_rate?.length ? douyinProcessVideos(aweme.video.bit_rate, Config.douyin.videoQuality)[0] : null);
41905
+ const shareLink = await buildWorkShareLink(Detail_Data, aweme.video?.bit_rate?.length ? douyinProcessVideos(aweme.video.bit_rate, Config.douyin.videoQuality)[0] : null);
41348
41906
  images = await renderRecommendImage({
41349
41907
  e,
41350
41908
  Detail_Data,