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

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 Gamepad2, $t as UsersRound, A as MdFitScreen, An as Plus, Ar as Bookmark, At as e, B as AiFillHeart, Bn as Maximize, Br as resolveTemplateStyle, Bt as s$1, C as webassembly_default, Cn as RotateCcw, Cr as Check, Ct as o, D as tailwind_css_default, Dn as Quote, Dr as Braces, Dt as a, E as tsdown_default, En as Radio, Er as Calendar, Et as m, F as FaCommentDots, Fn as Music, Fr as Button, Ft as c$1, G as RiArrowRightFill, Gn as LayoutTemplate, Gt as formatDistanceToNow, H as AiFillStar, Hn as LoaderCircle, Hr as require_react, Ht as zhCN, I as FaMusic, In as Moon, Ir as require_jsx_runtime, It as o$2, J as RiHeart3Fill, Jn as Heart, Jt as Zap, K as RiGroupLine, Kn as Info, Kt as format, L as FaTiktok, Ln as Monitor, Lr as twMerge, Lt as c, M as MdLightbulbOutline, Mn as Pencil, Mr as BellRing, Mt as a$1, N as MdLocationOn, Nn as Palette, Nr as ArrowRight, Nt as m$1, O as sqlite_default, On as QrCode, Or as Box, Ot as n, P as MdSchedule, Pn as Network$1, Pr as Chip, Pt as m$2, Q as RiMessage3Fill, Qn as Gauge, Qt as Users, R as FaUserGroup, Rn as MessageCircle, Rr as clsx, Rt as p, S as rehypeHighlight, Sn as Route, Sr as CircleAlert, St as r, T as typescript_default, Tn as RefreshCw, Tr as Camera, Tt as s, U as AiOutlineVideoCamera, Un as ListChecks, Ur as HtmlWrapper, Ut as parse, V as AiFillPushpin, Vn as MapPin, Vr as require_server_node, Vt as m$3, W as Markdown, Wn as Link, Wr as defineTemplate, Wt as fromUnixTime, X as RiHeartLine, Xn as GitBranch, Xt as WandSparkles, Y as RiHeart3Line, Yn as Hash, Yt as X, Z as RiLiveLine, Zn as Gift, Zt as Video, _ as use, _n as Share2, _r as CircleFadingArrowUp, _t as SiGithub, a as drawDither, an as Timer, ar as EyeOff, at as RiTrophyFill, b as install$2, bn as ScanLine, br as CircleCheck, bt as SiAnthropic, c as install$11, cn as Star, cr as Crown, ct as RiVideoLine, d as install$8, dn as Sparkles, dr as Copy, dt as SiSamsung, en as User, er as FingerprintPattern, et as RiShareForwardFill, f as install$7, fn as Smartphone, fr as Code, ft as SiOppo, g as install$5, gn as ShieldAlert, gr as CircleQuestionMark, gt as SiGooglephotos, h as install$6, hn as ShieldCheck, hr as Clapperboard, ht as SiHonor, in as Trash, ir as Eye, it as RiTiktokFill, j as MdInfoOutline, jn as Play, jr as BookOpenText, jt as c$2, k as react_default, kn as Puzzle, kr as Bot, kt as p$1, l as install$10, ln as Square, lr as Cpu, lt as SiXiaomi, m as install$4, mn as Shield, mr as Clock3, mt as SiHuawei, nn as Upload, nr as FilePlay, nt as RiStarLine, o as require_jpeg_js, on as Terminal, or as ExternalLink, ot as RiUserFollowLine, p as install$3, pn as ShoppingBag, pr as Clock, pt as SiOneplus, q as RiHashtag, qn as Image$1, qt as differenceInSeconds, rn as TriangleAlert, rr as FileExclamationPoint, rt as RiThumbUpFill, s as require_heic_decode, sn as Sun, sr as Download, st as RiVerifiedBadgeFill, tn as UserPlus, tr as FileText, tt as RiStarFill, u as install$9, un as SquarePen, ur as CornerDownLeft, ut as SiVivo, v as init, vn as Settings2, vr as CircleEllipsis, vt as SiBilibili, w as vite_default, wn as Repeat, wr as ChartColumn, wt as o$1, x as install$1, xn as Save, xr as CircleCheckBig, xt as o$3, y as install, yn as Search, yr as CircleDashed, yt as SiApple, z as BiImage, zn as Menu, zr as createTemplateRenderer, zt as n$1 } from "./vendor.js";
4
+ import { $ as RiPieChart2Fill, $n as Globe, $t as Video, A as MdFitScreen, An as Quote, Ar as Calendar, At as n, B as AiFillHeart, Bn as Monitor, 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 LoaderCircle, Gr as require_server_node, Gt as parse, H as AiFillStar, Hn as Menu, 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 LayoutTemplate, Jr as defineTemplate, Jt as format, K as RiGroupLine, Kn as ListChecks, 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 Hash, Qt as WandSparkles, R as FaUserGroup, Rn as Music, 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 CircleCheckBig, Tt as o, U as AiOutlineVideoCamera, Un as Maximize, Ur as createTemplateRenderer, Ut as m$3, V as AiFillPushpin, Vn as MessageCircle, Vr as twMerge, Vt as n$1, W as Markdown, Wn as MapPin, Wr as resolveTemplateStyle, Wt as zhCN, X as RiHeartLine, Xn as Image$1, Xt as Zap, Y as RiHeart3Line, Yn as Info, Yt as differenceInSeconds, Z as RiLiveLine, Zn as Heart, Zt as X, _ as use, _n as Shield, _r as Clock, _t as SiHonor, a as drawDither, an as Upload, ar as FileText, 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 GitBranch, 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 FingerprintPattern, 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 EyeOff, lt as SiXiaomi, m as install$4, mn as Sparkles, mr as CornerDownLeft, mt as SiOneplus, nn as User, nr as Gauge, nt as RiStarLine, o as require_jpeg_js, on as TriangleAlert, or as FilePlay, ot as RiUserFollowLine, p as install$3, pn as SquarePen, pr as Cpu, pt as SiOppo, q as RiHashtag, qn as Link, qr as HtmlWrapper, qt as formatDistanceToNow, rn as UserRound, rr as Gamepad2, rt as RiThumbUpFill, s as require_heic_decode, sn as Trash, sr as FileExclamationPoint, st as RiVerifiedBadgeFill, tn as UsersRound, tr as Gift, tt as RiStarFill, u as install$9, un as Sun, ur as ExternalLink, ut as SiXiaohongshu, v as init, vn as ShieldCheck, vr as Clock3, vt as SiGooglephotos, w as vite_default, wn as Save, wr as CircleCheck, 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 Moon, zr as Button, zt as c } 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";
@@ -15341,7 +15341,7 @@ var BilibiliQrcodeImg = import_react.memo((props) => {
15341
15341
  BilibiliQrcodeImg.displayName = "BilibiliQrcodeImg";
15342
15342
  //#endregion
15343
15343
  //#region ktr/template/bilibili/qrcodeImg/index.tsx
15344
- var qrcodeImg_default$1 = defineTemplate({
15344
+ var qrcodeImg_default$3 = defineTemplate({
15345
15345
  name: "登录二维码",
15346
15346
  description: "B站登录二维码展示模板",
15347
15347
  component: BilibiliQrcodeImg,
@@ -18974,7 +18974,7 @@ var DouyinQrcodeImg = import_react.memo((props) => {
18974
18974
  DouyinQrcodeImg.displayName = "DouyinQrcodeImg";
18975
18975
  //#endregion
18976
18976
  //#region ktr/template/douyin/qrcodeImg/index.tsx
18977
- var qrcodeImg_default = defineTemplate({
18977
+ var qrcodeImg_default$2 = defineTemplate({
18978
18978
  name: "登录二维码",
18979
18979
  description: "抖音登录二维码展示模板",
18980
18980
  component: DouyinQrcodeImg
@@ -20586,6 +20586,169 @@ var comment_default$1 = defineTemplate({
20586
20586
  validate: (data) => typeof data === "object" && data !== null && typeof data.share_url === "string"
20587
20587
  });
20588
20588
  //#endregion
20589
+ //#region ktr/template/kuaishou/qrcodeImg/components/qrcodeImg.tsx
20590
+ /**
20591
+ * 快手二维码登录组件
20592
+ * @param props 组件属性
20593
+ * @returns JSX元素
20594
+ */
20595
+ var KuaishouQrcodeImg = import_react.memo((props) => {
20596
+ const dark = isDark(props.ctx);
20597
+ const theme = {
20598
+ bg: dark ? "#000000" : "#FFFFFF",
20599
+ text: dark ? "#FFFFFF" : "#000000",
20600
+ subText: dark ? "#888888" : "#666666",
20601
+ accent: "#FF5000"
20602
+ };
20603
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DefaultLayout, {
20604
+ ...props,
20605
+ className: "relative overflow-hidden",
20606
+ style: {
20607
+ background: theme.bg,
20608
+ color: theme.text
20609
+ },
20610
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20611
+ className: "absolute inset-0 overflow-hidden pointer-events-none",
20612
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20613
+ className: "absolute top-[-40%] left-[-50%] w-400 h-600 rounded-full blur-[200px] opacity-25",
20614
+ style: { background: theme.accent }
20615
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20616
+ className: "absolute bottom-[-40%] right-[-50%] w-400 h-600 rounded-full blur-[200px] opacity-20",
20617
+ style: { background: "#FFB800" }
20618
+ })]
20619
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20620
+ className: "relative z-10 flex flex-col items-center justify-center h-full py-40 px-24 space-y-32",
20621
+ children: [
20622
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20623
+ className: "text-center space-y-12",
20624
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
20625
+ className: "text-8xl font-bold tracking-tight",
20626
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
20627
+ className: "bg-clip-text text-transparent",
20628
+ style: { backgroundImage: `linear-gradient(to right, ${theme.text}, ${theme.subText})` },
20629
+ children: "快手扫码登录"
20630
+ })
20631
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20632
+ className: "flex items-start justify-center gap-10 opacity-90",
20633
+ style: { color: theme.text },
20634
+ children: [
20635
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20636
+ className: "flex flex-col items-center gap-4",
20637
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20638
+ className: "p-5 rounded-3xl bg-surface/50 backdrop-blur-md",
20639
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SiKuaishou, { size: 64 })
20640
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
20641
+ className: "text-[28px] font-medium tracking-wide",
20642
+ children: "打开快手"
20643
+ })]
20644
+ }),
20645
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RiArrowRightFill, {
20646
+ size: 40,
20647
+ className: "mt-8 opacity-40"
20648
+ }),
20649
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20650
+ className: "flex flex-col items-center gap-4",
20651
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20652
+ className: "p-5 rounded-3xl bg-surface/50 backdrop-blur-md",
20653
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Menu, { size: 64 })
20654
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
20655
+ className: "text-[28px] font-medium tracking-wide",
20656
+ children: "左上角菜单"
20657
+ })]
20658
+ }),
20659
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RiArrowRightFill, {
20660
+ size: 40,
20661
+ className: "mt-8 opacity-40"
20662
+ }),
20663
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20664
+ className: "flex flex-col items-center gap-4",
20665
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20666
+ className: "p-5 rounded-3xl bg-surface/50 backdrop-blur-md",
20667
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScanLine, { size: 64 })
20668
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
20669
+ className: "text-[28px] font-medium tracking-wide",
20670
+ children: "扫一扫"
20671
+ })]
20672
+ })
20673
+ ]
20674
+ })]
20675
+ }),
20676
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20677
+ className: "relative group",
20678
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20679
+ className: "relative p-4",
20680
+ style: {
20681
+ width: "800px",
20682
+ height: "800px"
20683
+ },
20684
+ children: props.data.share_url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
20685
+ value: props.data.share_url,
20686
+ avatarUrl: props.data.avatarUrl,
20687
+ useDarkTheme: dark,
20688
+ alt: "QR Code",
20689
+ className: "w-full h-full object-contain"
20690
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20691
+ className: "w-full h-full flex flex-col items-center justify-center gap-8",
20692
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20693
+ className: "w-24 h-24 border-4 border-gray-200 rounded-full animate-spin",
20694
+ style: { borderTopColor: theme.accent }
20695
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
20696
+ className: "text-3xl text-muted",
20697
+ children: "未提供二维码图片"
20698
+ })]
20699
+ })
20700
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20701
+ className: "absolute -bottom-24 left-0 right-0 text-center",
20702
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
20703
+ className: "text-[28px] font-medium tracking-wide flex items-center justify-center gap-3",
20704
+ style: { color: theme.subText },
20705
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
20706
+ className: "h-6 w-1.5 rounded-full",
20707
+ style: { backgroundColor: theme.accent }
20708
+ }), "此二维码 1 分钟内有效,请及时扫码登录。"]
20709
+ })
20710
+ })]
20711
+ }),
20712
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20713
+ className: "w-full max-w-4xl pt-20",
20714
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
20715
+ className: "flex flex-col items-center space-y-6 text-center",
20716
+ children: [
20717
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
20718
+ className: "flex items-center justify-center w-16 h-16 mb-2",
20719
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ShieldCheck, {
20720
+ size: 64,
20721
+ style: { color: theme.text },
20722
+ strokeWidth: 1.5
20723
+ })
20724
+ }),
20725
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
20726
+ className: "text-[40px] font-bold",
20727
+ style: { color: theme.text },
20728
+ children: "安全承诺"
20729
+ }),
20730
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
20731
+ className: "text-[24px] leading-relaxed opacity-60 max-w-3xl",
20732
+ style: { color: theme.text },
20733
+ children: "扫码只会把快手网页端的登录凭证(ck)保存在本机配置里,用于请求快手官方接口;不会上传到任何第三方。请在可信环境下使用。"
20734
+ })
20735
+ ]
20736
+ })
20737
+ })
20738
+ ]
20739
+ })]
20740
+ });
20741
+ });
20742
+ KuaishouQrcodeImg.displayName = "KuaishouQrcodeImg";
20743
+ //#endregion
20744
+ //#region ktr/template/kuaishou/qrcodeImg/index.tsx
20745
+ var qrcodeImg_default$1 = defineTemplate({
20746
+ name: "登录二维码",
20747
+ description: "快手登录二维码展示模板",
20748
+ component: KuaishouQrcodeImg,
20749
+ validate: (data) => typeof data === "object" && data !== null && typeof data.share_url === "string"
20750
+ });
20751
+ //#endregion
20589
20752
  //#region ktr/template/other/changelog/components/changelog.tsx
