迁移wechat_ipad配置到环境变量并清理本地状态文件
This commit is contained in:
@@ -20,6 +20,8 @@ DASHBOARD_PORT=8888
|
||||
WECHAT_SERVER_URL=http://host.docker.internal:8059/
|
||||
WECHAT_SERVER_IP=host.docker.internal
|
||||
WECHAT_SERVER_PORT=8059
|
||||
WECHAT_SERVER_TYPE=legacy_855
|
||||
WECHAT_WXID=
|
||||
WECHAT_DEVICE_NAME=ABOTPad
|
||||
WECHAT_DEVICE_NAME=
|
||||
WECHAT_DEVICE_ID=
|
||||
WECHAT_STATE_FILE=temp/wechat_ipad/config.toml
|
||||
|
||||
20
.env.example
20
.env.example
@@ -32,6 +32,16 @@ ABOT_GLANCES_PORT=61208
|
||||
|
||||
ABOT_WX_ADMIN=admin
|
||||
|
||||
WECHAT_SERVER_URL=http://127.0.0.1:8059/
|
||||
WECHAT_SERVER_IP=127.0.0.1
|
||||
WECHAT_SERVER_PORT=8059
|
||||
WECHAT_SERVER_TYPE=legacy_855
|
||||
# 以下三项可留空,首次登录后会自动写入本地状态缓存文件。
|
||||
WECHAT_WXID=
|
||||
WECHAT_DEVICE_NAME=
|
||||
WECHAT_DEVICE_ID=
|
||||
WECHAT_STATE_FILE=temp/wechat_ipad/config.toml
|
||||
|
||||
ABOT_LLM_DEFAULT_BACKEND=dify_workflow_chat
|
||||
ABOT_LLM_DIFY_API_BASE_URL=http://127.0.0.1:8080/v1
|
||||
ABOT_LLM_DIFY_WORKFLOW_CHAT_API_KEY=
|
||||
@@ -56,13 +66,3 @@ ABOT_LLM_IMAGE_MODEL=gpt-image-1
|
||||
# 可选:若希望后台登录会话在重启后保持稳定,建议显式配置。
|
||||
ABOT_DASHBOARD_SECRET_KEY=
|
||||
|
||||
# Docker 场景下 wechat_ipad 的连接参数:
|
||||
# 1. 这组变量主要用于 docker-entrypoint 生成 wechat_ipad/config.toml;
|
||||
# 2. 本地直跑仍可继续使用现有 wechat_ipad/config.toml;
|
||||
# 3. 若 server 运行在宿主机,Docker Desktop / 新版 Linux Docker 可使用 host.docker.internal。
|
||||
WECHAT_SERVER_URL=http://127.0.0.1:8059/
|
||||
WECHAT_SERVER_IP=127.0.0.1
|
||||
WECHAT_SERVER_PORT=8059
|
||||
WECHAT_WXID=
|
||||
WECHAT_DEVICE_NAME=ABOTPad
|
||||
WECHAT_DEVICE_ID=
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ logs/
|
||||
*.log.*
|
||||
.DS_Store
|
||||
temp/
|
||||
wechat_ipad/config.toml
|
||||
|
||||
22
README.MD
22
README.MD
@@ -34,7 +34,7 @@ Windows PowerShell:
|
||||
Copy-Item .env.docker.example .env
|
||||
```
|
||||
|
||||
2. 按实际环境修改 `.env` 中的数据库密码、`WECHAT_SERVER_URL` 等参数
|
||||
2. 按实际环境修改 `.env` 中的数据库密码、`WECHAT_SERVER_URL`、`WECHAT_SERVER_TYPE` 等参数
|
||||
|
||||
3. 启动服务
|
||||
|
||||
@@ -93,13 +93,18 @@ python main.py
|
||||
|
||||
### wechat_ipad 配置
|
||||
|
||||
当前仓库仍保留 [wechat_ipad/config.toml](/d:/learn/abot/wechat_ipad/config.toml:1) 作为现有登录态与设备信息的本地配置文件。
|
||||
现在 `wechat_ipad` 的静态连接参数已经统一走 `.env` / `config.yaml`:
|
||||
|
||||
说明:
|
||||
- `WECHAT_SERVER_URL`
|
||||
- `WECHAT_SERVER_IP`
|
||||
- `WECHAT_SERVER_PORT`
|
||||
- `WECHAT_SERVER_TYPE`
|
||||
|
||||
- Docker 部署会通过环境变量在首次启动时生成该文件
|
||||
- 本地直跑继续兼容原有读取方式
|
||||
- 本轮未强制把全部 `wechat_ipad` 配置迁移进 `.env`
|
||||
登录后的 `wxid / device_id / device_name` 不再要求你手工维护,它们会自动写入本地状态文件:
|
||||
|
||||
- 默认路径:`temp/wechat_ipad/config.toml`
|
||||
- 可通过 `WECHAT_STATE_FILE` 覆盖
|
||||
- 启动时会自动兼容历史 `wechat_ipad/config.toml` 中已有的登录态
|
||||
|
||||
## 目录结构
|
||||
|
||||
@@ -131,7 +136,8 @@ abot/
|
||||
|
||||
- 应用、MariaDB、Redis 已拆分为独立服务
|
||||
- 提供 `docker-compose.yml`、`.dockerignore`、`.env.docker.example`
|
||||
- 保留 `wechat_ipad/config.toml` 的挂载方式,避免这次改动影响你现有登录态逻辑
|
||||
- 静态连接配置统一通过 `.env` 注入,更适合开源仓库和多环境部署
|
||||
- wechat 登录态缓存单独落到 `temp/wechat_ipad/config.toml`
|
||||
|
||||
当前仍建议你在正式生产前继续补充:
|
||||
|
||||
@@ -159,7 +165,7 @@ abot/
|
||||
|
||||
- `.env`
|
||||
- `config.yaml`
|
||||
- `wechat_ipad/config.toml` 中的真实 `wxid` / `device_id`
|
||||
- `temp/wechat_ipad/config.toml` 中的真实 `wxid` / `device_id`
|
||||
- 真实数据库密码、LLM API Key、Webhook Token
|
||||
|
||||
## 路线图
|
||||
|
||||
@@ -53,6 +53,6 @@
|
||||
|
||||
每次准备公开发布新版本时,至少做一次以下检查:
|
||||
|
||||
1. 检查是否误提交 `.env`、`config.yaml`、`wechat_ipad/config.toml`
|
||||
1. 检查是否误提交 `.env`、`config.yaml`、`temp/wechat_ipad/config.toml`
|
||||
2. 检查仓库中是否仍包含真实账号、真实 token、真实 webhook 密钥
|
||||
3. 检查新增的二进制、字体、图片、模板文件是否有明确来源与许可证
|
||||
|
||||
@@ -43,6 +43,21 @@ wx_config:
|
||||
# 微信管理账号,用于接收部分管理员指令。
|
||||
admin: [ "${ABOT_WX_ADMIN:admin}" ]
|
||||
|
||||
wechat_ipad:
|
||||
# wechat_ipad 静态连接配置统一走环境变量:
|
||||
# 1. 用户只需要维护 `.env`,不必再手工同步独立 TOML;
|
||||
# 2. 登录态缓存会单独写入 `state_file`,避免把运行期字段混进用户配置;
|
||||
# 3. `legacy_config_path` 仅用于兼容历史仓库中的 `wechat_ipad/config.toml`。
|
||||
server_url: "${WECHAT_SERVER_URL:http://127.0.0.1:8059/}"
|
||||
server_ip: "${WECHAT_SERVER_IP:127.0.0.1}"
|
||||
server_port: "${WECHAT_SERVER_PORT:8059}"
|
||||
server_type: "${WECHAT_SERVER_TYPE:legacy_855}"
|
||||
wxid: "${WECHAT_WXID:}"
|
||||
device_name: "${WECHAT_DEVICE_NAME:}"
|
||||
device_id: "${WECHAT_DEVICE_ID:}"
|
||||
state_file: "${WECHAT_STATE_FILE:temp/wechat_ipad/config.toml}"
|
||||
legacy_config_path: "${WECHAT_LEGACY_CONFIG_PATH:wechat_ipad/config.toml}"
|
||||
|
||||
llm:
|
||||
default_backend: "${ABOT_LLM_DEFAULT_BACKEND:dify_workflow_chat}"
|
||||
backends:
|
||||
|
||||
15
config.yaml
15
config.yaml
@@ -43,6 +43,21 @@ wx_config:
|
||||
# 微信管理账号,用于接收部分管理员指令。
|
||||
admin: [ "${ABOT_WX_ADMIN:admin}" ]
|
||||
|
||||
wechat_ipad:
|
||||
# wechat_ipad 静态连接配置统一走环境变量:
|
||||
# 1. 用户只需要维护 `.env`,不必再手工同步独立 TOML;
|
||||
# 2. 登录态缓存会单独写入 `state_file`,避免把运行期字段混进用户配置;
|
||||
# 3. `legacy_config_path` 仅用于兼容历史仓库中的 `wechat_ipad/config.toml`。
|
||||
server_url: "${WECHAT_SERVER_URL:http://127.0.0.1:8059/}"
|
||||
server_ip: "${WECHAT_SERVER_IP:127.0.0.1}"
|
||||
server_port: "${WECHAT_SERVER_PORT:8059}"
|
||||
server_type: "${WECHAT_SERVER_TYPE:legacy_855}"
|
||||
wxid: "${WECHAT_WXID:}"
|
||||
device_name: "${WECHAT_DEVICE_NAME:}"
|
||||
device_id: "${WECHAT_DEVICE_ID:}"
|
||||
state_file: "${WECHAT_STATE_FILE:temp/wechat_ipad/config.toml}"
|
||||
legacy_config_path: "${WECHAT_LEGACY_CONFIG_PATH:wechat_ipad/config.toml}"
|
||||
|
||||
llm:
|
||||
default_backend: "${ABOT_LLM_DEFAULT_BACKEND:dify_workflow_chat}"
|
||||
backends:
|
||||
|
||||
@@ -203,6 +203,28 @@ class Config(object):
|
||||
plugin_hot_reload["interval_seconds"] = self._safe_int(plugin_hot_reload.get("interval_seconds", 600), 600)
|
||||
normalized["plugin_hot_reload"] = plugin_hot_reload
|
||||
|
||||
# wechat_ipad 配置归一化:
|
||||
# 1. 静态连接参数现在统一走 config.yaml + .env,而不是要求用户维护独立 TOML;
|
||||
# 2. 登录后的 wxid / device_id / device_name 会落到本地状态文件,因此这里保留 state_file 配置;
|
||||
# 3. legacy_config_path 仅用于兼容历史仓库中的 `wechat_ipad/config.toml`,迁移完成后可逐步淡出。
|
||||
wechat_ipad_config = dict(normalized.get("wechat_ipad", {}) or {})
|
||||
wechat_ipad_config["server_port"] = self._safe_int(wechat_ipad_config.get("server_port", 8059), 8059)
|
||||
wechat_ipad_config["server_url"] = str(wechat_ipad_config.get("server_url", "") or "").strip()
|
||||
wechat_ipad_config["server_ip"] = str(wechat_ipad_config.get("server_ip", "") or "").strip()
|
||||
wechat_ipad_config["server_type"] = str(
|
||||
wechat_ipad_config.get("server_type", "legacy_855") or "legacy_855"
|
||||
).strip()
|
||||
wechat_ipad_config["wxid"] = str(wechat_ipad_config.get("wxid", "") or "").strip()
|
||||
wechat_ipad_config["device_name"] = str(wechat_ipad_config.get("device_name", "") or "").strip()
|
||||
wechat_ipad_config["device_id"] = str(wechat_ipad_config.get("device_id", "") or "").strip()
|
||||
wechat_ipad_config["state_file"] = str(
|
||||
wechat_ipad_config.get("state_file", "temp/wechat_ipad/config.toml") or "temp/wechat_ipad/config.toml"
|
||||
).strip()
|
||||
wechat_ipad_config["legacy_config_path"] = str(
|
||||
wechat_ipad_config.get("legacy_config_path", "wechat_ipad/config.toml") or "wechat_ipad/config.toml"
|
||||
).strip()
|
||||
normalized["wechat_ipad"] = wechat_ipad_config
|
||||
|
||||
return normalized
|
||||
|
||||
@classmethod
|
||||
@@ -317,6 +339,37 @@ class Config(object):
|
||||
"已配置告警接收人,但发件邮箱配置不完整,告警链路不可用。",
|
||||
)
|
||||
|
||||
def _validate_wechat_ipad_config(self, report: dict) -> None:
|
||||
"""检查 wechat_ipad 静态连接配置是否完整。"""
|
||||
wechat_ipad_config = self.wechat_ipad or {}
|
||||
server_url = str(wechat_ipad_config.get("server_url", "") or "").strip()
|
||||
server_ip = str(wechat_ipad_config.get("server_ip", "") or "").strip()
|
||||
server_port = wechat_ipad_config.get("server_port", 0)
|
||||
|
||||
if not server_url:
|
||||
self._append_issue(
|
||||
report["errors"],
|
||||
"missing_wechat_server_url",
|
||||
"wechat_ipad.server_url",
|
||||
"wechat_ipad server_url 未配置,机器人无法连接 wechat_ipad server。",
|
||||
)
|
||||
|
||||
if not server_ip:
|
||||
self._append_issue(
|
||||
report["errors"],
|
||||
"missing_wechat_server_ip",
|
||||
"wechat_ipad.server_ip",
|
||||
"wechat_ipad server_ip 未配置,机器人无法连接 wechat_ipad server。",
|
||||
)
|
||||
|
||||
if not server_port:
|
||||
self._append_issue(
|
||||
report["errors"],
|
||||
"missing_wechat_server_port",
|
||||
"wechat_ipad.server_port",
|
||||
"wechat_ipad server_port 未配置,机器人无法连接 wechat_ipad server。",
|
||||
)
|
||||
|
||||
def _validate_llm_config(self, report: dict) -> None:
|
||||
"""检查 LLM 配置的完整性与路由一致性。"""
|
||||
llm_config = self.llm or {}
|
||||
@@ -396,6 +449,7 @@ class Config(object):
|
||||
self._validate_unresolved_placeholders(report)
|
||||
self._validate_required_sections(report)
|
||||
self._validate_email_config(report)
|
||||
self._validate_wechat_ipad_config(report)
|
||||
self._validate_llm_config(report)
|
||||
self._validate_plaintext_secrets(report)
|
||||
return report
|
||||
@@ -451,6 +505,7 @@ class Config(object):
|
||||
self.redis = self.resolved_config.get("redis_config", {})
|
||||
self.email = self.resolved_config.get("email_config", {})
|
||||
self.wx_config = self.resolved_config.get("wx_config", {})
|
||||
self.wechat_ipad = self.resolved_config.get("wechat_ipad", {})
|
||||
self.llm = self.resolved_config.get("llm", {})
|
||||
|
||||
self.validation_report = self.validate()
|
||||
|
||||
@@ -80,16 +80,18 @@ services:
|
||||
WECHAT_SERVER_URL: ${WECHAT_SERVER_URL:-http://host.docker.internal:8059/}
|
||||
WECHAT_SERVER_IP: ${WECHAT_SERVER_IP:-host.docker.internal}
|
||||
WECHAT_SERVER_PORT: ${WECHAT_SERVER_PORT:-8059}
|
||||
WECHAT_SERVER_TYPE: ${WECHAT_SERVER_TYPE:-legacy_855}
|
||||
WECHAT_WXID: ${WECHAT_WXID:-}
|
||||
WECHAT_DEVICE_NAME: ${WECHAT_DEVICE_NAME:-ABOTPad}
|
||||
WECHAT_DEVICE_NAME: ${WECHAT_DEVICE_NAME:-}
|
||||
WECHAT_DEVICE_ID: ${WECHAT_DEVICE_ID:-}
|
||||
WECHAT_STATE_FILE: ${WECHAT_STATE_FILE:-temp/wechat_ipad/config.toml}
|
||||
ports:
|
||||
- "${DASHBOARD_PORT:-8888}:8888"
|
||||
volumes:
|
||||
# 日志目录映射到宿主机,方便排障与运维备份。
|
||||
- ./logs:/app/logs
|
||||
# 保留 wechat_ipad 的本地配置文件,避免容器重建后丢失登录态。
|
||||
- ./wechat_ipad/config.toml:/app/wechat_ipad/config.toml
|
||||
# 保留 wechat_ipad 的本地登录态缓存,避免容器重建后丢失 wxid / device 信息。
|
||||
- ./temp:/app/temp
|
||||
extra_hosts:
|
||||
# 兼容 Linux 环境下通过 host.docker.internal 访问宿主机上的 wechat_ipad server。
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
||||
@@ -46,24 +46,24 @@ glances:
|
||||
|
||||
wx_config:
|
||||
admin: [ "\${ABOT_WX_ADMIN:admin}" ]
|
||||
|
||||
wechat_ipad:
|
||||
server_url: "\${WECHAT_SERVER_URL:http://host.docker.internal:8059/}"
|
||||
server_ip: "\${WECHAT_SERVER_IP:host.docker.internal}"
|
||||
server_port: "\${WECHAT_SERVER_PORT:8059}"
|
||||
server_type: "\${WECHAT_SERVER_TYPE:legacy_855}"
|
||||
wxid: "\${WECHAT_WXID:}"
|
||||
device_name: "\${WECHAT_DEVICE_NAME:}"
|
||||
device_id: "\${WECHAT_DEVICE_ID:}"
|
||||
state_file: "\${WECHAT_STATE_FILE:temp/wechat_ipad/config.toml}"
|
||||
legacy_config_path: "\${WECHAT_LEGACY_CONFIG_PATH:wechat_ipad/config.toml}"
|
||||
EOF
|
||||
fi
|
||||
|
||||
# wechat_ipad 配置保留为独立文件:
|
||||
# 1. 兼容现有代码对 wechat_ipad/config.toml 的读取方式;
|
||||
# 2. 仅在文件缺失时生成,避免覆盖用户已有的登录态与设备信息;
|
||||
# 3. 这样既支持 Docker 一键部署,也不强行改动用户本地运行方式。
|
||||
mkdir -p /app/wechat_ipad
|
||||
if [ ! -f /app/wechat_ipad/config.toml ]; then
|
||||
cat > /app/wechat_ipad/config.toml <<EOF
|
||||
server_url = "${WECHAT_SERVER_URL}"
|
||||
wxid = "${WECHAT_WXID}"
|
||||
device_id = "${WECHAT_DEVICE_ID}"
|
||||
device_name = "${WECHAT_DEVICE_NAME}"
|
||||
server_ip = "${WECHAT_SERVER_IP}"
|
||||
server_port = "${WECHAT_SERVER_PORT}"
|
||||
login_time = ""
|
||||
EOF
|
||||
fi
|
||||
# wechat_ipad 登录态现在单独落到本地状态目录:
|
||||
# 1. 静态连接配置已统一走 `.env` / `config.yaml`;
|
||||
# 2. 这里只需要保证状态文件目录存在,供运行期自动写入 wxid / device 缓存;
|
||||
# 3. 这样容器启动脚本不再负责生成和维护第二份静态配置文件。
|
||||
mkdir -p /app/temp/wechat_ipad
|
||||
|
||||
exec "$@"
|
||||
|
||||
@@ -37,6 +37,7 @@ Copy-Item .env.docker.example .env
|
||||
- `WECHAT_SERVER_URL`
|
||||
- `WECHAT_SERVER_IP`
|
||||
- `WECHAT_SERVER_PORT`
|
||||
- `WECHAT_SERVER_TYPE`
|
||||
- `DASHBOARD_PORT`
|
||||
|
||||
## 3. 启动命令
|
||||
@@ -53,13 +54,24 @@ docker compose up -d --build
|
||||
|
||||
## 4. wechat_ipad 配置说明
|
||||
|
||||
Compose 已将宿主机的 [wechat_ipad/config.toml](/d:/learn/abot/wechat_ipad/config.toml:1) 映射进容器。
|
||||
现在 `wechat_ipad` 的静态连接参数统一走 `.env`:
|
||||
|
||||
这样做的原因:
|
||||
- `WECHAT_SERVER_URL`
|
||||
- `WECHAT_SERVER_IP`
|
||||
- `WECHAT_SERVER_PORT`
|
||||
- `WECHAT_SERVER_TYPE`
|
||||
|
||||
- 保留现有代码对 `wechat_ipad/config.toml` 的读取逻辑
|
||||
- 容器重建后不丢失已有登录态
|
||||
- 不强制你这次就把全部 wechat 配置迁移到 `.env`
|
||||
登录后的 `wxid / device_id / device_name` 会自动写入本地状态缓存:
|
||||
|
||||
- 默认路径:`temp/wechat_ipad/config.toml`
|
||||
- Compose 已将宿主机的 `./temp` 目录映射进容器
|
||||
- 因此容器重建后,已有登录态仍会保留
|
||||
|
||||
兼容说明:
|
||||
|
||||
- 启动时仍会尝试兼容历史 `wechat_ipad/config.toml`
|
||||
- 但新的人工维护入口已经变成 `.env`
|
||||
- 后续不再建议继续手工编辑旧 TOML 文件
|
||||
|
||||
## 5. 常用命令
|
||||
|
||||
@@ -104,5 +116,5 @@ docker compose up -d --build
|
||||
|
||||
1. `.env` 未提交
|
||||
2. `config.yaml` 未提交真实密钥
|
||||
3. `wechat_ipad/config.toml` 未包含真实 `wxid`、`device_id`
|
||||
3. `temp/wechat_ipad/config.toml` 未被误公开
|
||||
4. Dashboard 账号密码与 webhook token 已替换为你自己的值
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
当前微信接入实现仍需关注以下历史耦合点与残留影响:
|
||||
|
||||
- [robot.py](/d:/learn/abot/robot.py:221) 直接读取 `wechat_ipad/config.toml`
|
||||
- 历史版本曾直接依赖 `wechat_ipad/config.toml`,当前已开始切向 `config.yaml + .env`
|
||||
- `Robot` 的实例化入口虽然已切到 `WechatGateway`,但配置读取与业务初始化仍在主程序中
|
||||
- 855 的运行时职责已经迁入 provider,但 864 尚未接入验证,统一抽象仍需继续收敛
|
||||
- `wechat_ipad/client/*.py` 仍作为历史目录存在,接口路径、请求体、返回结构都面向旧 server 编写
|
||||
|
||||
79
robot.py
79
robot.py
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import asyncio
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import tomllib
|
||||
@@ -216,20 +217,22 @@ class Robot:
|
||||
def init_wechat_ipad(self):
|
||||
"""初始化wechat_ipad客户端"""
|
||||
try:
|
||||
# 读取config.toml文件
|
||||
with open("wechat_ipad/config.toml", "rb") as f:
|
||||
self.ipad_config = tomllib.load(f)
|
||||
# wechat_ipad 静态配置统一走 Config:
|
||||
# 1. 用户现在只需要维护 `.env` / `config.yaml`,不必再手工维护独立 TOML;
|
||||
# 2. 登录态仍保留本地缓存文件,但只作为运行期状态,不再作为主配置源;
|
||||
# 3. 这里先做一次“静态配置 + 本地状态缓存 + 历史 config.toml”的合并,保证升级不中断。
|
||||
self.ipad_config = self._build_wechat_ipad_runtime_config()
|
||||
|
||||
self.LOG.debug("正在初始化wechat_ipad客户端...")
|
||||
|
||||
# 检查必要的配置
|
||||
server_url = self.ipad_config.get("server_url", "")
|
||||
server_url = str(self.ipad_config.get("server_url", "") or "").strip()
|
||||
if server_url == "":
|
||||
self.LOG.error("server_url不能为空,wechat_ipad初始化失败")
|
||||
return False
|
||||
|
||||
server_ip = self.ipad_config.get("server_ip", "")
|
||||
server_port = self.ipad_config.get("server_port", 8059)
|
||||
server_ip = str(self.ipad_config.get("server_ip", "") or "").strip()
|
||||
server_port = int(self.ipad_config.get("server_port", 8059) or 8059)
|
||||
|
||||
# 当前阶段先通过 Gateway 承接 provider 选择:
|
||||
# 1. 默认仍走 legacy_855,保持现有现网协议行为;
|
||||
@@ -275,7 +278,7 @@ class Robot:
|
||||
# 3. 这样未来切到 864 时,主链路只需要替换 provider,而不是继续改这里的大循环。
|
||||
await self.ipad_bot.run_runtime(
|
||||
ipad_config=self.ipad_config,
|
||||
config_path="wechat_ipad/config.toml",
|
||||
state_path=str(self.ipad_config.get("state_file", "temp/wechat_ipad/config.toml") or "temp/wechat_ipad/config.toml"),
|
||||
logger=self.LOG,
|
||||
on_login_ready=self._on_ipad_login_ready,
|
||||
on_history_message=self._archive_startup_history_message,
|
||||
@@ -289,6 +292,68 @@ class Robot:
|
||||
self.LOG.exception(f"wechat_ipad客户端运行出错: {e}")
|
||||
self.ipad_running = False
|
||||
|
||||
def _build_wechat_ipad_runtime_config(self) -> dict:
|
||||
"""构建 wechat_ipad 的运行时配置快照。
|
||||
|
||||
合并顺序说明:
|
||||
1. 先取 `config.yaml + .env` 里的静态连接配置,作为新的唯一人工维护入口;
|
||||
2. 再补本地状态缓存中的 wxid / device 信息,避免每次启动都重新扫码;
|
||||
3. 最后兼容历史 `wechat_ipad/config.toml`,让老环境升级后可以平滑迁移。
|
||||
"""
|
||||
base_config = dict(getattr(self.config, "wechat_ipad", {}) or {})
|
||||
state_path = str(base_config.get("state_file", "temp/wechat_ipad/config.toml") or "temp/wechat_ipad/config.toml")
|
||||
legacy_config_path = str(
|
||||
base_config.get("legacy_config_path", "wechat_ipad/config.toml") or "wechat_ipad/config.toml"
|
||||
)
|
||||
|
||||
state_config = self._load_toml_config_if_exists(state_path)
|
||||
legacy_config = {}
|
||||
if os.path.abspath(state_path) != os.path.abspath(legacy_config_path):
|
||||
legacy_config = self._load_toml_config_if_exists(legacy_config_path)
|
||||
|
||||
merged_config = dict(base_config)
|
||||
|
||||
# 静态字段优先级:`.env/config.yaml` > 历史文件。
|
||||
# 这样每个人只要改 `.env` 就能切换自己的 server,不需要再同步别处。
|
||||
for field_name in ("server_url", "server_ip", "server_port", "server_type"):
|
||||
if not str(merged_config.get(field_name, "") or "").strip():
|
||||
legacy_value = legacy_config.get(field_name)
|
||||
if legacy_value not in (None, ""):
|
||||
merged_config[field_name] = legacy_value
|
||||
|
||||
# 动态字段优先级:显式环境变量 > 新状态文件 > 历史 config.toml。
|
||||
# 这样既支持用户手工覆盖,也保留现有登录缓存迁移能力。
|
||||
for field_name in ("wxid", "device_name", "device_id", "login_time"):
|
||||
current_value = merged_config.get(field_name)
|
||||
if str(current_value or "").strip():
|
||||
continue
|
||||
|
||||
state_value = state_config.get(field_name)
|
||||
if state_value not in (None, ""):
|
||||
merged_config[field_name] = state_value
|
||||
continue
|
||||
|
||||
legacy_value = legacy_config.get(field_name)
|
||||
if legacy_value not in (None, ""):
|
||||
merged_config[field_name] = legacy_value
|
||||
|
||||
merged_config["state_file"] = state_path
|
||||
merged_config["legacy_config_path"] = legacy_config_path
|
||||
return merged_config
|
||||
|
||||
def _load_toml_config_if_exists(self, file_path: str) -> dict:
|
||||
"""安全读取一个 TOML 文件,缺失或格式异常时回退为空配置。"""
|
||||
normalized_path = str(file_path or "").strip()
|
||||
if not normalized_path or not os.path.exists(normalized_path):
|
||||
return {}
|
||||
|
||||
try:
|
||||
with open(normalized_path, "rb") as f:
|
||||
return tomllib.load(f)
|
||||
except Exception as e:
|
||||
self.LOG.warning(f"读取 TOML 配置失败,将按空配置继续: path={normalized_path}, error={e}")
|
||||
return {}
|
||||
|
||||
async def _on_ipad_login_ready(self, login_identity: dict) -> None:
|
||||
"""处理 provider 登录成功后的项目侧初始化动作。
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
server_url = "http://192.168.2.170:8059/"
|
||||
wxid = "wxid_72ow1edm3kea22"
|
||||
device_id = "4978fc0fd191cf45a4e55fae7936b153"
|
||||
device_name = "shui niu's Pad"
|
||||
server_ip = "192.168.2.170"
|
||||
server_port = "8059"
|
||||
login_time = "2025-05-15 10:51:22"
|
||||
8
wechat_ipad/config.toml.example
Normal file
8
wechat_ipad/config.toml.example
Normal file
@@ -0,0 +1,8 @@
|
||||
server_url = "http://127.0.0.1:8059/"
|
||||
server_ip = "127.0.0.1"
|
||||
server_port = "8059"
|
||||
server_type = "legacy_855"
|
||||
wxid = ""
|
||||
device_id = ""
|
||||
device_name = ""
|
||||
login_time = ""
|
||||
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import os
|
||||
import time
|
||||
from typing import Any, Awaitable, Callable
|
||||
|
||||
@@ -40,7 +41,7 @@ class Legacy855RuntimeMixin:
|
||||
self,
|
||||
*,
|
||||
ipad_config: dict,
|
||||
config_path: str,
|
||||
state_path: str,
|
||||
logger,
|
||||
on_login_ready: AsyncCallback,
|
||||
on_history_message: AsyncCallback,
|
||||
@@ -52,7 +53,7 @@ class Legacy855RuntimeMixin:
|
||||
"""启动 855 provider 的完整运行时。
|
||||
|
||||
参数说明:
|
||||
1. `ipad_config` 与 `config_path` 由上层传入,provider 只负责更新和落盘登录态;
|
||||
1. `ipad_config` 与 `state_path` 由上层传入,provider 只负责更新和落盘登录态;
|
||||
2. `on_*` 回调保持尽量少,只暴露业务层真正需要接手的几个时机;
|
||||
3. 这样既避免 `Robot` 再写协议细节,也不额外引入复杂的事件总线或状态机层。
|
||||
"""
|
||||
@@ -70,7 +71,7 @@ class Legacy855RuntimeMixin:
|
||||
device_name=device_name,
|
||||
device_id=device_id,
|
||||
ipad_config=ipad_config,
|
||||
config_path=config_path,
|
||||
state_path=state_path,
|
||||
logger=logger,
|
||||
)
|
||||
|
||||
@@ -160,7 +161,7 @@ class Legacy855RuntimeMixin:
|
||||
device_name: str,
|
||||
device_id: str,
|
||||
ipad_config: dict,
|
||||
config_path: str,
|
||||
state_path: str,
|
||||
logger,
|
||||
) -> None:
|
||||
"""保证当前 provider 已完成登录,并把登录结果写回配置。
|
||||
@@ -168,7 +169,7 @@ class Legacy855RuntimeMixin:
|
||||
这里沿用现有 855 的行为:
|
||||
1. 优先复用缓存唤醒;
|
||||
2. 唤醒失败或无缓存时回退到二维码登录;
|
||||
3. 登录成功后继续把 wxid / device 信息写回 `config.toml`,保持现有部署习惯不变。
|
||||
3. 登录成功后只把 wxid / device 信息写回本地状态文件,不再要求用户手工维护 TOML。
|
||||
"""
|
||||
if await self.is_logged_in(wxid):
|
||||
self.wxid = wxid
|
||||
@@ -212,10 +213,39 @@ class Legacy855RuntimeMixin:
|
||||
ipad_config["device_name"] = device_name
|
||||
ipad_config["device_id"] = device_id
|
||||
ipad_config["login_time"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
toml.dump(ipad_config, f)
|
||||
self._save_runtime_state(
|
||||
state_path=state_path,
|
||||
state_payload={
|
||||
"wxid": self.wxid,
|
||||
"device_name": device_name,
|
||||
"device_id": device_id,
|
||||
"login_time": ipad_config["login_time"],
|
||||
},
|
||||
logger=logger,
|
||||
)
|
||||
break
|
||||
|
||||
@staticmethod
|
||||
def _save_runtime_state(*, state_path: str, state_payload: dict[str, Any], logger) -> None:
|
||||
"""把运行期登录状态写入本地缓存文件。
|
||||
|
||||
这里刻意只保存动态字段:
|
||||
1. server_url / server_ip / server_port 已经统一走 `.env`;
|
||||
2. 本地状态文件只承载登录缓存,避免用户再维护两套静态配置;
|
||||
3. 路径所在目录不存在时自动创建,兼容首次启动与 Docker 挂载目录。
|
||||
"""
|
||||
try:
|
||||
normalized_path = str(state_path or "").strip()
|
||||
if not normalized_path:
|
||||
return
|
||||
state_dir = os.path.dirname(normalized_path)
|
||||
if state_dir:
|
||||
os.makedirs(state_dir, exist_ok=True)
|
||||
with open(normalized_path, "w", encoding="utf-8") as f:
|
||||
toml.dump(state_payload, f)
|
||||
except Exception as e:
|
||||
logger.warning(f"写入 wechat_ipad 本地状态失败: path={state_path}, error={e}")
|
||||
|
||||
def _apply_login_result(self, *, data: dict, logger) -> None:
|
||||
"""把登录接口返回的用户信息统一写回当前 provider。"""
|
||||
acct_section = data.get("acctSectResp", {}) or {}
|
||||
|
||||
Reference in New Issue
Block a user