karin-plugin-kkk 2.44.1 → 2.45.0-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.
@@ -6,7 +6,7 @@ import { createRequire } from "node:module";
6
6
  import fs from "node:fs";
7
7
  import path, { resolve } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
- import karin, { BOT_CONNECT, app, authMiddleware, checkPkgUpdate, common, config, createBadRequestResponse, createNotFoundResponse, createServerErrorResponse, createSuccessResponse, db, defineConfig, ffmpeg, ffprobe, hooks, isDocker, karin as karin$1, karinPathHtml, logger, logs, mkdirSync, parseChangelog, range, render, restart, segment, updatePkg, watch } from "node-karin";
9
+ import karin, { BOT_CONNECT, app, authMiddleware, checkPkgUpdate, common, config, createBadRequestResponse, createNotFoundResponse, createServerErrorResponse, createSuccessResponse, db, defineConfig, exec, ffmpeg, ffprobe, getPkgVersion, getRemotePkgVersion, hooks, isDocker, karin as karin$1, karinPathHtml, logger, logs, mkdirSync, parseChangelog, range, render, restart, segment, updatePkg, watch } from "node-karin";
10
10
  import crypto from "node:crypto";
11
11
  import { deflateSync, inflateSync } from "node:zlib";
12
12
  import express from "node-karin/express";
@@ -7433,6 +7433,26 @@ var resolveEmojiSrc = (filename) => {
7433
7433
  };
7434
7434
  setUnicodeEmojiSrcResolver(resolveEmojiSrc);
7435
7435
  //#endregion
7436
+ //#region src/module/utils/releaseChannel.ts
7437
+ var parseReleaseChannel = (version) => {
7438
+ const v = version.trim().replace(/^[vV]/, "").split("+", 1)[0];
7439
+ const match = /^\d+\.\d+\.\d+(?:-(.+))?$/.exec(v);
7440
+ if (!match) return "Canary";
7441
+ if (!match[1]) return "Stable";
7442
+ const pre = match[1].split(".");
7443
+ if (pre[0] === "rc") return "Rc";
7444
+ if (pre[0] === "canary") return "Canary";
7445
+ if (pre[0] === "beta" && pre.length <= 2) return "Beta";
7446
+ return "Canary";
7447
+ };
7448
+ /** 渠道显示名(中文) */
7449
+ var RELEASE_CHANNEL_LABEL = {
7450
+ Stable: "正式版",
7451
+ Beta: "测试版",
7452
+ Rc: "预览版",
7453
+ Canary: "金丝雀"
7454
+ };
7455
+ //#endregion
7436
7456
  //#region src/module/utils/semver.ts
7437
7457
  /**
7438
7458
  * 语义化版本比较
@@ -7832,7 +7852,7 @@ var Render = async (event, path, data) => {
7832
7852
  plugin: "karin-plugin",
7833
7853
  pluginName: "kkk",
7834
7854
  pluginVersion: Root.pluginVersion,
7835
- releaseType: /^\d+\.\d+\.\d+$/.test(Root.pluginVersion) ? "Stable" : "Preview",
7855
+ releaseType: parseReleaseChannel(Root.pluginVersion),
7836
7856
  poweredBy: "Karin",
7837
7857
  frameworkVersion: Root.karinVersion,
7838
7858
  hasUpdate
@@ -11413,6 +11433,15 @@ var ViteLogo = ({ className = "w-auto h-12" }) => /* @__PURE__ */ (0, import_jsx
11413
11433
  * @param props 组件属性
11414
11434
  * @returns JSX元素
11415
11435
  */
11436
+ /**
11437
+ * 渠道徽章文案:ctx.version.releaseType → 中文渠道名(未知值回显原值兜底)
11438
+ */
11439
+ var RELEASE_TYPE_LABEL = {
11440
+ Stable: "正式版",
11441
+ Beta: "测试版",
11442
+ Rc: "预览版",
11443
+ Canary: "金丝雀"
11444
+ };
11416
11445
  var DefaultLayout = ({ children, ctx, className = "", style = {} }) => {
11417
11446
  const useDarkTheme = isDark(ctx);
11418
11447
  const { version } = ctx;
@@ -11480,21 +11509,21 @@ var DefaultLayout = ({ children, ctx, className = "", style = {} }) => {
11480
11509
  strokeWidth: 3,
11481
11510
  className: "w-4 h-4"
11482
11511
  }),
11483
- !version.hasUpdate && version.releaseType === "Preview" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, {
11512
+ !version.hasUpdate && (version.releaseType === "Beta" || version.releaseType === "Rc") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, {
11484
11513
  strokeWidth: 3,
11485
11514
  className: "w-4 h-4 text-warning"
11486
11515
  }),
11487
- !version.hasUpdate && version.releaseType !== "Stable" && version.releaseType !== "Preview" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Info, {
11516
+ !version.hasUpdate && version.releaseType === "Canary" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Info, {
11488
11517
  strokeWidth: 3,
11489
11518
  className: "w-4 h-4"
11490
11519
  }),
11491
11520
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
11492
- className: cn(version.hasUpdate && "text-success", !version.hasUpdate && version.releaseType === "Preview" && "text-warning"),
11493
- children: version.hasUpdate ? "有可用更新" : version.releaseType
11521
+ className: cn(version.hasUpdate && "text-success", !version.hasUpdate && (version.releaseType === "Beta" || version.releaseType === "Rc") && "text-warning"),
11522
+ children: version.hasUpdate ? "有可用更新" : RELEASE_TYPE_LABEL[version.releaseType] ?? version.releaseType
11494
11523
  })
11495
11524
  ]
11496
11525
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
11497
- className: cn("text-5xl font-bold tracking-wide", version.hasUpdate && "text-success", !version.hasUpdate && version.releaseType === "Preview" && "text-warning"),
11526
+ className: cn("text-5xl font-bold tracking-wide", version.hasUpdate && "text-success", !version.hasUpdate && (version.releaseType === "Beta" || version.releaseType === "Rc") && "text-warning"),
11498
11527
  children: ["v", version.pluginVersion]
