387 lines
10 KiB
Go
387 lines
10 KiB
Go
package docker
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"log"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/go-resty/resty/v2"
|
||
|
||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||
"gitee.ltd/lxh/wechat-robot/internal/model"
|
||
)
|
||
|
||
const (
|
||
// WechatBotLabelKey 微信机器人容器标签键
|
||
WechatBotLabelKey = "app.type"
|
||
// WechatBotLabelValue 微信机器人容器标签值
|
||
WechatBotLabelValue = "wechat-bot"
|
||
)
|
||
|
||
// 定义一个HTTP客户端生成函数,用于向容器内的API发出请求
|
||
func newHTTPClient() *resty.Client {
|
||
return resty.New().
|
||
SetTimeout(30*time.Second).
|
||
SetHeader("Content-Type", "application/json")
|
||
}
|
||
|
||
// CreateRobotContainer 创建微信机器人容器
|
||
func CreateRobotContainer(ctx context.Context, cfg *config.DockerConfig, robotName string, port int) (string, string, error) {
|
||
// 创建容器标签
|
||
labels := map[string]string{
|
||
WechatBotLabelKey: WechatBotLabelValue,
|
||
"robot.name": robotName,
|
||
}
|
||
|
||
// 创建环境变量
|
||
env := []string{
|
||
"BOT_NAME=" + robotName,
|
||
}
|
||
|
||
// 创建容器
|
||
containerID, err := CreateContainer(ctx, cfg, "wechat-bot-"+robotName, env, labels, port)
|
||
if err != nil {
|
||
return "", "", err
|
||
}
|
||
|
||
// 启动容器
|
||
err = StartContainer(ctx, containerID)
|
||
if err != nil {
|
||
// 如果启动失败,尝试删除容器
|
||
RemoveContainer(ctx, containerID, true)
|
||
return "", "", err
|
||
}
|
||
|
||
// 获取容器访问地址
|
||
containerHost, err := GetContainerHost(ctx, containerID, cfg)
|
||
if err != nil {
|
||
log.Printf("警告: 无法获取容器访问地址: %v", err)
|
||
containerHost = "localhost:9000" // 使用默认值
|
||
}
|
||
|
||
return containerID, containerHost, nil
|
||
}
|
||
|
||
// GetLoginQRCode 获取登录二维码(旧方法)
|
||
func GetLoginQRCode(ctx context.Context, containerID string) (string, error) {
|
||
return ExecuteCommandWithOutput(ctx, containerID, []string{"cat", "/data/qrcode.png"})
|
||
}
|
||
|
||
// GetQRCode 获取登录二维码
|
||
func GetQRCode(ctx context.Context, containerHost string) (*BaseResponse[QRCodeResponse], error) {
|
||
client := newHTTPClient()
|
||
|
||
url := fmt.Sprintf("http://%s/GetQRCode", containerHost)
|
||
|
||
var response BaseResponse[QRCodeResponse]
|
||
resp, err := client.R().
|
||
SetContext(ctx).
|
||
SetBody("{}").
|
||
SetResult(&response).
|
||
Post(url)
|
||
|
||
if err != nil {
|
||
return nil, fmt.Errorf("获取二维码请求失败: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode() != 200 {
|
||
return nil, fmt.Errorf("获取二维码API返回错误状态码: %d", resp.StatusCode())
|
||
}
|
||
|
||
if !response.Success {
|
||
return nil, fmt.Errorf("获取二维码API错误: %s", response.Message)
|
||
}
|
||
|
||
return &response, nil
|
||
}
|
||
|
||
// CheckUuid 检查二维码状态
|
||
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 BaseResponse[CheckUuidResponse]
|
||
resp, err := client.R().
|
||
SetContext(ctx).
|
||
SetBody(reqBody).
|
||
SetResult(&response).
|
||
Post(url)
|
||
|
||
if err != nil {
|
||
return nil, fmt.Errorf("检查二维码状态请求失败: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode() != 200 {
|
||
return nil, fmt.Errorf("检查二维码状态API返回错误状态码: %d", resp.StatusCode())
|
||
}
|
||
|
||
return &response, nil
|
||
}
|
||
|
||
// AwakenLogin 唤醒登录
|
||
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 BaseResponse[AwakenLoginResponse]
|
||
resp, err := client.R().
|
||
SetContext(ctx).
|
||
SetBody(reqBody).
|
||
SetResult(&response).
|
||
Post(url)
|
||
|
||
if err != nil {
|
||
return nil, fmt.Errorf("唤醒登录请求失败: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode() != 200 {
|
||
return nil, fmt.Errorf("唤醒登录API返回错误状态码: %d", resp.StatusCode())
|
||
}
|
||
|
||
return &response, nil
|
||
}
|
||
|
||
// AutoHeartbeatStart 开启自动心跳
|
||
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 BaseResponse[AutoHeartbeatResponse]
|
||
resp, err := client.R().
|
||
SetContext(ctx).
|
||
SetBody(reqBody).
|
||
SetResult(&response).
|
||
Post(url)
|
||
|
||
if err != nil {
|
||
return nil, fmt.Errorf("启动自动心跳请求失败: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode() != 200 {
|
||
return nil, fmt.Errorf("启动自动心跳API返回错误状态码: %d", resp.StatusCode())
|
||
}
|
||
|
||
if !response.Success {
|
||
return nil, fmt.Errorf("启动自动心跳API错误: %s", response.Message)
|
||
}
|
||
|
||
return &response, nil
|
||
}
|
||
|
||
// AutoHeartbeatStatus 获取自动心跳状态
|
||
func AutoHeartbeatStatus(ctx context.Context, wxid string, containerHost string) (*AutoHeartbeatStatusResponse, error) {
|
||
client := newHTTPClient()
|
||
|
||
url := fmt.Sprintf("http://%s/AutoHeartbeatStatus", containerHost)
|
||
reqBody := map[string]string{"Wxid": wxid}
|
||
|
||
var response AutoHeartbeatStatusResponse
|
||
resp, err := client.R().
|
||
SetContext(ctx).
|
||
SetBody(reqBody).
|
||
SetResult(&response).
|
||
Post(url)
|
||
|
||
if err != nil {
|
||
return nil, fmt.Errorf("获取自动心跳状态请求失败: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode() != 200 {
|
||
return nil, fmt.Errorf("获取自动心跳状态API返回错误状态码: %d", resp.StatusCode())
|
||
}
|
||
|
||
return &response, nil
|
||
}
|
||
|
||
// AutoHeartbeatStop 停止自动心跳
|
||
func AutoHeartbeatStop(ctx context.Context, wxid string, containerHost string) (*AutoHeartbeatResponse, error) {
|
||
client := newHTTPClient()
|
||
|
||
url := fmt.Sprintf("http://%s/AutoHeartbeatStop", containerHost)
|
||
reqBody := map[string]string{"Wxid": wxid}
|
||
|
||
var response AutoHeartbeatResponse
|
||
resp, err := client.R().
|
||
SetContext(ctx).
|
||
SetBody(reqBody).
|
||
SetResult(&response).
|
||
Post(url)
|
||
|
||
if err != nil {
|
||
return nil, fmt.Errorf("停止自动心跳请求失败: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode() != 200 {
|
||
return nil, fmt.Errorf("停止自动心跳API返回错误状态码: %d", resp.StatusCode())
|
||
}
|
||
|
||
if !response.Success {
|
||
return nil, fmt.Errorf("停止自动心跳API错误: %s", response.Message)
|
||
}
|
||
|
||
return &response, nil
|
||
}
|
||
|
||
// LogOut 登出微信
|
||
func LogOut(ctx context.Context, wxid string, containerHost string) (*AutoHeartbeatResponse, error) {
|
||
client := newHTTPClient()
|
||
|
||
url := fmt.Sprintf("http://%s/LogOut", containerHost)
|
||
reqBody := map[string]string{"Wxid": wxid}
|
||
|
||
var response AutoHeartbeatResponse
|
||
resp, err := client.R().
|
||
SetContext(ctx).
|
||
SetBody(reqBody).
|
||
SetResult(&response).
|
||
Post(url)
|
||
|
||
if err != nil {
|
||
return nil, fmt.Errorf("退出登录请求失败: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode() != 200 {
|
||
return nil, fmt.Errorf("退出登录API返回错误状态码: %d", resp.StatusCode())
|
||
}
|
||
|
||
if !response.Success {
|
||
return nil, fmt.Errorf("退出登录API错误: %s", response.Message)
|
||
}
|
||
|
||
return &response, nil
|
||
}
|
||
|
||
// GetWechatBotStatus 获取微信机器人状态(使用HTTP请求替代)
|
||
func GetWechatBotStatus(ctx context.Context, containerID string) (model.RobotStatus, string, error) {
|
||
// 检查容器状态
|
||
status, err := GetContainerStatus(ctx, containerID)
|
||
if err != nil {
|
||
return model.RobotStatusError, "", fmt.Errorf("failed to get container status: %w", err)
|
||
}
|
||
|
||
if status != "running" {
|
||
return model.RobotStatusOffline, "", nil
|
||
}
|
||
|
||
// 获取容器IP(简化处理,默认使用localhost)
|
||
hostIP := "localhost"
|
||
|
||
client := newHTTPClient()
|
||
url := fmt.Sprintf("http://%s:3000/api/status", hostIP)
|
||
|
||
var response BaseResponse[any]
|
||
resp, err := client.R().
|
||
SetContext(ctx).
|
||
SetResult(&response).
|
||
Get(url)
|
||
|
||
if err != nil {
|
||
return model.RobotStatusError, "", fmt.Errorf("failed to get status result: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode() != 200 {
|
||
return model.RobotStatusError, "", fmt.Errorf("status API returned non-200 status code: %d", resp.StatusCode())
|
||
}
|
||
|
||
if !response.Success {
|
||
return model.RobotStatusError, response.Message, nil
|
||
}
|
||
|
||
// 解析状态数据
|
||
data, ok := response.Data.(map[string]interface{})
|
||
if !ok {
|
||
return model.RobotStatusError, "invalid status data format", nil
|
||
}
|
||
|
||
isLoggedIn, ok := data["isLoggedIn"].(bool)
|
||
if !ok {
|
||
return model.RobotStatusError, "invalid login status format", nil
|
||
}
|
||
|
||
if isLoggedIn {
|
||
return model.RobotStatusOnline, "", nil
|
||
}
|
||
|
||
return model.RobotStatusOffline, "", nil
|
||
}
|
||
|
||
// ListWechatBots 列出所有微信机器人容器
|
||
func ListWechatBots(ctx context.Context) ([]ContainerInfo, error) {
|
||
// 过滤条件:所有微信机器人容器
|
||
filter := map[string][]string{
|
||
"label": {fmt.Sprintf("%s=%s", WechatBotLabelKey, WechatBotLabelValue)},
|
||
}
|
||
|
||
return ListContainers(ctx, filter)
|
||
}
|
||
|
||
// GetWechatContacts 获取微信联系人列表
|
||
func GetWechatContacts(ctx context.Context, containerHost string) (string, error) {
|
||
client := newHTTPClient()
|
||
url := fmt.Sprintf("http://%s/api/contacts", containerHost)
|
||
|
||
resp, err := client.R().
|
||
SetContext(ctx).
|
||
Get(url)
|
||
|
||
if err != nil {
|
||
return "", fmt.Errorf("获取联系人列表请求失败: %w", err)
|
||
}
|
||
|
||
return resp.String(), nil
|
||
}
|
||
|
||
// GetWechatGroupMembers 获取微信群成员
|
||
func GetWechatGroupMembers(ctx context.Context, groupID string, containerHost string) (string, error) {
|
||
client := newHTTPClient()
|
||
url := fmt.Sprintf("http://%s/api/group/%s/members", containerHost, groupID)
|
||
|
||
resp, err := client.R().
|
||
SetContext(ctx).
|
||
Get(url)
|
||
|
||
if err != nil {
|
||
return "", fmt.Errorf("获取群成员请求失败: %w", err)
|
||
}
|
||
|
||
return resp.String(), nil
|
||
}
|
||
|
||
// GetWechatMessages 获取微信消息历史
|
||
func GetWechatMessages(ctx context.Context, containerHost, robotWxId string) (msg []Message, isOffline bool, err error) {
|
||
client := newHTTPClient()
|
||
url := fmt.Sprintf("http://%s/Sync", containerHost)
|
||
|
||
var response BaseResponse[Sync]
|
||
_, err = client.R().
|
||
SetContext(ctx).
|
||
SetBody(map[string]any{
|
||
"Scene": 0,
|
||
"Synckey": "",
|
||
"Wxid": robotWxId,
|
||
}).
|
||
SetResult(&response).
|
||
Post(url)
|
||
|
||
if err != nil {
|
||
err = fmt.Errorf("获取消息历史请求失败: %w", err)
|
||
return
|
||
}
|
||
|
||
// 判断是否离线
|
||
if strings.Contains(response.Message, "用户可能退出") || strings.Contains(response.Message, "数据[DecryptData]失败") {
|
||
isOffline = true
|
||
}
|
||
|
||
msg = response.Data.AddMsgs
|
||
return
|
||
}
|