20590
20753
  var InlineCalloutCode = ({ children, className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
20591
20754
  className: `inline-flex items-center rounded-[0.55em] px-[0.48em] py-[0.18em] font-semibold ${className ?? ""}`.trim(),
@@ -24964,6 +25127,222 @@ var updateHelp_default = defineTemplate({
24964
25127
  component: UpdateHelp
24965
25128
  });
24966
25129
  //#endregion
25130
+ //#region ktr/template/other/verifyQr/components/verifyQr.tsx
25131
+ /**
25132
+ * 风控验证二维码(弥散信息海报系统)。
25133
+ *
25134
+ * 视觉中心只有一个 —— 二维码本身;其余元素按「做什么 → 怎么扫 → 扫完怎么办」
25135
+ * 三段排布,避免把说明文字铺成正文墙。二维码来自**平台官方页面地址**:
25136
+ * 扫码后在手机上完成验证即可,本页不做任何自动过码。
25137
+ * @param props 组件属性
25138
+ * @returns JSX元素
25139
+ */
25140
+ var VerifyQr = import_react.memo((props) => {
25141
+ const dark = isDark(props.ctx);
25142
+ const platform = props.data.platform ?? "";
25143
+ const theme = {
25144
+ bg: dark ? "#000000" : "#FFFFFF",
25145
+ text: dark ? "#FFFFFF" : "#000000",
25146
+ subText: dark ? "#8A8A8E" : "#6B6B70",
25147
+ accent: "#FF7A45",
25148
+ glow: "#FFB020"
25149
+ };
25150
+ const steps = [
25151
+ {
25152
+ icon: ScanLine,
25153
+ title: "手机扫码",
25154
+ desc: "相机或平台 App 均可"
25155
+ },
25156
+ {
25157
+ icon: Globe,
25158
+ title: "完成验证",
25159
+ desc: "在官方页面里手动通过验证"
25160
+ },
25161
+ {
25162
+ icon: CircleCheckBig,
25163
+ title: "自动继续",
25164
+ desc: "无需回复,我会自动接着解析"
25165
+ }
25166
+ ];
25167
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DefaultLayout, {
25168
+ ...props,
25169
+ className: "relative overflow-hidden",
25170
+ style: {
25171
+ background: theme.bg,
25172
+ color: theme.text
25173
+ },
25174
+ children: [
25175
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
25176
+ className: "absolute inset-0 overflow-hidden pointer-events-none",
25177
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
25178
+ className: "absolute top-[-45%] left-[-45%] w-500 h-700 rounded-full blur-[220px] opacity-25",
25179
+ style: { background: theme.accent }
25180
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
25181
+ className: "absolute bottom-[-45%] right-[-45%] w-500 h-700 rounded-full blur-[220px] opacity-20",
25182
+ style: { background: theme.glow }
25183
+ })]
25184
+ }),
25185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
25186
+ className: "absolute inset-0 pointer-events-none",
25187
+ style: { opacity: dark ? .16 : .2 },
25188
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
25189
+ className: "w-full h-full",
25190
+ xmlns: "http://www.w3.org/2000/svg",
25191
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("filter", {
25192
+ id: "verifyNoise",
25193
+ x: "0%",
25194
+ y: "0%",
25195
+ width: "100%",
25196
+ height: "100%",
25197
+ children: [
25198
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feTurbulence", {
25199
+ type: "fractalNoise",
25200
+ baseFrequency: "0.8",
25201
+ numOctaves: "2",
25202
+ stitchTiles: "stitch",
25203
+ result: "noise"
25204
+ }),
25205
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feColorMatrix", {
25206
+ type: "saturate",
25207
+ values: "0",
25208
+ result: "gray"
25209
+ }),
25210
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("feComponentTransfer", { children: [
25211
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncR", {
25212
+ type: "discrete",
25213
+ tableValues: "0 1"
25214
+ }),
25215
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncG", {
25216
+ type: "discrete",
25217
+ tableValues: "0 1"
25218
+ }),
25219
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncB", {
25220
+ type: "discrete",
25221
+ tableValues: "0 1"
25222
+ })
25223
+ ] })
25224
+ ]
25225
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", {
25226
+ width: "100%",
25227
+ height: "100%",
25228
+ filter: "url(#verifyNoise)"
25229
+ })]
25230
+ })
25231
+ }),
25232
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
25233
+ className: "relative z-10 flex flex-col items-center h-full py-36 px-24 space-y-24",
25234
+ children: [
25235
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
25236
+ className: "w-full max-w-5xl flex flex-col items-center gap-8",
25237
+ children: [
25238
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
25239
+ className: "flex items-center gap-4 px-6 py-2 rounded-full border border-white/15 bg-surface/40 backdrop-blur-md",
25240
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ShieldAlert, {
25241
+ size: 26,
25242
+ style: { color: theme.accent }
25243
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
25244
+ className: "text-[24px] font-medium tracking-[0.2em]",
25245
+ style: { color: theme.subText },
25246
+ children: platform ? `${platform} · HUMAN CHECK` : "HUMAN CHECK"
25247
+ })]
25248
+ }),
25249
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
25250
+ className: "text-8xl font-bold tracking-tight text-center",
25251
+ children: "需要人机验证"
25252
+ }),
25253
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
25254
+ className: "text-[26px] opacity-70 text-center max-w-3xl leading-relaxed",
25255
+ children: "平台对刚才的请求发起了风控。验证在手机上的官方页面完成,本机器人不做任何自动过码。"
25256
+ })
25257
+ ]
25258
+ }),
25259
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
25260
+ className: "w-full max-w-6xl flex items-center justify-center gap-16 flex-1",
25261
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
25262
+ className: "flex flex-col gap-10 min-w-0 flex-1 max-w-2xl",
25263
+ children: steps.map((step, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
25264
+ className: "flex items-start gap-8",
25265
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
25266
+ className: "flex flex-col items-center gap-3 shrink-0",
25267
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
25268
+ className: "p-5 rounded-3xl bg-surface/50 backdrop-blur-md border border-white/10",
25269
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(step.icon, {
25270
+ size: 48,
25271
+ style: { color: theme.text },
25272
+ strokeWidth: 1.5
25273
+ })
25274
+ }), index < steps.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
25275
+ className: "w-px h-10",
25276
+ style: { background: `${theme.subText}55` }
25277
+ })]
25278
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
25279
+ className: "flex flex-col gap-2 pt-2",
25280
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
25281
+ className: "text-[30px] font-semibold tracking-wide",
25282
+ children: step.title
25283
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
25284
+ className: "text-[22px] leading-relaxed opacity-60",
25285
+ children: step.desc
25286
+ })]
25287
+ })]
25288
+ }, step.title))
25289
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
25290
+ className: "relative shrink-0",
25291
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
25292
+ className: "relative p-6",
25293
+ style: {
25294
+ width: "760px",
25295
+ height: "760px"
25296
+ },
25297
+ children: props.data.share_url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
25298
+ value: props.data.share_url,
25299
+ avatarUrl: props.data.avatarUrl,
25300
+ useDarkTheme: dark,
25301
+ alt: "验证二维码",
25302
+ className: "w-full h-full object-contain"
25303
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
25304
+ className: "w-full h-full flex items-center justify-center",
25305
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
25306
+ className: "text-3xl text-muted",
25307
+ children: platform ? "平台未给出验证页地址" : "未提供验证地址"
25308
+ })
25309
+ })
25310
+ }), props.data.expiresHint && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
25311
+ className: "absolute -bottom-16 left-0 right-0 text-center text-[26px] font-medium tracking-wide flex items-center justify-center gap-3",
25312
+ style: { color: theme.subText },
25313
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
25314
+ className: "h-6 w-1.5 rounded-full",
25315
+ style: { backgroundColor: theme.accent }
25316
+ }), props.data.expiresHint]
25317
+ })]
25318
+ })]
25319
+ }),
25320
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
25321
+ className: "w-full max-w-5xl flex items-center gap-6 px-10 py-8 rounded-4xl bg-surface/55 backdrop-blur-md border border-white/20",
25322
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ShieldAlert, {
25323
+ size: 40,
25324
+ style: { color: theme.accent },
25325
+ strokeWidth: 1.5
25326
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
25327
+ className: "text-[22px] leading-relaxed opacity-70",
25328
+ children: "验证与本次会话绑定:请用同一账号在手机上完成,完成后不要再换 CK;二维码有时效,过期请重新触发一次解析。"
25329
+ })]
25330
+ })
25331
+ ]
25332
+ })
25333
+ ]
25334
+ });
25335
+ });
25336
+ VerifyQr.displayName = "VerifyQr";
25337
+ //#endregion
25338
+ //#region ktr/template/other/verifyQr/index.tsx
25339
+ var verifyQr_default = defineTemplate({
25340
+ name: "风控验证二维码",
25341
+ description: "把平台的风控验证页地址渲染成二维码,交给真人扫码过码",
25342
+ component: VerifyQr,
25343
+ validate: (data) => typeof data === "object" && data !== null && typeof data.share_url === "string"
25344
+ });
25345
+ //#endregion
24967
25346
  //#region ktr/template/other/version_warning/components/VersionWarning.tsx
