koishi-plugin-ggcevo 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -21,11 +21,14 @@ export interface Config {
21
21
  handleInactiveUnbindDays: number;
22
22
  /** 活动兑换管理员名字, 填写后兑换成功及兑换列表会提示向该管理员登记; 不填则不提醒 */
23
23
  exchangeAdminName: string;
24
+ /** 咕咕之战菜单渲染方式: text=文字菜单(默认, 无需额外服务), image=图片菜单(需启用 puppeteer 服务) */
25
+ menuStyle: 'text' | 'image';
24
26
  debugEnabled: boolean;
25
27
  }
26
28
  export declare const Config: Schema<Config>;
27
29
  export declare const inject: {
28
30
  required: string[];
31
+ optional: string[];
29
32
  };
30
33
  export declare const ItemConfig: Record<number, string>;
31
34
  export declare const LotteryPoolConfig: Record<number, string>;
package/lib/index.js CHANGED
@@ -1,6 +1,8 @@
1
+ var __create = Object.create;
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
4
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
7
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
6
8
  var __export = (target, all) => {
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -30,6 +40,10 @@ __export(src_exports, {
30
40
  });
31
41
  module.exports = __toCommonJS(src_exports);
32
42
  var import_koishi = require("koishi");
43
+ var import_node_fs = __toESM(require("node:fs"));
44
+ var import_node_path = __toESM(require("node:path"));
45
+ var import_node_crypto = require("node:crypto");
46
+ var import_promises = require("node:fs/promises");
33
47
  var name = "ggcevo";
34
48
  var Config = import_koishi.Schema.intersect([
35
49
  import_koishi.Schema.object({
@@ -61,12 +75,19 @@ var Config = import_koishi.Schema.intersect([
61
75
  handleInactiveUnbindEnabled: import_koishi.Schema.boolean().description("是否启用句柄长时间未使用自动解绑").default(false),
62
76
  handleInactiveUnbindDays: import_koishi.Schema.number().description("句柄超过该天数未使用将自动解绑(仅对非当前使用的句柄生效)").default(90).min(1)
63
77
  }).description("句柄自动解绑"),
78
+ import_koishi.Schema.object({
79
+ menuStyle: import_koishi.Schema.union([
80
+ import_koishi.Schema.const("text").description("文字菜单(默认, 无需额外服务)"),
81
+ import_koishi.Schema.const("image").description("图片菜单(需安装并启用 koishi-plugin-puppeteer 或 @shangxueink/puppeteer-without-canvas)")
82
+ ]).description("咕咕之战菜单渲染方式").default("text")
83
+ }).description("菜单设置"),
64
84
  import_koishi.Schema.object({
65
85
  debugEnabled: import_koishi.Schema.boolean().description("是否启用调试模式(启用后, ggcevo 所有指令的触发/参数/执行结果/错误信息会输出到 Koishi 日志)").default(false)
66
86
  }).description("调试模式")
67
87
  ]);
68
88
  var inject = {
69
- required: ["database"]
89
+ required: ["database"],
90
+ optional: ["puppeteer"]
70
91
  };
71
92
  var ItemConfig = {
72
93
  1: "金币",
@@ -2416,10 +2437,10 @@ ${rewardLines.join("\n")}` : "";
2416
2437
  return record || null;
2417
2438
  }
2418
2439
  }, "getValidDocsToken");
2419
- const docsApiRequest = /* @__PURE__ */ __name(async (method, path, data) => {
2440
+ const docsApiRequest = /* @__PURE__ */ __name(async (method, path2, data) => {
2420
2441
  const token = await getValidDocsToken();
2421
2442
  if (!token) throw new Error("腾讯文档应用级账号不可用, 请检查插件配置(Client ID / Client Secret)");
2422
- const url = `https://docs.qq.com/openapi${path}`;
2443
+ const url = `https://docs.qq.com/openapi${path2}`;
2423
2444
  const headers = {
2424
2445
  "Access-Token": token.access_token,
2425
2446
  "Client-Id": config.tencentDocsClientId,
@@ -2812,186 +2833,181 @@ ${rewardLines.join("\n")}` : "";
2812
2833
  }
2813
2834
  return "已退出封禁记录查询。";
2814
2835
  });
2815
- const MENU_PREFIX = "ggg:";
2816
2836
  const menuCategories = [
2817
2837
  {
2818
2838
  key: "daily",
2819
- label: "📅 每日玩法",
2839
+ label: "每日玩法",
2840
+ emoji: "📅",
2820
2841
  desc: "签到 / 挖矿 / 探索 / 补签",
2821
2842
  items: [
2822
- { key: "sign", label: "签到", desc: "每日签到, 获取金币与咕咕币", cmd: "签到" },
2823
- { key: "mine", label: "挖矿", desc: "领取挂机挖矿收益(每半小时4金币)", cmd: "挖矿" },
2824
- { key: "explore", label: "探索", desc: "选择星系进行12小时探索", cmd: "探索" },
2825
- { key: "makeup", label: "补签", desc: "使用补签券补签漏签日期", cmd: "补签" }
2843
+ { key: "sign", icon: "签", label: "签到", desc: "每日签到, 获取金币与咕咕币" },
2844
+ { key: "mine", icon: "挖", label: "挖矿", desc: "领取挂机挖矿收益(每半小时4金币)" },
2845
+ { key: "explore", icon: "探", label: "探索", desc: "选择星系进行12小时探索" },
2846
+ { key: "makeup", icon: "补", label: "补签", desc: "使用补签券补签漏签日期" }
2826
2847
  ]
2827
2848
  },
2828
2849
  {
2829
2850
  key: "bag",
2830
- label: "🎒 物品背包",
2851
+ label: "物品背包",
2852
+ emoji: "🎒",
2831
2853
  desc: "背包 / 个人信息 / 使用",
2832
2854
  items: [
2833
- { key: "inv", label: "背包", desc: "查看自己的物品背包", cmd: "背包" },
2834
- { key: "profile", label: "个人信息", desc: "查看签到统计与个人信息", cmd: "个人信息" },
2835
- { key: "use", label: "使用 <名称>", desc: "使用指定物品(如赎罪券等)", cmd: "使用", input: "请输入要使用的物品名称" }
2855
+ { key: "inv", icon: "包", label: "背包", desc: "查看自己的物品背包" },
2856
+ { key: "profile", icon: "信", label: "个人信息", desc: "查看签到统计与个人信息" },
2857
+ { key: "use", icon: "用", label: "使用 <名称>", desc: "使用指定物品(如赎罪券等)" }
2836
2858
  ]
2837
2859
  },
2838
2860
  {
2839
2861
  key: "lottery",
2840
- label: "🎲 抽奖兑换",
2862
+ label: "抽奖兑换",
2863
+ emoji: "🎲",
2841
2864
  desc: "抽奖 / 抽奖概率 / 兑换 / 兑换列表",
2842
2865
  items: [
2843
- { key: "lottery", label: "抽奖", desc: "抽奖, 可加选项 -p 奖池ID -c 次数", cmd: "抽奖" },
2844
- { key: "odds", label: "抽奖概率", desc: "查看各奖池抽奖概率与保底说明", cmd: "抽奖概率" },
2845
- { key: "exchange", label: "兑换 <名称>", desc: "使用兑换券兑换物品", cmd: "兑换", input: "请输入要兑换的物品名称" },
2846
- { key: "exchangeList", label: "兑换列表", desc: "查看可兑换物品列表", cmd: "兑换列表" }
2866
+ { key: "lottery", icon: "抽", label: "抽奖", desc: "抽奖, 可加选项 -p 奖池ID -c 次数" },
2867
+ { key: "odds", icon: "率", label: "抽奖概率", desc: "查看各奖池抽奖概率与保底说明" },
2868
+ { key: "exchange", icon: "兑", label: "兑换 <名称>", desc: "使用兑换券兑换物品" },
2869
+ { key: "exchangeList", icon: "表", label: "兑换列表", desc: "查看可兑换物品列表" }
2847
2870
  ]
2848
2871
  },
2849
2872
  {
2850
2873
  key: "activity",
2851
- label: "🎉 活动",
2874
+ label: "活动",
2875
+ emoji: "🎉",
2852
2876
  desc: "活动列表 / 领取活动",
2853
2877
  items: [
2854
- { key: "actList", label: "活动列表", desc: '查看进行中的活动(参数"全部"查看全部)', cmd: "活动列表" },
2855
- { key: "actClaim", label: "领取活动", desc: "领取指定(或最新)活动奖励", cmd: "领取活动" }
2878
+ { key: "actList", icon: "活", label: "活动列表", desc: '查看进行中的活动(参数"全部"查看全部)' },
2879
+ { key: "actClaim", icon: "领", label: "领取活动", desc: "领取指定(或最新)活动奖励" }
2856
2880
  ]
2857
2881
  },
2858
2882
  {
2859
2883
  key: "handle",
2860
- label: "🔗 句柄绑定",
2884
+ label: "句柄绑定",
2885
+ emoji: "🔗",
2861
2886
  desc: "绑定 / 句柄 / 切换 / 查询 / 解绑 / 迁移",
2862
2887
  items: [
2863
- { key: "bind", label: "绑定 <句柄>", desc: "绑定星际争霸2游戏句柄", cmd: "绑定", input: "请输入游戏句柄(格式: 区域-S2-服务器-档案, 如 5-S2-1-1234567)" },
2864
- { key: "list", label: "句柄", desc: "查询已绑定的游戏句柄", cmd: "句柄" },
2865
- { key: "switch", label: "切换", desc: "切换正在使用的游戏句柄", cmd: "切换" },
2866
- { key: "check", label: "查询", desc: "查询某句柄是否已被绑定", cmd: "查询" },
2867
- { key: "unbind", label: "解绑", desc: "解除绑定某个游戏句柄", cmd: "解绑" },
2868
- { key: "migrate", label: "迁移 <QQ>", desc: "将指定QQ号绑定的句柄迁移到当前账号", cmd: "迁移", input: "请输入要迁移的QQ号" }
2888
+ { key: "bind", icon: "绑", label: "绑定 <句柄>", desc: "绑定星际争霸2游戏句柄" },
2889
+ { key: "list", icon: "句", label: "句柄", desc: "查询已绑定的游戏句柄" },
2890
+ { key: "switch", icon: "切", label: "切换", desc: "切换正在使用的游戏句柄" },
2891
+ { key: "check", icon: "查", label: "查询", desc: "查询某句柄是否已被绑定" },
2892
+ { key: "unbind", icon: "解", label: "解绑", desc: "解除绑定某个游戏句柄" },
2893
+ { key: "migrate", icon: "迁", label: "迁移 <QQ>", desc: "将指定QQ号绑定的句柄迁移到当前账号" }
2869
2894
  ]
2870
2895
  },
2871
2896
  {
2872
2897
  key: "query",
2873
- label: "📊 查询信息",
2898
+ label: "查询信息",
2899
+ emoji: "📊",
2874
2900
  desc: "地图检测 / 封禁记录 / 签到奖励",
2875
2901
  items: [
2876
- { key: "map", label: "地图检测", desc: "查询已配置地图的检测状态", cmd: "地图检测" },
2877
- { key: "ban", label: "封禁记录", desc: "查询句柄的封禁记录", cmd: "封禁记录" },
2878
- { key: "reward", label: "签到奖励", desc: "查看签到奖励规则说明", cmd: "签到奖励" }
2902
+ { key: "map", icon: "图", label: "地图检测", desc: "查询已配置地图的检测状态" },
2903
+ { key: "ban", icon: "封", label: "封禁记录", desc: "查询句柄的封禁记录" },
2904
+ { key: "reward", icon: "奖", label: "签到奖励", desc: "查看签到奖励规则说明" }
2879
2905
  ]
2880
2906
  }
2881
2907
  ];
2882
- const buildPlainMenu = /* @__PURE__ */ __name((category) => {
2883
- if (category) {
2884
- return [`🐤 咕咕之战 · ${category.label}`, ...category.items.map((i) => `${i.label}:${i.desc}`)].join("\n");
2885
- }
2886
- return [
2887
- "🐤 咕咕之战菜单",
2888
- ...menuCategories.map((c) => `${c.label}
2889
- ${c.items.map((i) => i.label).join(" / ")}`),
2890
- "",
2891
- "提示: 当前平台不支持按钮菜单, 请直接输入对应指令使用。"
2892
- ].join("\n\n");
2893
- }, "buildPlainMenu");
2894
- const sendMenu = /* @__PURE__ */ __name(async (session, category) => {
2895
- if (session.platform !== "qq") {
2896
- await session.send(buildPlainMenu(category));
2897
- return;
2898
- }
2899
- let title;
2900
- let lines;
2901
- let rows;
2902
- if (category) {
2903
- title = `🐤 咕咕之战 · ${category.label}`;
2904
- lines = [
2905
- ...category.items.map((item, idx) => `${idx + 1}. **${item.label}**:${item.desc}`),
2906
- "",
2907
- "点击下方按钮即可使用对应功能, 点「⬅️ 返回」回到主菜单。"
2908
- ];
2909
- rows = [
2910
- category.items.map((item) => ({
2911
- id: `${MENU_PREFIX}run:${category.key}:${item.key}`,
2912
- label: item.label,
2913
- input: item.input,
2914
- primary: item.key === "sign"
2915
- })),
2916
- [{ id: `${MENU_PREFIX}back`, label: "⬅️ 返回" }]
2917
- ];
2918
- } else {
2919
- title = "🐤 咕咕之战菜单";
2920
- lines = [
2921
- ...menuCategories.map((cat, idx) => `${idx + 1}. ${cat.label}:${cat.desc}`),
2922
- "",
2923
- "点击下方分类按钮进入对应功能菜单。"
2924
- ];
2925
- rows = [
2926
- [
2927
- { id: `${MENU_PREFIX}cat:daily`, label: "📅 每日玩法", primary: true },
2928
- { id: `${MENU_PREFIX}cat:bag`, label: "🎒 物品背包" }
2929
- ],
2930
- [
2931
- { id: `${MENU_PREFIX}cat:lottery`, label: "🎲 抽奖兑换" },
2932
- { id: `${MENU_PREFIX}cat:activity`, label: "🎉 活动" }
2933
- ],
2934
- [
2935
- { id: `${MENU_PREFIX}cat:handle`, label: "🔗 句柄绑定" },
2936
- { id: `${MENU_PREFIX}cat:query`, label: "📊 查询信息" }
2937
- ]
2938
- ];
2939
- }
2940
- const elements = [
2941
- (0, import_koishi.h)("qq:markdown", `**${title}**
2942
-
2943
- ${lines.join("\n")}`),
2944
- ...rows.map((row) => (0, import_koishi.h)("button-group", row.map((btn) => (0, import_koishi.h)("button", {
2945
- id: btn.id,
2946
- class: btn.primary ? "primary" : void 0,
2947
- type: btn.input ? "input" : void 0,
2948
- text: btn.input
2949
- }, btn.label))))
2950
- ];
2951
- try {
2952
- await session.send(elements);
2953
- } catch (e) {
2954
- ctx.logger("ggcevo").warn("咕咕之战菜单 Markdown 发送失败, 回退为纯文本: %o", e);
2955
- await session.send(buildPlainMenu(category));
2956
- }
2957
- }, "sendMenu");
2958
- const runMenuCommand = /* @__PURE__ */ __name(async (session, name2, args = []) => {
2959
- session.messageId = "";
2960
- await session.execute({ name: name2, args, options: {} });
2961
- }, "runMenuCommand");
2962
- ctx.command("咕咕之战").alias("菜单").usage("生成咕咕之战内容分类菜单(QQ 官方机器人支持按钮点击使用)").action(async (argv) => {
2963
- await sendMenu(argv.session, void 0);
2964
- });
2965
- ctx.on("interaction/button", async (session) => {
2966
- const button = session.event?.button;
2967
- if (!button || typeof button.id !== "string" || !button.id.startsWith(MENU_PREFIX)) return;
2968
- const parts = button.id.split(":");
2908
+ const buildTextMenu = /* @__PURE__ */ __name(() => [
2909
+ "🐤 咕咕之战菜单",
2910
+ "",
2911
+ ...menuCategories.map((cat) => `${cat.emoji} ${cat.label}(${cat.desc})
2912
+ ${cat.items.map((item) => item.label).join(" / ")}`),
2913
+ "",
2914
+ "提示: 直接输入上方指令名即可使用对应功能。"
2915
+ ].join("\n"), "buildTextMenu");
2916
+ const buildMenuHtml = /* @__PURE__ */ __name((title, subtitle) => {
2917
+ const palette = ["#4a7dff", "#9b59b6", "#e67e22", "#27ae60", "#e74c3c", "#16a085", "#f39c12", "#8e44ad", "#2980b9", "#d35400", "#2ecc71", "#c0392b"];
2918
+ const groupHtml = menuCategories.map((group) => {
2919
+ const itemRows = group.items.map((item, i) => {
2920
+ const color = palette[i % palette.length];
2921
+ return `
2922
+ <div class="item">
2923
+ <div class="icon" style="background: ${color}">${item.icon}</div>
2924
+ <div class="info">
2925
+ <div class="name">${item.label}</div>
2926
+ <div class="desc">${item.desc}</div>
2927
+ </div>
2928
+ </div>`;
2929
+ }).join("");
2930
+ return `
2931
+ <div class="group">
2932
+ <div class="group-title">${group.label}</div>
2933
+ <div class="list">${itemRows}</div>
2934
+ </div>`;
2935
+ }).join("");
2936
+ return `<!DOCTYPE html>
2937
+ <html lang="zh">
2938
+ <head>
2939
+ <meta charset="UTF-8">
2940
+ <style>
2941
+ body { margin: 0; padding: 24px 28px; background:
2942
+ repeating-linear-gradient(45deg, rgba(255,255,255,0.02) 0 1px, transparent 1px 2px),
2943
+ linear-gradient(135deg, #1b2a4a 0%, #0f1a33 100%);
2944
+ font-family: "Microsoft YaHei", "PingFang SC", sans-serif; color: #e8eefc; }
2945
+ .header { text-align: center; padding-bottom: 18px; border-bottom: 2px solid rgba(120,160,255,0.35); }
2946
+ .title { font-size: 32px; font-weight: bold; letter-spacing: 6px; color: #8fc0ff; text-shadow: 0 0 18px rgba(90,140,255,0.45); }
2947
+ .subtitle { margin-top: 8px; font-size: 14px; color: #93a6c8; }
2948
+ .group { margin-top: 22px; }
2949
+ .group-title { font-size: 15px; font-weight: bold; color: #ffd98a; letter-spacing: 3px; margin-bottom: 10px; padding-left: 10px; border-left: 3px solid #ffd98a; }
2950
+ .list { display: flex; flex-direction: column; gap: 12px; }
2951
+ .item { display: flex; align-items: center; gap: 14px; background: rgba(255,255,255,0.07); border: 1px solid rgba(140,170,255,0.22); border-radius: 12px; padding: 12px 16px; }
2952
+ .icon { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 19px; font-weight: bold; color: #fff; flex-shrink: 0; }
2953
+ .info { flex: 1; }
2954
+ .name { font-size: 20px; font-weight: bold; color: #ffffff; }
2955
+ .desc { margin-top: 4px; font-size: 13px; color: #9db1d8; line-height: 1.5; }
2956
+ .footer { margin-top: 24px; text-align: center; font-size: 12px; color: #6b7ea6; }
2957
+ </style>
2958
+ </head>
2959
+ <body>
2960
+ <div class="header">
2961
+ <div class="title">${title}</div>
2962
+ <div class="subtitle">${subtitle}</div>
2963
+ </div>
2964
+ ${groupHtml}
2965
+ <div class="footer">输入上方指令名即可使用对应功能</div>
2966
+ </body>
2967
+ </html>`;
2968
+ }, "buildMenuHtml");
2969
+ const menuCacheDir = import_node_path.default.resolve(ctx.baseDir, "data/ggcevo/menu");
2970
+ const renderMenuImage = /* @__PURE__ */ __name(async (html) => {
2971
+ const logger = ctx.logger("ggcevo");
2972
+ const hash = (0, import_node_crypto.createHash)("sha256").update(html).digest("hex");
2973
+ const cachePath = import_node_path.default.resolve(menuCacheDir, `${hash}.jpg`);
2974
+ if (import_node_fs.default.existsSync(cachePath)) {
2975
+ const cached = await (0, import_promises.readFile)(cachePath);
2976
+ return import_koishi.h.image(`data:image/jpeg;base64,${cached.toString("base64")}`);
2977
+ }
2978
+ if (!ctx.puppeteer) {
2979
+ logger.warn("[menu-render] 未启用 puppeteer 服务");
2980
+ return "⚠️ 未启用 puppeteer 服务,无法渲染图片菜单。请安装并启用 `koishi-plugin-puppeteer` 或 `@shangxueink/puppeteer-without-canvas`。";
2981
+ }
2982
+ const page = await ctx.puppeteer.page();
2969
2983
  try {
2970
- if (parts[1] === "back") {
2971
- await sendMenu(session, void 0);
2972
- } else if (parts[1] === "cat") {
2973
- const category = menuCategories.find((c) => c.key === parts[2]);
2974
- if (category) await sendMenu(session, category);
2975
- } else if (parts[1] === "run") {
2976
- const category = menuCategories.find((c) => c.key === parts[2]);
2977
- const item = category?.items.find((i) => i.key === parts[3]);
2978
- if (!category || !item || !item.cmd) return;
2979
- if (item.input) {
2980
- const value = String(button.data ?? "").trim();
2981
- if (!value) {
2982
- await session.send(`请输入${item.label}后重试。`);
2983
- return;
2984
- }
2985
- await runMenuCommand(session, item.cmd, [value]);
2986
- } else {
2987
- await runMenuCommand(session, item.cmd);
2988
- }
2984
+ await page.setViewport({ width: 1280, height: 100, deviceScaleFactor: 1 });
2985
+ await page.setContent(html);
2986
+ await page.waitForNetworkIdle();
2987
+ const img = await page.screenshot({ type: "jpeg", quality: 85, fullPage: true, encoding: "binary" });
2988
+ logger.info("[menu-render] 菜单图渲染完成, 体积 %s KB", (img.length / 1024).toFixed(1));
2989
+ try {
2990
+ await (0, import_promises.mkdir)(menuCacheDir, { recursive: true });
2991
+ const files = await (0, import_promises.readdir)(menuCacheDir);
2992
+ for (const file of files) await (0, import_promises.unlink)(import_node_path.default.resolve(menuCacheDir, file));
2993
+ await (0, import_promises.writeFile)(cachePath, img);
2994
+ } catch (e) {
2995
+ logger.error("[menu-render] 写入菜单图缓存失败: %o", e);
2989
2996
  }
2997
+ return import_koishi.h.image(`data:image/jpeg;base64,${img.toString("base64")}`);
2990
2998
  } catch (e) {
2991
- ctx.logger("ggcevo").warn("咕咕之战按钮回调处理失败: %o", e);
2992
- await session.send("⚠️ 操作失败, 请重试或直接输入指令使用。").catch(() => {
2993
- });
2994
- }
2999
+ logger.warn("[menu-render] 渲染菜单图片失败: %o", e);
3000
+ return "⚠️ 菜单图片渲染失败,请查看日志后重试。";
3001
+ } finally {
3002
+ await page.close();
3003
+ }
3004
+ }, "renderMenuImage");
3005
+ ctx.command("咕咕之战", "查看咕咕之战内容分类菜单").alias("菜单").action(async ({ session }) => {
3006
+ if (config.menuStyle === "image") {
3007
+ const result = await renderMenuImage(buildMenuHtml("咕咕之战", "内容分类菜单 · 输入指令名即可使用"));
3008
+ return typeof result === "string" ? result : (0, import_koishi.h)("quote", { id: session.messageId }, result);
3009
+ }
3010
+ return buildTextMenu();
2995
3011
  });
2996
3012
  }
2997
3013
  __name(apply, "apply");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-ggcevo",
3
3
  "description": "GGCEVO 综合插件:星际争霸2游戏句柄绑定 + 签到/抽奖/兑换/背包/活动系统等虚拟物品玩法",
4
- "version": "1.3.0",
4
+ "version": "1.3.2",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -28,6 +28,16 @@
28
28
  }
29
29
  },
30
30
  "peerDependencies": {
31
- "koishi": "^4.18.7"
31
+ "koishi": "^4.18.7",
32
+ "koishi-plugin-puppeteer": "^3.9.0",
33
+ "@shangxueink/puppeteer-without-canvas": "^3.0.7"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "koishi-plugin-puppeteer": {
37
+ "optional": true
38
+ },
39
+ "@shangxueink/puppeteer-without-canvas": {
40
+ "optional": true
41
+ }
32
42
  }
33
43
  }
package/readme.md CHANGED
@@ -78,6 +78,12 @@ npm install koishi-plugin-ggcevo
78
78
  - 功能上线前已绑定的旧句柄无时间记录,首次检查时以当前时间作为绑定时间补齐。
79
79
  - 定时任务每天检查一次(启动后延迟 5 秒首次检查),删除超过阈值天数未使用且非当前使用的句柄。
80
80
 
81
+ ### 菜单设置
82
+
83
+ | 配置项 | 类型 | 默认值 | 说明 |
84
+ | --- | --- | --- | --- |
85
+ | `menuStyle` | `'text' \| 'image'` | `'text'` | 咕咕之战菜单渲染方式。`text`=文字菜单(默认,无需额外服务);`image`=图片菜单(需安装并启用 `koishi-plugin-puppeteer` 或 `@shangxueink/puppeteer-without-canvas` 其一,未启用时自动降级为文字菜单并提示) |
86
+
81
87
  ### 活动兑换
82
88
 
83
89
  | 配置项 | 类型 | 默认值 | 说明 |
@@ -102,7 +108,7 @@ npm install koishi-plugin-ggcevo
102
108
 
103
109
  | 指令 | 别名 | 权限 | 说明 |
104
110
  | --- | --- | --- | --- |
105
- | `咕咕之战` | 菜单 | — | 生成咕咕之战内容分类菜单(「每日玩法」「物品背包」「抽奖兑换」「活动」「句柄绑定」「查询信息」六类,仅收录普通用户可用指令)。QQ 官方机器人下使用 Markdown+按钮渲染,点击分类按钮进入子菜单、点击指令按钮直接执行(兑换/使用/绑定/迁移等需参数的指令支持输入框按钮);其余平台自动回退为纯文本菜单 |
111
+ | `咕咕之战` | 菜单 | — | 生成咕咕之战内容分类菜单(「每日玩法」「物品背包」「抽奖兑换」「活动」「句柄绑定」「查询信息」六类,仅收录普通用户可用指令)。根据配置项 `menuStyle` 选择渲染方式:`text`(默认)输出文字分类菜单,`image` 使用 puppeteer 渲染分类图片菜单(需启用 puppeteer 服务,未启用时自动降级为文字菜单) |
106
112
  | `签到` | — | — | 每日签到,获取奖励并发放签到券/补签券等。每月首次签到时,若签到者的游戏句柄匹配管理员福利文档记录,额外发放 50 咕咕币津贴(仅校验句柄) |
107
113
  | `挖矿` | — | — | 领取挂机挖矿收益(咕咕之战玩法)。每半小时收益 4 金币,单次存储上限 24 小时,领取后立即进入下一轮挖矿。累计挖矿总时长与累计收益金币数会被记录统计。受宵禁影响:17:00-24:00 禁止领取 |
108
114
  | `探索` | — | — | 选择星系进行 12 小时探索,结束后领取收益(咕咕之战玩法)。星系:天枢(成功率100%,成功80金币)、赤潮(成功率60%,成功120金币/失败60金币)、千帆(成功率80%,成功50金币/失败25金币,成功时独立概率获得补签券×1(10%)、咕咕币1-3枚(50%),失败无道具)。金币/道具发放到背包,按星系累计统计探索次数/成功次数/累计金币。受宵禁影响:17:00-24:00 禁止 |
@@ -173,6 +179,23 @@ npm install koishi-plugin-ggcevo
173
179
 
174
180
  ## 更新日志
175
181
 
182
+ ### v1.3.2
183
+
184
+ 图片菜单渲染对齐 preview-help 方案,新增磁盘缓存。
185
+
186
+ - **新增图片菜单磁盘缓存**:`咕咕之战` 图片菜单按 HTML 内容 hash 缓存到 `data/ggcevo/menu/`,命中缓存直接读回图片(免去重复渲染、无需 puppeteer 也可返回缓存图),渲染成功后会清理旧缓存文件
187
+ - **渲染参数对齐 preview-help**:viewport 由 900×DPR2 调整为 1280×DPR1,`setContent` 后改用 `waitForNetworkIdle()` 等待页面网络空闲
188
+
189
+ ### v1.3.1
190
+
191
+ 取消 Markdown 按钮菜单,改为可配置的文字/图片菜单。
192
+
193
+ - **取消 Markdown 按钮**:移除 `咕咕之战`(别名 `菜单`)的 QQ 官方机器人 Markdown + 按钮渲染及按钮回调处理,改为输出分类菜单(「每日玩法」「物品背包」「抽奖兑换」「活动」「句柄绑定」「查询信息」六类)
194
+ - **新增配置项 `menuStyle`**(默认 `text`):选择菜单渲染方式
195
+ - `text`:文字分类菜单,无需任何额外服务(默认)
196
+ - `image`:图片菜单,使用 puppeteer 将 HTML 渲染为图片后以 base64 data URL 发送(与 preview-help 插件同方案)
197
+ - **可选依赖 puppeteer 服务**:声明 `koishi-plugin-puppeteer`(^3.9.0)与 `@shangxueink/puppeteer-without-canvas`(^3.0.7)为可选 peer 依赖,任选其一安装启用即可渲染图片菜单;`image` 模式未启用 puppeteer 服务时自动降级为文字菜单并提示
198
+
176
199
  ### v1.3.0
177
200
 
178
201
  咕咕之战菜单改为 QQ 官方机器人 Markdown 按钮菜单(取代 v1.2.0 移除的图片渲染方案,不再依赖 puppeteer)。