import { OB11MessageData, OB11MessageDataType, OB11MessageMixType, OB11MessageNode, OB11PostContext, OB11PostSendMsg, } from '@/onebot/types'; import { ActionName, BaseCheckResult } from '@/onebot/action/types'; import { decodeCQCode } from '@/onebot/cqcode'; import { MessageUnique } from '@/common/message-unique'; import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendArkElement, SendMessageElement } from '@/core'; import BaseAction from '../BaseAction'; import { rawMsgWithSendMsg } from "@/core/packet/msg/converter"; import { PacketMsg } from "@/core/packet/msg/message"; import { ForwardMsgBuilder } from "@/common/forward-msg-builder"; export interface ReturnDataType { message_id: number; res_id?: string; } export enum ContextMode { Normal = 0, Private = 1, Group = 2 } // Normalizes a mixed type (CQCode/a single segment/segment array) into a segment array. export function normalize(message: OB11MessageMixType, autoEscape = false): OB11MessageData[] { return typeof message === 'string' ? ( autoEscape ? [{ type: OB11MessageDataType.text, data: { text: message } }] : decodeCQCode(message) ) : Array.isArray(message) ? message : [message]; } export async function createContext(core: NapCatCore, payload: OB11PostContext, contextMode: ContextMode): Promise { // This function determines the type of message by the existence of user_id / group_id, // not message_type. // This redundant design of Ob11 here should be blamed. if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) { return { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString(), }; } if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) { const Uid = await core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); if (!Uid) throw new Error('无法获取用户信息'); const isBuddy = await core.apis.FriendApi.isBuddy(Uid); if (!isBuddy) { const ret = await core.apis.MsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, Uid); if (ret.tmpChatInfo?.groupCode) { return { chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid: Uid, guildId: '', }; } if (payload.group_id) { return { chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid: Uid, guildId: payload.group_id.toString(), }; } return { chatType: ChatType.KCHATTYPEC2C, peerUid: Uid!, guildId: '', }; } return { chatType: ChatType.KCHATTYPEC2C, peerUid: Uid, guildId: '', }; } throw new Error('请指定 group_id 或 user_id'); } function getSpecialMsgNum(payload: OB11PostSendMsg, msgType: OB11MessageDataType): number { if (Array.isArray(payload.message)) { return payload.message.filter(msg => msg.type == msgType).length; } return 0; } export class SendMsg extends BaseAction { actionName = ActionName.SendMsg; contextMode = ContextMode.Normal; protected async check(payload: OB11PostSendMsg): Promise { const messages = normalize(payload.message); const nodeElementLength = getSpecialMsgNum(payload, OB11MessageDataType.node); if (nodeElementLength > 0 && nodeElementLength != messages.length) { return { valid: false, message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素', }; } return { valid: true }; } async _handle(payload: OB11PostSendMsg): Promise { this.contextMode = ContextMode.Normal; if (payload.message_type === 'group') this.contextMode = ContextMode.Group; if (payload.message_type === 'private') this.contextMode = ContextMode.Private; const peer = await createContext(this.core, payload, this.contextMode); const messages = normalize( payload.message, typeof payload.auto_escape === 'string' ? payload.auto_escape === 'true' : !!payload.auto_escape, ); if (getSpecialMsgNum(payload, OB11MessageDataType.node)) { const packetMode = this.core.apis.PacketApi.available; const returnMsgAndResId = packetMode ? await this.handleForwardedNodesPacket(peer, messages as OB11MessageNode[], payload.source, payload.news, payload.summary, payload.prompt) : await this.handleForwardedNodes(peer, messages as OB11MessageNode[]); if (returnMsgAndResId.message) { const msgShortId = MessageUnique.createUniqueMsgId({ guildId: '', peerUid: peer.peerUid, chatType: peer.chatType, }, (returnMsgAndResId.message)!.msgId); return { message_id: msgShortId!, res_id: returnMsgAndResId.res_id }; } else if (returnMsgAndResId.res_id && !returnMsgAndResId.message) { throw Error(`发送转发消息(res_id:${returnMsgAndResId.res_id} 失败`); } throw Error('发送转发消息失败'); } else { // if (getSpecialMsgNum(payload, OB11MessageDataType.music)) { // const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic; // if (music) { // } // } } // log("send msg:", peer, sendElements) const { sendElements, deleteAfterSentFiles } = await this.obContext.apis.MsgApi .createSendElements(messages, peer); const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, sendElements, deleteAfterSentFiles); return { message_id: returnMsg!.id! }; } // TODO: recursively handle forwarded nodes private async handleForwardedNodesPacket(msgPeer: Peer, messageNodes: OB11MessageNode[], source?: string, news?: { text: string }[], summary?: string, prompt?: string): Promise<{ message: RawMessage | null, res_id?: string }> { const logger = this.core.context.logger; const packetMsg: PacketMsg[] = []; for (const node of messageNodes) { if ((node.data.id && typeof node.data.content !== "string") || !node.data.id) { const OB11Data = normalize(node.data.content); const { sendElements } = await this.obContext.apis.MsgApi.createSendElements(OB11Data, msgPeer); const packetMsgElements: rawMsgWithSendMsg = { senderUin: node.data.user_id ?? +this.core.selfInfo.uin, senderName: node.data.nickname, groupId: msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : undefined, time: Date.now(), msg: sendElements, }; logger.logDebug(`handleForwardedNodesPacket 开始转换 ${JSON.stringify(packetMsgElements)}`); const transformedMsg = this.core.apis.PacketApi.packetSession?.packer.packetConverter.rawMsgWithSendMsgToPacketMsg(packetMsgElements); logger.logDebug(`handleForwardedNodesPacket 转换为 ${JSON.stringify(transformedMsg)}`); packetMsg.push(transformedMsg!); } else { logger.logDebug(`handleForwardedNodesPacket 跳过元素 ${JSON.stringify(node)}`); } } const resid = await this.core.apis.PacketApi.sendUploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0); const forwardJson = ForwardMsgBuilder.fromPacketMsg(resid, packetMsg, source, news, summary, prompt); const finallySendElements = { elementType: ElementType.ARK, elementId: "", arkElement: { bytesData: JSON.stringify(forwardJson), }, } as SendArkElement; let returnMsg: RawMessage | undefined; try { returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], [], true).catch(_ => undefined); } catch (e) { logger.logWarn("发送伪造合并转发消息失败!", e); } return { message: returnMsg ?? null, res_id: resid }; } private async handleForwardedNodes(destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<{ message: RawMessage | null, res_id?: string }> { const selfPeer = { chatType: ChatType.KCHATTYPEC2C, peerUid: this.core.selfInfo.uid, }; let nodeMsgIds: string[] = []; const logger = this.core.context.logger; for (const messageNode of messageNodes) { const nodeId = messageNode.data.id; if (nodeId) { // 对Msgid和OB11ID混用情况兜底 const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId)) || MessageUnique.getPeerByMsgId(nodeId); if (!nodeMsg) { logger.logError.bind(this.core.context.logger)('转发消息失败,未找到消息', nodeId); continue; } nodeMsgIds.push(nodeMsg.MsgId); } else { // 自定义的消息 try { const OB11Data = normalize(messageNode.data.content); //筛选node消息 const isNodeMsg = OB11Data.filter(e => e.type === OB11MessageDataType.node).length;//找到子转发消息 if (isNodeMsg !== 0) { if (isNodeMsg !== OB11Data.length) { logger.logError.bind(this.core.context.logger)('子消息中包含非node消息 跳过不合法部分'); continue; } const nodeMsg = await this.handleForwardedNodes(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node)); if (nodeMsg) { nodeMsgIds.push(nodeMsg.message!.msgId); MessageUnique.createUniqueMsgId(selfPeer, nodeMsg.message!.msgId); } //完成子卡片生成跳过后续 continue; } const { sendElements } = await this.obContext.apis.MsgApi .createSendElements(OB11Data, destPeer); //拆分消息 const MixElement = sendElements.filter( element => element.elementType !== ElementType.FILE && element.elementType !== ElementType.VIDEO && element.elementType !== ElementType.ARK ); const SingleElement = sendElements.filter( element => element.elementType === ElementType.FILE || element.elementType === ElementType.VIDEO || element.elementType === ElementType.ARK ).map(e => [e]); const AllElement: SendMessageElement[][] = [MixElement, ...SingleElement].filter(e => e !== undefined && e.length !== 0); const MsgNodeList: Promise[] = []; for (const sendElementsSplitElement of AllElement) { MsgNodeList.push(this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(selfPeer, sendElementsSplitElement, [], true).catch(_ => undefined)); } (await Promise.allSettled(MsgNodeList)).map((result) => { if (result.status === 'fulfilled' && result.value) { nodeMsgIds.push(result.value.msgId); MessageUnique.createUniqueMsgId(selfPeer, result.value.msgId); } }); } catch (e) { logger.logDebug('生成转发消息节点失败', e); } } } const nodeMsgArray: Array = []; let srcPeer: Peer | undefined = undefined; let needSendSelf = false; //检测是否处于同一个Peer 不在同一个peer则全部消息由自身发送 for (const msgId of nodeMsgIds) { const nodeMsgPeer = MessageUnique.getPeerByMsgId(msgId); if (!nodeMsgPeer) { logger.logError.bind(this.core.context.logger)('转发消息失败,未找到消息', msgId); continue; } const nodeMsg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsgPeer.Peer, [msgId])).msgList[0]; srcPeer = srcPeer ?? { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid }; if (srcPeer.peerUid !== nodeMsg.peerUid) { needSendSelf = true; } nodeMsgArray.push(nodeMsg); } nodeMsgIds = nodeMsgArray.map(msg => msg.msgId); let retMsgIds: string[] = []; if (needSendSelf) { for (const [, msg] of nodeMsgArray.entries()) { if (msg.peerUid === this.core.selfInfo.uid) { retMsgIds.push(msg.msgId); continue; } const ClonedMsg = await this.cloneMsg(msg); if (ClonedMsg) retMsgIds.push(ClonedMsg.msgId); } } else { retMsgIds = nodeMsgIds; } if (retMsgIds.length === 0) throw Error('转发消息失败,生成节点为空'); try { logger.logDebug('开发转发', srcPeer, destPeer, retMsgIds); return { message: await this.core.apis.MsgApi.multiForwardMsg(srcPeer!, destPeer, retMsgIds) }; } catch (e) { logger.logError.bind(this.core.context.logger)('forward failed', e); return { message: null }; } } async cloneMsg(msg: RawMessage): Promise { const selfPeer = { chatType: ChatType.KCHATTYPEC2C, peerUid: this.core.selfInfo.uid, }; const logger = this.core.context.logger; //msg 为待克隆消息 const sendElements: SendMessageElement[] = []; for (const element of msg.elements) { sendElements.push(element as SendMessageElement); } if (sendElements.length === 0) { logger.logDebug('需要clone的消息无法解析,将会忽略掉', msg); } try { return await this.core.apis.MsgApi.sendMsg(selfPeer, sendElements, true); } catch (e) { logger.logError.bind(this.core.context.logger)(e, '克隆转发消息失败,将忽略本条消息', msg); } } } export default SendMsg;