24968
25347
  var VersionWarning = (props) => {
24969
25348
  const dark = isDark(props.ctx);
@@ -28829,6 +29208,168 @@ var XiaohongshuNoteInfo = import_react.memo((props) => {
28829
29208
  });
28830
29209
  XiaohongshuNoteInfo.displayName = "XiaohongshuNoteInfo";
28831
29210
  //#endregion
29211
+ //#region ktr/template/xiaohongshu/noteInfo/index.tsx
29212
+ var noteInfo_default = defineTemplate({
29213
+ name: "笔记信息",
29214
+ description: "小红书笔记信息展示模板",
29215
+ component: XiaohongshuNoteInfo
29216
+ });
29217
+ //#endregion
29218
+ //#region ktr/template/xiaohongshu/qrcodeImg/components/qrcodeImg.tsx
29219
+ /**
29220
+ * 小红书二维码登录组件
29221
+ * @param props 组件属性
29222
+ * @returns JSX元素
29223
+ */
29224
+ var XiaohongshuQrcodeImg = import_react.memo((props) => {
29225
+ const dark = isDark(props.ctx);
29226
+ const theme = {
29227
+ bg: dark ? "#000000" : "#FFFFFF",
29228
+ text: dark ? "#FFFFFF" : "#000000",
29229
+ subText: dark ? "#888888" : "#666666",
29230
+ accent: "#FF2442"
29231
+ };
29232
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DefaultLayout, {
29233
+ ...props,
29234
+ className: "relative overflow-hidden",
29235
+ style: {
29236
+ background: theme.bg,
29237
+ color: theme.text
29238
+ },
29239
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
29240
+ className: "absolute inset-0 overflow-hidden pointer-events-none",
29241
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29242
+ className: "absolute top-[-40%] right-[-50%] w-400 h-600 rounded-full blur-[200px] opacity-25",
29243
+ style: { background: theme.accent }
29244
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29245
+ className: "absolute bottom-[-40%] left-[-50%] w-400 h-600 rounded-full blur-[200px] opacity-20",
29246
+ style: { background: "#FF8A9B" }
29247
+ })]
29248
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
29249
+ className: "relative z-10 flex flex-col items-center justify-center h-full py-40 px-24 space-y-32",
29250
+ children: [
29251
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
29252
+ className: "text-center space-y-12",
29253
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
29254
+ className: "text-8xl font-bold tracking-tight",
29255
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
29256
+ className: "bg-clip-text text-transparent",
29257
+ style: { backgroundImage: `linear-gradient(to right, ${theme.text}, ${theme.subText})` },
29258
+ children: "小红书扫码登录"
29259
+ })
29260
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
29261
+ className: "flex items-start justify-center gap-10 opacity-90",
29262
+ style: { color: theme.text },
29263
+ children: [
29264
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
29265
+ className: "flex flex-col items-center gap-4",
29266
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29267
+ className: "p-5 rounded-3xl bg-surface/50 backdrop-blur-md",
29268
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SiXiaohongshu, { size: 64 })
29269
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
29270
+ className: "text-[28px] font-medium tracking-wide",
29271
+ children: "打开小红书"
29272
+ })]
29273
+ }),
29274
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RiArrowRightFill, {
29275
+ size: 40,
29276
+ className: "mt-8 opacity-40"
29277
+ }),
29278
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
29279
+ className: "flex flex-col items-center gap-4",
29280
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29281
+ className: "p-5 rounded-3xl bg-surface/50 backdrop-blur-md",
29282
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UserRound, { size: 64 })
29283
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
29284
+ className: "text-[28px] font-medium tracking-wide",
29285
+ children: "右下角「我」"
29286
+ })]
29287
+ }),
29288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RiArrowRightFill, {
29289
+ size: 40,
29290
+ className: "mt-8 opacity-40"
29291
+ }),
29292
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
29293
+ className: "flex flex-col items-center gap-4",
29294
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29295
+ className: "p-5 rounded-3xl bg-surface/50 backdrop-blur-md",
29296
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScanLine, { size: 64 })
29297
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
29298
+ className: "text-[28px] font-medium tracking-wide",
29299
+ children: "顶部扫一扫"
29300
+ })]
29301
+ })
29302
+ ]
29303
+ })]
29304
+ }),
29305
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
29306
+ className: "relative group",
29307
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29308
+ className: "relative p-4",
29309
+ style: {
29310
+ width: "800px",
29311
+ height: "800px"
29312
+ },
29313
+ children: props.data.share_url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
29314
+ value: props.data.share_url,
29315
+ avatarUrl: props.data.avatarUrl,
29316
+ useDarkTheme: dark,
29317
+ alt: "QR Code",
29318
+ className: "w-full h-full object-contain"
29319
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
29320
+ className: "w-full h-full flex flex-col items-center justify-center gap-8",
29321
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29322
+ className: "w-24 h-24 border-4 border-gray-200 rounded-full animate-spin",
29323
+ style: { borderTopColor: theme.accent }
29324
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
29325
+ className: "text-3xl text-muted",
29326
+ children: "未提供二维码图片"
29327
+ })]
29328
+ })
29329
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29330
+ className: "absolute -bottom-24 left-0 right-0 text-center",
29331
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
29332
+ className: "text-[28px] font-medium tracking-wide flex items-center justify-center gap-3",
29333
+ style: { color: theme.subText },
29334
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
29335
+ className: "h-6 w-1.5 rounded-full",
29336
+ style: { backgroundColor: theme.accent }
29337
+ }), "此二维码 3 分钟内有效,请及时扫码登录。"]
29338
+ })
29339
+ })]
29340
+ }),
29341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29342
+ className: "w-full max-w-4xl pt-20",
29343
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
29344
+ className: "flex flex-col items-center space-y-6 text-center",
29345
+ children: [
29346
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29347
+ className: "flex items-center justify-center w-16 h-16 mb-2",
29348
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ShieldCheck, {
29349
+ size: 64,
29350
+ style: { color: theme.text },
29351
+ strokeWidth: 1.5
29352
+ })
29353
+ }),
29354
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
29355
+ className: "text-[40px] font-bold",
29356
+ style: { color: theme.text },
29357
+ children: "安全承诺"
29358
+ }),
29359
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
29360
+ className: "text-[24px] leading-relaxed opacity-60 max-w-3xl",
29361
+ style: { color: theme.text },
29362
+ children: "扫码只会把小红书网页端的登录凭证(ck)保存在本机配置里,用于请求小红书官方接口;不会上传到任何第三方。请在可信环境下使用。"
29363
+ })
29364
+ ]
29365
+ })
29366
+ })
29367
+ ]
29368
+ })]
29369
+ });
29370
+ });
29371
+ XiaohongshuQrcodeImg.displayName = "XiaohongshuQrcodeImg";
29372
+ //#endregion
28832
29373
  //#region .ktr/template-registry.ts
