soulsync 1.0.0 → 1.0.5
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 +79 -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 +104 -15
- package/src/interactive_auth.py +283 -0
- package/src/main.py +205 -81
- 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
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
SoulSync OpenClaw 插件主类 - 修复版
|
|
4
|
+
解决跨平台路径问题和导入问题
|
|
5
|
+
支持交互式认证
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
import re
|
|
13
|
+
import getpass
|
|
14
|
+
|
|
15
|
+
# 获取插件根目录
|
|
16
|
+
PLUGIN_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
17
|
+
SRC_DIR = os.path.join(PLUGIN_DIR, 'src')
|
|
18
|
+
|
|
19
|
+
# 添加 src 到路径
|
|
20
|
+
if SRC_DIR not in sys.path:
|
|
21
|
+
sys.path.insert(0, SRC_DIR)
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from client import OpenClawClient
|
|
25
|
+
from watcher import OpenClawMultiWatcher
|
|
26
|
+
from version_manager import VersionManager
|
|
27
|
+
from profiles import ProfilesClient
|
|
28
|
+
from sync import ProfileSync
|
|
29
|
+
except ImportError as e:
|
|
30
|
+
print(f"导入错误: {e}")
|
|
31
|
+
print(f"当前路径: {sys.path}")
|
|
32
|
+
print(f"SRC_DIR: {SRC_DIR}")
|
|
33
|
+
raise
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SoulSyncPlugin:
|
|
37
|
+
"""SoulSync OpenClaw 插件主类"""
|
|
38
|
+
|
|
39
|
+
def __init__(self):
|
|
40
|
+
self.config = None
|
|
41
|
+
self.client = None
|
|
42
|
+
self.profiles_client = None
|
|
43
|
+
self.watcher = None
|
|
44
|
+
self.version_manager = None
|
|
45
|
+
self.profile_sync = None
|
|
46
|
+
self.running = False
|
|
47
|
+
|
|
48
|
+
def get_input(self, prompt):
|
|
49
|
+
"""获取用户输入"""
|
|
50
|
+
try:
|
|
51
|
+
return input(prompt)
|
|
52
|
+
except KeyboardInterrupt:
|
|
53
|
+
print("\n\n操作已取消")
|
|
54
|
+
sys.exit(0)
|
|
55
|
+
|
|
56
|
+
def get_password(self, prompt):
|
|
57
|
+
"""获取密码(隐藏输入)"""
|
|
58
|
+
try:
|
|
59
|
+
return getpass.getpass(prompt)
|
|
60
|
+
except KeyboardInterrupt:
|
|
61
|
+
print("\n\n操作已取消")
|
|
62
|
+
sys.exit(0)
|
|
63
|
+
|
|
64
|
+
def is_valid_email(self, email):
|
|
65
|
+
"""验证邮箱格式"""
|
|
66
|
+
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
|
67
|
+
return re.match(pattern, email) is not None
|
|
68
|
+
|
|
69
|
+
def save_config(self):
|
|
70
|
+
"""保存配置文件"""
|
|
71
|
+
config_path = os.path.normpath(os.path.join(PLUGIN_DIR, 'config.json'))
|
|
72
|
+
try:
|
|
73
|
+
with open(config_path, 'w', encoding='utf-8') as f:
|
|
74
|
+
json.dump(self.config, f, indent=2, ensure_ascii=False)
|
|
75
|
+
return True
|
|
76
|
+
except Exception as e:
|
|
77
|
+
print(f"保存配置失败: {e}")
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
def interactive_auth(self):
|
|
81
|
+
"""交互式认证流程"""
|
|
82
|
+
print("\n" + "=" * 50)
|
|
83
|
+
print("欢迎使用 SoulSync!")
|
|
84
|
+
print("=" * 50)
|
|
85
|
+
print()
|
|
86
|
+
|
|
87
|
+
# 设置默认服务器
|
|
88
|
+
if not self.config.get('cloud_url'):
|
|
89
|
+
self.config['cloud_url'] = 'http://47.96.170.74:3000'
|
|
90
|
+
print(f"使用默认服务器: {self.config['cloud_url']}")
|
|
91
|
+
print()
|
|
92
|
+
|
|
93
|
+
# 创建临时客户端
|
|
94
|
+
self.client = OpenClawClient(self.config)
|
|
95
|
+
|
|
96
|
+
# 询问登录或注册
|
|
97
|
+
print("请选择:")
|
|
98
|
+
print("1. 登录已有账号")
|
|
99
|
+
print("2. 注册新账号")
|
|
100
|
+
print()
|
|
101
|
+
|
|
102
|
+
while True:
|
|
103
|
+
choice = self.get_input("输入选项 (1/2): ").strip()
|
|
104
|
+
if choice in ['1', '2']:
|
|
105
|
+
break
|
|
106
|
+
print("无效选项,请重新输入")
|
|
107
|
+
|
|
108
|
+
if choice == '1':
|
|
109
|
+
return self.interactive_login()
|
|
110
|
+
else:
|
|
111
|
+
return self.interactive_register()
|
|
112
|
+
|
|
113
|
+
def interactive_login(self):
|
|
114
|
+
"""交互式登录"""
|
|
115
|
+
print("\n--- 登录 ---")
|
|
116
|
+
|
|
117
|
+
# 输入邮箱
|
|
118
|
+
while True:
|
|
119
|
+
email = self.get_input("邮箱: ").strip()
|
|
120
|
+
if self.is_valid_email(email):
|
|
121
|
+
break
|
|
122
|
+
print("邮箱格式不正确,请重新输入")
|
|
123
|
+
|
|
124
|
+
# 输入密码
|
|
125
|
+
password = self.get_password("密码: ")
|
|
126
|
+
|
|
127
|
+
print("\n正在登录...")
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
result = self.client.login(email, password)
|
|
131
|
+
print(f"✅ 登录成功!")
|
|
132
|
+
|
|
133
|
+
# 保存到配置
|
|
134
|
+
self.config['email'] = email
|
|
135
|
+
self.config['password'] = password
|
|
136
|
+
self.save_config()
|
|
137
|
+
return True
|
|
138
|
+
except Exception as e:
|
|
139
|
+
print(f"❌ 登录失败: {e}")
|
|
140
|
+
print("\n请重新选择:")
|
|
141
|
+
return self.interactive_auth()
|
|
142
|
+
|
|
143
|
+
def interactive_register(self):
|
|
144
|
+
"""交互式注册"""
|
|
145
|
+
print("\n--- 注册新账号 ---")
|
|
146
|
+
|
|
147
|
+
# 输入邮箱
|
|
148
|
+
while True:
|
|
149
|
+
email = self.get_input("邮箱: ").strip()
|
|
150
|
+
if not self.is_valid_email(email):
|
|
151
|
+
print("邮箱格式不正确,请重新输入")
|
|
152
|
+
continue
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
# 发送验证码
|
|
156
|
+
print(f"\n正在发送验证码到 {email}...")
|
|
157
|
+
try:
|
|
158
|
+
self.client.send_verification_code(email)
|
|
159
|
+
print("✅ 验证码已发送!")
|
|
160
|
+
print("请查看服务器控制台获取验证码")
|
|
161
|
+
except Exception as e:
|
|
162
|
+
print(f"❌ 发送验证码失败: {e}")
|
|
163
|
+
return self.interactive_auth()
|
|
164
|
+
|
|
165
|
+
# 输入验证码
|
|
166
|
+
max_attempts = 3
|
|
167
|
+
for attempt in range(max_attempts):
|
|
168
|
+
code = self.get_input(f"请输入验证码 (剩余尝试 {max_attempts - attempt} 次): ").strip()
|
|
169
|
+
|
|
170
|
+
if len(code) == 4 and code.isdigit():
|
|
171
|
+
break
|
|
172
|
+
else:
|
|
173
|
+
print("❌ 验证码格式错误,应为4位数字")
|
|
174
|
+
if attempt == max_attempts - 1:
|
|
175
|
+
print("验证失败次数过多,请重新注册")
|
|
176
|
+
return self.interactive_auth()
|
|
177
|
+
|
|
178
|
+
# 输入密码
|
|
179
|
+
while True:
|
|
180
|
+
password = self.get_password("设置密码 (至少6位): ")
|
|
181
|
+
if len(password) >= 6:
|
|
182
|
+
break
|
|
183
|
+
print("密码太短,请至少输入6位")
|
|
184
|
+
|
|
185
|
+
# 确认密码
|
|
186
|
+
while True:
|
|
187
|
+
password2 = self.get_password("确认密码: ")
|
|
188
|
+
if password == password2:
|
|
189
|
+
break
|
|
190
|
+
print("两次密码不一致,请重新输入")
|
|
191
|
+
|
|
192
|
+
# 注册
|
|
193
|
+
print("\n正在创建账号...")
|
|
194
|
+
try:
|
|
195
|
+
result = self.client.register(email, password, code)
|
|
196
|
+
print("✅ 注册成功!")
|
|
197
|
+
|
|
198
|
+
# 保存配置
|
|
199
|
+
self.config['email'] = email
|
|
200
|
+
self.config['password'] = password
|
|
201
|
+
self.save_config()
|
|
202
|
+
|
|
203
|
+
return True
|
|
204
|
+
except Exception as e:
|
|
205
|
+
print(f"❌ 注册失败: {e}")
|
|
206
|
+
print("\n请重新选择:")
|
|
207
|
+
return self.interactive_auth()
|
|
208
|
+
|
|
209
|
+
def load_config(self):
|
|
210
|
+
"""加载配置文件"""
|
|
211
|
+
config_path = os.path.normpath(os.path.join(PLUGIN_DIR, 'config.json'))
|
|
212
|
+
|
|
213
|
+
print(f"Looking for config at: {config_path}")
|
|
214
|
+
|
|
215
|
+
# 如果配置文件不存在,创建默认配置并进行交互式认证
|
|
216
|
+
if not os.path.exists(config_path):
|
|
217
|
+
print("Config file not found, starting interactive setup...")
|
|
218
|
+
self.config = {}
|
|
219
|
+
|
|
220
|
+
# 设置默认 workspace
|
|
221
|
+
workspace = os.path.normpath(os.path.join(PLUGIN_DIR, 'workspace'))
|
|
222
|
+
self.config['workspace'] = './workspace'
|
|
223
|
+
self.config['watch_files'] = [
|
|
224
|
+
"SOUL.md",
|
|
225
|
+
"IDENTITY.md",
|
|
226
|
+
"USER.md",
|
|
227
|
+
"AGENTS.md",
|
|
228
|
+
"TOOLS.md",
|
|
229
|
+
"skills.json",
|
|
230
|
+
"memory/",
|
|
231
|
+
"MEMORY.md"
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
# 交互式认证
|
|
235
|
+
if not self.interactive_auth():
|
|
236
|
+
raise RuntimeError("Authentication failed")
|
|
237
|
+
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
|
242
|
+
self.config = json.load(f)
|
|
243
|
+
except json.JSONDecodeError as e:
|
|
244
|
+
raise ValueError(f"Invalid JSON in config.json: {e}")
|
|
245
|
+
|
|
246
|
+
# 处理 workspace 路径
|
|
247
|
+
workspace = self.config.get('workspace', './workspace')
|
|
248
|
+
if workspace.startswith('./'):
|
|
249
|
+
workspace = workspace[2:]
|
|
250
|
+
workspace = os.path.normpath(os.path.join(PLUGIN_DIR, workspace))
|
|
251
|
+
|
|
252
|
+
watch_files = self.config.get('watch_files', ['MEMORY.md', 'memory/'])
|
|
253
|
+
|
|
254
|
+
self.config['workspace'] = workspace
|
|
255
|
+
self.config['watch_files'] = watch_files
|
|
256
|
+
|
|
257
|
+
print(f"Config loaded:")
|
|
258
|
+
print(f" Cloud URL: {self.config.get('cloud_url')}")
|
|
259
|
+
print(f" Workspace: {workspace}")
|
|
260
|
+
print(f" Watch files: {watch_files}")
|
|
261
|
+
|
|
262
|
+
# 检查是否需要认证
|
|
263
|
+
email = self.config.get('email', '').strip()
|
|
264
|
+
password = self.config.get('password', '').strip()
|
|
265
|
+
|
|
266
|
+
if not email or not password:
|
|
267
|
+
print("\n邮箱或密码未配置,需要进行认证...")
|
|
268
|
+
if not self.interactive_auth():
|
|
269
|
+
raise RuntimeError("Authentication failed")
|
|
270
|
+
|
|
271
|
+
def initialize(self):
|
|
272
|
+
"""初始化组件"""
|
|
273
|
+
print("\n=== Initializing SoulSync Plugin ===\n")
|
|
274
|
+
|
|
275
|
+
self.client = OpenClawClient(self.config)
|
|
276
|
+
|
|
277
|
+
email = self.config.get('email')
|
|
278
|
+
password = self.config.get('password')
|
|
279
|
+
|
|
280
|
+
if not email or not password:
|
|
281
|
+
self.interactive_auth()
|
|
282
|
+
else:
|
|
283
|
+
try:
|
|
284
|
+
self.client.authenticate(email, password)
|
|
285
|
+
print(f"\n✅ 登录成功: {email}")
|
|
286
|
+
except Exception as e:
|
|
287
|
+
print(f"\n❌ 登录失败: {e}")
|
|
288
|
+
print("将进入交互式认证...")
|
|
289
|
+
self.interactive_auth()
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
profile = self.client.get_profile()
|
|
293
|
+
print(f"\nLogged in as: {profile.get('email')}")
|
|
294
|
+
subscription = profile.get('subscription', {})
|
|
295
|
+
print(f"Subscription: {subscription.get('status')} (days remaining: {subscription.get('daysRemaining', 0)})\n")
|
|
296
|
+
except Exception as e:
|
|
297
|
+
print(f"Warning: Could not get profile: {e}")
|
|
298
|
+
|
|
299
|
+
# 版本管理器
|
|
300
|
+
versions_file = os.path.normpath(os.path.join(PLUGIN_DIR, 'versions.json'))
|
|
301
|
+
self.version_manager = VersionManager(versions_file)
|
|
302
|
+
|
|
303
|
+
self.profiles_client = ProfilesClient(
|
|
304
|
+
self.config.get('cloud_url'),
|
|
305
|
+
self.client.token
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
self.profile_sync = ProfileSync(
|
|
309
|
+
self.profiles_client,
|
|
310
|
+
self.version_manager,
|
|
311
|
+
self.config.get('workspace')
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
print("Pulling all profiles from cloud...")
|
|
315
|
+
try:
|
|
316
|
+
self.profile_sync.pull_all()
|
|
317
|
+
except Exception as e:
|
|
318
|
+
print(f"Warning: Could not pull profiles: {e}")
|
|
319
|
+
|
|
320
|
+
print("\nStarting file watcher...")
|
|
321
|
+
watch_files = self.config.get('watch_files', [])
|
|
322
|
+
self.watcher = OpenClawMultiWatcher(
|
|
323
|
+
self.config.get('workspace'),
|
|
324
|
+
watch_files,
|
|
325
|
+
self.on_file_change
|
|
326
|
+
)
|
|
327
|
+
self.watcher.start()
|
|
328
|
+
|
|
329
|
+
print("\nConnecting to WebSocket...")
|
|
330
|
+
try:
|
|
331
|
+
self.client.connect_websocket(self.on_websocket_message)
|
|
332
|
+
except Exception as e:
|
|
333
|
+
print(f"Warning: Could not connect WebSocket: {e}")
|
|
334
|
+
|
|
335
|
+
self.running = True
|
|
336
|
+
|
|
337
|
+
def on_file_change(self, event_type: str, relative_path: str, absolute_path: str = None):
|
|
338
|
+
"""文件变化回调"""
|
|
339
|
+
print(f"\n[File {event_type}] {relative_path}")
|
|
340
|
+
|
|
341
|
+
if event_type in ['modified', 'created']:
|
|
342
|
+
time.sleep(0.5)
|
|
343
|
+
|
|
344
|
+
try:
|
|
345
|
+
self.profile_sync.push_file(relative_path)
|
|
346
|
+
print(f"Upload completed: {relative_path}")
|
|
347
|
+
except Exception as e:
|
|
348
|
+
print(f"Upload error: {e}")
|
|
349
|
+
|
|
350
|
+
elif event_type == 'deleted':
|
|
351
|
+
print(f"File deleted (not synced to cloud): {relative_path}")
|
|
352
|
+
|
|
353
|
+
def on_websocket_message(self, data: dict):
|
|
354
|
+
"""WebSocket 消息回调"""
|
|
355
|
+
event = data.get('event')
|
|
356
|
+
|
|
357
|
+
if event == 'file_updated':
|
|
358
|
+
file_path = data.get('file_path')
|
|
359
|
+
version = data.get('version')
|
|
360
|
+
print(f"\n[WebSocket] File updated: {file_path} (v{version})")
|
|
361
|
+
try:
|
|
362
|
+
self.profile_sync.on_remote_change(file_path, version)
|
|
363
|
+
except Exception as e:
|
|
364
|
+
print(f"Sync error: {e}")
|
|
365
|
+
|
|
366
|
+
elif event == 'new_memory':
|
|
367
|
+
print(f"\n[WebSocket] New memory available!")
|
|
368
|
+
try:
|
|
369
|
+
self.profile_sync.pull_all()
|
|
370
|
+
print("Memory synced from remote")
|
|
371
|
+
except Exception as e:
|
|
372
|
+
print(f"Sync error: {e}")
|
|
373
|
+
|
|
374
|
+
elif data.get('type') == 'authenticated':
|
|
375
|
+
print(f"[WebSocket] Authenticated, socket_id: {data.get('socket_id')}")
|
|
376
|
+
elif data.get('type') == 'error':
|
|
377
|
+
print(f"[WebSocket] Error: {data.get('message')}")
|
|
378
|
+
|
|
379
|
+
def run(self):
|
|
380
|
+
"""运行插件"""
|
|
381
|
+
print("\n" + "=" * 50)
|
|
382
|
+
print("SoulSync OpenClaw Plugin (Multi-File Sync)")
|
|
383
|
+
print("=" * 50 + "\n")
|
|
384
|
+
|
|
385
|
+
try:
|
|
386
|
+
self.load_config()
|
|
387
|
+
self.initialize()
|
|
388
|
+
|
|
389
|
+
print("\n=== Plugin Running ===")
|
|
390
|
+
print("Press Ctrl+C to stop\n")
|
|
391
|
+
|
|
392
|
+
while self.running:
|
|
393
|
+
time.sleep(1)
|
|
394
|
+
|
|
395
|
+
except KeyboardInterrupt:
|
|
396
|
+
print("\n\nShutting down...")
|
|
397
|
+
self.shutdown()
|
|
398
|
+
except Exception as e:
|
|
399
|
+
print(f"\nError: {e}")
|
|
400
|
+
import traceback
|
|
401
|
+
traceback.print_exc()
|
|
402
|
+
self.shutdown()
|
|
403
|
+
raise
|
|
404
|
+
|
|
405
|
+
def shutdown(self):
|
|
406
|
+
"""关闭插件"""
|
|
407
|
+
print("Shutting down SoulSync plugin...")
|
|
408
|
+
self.running = False
|
|
409
|
+
|
|
410
|
+
if self.watcher:
|
|
411
|
+
try:
|
|
412
|
+
self.watcher.stop()
|
|
413
|
+
print("File watcher stopped")
|
|
414
|
+
except Exception as e:
|
|
415
|
+
print(f"Error stopping watcher: {e}")
|
|
416
|
+
|
|
417
|
+
if self.client:
|
|
418
|
+
try:
|
|
419
|
+
self.client.close()
|
|
420
|
+
print("Client connection closed")
|
|
421
|
+
except Exception as e:
|
|
422
|
+
print(f"Error closing client: {e}")
|
|
423
|
+
|
|
424
|
+
print("Plugin shutdown complete")
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def main():
|
|
428
|
+
"""主函数"""
|
|
429
|
+
plugin = SoulSyncPlugin()
|
|
430
|
+
plugin.run()
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
if __name__ == '__main__':
|
|
434
|
+
main()
|
package/src/register.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import getpass
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Register:
|
|
6
|
+
def __init__(self, client):
|
|
7
|
+
self.client = client
|
|
8
|
+
|
|
9
|
+
def run(self):
|
|
10
|
+
print("\n" + "=" * 50)
|
|
11
|
+
print("SoulSync User Registration / SoulSync 用户注册")
|
|
12
|
+
print("=" * 50 + "\n")
|
|
13
|
+
|
|
14
|
+
email = self.get_email()
|
|
15
|
+
if not email:
|
|
16
|
+
return None
|
|
17
|
+
|
|
18
|
+
self.send_code(email)
|
|
19
|
+
code = self.get_code()
|
|
20
|
+
if not code:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
password = self.get_password()
|
|
24
|
+
if not password:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
result = self.register(email, password, code)
|
|
28
|
+
return result
|
|
29
|
+
|
|
30
|
+
def get_email(self):
|
|
31
|
+
while True:
|
|
32
|
+
email = input("Please enter your email / 请输入您的邮箱: ").strip()
|
|
33
|
+
if email:
|
|
34
|
+
if '@' in email and '.' in email:
|
|
35
|
+
return email
|
|
36
|
+
else:
|
|
37
|
+
print("Please enter a valid email address / 请输入有效的邮箱地址")
|
|
38
|
+
else:
|
|
39
|
+
print("Email cannot be empty / 邮箱不能为空")
|
|
40
|
+
|
|
41
|
+
def send_code(self, email):
|
|
42
|
+
print(f"\nSending verification code to {email} / 正在发送验证码到 {email}...")
|
|
43
|
+
try:
|
|
44
|
+
result = self.client.send_verification_code(email)
|
|
45
|
+
print("Verification code sent! / 验证码已发送!")
|
|
46
|
+
print("Please check server console for the code / 请查看服务器控制台获取验证码")
|
|
47
|
+
print(f"Code expires in / 验证码有效期: {result.get('expires_in', 300)} seconds\n")
|
|
48
|
+
except Exception as e:
|
|
49
|
+
print(f"Failed to send code: {e} / 发送验证码失败: {e}")
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
|
|
52
|
+
def get_code(self):
|
|
53
|
+
max_retries = 3
|
|
54
|
+
retries = 0
|
|
55
|
+
while retries < max_retries:
|
|
56
|
+
code = input("Please enter verification code / 请输入验证码: ").strip()
|
|
57
|
+
if code and len(code) == 6 and code.isdigit():
|
|
58
|
+
return code
|
|
59
|
+
else:
|
|
60
|
+
retries += 1
|
|
61
|
+
remaining = max_retries - retries
|
|
62
|
+
if remaining > 0:
|
|
63
|
+
print(f"Invalid code, please enter 6-digit code / 请输入6位数字验证码, remaining attempts: {remaining}")
|
|
64
|
+
else:
|
|
65
|
+
print("Max retries exceeded, exiting / 超过最大重试次数,退出")
|
|
66
|
+
sys.exit(1)
|
|
67
|
+
|
|
68
|
+
def get_password(self):
|
|
69
|
+
while True:
|
|
70
|
+
password = getpass.getpass("Please enter password / 请输入密码: ")
|
|
71
|
+
if len(password) < 6:
|
|
72
|
+
print("Password must be at least 6 characters / 密码至少6位")
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
confirm = getpass.getpass("Please confirm password / 请确认密码: ")
|
|
76
|
+
if password == confirm:
|
|
77
|
+
return password
|
|
78
|
+
else:
|
|
79
|
+
print("Passwords do not match, please try again / 两次密码不一致,请重新输入")
|
|
80
|
+
|
|
81
|
+
def register(self, email, password, code):
|
|
82
|
+
print("\nRegistering... / 正在注册...")
|
|
83
|
+
try:
|
|
84
|
+
result = self.client.register(email, password, code)
|
|
85
|
+
print("\nRegistration successful! / 注册成功!")
|
|
86
|
+
return result
|
|
87
|
+
except Exception as e:
|
|
88
|
+
print(f"\nRegistration failed: {e} / 注册失败: {e}")
|
|
89
|
+
sys.exit(1)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Login:
|
|
93
|
+
def __init__(self, client):
|
|
94
|
+
self.client = client
|
|
95
|
+
|
|
96
|
+
def run(self):
|
|
97
|
+
print("\n" + "=" * 50)
|
|
98
|
+
print("SoulSync User Login / SoulSync 用户登录")
|
|
99
|
+
print("=" * 50 + "\n")
|
|
100
|
+
|
|
101
|
+
email = self.get_email()
|
|
102
|
+
password = self.get_password()
|
|
103
|
+
|
|
104
|
+
result = self.login(email, password)
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
def get_email(self):
|
|
108
|
+
while True:
|
|
109
|
+
email = input("Please enter your email / 请输入您的邮箱: ").strip()
|
|
110
|
+
if email:
|
|
111
|
+
return email
|
|
112
|
+
else:
|
|
113
|
+
print("Email cannot be empty / 邮箱不能为空")
|
|
114
|
+
|
|
115
|
+
def get_password(self):
|
|
116
|
+
while True:
|
|
117
|
+
password = getpass.getpass("Please enter password / 请输入密码: ")
|
|
118
|
+
if password:
|
|
119
|
+
return password
|
|
120
|
+
else:
|
|
121
|
+
print("Password cannot be empty / 密码不能为空")
|
|
122
|
+
|
|
123
|
+
def login(self, email, password):
|
|
124
|
+
print("\nLogging in... / 正在登录...")
|
|
125
|
+
try:
|
|
126
|
+
result = self.client.login(email, password)
|
|
127
|
+
print("\nLogin successful! / 登录成功!")
|
|
128
|
+
return result
|
|
129
|
+
except Exception as e:
|
|
130
|
+
print(f"\nLogin failed: {e} / 登录失败: {e}")
|
|
131
|
+
sys.exit(1)
|