From 50e7dd960e1172e29089204d20f70e942b2f4e3f Mon Sep 17 00:00:00 2001 From: yincong Date: Fri, 8 May 2026 12:00:05 +0800 Subject: [PATCH] proto --- onebot/http.go | 24 +-- onebot/proto/wxproto/text_msg.pb.go | 228 ++++++++++++++++++++++++++++ onebot/proto/wxproto/text_msg.proto | 22 +++ onebot/script.js | 57 +++---- onebot/text_proto_builder.go | 43 ++++++ onebot/worker.go | 7 +- 6 files changed, 329 insertions(+), 52 deletions(-) create mode 100644 onebot/proto/wxproto/text_msg.pb.go create mode 100644 onebot/proto/wxproto/text_msg.proto create mode 100644 onebot/text_proto_builder.go diff --git a/onebot/http.go b/onebot/http.go index ad694a0..e805a0f 100644 --- a/onebot/http.go +++ b/onebot/http.go @@ -20,21 +20,21 @@ func sendHandler(w http.ResponseWriter, r *http.Request) { Error("仅支持 POST") return } - + req := new(SendRequest) if err := json.NewDecoder(r.Body).Decode(req); err != nil { http.Error(w, "无效的 JSON", http.StatusBadRequest) Error("无效的 JSON") return } - + // 参数校验 if len(req.Message) == 0 || (req.UserID == "" && req.GroupID == "") { http.Error(w, "参数缺失", http.StatusBadRequest) Error("参数缺失") return } - + sendContent := "" atUserID := "" for _, v := range req.Message { @@ -47,7 +47,7 @@ func sendHandler(w http.ResponseWriter, r *http.Request) { atUserID += v.Data.QQ + "," } } - + } else if v.Type == "image" || v.Type == "video" { msgChan <- &SendMsg{ UserId: req.UserID, @@ -57,7 +57,7 @@ func sendHandler(w http.ResponseWriter, r *http.Request) { } } } - + if sendContent != "" { msgChan <- &SendMsg{ UserId: req.UserID, @@ -67,7 +67,7 @@ func sendHandler(w http.ResponseWriter, r *http.Request) { AtUser: strings.TrimRight(atUserID, ","), } } - + json.NewEncoder(w).Encode(map[string]any{ "status": "ok", }) @@ -79,7 +79,7 @@ func SendHttpReq(jsonData []byte) { Error("http panic", "err", r, "stack", string(debug.Stack())) } }() - + time.Sleep(time.Duration(config.SendInterval) * time.Millisecond) jsonReq, err := HandleMsg(jsonData) if err != nil { @@ -89,20 +89,20 @@ func SendHttpReq(jsonData []byte) { if jsonReq == nil { return } - + Info("发送数据", "msg", string(jsonReq)) req, err := http.NewRequest("POST", config.SendURL, bytes.NewBuffer(jsonReq)) if err != nil { Error("创建请求失败", "err", err) return } - + // 5. 设置 Header (OneBot 接口通常要求 application/json) h := hmac.New(sha1.New, []byte(config.OnebotToken)) h.Write(jsonReq) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Signature", "sha1="+hex.EncodeToString(h.Sum(nil))) - + client := &http.Client{ Timeout: 10 * time.Second, } @@ -113,13 +113,13 @@ func SendHttpReq(jsonData []byte) { return } defer resp.Body.Close() - + // 7. 读取返回结果 body, err := io.ReadAll(resp.Body) if err != nil { Error("读取响应失败", "err", err) return } - + Info("返回内容", "status", resp.StatusCode, "body", string(body)) } diff --git a/onebot/proto/wxproto/text_msg.pb.go b/onebot/proto/wxproto/text_msg.pb.go new file mode 100644 index 0000000..63c83b9 --- /dev/null +++ b/onebot/proto/wxproto/text_msg.pb.go @@ -0,0 +1,228 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.5.1 +// source: proto/wxproto/text_msg.proto + +package wxproto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// 发送文本消息的protobuf结构 +type WxSendTextMsg struct { + state protoimpl.MessageState `protogen:"open.v1"` + Type int32 `protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"` + Body *WxSendTextBody `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WxSendTextMsg) Reset() { + *x = WxSendTextMsg{} + mi := &file_proto_wxproto_text_msg_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WxSendTextMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WxSendTextMsg) ProtoMessage() {} + +func (x *WxSendTextMsg) ProtoReflect() protoreflect.Message { + mi := &file_proto_wxproto_text_msg_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WxSendTextMsg.ProtoReflect.Descriptor instead. +func (*WxSendTextMsg) Descriptor() ([]byte, []int) { + return file_proto_wxproto_text_msg_proto_rawDescGZIP(), []int{0} +} + +func (x *WxSendTextMsg) GetType() int32 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *WxSendTextMsg) GetBody() *WxSendTextBody { + if x != nil { + return x.Body + } + return nil +} + +type WxSendTextBody struct { + state protoimpl.MessageState `protogen:"open.v1"` + Receiver *WxString `protobuf:"bytes,1,opt,name=receiver,proto3" json:"receiver,omitempty"` + Content []byte `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"` + Flag int32 `protobuf:"varint,3,opt,name=flag,proto3" json:"flag,omitempty"` + Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + MsgId int64 `protobuf:"varint,5,opt,name=msg_id,json=msgId,proto3" json:"msg_id,omitempty"` + Xml []byte `protobuf:"bytes,6,opt,name=xml,proto3" json:"xml,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WxSendTextBody) Reset() { + *x = WxSendTextBody{} + mi := &file_proto_wxproto_text_msg_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WxSendTextBody) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WxSendTextBody) ProtoMessage() {} + +func (x *WxSendTextBody) ProtoReflect() protoreflect.Message { + mi := &file_proto_wxproto_text_msg_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WxSendTextBody.ProtoReflect.Descriptor instead. +func (*WxSendTextBody) Descriptor() ([]byte, []int) { + return file_proto_wxproto_text_msg_proto_rawDescGZIP(), []int{1} +} + +func (x *WxSendTextBody) GetReceiver() *WxString { + if x != nil { + return x.Receiver + } + return nil +} + +func (x *WxSendTextBody) GetContent() []byte { + if x != nil { + return x.Content + } + return nil +} + +func (x *WxSendTextBody) GetFlag() int32 { + if x != nil { + return x.Flag + } + return 0 +} + +func (x *WxSendTextBody) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *WxSendTextBody) GetMsgId() int64 { + if x != nil { + return x.MsgId + } + return 0 +} + +func (x *WxSendTextBody) GetXml() []byte { + if x != nil { + return x.Xml + } + return nil +} + +var File_proto_wxproto_text_msg_proto protoreflect.FileDescriptor + +const file_proto_wxproto_text_msg_proto_rawDesc = "" + + "\n" + + "\x1cproto/wxproto/text_msg.proto\x12\awxproto\x1a\x1aproto/wxproto/wx_msg.proto\"P\n" + + "\rWxSendTextMsg\x12\x12\n" + + "\x04type\x18\x01 \x01(\x05R\x04type\x12+\n" + + "\x04body\x18\x02 \x01(\v2\x17.wxproto.WxSendTextBodyR\x04body\"\xb4\x01\n" + + "\x0eWxSendTextBody\x12-\n" + + "\breceiver\x18\x01 \x01(\v2\x11.wxproto.WxStringR\breceiver\x12\x18\n" + + "\acontent\x18\x02 \x01(\fR\acontent\x12\x12\n" + + "\x04flag\x18\x03 \x01(\x05R\x04flag\x12\x1c\n" + + "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\x12\x15\n" + + "\x06msg_id\x18\x05 \x01(\x03R\x05msgId\x12\x10\n" + + "\x03xml\x18\x06 \x01(\fR\x03xmlB>Z wxproto.WxSendTextBody + 2, // 1: wxproto.WxSendTextBody.receiver:type_name -> wxproto.WxString + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_proto_wxproto_text_msg_proto_init() } +func file_proto_wxproto_text_msg_proto_init() { + if File_proto_wxproto_text_msg_proto != nil { + return + } + file_proto_wxproto_wx_msg_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wxproto_text_msg_proto_rawDesc), len(file_proto_wxproto_text_msg_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proto_wxproto_text_msg_proto_goTypes, + DependencyIndexes: file_proto_wxproto_text_msg_proto_depIdxs, + MessageInfos: file_proto_wxproto_text_msg_proto_msgTypes, + }.Build() + File_proto_wxproto_text_msg_proto = out.File + file_proto_wxproto_text_msg_proto_goTypes = nil + file_proto_wxproto_text_msg_proto_depIdxs = nil +} diff --git a/onebot/proto/wxproto/text_msg.proto b/onebot/proto/wxproto/text_msg.proto new file mode 100644 index 0000000..52644cd --- /dev/null +++ b/onebot/proto/wxproto/text_msg.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package wxproto; + +option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto"; + +import "proto/wxproto/wx_msg.proto"; + +// 发送文本消息的protobuf结构 +message WxSendTextMsg { + int32 type = 1; + WxSendTextBody body = 2; +} + +message WxSendTextBody { + WxString receiver = 1; + bytes content = 2; + int32 flag = 3; + int64 timestamp = 4; + int64 msg_id = 5; + bytes xml = 6; +} diff --git a/onebot/script.js b/onebot/script.js index 28f7d8f..4edcb36 100644 --- a/onebot/script.js +++ b/onebot/script.js @@ -6,6 +6,14 @@ if (!baseAddr) { console.log("[+] WeChat base address: " + baseAddr); // -------------------------基础函数分区------------------------- +function hexToByteArray(hexStr) { + var bytes = []; + for (var i = 0; i < hexStr.length; i += 2) { + bytes.push(parseInt(hexStr.substr(i, 2), 16)); + } + return bytes; +} + function toVarint(n) { let res = []; while (n >= 128) { @@ -206,6 +214,8 @@ var videoCdnKeyGlobal = ""; var videoAesKeyGlobal = ""; var videoMd5KeyGlobal = ""; var videoIdGlobal = ""; +// 文本消息protobuf全局变量 (从Go直接传入hex编码) +var textProtoHexGlobal = ""; const fileCp = generateBytes(16) @@ -303,7 +313,7 @@ function patchTextProtoBuf() { setImmediate(patchTextProtoBuf); -function triggerSendTextMessage(taskId, receiver, content, atUser) { +function triggerSendTextMessage(taskId, receiver, content, atUser, protoHex) { // console.log("[+] Manual Trigger Started..."); if (!taskId || !receiver || !content) { console.error("[!] taskId or Receiver or Content is empty!"); @@ -321,7 +331,8 @@ function triggerSendTextMessage(taskId, receiver, content, atUser) { taskIdGlobal = taskId; receiverGlobal = receiver; contentGlobal = content; - atUserGlobal = atUser + atUserGlobal = atUser; + textProtoHexGlobal = protoHex; console.log("triggerSendTextMessage: receiver=" + receiverGlobal); textMessageAddr.add(0x08).writeU32(taskIdGlobal); @@ -434,48 +445,16 @@ function attachSendTextProto() { } console.log(`[+] 注入 Protobuf: receiver=${receiverGlobal}`); - const type = [0x08, 0x01, 0x12] - const receiverHeader = [0x0A, receiverGlobal.length + 2, 0x0A, receiverGlobal.length]; - const receiverProto = stringToHexArray(receiverGlobal); - const contentProto = stringToHexArray(contentGlobal); - const contentHeader = [0x12, ...toVarint(contentProto.length)]; - const tsHeader = [0x18, 0x01, 0x20]; - const tsBytes = getVarintTimestampBytes(); - const msgIdHeader = [0x28] - const msgId = generateRandom5ByteVarint() - - const htmlUpperPart = [0x3C, 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x3E] - let atUserHeader = [] - if (atUserGlobal) { - atUserHeader = atUserHeader.concat([0x3C, 0x61, 0x74, 0x75, 0x73, 0x65, 0x72, 0x6c, 0x69, 0x73, 0x74, 0x3e]).concat(stringToHexArray(atUserGlobal)).concat([0x3C, 0x2F, 0x61, 0x74, 0x75, 0x73, 0x65, 0x72, 0x6C, 0x69, 0x73, 0x74, 0x3E]) + // 使用Go传入的protobuf数据 + if (!textProtoHexGlobal || textProtoHexGlobal.length === 0) { + console.error("[!] textProtoHexGlobal 为空"); + return; } - const htmlLowerPart = [0x3C, 0x61, 0x6C, 0x6E, 0x6F, - 0x64, 0x65, 0x3E, 0x3C, 0x66, 0x72, 0x3E, 0x31, - 0x3C, 0x2F, 0x66, 0x72, 0x3E, 0x3C, 0x2F, 0x61, - 0x6C, 0x6E, 0x6F, 0x64, 0x65, 0x3E, 0x3C, 0x2F, - 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, - 0x63, 0x65, 0x3E, 0x00] - - const htmlHeader = [0x32, htmlUpperPart.length + atUserHeader.length + htmlLowerPart.length] - - - const valueLen = toVarint(receiverHeader.length + receiverProto.length + contentHeader.length + - contentProto.length + tsHeader.length + tsBytes.length + msgIdHeader.length + msgId.length + htmlHeader.length + - htmlUpperPart.length + atUserHeader.length + htmlLowerPart.length) - - // 合并数组 - const finalPayload = type.concat(valueLen).concat(receiverHeader).concat(receiverProto).concat(contentHeader).concat(contentProto).concat(tsHeader).concat(tsBytes).concat(msgIdHeader).concat(msgId).concat(htmlHeader).concat(htmlUpperPart).concat(atUserHeader).concat(htmlLowerPart); + const finalPayload = hexToByteArray(textProtoHexGlobal); textProtoX1PayloadAddr.writeByteArray(finalPayload); this.context.x1 = textProtoX1PayloadAddr; this.context.x2 = ptr(finalPayload.length); - - // console.log("[+] 文本寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(textProtoX1PayloadAddr, { - // offset: 0, - // length: 128, - // header: true, - // ansi: true - // })); }, }); } diff --git a/onebot/text_proto_builder.go b/onebot/text_proto_builder.go new file mode 100644 index 0000000..34bfa07 --- /dev/null +++ b/onebot/text_proto_builder.go @@ -0,0 +1,43 @@ +package main + +import ( + "encoding/hex" + "fmt" + "math/rand" + "time" + + "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto" + "google.golang.org/protobuf/proto" +) + +// BuildTextMsgProto 构建发送文本消息的protobuf并返回hex编码的字符串 +func BuildTextMsgProto(receiver, content, atUser string) (string, error) { + // 构建 msgsource XML + xmlStr := "" + if atUser != "" { + xmlStr += "" + atUser + "" + } + xmlStr += "1\x00" + + // 生成随机消息ID + msgId := rand.Int63n(1<<34) | (1 << 34) + + msg := &wxproto.WxSendTextMsg{ + Type: 1, + Body: &wxproto.WxSendTextBody{ + Receiver: &wxproto.WxString{Value: receiver}, + Content: []byte(content), + Flag: 1, + Timestamp: time.Now().Unix(), + MsgId: msgId, + Xml: []byte(xmlStr), + }, + } + + data, err := proto.Marshal(msg) + if err != nil { + return "", fmt.Errorf("marshal text proto failed: %w", err) + } + + return hex.EncodeToString(data), nil +} diff --git a/onebot/worker.go b/onebot/worker.go index 2008252..d07aaef 100644 --- a/onebot/worker.go +++ b/onebot/worker.go @@ -53,7 +53,12 @@ func SendWechatMsg(m *SendMsg) { switch m.Type { case "text": - result := fridaScript.ExportsCall("triggerSendTextMessage", currTaskId, targetId, m.Content, m.AtUser) + protoHex, err := BuildTextMsgProto(targetId, m.Content, m.AtUser) + if err != nil { + Error("构建文本protobuf失败", "err", err) + return + } + result := fridaScript.ExportsCall("triggerSendTextMessage", currTaskId, targetId, m.Content, m.AtUser, protoHex) Info("📩 发送文本任务执行结果", "result", result, "task_id", currTaskId, "target_id", targetId, "at_user", m.AtUser) if result != "1" { Error("发送文本失败", "task_id", currTaskId, "target_id", targetId, "result", result)