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
- 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, // 0x58 msgsour
- 0x63, 0x65, 0x3E // 0x60 ce>
- ];
-
- 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":