From 86bfd990dbc94b7c163e985de87872200e56bc3f Mon Sep 17 00:00:00 2001 From: pk5ls20 Date: Mon, 14 Oct 2024 02:25:56 +0800 Subject: [PATCH] feat: partly impl `UploadForwardMsg` --- src/core/apis/packet.ts | 7 ++ src/core/helper/packet/msg/builder.ts | 61 +++++++++ src/core/helper/packet/msg/element.ts | 117 ++++++++++++++++++ src/core/helper/packet/packer.ts | 44 +++++++ src/core/proto/message/action.ts | 117 ++++++++++++++++++ src/onebot/action/extends/UploadForwardMsg.ts | 39 ++++++ src/onebot/action/index.ts | 2 + src/onebot/action/types.ts | 3 +- 8 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 src/core/helper/packet/msg/builder.ts create mode 100644 src/core/helper/packet/msg/element.ts create mode 100644 src/core/proto/message/action.ts create mode 100644 src/onebot/action/extends/UploadForwardMsg.ts diff --git a/src/core/apis/packet.ts b/src/core/apis/packet.ts index cd8a4221..c18dea44 100644 --- a/src/core/apis/packet.ts +++ b/src/core/apis/packet.ts @@ -8,6 +8,7 @@ import {NapProtoMsg} from '../proto/NapProto'; import {OidbSvcTrpcTcp0X9067_202_Rsp_Body} from '../proto/oidb/Oidb.0x9067_202'; import {OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp} from '../proto/oidb/OidbBase'; import {OidbSvcTrpcTcp0XFE1_2RSP} from '../proto/oidb/Oidb.fe1_2'; +import {PacketForwardNode} from "@/core/helper/packet/msg/builder"; interface OffsetType { [key: string]: { @@ -118,4 +119,10 @@ export class NTQQPacketApi { let data = this.packetPacker.packSetSpecialTittlePacket(groupCode, uid, tittle); let ret = await this.core.apis.PacketApi.sendPacket('OidbSvcTrpcTcp.0x8fc_2', data, true); } + + async sendUploadForwardMsg(msg: PacketForwardNode[], groupUin: number = 0){ + let data = this.packetPacker.packUploadForwardMsg(this.core.selfInfo.uid, msg, groupUin); + let ret = await this.core.apis.PacketApi.sendPacket('trpc.group.long_msg_interface.MsgService.SsoSendLongMsg', data, true); + console.log(JSON.stringify(ret)); + } } diff --git a/src/core/helper/packet/msg/builder.ts b/src/core/helper/packet/msg/builder.ts new file mode 100644 index 00000000..a1afbc26 --- /dev/null +++ b/src/core/helper/packet/msg/builder.ts @@ -0,0 +1,61 @@ +import {PushMsgBody} from "@/core/proto/message/message"; +import {NapProtoEncodeStructType} from "@/core/proto/NapProto"; +import {SendMessageElement} from "@/core"; +import * as crypto from "crypto"; +import {IPacketMsgElement} from "@/core/helper/packet/msg/element"; + +export interface PacketForwardNode { + groupId?: number + senderId: number + senderName: string + time: number + msg: IPacketMsgElement[] +} + +export class PacketMsgBuilder { + buildFakeMsg(selfUid: string, element: PacketForwardNode[]): NapProtoEncodeStructType[] { + return element.map((node): NapProtoEncodeStructType => { + const avatar = `https://q.qlogo.cn/headimg_dl?dst_uin=${node.senderId}&spec=640&img_type=jpg` + return { + responseHead: { + fromUid: "", + fromUin: node.senderId, + toUid: node.groupId ? undefined : selfUid, + forward: node.groupId ? undefined : { + friendName: node.senderName, + }, + grp: node.groupId ? { + groupUin: node.groupId, + memberName: node.senderName, + unknown5: 2 + } : undefined, + }, + contentHead: { + type: node.groupId ? 82 : 9, + subType: node.groupId ? undefined : 4, + divSeq: node.groupId ? undefined : 4, + msgId: crypto.randomBytes(4).readUInt32LE(0), + sequence: crypto.randomBytes(4).readUInt32LE(0), + timeStamp: Math.floor(Date.now() / 1000), + field7: BigInt(1), + field8: 0, + field9: 0, + forward: { + field1: 0, + field2: 0, + field3: node.groupId ? 0 : 2, + unknownBase64: avatar, + avatar: avatar + } + }, + body: { + richText: { + elems: node.msg.map( + (msg) => msg.buildElement() ?? [] + ) + } + } + } + }); + } +} diff --git a/src/core/helper/packet/msg/element.ts b/src/core/helper/packet/msg/element.ts new file mode 100644 index 00000000..7bedee88 --- /dev/null +++ b/src/core/helper/packet/msg/element.ts @@ -0,0 +1,117 @@ +import {NapProtoEncodeStructType, NapProtoMsg} from "@/core/proto/NapProto"; +import {Elem, MentionExtra} from "@/core/proto/message/element"; +import { + AtType, + SendArkElement, + SendFaceElement, + SendFileElement, + SendMessageElement, + SendPicElement, + SendPttElement, + SendReplyElement, + SendTextElement, + SendVideoElement +} from "@/core"; + +// raw <-> packet +// TODO: check ob11 -> raw impl! +// TODO: parse to raw element +export abstract class IPacketMsgElement { + protected constructor(rawElement: T) { + } + + buildContent(): Uint8Array | undefined { + return undefined; + } + + buildElement(): NapProtoEncodeStructType | undefined { + return undefined; + } +} + +export class PacketMsgTextElement extends IPacketMsgElement { + text: string; + + constructor(element: SendTextElement) { + super(element); + console.log(JSON.stringify(element)); + this.text = element.textElement.content; + } + + buildElement(): NapProtoEncodeStructType { + return { + text: { + str: 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 { + const res = new NapProtoMsg(MentionExtra).encode({ + type: this.atAll ? 1 : 2, + uin: 0, + field5: 0, + uid: this.targetUid, + } + ) + return { + text: { + str: this.text, + pbReserve: res + } + } + } +} + +export class PacketMsgPttElement extends IPacketMsgElement { + constructor(element: SendPttElement) { + super(element); + } +} + +export class PacketMsgPicElement extends IPacketMsgElement { + constructor(element: SendPicElement) { + super(element); + } +} + +export class PacketMsgReplyElement extends IPacketMsgElement { + constructor(element: SendReplyElement) { + super(element); + } +} + +export class PacketMsgFaceElement extends IPacketMsgElement { + constructor(element: SendFaceElement) { + super(element); + } +} + +export class PacketMsgFileElement extends IPacketMsgElement { + constructor(element: SendFileElement) { + super(element); + } +} + +export class PacketMsgVideoElement extends IPacketMsgElement { + constructor(element: SendVideoElement) { + super(element); + } +} + +export class PacketMsgLightAppElement extends IPacketMsgElement { + constructor(element: SendArkElement) { + super(element); + } +} diff --git a/src/core/helper/packet/packer.ts b/src/core/helper/packet/packer.ts index 26cc0680..48066bde 100644 --- a/src/core/helper/packet/packer.ts +++ b/src/core/helper/packet/packer.ts @@ -1,13 +1,22 @@ +import * as zlib from "node:zlib"; import { NapProtoMsg } from "@/core/proto/NapProto"; import { OidbSvcTrpcTcpBase } from "@/core/proto/oidb/OidbBase"; import { OidbSvcTrpcTcp0X9067_202 } from "@/core/proto/oidb/Oidb.0x9067_202"; import { OidbSvcTrpcTcp0X8FC_2, OidbSvcTrpcTcp0X8FC_2_Body } from "@/core/proto/oidb/Oidb.0x8FC_2"; import { OidbSvcTrpcTcp0XFE1_2 } from "@/core/proto/oidb/Oidb.fe1_2"; import { OidbSvcTrpcTcp0XED3_1 } from "@/core/proto/oidb/Oidb.ed3_1"; +import {LongMsgResult, SendLongMsgReq} from "@/core/proto/message/action"; +import {PacketForwardNode, PacketMsgBuilder} from "@/core/helper/packet/msg/builder"; export type PacketHexStr = string & { readonly hexNya: unique symbol }; export class PacketPacker { + private packetBuilder: PacketMsgBuilder + + constructor() { + this.packetBuilder = new PacketMsgBuilder(); + } + private toHexStr(byteArray: Uint8Array): PacketHexStr { return Buffer.from(byteArray).toString('hex') as PacketHexStr; } @@ -75,4 +84,39 @@ export class PacketPacker { }); return this.toHexStr(this.packOidbPacket(0xfe1, 2, oidb_0xfe1_2)); } + + packUploadForwardMsg(selfUid: string, msg: PacketForwardNode[], groupUin: number = 0) : PacketHexStr { + // console.log("packUploadForwardMsg START!!!", selfUid, msg, groupUin); + const msgBody = this.packetBuilder.buildFakeMsg(selfUid, msg); + const longMsgResultData = new NapProtoMsg(LongMsgResult).encode( + { + action: { + actionCommand: "MultiMsg", + actionData: { + msgBody: msgBody + } + } + } + ) + // console.log("packUploadForwardMsg LONGMSGRESULT!!!", this.toHexStr(longMsgResultData)); + const payload = zlib.gzipSync(Buffer.from(longMsgResultData)); + // console.log("packUploadForwardMsg PAYLOAD!!!", payload); + const req = new NapProtoMsg(SendLongMsgReq).encode( + { + info: { + type: groupUin === 0 ? 1 : 3, + uid: { + uid: groupUin === 0 ? selfUid : groupUin.toString(), + }, + groupUin: groupUin, + payload: payload + }, + settings: { + field1: 4, field2: 1, field3: 7, field4: 0 + } + } + ) + // console.log("packUploadForwardMsg REQ!!!", req); + return this.toHexStr(req); + } } diff --git a/src/core/proto/message/action.ts b/src/core/proto/message/action.ts new file mode 100644 index 00000000..43cd09d9 --- /dev/null +++ b/src/core/proto/message/action.ts @@ -0,0 +1,117 @@ +import {ScalarType} from "@protobuf-ts/runtime"; +import {ProtoField} from "../NapProto"; +import {PushMsgBody} from "@/core/proto/message/message"; + +export const LongMsgResult = { + action: ProtoField(2, () => LongMsgAction) +}; + +export const LongMsgAction = { + actionCommand: ProtoField(1, ScalarType.STRING), + actionData: ProtoField(2, () => LongMsgContent) +}; + +export const LongMsgContent = { + msgBody: ProtoField(1, () => PushMsgBody, false, true) +}; + +export const RecvLongMsgReq = { + info: ProtoField(1, () => RecvLongMsgInfo, true), + settings: ProtoField(15, () => LongMsgSettings, true) +}; + +export const RecvLongMsgInfo = { + uid: ProtoField(1, () => LongMsgUid, true), + resId: ProtoField(2, ScalarType.STRING, true), + acquire: ProtoField(3, ScalarType.BOOL) +}; + +export const LongMsgUid = { + uid: ProtoField(2, ScalarType.STRING, true) +}; + +export const LongMsgSettings = { + field1: ProtoField(1, ScalarType.UINT32), + field2: ProtoField(2, ScalarType.UINT32), + field3: ProtoField(3, ScalarType.UINT32), + field4: ProtoField(4, ScalarType.UINT32) +}; + +export const RecvLongMsgResp = { + result: ProtoField(1, () => RecvLongMsgResult), + settings: ProtoField(15, () => LongMsgSettings) +}; + +export const RecvLongMsgResult = { + resId: ProtoField(3, ScalarType.STRING), + payload: ProtoField(4, ScalarType.BYTES) +}; + +export const SendLongMsgReq = { + info: ProtoField(2, () => SendLongMsgInfo), + settings: ProtoField(15, () => LongMsgSettings) +}; + +export const SendLongMsgInfo = { + type: ProtoField(1, ScalarType.UINT32), + uid: ProtoField(2, () => LongMsgUid, true), + groupUin: ProtoField(3, ScalarType.UINT32, true), + payload: ProtoField(4, ScalarType.BYTES, true) +}; + +export const SendLongMsgResp = { + result: ProtoField(2, () => SendLongMsgResult), + settings: ProtoField(15, () => LongMsgSettings) +}; + +export const SendLongMsgResult = { + resId: ProtoField(3, ScalarType.STRING) +}; + +export const SsoGetGroupMsg = { + info: ProtoField(1, () => SsoGetGroupMsgInfo), + direction: ProtoField(2, ScalarType.BOOL) +}; + +export const SsoGetGroupMsgInfo = { + groupUin: ProtoField(1, ScalarType.UINT32), + startSequence: ProtoField(2, ScalarType.UINT32), + endSequence: ProtoField(3, ScalarType.UINT32) +}; + +export const SsoGetGroupMsgResponse = { + body: ProtoField(3, () => SsoGetGroupMsgResponseBody) +}; + +export const SsoGetGroupMsgResponseBody = { + groupUin: ProtoField(3, ScalarType.UINT32), + startSequence: ProtoField(4, ScalarType.UINT32), + endSequence: ProtoField(5, ScalarType.UINT32), + messages: ProtoField(6, () => PushMsgBody, false, true) +}; + +export const SsoGetRoamMsg = { + friendUid: ProtoField(1, ScalarType.STRING, true), + time: ProtoField(2, ScalarType.UINT32), + random: ProtoField(3, ScalarType.UINT32), + count: ProtoField(4, ScalarType.UINT32), + direction: ProtoField(5, ScalarType.BOOL) +}; + +export const SsoGetRoamMsgResponse = { + friendUid: ProtoField(3, ScalarType.STRING), + timestamp: ProtoField(5, ScalarType.UINT32), + random: ProtoField(6, ScalarType.UINT32), + messages: ProtoField(7, () => PushMsgBody, false, true) +}; + +export const SsoGetC2cMsg = { + friendUid: ProtoField(2, ScalarType.STRING, true), + startSequence: ProtoField(3, ScalarType.UINT32), + endSequence: ProtoField(4, ScalarType.UINT32) +}; + +export const SsoGetC2cMsgResponse = { + friendUid: ProtoField(4, ScalarType.STRING), + messages: ProtoField(7, () => PushMsgBody, false, true) +}; diff --git a/src/onebot/action/extends/UploadForwardMsg.ts b/src/onebot/action/extends/UploadForwardMsg.ts new file mode 100644 index 00000000..815830bc --- /dev/null +++ b/src/onebot/action/extends/UploadForwardMsg.ts @@ -0,0 +1,39 @@ +import BaseAction from '../BaseAction'; +import { ActionName } from '../types'; +import { FromSchema, JSONSchema } from 'json-schema-to-ts'; +import {PacketMsgTextElement} from "@/core/helper/packet/msg/element"; +import {SendTextElement} from "@/core"; + + +const SchemaData = { + type: 'object', + properties: { + group_id: { type: ['number', 'string'] }, + }, + required: ['group_id'], +}as const satisfies JSONSchema; + +type Payload = FromSchema; + +export class UploadForwardMsg extends BaseAction { + actionName = ActionName.UploadForwardMsg; + payloadSchema = SchemaData; + + async _handle(payload: Payload) { + if (!this.core.apis.PacketApi.packetClient?.isConnected) { + throw new Error('PacketClient is not init'); + } + throw new Error('Not implemented'); + // return await this.core.apis.PacketApi.sendUploadForwardMsg([{ + // groupId: 0, + // senderId: 0, + // senderName: "NapCat", + // time: Math.floor(Date.now() / 1000), + // msg: [new PacketMsgTextElement({ + // textElement: { + // content: "Nya~" + // } + // } as SendTextElement)] + // }], 0); + } +} diff --git a/src/onebot/action/index.ts b/src/onebot/action/index.ts index 795a34dc..5f4acc89 100644 --- a/src/onebot/action/index.ts +++ b/src/onebot/action/index.ts @@ -88,6 +88,7 @@ import { GroupPoke } from './group/GroupPoke'; import { GetUserStatus } from './extends/GetUserStatus'; import { GetRkey } from './extends/GetRkey'; import { SetSpecialTittle } from './extends/SetSpecialTittle'; +import { UploadForwardMsg } from "@/onebot/action/extends/UploadForwardMsg"; export type ActionMap = Map>; @@ -188,6 +189,7 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo new GetUserStatus(obContext, core), new GetRkey(obContext, core), new SetSpecialTittle(obContext, core), + new UploadForwardMsg(obContext, core), ]; const actionMap = new Map(); for (const action of actionHandlers) { diff --git a/src/onebot/action/types.ts b/src/onebot/action/types.ts index 83c4def5..92bc99a9 100644 --- a/src/onebot/action/types.ts +++ b/src/onebot/action/types.ts @@ -69,7 +69,7 @@ export enum ActionName { GetRecord = 'get_record', CleanCache = 'clean_cache', GetCookies = 'get_cookies', - // 以下为go-cqhttp api + // 以下为go-cqhttp api GoCQHTTP_HandleQuickAction = '.handle_quick_operation', GetGroupHonorInfo = 'get_group_honor_info', GoCQHTTP_GetEssenceMsg = 'get_essence_msg_list', @@ -123,4 +123,5 @@ export enum ActionName { GetUserStatus = "nc_get_user_status", GetRkey = "nc_get_rkey", SetSpecialTittle = "set_group_special_title", + UploadForwardMsg = "upload_forward_msg", }