Files
2026-07-09 09:42:32 +08:00

163 lines
6.4 KiB
Go

package main
import (
"encoding/binary"
"encoding/hex"
"fmt"
"math/rand"
"time"
wxproto "github.com/yincongcyincong/weixin-macos/onebot/proto"
"google.golang.org/protobuf/proto"
)
// BuildTextMsgProto 构建发送文本消息的protobuf并返回hex编码的字符串
func BuildTextMsgProto(receiver, content, atUser string) (string, error) {
// 构建 msgsource XML
xmlStr := "<msgsource>"
if atUser != "" {
xmlStr += "<atuserlist>" + atUser + "</atuserlist>"
}
xmlStr += "<alnode><fr>1</fr></alnode></msgsource>"
// 生成随机消息ID
msgId := rand.Int63n(1<<34) | (1 << 34)
msg := &wxproto.WxSendTextMsg{
Type: 1,
Body: &wxproto.WxSendTextBody{
Receiver: &wxproto.WxString{Value: receiver},
Content: []byte(content),
Flag: 1,
Timestamp: time.Now().Unix(),
MsgId: msgId,
Xml: []byte(xmlStr),
},
}
data, err := proto.Marshal(msg)
if err != nil {
return "", fmt.Errorf("marshal text proto failed: %w", err)
}
//fmt.Printf("[text-proto] final protobuf hex dump:\n%s\n", HexDump(data, 0))
return hex.EncodeToString(data), nil
}
// BuildSendPayload 构建MMStartTask的完整payload (0x1A0字节),返回hex编码字符串
// msgType: "text", "img", "video"
// 布局: [taskId(4字节)] + [payloadData(412字节)],总长固定0x1A0字节
// JS中的writePointer使用绝对偏移(0x18, 0xb8, 0x190)进行patch
func BuildSendPayload(taskId int64, msgType string) string {
// 公共模板 (412字节,与JS原始img/video payloadData完全一致)
// 差异字节通过下方switch设置: [0],[1],[16],[28],[92],[93]
payloadData := []byte{
0x00, 0x00, 0x00, 0x00, // [0]-[3] 由msgType决定 abs 0x04
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x08
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [16] abs 0x10
0x40, 0xEC, 0x0E, 0x12, 0x01, 0x00, 0x00, 0x00, // abs 0x18 (被cgiAddr覆盖)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [28] abs 0x20
0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // abs 0x28
0x00, 0x01, 0x01, 0x01, 0x00, 0xAA, 0xAA, 0xAA, // abs 0x30
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // abs 0x38
0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // abs 0x40
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xAA, 0xAA, 0xAA, // abs 0x48
0xFF, 0xFF, 0xFF, 0xFF, 0xAA, 0xAA, 0xAA, 0xAA, // abs 0x50
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x58
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [92] abs 0x60
0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, 0x2D, // abs 0x68 "default-"
0x6C, 0x6F, 0x6E, 0x67, 0x6C, 0x69, 0x6E, 0x6B, // abs 0x70 "longlink"
0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x10, // abs 0x78
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x80
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x88
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x90
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x98
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xA0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xA8
0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // abs 0xB0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xB8 (被指针patch覆盖)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xC0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xC8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xD0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xD8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xE0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xE8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xF0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xF8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x100
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x108
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x110
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x118
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x120
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x128
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x130
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x138
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x140
0x01, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // abs 0x148
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x150
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x158
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x160
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x168
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x170
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x178
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x180
0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // abs 0x188
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x190 (被指针patch覆盖)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x198
}
// 根据msgType设置差异字节
switch msgType {
case "text":
payloadData[0] = 0x0A
payloadData[1] = 0x02
payloadData[16] = 0x01
payloadData[28] = 0x20
payloadData[92] = 0x0A
payloadData[93] = 0x02
case "img":
payloadData[0] = 0x6E
payloadData[16] = 0x10
payloadData[28] = 0x22
payloadData[92] = 0x6E
case "video":
payloadData[0] = 0x6E
payloadData[16] = 0x10
payloadData[28] = 0x21
payloadData[92] = 0x6E
case "reply":
payloadData[0] = 0x6E
payloadData[16] = 0x10
payloadData[28] = 0x20
payloadData[92] = 0x6E
case "file":
payloadData[0] = 0x6E
payloadData[16] = 0x10
payloadData[28] = 0x20
payloadData[92] = 0x6E
case "fileupload":
payloadData[0] = 0x6E
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
payloadData[28] = 0x21
payloadData[92] = 0x6E
}
// 构建完整buffer: taskId(4字节) + payloadData(412字节) = 0x1A0字节
payload := make([]byte, 0x1A0)
binary.LittleEndian.PutUint32(payload[0:4], uint32(taskId))
copy(payload[4:], payloadData)
return hex.EncodeToString(payload)
}