11499
11528
  })]
11500
11529
  }),
@@ -23944,7 +23973,7 @@ var qrlogin_default = defineTemplate({
23944
23973
  var RuntimeReport = import_react.memo((props) => {
23945
23974
  const { data } = props;
23946
23975
  const dark = isDark(props.ctx);
23947
- const releaseLabel = data.identity.releaseType.toLowerCase() === "stable" ? "正式版" : data.identity.releaseType.toLowerCase() === "preview" ? "预览版" : data.identity.releaseType;
23976
+ const releaseLabel = RELEASE_TYPE_LABEL[data.identity.releaseType] ?? data.identity.releaseType;
23948
23977
  const buildStatus = data.build.state === "matched" ? {
23949
23978
  label: "构建信息一致",
23950
23979
  icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleCheck, { className: "h-8 w-8" }),
@@ -24412,6 +24441,463 @@ var runtime_default = defineTemplate({
24412
24441
  component: RuntimeReport
24413
24442
  });
24414
24443
  //#endregion
24444
+ //#region ktr/template/other/updateHelp/components/updateHelp.tsx
24445
+ /**
24446
+ * `#kkk更新` 用法面板(kkk-design 系统 B × Apple 排版原则)。
24447
+ *
24448
+ * 主情绪色为深青(teal)——紫罗兰系在 runtime/更新日志等模板已被占用,避开色彩同质化。
24449
+ * 构图:横向 Hero 带(左主标题、右当前安装版本,版本号按长度分级缩放防溢出)→
24450
+ * 表格式渠道清单(固定列宽 grid,命令列纵向对齐)→ 命令→说明单行对。
24451
+ * 状态为完整 semver 比较:有更新 / 已是最新 / 低于当前版本三分,而非笼统布尔。
24452
+ * 噪点沿用 runtime 的高对比离散颗粒(深色 0.16 / 浅色 0.12)。
24453
+ */
24454
+ /** 完整 semver 比较(core 数字 + prerelease 段,规则与 core 的 semver.ts 一致) */
24455
+ var semverCompare = (a, b) => {
24456
+ const parse = (v) => {
24457
+ const [core, pre] = v.trim().replace(/^[vV]/, "").split("+")[0].split("-");
24458
+ const [maj, min, pat] = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
24459
+ return {
24460
+ maj,
24461
+ min,
24462
+ pat,
24463
+ pre: pre ? pre.split(".") : []
24464
+ };
24465
+ };
24466
+ const p = parse(a);
24467
+ const q = parse(b);
24468
+ if (p.maj !== q.maj) return p.maj - q.maj;
24469
+ if (p.min !== q.min) return p.min - q.min;
24470
+ if (p.pat !== q.pat) return p.pat - q.pat;
24471
+ if (p.pre.length === 0 && q.pre.length === 0) return 0;
24472
+ if (p.pre.length === 0) return 1;
24473
+ if (q.pre.length === 0) return -1;
24474
+ const len = Math.min(p.pre.length, q.pre.length);
24475
+ for (let i = 0; i < len; i++) {
24476
+ const an = /^\d+$/.test(p.pre[i]);
24477
+ const bn = /^\d+$/.test(q.pre[i]);
24478
+ if (an !== bn) return an ? -1 : 1;
24479
+ if (p.pre[i] !== q.pre[i]) {
24480
+ if (an && bn) return Number(p.pre[i]) - Number(q.pre[i]);
24481
+ return p.pre[i] > q.pre[i] ? 1 : -1;
24482
+ }
24483
+ }
24484
+ return p.pre.length === q.pre.length ? 0 : p.pre.length > q.pre.length ? 1 : -1;
24485
+ };
24486
+ /**
24487
+ * Hero 版本号布局:
24488
+ * - 短版本(纯 semver,≤6 字符)与主标题同行横排,150px;
24489
+ * - 长版本(canary 等 prerelease)独占整行堆叠在标题下方,字号按「可用宽 1180px ÷
24490
+ * 等宽字符宽 0.6em」自适应,上限仍 150px——标题与版本号不再挤同一行。
24491
+ */
24492
+ var heroStacked = (version) => version.trim().replace(/^[vV]/, "").length > 6;
24493
+ var heroVersionSize = (version) => {
24494
+ const len = version.trim().replace(/^[vV]/, "").length;
24495
+ if (len <= 6) return 150;
24496
+ return Math.max(72, Math.min(150, Math.floor(1180 / (len * .6))));
24497
+ };
24498
+ //#endregion
24499
+ //#region ktr/template/other/updateHelp/index.tsx
24500
+ var updateHelp_default = defineTemplate({
24501
+ name: "更新渠道面板",
24502
+ description: "#kkk更新 用法与各渠道可用版本一览",
24503
+ component: import_react.memo((props) => {
24504
+ const { data } = props;
24505
+ const palette = isDark(props.ctx) ? {
24506
+ background: "#081416",
24507
+ glowPrimary: "rgba(45, 212, 191, 0.28)",
24508
+ glowSecondary: "rgba(56, 130, 246, 0.14)",
24509
+ glowWarm: "rgba(250, 204, 21, 0.12)",
24510
+ bgWord: "#99f6e4",
24511
+ ink: "#effefd",
24512
+ muted: "rgba(239, 254, 253, 0.62)",
24513
+ faint: "rgba(239, 254, 253, 0.36)",
24514
+ pillBg: "rgba(45, 212, 191, 0.12)",
24515
+ pillBorder: "rgba(94, 234, 212, 0.32)",
24516
+ pillText: "#5eead4",
24517
+ accentText: "#5eead4",
24518
+ success: "#4ade80",
24519
+ warning: "#fbbf24",
24520
+ noiseOpacity: .16
24521
+ } : {
24522
+ background: "#effaf8",
24523
+ glowPrimary: "rgba(20, 184, 166, 0.26)",
24524
+ glowSecondary: "rgba(125, 211, 252, 0.40)",
24525
+ glowWarm: "rgba(250, 204, 21, 0.26)",
24526
+ bgWord: "#0c3b38",
24527
+ ink: "#0c3b38",
24528
+ muted: "rgba(12, 59, 56, 0.68)",
24529
+ faint: "rgba(12, 59, 56, 0.42)",
24530
+ pillBg: "rgba(20, 184, 166, 0.10)",
24531
+ pillBorder: "rgba(20, 184, 166, 0.30)",
24532
+ pillText: "#0f766e",
24533
+ accentText: "#0d9488",
24534
+ success: "#15803d",
24535
+ warning: "#8a5a06",
24536
+ noiseOpacity: .12
24537
+ };
24538
+ /**
24539
+ * 渠道状态:文案按具体状态区分(有更新 / 已是最新 / 低于当前版本 / 暂未发布 / 获取失败),
24540
+ * 颜色只分两档 —— 可升级(success)与置灰(faint):非可升级状态一律不引导行动。
24541
+ * active 供行内所有元素同步置灰。
24542
+ */
24543
+ const statusView = (channel) => {
24544
+ if (channel.status === "missing") return {
24545
+ dot: palette.faint,
24546
+ text: "该渠道暂未发布",
24547
+ active: false
24548
+ };
24549
+ if (channel.status === "error") return {
24550
+ dot: palette.faint,
24551
+ text: "获取失败",
24552
+ active: false
24553
+ };
24554
+ if (channel.status === "canary") return channel.hasUpdate ? {
24555
+ dot: palette.success,
24556
+ text: "有新构建可升级",
24557
+ active: true
24558
+ } : {
24559
+ dot: palette.accentText,
24560
+ text: "main 分支构建",
24561
+ active: true
24562
+ };
24563
+ const order = semverCompare(channel.version ?? "0.0.0", data.currentVersion);
24564
+ if (order > 0) return {
24565
+ dot: palette.success,
24566
+ text: "有更新可升级",
24567
+ active: true
24568
+ };
24569
+ if (order === 0) return {
24570
+ dot: palette.faint,
24571
+ text: "已是最新",
24572
+ active: false
24573
+ };
24574
+ return {
24575
+ dot: palette.faint,
24576
+ text: "低于当前版本",
24577
+ active: false
24578
+ };
24579
+ };
24580
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DefaultLayout, {
24581
+ ...props,
24582
+ className: "relative min-h-440 overflow-hidden",
24583
+ style: { backgroundColor: palette.background },
24584
+ children: [
24585
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24586
+ className: "pointer-events-none absolute inset-0 z-0 overflow-hidden",
24587
+ children: [
24588
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
24589
+ className: "absolute -left-130 -top-120 h-337.5 w-375 rounded-full blur-[230px]",
24590
+ style: { background: `radial-gradient(ellipse at center, ${palette.glowPrimary} 0%, transparent 70%)` }
24591
+ }),
24592
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
24593
+ className: "absolute -right-130 top-[36%] h-312.5 w-300 rounded-full blur-[240px]",
24594
+ style: { background: `radial-gradient(ellipse at center, ${palette.glowSecondary} 0%, transparent 72%)` }
24595
+ }),
24596
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
24597
+ className: "absolute -bottom-120 -left-80 h-287.5 w-337.5 rounded-full blur-[260px]",
24598
+ style: { background: `radial-gradient(ellipse at center, ${palette.glowWarm} 0%, transparent 72%)` }
24599
+ }),
24600
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
24601
+ className: "pointer-events-none absolute right-14 top-8 select-none font-bold leading-none tracking-[-0.02em]",
24602
+ style: {
24603
+ fontSize: "220px",
24604
+ opacity: .04,
24605
+ color: palette.bgWord
24606
+ },
24607
+ children: "UPDATE"
24608
+ })
24609
+ ]
24610
+ }),
24611
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
24612
+ className: "pointer-events-none absolute inset-0 z-0",
24613
+ style: { opacity: palette.noiseOpacity },
24614
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
24615
+ className: "h-full w-full",
24616
+ xmlns: "http://www.w3.org/2000/svg",
24617
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("filter", {
24618
+ id: "updateHelpNoise",
24619
+ x: "0%",
24620
+ y: "0%",
24621
+ width: "100%",
24622
+ height: "100%",
24623
+ children: [
24624
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feTurbulence", {
24625
+ type: "fractalNoise",
24626
+ baseFrequency: "0.8",
24627
+ numOctaves: "1",
24628
+ stitchTiles: "stitch",
24629
+ result: "noise"
24630
+ }),
24631
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feColorMatrix", {
24632
+ type: "saturate",
24633
+ values: "0",
24634
+ result: "gray"
24635
+ }),
24636
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("feComponentTransfer", { children: [
24637
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncR", {
24638
+ type: "discrete",
24639
+ tableValues: "0 1"
24640
+ }),
24641
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncG", {
24642
+ type: "discrete",
24643
+ tableValues: "0 1"
24644
+ }),
24645
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("feFuncB", {
24646
+ type: "discrete",
24647
+ tableValues: "0 1"
24648
+ })
24649
+ ] })
24650
+ ]
24651
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", {
24652
+ width: "100%",
24653
+ height: "100%",
24654
+ filter: "url(#updateHelpNoise)"
24655
+ })]
24656
+ })
24657
+ }),
24658
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", {
24659
+ className: "relative z-10 px-22 pb-16 pt-20",
24660
+ children: [
24661
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
24662
+ className: "relative flex items-center justify-between gap-12",
24663
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24664
+ className: "flex items-center gap-4",
24665
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24666
+ className: "h-3 w-3 rounded-full",
24667
+ style: { background: palette.accentText }
24668
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24669
+ className: "whitespace-nowrap text-[24px] font-bold tracking-[0.28em] text-foreground/44",
24670
+ children: "更新面板 · UPDATE PANEL"
24671
+ })]
24672
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24673
+ className: "whitespace-nowrap rounded-full border px-6 py-2 text-[22px] font-bold tracking-[0.14em]",
24674
+ style: {
24675
+ borderColor: palette.pillBorder,
24676
+ background: palette.pillBg,
24677
+ color: palette.pillText
24678
+ },
24679
+ children: data.currentChannel
24680
+ })]
24681
+ }),
24682
+ heroStacked(data.currentVersion) ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
24683
+ className: "whitespace-nowrap text-[96px] font-bold leading-[1.04] tracking-[-0.02em] text-foreground",
24684
+ children: "更新渠道"
24685
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
24686
+ className: "mt-6 whitespace-nowrap text-[30px] font-semibold text-foreground/48",
24687
+ children: data.currentChannel === "金丝雀" ? "金丝雀构建跟随 main 分支实时发布,可随时切换回正式渠道" : "各渠道可用版本与 #kkk更新 用法一览"
24688
+ })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24689
+ className: "relative mt-14",
24690
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
24691
+ className: "text-[22px] font-bold uppercase tracking-[0.22em] text-foreground/36",
24692
+ children: "当前安装 · INSTALLED"
24693
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24694
+ className: "mt-4 whitespace-nowrap font-mono font-bold leading-none tracking-[-0.05em] tabular-nums text-foreground",
24695
+ style: { fontSize: `${heroVersionSize(data.currentVersion)}px` },
24696
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24697
+ className: "mr-3 align-top text-[0.42em] font-bold text-foreground/40",
24698
+ children: "v"
24699
+ }), data.currentVersion]
24700
+ })]
24701
+ })] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24702
+ className: "relative mt-14 flex items-end justify-between gap-12",
24703
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
24704
+ className: "whitespace-nowrap text-[96px] font-bold leading-[1.04] tracking-[-0.02em] text-foreground",
24705
+ children: "更新渠道"
24706
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
24707
+ className: "mt-6 whitespace-nowrap text-[30px] font-semibold text-foreground/48",
24708
+ children: data.currentChannel === "金丝雀" ? "金丝雀构建跟随 main 分支实时发布,可随时切换回正式渠道" : "各渠道可用版本与 #kkk更新 用法一览"
24709
+ })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24710
+ className: "flex shrink-0 flex-col items-end",
24711
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
24712
+ className: "text-[22px] font-bold uppercase tracking-[0.22em] text-foreground/36",
24713
+ children: "当前安装 · INSTALLED"
24714
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24715
+ className: "mt-4 whitespace-nowrap font-mono font-bold leading-none tracking-[-0.05em] tabular-nums text-foreground",
24716
+ style: { fontSize: `${heroVersionSize(data.currentVersion)}px` },
24717
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24718
+ className: "mr-3 align-top text-[0.42em] font-bold text-foreground/40",
24719
+ children: "v"
24720
+ }), data.currentVersion]
24721
+ })]
24722
+ })]
24723
+ }),
24724
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
24725
+ className: "relative mt-24",
24726
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24727
+ className: "flex items-center gap-4",
24728
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24729
+ className: "h-3 w-3 rounded-full",
24730
+ style: { background: palette.accentText }
24731
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24732
+ className: "text-[24px] font-bold tracking-[0.28em] text-foreground/44",
24733
+ children: "可用版本 · AVAILABLE"
24734
+ })]
24735
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
24736
+ className: "mt-12 flex flex-col gap-14",
24737
+ children: data.channels.map((channel) => {
24738
+ const status = statusView(channel);
24739
+ const publishedDate = channel.publishedAt ? new Date(channel.publishedAt) : void 0;
24740
+ if (channel.status === "canary") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24741
+ className: "flex flex-col gap-4",
24742
+ children: [
24743
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24744
+ className: "flex items-center justify-between gap-10",
24745
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24746
+ className: "flex items-center gap-4",
24747
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24748
+ className: "whitespace-nowrap text-[30px] font-semibold",
24749
+ style: { color: status.active ? palette.muted : palette.faint },
24750
+ children: channel.label
24751
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24752
+ className: "whitespace-nowrap rounded-full border px-4 py-0.5 font-mono text-[20px] font-semibold",
24753
+ style: {
24754
+ borderColor: palette.pillBorder,
24755
+ background: palette.pillBg,
24756
+ color: status.active ? palette.pillText : palette.faint
24757
+ },
24758
+ children: channel.tag
24759
+ })]
24760
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24761
+ className: "flex items-center gap-6",
24762
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24763
+ className: "whitespace-nowrap font-mono text-[46px] font-bold leading-none",
24764
+ style: { color: status.active ? palette.ink : palette.faint },
24765
+ children: channel.version
24766
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
24767
+ className: "flex items-center gap-3 whitespace-nowrap text-[24px] font-semibold",
24768
+ style: { color: status.dot },
24769
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24770
+ className: "h-2.5 w-2.5 rounded-full",
24771
+ style: { background: status.dot }
24772
+ }), status.text]
24773
+ })]
24774
+ })]
24775
+ }),
24776
+ channel.installCommand && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
24777
+ className: "whitespace-nowrap rounded-3xl px-7 py-4 font-mono text-[27px] font-semibold",
24778
+ style: {
24779
+ background: status.active ? palette.pillBg : "transparent",
24780
+ color: status.active ? palette.pillText : palette.faint
24781
+ },
24782
+ children: channel.installCommand
24783
+ }),
24784
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24785
+ className: "flex items-center justify-between gap-10 whitespace-nowrap",
24786
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
24787
+ className: "text-[24px] text-foreground/42",
24788
+ children: ["发布于 ", publishedDate && format(publishedDate, "yyyy/MM/dd HH:mm")]
24789
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24790
+ className: "font-mono text-[24px] font-semibold",
24791
+ style: { color: palette.faint },
24792
+ title: channel.publishedAt,
24793
+ children: publishedDate && formatDistanceToNow(publishedDate, {
24794
+ addSuffix: true,
24795
+ locale: zhCN
24796
+ })
24797
+ })]
24798
+ })
24799
+ ]
24800
+ }, channel.tag);
24801
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24802
+ className: "grid grid-cols-[11rem_1fr_auto] items-center gap-8",
24803
+ children: [
24804
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24805
+ className: "flex flex-col gap-2",
24806
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24807
+ className: "whitespace-nowrap text-[30px] font-semibold",
24808
+ style: { color: status.active ? palette.muted : palette.faint },
24809
+ children: channel.label
24810
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24811
+ className: "w-fit whitespace-nowrap rounded-full border px-4 py-0.5 font-mono text-[20px] font-semibold",
24812
+ style: {
24813
+ borderColor: palette.pillBorder,
24814
+ background: palette.pillBg,
24815
+ color: status.active ? palette.pillText : palette.faint
24816
+ },
24817
+ children: channel.tag
24818
+ })]
24819
+ }),
24820
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24821
+ className: "whitespace-nowrap font-mono text-[24px] font-semibold",
24822
+ style: { color: status.active ? palette.muted : palette.faint },
24823
+ children: channel.command
24824
+ }),
24825
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24826
+ className: "flex flex-col items-end gap-2.5 whitespace-nowrap",
24827
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24828
+ className: "font-mono text-[56px] font-bold leading-none tracking-[-0.02em] tabular-nums",
24829
+ style: { color: status.active ? palette.ink : palette.faint },
24830
+ children: channel.status === "ok" ? `v${channel.version}` : "—"
24831
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
24832
+ className: "flex items-center gap-3 text-[24px] font-semibold",
24833
+ style: { color: status.dot },
24834
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24835
+ className: "h-2.5 w-2.5 rounded-full",
24836
+ style: { background: status.dot }
24837
+ }), status.text]
24838
+ })]
24839
+ })
24840
+ ]
24841
+ }, channel.tag);
24842
+ })
24843
+ })]
24844
+ }),
24845
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
24846
+ className: "relative mt-28",
24847
+ children: [
24848
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24849
+ className: "flex items-center gap-4",
24850
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24851
+ className: "h-3 w-3 rounded-full",
24852
+ style: { background: palette.accentText }
24853
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24854
+ className: "text-[24px] font-bold tracking-[0.28em] text-foreground/44",
24855
+ children: "用法 · COMMANDS"
24856
+ })]
24857
+ }),
24858
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
24859
+ className: "mt-12 flex flex-col gap-10",
24860
+ children: [
24861
+ {
24862
+ command: "#kkk更新",
24863
+ desc: "更新到最新正式版"
24864
+ },
24865
+ {
24866
+ command: "#kkk更新rc",
24867
+ desc: "更新到预览渠道最新版"
24868
+ },
24869
+ {
24870
+ command: "#kkk更新beta",
24871
+ desc: "更新到测试渠道最新版"
24872
+ },
24873
+ {
24874
+ command: "#kkk更新<版本号>",
24875
+ desc: "更新到指定 stable 版本,例如 #kkk更新2.45.0"
24876
+ }
24877
+ ].map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24878
+ className: "grid grid-cols-[24rem_1fr] items-baseline gap-8",
24879
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24880
+ className: "whitespace-nowrap font-mono text-[34px] font-bold text-foreground",
24881
+ children: item.command
24882
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
24883
+ className: "whitespace-nowrap text-[26px] font-semibold text-foreground/48",
24884
+ children: item.desc
24885
+ })]
24886
+ }, item.command))
24887
+ }),
24888
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24889
+ className: "mt-16 flex flex-col gap-2 whitespace-nowrap font-mono text-[22px] font-semibold text-foreground/28",
24890
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "金丝雀版本经 pkg.pr.new 分发,不提供渠道订阅与更新推送" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "指定低于当前安装版本的版本号属于降级,需回复「确认」才会执行" })]
24891
+ })
24892
+ ]
24893
+ })
24894
+ ]
24895
+ })
24896
+ ]
24897
+ });
24898
+ })
24899
+ });
24900
+ //#endregion
24415
24901
  //#region ktr/template/other/version_warning/components/VersionWarning.tsx
