mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
feat & refactor: decouple the forwardMsg
construction logic and implement the OB11 element conversion for the forward node.
This commit is contained in:
106
src/common/forward-msg-builder.ts
Normal file
106
src/common/forward-msg-builder.ts
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import {PacketMsg} from "@/core/packet/msg/message";
|
||||||
|
import * as crypto from "node:crypto";
|
||||||
|
|
||||||
|
interface ForwardMsgJson {
|
||||||
|
app: string
|
||||||
|
config: ForwardMsgJsonConfig,
|
||||||
|
desc: string,
|
||||||
|
extra: ForwardMsgJsonExtra,
|
||||||
|
meta: ForwardMsgJsonMeta,
|
||||||
|
prompt: string,
|
||||||
|
ver: string,
|
||||||
|
view: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForwardMsgJsonConfig {
|
||||||
|
autosize: number,
|
||||||
|
forward: number,
|
||||||
|
round: number,
|
||||||
|
type: string,
|
||||||
|
width: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForwardMsgJsonExtra {
|
||||||
|
filename: string,
|
||||||
|
tsum: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForwardMsgJsonMeta {
|
||||||
|
detail: ForwardMsgJsonMetaDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForwardMsgJsonMetaDetail {
|
||||||
|
news: {
|
||||||
|
text: string
|
||||||
|
}[],
|
||||||
|
resid: string,
|
||||||
|
source: string,
|
||||||
|
summary: string,
|
||||||
|
uniseq: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForwardAdaptMsg {
|
||||||
|
senderName?: string;
|
||||||
|
isGroupMsg?: boolean;
|
||||||
|
msg?: ForwardAdaptMsgElement[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForwardAdaptMsgElement {
|
||||||
|
preview?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ForwardMsgBuilder {
|
||||||
|
private static build(resId: string, msg: ForwardAdaptMsg[]): ForwardMsgJson {
|
||||||
|
const id = crypto.randomUUID();
|
||||||
|
const isGroupMsg = msg.some(m => m.isGroupMsg);
|
||||||
|
return {
|
||||||
|
app: "com.tencent.multimsg",
|
||||||
|
config: {
|
||||||
|
autosize: 1,
|
||||||
|
forward: 1,
|
||||||
|
round: 1,
|
||||||
|
type: "normal",
|
||||||
|
width: 300
|
||||||
|
},
|
||||||
|
desc: "[聊天记录]",
|
||||||
|
extra: {
|
||||||
|
filename: id,
|
||||||
|
tsum: msg.length,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
detail: {
|
||||||
|
news: msg.length === 0 ? [{
|
||||||
|
text: "Nya~ This message is send from NapCat.Packet!",
|
||||||
|
}] : msg.map(m => ({
|
||||||
|
text: `${m.senderName}: ${m.msg?.map(msg => msg.preview).join('')}`,
|
||||||
|
})),
|
||||||
|
resid: resId,
|
||||||
|
source: isGroupMsg ? "群聊的聊天记录" :
|
||||||
|
msg.length
|
||||||
|
? Array.from(new Set(msg.map(m => m.senderName)))
|
||||||
|
.join('和') + '的聊天记录'
|
||||||
|
: '聊天记录',
|
||||||
|
summary: `查看${msg.length}条转发消息`,
|
||||||
|
uniseq: id,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prompt: "[聊天记录]",
|
||||||
|
ver: "0.0.0.5",
|
||||||
|
view: "contact",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromResId(resId: string): ForwardMsgJson {
|
||||||
|
return this.build(resId, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromPacketMsg(resId: string, packetMsg: PacketMsg[]): ForwardMsgJson {
|
||||||
|
return this.build(resId, packetMsg.map(msg => ({
|
||||||
|
senderName: msg.senderName,
|
||||||
|
isGroupMsg: msg.groupId !== undefined,
|
||||||
|
msg: msg.msg.map(m => ({
|
||||||
|
preview: m.toPreview(),
|
||||||
|
}))
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
@@ -28,6 +28,7 @@ import {
|
|||||||
} from "@/core";
|
} from "@/core";
|
||||||
import {MsgInfo} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
import {MsgInfo} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||||
import {PacketMsg, PacketSendMsgElement} from "@/core/packet/msg/message";
|
import {PacketMsg, PacketSendMsgElement} from "@/core/packet/msg/message";
|
||||||
|
import {ForwardMsgBuilder} from "@/common/forward-msg-builder";
|
||||||
|
|
||||||
// raw <-> packet
|
// raw <-> packet
|
||||||
// TODO: check ob11 -> raw impl!
|
// TODO: check ob11 -> raw impl!
|
||||||
@@ -282,7 +283,7 @@ export class PacketMsgMarkFaceElement extends IPacketMsgElement<SendMarketFaceEl
|
|||||||
}
|
}
|
||||||
|
|
||||||
toPreview(): string {
|
toPreview(): string {
|
||||||
return this.emojiName;
|
return `[${this.emojiName}]`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,7 +325,7 @@ export class PacketMsgLightAppElement extends IPacketMsgElement<SendArkElement>
|
|||||||
}
|
}
|
||||||
|
|
||||||
toPreview(): string {
|
toPreview(): string {
|
||||||
return "[小程序]";
|
return "[卡片消息]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,7 +350,7 @@ export class PacketMsgMarkDownElement extends IPacketMsgElement<SendMarkdownElem
|
|||||||
}
|
}
|
||||||
|
|
||||||
toPreview(): string {
|
toPreview(): string {
|
||||||
return this.content;
|
return `[Markdown消息 ${this.content}]`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,55 +364,12 @@ export class PacketMultiMsgElement extends IPacketMsgElement<SendStructLongMsgEl
|
|||||||
this.message = message ?? [];
|
this.message = message ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
get isGroupMsg(): boolean {
|
|
||||||
return this.message.some(msg => msg.groupId !== undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
get JSON() {
|
|
||||||
const id = crypto.randomUUID();
|
|
||||||
return {
|
|
||||||
app: "com.tencent.multimsg",
|
|
||||||
config: {
|
|
||||||
autosize: 1,
|
|
||||||
forward: 1,
|
|
||||||
round: 1,
|
|
||||||
type: "normal",
|
|
||||||
width: 300
|
|
||||||
},
|
|
||||||
desc: "[聊天记录]",
|
|
||||||
extra: {
|
|
||||||
filename: id,
|
|
||||||
tsum: this.message.length,
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
detail: {
|
|
||||||
news: this.message.length === 0 ? [{
|
|
||||||
text: "[Nya~ This message is send from NapCat.Packet!]",
|
|
||||||
}] : this.message.map(packetMsg => ({
|
|
||||||
text: `${packetMsg.senderName}: ${packetMsg.msg.map(msg => msg.toPreview()).join('')}`,
|
|
||||||
})),
|
|
||||||
resid: this.resid,
|
|
||||||
source: this.isGroupMsg ? "群聊的聊天记录" :
|
|
||||||
this.message.length
|
|
||||||
? Array.from(new Set(this.message.map(msg => msg.senderName)))
|
|
||||||
.join('和') + '的聊天记录'
|
|
||||||
: '聊天记录',
|
|
||||||
summary: `查看${this.message.length}条转发消息`,
|
|
||||||
uniseq: id,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
prompt: "[聊天记录]",
|
|
||||||
ver: "0.0.0.5",
|
|
||||||
view: "contact",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||||
return [{
|
return [{
|
||||||
lightAppElem: {
|
lightAppElem: {
|
||||||
data: Buffer.concat([
|
data: Buffer.concat([
|
||||||
Buffer.from([0x01]),
|
Buffer.from([0x01]),
|
||||||
zlib.deflateSync(Buffer.from(JSON.stringify(this.JSON), 'utf-8'))
|
zlib.deflateSync(Buffer.from(JSON.stringify(ForwardMsgBuilder.fromPacketMsg(this.resid, this.message)), 'utf-8'))
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
@@ -13,7 +13,7 @@ import {ChatType, ElementType, NapCatCore, Peer, RawMessage, SendArkElement, Sen
|
|||||||
import BaseAction from '../BaseAction';
|
import BaseAction from '../BaseAction';
|
||||||
import {rawMsgWithSendMsg} from "@/core/packet/msg/converter";
|
import {rawMsgWithSendMsg} from "@/core/packet/msg/converter";
|
||||||
import {PacketMsg} from "@/core/packet/msg/message";
|
import {PacketMsg} from "@/core/packet/msg/message";
|
||||||
import {PacketMultiMsgElement} from "@/core/packet/msg/element";
|
import {ForwardMsgBuilder} from "@/common/forward-msg-builder";
|
||||||
|
|
||||||
export interface ReturnDataType {
|
export interface ReturnDataType {
|
||||||
message_id: number;
|
message_id: number;
|
||||||
@@ -126,6 +126,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
chatType: peer.chatType,
|
chatType: peer.chatType,
|
||||||
}, (returnMsgAndResId.message)!.msgId);
|
}, (returnMsgAndResId.message)!.msgId);
|
||||||
return {message_id: msgShortId!, res_id: returnMsgAndResId.res_id};
|
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('发送转发消息失败');
|
throw Error('发送转发消息失败');
|
||||||
} else {
|
} else {
|
||||||
@@ -143,6 +145,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
return {message_id: returnMsg!.id!};
|
return {message_id: returnMsg!.id!};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: recursively handle forwarded nodes
|
||||||
private async handleForwardedNodesPacket(msgPeer: Peer, messageNodes: OB11MessageNode[]): Promise<{
|
private async handleForwardedNodesPacket(msgPeer: Peer, messageNodes: OB11MessageNode[]): Promise<{
|
||||||
message: RawMessage | null,
|
message: RawMessage | null,
|
||||||
res_id?: string
|
res_id?: string
|
||||||
@@ -169,14 +172,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const resid = await this.core.apis.PacketApi.sendUploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0);
|
const resid = await this.core.apis.PacketApi.sendUploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0);
|
||||||
const forwardJson = new PacketMultiMsgElement({
|
const forwardJson = ForwardMsgBuilder.fromPacketMsg(resid, packetMsg);
|
||||||
elementType: ElementType.STRUCTLONGMSG,
|
|
||||||
elementId: "",
|
|
||||||
structLongMsgElement: {
|
|
||||||
xmlContent: "",
|
|
||||||
resId: resid
|
|
||||||
}
|
|
||||||
}, packetMsg).JSON;
|
|
||||||
const finallySendElements = {
|
const finallySendElements = {
|
||||||
elementType: ElementType.ARK,
|
elementType: ElementType.ARK,
|
||||||
elementId: "",
|
elementId: "",
|
||||||
@@ -184,7 +180,12 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
bytesData: JSON.stringify(forwardJson),
|
bytesData: JSON.stringify(forwardJson),
|
||||||
},
|
},
|
||||||
} as SendArkElement
|
} as SendArkElement
|
||||||
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], [], true).catch(_ => undefined)
|
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};
|
return {message: returnMsg ?? null, res_id: resid};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -35,6 +35,7 @@ import fs from 'node:fs';
|
|||||||
import fsPromise from 'node:fs/promises';
|
import fsPromise from 'node:fs/promises';
|
||||||
import {OB11FriendAddNoticeEvent} from '@/onebot/event/notice/OB11FriendAddNoticeEvent';
|
import {OB11FriendAddNoticeEvent} from '@/onebot/event/notice/OB11FriendAddNoticeEvent';
|
||||||
import {decodeSysMessage} from '@/core/packet/proto/old/ProfileLike';
|
import {decodeSysMessage} from '@/core/packet/proto/old/ProfileLike';
|
||||||
|
import {ForwardMsgBuilder} from "@/common/forward-msg-builder";
|
||||||
|
|
||||||
type RawToOb11Converters = {
|
type RawToOb11Converters = {
|
||||||
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
|
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
|
||||||
@@ -600,7 +601,13 @@ export class OneBotMsgApi {
|
|||||||
|
|
||||||
[OB11MessageDataType.node]: async () => undefined,
|
[OB11MessageDataType.node]: async () => undefined,
|
||||||
|
|
||||||
[OB11MessageDataType.forward]: async () => undefined,
|
[OB11MessageDataType.forward]: async ({ data }, context) => {
|
||||||
|
const jsonData = ForwardMsgBuilder.fromResId(data.id)
|
||||||
|
return this.ob11ToRawConverters.json({
|
||||||
|
data: { data: JSON.stringify(jsonData) },
|
||||||
|
type: OB11MessageDataType.json
|
||||||
|
}, context);
|
||||||
|
},
|
||||||
|
|
||||||
[OB11MessageDataType.xml]: async () => undefined,
|
[OB11MessageDataType.xml]: async () => undefined,
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user