28833
29374
  var templates = {
28834
29375
  "bilibili/bangumi": bangumi_default,
@@ -28839,7 +29380,7 @@ var templates = {
28839
29380
  "bilibili/dynamic/DYNAMIC_TYPE_FORWARD": DYNAMIC_TYPE_FORWARD_default,
28840
29381
  "bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD": DYNAMIC_TYPE_LIVE_RCMD_default,
28841
29382
  "bilibili/dynamic/DYNAMIC_TYPE_WORD": DYNAMIC_TYPE_WORD_default,
28842
- "bilibili/qrcodeImg": qrcodeImg_default$1,
29383
+ "bilibili/qrcodeImg": qrcodeImg_default$3,
28843
29384
  "bilibili/userlist": userlist_default$1,
28844
29385
  "bilibili/videoInfo": videoInfo_default,
28845
29386
  "douyin/article-work": article_work_default,
@@ -28849,12 +29390,13 @@ var templates = {
28849
29390
  "douyin/image-work": image_work_default,
28850
29391
  "douyin/live": live_default,
28851
29392
  "douyin/musicinfo": musicinfo_default,
28852
- "douyin/qrcodeImg": qrcodeImg_default,
29393
+ "douyin/qrcodeImg": qrcodeImg_default$2,
28853
29394
  "douyin/recommend-list": recommend_list_default,
28854
29395
  "douyin/user_profile": user_profile_default,
28855
29396
  "douyin/userlist": userlist_default,
28856
29397
  "douyin/video-work": video_work_default,
28857
29398
  "kuaishou/comment": comment_default$1,
29399
+ "kuaishou/qrcodeImg": qrcodeImg_default$1,
28858
29400
  "other/changelog": changelog_default,
28859
29401
  "other/handlerError": handlerError_default,
28860
29402
  "other/help": help_default,
@@ -28862,14 +29404,17 @@ var templates = {
28862
29404
  "other/qrlogin": qrlogin_default,
28863
29405
  "other/runtime": runtime_default,
28864
29406
  "other/updateHelp": updateHelp_default,
29407
+ "other/verifyQr": verifyQr_default,
28865
29408
  "other/version_warning": version_warning_default,
28866
29409
  "statistics/global": global_default,
28867
29410
  "statistics/group": group_default,
28868
29411
  "xiaohongshu/comment": comment_default,
28869
- "xiaohongshu/noteInfo": defineTemplate({
28870
- name: "笔记信息",
28871
- description: "小红书笔记信息展示模板",
28872
- component: XiaohongshuNoteInfo
29412
+ "xiaohongshu/noteInfo": noteInfo_default,
29413
+ "xiaohongshu/qrcodeImg": defineTemplate({
29414
+ name: "登录二维码",
29415
+ description: "小红书登录二维码展示模板",
29416
+ component: XiaohongshuQrcodeImg,
29417
+ validate: (data) => typeof data === "object" && data !== null && typeof data.share_url === "string"
28873
29418
  })
28874
29419
  };
28875
29420
  //#endregion
@@ -31682,7 +32227,7 @@ var mapping_table = (type) => {
31682
32227
  ],
31683
32228
  19: ["DYNAMIC_TYPE_MEDIALIST"]
31684
32229
  };
31685
- for (const key in Array) if (Array[key].includes(type)) return parseInt(key, 10);
32230
+ for (const key in Array) if (Array[key].includes(type)) return Number(key);
31686
32231
  return 1;
31687
32232
  };
31688
32233
  /**
@@ -32250,7 +32795,7 @@ var getBilibiliID = async (url) => {
32250
32795
  * @param e - 消息事件
32251
32796
  * @returns 带 send / recall / recallAll 的登记器
32252
32797
  */
32253
- var createMessageTracker$1 = (e) => {
32798
+ var createMessageTracker$3 = (e) => {
32254
32799
  const messageIds = [];
32255
32800
  const recall = async (id) => {
32256
32801
  try {
@@ -32286,7 +32831,7 @@ var createMessageTracker$1 = (e) => {
32286
32831
  * @param tracker - 消息登记器
32287
32832
  * @returns 二维码消息的 id,扫码后要单独撤回它
32288
32833
  */
32289
- var sendQrcode$1 = async (e, qrcode, tracker) => {
32834
+ var sendQrcode$3 = async (e, qrcode, tracker) => {
32290
32835
  const qrimg = await Render(e, "bilibili/qrcodeImg", {
32291
32836
  share_url: qrcode.content,
32292
32837
  avatarUrl: await resolveTriggerAvatarUrl(e)
@@ -32297,12 +32842,25 @@ var sendQrcode$1 = async (e, qrcode, tracker) => {
32297
32842
  return tracker.send(qrimg);
32298
32843
  };
32299
32844
  /** 把会话的失败原因翻成给用户看的话 */
32300
- var noticeOf$1 = (error) => {
32845
+ var noticeOf$3 = (error) => {
32301
32846
  if (error.kind === "risk") return `登录请求被B站风控拦截:${error.message}`;
32302
32847
  if (error.code === "COOKIE_EXPIRED") return "二维码已失效";
32303
32848
  return `登录未完成:${error.message}`;
32304
32849
  };
32305
32850
  /**
32851
+ * 从 B站扫码登录的响应体里取 refresh_token。
32852
+ *
32853
+ * 官方把续期令牌存在浏览器 `localStorage.ac_time_value`;扫码轮询接口返回的
32854
+ * `data.refresh_token` 就是同一份东西(见 amagi 的 `platforms/bilibili/cookieRefresh.ts`
32855
+ * 与 `endpoints/qrcodeStatus.ts` 的类型)。取不到就续不了期,只能重新扫码。
32856
+ * @param raw - 会话给出的 `credential.raw`(qrcode/status 的响应体)
32857
+ * @returns refresh_token;没有时 undefined
32858
+ */
32859
+ var readRefreshToken = (raw) => {
32860
+ const token = raw?.data?.refresh_token;
32861
+ return typeof token === "string" && token.trim() !== "" ? token : void 0;
32862
+ };
32863
+ /**
32306
32864
  * B站扫码登录。
32307
32865
  *
32308
32866
  * 轮询、退避、超时都在 amagi 的 v7 登录会话里(`client.bilibili.login.qrcode()`),
@@ -32316,13 +32874,13 @@ var noticeOf$1 = (error) => {
32316
32874
  * @param e - 消息事件
32317
32875
  */
32318
32876
  var bilibiliLogin = async (e) => {
32319
- const tracker = createMessageTracker$1(e);
32877
+ const tracker = createMessageTracker$3(e);
32320
32878
  let qrcodeMessageId;
32321
32879
  let scanned = false;
32322
32880
  try {
32323
32881
  const outcome = await getAmagiClient().bilibili.login.qrcode().watch({
32324
32882
  onQrcode: async (qrcode) => {
32325
- qrcodeMessageId = await sendQrcode$1(e, qrcode, tracker);
32883
+ qrcodeMessageId = await sendQrcode$3(e, qrcode, tracker);
32326
32884
  },
32327
32885
  onScanned: async () => {
32328
32886
  if (scanned) return;
@@ -32332,6 +32890,9 @@ var bilibiliLogin = async (e) => {
32332
32890
  },
32333
32891
  onSuccess: async (credential) => {
32334
32892
  await Config.Modify("amagi", "cookies.bilibili", credential.cookie);
32893
+ const refreshToken = readRefreshToken(credential.raw);
32894
+ if (refreshToken) await Config.Modify("amagi", "cookies.bilibiliRefreshToken", refreshToken);
32895
+ else logger$1.warn("[B站登录] 本次登录未返回 refresh_token,后续无法续期,只能重新扫码");
32335
32896
  const reloaded = reloadAmagiConfig();
32336
32897
  logger$1.mark(`[B站登录] 登录凭证已保存,Amagi Client ${reloaded ? "已重载" : "配置未变化"}`);
32337
32898
  }
@@ -32339,7 +32900,7 @@ var bilibiliLogin = async (e) => {
32339
32900
  if (outcome.ok) await e.reply("登录成功!用户登录凭证已保存至配置", { reply: true });
32340
32901
  else {
32341
32902
  logger$1.warn(`[B站登录] 会话结束于失败态: kind=${outcome.error.kind} code=${outcome.error.code}`);
32342
- await e.reply(noticeOf$1(outcome.error), { reply: true });
32903
+ await e.reply(noticeOf$3(outcome.error), { reply: true });
32343
32904
  }
32344
32905
  } catch (error) {
32345
32906
  logger$1.error("[B站登录] 登录流程出错:", error);
@@ -32348,6 +32909,43 @@ var bilibiliLogin = async (e) => {
32348
32909
  await tracker.recallAll();
32349
32910
  }
32350
32911
  };
32912
+ /**
32913
+ * B站登录态续期(官方四步协议)。
32914
+ *
32915
+ * 与「重新扫码」是两回事:走的是平台正规续期通道,用旧 cookie + 旧 refresh_token 换一份
32916
+ * 新 cookie 与**新的** refresh_token。两者必须整组替换 —— 旧 token 在第四步已被服务端
32917
+ * 作废(amagi 侧的说明见其 `platforms/bilibili/cookieRefresh.ts`)。失败时不动配置,
32918
+ * 用户可重新扫码。
32919
+ * @param e - 消息事件
32920
+ * @returns 固定 true,交回命令框架
32921
+ */
32922
+ var bilibiliRefreshLogin = async (e) => {
32923
+ const cookie = Config.amagi.cookies.bilibili;
32924
+ const refreshToken = Config.amagi.cookies.bilibiliRefreshToken;
32925
+ if (!cookie) {
32926
+ await e.reply("还没有配置 B站 cookie,请先执行 B站扫码登录", { reply: true });
32927
+ return true;
32928
+ }
32929
+ if (!refreshToken) {
32930
+ await e.reply("没有可用的续期令牌(refresh_token):请重新执行一次 B站扫码登录,新的登录会把令牌一并保存", { reply: true });
32931
+ return true;
32932
+ }
32933
+ const result = await getAmagiClient().bilibili.refreshCredential({
32934
+ cookie,
32935
+ refreshToken
32936
+ });
32937
+ if (!result.ok) {
32938
+ logger$1.warn(`[B站续期] 失败:kind=${result.error.kind} code=${result.error.code} ${result.error.message}`);
32939
+ await e.reply(`B站登录态续期失败:${result.error.message}`, { reply: true });
32940
+ return true;
32941
+ }
32942
+ await Config.Modify("amagi", "cookies.bilibili", result.credential.cookie);
32943
+ await Config.Modify("amagi", "cookies.bilibiliRefreshToken", result.credential.refreshToken);
32944
+ const reloaded = reloadAmagiConfig();
32945
+ logger$1.mark(`[B站续期] 新 cookie 与 refresh_token 已保存,Amagi Client ${reloaded ? "已重载" : "配置未变化"}`);
32946
+ await e.reply("B站登录态已续期,新的 cookie 与续期令牌已保存", { reply: true });
32947
+ return true;
32948
+ };
32351
32949
  //#endregion
32352
32950
  //#region src/platform/bilibili/live-status.ts
32353
32951
  var invalidLiveTimes = /* @__PURE__ */ new Set([
@@ -36248,10 +36846,11 @@ var DouYin = class DouYin extends Base {
36248
36846
  return true;
36249
36847
  }
36250
36848
  const room_data = JSON.parse(roomDataRaw);
36251
- const live_data = await this.amagi.douyin.fetcher.fetchLiveRoomInfo({
36849
+ const liveParams = {
36252
36850
  room_id: UserInfoData.data.user.room_id_str,
36253
36851
  web_rid: room_data.owner.web_rid
36254
- });
36852
+ };
36853
+ const live_data = await this.amagi.douyin.fetcher.fetchLiveRoomInfo(liveParams);
36255
36854
  const liveItem = live_data.data.data.data[0];
36256
36855
  const user = UserInfoData.data.user;
36257
36856
  const streamExtra = liveItem.stream_url?.extra;
@@ -36507,10 +37106,11 @@ async function processLiveStream(sec_uid, userinfo, item, targets, amagi) {
36507
37106
  return null;
36508
37107
  }
36509
37108
  const room_data = JSON.parse(UserInfoData.data.user.room_data);
36510
- const liveInfo = await amagi.douyin.fetcher.fetchLiveRoomInfo({
37109
+ const liveParams = {
36511
37110
  room_id: UserInfoData.data.user.room_id_str,
36512
37111
  web_rid: room_data.owner.web_rid
36513
- });
37112
+ };
37113
+ const liveInfo = await amagi.douyin.fetcher.fetchLiveRoomInfo(liveParams);
36514
37114
  if (!liveStatus.living) {
36515
37115
  logger$1.info(`用户 ${item.remark ?? sec_uid} 开播了`);
36516
37116
  return {
@@ -37593,6 +38193,191 @@ var fetchKuaishouData = async (type, opt) => {
37593
38193
  }
37594
38194
  };
37595
38195
  //#endregion
38196
+ //#region src/platform/kuaishou/login.ts
38197
+ /**
38198
+ * 等扫码的时限:撤回窗口是 120s,这里留 10s 余量给网络与程序时延 ——
38199
+ * 顶格写 120s 的话,超时那一刻二维码已经撤不掉了。
38200
+ */
38201
+ var SCAN_TIMEOUT$2 = 11e4;
38202
+ /** 等用户回「已完成验证」的时限(秒) */
38203
+ var VERIFY_INPUT_TIMEOUT$1 = 120;
38204
+ /** 登录凭证里需要确认下发的关键字段 */
38205
+ var REQUIRED_COOKIES$2 = ["passToken", "userId"];
38206
+ /**
38207
+ * 主动中断登录会话。
38208
+ *
38209
+ * v7 的 `watch` 只在调用方回调自己抛出时才 reject,所以从 `onChallenge` 里退出流程
38210
+ * 只能靠抛 —— 回调的返回类型是 `{ ticket }`,没有「放弃」这个取值。用一个专门的异常
38211
+ * 带上要发给用户的话,外层认出它就正常收尾。
38212
+ */
38213
+ var LoginAborted$2 = class extends Error {
38214
+ notice;
38215
+ constructor(notice) {
38216
+ super(notice);
38217
+ this.notice = notice;
38218
+ this.name = "LoginAborted";
38219
+ }
38220
+ };
38221
+ /**
38222
+ * 登录过程中发出的消息统一登记,结束时一起撤回,避免二维码留在群里
38223
+ * @param e - 消息事件
38224
+ * @returns 带 send / recallAll 的登记器
38225
+ */
38226
+ var createMessageTracker$2 = (e) => {
38227
+ const messageIds = [];
38228
+ return {
38229
+ /**
38230
+ * 发送并登记一条消息
38231
+ * @param message - 消息内容
38232
+ * @returns 发送结果
38233
+ */
38234
+ async send(message) {
38235
+ const sent = await e.reply(message, { reply: true });
38236
+ if (sent.messageId) messageIds.push(sent.messageId);
38237
+ return sent;
38238
+ },
38239
+ /** 撤回目前登记的全部消息 */
38240
+ async recallAll() {
38241
+ const pending = messageIds.splice(0, messageIds.length);
38242
+ await Promise.all(pending.map(async (id) => {
38243
+ try {
38244
+ await e.bot.recallMsg(e.contact, id);
38245
+ } catch (error) {
38246
+ logger$1.debug("[快手登录] 撤回消息失败:", error);
38247
+ }
38248
+ }));
38249
+ }
38250
+ };
38251
+ };
38252
+ /**
38253
+ * 渲染并发送登录二维码,同时落一份到临时目录方便排查
38254
+ * @param e - 消息事件
38255
+ * @param qrcode - 会话给出的二维码
38256
+ * @param tracker - 消息登记器
38257
+ */
38258
+ var sendQrcode$2 = async (e, qrcode, tracker) => {
38259
+ const rendered = await Render(e, "kuaishou/qrcodeImg", {
38260
+ share_url: qrcode.content,
38261
+ avatarUrl: await resolveTriggerAvatarUrl(e)
38262
+ });
38263
+ const base64Data = rendered[0]?.file;
38264
+ if (!base64Data) throw new Error("生成二维码图片失败");
38265
+ fs.writeFileSync(`${Common.tempDri.default}KuaishouLoginQrcode.png`, Buffer.from(base64Data.replace(/^base64:\/\//, ""), "base64"));
38266
+ await tracker.send(rendered);
38267
+ };
38268
+ /**
38269
+ * 把验证页交给真人,等他确认过完。
38270
+ *
38271
+ * amagi 的快手登录策略**只把验证页原样吐出来**(身份校验页 / 滑块),票据由服务端按
38272
+ * 会话解除 —— 没有可回填进下一次请求的字段。所以这里等的是「用户说过了」,随后让引擎
38273
+ * 自己接着轮询。
38274
+ *
38275
+ * **未真机验证**:amagi 侧标注二次验证之后的响应结构只有契约、没有真机扫码记录
38276
+ * (见其 `platforms/kuaishou/qrcodeLogin.ts` 模块头)。真机扫一次即可校正这一段。
38277
+ * @param e - 消息事件
38278
+ * @param url - 验证页地址(`challenge.imageUrl`)
38279
+ * @param tracker - 消息登记器
38280
+ * @throws {LoginAborted} 用户超时未确认
38281
+ */
38282
+ var waitForVerify$1 = async (e, url, tracker) => {
38283
+ await tracker.send(`此次登录需要人机验证\n请在浏览器里打开下面的地址完成验证:\n${url}\n完成后回复「已完成」` + (url === "" ? "\n(平台这次没有给出验证页地址,可改用手机快手重新扫码)" : ""));
38284
+ if (!await karin$1.ctx(e, {
38285
+ time: VERIFY_INPUT_TIMEOUT$1,
38286
+ reply: false,
38287
+ throwOnTimeout: false
38288
+ })) throw new LoginAborted$2("等待验证超时,登录已取消");
38289
+ };
38290
+ /**
38291
+ * 保存登录凭证并重载 Amagi 客户端
38292
+ * @param cookie - 完整登录 cookie
38293
+ */
38294
+ var persistCookie$2 = async (cookie) => {
38295
+ const present = new Set(cookie.split(";").map((pair) => pair.split("=")[0].trim()));
38296
+ const missing = REQUIRED_COOKIES$2.filter((name) => !present.has(name));
38297
+ if (missing.length > 0) logger$1.warn(`[快手登录] 以下字段未在本次登录中下发:${missing.join(", ")}`);
38298
+ await Config.Modify("amagi", "cookies.kuaishou", cookie);
38299
+ const reloaded = reloadAmagiConfig();
38300
+ logger$1.mark(`[快手登录] 登录凭证已保存,Amagi Client ${reloaded ? "已重载" : "配置未变化"}`);
38301
+ };
38302
+ /** 把会话的失败原因翻成给用户看的话 */
38303
+ var noticeOf$2 = (error) => {
38304
+ if (error.kind === "risk") return `登录请求被快手风控拦截:${error.message}\n请稍后再试`;
38305
+ if (error.code === "COOKIE_EXPIRED") return "二维码已失效,请重新发起登录";
38306
+ return `登录未完成:${error.message}`;
38307
+ };
38308
+ /**
38309
+ * 快手扫码登录。
38310
+ *
38311
+ * 协议与轮询编排都在 amagi 的 v7 登录会话里(`client.kuaishou.login.qrcode()`):
38312
+ * 本地生成 did、取码、两段 long-poll(先等扫码、再等手机确认)、退避、二次验证
38313
+ * 全部由引擎负责。这里只做三件与用户交互的事 —— 渲染二维码、把验证页转给人、
38314
+ * 把凭证落库。
38315
+ * @param e - 消息事件
38316
+ * @returns 固定 true,交回命令框架
38317
+ */
38318
+ var kuaishouLogin = async (e) => {
38319
+ const tracker = createMessageTracker$2(e);
38320
+ const controller = new AbortController();
38321
+ /** 自己中断时要发的话;有值说明不是引擎给出的失败 */
38322
+ let abortNotice;
38323
+ let scanned = false;
38324
+ let scanTimer;
38325
+ try {
38326
+ const outcome = await getAmagiClient().kuaishou.login.qrcode().watch({
38327
+ signal: controller.signal,
38328
+ timeoutMs: 29e4,
38329
+ onQrcode: async (qrcode) => {
38330
+ logger$1.mark(`[快手登录] 二维码已获取,有效期 ${qrcode.expiresInSec} 秒`);
38331
+ await sendQrcode$2(e, qrcode, tracker);
38332
+ scanTimer = setTimeout(() => {
38333
+ if (scanned) return;
38334
+ abortNotice = "登录超时!二维码已失效!";
38335
+ controller.abort();
38336
+ }, SCAN_TIMEOUT$2);
38337
+ },
38338
+ onScanned: async () => {
38339
+ if (scanned) return;
38340
+ scanned = true;
38341
+ clearTimeout(scanTimer);
38342
+ await tracker.recallAll();
38343
+ await tracker.send("二维码已扫描,请在手机上确认登录");
38344
+ },
38345
+ onChallenge: (async (challenge) => {
38346
+ scanned = true;
38347
+ clearTimeout(scanTimer);
38348
+ if (challenge.kind !== "captcha") throw new LoginAborted$2("这次登录要求短信验证,当前只支持人机验证,请改用手机快手重新扫码");
38349
+ await waitForVerify$1(e, challenge.imageUrl, tracker);
38350
+ return { ticket: "" };
38351
+ }),
38352
+ onSuccess: async (credential) => {
38353
+ await persistCookie$2(credential.cookie);
38354
+ }
38355
+ });
38356
+ await tracker.recallAll();
38357
+ if (outcome.ok) {
38358
+ await e.reply("登录成功!用户登录凭证已保存至配置", { reply: true });
38359
+ return true;
38360
+ }
38361
+ if (abortNotice) {
38362
+ await e.reply(abortNotice, { reply: true });
38363
+ return true;
38364
+ }
38365
+ logger$1.warn(`[快手登录] 会话结束于失败态: kind=${outcome.error.kind} code=${outcome.error.code}`);
38366
+ await e.reply(noticeOf$2(outcome.error), { reply: true });
38367
+ } catch (error) {
38368
+ await tracker.recallAll();
38369
+ if (error instanceof LoginAborted$2) {
38370
+ await e.reply(error.notice, { reply: true });
38371
+ return true;
38372
+ }
38373
+ logger$1.error("[快手登录] 登录流程出错:", error);
38374
+ await e.reply("登录过程出错,请查看控制台日志", { reply: true });
38375
+ } finally {
38376
+ clearTimeout(scanTimer);
38377
+ }
38378
+ return true;
38379
+ };
38380
+ //#endregion
37596
38381
  //#region src/platform/kuaishou/getID.ts
37597
38382
  /**
37598
38383
  *
@@ -37813,16 +38598,217 @@ var getXiaohongshuID = async (url, log = true) => {
37813
38598
  return result;
37814
38599
  };
37815
38600
  //#endregion
38601
+ //#region src/platform/xiaohongshu/login.ts
38602
+ /**
38603
+ * 等扫码的时限:撤回窗口是 120s,这里留 10s 余量给网络与程序时延 ——
38604
+ * 顶格写 120s 的话,超时那一刻二维码已经撤不掉了。
38605
+ */
38606
+ var SCAN_TIMEOUT$1 = 11e4;
38607
+ /** 等用户回「已完成验证」的时限(秒) */
38608
+ var VERIFY_INPUT_TIMEOUT = 120;
38609
+ /** 登录凭证里需要确认下发的关键字段 */
38610
+ var REQUIRED_COOKIES$1 = ["web_session"];
38611
+ /**
38612
+ * 主动中断登录会话。
38613
+ *
38614
+ * v7 的 `watch` 只在调用方回调自己抛出时才 reject,所以从 `onChallenge` 里退出流程
38615
+ * 只能靠抛 —— 回调的返回类型是 `{ ticket }`,没有「放弃」这个取值。用一个专门的异常
38616
+ * 带上要发给用户的话,外层认出它就正常收尾。
38617
+ */
38618
+ var LoginAborted$1 = class extends Error {
38619
+ notice;
38620
+ constructor(notice) {
38621
+ super(notice);
38622
+ this.notice = notice;
38623
+ this.name = "LoginAborted";
38624
+ }
38625
+ };
38626
+ /**
38627
+ * 登录过程中发出的消息统一登记,结束时一起撤回,避免二维码留在群里
38628
+ * @param e - 消息事件
38629
+ * @returns 带 send / recallAll 的登记器
38630
+ */
38631
+ var createMessageTracker$1 = (e) => {
38632
+ const messageIds = [];
38633
+ return {
38634
+ /**
38635
+ * 发送并登记一条消息
38636
+ * @param message - 消息内容
38637
+ * @returns 发送结果
38638
+ */
38639
+ async send(message) {
38640
+ const sent = await e.reply(message, { reply: true });
38641
+ if (sent.messageId) messageIds.push(sent.messageId);
38642
+ return sent;
38643
+ },
38644
+ /** 撤回目前登记的全部消息 */
38645
+ async recallAll() {
38646
+ const pending = messageIds.splice(0, messageIds.length);
38647
+ await Promise.all(pending.map(async (id) => {
38648
+ try {
38649
+ await e.bot.recallMsg(e.contact, id);
38650
+ } catch (error) {
38651
+ logger$1.debug("[小红书登录] 撤回消息失败:", error);
38652
+ }
38653
+ }));
38654
+ }
38655
+ };
38656
+ };
38657
+ /**
38658
+ * 渲染并发送登录二维码,同时落一份到临时目录方便排查
38659
+ * @param e - 消息事件
38660
+ * @param qrcode - 会话给出的二维码
38661
+ * @param tracker - 消息登记器
38662
+ */
38663
+ var sendQrcode$1 = async (e, qrcode, tracker) => {
38664
+ const rendered = await Render(e, "xiaohongshu/qrcodeImg", {
38665
+ share_url: qrcode.content,
38666
+ avatarUrl: await resolveTriggerAvatarUrl(e)
38667
+ });
38668
+ const base64Data = rendered[0]?.file;
38669
+ if (!base64Data) throw new Error("生成二维码图片失败");
38670
+ fs.writeFileSync(`${Common.tempDri.default}XiaohongshuLoginQrcode.png`, Buffer.from(base64Data.replace(/^base64:\/\//, ""), "base64"));
38671
+ await tracker.send(rendered);
38672
+ };
38673
+ /**
38674
+ * 把风控验证页交给真人,等他确认过完。
38675
+ *
38676
+ * 小红书的 redcaptcha 没有可回填进 `status` 调用的票据 —— 验证通过是**服务端按
38677
+ * `verifyUuid` 解除**的,解除后用同一套 cookie 重放 `status` 才会放行(amagi 策略的
38678
+ * `answer()` 只合并调用方带回的新 cookie,随后那次 poll 重放)。所以这里等的是
38679
+ * 「用户说过了」,接着让引擎自己继续轮询。
38680
+ *
38681
+ * **未真机验证**:amagi 侧标注扫码闭环无法在自动化环境端到端实测,需要真人用小红书
38682
+ * App 扫一次(见其 `platforms/xiaohongshu/qrcodeLogin.ts` 模块头)。
38683
+ * @param e - 消息事件
38684
+ * @param url - 验证页地址(`challenge.imageUrl`)
38685
+ * @param tracker - 消息登记器
38686
+ * @throws {LoginAborted} 用户超时未确认
38687
+ */
38688
+ var waitForVerify = async (e, url, tracker) => {
38689
+ await tracker.send(`此次登录触发了风控验证\n请在浏览器里打开下面的地址完成验证:\n${url}\n完成后回复「已完成」` + (url === "" ? "\n(平台这次没有给出验证页地址,可稍后重试)" : ""));
38690
+ if (!await karin$1.ctx(e, {
38691
+ time: VERIFY_INPUT_TIMEOUT,
38692
+ reply: false,
38693
+ throwOnTimeout: false
38694
+ })) throw new LoginAborted$1("等待验证超时,登录已取消");
38695
+ };
38696
+ /**
38697
+ * 保存登录凭证并重载 Amagi 客户端
38698
+ *
38699
+ * 只落 cookie:amagi 的 x-s 签名会在服务端要求时用会话密钥(webSsk)补 x6/x7,而扫码
38700
+ * 产物里没有这一项 —— 真机上如果登录态请求被 406 拒绝,再考虑把 webSsk 一并持久化。
38701
+ * @param cookie - 完整登录 cookie
38702
+ */
38703
+ var persistCookie$1 = async (cookie) => {
38704
+ const present = new Set(cookie.split(";").map((pair) => pair.split("=")[0].trim()));
38705
+ const missing = REQUIRED_COOKIES$1.filter((name) => !present.has(name));
38706
+ if (missing.length > 0) logger$1.warn(`[小红书登录] 以下字段未在本次登录中下发:${missing.join(", ")}`);
38707
+ await Config.Modify("amagi", "cookies.xiaohongshu", cookie);
38708
+ const reloaded = reloadAmagiConfig();
38709
+ logger$1.mark(`[小红书登录] 登录凭证已保存,Amagi Client ${reloaded ? "已重载" : "配置未变化"}`);
38710
+ };
38711
+ /** 把会话的失败原因翻成给用户看的话 */
38712
+ var noticeOf$1 = (error) => {
38713
+ if (error.kind === "risk") return `登录请求被小红书风控拦截:${error.message}\n请稍后再试`;
38714
+ if (error.code === "COOKIE_EXPIRED") return "二维码已失效,请重新发起登录";
38715
+ return `登录未完成:${error.message}`;
38716
+ };
38717
+ /**
38718
+ * 小红书扫码登录。
38719
+ *
38720
+ * 协议与轮询编排都在 amagi 的 v7 登录会话里(`client.xiaohongshu.login.qrcode()`):
38721
+ * 游客冷启动(a1 / webId / SSK)→ 取码 → 轮询 codeStatus → 换正式 web_session,
38722
+ * 以及风控二次验证的挑战中转都由引擎负责。这里只做三件与用户交互的事 ——
38723
+ * 渲染二维码、把验证页转给人、把凭证落库。
38724
+ * @param e - 消息事件
38725
+ * @returns 固定 true,交回命令框架
38726
+ */
38727
+ var xiaohongshuLogin = async (e) => {
38728
+ const tracker = createMessageTracker$1(e);
38729
+ const controller = new AbortController();
38730
+ /** 自己中断时要发的话;有值说明不是引擎给出的失败 */
38731
+ let abortNotice;
38732
+ let scanned = false;
38733
+ let scanTimer;
38734
+ try {
38735
+ const outcome = await getAmagiClient().xiaohongshu.login.qrcode().watch({
38736
+ signal: controller.signal,
38737
+ timeoutMs: 29e4,
38738
+ onQrcode: async (qrcode) => {
38739
+ logger$1.mark(`[小红书登录] 二维码已获取,有效期 ${qrcode.expiresInSec} 秒`);
38740
+ await sendQrcode$1(e, qrcode, tracker);
38741
+ scanTimer = setTimeout(() => {
38742
+ if (scanned) return;
38743
+ abortNotice = "登录超时!二维码已失效!";
38744
+ controller.abort();
38745
+ }, SCAN_TIMEOUT$1);
38746
+ },
38747
+ onScanned: async () => {
38748
+ if (scanned) return;
38749
+ scanned = true;
38750
+ clearTimeout(scanTimer);
38751
+ await tracker.recallAll();
38752
+ await tracker.send("二维码已扫描,请在手机上确认登录");
38753
+ },
38754
+ onChallenge: (async (challenge) => {
38755
+ scanned = true;
38756
+ clearTimeout(scanTimer);
38757
+ if (challenge.kind !== "captcha") throw new LoginAborted$1("这次登录要求短信验证,当前只支持风控验证,请稍后重试");
38758
+ await waitForVerify(e, challenge.imageUrl, tracker);
38759
+ return { ticket: "" };
38760
+ }),
38761
+ onSuccess: async (credential) => {
38762
+ await persistCookie$1(credential.cookie);
38763
+ }
38764
+ });
38765
+ await tracker.recallAll();
38766
+ if (outcome.ok) {
38767
+ await e.reply("登录成功!用户登录凭证已保存至配置", { reply: true });
38768
+ return true;
38769
+ }
38770
+ if (abortNotice) {
38771
+ await e.reply(abortNotice, { reply: true });
38772
+ return true;
38773
+ }
38774
+ logger$1.warn(`[小红书登录] 会话结束于失败态: kind=${outcome.error.kind} code=${outcome.error.code}`);
38775
+ await e.reply(noticeOf$1(outcome.error), { reply: true });
38776
+ } catch (error) {
38777
+ await tracker.recallAll();
38778
+ if (error instanceof LoginAborted$1) {
38779
+ await e.reply(error.notice, { reply: true });
38780
+ return true;
38781
+ }
38782
+ logger$1.error("[小红书登录] 登录流程出错:", error);
38783
+ await e.reply("登录过程出错,请查看控制台日志", { reply: true });
38784
+ } finally {
38785
+ clearTimeout(scanTimer);
38786
+ }
38787
+ return true;
38788
+ };
38789
+ //#endregion
37816
38790
  //#region src/platform/xiaohongshu/comments.ts
38791
+ /** 一次最多同时打几条一级评论的子评论请求 */
38792
+ var SUB_COMMENT_BATCH = 5;
37817
38793
  /**
37818
38794
  * 处理小红书评论数据。
37819
38795
  *
37820
38796
  * 这里直接输出结构化评论 JSON,正文部分使用 richtext 文档,避免后端拼接 HTML。
38797
+ *
38798
+ * 配了 ck 且 `subCommentLimit > 2` 时,会对带子评论的一级评论逐条请求子评论接口
38799
+ * (见 {@link shouldFetchSubComments}),取回后与响应自带的预览合并去重;
38800
+ * 否则完全用预览,一个额外请求都不发。
37821
38801
  */
37822
- var xiaohongshuComments = (data, emojiData) => {
38802
+ var xiaohongshuComments = async (data, emojiData, note) => {
37823
38803
  const rawComments = data?.data?.comments;
37824
38804
  if (!Array.isArray(rawComments) || rawComments.length === 0) return [];
37825
- return rawComments.map((comment) => {
38805
+ const ordered = [...rawComments].sort((a, b) => Number(normalizeTagNames(b.show_tags).includes("user_top")) - Number(normalizeTagNames(a.show_tags).includes("user_top"))).slice(0, Config.xiaohongshu.numcomment);
38806
+ const fetchEnabled = shouldFetchSubComments();
38807
+ const topLevelIds = new Set(ordered.map((comment) => comment.id));
38808
+ const comments = [];
38809
+ for (let index = 0; index < ordered.length; index += SUB_COMMENT_BATCH) comments.push(...await Promise.all(ordered.slice(index, index + SUB_COMMENT_BATCH).map(async (comment) => {
38810
+ const preview = Array.isArray(comment.sub_comments) ? comment.sub_comments : [];
38811
+ const fetched = fetchEnabled ? await fetchSubComments(comment, note) : [];
37826
38812
  const showTags = normalizeTagNames(comment.show_tags);
37827
38813
  return {
37828
38814
  id: comment.id,
@@ -37835,12 +38821,65 @@ var xiaohongshuComments = (data, emojiData) => {
37835
38821
  liked: comment.liked,
37836
38822
  pictures: Array.isArray(comment.pictures) ? comment.pictures : [],
37837
38823
  sub_comment_count: comment.sub_comment_count,
37838
- sub_comments: buildXiaohongshuSubComments(comment.sub_comments, emojiData),
38824
+ sub_comments: buildXiaohongshuSubComments(mergeSubComments(preview, fetched, topLevelIds), emojiData),
37839
38825
  show_tags: showTags,
37840
38826
  at_users: normalizeAtUsers(comment.at_users).map((item) => item.text),
37841
38827
  status: comment.status
37842
38828
  };
37843
- }).sort((a, b) => Number(b.show_tags.includes("user_top")) - Number(a.show_tags.includes("user_top"))).slice(0, Config.xiaohongshu.numcomment);
38829
+ })));
38830
+ return comments;
38831
+ };
38832
+ /**
38833
+ * 要不要请求子评论接口。两个条件缺一不可:
38834
+ *
38835
+ * 1. **配了小红书 ck** —— 平台不允许游客拿二级评论,没 ck 请求必然失败;
38836
+ * 2. **`subCommentLimit > 2`** —— 一级评论响应自带的预览就是每条最多 2 条,
38837
+ * 配置 ≤ 2 时请求回来也不会多出任何东西,默认值 2 即「不请求」。
38838
+ */
38839
+ var shouldFetchSubComments = () => Config.xiaohongshu.subCommentLimit > 2 && Config.amagi.cookies.xiaohongshu.trim() !== "";
38840
+ /**
38841
+ * 取一条一级评论下的子评论(楼中楼)。
38842
+ *
38843
+ * 失败只吞掉这一条并记 debug —— 评论图是尽力而为的展示,一条楼中楼取不到
38844
+ * 不该带崩整张图。本来就没有子评论的(`sub_comment_count` 为 0)直接跳过。
38845
+ * @param comment - 一级评论
38846
+ * @param note - 笔记定位信息
38847
+ * @returns 接口取回的子评论;失败或没有子评论时为空数组
38848
+ */
38849
+ var fetchSubComments = async (comment, note) => {
38850
+ if (Number(comment.sub_comment_count) <= 0) return [];
38851
+ try {
38852
+ const fetched = (await xiaohongshuFetcher.fetchSubComments({
38853
+ note_id: note.note_id,
38854
+ root_comment_id: comment.id,
38855
+ xsec_token: note.xsec_token,
38856
+ number: Config.xiaohongshu.subCommentLimit
38857
+ })).data?.data?.comments;
38858
+ return Array.isArray(fetched) ? fetched : [];
38859
+ } catch (error) {
38860
+ logger$1.debug(`[xiaohongshu] 取子评论失败(${comment.id}): ${error}`);
38861
+ return [];
38862
+ }
38863
+ };
38864
+ /**
38865
+ * 合并预览与接口取回的子评论:**预览在前,新增的追加在后**。
38866
+ *
38867
+ * 两类重复按 id 排除:① 同一条一级评论自带的那几条预览;② 一级评论列表里出现过的 id
38868
+ * —— 楼中楼偶尔会把同一条评论也挂在顶层,两处都渲染就成了重复条目。
38869
+ * @param preview - 一级评论响应自带的子评论
38870
+ * @param fetched - 子评论接口取回的列表
38871
+ * @param topLevelIds - 本次渲染的一级评论 id 集合
38872
+ * @returns 去重后的子评论列表
38873
+ */
38874
+ var mergeSubComments = (preview, fetched, topLevelIds) => {
38875
+ const seen = /* @__PURE__ */ new Set([...topLevelIds, ...preview.map((item) => item.id)]);
38876
+ const merged = [...preview];
38877
+ for (const item of fetched) {
38878
+ if (!item?.id || seen.has(item.id)) continue;
38879
+ seen.add(item.id);
38880
+ merged.push(item);
38881
+ }
38882
+ return merged;
37844
38883
  };
37845
38884
  var buildXiaohongshuSubComments = (subComments, emojiData) => {
37846
38885
  if (!Array.isArray(subComments)) return [];
@@ -38007,7 +39046,10 @@ var Xiaohongshu = class extends Base {
38007
39046
  const CommentData = await this.fetchConfiguredNoteComments(data);
38008
39047
  if (!CommentData.data.comments || CommentData.data.comments.length === 0) await this.e.reply("这个笔记没有评论 ~");
38009
39048
  else {
38010
- const processedComments = xiaohongshuComments(CommentData, formattedEmojis);
39049
+ const processedComments = await xiaohongshuComments(CommentData, formattedEmojis, {
39050
+ note_id: data.note_id,
39051
+ xsec_token: data.xsec_token
39052
+ });
38011
39053
  const commentListImg = await Render(this.e, "xiaohongshu/comment", {
38012
39054
  Type: NoteData.data.data.items[0].note_card.video ? "视频" : "图文",
38013
39055
  CommentsData: processedComments,
@@ -38315,8 +39357,11 @@ var XiaohongshuEmoji = (data) => {
38315
39357
  };
38316
39358
  //#endregion
38317
39359
  //#region src/platform/douyin/login.ts
38318
- /** 等待用户扫码的时限上限,与消息可撤回窗口(2 分钟)对齐 */
38319
- var SCAN_TIMEOUT = 12e4;
39360
+ /**
39361
+ * 等扫码的时限:撤回窗口是 120s,这里留 10s 余量给网络与程序时延 ——
39362
+ * 顶格写 120s 的话,超时那一刻二维码已经撤不掉了。
39363
+ */
39364
+ var SCAN_TIMEOUT = 11e4;
38320
39365
  /** 等待用户回填短信验证码的时限(秒) */
38321
39366
  var CODE_INPUT_TIMEOUT = 90;
38322
39367
  /** 短信验证码允许的重试次数 */
@@ -38466,7 +39511,7 @@ var douyinLogin = async (e) => {
38466
39511
  try {
38467
39512
  const outcome = await getAmagiClient().douyin.login.qrcode().watch({
38468
39513
  signal: controller.signal,
38469
- timeoutMs: 3e5,
39514
+ timeoutMs: 29e4,
38470
39515
  onQrcode: async (qrcode) => {
38471
39516
  logger$1.mark(`[抖音登录] 二维码已获取,有效期 ${qrcode.expiresInSec} 秒`);
38472
39517
  await sendQrcode(e, qrcode, tracker);
@@ -38537,6 +39582,18 @@ var handleDouyinLogin = wrapWithErrorHandler(async (e) => {
38537
39582
  await douyinLogin(e);
38538
39583
  return true;
38539
39584
  }, { businessName: "抖音登录" });
39585
+ var handleKuaishouLogin = wrapWithErrorHandler(async (e) => {
39586
+ await kuaishouLogin(e);
39587
+ return true;
39588
+ }, { businessName: "快手登录" });
39589
+ var handleXiaohongshuLogin = wrapWithErrorHandler(async (e) => {
39590
+ await xiaohongshuLogin(e);
39591
+ return true;
39592
+ }, { businessName: "小红书登录" });
39593
+ var handleBilibiliRefresh = wrapWithErrorHandler(async (e) => {
39594
+ await bilibiliRefreshLogin(e);
39595
+ return true;
39596
+ }, { businessName: "B站登录态续期" });
38540
39597
  var handlTestWrapWithErrorHandler = wrapWithErrorHandler(async (_e) => {
38541
39598
  logger$1.trace("测试跟踪日志");
38542
39599
  logger$1.debug("测试调试日志");
@@ -38555,6 +39612,18 @@ var dylogin = karin.command(/^#?(kkk)?抖音(扫码)?登录$/, handleDouyinLogin
38555
39612
  perm: Config.douyin.loginPerm,
38556
39613
  name: "kkk-ck管理"
38557
39614
  });
39615
+ var ksLogin = karin.command(/^#?(kkk)?快手(扫码)?登录$/, handleKuaishouLogin, {
39616
+ perm: Config.kuaishou.loginPerm,
39617
+ name: "kkk-ck管理"
39618
+ });
39619
+ var xhsLogin = karin.command(/^#?(kkk)?小红书(扫码)?登录$/, handleXiaohongshuLogin, {
39620
+ perm: Config.xiaohongshu.loginPerm,
39621
+ name: "kkk-ck管理"
39622
+ });
39623
+ var biRefreshLogin = karin.command(/^#?(kkk)?B站(登录态)?(续期|刷新)$/i, handleBilibiliRefresh, {
39624
+ perm: Config.bilibili.loginPerm,
39625
+ name: "kkk-ck管理"
39626
+ });
38558
39627
  var testWrapWithErrorHandler = process.env.NODE_ENV === "development" ? karin.command(/^#?(kkk)?\s*测试\s*错误捕获$/i, handlTestWrapWithErrorHandler, {
38559
39628
  perm: Config.bilibili.loginPerm,
38560
39629
  name: "kkk-测试命令-错误捕获"
@@ -40115,10 +41184,11 @@ var handleTestPush = wrapWithErrorHandler(async (e) => {
40115
41184
  return true;
40116
41185
  }
40117
41186
  const room_data = JSON.parse(user.room_data);
40118
- const liveInfo = await douyinFetcher.fetchLiveRoomInfo({
41187
+ const liveParams = {
40119
41188
  room_id: user.room_id_str,
40120
41189
  web_rid: room_data.owner.web_rid
40121
- });
41190
+ };
41191
+ const liveInfo = await douyinFetcher.fetchLiveRoomInfo(liveParams);
40122
41192
  images = await renderLiveImage({
40123
41193
  e,
40124
41194
  Detail_Data: {
@@ -41837,4 +42907,4 @@ mkdirSync(`${karinPathBase}/${Root.pluginName}/data`);
41837
42907
  mkdirSync(Common.tempDri.images);
41838
42908
  mkdirSync(Common.tempDri.video);
41839
42909
  //#endregion
41840
- export { reloadAmagiConfig as $, templates as A, StatisticsDBBase as B, help as C, removeOldFiles as D, dylogin as E, getBilibiliDB as F, AmagiError as G, DouyinDBBase as H, getDouyinDB as I, douyinFetcher as J, SOFT_ERROR_CODES as K, getStatisticsDB as L, bilibiliDBInstance as M, cleanOldDynamicCache as N, task as O, douyinDBInstance as P, registerAmagiReloadListener as Q, initAllDatabases as R, changelog as S, biLogin as T, BilibiliDBBase as U, resolveMetricBucket as V, AmagiBase as W, isSoftFailure as X, getAmagiClient as Y, kuaishouFetcher as Z, douyinPushList as _, bilibiliAPP as a, setbiliPush as b, prefix as c, globalStatistics as d, softFetch as et, groupStatistics as f, douyinPush as g, bilibiliPushList as h, update as i, webConfig as j, testWrapWithErrorHandler as k, xiaohongshuAPP as l, bilibiliPush as m, kkkUpdateCommand as n, Root as nt, douyinAPP as o, qrLogin as p, bilibiliFetcher as q, kkkUpdateTest as r, kuaishouAPP as s, kkkUpdate as t, xiaohongshuFetcher as tt, testPush as u, forcePush as v, version as w, setdyPush as x, globalIgnore as y, METRIC_BUCKETS as z };
42910
+ export { isSoftFailure as $, task as A, getStatisticsDB as B, help as C, dylogin as D, biRefreshLogin as E, bilibiliDBInstance as F, DouyinDBBase as G, METRIC_BUCKETS as H, cleanOldDynamicCache as I, AmagiError as J, BilibiliDBBase as K, douyinDBInstance as L, xhsLogin as M, templates as N, ksLogin as O, webConfig as P, getAmagiClient as Q, getBilibiliDB as R, changelog as S, biLogin as T, StatisticsDBBase as U, initAllDatabases as V, resolveMetricBucket as W, bilibiliFetcher as X, SOFT_ERROR_CODES as Y, douyinFetcher as Z, douyinPushList as _, bilibiliAPP as a, Root as at, setbiliPush as b, prefix as c, globalStatistics as d, kuaishouFetcher as et, groupStatistics as f, douyinPush as g, bilibiliPushList as h, update as i, xiaohongshuFetcher as it, testWrapWithErrorHandler as j, removeOldFiles as k, xiaohongshuAPP as l, bilibiliPush as m, kkkUpdateCommand as n, reloadAmagiConfig as nt, douyinAPP as o, qrLogin as p, AmagiBase as q, kkkUpdateTest as r, softFetch as rt, kuaishouAPP as s, kkkUpdate as t, registerAmagiReloadListener as tt, testPush as u, forcePush as v, version as w, setdyPush as x, globalIgnore as y, getDouyinDB as z };