add file reply voice

This commit is contained in:
yincong
2026-07-09 09:42:32 +08:00
parent a5c5ed3445
commit fb716fffb3
17 changed files with 2860 additions and 9 deletions
+258
View File
@@ -0,0 +1,258 @@
package main
import (
"encoding/hex"
"fmt"
"math/rand"
"time"
"google.golang.org/protobuf/proto"
wxproto "github.com/yincongcyincong/weixin-macos/onebot/proto"
)
// BuildFileUploadMsgProto 构建 /cgi-bin/micromsg-bin/sendfileuploadmsg 请求的protobuf
func BuildFileUploadMsgProto(targetId string, fileInfo *FileInfo) (string, error) {
// 复用上传时生成的 clientMsgId(去掉 _1 后缀),保证与 upload body 里的 filekey 一致
clientMsgId := fileInfo.ClientMsgId
if clientMsgId == "" {
now := time.Now().Unix()
clientMsgId = fmt.Sprintf("%s_%d_%d", targetId, now, rand.Intn(1000))
fileInfo.ClientMsgId = clientMsgId
}
version := NextVersion()
msgType := uint32(1)
field7 := uint32(0)
field12 := uint32(0)
fileSize := uint32(fileInfo.TotalLen)
msg := &wxproto.SendFileUploadMsgRequest{
BaseRequest: &wxproto.ReplyMsgHeader{
Flag: []byte{0x00},
SessionId: &globalSessionId,
ClientProof: globalClientProof,
DeviceId: &globalDeviceId,
Platform: proto.String("UnifiedPCMac 26 arm64"),
Version: &version,
},
ToUserName: &targetId,
ClientMsgId: &clientMsgId,
MsgType: &msgType,
FileInfo: &wxproto.FileUploadFileInfo{
FileName: &fileInfo.FileName,
FileExt: &fileInfo.FileExt,
FileMd5: &fileInfo.Md5,
FileSize: &fileSize,
},
Field7: &field7,
Field12: &field12,
}
data, err := proto.Marshal(msg)
if err != nil {
return "", fmt.Errorf("marshal file upload msg proto failed: %w", err)
}
fmt.Printf("[file-upload-proto] final protobuf hex dump:\n%s\n", HexDump(data, 0))
return hex.EncodeToString(data), nil
}
// ParseFileUploadMsgResponse 解析 sendfileuploadmsg 响应,提取 fileUploadToken 和 msgSvrId
func ParseFileUploadMsgResponse(data []byte) (string, string, error) {
resp := &wxproto.SendFileUploadMsgResponse{}
err := proto.Unmarshal(data, resp)
if err != nil {
return "", "", fmt.Errorf("unmarshal file upload response failed: %w", err)
}
if resp.BaseResponse != nil && resp.BaseResponse.Ret != nil && *resp.BaseResponse.Ret != 0 {
errMsg := ""
if resp.BaseResponse.ErrMsg != nil {
errMsg = resp.BaseResponse.ErrMsg.GetMsg()
}
return "", "", fmt.Errorf("sendfileuploadmsg failed, ret=%d, errMsg=%s", *resp.BaseResponse.Ret, errMsg)
}
token := resp.GetFileUploadToken()
msgSvrId := fmt.Sprintf("%d", resp.GetMsgSvrId())
return token, msgSvrId, nil
}
// FileInfo 文件发送消息需要的信息
type FileInfo struct {
FileName string // 文件名 (title)
TotalLen int64 // 文件大小
AttachId string // 附件ID (来自upload完成回调)
FileExt string // 文件扩展名
CdnAttachURL string // CDN附件URL
AesKey string // AES密钥
Md5 string // 文件MD5
OverwriteMsgId string // overwrite_newmsgid
FileUploadToken string // fileuploadtoken
ClientMsgId string // 由 BuildFileUploadMsgProto 生成,BuildFileMsgProto 复用
}
// BuildFileMsgProto 构建发送文件消息的protobuf并返回hex编码的字符串
// 复用reply_msg.proto (WxSendReplyMsg)msg_type=6
func BuildFileMsgProto(sender, receiver string, fileInfo *FileInfo) (string, error) {
now := time.Now().Unix()
// 构建文件appmsg XML
appmsgXml := buildFileAppmsgXml(sender, fileInfo)
// 构建客户端消息ID: 复用 BuildFileUploadMsgProto 的 clientMsgId + 后缀
clientMsgId := fileInfo.ClientMsgId + "_xwechat_1"
// msgsource
msgsource := "<msgsource><alnode><fr>1</fr><cf>2</cf></alnode></msgsource>"
// proto2 需要使用指针
var (
unknown2 = []byte("wx6618f1cfc6c132f8") // appid
unknown3 = int32(0)
msgType = int32(6) // 文件消息类型
flag = int32(1)
unknown13 = []byte{}
unknown14 = []byte{}
unknown15 = []byte{}
version = NextVersion()
)
msg := &wxproto.WxSendReplyMsg{
Header: &wxproto.ReplyMsgHeader{
Flag: []byte{0x00},
SessionId: &globalSessionId,
ClientProof: globalClientProof,
DeviceId: &globalDeviceId,
Platform: proto.String("UnifiedPCMac 26 arm64"),
Version: &version,
},
Body: &wxproto.ReplyMsgBody{
Sender: &sender,
Unknown2: unknown2,
Unknown3: &unknown3,
Receiver: &receiver,
MsgType: &msgType,
Content: []byte(appmsgXml),
SendTimestamp: proto.Int64(now),
ClientMsgId: &clientMsgId,
Flag: &flag,
Msgsource: []byte(msgsource),
Unknown13: unknown13,
Unknown14: unknown14,
Unknown15: unknown15,
},
Unknown5: []byte(fileInfo.Md5),
Unknown9: proto.Int32(1),
Unknown10: proto.Uint64(uint64(rand.Uint32())),
Unknown11: proto.Int32(2),
}
data, err := proto.Marshal(msg)
if err != nil {
return "", fmt.Errorf("marshal file proto failed: %w", err)
}
fmt.Printf("[file-proto] final protobuf hex dump:\n%s\n", HexDump(data, 0))
return hex.EncodeToString(data), nil
}
// buildFileAppmsgXml 构建文件消息的appmsg XML
func buildFileAppmsgXml(selfWxid string, info *FileInfo) string {
xml := `<appmsg appid="wx6618f1cfc6c132f8" sdkver="0">`
xml += `<title>` + escapeXmlStr(info.FileName) + `</title>`
xml += `<des></des>`
xml += `<action></action>`
xml += `<type>6</type>`
xml += `<showtype>0</showtype>`
xml += `<soundtype>0</soundtype>`
xml += `<mediatagname></mediatagname>`
xml += `<messageext></messageext>`
xml += `<messageaction></messageaction>`
xml += `<content></content>`
xml += `<contentattr>0</contentattr>`
xml += `<url></url>`
xml += `<lowurl></lowurl>`
xml += `<dataurl></dataurl>`
xml += `<lowdataurl></lowdataurl>`
xml += `<songalbumurl></songalbumurl>`
xml += `<songlyric></songlyric>`
xml += `<template_id></template_id>`
xml += `<appattach>`
xml += `<totallen>` + fmt.Sprintf("%d", info.TotalLen) + `</totallen>`
xml += `<attachid>` + escapeXmlStr(info.AttachId) + `</attachid>`
xml += `<emoticonmd5></emoticonmd5>`
xml += `<fileext>` + escapeXmlStr(info.FileExt) + `</fileext>`
xml += `<cdnattachurl>` + escapeXmlStr(info.CdnAttachURL) + `</cdnattachurl>`
xml += `<aeskey>` + escapeXmlStr(info.AesKey) + `</aeskey>`
xml += `<encryver>0</encryver>`
xml += `<overwrite_newmsgid>` + escapeXmlStr(info.OverwriteMsgId) + `</overwrite_newmsgid>`
xml += `<fileuploadtoken>` + escapeXmlStr(info.FileUploadToken) + `</fileuploadtoken>`
xml += `</appattach>`
xml += `<extinfo></extinfo>`
xml += `<sourceusername></sourceusername>`
xml += `<sourcedisplayname></sourcedisplayname>`
xml += `<thumburl></thumburl>`
xml += `<md5>` + escapeXmlStr(info.Md5) + `</md5>`
xml += `<statextstr></statextstr>`
xml += `</appmsg>`
xml += `<fromusername>` + escapeXmlStr(selfWxid) + `</fromusername>`
return xml
}
// BuildCheckMd5Proto 构建 /cgi-bin/micromsg-bin/checkmd5 请求的protobuf
func BuildCheckMd5Proto(fileInfo *FileInfo) (string, error) {
version := NextVersion()
field5 := uint32(0)
msg := &wxproto.CheckMd5Request{
BaseRequest: &wxproto.ReplyMsgHeader{
Flag: []byte{0x00},
SessionId: &globalSessionId,
ClientProof: globalClientProof,
DeviceId: &globalDeviceId,
Platform: proto.String("UnifiedPCMac 26 arm64"),
Version: &version,
},
FileKey: &fileInfo.CdnAttachURL,
FileMd5: &fileInfo.Md5,
Field4: []byte{},
Field5: &field5,
}
data, err := proto.Marshal(msg)
if err != nil {
return "", fmt.Errorf("marshal checkmd5 proto failed: %w", err)
}
//fmt.Printf("[checkmd5-proto] final protobuf hex dump:\n%s\n", HexDump(data, 0))
return hex.EncodeToString(data), nil
}
// ParseCheckMd5Response 解析 checkmd5 响应
// ret=0 表示文件已存在(秒传),ret=102 表示文件不存在需要正常发送,两者都视为成功
func ParseCheckMd5Response(data []byte) error {
resp := &wxproto.CheckMd5Response{}
err := proto.Unmarshal(data, resp)
if err != nil {
return fmt.Errorf("unmarshal checkmd5 response failed: %w", err)
}
if resp.BaseResponse != nil && resp.BaseResponse.Ret != nil {
ret := *resp.BaseResponse.Ret
// 0=文件已存在(秒传), 102=文件不存在(正常发送), 都可以继续
if ret != 0 {
errMsg := ""
if resp.BaseResponse.ErrMsg != nil {
errMsg = resp.BaseResponse.ErrMsg.GetMsg()
}
return fmt.Errorf("checkmd5 failed, ret=%d, errMsg=%s", ret, errMsg)
}
Info("checkmd5 ret", "ret", ret)
}
return nil
}
+1 -1
View File
@@ -4,6 +4,7 @@ go 1.25.0
require (
github.com/frida/frida-go v1.0.2
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
github.com/rs/zerolog v1.35.1
@@ -14,7 +15,6 @@ require (
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.22 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
+48 -2
View File
@@ -9,6 +9,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"runtime/debug"
"strings"
"time"
@@ -49,13 +50,21 @@ func sendHandler(w http.ResponseWriter, r *http.Request) {
}
}
} else if v.Type == "image" || v.Type == "video" {
} else if v.Type == "image" || v.Type == "video" || v.Type == "record" || v.Type == "voice" || v.Type == "file" {
msgType := v.Type
if msgType == "record" || msgType == "voice" {
msgType = "voice"
}
// file: 走 iPad860 风格 uploadappattach 直传(不走 CDN)
if msgType == "file" {
msgType = "send_file_simple"
}
ch := make(chan error, 1)
msg := &SendMsg{
UserId: req.UserID,
GroupID: req.GroupID,
Content: v.Data.File,
Type: v.Type,
Type: msgType,
ResultChan: ch,
}
msgChan <- msg
@@ -212,3 +221,40 @@ func jsonUnescapeString(s string) string {
return result
}
// getFileExt 从文件名中提取扩展名(不含.)
func getFileExt(fileName string) string {
idx := strings.LastIndex(fileName, ".")
if idx == -1 || idx == len(fileName)-1 {
return ""
}
return fileName[idx+1:]
}
// extractFileName 从路径或URL中提取文件名
func extractFileName(s string) string {
if s == "" {
return ""
}
// 跳过 base64 数据
if strings.HasPrefix(s, "base64://") || strings.Contains(s, ";base64,") {
return ""
}
// URL 路径
if strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") || strings.HasPrefix(s, "file://") {
if u, err := url.Parse(s); err == nil {
p := u.Path
if idx := strings.LastIndex(p, "/"); idx != -1 {
return p[idx+1:]
}
return p
}
}
// 本地文件路径
if strings.Contains(s, "/") || strings.Contains(s, "\\") {
if idx := strings.LastIndexAny(s, "/\\"); idx != -1 {
return s[idx+1:]
}
}
return s
}
+34 -1
View File
@@ -61,7 +61,7 @@ func initFlag() {
flag.StringVar(&config.FridaGadgetAddr, "gadget_addr", "127.0.0.1:27042", "Gadget 地址: 127.0.0.1:27042 仅当 type 为 gadget 时有效")
flag.StringVar(&config.OnebotToken, "token", "MuseBot", "OneBot Token: MuseBot")
flag.StringVar(&config.ImagePath, "image_path", "", "图片路径: /Users/xxx/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/xxx/temp/xxx/2026-01/Img/")
flag.StringVar(&config.WechatConf, "wechat_conf", "../wechat_version/4_1_9_58_mac.json", "微信配置文件路径: ../wechat_version/4_1_6_12_mac.json")
flag.StringVar(&config.WechatConf, "wechat_conf", "../wechat_version/4_1_10_53_mac.json", "微信配置文件路径: ../wechat_version/4_1_6_12_mac.json")
flag.StringVar(&config.ConnType, "conn_type", "http", "连接类型: http | websocket")
flag.IntVar(&config.SendInterval, "send_interval", 1000, "发送间隔: ms")
flag.IntVar(&config.WechatPid, "wechat_pid", 0, "微信进程 PID,不设置则自动查找")
@@ -294,6 +294,39 @@ func loadJs() {
m.ResultChan = ch.(chan error)
}
msgChan <- m
case "upload_voice_finish":
m := &SendMsg{
Type: "send_voice",
}
targetId := ""
if targetIdInter, ok := pMap["target_id"]; ok {
targetId = targetIdInter.(string)
if strings.Contains(targetId, "wxid_") {
m.UserId = targetId
} else {
m.GroupID = targetId
}
}
if cdnKey, ok := pMap["cdn_key"]; ok {
m.CdnKey = cdnKey.(string)
}
if aesKey, ok := pMap["aes_key"]; ok {
m.AesKey = aesKey.(string)
}
if voiceDuration, ok := pMap["voice_duration"]; ok {
if vd, ok := voiceDuration.(float64); ok {
m.VoiceDuration = int32(vd)
}
}
if silkDataLen, ok := pMap["silk_data_len"]; ok {
if sdl, ok := silkDataLen.(float64); ok {
m.SilkDataLen = int32(sdl)
}
}
if ch, ok := pendingResultMap.LoadAndDelete(targetId); ok {
m.ResultChan = ch.(chan error)
}
msgChan <- m
case "download":
err = Download(payloadJson)
if err != nil {
+16 -4
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"math/rand"
"sync"
"sync/atomic"
"github.com/frida/frida-go/frida"
)
@@ -27,12 +28,19 @@ var (
config = &Config{}
userID2NicknameMap sync.Map
userID2FileMsgMap sync.Map
videoInfoMap sync.Map // targetId -> *VideoInfo
buf2RespChan = make(chan *Buf2RespData, 10)
userID2NicknameMap sync.Map
userID2FileMsgMap sync.Map
videoInfoMap sync.Map // targetId -> *VideoInfo
buf2RespChan = make(chan *Buf2RespData, 10)
debugRespChan = make(chan []byte, 1)
appAttachRespChan = make(chan []byte, 1)
)
// NextVersion 获取当前taskId作为版本号
func NextVersion() uint32 {
return uint32(atomic.LoadInt64(&taskId))
}
type WechatMessage struct {
GroupId string `json:"group_id"`
SelfID string `json:"self_id"`
@@ -73,6 +81,10 @@ type SendMsg struct {
Duration int32
VideoSize int32
VoiceDuration int32
SilkDataLen int32
Unknown13 int32
ReferMsgId string
ReferMsgSender string
ReferMsgType int
+213
View File
@@ -0,0 +1,213 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v3.5.1
// source: check_md5.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)
)
// /cgi-bin/micromsg-bin/checkmd5 请求
type CheckMd5Request struct {
state protoimpl.MessageState `protogen:"open.v1"`
BaseRequest *ReplyMsgHeader `protobuf:"bytes,1,opt,name=baseRequest" json:"baseRequest,omitempty"`
FileKey *string `protobuf:"bytes,2,opt,name=fileKey" json:"fileKey,omitempty"` // CDN file key (attachid)
FileMd5 *string `protobuf:"bytes,3,opt,name=fileMd5" json:"fileMd5,omitempty"` // 文件MD5 (32位hex字符串)
Field4 []byte `protobuf:"bytes,4,opt,name=field4" json:"field4,omitempty"` // 空
Field5 *uint32 `protobuf:"varint,5,opt,name=field5" json:"field5,omitempty"` // 0
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *CheckMd5Request) Reset() {
*x = CheckMd5Request{}
mi := &file_check_md5_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CheckMd5Request) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CheckMd5Request) ProtoMessage() {}
func (x *CheckMd5Request) ProtoReflect() protoreflect.Message {
mi := &file_check_md5_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 CheckMd5Request.ProtoReflect.Descriptor instead.
func (*CheckMd5Request) Descriptor() ([]byte, []int) {
return file_check_md5_proto_rawDescGZIP(), []int{0}
}
func (x *CheckMd5Request) GetBaseRequest() *ReplyMsgHeader {
if x != nil {
return x.BaseRequest
}
return nil
}
func (x *CheckMd5Request) GetFileKey() string {
if x != nil && x.FileKey != nil {
return *x.FileKey
}
return ""
}
func (x *CheckMd5Request) GetFileMd5() string {
if x != nil && x.FileMd5 != nil {
return *x.FileMd5
}
return ""
}
func (x *CheckMd5Request) GetField4() []byte {
if x != nil {
return x.Field4
}
return nil
}
func (x *CheckMd5Request) GetField5() uint32 {
if x != nil && x.Field5 != nil {
return *x.Field5
}
return 0
}
// /cgi-bin/micromsg-bin/checkmd5 响应
type CheckMd5Response struct {
state protoimpl.MessageState `protogen:"open.v1"`
BaseResponse *BaseResponse `protobuf:"bytes,1,opt,name=baseResponse" json:"baseResponse,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *CheckMd5Response) Reset() {
*x = CheckMd5Response{}
mi := &file_check_md5_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CheckMd5Response) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CheckMd5Response) ProtoMessage() {}
func (x *CheckMd5Response) ProtoReflect() protoreflect.Message {
mi := &file_check_md5_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 CheckMd5Response.ProtoReflect.Descriptor instead.
func (*CheckMd5Response) Descriptor() ([]byte, []int) {
return file_check_md5_proto_rawDescGZIP(), []int{1}
}
func (x *CheckMd5Response) GetBaseResponse() *BaseResponse {
if x != nil {
return x.BaseResponse
}
return nil
}
var File_check_md5_proto protoreflect.FileDescriptor
const file_check_md5_proto_rawDesc = "" +
"\n" +
"\x0fcheck_md5.proto\x12\awxproto\x1a\rapp_msg.proto\x1a\x11common_resp.proto\"\xb0\x01\n" +
"\x0fCheckMd5Request\x129\n" +
"\vbaseRequest\x18\x01 \x01(\v2\x17.wxproto.ReplyMsgHeaderR\vbaseRequest\x12\x18\n" +
"\afileKey\x18\x02 \x01(\tR\afileKey\x12\x18\n" +
"\afileMd5\x18\x03 \x01(\tR\afileMd5\x12\x16\n" +
"\x06field4\x18\x04 \x01(\fR\x06field4\x12\x16\n" +
"\x06field5\x18\x05 \x01(\rR\x06field5\"M\n" +
"\x10CheckMd5Response\x129\n" +
"\fbaseResponse\x18\x01 \x01(\v2\x15.wxproto.BaseResponseR\fbaseResponseB>Z<github.com/yincongcyincong/weixin-macos/onebot/proto;wxproto"
var (
file_check_md5_proto_rawDescOnce sync.Once
file_check_md5_proto_rawDescData []byte
)
func file_check_md5_proto_rawDescGZIP() []byte {
file_check_md5_proto_rawDescOnce.Do(func() {
file_check_md5_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_check_md5_proto_rawDesc), len(file_check_md5_proto_rawDesc)))
})
return file_check_md5_proto_rawDescData
}
var file_check_md5_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_check_md5_proto_goTypes = []any{
(*CheckMd5Request)(nil), // 0: wxproto.CheckMd5Request
(*CheckMd5Response)(nil), // 1: wxproto.CheckMd5Response
(*ReplyMsgHeader)(nil), // 2: wxproto.ReplyMsgHeader
(*BaseResponse)(nil), // 3: wxproto.BaseResponse
}
var file_check_md5_proto_depIdxs = []int32{
2, // 0: wxproto.CheckMd5Request.baseRequest:type_name -> wxproto.ReplyMsgHeader
3, // 1: wxproto.CheckMd5Response.baseResponse:type_name -> wxproto.BaseResponse
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_check_md5_proto_init() }
func file_check_md5_proto_init() {
if File_check_md5_proto != nil {
return
}
file_app_msg_proto_init()
file_common_resp_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_check_md5_proto_rawDesc), len(file_check_md5_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_check_md5_proto_goTypes,
DependencyIndexes: file_check_md5_proto_depIdxs,
MessageInfos: file_check_md5_proto_msgTypes,
}.Build()
File_check_md5_proto = out.File
file_check_md5_proto_goTypes = nil
file_check_md5_proto_depIdxs = nil
}
+257
View File
@@ -0,0 +1,257 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v3.5.1
// source: file_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)
)
// /cgi-bin/micromsg-bin/sendfileuploadmsg 请求
type SendFileUploadMsgRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
BaseRequest *ReplyMsgHeader `protobuf:"bytes,1,opt,name=baseRequest" json:"baseRequest,omitempty"`
ToUserName *string `protobuf:"bytes,2,opt,name=toUserName" json:"toUserName,omitempty"`
ClientMsgId *string `protobuf:"bytes,3,opt,name=clientMsgId" json:"clientMsgId,omitempty"`
MsgType *uint32 `protobuf:"varint,4,opt,name=msgType" json:"msgType,omitempty"`
FileInfo *FileUploadFileInfo `protobuf:"bytes,5,opt,name=fileInfo" json:"fileInfo,omitempty"`
Field7 *uint32 `protobuf:"varint,7,opt,name=field7" json:"field7,omitempty"`
Field12 *uint32 `protobuf:"varint,12,opt,name=field12" json:"field12,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SendFileUploadMsgRequest) Reset() {
*x = SendFileUploadMsgRequest{}
mi := &file_file_msg_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SendFileUploadMsgRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendFileUploadMsgRequest) ProtoMessage() {}
func (x *SendFileUploadMsgRequest) ProtoReflect() protoreflect.Message {
mi := &file_file_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 SendFileUploadMsgRequest.ProtoReflect.Descriptor instead.
func (*SendFileUploadMsgRequest) Descriptor() ([]byte, []int) {
return file_file_msg_proto_rawDescGZIP(), []int{0}
}
func (x *SendFileUploadMsgRequest) GetBaseRequest() *ReplyMsgHeader {
if x != nil {
return x.BaseRequest
}
return nil
}
func (x *SendFileUploadMsgRequest) GetToUserName() string {
if x != nil && x.ToUserName != nil {
return *x.ToUserName
}
return ""
}
func (x *SendFileUploadMsgRequest) GetClientMsgId() string {
if x != nil && x.ClientMsgId != nil {
return *x.ClientMsgId
}
return ""
}
func (x *SendFileUploadMsgRequest) GetMsgType() uint32 {
if x != nil && x.MsgType != nil {
return *x.MsgType
}
return 0
}
func (x *SendFileUploadMsgRequest) GetFileInfo() *FileUploadFileInfo {
if x != nil {
return x.FileInfo
}
return nil
}
func (x *SendFileUploadMsgRequest) GetField7() uint32 {
if x != nil && x.Field7 != nil {
return *x.Field7
}
return 0
}
func (x *SendFileUploadMsgRequest) GetField12() uint32 {
if x != nil && x.Field12 != nil {
return *x.Field12
}
return 0
}
type FileUploadFileInfo struct {
state protoimpl.MessageState `protogen:"open.v1"`
FileName *string `protobuf:"bytes,1,opt,name=fileName" json:"fileName,omitempty"`
FileExt *string `protobuf:"bytes,2,opt,name=fileExt" json:"fileExt,omitempty"`
FileMd5 *string `protobuf:"bytes,3,opt,name=fileMd5" json:"fileMd5,omitempty"`
FileSize *uint32 `protobuf:"varint,4,opt,name=fileSize" json:"fileSize,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *FileUploadFileInfo) Reset() {
*x = FileUploadFileInfo{}
mi := &file_file_msg_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *FileUploadFileInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FileUploadFileInfo) ProtoMessage() {}
func (x *FileUploadFileInfo) ProtoReflect() protoreflect.Message {
mi := &file_file_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 FileUploadFileInfo.ProtoReflect.Descriptor instead.
func (*FileUploadFileInfo) Descriptor() ([]byte, []int) {
return file_file_msg_proto_rawDescGZIP(), []int{1}
}
func (x *FileUploadFileInfo) GetFileName() string {
if x != nil && x.FileName != nil {
return *x.FileName
}
return ""
}
func (x *FileUploadFileInfo) GetFileExt() string {
if x != nil && x.FileExt != nil {
return *x.FileExt
}
return ""
}
func (x *FileUploadFileInfo) GetFileMd5() string {
if x != nil && x.FileMd5 != nil {
return *x.FileMd5
}
return ""
}
func (x *FileUploadFileInfo) GetFileSize() uint32 {
if x != nil && x.FileSize != nil {
return *x.FileSize
}
return 0
}
var File_file_msg_proto protoreflect.FileDescriptor
const file_file_msg_proto_rawDesc = "" +
"\n" +
"\x0efile_msg.proto\x12\awxproto\x1a\rapp_msg.proto\"\x9c\x02\n" +
"\x18SendFileUploadMsgRequest\x129\n" +
"\vbaseRequest\x18\x01 \x01(\v2\x17.wxproto.ReplyMsgHeaderR\vbaseRequest\x12\x1e\n" +
"\n" +
"toUserName\x18\x02 \x01(\tR\n" +
"toUserName\x12 \n" +
"\vclientMsgId\x18\x03 \x01(\tR\vclientMsgId\x12\x18\n" +
"\amsgType\x18\x04 \x01(\rR\amsgType\x127\n" +
"\bfileInfo\x18\x05 \x01(\v2\x1b.wxproto.FileUploadFileInfoR\bfileInfo\x12\x16\n" +
"\x06field7\x18\a \x01(\rR\x06field7\x12\x18\n" +
"\afield12\x18\f \x01(\rR\afield12\"\x80\x01\n" +
"\x12FileUploadFileInfo\x12\x1a\n" +
"\bfileName\x18\x01 \x01(\tR\bfileName\x12\x18\n" +
"\afileExt\x18\x02 \x01(\tR\afileExt\x12\x18\n" +
"\afileMd5\x18\x03 \x01(\tR\afileMd5\x12\x1a\n" +
"\bfileSize\x18\x04 \x01(\rR\bfileSizeB>Z<github.com/yincongcyincong/weixin-macos/onebot/proto;wxproto"
var (
file_file_msg_proto_rawDescOnce sync.Once
file_file_msg_proto_rawDescData []byte
)
func file_file_msg_proto_rawDescGZIP() []byte {
file_file_msg_proto_rawDescOnce.Do(func() {
file_file_msg_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_file_msg_proto_rawDesc), len(file_file_msg_proto_rawDesc)))
})
return file_file_msg_proto_rawDescData
}
var file_file_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_file_msg_proto_goTypes = []any{
(*SendFileUploadMsgRequest)(nil), // 0: wxproto.SendFileUploadMsgRequest
(*FileUploadFileInfo)(nil), // 1: wxproto.FileUploadFileInfo
(*ReplyMsgHeader)(nil), // 2: wxproto.ReplyMsgHeader
}
var file_file_msg_proto_depIdxs = []int32{
2, // 0: wxproto.SendFileUploadMsgRequest.baseRequest:type_name -> wxproto.ReplyMsgHeader
1, // 1: wxproto.SendFileUploadMsgRequest.fileInfo:type_name -> wxproto.FileUploadFileInfo
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_file_msg_proto_init() }
func file_file_msg_proto_init() {
if File_file_msg_proto != nil {
return
}
file_app_msg_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_file_msg_proto_rawDesc), len(file_file_msg_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_file_msg_proto_goTypes,
DependencyIndexes: file_file_msg_proto_depIdxs,
MessageInfos: file_file_msg_proto_msgTypes,
}.Build()
File_file_msg_proto = out.File
file_file_msg_proto_goTypes = nil
file_file_msg_proto_depIdxs = nil
}
+495
View File
@@ -0,0 +1,495 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v3.5.1
// source: send_app_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)
)
// /cgi-bin/micromsg-bin/sendappmsg 请求
// 字段号严格对齐 wechat7016 SendAppMsgRequest
// 但 baseRequest 使用本项目 macOS 风格的 ReplyMsgHeader。
type SendAppMsgReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
BaseRequest *ReplyMsgHeader `protobuf:"bytes,1,opt,name=baseRequest" json:"baseRequest,omitempty"`
Msg *AppMsgBody `protobuf:"bytes,2,opt,name=msg" json:"msg,omitempty"`
CommentUrl *string `protobuf:"bytes,3,opt,name=commentUrl" json:"commentUrl,omitempty"`
ReqTime *uint32 `protobuf:"varint,4,opt,name=reqTime" json:"reqTime,omitempty"`
Md5 *string `protobuf:"bytes,5,opt,name=md5" json:"md5,omitempty"`
FileType *uint32 `protobuf:"varint,6,opt,name=fileType" json:"fileType,omitempty"`
Signature *string `protobuf:"bytes,7,opt,name=signature" json:"signature,omitempty"`
FromSence *string `protobuf:"bytes,8,opt,name=fromSence" json:"fromSence,omitempty"`
HitMd5 *uint32 `protobuf:"varint,9,opt,name=hitMd5" json:"hitMd5,omitempty"`
Crc32 *uint32 `protobuf:"varint,10,opt,name=crc32" json:"crc32,omitempty"`
MsgForwardType *uint32 `protobuf:"varint,11,opt,name=msgForwardType" json:"msgForwardType,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SendAppMsgReq) Reset() {
*x = SendAppMsgReq{}
mi := &file_send_app_msg_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SendAppMsgReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendAppMsgReq) ProtoMessage() {}
func (x *SendAppMsgReq) ProtoReflect() protoreflect.Message {
mi := &file_send_app_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 SendAppMsgReq.ProtoReflect.Descriptor instead.
func (*SendAppMsgReq) Descriptor() ([]byte, []int) {
return file_send_app_msg_proto_rawDescGZIP(), []int{0}
}
func (x *SendAppMsgReq) GetBaseRequest() *ReplyMsgHeader {
if x != nil {
return x.BaseRequest
}
return nil
}
func (x *SendAppMsgReq) GetMsg() *AppMsgBody {
if x != nil {
return x.Msg
}
return nil
}
func (x *SendAppMsgReq) GetCommentUrl() string {
if x != nil && x.CommentUrl != nil {
return *x.CommentUrl
}
return ""
}
func (x *SendAppMsgReq) GetReqTime() uint32 {
if x != nil && x.ReqTime != nil {
return *x.ReqTime
}
return 0
}
func (x *SendAppMsgReq) GetMd5() string {
if x != nil && x.Md5 != nil {
return *x.Md5
}
return ""
}
func (x *SendAppMsgReq) GetFileType() uint32 {
if x != nil && x.FileType != nil {
return *x.FileType
}
return 0
}
func (x *SendAppMsgReq) GetSignature() string {
if x != nil && x.Signature != nil {
return *x.Signature
}
return ""
}
func (x *SendAppMsgReq) GetFromSence() string {
if x != nil && x.FromSence != nil {
return *x.FromSence
}
return ""
}
func (x *SendAppMsgReq) GetHitMd5() uint32 {
if x != nil && x.HitMd5 != nil {
return *x.HitMd5
}
return 0
}
func (x *SendAppMsgReq) GetCrc32() uint32 {
if x != nil && x.Crc32 != nil {
return *x.Crc32
}
return 0
}
func (x *SendAppMsgReq) GetMsgForwardType() uint32 {
if x != nil && x.MsgForwardType != nil {
return *x.MsgForwardType
}
return 0
}
// AppMsg 消息体 (wechat7016 AppMsg)
type AppMsgBody struct {
state protoimpl.MessageState `protogen:"open.v1"`
FromUserName *string `protobuf:"bytes,1,opt,name=fromUserName" json:"fromUserName,omitempty"`
AppId *string `protobuf:"bytes,2,opt,name=appId" json:"appId,omitempty"`
SdkVersion *uint32 `protobuf:"varint,3,opt,name=sdkVersion" json:"sdkVersion,omitempty"`
ToUserName *string `protobuf:"bytes,4,opt,name=toUserName" json:"toUserName,omitempty"`
Type *uint32 `protobuf:"varint,5,opt,name=type" json:"type,omitempty"` // 6 = 文件
Content *string `protobuf:"bytes,6,opt,name=content" json:"content,omitempty"` // appmsg XML
CreateTime *int64 `protobuf:"varint,7,opt,name=createTime" json:"createTime,omitempty"`
ClientMsgId *string `protobuf:"bytes,8,opt,name=clientMsgId" json:"clientMsgId,omitempty"`
MsgSource *string `protobuf:"bytes,12,opt,name=msgSource" json:"msgSource,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AppMsgBody) Reset() {
*x = AppMsgBody{}
mi := &file_send_app_msg_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AppMsgBody) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AppMsgBody) ProtoMessage() {}
func (x *AppMsgBody) ProtoReflect() protoreflect.Message {
mi := &file_send_app_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 AppMsgBody.ProtoReflect.Descriptor instead.
func (*AppMsgBody) Descriptor() ([]byte, []int) {
return file_send_app_msg_proto_rawDescGZIP(), []int{1}
}
func (x *AppMsgBody) GetFromUserName() string {
if x != nil && x.FromUserName != nil {
return *x.FromUserName
}
return ""
}
func (x *AppMsgBody) GetAppId() string {
if x != nil && x.AppId != nil {
return *x.AppId
}
return ""
}
func (x *AppMsgBody) GetSdkVersion() uint32 {
if x != nil && x.SdkVersion != nil {
return *x.SdkVersion
}
return 0
}
func (x *AppMsgBody) GetToUserName() string {
if x != nil && x.ToUserName != nil {
return *x.ToUserName
}
return ""
}
func (x *AppMsgBody) GetType() uint32 {
if x != nil && x.Type != nil {
return *x.Type
}
return 0
}
func (x *AppMsgBody) GetContent() string {
if x != nil && x.Content != nil {
return *x.Content
}
return ""
}
func (x *AppMsgBody) GetCreateTime() int64 {
if x != nil && x.CreateTime != nil {
return *x.CreateTime
}
return 0
}
func (x *AppMsgBody) GetClientMsgId() string {
if x != nil && x.ClientMsgId != nil {
return *x.ClientMsgId
}
return ""
}
func (x *AppMsgBody) GetMsgSource() string {
if x != nil && x.MsgSource != nil {
return *x.MsgSource
}
return ""
}
// /cgi-bin/micromsg-bin/sendappmsg 响应
type SendAppMsgResp struct {
state protoimpl.MessageState `protogen:"open.v1"`
BaseResponse *BaseResponse `protobuf:"bytes,1,opt,name=baseResponse" json:"baseResponse,omitempty"`
AppId *string `protobuf:"bytes,2,opt,name=appId" json:"appId,omitempty"`
FromUserName *string `protobuf:"bytes,3,opt,name=fromUserName" json:"fromUserName,omitempty"`
ToUserName *string `protobuf:"bytes,4,opt,name=toUserName" json:"toUserName,omitempty"`
MsgId *int32 `protobuf:"varint,5,opt,name=msgId" json:"msgId,omitempty"`
ClientMsgId *string `protobuf:"bytes,6,opt,name=clientMsgId" json:"clientMsgId,omitempty"`
CreateTime *int32 `protobuf:"varint,7,opt,name=createTime" json:"createTime,omitempty"`
Type *int32 `protobuf:"varint,8,opt,name=type" json:"type,omitempty"`
NewMsgId *int64 `protobuf:"varint,9,opt,name=newMsgId" json:"newMsgId,omitempty"`
Aeskey *string `protobuf:"bytes,10,opt,name=aeskey" json:"aeskey,omitempty"`
MsgSource *string `protobuf:"bytes,11,opt,name=msgSource" json:"msgSource,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SendAppMsgResp) Reset() {
*x = SendAppMsgResp{}
mi := &file_send_app_msg_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SendAppMsgResp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendAppMsgResp) ProtoMessage() {}
func (x *SendAppMsgResp) ProtoReflect() protoreflect.Message {
mi := &file_send_app_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 SendAppMsgResp.ProtoReflect.Descriptor instead.
func (*SendAppMsgResp) Descriptor() ([]byte, []int) {
return file_send_app_msg_proto_rawDescGZIP(), []int{2}
}
func (x *SendAppMsgResp) GetBaseResponse() *BaseResponse {
if x != nil {
return x.BaseResponse
}
return nil
}
func (x *SendAppMsgResp) GetAppId() string {
if x != nil && x.AppId != nil {
return *x.AppId
}
return ""
}
func (x *SendAppMsgResp) GetFromUserName() string {
if x != nil && x.FromUserName != nil {
return *x.FromUserName
}
return ""
}
func (x *SendAppMsgResp) GetToUserName() string {
if x != nil && x.ToUserName != nil {
return *x.ToUserName
}
return ""
}
func (x *SendAppMsgResp) GetMsgId() int32 {
if x != nil && x.MsgId != nil {
return *x.MsgId
}
return 0
}
func (x *SendAppMsgResp) GetClientMsgId() string {
if x != nil && x.ClientMsgId != nil {
return *x.ClientMsgId
}
return ""
}
func (x *SendAppMsgResp) GetCreateTime() int32 {
if x != nil && x.CreateTime != nil {
return *x.CreateTime
}
return 0
}
func (x *SendAppMsgResp) GetType() int32 {
if x != nil && x.Type != nil {
return *x.Type
}
return 0
}
func (x *SendAppMsgResp) GetNewMsgId() int64 {
if x != nil && x.NewMsgId != nil {
return *x.NewMsgId
}
return 0
}
func (x *SendAppMsgResp) GetAeskey() string {
if x != nil && x.Aeskey != nil {
return *x.Aeskey
}
return ""
}
func (x *SendAppMsgResp) GetMsgSource() string {
if x != nil && x.MsgSource != nil {
return *x.MsgSource
}
return ""
}
var File_send_app_msg_proto protoreflect.FileDescriptor
const file_send_app_msg_proto_rawDesc = "" +
"\n" +
"\x12send_app_msg.proto\x12\awxproto\x1a\rapp_msg.proto\x1a\x11common_resp.proto\"\xeb\x02\n" +
"\rSendAppMsgReq\x129\n" +
"\vbaseRequest\x18\x01 \x01(\v2\x17.wxproto.ReplyMsgHeaderR\vbaseRequest\x12%\n" +
"\x03msg\x18\x02 \x01(\v2\x13.wxproto.AppMsgBodyR\x03msg\x12\x1e\n" +
"\n" +
"commentUrl\x18\x03 \x01(\tR\n" +
"commentUrl\x12\x18\n" +
"\areqTime\x18\x04 \x01(\rR\areqTime\x12\x10\n" +
"\x03md5\x18\x05 \x01(\tR\x03md5\x12\x1a\n" +
"\bfileType\x18\x06 \x01(\rR\bfileType\x12\x1c\n" +
"\tsignature\x18\a \x01(\tR\tsignature\x12\x1c\n" +
"\tfromSence\x18\b \x01(\tR\tfromSence\x12\x16\n" +
"\x06hitMd5\x18\t \x01(\rR\x06hitMd5\x12\x14\n" +
"\x05crc32\x18\n" +
" \x01(\rR\x05crc32\x12&\n" +
"\x0emsgForwardType\x18\v \x01(\rR\x0emsgForwardType\"\x94\x02\n" +
"\n" +
"AppMsgBody\x12\"\n" +
"\ffromUserName\x18\x01 \x01(\tR\ffromUserName\x12\x14\n" +
"\x05appId\x18\x02 \x01(\tR\x05appId\x12\x1e\n" +
"\n" +
"sdkVersion\x18\x03 \x01(\rR\n" +
"sdkVersion\x12\x1e\n" +
"\n" +
"toUserName\x18\x04 \x01(\tR\n" +
"toUserName\x12\x12\n" +
"\x04type\x18\x05 \x01(\rR\x04type\x12\x18\n" +
"\acontent\x18\x06 \x01(\tR\acontent\x12\x1e\n" +
"\n" +
"createTime\x18\a \x01(\x03R\n" +
"createTime\x12 \n" +
"\vclientMsgId\x18\b \x01(\tR\vclientMsgId\x12\x1c\n" +
"\tmsgSource\x18\f \x01(\tR\tmsgSource\"\xe3\x02\n" +
"\x0eSendAppMsgResp\x129\n" +
"\fbaseResponse\x18\x01 \x01(\v2\x15.wxproto.BaseResponseR\fbaseResponse\x12\x14\n" +
"\x05appId\x18\x02 \x01(\tR\x05appId\x12\"\n" +
"\ffromUserName\x18\x03 \x01(\tR\ffromUserName\x12\x1e\n" +
"\n" +
"toUserName\x18\x04 \x01(\tR\n" +
"toUserName\x12\x14\n" +
"\x05msgId\x18\x05 \x01(\x05R\x05msgId\x12 \n" +
"\vclientMsgId\x18\x06 \x01(\tR\vclientMsgId\x12\x1e\n" +
"\n" +
"createTime\x18\a \x01(\x05R\n" +
"createTime\x12\x12\n" +
"\x04type\x18\b \x01(\x05R\x04type\x12\x1a\n" +
"\bnewMsgId\x18\t \x01(\x03R\bnewMsgId\x12\x16\n" +
"\x06aeskey\x18\n" +
" \x01(\tR\x06aeskey\x12\x1c\n" +
"\tmsgSource\x18\v \x01(\tR\tmsgSourceB>Z<github.com/yincongcyincong/weixin-macos/onebot/proto;wxproto"
var (
file_send_app_msg_proto_rawDescOnce sync.Once
file_send_app_msg_proto_rawDescData []byte
)
func file_send_app_msg_proto_rawDescGZIP() []byte {
file_send_app_msg_proto_rawDescOnce.Do(func() {
file_send_app_msg_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_send_app_msg_proto_rawDesc), len(file_send_app_msg_proto_rawDesc)))
})
return file_send_app_msg_proto_rawDescData
}
var file_send_app_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_send_app_msg_proto_goTypes = []any{
(*SendAppMsgReq)(nil), // 0: wxproto.SendAppMsgReq
(*AppMsgBody)(nil), // 1: wxproto.AppMsgBody
(*SendAppMsgResp)(nil), // 2: wxproto.SendAppMsgResp
(*ReplyMsgHeader)(nil), // 3: wxproto.ReplyMsgHeader
(*BaseResponse)(nil), // 4: wxproto.BaseResponse
}
var file_send_app_msg_proto_depIdxs = []int32{
3, // 0: wxproto.SendAppMsgReq.baseRequest:type_name -> wxproto.ReplyMsgHeader
1, // 1: wxproto.SendAppMsgReq.msg:type_name -> wxproto.AppMsgBody
4, // 2: wxproto.SendAppMsgResp.baseResponse:type_name -> wxproto.BaseResponse
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_send_app_msg_proto_init() }
func file_send_app_msg_proto_init() {
if File_send_app_msg_proto != nil {
return
}
file_app_msg_proto_init()
file_common_resp_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_send_app_msg_proto_rawDesc), len(file_send_app_msg_proto_rawDesc)),
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_send_app_msg_proto_goTypes,
DependencyIndexes: file_send_app_msg_proto_depIdxs,
MessageInfos: file_send_app_msg_proto_msgTypes,
}.Build()
File_send_app_msg_proto = out.File
file_send_app_msg_proto_goTypes = nil
file_send_app_msg_proto_depIdxs = nil
}
+404
View File
@@ -0,0 +1,404 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v3.5.1
// source: upload_app_attach.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)
)
// 通用二进制缓冲(iPad860 SKBuiltinBufferT 对齐)
type SKBuiltinBufferT struct {
state protoimpl.MessageState `protogen:"open.v1"`
ILen *uint32 `protobuf:"varint,1,opt,name=iLen" json:"iLen,omitempty"`
Buffer []byte `protobuf:"bytes,2,opt,name=buffer" json:"buffer,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SKBuiltinBufferT) Reset() {
*x = SKBuiltinBufferT{}
mi := &file_upload_app_attach_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SKBuiltinBufferT) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SKBuiltinBufferT) ProtoMessage() {}
func (x *SKBuiltinBufferT) ProtoReflect() protoreflect.Message {
mi := &file_upload_app_attach_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 SKBuiltinBufferT.ProtoReflect.Descriptor instead.
func (*SKBuiltinBufferT) Descriptor() ([]byte, []int) {
return file_upload_app_attach_proto_rawDescGZIP(), []int{0}
}
func (x *SKBuiltinBufferT) GetILen() uint32 {
if x != nil && x.ILen != nil {
return *x.ILen
}
return 0
}
func (x *SKBuiltinBufferT) GetBuffer() []byte {
if x != nil {
return x.Buffer
}
return nil
}
// /cgi-bin/micromsg-bin/uploadappattach 请求
// 参考 we-chat-ipad860 wechat.proto UploadAppAttachRequest,
// 但 baseRequest 使用本项目 macOS 风格的 ReplyMsgHeader。
type UploadAppAttachRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
BaseRequest *ReplyMsgHeader `protobuf:"bytes,1,opt,name=baseRequest" json:"baseRequest,omitempty"`
AppId *string `protobuf:"bytes,2,opt,name=appId" json:"appId,omitempty"`
SdkVersion *uint32 `protobuf:"varint,3,opt,name=sdkVersion" json:"sdkVersion,omitempty"`
ClientAppDataId *string `protobuf:"bytes,4,opt,name=clientAppDataId" json:"clientAppDataId,omitempty"` // {receiver}_{ts}_UploadFile
UserName *string `protobuf:"bytes,5,opt,name=userName" json:"userName,omitempty"` // 接收方 wxid
TotalLen *uint32 `protobuf:"varint,6,opt,name=totalLen" json:"totalLen,omitempty"` // 文件总长度
StartPos *uint32 `protobuf:"varint,7,opt,name=startPos" json:"startPos,omitempty"` // 本分片偏移
DataLen *uint32 `protobuf:"varint,8,opt,name=dataLen" json:"dataLen,omitempty"` // 本分片长度
Data *SKBuiltinBufferT `protobuf:"bytes,9,opt,name=data" json:"data,omitempty"` // 本分片原始字节
Type *uint32 `protobuf:"varint,10,opt,name=type" json:"type,omitempty"` // 6 = 文件
Md5 *string `protobuf:"bytes,11,opt,name=md5" json:"md5,omitempty"` // 文件 MD5
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UploadAppAttachRequest) Reset() {
*x = UploadAppAttachRequest{}
mi := &file_upload_app_attach_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UploadAppAttachRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UploadAppAttachRequest) ProtoMessage() {}
func (x *UploadAppAttachRequest) ProtoReflect() protoreflect.Message {
mi := &file_upload_app_attach_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 UploadAppAttachRequest.ProtoReflect.Descriptor instead.
func (*UploadAppAttachRequest) Descriptor() ([]byte, []int) {
return file_upload_app_attach_proto_rawDescGZIP(), []int{1}
}
func (x *UploadAppAttachRequest) GetBaseRequest() *ReplyMsgHeader {
if x != nil {
return x.BaseRequest
}
return nil
}
func (x *UploadAppAttachRequest) GetAppId() string {
if x != nil && x.AppId != nil {
return *x.AppId
}
return ""
}
func (x *UploadAppAttachRequest) GetSdkVersion() uint32 {
if x != nil && x.SdkVersion != nil {
return *x.SdkVersion
}
return 0
}
func (x *UploadAppAttachRequest) GetClientAppDataId() string {
if x != nil && x.ClientAppDataId != nil {
return *x.ClientAppDataId
}
return ""
}
func (x *UploadAppAttachRequest) GetUserName() string {
if x != nil && x.UserName != nil {
return *x.UserName
}
return ""
}
func (x *UploadAppAttachRequest) GetTotalLen() uint32 {
if x != nil && x.TotalLen != nil {
return *x.TotalLen
}
return 0
}
func (x *UploadAppAttachRequest) GetStartPos() uint32 {
if x != nil && x.StartPos != nil {
return *x.StartPos
}
return 0
}
func (x *UploadAppAttachRequest) GetDataLen() uint32 {
if x != nil && x.DataLen != nil {
return *x.DataLen
}
return 0
}
func (x *UploadAppAttachRequest) GetData() *SKBuiltinBufferT {
if x != nil {
return x.Data
}
return nil
}
func (x *UploadAppAttachRequest) GetType() uint32 {
if x != nil && x.Type != nil {
return *x.Type
}
return 0
}
func (x *UploadAppAttachRequest) GetMd5() string {
if x != nil && x.Md5 != nil {
return *x.Md5
}
return ""
}
// /cgi-bin/micromsg-bin/uploadappattach 响应
type UploadAppAttachResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
BaseResponse *BaseResponse `protobuf:"bytes,1,opt,name=baseResponse" json:"baseResponse,omitempty"`
AppId *string `protobuf:"bytes,2,opt,name=appId" json:"appId,omitempty"`
MediaId *string `protobuf:"bytes,3,opt,name=mediaId" json:"mediaId,omitempty"` // attachid
ClientAppDataId *string `protobuf:"bytes,4,opt,name=clientAppDataId" json:"clientAppDataId,omitempty"`
UserName *string `protobuf:"bytes,5,opt,name=userName" json:"userName,omitempty"`
TotalLen *uint32 `protobuf:"varint,6,opt,name=totalLen" json:"totalLen,omitempty"`
StartPos *uint32 `protobuf:"varint,7,opt,name=startPos" json:"startPos,omitempty"`
DataLen *uint32 `protobuf:"varint,8,opt,name=dataLen" json:"dataLen,omitempty"`
CreateTime *uint64 `protobuf:"varint,9,opt,name=createTime" json:"createTime,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UploadAppAttachResponse) Reset() {
*x = UploadAppAttachResponse{}
mi := &file_upload_app_attach_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UploadAppAttachResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UploadAppAttachResponse) ProtoMessage() {}
func (x *UploadAppAttachResponse) ProtoReflect() protoreflect.Message {
mi := &file_upload_app_attach_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 UploadAppAttachResponse.ProtoReflect.Descriptor instead.
func (*UploadAppAttachResponse) Descriptor() ([]byte, []int) {
return file_upload_app_attach_proto_rawDescGZIP(), []int{2}
}
func (x *UploadAppAttachResponse) GetBaseResponse() *BaseResponse {
if x != nil {
return x.BaseResponse
}
return nil
}
func (x *UploadAppAttachResponse) GetAppId() string {
if x != nil && x.AppId != nil {
return *x.AppId
}
return ""
}
func (x *UploadAppAttachResponse) GetMediaId() string {
if x != nil && x.MediaId != nil {
return *x.MediaId
}
return ""
}
func (x *UploadAppAttachResponse) GetClientAppDataId() string {
if x != nil && x.ClientAppDataId != nil {
return *x.ClientAppDataId
}
return ""
}
func (x *UploadAppAttachResponse) GetUserName() string {
if x != nil && x.UserName != nil {
return *x.UserName
}
return ""
}
func (x *UploadAppAttachResponse) GetTotalLen() uint32 {
if x != nil && x.TotalLen != nil {
return *x.TotalLen
}
return 0
}
func (x *UploadAppAttachResponse) GetStartPos() uint32 {
if x != nil && x.StartPos != nil {
return *x.StartPos
}
return 0
}
func (x *UploadAppAttachResponse) GetDataLen() uint32 {
if x != nil && x.DataLen != nil {
return *x.DataLen
}
return 0
}
func (x *UploadAppAttachResponse) GetCreateTime() uint64 {
if x != nil && x.CreateTime != nil {
return *x.CreateTime
}
return 0
}
var File_upload_app_attach_proto protoreflect.FileDescriptor
const file_upload_app_attach_proto_rawDesc = "" +
"\n" +
"\x17upload_app_attach.proto\x12\awxproto\x1a\rapp_msg.proto\x1a\x11common_resp.proto\">\n" +
"\x10SKBuiltinBufferT\x12\x12\n" +
"\x04iLen\x18\x01 \x01(\rR\x04iLen\x12\x16\n" +
"\x06buffer\x18\x02 \x01(\fR\x06buffer\"\xf6\x02\n" +
"\x16UploadAppAttachRequest\x129\n" +
"\vbaseRequest\x18\x01 \x01(\v2\x17.wxproto.ReplyMsgHeaderR\vbaseRequest\x12\x14\n" +
"\x05appId\x18\x02 \x01(\tR\x05appId\x12\x1e\n" +
"\n" +
"sdkVersion\x18\x03 \x01(\rR\n" +
"sdkVersion\x12(\n" +
"\x0fclientAppDataId\x18\x04 \x01(\tR\x0fclientAppDataId\x12\x1a\n" +
"\buserName\x18\x05 \x01(\tR\buserName\x12\x1a\n" +
"\btotalLen\x18\x06 \x01(\rR\btotalLen\x12\x1a\n" +
"\bstartPos\x18\a \x01(\rR\bstartPos\x12\x18\n" +
"\adataLen\x18\b \x01(\rR\adataLen\x12-\n" +
"\x04data\x18\t \x01(\v2\x19.wxproto.SKBuiltinBufferTR\x04data\x12\x12\n" +
"\x04type\x18\n" +
" \x01(\rR\x04type\x12\x10\n" +
"\x03md5\x18\v \x01(\tR\x03md5\"\xbc\x02\n" +
"\x17UploadAppAttachResponse\x129\n" +
"\fbaseResponse\x18\x01 \x01(\v2\x15.wxproto.BaseResponseR\fbaseResponse\x12\x14\n" +
"\x05appId\x18\x02 \x01(\tR\x05appId\x12\x18\n" +
"\amediaId\x18\x03 \x01(\tR\amediaId\x12(\n" +
"\x0fclientAppDataId\x18\x04 \x01(\tR\x0fclientAppDataId\x12\x1a\n" +
"\buserName\x18\x05 \x01(\tR\buserName\x12\x1a\n" +
"\btotalLen\x18\x06 \x01(\rR\btotalLen\x12\x1a\n" +
"\bstartPos\x18\a \x01(\rR\bstartPos\x12\x18\n" +
"\adataLen\x18\b \x01(\rR\adataLen\x12\x1e\n" +
"\n" +
"createTime\x18\t \x01(\x04R\n" +
"createTimeB>Z<github.com/yincongcyincong/weixin-macos/onebot/proto;wxproto"
var (
file_upload_app_attach_proto_rawDescOnce sync.Once
file_upload_app_attach_proto_rawDescData []byte
)
func file_upload_app_attach_proto_rawDescGZIP() []byte {
file_upload_app_attach_proto_rawDescOnce.Do(func() {
file_upload_app_attach_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_upload_app_attach_proto_rawDesc), len(file_upload_app_attach_proto_rawDesc)))
})
return file_upload_app_attach_proto_rawDescData
}
var file_upload_app_attach_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_upload_app_attach_proto_goTypes = []any{
(*SKBuiltinBufferT)(nil), // 0: wxproto.SKBuiltinBufferT
(*UploadAppAttachRequest)(nil), // 1: wxproto.UploadAppAttachRequest
(*UploadAppAttachResponse)(nil), // 2: wxproto.UploadAppAttachResponse
(*ReplyMsgHeader)(nil), // 3: wxproto.ReplyMsgHeader
(*BaseResponse)(nil), // 4: wxproto.BaseResponse
}
var file_upload_app_attach_proto_depIdxs = []int32{
3, // 0: wxproto.UploadAppAttachRequest.baseRequest:type_name -> wxproto.ReplyMsgHeader
0, // 1: wxproto.UploadAppAttachRequest.data:type_name -> wxproto.SKBuiltinBufferT
4, // 2: wxproto.UploadAppAttachResponse.baseResponse:type_name -> wxproto.BaseResponse
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_upload_app_attach_proto_init() }
func file_upload_app_attach_proto_init() {
if File_upload_app_attach_proto != nil {
return
}
file_app_msg_proto_init()
file_common_resp_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_upload_app_attach_proto_rawDesc), len(file_upload_app_attach_proto_rawDesc)),
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_upload_app_attach_proto_goTypes,
DependencyIndexes: file_upload_app_attach_proto_depIdxs,
MessageInfos: file_upload_app_attach_proto_msgTypes,
}.Build()
File_upload_app_attach_proto = out.File
file_upload_app_attach_proto_goTypes = nil
file_upload_app_attach_proto_depIdxs = nil
}
+388
View File
@@ -0,0 +1,388 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v3.5.1
// source: voice_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 WxSendVoiceMsg struct {
state protoimpl.MessageState `protogen:"open.v1"`
FromUser string `protobuf:"bytes,1,opt,name=from_user,json=fromUser,proto3" json:"from_user,omitempty"`
ToUser string `protobuf:"bytes,2,opt,name=to_user,json=toUser,proto3" json:"to_user,omitempty"`
Unknown3 int32 `protobuf:"varint,3,opt,name=unknown3,proto3" json:"unknown3,omitempty"`
Unknown4 int32 `protobuf:"varint,4,opt,name=unknown4,proto3" json:"unknown4,omitempty"`
ClientMsgId string `protobuf:"bytes,5,opt,name=client_msg_id,json=clientMsgId,proto3" json:"client_msg_id,omitempty"`
// field 6 skipped
Duration int32 `protobuf:"varint,7,opt,name=duration,proto3" json:"duration,omitempty"`
// field 8 skipped
Unknown9 int32 `protobuf:"varint,9,opt,name=unknown9,proto3" json:"unknown9,omitempty"`
Header *VoiceMsgHeader `protobuf:"bytes,10,opt,name=header,proto3" json:"header,omitempty"`
Unknown11 int32 `protobuf:"varint,11,opt,name=unknown11,proto3" json:"unknown11,omitempty"`
// field 12 skipped
Unknown13 int32 `protobuf:"varint,13,opt,name=unknown13,proto3" json:"unknown13,omitempty"`
// field 14 skipped
Unknown15 int32 `protobuf:"varint,15,opt,name=unknown15,proto3" json:"unknown15,omitempty"`
Unknown16 int32 `protobuf:"varint,16,opt,name=unknown16,proto3" json:"unknown16,omitempty"`
Unknown17 int64 `protobuf:"varint,17,opt,name=unknown17,proto3" json:"unknown17,omitempty"`
// fields 18-19 skipped
AesKey []byte `protobuf:"bytes,20,opt,name=aes_key,json=aesKey,proto3" json:"aes_key,omitempty"`
CdnKey []byte `protobuf:"bytes,21,opt,name=cdn_key,json=cdnKey,proto3" json:"cdn_key,omitempty"`
// fields 22-23 skipped
Unknown24 []byte `protobuf:"bytes,24,opt,name=unknown24,proto3" json:"unknown24,omitempty"`
Unknown25 int32 `protobuf:"varint,25,opt,name=unknown25,proto3" json:"unknown25,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WxSendVoiceMsg) Reset() {
*x = WxSendVoiceMsg{}
mi := &file_voice_msg_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *WxSendVoiceMsg) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WxSendVoiceMsg) ProtoMessage() {}
func (x *WxSendVoiceMsg) ProtoReflect() protoreflect.Message {
mi := &file_voice_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 WxSendVoiceMsg.ProtoReflect.Descriptor instead.
func (*WxSendVoiceMsg) Descriptor() ([]byte, []int) {
return file_voice_msg_proto_rawDescGZIP(), []int{0}
}
func (x *WxSendVoiceMsg) GetFromUser() string {
if x != nil {
return x.FromUser
}
return ""
}
func (x *WxSendVoiceMsg) GetToUser() string {
if x != nil {
return x.ToUser
}
return ""
}
func (x *WxSendVoiceMsg) GetUnknown3() int32 {
if x != nil {
return x.Unknown3
}
return 0
}
func (x *WxSendVoiceMsg) GetUnknown4() int32 {
if x != nil {
return x.Unknown4
}
return 0
}
func (x *WxSendVoiceMsg) GetClientMsgId() string {
if x != nil {
return x.ClientMsgId
}
return ""
}
func (x *WxSendVoiceMsg) GetDuration() int32 {
if x != nil {
return x.Duration
}
return 0
}
func (x *WxSendVoiceMsg) GetUnknown9() int32 {
if x != nil {
return x.Unknown9
}
return 0
}
func (x *WxSendVoiceMsg) GetHeader() *VoiceMsgHeader {
if x != nil {
return x.Header
}
return nil
}
func (x *WxSendVoiceMsg) GetUnknown11() int32 {
if x != nil {
return x.Unknown11
}
return 0
}
func (x *WxSendVoiceMsg) GetUnknown13() int32 {
if x != nil {
return x.Unknown13
}
return 0
}
func (x *WxSendVoiceMsg) GetUnknown15() int32 {
if x != nil {
return x.Unknown15
}
return 0
}
func (x *WxSendVoiceMsg) GetUnknown16() int32 {
if x != nil {
return x.Unknown16
}
return 0
}
func (x *WxSendVoiceMsg) GetUnknown17() int64 {
if x != nil {
return x.Unknown17
}
return 0
}
func (x *WxSendVoiceMsg) GetAesKey() []byte {
if x != nil {
return x.AesKey
}
return nil
}
func (x *WxSendVoiceMsg) GetCdnKey() []byte {
if x != nil {
return x.CdnKey
}
return nil
}
func (x *WxSendVoiceMsg) GetUnknown24() []byte {
if x != nil {
return x.Unknown24
}
return nil
}
func (x *WxSendVoiceMsg) GetUnknown25() int32 {
if x != nil {
return x.Unknown25
}
return 0
}
type VoiceMsgHeader struct {
state protoimpl.MessageState `protogen:"open.v1"`
Flag []byte `protobuf:"bytes,1,opt,name=flag,proto3" json:"flag,omitempty"`
SessionId int64 `protobuf:"varint,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
ClientProof []byte `protobuf:"bytes,3,opt,name=client_proof,json=clientProof,proto3" json:"client_proof,omitempty"`
DeviceId int64 `protobuf:"varint,4,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"`
Platform []byte `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"`
Version int32 `protobuf:"varint,6,opt,name=version,proto3" json:"version,omitempty"`
// field 7 skipped
Unknown8 int32 `protobuf:"varint,8,opt,name=unknown8,proto3" json:"unknown8,omitempty"`
// fields 9-14 skipped
Unknown15 int32 `protobuf:"varint,15,opt,name=unknown15,proto3" json:"unknown15,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *VoiceMsgHeader) Reset() {
*x = VoiceMsgHeader{}
mi := &file_voice_msg_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *VoiceMsgHeader) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VoiceMsgHeader) ProtoMessage() {}
func (x *VoiceMsgHeader) ProtoReflect() protoreflect.Message {
mi := &file_voice_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 VoiceMsgHeader.ProtoReflect.Descriptor instead.
func (*VoiceMsgHeader) Descriptor() ([]byte, []int) {
return file_voice_msg_proto_rawDescGZIP(), []int{1}
}
func (x *VoiceMsgHeader) GetFlag() []byte {
if x != nil {
return x.Flag
}
return nil
}
func (x *VoiceMsgHeader) GetSessionId() int64 {
if x != nil {
return x.SessionId
}
return 0
}
func (x *VoiceMsgHeader) GetClientProof() []byte {
if x != nil {
return x.ClientProof
}
return nil
}
func (x *VoiceMsgHeader) GetDeviceId() int64 {
if x != nil {
return x.DeviceId
}
return 0
}
func (x *VoiceMsgHeader) GetPlatform() []byte {
if x != nil {
return x.Platform
}
return nil
}
func (x *VoiceMsgHeader) GetVersion() int32 {
if x != nil {
return x.Version
}
return 0
}
func (x *VoiceMsgHeader) GetUnknown8() int32 {
if x != nil {
return x.Unknown8
}
return 0
}
func (x *VoiceMsgHeader) GetUnknown15() int32 {
if x != nil {
return x.Unknown15
}
return 0
}
var File_voice_msg_proto protoreflect.FileDescriptor
const file_voice_msg_proto_rawDesc = "" +
"\n" +
"\x0fvoice_msg.proto\x12\awxproto\"\x8f\x04\n" +
"\x0eWxSendVoiceMsg\x12\x1b\n" +
"\tfrom_user\x18\x01 \x01(\tR\bfromUser\x12\x17\n" +
"\ato_user\x18\x02 \x01(\tR\x06toUser\x12\x1a\n" +
"\bunknown3\x18\x03 \x01(\x05R\bunknown3\x12\x1a\n" +
"\bunknown4\x18\x04 \x01(\x05R\bunknown4\x12\"\n" +
"\rclient_msg_id\x18\x05 \x01(\tR\vclientMsgId\x12\x1a\n" +
"\bduration\x18\a \x01(\x05R\bduration\x12\x1a\n" +
"\bunknown9\x18\t \x01(\x05R\bunknown9\x12/\n" +
"\x06header\x18\n" +
" \x01(\v2\x17.wxproto.VoiceMsgHeaderR\x06header\x12\x1c\n" +
"\tunknown11\x18\v \x01(\x05R\tunknown11\x12\x1c\n" +
"\tunknown13\x18\r \x01(\x05R\tunknown13\x12\x1c\n" +
"\tunknown15\x18\x0f \x01(\x05R\tunknown15\x12\x1c\n" +
"\tunknown16\x18\x10 \x01(\x05R\tunknown16\x12\x1c\n" +
"\tunknown17\x18\x11 \x01(\x03R\tunknown17\x12\x17\n" +
"\aaes_key\x18\x14 \x01(\fR\x06aesKey\x12\x17\n" +
"\acdn_key\x18\x15 \x01(\fR\x06cdnKey\x12\x1c\n" +
"\tunknown24\x18\x18 \x01(\fR\tunknown24\x12\x1c\n" +
"\tunknown25\x18\x19 \x01(\x05R\tunknown25\"\xf3\x01\n" +
"\x0eVoiceMsgHeader\x12\x12\n" +
"\x04flag\x18\x01 \x01(\fR\x04flag\x12\x1d\n" +
"\n" +
"session_id\x18\x02 \x01(\x03R\tsessionId\x12!\n" +
"\fclient_proof\x18\x03 \x01(\fR\vclientProof\x12\x1b\n" +
"\tdevice_id\x18\x04 \x01(\x03R\bdeviceId\x12\x1a\n" +
"\bplatform\x18\x05 \x01(\fR\bplatform\x12\x18\n" +
"\aversion\x18\x06 \x01(\x05R\aversion\x12\x1a\n" +
"\bunknown8\x18\b \x01(\x05R\bunknown8\x12\x1c\n" +
"\tunknown15\x18\x0f \x01(\x05R\tunknown15B>Z<github.com/yincongcyincong/weixin-macos/onebot/proto;wxprotob\x06proto3"
var (
file_voice_msg_proto_rawDescOnce sync.Once
file_voice_msg_proto_rawDescData []byte
)
func file_voice_msg_proto_rawDescGZIP() []byte {
file_voice_msg_proto_rawDescOnce.Do(func() {
file_voice_msg_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_voice_msg_proto_rawDesc), len(file_voice_msg_proto_rawDesc)))
})
return file_voice_msg_proto_rawDescData
}
var file_voice_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_voice_msg_proto_goTypes = []any{
(*WxSendVoiceMsg)(nil), // 0: wxproto.WxSendVoiceMsg
(*VoiceMsgHeader)(nil), // 1: wxproto.VoiceMsgHeader
}
var file_voice_msg_proto_depIdxs = []int32{
1, // 0: wxproto.WxSendVoiceMsg.header:type_name -> wxproto.VoiceMsgHeader
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_voice_msg_proto_init() }
func file_voice_msg_proto_init() {
if File_voice_msg_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_voice_msg_proto_rawDesc), len(file_voice_msg_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_voice_msg_proto_goTypes,
DependencyIndexes: file_voice_msg_proto_depIdxs,
MessageInfos: file_voice_msg_proto_msgTypes,
}.Build()
File_voice_msg_proto = out.File
file_voice_msg_proto_goTypes = nil
file_voice_msg_proto_depIdxs = nil
}
+46 -1
View File
@@ -93,6 +93,7 @@ function initAddresses() {
setImmediate(setupSendTextMessageDynamic);
setImmediate(setupSendFileMessageDynamic);
setImmediate(setupSendFileUploadMessageDynamic);
setImmediate(setupSendAppAttachMessageDynamic);
setImmediate(attachBlrX8Hook);
setImmediate(AttachSendFunc);
setImmediate(attachReq2buf);
@@ -332,6 +333,8 @@ var replyProtoHexGlobal = "";
// 文件消息protobuf全局变量 (从Go直接传入hex编码)
var fileProtoHexGlobal = "";
var fileUploadProtoHexGlobal = "";
// uploadappattach protobuf全局变量 (从Go直接传入hex编码)
var appAttachProtoHexGlobal = "";
// 文件消息全局变量
var fileCgiAddr = ptr(0);
@@ -345,6 +348,11 @@ var fileUploadCgiAddr = ptr(0);
var sendFileUploadMessageAddr = ptr(0);
var fileUploadMessageAddr = ptr(0);
// uploadappattach 全局变量
var appAttachCgiAddr = ptr(0);
var sendAppAttachMessageAddr = ptr(0);
var appAttachMessageAddr = ptr(0);
// 回复消息全局变量
var replyMessageCallbackFunc;
var replyCgiAddr = ptr(0);
@@ -362,7 +370,7 @@ function setupSendTextMessageDynamic() {
textCgiAddr = Memory.alloc(128);
sendTextMessageAddr = Memory.alloc(256);
textMessageAddr = Memory.alloc(256);
textProtoDataAddr = Memory.alloc(4096);
textProtoDataAddr = Memory.alloc(64 * 1024); // 支持 50KB 分片(uploadappattach)的 protobuf
// A. 写入字符串内容
patchString(textCgiAddr, "/cgi-bin/micromsg-bin/newsendmsg");
@@ -452,6 +460,35 @@ function triggerSendFileUploadMessage(taskId, sender, receiver, protoHex, payloa
return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "fileupload");
}
// -------------------------uploadappattach分区-------------------------
function setupSendAppAttachMessageDynamic() {
appAttachCgiAddr = Memory.alloc(128);
sendAppAttachMessageAddr = Memory.alloc(256);
appAttachMessageAddr = Memory.alloc(256);
patchString(appAttachCgiAddr, "/cgi-bin/micromsg-bin/uploadappattach");
sendAppAttachMessageAddr.add(0x00).writeU64(0);
sendAppAttachMessageAddr.add(0x08).writeU64(0);
sendAppAttachMessageAddr.add(0x10).writeU64(0);
sendAppAttachMessageAddr.add(0x18).writeU64(1);
sendAppAttachMessageAddr.add(0x20).writeU32(taskIdGlobal);
sendAppAttachMessageAddr.add(0x28).writePointer(appAttachMessageAddr);
appAttachMessageAddr.add(0x00).writePointer(fakeVtable);
appAttachMessageAddr.add(0x08).writeU32(taskIdGlobal);
appAttachMessageAddr.add(0x0c).writeU32(0x6e);
appAttachMessageAddr.add(0x10).writeU64(0x3);
appAttachMessageAddr.add(0x18).writePointer(appAttachCgiAddr);
appAttachMessageAddr.add(0x20).writeU64(0x25);
appAttachMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030"));
appAttachMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100"));
}
function triggerUploadAppAttach(taskId, sender, receiver, protoHex, payloadHex) {
return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "appattach");
}
// -------------------------发送文件消息分区-------------------------
@@ -502,6 +539,8 @@ function attachBlrX8Hook() {
protoHex = fileProtoHexGlobal;
} else if (sendMsgType === "fileupload") {
protoHex = fileUploadProtoHexGlobal;
} else if (sendMsgType === "appattach") {
protoHex = appAttachProtoHexGlobal;
} else if (sendMsgType === "voice") {
protoHex = voiceProtoHexGlobal;
}
@@ -587,6 +626,10 @@ function attachReq2buf() {
insertMsgAddr.writePointer(sendFileUploadMessageAddr);
console.log("[+] 发送fileUploadMsg成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendFileUploadMessageAddr +
"[+] Req2Buf 写入后内存预览: " + insertMsgAddr);
} else if (sendMsgType === "appattach") {
insertMsgAddr.writePointer(sendAppAttachMessageAddr);
console.log("[+] 发送uploadAppAttach成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendAppAttachMessageAddr +
"[+] Req2Buf 写入后内存预览: " + insertMsgAddr);
}
}
});
@@ -723,6 +766,7 @@ function triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex,
"voice": { messageAddr: voiceMessageAddr, sendMessageAddr: sendVoiceMessageAddr, cgiAddr: voiceCgiAddr, protoHexSetter: function(h) { voiceProtoHexGlobal = h; } },
"file": { messageAddr: fileMessageAddr, sendMessageAddr: sendFileMessageAddr, cgiAddr: fileCgiAddr, protoHexSetter: function(h) { fileProtoHexGlobal = h; } },
"fileupload": { messageAddr: fileUploadMessageAddr, sendMessageAddr: sendFileUploadMessageAddr, cgiAddr: fileUploadCgiAddr, protoHexSetter: function(h) { fileUploadProtoHexGlobal = h; } },
"appattach": { messageAddr: appAttachMessageAddr, sendMessageAddr: sendAppAttachMessageAddr, cgiAddr: appAttachCgiAddr, protoHexSetter: function(h) { appAttachProtoHexGlobal = h; } },
};
var info = msgAddrInfo[msgType];
@@ -998,6 +1042,7 @@ rpc.exports = {
triggerSendFileMessage: triggerSendFileMessage,
triggerSendFileUploadMessage: triggerSendFileUploadMessage,
triggerUploadFile: triggerUploadFile,
triggerUploadAppAttach: triggerUploadAppAttach,
};
// -------------------------发送图片消息分区-------------------------
+5
View File
@@ -141,6 +141,11 @@ func BuildSendPayload(taskId int64, msgType string) string {
payloadData[16] = 0x10
payloadData[28] = 0x27
payloadData[92] = 0x6E
case "appattach":
payloadData[0] = 0x6E
payloadData[16] = 0x10
payloadData[28] = 0x25
payloadData[92] = 0x6E
case "voice":
payloadData[0] = 0x6E
payloadData[16] = 0x10
+194
View File
@@ -0,0 +1,194 @@
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"os"
"time"
"google.golang.org/protobuf/proto"
wxproto "github.com/yincongcyincong/weixin-macos/onebot/proto"
)
// uploadAppAttachChunkSize 每个分片大小 (参考 we-chat-ipad860 的 50000 字节)
const uploadAppAttachChunkSize = 50000
// BuildUploadAppAttachChunks 读取文件并按分片构造 uploadappattach 请求的 protobuf hex 列表。
// 返回: 每个分片的 protobuf hex、填充好基础信息的 FileInfo、错误。
// 参考 we-chat-ipad860 models/Tools/UploadApp.go 的 SendUploadAppAttach。
func BuildUploadAppAttachChunks(receiver, filePath string) ([]string, *FileInfo, error) {
fileData, err := os.ReadFile(filePath)
if err != nil {
return nil, nil, fmt.Errorf("读取文件失败: %w", err)
}
total := len(fileData)
if total == 0 {
return nil, nil, fmt.Errorf("文件为空: %s", filePath)
}
sum := md5.Sum(fileData)
fileMd5 := hex.EncodeToString(sum[:])
// clientAppDataId: {receiver}_{ts}_UploadFile (对齐 iPad860)
clientAppDataId := fmt.Sprintf("%s_%d_UploadFile", receiver, time.Now().Unix())
fileInfo := &FileInfo{
FileName: extractFileName(filePath),
TotalLen: int64(total),
FileExt: getFileExt(filePath),
Md5: fileMd5,
ClientMsgId: clientAppDataId,
}
var hexChunks []string
for startPos := 0; startPos < total; startPos += uploadAppAttachChunkSize {
end := startPos + uploadAppAttachChunkSize
if end > total {
end = total
}
chunk := fileData[startPos:end]
hexStr, err := buildUploadAppAttachChunk(receiver, clientAppDataId, fileMd5,
uint32(total), uint32(startPos), chunk)
if err != nil {
return nil, nil, err
}
hexChunks = append(hexChunks, hexStr)
}
return hexChunks, fileInfo, nil
}
// buildUploadAppAttachChunk 构造单个分片的 UploadAppAttachRequest protobuf hex。
func buildUploadAppAttachChunk(receiver, clientAppDataId, fileMd5 string,
totalLen, startPos uint32, chunk []byte) (string, error) {
version := NextVersion()
appId := ""
sdkVersion := uint32(0)
dataLen := uint32(len(chunk))
fileType := uint32(6) // 6 = 文件
req := &wxproto.UploadAppAttachRequest{
BaseRequest: &wxproto.ReplyMsgHeader{
Flag: []byte{0x00},
SessionId: &globalSessionId,
ClientProof: globalClientProof,
DeviceId: &globalDeviceId,
Platform: proto.String("UnifiedPCMac 26 arm64"),
Version: &version,
},
AppId: &appId,
SdkVersion: &sdkVersion,
ClientAppDataId: &clientAppDataId,
UserName: &receiver,
TotalLen: &totalLen,
StartPos: &startPos,
DataLen: &dataLen,
Data: &wxproto.SKBuiltinBufferT{
ILen: &dataLen,
Buffer: chunk,
},
Type: &fileType,
Md5: &fileMd5,
}
data, err := proto.Marshal(req)
if err != nil {
return "", fmt.Errorf("marshal uploadappattach chunk failed: %w", err)
}
//fmt.Printf("[uploadappattach-proto] startPos=%d dataLen=%d totalLen=%d len=%d\n%s\n",
// startPos, dataLen, totalLen, len(data), HexDump(data, 0))
return hex.EncodeToString(data), nil
}
// ParseUploadAppAttachResponse 解析 uploadappattach 响应,提取 mediaId(attachid)。
func ParseUploadAppAttachResponse(data []byte) (string, error) {
// fmt.Printf("[uploadappattach-resp] len=%d\n%s\n", len(data), HexDump(data, 0))
resp := &wxproto.UploadAppAttachResponse{}
if err := proto.Unmarshal(data, resp); err != nil {
return "", fmt.Errorf("unmarshal uploadappattach response failed: %w", err)
}
if resp.BaseResponse != nil && resp.BaseResponse.Ret != nil && *resp.BaseResponse.Ret != 0 {
errMsg := ""
if resp.BaseResponse.ErrMsg != nil {
errMsg = resp.BaseResponse.ErrMsg.GetMsg()
}
return "", fmt.Errorf("uploadappattach failed, ret=%d, errMsg=%s",
*resp.BaseResponse.Ret, errMsg)
}
return resp.GetMediaId(), nil
}
// BuildSimpleFileMsgProto 构建发送文件消息的 protobufuploadappattach 直传后用)。
// 严格按 wechat7016 WXSendMsgFile / SendAppMsgRequest 格式:
// - 顶层 SendAppMsgReq{baseRequest, msg=AppMsgBody{...}}
// - AppMsgBody 只填 fromUserName/toUserName/type=6/content(XML)/clientMsgId/createTime
// - appmsg XML 极简:只有 title/type/appattach(totallen/attachid/fileext),不含 cdnattachurl
func BuildSimpleFileMsgProto(sender, receiver string, fileInfo *FileInfo) (string, error) {
now := time.Now().Unix()
// wechat7016 WXSendMsgFile 的极简 XMLattachid 用完整 mediaId,不加 cdnattachurl
xml := `<?xml version="1.0"?>` + "\n"
xml += `<appmsg appid='' sdkver=''>`
xml += `<title>` + escapeXmlStr(fileInfo.FileName) + `</title>`
xml += `<des></des>`
xml += `<action></action>`
xml += `<type>6</type>`
xml += `<content></content>`
xml += `<url></url>`
xml += `<lowurl></lowurl>`
xml += `<appattach>`
xml += `<totallen>` + fmt.Sprintf("%d", fileInfo.TotalLen) + `</totallen>`
xml += `<attachid>` + escapeXmlStr(fileInfo.AttachId) + `</attachid>`
xml += `<fileext>` + escapeXmlStr(fileInfo.FileExt) + `</fileext>`
xml += `</appattach>`
xml += `<extinfo></extinfo>`
xml += `</appmsg>`
version := NextVersion()
appId := ""
sdkVersion := uint32(0)
msgType := uint32(6) // 6 = 文件
clientMsgId := fmt.Sprintf("%d", now)
msgSource := "<msgsource><alnode><fr>1</fr><cf>2</cf></alnode></msgsource>"
req := &wxproto.SendAppMsgReq{
BaseRequest: &wxproto.ReplyMsgHeader{
Flag: []byte{0x00},
SessionId: &globalSessionId,
ClientProof: globalClientProof,
DeviceId: &globalDeviceId,
Platform: proto.String("UnifiedPCMac 26 arm64"),
Version: &version,
},
Msg: &wxproto.AppMsgBody{
FromUserName: &sender,
AppId: &appId,
SdkVersion: &sdkVersion,
ToUserName: &receiver,
Type: &msgType,
Content: &xml,
CreateTime: proto.Int64(now),
ClientMsgId: &clientMsgId,
MsgSource: &msgSource,
},
}
data, err := proto.Marshal(req)
if err != nil {
return "", fmt.Errorf("marshal SendAppMsgReq failed: %w", err)
}
// fmt.Printf("[simple-file-proto] xml=%s\n[simple-file-proto] hex dump:\n%s\n", xml, HexDump(data, 0))
return hex.EncodeToString(data), nil
}
+114
View File
@@ -119,3 +119,117 @@ func BuildUploadPayload(uploadType string) string {
return hex.EncodeToString(payload)
}
// BuildVoiceUploadPayload 构建语音上传的payload模板,返回hex编码字符串
// 基于WeChat实际内存dump校正
// JS侧会在固定偏移写入运行时指针: 0x00, 0x08, 0x48(voiceIdAddr), 0x50(voiceId长度), 0x58(voiceId容量), 0x68(receiver), 0x100(audioData), 0x108(audioLen), 0x110(audioCap)
func BuildVoiceUploadPayload() string {
payload := []byte{
// 0x00 (被uploadFunc1Addr覆盖)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x08 (被uploadFunc2Addr覆盖)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x10
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x18
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x20
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x28
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x30
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x38
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x40
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
// 0x48 (被voiceIdAddr覆盖)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x50 voiceId length (JS实时写入)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x58 voiceId capacity (JS实时写入)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
// 0x60
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x68 (被receiver覆盖, inline string区域)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x70
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x78 [0x7F]=receiver length
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13,
// 0x80
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x88
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x90
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x98 [0x9C]=0x0F voice type
0x01, 0xAA, 0xAA, 0xAA, 0x0F, 0x00, 0x00, 0x00,
// 0xA0
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
// 0xA8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0xB0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0xB8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0xC0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0xC8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0xD0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0xD8 音频参数
0x10, 0x0E, 0x00, 0x00, 0x08, 0x07, 0x00, 0x00,
// 0xE0 文件大小(-1=未知)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xE8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0xF0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0xF8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x100 (被voiceAudioDataAddr覆盖 - 音频数据指针)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x108 音频数据长度 (JS写入)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x110 音频数据容量 (JS写入, 带high bit)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
// 0x118
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x120
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x128
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x130
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x138
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x140
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x148
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x150
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x158
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x160
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x168
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x170 [0x174]=0x01
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
// 0x178
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x180
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x188
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x190
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x198
0x00, 0xAA, 0xAA, 0xAA, 0x01, 0x00, 0x00, 0x00,
}
return hex.EncodeToString(payload)
}
+193
View File
@@ -61,6 +61,199 @@ func SaveBase64Image(base64Data string) (string, string, error) {
return targetPath, md5Str, nil
}
// SaveVoiceFile 解码base64音频数据并保存为文件(不追加salt,保持二进制完整性)
// 返回原始字节、文件路径、错误
func SaveVoiceFile(base64Data string) ([]byte, string, error) {
rawContents := base64Data
if strings.HasPrefix(base64Data, "base64://") {
rawContents = strings.TrimPrefix(base64Data, "base64://")
} else if idx := strings.Index(base64Data, ","); idx != -1 {
rawContents = base64Data[idx+1:]
}
data, err := base64.StdEncoding.DecodeString(rawContents)
if err != nil {
return nil, "", fmt.Errorf("base64 decode failed: %v", err)
}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
randomNumber := r.Intn(1000)
timestamp := time.Now().Unix()
ext := DetectFileFormat(data)
fileName := fmt.Sprintf("%d_%d.%s", randomNumber, timestamp, ext)
targetPath := config.ImagePath + fileName
dir := filepath.Dir(targetPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, "", fmt.Errorf("create directory failed: %v", err)
}
err = os.WriteFile(targetPath, data, 0666)
if err != nil {
return nil, "", fmt.Errorf("write file failed: %v", err)
}
os.Chmod(targetPath, 0666)
return data, targetPath, nil
}
// SaveBase64File 解码 base64 数据并以指定扩展名保存文件,返回文件路径和 MD5
func SaveBase64File(base64Data string, ext string) (string, string, error) {
rawContents := base64Data
if strings.HasPrefix(base64Data, "base64://") {
rawContents = strings.TrimPrefix(base64Data, "base64://")
} else if idx := strings.Index(base64Data, ","); idx != -1 {
rawContents = base64Data[idx+1:]
}
data, err := base64.StdEncoding.DecodeString(rawContents)
if err != nil {
return "", "", fmt.Errorf("base64 decode failed: %v", err)
}
// 如果没有传入扩展名,尝试自动检测
if ext == "" {
ext = DetectFileFormat(data)
if ext == "unknown" {
// fallback: 用 MIME 类型推断
mimeType := http.DetectContentType(data)
ext = mimeToExt(mimeType)
}
}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
randomNumber := r.Intn(1000)
timestamp := time.Now().Unix()
fileName := fmt.Sprintf("%d_%d.%s", randomNumber, timestamp, ext)
targetPath := config.ImagePath + fileName
dir := filepath.Dir(targetPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return "", "", fmt.Errorf("create directory failed: %v", err)
}
err = os.WriteFile(targetPath, data, 0666)
if err != nil {
return "", "", fmt.Errorf("write file failed: %v", err)
}
os.Chmod(targetPath, 0666)
md5Str, err := GetFileMD5(targetPath)
if err != nil {
return "", "", fmt.Errorf("get file md5 failed: %v", err)
}
return targetPath, md5Str, nil
}
// mimeToExt 将 MIME 类型转换为文件扩展名
func mimeToExt(mimeType string) string {
if idx := strings.Index(mimeType, ";"); idx != -1 {
mimeType = strings.TrimSpace(mimeType[:idx])
}
switch mimeType {
case "text/plain":
return "txt"
case "text/html":
return "html"
case "text/xml", "application/xml":
return "xml"
case "application/json":
return "json"
case "application/pdf":
return "pdf"
case "application/zip":
return "zip"
case "application/gzip":
return "gz"
case "image/jpeg":
return "jpg"
case "image/png":
return "png"
case "image/gif":
return "gif"
case "image/webp":
return "webp"
case "video/mp4":
return "mp4"
case "audio/mpeg":
return "mp3"
default:
return "bin"
}
}
// ConvertToSilk 将音频数据(任意格式)通过ffmpeg转为PCM,再编码为SILK格式
// 微信要求格式: \x02#!SILK_V3 开头 (tencent silk)
// 如果输入已经是该格式,则直接返回
// 返回: silkData, 时长(毫秒), error
func ConvertToSilk(audioData []byte) ([]byte, int32, error) {
// 已经是tencent SILK格式 (\x02#!SILK_V3),直接返回,时长未知设为0
if len(audioData) > 10 && audioData[0] == 0x02 && bytes.HasPrefix(audioData[1:], []byte("#!SILK_V3")) {
return audioData, 0, nil
}
// 先用ffmpeg将输入音频转为PCM (s16le, 16000Hz, mono)
cmd := exec.Command("ffmpeg",
"-i", "pipe:0",
"-f", "s16le",
"-ar", "16000",
"-ac", "1",
"pipe:1",
)
cmd.Stdin = bytes.NewReader(audioData)
var out bytes.Buffer
cmd.Stdout = &out
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return nil, 0, fmt.Errorf("ffmpeg to pcm error: %v, details: %s", err, stderr.String())
}
pcmBytes := out.Bytes()
// 时长(ms) = pcm字节数 * 1000 / (采样率 * 通道数 * 每样本字节数)
durationMs := int32(int64(len(pcmBytes)) * 1000 / (16000 * 2))
// 尝试使用外部silk-encoder(和微信兼容性更好)
silkData, err := encodeSilkExternal(pcmBytes)
if err != nil {
// fallback: 使用go-silk库
silkData, err = silk.EncodePcmBuffToSilk(pcmBytes, 16000, 16000, true)
if err != nil {
return nil, 0, fmt.Errorf("encode silk error: %v", err)
}
}
return silkData, durationMs, nil
}
// encodeSilkExternal 使用外部pilk(Python)工具编码pcm->silk(和微信兼容)
func encodeSilkExternal(pcmBytes []byte) ([]byte, error) {
tmpPcm, err := os.CreateTemp("", "voice_*.pcm")
if err != nil {
return nil, err
}
defer os.Remove(tmpPcm.Name())
if _, err := tmpPcm.Write(pcmBytes); err != nil {
tmpPcm.Close()
return nil, err
}
tmpPcm.Close()
tmpSilk := tmpPcm.Name() + ".silk"
defer os.Remove(tmpSilk)
pyScript := fmt.Sprintf(`import pilk; pilk.encode("%s", "%s", pcm_rate=16000, tencent=True)`, tmpPcm.Name(), tmpSilk)
cmd := exec.Command("python3", "-c", pyScript)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("pilk encode failed: %v, %s", err, stderr.String())
}
return os.ReadFile(tmpSilk)
}
// GetVideoDuration 使用ffprobe获取视频时长(秒)
func GetVideoDuration(filePath string) (int32, error) {
cmd := exec.Command("ffprobe",
+67
View File
@@ -0,0 +1,67 @@
package main
import (
"encoding/hex"
"fmt"
"time"
"github.com/google/uuid"
wxproto "github.com/yincongcyincong/weixin-macos/onebot/proto"
"google.golang.org/protobuf/proto"
)
// BuildVoiceMsgProto 构建发送语音消息的protobuf并返回hex编码的字符串
func BuildVoiceMsgProto(sender, targetId, cdnKey, aesKey string, voiceDuration int32, silkDataLen int32, unknown13 int32) (string, error) {
// client_msg_id: UUID格式
clientMsgId := uuid.New().String()
msg := &wxproto.WxSendVoiceMsg{
FromUser: sender,
ToUser: targetId,
Unknown3: 0,
Unknown4: silkDataLen,
ClientMsgId: clientMsgId,
Duration: voiceDuration,
Unknown9: 1,
Header: &wxproto.VoiceMsgHeader{
Flag: []byte{0x00},
SessionId: int64(globalSessionId),
ClientProof: globalClientProof,
DeviceId: int64(globalDeviceId),
Platform: []byte("UnifiedPCMac 26 arm64"),
Version: int32(NextVersion()),
Unknown8: 4,
Unknown15: 0,
},
Unknown11: 0,
Unknown13: 4,
Unknown15: 0,
Unknown16: 0,
Unknown17: time.Now().Unix(),
CdnKey: []byte(cdnKey),
AesKey: []byte(aesKey),
Unknown24: []byte{},
Unknown25: 0,
}
data, err := proto.Marshal(msg)
if err != nil {
return "", fmt.Errorf("marshal voice proto failed: %w", err)
}
// 手动追加proto3不会序列化的0值字段
// field 3 (unknown3=0)
data = appendZeroVarintField(data, 3)
// field 11 (unknown11=0)
data = appendZeroVarintField(data, 11)
// field 15 (unknown15=0)
data = appendZeroVarintField(data, 15)
// field 16 (unknown16=0)
data = appendZeroVarintField(data, 16)
// field 25 (unknown25=0)
data = appendZeroVarintField(data, 25)
//fmt.Printf("[voice-proto] final protobuf hex dump:\n%s\n", HexDump(data, 0))
return hex.EncodeToString(data), nil
}
+127
View File
@@ -195,6 +195,126 @@ func SendWechatMsg(m *SendMsg) {
sendErr = errors.New("send reply failed")
return
}
case "voice":
// 直接base64解码,不追加salt(音频二进制不能被修改)
rawAudio, targetPath, err := SaveVoiceFile(m.Content)
if err != nil {
Error("保存语音文件失败", "err", err)
sendErr = err
return
}
// 转换为SILK格式
silkData, voiceDurationMs, err := ConvertToSilk(rawAudio)
if err != nil {
Error("转换SILK格式失败", "err", err)
sendErr = err
return
}
audioHex := hex.EncodeToString(silkData)
uploadPayloadHex := BuildVoiceUploadPayload()
result := fridaScript.ExportsCall("triggerUploadVoice", targetId, targetPath, uploadPayloadHex, audioHex, voiceDurationMs)
Info("📩 上传语音任务执行结果", "result", result, "target_id", targetId, "path", targetPath, "silk_len", len(silkData), "duration_ms", voiceDurationMs)
if result != "0" {
Error("上传语音失败", "target_id", targetId, "result", result)
sendErr = errors.New("upload voice failed")
return
}
if m.ResultChan != nil {
pendingResultMap.Store(targetId, m.ResultChan)
m.ResultChan = nil
}
return
case "send_voice":
protoHex, err := BuildVoiceMsgProto(myWechatId, targetId, m.CdnKey, m.AesKey, m.VoiceDuration, m.SilkDataLen, m.Unknown13)
if err != nil {
Error("构建语音protobuf失败", "err", err)
sendErr = err
return
}
payloadHex := BuildSendPayload(currTaskId, "voice")
result := fridaScript.ExportsCall("triggerSendVoiceMessage", currTaskId, myWechatId, targetId, protoHex, payloadHex)
Info("📩 发送语音任务执行结果", "result", result, "task_id", currTaskId, "wechat_id", myWechatId, "target_id", targetId, "unknown13", m.Unknown13)
if result != "1" {
Error("发送语音失败", "task_id", currTaskId, "target_id", targetId, "result", result)
sendErr = errors.New("send voice failed")
return
}
case "send_file_simple":
// iPad860 风格: uploadappattach 分片直传 → sendappmsg,不走 CDN。
// 文件名/扩展名由内容自动识别 + 时间戳随机生成(SaveBase64File 内部完成)。
targetPath, _, err := SaveBase64File(m.Content, "")
if err != nil {
Error("保存文件失败", "err", err)
sendErr = err
return
}
chunks, fileInfo, err := BuildUploadAppAttachChunks(targetId, targetPath)
if err != nil {
Error("构建uploadappattach分片失败", "err", err)
sendErr = err
return
}
Info("📩 开始uploadappattach直传", "target_id", targetId, "chunks", len(chunks),
"file_name", fileInfo.FileName, "file_ext", fileInfo.FileExt,
"total_len", fileInfo.TotalLen, "md5", fileInfo.Md5)
var attachId string
for i, chunkHex := range chunks {
chunkTaskId := atomic.AddInt64(&taskId, 1)
payloadHex := BuildSendPayload(chunkTaskId, "appattach")
result := fridaScript.ExportsCall("triggerUploadAppAttach", chunkTaskId, myWechatId, targetId, chunkHex, payloadHex)
if result != "1" {
Error("uploadappattach分片发送失败", "chunk", i, "result", result)
sendErr = errors.New("upload app attach chunk failed")
return
}
select {
case <-ctx.Done():
Error("等待uploadappattach响应超时", "chunk", i)
sendErr = errors.New("upload app attach timeout")
return
case data := <-appAttachRespChan:
id, perr := ParseUploadAppAttachResponse(data)
if perr != nil {
Error("解析uploadappattach响应失败", "chunk", i, "err", perr)
sendErr = perr
return
}
if id != "" {
attachId = id
}
Info("📩 uploadappattach分片完成", "chunk", i, "attach_id", id)
}
}
if attachId == "" {
Error("uploadappattach未返回attachId", "target_id", targetId)
sendErr = errors.New("upload app attach no attachId")
return
}
fileInfo.AttachId = attachId
// sendappmsg (type=6),精简版 appmsgcdnattachurl 也填 attachId
currTaskId = atomic.AddInt64(&taskId, 1)
protoHex, err := BuildSimpleFileMsgProto(myWechatId, targetId, fileInfo)
if err != nil {
Error("构建文件protobuf失败", "err", err)
sendErr = err
return
}
payloadHex := BuildSendPayload(currTaskId, "file")
result := fridaScript.ExportsCall("triggerSendFileMessage", currTaskId, myWechatId, targetId, protoHex, payloadHex)
Info("📩 发送文件消息(simple)执行结果", "result", result, "task_id", currTaskId, "target_id", targetId)
if result != "1" {
Error("发送文件失败(simple)", "task_id", currTaskId, "target_id", targetId, "result", result)
sendErr = errors.New("send file failed")
return
}
}
select {
@@ -383,6 +503,13 @@ func HandleBuf2Resp(msgType string, data []byte) {
return
}
switch msgType {
case "appattach":
Info("收到uploadappattach响应", "data_len", len(data))
appAttachRespChan <- data
return
}
ret, errMsg, err := ParseSendMsgResponse(data)
if err != nil {
Info("buf2resp响应无法提取错误码,视为成功", "msg_type", msgType, "err", err)