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/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
- url = f"{self.cloud_url}/api/auth/device"
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 == 201:
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('user_id')
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 / 配置失败")