2025-04-09 10:22:41 +08:00

387 lines
10 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"
"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
}