karin-plugin-kkk 2.45.2-beta.3 → 2.45.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  globalThis.__KTR_BUNDLED__ = true;
2
2
  import { o as __toESM } from "./rolldown-runtime.js";
3
3
  import { a as createBilibiliRoutes, c as createXiaohongshuRoutes, i as bilibiliApiUrls, l as wbi_sign, n as DynamicType, o as createDouyinRoutes, r as MajorType, s as createKuaishouRoutes, t as Client } from "./default.js";
4
- import { $ as RiPieChart2Fill, $n as GitBranch, $t as Video, A as MdFitScreen, An as Quote, Ar as Calendar, At as n, B as AiFillHeart, Bn as MessageCircle, Br as require_jsx_runtime, Bt as p, C as webassembly_default, Cn as ScanLine, Cr as CircleDashed, Ct as o$3, D as tailwind_css_default, Dn as Repeat, Dr as Check, Dt as s, E as tsdown_default, En as RotateCcw, Er as CircleAlert, Et as o$1, F as FaCommentDots, Fn as Pencil, Fr as BookOpenText, Ft as m$1, G as RiArrowRightFill, Gn as ListChecks, Gr as require_server_node, Gt as parse, H as AiFillStar, Hn as Maximize, Hr as clsx, Ht as s$1, I as FaMusic, In as Palette, Ir as BellRing, It as m$2, J as RiHeart3Fill, Jn as Info, Jr as defineTemplate, Jt as format, K as RiGroupLine, Kn as Link, Kr as require_react, Kt as fromUnixTime, L as FaTiktok, Ln as Network$1, Lr as ArrowRight, Lt as c$1, M as MdLightbulbOutline, Mn as Puzzle, Mr as Box, Mt as e, N as MdLocationOn, Nn as Plus, Nr as Bot, Nt as c$2, O as sqlite_default, On as RefreshCw, Or as ChartColumn, Ot as m, P as MdSchedule, Pn as Play, Pr as Bookmark, Pt as a$1, Q as RiMessage3Fill, Qn as Globe, Qt as WandSparkles, R as FaUserGroup, Rn as Moon, Rr as Chip, Rt as o$2, S as rehypeHighlight, Sn as Search, Sr as CircleEllipsis, St as SiAnthropic, T as typescript_default, Tn as Route, Tr as CircleCheck, Tt as o, U as AiOutlineVideoCamera, Un as MapPin, Ur as createTemplateRenderer, Ut as m$3, V as AiFillPushpin, Vn as Menu, Vr as twMerge, Vt as n$1, W as Markdown, Wn as LoaderCircle, Wr as resolveTemplateStyle, Wt as zhCN, X as RiHeartLine, Xn as Heart, Xt as Zap, Y as RiHeart3Line, Yn as Image$1, Yt as differenceInSeconds, Z as RiLiveLine, Zn as Hash, Zt as X, _ as use, _n as Shield, _r as Clock, _t as SiHonor, a as drawDither, an as Upload, ar as FilePlay, at as RiTrophyFill, b as install$2, bn as Share2, br as CircleQuestionMark, bt as SiBilibili, c as install$11, cn as Timer, cr as Eye, ct as RiVideoLine, d as install$8, dn as Star, dr as Download, dt as SiVivo, en as Users, er as Gift, et as RiShareForwardFill, f as install$7, fn as Square, fr as Crown, ft as SiSamsung, g as install$5, gn as ShoppingBag, gr as Code, gt as SiHuawei, h as install$6, hn as Smartphone, hr as Copy, ht as SiKuaishou, in as UserPlus, ir as FileText, it as RiTiktokFill, j as MdInfoOutline, jn as QrCode, jr as Braces, jt as p$1, k as react_default, kn as Radio, kr as Camera, kt as a, l as install$10, ln as Terminal, lr as ExternalLink, lt as SiXiaomi, m as install$4, mn as Sparkles, mr as CornerDownLeft, mt as SiOneplus, nn as User, nr as Gamepad2, nt as RiStarLine, o as require_jpeg_js, on as TriangleAlert, or as FileExclamationPoint, ot as RiUserFollowLine, p as install$3, pn as SquarePen, pr as Cpu, pt as SiOppo, q as RiHashtag, qn as LayoutTemplate, qr as HtmlWrapper, qt as formatDistanceToNow, rn as UserRound, rr as FingerprintPattern, rt as RiThumbUpFill, s as require_heic_decode, sn as Trash, sr as EyeOff, st as RiVerifiedBadgeFill, tn as UsersRound, tr as Gauge, tt as RiStarFill, u as install$9, un as Sun, ur as EighthNotes, ut as SiXiaohongshu, v as init, vn as ShieldCheck, vr as Clock3, vt as SiGooglephotos, w as vite_default, wn as Save, wr as CircleCheckBig, wt as r, x as install$1, xn as Settings2, xr as CircleFadingArrowUp, xt as SiApple, y as install, yn as ShieldAlert, yr as Clapperboard, yt as SiGithub, z as BiImage, zn as Monitor, zr as Button, zt as c } from "./vendor.js";
4
+ import { $ as RiPieChart2Fill, $n as Gauge, $t as User, A as MdFitScreen, An as Plus, Ar as Box, At as m, B as AiFillHeart, Bn as Maximize, Br as format, Bt as o$2, C as webassembly_default, Cn as RotateCcw, Cr as CircleCheckBig, Ct as Chip, D as tailwind_css_default, Dn as Quote, Dr as Camera, Dt as o, E as tsdown_default, En as Radio, Er as ChartColumn, Et as r, F as FaCommentDots, Fn as MousePointerClick, Fr as ArrowRight, Ft as c$2, G as RiArrowRightFill, Gn as LayoutTemplate, Gr as createTemplateRenderer, Gt as m$3, H as AiFillStar, Hn as LoaderCircle, Hr as require_jsx_runtime, Ht as p, I as FaMusic, In as Moon, Ir as ArrowDownWideNarrow, It as a$1, J as RiHeart3Fill, Jn as Heart, Jr as require_react, Jt as X, K as RiGroupLine, Kn as Info, Kr as resolveTemplateStyle, Kt as zhCN, L as FaTiktok, Ln as Monitor, Lr as parse, Lt as m$1, M as MdLightbulbOutline, Mn as Pencil, Mr as Bookmark, Mt as n, N as MdLocationOn, Nn as Palette, Nr as BookOpenText, Nt as p$1, O as sqlite_default, On as QrCode, Or as Calendar, Ot as o$1, P as MdSchedule, Pn as Network$1, Pr as BellRing, Pt as e, Q as RiMessage3Fill, Qn as Gift, Qt as UsersRound, R as FaUserGroup, Rn as MessageCircle, Rr as fromUnixTime, Rt as m$2, S as rehypeHighlight, Sn as Route, Sr as CircleCheck, St as SiAnthropic, T as typescript_default, Tn as RefreshCw, Tr as Check, Tt as o$3, U as AiOutlineVideoCamera, Un as ListChecks, Ur as twMerge, Ut as n$1, V as AiFillPushpin, Vn as MapPin, Vr as differenceInSeconds, Vt as c, W as Markdown, Wn as Link, Wr as clsx, Wt as s$1, X as RiHeartLine, Xn as Globe, Xr as defineTemplate, Xt as Video, Y as RiHeart3Line, Yn as Hash, Yr as HtmlWrapper, Yt as WandSparkles, Z as RiLiveLine, Zn as GitBranch, Zt as Users, _ as use, _n as Share2, _r as Clapperboard, _t as SiHonor, a as drawDither, an as Timer, ar as Eye, at as RiTrophyFill, b as install$2, bn as ScanLine, br as CircleEllipsis, bt as SiBilibili, c as install$11, cn as Star, cr as EighthNotes, ct as RiVideoLine, d as install$8, dn as Sparkles, dr as Cpu, dt as SiVivo, en as UserRound, er as Gamepad2, et as RiShareForwardFill, f as install$7, fn as Smartphone, fr as CornerDownLeft, ft as SiSamsung, g as install$5, gn as ShieldAlert, gr as Clock3, gt as SiHuawei, h as install$6, hn as ShieldCheck, hr as Clock, ht as SiKuaishou, in as Trash, ir as FileExclamationPoint, it as RiTiktokFill, j as MdInfoOutline, jn as Play, jr as Bot, jt as a, k as react_default, kn as Puzzle, kr as Braces, kt as s, l as install$10, ln as Square, lr as Download, lt as SiXiaomi, m as install$4, mn as Shield, mr as Code, mt as SiOneplus, nn as Upload, nr as FileText, nt as RiStarLine, o as require_jpeg_js, on as Terminal, or as EyeOff, ot as RiUserFollowLine, p as install$3, pn as ShoppingBag, pr as Copy, pt as SiOppo, q as RiHashtag, qn as Image$1, qr as require_server_node, qt as Zap, rn as TriangleAlert, rr as FilePlay, rt as RiThumbUpFill, s as require_heic_decode, sn as Sun, sr as ExternalLink, st as RiVerifiedBadgeFill, tn as UserPlus, tr as FingerprintPattern, tt as RiStarFill, u as install$9, un as SquarePen, ur as Crown, ut as SiXiaohongshu, v as init, vn as Settings2, vr as CircleQuestionMark, vt as SiGooglephotos, w as vite_default, wn as Repeat, wr as CircleAlert, wt as Button, x as install$1, xn as Save, xr as CircleDashed, xt as SiApple, y as install, yn as Search, yr as CircleFadingArrowUp, yt as SiGithub, z as BiImage, zn as Menu, zr as formatDistanceToNow, zt as c$1 } from "./vendor.js";
5
5
  import { createRequire } from "node:module";
6
6
  import fs from "node:fs";
7
7
  import path, { resolve } from "node:path";
@@ -1362,6 +1362,8 @@ function getEmojiId(e, type) {
1362
1362
  return (platform in PLATFORM_EMOJI_IDS ? PLATFORM_EMOJI_IDS[platform] : PLATFORM_EMOJI_IDS.other)[type];
1363
1363
  }
1364
1364
  PLATFORM_EMOJI_IDS.qq;
1365
+ /** 同一条消息上的表情写操作按发起顺序串行执行:适配器并发处理时,后发的 remove 不能先于先发的 add 生效 */
1366
+ var reactionChains = /* @__PURE__ */ new WeakMap();
1365
1367
  /**
1366
1368
  * 设置消息表情回复
1367
1369
  * @param e 消息事件对象
@@ -1372,13 +1374,19 @@ PLATFORM_EMOJI_IDS.qq;
1372
1374
  async function setEmojiReaction(e, emojiId, isSet = true) {
1373
1375
  if (!Config.app.EmojiReply) return false;
1374
1376
  if (e.isPrivate) return false;
1375
- try {
1376
- await e.bot.setMsgReaction(e.contact, e.messageId, emojiId, isSet);
1377
- return true;
1378
- } catch (err) {
1379
- logger$1.debug("[EmojiReaction] 设置表情回复失败(已忽略):", err);
1380
- return false;
1381
- }
1377
+ const write = async () => {
1378
+ try {
1379
+ await e.bot.setMsgReaction(e.contact, e.messageId, emojiId, isSet);
1380
+ return true;
1381
+ } catch (err) {
1382
+ logger$1.debug("[EmojiReaction] 设置表情回复失败(已忽略):", err);
1383
+ return false;
1384
+ }
1385
+ };
1386
+ const previous = reactionChains.get(e);
1387
+ const current = previous ? previous.then(write, write) : write();
1388
+ reactionChains.set(e, current);
1389
+ return current;
1382
1390
  }
1383
1391
  /**
1384
1392
  * 表情回复管理器
@@ -1488,6 +1496,85 @@ var EmojiReactionManager = class {
1488
1496
  return this.emojiIds.size;
1489
1497
  }
1490
1498
  };
1499
+ /** 表情反馈节奏:收到消息 1.5 秒后补「处理中」,结束时再把「处理中」换成结果 */
1500
+ var EMOJI_PROCESSING_DELAY_MS = 1500;
1501
+ var EMOJI_RESULT_DELAY_MS = 1500;
1502
+ /** 已由业务侧收尾的消息及结果;失败结果可以覆盖先前的成功,成功不会覆盖失败 */
1503
+ var settledEvents = /* @__PURE__ */ new WeakMap();
1504
+ /** 这条消息最近一次收尾的完整过程;成功收尾前要等它落地,确认业务侧是否真的收尾成功 */
1505
+ var settleTasks = /* @__PURE__ */ new WeakMap();
1506
+ /**
1507
+ * 给一条消息的表情收尾:撤掉「处理中」,补上结果表情。
1508
+ *
1509
+ * 子上下文处理完成后用它把父消息也收尾;收尾过的消息会被标记,
1510
+ * 外层 `wrapWithErrorHandler` 之后不会再改它的表情。
1511
+ * 失败可以覆盖先前的成功(先撤掉 ✅ 再补 ❌),成功不会覆盖失败。
1512
+ * @param e 需要收尾的消息
1513
+ * @param type 结果表情,默认 SUCCESS
1514
+ */
1515
+ var settleEmojiReaction = async (e, type = "SUCCESS") => {
1516
+ const settled = settledEvents.get(e);
1517
+ if (settled === "ERROR" || settled === type) return;
1518
+ settledEvents.set(e, type);
1519
+ const task = (async () => {
1520
+ await setEmojiReaction(e, getEmojiId(e, "PROCESSING"), false);
1521
+ if (settled === "SUCCESS") await setEmojiReaction(e, getEmojiId(e, "SUCCESS"), false);
1522
+ if (!await setEmojiReaction(e, getEmojiId(e, type), true) && settledEvents.get(e) === type) settledEvents.delete(e);
1523
+ })();
1524
+ settleTasks.set(e, task);
1525
+ await task;
1526
+ };
1527
+ /** 等这条消息上最近一次收尾过程落地(没有收尾过则立即返回)。 */
1528
+ var waitForSettle = async (e) => {
1529
+ const task = settleTasks.get(e);
1530
+ if (task) await task;
1531
+ };
1532
+ /** 这条消息的表情是否已经由业务侧收尾。 */
1533
+ var isEmojiReactionSettled = (e) => settledEvents.has(e);
1534
+ /**
1535
+ * 给一条消息创建表情反馈流程。
1536
+ * @param e 触发本次处理的消息
1537
+ */
1538
+ var createEmojiReactionFlow = (e) => {
1539
+ const manager = new EmojiReactionManager(e);
1540
+ let processingTimer = null;
1541
+ /** 定时器补 ⏳ 的请求;失败时要等它落地再撤,否则本地还没记录会漏撤 */
1542
+ let processingAdd = null;
1543
+ let finished = false;
1544
+ const begin = async () => {
1545
+ await manager.add("EYES");
1546
+ processingTimer = setTimeout(() => {
1547
+ if (isEmojiReactionSettled(e)) return;
1548
+ processingAdd = manager.add("PROCESSING");
1549
+ }, EMOJI_PROCESSING_DELAY_MS);
1550
+ };
1551
+ const succeed = async () => {
1552
+ if (finished) return;
1553
+ await waitForSettle(e);
1554
+ if (finished || isEmojiReactionSettled(e)) return;
1555
+ finished = true;
1556
+ setTimeout(() => {
1557
+ manager.replace("PROCESSING", "SUCCESS").catch(() => {});
1558
+ }, EMOJI_RESULT_DELAY_MS);
1559
+ };
1560
+ const fail = async () => {
1561
+ if (finished) return;
1562
+ finished = true;
1563
+ if (processingTimer) clearTimeout(processingTimer);
1564
+ if (processingAdd) await processingAdd;
1565
+ if (settledEvents.get(e) === "SUCCESS") {
1566
+ await settleEmojiReaction(e, "ERROR");
1567
+ return;
1568
+ }
1569
+ if (manager.has(getEmojiId(e, "PROCESSING"))) await manager.remove("PROCESSING");
1570
+ await manager.add("ERROR");
1571
+ };
1572
+ return {
1573
+ begin,
1574
+ succeed,
1575
+ fail
1576
+ };
1577
+ };
1491
1578
  //#endregion
1492
1579
  //#region src/module/utils/MotionPhoto.ts
1493
1580
  var xmpHeaderBuffer = Buffer.from("http://ns.adobe.com/xap/1.0/\0", "utf8");
@@ -10436,11 +10523,6 @@ var webConfig = defineConfig({
10436
10523
  }
10437
10524
  });
10438
10525
  //#endregion
10439
- //#region ktr/utils/cn.ts
10440
- function cn(...inputs) {
10441
- return twMerge(clsx(inputs));
10442
- }
10443
- //#endregion
10444
10526
  //#region ktr/utils/theme.ts
10445
10527
  /**
10446
10528
  * 判断当前渲染是否为深色主题。
@@ -10450,6 +10532,48 @@ function cn(...inputs) {
10450
10532
  * @returns 深色主题返回 true。
10451
10533
  */
10452
10534
  var isDark = (ctx) => ctx.theme?.mode === "dark";
10535
+ /** 主题色压色罩不透明度 (0~1):edge 为顶/底两端,middle 为中间带;调小 middle 封面色更透 */
10536
+ var AMBIENT_OVERLAY_OPACITY = {
10537
+ edge: .9,
10538
+ middle: .2
10539
+ };
10540
+ /** 用 color-mix 生成三段压色渐变(0% / 50% / 100% 停靠点对应 edge / middle / edge) */
10541
+ var overlayGradient = (color, edge, middle) => `linear-gradient(to bottom, color-mix(in oklab, ${color} ${edge * 100}%, transparent), color-mix(in oklab, ${color} ${middle * 100}%, transparent), color-mix(in oklab, ${color} ${edge * 100}%, transparent))`;
10542
+ /**
10543
+ * 氛围背景基础层:高斯模糊封面 + 主题色压色罩。
10544
+ * 压色罩浅色用主题背景色、深色用纯黑(两个层按 dark 变体显隐),
10545
+ * 各模板在此之上再叠加自己的杂色纹理层。
10546
+ * 贡献度参数优先取 ctx.ambientCover(core 注入的用户配置),缺省用模块内默认值。
10547
+ */
10548
+ var AmbientCover = import_react.memo(({ src, ctx }) => {
10549
+ const coverOpacity = ctx?.ambientCover?.coverOpacity ?? .7;
10550
+ const edge = ctx?.ambientCover?.overlayEdgeOpacity ?? AMBIENT_OVERLAY_OPACITY.edge;
10551
+ const middle = ctx?.ambientCover?.overlayMiddleOpacity ?? AMBIENT_OVERLAY_OPACITY.middle;
10552
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
10553
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
10554
+ src,
10555
+ alt: "",
10556
+ className: "absolute inset-0 h-full w-full scale-150 object-cover blur-[120px] saturate-[1.8]",
10557
+ style: { opacity: coverOpacity },
10558
+ referrerPolicy: "no-referrer",
10559
+ crossOrigin: "anonymous"
10560
+ }),
10561
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
10562
+ className: "absolute inset-0 dark:hidden",
10563
+ style: { background: overlayGradient("var(--background)", edge, middle) }
10564
+ }),
10565
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
10566
+ className: "absolute inset-0 hidden dark:block",
10567
+ style: { background: overlayGradient("#000", edge, middle) }
10568
+ })
10569
+ ] });
10570
+ });
10571
+ AmbientCover.displayName = "AmbientCover";
10572
+ //#endregion
10573
+ //#region ktr/utils/cn.ts
10574
+ function cn(...inputs) {
10575
+ return twMerge(clsx(inputs));
10576
+ }
10453
10577
  //#endregion
