diff --git a/onebot/img_proto_builder.go b/onebot/img_proto_builder.go new file mode 100644 index 0000000..d2b8f0f --- /dev/null +++ b/onebot/img_proto_builder.go @@ -0,0 +1,110 @@ +package main + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + mathrand "math/rand" + "strconv" + "time" + + "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto" + "google.golang.org/protobuf/encoding/protowire" + "google.golang.org/protobuf/proto" +) + +// BuildImgMsgProto 构建发送图片消息的protobuf并返回hex编码的字符串 +func BuildImgMsgProto(sender, targetId, cdnKey, aesKey, md5Key string) (string, error) { + // 生成16字节随机client_proof (模拟JS的generateBytes(16)) + clientProof := make([]byte, 16) + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + randBytes := make([]byte, 16) + rand.Read(randBytes) + for i := range clientProof { + clientProof[i] = chars[int(randBytes[i])%len(chars)] + } + + // 生成5字节varint范围的msgId (2^28 ~ 2^35-1) + msgId := mathrand.Int63n(1<<35-1<<28) + (1 << 28) + + // header.unknown4 的值: 从 [0xAF, 0xAC, 0x90, 0x93, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01] 解码 + // = -228321745 (signed int64) + const headerUnknown4 int64 = -228321745 + + // client_msg_id: targetId_timestamp_160_xwechat_3 + timestamp := time.Now().Unix() + clientMsgId := targetId + "_" + strconv.FormatInt(timestamp, 10) + "_160_xwechat_3" + + // xml内容 + xmlContent := []byte("1") + + msg := &wxproto.WxSendImgMsg{ + Header: &wxproto.ImgMsgHeader{ + Flag: []byte{0x00}, + MsgId: msgId, + ClientProof: clientProof, + Unknown4: headerUnknown4, + SysInfo: []byte("UnifiedPCMac 26 arm64"), + Unknown6: 304, + }, + ClientMsgId: &wxproto.WxString{Value: clientMsgId}, + Sender: &wxproto.WxString{Value: sender}, + Receiver: &wxproto.WxString{Value: targetId}, + Unknown5: 1524, + // MsgType: 0, // proto3不会序列化0值,需要手动追加 + Unknown7: 1524, + // Unknown8: 手动追加 [0x42, 0x04, 0x08, 0x00, 0x12, 0x00] + Unknown9: 3, + Xml: xmlContent, + Unknown11: 1, + Unknown12: 2, + // Unknown13: 0, // 需要手动追加 + CdnKey: []byte(cdnKey), + CdnKey2: []byte(cdnKey), + AesKey: []byte(aesKey), + Unknown18: 1, + Unknown19: 2559, + Unknown20: 2559, + CdnKey3: []byte(cdnKey), + Unknown22: 1524, + Unknown23: 104, + Unknown24: 58, + AesKey2: []byte(aesKey), + Md5Key: []byte(md5Key), + Unknown28: 779219929, + // Unknown30: 0, // 需要手动追加 + // Unknown36: 0, // 需要手动追加 + // Unknown41: 0, // 需要手动追加 + } + + data, err := proto.Marshal(msg) + if err != nil { + return "", fmt.Errorf("marshal img proto failed: %w", err) + } + + // 手动追加proto3不会序列化的0值字段 + // field 6 (msg_type=0): tag=0x30, value=0x00 + data = appendZeroVarintField(data, 6) + // field 8 (ImgMsgExtra with field1=0, field2=""): [0x42, 0x04, 0x08, 0x00, 0x12, 0x00] + data = append(data, 0x42, 0x04, 0x08, 0x00, 0x12, 0x00) + // field 13 (unknown13=0): tag=0x68, value=0x00 + data = appendZeroVarintField(data, 13) + // field 30 (unknown30=0): tag=[0xF0, 0x01], value=0x00 + data = appendZeroVarintField(data, 30) + // field 36 (unknown36=0): tag=[0xA0, 0x02], value=0x00 + data = appendZeroVarintField(data, 36) + // field 41 (unknown41=0): tag=[0xC8, 0x02], value=0x00 + data = appendZeroVarintField(data, 41) + + // 追加尾部的 \x00 字节 + data = append(data, 0x00) + + return hex.EncodeToString(data), nil +} + +// appendZeroVarintField 追加一个值为0的varint字段 +func appendZeroVarintField(data []byte, fieldNum protowire.Number) []byte { + data = protowire.AppendTag(data, fieldNum, protowire.VarintType) + data = protowire.AppendVarint(data, 0) + return data +} diff --git a/onebot/param.go b/onebot/param.go index ef4f79a..f750479 100644 --- a/onebot/param.go +++ b/onebot/param.go @@ -22,6 +22,7 @@ var ( userID2NicknameMap sync.Map userID2FileMsgMap sync.Map + videoDurationMap sync.Map // targetId -> int32(duration seconds) ) type WechatMessage struct { @@ -57,9 +58,10 @@ type SendMsg struct { FilePath string FileType int - CdnKey string - Md5Key string - VideoId string + CdnKey string + Md5Key string + VideoId string + Duration int32 } // SendRequest 请求结构体 diff --git a/onebot/proto/wxproto/img_msg.pb.go b/onebot/proto/wxproto/img_msg.pb.go new file mode 100644 index 0000000..f0d4cdf --- /dev/null +++ b/onebot/proto/wxproto/img_msg.pb.go @@ -0,0 +1,536 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.5.1 +// source: proto/wxproto/img_msg.proto + +package wxproto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// 发送图片消息的protobuf结构 +type WxSendImgMsg struct { + state protoimpl.MessageState `protogen:"open.v1"` + Header *ImgMsgHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` + ClientMsgId *WxString `protobuf:"bytes,2,opt,name=client_msg_id,json=clientMsgId,proto3" json:"client_msg_id,omitempty"` + Sender *WxString `protobuf:"bytes,3,opt,name=sender,proto3" json:"sender,omitempty"` + Receiver *WxString `protobuf:"bytes,4,opt,name=receiver,proto3" json:"receiver,omitempty"` + Unknown5 int32 `protobuf:"varint,5,opt,name=unknown5,proto3" json:"unknown5,omitempty"` + MsgType int32 `protobuf:"varint,6,opt,name=msg_type,json=msgType,proto3" json:"msg_type,omitempty"` + Unknown7 int32 `protobuf:"varint,7,opt,name=unknown7,proto3" json:"unknown7,omitempty"` + Unknown8 *ImgMsgExtra `protobuf:"bytes,8,opt,name=unknown8,proto3" json:"unknown8,omitempty"` + Unknown9 int32 `protobuf:"varint,9,opt,name=unknown9,proto3" json:"unknown9,omitempty"` + Xml []byte `protobuf:"bytes,10,opt,name=xml,proto3" json:"xml,omitempty"` + Unknown11 int32 `protobuf:"varint,11,opt,name=unknown11,proto3" json:"unknown11,omitempty"` + Unknown12 int32 `protobuf:"varint,12,opt,name=unknown12,proto3" json:"unknown12,omitempty"` + Unknown13 int32 `protobuf:"varint,13,opt,name=unknown13,proto3" json:"unknown13,omitempty"` + // field 14 skipped + CdnKey []byte `protobuf:"bytes,15,opt,name=cdn_key,json=cdnKey,proto3" json:"cdn_key,omitempty"` + CdnKey2 []byte `protobuf:"bytes,16,opt,name=cdn_key2,json=cdnKey2,proto3" json:"cdn_key2,omitempty"` + AesKey []byte `protobuf:"bytes,17,opt,name=aes_key,json=aesKey,proto3" json:"aes_key,omitempty"` + Unknown18 int32 `protobuf:"varint,18,opt,name=unknown18,proto3" json:"unknown18,omitempty"` + Unknown19 int32 `protobuf:"varint,19,opt,name=unknown19,proto3" json:"unknown19,omitempty"` + Unknown20 int32 `protobuf:"varint,20,opt,name=unknown20,proto3" json:"unknown20,omitempty"` + CdnKey3 []byte `protobuf:"bytes,21,opt,name=cdn_key3,json=cdnKey3,proto3" json:"cdn_key3,omitempty"` + Unknown22 int32 `protobuf:"varint,22,opt,name=unknown22,proto3" json:"unknown22,omitempty"` + Unknown23 int32 `protobuf:"varint,23,opt,name=unknown23,proto3" json:"unknown23,omitempty"` + Unknown24 int32 `protobuf:"varint,24,opt,name=unknown24,proto3" json:"unknown24,omitempty"` + AesKey2 []byte `protobuf:"bytes,25,opt,name=aes_key2,json=aesKey2,proto3" json:"aes_key2,omitempty"` + // field 26 skipped + Md5Key []byte `protobuf:"bytes,27,opt,name=md5_key,json=md5Key,proto3" json:"md5_key,omitempty"` + Unknown28 int64 `protobuf:"varint,28,opt,name=unknown28,proto3" json:"unknown28,omitempty"` + // field 29 skipped + Unknown30 int32 `protobuf:"varint,30,opt,name=unknown30,proto3" json:"unknown30,omitempty"` + // fields 31-35 skipped + Unknown36 int32 `protobuf:"varint,36,opt,name=unknown36,proto3" json:"unknown36,omitempty"` + // fields 37-40 skipped + Unknown41 int32 `protobuf:"varint,41,opt,name=unknown41,proto3" json:"unknown41,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WxSendImgMsg) Reset() { + *x = WxSendImgMsg{} + mi := &file_proto_wxproto_img_msg_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WxSendImgMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WxSendImgMsg) ProtoMessage() {} + +func (x *WxSendImgMsg) ProtoReflect() protoreflect.Message { + mi := &file_proto_wxproto_img_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 WxSendImgMsg.ProtoReflect.Descriptor instead. +func (*WxSendImgMsg) Descriptor() ([]byte, []int) { + return file_proto_wxproto_img_msg_proto_rawDescGZIP(), []int{0} +} + +func (x *WxSendImgMsg) GetHeader() *ImgMsgHeader { + if x != nil { + return x.Header + } + return nil +} + +func (x *WxSendImgMsg) GetClientMsgId() *WxString { + if x != nil { + return x.ClientMsgId + } + return nil +} + +func (x *WxSendImgMsg) GetSender() *WxString { + if x != nil { + return x.Sender + } + return nil +} + +func (x *WxSendImgMsg) GetReceiver() *WxString { + if x != nil { + return x.Receiver + } + return nil +} + +func (x *WxSendImgMsg) GetUnknown5() int32 { + if x != nil { + return x.Unknown5 + } + return 0 +} + +func (x *WxSendImgMsg) GetMsgType() int32 { + if x != nil { + return x.MsgType + } + return 0 +} + +func (x *WxSendImgMsg) GetUnknown7() int32 { + if x != nil { + return x.Unknown7 + } + return 0 +} + +func (x *WxSendImgMsg) GetUnknown8() *ImgMsgExtra { + if x != nil { + return x.Unknown8 + } + return nil +} + +func (x *WxSendImgMsg) GetUnknown9() int32 { + if x != nil { + return x.Unknown9 + } + return 0 +} + +func (x *WxSendImgMsg) GetXml() []byte { + if x != nil { + return x.Xml + } + return nil +} + +func (x *WxSendImgMsg) GetUnknown11() int32 { + if x != nil { + return x.Unknown11 + } + return 0 +} + +func (x *WxSendImgMsg) GetUnknown12() int32 { + if x != nil { + return x.Unknown12 + } + return 0 +} + +func (x *WxSendImgMsg) GetUnknown13() int32 { + if x != nil { + return x.Unknown13 + } + return 0 +} + +func (x *WxSendImgMsg) GetCdnKey() []byte { + if x != nil { + return x.CdnKey + } + return nil +} + +func (x *WxSendImgMsg) GetCdnKey2() []byte { + if x != nil { + return x.CdnKey2 + } + return nil +} + +func (x *WxSendImgMsg) GetAesKey() []byte { + if x != nil { + return x.AesKey + } + return nil +} + +func (x *WxSendImgMsg) GetUnknown18() int32 { + if x != nil { + return x.Unknown18 + } + return 0 +} + +func (x *WxSendImgMsg) GetUnknown19() int32 { + if x != nil { + return x.Unknown19 + } + return 0 +} + +func (x *WxSendImgMsg) GetUnknown20() int32 { + if x != nil { + return x.Unknown20 + } + return 0 +} + +func (x *WxSendImgMsg) GetCdnKey3() []byte { + if x != nil { + return x.CdnKey3 + } + return nil +} + +func (x *WxSendImgMsg) GetUnknown22() int32 { + if x != nil { + return x.Unknown22 + } + return 0 +} + +func (x *WxSendImgMsg) GetUnknown23() int32 { + if x != nil { + return x.Unknown23 + } + return 0 +} + +func (x *WxSendImgMsg) GetUnknown24() int32 { + if x != nil { + return x.Unknown24 + } + return 0 +} + +func (x *WxSendImgMsg) GetAesKey2() []byte { + if x != nil { + return x.AesKey2 + } + return nil +} + +func (x *WxSendImgMsg) GetMd5Key() []byte { + if x != nil { + return x.Md5Key + } + return nil +} + +func (x *WxSendImgMsg) GetUnknown28() int64 { + if x != nil { + return x.Unknown28 + } + return 0 +} + +func (x *WxSendImgMsg) GetUnknown30() int32 { + if x != nil { + return x.Unknown30 + } + return 0 +} + +func (x *WxSendImgMsg) GetUnknown36() int32 { + if x != nil { + return x.Unknown36 + } + return 0 +} + +func (x *WxSendImgMsg) GetUnknown41() int32 { + if x != nil { + return x.Unknown41 + } + return 0 +} + +type ImgMsgHeader struct { + state protoimpl.MessageState `protogen:"open.v1"` + Flag []byte `protobuf:"bytes,1,opt,name=flag,proto3" json:"flag,omitempty"` + MsgId int64 `protobuf:"varint,2,opt,name=msg_id,json=msgId,proto3" json:"msg_id,omitempty"` + ClientProof []byte `protobuf:"bytes,3,opt,name=client_proof,json=clientProof,proto3" json:"client_proof,omitempty"` + Unknown4 int64 `protobuf:"varint,4,opt,name=unknown4,proto3" json:"unknown4,omitempty"` + SysInfo []byte `protobuf:"bytes,5,opt,name=sys_info,json=sysInfo,proto3" json:"sys_info,omitempty"` + Unknown6 int32 `protobuf:"varint,6,opt,name=unknown6,proto3" json:"unknown6,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ImgMsgHeader) Reset() { + *x = ImgMsgHeader{} + mi := &file_proto_wxproto_img_msg_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ImgMsgHeader) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImgMsgHeader) ProtoMessage() {} + +func (x *ImgMsgHeader) ProtoReflect() protoreflect.Message { + mi := &file_proto_wxproto_img_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 ImgMsgHeader.ProtoReflect.Descriptor instead. +func (*ImgMsgHeader) Descriptor() ([]byte, []int) { + return file_proto_wxproto_img_msg_proto_rawDescGZIP(), []int{1} +} + +func (x *ImgMsgHeader) GetFlag() []byte { + if x != nil { + return x.Flag + } + return nil +} + +func (x *ImgMsgHeader) GetMsgId() int64 { + if x != nil { + return x.MsgId + } + return 0 +} + +func (x *ImgMsgHeader) GetClientProof() []byte { + if x != nil { + return x.ClientProof + } + return nil +} + +func (x *ImgMsgHeader) GetUnknown4() int64 { + if x != nil { + return x.Unknown4 + } + return 0 +} + +func (x *ImgMsgHeader) GetSysInfo() []byte { + if x != nil { + return x.SysInfo + } + return nil +} + +func (x *ImgMsgHeader) GetUnknown6() int32 { + if x != nil { + return x.Unknown6 + } + return 0 +} + +type ImgMsgExtra struct { + state protoimpl.MessageState `protogen:"open.v1"` + Field1 int32 `protobuf:"varint,1,opt,name=field1,proto3" json:"field1,omitempty"` + Field2 []byte `protobuf:"bytes,2,opt,name=field2,proto3" json:"field2,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ImgMsgExtra) Reset() { + *x = ImgMsgExtra{} + mi := &file_proto_wxproto_img_msg_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ImgMsgExtra) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImgMsgExtra) ProtoMessage() {} + +func (x *ImgMsgExtra) ProtoReflect() protoreflect.Message { + mi := &file_proto_wxproto_img_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 ImgMsgExtra.ProtoReflect.Descriptor instead. +func (*ImgMsgExtra) Descriptor() ([]byte, []int) { + return file_proto_wxproto_img_msg_proto_rawDescGZIP(), []int{2} +} + +func (x *ImgMsgExtra) GetField1() int32 { + if x != nil { + return x.Field1 + } + return 0 +} + +func (x *ImgMsgExtra) GetField2() []byte { + if x != nil { + return x.Field2 + } + return nil +} + +var File_proto_wxproto_img_msg_proto protoreflect.FileDescriptor + +const file_proto_wxproto_img_msg_proto_rawDesc = "" + + "\n" + + "\x1bproto/wxproto/img_msg.proto\x12\awxproto\x1a\x1aproto/wxproto/wx_msg.proto\"\xa3\a\n" + + "\fWxSendImgMsg\x12-\n" + + "\x06header\x18\x01 \x01(\v2\x15.wxproto.ImgMsgHeaderR\x06header\x125\n" + + "\rclient_msg_id\x18\x02 \x01(\v2\x11.wxproto.WxStringR\vclientMsgId\x12)\n" + + "\x06sender\x18\x03 \x01(\v2\x11.wxproto.WxStringR\x06sender\x12-\n" + + "\breceiver\x18\x04 \x01(\v2\x11.wxproto.WxStringR\breceiver\x12\x1a\n" + + "\bunknown5\x18\x05 \x01(\x05R\bunknown5\x12\x19\n" + + "\bmsg_type\x18\x06 \x01(\x05R\amsgType\x12\x1a\n" + + "\bunknown7\x18\a \x01(\x05R\bunknown7\x120\n" + + "\bunknown8\x18\b \x01(\v2\x14.wxproto.ImgMsgExtraR\bunknown8\x12\x1a\n" + + "\bunknown9\x18\t \x01(\x05R\bunknown9\x12\x10\n" + + "\x03xml\x18\n" + + " \x01(\fR\x03xml\x12\x1c\n" + + "\tunknown11\x18\v \x01(\x05R\tunknown11\x12\x1c\n" + + "\tunknown12\x18\f \x01(\x05R\tunknown12\x12\x1c\n" + + "\tunknown13\x18\r \x01(\x05R\tunknown13\x12\x17\n" + + "\acdn_key\x18\x0f \x01(\fR\x06cdnKey\x12\x19\n" + + "\bcdn_key2\x18\x10 \x01(\fR\acdnKey2\x12\x17\n" + + "\aaes_key\x18\x11 \x01(\fR\x06aesKey\x12\x1c\n" + + "\tunknown18\x18\x12 \x01(\x05R\tunknown18\x12\x1c\n" + + "\tunknown19\x18\x13 \x01(\x05R\tunknown19\x12\x1c\n" + + "\tunknown20\x18\x14 \x01(\x05R\tunknown20\x12\x19\n" + + "\bcdn_key3\x18\x15 \x01(\fR\acdnKey3\x12\x1c\n" + + "\tunknown22\x18\x16 \x01(\x05R\tunknown22\x12\x1c\n" + + "\tunknown23\x18\x17 \x01(\x05R\tunknown23\x12\x1c\n" + + "\tunknown24\x18\x18 \x01(\x05R\tunknown24\x12\x19\n" + + "\baes_key2\x18\x19 \x01(\fR\aaesKey2\x12\x17\n" + + "\amd5_key\x18\x1b \x01(\fR\x06md5Key\x12\x1c\n" + + "\tunknown28\x18\x1c \x01(\x03R\tunknown28\x12\x1c\n" + + "\tunknown30\x18\x1e \x01(\x05R\tunknown30\x12\x1c\n" + + "\tunknown36\x18$ \x01(\x05R\tunknown36\x12\x1c\n" + + "\tunknown41\x18) \x01(\x05R\tunknown41\"\xaf\x01\n" + + "\fImgMsgHeader\x12\x12\n" + + "\x04flag\x18\x01 \x01(\fR\x04flag\x12\x15\n" + + "\x06msg_id\x18\x02 \x01(\x03R\x05msgId\x12!\n" + + "\fclient_proof\x18\x03 \x01(\fR\vclientProof\x12\x1a\n" + + "\bunknown4\x18\x04 \x01(\x03R\bunknown4\x12\x19\n" + + "\bsys_info\x18\x05 \x01(\fR\asysInfo\x12\x1a\n" + + "\bunknown6\x18\x06 \x01(\x05R\bunknown6\"=\n" + + "\vImgMsgExtra\x12\x16\n" + + "\x06field1\x18\x01 \x01(\x05R\x06field1\x12\x16\n" + + "\x06field2\x18\x02 \x01(\fR\x06field2B>Z wxproto.ImgMsgHeader + 3, // 1: wxproto.WxSendImgMsg.client_msg_id:type_name -> wxproto.WxString + 3, // 2: wxproto.WxSendImgMsg.sender:type_name -> wxproto.WxString + 3, // 3: wxproto.WxSendImgMsg.receiver:type_name -> wxproto.WxString + 2, // 4: wxproto.WxSendImgMsg.unknown8:type_name -> wxproto.ImgMsgExtra + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_proto_wxproto_img_msg_proto_init() } +func file_proto_wxproto_img_msg_proto_init() { + if File_proto_wxproto_img_msg_proto != nil { + return + } + file_proto_wxproto_wx_msg_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wxproto_img_msg_proto_rawDesc), len(file_proto_wxproto_img_msg_proto_rawDesc)), + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proto_wxproto_img_msg_proto_goTypes, + DependencyIndexes: file_proto_wxproto_img_msg_proto_depIdxs, + MessageInfos: file_proto_wxproto_img_msg_proto_msgTypes, + }.Build() + File_proto_wxproto_img_msg_proto = out.File + file_proto_wxproto_img_msg_proto_goTypes = nil + file_proto_wxproto_img_msg_proto_depIdxs = nil +} diff --git a/onebot/proto/wxproto/img_msg.proto b/onebot/proto/wxproto/img_msg.proto new file mode 100644 index 0000000..ab8fa13 --- /dev/null +++ b/onebot/proto/wxproto/img_msg.proto @@ -0,0 +1,59 @@ +syntax = "proto3"; + +package wxproto; + +option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto"; + +import "proto/wxproto/wx_msg.proto"; + +// 发送图片消息的protobuf结构 +message WxSendImgMsg { + ImgMsgHeader header = 1; + WxString client_msg_id = 2; + WxString sender = 3; + WxString receiver = 4; + int32 unknown5 = 5; + int32 msg_type = 6; + int32 unknown7 = 7; + ImgMsgExtra unknown8 = 8; + int32 unknown9 = 9; + bytes xml = 10; + int32 unknown11 = 11; + int32 unknown12 = 12; + int32 unknown13 = 13; + // field 14 skipped + bytes cdn_key = 15; + bytes cdn_key2 = 16; + bytes aes_key = 17; + int32 unknown18 = 18; + int32 unknown19 = 19; + int32 unknown20 = 20; + bytes cdn_key3 = 21; + int32 unknown22 = 22; + int32 unknown23 = 23; + int32 unknown24 = 24; + bytes aes_key2 = 25; + // field 26 skipped + bytes md5_key = 27; + int64 unknown28 = 28; + // field 29 skipped + int32 unknown30 = 30; + // fields 31-35 skipped + int32 unknown36 = 36; + // fields 37-40 skipped + int32 unknown41 = 41; +} + +message ImgMsgHeader { + bytes flag = 1; + int64 msg_id = 2; + bytes client_proof = 3; + int64 unknown4 = 4; + bytes sys_info = 5; + int32 unknown6 = 6; +} + +message ImgMsgExtra { + int32 field1 = 1; + bytes field2 = 2; +} diff --git a/onebot/proto/wxproto/video_msg.pb.go b/onebot/proto/wxproto/video_msg.pb.go new file mode 100644 index 0000000..ec15bc4 --- /dev/null +++ b/onebot/proto/wxproto/video_msg.pb.go @@ -0,0 +1,539 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.5.1 +// source: proto/wxproto/video_msg.proto + +package wxproto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// 发送视频消息的protobuf结构 +type WxSendVideoMsg struct { + state protoimpl.MessageState `protogen:"open.v1"` + Header *VideoMsgHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` + ClientMsgId []byte `protobuf:"bytes,2,opt,name=client_msg_id,json=clientMsgId,proto3" json:"client_msg_id,omitempty"` + Sender []byte `protobuf:"bytes,3,opt,name=sender,proto3" json:"sender,omitempty"` + Receiver []byte `protobuf:"bytes,4,opt,name=receiver,proto3" json:"receiver,omitempty"` + Unknown5 int32 `protobuf:"varint,5,opt,name=unknown5,proto3" json:"unknown5,omitempty"` + Unknown6 int32 `protobuf:"varint,6,opt,name=unknown6,proto3" json:"unknown6,omitempty"` + Unknown7 *VideoMsgExtra `protobuf:"bytes,7,opt,name=unknown7,proto3" json:"unknown7,omitempty"` + Unknown8 int32 `protobuf:"varint,8,opt,name=unknown8,proto3" json:"unknown8,omitempty"` + Unknown9 int32 `protobuf:"varint,9,opt,name=unknown9,proto3" json:"unknown9,omitempty"` + Unknown10 *VideoMsgExtra `protobuf:"bytes,10,opt,name=unknown10,proto3" json:"unknown10,omitempty"` + Duration int32 `protobuf:"varint,11,opt,name=duration,proto3" json:"duration,omitempty"` + Unknown12 int32 `protobuf:"varint,12,opt,name=unknown12,proto3" json:"unknown12,omitempty"` + Unknown13 int32 `protobuf:"varint,13,opt,name=unknown13,proto3" json:"unknown13,omitempty"` + Unknown14 int32 `protobuf:"varint,14,opt,name=unknown14,proto3" json:"unknown14,omitempty"` + Xml []byte `protobuf:"bytes,15,opt,name=xml,proto3" json:"xml,omitempty"` + CdnKey []byte `protobuf:"bytes,16,opt,name=cdn_key,json=cdnKey,proto3" json:"cdn_key,omitempty"` + AesKey []byte `protobuf:"bytes,17,opt,name=aes_key,json=aesKey,proto3" json:"aes_key,omitempty"` + Unknown18 int32 `protobuf:"varint,18,opt,name=unknown18,proto3" json:"unknown18,omitempty"` + CdnKey2 []byte `protobuf:"bytes,19,opt,name=cdn_key2,json=cdnKey2,proto3" json:"cdn_key2,omitempty"` + Unknown20 int32 `protobuf:"varint,20,opt,name=unknown20,proto3" json:"unknown20,omitempty"` + Unknown21 int32 `protobuf:"varint,21,opt,name=unknown21,proto3" json:"unknown21,omitempty"` + Unknown22 int32 `protobuf:"varint,22,opt,name=unknown22,proto3" json:"unknown22,omitempty"` + AesKey2 []byte `protobuf:"bytes,23,opt,name=aes_key2,json=aesKey2,proto3" json:"aes_key2,omitempty"` + // fields 24-25 skipped + Md5Key []byte `protobuf:"bytes,26,opt,name=md5_key,json=md5Key,proto3" json:"md5_key,omitempty"` + // fields 27-36 skipped + VideoId []byte `protobuf:"bytes,37,opt,name=video_id,json=videoId,proto3" json:"video_id,omitempty"` + Unknown38 int32 `protobuf:"varint,38,opt,name=unknown38,proto3" json:"unknown38,omitempty"` + // fields 39-47 skipped + Md5Key2 []byte `protobuf:"bytes,48,opt,name=md5_key2,json=md5Key2,proto3" json:"md5_key2,omitempty"` + CdnKey3 []byte `protobuf:"bytes,49,opt,name=cdn_key3,json=cdnKey3,proto3" json:"cdn_key3,omitempty"` + AesKey3 []byte `protobuf:"bytes,50,opt,name=aes_key3,json=aesKey3,proto3" json:"aes_key3,omitempty"` + Unknown51 int32 `protobuf:"varint,51,opt,name=unknown51,proto3" json:"unknown51,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WxSendVideoMsg) Reset() { + *x = WxSendVideoMsg{} + mi := &file_proto_wxproto_video_msg_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WxSendVideoMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WxSendVideoMsg) ProtoMessage() {} + +func (x *WxSendVideoMsg) ProtoReflect() protoreflect.Message { + mi := &file_proto_wxproto_video_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 WxSendVideoMsg.ProtoReflect.Descriptor instead. +func (*WxSendVideoMsg) Descriptor() ([]byte, []int) { + return file_proto_wxproto_video_msg_proto_rawDescGZIP(), []int{0} +} + +func (x *WxSendVideoMsg) GetHeader() *VideoMsgHeader { + if x != nil { + return x.Header + } + return nil +} + +func (x *WxSendVideoMsg) GetClientMsgId() []byte { + if x != nil { + return x.ClientMsgId + } + return nil +} + +func (x *WxSendVideoMsg) GetSender() []byte { + if x != nil { + return x.Sender + } + return nil +} + +func (x *WxSendVideoMsg) GetReceiver() []byte { + if x != nil { + return x.Receiver + } + return nil +} + +func (x *WxSendVideoMsg) GetUnknown5() int32 { + if x != nil { + return x.Unknown5 + } + return 0 +} + +func (x *WxSendVideoMsg) GetUnknown6() int32 { + if x != nil { + return x.Unknown6 + } + return 0 +} + +func (x *WxSendVideoMsg) GetUnknown7() *VideoMsgExtra { + if x != nil { + return x.Unknown7 + } + return nil +} + +func (x *WxSendVideoMsg) GetUnknown8() int32 { + if x != nil { + return x.Unknown8 + } + return 0 +} + +func (x *WxSendVideoMsg) GetUnknown9() int32 { + if x != nil { + return x.Unknown9 + } + return 0 +} + +func (x *WxSendVideoMsg) GetUnknown10() *VideoMsgExtra { + if x != nil { + return x.Unknown10 + } + return nil +} + +func (x *WxSendVideoMsg) GetDuration() int32 { + if x != nil { + return x.Duration + } + return 0 +} + +func (x *WxSendVideoMsg) GetUnknown12() int32 { + if x != nil { + return x.Unknown12 + } + return 0 +} + +func (x *WxSendVideoMsg) GetUnknown13() int32 { + if x != nil { + return x.Unknown13 + } + return 0 +} + +func (x *WxSendVideoMsg) GetUnknown14() int32 { + if x != nil { + return x.Unknown14 + } + return 0 +} + +func (x *WxSendVideoMsg) GetXml() []byte { + if x != nil { + return x.Xml + } + return nil +} + +func (x *WxSendVideoMsg) GetCdnKey() []byte { + if x != nil { + return x.CdnKey + } + return nil +} + +func (x *WxSendVideoMsg) GetAesKey() []byte { + if x != nil { + return x.AesKey + } + return nil +} + +func (x *WxSendVideoMsg) GetUnknown18() int32 { + if x != nil { + return x.Unknown18 + } + return 0 +} + +func (x *WxSendVideoMsg) GetCdnKey2() []byte { + if x != nil { + return x.CdnKey2 + } + return nil +} + +func (x *WxSendVideoMsg) GetUnknown20() int32 { + if x != nil { + return x.Unknown20 + } + return 0 +} + +func (x *WxSendVideoMsg) GetUnknown21() int32 { + if x != nil { + return x.Unknown21 + } + return 0 +} + +func (x *WxSendVideoMsg) GetUnknown22() int32 { + if x != nil { + return x.Unknown22 + } + return 0 +} + +func (x *WxSendVideoMsg) GetAesKey2() []byte { + if x != nil { + return x.AesKey2 + } + return nil +} + +func (x *WxSendVideoMsg) GetMd5Key() []byte { + if x != nil { + return x.Md5Key + } + return nil +} + +func (x *WxSendVideoMsg) GetVideoId() []byte { + if x != nil { + return x.VideoId + } + return nil +} + +func (x *WxSendVideoMsg) GetUnknown38() int32 { + if x != nil { + return x.Unknown38 + } + return 0 +} + +func (x *WxSendVideoMsg) GetMd5Key2() []byte { + if x != nil { + return x.Md5Key2 + } + return nil +} + +func (x *WxSendVideoMsg) GetCdnKey3() []byte { + if x != nil { + return x.CdnKey3 + } + return nil +} + +func (x *WxSendVideoMsg) GetAesKey3() []byte { + if x != nil { + return x.AesKey3 + } + return nil +} + +func (x *WxSendVideoMsg) GetUnknown51() int32 { + if x != nil { + return x.Unknown51 + } + return 0 +} + +type VideoMsgHeader struct { + state protoimpl.MessageState `protogen:"open.v1"` + Flag []byte `protobuf:"bytes,1,opt,name=flag,proto3" json:"flag,omitempty"` + MsgId int64 `protobuf:"varint,2,opt,name=msg_id,json=msgId,proto3" json:"msg_id,omitempty"` + ClientProof []byte `protobuf:"bytes,3,opt,name=client_proof,json=clientProof,proto3" json:"client_proof,omitempty"` + Unknown4 int64 `protobuf:"varint,4,opt,name=unknown4,proto3" json:"unknown4,omitempty"` + SysInfo []byte `protobuf:"bytes,5,opt,name=sys_info,json=sysInfo,proto3" json:"sys_info,omitempty"` + Unknown6 int32 `protobuf:"varint,6,opt,name=unknown6,proto3" json:"unknown6,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VideoMsgHeader) Reset() { + *x = VideoMsgHeader{} + mi := &file_proto_wxproto_video_msg_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VideoMsgHeader) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VideoMsgHeader) ProtoMessage() {} + +func (x *VideoMsgHeader) ProtoReflect() protoreflect.Message { + mi := &file_proto_wxproto_video_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 VideoMsgHeader.ProtoReflect.Descriptor instead. +func (*VideoMsgHeader) Descriptor() ([]byte, []int) { + return file_proto_wxproto_video_msg_proto_rawDescGZIP(), []int{1} +} + +func (x *VideoMsgHeader) GetFlag() []byte { + if x != nil { + return x.Flag + } + return nil +} + +func (x *VideoMsgHeader) GetMsgId() int64 { + if x != nil { + return x.MsgId + } + return 0 +} + +func (x *VideoMsgHeader) GetClientProof() []byte { + if x != nil { + return x.ClientProof + } + return nil +} + +func (x *VideoMsgHeader) GetUnknown4() int64 { + if x != nil { + return x.Unknown4 + } + return 0 +} + +func (x *VideoMsgHeader) GetSysInfo() []byte { + if x != nil { + return x.SysInfo + } + return nil +} + +func (x *VideoMsgHeader) GetUnknown6() int32 { + if x != nil { + return x.Unknown6 + } + return 0 +} + +type VideoMsgExtra struct { + state protoimpl.MessageState `protogen:"open.v1"` + Field1 int32 `protobuf:"varint,1,opt,name=field1,proto3" json:"field1,omitempty"` + Field2 []byte `protobuf:"bytes,2,opt,name=field2,proto3" json:"field2,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VideoMsgExtra) Reset() { + *x = VideoMsgExtra{} + mi := &file_proto_wxproto_video_msg_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VideoMsgExtra) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VideoMsgExtra) ProtoMessage() {} + +func (x *VideoMsgExtra) ProtoReflect() protoreflect.Message { + mi := &file_proto_wxproto_video_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 VideoMsgExtra.ProtoReflect.Descriptor instead. +func (*VideoMsgExtra) Descriptor() ([]byte, []int) { + return file_proto_wxproto_video_msg_proto_rawDescGZIP(), []int{2} +} + +func (x *VideoMsgExtra) GetField1() int32 { + if x != nil { + return x.Field1 + } + return 0 +} + +func (x *VideoMsgExtra) GetField2() []byte { + if x != nil { + return x.Field2 + } + return nil +} + +var File_proto_wxproto_video_msg_proto protoreflect.FileDescriptor + +const file_proto_wxproto_video_msg_proto_rawDesc = "" + + "\n" + + "\x1dproto/wxproto/video_msg.proto\x12\awxproto\"\x9c\a\n" + + "\x0eWxSendVideoMsg\x12/\n" + + "\x06header\x18\x01 \x01(\v2\x17.wxproto.VideoMsgHeaderR\x06header\x12\"\n" + + "\rclient_msg_id\x18\x02 \x01(\fR\vclientMsgId\x12\x16\n" + + "\x06sender\x18\x03 \x01(\fR\x06sender\x12\x1a\n" + + "\breceiver\x18\x04 \x01(\fR\breceiver\x12\x1a\n" + + "\bunknown5\x18\x05 \x01(\x05R\bunknown5\x12\x1a\n" + + "\bunknown6\x18\x06 \x01(\x05R\bunknown6\x122\n" + + "\bunknown7\x18\a \x01(\v2\x16.wxproto.VideoMsgExtraR\bunknown7\x12\x1a\n" + + "\bunknown8\x18\b \x01(\x05R\bunknown8\x12\x1a\n" + + "\bunknown9\x18\t \x01(\x05R\bunknown9\x124\n" + + "\tunknown10\x18\n" + + " \x01(\v2\x16.wxproto.VideoMsgExtraR\tunknown10\x12\x1a\n" + + "\bduration\x18\v \x01(\x05R\bduration\x12\x1c\n" + + "\tunknown12\x18\f \x01(\x05R\tunknown12\x12\x1c\n" + + "\tunknown13\x18\r \x01(\x05R\tunknown13\x12\x1c\n" + + "\tunknown14\x18\x0e \x01(\x05R\tunknown14\x12\x10\n" + + "\x03xml\x18\x0f \x01(\fR\x03xml\x12\x17\n" + + "\acdn_key\x18\x10 \x01(\fR\x06cdnKey\x12\x17\n" + + "\aaes_key\x18\x11 \x01(\fR\x06aesKey\x12\x1c\n" + + "\tunknown18\x18\x12 \x01(\x05R\tunknown18\x12\x19\n" + + "\bcdn_key2\x18\x13 \x01(\fR\acdnKey2\x12\x1c\n" + + "\tunknown20\x18\x14 \x01(\x05R\tunknown20\x12\x1c\n" + + "\tunknown21\x18\x15 \x01(\x05R\tunknown21\x12\x1c\n" + + "\tunknown22\x18\x16 \x01(\x05R\tunknown22\x12\x19\n" + + "\baes_key2\x18\x17 \x01(\fR\aaesKey2\x12\x17\n" + + "\amd5_key\x18\x1a \x01(\fR\x06md5Key\x12\x19\n" + + "\bvideo_id\x18% \x01(\fR\avideoId\x12\x1c\n" + + "\tunknown38\x18& \x01(\x05R\tunknown38\x12\x19\n" + + "\bmd5_key2\x180 \x01(\fR\amd5Key2\x12\x19\n" + + "\bcdn_key3\x181 \x01(\fR\acdnKey3\x12\x19\n" + + "\baes_key3\x182 \x01(\fR\aaesKey3\x12\x1c\n" + + "\tunknown51\x183 \x01(\x05R\tunknown51\"\xb1\x01\n" + + "\x0eVideoMsgHeader\x12\x12\n" + + "\x04flag\x18\x01 \x01(\fR\x04flag\x12\x15\n" + + "\x06msg_id\x18\x02 \x01(\x03R\x05msgId\x12!\n" + + "\fclient_proof\x18\x03 \x01(\fR\vclientProof\x12\x1a\n" + + "\bunknown4\x18\x04 \x01(\x03R\bunknown4\x12\x19\n" + + "\bsys_info\x18\x05 \x01(\fR\asysInfo\x12\x1a\n" + + "\bunknown6\x18\x06 \x01(\x05R\bunknown6\"?\n" + + "\rVideoMsgExtra\x12\x16\n" + + "\x06field1\x18\x01 \x01(\x05R\x06field1\x12\x16\n" + + "\x06field2\x18\x02 \x01(\fR\x06field2B>Z wxproto.VideoMsgHeader + 2, // 1: wxproto.WxSendVideoMsg.unknown7:type_name -> wxproto.VideoMsgExtra + 2, // 2: wxproto.WxSendVideoMsg.unknown10:type_name -> wxproto.VideoMsgExtra + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_proto_wxproto_video_msg_proto_init() } +func file_proto_wxproto_video_msg_proto_init() { + if File_proto_wxproto_video_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_video_msg_proto_rawDesc), len(file_proto_wxproto_video_msg_proto_rawDesc)), + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proto_wxproto_video_msg_proto_goTypes, + DependencyIndexes: file_proto_wxproto_video_msg_proto_depIdxs, + MessageInfos: file_proto_wxproto_video_msg_proto_msgTypes, + }.Build() + File_proto_wxproto_video_msg_proto = out.File + file_proto_wxproto_video_msg_proto_goTypes = nil + file_proto_wxproto_video_msg_proto_depIdxs = nil +} diff --git a/onebot/proto/wxproto/video_msg.proto b/onebot/proto/wxproto/video_msg.proto new file mode 100644 index 0000000..fb0edfe --- /dev/null +++ b/onebot/proto/wxproto/video_msg.proto @@ -0,0 +1,56 @@ +syntax = "proto3"; + +package wxproto; + +option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto"; + +// 发送视频消息的protobuf结构 +message WxSendVideoMsg { + VideoMsgHeader header = 1; + bytes client_msg_id = 2; + bytes sender = 3; + bytes receiver = 4; + int32 unknown5 = 5; + int32 unknown6 = 6; + VideoMsgExtra unknown7 = 7; + int32 unknown8 = 8; + int32 unknown9 = 9; + VideoMsgExtra unknown10 = 10; + int32 duration = 11; + int32 unknown12 = 12; + int32 unknown13 = 13; + int32 unknown14 = 14; + bytes xml = 15; + bytes cdn_key = 16; + bytes aes_key = 17; + int32 unknown18 = 18; + bytes cdn_key2 = 19; + int32 unknown20 = 20; + int32 unknown21 = 21; + int32 unknown22 = 22; + bytes aes_key2 = 23; + // fields 24-25 skipped + bytes md5_key = 26; + // fields 27-36 skipped + bytes video_id = 37; + int32 unknown38 = 38; + // fields 39-47 skipped + bytes md5_key2 = 48; + bytes cdn_key3 = 49; + bytes aes_key3 = 50; + int32 unknown51 = 51; +} + +message VideoMsgHeader { + bytes flag = 1; + int64 msg_id = 2; + bytes client_proof = 3; + int64 unknown4 = 4; + bytes sys_info = 5; + int32 unknown6 = 6; +} + +message VideoMsgExtra { + int32 field1 = 1; + bytes field2 = 2; +} diff --git a/onebot/script.js b/onebot/script.js index 4edcb36..0a92731 100644 --- a/onebot/script.js +++ b/onebot/script.js @@ -14,63 +14,6 @@ function hexToByteArray(hexStr) { return bytes; } -function toVarint(n) { - let res = []; - while (n >= 128) { - res.push((n & 0x7F) | 0x80); // 取后7位,最高位置1 - n = n >> 7; // 右移7位 - } - res.push(n); // 最后一位最高位为0 - return res; -} - - -function stringToHexArray(str) { - var utf8Str = unescape(encodeURIComponent(str)); - var arr = []; - for (var i = 0; i < utf8Str.length; i++) { - arr.push(utf8Str.charCodeAt(i)); // 获取字符的 ASCII 码 (即十六进制值) - } - return arr; -} - - -function generateRandom5ByteVarint() { - let res = []; - - // 前 4 个字节:最高位(bit 7)必须是 1,低 7 位随机 - for (let i = 0; i < 4; i++) { - let random7Bit = Math.floor(Math.random() * 128); - res.push(random7Bit | 0x80); // 强制设置最高位为 1 - } - - // 第 5 个字节:最高位必须是 0,为了确保不变成 4 字节,低 7 位不能全为 0 - let lastByte = Math.floor(Math.random() * 127) + 1; - res.push(lastByte & 0x7F); // 确保最高位为 0 - - return res; -} - - -// 辅助函数:Protobuf Varint 编码 (对应 get_varint_timestamp_bytes) -function getVarintTimestampBytes() { - let ts = Math.floor(Date.now() / 1000); - let encodedBytes = []; - let tempTs = ts >>> 0; // 强制转为 32位 无符号整数 - - while (true) { - let byte = tempTs & 0x7F; - tempTs >>>= 7; - if (tempTs !== 0) { - encodedBytes.push(byte | 0x80); - } else { - encodedBytes.push(byte); - break; - } - } - return encodedBytes; -} - function patchString(addr, plainStr) { const bytes = []; for (let i = 0; i < plainStr.length; i++) { @@ -90,19 +33,6 @@ function generateAESKey() { return key; } - -function generateBytes(n) { - // 生成随机字符串 - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let result = ''; - - for (let i = 0; i < n; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)); - } - - return stringToHexArray(result); -} - // -------------------------基础函数分区------------------------- // -------------------------全局变量分区------------------------- @@ -206,18 +136,12 @@ var senderGlobal = "wxid_" var lastSendTime = 0; var atUserGlobal = ""; -// 图片/视频发送cdn信息全局变量 (从Go直接传入) -var imgCdnKeyGlobal = ""; -var imgAesKeyGlobal = ""; -var imgMd5KeyGlobal = ""; -var videoCdnKeyGlobal = ""; -var videoAesKeyGlobal = ""; -var videoMd5KeyGlobal = ""; -var videoIdGlobal = ""; // 文本消息protobuf全局变量 (从Go直接传入hex编码) var textProtoHexGlobal = ""; - -const fileCp = generateBytes(16) +// 图片消息protobuf全局变量 (从Go直接传入hex编码) +var imgProtoHexGlobal = ""; +// 视频消息protobuf全局变量 (从Go直接传入hex编码) +var videoProtoHexGlobal = ""; // -------------------------全局变量分区------------------------- @@ -532,7 +456,7 @@ function setupSendImgMessageDynamic() { uploadAesKeyAddr = Memory.alloc(256); ImagePathAddr1 = Memory.alloc(256); uploadImageX1 = Memory.alloc(1024); - imgProtoX1PayloadAddr = Memory.alloc(1024); + imgProtoX1PayloadAddr = Memory.alloc(2048); // 图片数据写入 patchString(imgCgiAddr, "/cgi-bin/micromsg-bin/uploadmsgimg"); @@ -688,7 +612,7 @@ function patchVideoProtoBuf() { setImmediate(patchVideoProtoBuf); -function triggerSendImgMessage(taskId, sender, receiver, cdnKey, aesKey, md5Key) { +function triggerSendImgMessage(taskId, sender, receiver, protoHex) { if (!taskId || !receiver || !sender) { console.error("[!] taskId or receiver or sender is empty!"); return "fail"; @@ -699,10 +623,8 @@ function triggerSendImgMessage(taskId, sender, receiver, cdnKey, aesKey, md5Key) return "fail"; } - // 保存cdn信息供attachProto使用 - imgCdnKeyGlobal = cdnKey; - imgAesKeyGlobal = aesKey; - imgMd5KeyGlobal = md5Key; + // 保存protobuf hex供attachProto使用 + imgProtoHexGlobal = protoHex; // 获取当前时间戳 (秒) const timestamp = Math.floor(Date.now() / 1000); @@ -787,7 +709,7 @@ function triggerSendImgMessage(taskId, sender, receiver, cdnKey, aesKey, md5Key) } } -function triggerSendVideoMessage(taskId, sender, receiver, cdnKey, aesKey, md5Key, videoId) { +function triggerSendVideoMessage(taskId, sender, receiver, protoHex) { if (!taskId || !receiver || !sender) { console.error("[!] taskId or receiver or sender is empty!"); return "fail"; @@ -798,11 +720,8 @@ function triggerSendVideoMessage(taskId, sender, receiver, cdnKey, aesKey, md5Ke return "fail"; } - // 保存cdn信息供attachProto使用 - videoCdnKeyGlobal = cdnKey; - videoAesKeyGlobal = aesKey; - videoMd5KeyGlobal = md5Key; - videoIdGlobal = videoId; + // 保存protobuf hex供attachProto使用 + videoProtoHexGlobal = protoHex; // 获取当前时间戳 (秒) const timestamp = Math.floor(Date.now() / 1000); @@ -898,104 +817,16 @@ function attachProto() { return; } - // 使用从Go传入的全局cdn信息 - let cdnKey = imgCdnKeyGlobal; - let aesKey = imgAesKeyGlobal; - let md5Key = imgMd5KeyGlobal; - let targetId = receiverGlobal; - - if (!cdnKey || !aesKey || !md5Key) { - console.error("[!] 无法获取图片上传信息") - return + if (!imgProtoHexGlobal || imgProtoHexGlobal.length === 0) { + console.error("[!] imgProtoHexGlobal 为空"); + return; } - const type = [0x0A, 0x40, 0x0A, 0x01, 0x00] - const msgId = [0x10].concat(generateRandom5ByteVarint()) - const cpHeader = [0x1A, 0x10] - - const randomId = [0x20, 0xAF, 0xAC, 0x90, 0x93, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01] - const sysHeader = [0x2A, 0x15] - // UnifiedPCMac 26 arm64 - const sys = [0x55, 0x6E, 0x69, 0x66, 0x69, 0x65, 0x64, 0x50, 0x43, 0x4D, 0x61, 0x63, 0x20, 0x32, 0x36, 0x20, 0x61, 0x72, 0x6D, 0x36, 0x34, 0x30] - - // 45872025384@chatroom_176787000_60_xwechat_1 只需要改这个时间戳就能重复发送 - const receiverMsgId = stringToHexArray(targetId).concat([0x5F]) - .concat(stringToHexArray(Math.floor(Date.now() / 1000).toString())) - .concat([0x5F, 0x31, 0x36, 0x30, 0x5F, 0x78, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x5F, 0x33]); - - // 0xb0, 0x02 是长度,需要看一下什么的长度 - const msgIdHeader = [0xb0, 0x02, 0x12, receiverMsgId.length + 2, 0x0A, receiverMsgId.length] - - const senderHeader = [0x1A, senderGlobal.length + 2, 0x0A, senderGlobal.length]; - // wxid_xxxx 或者 chatroom - const sender = stringToHexArray(senderGlobal); - const receiverHeader = [0x22, targetId.length + 2, 0x0A, targetId.length] - // wxid_xxxx - const receiver = stringToHexArray(targetId) - const randomId1 = [0x28, 0xF4, 0x0B] - const type1 = [0x30, 0x00] - const randomId2 = [0x38, 0xF4, 0x0B] - const randomId3 = [0x42, 0x04, 0x08, 0x00, 0x12, 0x00] - const randomId4 = [0x48, 0x03] - const htmlHeader = [0x52, 0x32]; - - const html = [0x3C, - 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, // 0x30 msgsour - 0x63, 0x65, 0x3E, 0x3C, 0x61, 0x6C, 0x6E, 0x6F, // 0x38 ce>1 - 0x3C, 0x2F, 0x66, 0x72, 0x3E, 0x3C, 0x2F, 0x61, // 0x48 - ]; - - const cdnHeader = [0x58, 0x01, 0x60, 0x02, 0x68, 0x00, 0x7A, 0xB2, 0x01] - // 3057 开头的cdn key - const cdn = stringToHexArray(cdnKey); - - const cdn2Header = [0x82, 0x01, 0xB2, 0x01] - const cdn2 = stringToHexArray(cdnKey) - - const aesKeyHeader = [0x8A, 0x01, 0x20] - const aesKeyBytes = stringToHexArray(aesKey) - - const randomId5 = [0x90, 0x01, 0x01, 0x98, 0x01, 0xFF, // 0x2C8 - 0x13, 0xA0, 0x01, 0xFF, 0x13] - - const cdn3Header = [0xAA, 0x01, 0xB2, 0x01] - const cdn3 = stringToHexArray(cdnKey) - - const randomId6 = [0xB0, 0x01, 0xF4, 0x0B] - const randomId7 = [0xB8, 0x01, 0x68] - const randomId8 = [0xC0, 0x01, 0x3A] - const aesKey1Header = [0xCA, 0x01, 0x20] - const aesKey1 = stringToHexArray(aesKey) - const md5Header = [0xDA, 0x01, 0x20] - const me5Key = stringToHexArray(md5Key) - - const randomId9 = [0xE0, 0x01, 0xd9, 0xe7, 0xc7, 0xF3, 0x02] - - var left0 = [ - 0xF0, 0x01, 0x00, 0xA0, 0x02, 0x00, // 0x3E0 - 0xC8, 0x02, 0x00, 0x00 // 0x3E8 - ] - - const finalPayload = type.concat(msgId, cpHeader, fileCp, randomId, sysHeader, sys, msgIdHeader, receiverMsgId, - senderHeader, sender, receiverHeader, receiver, randomId1, type1, randomId2, randomId3, randomId4, htmlHeader, html, - cdnHeader, cdn, cdn2Header, cdn2, aesKeyHeader, aesKeyBytes, randomId5, cdn3Header, cdn3, randomId6, randomId7, randomId8, - aesKey1Header, aesKey1, md5Header, me5Key, randomId9, left0) - + const finalPayload = hexToByteArray(imgProtoHexGlobal); imgProtoX1PayloadAddr.writeByteArray(finalPayload); this.context.x1 = imgProtoX1PayloadAddr; this.context.x2 = ptr(finalPayload.length); - - // console.log("[+] 图片 寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(imgProtoX1PayloadAddr, { - // offset: 0, - // length: 256, - // header: true, - // ansi: true - // })); }, }); @@ -1007,109 +838,16 @@ function attachProto() { return; } - // 使用从Go传入的全局cdn信息 - let cdnKey = videoCdnKeyGlobal; - let aesKey = videoAesKeyGlobal; - let md5Key = videoMd5KeyGlobal; - let videoId = videoIdGlobal; - let targetId = receiverGlobal; - - if (!cdnKey || !aesKey || !md5Key) { - console.error("[!] 无法获取视频上传信息"); + if (!videoProtoHexGlobal || videoProtoHexGlobal.length === 0) { + console.error("[!] videoProtoHexGlobal 为空"); return; } - const type = [0x0A, 0x3f, 0x0A, 0x01, 0x00] - const msgId = [0x10].concat(generateRandom5ByteVarint()) - const cpHeader = [0x1A, 0x10] - - const randomId = [0x20, 0xAF, 0xAC, 0x90, 0x93, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01] - const sysHeader = [0x2A, 0x15] - // UnifiedPCMac 26 arm64 - const sys = [0x55, 0x6E, 0x69, 0x66, 0x69, 0x65, 0x64, 0x50, 0x43, 0x4D, 0x61, 0x63, 0x20, 0x32, 0x36, 0x20, 0x61, 0x72, 0x6D, 0x36, 0x34] - - // 注意:这里 sender 和 receiver 互换了 - const receiverMsgId = stringToHexArray(targetId).concat([0x5F]) - .concat(stringToHexArray(Math.floor(Date.now() / 1000).toString())) - .concat([0x5F, 0x31, 0x36, 0x30, 0x5F, 0x78, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x5F, 0x31]); - - // 0x81, 0x01 是 tag,0x12, 0x2b 是长度=43 - const msgIdHeader = [0x30, 0x76, 0x12, receiverMsgId.length] - - const senderHeader = [0x1A, senderGlobal.length]; - // sender 和 receiver 互换了,sender 是 wxid_ldftuhe36izg19 - const sender = stringToHexArray(senderGlobal); - const receiverHeader = [0x22, targetId.length] - // receiver 是 wxid_7wd1ece99f7i21 - const receiver = stringToHexArray(targetId) - - const randomId1 = [0x28, 0xac, 0x73, 0x30, 0xac, 0x73, 0x3a, 0x04, 0x08, 0x00, 0x12, 0x00] - const type1 = [0x40, 0xe8, 0xf2, 0x6f] - const randomId2 = [0x48, 0xe8, 0xf2, 0x6f] - const randomId3 = [0x52, 0x04, 0x08, 0x00, 0x12, 0x00] - const randomId4 = [0x58, 0x0d, 0x60, 0x01, 0x68, 0x02, 0x70, 0x00] - const htmlHeader = [0x7a, 0x3c]; - - const html = [0x3C, 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, 0x63, 0x65, - 0x3E, 0x3C, 0x61, 0x6C, 0x6E, 0x6F, 0x64, 0x65, 0x3E, 0x3C, 0x66, 0x72, - 0x3E, 0x31, 0x3C, 0x2F, 0x66, 0x72, 0x3E, 0x3C, 0x63, 0x66, 0x3E, 0x33, - 0x3C, 0x2F, 0x63, 0x66, 0x3E, 0x3C, 0x2F, 0x61, 0x6C, 0x6E, 0x6F, 0x64, - 0x65, 0x3E, 0x3C, 0x2F, 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, 0x63, - 0x65, 0x3E] - - const cdnHeader = [0x82, 0x01, ...toVarint(cdnKey.length)] - // 3057 开头的cdn key - const cdn = stringToHexArray(cdnKey); - - const aesKeyHeader = [0x8A, 0x01, 0x20] - const aesKeyBytes = stringToHexArray(aesKey) - - const randomId5 = [0x90, 0x01, 0x01, 0x9A, 0x01, ...toVarint(cdnKey.length)] - - const cdn2 = stringToHexArray(cdnKey) - - const randomId6 = [0xA0, 0x01, 0xAC, 0x73, 0xA8, 0x01, 0xE8, 0x02, 0xB0, 0x01, 0xCB, 0x01] - - const aesKey1Header = [0xBA, 0x01, 0x20] - const aesKey1 = stringToHexArray(aesKey) - const md5Header = [0xd2, 0x01, 0x20] - const md5KeyBytes = stringToHexArray(md5Key) - - const md5Header1 = [0xAA, 0x02, 0x20] - const md5Key1 = stringToHexArray(videoId) - - const randomId7 = [0xB0, 0x02, 0x00] - - const md5Key2Header = [0x82, 0x03, 0x20] - const md5Key2 = stringToHexArray(md5Key) - - const cdn3Header = [0x8A, 0x03, ...toVarint(cdnKey.length)] - const cdn3 = stringToHexArray(cdnKey) - - const randomId8 = [0x92, 0x03, 0x20] - - const md5Key3 = stringToHexArray(aesKey) - - var left0 = [ - 0x98, 0x03, 0xe8, 0xf2, 0x6f - ] - - const finalPayload = type.concat(msgId, cpHeader, fileCp, randomId, sysHeader, sys, msgIdHeader, receiverMsgId, - senderHeader, sender, receiverHeader, receiver, randomId1, type1, randomId2, randomId3, randomId4, htmlHeader, html, - cdnHeader, cdn, aesKeyHeader, aesKeyBytes, randomId5, cdn2, randomId6, aesKey1Header, aesKey1, md5Header, md5KeyBytes, md5Header1, - md5Key1, randomId7, md5Key2Header, md5Key2, cdn3Header, cdn3, randomId8, md5Key3, left0) - + const finalPayload = hexToByteArray(videoProtoHexGlobal); videoProtoX1PayloadAddr.writeByteArray(finalPayload); this.context.x1 = videoProtoX1PayloadAddr; this.context.x2 = ptr(finalPayload.length); - - // console.log("[+] 视频寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(videoProtoX1PayloadAddr, { - // offset: 0, - // length: finalPayload.length, - // header: true, - // ansi: true - // })); }, }); } diff --git a/onebot/utils.go b/onebot/utils.go index 03d1f61..1644022 100644 --- a/onebot/utils.go +++ b/onebot/utils.go @@ -61,6 +61,29 @@ func SaveBase64Image(base64Data string) (string, string, error) { return targetPath, md5Str, nil } +// GetVideoDuration 使用ffprobe获取视频时长(秒) +func GetVideoDuration(filePath string) (int32, error) { + cmd := exec.Command("ffprobe", + "-v", "error", + "-show_entries", "format=duration", + "-of", "default=noprint_wrappers=1:nokey=1", + filePath, + ) + var out bytes.Buffer + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + return 0, fmt.Errorf("ffprobe error: %v", err) + } + + durationStr := strings.TrimSpace(out.String()) + durationFloat, err := strconv.ParseFloat(durationStr, 64) + if err != nil { + return 0, fmt.Errorf("parse duration failed: %v", err) + } + + return int32(durationFloat), nil +} + func GetFileMD5(filePath string) (string, error) { file, err := os.Open(filePath) if err != nil { diff --git a/onebot/video_proto_builder.go b/onebot/video_proto_builder.go new file mode 100644 index 0000000..2a2de0e --- /dev/null +++ b/onebot/video_proto_builder.go @@ -0,0 +1,97 @@ +package main + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + mathrand "math/rand" + "strconv" + "time" + + "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto" + "google.golang.org/protobuf/proto" +) + +// BuildVideoMsgProto 构建发送视频消息的protobuf并返回hex编码的字符串 +func BuildVideoMsgProto(sender, targetId, cdnKey, aesKey, md5Key, videoId string, duration int32) (string, error) { + // 生成16字节随机client_proof + clientProof := make([]byte, 16) + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + randBytes := make([]byte, 16) + rand.Read(randBytes) + for i := range clientProof { + clientProof[i] = chars[int(randBytes[i])%len(chars)] + } + + // 生成5字节varint范围的msgId (2^28 ~ 2^35-1) + msgId := mathrand.Int63n(1<<35-1<<28) + (1 << 28) + + // header.unknown4: 同图片proto + const headerUnknown4 int64 = -228321745 + + // client_msg_id: targetId_timestamp_160_xwechat_1 + timestamp := time.Now().Unix() + clientMsgId := targetId + "_" + strconv.FormatInt(timestamp, 10) + "_160_xwechat_1" + + // xml内容 (视频多了3) + xmlContent := []byte("13") + + msg := &wxproto.WxSendVideoMsg{ + Header: &wxproto.VideoMsgHeader{ + Flag: []byte{0x00}, + MsgId: msgId, + ClientProof: clientProof, + Unknown4: headerUnknown4, + SysInfo: []byte("UnifiedPCMac 26 arm64"), + Unknown6: 118, + }, + ClientMsgId: []byte(clientMsgId), + Sender: []byte(sender), + Receiver: []byte(targetId), + Unknown5: 14764, + Unknown6: 14764, + Unknown7: &wxproto.VideoMsgExtra{ + Field1: 0, + Field2: []byte{}, + }, + Unknown8: 1833320, + Unknown9: 1833320, + Unknown10: &wxproto.VideoMsgExtra{ + Field1: 0, + Field2: []byte{}, + }, + Duration: duration, + Unknown12: 1, + Unknown13: 2, + // Unknown14: 0, // 需要手动追加 + Xml: xmlContent, + CdnKey: []byte(cdnKey), + AesKey: []byte(aesKey), + Unknown18: 1, + CdnKey2: []byte(cdnKey), + Unknown20: 14764, + Unknown21: 360, + Unknown22: 203, + AesKey2: []byte(aesKey), + Md5Key: []byte(md5Key), + VideoId: []byte(videoId), + // Unknown38: 0, // 需要手动追加 + Md5Key2: []byte(md5Key), + CdnKey3: []byte(cdnKey), + AesKey3: []byte(aesKey), + Unknown51: 1833320, + } + + data, err := proto.Marshal(msg) + if err != nil { + return "", fmt.Errorf("marshal video proto failed: %w", err) + } + + // 手动追加proto3不会序列化的0值字段 + // field 14 (unknown14=0) + data = appendZeroVarintField(data, 14) + // field 38 (unknown38=0) + data = appendZeroVarintField(data, 38) + + return hex.EncodeToString(data), nil +} diff --git a/onebot/worker.go b/onebot/worker.go index d07aaef..ac92430 100644 --- a/onebot/worker.go +++ b/onebot/worker.go @@ -78,7 +78,12 @@ func SendWechatMsg(m *SendMsg) { return } case "send_image": - result := fridaScript.ExportsCall("triggerSendImgMessage", currTaskId, myWechatId, targetId, m.CdnKey, m.AesKey, m.Md5Key) + protoHex, err := BuildImgMsgProto(myWechatId, targetId, m.CdnKey, m.AesKey, m.Md5Key) + if err != nil { + Error("构建图片protobuf失败", "err", err) + return + } + result := fridaScript.ExportsCall("triggerSendImgMessage", currTaskId, myWechatId, targetId, protoHex) Info("📩 发送图片任务执行结果", "result", result, "task_id", currTaskId, "wechat_id", myWechatId, "target_id", targetId) if result != "1" { Error("上传图片失败", "task_id", currTaskId, "target_id", targetId, "result", result) @@ -90,18 +95,35 @@ func SendWechatMsg(m *SendMsg) { Error("保存图片失败", "err", err) return } - + + // 获取视频时长 + duration, err := GetVideoDuration(targetPath) + if err != nil { + Error("获取视频时长失败", "err", err) + } else { + videoDurationMap.Store(targetId, duration) + } + result := fridaScript.ExportsCall("triggerUploadVideo", targetId, md5Str, targetPath) - Info("📩 上传视频任务执行结果", "result", result, "target_id", targetId, "md5", md5Str, "path", targetPath) + Info("📩 上传视频任务执行结果", "result", result, "target_id", targetId, "md5", md5Str, "path", targetPath, "duration", duration) if result != "0" { Error("上传视频失败", "target_id", targetId, "md5", md5Str, "result", result) return } case "send_video": - result := fridaScript.ExportsCall("triggerSendVideoMessage", currTaskId, myWechatId, targetId, m.CdnKey, m.AesKey, m.Md5Key, m.VideoId) - Info("📩 发送视频任务执行结果", "result", result, "task_id", currTaskId, "wechat_id", myWechatId, "target_id", targetId) + var duration int32 + if d, ok := videoDurationMap.LoadAndDelete(targetId); ok { + duration = d.(int32) + } + protoHex, err := BuildVideoMsgProto(myWechatId, targetId, m.CdnKey, m.AesKey, m.Md5Key, m.VideoId, duration) + if err != nil { + Error("构建视频protobuf失败", "err", err) + return + } + result := fridaScript.ExportsCall("triggerSendVideoMessage", currTaskId, myWechatId, targetId, protoHex) + Info("📩 发送视频任务执行结果", "result", result, "task_id", currTaskId, "wechat_id", myWechatId, "target_id", targetId, "duration", duration) if result != "1" { - Error("上传图片失败", "task_id", currTaskId, "target_id", targetId, "result", result) + Error("发送视频失败", "task_id", currTaskId, "target_id", targetId, "result", result) return } case "download":