mirror of
https://github.com/yincongcyincong/wechat_chatter.git
synced 2026-07-15 10:26:52 +08:00
support openclaw
This commit is contained in:
+41
-2
@@ -86,7 +86,7 @@ function setReceiver() {
|
||||
|
||||
} else {
|
||||
// 处理用户的名称
|
||||
const splitIndex = userContent.indexOf(':')
|
||||
const splitIndex = userContent?.indexOf(':')
|
||||
senderNickname = userContent.substring(0, splitIndex).trim();
|
||||
messages.push({type: "text", data: {text: content}});
|
||||
}
|
||||
@@ -231,4 +231,43 @@ function generateAESKey() {
|
||||
key += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------测试函数-------------------------
|
||||
|
||||
function testGetProtobufRawBytes() {
|
||||
const rawMemoryData = [
|
||||
0x08, 0x00, 0x12, 0xb6, 0x02, 0x08, 0x01, 0x12, 0xb1, 0x02, 0x08, 0x05, 0x12, 0xac, 0x02, 0x08,
|
||||
0xa6, 0x02, 0x12, 0xa6, 0x02, 0x08, 0xec, 0xfc, 0xb9, 0x96, 0x03, 0x12, 0x15, 0x0a, 0x13, 0x77,
|
||||
0x78, 0x69, 0x64, 0x5f, 0x35, 0x79, 0x72, 0x74, 0x6c, 0x79, 0xf6, 0x33, 0x32, 0x68, 0x39, 0x75,
|
||||
0x31, 0x32, 0x1a, 0x08, 0x0a, 0x06, 0x6b, 0x74, 0x73, 0x6b, 0x74, 0x73, 0x20, 0x01, 0x2a, 0x0d,
|
||||
0x0a, 0x0b, 0x6a, 0x64, 0x6a, 0x78, 0x68, 0x64, 0x62, 0x62, 0x64, 0x64, 0x20, 0x30, 0x03, 0x38,
|
||||
0x01, 0x42, 0x02, 0x08, 0x00, 0x48, 0xaa, 0xdb, 0xff, 0xcc, 0x06, 0x52, 0xc7, 0x01, 0x3c, 0x6d,
|
||||
0x73, 0x67, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x3e, 0x0a, 0x09, 0x3c, 0x62, 0x69, 0x7a, 0x66,
|
||||
0x6c, 0x61, 0x67, 0x3e, 0x30, 0x3c, 0x2f, 0x62, 0x69, 0x7a, 0x66, 0x6c, 0x61, 0x67, 0x3e, 0x0a,
|
||||
0x09, 0x3c, 0x70, 0x75, 0x61, 0x3e, 0x31, 0x3c, 0x2f, 0x70, 0x75, 0x61, 0x3e, 0x0a, 0x09, 0x3c,
|
||||
0x65, 0x67, 0x67, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x3e, 0x31, 0x3c, 0x2f, 0x65,
|
||||
0x67, 0x67, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x3e, 0x0a, 0x09, 0x3c, 0x73, 0x69,
|
||||
0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x3e, 0x4e, 0x30, 0x5f, 0x56, 0x31, 0x5f, 0x78, 0x6e,
|
||||
0x68, 0x6b, 0x4e, 0x65, 0x68, 0x4b, 0x7c, 0x76, 0x31, 0x5f, 0x42, 0x4b, 0x71, 0x2b, 0x62, 0x45,
|
||||
0x36, 0x31, 0x3c, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x3e, 0x0a, 0x09,
|
||||
0x3c, 0x74, 0x6d, 0x70, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x70, 0x75,
|
||||
0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x2d, 0x69, 0x64, 0x3e, 0x3c, 0x2f, 0x70, 0x75, 0x62,
|
||||
0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x2d, 0x69, 0x64, 0x3e, 0x3c, 0x2f, 0x70, 0x75, 0x62, 0x6c,
|
||||
];
|
||||
|
||||
const pBuffer = {
|
||||
// 模拟指针读取内存返回 ArrayBuffer
|
||||
readByteArray: function(size) {
|
||||
// 返回模拟数据的 ArrayBuffer 副本
|
||||
const slice = rawMemoryData.slice(0, size);
|
||||
const ab = new ArrayBuffer(slice.length);
|
||||
const view = new Uint8Array(ab);
|
||||
for (let i = 0; i < slice.length; i++) view[i] = slice[i];
|
||||
return ab;
|
||||
}
|
||||
};
|
||||
|
||||
const results = getProtobufRawBytes(pBuffer, rawMemoryData.length);
|
||||
console.log(results);
|
||||
}
|
||||
|
||||
+4
-1
@@ -2,6 +2,9 @@ module github.com/yincongcyincong/weixin-macos/onebot
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require github.com/frida/frida-go v1.0.0
|
||||
require (
|
||||
github.com/frida/frida-go v1.0.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
)
|
||||
|
||||
require github.com/google/uuid v1.6.0 // indirect
|
||||
|
||||
@@ -2,3 +2,5 @@ github.com/frida/frida-go v1.0.0 h1:Xlq1CB8QSAC6zbOFdjCX0oK8RjQGtdl2yATbUQKROwo=
|
||||
github.com/frida/frida-go v1.0.0/go.mod h1:O8Dg1YBGfQsBEL1a8x3GURw/JllJrcuvg78ga2OgdM4=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func sendHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "仅支持 POST", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
req := new(SendRequest)
|
||||
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
|
||||
http.Error(w, "无效的 JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 参数校验
|
||||
if len(req.Message) == 0 || (req.UserID == "" && req.GroupID == "") {
|
||||
http.Error(w, "参数缺失", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
sendContent := ""
|
||||
atUserID := ""
|
||||
for _, v := range req.Message {
|
||||
if v.Type == "text" {
|
||||
sendContent += v.Data.Text
|
||||
} else if v.Type == "at" {
|
||||
if req.GroupID != "" {
|
||||
if nicknameInter, ok := userID2NicknameMap.Load(req.GroupID + "_" + v.Data.QQ); ok {
|
||||
sendContent += fmt.Sprintf("@%s\u2005", nicknameInter.(string))
|
||||
atUserID += v.Data.QQ + ","
|
||||
}
|
||||
}
|
||||
|
||||
} else if v.Type == "image" {
|
||||
msgChan <- &SendMsg{
|
||||
UserId: req.UserID,
|
||||
GroupID: req.GroupID,
|
||||
Content: v.Data.File,
|
||||
Type: v.Type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sendContent != "" {
|
||||
msgChan <- &SendMsg{
|
||||
UserId: req.UserID,
|
||||
GroupID: req.GroupID,
|
||||
Content: sendContent,
|
||||
Type: "text",
|
||||
AtUser: strings.TrimRight(atUserID, ","),
|
||||
}
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]any{
|
||||
"status": "ok",
|
||||
})
|
||||
}
|
||||
|
||||
func SendHttpReq(msg map[string]interface{}) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
// 这里处理你的 X1 数据
|
||||
jsonData, err := json.Marshal(msg["payload"])
|
||||
if err != nil {
|
||||
log.Printf("JSON 序列化失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("发送数据: %s\n", string(jsonData))
|
||||
if myWechatId == "" {
|
||||
m := new(WechatMessage)
|
||||
err = json.Unmarshal(jsonData, m)
|
||||
if err != nil {
|
||||
log.Printf("解析消息失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
myWechatId = m.SelfID
|
||||
|
||||
if m.GroupId != "" {
|
||||
userID2NicknameMap.Store(m.GroupId+"_"+m.UserID, m.Sender.Nickname)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 创建 POST 请求
|
||||
req, err := http.NewRequest("POST", config.SendURL, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
log.Printf("创建请求失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 5. 设置 Header (OneBot 接口通常要求 application/json)
|
||||
h := hmac.New(sha1.New, []byte(config.OnebotToken))
|
||||
h.Write(jsonData)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Signature", "sha1="+hex.EncodeToString(h.Sum(nil)))
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
// 6. 执行请求
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("请求执行失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 7. 读取返回结果
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("读取响应失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("状态码: %d 返回内容: %s\n", resp.StatusCode, string(body))
|
||||
}
|
||||
+38
-294
@@ -2,28 +2,17 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/frida/frida-go/frida"
|
||||
)
|
||||
@@ -90,10 +79,42 @@ type Config struct {
|
||||
WechatPid int `json:"wechat_pid"`
|
||||
OnebotToken string `json:"onebot_token"`
|
||||
ImagePath string `json:"image_path"`
|
||||
ConnType string `json:"conn_type"`
|
||||
|
||||
WechatConf string `json:"wechat_conf"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
initFlag()
|
||||
if config.FridaType == "gadget" {
|
||||
initFridaGadget()
|
||||
} else {
|
||||
initFrida()
|
||||
}
|
||||
go SendWorker()
|
||||
|
||||
http.HandleFunc("/send_private_msg", sendHandler)
|
||||
http.HandleFunc("/send_group_msg", sendHandler)
|
||||
|
||||
http.HandleFunc("/ws", handleWebSocket)
|
||||
http.HandleFunc("/test_ws", testWebSocket)
|
||||
|
||||
stop := make(chan os.Signal, 1)
|
||||
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
<-stop
|
||||
log.Fatalf("\n正在释放 Frida 资源并退出...")
|
||||
}()
|
||||
|
||||
// 3. 启动服务
|
||||
fmt.Printf("HTTP 服务启动在 %s", config.ReceiveHost)
|
||||
if err := http.ListenAndServe(config.ReceiveHost, nil); err != nil {
|
||||
log.Printf("服务启动失败: %v\n", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func initFlag() {
|
||||
flag.StringVar(&config.FridaType, "type", "local", "frida 类型: local | gadget")
|
||||
flag.StringVar(&config.SendURL, "send_url", "http://127.0.0.1:36060/onebot", "发送消息的 URL: http://127.0.0.1:36060/onebot")
|
||||
@@ -103,6 +124,7 @@ func initFlag() {
|
||||
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_7_55_mac.json", "微信配置文件路径: ../wechat_version/4_1_6_12_mac.json")
|
||||
flag.StringVar(&config.ConnType, "conn_type", "http", "连接类型: http | websocket")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
@@ -201,7 +223,11 @@ func loadJs() {
|
||||
if t, ok := pMap["type"]; ok {
|
||||
switch t.(string) {
|
||||
case "send":
|
||||
go SendHttpReq(msg)
|
||||
if config.ConnType == "http" {
|
||||
go SendHttpReq(msg)
|
||||
} else {
|
||||
go SendWebSocketMsg(msg)
|
||||
}
|
||||
case "finish":
|
||||
finishChan <- struct{}{}
|
||||
case "upload":
|
||||
@@ -243,285 +269,3 @@ func loadJs() {
|
||||
fridaScript = script
|
||||
fmt.Printf("✅ Frida 已就绪,微信控制通道已打通")
|
||||
}
|
||||
|
||||
func sendHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "仅支持 POST", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
req := new(SendRequest)
|
||||
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
|
||||
http.Error(w, "无效的 JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 参数校验
|
||||
if len(req.Message) == 0 || (req.UserID == "" && req.GroupID == "") {
|
||||
http.Error(w, "参数缺失", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
sendContent := ""
|
||||
atUserID := ""
|
||||
for _, v := range req.Message {
|
||||
if v.Type == "text" {
|
||||
sendContent += v.Data.Text
|
||||
} else if v.Type == "at" {
|
||||
if req.GroupID != "" {
|
||||
if nicknameInter, ok := userID2NicknameMap.Load(req.GroupID + "_" + v.Data.QQ); ok {
|
||||
sendContent += fmt.Sprintf("@%s\u2005", nicknameInter.(string))
|
||||
atUserID += v.Data.QQ + ","
|
||||
}
|
||||
}
|
||||
|
||||
} else if v.Type == "image" {
|
||||
msgChan <- &SendMsg{
|
||||
UserId: req.UserID,
|
||||
GroupID: req.GroupID,
|
||||
Content: v.Data.File,
|
||||
Type: v.Type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sendContent != "" {
|
||||
msgChan <- &SendMsg{
|
||||
UserId: req.UserID,
|
||||
GroupID: req.GroupID,
|
||||
Content: sendContent,
|
||||
Type: "text",
|
||||
AtUser: strings.TrimRight(atUserID, ","),
|
||||
}
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]any{
|
||||
"status": "ok",
|
||||
})
|
||||
}
|
||||
|
||||
func SendWorker() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("💥 SendWorker 异常: %v\n", err)
|
||||
go SendWorker()
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-finishChan:
|
||||
fmt.Printf("收到完成信号 \n")
|
||||
case m, ok := <-msgChan:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
SendWechatMsg(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SendWechatMsg(m *SendMsg) {
|
||||
time.Sleep(1 * time.Second)
|
||||
currTaskId := atomic.AddInt64(&taskId, 1)
|
||||
log.Printf("📩 收到任务: %d\n", currTaskId)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
targetId := m.UserId
|
||||
if m.GroupID != "" && targetId == "" {
|
||||
targetId = m.GroupID
|
||||
}
|
||||
|
||||
switch m.Type {
|
||||
case "text":
|
||||
result := fridaScript.ExportsCall("triggerSendTextMessage", currTaskId, targetId, m.Content, m.AtUser)
|
||||
log.Printf("📩 发送文本任务执行结果:%s, 参数:currTaskId: %d, targetId: %s, content: %s, atUser: %s\n",
|
||||
result, currTaskId, targetId, m.Content, m.AtUser)
|
||||
case "image":
|
||||
targetPath, md5Str, err := SaveBase64Image(m.Content)
|
||||
if err != nil {
|
||||
log.Printf("保存图片失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
result := fridaScript.ExportsCall("triggerUploadImg", targetId, md5Str, targetPath)
|
||||
log.Printf("📩 上传图片任务执行结果%s, 参数:targetId: %s, md5Str: %s, targetPath: %s\n", result, targetId, md5Str, targetPath)
|
||||
case "send_image":
|
||||
result := fridaScript.ExportsCall("triggerSendImgMessage", currTaskId, myWechatId, targetId)
|
||||
log.Printf("📩 发送图片任务执行结果%s, 参数:currTaskId: %d, myWechatId: %s, targetId: %s\n", result, currTaskId, myWechatId, targetId)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Printf("任务 %d 执行超时!\n", currTaskId)
|
||||
case <-finishChan:
|
||||
log.Printf("收到完成信号,任务 %d 完成\n", currTaskId)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
initFlag()
|
||||
if config.FridaType == "gadget" {
|
||||
initFridaGadget()
|
||||
} else {
|
||||
initFrida()
|
||||
}
|
||||
go SendWorker()
|
||||
|
||||
http.HandleFunc("/send_private_msg", sendHandler)
|
||||
http.HandleFunc("/send_group_msg", sendHandler)
|
||||
|
||||
stop := make(chan os.Signal, 1)
|
||||
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
<-stop
|
||||
log.Fatalf("\n正在释放 Frida 资源并退出...")
|
||||
}()
|
||||
|
||||
// 3. 启动服务
|
||||
fmt.Printf("HTTP 服务启动在 %s", config.ReceiveHost)
|
||||
if err := http.ListenAndServe(config.ReceiveHost, nil); err != nil {
|
||||
log.Printf("服务启动失败: %v\n", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func SendHttpReq(msg map[string]interface{}) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
// 这里处理你的 X1 数据
|
||||
jsonData, err := json.Marshal(msg["payload"])
|
||||
if err != nil {
|
||||
log.Printf("JSON 序列化失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("发送数据: %s\n", string(jsonData))
|
||||
if myWechatId == "" {
|
||||
m := new(WechatMessage)
|
||||
err = json.Unmarshal(jsonData, m)
|
||||
if err != nil {
|
||||
log.Printf("解析消息失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
myWechatId = m.SelfID
|
||||
|
||||
if m.GroupId != "" {
|
||||
userID2NicknameMap.Store(m.GroupId+"_"+m.UserID, m.Sender.Nickname)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 创建 POST 请求
|
||||
req, err := http.NewRequest("POST", config.SendURL, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
log.Printf("创建请求失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 5. 设置 Header (OneBot 接口通常要求 application/json)
|
||||
h := hmac.New(sha1.New, []byte(config.OnebotToken))
|
||||
h.Write(jsonData)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Signature", "sha1="+hex.EncodeToString(h.Sum(nil)))
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
// 6. 执行请求
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("请求执行失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 7. 读取返回结果
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("读取响应失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("状态码: %d 返回内容: %s\n", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
func SaveBase64Image(base64Data 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)
|
||||
}
|
||||
salt := []byte(fmt.Sprintf("\n#md5_salt_%d_%d#", time.Now().UnixNano(), rand.Intn(10000)))
|
||||
data = append(data, salt...)
|
||||
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
randomNumber := r.Intn(1000) // 生成 0-999 的随机数
|
||||
timestamp := time.Now().Unix()
|
||||
fileName := fmt.Sprintf("%d_%d.%s", randomNumber, timestamp, DetectImageFormat(data))
|
||||
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, 0644)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("write file failed: %v", err)
|
||||
}
|
||||
|
||||
md5Str, err := GetFileMD5(targetPath)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("get file md5 failed: %v", err)
|
||||
}
|
||||
|
||||
return targetPath, md5Str, nil
|
||||
}
|
||||
|
||||
func GetFileMD5(filePath string) (string, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hash := md5.New()
|
||||
if _, err := io.Copy(hash, file); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func DetectImageFormat(data []byte) string {
|
||||
if len(data) < 12 {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
switch {
|
||||
case bytes.HasPrefix(data, []byte{0xFF, 0xD8, 0xFF}):
|
||||
return "jpg"
|
||||
case bytes.HasPrefix(data, []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}):
|
||||
return "png"
|
||||
case bytes.HasPrefix(data, []byte("GIF87a")) || bytes.HasPrefix(data, []byte("GIF89a")):
|
||||
return "gif"
|
||||
case bytes.HasPrefix(data, []byte{0x42, 0x4D}):
|
||||
return "bmp"
|
||||
case bytes.HasPrefix(data, []byte("RIFF")) && bytes.HasPrefix(data[8:], []byte("WEBP")):
|
||||
return "webp"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
{
|
||||
"meta": {
|
||||
"lastTouchedVersion": "2026.2.24",
|
||||
"lastTouchedAt": "2026-02-26T06:26:29.404Z"
|
||||
},
|
||||
"wizard": {
|
||||
"lastRunAt": "2026-02-26T02:54:36.789Z",
|
||||
"lastRunVersion": "2026.2.24",
|
||||
"lastRunCommand": "onboard",
|
||||
"lastRunMode": "local"
|
||||
},
|
||||
"models": {
|
||||
"mode": "merge",
|
||||
"providers": {
|
||||
"custom-api-deepseek-com": {
|
||||
"baseUrl": "https://api.deepseek.com/v1",
|
||||
"apiKey": "sk-xxx",
|
||||
"api": "openai-completions",
|
||||
"models": [
|
||||
{
|
||||
"id": "deepseek-chat",
|
||||
"name": "deepseek-chat (Custom Provider)",
|
||||
"reasoning": false,
|
||||
"input": [
|
||||
"text"
|
||||
],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 16000,
|
||||
"maxTokens": 16000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model": {
|
||||
"primary": "custom-api-deepseek-com/deepseek-chat"
|
||||
},
|
||||
"models": {
|
||||
"custom-api-deepseek-com/deepseek-chat": {
|
||||
"alias": "deepseek-chat"
|
||||
}
|
||||
},
|
||||
"workspace": "/Users/yincong/.openclaw/workspace",
|
||||
"compaction": {
|
||||
"mode": "safeguard"
|
||||
},
|
||||
"maxConcurrent": 4,
|
||||
"subagents": {
|
||||
"maxConcurrent": 8
|
||||
}
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"ackReactionScope": "group-mentions"
|
||||
},
|
||||
"commands": {
|
||||
"native": "auto",
|
||||
"nativeSkills": "auto",
|
||||
"restart": true,
|
||||
"ownerDisplay": "raw"
|
||||
},
|
||||
"session": {
|
||||
"dmScope": "per-channel-peer"
|
||||
},
|
||||
"hooks": {
|
||||
"internal": {
|
||||
"enabled": true,
|
||||
"entries": {
|
||||
"command-logger": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"channels": {
|
||||
"telegram": {
|
||||
"enabled": true,
|
||||
"dmPolicy": "pairing",
|
||||
"botToken": "8430228911:xxx",
|
||||
"groupPolicy": "allowlist",
|
||||
"streaming": "off"
|
||||
},
|
||||
"qq": {
|
||||
"wsUrl": "ws://127.0.0.1:58080/ws",
|
||||
"accessToken": "MuseBot",
|
||||
"admins": "12345678,87654321",
|
||||
"adminOnlyChat": false,
|
||||
"notifyNonAdminBlocked": false,
|
||||
"nonAdminBlockedMessage": "当前仅管理员可触发机器人。\n如需使用请联系管理员。",
|
||||
"blockedNotifyCooldownMs": 10000,
|
||||
"showProcessingStatus": true,
|
||||
"processingStatusDelayMs": 500,
|
||||
"processingStatusText": "输入中",
|
||||
"allowedGroups": "10001,10002",
|
||||
"blockedUsers": "999999",
|
||||
"systemPrompt": "你是一个名为“人工智障”的QQ机器人,说话风格要风趣幽默。",
|
||||
"historyLimit": 3,
|
||||
"keywordTriggers": "小助手, 帮助",
|
||||
"autoApproveRequests": true,
|
||||
"enableGuilds": true,
|
||||
"enableTTS": false,
|
||||
"sharedMediaHostDir": "/Users/yourname/openclaw_qq/deploy/napcat/shared_media",
|
||||
"sharedMediaContainerDir": "/openclaw_media",
|
||||
"rateLimitMs": 1000,
|
||||
"formatMarkdown": true,
|
||||
"antiRiskMode": false,
|
||||
"maxMessageLength": 4000
|
||||
}
|
||||
},
|
||||
"gateway": {
|
||||
"port": 18789,
|
||||
"mode": "local",
|
||||
"bind": "loopback",
|
||||
"auth": {
|
||||
"mode": "token",
|
||||
"token": "xxx"
|
||||
},
|
||||
"tailscale": {
|
||||
"mode": "off",
|
||||
"resetOnExit": false
|
||||
},
|
||||
"nodes": {
|
||||
"denyCommands": [
|
||||
"camera.snap",
|
||||
"camera.clip",
|
||||
"screen.record",
|
||||
"calendar.add",
|
||||
"contacts.add",
|
||||
"reminders.add"
|
||||
]
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"allow": [
|
||||
"qq",
|
||||
"telegram"
|
||||
],
|
||||
"entries": {
|
||||
"telegram": {
|
||||
"enabled": true
|
||||
},
|
||||
"qq": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"installs": {
|
||||
"qq": {
|
||||
"source": "path",
|
||||
"sourcePath": "/Users/xxx/go/src/github.com/yincongcyincong/openclaw/extensions/qq",
|
||||
"installPath": "/Users/xxx/.openclaw/extensions/qq",
|
||||
"version": "1.0.0",
|
||||
"installedAt": "2026-02-26T03:12:47.749Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -36,4 +36,24 @@ curl -i -X POST \
|
||||
返回结果:
|
||||
{"status":"ok"}
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
## 接入openclaw
|
||||
```
|
||||
# 进入插件目录
|
||||
cd openclaw/extensions
|
||||
# 克隆仓库
|
||||
git clone https://github.com/constansino/openclaw_qq.git qq
|
||||
# 安装依赖并构建
|
||||
cd ../..
|
||||
pnpm install && pnpm build
|
||||
# 安装openclaw命令
|
||||
npm install -g openclaw@latest
|
||||
# 根据 https://github.com/constansino/openclaw_qq 把extension放到extensions目录下
|
||||
openclaw plugins install ./extensions/qq
|
||||
# 启动openclaw
|
||||
openclaw gateway run
|
||||
# 启动onebot
|
||||
./onebot -wechat_pid=18835 -image_path='/Users/xxx/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_xxx/temp/xxx/2026-01/Img/ -conn_type=websocket
|
||||
```
|
||||
|
||||
|
||||
+25
-12
@@ -1077,8 +1077,14 @@ function setReceiver() {
|
||||
// 3. 开始拦截
|
||||
Interceptor.attach(buf2RespAddr, {
|
||||
onEnter: function (args) {
|
||||
|
||||
const currentPtr = this.context.x1;
|
||||
// console.log(" [+] currentPtr: ", hexdump(currentPtr, {
|
||||
// offset: 0,
|
||||
// length: 512,
|
||||
// header: true,
|
||||
// ansi: true
|
||||
// }));
|
||||
|
||||
let start = 0x1e;
|
||||
let senderLen = currentPtr.add(start).readU8();
|
||||
if (senderLen !== 0x14 && senderLen !== 0x13) {
|
||||
@@ -1114,14 +1120,12 @@ function setReceiver() {
|
||||
var messages = [];
|
||||
var senderNickname = ""
|
||||
|
||||
let splitIndex = content.indexOf(':')
|
||||
let pureContent = content.substring(splitIndex + 1).trim();
|
||||
|
||||
if (sender.includes("@chatroom")) {
|
||||
msgType = "group"
|
||||
groupId = sender
|
||||
|
||||
senderUser = content.substring(0, splitIndex).trim();
|
||||
let splitIndex = content?.indexOf(':')
|
||||
let pureContent = content?.substring(splitIndex + 1).trim();
|
||||
const parts = pureContent.split('\u2005');
|
||||
for (let part of parts) {
|
||||
part = part.trim();
|
||||
@@ -1145,23 +1149,31 @@ function setReceiver() {
|
||||
}
|
||||
|
||||
// 处理用户的名称
|
||||
splitIndex = userContent.indexOf(':')
|
||||
splitIndex = userContent?.indexOf(':')
|
||||
if (splitIndex === -1) {
|
||||
splitIndex = userContent.indexOf('在群聊中@了你')
|
||||
senderNickname = userContent.substring(0, splitIndex).trim();
|
||||
splitIndex = userContent?.indexOf('在群聊中@了你')
|
||||
senderNickname = userContent?.substring(0, splitIndex).trim();
|
||||
} else {
|
||||
senderNickname = userContent.substring(0, splitIndex).trim();
|
||||
senderNickname = userContent?.substring(0, splitIndex).trim();
|
||||
}
|
||||
if (!senderNickname) {
|
||||
senderNickname = sender
|
||||
}
|
||||
|
||||
} else {
|
||||
// 处理用户的名称
|
||||
const splitIndex = userContent.indexOf(':')
|
||||
senderNickname = userContent.substring(0, splitIndex).trim();
|
||||
messages.push({type: "text", data: {text: pureContent}});
|
||||
const splitIndex = userContent?.indexOf(':')
|
||||
senderNickname = userContent?.substring(0, splitIndex).trim();
|
||||
if (!senderNickname) {
|
||||
senderNickname = sender
|
||||
}
|
||||
messages.push({type: "text", data: {text: content}});
|
||||
}
|
||||
|
||||
const msgId = generateAESKey()
|
||||
send({
|
||||
time: Date.now(),
|
||||
post_type: "message",
|
||||
message_type: msgType,
|
||||
user_id: senderUser, // 发送人的 ID
|
||||
self_id: selfId, // 接收人的 ID
|
||||
@@ -1171,6 +1183,7 @@ function setReceiver() {
|
||||
raw: {peerUid: msgId},
|
||||
message: messages,
|
||||
sender: {user_id: senderUser, nickname: senderNickname},
|
||||
raw_message: ""
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SaveBase64Image(base64Data 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)
|
||||
}
|
||||
salt := []byte(fmt.Sprintf("\n#md5_salt_%d_%d#", time.Now().UnixNano(), rand.Intn(10000)))
|
||||
data = append(data, salt...)
|
||||
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
randomNumber := r.Intn(1000) // 生成 0-999 的随机数
|
||||
timestamp := time.Now().Unix()
|
||||
fileName := fmt.Sprintf("%d_%d.%s", randomNumber, timestamp, DetectImageFormat(data))
|
||||
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, 0644)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("write file failed: %v", err)
|
||||
}
|
||||
|
||||
md5Str, err := GetFileMD5(targetPath)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("get file md5 failed: %v", err)
|
||||
}
|
||||
|
||||
return targetPath, md5Str, nil
|
||||
}
|
||||
|
||||
func GetFileMD5(filePath string) (string, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hash := md5.New()
|
||||
if _, err := io.Copy(hash, file); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func DetectImageFormat(data []byte) string {
|
||||
if len(data) < 12 {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
switch {
|
||||
case bytes.HasPrefix(data, []byte{0xFF, 0xD8, 0xFF}):
|
||||
return "jpg"
|
||||
case bytes.HasPrefix(data, []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}):
|
||||
return "png"
|
||||
case bytes.HasPrefix(data, []byte("GIF87a")) || bytes.HasPrefix(data, []byte("GIF89a")):
|
||||
return "gif"
|
||||
case bytes.HasPrefix(data, []byte{0x42, 0x4D}):
|
||||
return "bmp"
|
||||
case bytes.HasPrefix(data, []byte("RIFF")) && bytes.HasPrefix(data[8:], []byte("WEBP")):
|
||||
return "webp"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{}
|
||||
var conn *websocket.Conn
|
||||
|
||||
type OneBotWSMsg struct {
|
||||
Action string `json:"action"`
|
||||
Echo string `json:"echo"`
|
||||
Params *WSParams `json:"params"`
|
||||
}
|
||||
|
||||
type WSParams struct {
|
||||
Message interface{} `json:"message"`
|
||||
UserID string `json:"user_id"`
|
||||
GroupID string `json:"group_id"`
|
||||
}
|
||||
|
||||
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
conn, err = upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println("升级失败:", err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
fmt.Println("机器人已连接!")
|
||||
|
||||
for {
|
||||
m := new(OneBotWSMsg)
|
||||
_, msgByte, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("读取失败:", err)
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Println("收到消息: " + string(msgByte))
|
||||
err = json.Unmarshal(msgByte, m)
|
||||
if err != nil {
|
||||
log.Println("解析失败:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
switch m.Action {
|
||||
case "get_login_info":
|
||||
err = conn.WriteJSON(map[string]any{
|
||||
"echo": m.Echo,
|
||||
"status": "ok",
|
||||
"data": map[string]any{
|
||||
"user_id": myWechatId,
|
||||
"nickname": myWechatId,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("写入失败:", err)
|
||||
break
|
||||
}
|
||||
case "send_private_msg", "send_group_msg":
|
||||
err = SendWS(m.Params)
|
||||
if err != nil {
|
||||
log.Println("发送失败:", err)
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func SendWebSocketMsg(msg map[string]interface{}) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
// 这里处理你的 X1 数据
|
||||
jsonData, err := json.Marshal(msg["payload"])
|
||||
if err != nil {
|
||||
log.Printf("JSON 序列化失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("发送数据: %s\n", string(jsonData))
|
||||
if myWechatId == "" {
|
||||
m := new(WechatMessage)
|
||||
err = json.Unmarshal(jsonData, m)
|
||||
if err != nil {
|
||||
log.Printf("解析消息失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
myWechatId = m.SelfID
|
||||
|
||||
if m.GroupId != "" {
|
||||
userID2NicknameMap.Store(m.GroupId+"_"+m.UserID, m.Sender.Nickname)
|
||||
}
|
||||
}
|
||||
|
||||
err = conn.WriteMessage(websocket.TextMessage, jsonData)
|
||||
if err != nil {
|
||||
log.Printf("发送消息失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func SendWS(req *WSParams) error {
|
||||
sendContent := ""
|
||||
atUserID := ""
|
||||
if msg, ok := req.Message.(string); ok {
|
||||
sendContent = msg
|
||||
} else {
|
||||
bytes, err := json.Marshal(req.Message)
|
||||
if err != nil {
|
||||
log.Printf("JSON 序列化失败: %v\n", err)
|
||||
return err
|
||||
}
|
||||
msgs := make([]*Message, 0)
|
||||
err = json.Unmarshal(bytes, &msgs)
|
||||
if err != nil {
|
||||
log.Printf("JSON 反序列化失败: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range msgs {
|
||||
if v.Type == "text" {
|
||||
sendContent += v.Data.Text
|
||||
} else if v.Type == "at" {
|
||||
if req.GroupID != "" {
|
||||
if nicknameInter, ok := userID2NicknameMap.Load(req.GroupID + "_" + v.Data.QQ); ok {
|
||||
sendContent += fmt.Sprintf("@%s\u2005", nicknameInter.(string))
|
||||
atUserID += v.Data.QQ + ","
|
||||
}
|
||||
}
|
||||
|
||||
} else if v.Type == "image" {
|
||||
msgChan <- &SendMsg{
|
||||
UserId: req.UserID,
|
||||
GroupID: req.GroupID,
|
||||
Content: v.Data.File,
|
||||
Type: v.Type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if sendContent != "" {
|
||||
msgChan <- &SendMsg{
|
||||
UserId: req.UserID,
|
||||
GroupID: req.GroupID,
|
||||
Content: sendContent,
|
||||
Type: "text",
|
||||
AtUser: strings.TrimRight(atUserID, ","),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
jsonData, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Printf("读取消息失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = conn.WriteMessage(websocket.TextMessage, jsonData)
|
||||
if err != nil {
|
||||
log.Printf("发送消息失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SendWorker() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("💥 SendWorker 异常: %v\n", err)
|
||||
go SendWorker()
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-finishChan:
|
||||
fmt.Printf("收到完成信号 \n")
|
||||
case m, ok := <-msgChan:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
SendWechatMsg(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SendWechatMsg(m *SendMsg) {
|
||||
time.Sleep(1 * time.Second)
|
||||
currTaskId := atomic.AddInt64(&taskId, 1)
|
||||
log.Printf("📩 收到任务: %d\n", currTaskId)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
targetId := m.UserId
|
||||
if m.GroupID != "" && targetId == "" {
|
||||
targetId = m.GroupID
|
||||
}
|
||||
|
||||
switch m.Type {
|
||||
case "text":
|
||||
result := fridaScript.ExportsCall("triggerSendTextMessage", currTaskId, targetId, m.Content, m.AtUser)
|
||||
log.Printf("📩 发送文本任务执行结果:%s, 参数:currTaskId: %d, targetId: %s, content: %s, atUser: %s\n",
|
||||
result, currTaskId, targetId, m.Content, m.AtUser)
|
||||
case "image":
|
||||
targetPath, md5Str, err := SaveBase64Image(m.Content)
|
||||
if err != nil {
|
||||
log.Printf("保存图片失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
result := fridaScript.ExportsCall("triggerUploadImg", targetId, md5Str, targetPath)
|
||||
log.Printf("📩 上传图片任务执行结果%s, 参数:targetId: %s, md5Str: %s, targetPath: %s\n", result, targetId, md5Str, targetPath)
|
||||
case "send_image":
|
||||
result := fridaScript.ExportsCall("triggerSendImgMessage", currTaskId, myWechatId, targetId)
|
||||
log.Printf("📩 发送图片任务执行结果%s, 参数:currTaskId: %d, myWechatId: %s, targetId: %s\n", result, currTaskId, myWechatId, targetId)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Printf("任务 %d 执行超时!\n", currTaskId)
|
||||
case <-finishChan:
|
||||
log.Printf("收到完成信号,任务 %d 完成\n", currTaskId)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user