2025-04-02 14:29:44 +08:00

453 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package docker
import (
"context"
"encoding/json"
"fmt"
"log"
"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"
)
// 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 {
Status int `json:"Status"`
} `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().
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, containerID string, containerHost string) (*QRCodeResponse, error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/GetQRCode", containerHost)
var response 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, containerID string, uuid string, containerHost string) (*CheckUuidResponse, error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/CheckUuid", containerHost)
reqBody := map[string]string{"Uuid": uuid}
var response 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, containerID string, wxid string, containerHost string) (*AwakenLoginResponse, error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/AwakenLogin", containerHost)
reqBody := map[string]string{"Wxid": wxid}
var response 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, containerID string, wxid string, containerHost string) (*AutoHeartbeatResponse, error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/AutoHeartbeatStart", 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
}
// AutoHeartbeatStatus 获取自动心跳状态
func AutoHeartbeatStatus(ctx context.Context, containerID string, 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, containerID string, 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, containerID string, 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
}
// LogoutWechatBot 登出微信机器人(旧方法使用HTTP请求替代)
func LogoutWechatBot(ctx context.Context, containerID string) error {
// 获取容器IP(简化处理默认使用localhost)
hostIP := "localhost"
client := newHTTPClient()
url := fmt.Sprintf("http://%s:3000/api/logout", hostIP)
var response APIResponse
resp, err := client.R().
SetContext(ctx).
SetResult(&response).
Post(url)
if err != nil {
return fmt.Errorf("failed to execute logout command: %w", err)
}
if resp.StatusCode() != 200 {
return fmt.Errorf("logout API returned non-200 status code: %d", resp.StatusCode())
}
if !response.Success {
return fmt.Errorf("logout failed: %s", response.Message)
}
return 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 APIResponse
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, containerID string, 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, containerID string, 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, containerID string, contactID string, limit int, containerHost string) (string, error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/api/messages/%s?limit=%d", containerHost, contactID, limit)
resp, err := client.R().
SetContext(ctx).
Get(url)
if err != nil {
return "", fmt.Errorf("获取消息历史请求失败: %w", err)
}
return resp.String(), nil
}