mirror of
https://github.com/yincongcyincong/wechat_chatter.git
synced 2026-07-15 10:26:52 +08:00
optimize receive
This commit is contained in:
@@ -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,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=
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
// -------------------------接收消息分区-------------------------
|
||||
|
||||
Reference in New Issue
Block a user