soulsync 1.0.21 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +41 -43
- package/package.json +5 -3
- package/src/api-client.js +84 -0
- package/src/config.js +8 -8
- package/src/daemon.js +94 -0
- package/src/sync-engine.js +745 -0
- package/debug.py +0 -221
- package/requirements.txt +0 -3
- package/setup.sh +0 -91
- package/src/__init__.py +0 -1
- package/src/client.py +0 -300
- package/src/main.py +0 -479
- package/src/profiles.py +0 -88
- package/src/sync.py +0 -210
- package/src/version_manager.py +0 -61
- package/src/watcher.py +0 -133
- package/test_ssl_fix.py +0 -58
package/debug.py
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
SoulSync OpenClaw 插件调试工具
|
|
4
|
-
用于检查安装环境和依赖
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import sys
|
|
8
|
-
import os
|
|
9
|
-
import json
|
|
10
|
-
import importlib.util
|
|
11
|
-
|
|
12
|
-
def check_python_version():
|
|
13
|
-
"""检查 Python 版本"""
|
|
14
|
-
print("=" * 50)
|
|
15
|
-
print("Python 版本检查")
|
|
16
|
-
print("=" * 50)
|
|
17
|
-
version = sys.version_info
|
|
18
|
-
print(f"Python 版本: {version.major}.{version.minor}.{version.micro}")
|
|
19
|
-
if version.major < 3 or (version.major == 3 and version.minor < 7):
|
|
20
|
-
print("❌ 需要 Python 3.7 或更高版本")
|
|
21
|
-
return False
|
|
22
|
-
print("✅ Python 版本符合要求")
|
|
23
|
-
return True
|
|
24
|
-
|
|
25
|
-
def check_dependencies():
|
|
26
|
-
"""检查依赖包"""
|
|
27
|
-
print("\n" + "=" * 50)
|
|
28
|
-
print("依赖包检查")
|
|
29
|
-
print("=" * 50)
|
|
30
|
-
|
|
31
|
-
required = {
|
|
32
|
-
'requests': 'requests>=2.28.0',
|
|
33
|
-
'watchdog': 'watchdog>=3.0.0',
|
|
34
|
-
'websocket': 'websocket-client>=1.6.0'
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
all_ok = True
|
|
38
|
-
for module, package in required.items():
|
|
39
|
-
try:
|
|
40
|
-
if module == 'websocket':
|
|
41
|
-
importlib.import_module('websocket')
|
|
42
|
-
else:
|
|
43
|
-
importlib.import_module(module)
|
|
44
|
-
print(f"✅ {package}")
|
|
45
|
-
except ImportError:
|
|
46
|
-
print(f"❌ {package} - 未安装")
|
|
47
|
-
all_ok = False
|
|
48
|
-
|
|
49
|
-
return all_ok
|
|
50
|
-
|
|
51
|
-
def check_file_structure():
|
|
52
|
-
"""检查文件结构"""
|
|
53
|
-
print("\n" + "=" * 50)
|
|
54
|
-
print("文件结构检查")
|
|
55
|
-
print("=" * 50)
|
|
56
|
-
|
|
57
|
-
plugin_dir = os.path.dirname(os.path.abspath(__file__))
|
|
58
|
-
print(f"插件目录: {plugin_dir}")
|
|
59
|
-
|
|
60
|
-
required_files = [
|
|
61
|
-
'openclaw.plugin.json',
|
|
62
|
-
'config.json',
|
|
63
|
-
'requirements.txt',
|
|
64
|
-
'src/main.py',
|
|
65
|
-
'src/__init__.py',
|
|
66
|
-
'src/client.py',
|
|
67
|
-
'src/watcher.py',
|
|
68
|
-
'src/sync.py',
|
|
69
|
-
]
|
|
70
|
-
|
|
71
|
-
all_ok = True
|
|
72
|
-
for file in required_files:
|
|
73
|
-
path = os.path.join(plugin_dir, file)
|
|
74
|
-
exists = os.path.exists(path)
|
|
75
|
-
status = "✅" if exists else "❌"
|
|
76
|
-
print(f"{status} {file}")
|
|
77
|
-
if not exists:
|
|
78
|
-
all_ok = False
|
|
79
|
-
|
|
80
|
-
return all_ok
|
|
81
|
-
|
|
82
|
-
def check_config():
|
|
83
|
-
"""检查配置文件"""
|
|
84
|
-
print("\n" + "=" * 50)
|
|
85
|
-
print("配置文件检查")
|
|
86
|
-
print("=" * 50)
|
|
87
|
-
|
|
88
|
-
config_path = os.path.join(os.path.dirname(__file__), 'config.json')
|
|
89
|
-
|
|
90
|
-
if not os.path.exists(config_path):
|
|
91
|
-
print("❌ config.json 不存在")
|
|
92
|
-
return False
|
|
93
|
-
|
|
94
|
-
try:
|
|
95
|
-
with open(config_path, 'r', encoding='utf-8') as f:
|
|
96
|
-
config = json.load(f)
|
|
97
|
-
|
|
98
|
-
print("✅ config.json 格式正确")
|
|
99
|
-
|
|
100
|
-
# 检查必要字段
|
|
101
|
-
required_fields = ['cloud_url', 'email', 'password', 'workspace']
|
|
102
|
-
for field in required_fields:
|
|
103
|
-
value = config.get(field)
|
|
104
|
-
if value:
|
|
105
|
-
if field in ['email', 'password']:
|
|
106
|
-
print(f"✅ {field}: {'*' * len(str(value))}")
|
|
107
|
-
else:
|
|
108
|
-
print(f"✅ {field}: {value}")
|
|
109
|
-
else:
|
|
110
|
-
print(f"⚠️ {field}: 未配置")
|
|
111
|
-
|
|
112
|
-
return True
|
|
113
|
-
except json.JSONDecodeError as e:
|
|
114
|
-
print(f"❌ config.json 格式错误: {e}")
|
|
115
|
-
return False
|
|
116
|
-
except Exception as e:
|
|
117
|
-
print(f"❌ 读取 config.json 失败: {e}")
|
|
118
|
-
return False
|
|
119
|
-
|
|
120
|
-
def check_workspace():
|
|
121
|
-
"""检查工作目录"""
|
|
122
|
-
print("\n" + "=" * 50)
|
|
123
|
-
print("工作目录检查")
|
|
124
|
-
print("=" * 50)
|
|
125
|
-
|
|
126
|
-
plugin_dir = os.path.dirname(os.path.abspath(__file__))
|
|
127
|
-
workspace = os.path.join(plugin_dir, 'workspace')
|
|
128
|
-
|
|
129
|
-
if os.path.exists(workspace):
|
|
130
|
-
print(f"✅ workspace 目录存在: {workspace}")
|
|
131
|
-
|
|
132
|
-
# 检查是否可写
|
|
133
|
-
if os.access(workspace, os.W_OK):
|
|
134
|
-
print("✅ workspace 目录可写")
|
|
135
|
-
else:
|
|
136
|
-
print("❌ workspace 目录不可写")
|
|
137
|
-
return False
|
|
138
|
-
else:
|
|
139
|
-
print(f"⚠️ workspace 目录不存在: {workspace}")
|
|
140
|
-
try:
|
|
141
|
-
os.makedirs(workspace, exist_ok=True)
|
|
142
|
-
print("✅ 已创建 workspace 目录")
|
|
143
|
-
except Exception as e:
|
|
144
|
-
print(f"❌ 创建 workspace 目录失败: {e}")
|
|
145
|
-
return False
|
|
146
|
-
|
|
147
|
-
return True
|
|
148
|
-
|
|
149
|
-
def test_import():
|
|
150
|
-
"""测试导入主模块"""
|
|
151
|
-
print("\n" + "=" * 50)
|
|
152
|
-
print("模块导入测试")
|
|
153
|
-
print("=" * 50)
|
|
154
|
-
|
|
155
|
-
plugin_dir = os.path.dirname(os.path.abspath(__file__))
|
|
156
|
-
src_dir = os.path.join(plugin_dir, 'src')
|
|
157
|
-
|
|
158
|
-
if src_dir not in sys.path:
|
|
159
|
-
sys.path.insert(0, src_dir)
|
|
160
|
-
|
|
161
|
-
try:
|
|
162
|
-
from main import SoulSyncPlugin
|
|
163
|
-
print("✅ 成功导入 SoulSyncPlugin")
|
|
164
|
-
return True
|
|
165
|
-
except Exception as e:
|
|
166
|
-
print(f"❌ 导入失败: {e}")
|
|
167
|
-
import traceback
|
|
168
|
-
traceback.print_exc()
|
|
169
|
-
return False
|
|
170
|
-
|
|
171
|
-
def main():
|
|
172
|
-
"""主函数"""
|
|
173
|
-
print("\n" + "=" * 50)
|
|
174
|
-
print("SoulSync OpenClaw 插件调试工具")
|
|
175
|
-
print("=" * 50 + "\n")
|
|
176
|
-
|
|
177
|
-
checks = [
|
|
178
|
-
("Python 版本", check_python_version),
|
|
179
|
-
("依赖包", check_dependencies),
|
|
180
|
-
("文件结构", check_file_structure),
|
|
181
|
-
("配置文件", check_config),
|
|
182
|
-
("工作目录", check_workspace),
|
|
183
|
-
("模块导入", test_import),
|
|
184
|
-
]
|
|
185
|
-
|
|
186
|
-
results = []
|
|
187
|
-
for name, check_func in checks:
|
|
188
|
-
try:
|
|
189
|
-
result = check_func()
|
|
190
|
-
results.append((name, result))
|
|
191
|
-
except Exception as e:
|
|
192
|
-
print(f"\n❌ {name} 检查出错: {e}")
|
|
193
|
-
results.append((name, False))
|
|
194
|
-
|
|
195
|
-
# 总结
|
|
196
|
-
print("\n" + "=" * 50)
|
|
197
|
-
print("检查结果总结")
|
|
198
|
-
print("=" * 50)
|
|
199
|
-
|
|
200
|
-
for name, result in results:
|
|
201
|
-
status = "✅ 通过" if result else "❌ 失败"
|
|
202
|
-
print(f"{status} - {name}")
|
|
203
|
-
|
|
204
|
-
all_passed = all(result for _, result in results)
|
|
205
|
-
|
|
206
|
-
print("\n" + "=" * 50)
|
|
207
|
-
if all_passed:
|
|
208
|
-
print("🎉 所有检查通过!插件应该可以正常运行。")
|
|
209
|
-
print("\n运行命令: python3 src/main.py")
|
|
210
|
-
else:
|
|
211
|
-
print("⚠️ 部分检查未通过,请根据上方提示修复问题。")
|
|
212
|
-
print("\n常见问题解决:")
|
|
213
|
-
print("1. 安装依赖: pip3 install -r requirements.txt")
|
|
214
|
-
print("2. 修复权限: chmod -R 755 .")
|
|
215
|
-
print("3. 创建目录: mkdir -p workspace/memory")
|
|
216
|
-
print("=" * 50)
|
|
217
|
-
|
|
218
|
-
return 0 if all_passed else 1
|
|
219
|
-
|
|
220
|
-
if __name__ == '__main__':
|
|
221
|
-
sys.exit(main())
|
package/requirements.txt
DELETED
package/setup.sh
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# SoulSync OpenClaw 插件 Linux 安装脚本
|
|
3
|
-
|
|
4
|
-
set -e # 遇到错误立即退出
|
|
5
|
-
|
|
6
|
-
echo "======================================"
|
|
7
|
-
echo "SoulSync OpenClaw 插件安装脚本"
|
|
8
|
-
echo "======================================"
|
|
9
|
-
echo ""
|
|
10
|
-
|
|
11
|
-
# 获取脚本所在目录
|
|
12
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
-
cd "$SCRIPT_DIR"
|
|
14
|
-
|
|
15
|
-
echo "插件目录: $SCRIPT_DIR"
|
|
16
|
-
echo ""
|
|
17
|
-
|
|
18
|
-
# 检查 Python
|
|
19
|
-
if ! command -v python3 &> /dev/null; then
|
|
20
|
-
echo "❌ 未找到 Python3"
|
|
21
|
-
echo "请安装 Python3: sudo apt-get install python3"
|
|
22
|
-
exit 1
|
|
23
|
-
fi
|
|
24
|
-
|
|
25
|
-
echo "✅ Python3 已安装: $(python3 --version)"
|
|
26
|
-
|
|
27
|
-
# 检查 pip
|
|
28
|
-
if ! command -v pip3 &> /dev/null; then
|
|
29
|
-
echo "❌ 未找到 pip3"
|
|
30
|
-
echo "请安装 pip3: sudo apt-get install python3-pip"
|
|
31
|
-
exit 1
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
echo "✅ pip3 已安装"
|
|
35
|
-
|
|
36
|
-
# 安装依赖
|
|
37
|
-
echo ""
|
|
38
|
-
echo "正在安装 Python 依赖..."
|
|
39
|
-
pip3 install -r requirements.txt --user
|
|
40
|
-
|
|
41
|
-
echo "✅ 依赖安装完成"
|
|
42
|
-
|
|
43
|
-
# 创建必要的目录
|
|
44
|
-
echo ""
|
|
45
|
-
echo "创建工作目录..."
|
|
46
|
-
mkdir -p workspace/memory
|
|
47
|
-
echo "✅ 工作目录创建完成"
|
|
48
|
-
|
|
49
|
-
# 检查配置文件
|
|
50
|
-
echo ""
|
|
51
|
-
if [ ! -f "config.json" ]; then
|
|
52
|
-
echo "⚠️ config.json 不存在"
|
|
53
|
-
if [ -f "config.json.example" ]; then
|
|
54
|
-
cp config.json.example config.json
|
|
55
|
-
echo "✅ 已从模板创建 config.json"
|
|
56
|
-
echo "⚠️ 请编辑 config.json 配置你的账号信息"
|
|
57
|
-
else
|
|
58
|
-
echo "❌ 未找到配置文件模板"
|
|
59
|
-
exit 1
|
|
60
|
-
fi
|
|
61
|
-
else
|
|
62
|
-
echo "✅ config.json 已存在"
|
|
63
|
-
fi
|
|
64
|
-
|
|
65
|
-
# 检查权限
|
|
66
|
-
echo ""
|
|
67
|
-
echo "检查文件权限..."
|
|
68
|
-
chmod -R 755 "$SCRIPT_DIR"
|
|
69
|
-
chmod +x debug.py
|
|
70
|
-
if [ -f "setup.sh" ]; then
|
|
71
|
-
chmod +x setup.sh
|
|
72
|
-
fi
|
|
73
|
-
echo "✅ 权限设置完成"
|
|
74
|
-
|
|
75
|
-
# 运行调试检查
|
|
76
|
-
echo ""
|
|
77
|
-
echo "运行安装检查..."
|
|
78
|
-
python3 debug.py
|
|
79
|
-
|
|
80
|
-
echo ""
|
|
81
|
-
echo "======================================"
|
|
82
|
-
echo "安装完成!"
|
|
83
|
-
echo "======================================"
|
|
84
|
-
echo ""
|
|
85
|
-
echo "使用方法:"
|
|
86
|
-
echo "1. 编辑 config.json 配置你的账号"
|
|
87
|
-
echo "2. 运行插件: python3 src/main.py"
|
|
88
|
-
echo "3. 或使用修复版: python3 src/main_fixed.py"
|
|
89
|
-
echo ""
|
|
90
|
-
echo "调试命令: python3 debug.py"
|
|
91
|
-
echo ""
|
package/src/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# SoulSync OpenClaw Plugin
|
package/src/client.py
DELETED
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import os
|
|
3
|
-
import ssl
|
|
4
|
-
import uuid
|
|
5
|
-
import requests
|
|
6
|
-
import websocket
|
|
7
|
-
from requests.adapters import HTTPAdapter
|
|
8
|
-
from urllib3.poolmanager import PoolManager
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TLSAdapter(HTTPAdapter):
|
|
12
|
-
"""使用兼容 Python 3.13 的 SSL 适配器"""
|
|
13
|
-
def init_poolmanager(self, *args, **kwargs):
|
|
14
|
-
# 使用 create_default_context() 而不是 SSLContext(PROTOCOL_TLS_CLIENT)
|
|
15
|
-
# 这在 Python 3.13 + Windows 上更稳定
|
|
16
|
-
ctx = ssl.create_default_context()
|
|
17
|
-
# 可选:设置最低 TLS 版本为 1.2
|
|
18
|
-
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
|
|
19
|
-
kwargs['ssl_context'] = ctx
|
|
20
|
-
return super().init_poolmanager(*args, **kwargs)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class OpenClawClient:
|
|
24
|
-
"""OpenClaw 插件的 API/WS 客户端"""
|
|
25
|
-
|
|
26
|
-
def __init__(self, config: dict):
|
|
27
|
-
self.config = config
|
|
28
|
-
self.cloud_url = config.get('cloud_url', '').rstrip('/')
|
|
29
|
-
self.token = None
|
|
30
|
-
self.user_id = None
|
|
31
|
-
self.device_id = self._load_or_generate_device_id()
|
|
32
|
-
self.ws = None
|
|
33
|
-
self.ws_thread = None
|
|
34
|
-
|
|
35
|
-
# 创建使用 TLS 1.2 的 session
|
|
36
|
-
self.session = requests.Session()
|
|
37
|
-
self.session.mount('https://', TLSAdapter())
|
|
38
|
-
|
|
39
|
-
def _load_or_generate_device_id(self) -> str:
|
|
40
|
-
if self.config.get('device_id'):
|
|
41
|
-
return self.config['device_id']
|
|
42
|
-
|
|
43
|
-
plugin_dir = os.path.dirname(os.path.dirname(__file__))
|
|
44
|
-
device_id_file = os.path.join(plugin_dir, 'device_id')
|
|
45
|
-
|
|
46
|
-
if os.path.exists(device_id_file):
|
|
47
|
-
with open(device_id_file, 'r') as f:
|
|
48
|
-
return f.read().strip()
|
|
49
|
-
|
|
50
|
-
new_device_id = str(uuid.uuid4())
|
|
51
|
-
with open(device_id_file, 'w') as f:
|
|
52
|
-
f.write(new_device_id)
|
|
53
|
-
return new_device_id
|
|
54
|
-
|
|
55
|
-
def _save_token(self, token: str):
|
|
56
|
-
"""保存 token"""
|
|
57
|
-
plugin_dir = os.path.dirname(os.path.dirname(__file__))
|
|
58
|
-
token_file = os.path.join(plugin_dir, 'token')
|
|
59
|
-
with open(token_file, 'w') as f:
|
|
60
|
-
f.write(token)
|
|
61
|
-
self.token = token
|
|
62
|
-
|
|
63
|
-
def _load_token(self) -> str:
|
|
64
|
-
"""加载 token"""
|
|
65
|
-
plugin_dir = os.path.dirname(os.path.dirname(__file__))
|
|
66
|
-
token_file = os.path.join(plugin_dir, 'token')
|
|
67
|
-
|
|
68
|
-
if os.path.exists(token_file):
|
|
69
|
-
with open(token_file, 'r') as f:
|
|
70
|
-
return f.read().strip()
|
|
71
|
-
|
|
72
|
-
if self.config and self.config.get('token'):
|
|
73
|
-
return self.config.get('token')
|
|
74
|
-
|
|
75
|
-
return None
|
|
76
|
-
|
|
77
|
-
def _get_headers(self) -> dict:
|
|
78
|
-
headers = {'Content-Type': 'application/json'}
|
|
79
|
-
if self.token:
|
|
80
|
-
headers['Authorization'] = f'Bearer {self.token}'
|
|
81
|
-
if self.device_id:
|
|
82
|
-
headers['X-Device-ID'] = self.device_id
|
|
83
|
-
return headers
|
|
84
|
-
|
|
85
|
-
def authenticate(self, email: str = None, password: str = None) -> dict:
|
|
86
|
-
"""认证:注册或登录
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
email: 邮箱 (首次注册需要)
|
|
90
|
-
password: 密码 (首次注册需要)
|
|
91
|
-
|
|
92
|
-
Returns:
|
|
93
|
-
认证结果
|
|
94
|
-
"""
|
|
95
|
-
self.token = self._load_token()
|
|
96
|
-
|
|
97
|
-
if self.token:
|
|
98
|
-
try:
|
|
99
|
-
profile = self.get_profile()
|
|
100
|
-
print(f"Using existing token, user: {profile.get('email', 'unknown')}")
|
|
101
|
-
return profile
|
|
102
|
-
except Exception as e:
|
|
103
|
-
print(f"Token invalid, re-authenticating: {e}")
|
|
104
|
-
self.token = None
|
|
105
|
-
|
|
106
|
-
if not email:
|
|
107
|
-
email = self.config.get('email', '')
|
|
108
|
-
if not password:
|
|
109
|
-
password = self.config.get('password', '')
|
|
110
|
-
|
|
111
|
-
if not email or not password:
|
|
112
|
-
raise ValueError("Email and password required for first-time authentication")
|
|
113
|
-
|
|
114
|
-
# Try to login first
|
|
115
|
-
url = f"{self.cloud_url}/api/auth/login"
|
|
116
|
-
data = {
|
|
117
|
-
'email': email,
|
|
118
|
-
'password': password
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
response = self.session.post(url, json=data, headers={'Content-Type': 'application/json'})
|
|
122
|
-
|
|
123
|
-
if response.status_code == 200:
|
|
124
|
-
result = response.json()
|
|
125
|
-
self.token = result.get('token')
|
|
126
|
-
self.user_id = result.get('user', {}).get('id')
|
|
127
|
-
self._save_token(self.token)
|
|
128
|
-
print(f"Logged in: {email}")
|
|
129
|
-
return result
|
|
130
|
-
elif response.status_code == 401:
|
|
131
|
-
# Login failed
|
|
132
|
-
raise Exception(f"Invalid email or password / 邮箱或密码错误")
|
|
133
|
-
elif response.status_code == 429:
|
|
134
|
-
# Rate limited
|
|
135
|
-
error = response.json().get('error', 'Too many attempts')
|
|
136
|
-
raise Exception(f"429: {error}")
|
|
137
|
-
else:
|
|
138
|
-
error = response.json().get('error', 'Unknown error')
|
|
139
|
-
raise Exception(f"Authentication failed: {error}")
|
|
140
|
-
|
|
141
|
-
def get_profile(self) -> dict:
|
|
142
|
-
"""获取用户信息
|
|
143
|
-
|
|
144
|
-
Returns:
|
|
145
|
-
用户信息
|
|
146
|
-
"""
|
|
147
|
-
url = f"{self.cloud_url}/api/profiles"
|
|
148
|
-
|
|
149
|
-
response = self.session.get(url, headers=self._get_headers())
|
|
150
|
-
|
|
151
|
-
if response.status_code == 200:
|
|
152
|
-
return response.json()
|
|
153
|
-
else:
|
|
154
|
-
error = response.json().get('error', 'Unknown error')
|
|
155
|
-
raise Exception(f"Get profile failed: {error}")
|
|
156
|
-
|
|
157
|
-
def get_user_info(self) -> dict:
|
|
158
|
-
"""获取当前登录用户信息
|
|
159
|
-
|
|
160
|
-
Returns:
|
|
161
|
-
用户信息,包含 email, name, subscription_status
|
|
162
|
-
"""
|
|
163
|
-
url = f"{self.cloud_url}/api/auth/user/info"
|
|
164
|
-
|
|
165
|
-
response = self.session.get(url, headers=self._get_headers())
|
|
166
|
-
|
|
167
|
-
if response.status_code == 200:
|
|
168
|
-
return response.json()
|
|
169
|
-
else:
|
|
170
|
-
error = response.json().get('error', 'Unknown error')
|
|
171
|
-
raise Exception(f"Get user info failed: {error}")
|
|
172
|
-
|
|
173
|
-
def connect_websocket(self, on_message_callback):
|
|
174
|
-
"""连接 WebSocket
|
|
175
|
-
|
|
176
|
-
Args:
|
|
177
|
-
on_message_callback: 消息回调函数
|
|
178
|
-
"""
|
|
179
|
-
ws_url = self.cloud_url.replace('http', 'ws') + '/ws'
|
|
180
|
-
|
|
181
|
-
def on_ws_message(ws, message):
|
|
182
|
-
try:
|
|
183
|
-
data = json.loads(message)
|
|
184
|
-
on_message_callback(data)
|
|
185
|
-
except Exception as e:
|
|
186
|
-
print(f"WebSocket message error: {e}")
|
|
187
|
-
|
|
188
|
-
def on_ws_error(ws, error):
|
|
189
|
-
print(f"WebSocket error: {error}")
|
|
190
|
-
|
|
191
|
-
def on_ws_close(ws, close_status_code, close_msg):
|
|
192
|
-
print(f"WebSocket closed: {close_status_code} - {close_msg}")
|
|
193
|
-
|
|
194
|
-
def on_ws_open(ws):
|
|
195
|
-
print("WebSocket connected")
|
|
196
|
-
if self.token:
|
|
197
|
-
ws.send(json.dumps({'type': 'auth', 'token': self.token}))
|
|
198
|
-
|
|
199
|
-
self.ws = websocket.WebSocketApp(
|
|
200
|
-
ws_url,
|
|
201
|
-
on_message=on_ws_message,
|
|
202
|
-
on_error=on_ws_error,
|
|
203
|
-
on_close=on_ws_close,
|
|
204
|
-
on_open=on_ws_open
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
self.ws.on_pong = lambda ws, data: None
|
|
208
|
-
|
|
209
|
-
import threading
|
|
210
|
-
self.ws_thread = threading.Thread(target=self.ws.run_forever, daemon=True)
|
|
211
|
-
self.ws_thread.start()
|
|
212
|
-
|
|
213
|
-
def disconnect_websocket(self):
|
|
214
|
-
"""断开 WebSocket 连接"""
|
|
215
|
-
if self.ws:
|
|
216
|
-
self.ws.close()
|
|
217
|
-
self.ws = None
|
|
218
|
-
|
|
219
|
-
def send_ping(self):
|
|
220
|
-
"""发送 ping 保持连接"""
|
|
221
|
-
if self.ws and self.ws.sock and self.ws.sock.connected:
|
|
222
|
-
self.ws.send(json.dumps({'type': 'ping'}))
|
|
223
|
-
|
|
224
|
-
def send_verification_code(self, email: str) -> dict:
|
|
225
|
-
"""发送验证码
|
|
226
|
-
|
|
227
|
-
Args:
|
|
228
|
-
email: 邮箱地址
|
|
229
|
-
|
|
230
|
-
Returns:
|
|
231
|
-
发送结果
|
|
232
|
-
"""
|
|
233
|
-
url = f"{self.cloud_url}/api/auth/send-code"
|
|
234
|
-
data = {'email': email}
|
|
235
|
-
|
|
236
|
-
response = self.session.post(url, json=data, headers={'Content-Type': 'application/json'})
|
|
237
|
-
|
|
238
|
-
if response.status_code == 200:
|
|
239
|
-
return response.json()
|
|
240
|
-
elif response.status_code == 429:
|
|
241
|
-
error = response.json().get('error', 'Rate limit')
|
|
242
|
-
raise Exception(f"Send code failed: {error}")
|
|
243
|
-
else:
|
|
244
|
-
error = response.json().get('error', 'Unknown error')
|
|
245
|
-
raise Exception(f"Send code failed: {error}")
|
|
246
|
-
|
|
247
|
-
def register(self, email: str, password: str, code: str) -> dict:
|
|
248
|
-
"""用户注册
|
|
249
|
-
|
|
250
|
-
Args:
|
|
251
|
-
email: 邮箱地址
|
|
252
|
-
password: 密码
|
|
253
|
-
code: 验证码
|
|
254
|
-
|
|
255
|
-
Returns:
|
|
256
|
-
注册结果
|
|
257
|
-
"""
|
|
258
|
-
url = f"{self.cloud_url}/api/auth/register"
|
|
259
|
-
data = {'email': email, 'password': password, 'code': code}
|
|
260
|
-
|
|
261
|
-
response = self.session.post(url, json=data, headers={'Content-Type': 'application/json'})
|
|
262
|
-
|
|
263
|
-
if response.status_code == 201:
|
|
264
|
-
result = response.json()
|
|
265
|
-
self.token = result.get('token')
|
|
266
|
-
self.user_id = result.get('user_id')
|
|
267
|
-
self._save_token(self.token)
|
|
268
|
-
if self.config:
|
|
269
|
-
self.config['token'] = self.token
|
|
270
|
-
return result
|
|
271
|
-
else:
|
|
272
|
-
error = response.json().get('error', 'Unknown error')
|
|
273
|
-
raise Exception(f"Registration failed: {error}")
|
|
274
|
-
|
|
275
|
-
def login(self, email: str, password: str) -> dict:
|
|
276
|
-
"""用户登录
|
|
277
|
-
|
|
278
|
-
Args:
|
|
279
|
-
email: 邮箱地址
|
|
280
|
-
password: 密码
|
|
281
|
-
|
|
282
|
-
Returns:
|
|
283
|
-
登录结果
|
|
284
|
-
"""
|
|
285
|
-
url = f"{self.cloud_url}/api/auth/login"
|
|
286
|
-
data = {'email': email, 'password': password}
|
|
287
|
-
|
|
288
|
-
response = self.session.post(url, json=data, headers={'Content-Type': 'application/json'})
|
|
289
|
-
|
|
290
|
-
if response.status_code == 200:
|
|
291
|
-
result = response.json()
|
|
292
|
-
self.token = result.get('token')
|
|
293
|
-
self.user_id = result.get('user_id')
|
|
294
|
-
self._save_token(self.token)
|
|
295
|
-
if self.config:
|
|
296
|
-
self.config['token'] = self.token
|
|
297
|
-
return result
|
|
298
|
-
else:
|
|
299
|
-
error = response.json().get('error', 'Unknown error')
|
|
300
|
-
raise Exception(f"Login failed: {error}")
|