10454
10578
  //#region ktr/template/components/GlowImage.tsx
10455
10579
  var GlowImage = ({ src, children, alt, className, imgClassName, mode = "blur-layer", blurRadius = 18, glowStrength = .6, scale = 1.06, shadowRadius = 28, crossOrigin }) => {
@@ -11793,390 +11917,724 @@ var DecorationCard = ({ data }) => {
11793
11917
  //#region ktr/template/bilibili/bangumi/components/bangumi.tsx
11794
11918
  /** 海报每页展示的集数,和 core 的 EPISODE_SELECTION_PAGE_SIZE 保持一致 */
11795
11919
  var DEFAULT_POSTER_PAGE_SIZE$1 = 15;
11796
- /**
11797
- * 格式化数字显示
11798
- * @param num 要格式化的数字
11799
- * @returns 格式化后的字符串
11800
- */
11801
- var formatNumber$2 = (num) => {
11802
- if (num >= 1e8) return `${(num / 1e8).toFixed(1)}亿`;
11803
- if (num >= 1e4) return `${(num / 1e4).toFixed(1)}万`;
11804
- return num.toString();
11920
+ /** 选集有效期兜底(秒),正常由 data.timeoutSeconds 下发,和 core 的 EPISODE_SELECTION_TTL_SECONDS 保持一致 */
11921
+ var DEFAULT_TIMEOUT_SECONDS$1 = 120;
11922
+ /** QQBot 键盘每行的数字按钮数,和 core 的 EPISODE_SELECTION_BUTTONS_PER_ROW 保持一致 */
11923
+ var KEYS_PER_ROW$1 = 5;
11924
+ /** 演职员栏最多列出的「角色 声优」对数,其余折叠成「等 N 人」 */
11925
+ var CAST_LIMIT = 5;
11926
+ /** B 站粉:浅色主题压深到 #E04F86(大字对比度约 3.4:1),深色主题用品牌色 #FB7299 */
11927
+ var ACCENT_TEXT = "text-[#E04F86] dark:text-[#FB7299]";
11928
+ var ACCENT_BG = "bg-[#E04F86] dark:bg-[#FB7299]";
11929
+ /** 命令 chip 底色:覆盆子粉,白字对比度约 4.7:1 */
11930
+ var CHIP_BG$1 = "#CC3D72";
11931
+ /** 徽标缺色时的中性兜底 */
11932
+ var BADGE_FALLBACK = "#71717A";
11933
+ /** 网格补位块的跨列类名(Tailwind 只认静态字面量) */
11934
+ var COL_SPAN$1 = {
11935
+ 1: "col-span-1",
11936
+ 2: "col-span-2",
11937
+ 3: "col-span-3",
11938
+ 4: "col-span-4"
11939
+ };
11940
+ var COPYRIGHT_LABEL = { dujia: "独家" };
11941
+ /** 万/亿 计数拆成数值与单位:10 亿以下的亿级两位小数,其余一位,去掉末尾 0(306.0万 → 306万);不足 1 万原样返回 */
11942
+ var splitCount$1 = (count) => {
11943
+ if (count >= 1e9) return {
11944
+ value: `${Number((count / 1e8).toFixed(1))}`,
11945
+ unit: "亿"
11946
+ };
11947
+ if (count >= 1e8) return {
11948
+ value: `${Number((count / 1e8).toFixed(2))}`,
11949
+ unit: "亿"
11950
+ };
11951
+ if (count >= 1e4) return {
11952
+ value: `${Number((count / 1e4).toFixed(1))}`,
11953
+ unit: "万"
11954
+ };
11955
+ return {
11956
+ value: Math.max(0, Math.round(count)).toString(),
11957
+ unit: ""
11958
+ };
11805
11959
  };
11806
- /**
11807
- * 格式化日期部分
11808
- * @param timestamp 时间戳
11809
- * @returns 包含月份和日期的对象
11810
- */
11811
- var formatDateParts = (timestamp) => {
11812
- const date = /* @__PURE__ */ new Date(timestamp * 1e3);
11960
+ /** 「1800.1万追番」「1158.7万系列追番」拆成数值 / 单位 / 标签;数值照抄 B 站的文本,和番剧页显示一致 */
11961
+ var parseFollowText = (text) => {
11962
+ const match = /^\s*(\d+(?:\.\d+)?)\s*(万|亿)?\s*(.*)$/.exec(text || "");
11963
+ if (!match) return null;
11813
11964
  return {
11814
- month: date.toLocaleDateString("zh-CN", { month: "short" }),
11815
- day: date.getDate().toString().padStart(2, "0")
11965
+ value: match[1],
11966
+ unit: match[2] ?? "",
11967
+ label: match[3].trim() || "追番"
11816
11968
  };
11817
11969
  };
11818
- /**
11819
- * B站番剧头部组件
11820
- * @param props 头部组件属性
11821
- */
11822
- var BangumiBilibiliHeader = (props) => {
11823
- const actorList = props.actors ? props.actors.split(/[,,、\s]+/).filter((actor) => actor.trim()) : [];
11824
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
11825
- className: "overflow-hidden relative rounded-6xl",
11826
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11827
- className: "flex relative z-10 gap-25 p-25",
11828
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11829
- className: "flex flex-col shrink-0 gap-20",
11830
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
11831
- className: "overflow-hidden rounded-4xl w-120 h-160",
11832
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnhancedImage, {
11833
- src: props.mainCover,
11834
- alt: props.title,
11835
- className: "object-cover w-full h-full select-text"
11836
- })
11837
- }), props.upInfo && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11838
- className: "flex gap-12 items-center mt-15",
11839
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11840
- className: "relative",
11841
- children: [
11842
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnhancedImage, {
11843
- className: "w-28 h-28 rounded-full select-text",
11844
- src: `https://images.weserv.nl/?url=${encodeURIComponent(props.upInfo.avatar)}`,
11845
- alt: props.upInfo.uname
11846
- }),
11847
- props.upInfo.avatar_subscript_url && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnhancedImage, {
11848
- className: "absolute -right-1 -bottom-1 w-8 h-8 select-text",
11849
- src: props.upInfo.avatar_subscript_url,
11850
- alt: "头像角标"
11851
- }),
11852
- props.upInfo.pendant?.image && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnhancedImage, {
11853
- className: "absolute inset-0 w-full h-full transform select-text scale-160",
11854
- src: props.upInfo.pendant.image,
11855
- alt: props.upInfo.pendant.name || "挂件"
11856
- })
11857
- ]
11858
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11859
- className: "flex flex-col gap-4",
11860
- children: [
11861
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11862
- className: "flex gap-3 items-center",
11970
+ /** 演职员表:后端给的是换行分隔的「角色:声优」「职务:人名」「其他配音演员:a、b、c」 */
11971
+ var STAFF_ROLE = /导演|监督|统筹|监制|制片|出品|编剧|原作|原著|音乐|作曲|美术|剪辑|分镜|作画|动画|总监|策划|配音指导/;
11972
+ var EXTRA_CAST_ROLE = /^(其他)?(配音)?演员$|^其他配音$|^其他声优$/;
11973
+ var parseCredits = (raw) => {
11974
+ const cast = [];
11975
+ const staff = [];
11976
+ const extras = [];
11977
+ for (const line of (raw || "").split(/\r?\n/)) {
11978
+ const text = line.trim();
11979
+ if (!text) continue;
11980
+ const index = text.search(/[::]/);
11981
+ if (index < 0) {
11982
+ extras.push(...text.split(/[、,,]/).map((name) => name.trim()).filter(Boolean));
11983
+ continue;
11984
+ }
11985
+ const role = text.slice(0, index).replace(/\s+/g, "");
11986
+ const value = text.slice(index + 1).trim();
11987
+ if (!role || !value) continue;
11988
+ if (EXTRA_CAST_ROLE.test(role)) extras.push(...value.split(/[、,,]/).map((name) => name.trim()).filter(Boolean));
11989
+ else if (STAFF_ROLE.test(role)) staff.push({
11990
+ role,
11991
+ name: value
11992
+ });
11993
+ else cast.push({
11994
+ role,
11995
+ actor: value
11996
+ });
11997
+ }
11998
+ return {
11999
+ cast,
12000
+ staff,
12001
+ extras,
12002
+ voiceCount: (/* @__PURE__ */ new Set([...cast.map((item) => item.actor), ...extras])).size
12003
+ };
12004
+ };
12005
+ /** 版权番剧没有 up_info 时,从制作人员里取制作公司:优先「动画制作」,其次「制作/製作」 */
12006
+ var parseMaker = (raw) => {
12007
+ let fallback = "";
12008
+ for (const line of (raw || "").split(/\r?\n/)) {
12009
+ const match = /^\s*(动画制作公司|动画制作|制作公司|制作|製作)[::]\s*(.+?)\s*$/.exec(line);
12010
+ if (!match) continue;
12011
+ if (/动画制作/.test(match[1])) return match[2];
12012
+ if (!fallback) fallback = match[2];
12013
+ }
12014
+ return fallback;
12015
+ };
12016
+ /** 发布日期统一写成 2026.10.24,等宽数字列对齐,跨年也不歧义 */
12017
+ var formatPubDate = (seconds) => seconds > 0 ? format(/* @__PURE__ */ new Date(seconds * 1e3), "yyyy.MM.dd") : "";
12018
+ /** 按展示顺序(最新在前)写区间;只有一集时写「第 a 集」 */
12019
+ var formatEpisodeRange$1 = (first, last) => first === last ? `第 ${first} 集` : `第 ${first}–${last} 集`;
12020
+ /** 标题按字数分档:短标题撑满版面,长标题收字号换行 */
12021
+ var getTitleClassName$4 = (length) => {
12022
+ if (length <= 6) return "text-[132px] leading-[1.05]";
12023
+ if (length <= 10) return "text-[112px] leading-[1.08]";
12024
+ if (length <= 16) return "text-[96px] leading-[1.1] line-clamp-2";
12025
+ if (length <= 24) return "text-[80px] leading-[1.14] line-clamp-2";
12026
+ return "text-[68px] leading-[1.18] line-clamp-3";
12027
+ };
12028
+ /** 巨型区间数字的字宽系数(字号的倍数,含 -0.04em 字距),按 tabular 数字和短横线的实测宽度取上限 */
12029
+ var RANGE_NUMERAL_ADVANCE$1 = .56;
12030
+ /** 目录页巨型区间数字的字号:按左栏内容宽度和字符数反推,最大 184px,4 位集号的长区间也不会溢出 */
12031
+ var getRangeNumeralSize$1 = (length, compact) => Math.min(184, Math.floor((compact ? 448 : 520) / (Math.max(length, 1) * RANGE_NUMERAL_ADVANCE$1)));
12032
+ /** 背景:封面弥散 + 离散噪点,与兄弟模板同一套图层 */
12033
+ var PosterBackground$1 = ({ src, ctx }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12034
+ className: "pointer-events-none absolute inset-0 overflow-hidden select-none",
12035
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AmbientCover, {
12036
+ src,
12037
+ ctx
12038
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12039
+ className: "absolute inset-0 opacity-[0.35] mix-blend-overlay dark:mix-blend-soft-light",
12040
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
12041
+ className: "h-full w-full",
12042
+ xmlns: "http://www.w3.org/2000/svg",
12043
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("filter", {
12044
+ id: "bangumiNoise",
12045
+ children: [
12046
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feTurbulence", {
12047
+ type: "fractalNoise",
12048
+ baseFrequency: "0.8",
12049
+ numOctaves: "2",
12050
+ stitchTiles: "stitch"
12051
+ }),
12052
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feColorMatrix", {
12053
+ type: "saturate",
12054
+ values: "0"
12055
+ }),
12056
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("feComponentTransfer", { children: [
12057
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncR", {
12058
+ type: "discrete",
12059
+ tableValues: "0 1"
12060
+ }),
12061
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncG", {
12062
+ type: "discrete",
12063
+ tableValues: "0 1"
12064
+ }),
12065
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncB", {
12066
+ type: "discrete",
12067
+ tableValues: "0 1"
12068
+ })
12069
+ ] }),
12070
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feComponentTransfer", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncA", {
12071
+ type: "linear",
12072
+ slope: "2",
12073
+ intercept: "-0.5"
12074
+ }) })
12075
+ ]
12076
+ }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", {
12077
+ width: "100%",
12078
+ height: "100%",
12079
+ filter: "url(#bangumiNoise)",
12080
+ fill: "white"
12081
+ })]
12082
+ })
12083
+ })]
12084
+ });
12085
+ /** 刊头:栏目名 + 平台 logo,靠字距和留白收束 */
12086
+ var Masthead$1 = ({ label, ctx }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
12087
+ className: "flex items-center justify-between pb-7",
12088
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12089
+ className: "flex items-center gap-5 text-[26px] font-bold tracking-[0.32em] text-foreground",
12090
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `h-5 w-5 ${ACCENT_BG}` }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: label })]
12091
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
12092
+ src: isDark(ctx) ? "/image/bilibili/bilibili-light.png" : "/image/bilibili/bilibili.png",
12093
+ alt: "哔哩哔哩",
12094
+ className: "h-14 w-auto"
12095
+ })]
12096
+ });
12097
+ /** 眉题:强调色小号粗体,用细点分隔 */
12098
+ var Kicker$1 = ({ items }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12099
+ className: `flex flex-wrap items-center gap-x-4 text-[30px] font-bold ${ACCENT_TEXT}`,
12100
+ children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [index > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12101
+ className: "text-muted",
12102
+ children: "·"
12103
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: item })] }, `${index}-${item}`))
12104
+ });
12105
+ /** 数据栏:四格铺在浅色面板上,用留白代替分隔线,小标签 + 粗体大数字 + 小单位 */
12106
+ var Ledger$1 = ({ items }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12107
+ className: "grid grid-cols-4 gap-x-10 rounded-3xl bg-foreground/5 px-10 py-8",
12108
+ children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12109
+ className: "text-[24px] font-medium text-foreground/65",
12110
+ children: item.label
12111
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12112
+ className: "mt-1 flex items-baseline gap-1.5 text-foreground",
12113
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12114
+ className: "text-[60px] leading-none font-black tabular-nums",
12115
+ children: item.value
12116
+ }), item.unit && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12117
+ className: "text-[26px] font-bold",
12118
+ children: item.unit
12119
+ })]
12120
+ })] }, item.label))
12121
+ });
12122
+ /** 演职员栏的一行:左侧小标签,右侧内容 */
12123
+ var CreditRow = ({ label, children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12124
+ className: "flex gap-6",
12125
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
12126
+ className: "w-16 shrink-0 pt-0.5 text-[24px] font-bold text-foreground/65",
12127
+ children: label
12128
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
12129
+ className: "min-w-0 flex-1 text-[26px] leading-[1.55] text-foreground",
12130
+ children
12131
+ })]
12132
+ });
12133
+ /** 专题 hero:左封面,右眉题 / 大标题 / 类型 / 简介 / 演职员栏 */
12134
+ var Hero$1 = ({ data }) => {
12135
+ const credits = parseCredits(data.Actors);
12136
+ const shownCast = credits.cast.slice(0, CAST_LIMIT);
12137
+ const shownExtras = shownCast.length > 0 ? [] : credits.extras.slice(0, CAST_LIMIT);
12138
+ const shownCount = shownCast.length + shownExtras.length;
12139
+ const statusParts = (data.newEP?.desc || "").split(/[,,]/).map((part) => part.trim()).filter(Boolean);
12140
+ const copyright = COPYRIGHT_LABEL[data.Copyright];
12141
+ const kicker = [
12142
+ "番剧",
12143
+ ...copyright ? [copyright] : [],
12144
+ ...statusParts
12145
+ ];
12146
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12147
+ className: "flex items-stretch gap-16",
12148
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12149
+ className: "relative min-h-146.75 w-110 shrink-0 overflow-hidden rounded-8xl bg-surface-secondary shadow-2xl shadow-black/20",
12150
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12151
+ className: "absolute inset-0",
12152
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnhancedImage, {
12153
+ src: data.mainCover,
12154
+ alt: data.Title,
12155
+ className: "h-full w-full object-cover"
12156
+ })
12157
+ })
12158
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12159
+ className: "flex min-w-0 flex-1 flex-col",
12160
+ children: [
12161
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kicker$1, { items: kicker }),
12162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
12163
+ className: `mt-5 font-black tracking-tight text-balance text-foreground ${getTitleClassName$4(data.Title.length)}`,
12164
+ children: data.Title
12165
+ }),
12166
+ data.Styles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12167
+ className: "mt-3 text-[30px] font-medium text-foreground/70",
12168
+ children: data.Styles.join(" / ")
12169
+ }),
12170
+ data.Evaluate && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
12171
+ className: "mt-5 line-clamp-3 text-[30px] leading-[1.55] font-light text-foreground/80",
12172
+ children: data.Evaluate
12173
+ }),
12174
+ (shownCount > 0 || credits.staff.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12175
+ className: "mt-auto pt-8",
12176
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", {
12177
+ className: "flex flex-col gap-2.5 pt-6",
12178
+ children: [shownCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CreditRow, {
12179
+ label: "声优",
12180
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
12181
+ className: "flex flex-wrap gap-x-7",
11863
12182
  children: [
11864
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
11865
- className: "text-4xl font-medium select-text",
11866
- style: { color: props.upInfo.vip_status === 1 ? props.upInfo.nickname_color || "#FB7299" : "#EDEDED" },
11867
- children: props.upInfo.uname
11868
- }),
11869
- props.upInfo.verify_type > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
11870
- className: "flex items-center",
11871
- children: props.upInfo.verify_type === 1 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shield, {
11872
- width: 20,
11873
- className: "text-warning"
11874
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Crown, {
11875
- width: 20,
11876
- className: "text-accent"
12183
+ shownCast.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
12184
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12185
+ className: "text-foreground/60",
12186
+ children: item.role
12187
+ }),
12188
+ " ",
12189
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12190
+ className: "font-bold",
12191
+ children: item.actor
11877
12192
  })
