🎨 添加定时任务调度,优化消息同步逻辑,更新相关API接口调用
All checks were successful
BuildImage / build-image (push) Successful in 2m2s
All checks were successful
BuildImage / build-image (push) Successful in 2m2s
This commit is contained in:
parent
2f6b3fac01
commit
080fdb66ab
3
go.mod
3
go.mod
@ -5,6 +5,7 @@ go 1.24.0
|
||||
require (
|
||||
github.com/docker/docker v25.0.3+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/go-co-op/gocron/v2 v2.16.1
|
||||
github.com/go-resty/resty/v2 v2.10.0
|
||||
github.com/goccy/go-json v0.10.5
|
||||
github.com/gofiber/fiber/v2 v2.52.6
|
||||
@ -39,6 +40,7 @@ require (
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
@ -51,6 +53,7 @@ require (
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
|
8
go.sum
8
go.sum
@ -25,6 +25,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-co-op/gocron/v2 v2.16.1 h1:ux/5zxVRveCaCuTtNI3DiOk581KC1KpJbpJFYUEVYwo=
|
||||
github.com/go-co-op/gocron/v2 v2.16.1/go.mod h1:opexeOFy5BplhsKdA7bzY9zeYih8I8/WNJ4arTIFPVc=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@ -68,6 +70,8 @@ github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
@ -102,6 +106,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||
@ -152,6 +158,8 @@ go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lI
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
@ -2,9 +2,9 @@ package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
@ -20,63 +20,6 @@ const (
|
||||
WechatBotLabelValue = "wechat-bot"
|
||||
)
|
||||
|
||||
// APIResponse API响应结构
|
||||
type APIResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// QRCodeResponse 获取二维码响应
|
||||
type QRCodeResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
UUID string `json:"Uuid"`
|
||||
ExpiredTime string `json:"ExpiredTime"`
|
||||
QRCodeBase64 string `json:"QRCodeBase64"`
|
||||
QRCodeURL string `json:"QRCodeURL"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// CheckUuidResponse 检查二维码状态响应
|
||||
type CheckUuidResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
Uuid string `json:"uuid"`
|
||||
Status int `json:"status"` // 状态
|
||||
PushLoginUrlExpiredTime int `json:"pushLoginUrlexpiredTime"` // 推送登录url过期时间
|
||||
ExpiredTime int `json:"expiredTime"` // 过期时间(秒)
|
||||
HeadImgUrl string `json:"headImgUrl"` // 头像
|
||||
NickName string `json:"nickName"` // 昵称
|
||||
AcctSectResp map[string]any `json:"acctSectResp"` // 账号信息-登录成功之后才有
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// AwakenLoginResponse 唤醒登录响应
|
||||
type AwakenLoginResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
QrCodeResponse json.RawMessage `json:"QrCodeResponse"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// AutoHeartbeatResponse 心跳响应
|
||||
type AutoHeartbeatResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data struct{}
|
||||
}
|
||||
|
||||
// AutoHeartbeatStatusResponse 心跳状态响应
|
||||
type AutoHeartbeatStatusResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Running bool `json:"running"`
|
||||
}
|
||||
|
||||
// 定义一个HTTP客户端生成函数,用于向容器内的API发出请求
|
||||
func newHTTPClient() *resty.Client {
|
||||
return resty.New().
|
||||
@ -127,12 +70,12 @@ func GetLoginQRCode(ctx context.Context, containerID string) (string, error) {
|
||||
}
|
||||
|
||||
// GetQRCode 获取登录二维码
|
||||
func GetQRCode(ctx context.Context, containerID string, containerHost string) (*QRCodeResponse, error) {
|
||||
func GetQRCode(ctx context.Context, containerHost string) (*BaseResponse[QRCodeResponse], error) {
|
||||
client := newHTTPClient()
|
||||
|
||||
url := fmt.Sprintf("http://%s/GetQRCode", containerHost)
|
||||
|
||||
var response QRCodeResponse
|
||||
var response BaseResponse[QRCodeResponse]
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
SetBody("{}").
|
||||
@ -155,13 +98,13 @@ func GetQRCode(ctx context.Context, containerID string, containerHost string) (*
|
||||
}
|
||||
|
||||
// CheckUuid 检查二维码状态
|
||||
func CheckUuid(ctx context.Context, uuid string, containerHost string) (*CheckUuidResponse, error) {
|
||||
func CheckUuid(ctx context.Context, uuid string, containerHost string) (*BaseResponse[CheckUuidResponse], error) {
|
||||
client := newHTTPClient()
|
||||
|
||||
url := fmt.Sprintf("http://%s/CheckUuid", containerHost)
|
||||
reqBody := map[string]string{"Uuid": uuid}
|
||||
|
||||
var response CheckUuidResponse
|
||||
var response BaseResponse[CheckUuidResponse]
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
SetBody(reqBody).
|
||||
@ -180,13 +123,13 @@ func CheckUuid(ctx context.Context, uuid string, containerHost string) (*CheckUu
|
||||
}
|
||||
|
||||
// AwakenLogin 唤醒登录
|
||||
func AwakenLogin(ctx context.Context, containerID string, wxid string, containerHost string) (*AwakenLoginResponse, error) {
|
||||
func AwakenLogin(ctx context.Context, wxid string, containerHost string) (*BaseResponse[AwakenLoginResponse], error) {
|
||||
client := newHTTPClient()
|
||||
|
||||
url := fmt.Sprintf("http://%s/AwakenLogin", containerHost)
|
||||
reqBody := map[string]string{"Wxid": wxid}
|
||||
|
||||
var response AwakenLoginResponse
|
||||
var response BaseResponse[AwakenLoginResponse]
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
SetBody(reqBody).
|
||||
@ -205,13 +148,13 @@ func AwakenLogin(ctx context.Context, containerID string, wxid string, container
|
||||
}
|
||||
|
||||
// AutoHeartbeatStart 开启自动心跳
|
||||
func AutoHeartbeatStart(ctx context.Context, containerID string, wxid string, containerHost string) (*AutoHeartbeatResponse, error) {
|
||||
func AutoHeartbeatStart(ctx context.Context, wxid string, containerHost string) (*BaseResponse[AutoHeartbeatResponse], error) {
|
||||
client := newHTTPClient()
|
||||
|
||||
url := fmt.Sprintf("http://%s/AutoHeartbeatStart", containerHost)
|
||||
reqBody := map[string]string{"Wxid": wxid}
|
||||
|
||||
var response AutoHeartbeatResponse
|
||||
var response BaseResponse[AutoHeartbeatResponse]
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
SetBody(reqBody).
|
||||
@ -234,7 +177,7 @@ func AutoHeartbeatStart(ctx context.Context, containerID string, wxid string, co
|
||||
}
|
||||
|
||||
// AutoHeartbeatStatus 获取自动心跳状态
|
||||
func AutoHeartbeatStatus(ctx context.Context, containerID string, wxid string, containerHost string) (*AutoHeartbeatStatusResponse, error) {
|
||||
func AutoHeartbeatStatus(ctx context.Context, wxid string, containerHost string) (*AutoHeartbeatStatusResponse, error) {
|
||||
client := newHTTPClient()
|
||||
|
||||
url := fmt.Sprintf("http://%s/AutoHeartbeatStatus", containerHost)
|
||||
@ -259,7 +202,7 @@ func AutoHeartbeatStatus(ctx context.Context, containerID string, wxid string, c
|
||||
}
|
||||
|
||||
// AutoHeartbeatStop 停止自动心跳
|
||||
func AutoHeartbeatStop(ctx context.Context, containerID string, wxid string, containerHost string) (*AutoHeartbeatResponse, error) {
|
||||
func AutoHeartbeatStop(ctx context.Context, wxid string, containerHost string) (*AutoHeartbeatResponse, error) {
|
||||
client := newHTTPClient()
|
||||
|
||||
url := fmt.Sprintf("http://%s/AutoHeartbeatStop", containerHost)
|
||||
@ -288,7 +231,7 @@ func AutoHeartbeatStop(ctx context.Context, containerID string, wxid string, con
|
||||
}
|
||||
|
||||
// LogOut 登出微信
|
||||
func LogOut(ctx context.Context, containerID string, wxid string, containerHost string) (*AutoHeartbeatResponse, error) {
|
||||
func LogOut(ctx context.Context, wxid string, containerHost string) (*AutoHeartbeatResponse, error) {
|
||||
client := newHTTPClient()
|
||||
|
||||
url := fmt.Sprintf("http://%s/LogOut", containerHost)
|
||||
@ -324,7 +267,7 @@ func LogoutWechatBot(ctx context.Context, containerID string) error {
|
||||
client := newHTTPClient()
|
||||
url := fmt.Sprintf("http://%s:3000/api/logout", hostIP)
|
||||
|
||||
var response APIResponse
|
||||
var response BaseResponse[any]
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
SetResult(&response).
|
||||
@ -363,7 +306,7 @@ func GetWechatBotStatus(ctx context.Context, containerID string) (model.RobotSta
|
||||
client := newHTTPClient()
|
||||
url := fmt.Sprintf("http://%s:3000/api/status", hostIP)
|
||||
|
||||
var response APIResponse
|
||||
var response BaseResponse[any]
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
SetResult(&response).
|
||||
@ -410,7 +353,7 @@ func ListWechatBots(ctx context.Context) ([]ContainerInfo, error) {
|
||||
}
|
||||
|
||||
// GetWechatContacts 获取微信联系人列表
|
||||
func GetWechatContacts(ctx context.Context, containerID string, containerHost string) (string, error) {
|
||||
func GetWechatContacts(ctx context.Context, containerHost string) (string, error) {
|
||||
client := newHTTPClient()
|
||||
url := fmt.Sprintf("http://%s/api/contacts", containerHost)
|
||||
|
||||
@ -426,7 +369,7 @@ func GetWechatContacts(ctx context.Context, containerID string, containerHost st
|
||||
}
|
||||
|
||||
// GetWechatGroupMembers 获取微信群成员
|
||||
func GetWechatGroupMembers(ctx context.Context, containerID string, groupID string, containerHost string) (string, error) {
|
||||
func GetWechatGroupMembers(ctx context.Context, groupID string, containerHost string) (string, error) {
|
||||
client := newHTTPClient()
|
||||
url := fmt.Sprintf("http://%s/api/group/%s/members", containerHost, groupID)
|
||||
|
||||
@ -442,17 +385,31 @@ func GetWechatGroupMembers(ctx context.Context, containerID string, groupID stri
|
||||
}
|
||||
|
||||
// GetWechatMessages 获取微信消息历史
|
||||
func GetWechatMessages(ctx context.Context, containerID string, contactID string, limit int, containerHost string) (string, error) {
|
||||
func GetWechatMessages(ctx context.Context, containerHost, robotWxId string) (msg []Message, isOffline bool, err error) {
|
||||
client := newHTTPClient()
|
||||
url := fmt.Sprintf("http://%s/api/messages/%s?limit=%d", containerHost, contactID, limit)
|
||||
url := fmt.Sprintf("http://%s/Sync", containerHost)
|
||||
|
||||
resp, err := client.R().
|
||||
var response BaseResponse[Sync]
|
||||
_, err = client.R().
|
||||
SetContext(ctx).
|
||||
Get(url)
|
||||
SetBody(map[string]any{
|
||||
"Scene": 0,
|
||||
"Synckey": "",
|
||||
"Wxid": robotWxId,
|
||||
}).
|
||||
SetResult(&response).
|
||||
Post(url)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取消息历史请求失败: %w", err)
|
||||
err = fmt.Errorf("获取消息历史请求失败: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
return resp.String(), nil
|
||||
// 判断是否离线
|
||||
if strings.Contains(response.Message, "用户可能退出") || strings.Contains(response.Message, "数据[DecryptData]失败") {
|
||||
isOffline = true
|
||||
}
|
||||
|
||||
msg = response.Data.AddMsgs
|
||||
return
|
||||
}
|
||||
|
105
internal/docker/robot_model.go
Normal file
105
internal/docker/robot_model.go
Normal file
@ -0,0 +1,105 @@
|
||||
package docker
|
||||
|
||||
import "gitee.ltd/lxh/wechat-robot/internal/types"
|
||||
|
||||
// BaseResponse API响应结构
|
||||
type BaseResponse[T any] struct {
|
||||
Success bool `json:"Success"`
|
||||
Code int `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
Data T `json:"Data"`
|
||||
}
|
||||
|
||||
// QRCodeResponse 获取二维码响应
|
||||
type QRCodeResponse struct {
|
||||
UUID string `json:"Uuid"`
|
||||
ExpiredTime string `json:"ExpiredTime"`
|
||||
QRCodeBase64 string `json:"QRCodeBase64"`
|
||||
QRCodeURL string `json:"QRCodeURL"`
|
||||
}
|
||||
|
||||
// CheckUuidResponse 检查二维码状态响应
|
||||
type CheckUuidResponse struct {
|
||||
Uuid string `json:"uuid"`
|
||||
Status int `json:"status"` // 状态
|
||||
PushLoginUrlExpiredTime int `json:"pushLoginUrlexpiredTime"` // 推送登录url过期时间
|
||||
ExpiredTime int `json:"expiredTime"` // 过期时间(秒)
|
||||
HeadImgUrl string `json:"headImgUrl"` // 头像
|
||||
NickName string `json:"nickName"` // 昵称
|
||||
AcctSectResp map[string]any `json:"acctSectResp"` // 账号信息-登录成功之后才有
|
||||
}
|
||||
|
||||
// AwakenLoginResponse 唤醒登录响应
|
||||
type AwakenLoginResponse struct {
|
||||
QrCodeResponse struct {
|
||||
BaseResponse any `json:"BaseResponse"`
|
||||
BlueToothBroadCastContent any `json:"BlueToothBroadCastContent"`
|
||||
BlueToothBroadCastUuid string `json:"BlueToothBroadCastUuid"`
|
||||
CheckTime int `json:"CheckTime"`
|
||||
ExpiredTime int `json:"ExpiredTime"`
|
||||
NotifyKey any `json:"NotifyKey"`
|
||||
Uuid string `json:"Uuid"`
|
||||
} `json:"QrCodeResponse"`
|
||||
}
|
||||
|
||||
// AutoHeartbeatResponse 心跳响应
|
||||
type AutoHeartbeatResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data struct{}
|
||||
}
|
||||
|
||||
// AutoHeartbeatStatusResponse 心跳状态响应
|
||||
type AutoHeartbeatStatusResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Running bool `json:"running"`
|
||||
}
|
||||
|
||||
// Sync
|
||||
// @description: 同步微信消息返回值
|
||||
type Sync struct {
|
||||
ModUserInfos any `json:"ModUserInfos"`
|
||||
ModContacts any `json:"ModContacts"`
|
||||
DelContacts any `json:"DelContacts"`
|
||||
ModUserImgs any `json:"ModUserImgs"`
|
||||
FunctionSwitchs any `json:"FunctionSwitchs"`
|
||||
UserInfoExts any `json:"UserInfoExts"`
|
||||
AddMsgs []Message `json:"AddMsgs"`
|
||||
ContinueFlag int `json:"ContinueFlag"`
|
||||
KeyBuf struct {
|
||||
ILen int `json:"iLen"`
|
||||
Buffer string `json:"buffer"`
|
||||
} `json:"KeyBuf"`
|
||||
Status int `json:"Status"`
|
||||
Continue int `json:"Continue"`
|
||||
Time int `json:"Time"`
|
||||
UnknownCmdId string `json:"UnknownCmdId"`
|
||||
Remarks string `json:"Remarks"`
|
||||
}
|
||||
|
||||
// Message
|
||||
// @description: 微信消息
|
||||
type Message struct {
|
||||
MsgId int `json:"MsgId"`
|
||||
FromUserName struct {
|
||||
String string `json:"string"`
|
||||
} `json:"FromUserName"`
|
||||
ToWxid struct {
|
||||
String string `json:"string"`
|
||||
} `json:"ToWxid"`
|
||||
MsgType types.MessageType `json:"MsgType"`
|
||||
Content struct {
|
||||
String string `json:"string"`
|
||||
} `json:"Content"`
|
||||
Status int `json:"Status"`
|
||||
ImgStatus int `json:"ImgStatus"`
|
||||
ImgBuf struct {
|
||||
ILen int `json:"iLen"`
|
||||
} `json:"ImgBuf"`
|
||||
CreateTime int `json:"CreateTime"`
|
||||
MsgSource string `json:"MsgSource"`
|
||||
NewMsgId int64 `json:"NewMsgId"`
|
||||
MsgSeq int `json:"MsgSeq"`
|
||||
PushContent string `json:"PushContent,omitempty"`
|
||||
}
|
@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/tasks"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"strconv"
|
||||
"time"
|
||||
@ -82,7 +83,7 @@ func CheckQRCodeStatus(c *fiber.Ctx) error {
|
||||
|
||||
// 开启自动心跳,传递容器访问地址
|
||||
if robot.WechatID != "" {
|
||||
_, _ = docker.AutoHeartbeatStart(ctx, robot.ContainerID, robot.WechatID, robot.ContainerHost)
|
||||
_, _ = docker.AutoHeartbeatStart(ctx, robot.WechatID, robot.ContainerHost)
|
||||
}
|
||||
|
||||
// 更新机器人状态
|
||||
@ -90,6 +91,9 @@ func CheckQRCodeStatus(c *fiber.Ctx) error {
|
||||
now := time.Now()
|
||||
robot.LastLoginAt = &now
|
||||
db.Save(&robot)
|
||||
|
||||
// 添加定时任务
|
||||
tasks.AddJob(robot)
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
|
@ -1,15 +1,10 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
|
||||
"gitee.ltd/lxh/wechat-robot/internal/docker"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/model"
|
||||
)
|
||||
|
||||
@ -32,56 +27,10 @@ func ListContacts(c *fiber.Ctx) error {
|
||||
|
||||
// 获取联系人列表
|
||||
var contacts []model.Contact
|
||||
if err := db.Where("robot_id = ?", robotID).Find(&contacts).Error; err != nil {
|
||||
if err = db.Where("robot_id = ?", robotID).Find(&contacts).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "获取联系人列表失败")
|
||||
}
|
||||
|
||||
// 如果机器人在线并且没有联系人,则从机器人获取最新联系人
|
||||
if robot.Status == model.RobotStatusOnline && len(contacts) == 0 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 调用Docker API获取联系人,传递容器访问地址
|
||||
contactsData, err := docker.GetWechatContacts(ctx, robot.ContainerID, robot.ContainerHost)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "从机器人获取联系人失败: "+err.Error())
|
||||
}
|
||||
|
||||
// 解析联系人数据
|
||||
var response docker.APIResponse
|
||||
if err := json.Unmarshal([]byte(contactsData), &response); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "解析联系人数据失败: "+err.Error())
|
||||
}
|
||||
|
||||
// 保存联系人到数据库
|
||||
if response.Success {
|
||||
contactList, ok := response.Data.([]interface{})
|
||||
if ok {
|
||||
for _, c := range contactList {
|
||||
contactMap, ok := c.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
contact := model.Contact{
|
||||
RobotID: uint(robotID),
|
||||
WechatID: contactMap["id"].(string),
|
||||
Nickname: contactMap["name"].(string),
|
||||
Avatar: contactMap["avatar"].(string),
|
||||
Type: model.ContactTypeFriend,
|
||||
}
|
||||
|
||||
if contactMap["type"].(string) == "group" {
|
||||
contact.Type = model.ContactTypeGroup
|
||||
}
|
||||
|
||||
db.Create(&contact)
|
||||
contacts = append(contacts, contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c.Render("contact/index", fiber.Map{
|
||||
"Title": "联系人列表",
|
||||
"Robot": robot,
|
||||
@ -169,59 +118,10 @@ func ListGroupMembers(c *fiber.Ctx) error {
|
||||
|
||||
// 获取群成员
|
||||
var members []model.GroupMember
|
||||
if err := db.Where("group_id = ?", contactID).Find(&members).Error; err != nil {
|
||||
if err = db.Where("group_id = ?", contactID).Find(&members).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "获取群成员失败")
|
||||
}
|
||||
|
||||
// 如果机器人在线并且没有群成员,则从机器人获取
|
||||
if robot.Status == model.RobotStatusOnline && len(members) == 0 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 调用Docker API获取群成员,传递容器访问地址
|
||||
membersData, err := docker.GetWechatGroupMembers(ctx, robot.ContainerID, contact.WechatID, robot.ContainerHost)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "从机器人获取群成员失败: "+err.Error())
|
||||
}
|
||||
|
||||
// 解析群成员数据
|
||||
var response docker.APIResponse
|
||||
if err := json.Unmarshal([]byte(membersData), &response); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "解析群成员数据失败: "+err.Error())
|
||||
}
|
||||
|
||||
// 保存群成员到数据库
|
||||
if response.Success {
|
||||
memberList, ok := response.Data.([]interface{})
|
||||
if ok {
|
||||
for _, m := range memberList {
|
||||
memberMap, ok := m.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
role := model.MemberRoleMember
|
||||
if memberMap["role"].(string) == "owner" {
|
||||
role = model.MemberRoleOwner
|
||||
} else if memberMap["role"].(string) == "admin" {
|
||||
role = model.MemberRoleAdmin
|
||||
}
|
||||
|
||||
member := model.GroupMember{
|
||||
GroupID: contact.ID,
|
||||
WechatID: memberMap["id"].(string),
|
||||
Nickname: memberMap["name"].(string),
|
||||
Avatar: memberMap["avatar"].(string),
|
||||
Role: role,
|
||||
}
|
||||
|
||||
db.Create(&member)
|
||||
members = append(members, member)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c.Render("contact/members", fiber.Map{
|
||||
"Title": contact.Nickname + " 群成员",
|
||||
"Robot": robot,
|
||||
|
@ -1,15 +1,11 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
|
||||
"gitee.ltd/lxh/wechat-robot/internal/docker"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/model"
|
||||
)
|
||||
|
||||
@ -33,16 +29,16 @@ func ListMessages(c *fiber.Ctx) error {
|
||||
db := model.GetDB()
|
||||
|
||||
var robot model.Robot
|
||||
if err := db.First(&robot, robotID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
if err = db.First(&robot, robotID).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "机器人不存在")
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "查询机器人失败")
|
||||
}
|
||||
|
||||
var contact model.Contact
|
||||
if err := db.First(&contact, contactID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
if err = db.First(&contact, contactID).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "联系人不存在")
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "查询联系人失败")
|
||||
@ -50,7 +46,7 @@ func ListMessages(c *fiber.Ctx) error {
|
||||
|
||||
// 获取消息
|
||||
var messages []model.Message
|
||||
if err := db.Where("robot_id = ? AND contact_id = ?", robotID, contactID).
|
||||
if err = db.Where("robot_id = ? AND contact_id = ?", robotID, contactID).
|
||||
Order("send_time DESC").
|
||||
Offset(offset).Limit(limit).
|
||||
Find(&messages).Error; err != nil {
|
||||
@ -61,72 +57,6 @@ func ListMessages(c *fiber.Ctx) error {
|
||||
var total int64
|
||||
db.Model(&model.Message{}).Where("robot_id = ? AND contact_id = ?", robotID, contactID).Count(&total)
|
||||
|
||||
// 如果机器人在线并且没有消息,则从机器人获取最新消息
|
||||
if robot.Status == model.RobotStatusOnline && len(messages) == 0 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 调用Docker API获取消息,传递容器访问地址
|
||||
messagesData, err := docker.GetWechatMessages(ctx, robot.ContainerID, contact.WechatID, 50, robot.ContainerHost)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "从机器人获取消息失败: "+err.Error())
|
||||
}
|
||||
|
||||
// 解析消息数据
|
||||
var response docker.APIResponse
|
||||
if err := json.Unmarshal([]byte(messagesData), &response); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "解析消息数据失败: "+err.Error())
|
||||
}
|
||||
|
||||
// 保存消息到数据库
|
||||
if response.Success {
|
||||
messageList, ok := response.Data.([]interface{})
|
||||
if ok {
|
||||
for _, m := range messageList {
|
||||
msgMap, ok := m.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
contentType := model.ContentTypeText
|
||||
switch msgMap["type"].(string) {
|
||||
case "image":
|
||||
contentType = model.ContentTypeImage
|
||||
case "voice":
|
||||
contentType = model.ContentTypeVoice
|
||||
case "video":
|
||||
contentType = model.ContentTypeVideo
|
||||
case "file":
|
||||
contentType = model.ContentTypeFile
|
||||
case "location":
|
||||
contentType = model.ContentTypeLocation
|
||||
}
|
||||
|
||||
sendTimeStr := msgMap["time"].(string)
|
||||
sendTime, _ := time.Parse(time.RFC3339, sendTimeStr)
|
||||
|
||||
message := model.Message{
|
||||
RobotID: uint(robotID),
|
||||
ContactID: uint(contactID),
|
||||
SenderID: msgMap["sender_id"].(string),
|
||||
SenderName: msgMap["sender_name"].(string),
|
||||
Content: msgMap["content"].(string),
|
||||
ContentType: contentType,
|
||||
SendTime: sendTime,
|
||||
IsFromMe: msgMap["from_me"].(bool),
|
||||
}
|
||||
|
||||
if mediaURL, ok := msgMap["media_url"].(string); ok {
|
||||
message.MediaURL = mediaURL
|
||||
}
|
||||
|
||||
db.Create(&message)
|
||||
messages = append(messages, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 计算总页数
|
||||
totalPages := int(total) / limit
|
||||
if int(total)%limit > 0 {
|
||||
|
@ -218,7 +218,7 @@ func RobotLogin(c *fiber.Ctx) error {
|
||||
defer cancel()
|
||||
|
||||
// 使用新的GetQRCode接口获取二维码,并传递容器访问地址
|
||||
qrcodeResp, err := docker.GetQRCode(ctx, robot.ContainerID, robot.ContainerHost)
|
||||
qrcodeResp, err := docker.GetQRCode(ctx, robot.ContainerHost)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "获取登录二维码失败: "+err.Error())
|
||||
}
|
||||
@ -256,7 +256,7 @@ func RobotLogout(c *fiber.Ctx) error {
|
||||
|
||||
// 使用新的LogOut API接口,传递容器访问地址
|
||||
if robot.WechatID != "" {
|
||||
if _, err = docker.LogOut(ctx, robot.ContainerID, robot.WechatID, robot.ContainerHost); err != nil {
|
||||
if _, err = docker.LogOut(ctx, robot.WechatID, robot.ContainerHost); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "登出微信失败: "+err.Error())
|
||||
}
|
||||
} else {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"gitee.ltd/lxh/wechat-robot/internal/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -20,15 +21,16 @@ const (
|
||||
// Message 表示微信消息
|
||||
type Message struct {
|
||||
BaseModel
|
||||
RobotID uint `gorm:"column:robot_id;index" json:"robot_id"`
|
||||
ContactID uint `gorm:"column:contact_id;index" json:"contact_id"`
|
||||
SenderID string `gorm:"column:sender_id;index:idx_sender_id,length:64" json:"sender_id"` // 添加索引长度
|
||||
SenderName string `gorm:"column:sender_name" json:"sender_name"` // 发送者昵称
|
||||
Content string `gorm:"column:content;type:text" json:"content"`
|
||||
ContentType ContentType `gorm:"column:content_type" json:"content_type"`
|
||||
MediaURL string `gorm:"column:media_url" json:"media_url"` // 媒体文件URL
|
||||
SendTime time.Time `gorm:"column:send_time;index" json:"send_time"`
|
||||
IsFromMe bool `gorm:"column:is_from_me" json:"is_from_me"` // 是否由机器人发送
|
||||
RobotId uint // 机器人Id
|
||||
MsgId int64 // 消息Id
|
||||
CreateTime int // 发送时间戳
|
||||
CreateAt time.Time // 发送时间
|
||||
Type types.MessageType // 消息类型
|
||||
Content string // 内容
|
||||
DisplayFullContent string // 显示的完整内容
|
||||
FromUser string // 发送者
|
||||
GroupUser string // 群成员
|
||||
ToUser string // 接收者
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
|
@ -1,6 +0,0 @@
|
||||
# 数据访问层
|
||||
|
||||
此目录包含与数据库交互的代码:
|
||||
- 数据库CRUD操作
|
||||
- 查询构建
|
||||
- 数据持久化逻辑
|
@ -1,6 +0,0 @@
|
||||
# 业务逻辑层
|
||||
|
||||
此目录包含应用程序的核心业务逻辑:
|
||||
- 微信机器人管理
|
||||
- 聊天记录处理
|
||||
- 联系人管理
|
72
internal/tasks/sync.go
Normal file
72
internal/tasks/sync.go
Normal file
@ -0,0 +1,72 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/docker"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/model"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/types"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// syncMessage
|
||||
// @description: 同步指定机器人的消息
|
||||
// @param containerHost 机器人接口地址
|
||||
// @param robotWxId 机器人微信Id
|
||||
// @param robotId 机器人数据Id
|
||||
func syncMessage(containerHost, robotWxId string, robotId uint) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
messages, isOffline, err := docker.GetWechatMessages(ctx, containerHost, robotWxId)
|
||||
if err != nil {
|
||||
// 处理错误
|
||||
return
|
||||
}
|
||||
db := model.GetDB()
|
||||
if isOffline {
|
||||
// 删除定时任务
|
||||
DeleteJob(robotId)
|
||||
// 修改机器人状态
|
||||
db.Model(&model.Robot{}).Where("id = ?", robotId).Update("status", "offline")
|
||||
return
|
||||
}
|
||||
|
||||
if len(messages) == 0 {
|
||||
// 没有消息,直接返回
|
||||
return
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
var msg []model.Message
|
||||
for _, message := range messages {
|
||||
var m model.Message
|
||||
m.RobotId = robotId
|
||||
m.MsgId = message.NewMsgId
|
||||
m.CreateTime = message.CreateTime
|
||||
m.CreateAt = time.Unix(int64(message.CreateTime), 0)
|
||||
m.Type = message.MsgType
|
||||
m.Content = message.Content.String
|
||||
m.DisplayFullContent = message.PushContent
|
||||
m.FromUser = message.FromUserName.String
|
||||
m.ToUser = message.ToWxid.String
|
||||
|
||||
// 如果是群聊消息,单独处理一下
|
||||
// Sys类型的消息正文不包含微信 Id,所以不需要处理
|
||||
if strings.HasSuffix(message.FromUserName.String, "@chatroom") && message.MsgType != types.MsgTypeSys {
|
||||
// 群消息,处理一下消息和发信人
|
||||
groupUser := strings.Split(m.Content, "\n")[0]
|
||||
groupUser = strings.ReplaceAll(groupUser, ":", "")
|
||||
// 如果两个id一致,说明是系统发的
|
||||
if m.FromUser != groupUser {
|
||||
m.GroupUser = groupUser
|
||||
}
|
||||
// 用户的操作单独提出来处理一下
|
||||
m.Content = strings.Join(strings.Split(m.Content, "\n")[1:], "\n")
|
||||
}
|
||||
|
||||
msg = append(msg, m)
|
||||
}
|
||||
// 保存入库
|
||||
db.Save(&msg)
|
||||
}
|
78
internal/tasks/tasks.go
Normal file
78
internal/tasks/tasks.go
Normal file
@ -0,0 +1,78 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"gitee.ltd/lxh/wechat-robot/internal/model"
|
||||
"github.com/go-co-op/gocron/v2"
|
||||
"github.com/google/uuid"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 定时任务调度器
|
||||
var scheduler gocron.Scheduler
|
||||
|
||||
// 已启动定时任务的微信机器人
|
||||
var enabledMap = sync.Map{}
|
||||
|
||||
// Start
|
||||
// @description: 启动任务
|
||||
func Start() {
|
||||
var err error
|
||||
scheduler, err = gocron.NewScheduler()
|
||||
if err != nil {
|
||||
log.Panicf("定时任务启动失败: %v", err)
|
||||
}
|
||||
|
||||
// 预添加已经确定的任务
|
||||
db := model.GetDB()
|
||||
// 查询数据库,获取在线的机器人
|
||||
var robots []model.Robot
|
||||
if err = db.Where("`status` = 'online'").Find(&robots).Error; err != nil {
|
||||
log.Panicf("查询机器人失败: %v", err)
|
||||
}
|
||||
// 遍历机器人,添加任务
|
||||
for _, robot := range robots {
|
||||
var job gocron.Job
|
||||
job, err = scheduler.NewJob(
|
||||
gocron.CronJob("*/5 * * * * *", true), // 五秒钟同步一次
|
||||
gocron.NewTask(syncMessage, robot.ContainerHost, robot.WechatID, robot.ID),
|
||||
)
|
||||
if err != nil {
|
||||
log.Panicf("添加定时任务失败: %v", err)
|
||||
}
|
||||
// 添加到已启动的任务列表
|
||||
enabledMap.Store(robot.ID, job.ID())
|
||||
}
|
||||
|
||||
// 启动定时任务
|
||||
scheduler.Start()
|
||||
log.Println("定时任务已启动")
|
||||
}
|
||||
|
||||
// AddJob
|
||||
// @description: 添加任务
|
||||
func AddJob(robot model.Robot) {
|
||||
job, err := scheduler.NewJob(
|
||||
gocron.CronJob("*/5 * * * * *", true), // 五秒钟同步一次
|
||||
gocron.NewTask(syncMessage, robot.ContainerHost, robot.WechatID, robot.ID),
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("添加定时任务失败: %v", err)
|
||||
return
|
||||
}
|
||||
// 添加到已启动的任务列表
|
||||
enabledMap.Store(robot.ID, job.ID())
|
||||
}
|
||||
|
||||
// DeleteJob
|
||||
// @description: 删除定时任务
|
||||
// @param robotId
|
||||
func DeleteJob(robotId uint) {
|
||||
// 先取出任务Id
|
||||
jobId, ok := enabledMap.Load(robotId)
|
||||
if !ok {
|
||||
log.Printf("定时任务不存在,robotId: %d", robotId)
|
||||
return
|
||||
}
|
||||
_ = scheduler.RemoveJob(jobId.(uuid.UUID))
|
||||
}
|
51
internal/types/message.go
Normal file
51
internal/types/message.go
Normal file
@ -0,0 +1,51 @@
|
||||
package types
|
||||
|
||||
import "fmt"
|
||||
|
||||
type MessageType int
|
||||
|
||||
// 微信定义的消息类型
|
||||
const (
|
||||
MsgTypeText MessageType = 1 // 文本消息
|
||||
MsgTypeImage MessageType = 3 // 图片消息
|
||||
MsgTypeVoice MessageType = 34 // 语音消息
|
||||
MsgTypeVerify MessageType = 37 // 认证消息
|
||||
MsgTypePossibleFriend MessageType = 40 // 好友推荐消息
|
||||
MsgTypeShareCard MessageType = 42 // 名片消息
|
||||
MsgTypeVideo MessageType = 43 // 视频消息
|
||||
MsgTypeEmoticon MessageType = 47 // 表情消息
|
||||
MsgTypeLocation MessageType = 48 // 地理位置消息
|
||||
MsgTypeApp MessageType = 49 // APP消息
|
||||
MsgTypeVoip MessageType = 50 // VOIP消息
|
||||
MsgTypeVoipNotify MessageType = 52 // VOIP结束消息
|
||||
MsgTypeVoipInvite MessageType = 53 // VOIP邀请
|
||||
MsgTypeMicroVideo MessageType = 62 // 小视频消息
|
||||
MsgTypeSys MessageType = 10000 // 系统消息
|
||||
MsgTypeRecalled MessageType = 10002 // 消息撤回
|
||||
)
|
||||
|
||||
var MessageTypeMap = map[MessageType]string{
|
||||
MsgTypeText: "文本消息",
|
||||
MsgTypeImage: "图片消息",
|
||||
MsgTypeVoice: "语音消息",
|
||||
MsgTypeVerify: "认证消息",
|
||||
MsgTypePossibleFriend: "好友推荐消息",
|
||||
MsgTypeShareCard: "名片消息",
|
||||
MsgTypeVideo: "视频消息",
|
||||
MsgTypeEmoticon: "表情消息",
|
||||
MsgTypeLocation: "地理位置消息",
|
||||
MsgTypeApp: "APP消息",
|
||||
MsgTypeVoip: "VOIP消息",
|
||||
MsgTypeVoipNotify: "VOIP结束消息",
|
||||
MsgTypeVoipInvite: "VOIP邀请",
|
||||
MsgTypeMicroVideo: "小视频消息",
|
||||
MsgTypeSys: "系统消息",
|
||||
MsgTypeRecalled: "消息撤回",
|
||||
}
|
||||
|
||||
func (mt MessageType) String() string {
|
||||
if msg, ok := MessageTypeMap[mt]; ok {
|
||||
return msg
|
||||
}
|
||||
return fmt.Sprintf("未知消息类型(%d)", mt)
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
# 微信API客户端
|
||||
|
||||
此目录包含与微信API交互的代码:
|
||||
- 登录处理
|
||||
- 消息发送接收
|
||||
- 联系人管理
|
||||
- 群组管理
|
4
main.go
4
main.go
@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/tasks"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
@ -60,6 +61,9 @@ func main() {
|
||||
|
||||
log.Println("Server started successfully")
|
||||
|
||||
// 启动定时任务
|
||||
tasks.Start()
|
||||
|
||||
// 优雅关闭
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
|
||||
|
Loading…
x
Reference in New Issue
Block a user