optimize receive

This commit is contained in:
yincong
2026-05-08 11:27:36 +08:00
parent e0989d288b
commit dc7925edd2
7 changed files with 841 additions and 404 deletions
+1
View File
@@ -17,6 +17,7 @@ require (
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
golang.org/x/sys v0.12.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
modernc.org/libc v1.8.1 // indirect
modernc.org/mathutil v1.2.2 // indirect
modernc.org/memory v1.0.4 // indirect
+4
View File
@@ -4,6 +4,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/frida/frida-go v1.0.0 h1:Xlq1CB8QSAC6zbOFdjCX0oK8RjQGtdl2yATbUQKROwo=
github.com/frida/frida-go v1.0.0/go.mod h1:O8Dg1YBGfQsBEL1a8x3GURw/JllJrcuvg78ga2OgdM4=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
@@ -35,6 +37,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+2
View File
@@ -199,6 +199,8 @@ func loadJs() {
payloadJson, _ := json.Marshal(pMap)
if t, ok := pMap["type"]; ok {
switch t.(string) {
case "protobuf_msg":
go HandleProtobufMsgAndSend(pMap)
case "send":
if config.ConnType == "http" {
go SendHttpReq(payloadJson)
+534
View File
@@ -0,0 +1,534 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v3.5.1
// source: proto/wxproto/wx_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)
)
// 最外层消息
type WxRecvMsg struct {
state protoimpl.MessageState `protogen:"open.v1"`
RecvType int32 `protobuf:"varint,1,opt,name=recv_type,json=recvType,proto3" json:"recv_type,omitempty"`
Wrapper *WxRecvMsgWrapper `protobuf:"bytes,2,opt,name=wrapper,proto3" json:"wrapper,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WxRecvMsg) Reset() {
*x = WxRecvMsg{}
mi := &file_proto_wxproto_wx_msg_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *WxRecvMsg) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WxRecvMsg) ProtoMessage() {}
func (x *WxRecvMsg) ProtoReflect() protoreflect.Message {
mi := &file_proto_wxproto_wx_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 WxRecvMsg.ProtoReflect.Descriptor instead.
func (*WxRecvMsg) Descriptor() ([]byte, []int) {
return file_proto_wxproto_wx_msg_proto_rawDescGZIP(), []int{0}
}
func (x *WxRecvMsg) GetRecvType() int32 {
if x != nil {
return x.RecvType
}
return 0
}
func (x *WxRecvMsg) GetWrapper() *WxRecvMsgWrapper {
if x != nil {
return x.Wrapper
}
return nil
}
// 第二层
type WxRecvMsgWrapper struct {
state protoimpl.MessageState `protogen:"open.v1"`
MsgType int32 `protobuf:"varint,1,opt,name=msg_type,json=msgType,proto3" json:"msg_type,omitempty"`
Body *WxRecvMsgBody `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WxRecvMsgWrapper) Reset() {
*x = WxRecvMsgWrapper{}
mi := &file_proto_wxproto_wx_msg_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *WxRecvMsgWrapper) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WxRecvMsgWrapper) ProtoMessage() {}
func (x *WxRecvMsgWrapper) ProtoReflect() protoreflect.Message {
mi := &file_proto_wxproto_wx_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 WxRecvMsgWrapper.ProtoReflect.Descriptor instead.
func (*WxRecvMsgWrapper) Descriptor() ([]byte, []int) {
return file_proto_wxproto_wx_msg_proto_rawDescGZIP(), []int{1}
}
func (x *WxRecvMsgWrapper) GetMsgType() int32 {
if x != nil {
return x.MsgType
}
return 0
}
func (x *WxRecvMsgWrapper) GetBody() *WxRecvMsgBody {
if x != nil {
return x.Body
}
return nil
}
// 第三层: 包含消息类型
type WxRecvMsgBody struct {
state protoimpl.MessageState `protogen:"open.v1"`
MsgType int32 `protobuf:"varint,1,opt,name=msg_type,json=msgType,proto3" json:"msg_type,omitempty"`
Content *WxRecvMsgContent `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WxRecvMsgBody) Reset() {
*x = WxRecvMsgBody{}
mi := &file_proto_wxproto_wx_msg_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *WxRecvMsgBody) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WxRecvMsgBody) ProtoMessage() {}
func (x *WxRecvMsgBody) ProtoReflect() protoreflect.Message {
mi := &file_proto_wxproto_wx_msg_proto_msgTypes[2]
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 WxRecvMsgBody.ProtoReflect.Descriptor instead.
func (*WxRecvMsgBody) Descriptor() ([]byte, []int) {
return file_proto_wxproto_wx_msg_proto_rawDescGZIP(), []int{2}
}
func (x *WxRecvMsgBody) GetMsgType() int32 {
if x != nil {
return x.MsgType
}
return 0
}
func (x *WxRecvMsgBody) GetContent() *WxRecvMsgContent {
if x != nil {
return x.Content
}
return nil
}
// 第四层
type WxRecvMsgContent struct {
state protoimpl.MessageState `protogen:"open.v1"`
DataLen int32 `protobuf:"varint,1,opt,name=data_len,json=dataLen,proto3" json:"data_len,omitempty"`
Data *WxRecvMsgData `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WxRecvMsgContent) Reset() {
*x = WxRecvMsgContent{}
mi := &file_proto_wxproto_wx_msg_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *WxRecvMsgContent) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WxRecvMsgContent) ProtoMessage() {}
func (x *WxRecvMsgContent) ProtoReflect() protoreflect.Message {
mi := &file_proto_wxproto_wx_msg_proto_msgTypes[3]
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 WxRecvMsgContent.ProtoReflect.Descriptor instead.
func (*WxRecvMsgContent) Descriptor() ([]byte, []int) {
return file_proto_wxproto_wx_msg_proto_rawDescGZIP(), []int{3}
}
func (x *WxRecvMsgContent) GetDataLen() int32 {
if x != nil {
return x.DataLen
}
return 0
}
func (x *WxRecvMsgContent) GetData() *WxRecvMsgData {
if x != nil {
return x.Data
}
return nil
}
// 第五层: 实际消息数据
type WxRecvMsgData struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Sender *WxString `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"`
Receiver *WxString `protobuf:"bytes,3,opt,name=receiver,proto3" json:"receiver,omitempty"`
Flag int32 `protobuf:"varint,4,opt,name=flag,proto3" json:"flag,omitempty"`
Content *WxString `protobuf:"bytes,5,opt,name=content,proto3" json:"content,omitempty"`
ContentType int32 `protobuf:"varint,6,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
SubType int32 `protobuf:"varint,7,opt,name=sub_type,json=subType,proto3" json:"sub_type,omitempty"`
MediaContent []byte `protobuf:"bytes,8,opt,name=media_content,json=mediaContent,proto3" json:"media_content,omitempty"`
CreateTime int64 `protobuf:"varint,9,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty"`
Xml []byte `protobuf:"bytes,10,opt,name=xml,proto3" json:"xml,omitempty"`
UserContent []byte `protobuf:"bytes,11,opt,name=user_content,json=userContent,proto3" json:"user_content,omitempty"`
MsgId int64 `protobuf:"varint,12,opt,name=msg_id,json=msgId,proto3" json:"msg_id,omitempty"`
MsgOrder int64 `protobuf:"varint,13,opt,name=msg_order,json=msgOrder,proto3" json:"msg_order,omitempty"`
ExtraXml []byte `protobuf:"bytes,14,opt,name=extra_xml,json=extraXml,proto3" json:"extra_xml,omitempty"`
Unknown15 int32 `protobuf:"varint,15,opt,name=unknown15,proto3" json:"unknown15,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WxRecvMsgData) Reset() {
*x = WxRecvMsgData{}
mi := &file_proto_wxproto_wx_msg_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *WxRecvMsgData) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WxRecvMsgData) ProtoMessage() {}
func (x *WxRecvMsgData) ProtoReflect() protoreflect.Message {
mi := &file_proto_wxproto_wx_msg_proto_msgTypes[4]
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 WxRecvMsgData.ProtoReflect.Descriptor instead.
func (*WxRecvMsgData) Descriptor() ([]byte, []int) {
return file_proto_wxproto_wx_msg_proto_rawDescGZIP(), []int{4}
}
func (x *WxRecvMsgData) GetId() int64 {
if x != nil {
return x.Id
}
return 0
}
func (x *WxRecvMsgData) GetSender() *WxString {
if x != nil {
return x.Sender
}
return nil
}
func (x *WxRecvMsgData) GetReceiver() *WxString {
if x != nil {
return x.Receiver
}
return nil
}
func (x *WxRecvMsgData) GetFlag() int32 {
if x != nil {
return x.Flag
}
return 0
}
func (x *WxRecvMsgData) GetContent() *WxString {
if x != nil {
return x.Content
}
return nil
}
func (x *WxRecvMsgData) GetContentType() int32 {
if x != nil {
return x.ContentType
}
return 0
}
func (x *WxRecvMsgData) GetSubType() int32 {
if x != nil {
return x.SubType
}
return 0
}
func (x *WxRecvMsgData) GetMediaContent() []byte {
if x != nil {
return x.MediaContent
}
return nil
}
func (x *WxRecvMsgData) GetCreateTime() int64 {
if x != nil {
return x.CreateTime
}
return 0
}
func (x *WxRecvMsgData) GetXml() []byte {
if x != nil {
return x.Xml
}
return nil
}
func (x *WxRecvMsgData) GetUserContent() []byte {
if x != nil {
return x.UserContent
}
return nil
}
func (x *WxRecvMsgData) GetMsgId() int64 {
if x != nil {
return x.MsgId
}
return 0
}
func (x *WxRecvMsgData) GetMsgOrder() int64 {
if x != nil {
return x.MsgOrder
}
return 0
}
func (x *WxRecvMsgData) GetExtraXml() []byte {
if x != nil {
return x.ExtraXml
}
return nil
}
func (x *WxRecvMsgData) GetUnknown15() int32 {
if x != nil {
return x.Unknown15
}
return 0
}
// 字符串包装
type WxString struct {
state protoimpl.MessageState `protogen:"open.v1"`
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WxString) Reset() {
*x = WxString{}
mi := &file_proto_wxproto_wx_msg_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *WxString) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WxString) ProtoMessage() {}
func (x *WxString) ProtoReflect() protoreflect.Message {
mi := &file_proto_wxproto_wx_msg_proto_msgTypes[5]
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 WxString.ProtoReflect.Descriptor instead.
func (*WxString) Descriptor() ([]byte, []int) {
return file_proto_wxproto_wx_msg_proto_rawDescGZIP(), []int{5}
}
func (x *WxString) GetValue() string {
if x != nil {
return x.Value
}
return ""
}
var File_proto_wxproto_wx_msg_proto protoreflect.FileDescriptor
const file_proto_wxproto_wx_msg_proto_rawDesc = "" +
"\n" +
"\x1aproto/wxproto/wx_msg.proto\x12\awxproto\"]\n" +
"\tWxRecvMsg\x12\x1b\n" +
"\trecv_type\x18\x01 \x01(\x05R\brecvType\x123\n" +
"\awrapper\x18\x02 \x01(\v2\x19.wxproto.WxRecvMsgWrapperR\awrapper\"Y\n" +
"\x10WxRecvMsgWrapper\x12\x19\n" +
"\bmsg_type\x18\x01 \x01(\x05R\amsgType\x12*\n" +
"\x04body\x18\x02 \x01(\v2\x16.wxproto.WxRecvMsgBodyR\x04body\"_\n" +
"\rWxRecvMsgBody\x12\x19\n" +
"\bmsg_type\x18\x01 \x01(\x05R\amsgType\x123\n" +
"\acontent\x18\x02 \x01(\v2\x19.wxproto.WxRecvMsgContentR\acontent\"Y\n" +
"\x10WxRecvMsgContent\x12\x19\n" +
"\bdata_len\x18\x01 \x01(\x05R\adataLen\x12*\n" +
"\x04data\x18\x02 \x01(\v2\x16.wxproto.WxRecvMsgDataR\x04data\"\xe2\x03\n" +
"\rWxRecvMsgData\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x03R\x02id\x12)\n" +
"\x06sender\x18\x02 \x01(\v2\x11.wxproto.WxStringR\x06sender\x12-\n" +
"\breceiver\x18\x03 \x01(\v2\x11.wxproto.WxStringR\breceiver\x12\x12\n" +
"\x04flag\x18\x04 \x01(\x05R\x04flag\x12+\n" +
"\acontent\x18\x05 \x01(\v2\x11.wxproto.WxStringR\acontent\x12!\n" +
"\fcontent_type\x18\x06 \x01(\x05R\vcontentType\x12\x19\n" +
"\bsub_type\x18\a \x01(\x05R\asubType\x12#\n" +
"\rmedia_content\x18\b \x01(\fR\fmediaContent\x12\x1f\n" +
"\vcreate_time\x18\t \x01(\x03R\n" +
"createTime\x12\x10\n" +
"\x03xml\x18\n" +
" \x01(\fR\x03xml\x12!\n" +
"\fuser_content\x18\v \x01(\fR\vuserContent\x12\x15\n" +
"\x06msg_id\x18\f \x01(\x03R\x05msgId\x12\x1b\n" +
"\tmsg_order\x18\r \x01(\x03R\bmsgOrder\x12\x1b\n" +
"\textra_xml\x18\x0e \x01(\fR\bextraXml\x12\x1c\n" +
"\tunknown15\x18\x0f \x01(\x05R\tunknown15\" \n" +
"\bWxString\x12\x14\n" +
"\x05value\x18\x01 \x01(\tR\x05valueB>Z<github.com/yincongcyincong/weixin-macos/onebot/proto/wxprotob\x06proto3"
var (
file_proto_wxproto_wx_msg_proto_rawDescOnce sync.Once
file_proto_wxproto_wx_msg_proto_rawDescData []byte
)
func file_proto_wxproto_wx_msg_proto_rawDescGZIP() []byte {
file_proto_wxproto_wx_msg_proto_rawDescOnce.Do(func() {
file_proto_wxproto_wx_msg_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_wxproto_wx_msg_proto_rawDesc), len(file_proto_wxproto_wx_msg_proto_rawDesc)))
})
return file_proto_wxproto_wx_msg_proto_rawDescData
}
var file_proto_wxproto_wx_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_proto_wxproto_wx_msg_proto_goTypes = []any{
(*WxRecvMsg)(nil), // 0: wxproto.WxRecvMsg
(*WxRecvMsgWrapper)(nil), // 1: wxproto.WxRecvMsgWrapper
(*WxRecvMsgBody)(nil), // 2: wxproto.WxRecvMsgBody
(*WxRecvMsgContent)(nil), // 3: wxproto.WxRecvMsgContent
(*WxRecvMsgData)(nil), // 4: wxproto.WxRecvMsgData
(*WxString)(nil), // 5: wxproto.WxString
}
var file_proto_wxproto_wx_msg_proto_depIdxs = []int32{
1, // 0: wxproto.WxRecvMsg.wrapper:type_name -> wxproto.WxRecvMsgWrapper
2, // 1: wxproto.WxRecvMsgWrapper.body:type_name -> wxproto.WxRecvMsgBody
3, // 2: wxproto.WxRecvMsgBody.content:type_name -> wxproto.WxRecvMsgContent
4, // 3: wxproto.WxRecvMsgContent.data:type_name -> wxproto.WxRecvMsgData
5, // 4: wxproto.WxRecvMsgData.sender:type_name -> wxproto.WxString
5, // 5: wxproto.WxRecvMsgData.receiver:type_name -> wxproto.WxString
5, // 6: wxproto.WxRecvMsgData.content:type_name -> wxproto.WxString
7, // [7:7] is the sub-list for method output_type
7, // [7:7] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] is the sub-list for field type_name
}
func init() { file_proto_wxproto_wx_msg_proto_init() }
func file_proto_wxproto_wx_msg_proto_init() {
if File_proto_wxproto_wx_msg_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wxproto_wx_msg_proto_rawDesc), len(file_proto_wxproto_wx_msg_proto_rawDesc)),
NumEnums: 0,
NumMessages: 6,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proto_wxproto_wx_msg_proto_goTypes,
DependencyIndexes: file_proto_wxproto_wx_msg_proto_depIdxs,
MessageInfos: file_proto_wxproto_wx_msg_proto_msgTypes,
}.Build()
File_proto_wxproto_wx_msg_proto = out.File
file_proto_wxproto_wx_msg_proto_goTypes = nil
file_proto_wxproto_wx_msg_proto_depIdxs = nil
}
+53
View File
@@ -0,0 +1,53 @@
syntax = "proto3";
package wxproto;
option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto";
// 最外层消息
message WxRecvMsg {
int32 recv_type = 1;
WxRecvMsgWrapper wrapper = 2;
}
// 第二层
message WxRecvMsgWrapper {
int32 msg_type = 1;
WxRecvMsgBody body = 2;
}
// 第三层: 包含消息类型
message WxRecvMsgBody {
int32 msg_type = 1;
WxRecvMsgContent content = 2;
}
// 第四层
message WxRecvMsgContent {
int32 data_len = 1;
WxRecvMsgData data = 2;
}
// 第五层: 实际消息数据
message WxRecvMsgData {
int64 id = 1;
WxString sender = 2;
WxString receiver = 3;
int32 flag = 4;
WxString content = 5;
int32 content_type = 6;
int32 sub_type = 7;
bytes media_content = 8;
int64 create_time = 9;
bytes xml = 10;
bytes user_content = 11;
int64 msg_id = 12;
int64 msg_order = 13;
bytes extra_xml = 14;
int32 unknown15 = 15;
}
// 字符串包装
message WxString {
string value = 1;
}
+238
View File
@@ -0,0 +1,238 @@
package main
import (
"encoding/json"
"fmt"
"regexp"
"strings"
"time"
"github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto"
"google.golang.org/protobuf/proto"
)
func HandleProtobufMsgAndSend(payload map[string]interface{}) {
jsonData, err := HandleProtobufMsg(payload)
if err != nil {
Error("protobuf消息处理失败", "err", err)
return
}
if jsonData == nil {
return
}
// jsonData 是 WechatMessage JSON, 直接交给现有的发送流程处理
if config.ConnType == "http" {
SendHttpReq(jsonData)
} else {
SendWebSocketMsg(jsonData)
}
}
func HandleProtobufMsg(payload map[string]interface{}) ([]byte, error) {
dataInter, ok := payload["data"]
if !ok {
return nil, fmt.Errorf("protobuf_msg: missing data field")
}
dataArr, ok := dataInter.([]interface{})
if !ok {
return nil, fmt.Errorf("protobuf_msg: data is not array")
}
rawBytes := make([]byte, len(dataArr))
for i, v := range dataArr {
num, ok := v.(float64)
if !ok {
return nil, fmt.Errorf("protobuf_msg: data[%d] is not number", i)
}
rawBytes[i] = byte(int(num))
}
msg := &wxproto.WxRecvMsg{}
err := proto.Unmarshal(rawBytes, msg)
if err != nil {
return nil, fmt.Errorf("protobuf unmarshal failed: %w", err)
}
data := getWxMsgData(msg)
if data == nil {
return nil, fmt.Errorf("protobuf_msg: cannot extract message data")
}
sender := ""
receiver := ""
content := ""
if data.Sender != nil {
sender = data.Sender.Value
}
if data.Receiver != nil {
receiver = data.Receiver.Value
}
if data.Content != nil {
content = data.Content.Value
}
xmlStr := string(data.Xml)
userContent := string(data.UserContent)
msgId := fmt.Sprintf("%d", data.MsgId)
if sender == "" || receiver == "" || content == "" || msgId == "" || msgId == "0" {
return nil, fmt.Errorf("protobuf_msg: missing required fields sender=%s receiver=%s content_len=%d msgId=%s",
sender, receiver, len(content), msgId)
}
selfId := receiver
msgType := "private"
groupId := ""
senderUser := sender
senderNickname := ""
messages := getMessagesFromProto(content, sender, data.MediaContent)
if strings.Contains(sender, "@chatroom") {
msgType = "group"
groupId = sender
splitIndex := strings.Index(content, ":")
sendUserStart := strings.Index(content, "wxid_")
if sendUserStart >= 0 && splitIndex > sendUserStart {
senderUser = strings.TrimSpace(content[sendUserStart:splitIndex])
}
atUserMatch := regexp.MustCompile(`<atuserlist>([\s\S]*?)</atuserlist>`).FindStringSubmatch(xmlStr)
if len(atUserMatch) > 1 {
atUsers := strings.Split(atUserMatch[1], ",")
for _, atUser := range atUsers {
atUser = strings.TrimSpace(atUser)
if atUser != "" {
messages = append(messages, &Message{Type: "at", Data: &SendRequestData{QQ: atUser}})
}
}
}
// 处理用户的名称
splitIdx := strings.Index(userContent, ":")
if splitIdx == -1 {
if idx := strings.Index(userContent, "在群聊中@了你"); idx != -1 {
senderNickname = strings.TrimSpace(userContent[:idx])
} else if idx := strings.Index(userContent, "在群聊中发了一段语"); idx != -1 {
senderNickname = strings.TrimSpace(userContent[:idx])
}
} else {
senderNickname = strings.TrimSpace(userContent[:splitIdx])
}
if senderNickname == "" {
senderNickname = senderUser
}
} else {
splitIdx := strings.Index(userContent, ":")
if splitIdx != -1 {
senderNickname = strings.TrimSpace(userContent[:splitIdx])
}
if senderNickname == "" {
senderNickname = senderUser
}
}
myWechatId = selfId
if groupId != "" {
userID2NicknameMap.Store(groupId+"_"+senderUser, senderNickname)
}
wechatMsg := &WechatMessage{
GroupId: groupId,
SelfID: selfId,
UserID: senderUser,
Sender: &Sender{UserID: senderUser, Nickname: senderNickname},
Time: time.Now().UnixMilli(),
PostType: "message",
MessageId: msgId,
Message: messages,
MsgResource: xmlStr,
RawMessage: content,
ShowContent: userContent,
MessageType: msgType,
}
return json.Marshal(wechatMsg)
}
func getWxMsgData(msg *wxproto.WxRecvMsg) *wxproto.WxRecvMsgData {
if msg == nil || msg.Wrapper == nil {
return nil
}
if msg.Wrapper.Body == nil {
return nil
}
if msg.Wrapper.Body.Content == nil {
return nil
}
return msg.Wrapper.Body.Content.Data
}
func getMessagesFromProto(content, sender string, mediaContent []byte) []*Message {
var messages []*Message
if strings.Contains(sender, "@chatroom") {
splitIndex := strings.Index(content, ":")
pureContent := ""
if splitIndex >= 0 {
pureContent = strings.TrimSpace(content[splitIndex+1:])
} else {
pureContent = content
}
parts := strings.Split(pureContent, "\u2005")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
messages = append(messages, classifyMessage(part, nil))
}
} else {
messages = append(messages, classifyMessage(content, mediaContent))
}
return messages
}
func classifyMessage(content string, mediaContent []byte) *Message {
content = strings.ReplaceAll(content, "\t", "")
content = strings.ReplaceAll(content, "\n", "")
switch {
case strings.HasPrefix(content, "<?xml version=\"1.0\"?><msg><img"):
return &Message{Type: "image", Data: &SendRequestData{Text: content}}
case strings.HasPrefix(content, "<msg><voicemsg"):
if mediaContent != nil {
// 找到 silk 音频数据起始位置
for i, b := range mediaContent {
if b == 0x02 {
mediaContent = mediaContent[i:]
break
}
}
return &Message{Type: "record", Data: &SendRequestData{Text: content, Media: mediaContent}}
}
return &Message{Type: "record", Data: &SendRequestData{Text: content}}
case strings.HasPrefix(content, "<?xml version=\"1.0\"?><msg><appmsg"):
re := regexp.MustCompile(`<type>(.*?)</type>`)
match := re.FindStringSubmatch(content)
if len(match) > 1 {
switch match[1] {
case "5":
return &Message{Type: "share", Data: &SendRequestData{Text: content}}
case "6":
return &Message{Type: "file", Data: &SendRequestData{Text: content}}
}
}
return &Message{Type: "text", Data: &SendRequestData{Text: content}}
case strings.HasPrefix(content, "<msg><emoji"):
return &Message{Type: "face", Data: &SendRequestData{Text: content}}
case strings.HasPrefix(content, "<?xml version=\"1.0\"?><msg><videomsg"):
return &Message{Type: "video", Data: &SendRequestData{Text: content}}
case strings.HasPrefix(content, "<sysmsg") || strings.HasPrefix(content, "<?xml version=\"1.0\"?><sysmsg"):
return &Message{Type: "sys", Data: &SendRequestData{Text: content}}
default:
return &Message{Type: "text", Data: &SendRequestData{Text: content}}
}
}
+9 -404
View File
@@ -82,268 +82,6 @@ function generateAESKey() {
return key;
}
function getProtobufRawBytes(pBuffer, scanSize) {
const tags = [0x12, 0x1A, 0x2A, 0x42, 0x52, 0x5A];
let uint8Array;
try {
const mem = pBuffer.readByteArray(scanSize);
if (!mem) return [];
uint8Array = new Uint8Array(mem);
} catch (e) {
console.error("读取内存失败: " + e);
return [];
}
let finalResults = [];
let i = 0x1a;
tags.forEach(targetTag => {
let found = false;
for (; i < uint8Array.length; i++) {
if (uint8Array[i] === targetTag) {
// 1. 解析 Varint 长度 (支持 1-5 字节长度标识)
let length = 0;
let shift = 0;
let bytesReadForLen = 0;
i = i + 1;
let lenNum = 0;
while (i < uint8Array.length) {
let b = uint8Array[i];
length |= (b & 0x7F) << shift;
bytesReadForLen++;
i++;
lenNum++;
if (!(b & 0x80)) break;
shift += 7;
}
// 2. 截取原始 Byte 数据
if (i + length <= uint8Array.length) {
let addNum = 0
if (targetTag === 0x12 || targetTag === 0x1A || targetTag === 0x2A) {
addNum = lenNum + 1;
}
let rawData = uint8Array.slice(i, i + length);
if (targetTag === 0x42) {
finalResults.push(rawData);
} else {
finalResults.push(decodeProtobufString(rawData));
}
i += length;
} else {
finalResults.push(null); // 长度越界
}
found = true;
break; // 找到第一个匹配的 Tag 就跳出
}
}
if (!found) finalResults.push(null); // 未找到该 Tag
});
for (; i < uint8Array.length; i++) {
if (uint8Array[i] === 0x60 && i + 10 <= uint8Array.length) {
finalResults.push(uint8Array.slice(i + 1, i + 10))
}
}
return finalResults;
}
function getCleanString(uint8Array) {
var out = "";
var i = 0;
var len = uint8Array.length;
while (i < len) {
var c = uint8Array[i++];
// 1. 处理单字节 (ASCII: 0xxxxxxx)
if (c < 0x80) {
// 只保留可见字符 (Space 32 到 ~ 126)
if (c >= 32 && c <= 126) {
out += String.fromCharCode(c);
}
}
// 2. 处理双字节 (110xxxxx 10xxxxxx)
else if ((c & 0xE0) === 0xC0 && i < len) {
var c2 = uint8Array[i++];
if ((c2 & 0xC0) === 0x80) {
// 这种通常是特殊拉丁字母等,按需保留
var charCode = ((c & 0x1F) << 6) | (c2 & 0x3F);
out += String.fromCharCode(charCode);
} else {
i--;
}
}
// 3. 处理三字节 (1110xxxx 10xxxxxx 10xxxxxx) -> 绝大多数汉字在此
else if ((c & 0xF0) === 0xE0 && i + 1 < len) {
var c2 = uint8Array[i++];
var c3 = uint8Array[i++];
if ((c2 & 0xC0) === 0x80 && (c3 & 0xC0) === 0x80) {
var charCode = ((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
if (
(charCode >= 0x4E00 && charCode <= 0x9FA5) || // 基本汉字
(charCode >= 0x3000 && charCode <= 0x303F) || // 常用中文标点 (。,、)
(charCode >= 0xFF00 && charCode <= 0xFFEF) || // 全角符号/标点 (!:?)
(charCode >= 0x2000 && charCode <= 0x206F) || // 常用标点扩展 (含 \u2005)
(charCode >= 0x3400 && charCode <= 0x4DBF) // 扩展 A 区汉字
) {
out += String.fromCharCode(charCode);
}
} else {
i -= 2;
}
} else if ((c & 0xF8) === 0xF0 && i + 2 < len) {
var c2 = uint8Array[i++];
var c3 = uint8Array[i++];
var c4 = uint8Array[i++];
if ((c2 & 0xC0) === 0x80 && (c3 & 0xC0) === 0x80 && (c4 & 0xC0) === 0x80) {
// 计算 Unicode 码点
var codePoint = ((c & 0x07) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F);
// Emoji 范围通常在 U+1F000 到 U+1F9FF 之间
if (codePoint >= 0x1F000 && codePoint <= 0x1FADF) {
// 使用 fromCodePoint 处理 4 字节字符
out += String.fromCodePoint(codePoint);
}
} else {
i -= 3;
}
}
}
return out;
}
function readProtobufVarint(uint8Array, offset) {
let value = 0;
let shift = 0;
for (let i = offset; i < uint8Array.length && i - offset < 10; i++) {
const b = uint8Array[i];
value += (b & 0x7F) * Math.pow(2, shift);
if ((b & 0x80) === 0) {
return {value: value, nextOffset: i + 1};
}
shift += 7;
}
return null;
}
function decodeProtobufString(uint8Array, depth) {
if (depth === undefined) {
depth = 0;
}
if (depth < 2) {
const nested = tryDecodeNestedProtobufString(uint8Array, depth);
if (nested !== null) {
return nested;
}
}
return getCleanString(uint8Array);
}
function tryDecodeNestedProtobufString(uint8Array, depth) {
if (!uint8Array || uint8Array.length < 2) {
return null;
}
let offset = 0;
const candidates = [];
while (offset < uint8Array.length) {
const tagInfo = readProtobufVarint(uint8Array, offset);
if (!tagInfo) {
return null;
}
const fieldNumber = Math.floor(tagInfo.value / 8);
const wireType = tagInfo.value & 0x07;
if (fieldNumber <= 0) {
return null;
}
offset = tagInfo.nextOffset;
if (wireType === 2) {
const lenInfo = readProtobufVarint(uint8Array, offset);
if (!lenInfo) {
return null;
}
const valueStart = lenInfo.nextOffset;
const valueEnd = valueStart + lenInfo.value;
if (valueEnd > uint8Array.length) {
return null;
}
const value = uint8Array.slice(valueStart, valueEnd);
const text = decodeProtobufString(value, depth + 1);
if (text) {
candidates.push({fieldNumber: fieldNumber, text: text});
}
offset = valueEnd;
} else if (wireType === 0) {
const valueInfo = readProtobufVarint(uint8Array, offset);
if (!valueInfo) {
return null;
}
offset = valueInfo.nextOffset;
} else if (wireType === 1) {
offset += 8;
if (offset > uint8Array.length) {
return null;
}
} else if (wireType === 5) {
offset += 4;
if (offset > uint8Array.length) {
return null;
}
} else {
return null;
}
}
if (candidates.length === 0) {
return null;
}
for (const candidate of candidates) {
if (candidate.fieldNumber === 2) {
return candidate.text;
}
}
return candidates.map(candidate => candidate.text).join("");
}
function protobufVarintToNumberString(uint8Array) {
let result = BigInt(0);
let shift = BigInt(0);
for (let i = 0; i < uint8Array?.length; i++) {
const byte = uint8Array[i];
// 1. 取出低 7 位并累加到结果中
// (BigInt(byte & 0x7F) << shift)
result += BigInt(byte & 0x7F) << shift;
// 2. 检查最高位 (MSB)。如果为 0,说明这个数字结束了
if ((byte & 0x80) === 0) {
return result.toString();
}
// 3. 准备处理下一个 7 位
shift += BigInt(7);
}
return result.toString();
}
function generateBytes(n) {
// 生成随机字符串
@@ -1858,83 +1596,21 @@ function setReceiver() {
// header: true,
// ansi: true
// }));
const fields = getProtobufRawBytes(currentPtr, x2)
const sender = fields[0]
const receiver = fields[1]
const content = fields[2]
const mediaContent = fields[3]
const xml = fields[4]
const userContent = fields[5]
const msgId = protobufVarintToNumberString(fields[6])
if (typeof sender !== "string" || sender === "" || typeof receiver !== "string" || receiver === "" ||
typeof content !== "string" || content === "" || typeof msgId !== "string" || msgId === "") {
// 过滤非聊天消息:
// 1. field 2 是 varint (tag=0x10) 而非嵌套 message (tag=0x12) → 同步/通知消息
// 2. field 2 wrapper长度 < 128 (单字节varint) → wrapper内容过小,非聊天消息
if (currentPtr.add(2).readU8() === 0x10 || currentPtr.add(2).readU8() === 0x16 || (currentPtr.add(3).readU8() & 0x80) === 0) {
return;
}
var selfId = receiver
var msgType = "private"
var groupId = ""
var senderUser = sender
var senderNickname = ""
var messages = getMessages(content, sender, mediaContent);
if (sender.includes("@chatroom")) {
msgType = "group"
groupId = sender
let splitIndex = content.indexOf(':')
const sendUserStart = content.indexOf('wxid_')
senderUser = content.substring(sendUserStart, splitIndex).trim();
const atUserMatch = xml.match(/<atuserlist>([\s\S]*?)<\/atuserlist>/);
const atUser = atUserMatch ? atUserMatch[1] : null;
if (atUser) {
atUser.split(',').forEach(atUser => {
atUser = atUser.trim();
if (atUser) {
messages.push({type: "at", data: {qq: atUser}});
}
});
}
// 处理用户的名称
splitIndex = userContent?.indexOf(':')
if (splitIndex === -1) {
splitIndex = userContent?.indexOf('在群聊中@了你') !== -1 ? userContent?.indexOf('在群聊中@了你') : userContent?.indexOf('在群聊中发了一段语')
senderNickname = userContent?.substring(0, splitIndex).trim();
} else {
senderNickname = userContent?.substring(0, splitIndex).trim();
}
if (!senderNickname) {
senderNickname = senderUser
}
} else {
// 处理用户的名称
const splitIndex = userContent?.indexOf(':')
senderNickname = userContent?.substring(0, splitIndex).trim();
if (!senderNickname) {
senderNickname = senderUser
}
}
const mem = currentPtr.readByteArray(x2);
if (!mem) return;
const uint8Array = new Uint8Array(mem);
send({
time: Date.now(),
post_type: "message",
message_type: msgType,
user_id: senderUser, // 发送人的 ID
self_id: selfId, // 接收人的 ID
group_id: groupId, // 群 ID
message_id: msgId,
type: "send",
raw: {peerUid: msgId},
message: messages,
sender: {user_id: senderUser, nickname: senderNickname},
msgsource: xml,
raw_message: content,
show_content: userContent
type: "protobuf_msg",
data: Array.from(uint8Array),
})
},
});
@@ -2179,75 +1855,4 @@ function triggerDownload(receiver, cdnUrl, aesKey, filePath, fileType) {
return startDwMedia(downloadGlobalX0, downloadFileX1);
}
function getMessages(content, sender, mediaContent) {
var messages = [];
if (sender.includes("@chatroom")) {
let splitIndex = content.indexOf(':')
let pureContent = content.substring(splitIndex + 1).trim();
const parts = pureContent.split('\u2005');
for (let part of parts) {
part = part.trim();
if (part.startsWith("<?xml version=\"1.0\"?><msg><img")) {
messages.push({type: "image", data: {text: part}});
} else if (part.startsWith("<msg><voicemsg")) {
messages.push({type: "record", data: {text: part}});
} else if (part.startsWith("<?xml version=\"1.0\"?><msg><appmsg")) {
const regex = /<type>(.*?)<\/type>/s;
const match = part.match(regex);
if (match.length > 1) {
switch (match[1]) {
case "5":
messages.push({type: "share", data: {text: part}});
break
case "6":
messages.push({type: "file", data: {text: part}});
break
}
}
} else if (part.startsWith("<msg><emoji")) {
messages.push({type: "face", data: {text: part}});
} else if (part.startsWith("<?xml version=\"1.0\"?><msg><videomsg")) {
messages.push({type: "video", data: {text: part}});
} else if (part.startsWith("<sysmsg") || part.startsWith("<?xml version=\"1.0\"?><sysmsg")) {
messages.push({type: "sys", data: {text: part}});
} else {
messages.push({type: "text", data: {text: part}});
}
}
} else {
if (content.startsWith("<?xml version=\"1.0\"?><msg><img")) {
messages.push({type: "image", data: {text: content}});
} else if (content.startsWith("<msg><voicemsg")) {
const audioStart = mediaContent.indexOf(2);
if (audioStart !== -1) {
mediaContent = mediaContent.subarray(audioStart);
}
messages.push({type: "record", data: {text: content, media: Array.from(mediaContent)}});
} else if (content.startsWith("<?xml version=\"1.0\"?><msg><appmsg")) {
const regex = /<type>(.*?)<\/type>/s;
const match = content.match(regex);
if (match.length > 1) {
switch (match[1]) {
case "5":
messages.push({type: "share", data: {text: content}});
break
case "6":
messages.push({type: "file", data: {text: content}});
break
}
}
} else if (content.startsWith("<msg><emoji")) {
messages.push({type: "face", data: {text: content}});
} else if (content.startsWith("<?xml version=\"1.0\"?><msg><videomsg")) {
messages.push({type: "video", data: {text: content}});
} else if (content.startsWith("<sysmsg") || content.startsWith("<?xml version=\"1.0\"?><sysmsg")) {
messages.push({type: "sys", data: {text: content}});
} else {
messages.push({type: "text", data: {text: content}});
}
}
return messages;
}
// -------------------------接收消息分区-------------------------