12193
+ ] }, `${item.role}-${item.actor}`)),
12194
+ shownExtras.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12195
+ className: "font-bold",
12196
+ children: shownExtras.join("、")
11878
12197
  }),
11879
- props.upInfo.vip_status === 1 && props.upInfo.vip_label?.text && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Chip, {
11880
- size: "sm",
11881
- className: "px-2 py-1 text-xs select-text",
11882
- style: {
11883
- backgroundColor: props.upInfo.vip_label.bg_color || "#FB7299",
11884
- color: props.upInfo.vip_label.text_color || "#FFFFFF",
11885
- borderColor: props.upInfo.vip_label.border_color || "transparent"
11886
- },
11887
- children: props.upInfo.vip_label.text
12198
+ credits.voiceCount > shownCount && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
12199
+ className: "text-foreground/60",
12200
+ children: [
12201
+ "等 ",
12202
+ credits.voiceCount,
12203
+ " 人"
12204
+ ]
11888
12205
  })
11889
12206
  ]
11890
- }),
11891
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11892
- className: "flex gap-6 items-center text-3xl select-text text-foreground",
11893
- children: [
11894
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Users, { size: 30 }),
11895
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [formatNumber$2(props.upInfo.follower), "粉丝"] }),
11896
- props.upInfo.is_follow === 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Chip, {
11897
- size: "sm",
11898
- color: "accent",
11899
- variant: "soft",
11900
- className: "text-xs select-text",
11901
- children: "已关注"
12207
+ })
12208
+ }), credits.staff.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CreditRow, {
12209
+ label: "制作",
12210
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12211
+ className: "flex flex-wrap gap-x-7",
12212
+ children: credits.staff.slice(0, 3).map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
12213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12214
+ className: "text-foreground/60",
12215
+ children: item.role
12216
+ }),
12217
+ " ",
12218
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12219
+ className: "font-bold",
12220
+ children: item.name
11902
12221
  })
11903
- ]
11904
- }),
11905
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11906
- className: "flex gap-2 items-center text-2xl select-text text-foreground/70",
11907
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Hash, { size: 20 }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: ["UID: ", props.upInfo.mid] })]
12222
+ ] }, `${item.role}-${item.name}`))
11908
12223
  })
11909
- ]
11910
- })]
11911
- })]
11912
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11913
- className: "flex flex-col flex-1 justify-between text-foreground",
11914
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
11915
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
11916
- className: "mb-8 text-8xl font-bold leading-tight select-text",
11917
- children: props.title
11918
- }),
11919
- props.subtitle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
11920
- className: "mb-12 text-4xl select-text text-foreground",
11921
- children: props.subtitle
11922
- }),
11923
- props.styles && props.styles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
11924
- className: "flex flex-wrap gap-8 mb-12",
11925
- children: props.styles.map((style, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Chip, {
11926
- size: "lg",
11927
- className: "w-32 h-12 rounded-xl bg-surface-secondary px-4 py-2 text-3xl text-foreground backdrop-blur-sm select-text",
11928
- children: style
11929
- }, index))
11930
- }),
11931
- actorList.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11932
- className: "mb-12",
11933
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11934
- className: "flex gap-6 items-center mb-6 text-3xl select-text text-foreground",
11935
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Users, { size: 30 }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "声优阵容" })]
11936
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11937
- className: "flex flex-wrap gap-8",
11938
- children: [actorList.slice(0, 6).map((actor, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
11939
- className: "text-3xl select-text text-foreground",
11940
- children: actor
11941
- }, index)), actorList.length > 6 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11942
- className: "text-3xl select-text text-foreground",
11943
- children: [
11944
- "等",
11945
- actorList.length,
11946
- "人"
11947
- ]
11948
- })]
11949
- })]
11950
- }),
11951
- props.evaluate && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11952
- className: "mb-12",
11953
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11954
- className: "flex gap-6 items-center mb-6 text-3xl select-text text-foreground",
11955
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Star, { size: 30 }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "评价" })]
11956
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
11957
- className: "text-3xl leading-relaxed select-text text-foreground",
11958
- children: props.evaluate
11959
12224
  })]
11960
12225
  })
11961
- ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11962
- className: "grid grid-cols-4 gap-16",
12226
+ })
12227
+ ]
12228
+ })]
12229
+ });
12230
+ };
12231
+ /** 可执行命令:统一实心高对比 chip */
12232
+ var CommandChip$1 = ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12233
+ className: "inline-flex h-15 items-center rounded-2xl px-6 text-[30px] font-bold whitespace-nowrap text-white shadow-lg shadow-black/15",
12234
+ style: { backgroundColor: CHIP_BG$1 },
12235
+ children
12236
+ });
12237
+ /** 有效期说明:两种模式共用;主句加粗,补充说明降一档。只有一页时没有翻页,不提重新计时 */
12238
+ var ValidityNote$1 = ({ seconds, pageCount }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12239
+ className: "flex items-start gap-3 text-[26px] leading-snug",
12240
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Timer, {
12241
+ className: "mt-0.5 h-7 w-7 shrink-0 text-foreground/70",
12242
+ strokeWidth: 2.2
12243
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12244
+ className: "font-bold text-foreground",
12245
+ children: [
12246
+ seconds,
12247
+ " 秒内有效",
12248
+ pageCount > 1 && ",翻页重新计时"
12249
+ ]
12250
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12251
+ className: "mt-0.5 text-foreground/65",
12252
+ children: "过期请重新发送原链接"
12253
+ })] })]
12254
+ });
12255
+ /** 文字命令模式:只列出当前页真正能用的命令;后端也认纯数字集号,用本页第一集举例 */
12256
+ var TextGuide$1 = ({ first, page, pageCount, seconds }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12257
+ className: "flex flex-col gap-5",
12258
+ children: [
12259
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12260
+ className: "text-[34px] font-black text-foreground",
12261
+ children: "回复文字选集"
12262
+ }),
12263
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12264
+ className: "flex items-center gap-5",
12265
+ children: [
12266
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12267
+ className: "w-19 shrink-0 text-[28px] font-bold text-foreground",
12268
+ children: "选集"
12269
+ }),
12270
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12271
+ className: "text-[26px] text-foreground/65",
12272
+ children: "发送"
12273
+ }),
12274
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip$1, { children: "第?集" }),
12275
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
12276
+ className: "text-[26px] text-foreground/65",
12277
+ children: ["也可只发集号,如 ", first]
12278
+ })
12279
+ ]
12280
+ }),
12281
+ pageCount > 1 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12282
+ className: "flex items-center gap-5",
12283
+ children: [
12284
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12285
+ className: "w-19 shrink-0 text-[28px] font-bold text-foreground",
12286
+ children: "翻页"
12287
+ }),
12288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12289
+ className: "text-[26px] text-foreground/65",
12290
+ children: "发送"
12291
+ }),
12292
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12293
+ className: "flex flex-wrap gap-3",
11963
12294
  children: [
11964
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11965
- className: "items-center min-w-0",
11966
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11967
- className: "flex items-baseline",
11968
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
11969
- className: "text-4xl font-bold select-text text-foreground",
11970
- children: formatNumber$2(props.stat.views).slice(0, -1)
11971
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
11972
- className: "text-xl font-bold select-text text-foreground",
11973
- children: formatNumber$2(props.stat.views).slice(-1)
11974
- })]
11975
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
11976
- className: "text-2xl whitespace-nowrap select-text",
11977
- children: "播放"
11978
- })]
11979
- }),
11980
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11981
- className: "items-center min-w-0",
11982
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11983
- className: "flex items-baseline",
11984
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
11985
- className: "text-4xl font-bold select-text text-foreground",
11986
- children: formatNumber$2(props.stat.favorites).slice(0, -1)
11987
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
11988
- className: "text-xl font-bold select-text text-foreground",
11989
- children: formatNumber$2(props.stat.favorites).slice(-1)
11990
- })]
11991
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
11992
- className: "text-2xl whitespace-nowrap select-text",
11993
- children: "收藏"
11994
- })]
11995
- }),
11996
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11997
- className: "items-center min-w-0",
11998
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11999
- className: "flex items-baseline",
12000
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12001
- className: "text-4xl font-bold select-text text-foreground",
12002
- children: formatNumber$2(props.stat.danmakus).slice(0, -1)
12003
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12004
- className: "text-xl font-bold select-text text-foreground",
12005
- children: formatNumber$2(props.stat.danmakus).slice(-1)
12006
- })]
12007
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12008
- className: "text-2xl whitespace-nowrap select-text",
12009
- children: "弹幕"
12010
- })]
12011
- }),
12012
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12013
- className: "items-center min-w-0",
12014
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12015
- className: "flex items-baseline",
12016
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12017
- className: "text-4xl font-bold select-text text-foreground",
12018
- children: formatNumber$2(props.stat.coins).slice(0, -1)
12019
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12020
- className: "text-xl font-bold select-text text-foreground",
12021
- children: formatNumber$2(props.stat.coins).slice(-1)
12022
- })]
12023
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12024
- className: "text-2xl whitespace-nowrap select-text",
12025
- children: "投币"
12026
- })]
12027
- })
12295
+ page > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip$1, { children: "上一页" }),
12296
+ page < pageCount && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip$1, { children: "下一页" }),
12297
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip$1, { children: "第?页" })
12028
12298
  ]
12029
- })]
12299
+ })
12300
+ ]
12301
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12302
+ className: "flex items-center gap-5",
12303
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12304
+ className: "w-19 shrink-0 text-[28px] font-bold text-foreground",
12305
+ children: "翻页"
12306
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12307
+ className: "text-[26px] text-foreground/65",
12308
+ children: "只有这一页,无需翻页"
12030
12309
  })]
12310
+ }),
12311
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ValidityNote$1, {
12312
+ seconds,
12313
+ pageCount
12031
12314
  })
12032
- });
12033
- };
12034
- /** 文字选集提示里的命令高亮,和海报的强调色保持一致 */
12035
- var CommandChip$1 = ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12036
- className: "inline-flex items-center rounded-xl bg-danger px-4 py-1.5 font-semibold text-danger-foreground",
12315
+ ]
12316
+ });
12317
+ /** 键盘示意里的一枚按钮:数字键与「跳转」是蓝框键(style 1),翻页是灰框键;small 给 4 位集号用 */
12318
+ var SketchKey$1 = ({ children, quiet, small }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12319
+ className: quiet ? "flex h-11 flex-1 items-center justify-center rounded-lg border-2 border-foreground/20 text-[24px] font-bold text-foreground/60" : `flex h-11 flex-1 items-center justify-center rounded-lg border-2 border-foreground/45 font-bold text-foreground tabular-nums ${small ? "text-[18px]" : "text-[24px]"}`,
12037
12320
  children
12038
12321
  });
12039
- /**
12040
- * B站番剧剧集列表组件。
12041
- * 每页最多 15 集,3 列网格展示封面 + 标题;长番剧不会再生成超长截图。
12042
- * @param props 剧集列表组件属性
12043
- */
12044
- var BangumiBilibiliEpisodes = (props) => {
12045
- const totalEpisodes = props.length > 0 ? props.length : props.Episodes.length;
12046
- const pageSize = Math.max(1, props.posterPageSize ?? DEFAULT_POSTER_PAGE_SIZE$1);
12047
- const pageCount = Math.max(1, Math.ceil(totalEpisodes / pageSize));
12048
- const page = Math.min(Math.max(1, props.posterPage ?? 1), pageCount);
12049
- const start = (page - 1) * pageSize;
12050
- const visibleEpisodes = props.Episodes.slice(start, start + pageSize);
12051
- if (visibleEpisodes.length === 0) return null;
12052
- const episodeNumbers = visibleEpisodes.map((episode, index) => episode.episode ?? start + index + 1);
12053
- const firstEpisode = episodeNumbers[0];
12054
- const lastEpisode = episodeNumbers[episodeNumbers.length - 1];
12055
- const range = firstEpisode === lastEpisode ? `第 ${firstEpisode} 集` : `第 ${firstEpisode}–${lastEpisode} 集`;
12322
+ /** 按钮模式的键盘示意:只画本页真实存在的按钮,排布与 QQBot 键盘一致;宽度固定,4 位集号收小字号,左侧说明不被挤到换行 */
12323
+ var KeySketch$1 = ({ numbers, page, pageCount }) => {
12324
+ const rows = [];
12325
+ for (let index = 0; index < numbers.length; index += KEYS_PER_ROW$1) rows.push(numbers.slice(index, index + KEYS_PER_ROW$1));
12326
+ const navigation = [page > 1 ? "上一页" : "", page < pageCount ? "下一页" : ""].filter(Boolean);
12327
+ const small = numbers.some((number) => number >= 1e3);
12056
12328
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12057
- className: "px-10",
12329
+ className: "flex w-82.5 shrink-0 flex-col gap-1.5 self-start rounded-3xl bg-surface/60 p-3",
12330
+ children: [
12331
+ pageCount > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SketchKey$1, { children: "跳转到第x页" }),
12332
+ rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12333
+ className: "flex gap-1.5",
12334
+ children: row.map((number) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SketchKey$1, {
12335
+ small,
12336
+ children: number
12337
+ }, number))
12338
+ }, row[0])),
12339
+ navigation.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12340
+ className: "flex gap-1.5",
12341
+ children: navigation.map((label) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SketchKey$1, {
12342
+ quiet: true,
12343
+ children: label
12344
+ }, label))
12345
+ })
12346
+ ]
12347
+ });
12348
+ };
12349
+ /** 按钮模式:引导点按钮,不出现任何手打命令 */
12350
+ var ButtonGuide$1 = ({ numbers, page, pageCount, seconds }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12351
+ className: "flex gap-6",
12352
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12353
+ className: "flex min-w-0 flex-1 flex-col gap-4",
12058
12354
  children: [
12059
12355
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12060
- className: "flex flex-wrap gap-6 items-center mb-16 text-5xl font-bold select-text text-foreground",
12061
- children: [
12062
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Play, { size: 46 }),
12063
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "剧集列表" }),
12064
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Chip, {
12065
- size: "lg",
12066
- color: "danger",
12067
- variant: "soft",
12068
- className: "h-18 px-4 py-2 text-4xl select-text",
12069
- children: [
12070
- "共",
12071
- totalEpisodes,
12072
- "集"
12073
- ]
12074
- }),
12075
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12076
- className: "text-3xl font-semibold select-text text-muted",
12077
- children: props.selectionMode === "button" ? range : `第 ${page}/${pageCount} 页 · ${range}`
12078
- })
12079
- ]
12356
+ className: "flex items-center gap-3 text-[34px] font-black text-foreground",
12357
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MousePointerClick, {
12358
+ className: `h-9 w-9 shrink-0 ${ACCENT_TEXT}`,
12359
+ strokeWidth: 2.4
12360
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "点击下方按钮选集" })]
12080
12361
  }),
12081
- props.selectionMode !== "button" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12082
- className: "flex flex-col gap-4 mb-12 text-3xl select-text text-foreground/80",
12083
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12084
- className: "flex flex-wrap gap-3 items-center",
12085
- children: [
12086
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "发送" }),
12087
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip$1, { children: "第?集" }),
12088
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "选集" })
12089
- ]
12090
- }), pageCount > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12091
- className: "flex flex-wrap gap-3 items-center",
12092
- children: [
12093
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "发送" }),
12094
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip$1, { children: "上一页" }),
12095
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12096
- className: "text-foreground/30",
12097
- children: "/"
12098
- }),
12099
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip$1, { children: "下一页" }),
12100
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12101
- className: "text-foreground/30",
12102
- children: "/"
12103
- }),
12104
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip$1, { children: "第?页" }),
12105
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "翻页" })
12106
- ]
12107
- })]
12362
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", {
12363
+ className: "flex flex-col gap-2 text-[26px] leading-snug text-foreground/80",
12364
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "数字键即集号,点一下直接解析" }), pageCount > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "「跳转到第x页」补上页码再发送" })]
12108
12365
  }),
12109
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12110
- className: "grid grid-cols-3 gap-8",
12111
- children: visibleEpisodes.map((episode, index) => {
12112
- const episodeNumber = episodeNumbers[index];
12113
- const { month, day } = formatDateParts(episode.pub_time);
12114
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12115
- className: "flex flex-col overflow-hidden bg-surface rounded-3xl shadow-large",
12116
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12117
- className: "overflow-hidden relative aspect-video",
12118
- children: [
12119
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnhancedImage, {
12120
- src: episode.cover,
12121
- alt: `第${episodeNumber}集 ${episode.long_title}`,
12122
- className: "object-cover w-full h-full select-text"
12123
- }),
12124
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12125
- className: "absolute top-4 left-4 px-3 py-1 text-2xl font-bold text-white rounded-xl bg-black/65",
12126
- children: [
12127
- "第",
12128
- episodeNumber,
12129
- "集"
12130
- ]
12131
- }),
12132
- episode.badge && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Chip, {
12133
- size: "sm",
12134
- className: "absolute top-4 right-4 px-2 py-1 text-lg font-medium select-text",
12135
- style: {
12136
- backgroundColor: episode.badge_info?.bg_color || "#FB7299",
12137
- color: "#FFFFFF"
12138
- },
12139
- children: episode.badge_info?.text || episode.badge
12140
- })
12141
- ]
12142
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12143
- className: "flex flex-col flex-1 gap-3 p-5",
12144
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12145
- className: "text-3xl font-semibold leading-snug select-text text-foreground line-clamp-2",
12146
- children: episode.long_title || `第${episodeNumber}集`
12147
- }), episode.pub_time > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12148
- className: "mt-auto text-2xl select-text text-foreground/60",
12149
- children: [
12150
- month,
12151
- day,
12152
- "日"
12153
- ]
12154
- })]
12155
- })]
12156
- }, `${episodeNumber}-${episode.bvid}`);
12157
- })
12366
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ValidityNote$1, {
12367
+ seconds,
12368
+ pageCount
12158
12369
  })
12159
12370
  ]
