import assert from "node:assert"; import * as zlib from "node:zlib"; import { NapProtoEncodeStructType, NapProtoMsg } from "@/core/packet/proto/NapProto"; import { CustomFace, Elem, MarkdownData, MentionExtra, NotOnlineImage, QBigFaceExtra, QSmallFaceExtra } from "@/core/packet/proto/message/element"; import { AtType, PicType, SendArkElement, SendFaceElement, SendFileElement, SendMarkdownElement, SendMarketFaceElement, SendPicElement, SendPttElement, SendReplyElement, SendStructLongMsgElement, SendTextElement, SendVideoElement } from "@/core"; import {MsgInfo} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq"; import {PacketMsg, PacketSendMsgElement} from "@/core/packet/msg/message"; import {ForwardMsgBuilder} from "@/common/forward-msg-builder"; import { FileExtra, GroupFileExtra } from "@/core/packet/proto/message/component"; import { OidbSvcTrpcTcp0XE37_800Response } from "@/core/packet/proto/oidb/Oidb.0XE37_800"; // raw <-> packet // TODO: SendStructLongMsgElement export abstract class IPacketMsgElement { protected constructor(rawElement: T) { } buildContent(): Uint8Array | undefined { return undefined; } buildElement(): NapProtoEncodeStructType[] { return []; } toPreview(): string { return '[暂不支持该消息类型喵~]'; } } export class PacketMsgTextElement extends IPacketMsgElement { text: string; constructor(element: SendTextElement) { super(element); this.text = element.textElement.content; } buildElement(): NapProtoEncodeStructType[] { return [{ text: { str: this.text } }]; } toPreview(): string { return this.text; } } export class PacketMsgAtElement extends PacketMsgTextElement { targetUid: string; atAll: boolean; constructor(element: SendTextElement) { super(element); this.targetUid = element.textElement.atNtUid; this.atAll = element.textElement.atType === AtType.atAll; } buildElement(): NapProtoEncodeStructType[] { return [{ text: { str: this.text, pbReserve: new NapProtoMsg(MentionExtra).encode({ type: this.atAll ? 1 : 2, uin: 0, field5: 0, uid: this.targetUid, } ) } }]; } } export class PacketMsgPicElement extends IPacketMsgElement { path: string; name: string; size: number; md5: string; width: number; height: number; picType: PicType; sha1: string | null = null; msgInfo: NapProtoEncodeStructType | null = null; groupPicExt: NapProtoEncodeStructType | null = null; c2cPicExt: NapProtoEncodeStructType | null = null; constructor(element: SendPicElement) { super(element); this.path = element.picElement.sourcePath; this.name = element.picElement.fileName; this.size = +element.picElement.fileSize; this.md5 = element.picElement.md5HexStr ?? ''; this.width = element.picElement.picWidth; this.height = element.picElement.picHeight; this.picType = element.picElement.picType; } buildElement(): NapProtoEncodeStructType[] { assert(this.msgInfo !== null, 'msgInfo is null, expected not null'); return [{ commonElem: { serviceType: 48, pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo), businessType: 10, } }]; } toPreview(): string { return "[图片]"; } } export class PacketMsgReplyElement extends IPacketMsgElement { messageId: bigint; messageSeq: number; messageClientSeq: number; targetUin: number; targetUid: string; time: number; elems: PacketMsg[]; constructor(element: SendReplyElement) { super(element); this.messageId = BigInt(element.replyElement.replayMsgId ?? 0); this.messageSeq = +(element.replyElement.replayMsgSeq ?? 0); this.messageClientSeq = +(element.replyElement.replyMsgClientSeq ?? 0); this.targetUin = +(element.replyElement.senderUin ?? 0); this.targetUid = element.replyElement.senderUidStr ?? ''; this.time = +(element.replyElement.replyMsgTime ?? 0); this.elems = []; // TODO: in replyElement.sourceMsgTextElems } get isGroupReply(): boolean { return this.messageClientSeq !== 0; } buildElement(): NapProtoEncodeStructType[] { return [{ srcMsg: { origSeqs: [this.isGroupReply ? this.messageClientSeq : this.messageSeq], senderUin: BigInt(this.targetUin), time: this.time, elems: [], // TODO: in replyElement.sourceMsgTextElems pbReserve: { messageId: this.messageId, }, toUin: BigInt(0), } }, { text: this.isGroupReply ? { str: 'nya~', pbReserve: new NapProtoMsg(MentionExtra).encode({ type: this.targetUin === 0 ? 1 : 2, uin: 0, field5: 0, uid: String(this.targetUid), }), } : undefined, }]; } toPreview(): string { return "[回复消息]"; } } export class PacketMsgFaceElement extends IPacketMsgElement { faceId: number; isLargeFace: boolean; constructor(element: SendFaceElement) { super(element); this.faceId = element.faceElement.faceIndex; this.isLargeFace = element.faceElement.faceType === 3; } buildElement(): NapProtoEncodeStructType[] { if (this.isLargeFace) { return [{ commonElem: { serviceType: 37, pbElem: new NapProtoMsg(QBigFaceExtra).encode({ aniStickerPackId: "1", aniStickerId: "8", faceId: this.faceId, field4: 1, field6: "", preview: "", field9: 1 }), businessType: 1 } }]; } else if (this.faceId < 260) { return [{ face: { index: this.faceId } }]; } else { return [{ commonElem: { serviceType: 33, pbElem: new NapProtoMsg(QSmallFaceExtra).encode({ faceId: this.faceId, preview: "", preview2: "" }), businessType: 1 } }]; } } toPreview(): string { return "[表情]"; } } export class PacketMsgMarkFaceElement extends IPacketMsgElement { emojiName: string; emojiId: string; emojiPackageId: number; emojiKey: string; constructor(element: SendMarketFaceElement) { super(element); this.emojiName = element.marketFaceElement.faceName; this.emojiId = element.marketFaceElement.emojiId; this.emojiPackageId = element.marketFaceElement.emojiPackageId; this.emojiKey = element.marketFaceElement.key; } buildElement(): NapProtoEncodeStructType[] { return [{ marketFace: { faceName: this.emojiName, itemType: 6, faceInfo: 1, faceId: Buffer.from(this.emojiId, 'hex'), tabId: this.emojiPackageId, subType: 3, key: this.emojiKey, imageWidth: 300, imageHeight: 300, pbReserve: { field8: 1 } } }]; } toPreview(): string { return `[${this.emojiName}]`; } } export class PacketMsgVideoElement extends IPacketMsgElement { fileSize?: string; filePath?: string; thumbSize?: number; thumbPath?: string; fileMd5?: string; thumbMd5?: string; thumbWidth?: number; thumbHeight?: number; msgInfo: NapProtoEncodeStructType | null = null; constructor(element: SendVideoElement) { super(element); this.fileSize = element.videoElement.fileSize; this.filePath = element.videoElement.filePath; this.thumbSize = element.videoElement.thumbSize; this.thumbPath = element.videoElement.thumbPath?.get(0); this.fileMd5 = element.videoElement.videoMd5 this.thumbMd5 = element.videoElement.thumbMd5; this.thumbWidth = element.videoElement.thumbWidth; this.thumbHeight = element.videoElement.thumbHeight; } buildElement(): NapProtoEncodeStructType[] { assert(this.msgInfo !== null, 'msgInfo is null, expected not null'); return [{ commonElem: { serviceType: 48, pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo), businessType: 21, } }]; } toPreview(): string { return "[视频]"; } } export class PacketMsgPttElement extends IPacketMsgElement { filePath: string; fileSize: number; fileMd5: string; fileDuration: number; msgInfo: NapProtoEncodeStructType | null = null; constructor(element: SendPttElement) { super(element); this.filePath = element.pttElement.filePath; this.fileSize = +element.pttElement.fileSize; // TODO: cc this.fileMd5 = element.pttElement.md5HexStr; this.fileDuration = Math.round(element.pttElement.duration); // TODO: cc } buildElement(): NapProtoEncodeStructType[] { assert(this.msgInfo !== null, 'msgInfo is null, expected not null'); return [{ commonElem: { serviceType: 48, pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo), businessType: 22, } }]; } toPreview(): string { return "[语音]"; } } export class PacketMsgFileElement extends IPacketMsgElement { fileName: string; filePath: string; fileSize: number; fileSha1?: Uint8Array; fileMd5?: Uint8Array; fileUuid?: string; fileHash?: string; isGroupFile?: boolean; _private_send_uid?: string; _private_recv_uid?: string; _e37_800_rsp?: NapProtoEncodeStructType constructor(element: SendFileElement) { super(element); this.fileName = element.fileElement.fileName; this.filePath = element.fileElement.filePath; this.fileSize = +element.fileElement.fileSize; } buildContent(): Uint8Array | undefined { if (this.isGroupFile) return undefined; return new NapProtoMsg(FileExtra).encode({ file: { fileType: 0, fileUuid: this.fileUuid, fileMd5: this.fileMd5, fileName: this.fileName, fileSize: BigInt(this.fileSize), subcmd: 1, dangerEvel: 0, expireTime: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60, fileHash: this.fileHash, }, field6: { field2: { field1: this._e37_800_rsp?.body?.field30?.field110, fileUuid: this.fileUuid, fileName: this.fileName, field6: this._e37_800_rsp?.body?.field30?.field3, field7: this._e37_800_rsp?.body?.field30?.field101, field8: this._e37_800_rsp?.body?.field30?.field100, timestamp1: this._e37_800_rsp?.body?.field30?.timestamp1, fileHash: this.fileHash, selfUid: this._private_send_uid, destUid: this._private_recv_uid, } } }) } buildElement(): NapProtoEncodeStructType[] { if (!this.isGroupFile) return []; const lb = Buffer.alloc(2); const transElemVal = new NapProtoMsg(GroupFileExtra).encode({ field1: 6, fileName: this.fileName, inner: { info: { busId: 102, fileId: this.fileUuid, fileSize: BigInt(this.fileSize), fileName: this.fileName, fileSha: this.fileSha1, extInfoString: "", fileMd5: this.fileMd5, } } }) lb.writeUInt16BE(transElemVal.length); return [{ transElem: { elemType: 24, elemValue: Buffer.concat([Buffer.from([0x01]), lb, transElemVal]) // TLV } }]; } toPreview(): string { return `[文件]${this.fileName}`; } } export class PacketMsgLightAppElement extends IPacketMsgElement { payload: string; constructor(element: SendArkElement) { super(element); this.payload = element.arkElement.bytesData; } buildElement(): NapProtoEncodeStructType[] { return [{ lightAppElem: { data: Buffer.concat([ Buffer.from([0x01]), zlib.deflateSync(Buffer.from(this.payload, 'utf-8')) ]) } }]; } toPreview(): string { return "[卡片消息]"; } } export class PacketMsgMarkDownElement extends IPacketMsgElement { content: string; constructor(element: SendMarkdownElement) { super(element); this.content = element.markdownElement.content; } buildElement(): NapProtoEncodeStructType[] { return [{ commonElem: { serviceType: 45, pbElem: new NapProtoMsg(MarkdownData).encode({ content: this.content }), businessType: 1 } }]; } toPreview(): string { return `[Markdown消息 ${this.content}]`; } } export class PacketMultiMsgElement extends IPacketMsgElement { resid: string; message: PacketMsg[]; constructor(rawElement: SendStructLongMsgElement, message?: PacketMsg[]) { super(rawElement); this.resid = rawElement.structLongMsgElement.resId; this.message = message ?? []; } buildElement(): NapProtoEncodeStructType[] { return [{ lightAppElem: { data: Buffer.concat([ Buffer.from([0x01]), zlib.deflateSync(Buffer.from(JSON.stringify(ForwardMsgBuilder.fromPacketMsg(this.resid, this.message)), 'utf-8')) ]) } }]; } toPreview(): string { return "[聊天记录]"; } }