diff --git a/onebot/go.mod b/onebot/go.mod index e0f5f77..68760b7 100644 --- a/onebot/go.mod +++ b/onebot/go.mod @@ -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 diff --git a/onebot/go.sum b/onebot/go.sum index 2d5f176..9bce971 100644 --- a/onebot/go.sum +++ b/onebot/go.sum @@ -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= diff --git a/onebot/main.go b/onebot/main.go index 91b14b6..7d1acdc 100644 --- a/onebot/main.go +++ b/onebot/main.go @@ -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) diff --git a/onebot/proto/wxproto/wx_msg.pb.go b/onebot/proto/wxproto/wx_msg.pb.go new file mode 100644 index 0000000..61f0e30 --- /dev/null +++ b/onebot/proto/wxproto/wx_msg.pb.go @@ -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 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 +} diff --git a/onebot/proto/wxproto/wx_msg.proto b/onebot/proto/wxproto/wx_msg.proto new file mode 100644 index 0000000..d79fb61 --- /dev/null +++ b/onebot/proto/wxproto/wx_msg.proto @@ -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; +} diff --git a/onebot/protobuf_handler.go b/onebot/protobuf_handler.go new file mode 100644 index 0000000..506f614 --- /dev/null +++ b/onebot/protobuf_handler.go @@ -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(`([\s\S]*?)`).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, "(.*?)`) + 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, " { - 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(/([\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("(.*?)<\/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("(.*?)<\/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("