12371
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(KeySketch$1, {
12372
+ numbers,
12373
+ page,
12374
+ pageCount
12375
+ })]
12376
+ });
12377
+ /** 目录页开篇:巨型区间数字 + 操作说明 */
12378
+ var Contents$1 = ({ items, page, pageCount, mode, seconds }) => {
12379
+ const first = items[0]?.number ?? 0;
12380
+ const last = items[items.length - 1]?.number ?? 0;
12381
+ const numeral = first === last ? `${first}` : `${first}–${last}`;
12382
+ const compact = mode === "button";
12383
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12384
+ className: "flex items-end justify-between",
12385
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12386
+ className: "flex items-baseline gap-5",
12387
+ children: [
12388
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `h-6 w-6 self-center ${ACCENT_BG}` }),
12389
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
12390
+ className: "text-[72px] leading-none font-black text-foreground",
12391
+ children: "选集"
12392
+ }),
12393
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12394
+ className: "text-[24px] font-bold tracking-[0.3em] text-foreground/55",
12395
+ children: "CONTENTS"
12396
+ })
12397
+ ]
12398
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12399
+ className: "rounded-full bg-foreground/10 px-7 py-2 text-[34px] font-bold text-foreground tabular-nums",
12400
+ children: [
12401
+ "第 ",
12402
+ page,
12403
+ " / ",
12404
+ pageCount,
12405
+ " 页"
12406
+ ]
12407
+ })]
12408
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12409
+ className: "mt-8 flex items-stretch",
12410
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12411
+ className: compact ? "w-120 shrink-0 pr-8" : "w-140 shrink-0 pr-10",
12412
+ children: [
12413
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12414
+ className: "leading-[0.84] font-light tracking-[-0.04em] whitespace-nowrap text-foreground tabular-nums",
12415
+ style: { fontSize: getRangeNumeralSize$1(numeral.length, compact) },
12416
+ children: numeral
12417
+ }),
12418
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12419
+ className: "mt-6 text-[30px] leading-snug font-bold text-foreground",
12420
+ children: formatEpisodeRange$1(first, last)
12421
+ }),
12422
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12423
+ className: "mt-1 flex items-center gap-2.5 text-[28px] font-bold text-foreground",
12424
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowDownWideNarrow, {
12425
+ className: `h-8 w-8 shrink-0 ${ACCENT_TEXT}`,
12426
+ strokeWidth: 2.4
12427
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "最新在前" })]
12428
+ })
12429
+ ]
12430
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12431
+ className: "min-w-0 flex-1 pl-10",
12432
+ children: mode === "button" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonGuide$1, {
12433
+ numbers: items.map((item) => item.number),
12434
+ page,
12435
+ pageCount,
12436
+ seconds
12437
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextGuide$1, {
12438
+ first,
12439
+ page,
12440
+ pageCount,
12441
+ seconds
12442
+ })
12443
+ })]
12444
+ })] });
12445
+ };
12446
+ /** 集号栏:按本页最大位数取宽度,同一页标题左缘对齐;4 位集号降字号,不挤占标题 */
12447
+ var NUMBER_COLUMN_CLASS = [
12448
+ "min-w-14 text-[80px]",
12449
+ "min-w-26 text-[80px]",
12450
+ "min-w-37.5 text-[80px]",
12451
+ "min-w-37 text-[60px]"
12452
+ ];
12453
+ /** 选集单元:封面右上角是 B 站徽标,下方大集号在左,标题两行 + 日期作说明文字 */
12454
+ var BangumiCell = ({ item, dark, numberClassName }) => {
12455
+ const { episode, number } = item;
12456
+ const badge = episode.badge || episode.badge_info?.text || "";
12457
+ const badgeColor = (dark ? episode.badge_info?.bg_color_night || episode.badge_info?.bg_color : episode.badge_info?.bg_color) || BADGE_FALLBACK;
12458
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12459
+ className: "min-w-0",
12460
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12461
+ className: "relative aspect-video overflow-hidden rounded-3xl bg-surface-secondary shadow-lg shadow-black/10",
12462
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnhancedImage, {
12463
+ src: episode.cover,
12464
+ alt: episode.long_title || `第 ${number} 集`,
12465
+ className: "h-full w-full object-cover"
12466
+ }), badge && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12467
+ className: "absolute top-4 right-4 rounded-xl px-3.5 py-1 text-[24px] leading-normal font-bold text-white shadow-md shadow-black/20",
12468
+ style: { backgroundColor: badgeColor },
12469
+ children: badge
12470
+ })]
12471
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12472
+ className: "mt-4 flex gap-4",
12473
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
12474
+ className: `shrink-0 leading-[0.9] font-black tracking-tight text-foreground tabular-nums ${numberClassName}`,
12475
+ children: number
12476
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12477
+ className: "min-w-0 flex-1 pt-0.5",
12478
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12479
+ className: "line-clamp-2 text-[30px] leading-tight font-bold text-pretty text-foreground",
12480
+ children: episode.long_title || `第 ${number} 集`
12481
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12482
+ className: "mt-1 font-mono text-[26px] leading-tight text-foreground/65",
12483
+ children: formatPubDate(episode.pub_time)
12484
+ })]
12485
+ })]
12486
+ })]
12487
+ });
12488
+ };
12489
+ /** 最后一页不满一行时的收尾块:说明最新剧集在哪一页,并给出往回翻的方式 */
12490
+ var EndCard$1 = ({ span, listed, pageCount, mode }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12491
+ className: `${COL_SPAN$1[span] ?? "col-span-1"} flex flex-col justify-center self-stretch rounded-3xl bg-foreground/5 p-9`,
12492
+ children: [
12493
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12494
+ className: "flex items-center gap-4 text-[40px] leading-tight font-black text-foreground",
12495
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `h-5 w-5 shrink-0 ${ACCENT_BG}` }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: pageCount > 1 ? "已是最后一页" : "只有这一页" })]
12496
+ }),
12497
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
12498
+ className: "mt-4 text-[28px] leading-normal text-foreground/75",
12499
+ children: pageCount > 1 ? "最新的剧集在第 1 页" : `共 ${listed} 集`
12500
+ }),
12501
+ pageCount > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12502
+ className: "flex flex-wrap items-center gap-3 pt-6 text-[28px] text-foreground/70",
12503
+ children: mode === "button" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "点键盘「上一页」往回翻" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
12504
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "发送" }),
12505
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip$1, { children: "上一页" }),
12506
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "往回翻" })
12507
+ ] })
12508
+ })
12509
+ ]
12510
+ });
12511
+ /** 选集网格:3 列,标题两行可读;最后一页不满一行时补收尾块 */
12512
+ var EpisodeGrid$1 = ({ items, dark, isLastPage, pageCount, listed, mode }) => {
12513
+ const columns = 3;
12514
+ const remainder = items.length % columns;
12515
+ const fill = isLastPage && remainder > 0 ? columns - remainder : 0;
12516
+ const digits = Math.max(1, ...items.map((item) => `${item.number}`.length));
12517
+ const numberClassName = NUMBER_COLUMN_CLASS[Math.min(digits, 4) - 1];
12518
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12519
+ className: "grid grid-cols-3 gap-x-10 gap-y-11",
12520
+ children: [items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BangumiCell, {
12521
+ item,
12522
+ dark,
12523
+ numberClassName
12524
+ }, `${item.number}-${item.episode.bvid}`)), fill > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EndCard$1, {
12525
+ span: fill,
12526
+ listed,
12527
+ pageCount,
12528
+ mode
12529
+ })]
12530
+ });
12531
+ };
12532
+ /** 版权栏:左侧列出数量、UP 主与 season 号,右侧生成时间 */
12533
+ var Colophon$1 = ({ notes }) => {
12534
+ const [generatedAt] = import_react.useState(() => format(/* @__PURE__ */ new Date(), "yyyy-MM-dd HH:mm:ss"));
12535
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", {
12536
+ className: "flex items-start justify-between gap-10 pt-7 text-[24px] leading-relaxed text-foreground/65",
12537
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12538
+ className: "min-w-0",
12539
+ children: notes.join(" · ")
12540
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12541
+ className: "shrink-0 select-text",
12542
+ children: ["图片生成于: ", generatedAt]
12543
+ })]
12160
12544
  });
12161
12545
  };
12162
12546
  var BangumiBilibili = import_react.memo((props) => {
12163
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultLayout, {
12547
+ const { data, ctx } = props;
12548
+ const dark = isDark(ctx);
12549
+ const total = data.length > 0 ? data.length : data.Episodes.length;
12550
+ const numbered = data.Episodes.map((episode, index) => ({
12551
+ episode,
12552
+ number: episode.episode ?? total - index
12553
+ }));
12554
+ const pageSize = Math.max(1, data.posterPageSize ?? DEFAULT_POSTER_PAGE_SIZE$1);
12555
+ const pageCount = Math.max(1, Math.ceil(numbered.length / pageSize));
12556
+ const page = Math.min(Math.max(1, data.posterPage ?? 1), pageCount);
12557
+ const visible = numbered.slice((page - 1) * pageSize, page * pageSize);
12558
+ const mode = data.selectionMode ?? "text";
12559
+ const seconds = data.timeoutSeconds && data.timeoutSeconds > 0 ? data.timeoutSeconds : DEFAULT_TIMEOUT_SECONDS$1;
12560
+ const follow = parseFollowText(data.Stat.follow_text) ?? {
12561
+ ...splitCount$1(data.Stat.favorites),
12562
+ label: "追番"
12563
+ };
12564
+ const ledger = [
12565
+ {
12566
+ label: "播放",
12567
+ ...splitCount$1(data.Stat.views)
12568
+ },
12569
+ {
12570
+ label: follow.label,
12571
+ value: follow.value,
12572
+ unit: follow.unit
12573
+ },
12574
+ {
12575
+ label: "弹幕",
12576
+ ...splitCount$1(data.Stat.danmakus)
12577
+ },
12578
+ {
12579
+ label: "投币",
12580
+ ...splitCount$1(data.Stat.coins)
12581
+ }
12582
+ ];
12583
+ const maker = data.UPInfo ? "" : parseMaker(data.Staff ?? "");
12584
+ const upNote = data.UPInfo ? `UP 主 ${data.UPInfo.uname} · 粉丝 ${Object.values(splitCount$1(data.UPInfo.follower)).join("")}` : [maker && `制作 ${maker}`, data.Stat.follow_text].filter(Boolean).join(" · ");
12585
+ const notes = [
12586
+ `已列出 ${numbered.length} 集`,
12587
+ ...upNote ? [upNote] : [],
12588
+ ...data.seasonID ? [`ss${data.seasonID}`] : []
12589
+ ];
12590
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DefaultLayout, {
12164
12591
  ...props,
12165
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12166
- className: "p-4",
12167
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(BangumiBilibiliHeader, {
12168
- title: props.data.Title,
12169
- mainCover: props.data.mainCover,
12170
- evaluate: props.data.Evaluate,
12171
- actors: props.data.Actors,
12172
- styles: props.data.Styles,
12173
- subtitle: props.data.subtitle,
12174
- upInfo: props.data.UPInfo,
12175
- stat: props.data.Stat,
12176
- copyright: props.data.Copyright,
12177
- seasonID: props.data.seasonID
12178
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BangumiBilibiliEpisodes, { ...props.data })]
12179
- })
12592
+ className: "relative overflow-hidden",
12593
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PosterBackground$1, {
12594
+ src: data.mainCover,
12595
+ ctx
12596
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
12597
+ className: "relative z-10 px-20 pt-18",
12598
+ children: [
12599
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Masthead$1, {
12600
+ label: "BILIBILI BANGUMI · EPISODE GUIDE",
12601
+ ctx
12602
+ }),
12603
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12604
+ className: "mt-12",
12605
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Hero$1, { data })
12606
+ }),
12607
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12608
+ className: "mt-12",
12609
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Ledger$1, { items: ledger })
12610
+ }),
12611
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12612
+ className: "mt-16",
12613
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Contents$1, {
12614
+ items: visible,
12615
+ page,
12616
+ pageCount,
12617
+ mode,
12618
+ seconds
12619
+ })
12620
+ }),
12621
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12622
+ className: "mt-12",
12623
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EpisodeGrid$1, {
12624
+ items: visible,
12625
+ dark,
12626
+ isLastPage: page === pageCount,
12627
+ pageCount,
12628
+ listed: numbered.length,
12629
+ mode
12630
+ })
12631
+ }),
12632
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
12633
+ className: "mt-14",
12634
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Colophon$1, { notes })
12635
+ })
12636
+ ]
12637
+ })]
12180
12638
  });
12181
12639
  });
12182
12640
  BangumiBilibili.displayName = "BangumiBilibili";
@@ -14634,43 +15092,6 @@ var DYNAMIC_TYPE_FORWARD_default = defineTemplate({
14634
15092
  component: BilibiliForwardDynamic,
14635
15093
  validate: (data) => typeof data === "object" && data !== null && typeof data.share_url === "string"
14636
15094
  });
14637
- /** 主题色压色罩不透明度 (0~1):edge 为顶/底两端,middle 为中间带;调小 middle 封面色更透 */
14638
- var AMBIENT_OVERLAY_OPACITY = {
14639
- edge: .9,
14640
- middle: .2
14641
- };
14642
- /** 用 color-mix 生成三段压色渐变(0% / 50% / 100% 停靠点对应 edge / middle / edge) */
14643
- var overlayGradient = (color, edge, middle) => `linear-gradient(to bottom, color-mix(in oklab, ${color} ${edge * 100}%, transparent), color-mix(in oklab, ${color} ${middle * 100}%, transparent), color-mix(in oklab, ${color} ${edge * 100}%, transparent))`;
14644
- /**
14645
- * 氛围背景基础层:高斯模糊封面 + 主题色压色罩。
14646
- * 压色罩浅色用主题背景色、深色用纯黑(两个层按 dark 变体显隐),
14647
- * 各模板在此之上再叠加自己的杂色纹理层。
14648
- * 贡献度参数优先取 ctx.ambientCover(core 注入的用户配置),缺省用模块内默认值。
14649
- */
14650
- var AmbientCover = import_react.memo(({ src, ctx }) => {
14651
- const coverOpacity = ctx?.ambientCover?.coverOpacity ?? .7;
14652
- const edge = ctx?.ambientCover?.overlayEdgeOpacity ?? AMBIENT_OVERLAY_OPACITY.edge;
14653
- const middle = ctx?.ambientCover?.overlayMiddleOpacity ?? AMBIENT_OVERLAY_OPACITY.middle;
14654
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
14655
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
14656
- src,
14657
- alt: "",
14658
- className: "absolute inset-0 h-full w-full scale-150 object-cover blur-[120px] saturate-[1.8]",
14659
- style: { opacity: coverOpacity },
14660
- referrerPolicy: "no-referrer",
14661
- crossOrigin: "anonymous"
14662
- }),
14663
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14664
- className: "absolute inset-0 dark:hidden",
14665
- style: { background: overlayGradient("var(--background)", edge, middle) }
14666
- }),
14667
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
14668
- className: "absolute inset-0 hidden dark:block",
14669
- style: { background: overlayGradient("#000", edge, middle) }
14670
- })
14671
- ] });
14672
- });
14673
- AmbientCover.displayName = "AmbientCover";
14674
15095
  //#endregion
14675
15096
  //#region ktr/template/bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD/components/DYNAMIC_TYPE_LIVE_RCMD.tsx
14676
15097
  /** B站品牌粉,直播状态徽章 / 脉冲灯 / 均衡器强调条统一使用,与封面取色无关。 */
@@ -18537,22 +18958,41 @@ var live_default = defineTemplate({
18537
18958
  //#region ktr/template/douyin/mix/components/Mix.tsx
18538
18959
  /** 海报每页展示的集数,和 core 的 EPISODE_SELECTION_PAGE_SIZE 保持一致 */
18539
18960
  var DEFAULT_POSTER_PAGE_SIZE = 15;
18540
- /** 万/亿 计数,数值与单位分开返回:数字用大号等宽,单位用小号 */
18961
+ /** 选集有效期兜底(秒),正常由 data.timeoutSeconds 下发 */
18962
+ var DEFAULT_TIMEOUT_SECONDS = 120;
18963
+ /** QQBot 键盘每行的数字按钮数,和 core 的 EPISODE_SELECTION_BUTTONS_PER_ROW 保持一致 */
18964
+ var KEYS_PER_ROW = 5;
18965
+ /** 抖音红:眉题、章节方块等装饰的唯一强调色 */
18966
+ var ACCENT = "#FE2C55";
18967
+ /** 命令 chip 底色:同色相压深一档,白字对比度约 4.8:1 */
18968
+ var CHIP_BG = "#E0144C";
18969
+ /** 网格补位块的跨列类名(Tailwind 只认静态字面量) */
18970
+ var COL_SPAN = {
18971
+ 1: "col-span-1",
18972
+ 2: "col-span-2",
18973
+ 3: "col-span-3",
18974
+ 4: "col-span-4"
18975
+ };
18976
+ /** 万/亿 计数拆成数值与单位:10 亿以下的亿级两位小数,其余一位,去掉末尾 0(596.0万 → 596万);不足 1 万原样返回 */
18541
18977
  var splitCount = (count) => {
18978
+ if (count >= 1e9) return {
18979
+ value: `${Number((count / 1e8).toFixed(1))}`,
18980
+ unit: "亿"
18981
+ };
18542
18982
  if (count >= 1e8) return {
18543
- value: (count / 1e8).toFixed(1),
18983
+ value: `${Number((count / 1e8).toFixed(2))}`,
18544
18984
  unit: "亿"
18545
18985
  };
18546
18986
  if (count >= 1e4) return {
18547
- value: (count / 1e4).toFixed(1),
18987
+ value: `${Number((count / 1e4).toFixed(1))}`,
18548
18988
  unit: "万"
18549
18989
  };
18550
18990
  return {
18551
- value: count.toString(),
18991
+ value: Math.max(0, Math.round(count)).toString(),
18552
18992
  unit: ""
18553
18993
  };
18554
18994
  };
18555
- /** 总时长:不到一小时按分钟,否则按小时保留一位 */
18995
+ /** 总时长:不到一小时按分钟,否则按小时保留一位,去掉末尾 0 */
18556
18996
  var splitRuntime = (milliseconds) => {
18557
18997
  const minutes = Math.round(milliseconds / 6e4);
18558
18998
  if (minutes < 60) return {
@@ -18560,29 +19000,56 @@ var splitRuntime = (milliseconds) => {
18560
19000
  unit: "分钟"
18561
19001
  };
18562
19002
  return {
18563
- value: (minutes / 60).toFixed(1),
19003
+ value: `${Number((minutes / 60).toFixed(1))}`,
18564
19004
  unit: "小时"
18565
19005
  };
18566
19006
  };
18567
- /** 单集时长 mm:ss */
19007
+ /** 单集时长 m:ss,超过一小时 h:mm:ss */
18568
19008
  var formatDuration$4 = (milliseconds) => {
18569
- const totalSeconds = Math.round(milliseconds / 1e3);
18570
- const minutes = Math.floor(totalSeconds / 60);
18571
- const seconds = totalSeconds % 60;
18572
- return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
19009
+ const total = Math.max(0, Math.round(milliseconds / 1e3));
19010
+ const h = Math.floor(total / 3600);
19011
+ const m = Math.floor(total % 3600 / 60);
19012
+ const s = (total % 60).toString().padStart(2, "0");
19013
+ return h > 0 ? `${h}:${m.toString().padStart(2, "0")}:${s}` : `${m}:${s}`;
18573
19014
  };
18574
- /** 标题按字数分档:短标题撑满,长标题收小并最多三行 */
18575
- var getTitleClassName$1 = (titleLength) => {
18576
- if (titleLength <= 8) return "text-[112px] leading-[1.05]";
18577
- if (titleLength <= 14) return "text-[96px] leading-[1.08]";
18578
- if (titleLength <= 22) return "text-[76px] leading-[1.12]";
18579
- return "text-[60px] leading-[1.18]";
18580
- };
18581
- /** 弥散背景:海报模糊铺底 + 单色离散杂色 */
18582
- var MixBackground = ({ data, ctx }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19015
+ /** 缺集说明最多列出的段数,超出折成「等 N 集」 */
19016
+ var MISSING_SEGMENT_LIMIT = 8;
19017
+ /**
19018
+ * 把升序集号写成「17、94、99–230 集」:连续 3 个及以上压成区间,两个相邻的照常列出;
19019
+ * 段数超过上限时只列前几段,后面接「等 N 集」,N 是全部缺集数。
19020
+ */
19021
+ var formatMissing = (numbers, unit) => {
19022
+ const parts = [];
19023
+ let index = 0;
19024
+ while (index < numbers.length) {
19025
+ let end = index;
19026
+ while (end + 1 < numbers.length && numbers[end + 1] === numbers[end] + 1) end++;
19027
+ if (end - index >= 2) parts.push(`${numbers[index]}–${numbers[end]}`);
19028
+ else parts.push(...numbers.slice(index, end + 1).map(String));
19029
+ index = end + 1;
19030
+ }
19031
+ if (parts.length <= MISSING_SEGMENT_LIMIT) return `第 ${parts.join("、")} ${unit}`;
19032
+ return `第 ${parts.slice(0, MISSING_SEGMENT_LIMIT).join("、")} 等 ${numbers.length} ${unit}`;
19033
+ };
19034
+ /** 按展示顺序(最新在前)写区间;只有一集时写「第 a 集」 */
19035
+ var formatEpisodeRange = (first, last) => first === last ? `第 ${first} 集` : `第 ${first}–${last} 集`;
19036
+ /** 标题按字数分档:短标题撑满版面,长标题收字号换行 */
19037
+ var getTitleClassName$1 = (length) => {
19038
+ if (length <= 6) return "text-[132px] leading-[1.05]";
19039
+ if (length <= 10) return "text-[112px] leading-[1.08]";
19040
+ if (length <= 16) return "text-[96px] leading-[1.1] line-clamp-2";
19041
+ if (length <= 24) return "text-[80px] leading-[1.14] line-clamp-2";
19042
+ return "text-[68px] leading-[1.18] line-clamp-3";
19043
+ };
19044
+ /** 巨型区间数字的字宽系数(字号的倍数,含 -0.04em 字距),按 tabular 数字和短横线的实测宽度取上限 */
19045
+ var RANGE_NUMERAL_ADVANCE = .56;
19046
+ /** 目录页巨型区间数字的字号:按左栏内容宽度和字符数反推,最大 184px,4 位集号的长区间也不会溢出 */
19047
+ var getRangeNumeralSize = (length, compact) => Math.min(184, Math.floor((compact ? 448 : 520) / (Math.max(length, 1) * RANGE_NUMERAL_ADVANCE)));
19048
+ /** 背景:封面弥散 + 离散噪点,与兄弟模板同一套图层 */
19049
+ var PosterBackground = ({ src, ctx }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18583
19050
  className: "pointer-events-none absolute inset-0 overflow-hidden select-none",
18584
19051
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AmbientCover, {
18585
- src: data.image_url,
19052
+ src,
18586
19053
  ctx
18587
19054
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18588
19055
  className: "absolute inset-0 opacity-[0.35] mix-blend-overlay dark:mix-blend-soft-light",
@@ -18631,312 +19098,558 @@ var MixBackground = ({ data, ctx }) => /* @__PURE__ */ (0, import_jsx_runtime.js
18631
19098
  })
18632
19099
  })]
18633
19100
  });
