soulsync 1.0.0 → 1.0.4
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/CHANGES.md +120 -0
- package/DEPLOY_CHECKLIST.md +151 -0
- package/INSTALL.md +103 -0
- package/SPEC.md +170 -0
- package/TROUBLESHOOTING.md +199 -0
- package/{config.json → config.json.example} +3 -3
- package/debug.py +221 -0
- package/index.js +54 -0
- package/openclaw.plugin.json +18 -0
- package/package.json +7 -21
- package/setup.sh +91 -0
- package/src/__init__.py +1 -0
- package/src/client.py +81 -11
- package/src/interactive_auth.py +283 -0
- package/src/main.py +130 -83
- package/src/main_fixed.py +434 -0
- package/src/register.py +131 -0
- package/src/sync.py +172 -109
- package/workspace/MEMORY.md +0 -5
package/src/client.py
CHANGED
|
@@ -88,29 +88,25 @@ class OpenClawClient:
|
|
|
88
88
|
if not email or not password:
|
|
89
89
|
raise ValueError("Email and password required for first-time authentication")
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
# Try to login first
|
|
92
|
+
url = f"{self.cloud_url}/api/auth/login"
|
|
92
93
|
data = {
|
|
93
|
-
'device_id': self.device_id,
|
|
94
94
|
'email': email,
|
|
95
95
|
'password': password
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
response = requests.post(url, json=data, headers={'Content-Type': 'application/json'})
|
|
99
99
|
|
|
100
|
-
if response.status_code ==
|
|
101
|
-
result = response.json()
|
|
102
|
-
self.token = result.get('token')
|
|
103
|
-
self.user_id = result.get('user_id')
|
|
104
|
-
self._save_token(self.token)
|
|
105
|
-
print(f"Registered new user: {email}")
|
|
106
|
-
return result
|
|
107
|
-
elif response.status_code == 200:
|
|
100
|
+
if response.status_code == 200:
|
|
108
101
|
result = response.json()
|
|
109
102
|
self.token = result.get('token')
|
|
110
|
-
self.user_id = result.get('
|
|
103
|
+
self.user_id = result.get('user', {}).get('id')
|
|
111
104
|
self._save_token(self.token)
|
|
112
105
|
print(f"Logged in: {email}")
|
|
113
106
|
return result
|
|
107
|
+
elif response.status_code == 401:
|
|
108
|
+
# Login failed, try register (not supported in this flow)
|
|
109
|
+
raise Exception(f"Invalid email or password / 邮箱或密码错误")
|
|
114
110
|
else:
|
|
115
111
|
error = response.json().get('error', 'Unknown error')
|
|
116
112
|
raise Exception(f"Authentication failed: {error}")
|
|
@@ -222,3 +218,77 @@ class OpenClawClient:
|
|
|
222
218
|
"""发送 ping 保持连接"""
|
|
223
219
|
if self.ws and self.ws.sock and self.ws.sock.connected:
|
|
224
220
|
self.ws.send(json.dumps({'type': 'ping'}))
|
|
221
|
+
|
|
222
|
+
def send_verification_code(self, email: str) -> dict:
|
|
223
|
+
"""发送验证码
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
email: 邮箱地址
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
发送结果
|
|
230
|
+
"""
|
|
231
|
+
url = f"{self.cloud_url}/api/auth/send-code"
|
|
232
|
+
data = {'email': email}
|
|
233
|
+
|
|
234
|
+
response = requests.post(url, json=data, headers={'Content-Type': 'application/json'})
|
|
235
|
+
|
|
236
|
+
if response.status_code == 200:
|
|
237
|
+
return response.json()
|
|
238
|
+
elif response.status_code == 429:
|
|
239
|
+
error = response.json().get('error', 'Rate limit')
|
|
240
|
+
raise Exception(f"Send code failed: {error}")
|
|
241
|
+
else:
|
|
242
|
+
error = response.json().get('error', 'Unknown error')
|
|
243
|
+
raise Exception(f"Send code failed: {error}")
|
|
244
|
+
|
|
245
|
+
def register(self, email: str, password: str, code: str) -> dict:
|
|
246
|
+
"""用户注册
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
email: 邮箱地址
|
|
250
|
+
password: 密码
|
|
251
|
+
code: 验证码
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
注册结果
|
|
255
|
+
"""
|
|
256
|
+
url = f"{self.cloud_url}/api/auth/register"
|
|
257
|
+
data = {'email': email, 'password': password, 'code': code}
|
|
258
|
+
|
|
259
|
+
response = requests.post(url, json=data, headers={'Content-Type': 'application/json'})
|
|
260
|
+
|
|
261
|
+
if response.status_code == 201:
|
|
262
|
+
result = response.json()
|
|
263
|
+
self.token = result.get('token')
|
|
264
|
+
self.user_id = result.get('user_id')
|
|
265
|
+
self._save_token(self.token)
|
|
266
|
+
return result
|
|
267
|
+
else:
|
|
268
|
+
error = response.json().get('error', 'Unknown error')
|
|
269
|
+
raise Exception(f"Registration failed: {error}")
|
|
270
|
+
|
|
271
|
+
def login(self, email: str, password: str) -> dict:
|
|
272
|
+
"""用户登录
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
email: 邮箱地址
|
|
276
|
+
password: 密码
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
登录结果
|
|
280
|
+
"""
|
|
281
|
+
url = f"{self.cloud_url}/api/auth/login"
|
|
282
|
+
data = {'email': email, 'password': password}
|
|
283
|
+
|
|
284
|
+
response = requests.post(url, json=data, headers={'Content-Type': 'application/json'})
|
|
285
|
+
|
|
286
|
+
if response.status_code == 200:
|
|
287
|
+
result = response.json()
|
|
288
|
+
self.token = result.get('token')
|
|
289
|
+
self.user_id = result.get('user_id')
|
|
290
|
+
self._save_token(self.token)
|
|
291
|
+
return result
|
|
292
|
+
else:
|
|
293
|
+
error = response.json().get('error', 'Unknown error')
|
|
294
|
+
raise Exception(f"Login failed: {error}")
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
SoulSync 交互式认证模块
|
|
4
|
+
处理用户注册、登录、邮箱验证
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
import getpass
|
|
12
|
+
|
|
13
|
+
# 获取插件目录
|
|
14
|
+
PLUGIN_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
15
|
+
CONFIG_PATH = os.path.join(PLUGIN_DIR, 'config.json')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_input(prompt):
|
|
19
|
+
"""获取用户输入"""
|
|
20
|
+
try:
|
|
21
|
+
return input(prompt)
|
|
22
|
+
except KeyboardInterrupt:
|
|
23
|
+
print("\n\nOperation cancelled / 操作已取消")
|
|
24
|
+
sys.exit(0)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_password(prompt):
|
|
28
|
+
"""获取密码(隐藏输入)"""
|
|
29
|
+
try:
|
|
30
|
+
return getpass.getpass(prompt)
|
|
31
|
+
except KeyboardInterrupt:
|
|
32
|
+
print("\n\nOperation cancelled / 操作已取消")
|
|
33
|
+
sys.exit(0)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def is_valid_email(email):
|
|
37
|
+
"""验证邮箱格式"""
|
|
38
|
+
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
|
39
|
+
return re.match(pattern, email) is not None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def load_config():
|
|
43
|
+
"""加载配置文件"""
|
|
44
|
+
if os.path.exists(CONFIG_PATH):
|
|
45
|
+
try:
|
|
46
|
+
with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
|
|
47
|
+
return json.load(f)
|
|
48
|
+
except:
|
|
49
|
+
return {}
|
|
50
|
+
return {}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def save_config(config):
|
|
54
|
+
"""保存配置文件"""
|
|
55
|
+
try:
|
|
56
|
+
with open(CONFIG_PATH, 'w', encoding='utf-8') as f:
|
|
57
|
+
json.dump(config, f, indent=2, ensure_ascii=False)
|
|
58
|
+
return True
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print(f"Failed to save config: {e} / 保存配置失败: {e}")
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def check_existing_config():
|
|
65
|
+
"""检查是否已有配置"""
|
|
66
|
+
config = load_config()
|
|
67
|
+
email = config.get('email', '').strip()
|
|
68
|
+
password = config.get('password', '').strip()
|
|
69
|
+
|
|
70
|
+
if email and password:
|
|
71
|
+
return config
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def interactive_setup(client):
|
|
76
|
+
"""
|
|
77
|
+
交互式设置流程
|
|
78
|
+
返回配置好的 config 字典
|
|
79
|
+
"""
|
|
80
|
+
print("\n" + "=" * 50)
|
|
81
|
+
print("Welcome to SoulSync! / 欢迎使用 SoulSync!")
|
|
82
|
+
print("=" * 50)
|
|
83
|
+
print()
|
|
84
|
+
|
|
85
|
+
# 检查现有配置
|
|
86
|
+
existing = check_existing_config()
|
|
87
|
+
if existing:
|
|
88
|
+
print(f"Detected existing account / 已检测到现有账号: {existing['email']}")
|
|
89
|
+
choice = get_input("Use existing account? (y/n) / 是否使用现有账号?: ").lower().strip()
|
|
90
|
+
if choice in ['y', 'yes', '是']:
|
|
91
|
+
return existing
|
|
92
|
+
print()
|
|
93
|
+
|
|
94
|
+
# 询问是否有账号
|
|
95
|
+
print("Please select / 请选择:")
|
|
96
|
+
print("1. Login / 登录已有账号")
|
|
97
|
+
print("2. Register / 注册新账号")
|
|
98
|
+
print()
|
|
99
|
+
|
|
100
|
+
while True:
|
|
101
|
+
choice = get_input("Enter option / 输入选项 (1/2): ").strip()
|
|
102
|
+
if choice in ['1', '2']:
|
|
103
|
+
break
|
|
104
|
+
print("Invalid option / 无效选项,请重新输入")
|
|
105
|
+
|
|
106
|
+
if choice == '1':
|
|
107
|
+
return interactive_login(client)
|
|
108
|
+
else:
|
|
109
|
+
return interactive_register(client)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def interactive_login(client):
|
|
113
|
+
"""交互式登录"""
|
|
114
|
+
print("\n--- Login / 登录 ---")
|
|
115
|
+
|
|
116
|
+
# 输入邮箱
|
|
117
|
+
while True:
|
|
118
|
+
email = get_input("Email / 邮箱: ").strip()
|
|
119
|
+
if is_valid_email(email):
|
|
120
|
+
break
|
|
121
|
+
print("Invalid email format / 邮箱格式不正确,请重新输入")
|
|
122
|
+
|
|
123
|
+
# 输入密码
|
|
124
|
+
password = get_password("Password / 密码: ")
|
|
125
|
+
|
|
126
|
+
print("\nLogging in... / 正在登录...")
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
# 尝试登录
|
|
130
|
+
result = client.authenticate(email, password)
|
|
131
|
+
|
|
132
|
+
if result:
|
|
133
|
+
print("✅ Login successful! / 登录成功!")
|
|
134
|
+
|
|
135
|
+
# 保存配置
|
|
136
|
+
config = load_config()
|
|
137
|
+
config['email'] = email
|
|
138
|
+
config['password'] = password
|
|
139
|
+
save_config(config)
|
|
140
|
+
|
|
141
|
+
return config
|
|
142
|
+
else:
|
|
143
|
+
print("❌ Login failed, invalid email or password / 登录失败,邮箱或密码错误")
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
except Exception as e:
|
|
147
|
+
print(f"❌ Login error: {e} / 登录出错: {e}")
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def interactive_register(client):
|
|
152
|
+
"""交互式注册"""
|
|
153
|
+
print("\n--- Register / 注册新账号 ---")
|
|
154
|
+
|
|
155
|
+
# 输入邮箱
|
|
156
|
+
while True:
|
|
157
|
+
email = get_input("Email / 邮箱: ").strip()
|
|
158
|
+
if not is_valid_email(email):
|
|
159
|
+
print("Invalid email format / 邮箱格式不正确,请重新输入")
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
# 检查邮箱是否已注册
|
|
163
|
+
print("Checking email availability... / 检查邮箱可用性...")
|
|
164
|
+
# TODO: 调用后端 API 检查邮箱是否已存在
|
|
165
|
+
# 暂时假设可用
|
|
166
|
+
break
|
|
167
|
+
|
|
168
|
+
# 输入密码
|
|
169
|
+
while True:
|
|
170
|
+
password = get_password("Set password (min 6 characters) / 设置密码 (至少6位): ")
|
|
171
|
+
if len(password) >= 6:
|
|
172
|
+
break
|
|
173
|
+
print("Password too short, minimum 6 characters / 密码太短,请至少输入6位")
|
|
174
|
+
|
|
175
|
+
# 确认密码
|
|
176
|
+
while True:
|
|
177
|
+
password2 = get_password("Confirm password / 确认密码: ")
|
|
178
|
+
if password == password2:
|
|
179
|
+
break
|
|
180
|
+
print("Passwords do not match, please try again / 两次密码不一致,请重新输入")
|
|
181
|
+
|
|
182
|
+
# 发送验证码
|
|
183
|
+
print(f"\nSending verification code to {email} / 正在发送验证码到 {email}...")
|
|
184
|
+
try:
|
|
185
|
+
client.send_verification_code(email)
|
|
186
|
+
print("✅ Verification code sent! / 验证码已发送!")
|
|
187
|
+
except Exception as e:
|
|
188
|
+
print(f"❌ Failed to send code: {e} / 发送验证码失败: {e}")
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
# 输入验证码
|
|
192
|
+
max_attempts = 3
|
|
193
|
+
for attempt in range(max_attempts):
|
|
194
|
+
code = get_input(f"Enter verification code / 请输入验证码 (remaining attempts / 剩余尝试: {max_attempts - attempt}): ").strip()
|
|
195
|
+
|
|
196
|
+
# Verify code format (6 digits)
|
|
197
|
+
if len(code) == 6 and code.isdigit():
|
|
198
|
+
# Verify with server
|
|
199
|
+
try:
|
|
200
|
+
result = client.register(email, password, code)
|
|
201
|
+
print("✅ Verification successful! / 验证成功!")
|
|
202
|
+
break
|
|
203
|
+
except Exception as e:
|
|
204
|
+
print(f"❌ Verification failed: {e} / 验证失败: {e}")
|
|
205
|
+
if attempt == max_attempts - 1:
|
|
206
|
+
print("Too many failed attempts, please re-register / 验证失败次数过多,请重新注册")
|
|
207
|
+
return None
|
|
208
|
+
else:
|
|
209
|
+
print("❌ Invalid code format, please enter 6-digit code / 验证码格式错误,请输入6位数字验证码")
|
|
210
|
+
if attempt == max_attempts - 1:
|
|
211
|
+
print("Too many failed attempts, please re-register / 验证失败次数过多,请重新注册")
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
# 完成注册
|
|
215
|
+
print("\nCreating account... / 正在创建账号...")
|
|
216
|
+
try:
|
|
217
|
+
print("✅ Registration successful! / 注册成功!")
|
|
218
|
+
|
|
219
|
+
# 保存配置
|
|
220
|
+
config = load_config()
|
|
221
|
+
config['email'] = email
|
|
222
|
+
config['password'] = password
|
|
223
|
+
save_config(config)
|
|
224
|
+
|
|
225
|
+
return config
|
|
226
|
+
|
|
227
|
+
except Exception as e:
|
|
228
|
+
print(f"❌ Registration failed: {e} / 注册失败: {e}")
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def prompt_for_missing_config(client):
|
|
233
|
+
"""
|
|
234
|
+
当配置缺失时,提示用户输入
|
|
235
|
+
用于插件启动时自动检测
|
|
236
|
+
"""
|
|
237
|
+
config = load_config()
|
|
238
|
+
|
|
239
|
+
email = config.get('email', '').strip()
|
|
240
|
+
password = config.get('password', '').strip()
|
|
241
|
+
cloud_url = config.get('cloud_url', '').strip()
|
|
242
|
+
|
|
243
|
+
# 检查是否需要交互式配置
|
|
244
|
+
need_setup = not email or not password or not cloud_url
|
|
245
|
+
|
|
246
|
+
if need_setup:
|
|
247
|
+
print("\nFirst time using SoulSync, configuration required... / 首次使用 SoulSync,需要进行配置...")
|
|
248
|
+
|
|
249
|
+
# 如果没有 cloud_url,设置默认值
|
|
250
|
+
if not cloud_url:
|
|
251
|
+
config['cloud_url'] = 'http://47.96.170.74:3000'
|
|
252
|
+
print(f"Using default server / 使用默认服务器: {config['cloud_url']}")
|
|
253
|
+
|
|
254
|
+
# 交互式登录/注册
|
|
255
|
+
result = interactive_setup(client)
|
|
256
|
+
|
|
257
|
+
if result:
|
|
258
|
+
return result
|
|
259
|
+
else:
|
|
260
|
+
print("\n❌ Configuration failed, plugin cannot start / 配置失败,插件无法启动")
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
return config
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if __name__ == '__main__':
|
|
267
|
+
# 测试代码
|
|
268
|
+
print("Interactive Auth Module Test / 交互式认证模块测试")
|
|
269
|
+
print("=" * 50)
|
|
270
|
+
|
|
271
|
+
# 模拟客户端
|
|
272
|
+
class MockClient:
|
|
273
|
+
def authenticate(self, email, password):
|
|
274
|
+
print(f"Simulating login / 模拟登录: {email}")
|
|
275
|
+
return True
|
|
276
|
+
|
|
277
|
+
client = MockClient()
|
|
278
|
+
result = interactive_setup(client)
|
|
279
|
+
|
|
280
|
+
if result:
|
|
281
|
+
print(f"\nConfiguration complete / 配置完成: {result}")
|
|
282
|
+
else:
|
|
283
|
+
print("\nConfiguration failed / 配置失败")
|