koishi-plugin-ggcevo 1.3.1 → 1.3.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.
- package/lib/index.js +77 -7
- package/package.json +1 -1
- package/readme.md +15 -0
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({
|
|
@@ -2423,10 +2437,10 @@ ${rewardLines.join("\n")}` : "";
|
|
|
2423
2437
|
return record || null;
|
|
2424
2438
|
}
|
|
2425
2439
|
}, "getValidDocsToken");
|
|
2426
|
-
const docsApiRequest = /* @__PURE__ */ __name(async (method,
|
|
2440
|
+
const docsApiRequest = /* @__PURE__ */ __name(async (method, path2, data) => {
|
|
2427
2441
|
const token = await getValidDocsToken();
|
|
2428
2442
|
if (!token) throw new Error("腾讯文档应用级账号不可用, 请检查插件配置(Client ID / Client Secret)");
|
|
2429
|
-
const url = `https://docs.qq.com/openapi${
|
|
2443
|
+
const url = `https://docs.qq.com/openapi${path2}`;
|
|
2430
2444
|
const headers = {
|
|
2431
2445
|
"Access-Token": token.access_token,
|
|
2432
2446
|
"Client-Id": config.tencentDocsClientId,
|
|
@@ -2952,19 +2966,35 @@ ${rewardLines.join("\n")}` : "";
|
|
|
2952
2966
|
</body>
|
|
2953
2967
|
</html>`;
|
|
2954
2968
|
}, "buildMenuHtml");
|
|
2969
|
+
const menuCacheDir = import_node_path.default.resolve(ctx.baseDir, "data/ggcevo/menu");
|
|
2955
2970
|
const renderMenuImage = /* @__PURE__ */ __name(async (html) => {
|
|
2956
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 `data:image/jpeg;base64,${cached.toString("base64")}`;
|
|
2977
|
+
}
|
|
2957
2978
|
if (!ctx.puppeteer) {
|
|
2958
2979
|
logger.warn("[menu-render] 未启用 puppeteer 服务");
|
|
2959
2980
|
return "⚠️ 未启用 puppeteer 服务,无法渲染图片菜单。请安装并启用 `koishi-plugin-puppeteer` 或 `@shangxueink/puppeteer-without-canvas`。";
|
|
2960
2981
|
}
|
|
2961
2982
|
const page = await ctx.puppeteer.page();
|
|
2962
2983
|
try {
|
|
2963
|
-
await page.setViewport({ width:
|
|
2964
|
-
await page.setContent(html
|
|
2984
|
+
await page.setViewport({ width: 1280, height: 100, deviceScaleFactor: 1 });
|
|
2985
|
+
await page.setContent(html);
|
|
2986
|
+
await page.waitForNetworkIdle();
|
|
2965
2987
|
const img = await page.screenshot({ type: "jpeg", quality: 85, fullPage: true, encoding: "binary" });
|
|
2966
2988
|
logger.info("[menu-render] 菜单图渲染完成, 体积 %s KB", (img.length / 1024).toFixed(1));
|
|
2967
|
-
|
|
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);
|
|
2996
|
+
}
|
|
2997
|
+
return `data:image/jpeg;base64,${img.toString("base64")}`;
|
|
2968
2998
|
} catch (e) {
|
|
2969
2999
|
logger.warn("[menu-render] 渲染菜单图片失败: %o", e);
|
|
2970
3000
|
return "⚠️ 菜单图片渲染失败,请查看日志后重试。";
|
|
@@ -2972,10 +3002,50 @@ ${rewardLines.join("\n")}` : "";
|
|
|
2972
3002
|
await page.close();
|
|
2973
3003
|
}
|
|
2974
3004
|
}, "renderMenuImage");
|
|
3005
|
+
const sendMenuImage = /* @__PURE__ */ __name(async (session, dataUrl) => {
|
|
3006
|
+
const logger = ctx.logger("ggcevo");
|
|
3007
|
+
if (!dataUrl.startsWith("data:image")) return dataUrl;
|
|
3008
|
+
if (session.platform !== "qq") return import_koishi.h.image(dataUrl);
|
|
3009
|
+
const bot = session.bot;
|
|
3010
|
+
const match = /^data:image\/[\w.+-]+;base64,(.*)$/.exec(dataUrl);
|
|
3011
|
+
if (!match) return "⚠️ 菜单图片数据无效。";
|
|
3012
|
+
try {
|
|
3013
|
+
const fileData = match[1];
|
|
3014
|
+
const msgSeq = (session["seq"] || 0) + 1;
|
|
3015
|
+
session["seq"] = msgSeq;
|
|
3016
|
+
if (session.isDirect) {
|
|
3017
|
+
const fileRes = await bot.internal.sendFilePrivate(session.userId, { file_type: 1, srv_send_msg: false, file_data: fileData });
|
|
3018
|
+
logger.info("[menu-render] QQ 官方图片上传响应: %o", fileRes);
|
|
3019
|
+
await bot.internal.sendPrivateMessage(session.channelId, {
|
|
3020
|
+
msg_type: 7,
|
|
3021
|
+
media: fileRes,
|
|
3022
|
+
content: "",
|
|
3023
|
+
msg_id: session.messageId,
|
|
3024
|
+
msg_seq: msgSeq
|
|
3025
|
+
});
|
|
3026
|
+
} else {
|
|
3027
|
+
const fileRes = await bot.internal.sendFileGuild(session.channelId, { file_type: 1, srv_send_msg: false, file_data: fileData });
|
|
3028
|
+
logger.info("[menu-render] QQ 官方图片上传响应: %o", fileRes);
|
|
3029
|
+
const msgRes = await bot.internal.sendMessage(session.channelId, {
|
|
3030
|
+
msg_type: 7,
|
|
3031
|
+
media: fileRes,
|
|
3032
|
+
content: "",
|
|
3033
|
+
msg_id: session.messageId,
|
|
3034
|
+
msg_seq: msgSeq
|
|
3035
|
+
});
|
|
3036
|
+
logger.info("[menu-render] QQ 官方消息发送响应: %o", msgRes);
|
|
3037
|
+
if (msgRes?.audit_id) logger.warn("[menu-render] QQ 消息进入审核(audit_id=%s), 需开启 MESSAGE_AUDIT intent 才能确认最终是否发出", msgRes.audit_id);
|
|
3038
|
+
}
|
|
3039
|
+
return;
|
|
3040
|
+
} catch (e) {
|
|
3041
|
+
logger.error("[menu-render] QQ 官方发送菜单图失败: %o", e);
|
|
3042
|
+
return "⚠️ QQ 发送菜单图失败,请查看日志中的错误信息。";
|
|
3043
|
+
}
|
|
3044
|
+
}, "sendMenuImage");
|
|
2975
3045
|
ctx.command("咕咕之战", "查看咕咕之战内容分类菜单").alias("菜单").action(async ({ session }) => {
|
|
2976
3046
|
if (config.menuStyle === "image") {
|
|
2977
|
-
const
|
|
2978
|
-
return
|
|
3047
|
+
const dataUrl = await renderMenuImage(buildMenuHtml("咕咕之战", "内容分类菜单 · 输入指令名即可使用"));
|
|
3048
|
+
return sendMenuImage(session, dataUrl);
|
|
2979
3049
|
}
|
|
2980
3050
|
return buildTextMenu();
|
|
2981
3051
|
});
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -179,6 +179,21 @@ npm install koishi-plugin-ggcevo
|
|
|
179
179
|
|
|
180
180
|
## 更新日志
|
|
181
181
|
|
|
182
|
+
### v1.3.3
|
|
183
|
+
|
|
184
|
+
修复 QQ 官方机器人不发送图片菜单且无报错的问题。
|
|
185
|
+
|
|
186
|
+
- **移除图片消息的引用回复**:`咕咕之战` 图片菜单不再包裹 `<quote>` 引用(与 preview-help 一致,裸发图片)。此前消息同时携带 `message_reference` + 被动回复 `msg_id` + `media` 的组合,在 QQ 官方接口下可能被静默丢弃
|
|
187
|
+
- **QQ 官方机器人手动发送 + 真实响应日志**:图片模式下,QQ 平台不再走适配器(适配器会吞掉 QQ 接口错误导致「无报错」),改为插件直接调用 QQ 官方富媒体上传(`/v2/groups/{id}/files`)与消息发送接口,并将 QQ 真实返回(含 `file_uuid`/`audit_id`/错误码)输出到 `[menu-render]` 日志;若返回 `audit_id`(消息进入审核)会额外警告,提示需开启 `MESSAGE_AUDIT` intent
|
|
188
|
+
- **非 QQ 平台**:图片直接发送,不再带引用
|
|
189
|
+
|
|
190
|
+
### v1.3.2
|
|
191
|
+
|
|
192
|
+
图片菜单渲染对齐 preview-help 方案,新增磁盘缓存。
|
|
193
|
+
|
|
194
|
+
- **新增图片菜单磁盘缓存**:`咕咕之战` 图片菜单按 HTML 内容 hash 缓存到 `data/ggcevo/menu/`,命中缓存直接读回图片(免去重复渲染、无需 puppeteer 也可返回缓存图),渲染成功后会清理旧缓存文件
|
|
195
|
+
- **渲染参数对齐 preview-help**:viewport 由 900×DPR2 调整为 1280×DPR1,`setContent` 后改用 `waitForNetworkIdle()` 等待页面网络空闲
|
|
196
|
+
|
|
182
197
|
### v1.3.1
|
|
183
198
|
|
|
184
199
|
取消 Markdown 按钮菜单,改为可配置的文字/图片菜单。
|