Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions src/AI/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { sendITTRequest } from "../service";
import { generateId } from "../utils/utils";
import { logger } from "../logger";
import { AI } from "./AI";
import { MessageSegment } from "../utils/utils_string";
import { MessageSegment, parseSpecialTokens } from "../utils/utils_string";

export class Image {
static validKeys: (keyof Image)[] = ['id', 'file', 'content'];
Expand Down Expand Up @@ -257,15 +257,21 @@ ${img.CQCode}`;
}

static async extractExistingImagesToSave(ctx: seal.MsgContext, ai: AI, s: string): Promise<Image[]> {
const images = [];
const match = s.match(/[<<][\|│|]img:.+?(?:[\|│|][>>]|[\|│|>>])/g);
if (match) {
for (let i = 0; i < match.length; i++) {
const id = match[i].match(/[<<][\|│|]img:(.+?)(?:[\|│|][>>]|[\|│|>>])/)[1];
const image = await ai.context.findImage(ctx, id);
if (image) {
if (image.type === 'url') await image.urlToBase64();
images.push(image);
const segs = parseSpecialTokens(s);
const images: Image[] = [];
for (const seg of segs) {
switch (seg.type) {
case 'img': {
const id = seg.content;
const image = await ai.context.findImage(ctx, id);

if (image) {
if (image.type === 'url') await image.urlToBase64();
images.push(image);
} else {
logger.warning(`无法找到图片:${id}`);
}
break;
}
}
}
Expand Down
196 changes: 185 additions & 11 deletions src/tool/tool_message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { AIManager } from "../AI/AI";
import { ConfigManager } from "../config/configManager";
import { replyToSender, transformMsgIdBack } from "../utils/utils";
import { getCtxAndMsg } from "../utils/utils_seal";
import { handleReply, MessageSegment, transformArrayToContent } from "../utils/utils_string";
import { handleReply, MessageSegment, parseSpecialTokens, transformArrayToContent } from "../utils/utils_string";
import { Tool, ToolManager } from "./tool";
import { CQTYPESALLOW } from "../config/config";
import { deleteMsg, getGroupMemberInfo, getMsg, netExists } from "../utils/utils_ob11";
import { CQTYPESALLOW, faceMap } from "../config/config";
import { deleteMsg, getGroupMemberInfo, getMsg, sendGroupForwardMsg, sendPrivateForwardMsg, netExists } from "../utils/utils_ob11";
import { logger } from "../logger";
import { Image } from "../AI/image";

export function registerMessage() {
const toolSend = new Tool({
Expand Down Expand Up @@ -50,13 +52,17 @@ export function registerMessage() {
`来自<${ctx.player.name}>${showNumber ? `(${ctx.player.userId.replace(/^.+:/, '')})` : ``}` :
`来自群聊<${ctx.group.groupName}>${showNumber ? `(${ctx.group.groupId.replace(/^.+:/, '')})` : ``}`;

const originalImages = [];
const match = content.match(/[<<][\|│|]img:.+?(?:[\|│|][>>]|[\|│|>>])/g);
if (match) {
for (let i = 0; i < match.length; i++) {
const id = match[i].match(/[<<][\|│|]img:(.+?)(?:[\|│|][>>]|[\|│|>>])/)[1].trim().slice(0, 6);
const image = await ai.context.findImage(ctx, id);
if (image) originalImages.push(image);
const segs = parseSpecialTokens(content);
const originalImages: Image[] = [];
for (const seg of segs) {
switch (seg.type) {
case 'img': {
const id = seg.content;
const image = await ai.context.findImage(ctx, id);
if (image) originalImages.push(image);
else logger.warning(`无法找到图片:${id}`);
break;
}
}
}

Expand Down Expand Up @@ -178,4 +184,172 @@ export function registerMessage() {
await deleteMsg(epId, transformMsgIdBack(msg_id));
return { content: `已撤回消息${msg_id}`, images: [] };
}
}

const toolMerge = new Tool({
type: 'function',
function: {
name: 'send_forward_msg',
description: '发送合并转发消息',
parameters: {
type: 'object',
properties: {
msg_type: {
type: 'string',
description: '消息类型,私聊或群聊',
enum: ['private', 'group']
},
name: {
type: 'string',
description: '用户名称或群聊名称' + (ConfigManager.message.showNumber ? '或纯数字QQ号、群号' : '') + ',实际使用时与消息类型对应'
},
messages: {
type: 'array',
description: '消息节点列表,可以有多个',
items: {
type: 'object',
properties: {
name: {
type: 'string',
description: '用户名称' + (ConfigManager.message.showNumber ? '或纯数字QQ号' : '')
},
nickname: {
type: 'string',
description: '发送者名称,默认与name相同'
},
content: {
type: 'string',
description: '消息内容'
}
},
required: ['content']
}
}
},
required: ['msg_type', 'name', 'messages']
}
}
});
toolMerge.solve = async (ctx, _, ai, args) => {
const { msg_type, name, messages } = args;

if (!netExists()) return { content: `未找到ob11网络连接依赖,请提示用户安装`, images: [] };

const messagesToSend = [];
const images: Image[] = [];
for (const messageItem of messages) {
const segs = parseSpecialTokens(messageItem.content);
const content: MessageSegment[] = [];
for (const seg of segs) {
switch (seg.type) {
case 'text': {
content.push({
type: 'text',
data: {
text: seg.content
}
})
break;
}
case 'at': {
const name = seg.content;
const ui = await ai.context.findUserInfo(ctx, name);
if (ui !== null) {
content.push({
type: 'at',
data: {
qq: ui.id.replace(/^.+:/, "")
}
})
} else {
logger.warning(`无法找到用户:${name}`);
content.push({
type: 'text',
data: {
text: ` @${name} `
}
})
}
break;
}
case 'quote': {
const msgId = seg.content;
content.push({
type: 'reply',
data: { id: String(transformMsgIdBack(msgId)) }
})
break;
}
case 'img': {
const id = seg.content;
const image = await ai.context.findImage(ctx, id);

if (image) {
if (image.type === 'local') break;
images.push(image);
content.push({
type: 'image',
data: { file: image.type === 'base64' ? seal.base64ToImage(image.base64) : image.file }
})
} else {
logger.warning(`无法找到图片:${id}`);
}
break;
}
case 'face': {
const faceId = Object.keys(faceMap).find(key => faceMap[key] === seg.content) || '';
content.push({
type: 'face',
data: { id: faceId }
})
break;
}
}
}

if (content.length === 0) {
return { content: `消息长度不能为0`, images: [] };
}

let userId = "";
let nickname = messageItem.nickname || "";
const ui = await ai.context.findUserInfo(ctx, messageItem.name, true);
if (ui !== null) {
userId = ui.id.replace(/^.+:/, "");
nickname = nickname || ui.name;
}

messagesToSend.push({
type: 'node',
data: {
user_id: userId,
nickname: nickname,
content: content
}
});
}

const news = null;
const prompt = "";
const summary = "";
const source = "";

if (msg_type === "private") {
const ui = await ai.context.findUserInfo(ctx, name, true);
if (ui === null) return { content: `未找到<${name}>`, images: [] };
if (ui.id === ctx.endPoint.userId) return { content: `禁止向自己发送消息`, images: [] };

await sendPrivateForwardMsg(ctx.endPoint.userId, ui.id.replace(/^.+:/, ""), messagesToSend, news, prompt, summary, source);
} else if (msg_type === "group") {
const gi = await ai.context.findGroupInfo(ctx, name);
if (gi === null) return { content: `未找到<${name}>`, images: [] };

await sendGroupForwardMsg(ctx.endPoint.userId, gi.id.replace(/^.+:/, ""), messagesToSend, news, prompt, summary, source);
} else {
return { content: `未知的消息类型<${msg_type}>`, images: [] };
}

return { content: `发送合并消息成功`, images: images };
}
}

// TODO: 合并消息嵌套
5 changes: 4 additions & 1 deletion src/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ export const updateInfo = {
"4.12.1": `- 新增按时间搜索记忆
- 新增图片头像ID发送
- 将img命令改为ai子命令
- 新增render嵌入图片`,
- 新增render嵌入图片
- 新增tti保存
- 修复tool关闭状态发生错误反转的问题
- 新增合并转发tool`,
"4.12.0": `- 新增通过名称选择角色设定功能
- 修复获取好友、群聊等列表时的bug
- 修复了调用函数时,无需cmdArgs的函数也会报错的问题
Expand Down
50 changes: 49 additions & 1 deletion src/utils/utils_ob11.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,52 @@ export async function sendGroupAISound(epId: string, characterId: string, group_
logger.error(`发送群 ${group_id} AI 声聊合成语音失败:${e}`);
return;
}
}
}

export async function sendPrivateForwardMsg(epId: string, user_id: string,
messages: MessageSegment[],
news: string[],
prompt: string,
summary: string,
source: string): Promise<any> {
const net = getNet();
if (!net) return null;
try {
const data = await net.callApi(epId, 'send_private_forward_msg', {
user_id,
messages,
news,
prompt,
summary,
source
})
return data;
} catch (e) {
logger.error(`发送用户 ${user_id} 转发消息失败:${e}`);
return null;
}
}

export async function sendGroupForwardMsg(epId: string, group_id: string,
messages: MessageSegment[],
news: string[],
prompt: string,
summary: string,
source: string): Promise<any> {
const net = getNet();
if (!net) return null;
try {
const data = await net.callApi(epId, 'send_group_forward_msg', {
group_id,
messages,
news,
prompt,
summary,
source
})
return data;
} catch (e) {
logger.error(`发送群 ${group_id} 转发消息失败:${e}`);
return null;
}
}