18634
- var MixHeader = ({ ctx }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
18635
- className: "flex items-center justify-between",
19101
+ /** 刊头:栏目名 + 平台 logo,靠字距和留白收束 */
19102
+ var Masthead = ({ label, ctx }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
19103
+ className: "flex items-center justify-between pb-7",
18636
19104
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18637
- className: "flex items-center gap-4",
18638
- children: [
18639
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "h-4 w-4 rounded-full bg-foreground/80" }),
18640
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18641
- className: "text-[26px] font-bold uppercase tracking-[0.24em] text-muted",
18642
- children: "Douyin Mix"
18643
- }),
18644
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18645
- className: "text-[26px] font-bold text-muted",
18646
- children: "· 合集选集"
18647
- })
18648
- ]
19105
+ className: "flex items-center gap-5 text-[26px] font-bold tracking-[0.32em] text-foreground",
19106
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19107
+ className: "h-5 w-5",
19108
+ style: { backgroundColor: ACCENT }
19109
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: label })]
18649
19110
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
18650
19111
  src: isDark(ctx) ? "/image/douyin/dylogo-light.svg" : "/image/douyin/dylogo-dark.svg",
18651
19112
  alt: "抖音",
18652
- className: "h-17 w-auto shrink-0 object-contain opacity-90"
19113
+ className: "h-14 w-auto"
18653
19114
  })]
18654
19115
  });
18655
- var MixHero = ({ data }) => {
18656
- const { kind, image_url, mix_name, author, statistics, episodes, total_episode, updated_to_episode } = data;
18657
- const countUnit = kind === "collection" ? "个视频" : "集";
18658
- const runtime = splitRuntime(episodes.reduce((sum, item) => sum + item.duration, 0));
18659
- const stats = [
18660
- {
18661
- icon: a,
18662
- label: "总播放",
18663
- ...splitCount(statistics.play_count)
18664
- },
18665
- {
18666
- icon: DouyinLikeIcon,
18667
- label: "总点赞",
18668
- ...splitCount(statistics.digg_count)
18669
- },
18670
- {
18671
- icon: DouyinFavoriteIcon,
18672
- label: "总收藏",
18673
- ...splitCount(statistics.collect_count)
18674
- },
18675
- {
18676
- icon: c,
18677
- label: "总时长",
18678
- ...runtime
18679
- }
18680
- ];
18681
- const follower = splitCount(author.follower_count);
18682
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
18683
- className: "mt-14 flex gap-14",
19116
+ /** 眉题:强调色小号粗体,用细点分隔 */
19117
+ var Kicker = ({ items }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19118
+ className: "flex flex-wrap items-center gap-x-4 text-[30px] font-bold",
19119
+ style: { color: ACCENT },
19120
+ children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [index > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19121
+ className: "text-muted",
19122
+ children: "·"
19123
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: item })] }, `${index}-${item}`))
19124
+ });
19125
+ /** 数据栏:四格铺在浅色面板上,用留白代替分隔线,小标签 + 粗体大数字 + 小单位 */
19126
+ var Ledger = ({ items }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19127
+ className: "grid grid-cols-4 gap-x-10 rounded-3xl bg-foreground/5 px-10 py-8",
19128
+ children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19129
+ className: "text-[24px] font-medium text-foreground/65",
19130
+ children: item.label
19131
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19132
+ className: "mt-1 flex items-baseline gap-1.5 text-foreground",
19133
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19134
+ className: "text-[60px] leading-none font-black tabular-nums",
19135
+ children: item.value
19136
+ }), item.unit && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19137
+ className: "text-[26px] font-bold",
19138
+ children: item.unit
19139
+ })]
19140
+ })] }, item.label))
19141
+ });
19142
+ /** 署名行:头像 + 作者名 + 认证 + 次要信息 */
19143
+ var Byline = ({ avatar, name, verified, meta }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19144
+ className: "flex items-center gap-5",
19145
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19146
+ className: "h-19 w-19 shrink-0 overflow-hidden rounded-full ring-2 ring-foreground/10",
19147
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnhancedImage, {
19148
+ src: avatar,
19149
+ alt: name,
19150
+ className: "h-full w-full object-cover",
19151
+ isCircular: true
19152
+ })
19153
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19154
+ className: "min-w-0",
18684
19155
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18685
- className: `relative ${kind === "collection" ? "h-120" : "h-180"} w-120 shrink-0`,
18686
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
18687
- src: image_url,
18688
- alt: "",
18689
- "aria-hidden": "true",
18690
- className: "absolute inset-0 h-full w-full scale-[1.06] rounded-[40px] object-cover opacity-55 blur-[36px] saturate-[1.2]",
18691
- referrerPolicy: "no-referrer",
18692
- crossOrigin: "anonymous"
18693
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
18694
- src: image_url,
18695
- alt: "合集海报",
18696
- className: "relative h-full w-full rounded-[40px] object-cover shadow-2xl",
18697
- referrerPolicy: "no-referrer",
18698
- crossOrigin: "anonymous"
19156
+ className: "flex items-center gap-2",
19157
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19158
+ className: "truncate text-[34px] font-bold text-foreground",
19159
+ children: name
19160
+ }), verified && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RiVerifiedBadgeFill, {
19161
+ className: "h-8 w-8 shrink-0",
19162
+ style: { color: ACCENT }
18699
19163
  })]
19164
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19165
+ className: "mt-0.5 text-[24px] text-foreground/65",
19166
+ children: meta
19167
+ })]
19168
+ })]
19169
+ });
19170
+ /** 专题 hero:左封面,右眉题 / 大标题 / 导语 / 署名 */
19171
+ var Hero = ({ data }) => {
19172
+ const isPlaylet = data.kind === "playlet";
19173
+ const total = data.total_episode;
19174
+ const updated = data.updated_to_episode;
19175
+ const kicker = isPlaylet ? ["短剧", ...total > 0 && updated < total ? [`共 ${total} 集`, `更新至第 ${updated} 集`] : total > 0 ? [`全 ${total} 集`, "已完结"] : []] : ["合集", ...total > 0 ? [`共 ${total} 个视频`] : []];
19176
+ const authorMeta = [data.author.short_id ? `抖音号 ${data.author.short_id}` : "", `粉丝 ${Object.values(splitCount(data.author.follower_count)).join("")}`].filter(Boolean).join(" · ");
19177
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19178
+ className: "flex items-stretch gap-16",
19179
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19180
+ className: isPlaylet ? "relative min-h-150 w-100 shrink-0 overflow-hidden rounded-8xl bg-surface-secondary shadow-2xl shadow-black/20" : "relative min-h-100 w-100 shrink-0 overflow-hidden rounded-8xl bg-surface-secondary shadow-2xl shadow-black/20",
19181
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19182
+ className: "absolute inset-0",
19183
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnhancedImage, {
19184
+ src: data.image_url,
19185
+ alt: data.mix_name,
19186
+ className: "h-full w-full object-cover"
19187
+ })
19188
+ })
18700
19189
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18701
- className: "flex min-w-0 flex-1 flex-col justify-between gap-10",
18702
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
19190
+ className: "flex min-w-0 flex-1 flex-col",
19191
+ children: [
19192
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kicker, { items: kicker }),
18703
19193
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
18704
- className: `${getTitleClassName$1(mix_name.length)} line-clamp-3 font-bold text-balance text-foreground select-text`,
18705
- style: {
18706
- wordBreak: "break-word",
18707
- overflowWrap: "break-word"
18708
- },
18709
- children: mix_name
19194
+ className: `mt-5 font-black tracking-tight text-balance text-foreground ${getTitleClassName$1(data.mix_name.length)}`,
19195
+ children: data.mix_name
18710
19196
  }),
18711
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18712
- className: "mt-8 flex flex-wrap items-center gap-4",
18713
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18714
- className: "rounded-full bg-foreground px-7 py-3 text-[30px] font-bold leading-none text-background",
18715
- children: [
18716
- "共 ",
18717
- total_episode,
18718
- " ",
18719
- countUnit
18720
- ]
18721
- }), updated_to_episode < total_episode && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18722
- className: "rounded-full border border-foreground/20 px-7 py-3 text-[30px] font-semibold leading-none text-foreground/80",
18723
- children: [
18724
- "更新至第 ",
18725
- updated_to_episode,
18726
- " 集"
18727
- ]
18728
- })]
19197
+ data.desc && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
19198
+ className: `mt-7 text-[32px] leading-[1.6] font-light text-foreground/80 ${isPlaylet ? "line-clamp-4" : "line-clamp-2"}`,
19199
+ children: data.desc
18729
19200
  }),
18730
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18731
- className: "mt-10 flex min-w-0 items-center gap-5",
18732
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
18733
- src: author.avatar,
18734
- alt: "头像",
18735
- className: "h-20 w-20 shrink-0 rounded-full object-cover shadow-xl",
18736
- referrerPolicy: "no-referrer",
18737
- crossOrigin: "anonymous"
18738
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18739
- className: "min-w-0",
18740
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18741
- className: "flex min-w-0 items-center gap-2",
18742
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18743
- className: "truncate text-[38px] font-bold leading-tight text-foreground select-text",
18744
- children: author.nickname
18745
- }), author.verified && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RiVerifiedBadgeFill, {
18746
- size: 34,
18747
- className: "shrink-0 text-foreground/70"
18748
- })]
18749
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18750
- className: "mt-1 truncate text-[26px] text-muted select-text",
18751
- children: [
18752
- "抖音号 ",
18753
- author.short_id,
18754
- " · 粉丝 ",
18755
- follower.value,
18756
- follower.unit
18757
- ]
18758
- })]
18759
- })]
18760
- })
18761
- ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18762
- className: "grid grid-cols-2 gap-x-10 gap-y-8",
18763
- children: stats.map((stat) => {
18764
- const Icon = stat.icon;
18765
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18766
- className: "min-w-0",
18767
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18768
- className: "flex items-center gap-3 text-[28px] font-semibold text-muted",
18769
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, {
18770
- size: 32,
18771
- weight: "fill",
18772
- className: "text-foreground/80"
18773
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: stat.label })]
18774
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18775
- className: "mt-2 flex items-baseline gap-2 text-foreground select-text",
18776
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18777
- className: "text-[64px] font-semibold leading-none tabular-nums",
18778
- children: stat.value
18779
- }), stat.unit && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18780
- className: "text-[28px] font-bold",
18781
- children: stat.unit
18782
- })]
18783
- })]
18784
- }, stat.label);
19201
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19202
+ className: "mt-auto pt-18",
19203
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Byline, {
19204
+ avatar: data.author.avatar,
19205
+ name: data.author.nickname,
19206
+ verified: data.author.verified,
19207
+ meta: authorMeta
19208
+ })
18785
19209
  })
18786
- })]
18787
- })]
18788
- });
18789
- };
18790
- /** 区块标题:竖条 + 标题 */
18791
- var SectionTitle$1 = ({ children, extra }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18792
- className: "flex items-end justify-between gap-8",
18793
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18794
- className: "flex items-center gap-4",
18795
- 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", {
18796
- className: "text-[44px] font-bold leading-none text-foreground",
18797
- children
18798
- })]
18799
- }), extra]
18800
- });
18801
- var MixSynopsis = ({ data }) => {
18802
- if (!data.desc) return null;
18803
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
18804
- className: "mt-16",
18805
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle$1, { children: "简介" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
18806
- className: "mt-6 line-clamp-5 text-[40px] leading-[1.6] text-foreground/85 select-text",
18807
- style: { overflowWrap: "break-word" },
18808
- children: data.desc
19210
+ ]
18809
19211
  })]
18810
19212
  });
18811
19213
  };
18812
- /** 文字选集提示里的命令高亮,和 B 站番剧模板保持一致 */
19214
+ /** 可执行命令:统一实心高对比 chip */
18813
19215
  var CommandChip = ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18814
- className: "inline-flex items-center rounded-xl bg-danger px-4 py-1.5 font-semibold text-danger-foreground",
19216
+ className: "inline-flex h-15 items-center rounded-2xl px-6 text-[30px] font-bold whitespace-nowrap text-white shadow-lg shadow-black/15",
19217
+ style: { backgroundColor: CHIP_BG },
18815
19218
  children
18816
19219
  });
18817
- var MixEpisodes = ({ data }) => {
18818
- const { kind, episodes, total_episode, updated_to_episode, selectionMode } = data;
18819
- const pageSize = Math.max(1, data.posterPageSize ?? DEFAULT_POSTER_PAGE_SIZE);
18820
- const pageCount = Math.max(1, Math.ceil(episodes.length / pageSize));
18821
- const page = Math.min(Math.max(1, data.posterPage ?? 1), pageCount);
18822
- const start = (page - 1) * pageSize;
18823
- const visibleEpisodes = episodes.slice(start, start + pageSize);
18824
- if (visibleEpisodes.length === 0) return null;
18825
- const firstEpisode = visibleEpisodes[0].episode;
18826
- const lastEpisode = visibleEpisodes[visibleEpisodes.length - 1].episode;
18827
- const range = firstEpisode === lastEpisode ? `第 ${firstEpisode} 集` : `第 ${firstEpisode}–${lastEpisode} 集`;
18828
- const countUnit = kind === "collection" ? "个视频" : "集";
18829
- const maxEpisode = episodes.reduce((max, item) => Math.max(max, item.episode), 0);
18830
- const episodeSet = new Set(episodes.map((item) => item.episode));
18831
- const missingEpisodes = [];
18832
- for (let episode = 1; episode <= Math.max(maxEpisode, updated_to_episode); episode++) if (!episodeSet.has(episode)) missingEpisodes.push(episode);
18833
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
18834
- className: "mt-16",
19220
+ /** 有效期说明:两种模式共用;主句加粗,补充说明降一档。只有一页时没有翻页,不提重新计时 */
19221
+ var ValidityNote = ({ seconds, pageCount }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19222
+ className: "flex items-start gap-3 text-[26px] leading-snug",
19223
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Timer, {
19224
+ className: "mt-0.5 h-7 w-7 shrink-0 text-foreground/70",
19225
+ strokeWidth: 2.2
19226
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19227
+ className: "font-bold text-foreground",
18835
19228
  children: [
18836
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle$1, {
18837
- extra: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18838
- className: "text-[30px] font-semibold text-muted",
18839
- children: selectionMode === "button" ? range : `第 ${page}/${pageCount} 页 · ${range}`
19229
+ seconds,
19230
+ " 秒内有效",
19231
+ pageCount > 1 && ",翻页重新计时"
19232
+ ]
19233
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19234
+ className: "mt-0.5 text-foreground/65",
19235
+ children: "过期请重新发送原链接"
19236
+ })] })]
19237
+ });
19238
+ /** 文字命令模式:只列出当前页真正能用的命令;后端也认纯数字集号,用本页第一集举例 */
19239
+ var TextGuide = ({ first, page, pageCount, seconds }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19240
+ className: "flex flex-col gap-5",
19241
+ children: [
19242
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19243
+ className: "text-[34px] font-black text-foreground",
19244
+ children: "回复文字选集"
19245
+ }),
19246
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19247
+ className: "flex items-center gap-5",
19248
+ children: [
19249
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19250
+ className: "w-19 shrink-0 text-[28px] font-bold text-foreground",
19251
+ children: "选集"
18840
19252
  }),
