From 7a927535da07671c937bb230daaaf78978a25bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BD=E7=83=AD?= <164668043+yichere@users.noreply.github.com> Date: Sun, 5 Oct 2025 13:20:05 +0800 Subject: [PATCH 1/8] =?UTF-8?q?feature:=E6=96=B0=E5=A2=9E=E8=A1=A8?= =?UTF-8?q?=E6=83=85=E5=8C=85=E5=88=B6=E4=BD=9C=E5=B7=A5=E5=85=B7=E5=87=BD?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tool/tool_meme.ts | 106 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/tool/tool_meme.ts diff --git a/src/tool/tool_meme.ts b/src/tool/tool_meme.ts new file mode 100644 index 0000000..8b65f47 --- /dev/null +++ b/src/tool/tool_meme.ts @@ -0,0 +1,106 @@ +import { Tool, ToolInfo, ToolManager } from "./tool"; + +export function registerMeme() { + const list_info: ToolInfo = { + type: "function", + function: { + name: "meme_list", + description: `访问可用表情包列表,配合 meme_key 使用,将返回的结果选择其中一个用于调用 meme_key 的 name 参数`, + parameters: { + type: "object", + properties: { + name: { + type: "string", + description: "随便输入一个,输入该参数防止输入内容不被解析为 json 而报错" + } + + }, + required: ["name"] // 必需参数 + } + } + } + + const tool_list = new Tool(list_info); // 创建一个新tool + const baseurl = "http://meme.lovesealdice.online/"; + tool_list.solve = async (ctx, msg, ai, args) => { // 实现方法,返回字符串提供给AI + return await fetch(baseurl + "get_command").then(res => res.json()) + .then(json => { return JSON.stringify(json) }).catch(err => { return "api 失效,请等待修复。"}); + } + + const get_key = async (name: string) => { + return await fetch(baseurl + name + "/key").then(res => res.json()) + .then(json => { return json }).catch(err => { return "Error: " + err.message }); + } + + + const get_info = async (key: string) => { + return await fetch(baseurl + key + "/info").then(res => res.json()) + .then(json => { return json }).catch(err => { return "Error: " + err.message }); + } + + + const generator_info: ToolInfo = { + type: "function", + function: { + name: "meme_generotor", + description: `制作表情包,使用之前需要必须调用 meme_list`, + parameters: { + type: "object", + properties: { + name: { + type: "string", + description: "表情包名字,为 meme_list 返回的结果" + }, + text: { + type: "array", + items: { type: "string" }, + description: "文字列表" + }, + image: { + type: "array", + items: { type: "string" }, + description: "使用群友的头像作为参数,格式为 [CQ:at,qq=${QQ号}]" + }, + }, + required: ["name"] // 必需参数 + } + } + } + + const tool_generator = new Tool(generator_info); // 创建一个新tool + tool_generator.solve = async (ctx, msg, ai, args) => { // 实现方法,返回字符串提供给AI + let { text, image } = args; + const key = await get_key(args.name).then(res => { return res.result }).catch(err => { return "Error: " + err.message }); + const limit = await get_info(key).then(res => { return res }).catch(err => { return "Error: " + err.message }); + if (text?.length == undefined) text = []; if (image?.length == undefined) image = []; // 该段内容防止 ai 传参时为 null 导致报错 + if (text.length > limit.params_type.max_texts) return `Error: 文字数量过多,该表情包最多支持 ${limit.params_type.max_texts} 段文字`; + if (image.length > limit.params_type.max_images) return `Error: 图片数量过多,该表情包最多支持 ${limit.params_type.max_images} 张图片`; + if (text.length < limit.params_type.min_texts) return `Error: 文字数量过少,该表情包最少需要 ${limit.params_type.min_texts} 段文字`; + if (image.length < limit.params_type.min_images) return `Error: 图片数量过少,该表情包最少需要 ${limit.params_type.min_images} 张图片`; + + const request = { key: key, text: text, image: image, args: {} } + + + console.log(JSON.stringify(request)) + await fetch(baseurl + "meme_generate",{ + method: "POST", + body: JSON.stringify(request), + }).then(res => res.json()) + .then(json => { + if (json.status == "success") { + seal.replyToSender(ctx, msg, `[CQ:image,file=${seal.base64ToImage(json.message)}]`) + return "发送成功"; + } else { + return "Error: " + json.message; + } + }) + + return "" + } + + + + // 注册到toolMap中 + ToolManager.toolMap[list_info.function.name] = tool_list; + ToolManager.toolMap[generator_info.function.name] = tool_generator; +} \ No newline at end of file From 0f572c236e14ca2b1d39137a58fe47ea733ba95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BD=E7=83=AD?= <164668043+yichere@users.noreply.github.com> Date: Sun, 5 Oct 2025 13:22:15 +0800 Subject: [PATCH 2/8] =?UTF-8?q?feature:=E6=96=B0=E5=A2=9E=E8=A1=A8?= =?UTF-8?q?=E6=83=85=E5=8C=85=E5=88=B6=E4=BD=9C=E5=B7=A5=E5=85=B7=E5=87=BD?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tool/tool.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tool/tool.ts b/src/tool/tool.ts index 8087d6d..5d71e1c 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -21,6 +21,7 @@ import { registerGetContext } from "./tool_context" import { registerGetGroupMemberList, registerGetList, registerSearchChat, registerSearchCommonGroup } from "./tool_qq_list" import { registerSetTriggerCondition } from "./tool_trigger" import { registerMusicPlay } from "./tool_music" +import { registerMeme } from "./tool_meme" import { logger } from "../logger" export interface ToolInfo { @@ -212,6 +213,7 @@ export class ToolManager { registerSearchCommonGroup(); registerSetTriggerCondition(); registerMusicPlay(); + registerMeMe(); } /** @@ -497,4 +499,4 @@ export class ToolManager { return ''; } -} \ No newline at end of file +} From 4e7e0c56757959c7796f47f8b94f022489e0ddd7 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Mon, 6 Oct 2025 00:24:02 +0800 Subject: [PATCH 3/8] fix spell & make lint happy --- src/tool/tool.ts | 2 +- src/tool/tool_meme.ts | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/tool/tool.ts b/src/tool/tool.ts index 5d71e1c..c5b2dbe 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -213,7 +213,7 @@ export class ToolManager { registerSearchCommonGroup(); registerSetTriggerCondition(); registerMusicPlay(); - registerMeMe(); + registerMeme(); } /** diff --git a/src/tool/tool_meme.ts b/src/tool/tool_meme.ts index 8b65f47..04d9e82 100644 --- a/src/tool/tool_meme.ts +++ b/src/tool/tool_meme.ts @@ -22,9 +22,9 @@ export function registerMeme() { const tool_list = new Tool(list_info); // 创建一个新tool const baseurl = "http://meme.lovesealdice.online/"; - tool_list.solve = async (ctx, msg, ai, args) => { // 实现方法,返回字符串提供给AI + tool_list.solve = async (_, __, ___, ____) => { // 实现方法,返回字符串提供给AI return await fetch(baseurl + "get_command").then(res => res.json()) - .then(json => { return JSON.stringify(json) }).catch(err => { return "api 失效,请等待修复。"}); + .then(json => { return JSON.stringify(json) }).catch(_ => { return "api 失效,请等待修复。" }); } const get_key = async (name: string) => { @@ -32,13 +32,11 @@ export function registerMeme() { .then(json => { return json }).catch(err => { return "Error: " + err.message }); } - const get_info = async (key: string) => { return await fetch(baseurl + key + "/info").then(res => res.json()) .then(json => { return json }).catch(err => { return "Error: " + err.message }); } - const generator_info: ToolInfo = { type: "function", function: { @@ -68,7 +66,7 @@ export function registerMeme() { } const tool_generator = new Tool(generator_info); // 创建一个新tool - tool_generator.solve = async (ctx, msg, ai, args) => { // 实现方法,返回字符串提供给AI + tool_generator.solve = async (ctx, msg, _, args) => { // 实现方法,返回字符串提供给AI let { text, image } = args; const key = await get_key(args.name).then(res => { return res.result }).catch(err => { return "Error: " + err.message }); const limit = await get_info(key).then(res => { return res }).catch(err => { return "Error: " + err.message }); @@ -78,11 +76,10 @@ export function registerMeme() { if (text.length < limit.params_type.min_texts) return `Error: 文字数量过少,该表情包最少需要 ${limit.params_type.min_texts} 段文字`; if (image.length < limit.params_type.min_images) return `Error: 图片数量过少,该表情包最少需要 ${limit.params_type.min_images} 张图片`; - const request = { key: key, text: text, image: image, args: {} } - + const request = { key: key, text: text, image: image, args: {} } console.log(JSON.stringify(request)) - await fetch(baseurl + "meme_generate",{ + await fetch(baseurl + "meme_generate", { method: "POST", body: JSON.stringify(request), }).then(res => res.json()) @@ -98,8 +95,6 @@ export function registerMeme() { return "" } - - // 注册到toolMap中 ToolManager.toolMap[list_info.function.name] = tool_list; ToolManager.toolMap[generator_info.function.name] = tool_generator; From 17614dd18f02bbc5a7190e096dac8e8d0c517d08 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Mon, 6 Oct 2025 01:03:20 +0800 Subject: [PATCH 4/8] style and bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 进行一些调整。 --- src/tool/tool_meme.ts | 73 ++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/src/tool/tool_meme.ts b/src/tool/tool_meme.ts index 04d9e82..8416954 100644 --- a/src/tool/tool_meme.ts +++ b/src/tool/tool_meme.ts @@ -1,47 +1,45 @@ +import { ConfigManager } from "../config/config"; import { Tool, ToolInfo, ToolManager } from "./tool"; +import { logger } from "../logger" + +const baseurl = "http://meme.lovesealdice.online/"; + +const get_key = async (name: string) => { + return await fetch(baseurl + name + "/key").then(res => res.json()) + .then(json => { return json }).catch(err => { return "Error: " + err.message }); +} + +const get_info = async (key: string) => { + return await fetch(baseurl + key + "/info").then(res => res.json()) + .then(json => { return json }).catch(err => { return "Error: " + err.message }); +} export function registerMeme() { const list_info: ToolInfo = { type: "function", function: { name: "meme_list", - description: `访问可用表情包列表,配合 meme_key 使用,将返回的结果选择其中一个用于调用 meme_key 的 name 参数`, + description: `访问可用表情包列表`, parameters: { type: "object", properties: { - name: { - type: "string", - description: "随便输入一个,输入该参数防止输入内容不被解析为 json 而报错" - } - }, - required: ["name"] // 必需参数 + required: [] // 必需参数 } } } const tool_list = new Tool(list_info); // 创建一个新tool - const baseurl = "http://meme.lovesealdice.online/"; tool_list.solve = async (_, __, ___, ____) => { // 实现方法,返回字符串提供给AI return await fetch(baseurl + "get_command").then(res => res.json()) .then(json => { return JSON.stringify(json) }).catch(_ => { return "api 失效,请等待修复。" }); } - const get_key = async (name: string) => { - return await fetch(baseurl + name + "/key").then(res => res.json()) - .then(json => { return json }).catch(err => { return "Error: " + err.message }); - } - - const get_info = async (key: string) => { - return await fetch(baseurl + key + "/info").then(res => res.json()) - .then(json => { return json }).catch(err => { return "Error: " + err.message }); - } - const generator_info: ToolInfo = { type: "function", function: { - name: "meme_generotor", - description: `制作表情包,使用之前需要必须调用 meme_list`, + name: "meme_generator", + description: `制作表情包,使用之前需要调用 meme_list获取可用表情包列表`, parameters: { type: "object", properties: { @@ -54,31 +52,42 @@ export function registerMeme() { items: { type: "string" }, description: "文字列表" }, - image: { + members: { type: "array", items: { type: "string" }, - description: "使用群友的头像作为参数,格式为 [CQ:at,qq=${QQ号}]" + description: '被用来绘制meme的用户名称' + (ConfigManager.message.showNumber ? '或纯数字QQ号' : '') }, }, - required: ["name"] // 必需参数 + required: ["name", "text", "members"] // 必需参数 } } } const tool_generator = new Tool(generator_info); // 创建一个新tool - tool_generator.solve = async (ctx, msg, _, args) => { // 实现方法,返回字符串提供给AI - let { text, image } = args; - const key = await get_key(args.name).then(res => { return res.result }).catch(err => { return "Error: " + err.message }); + tool_generator.solve = async (ctx, msg, ai, args) => { // 实现方法,返回字符串提供给AI + const { name, text = [], members = [] } = args; + const key = await get_key(name).then(res => { return res.result }).catch(err => { return "Error: " + err.message }); const limit = await get_info(key).then(res => { return res }).catch(err => { return "Error: " + err.message }); - if (text?.length == undefined) text = []; if (image?.length == undefined) image = []; // 该段内容防止 ai 传参时为 null 导致报错 - if (text.length > limit.params_type.max_texts) return `Error: 文字数量过多,该表情包最多支持 ${limit.params_type.max_texts} 段文字`; - if (image.length > limit.params_type.max_images) return `Error: 图片数量过多,该表情包最多支持 ${limit.params_type.max_images} 张图片`; - if (text.length < limit.params_type.min_texts) return `Error: 文字数量过少,该表情包最少需要 ${limit.params_type.min_texts} 段文字`; - if (image.length < limit.params_type.min_images) return `Error: 图片数量过少,该表情包最少需要 ${limit.params_type.min_images} 张图片`; + + if (text.length > limit.params_type.max_texts || text.length < limit.params_type.min_texts) { + return `文字数量错误,该表情包文字范围为 ${limit.params_type.min_texts} - ${limit.params_type.max_texts} 段,用户范围为 ${limit.params_type.min_images} - ${limit.params_type.max_images} 名`; + } + if (members.length > limit.params_type.max_images || members.length < limit.params_type.min_images) { + return `用户数量错误,该表情包文字范围为 ${limit.params_type.min_texts} - ${limit.params_type.max_texts} 段,用户范围为 ${limit.params_type.min_images} - ${limit.params_type.max_images} 名`; + } + + const image = []; + for (const name of members) { + const uid = await ai.context.findUserId(ctx, name); + if (uid === null) { + return `未找到<${name}>`; + } + image.push(`[CQ:at,qq=${uid.replace(/\D/g, "")}]`); + } const request = { key: key, text: text, image: image, args: {} } + logger.info(JSON.stringify(request, null, 2)); - console.log(JSON.stringify(request)) await fetch(baseurl + "meme_generate", { method: "POST", body: JSON.stringify(request), From 56c2421ba581f3c1e662f8f9a2788497f4b63120 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Mon, 6 Oct 2025 01:38:06 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=A1=A8=E6=83=85?= =?UTF-8?q?=E5=8C=85info=E5=B0=81=E8=A3=85=E5=88=B0=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tool/tool_meme.ts | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/tool/tool_meme.ts b/src/tool/tool_meme.ts index 8416954..800a011 100644 --- a/src/tool/tool_meme.ts +++ b/src/tool/tool_meme.ts @@ -4,14 +4,26 @@ import { logger } from "../logger" const baseurl = "http://meme.lovesealdice.online/"; -const get_key = async (name: string) => { - return await fetch(baseurl + name + "/key").then(res => res.json()) - .then(json => { return json }).catch(err => { return "Error: " + err.message }); +interface MemeInfo { + params_type: { + min_texts: number, + max_texts: number, + min_images: number, + max_images: number, + } } -const get_info = async (key: string) => { - return await fetch(baseurl + key + "/info").then(res => res.json()) - .then(json => { return json }).catch(err => { return "Error: " + err.message }); +async function getInfo(name: string): Promise<{ key: string, info: MemeInfo }> { + try { + const res1 = await fetch(baseurl + name + "/key"); + const json1 = await res1.json(); + const key = json1.result; + const res2 = await fetch(baseurl + key + "/info"); + const json2 = await res2.json(); + return { key, info: json2 }; + } catch (err) { + throw new Error("获取表情包信息失败"); + } } export function registerMeme() { @@ -66,9 +78,8 @@ export function registerMeme() { const tool_generator = new Tool(generator_info); // 创建一个新tool tool_generator.solve = async (ctx, msg, ai, args) => { // 实现方法,返回字符串提供给AI const { name, text = [], members = [] } = args; - const key = await get_key(name).then(res => { return res.result }).catch(err => { return "Error: " + err.message }); - const limit = await get_info(key).then(res => { return res }).catch(err => { return "Error: " + err.message }); + const { key, info: limit } = await getInfo(name); if (text.length > limit.params_type.max_texts || text.length < limit.params_type.min_texts) { return `文字数量错误,该表情包文字范围为 ${limit.params_type.min_texts} - ${limit.params_type.max_texts} 段,用户范围为 ${limit.params_type.min_images} - ${limit.params_type.max_images} 名`; } From 0ad63abc3418848ba67ffcddcf7cb6b005d29fba Mon Sep 17 00:00:00 2001 From: yichere <3142438625@qq.com> Date: Mon, 6 Oct 2025 02:56:23 +0800 Subject: [PATCH 6/8] fix image array bug modified: src/tool/tool_meme.ts --- src/tool/tool_meme.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tool/tool_meme.ts b/src/tool/tool_meme.ts index 800a011..5444d55 100644 --- a/src/tool/tool_meme.ts +++ b/src/tool/tool_meme.ts @@ -1,6 +1,5 @@ import { ConfigManager } from "../config/config"; import { Tool, ToolInfo, ToolManager } from "./tool"; -import { logger } from "../logger" const baseurl = "http://meme.lovesealdice.online/"; @@ -93,11 +92,12 @@ export function registerMeme() { if (uid === null) { return `未找到<${name}>`; } - image.push(`[CQ:at,qq=${uid.replace(/\D/g, "")}]`); + image.push(`https://q.qlogo.cn/headimg_dl?dst_uin=${uid.replace(/\D/g, "")}&spec=640&img_type=jpg`); } + // 忘记了之前插件是手动处理的 cq 码() const request = { key: key, text: text, image: image, args: {} } - logger.info(JSON.stringify(request, null, 2)); + // 调试信息忘记删了果咩纳塞 await fetch(baseurl + "meme_generate", { method: "POST", From e55a940f8efed3785df07b65d3500619acaebac2 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Mon, 6 Oct 2025 16:03:19 +0800 Subject: [PATCH 7/8] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E8=BF=94=E5=9B=9E=E6=96=87=E6=9C=AC=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tool/tool_meme.ts | 46 +++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/tool/tool_meme.ts b/src/tool/tool_meme.ts index 5444d55..1991517 100644 --- a/src/tool/tool_meme.ts +++ b/src/tool/tool_meme.ts @@ -42,8 +42,13 @@ export function registerMeme() { const tool_list = new Tool(list_info); // 创建一个新tool tool_list.solve = async (_, __, ___, ____) => { // 实现方法,返回字符串提供给AI - return await fetch(baseurl + "get_command").then(res => res.json()) - .then(json => { return JSON.stringify(json) }).catch(_ => { return "api 失效,请等待修复。" }); + try { + const res = await fetch(baseurl + "get_command"); + const json = await res.json(); + return json.map((item: string[]) => item[0]).join("、"); + } catch (err) { + return "获取表情包列表失败:" + err.message; + } } const generator_info: ToolInfo = { @@ -94,25 +99,28 @@ export function registerMeme() { } image.push(`https://q.qlogo.cn/headimg_dl?dst_uin=${uid.replace(/\D/g, "")}&spec=640&img_type=jpg`); } - // 忘记了之前插件是手动处理的 cq 码() - - const request = { key: key, text: text, image: image, args: {} } - // 调试信息忘记删了果咩纳塞 - await fetch(baseurl + "meme_generate", { - method: "POST", - body: JSON.stringify(request), - }).then(res => res.json()) - .then(json => { - if (json.status == "success") { - seal.replyToSender(ctx, msg, `[CQ:image,file=${seal.base64ToImage(json.message)}]`) - return "发送成功"; - } else { - return "Error: " + json.message; - } - }) + try { + const res = await fetch(baseurl + "meme_generate", { + method: "POST", + body: JSON.stringify({ + key, + text, + image, + args: {} + }), + }); - return "" + const json = await res.json(); + if (json.status == "success") { + seal.replyToSender(ctx, msg, `[CQ:image,file=${seal.base64ToImage(json.message)}]`) + return "发送成功"; + } else { + throw new Error(json.message); + } + } catch (err) { + return "生成表情包失败:" + err.message; + } } // 注册到toolMap中 From 3d0e7ba7116f02d8cf1d7a9955b3e9a2101445e1 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Mon, 6 Oct 2025 16:41:52 +0800 Subject: [PATCH 8/8] Update update.ts --- src/update.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/update.ts b/src/update.ts index 6b55db3..1cd6790 100644 --- a/src/update.ts +++ b/src/update.ts @@ -1,7 +1,8 @@ // 版本更新日志,格式为 "版本号": "更新内容",版本号格式为 "x.y.z",按照时间顺序从新到旧排列。 export const updateInfo = { "4.10.2":`- 新增请求超时相关 -- 修复addMemory时,keywords可以为null的问题`, +- 修复addMemory时,keywords可以为null的问题 +- 新增表情包制作工具`, "4.10.1": `- 可能修复了非指令无法响应的问题 - 修复了构建ctx时,isPrivate始终为0的问题 - 新增保存图片功能