24416
24902
  var VersionWarning = (props) => {
24417
24903
  const dark = isDark(props.ctx);
@@ -28329,6 +28815,7 @@ var templates = {
28329
28815
  "other/live-photo-tip": live_photo_tip_default,
28330
28816
  "other/qrlogin": qrlogin_default,
28331
28817
  "other/runtime": runtime_default,
28818
+ "other/updateHelp": updateHelp_default,
28332
28819
  "other/version_warning": version_warning_default,
28333
28820
  "statistics/global": global_default,
28334
28821
  "statistics/group": group_default,
@@ -38114,7 +38601,7 @@ var collectRuntimeReport = (event) => {
38114
38601
  pluginName: Root.pluginName,
38115
38602
  pluginVersion: Root.pluginVersion,
38116
38603
  karinVersion: Root.karinVersion || "未知",
38117
- releaseType: /^\d+\.\d+\.\d+$/.test(Root.pluginVersion) ? "Stable" : "Preview",
38604
+ releaseType: parseReleaseChannel(Root.pluginVersion),
38118
38605
  requiredNodeVersion: packageMetadata.engines?.node ?? "未声明",
38119
38606
  requiredKarinVersion: packageMetadata.karin?.engines ?? packageMetadata.engines?.karin ?? "未声明"
38120
38607
  },
@@ -39928,6 +40415,7 @@ var getChangelogImage = async (ctx, props) => {
39928
40415
  const localMeta = getBuildMetadata();
39929
40416
  if (localMeta?.buildTime) buildTime = formatBuildTime(localMeta.buildTime);
39930
40417
  }
40418
+ changelog = changelog.replace(/^(## \[\d+\.\d+\.\d+-[^\]]+\][^\r\n]*)/gm, "$1 「测试版」");
39931
40419
  return await Render(event, "other/changelog", {
39932
40420
  markdown: changelog,
39933
40421
  Tip: props.Tip,
@@ -39941,32 +40429,43 @@ var getChangelogImage = async (ctx, props) => {
39941
40429
  //#endregion
39942
40430
  //#region src/apps/update.ts
39943
40431
  var UPDATE_LOCK_KEY = "kkk:update:lock";
39944
- var UPDATE_MSGID_KEY = "kkk:update:msgId";
40432
+ var UPDATE_MSGID_KEY = "kkk:update:msgid";
39945
40433
  /**
39946
40434
  * 定时更新检测处理器
39947
- * 主动获取 Bot 列表与好友关系,匹配主人可用的 Bot,
39948
- * 渲染更新日志并私聊通知所有主人。
40435
+ * 按订阅渠道检查远程版本(latest 恒参与 —— 装 prerelease 的用户也要收到转正提醒),
40436
+ * 候选取 semver 最大且大于本地者,渲染变更日志并私聊通知所有主人。
39949
40437
  *
39950
40438
  * @returns 是否继续后续任务
39951
40439
  */
39952
40440
  var Handler = async () => {
39953
- let upd;
39954
- try {
39955
- upd = await checkPkgUpdate(Root.pluginName, { compare: "semver" });
39956
- } catch {
39957
- return true;
39958
- }
39959
- if (upd.status === "yes" && !isSemverGreater(upd.remote, upd.local)) return true;
39960
- if (upd.status !== "yes") return true;
40441
+ const tags = ["latest", ...(Config.app.UpdateNotifyChannels?.length ? Config.app.UpdateNotifyChannels : ["stable"]).filter((c) => c !== "stable")];
40442
+ const installedIsCanary = parseReleaseChannel(Root.pluginVersion) === "Canary";
40443
+ const installedBuildTime = getBuildMetadata()?.buildTimestamp;
40444
+ const remotes = await Promise.all(tags.map(async (tag) => {
40445
+ try {
40446
+ return await getRemotePkgVersion(Root.pluginName, tag) || null;
40447
+ } catch {
40448
+ return null;
40449
+ }
40450
+ }));
40451
+ const canaryInfo = installedIsCanary ? await fetchCanaryInfo() : null;
40452
+ const remote = [...new Set(remotes.filter((v) => !!v))].filter((version) => {
40453
+ if (installedIsCanary && installedBuildTime !== void 0) {
40454
+ const releaseNode = canaryInfo?.nodes.find((n) => n.message.includes(version));
40455
+ if (releaseNode) return releaseNode.time > installedBuildTime;
40456
+ }
40457
+ return isSemverGreater(version, Root.pluginVersion);
40458
+ }).sort((a, b) => isSemverGreater(b, a) ? 1 : -1)[0];
40459
+ if (!remote) return true;
39961
40460
  try {
39962
40461
  const lockedVersion = await db.get(UPDATE_LOCK_KEY);
39963
40462
  if (typeof lockedVersion === "string" && lockedVersion.length > 0) {
39964
40463
  if (!isSemverGreater(lockedVersion, Root.pluginVersion)) await db.del(UPDATE_LOCK_KEY);
39965
- else if (!isSemverGreater(upd.remote, lockedVersion)) return true;
40464
+ else if (!isSemverGreater(remote, lockedVersion)) return true;
39966
40465
  }
39967
40466
  } catch {}
39968
40467
  try {
39969
- await db.set(UPDATE_LOCK_KEY, upd.remote);
40468
+ await db.set(UPDATE_LOCK_KEY, remote);
39970
40469
  } catch {}
39971
40470
  const masters = config.master().filter((id) => id !== "console");
39972
40471
  if (masters.length === 0) return true;
@@ -39977,9 +40476,7 @@ var Handler = async () => {
39977
40476
  try {
39978
40477
  const list = await item.bot.getFriendList();
39979
40478
  friendsMap.set(item.bot.account.selfId, list || []);
39980
- } catch {
39981
- friendsMap.set(item.bot.account.selfId, []);
39982
- }
40479
+ } catch {}
39983
40480
  }));
39984
40481
  const masterToBot = /* @__PURE__ */ new Map();
39985
40482
  for (const owner of masters) {
@@ -39991,7 +40488,7 @@ var Handler = async () => {
39991
40488
  if (!Array.from(masterToBot.entries()).some(([, b]) => b.account.selfId === item.bot.account.selfId)) continue;
39992
40489
  const img = await getChangelogImage({ bot: item.bot }, {
39993
40490
  localVersion: Root.pluginVersion,
39994
- remoteVersion: upd.remote,
40491
+ remoteVersion: remote,
39995
40492
  Tip: true
39996
40493
  });
39997
40494
  if (img && img.length > 0) botToImage.set(item.bot.account.selfId, [segment.text("karin-plugin-kkk 有新的更新!"), ...img]);
@@ -40033,39 +40530,214 @@ var kkkUpdate = hooks.message.friend(async (e, next) => {
40033
40530
  }
40034
40531
  next();
40035
40532
  }, { priority: 100 });
40533
+ /** 解析 #kkk更新 的可选参数:缺省/stable=正式版,beta/rc=渠道 tag,其余识别为版本号 */
40534
+ var parseUpdateArg = (arg) => {
40535
+ if (!arg) return { type: "latest" };
40536
+ if (arg === "stable" || arg === "latest") return { type: "latest" };
40537
+ if (arg === "beta" || arg === "rc") return {
40538
+ type: "tag",
40539
+ tag: arg
40540
+ };
40541
+ if (/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(arg)) return {
40542
+ type: "version",
40543
+ version: arg
40544
+ };
40545
+ return { type: "unknown" };
40546
+ };
40547
+ /** 解析目标版本号:latest / 渠道 tag 取对应 dist-tag,指定版本号校验存在性 */
40548
+ var resolveUpdateTarget = async (parsed) => {
40549
+ if (parsed.type === "latest") {
40550
+ const version = await getRemotePkgVersion(Root.pluginName).catch(() => "");
40551
+ return version ? {
40552
+ version,
40553
+ tag: "latest"
40554
+ } : { error: "获取远程版本失败" };
40555
+ }
40556
+ if (parsed.type === "tag") {
40557
+ const version = await getRemotePkgVersion(Root.pluginName, parsed.tag).catch(() => "");
40558
+ return version ? {
40559
+ version,
40560
+ tag: parsed.tag
40561
+ } : { error: `渠道 ${parsed.tag} 暂无可用版本` };
40562
+ }
40563
+ if (parsed.type === "version") {
40564
+ const { status, stdout } = await exec(`npm view ${Root.pluginName}@${parsed.version} version`);
40565
+ const found = status ? stdout.toString().trim() : "";
40566
+ return found ? {
40567
+ version: found,
40568
+ tag: ""
40569
+ } : { error: `版本 ${parsed.version} 不存在` };
40570
+ }
40571
+ return { error: "未识别的渠道或版本" };
40572
+ };
40573
+ /** 安装指定版本并重启(含降级;调用前必须已完成二次确认) */
40574
+ var installAndRestart = async (e, version) => {
40575
+ await e.reply(`开始更新 karin-plugin-kkk → ${version} ......`, { reply: true });
40576
+ const { error } = await exec(`pnpm up ${Root.pluginName}@${version}`);
40577
+ if (error) {
40578
+ e.reply(`${Root.pluginName} 更新失败: ${error}`, { reply: true });
40579
+ return;
40580
+ }
40581
+ const installed = await getPkgVersion(Root.pluginName);
40582
+ if (installed !== version) {
40583
+ e.reply(`更新失败:安装后版本为 ${installed},预期 ${version}。可尝试手动执行 pnpm up ${Root.pluginName}@${version}`, { reply: true });
40584
+ return;
40585
+ }
40586
+ const msgResult = await e.reply(`${Root.pluginName} 更新成功!→ ${version}\n开始执行重启......`);
40587
+ if (msgResult.messageId) try {
40588
+ await db.del(UPDATE_MSGID_KEY);
40589
+ await db.del(UPDATE_LOCK_KEY);
40590
+ } catch {}
40591
+ await restart(e.selfId, e.contact, msgResult.messageId);
40592
+ };
40593
+ /** pkg.pr.new 仓库(金丝雀数据源,只跟踪 main 分支构建) */
40594
+ var PKG_PR_NEW_REPO = "ikenxuan/karin-plugin-kkk";
40595
+ /** 拉取 pkg.pr.new 的发布历史与 main 分支最新构建(失败返回 null,由调用方降级展示) */
40596
+ var fetchCanaryInfo = async () => {
40597
+ try {
40598
+ const [owner, repo] = PKG_PR_NEW_REPO.split("/");
40599
+ const nodes = (await axios.get(`https://pkg.pr.new/api/repo/commits?owner=${owner}&repo=${repo}`, { timeout: 1e4 })).data?.target?.history?.nodes;
40600
+ if (!Array.isArray(nodes)) return null;
40601
+ const history = nodes.map((n) => ({
40602
+ sha: (n.abbreviatedOid ?? "").slice(0, 7),
40603
+ time: n.authoredDate ? new Date(n.authoredDate).getTime() : 0,
40604
+ message: n.message ?? ""
40605
+ }));
40606
+ const mainNode = nodes.find((n) => n.branch === "main" && n.statusCheckRollup?.contexts?.nodes?.some((c) => c.packages?.some((pkg) => pkg.installUrl)));
40607
+ const pkg = mainNode?.statusCheckRollup?.contexts?.nodes?.flatMap((c) => c.packages ?? []).find((p) => p.installUrl);
40608
+ if (!mainNode?.abbreviatedOid || !pkg?.installUrl) return null;
40609
+ return {
40610
+ channel: {
40611
+ label: "金丝雀",
40612
+ tag: "pkg.pr.new · main",
40613
+ status: "canary",
40614
+ version: mainNode.abbreviatedOid.slice(0, 7),
40615
+ installCommand: `pnpm add ${pkg.installUrl} -w`,
40616
+ publishedAt: mainNode.authoredDate
40617
+ },
40618
+ nodes: history
40619
+ };
40620
+ } catch {
40621
+ return null;
40622
+ }
40623
+ };
40624
+ /** 渲染 #kkk更新 用法面板(含各渠道可用版本) */
40625
+ var getUpdateHelpImage = async (e) => {
40626
+ const { status, stdout } = await exec(`npm view ${Root.pluginName} dist-tags --json`);
40627
+ let distTags = {};
40628
+ if (status) try {
40629
+ distTags = JSON.parse(stdout.toString());
40630
+ } catch {}
40631
+ const canaryInfo = await fetchCanaryInfo();
40632
+ const installedIsCanary = parseReleaseChannel(Root.pluginVersion) === "Canary";
40633
+ const build = getBuildMetadata();
40634
+ const installedBuildTime = build?.buildTimestamp;
40635
+ const channels = [
40636
+ {
40637
+ label: "正式版",
40638
+ tag: "latest",
40639
+ command: "#kkk更新"
40640
+ },
40641
+ {
40642
+ label: "预览版",
40643
+ tag: "rc",
40644
+ command: "#kkk更新rc"
40645
+ },
40646
+ {
40647
+ label: "测试版",
40648
+ tag: "beta",
40649
+ command: "#kkk更新beta"
40650
+ }
40651
+ ].map((def) => {
40652
+ if (!status) return {
40653
+ ...def,
40654
+ status: "error"
40655
+ };
40656
+ const version = distTags[def.tag];
40657
+ if (!version) return {
40658
+ ...def,
40659
+ status: "missing"
40660
+ };
40661
+ if (installedIsCanary && installedBuildTime !== void 0) {
40662
+ const releaseNode = canaryInfo?.nodes.find((n) => n.message.includes(version));
40663
+ if (releaseNode) return {
40664
+ ...def,
40665
+ status: "ok",
40666
+ version,
40667
+ hasUpdate: releaseNode.time > installedBuildTime
40668
+ };
40669
+ }
40670
+ return {
40671
+ ...def,
40672
+ status: "ok",
40673
+ version,
40674
+ hasUpdate: isSemverGreater(version, Root.pluginVersion)
40675
+ };
40676
+ });
40677
+ const canaryChannel = canaryInfo?.channel ?? {
40678
+ label: "金丝雀",
40679
+ tag: "pkg.pr.new · main",
40680
+ status: "error"
40681
+ };
40682
+ const installedSha = (build?.shortCommitHash ?? "").slice(0, 7);
40683
+ if (installedIsCanary && canaryChannel.status === "canary" && installedSha) canaryChannel.hasUpdate = installedSha !== canaryChannel.version;
40684
+ channels.push(canaryChannel);
40685
+ return await Render(e, "other/updateHelp", {
40686
+ currentVersion: Root.pluginVersion,
40687
+ currentChannel: RELEASE_CHANNEL_LABEL[parseReleaseChannel(Root.pluginVersion)],
40688
+ channels
40689
+ });
40690
+ };
40036
40691
  var handleKkkUpdate = wrapWithErrorHandler(async (e) => {
40037
- const upd = await checkPkgUpdate(Root.pluginName, { compare: "semver" });
40038
- if (upd.status === "error") {
40039
- e.reply(`获取远程版本失败:${upd.error?.message ?? String(upd.error)}`);
40692
+ const arg = /^#?kkk更新(?:\s*(\S+))?$/.exec(e.msg)?.[1];
40693
+ const parsed = parseUpdateArg(arg);
40694
+ if (parsed.type === "unknown") {
40695
+ const img = await getUpdateHelpImage(e).catch(() => null);
40696
+ if (img && img.length > 0) e.reply(img, { reply: true });
40697
+ else e.reply("渲染更新面板失败。用法:#kkk更新 | #kkk更新beta | #kkk更新rc | #kkk更新<版本号>", { reply: true });
40040
40698
  return;
40041
40699
  }
40042
- if (upd.status === "no") {
40043
- e.reply(`当前已是最新版本:${upd.local}`, { reply: true });
40700
+ const resolved = await resolveUpdateTarget(parsed);
40701
+ if ("error" in resolved) {
40702
+ e.reply(resolved.error, { reply: true });
40044
40703
  return;
40045
40704
  }
40046
- if (upd.status === "yes" && !isSemverGreater(upd.remote, upd.local)) {
40047
- e.reply(`当前已是最新或预览版本:${upd.local}`, { reply: true });
40705
+ const { version: remote } = resolved;
40706
+ const local = Root.pluginVersion;
40707
+ if (remote === local) {
40708
+ e.reply(`当前已是该版本:${local}`, { reply: true });
40709
+ return;
40710
+ }
40711
+ if (!isSemverGreater(remote, local)) {
40712
+ e.reply([
40713
+ `⚠️ 检测到降级更新:当前 ${local} → 目标 ${remote}`,
40714
+ "降级可能带来配置或数据兼容问题,请确认你知道自己在做什么。",
40715
+ "回复「确认」继续更新,回复其他内容放弃(2 分钟内有效)。"
40716
+ ].join("\n"), { reply: true });
40717
+ const confirmCtx = await karin.ctx(e, {
40718
+ time: 120,
40719
+ reply: true,
40720
+ throwOnTimeout: false
40721
+ });
40722
+ if (!confirmCtx) return;
40723
+ if (confirmCtx.msg.trim() !== "确认") {
40724
+ e.reply("已放弃降级更新。", { reply: true });
40725
+ return;
40726
+ }
40727
+ await installAndRestart(e, remote);
40048
40728
  return;
40049
40729
  }
40050
40730
  const ChangeLogImg = await getChangelogImage(e, {
40051
- localVersion: Root.pluginVersion,
40052
- remoteVersion: upd.remote,
40731
+ localVersion: local,
40732
+ remoteVersion: remote,
40053
40733
  Tip: false,
40054
40734
  isRemote: true
40055
40735
  });
40056
40736
  if (ChangeLogImg) e.reply([segment.text(`${Root.pluginName} 的更新日志:`), ...ChangeLogImg], { reply: true });
40057
40737
  else e.reply("获取更新日志失败,更新进程继续......", { reply: true });
40058
- const result = await updatePkg(Root.pluginName);
40059
- if (result.status === "ok") {
40060
- const msgResult = await e.reply(`${Root.pluginName} 更新成功!\n${result.local} -> ${result.remote}\n开始执行重启......`);
40061
- if (msgResult.messageId) try {
40062
- await db.del(UPDATE_MSGID_KEY);
40063
- await db.del(UPDATE_LOCK_KEY);
40064
- } catch {}
40065
- await restart(e.selfId, e.contact, msgResult.messageId);
40066
- } else e.reply(`${Root.pluginName} 更新失败: ${result.data ?? "更新执行失败"}`);
40738
+ await installAndRestart(e, remote);
40067
40739
  }, { businessName: "KKK更新" });
40068
- var kkkUpdateCommand = karin.command(/^#?kkk更新$/, handleKkkUpdate, {
40740
+ var kkkUpdateCommand = karin.command(/^#?kkk更新(?:\s*(\S+))?$/, handleKkkUpdate, {
40069
40741
  name: "kkk-更新",
40070
40742
  perm: "master"
40071
40743
  });