18841
- children: "选集"
18842
- }),
18843
- selectionMode !== "button" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18844
- className: "flex flex-col gap-4 mt-8 text-[32px] select-text text-foreground/80",
18845
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18846
- className: "flex flex-wrap gap-3 items-center",
18847
- children: [
18848
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "发送" }),
18849
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip, { children: "第?集" }),
18850
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "选集" })
18851
- ]
18852
- }), pageCount > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18853
- className: "flex flex-wrap gap-3 items-center",
19253
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19254
+ className: "text-[26px] text-foreground/65",
19255
+ children: "发送"
19256
+ }),
19257
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip, { children: "第?集" }),
19258
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
19259
+ className: "text-[26px] text-foreground/65",
19260
+ children: ["也可只发集号,如 ", first]
19261
+ })
19262
+ ]
19263
+ }),
19264
+ pageCount > 1 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19265
+ className: "flex items-center gap-5",
19266
+ children: [
19267
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19268
+ className: "w-19 shrink-0 text-[28px] font-bold text-foreground",
19269
+ children: "翻页"
19270
+ }),
19271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19272
+ className: "text-[26px] text-foreground/65",
19273
+ children: "发送"
19274
+ }),
19275
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19276
+ className: "flex flex-wrap gap-3",
18854
19277
  children: [
18855
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "发送" }),
18856
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip, { children: "上一页" }),
18857
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18858
- className: "text-foreground/30",
18859
- children: "/"
18860
- }),
18861
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip, { children: "下一页" }),
18862
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18863
- className: "text-foreground/30",
18864
- children: "/"
18865
- }),
18866
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip, { children: "第?页" }),
18867
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "翻页" })
19278
+ page > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip, { children: "上一页" }),
19279
+ page < pageCount && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip, { children: "下一页" }),
19280
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip, { children: "第?页" })
18868
19281
  ]
18869
- })]
18870
- }),
18871
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18872
- className: "grid grid-cols-3 gap-8 mt-8",
18873
- children: visibleEpisodes.map((episode) => {
18874
- const cover = kind === "collection" ? episode.cover || episode.originCover : episode.originCover || episode.cover;
18875
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18876
- className: "flex flex-col overflow-hidden bg-surface rounded-3xl shadow-large",
18877
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18878
- className: `relative overflow-hidden ${kind === "collection" ? "aspect-square" : "aspect-[3/4]"}`,
18879
- children: [
18880
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnhancedImage, {
18881
- src: cover || "",
18882
- alt: `第${episode.episode}集 ${episode.title}`,
18883
- className: "object-cover w-full h-full select-text"
18884
- }),
18885
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18886
- className: "absolute top-4 left-4 px-3 py-1 text-2xl font-bold text-white rounded-xl bg-black/65",
18887
- children: [
18888
- "第",
18889
- episode.episode,
18890
- "集"
18891
- ]
18892
- }),
18893
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18894
- className: "absolute right-4 bottom-4 px-3 py-1 text-xl font-semibold text-white rounded-xl bg-black/65 tabular-nums",
18895
- children: formatDuration$4(episode.duration)
18896
- })
18897
- ]
18898
- }), kind === "collection" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18899
- className: "flex flex-col flex-1 gap-3 p-5",
18900
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18901
- className: "text-3xl font-semibold leading-snug select-text text-foreground line-clamp-2",
18902
- children: episode.title || `第${episode.episode}集`
18903
- })
18904
- })]
18905
- }, `${episode.episode}-${episode.aweme_id}`);
18906
19282
  })
18907
- }),
18908
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18909
- className: "flex flex-wrap items-center justify-between gap-6 mt-8 text-[26px] text-muted",
18910
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
18911
- "已列出 ",
18912
- episodes.length,
18913
- " / ",
18914
- total_episode,
18915
- " ",
18916
- countUnit,
18917
- missingEpisodes.length > 0 && `;接口未返回第 ${missingEpisodes.join("、")} ${countUnit},暂不能解析。`,
18918
- updated_to_episode < total_episode && `;已更新至第 ${updated_to_episode} ${countUnit}。`
18919
- ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
18920
- className: "select-text",
18921
- children: ["图片生成于: ", format(/* @__PURE__ */ new Date(), "yyyy-MM-dd HH:mm:ss")]
18922
- })]
19283
+ ]
19284
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19285
+ className: "flex items-center gap-5",
19286
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19287
+ className: "w-19 shrink-0 text-[28px] font-bold text-foreground",
19288
+ children: "翻页"
19289
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19290
+ className: "text-[26px] text-foreground/65",
19291
+ children: "只有这一页,无需翻页"
19292
+ })]
19293
+ }),
19294
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ValidityNote, {
19295
+ seconds,
19296
+ pageCount
19297
+ })
19298
+ ]
19299
+ });
19300
+ /** 键盘示意里的一枚按钮:数字键与「跳转」是蓝框键(style 1),翻页是灰框键;small 给 4 位集号用 */
19301
+ var SketchKey = ({ children, quiet, small }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19302
+ className: quiet ? "flex h-11 flex-1 items-center justify-center rounded-lg border-2 border-foreground/20 text-[24px] font-bold text-foreground/60" : `flex h-11 flex-1 items-center justify-center rounded-lg border-2 border-foreground/45 font-bold text-foreground tabular-nums ${small ? "text-[18px]" : "text-[24px]"}`,
19303
+ children
19304
+ });
19305
+ /** 按钮模式的键盘示意:只画本页真实存在的按钮,排布与 QQBot 键盘一致;宽度固定,4 位集号收小字号,左侧说明不被挤到换行 */
19306
+ var KeySketch = ({ episodes, page, pageCount }) => {
19307
+ const rows = [];
19308
+ for (let index = 0; index < episodes.length; index += KEYS_PER_ROW) rows.push(episodes.slice(index, index + KEYS_PER_ROW));
19309
+ const navigation = [page > 1 ? "上一页" : "", page < pageCount ? "下一页" : ""].filter(Boolean);
19310
+ const small = episodes.some((episode) => episode >= 1e3);
19311
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19312
+ className: "flex w-82.5 shrink-0 flex-col gap-1.5 self-start rounded-3xl bg-surface/60 p-3",
19313
+ children: [
19314
+ pageCount > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SketchKey, { children: "跳转到第x页" }),
19315
+ rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19316
+ className: "flex gap-1.5",
19317
+ children: row.map((episode) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SketchKey, {
19318
+ small,
19319
+ children: episode
19320
+ }, episode))
19321
+ }, row[0])),
19322
+ navigation.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19323
+ className: "flex gap-1.5",
19324
+ children: navigation.map((label) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SketchKey, {
19325
+ quiet: true,
19326
+ children: label
19327
+ }, label))
18923
19328
  })
18924
19329
  ]
18925
19330
  });
18926
19331
  };
18927
- var DouyinMix = import_react.memo((props) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DefaultLayout, {
18928
- ...props,
18929
- className: "relative overflow-hidden",
18930
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MixBackground, { ...props }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
18931
- className: "relative z-10 px-20 pt-18",
19332
+ /** 按钮模式:引导点按钮,不出现任何手打命令 */
19333
+ var ButtonGuide = ({ episodes, page, pageCount, seconds }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19334
+ className: "flex gap-6",
19335
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19336
+ className: "flex min-w-0 flex-1 flex-col gap-4",
18932
19337
  children: [
18933
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MixHeader, { ...props }),
18934
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MixHero, { ...props }),
18935
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MixSynopsis, { ...props }),
18936
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MixEpisodes, { ...props })
19338
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19339
+ className: "flex items-center gap-3 text-[34px] font-black text-foreground",
19340
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MousePointerClick, {
19341
+ className: "h-9 w-9 shrink-0",
19342
+ strokeWidth: 2.4,
19343
+ style: { color: ACCENT }
19344
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "点击下方按钮选集" })]
19345
+ }),
19346
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", {
19347
+ className: "flex flex-col gap-2 text-[26px] leading-snug text-foreground/80",
19348
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "数字键即集号,点一下直接解析" }), pageCount > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "「跳转到第x页」补上页码再发送" })]
19349
+ }),
19350
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ValidityNote, {
19351
+ seconds,
19352
+ pageCount
19353
+ })
18937
19354
  ]
19355
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(KeySketch, {
19356
+ episodes,
19357
+ page,
19358
+ pageCount
18938
19359
  })]
18939
- }));
19360
+ });
19361
+ /** 目录页开篇:巨型区间数字 + 操作说明 */
19362
+ var Contents = ({ episodes, missingNote, page, pageCount, mode, seconds }) => {
19363
+ const first = episodes[0]?.episode ?? 0;
19364
+ const last = episodes[episodes.length - 1]?.episode ?? 0;
19365
+ const numeral = first === last ? `${first}` : `${first}–${last}`;
19366
+ const compact = mode === "button";
19367
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19368
+ className: "flex items-end justify-between",
19369
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19370
+ className: "flex items-baseline gap-5",
19371
+ children: [
19372
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19373
+ className: "h-6 w-6 self-center",
19374
+ style: { backgroundColor: ACCENT }
19375
+ }),
19376
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
19377
+ className: "text-[72px] leading-none font-black text-foreground",
19378
+ children: "选集"
19379
+ }),
19380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19381
+ className: "text-[24px] font-bold tracking-[0.3em] text-foreground/55",
19382
+ children: "CONTENTS"
19383
+ })
19384
+ ]
19385
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19386
+ className: "rounded-full bg-foreground/10 px-7 py-2 text-[34px] font-bold text-foreground tabular-nums",
19387
+ children: [
19388
+ "第 ",
19389
+ page,
19390
+ " / ",
19391
+ pageCount,
19392
+ " 页"
19393
+ ]
19394
+ })]
19395
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19396
+ className: "mt-8 flex items-stretch",
19397
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19398
+ className: compact ? "w-120 shrink-0 pr-8" : "w-140 shrink-0 pr-10",
19399
+ children: [
19400
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19401
+ className: "leading-[0.84] font-light tracking-[-0.04em] whitespace-nowrap text-foreground tabular-nums",
19402
+ style: { fontSize: getRangeNumeralSize(numeral.length, compact) },
19403
+ children: numeral
19404
+ }),
19405
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19406
+ className: "mt-6 text-[30px] leading-snug font-bold text-foreground",
19407
+ children: formatEpisodeRange(first, last)
19408
+ }),
19409
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19410
+ className: "mt-1 flex items-center gap-2.5 text-[28px] font-bold text-foreground",
19411
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowDownWideNarrow, {
19412
+ className: "h-8 w-8 shrink-0",
19413
+ strokeWidth: 2.4,
19414
+ style: { color: ACCENT }
19415
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "最新在前" })]
19416
+ }),
19417
+ missingNote && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19418
+ className: "mt-2 text-[26px] leading-snug text-foreground/65",
19419
+ children: missingNote
19420
+ })
19421
+ ]
19422
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19423
+ className: "min-w-0 flex-1 pl-10",
19424
+ children: mode === "button" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonGuide, {
19425
+ episodes: episodes.map((episode) => episode.episode),
19426
+ page,
19427
+ pageCount,
19428
+ seconds
19429
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextGuide, {
19430
+ first,
19431
+ page,
19432
+ pageCount,
19433
+ seconds
19434
+ })
19435
+ })]
19436
+ })] });
19437
+ };
19438
+ /** 短剧单元:每集文案都是同一段简介,只留集号 + 时长,像目录条目一样左右分列 */
19439
+ var PlayletCell = ({ episode, numberClassName }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19440
+ className: "min-w-0",
19441
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19442
+ className: "aspect-3/4 overflow-hidden rounded-3xl bg-surface-secondary shadow-lg shadow-black/10",
19443
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnhancedImage, {
19444
+ src: episode.originCover || episode.cover || "",
19445
+ alt: `第 ${episode.episode} 集`,
19446
+ className: "h-full w-full object-cover object-[50%_30%]"
19447
+ })
19448
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19449
+ className: "mt-4 flex items-baseline gap-3",
19450
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19451
+ className: `leading-none font-black tracking-tight text-foreground tabular-nums ${numberClassName}`,
19452
+ children: episode.episode
19453
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19454
+ className: "font-mono text-[26px] text-foreground/65",
19455
+ children: formatDuration$4(episode.duration)
19456
+ })]
19457
+ })]
19458
+ });
19459
+ /** 短剧集号字号:5 列单元只有约 230px 宽,4 位集号降一档,给时长留位置;min-w 让同页时长列对齐 */
19460
+ var PLAYLET_NUMBER_CLASS = [
19461
+ "min-w-14 text-[72px]",
19462
+ "min-w-24 text-[72px]",
19463
+ "min-w-32 text-[72px]",
19464
+ "min-w-34 text-[56px]"
19465
+ ];
19466
+ /** 合集集号栏:按本页最大位数取宽度,同一页标题左缘对齐;4 位集号降字号,不挤占标题 */
19467
+ var COLLECTION_NUMBER_CLASS = [
19468
+ "min-w-14 text-[80px]",
19469
+ "min-w-26 text-[80px]",
19470
+ "min-w-37.5 text-[80px]",
19471
+ "min-w-37 text-[60px]"
19472
+ ];
19473
+ /** 普通合集单元:时长压在封面右下角,标题让出整列宽度显示三行 */
19474
+ var CollectionCell = ({ episode, numberClassName }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19475
+ className: "min-w-0",
19476
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19477
+ className: "relative aspect-video overflow-hidden rounded-3xl bg-surface-secondary shadow-lg shadow-black/10",
19478
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnhancedImage, {
19479
+ src: episode.originCover || episode.cover || "",
19480
+ alt: episode.title,
19481
+ className: "h-full w-full object-cover"
19482
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19483
+ className: "absolute right-3 bottom-3 rounded-lg bg-black/60 px-2.5 py-0.5 font-mono text-[24px] text-white",
19484
+ children: formatDuration$4(episode.duration)
19485
+ })]
19486
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19487
+ className: "mt-4 flex gap-4",
19488
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19489
+ className: `shrink-0 leading-[0.9] font-black tracking-tight text-foreground tabular-nums ${numberClassName}`,
19490
+ children: episode.episode
19491
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19492
+ className: "line-clamp-3 min-w-0 flex-1 pt-0.5 text-[30px] leading-tight font-bold text-pretty text-foreground",
19493
+ children: episode.title || "未命名视频"
19494
+ })]
19495
+ })]
19496
+ });
19497
+ /** 最后一页不满一行时的收尾块:说明最新内容在哪一页,并给出往回翻的方式 */
19498
+ var EndCard = ({ span, noun, countText, pageCount, mode }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19499
+ className: `${COL_SPAN[span] ?? "col-span-1"} flex flex-col justify-center self-stretch rounded-3xl bg-foreground/5 p-9`,
19500
+ children: [
19501
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19502
+ className: "flex items-center gap-4 text-[40px] leading-tight font-black text-foreground",
19503
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
19504
+ className: "h-5 w-5 shrink-0",
19505
+ style: { backgroundColor: ACCENT }
19506
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: pageCount > 1 ? "已是最后一页" : "只有这一页" })]
19507
+ }),
19508
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
19509
+ className: "mt-4 text-[28px] leading-normal text-foreground/75",
19510
+ children: pageCount > 1 ? `最新的${noun}在第 1 页` : countText
19511
+ }),
19512
+ pageCount > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19513
+ className: "flex flex-wrap items-center gap-3 pt-6 text-[28px] text-foreground/70",
19514
+ children: mode === "button" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "点键盘「上一页」往回翻" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
19515
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "发送" }),
19516
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandChip, { children: "上一页" }),
19517
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "往回翻" })
19518
+ ] })
19519
+ })
19520
+ ]
19521
+ });
19522
+ /** 选集网格:短剧 5 列对应键盘每行 5 个按钮;普通合集 3 列,保证标题三行可读 */
19523
+ var EpisodeGrid = ({ episodes, isPlaylet, isLastPage, pageCount, listed, mode }) => {
19524
+ const columns = isPlaylet ? 5 : 3;
19525
+ const remainder = episodes.length % columns;
19526
+ const fill = isLastPage && remainder > 0 ? columns - remainder : 0;
19527
+ const showEndCard = fill > 1 || fill === 1 && !isPlaylet;
19528
+ const digits = Math.min(Math.max(1, ...episodes.map((episode) => `${episode.episode}`.length)), 4);
19529
+ const numberClassName = isPlaylet ? PLAYLET_NUMBER_CLASS[digits - 1] : COLLECTION_NUMBER_CLASS[digits - 1];
19530
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19531
+ className: isPlaylet ? "grid grid-cols-5 gap-x-7 gap-y-10" : "grid grid-cols-3 gap-x-10 gap-y-11",
19532
+ children: [episodes.map((episode) => isPlaylet ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlayletCell, {
19533
+ episode,
19534
+ numberClassName
19535
+ }, episode.aweme_id) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollectionCell, {
19536
+ episode,
19537
+ numberClassName
19538
+ }, episode.aweme_id)), showEndCard && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EndCard, {
19539
+ span: fill,
19540
+ noun: isPlaylet ? "剧集" : "视频",
19541
+ countText: isPlaylet ? `共 ${listed} 集` : `共 ${listed} 个视频`,
19542
+ pageCount,
19543
+ mode
19544
+ })]
19545
+ });
19546
+ };
19547
+ /** 版权栏:左侧列出数量与缺集说明,右侧生成时间 */
19548
+ var Colophon = ({ notes }) => {
19549
+ const [generatedAt] = import_react.useState(() => format(/* @__PURE__ */ new Date(), "yyyy-MM-dd HH:mm:ss"));
19550
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", {
19551
+ className: "flex items-start justify-between gap-10 pt-7 text-[24px] leading-relaxed text-foreground/65",
19552
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19553
+ className: "min-w-0",
19554
+ children: notes.join(" · ")
19555
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19556
+ className: "shrink-0 select-text",
19557
+ children: ["图片生成于: ", generatedAt]
19558
+ })]
19559
+ });
19560
+ };
19561
+ var DouyinMix = import_react.memo((props) => {
19562
+ const { data, ctx } = props;
19563
+ const isPlaylet = data.kind === "playlet";
19564
+ const pageSize = Math.max(1, data.posterPageSize ?? DEFAULT_POSTER_PAGE_SIZE);
19565
+ const pageCount = Math.max(1, Math.ceil(data.episodes.length / pageSize));
19566
+ const page = Math.min(Math.max(1, data.posterPage ?? 1), pageCount);
19567
+ const visible = data.episodes.slice((page - 1) * pageSize, page * pageSize);
19568
+ const mode = data.selectionMode ?? "text";
19569
+ const seconds = data.timeoutSeconds > 0 ? data.timeoutSeconds : DEFAULT_TIMEOUT_SECONDS;
19570
+ const listedSet = new Set(data.episodes.map((episode) => episode.episode));
19571
+ const maxEpisode = data.episodes.reduce((max, episode) => Math.max(max, episode.episode), 0);
19572
+ const missing = [];
19573
+ for (let episode = 1; episode <= Math.max(maxEpisode, data.updated_to_episode); episode++) if (!listedSet.has(episode)) missing.push(episode);
19574
+ const upper = page > 1 ? data.episodes[(page - 1) * pageSize - 1].episode : Infinity;
19575
+ const lower = page < pageCount ? visible[visible.length - 1].episode : 0;
19576
+ const pageMissing = missing.filter((episode) => episode < upper && episode > lower);
19577
+ const unit = isPlaylet ? "集" : "个视频";
19578
+ const totalDuration = data.episodes.reduce((sum, episode) => sum + episode.duration, 0);
19579
+ const ledger = [
19580
+ {
19581
+ label: "播放",
19582
+ ...splitCount(data.statistics.play_count)
19583
+ },
19584
+ {
19585
+ label: "点赞",
19586
+ ...splitCount(data.statistics.digg_count)
19587
+ },
19588
+ {
19589
+ label: "收藏",
19590
+ ...splitCount(data.statistics.collect_count)
19591
+ },
19592
+ isPlaylet ? {
19593
+ label: "单集均长",
19594
+ value: formatDuration$4(data.episodes.length ? totalDuration / data.episodes.length : 0),
19595
+ unit: ""
19596
+ } : {
19597
+ label: "总时长",
19598
+ ...splitRuntime(totalDuration)
19599
+ }
19600
+ ];
19601
+ const notes = [data.total_episode > 0 ? `已列出 ${data.episodes.length} / ${data.total_episode} ${unit}` : `已列出 ${data.episodes.length} ${unit}`, ...missing.length > 0 ? [`接口未返回${formatMissing(missing, unit)},暂不能选`] : []];
19602
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DefaultLayout, {
19603
+ ...props,
19604
+ className: "relative overflow-hidden",
19605
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PosterBackground, {
19606
+ src: data.image_url,
19607
+ ctx
19608
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
19609
+ className: "relative z-10 px-20 pt-18",
19610
+ children: [
19611
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Masthead, {
19612
+ label: isPlaylet ? "DOUYIN PLAYLET · EPISODE GUIDE" : "DOUYIN COLLECTION · EPISODE GUIDE",
19613
+ ctx
19614
+ }),
19615
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19616
+ className: "mt-12",
19617
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Hero, { data })
19618
+ }),
19619
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19620
+ className: "mt-12",
19621
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Ledger, { items: ledger })
19622
+ }),
19623
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19624
+ className: "mt-16",
19625
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Contents, {
19626
+ episodes: visible,
19627
+ missingNote: pageMissing.length > 0 ? `缺${formatMissing(pageMissing, unit)},暂不能选` : "",
19628
+ page,
19629
+ pageCount,
19630
+ mode,
19631
+ seconds
19632
+ })
19633
+ }),
19634
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19635
+ className: "mt-12",
19636
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EpisodeGrid, {
19637
+ episodes: visible,
19638
+ isPlaylet,
19639
+ isLastPage: page === pageCount,
19640
+ pageCount,
19641
+ listed: data.episodes.length,
19642
+ mode
19643
+ })
19644
+ }),
19645
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19646
+ className: "mt-14",
19647
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Colophon, { notes })
19648
+ })
19649
+ ]
19650
+ })]
19651
+ });
19652
+ });
18940
19653
  DouyinMix.displayName = "DouyinMix";
18941
19654
  //#endregion
18942
19655
  //#region ktr/template/douyin/mix/index.tsx
@@ -30524,31 +31237,16 @@ var wrapWithErrorHandler = (fn, options) => {
30524
31237
  const rawEvent = e;
30525
31238
  const normalizedEvent = await injectBotToEventForPushTask(rawEvent, options.businessName);
30526
31239
  const normalizedNext = next ?? (() => void 0);
30527
- const emojiManager = Boolean(rawEvent) && !isPushTask(rawEvent, options.businessName) ? new EmojiReactionManager(rawEvent) : void 0;
30528
- let processingTimer = null;
30529
- let successTimer = null;
30530
- if (emojiManager) {
30531
- await emojiManager.add("EYES");
30532
- processingTimer = setTimeout(() => {
30533
- emojiManager.add("PROCESSING").catch(() => {});
30534
- }, 1500);
30535
- }
31240
+ const emojiFlow = Boolean(rawEvent) && !isPushTask(rawEvent, options.businessName) ? createEmojiReactionFlow(rawEvent) : void 0;
31241
+ if (emojiFlow) await emojiFlow.begin();
30536
31242
  const runHandler = () => fn(normalizedEvent, normalizedNext);
30537
31243
  const ctx = logger$1.runContext(async () => rawEvent ? runWithVerifyContext(rawEvent, runHandler) : runHandler());
30538
31244
  try {
30539
31245
  const result = await ctx.run();
30540
- if (emojiManager) successTimer = setTimeout(() => {
30541
- emojiManager.replace("PROCESSING", "SUCCESS").catch(() => {});
30542
- }, 1500);
31246
+ await emojiFlow?.succeed();
30543
31247
  return result;
30544
31248
  } catch (error) {
30545
- if (processingTimer) clearTimeout(processingTimer);
30546
- if (successTimer) clearTimeout(successTimer);
30547
- if (emojiManager) {
30548
- const processingEmojiId = emojiManager["getPlatformEmojiId"]("PROCESSING");
30549
- if (emojiManager.has(processingEmojiId)) await emojiManager.remove("PROCESSING");
30550
- await emojiManager.add("ERROR");
30551
- }
31249
+ await emojiFlow?.fail();
30552
31250
  await new Promise((resolve) => setTimeout(resolve, 100));
30553
31251
  if (await handleBusinessError(error, options, parseLogsToStructured(ctx.logs()), normalizedEvent) === "handled") return void 0;
30554
31252
  throw error;
@@ -30669,6 +31367,7 @@ var createEpisodeSelection = (scope, options) => {
30669
31367
  title: options.title,
30670
31368
  platform: options.platform,
30671
31369
  scene: options.scene,
31370
+ ownerId: options.ownerId,
30672
31371
  pageSize: Math.max(1, options.pageSize ?? 15),
30673
31372
  poster: options.poster,
30674
31373
  posterPages: /* @__PURE__ */ new Map([[options.posterPage ?? 1, options.poster]]),
@@ -30699,6 +31398,13 @@ var getEpisodeSelection = (token, scope) => {
30699
31398
  }
30700
31399
  return session;
30701
31400
  };
31401
+ /**
31402
+ * 判断用户是否有权操作这次选集。
31403
+ * 只有发起人本人能翻页/选集;没有记录发起人时退回旧行为(不做限制)。
31404
+ */
31405
+ var isEpisodeSelectionOwner = (session, userId) => {
31406
+ return !session.ownerId || session.ownerId === userId;
31407
+ };
30702
31408
  /** 翻页成功后重新计算 120 秒有效期。 */
30703
31409
  var touchEpisodeSelection = (token, scope) => {
30704
31410
  const session = getEpisodeSelection(token, scope);
@@ -30759,34 +31465,41 @@ var getEpisodeSelectionPoster = async (session, posterPage) => {
30759
31465
  * 生成 QQBot keyboard 行。
30760
31466
  * 跳转输入行在最上面,集数按钮每行 5 个,翻页按钮单独一行;
30761
31467
  * 15 集一页时正好 1 + 3 + 1 = 5 行,不会超过 QQBot 的上限。
31468
+ * 群聊里每个按钮都带发起人的点击权限(QQBot 原生 permission),别人点了不生效;
31469
+ * 私聊只有对方一人可见,且 QQ 私聊不支持「指定用户」权限,不加 list,靠命令侧校验。
30762
31470
  */
30763
31471
  var buildEpisodeKeyboardRows = (session, page) => {
30764
31472
  const data = getEpisodePage(session, page);
30765
31473
  if (!data) return [];
31474
+ const ownerPermission = session.ownerId && session.scene === "group" ? { list: [session.ownerId] } : {};
30766
31475
  const rows = [];
30767
31476
  if (data.pageCount > 1) rows.push([{
30768
31477
  text: "跳转到第x页",
30769
31478
  data: `#选集 ${session.token} page:`,
30770
31479
  enter: false,
30771
- style: 1
31480
+ style: 1,
31481
+ ...ownerPermission
30772
31482
  }]);
30773
31483
  for (let index = 0; index < data.items.length; index += 5) rows.push(data.items.slice(index, index + 5).map((item) => ({
30774
31484
  text: String(item.episode),
30775
31485
  show: String(item.episode),
30776
31486
  callback: true,
30777
31487
  data: `#选集 ${session.token} ${item.episode}`,
30778
- style: 1
31488
+ style: 1,
31489
+ ...ownerPermission
30779
31490
  })));
30780
31491
  const navigation = [];
30781
31492
  if (data.page > 1) navigation.push({
30782
31493
  text: "上一页",
30783
31494
  callback: true,
30784
- data: `#选集 ${session.token} page:${data.page - 1}`
31495
+ data: `#选集 ${session.token} page:${data.page - 1}`,
31496
+ ...ownerPermission
30785
31497
  });
30786
31498
  if (data.page < data.pageCount) navigation.push({
30787
31499
  text: "下一页",
30788
31500
  callback: true,
30789
- data: `#选集 ${session.token} page:${data.page + 1}`
31501
+ data: `#选集 ${session.token} page:${data.page + 1}`,
31502
+ ...ownerPermission
30790
31503
  });
30791
31504
  if (navigation.length) rows.push(navigation);
30792
31505
  return rows;
@@ -30796,7 +31509,8 @@ var buildEpisodeSelectionText = (session, page) => {
30796
31509
  const data = getEpisodePage(session, page);
30797
31510
  if (!data) return "";
30798
31511
  const range = data.firstEpisode === data.lastEpisode ? `第 ${data.firstEpisode} 集` : `第 ${data.firstEpisode}–${data.lastEpisode} 集`;
30799
- return `《${session.title}》\n${range} · 第 ${data.page}/${data.pageCount} 页\n点击下方按钮选集(120 秒内有效)。`;
31512
+ const ownerHint = session.scene === "group" ? ",仅发起人可操作" : "";
31513
+ return `《${session.title}》\n${range} · 第 ${data.page}/${data.pageCount} 页\n点击下方按钮选集(120 秒内有效${ownerHint})。`;
30800
31514
  };
30801
31515
  /** 按实际集号查找目标。 */
30802
31516
  var resolveEpisodeTarget = (session, episode) => {
@@ -32128,6 +32842,8 @@ var Bilibili = class extends Base {
32128
32842
  const linkedSourceIndex = linkedEpId ? sourceEpisodes.findIndex((item) => String(item.ep_id) === linkedEpId) : -1;
32129
32843
  const linkedDisplayIndex = linkedSourceIndex >= 0 ? sourceEpisodes.length - 1 - linkedSourceIndex : -1;
32130
32844
  let selectedIndex = autoSelectEpisode ? linkedSourceIndex : -1;
32845
+ /** 文字选集消息的表情反馈流程:翻页留到新海报发出、选集留到下载结束后再收尾 */
32846
+ let emojiFlow = null;
32131
32847
  const buttonSelection = isButtonSelectionScene(this.e);
32132
32848
  const selectionMode = buttonSelection ? "button" : "text";
32133
32849
  /** 按海报页码渲染:15 集一页,长番剧不会撑爆单张 PNG */
@@ -32142,10 +32858,12 @@ var Bilibili = class extends Base {
32142
32858
  seasonID: videoInfo.data.result.season_id,
32143
32859
  subtitle: videoInfo.data.result.subtitle,
32144
32860
  UPInfo: videoInfo.data.result.up_info,
32861
+ Staff: videoInfo.data.result.staff,
32145
32862
  Copyright: videoInfo.data.result.rights.copyright,
32146
32863
  Stat: videoInfo.data.result.stat,
32147
32864
  Episodes: displayEpisodes,
32148
32865
  length: sourceEpisodes.length,
32866
+ timeoutSeconds: 120,
32149
32867
  selectionMode,
32150
32868
  posterPage,
32151
32869
  posterPageSize: 15
@@ -32162,6 +32880,7 @@ var Bilibili = class extends Base {
32162
32880
  title: videoInfo.data.result.title,
32163
32881
  platform: "bilibili",
32164
32882
  scene: this.e.isFriend ? "friend" : "group",
32883
+ ownerId: this.e.userId,
32165
32884
  poster,
32166
32885
  posterPage: initialPage,
32167
32886
  renderPoster,
@@ -32178,95 +32897,123 @@ var Bilibili = class extends Base {
32178
32897
  /** 上一页海报消息,翻页发送成功后撤回,避免聊天里堆一串过期海报 */
32179
32898
  let posterMessageId = "";
32180
32899
  let posterMessageSentAt = 0;
32181
- for (;;) {
32182
- const poster = await renderPoster(posterPage);
32183
- const start = (posterPage - 1) * 15;
32184
- const pageEpisodes = displayEpisodes.slice(start, start + 15);
32185
- const firstEpisode = pageEpisodes[0]?.episode ?? 0;
32186
- const lastEpisode = pageEpisodes[pageEpisodes.length - 1]?.episode ?? 0;
32187
- const range = firstEpisode === lastEpisode ? `第 ${firstEpisode} 集` : `第 ${firstEpisode}–${lastEpisode} 集`;
32188
- const sentAt = Date.now();
32189
- const result = await this.e.reply([...poster, segment.text(`《${videoInfo.data.result.title}》\n${range}(共 ${displayEpisodes.length} 集)\n第 ${posterPage}/${posterPageCount} 页\n发送 第?集 选集${posterPageCount > 1 ? ",发送 上一页 / 下一页 / 第?页 翻页" : ""}(120 秒内有效)`)]);
32190
- const previousMessageId = posterMessageId;
32191
- const previousMessageSentAt = posterMessageSentAt;
32192
- posterMessageId = result.messageId;
32193
- posterMessageSentAt = result.messageId ? sentAt : 0;
32194
- if (previousMessageId && canRecallEpisodeMessage(previousMessageId, previousMessageSentAt)) try {
32195
- await this.e.bot.recallMsg(this.e.contact, previousMessageId);
32196
- } catch (error) {
32197
- logger$1.debug("[选集] 撤回上一页失败:", error);
32198
- }
32199
- const context = await karin.ctx(this.e, {
32200
- reply: true,
32201
- throwOnTimeout: false,
32202
- time: 120
32203
- });
32204
- if (!context) return true;
32205
- const replyText = context.msg.trim();
32206
- if (/^[#/]/.test(replyText) || /(https?:\/\/|b23\.tv|bilibili\.com|douyin\.com|iesdouyin\.com)/i.test(replyText)) return true;
32207
- const pageAction = parseEpisodePosterPageAction(context.msg);
32208
- if (pageAction) {
32209
- const nextPage = pageAction.type === "prev" ? posterPage - 1 : pageAction.type === "next" ? posterPage + 1 : pageAction.page;
32210
- if (nextPage < 1 || nextPage > posterPageCount) {
32211
- await this.e.reply(`页码超出范围,请发送 1-${posterPageCount} 之间的页码`);
32900
+ try {
32901
+ for (;;) {
32902
+ const poster = await renderPoster(posterPage);
32903
+ const start = (posterPage - 1) * 15;
32904
+ const pageEpisodes = displayEpisodes.slice(start, start + 15);
32905
+ const firstEpisode = pageEpisodes[0]?.episode ?? 0;
32906
+ const lastEpisode = pageEpisodes[pageEpisodes.length - 1]?.episode ?? 0;
32907
+ const range = firstEpisode === lastEpisode ? `第 ${firstEpisode} 集` : `第 ${firstEpisode}–${lastEpisode} 集`;
32908
+ const sentAt = Date.now();
32909
+ const result = await this.e.reply([...poster, segment.text(`《${videoInfo.data.result.title}》\n${range}(共 ${displayEpisodes.length} 集)\n第 ${posterPage}/${posterPageCount} 页\n发送 第?集 选集${posterPageCount > 1 ? ",发送 上一页 / 下一页 / 第?页 翻页" : ""}(120 秒内有效)`)]);
32910
+ if (emojiFlow) {
32911
+ await emojiFlow.succeed();
32912
+ settleEmojiReaction(this.e);
32913
+ emojiFlow = null;
32914
+ }
32915
+ const previousMessageId = posterMessageId;
32916
+ const previousMessageSentAt = posterMessageSentAt;
32917
+ posterMessageId = result.messageId;
32918
+ posterMessageSentAt = result.messageId ? sentAt : 0;
32919
+ if (previousMessageId && canRecallEpisodeMessage(previousMessageId, previousMessageSentAt)) try {
32920
+ await this.e.bot.recallMsg(this.e.contact, previousMessageId);
32921
+ } catch (error) {
32922
+ logger$1.debug("[选集] 撤回上一页失败:", error);
32923
+ }
32924
+ const context = await karin.ctx(this.e, {
32925
+ reply: true,
32926
+ throwOnTimeout: false,
32927
+ time: 120
32928
+ });
32929
+ if (!context) return true;
32930
+ const replyText = context.msg.trim();
32931
+ if (/^[#/]/.test(replyText) || /(https?:\/\/|b23\.tv|bilibili\.com|douyin\.com|iesdouyin\.com)/i.test(replyText)) return true;
32932
+ emojiFlow = createEmojiReactionFlow(context);
32933
+ await emojiFlow.begin();
32934
+ const pageAction = parseEpisodePosterPageAction(context.msg);
32935
+ if (pageAction) {
32936
+ const nextPage = pageAction.type === "prev" ? posterPage - 1 : pageAction.type === "next" ? posterPage + 1 : pageAction.page;
32937
+ if (nextPage < 1 || nextPage > posterPageCount) {
32938
+ await this.e.reply(`页码超出范围,请发送 1-${posterPageCount} 之间的页码`);
32939
+ await emojiFlow.fail();
32940
+ settleEmojiReaction(this.e);
32941
+ emojiFlow = null;
32942
+ continue;
32943
+ }
32944
+ posterPage = nextPage;
32212
32945
  continue;
32213
32946
  }
32214
- posterPage = nextPage;
32215
- continue;
32216
- }
32217
- const parsedEpisode = parseEpisodeNumber(context.msg);
32218
- if (parsedEpisode === null || parsedEpisode > sourceEpisodes.length) {
32219
- await this.e.reply(parsedEpisode === null ? "匹配内容失败,请发送 第?集 选择集数,或发送 上一页 / 下一页 / 第?页 翻页" : `第${parsedEpisode}集不在可解析的剧集里,请重新选择`);
32220
- continue;
32947
+ const parsedEpisode = parseEpisodeNumber(context.msg);
32948
+ if (parsedEpisode === null || parsedEpisode > sourceEpisodes.length) {
32949
+ await this.e.reply(parsedEpisode === null ? "匹配内容失败,请发送 第?集 选择集数,或发送 上一页 / 下一页 / 第?页 翻页" : `第${parsedEpisode}集不在可解析的剧集里,请重新选择`);
32950
+ await emojiFlow.fail();
32951
+ settleEmojiReaction(this.e);
32952
+ emojiFlow = null;
32953
+ continue;
32954
+ }
32955
+ selectedIndex = parsedEpisode - 1;
32956
+ break;
32221
32957
  }
32222
- selectedIndex = parsedEpisode - 1;
32223
- break;
32958
+ } catch (error) {
32959
+ if (emojiFlow) await emojiFlow.fail();
32960
+ throw error;
32224
32961
  }
32225
32962
  }
32226
32963
  const episodeInfo = sourceEpisodes[selectedIndex];
32227
- if (!episodeInfo) {
32228
- await this.e.reply("未找到该集,请重新发送番剧链接");
32229
- return true;
32230
- }
32231
- const displayEpisode = selectedIndex + 1;
32232
- this.downloadfilename = episodeInfo.share_copy.substring(0, 50).replace(/[\\/:*?"<>|\r\n\s]/g, " ");
32233
- await this.e.reply(`收到请求,第${displayEpisode}集\n${this.downloadfilename}\n正在下载中`);
32234
- const bangumidataBASEURL = bilibiliApiUrls.getBangumiStream({
32235
- cid: episodeInfo.cid,
32236
- ep_id: episodeInfo.ep_id.toString()
32237
- });
32238
- const Params = await genParams(bangumidataBASEURL);
32239
- if (!this.islogin) await this.e.reply("B站ck未配置或已失效,无法获取视频流,可尝试【#B站登录】以配置新ck");
32240
- const playUrlData = await new Network({
32241
- url: bangumidataBASEURL + Params,
32242
- headers: this.headers
32243
- }).getData();
32244
- if (episodeInfo.badge === "会员" && !this.isVIP) {
32245
- logger$1.warn("该CK不是大会员,无法获取视频流");
32246
- return true;
32247
- }
32248
- if (Config.bilibili.videoQuality === 0) {
32249
- /** 提取出视频流信息对象,并排除清晰度重复的视频流 */
32250
- const simplify = playUrlData.result.dash.video.filter((item, index, self) => {
32251
- return self.findIndex((t) => {
32252
- return t.id === item.id;
32253
- }) === index;
32964
+ try {
32965
+ if (!episodeInfo) {
32966
+ await this.e.reply("未找到该集,请重新发送番剧链接");
32967
+ if (emojiFlow) await emojiFlow.fail();
32968
+ await settleEmojiReaction(this.e, "ERROR");
32969
+ return true;
32970
+ }
32971
+ const displayEpisode = selectedIndex + 1;
32972
+ this.downloadfilename = episodeInfo.share_copy.substring(0, 50).replace(/[\\/:*?"<>|\r\n\s]/g, " ");
32973
+ await this.e.reply(`收到请求,第${displayEpisode}集\n${this.downloadfilename}\n正在下载中`);
32974
+ const bangumidataBASEURL = bilibiliApiUrls.getBangumiStream({
32975
+ cid: episodeInfo.cid,
32976
+ ep_id: episodeInfo.ep_id.toString()
32254
32977
  });
32255
- /** 替换原始的视频信息对象 */
32256
- playUrlData.result.dash.video = simplify;
32257
- /** 给视频信息对象删除不符合条件的视频流 */
32258
- const correctList = await bilibiliProcessVideos({
32259
- accept_description: playUrlData.result.accept_description,
32260
- bvid: videoInfo.data.result.season_id.toString(),
32261
- qn: Config.bilibili.videoQuality
32262
- }, simplify, getBilibiliStreamUrl(playUrlData.result.dash.audio?.[0]));
32263
- playUrlData.result.dash.video = correctList.videoList;
32264
- playUrlData.result.cept_description = correctList.accept_description;
32978
+ const Params = await genParams(bangumidataBASEURL);
32979
+ if (!this.islogin) await this.e.reply("B站ck未配置或已失效,无法获取视频流,可尝试【#B站登录】以配置新ck");
32980
+ const playUrlData = await new Network({
32981
+ url: bangumidataBASEURL + Params,
32982
+ headers: this.headers
32983
+ }).getData();
32984
+ if (episodeInfo.badge === "会员" && !this.isVIP) {
32985
+ logger$1.warn("该CK不是大会员,无法获取视频流");
32986
+ if (emojiFlow) await emojiFlow.fail();
32987
+ await settleEmojiReaction(this.e, "ERROR");
32988
+ return true;
32989
+ }
32990
+ if (Config.bilibili.videoQuality === 0) {
32991
+ /** 提取出视频流信息对象,并排除清晰度重复的视频流 */
32992
+ const simplify = playUrlData.result.dash.video.filter((item, index, self) => {
32993
+ return self.findIndex((t) => {
32994
+ return t.id === item.id;
32995
+ }) === index;
32996
+ });
32997
+ /** 替换原始的视频信息对象 */
32998
+ playUrlData.result.dash.video = simplify;
32999
+ /** 给视频信息对象删除不符合条件的视频流 */
33000
+ const correctList = await bilibiliProcessVideos({
33001
+ accept_description: playUrlData.result.accept_description,
33002
+ bvid: videoInfo.data.result.season_id.toString(),
33003
+ qn: Config.bilibili.videoQuality
33004
+ }, simplify, getBilibiliStreamUrl(playUrlData.result.dash.audio?.[0]));
33005
+ playUrlData.result.dash.video = correctList.videoList;
33006
+ playUrlData.result.cept_description = correctList.accept_description;
33007
+ }
33008
+ await this.getvideo({
33009
+ infoData: videoInfo.data,
33010
+ playUrlData
33011
+ });
33012
+ if (emojiFlow) await emojiFlow.succeed();
33013
+ } catch (error) {
33014
+ if (emojiFlow) await emojiFlow.fail();
33015
+ throw error;
32265
33016
  }
32266
- await this.getvideo({
32267
- infoData: videoInfo.data,
32268
- playUrlData
32269
- });
32270
33017
  break;
32271
33018
  }
32272
33019
  case "dynamic_info": {
@@ -37126,13 +37873,13 @@ var DouYin = class DouYin extends Base {
37126
37873
  if (VideoData.data.aweme_detail === null) throw new Error("获取作品详情失败,可能是因为该作品已被删除或设置为私密。");
37127
37874
  /**
37128
37875
  * 根据 aweme_type 判断作品类型
37129
- * - `0`、`4`、`55`:视频作品;`4` 是剧集合集
37876
+ * - `0`、`4`、`55`、`61`:视频作品;`4` 是剧集合集
37130
37877
  * - `163`:文章作品
37131
37878
  * - `68`:图文作品(含图集、合辑等)
37132
37879
  */
37133
37880
  const aweme_type = VideoData.data.aweme_detail.aweme_type;
37134
37881
  const isArticle = aweme_type === 163;
37135
- const isVideo = aweme_type === 0 || aweme_type === 4 || aweme_type === 55;
37882
+ const isVideo = aweme_type === 0 || aweme_type === 4 || aweme_type === 55 || aweme_type === 61;
37136
37883
  const CommentsData = await this.amagi.douyin.fetcher.fetchWorkComments({
37137
37884
  aweme_id: data.aweme_id,
37138
37885
  number: Config.douyin.numcomment
@@ -37709,28 +38456,17 @@ var DouYin = class DouYin extends Base {
37709
38456
  if (context) {
37710
38457
  const num = parseInt(context.msg.trim());
37711
38458
  if (!isNaN(num) && num >= 1 && num <= displayVideos.length) {
37712
- const emojiManager = new EmojiReactionManager(context);
37713
- let processingTimer = null;
37714
- let successTimer = null;
37715
- await emojiManager.add("EYES");
37716
- processingTimer = setTimeout(() => {
37717
- emojiManager.add("PROCESSING").catch(() => {});
37718
- }, 1500);
38459
+ const emojiFlow = createEmojiReactionFlow(context);
38460
+ await emojiFlow.begin();
37719
38461
  try {
37720
38462
  const targetData = {
37721
38463
  type: "one_work",
37722
38464
  aweme_id: displayVideos[num - 1].aweme_id
37723
38465
  };
37724
38466
  await new DouYin(context, targetData).DouyinHandler(targetData);
37725
- successTimer = setTimeout(() => {
37726
- emojiManager.replace("PROCESSING", "SUCCESS").catch(() => {});
37727
- }, 1500);
38467
+ await emojiFlow.succeed();
37728
38468
  } catch (error) {
37729
- if (processingTimer) clearTimeout(processingTimer);
37730
- if (successTimer) clearTimeout(successTimer);
37731
- const processingEmojiId = getEmojiId(context, "PROCESSING");
37732
- if (emojiManager.has(processingEmojiId)) await emojiManager.remove("PROCESSING");
37733
- await emojiManager.add("ERROR");
38469
+ await emojiFlow.fail();
37734
38470
  throw error;
37735
38471
  }
37736
38472
  }
@@ -37795,6 +38531,7 @@ var DouYin = class DouYin extends Base {
37795
38531
  title: mixInfo.mix_name,
37796
38532
  platform: "douyin",
37797
38533
  scene: this.e.isFriend ? "friend" : "group",
38534
+ ownerId: this.e.userId,
37798
38535
  poster,
37799
38536
  posterPage: initialPage,
37800
38537
  renderPoster,
@@ -37817,56 +38554,79 @@ var DouYin = class DouYin extends Base {
37817
38554
  /** 上一页海报消息,翻页发送成功后撤回,避免聊天里堆一串过期海报 */
37818
38555
  let posterMessageId = "";
37819
38556
  let posterMessageSentAt = 0;
37820
- for (;;) {
37821
- const poster = await renderPoster(posterPage);
37822
- const start = (posterPage - 1) * 15;
37823
- const pageEpisodes = displayEpisodes.slice(start, start + 15);
37824
- const firstEpisode = pageEpisodes[0]?.episode ?? 0;
37825
- const lastEpisode = pageEpisodes[pageEpisodes.length - 1]?.episode ?? 0;
37826
- const range = firstEpisode === lastEpisode ? `第 ${firstEpisode} 集` : `第 ${firstEpisode}–${lastEpisode} 集`;
37827
- const sentAt = Date.now();
37828
- const result = await this.e.reply([...poster, segment.text(`《${mixInfo.mix_name}》\n${range}(共 ${totalEpisode} 集,已列出 ${displayEpisodes.length} 集)\n第 ${posterPage}/${posterPageCount} 页\n发送 第?集 选集${posterPageCount > 1 ? ",发送 上一页 / 下一页 / 第?页 翻页" : ""}(${timeoutSeconds} 秒内有效)`)]);
37829
- const previousMessageId = posterMessageId;
37830
- const previousMessageSentAt = posterMessageSentAt;
37831
- posterMessageId = result.messageId;
37832
- posterMessageSentAt = result.messageId ? sentAt : 0;
37833
- if (previousMessageId && canRecallEpisodeMessage(previousMessageId, previousMessageSentAt)) try {
37834
- await this.e.bot.recallMsg(this.e.contact, previousMessageId);
37835
- } catch (error) {
37836
- logger.debug("[选集] 撤回上一页失败:", error);
37837
- }
37838
- const context = await karin.ctx(this.e, {
37839
- reply: true,
37840
- throwOnTimeout: false,
37841
- time: timeoutSeconds
37842
- });
37843
- if (!context) return true;
37844
- const replyText = context.msg.trim();
37845
- if (/^[#/]/.test(replyText) || /(https?:\/\/|b23\.tv|bilibili\.com|douyin\.com|iesdouyin\.com)/i.test(replyText)) return true;
37846
- const pageAction = parseEpisodePosterPageAction(context.msg);
37847
- if (pageAction) {
37848
- const nextPage = pageAction.type === "prev" ? posterPage - 1 : pageAction.type === "next" ? posterPage + 1 : pageAction.page;
37849
- if (nextPage < 1 || nextPage > posterPageCount) {
37850
- await this.e.reply(`页码超出范围,请发送 1-${posterPageCount} 之间的页码`);
38557
+ /** 子上下文消息的表情反馈流程:翻页时留到新海报发出后再算成功 */
38558
+ let emojiFlow = null;
38559
+ try {
38560
+ for (;;) {
38561
+ const poster = await renderPoster(posterPage);
38562
+ const start = (posterPage - 1) * 15;
38563
+ const pageEpisodes = displayEpisodes.slice(start, start + 15);
38564
+ const firstEpisode = pageEpisodes[0]?.episode ?? 0;
38565
+ const lastEpisode = pageEpisodes[pageEpisodes.length - 1]?.episode ?? 0;
38566
+ const range = firstEpisode === lastEpisode ? `第 ${firstEpisode} 集` : `第 ${firstEpisode}–${lastEpisode} 集`;
38567
+ const sentAt = Date.now();
38568
+ const result = await this.e.reply([...poster, segment.text(`《${mixInfo.mix_name}》\n${range}(共 ${totalEpisode} 集,已列出 ${displayEpisodes.length} 集)\n第 ${posterPage}/${posterPageCount} 页\n发送 第?集 选集${posterPageCount > 1 ? ",发送 上一页 / 下一页 / 第?页 翻页" : ""}(${timeoutSeconds} 秒内有效)`)]);
38569
+ if (emojiFlow) {
38570
+ await emojiFlow.succeed();
38571
+ settleEmojiReaction(this.e);
38572
+ emojiFlow = null;
38573
+ }
38574
+ const previousMessageId = posterMessageId;
38575
+ const previousMessageSentAt = posterMessageSentAt;
38576
+ posterMessageId = result.messageId;
38577
+ posterMessageSentAt = result.messageId ? sentAt : 0;
38578
+ if (previousMessageId && canRecallEpisodeMessage(previousMessageId, previousMessageSentAt)) try {
38579
+ await this.e.bot.recallMsg(this.e.contact, previousMessageId);
38580
+ } catch (error) {
38581
+ logger.debug("[选集] 撤回上一页失败:", error);
38582
+ }
38583
+ const context = await karin.ctx(this.e, {
38584
+ reply: true,
38585
+ throwOnTimeout: false,
38586
+ time: timeoutSeconds
38587
+ });
38588
+ if (!context) return true;
38589
+ const replyText = context.msg.trim();
38590
+ if (/^[#/]/.test(replyText) || /(https?:\/\/|b23\.tv|bilibili\.com|douyin\.com|iesdouyin\.com)/i.test(replyText)) return true;
38591
+ emojiFlow = createEmojiReactionFlow(context);
38592
+ await emojiFlow.begin();
38593
+ const pageAction = parseEpisodePosterPageAction(context.msg);
38594
+ if (pageAction) {
38595
+ const nextPage = pageAction.type === "prev" ? posterPage - 1 : pageAction.type === "next" ? posterPage + 1 : pageAction.page;
38596
+ if (nextPage < 1 || nextPage > posterPageCount) {
38597
+ await this.e.reply(`页码超出范围,请发送 1-${posterPageCount} 之间的页码`);
38598
+ await emojiFlow.fail();
38599
+ settleEmojiReaction(this.e);
38600
+ emojiFlow = null;
38601
+ continue;
38602
+ }
38603
+ posterPage = nextPage;
37851
38604
  continue;
37852
38605
  }
37853
- posterPage = nextPage;
37854
- continue;
37855
- }
37856
- const episode = parseEpisodeNumber(context.msg);
37857
- const target = episode === null ? void 0 : displayEpisodes.find((item) => item.episode === episode);
37858
- if (!target) {
37859
- await this.e.reply(episode === null ? "匹配内容失败,请发送 第?集 选择集数,或发送 上一页 / 下一页 / 第?页 翻页" : `第${episode}集不在可解析的剧集里,请重新选择`);
37860
- continue;
38606
+ const episode = parseEpisodeNumber(context.msg);
38607
+ const target = episode === null ? void 0 : displayEpisodes.find((item) => item.episode === episode);
38608
+ if (!target) {
38609
+ await this.e.reply(episode === null ? "匹配内容失败,请发送 第?集 选择集数,或发送 上一页 / 下一页 / 第?页 翻页" : `第${episode}集不在可解析的剧集里,请重新选择`);
38610
+ await emojiFlow.fail();
38611
+ settleEmojiReaction(this.e);
38612
+ emojiFlow = null;
38613
+ continue;
38614
+ }
38615
+ const episodeTitle = (isPlaylet ? mixInfo.mix_name : target.title || mixInfo.mix_name).replace(/\s+/g, " ").trim();
38616
+ await this.e.reply(`收到请求,第${episode}集\n${episodeTitle}\n正在解析中`);
38617
+ const targetData = {
38618
+ type: "one_work",
38619
+ aweme_id: target.aweme_id
38620
+ };
38621
+ await new DouYin(context, targetData, { forceBurnDanmaku: this.forceBurnDanmaku }).DouyinHandler(targetData);
38622
+ await emojiFlow.succeed();
38623
+ settleEmojiReaction(this.e);
38624
+ emojiFlow = null;
38625
+ return true;
37861
38626
  }
37862
- const episodeTitle = (isPlaylet ? mixInfo.mix_name : target.title || mixInfo.mix_name).replace(/\s+/g, " ").trim();
37863
- await this.e.reply(`收到请求,第${episode}集\n${episodeTitle}\n正在解析中`);
37864
- const targetData = {
37865
- type: "one_work",
37866
- aweme_id: target.aweme_id
37867
- };
37868
- await new DouYin(context, targetData, { forceBurnDanmaku: this.forceBurnDanmaku }).DouyinHandler(targetData);
37869
- return true;
38627
+ } catch (error) {
38628
+ if (emojiFlow) await emojiFlow.fail();
38629
+ throw error;
37870
38630
  }
37871
38631
  }
37872
38632
  case "music_work": {
@@ -42467,6 +43227,10 @@ var handleEpisodeSelection = wrapWithErrorHandler(async (e, next) => {
42467
43227
  await e.reply("本次选集已结束,请重新发送原链接", { at: true });
42468
43228
  return true;
42469
43229
  }
43230
+ if (!isEpisodeSelectionOwner(session, e.userId)) {
43231
+ await e.reply("这次选集只对发起人生效,请自己发送原链接", { at: true });
43232
+ return true;
43233
+ }
42470
43234
  if (!actionText) {
42471
43235
  await e.reply("请点击下方按钮,或点击「跳转到第x页」输入页码");
42472
43236
  return true;