pixelarraythirdparty 1.2.3__tar.gz → 1.2.5__tar.gz
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.
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/PKG-INFO +1 -1
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/__init__.py +1 -1
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/unified_login/__init__.py +2 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/unified_login/unified_login.py +124 -385
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty.egg-info/PKG-INFO +1 -1
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pyproject.toml +1 -1
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/LICENSE +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/MANIFEST.in +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/client.py +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/cron/__init__.py +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/cron/cron.py +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/feedback/__init__.py +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/feedback/feedback.py +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/order/__init__.py +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/order/order.py +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/product/__init__.py +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/product/product.py +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/project/__init__.py +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/project/project.py +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/user/__init__.py +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/user/user.py +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty.egg-info/SOURCES.txt +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty.egg-info/dependency_links.txt +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty.egg-info/requires.txt +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty.egg-info/top_level.txt +0 -0
- {pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/setup.cfg +0 -0
|
@@ -4,6 +4,7 @@ from .unified_login import (
|
|
|
4
4
|
WechatLogin,
|
|
5
5
|
GitHubLogin,
|
|
6
6
|
DouyinLogin,
|
|
7
|
+
TiktokLogin,
|
|
7
8
|
GitLabLogin,
|
|
8
9
|
SMSLogin,
|
|
9
10
|
EmailLogin,
|
|
@@ -15,6 +16,7 @@ __all__ = [
|
|
|
15
16
|
"WechatLogin",
|
|
16
17
|
"GitHubLogin",
|
|
17
18
|
"DouyinLogin",
|
|
19
|
+
"TiktokLogin",
|
|
18
20
|
"GitLabLogin",
|
|
19
21
|
"SMSLogin",
|
|
20
22
|
"EmailLogin",
|
|
@@ -9,57 +9,29 @@ from pixelarraythirdparty.client import AsyncClient
|
|
|
9
9
|
class OAuth2Login(AsyncClient):
|
|
10
10
|
"""
|
|
11
11
|
统一的 OAuth2 登录客户端基类
|
|
12
|
-
|
|
13
|
-
支持所有基于 OAuth2 的第三方登录(Google、微信、GitHub、GitLab
|
|
12
|
+
|
|
13
|
+
支持所有基于 OAuth2 的第三方登录(Google、微信、GitHub、GitLab、抖音、TikTok等)
|
|
14
14
|
通过配置不同的端点来支持不同的提供商
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
使用示例:
|
|
17
17
|
```
|
|
18
18
|
# 服务端使用场景(推荐)
|
|
19
|
-
|
|
19
|
+
oauth2_login = OAuth2Login(
|
|
20
20
|
api_key="your_api_key",
|
|
21
|
-
provider="google"
|
|
21
|
+
provider="google" # 支持的提供商:google, wechat, github, gitlab, douyin
|
|
22
22
|
)
|
|
23
23
|
# 1. 获取授权URL
|
|
24
|
-
auth_data, success = await
|
|
24
|
+
auth_data, success = await oauth2_login.get_auth_url()
|
|
25
25
|
if success:
|
|
26
26
|
auth_url = auth_data.get("auth_url")
|
|
27
27
|
state = auth_data.get("state")
|
|
28
28
|
# 将auth_url返回给前端,让用户点击授权
|
|
29
29
|
# 2. 等待登录结果(在服务端轮询)
|
|
30
|
-
user_info, success = await
|
|
31
|
-
|
|
32
|
-
# 测试场景(仅用于本地测试,会打开浏览器)
|
|
33
|
-
google = OAuth2Login(
|
|
34
|
-
api_key="your_api_key",
|
|
35
|
-
provider="google"
|
|
36
|
-
)
|
|
37
|
-
user_info, success = await google.login()
|
|
38
|
-
|
|
39
|
-
# 微信PC端扫码登录
|
|
40
|
-
wechat = OAuth2Login(
|
|
41
|
-
api_key="your_api_key",
|
|
42
|
-
provider="wechat",
|
|
43
|
-
login_type="desktop"
|
|
44
|
-
)
|
|
45
|
-
user_info, success = await wechat.login()
|
|
46
|
-
|
|
47
|
-
# 微信手机端登录
|
|
48
|
-
wechat_mobile = OAuth2Login(
|
|
49
|
-
api_key="your_api_key",
|
|
50
|
-
provider="wechat",
|
|
51
|
-
login_type="mobile"
|
|
52
|
-
)
|
|
53
|
-
user_info, success = await wechat_mobile.login()
|
|
54
|
-
|
|
55
|
-
# 支持refresh_token的提供商(如Google、GitLab)
|
|
56
|
-
if user_info.get("refresh_token"):
|
|
57
|
-
token_data, success = await google.refresh_access_token(
|
|
58
|
-
user_info.get("refresh_token")
|
|
59
|
-
)
|
|
30
|
+
user_info, success = await oauth2_login.wait_for_login(state, timeout=180)
|
|
31
|
+
print(user_info, success)
|
|
60
32
|
```
|
|
61
33
|
"""
|
|
62
|
-
|
|
34
|
+
|
|
63
35
|
# 提供商端点映射
|
|
64
36
|
PROVIDER_ENDPOINTS = {
|
|
65
37
|
"google": {
|
|
@@ -88,38 +60,37 @@ class OAuth2Login(AsyncClient):
|
|
|
88
60
|
"auth_url": "/api/unified-login/douyin/auth-url",
|
|
89
61
|
"wait_login": "/api/unified-login/douyin/wait-login",
|
|
90
62
|
},
|
|
63
|
+
"tiktok": {
|
|
64
|
+
"auth_url": "/api/unified-login/tiktok/auth-url",
|
|
65
|
+
"wait_login": "/api/unified-login/tiktok/wait-login",
|
|
66
|
+
},
|
|
91
67
|
}
|
|
92
|
-
|
|
93
|
-
def __init__(
|
|
94
|
-
self,
|
|
95
|
-
api_key: str,
|
|
96
|
-
provider: str,
|
|
97
|
-
login_type: Optional[str] = None
|
|
98
|
-
):
|
|
68
|
+
|
|
69
|
+
def __init__(self, api_key: str, provider: str, login_type: Optional[str] = None):
|
|
99
70
|
"""
|
|
100
71
|
description:
|
|
101
72
|
初始化OAuth2登录客户端
|
|
102
73
|
parameters:
|
|
103
74
|
api_key(str): API密钥
|
|
104
|
-
provider(str): 提供商名称,可选值:google, wechat, github, gitlab, douyin
|
|
75
|
+
provider(str): 提供商名称,可选值:google, wechat, github, gitlab, douyin, tiktok
|
|
105
76
|
login_type(str, optional): 登录类型,仅对微信有效,可选值:desktop(PC端扫码), mobile(手机端)
|
|
106
77
|
"""
|
|
107
78
|
super().__init__(api_key)
|
|
108
79
|
self.provider = provider.lower()
|
|
109
80
|
self.login_type = login_type
|
|
110
|
-
|
|
81
|
+
|
|
111
82
|
# 验证提供商是否支持
|
|
112
83
|
if self.provider not in self.PROVIDER_ENDPOINTS:
|
|
113
84
|
raise ValueError(
|
|
114
85
|
f"不支持的提供商: {provider}。"
|
|
115
86
|
f"支持的提供商: {', '.join(self.PROVIDER_ENDPOINTS.keys())}"
|
|
116
87
|
)
|
|
117
|
-
|
|
88
|
+
|
|
118
89
|
# 微信特殊处理:根据login_type选择不同的端点
|
|
119
90
|
if self.provider == "wechat":
|
|
120
91
|
if login_type == "mobile":
|
|
121
92
|
self.provider = "wechat-official"
|
|
122
|
-
|
|
93
|
+
|
|
123
94
|
def _get_endpoint(self, endpoint_type: str) -> str:
|
|
124
95
|
"""
|
|
125
96
|
description:
|
|
@@ -132,16 +103,14 @@ class OAuth2Login(AsyncClient):
|
|
|
132
103
|
endpoints = self.PROVIDER_ENDPOINTS.get(self.provider, {})
|
|
133
104
|
endpoint = endpoints.get(endpoint_type)
|
|
134
105
|
if not endpoint:
|
|
135
|
-
raise ValueError(
|
|
136
|
-
f"提供商 {self.provider} 不支持 {endpoint_type} 端点"
|
|
137
|
-
)
|
|
106
|
+
raise ValueError(f"提供商 {self.provider} 不支持 {endpoint_type} 端点")
|
|
138
107
|
return endpoint
|
|
139
|
-
|
|
108
|
+
|
|
140
109
|
async def get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
141
110
|
"""
|
|
142
111
|
description:
|
|
143
112
|
获取OAuth授权URL(公共方法,供服务端调用)
|
|
144
|
-
|
|
113
|
+
|
|
145
114
|
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
146
115
|
获取到state后,使用wait_for_login方法等待登录结果。
|
|
147
116
|
return:
|
|
@@ -156,35 +125,12 @@ class OAuth2Login(AsyncClient):
|
|
|
156
125
|
if not auth_url:
|
|
157
126
|
return None, False
|
|
158
127
|
return data, True
|
|
159
|
-
|
|
160
|
-
def
|
|
161
|
-
"""
|
|
162
|
-
description:
|
|
163
|
-
从授权URL中提取state参数
|
|
164
|
-
parameters:
|
|
165
|
-
auth_url(str, optional): 授权URL
|
|
166
|
-
return:
|
|
167
|
-
state(str, optional): state参数值,如果提取失败则返回None
|
|
168
|
-
"""
|
|
169
|
-
if not auth_url:
|
|
170
|
-
return None
|
|
171
|
-
try:
|
|
172
|
-
parsed = urllib.parse.urlparse(auth_url)
|
|
173
|
-
query = urllib.parse.parse_qs(parsed.query)
|
|
174
|
-
values = query.get("state")
|
|
175
|
-
if values:
|
|
176
|
-
return values[0]
|
|
177
|
-
except Exception:
|
|
178
|
-
return None
|
|
179
|
-
return None
|
|
180
|
-
|
|
181
|
-
async def wait_for_login(
|
|
182
|
-
self, state: str, timeout: int = 180
|
|
183
|
-
) -> Tuple[Dict, bool]:
|
|
128
|
+
|
|
129
|
+
async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
184
130
|
"""
|
|
185
131
|
description:
|
|
186
132
|
等待登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
187
|
-
|
|
133
|
+
|
|
188
134
|
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
189
135
|
此方法会持续轮询直到登录成功或超时。
|
|
190
136
|
parameters:
|
|
@@ -197,56 +143,29 @@ class OAuth2Login(AsyncClient):
|
|
|
197
143
|
interval = 2
|
|
198
144
|
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
199
145
|
endpoint = self._get_endpoint("wait_login")
|
|
200
|
-
|
|
146
|
+
|
|
201
147
|
for _ in range(total_checks):
|
|
202
148
|
status, response = await self._request_raw(
|
|
203
149
|
"POST",
|
|
204
150
|
endpoint,
|
|
205
151
|
json={"state": state},
|
|
206
152
|
)
|
|
207
|
-
|
|
153
|
+
|
|
208
154
|
if status == 200 and response.get("success") is True:
|
|
209
155
|
return response.get("data", {}), True
|
|
210
|
-
|
|
156
|
+
|
|
211
157
|
if status in (400, 408):
|
|
212
158
|
break
|
|
213
|
-
|
|
159
|
+
|
|
214
160
|
await asyncio.sleep(interval)
|
|
215
|
-
|
|
161
|
+
|
|
216
162
|
return {}, False
|
|
217
|
-
|
|
218
|
-
async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
219
|
-
"""
|
|
220
|
-
description:
|
|
221
|
-
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
|
|
222
|
-
|
|
223
|
-
注意:此方法仅用于本地测试场景,会自动打开浏览器。
|
|
224
|
-
在生产环境的服务端场景中,应该使用get_auth_url和wait_for_login方法。
|
|
225
|
-
parameters:
|
|
226
|
-
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
227
|
-
return:
|
|
228
|
-
user_info(dict): 用户信息字典
|
|
229
|
-
success(bool): 是否成功
|
|
230
|
-
"""
|
|
231
|
-
auth_data, success = await self.get_auth_url()
|
|
232
|
-
if not success or not auth_data:
|
|
233
|
-
return {}, False
|
|
234
|
-
|
|
235
|
-
auth_url = auth_data.get("auth_url")
|
|
236
|
-
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
237
|
-
|
|
238
|
-
if not auth_url or not state:
|
|
239
|
-
return {}, False
|
|
240
|
-
|
|
241
|
-
webbrowser.open(auth_url, new=2)
|
|
242
|
-
|
|
243
|
-
return await self.wait_for_login(state, timeout)
|
|
244
|
-
|
|
163
|
+
|
|
245
164
|
async def refresh_access_token(self, refresh_token: str) -> Tuple[Dict, bool]:
|
|
246
165
|
"""
|
|
247
166
|
description:
|
|
248
167
|
使用refresh_token刷新access_token(仅支持Google和GitLab)
|
|
249
|
-
|
|
168
|
+
|
|
250
169
|
注意:GitLab 采用 token rotation(令牌轮换)机制,每次刷新时会返回新的 refresh_token,
|
|
251
170
|
旧的 refresh_token 会立即失效。必须保存新的 refresh_token 并替换旧的。
|
|
252
171
|
Google 也可能在某些情况下返回新的 refresh_token。
|
|
@@ -260,7 +179,7 @@ class OAuth2Login(AsyncClient):
|
|
|
260
179
|
endpoint = self._get_endpoint("refresh_token")
|
|
261
180
|
except ValueError:
|
|
262
181
|
return {}, False
|
|
263
|
-
|
|
182
|
+
|
|
264
183
|
data, success = await self._request(
|
|
265
184
|
"POST",
|
|
266
185
|
endpoint,
|
|
@@ -290,16 +209,6 @@ class GoogleLogin(AsyncClient):
|
|
|
290
209
|
if success:
|
|
291
210
|
access_token = user_info.get("access_token")
|
|
292
211
|
refresh_token = user_info.get("refresh_token")
|
|
293
|
-
|
|
294
|
-
# 使用refresh_token刷新access_token
|
|
295
|
-
if refresh_token:
|
|
296
|
-
token_data, success = await google.refresh_access_token(refresh_token)
|
|
297
|
-
if success:
|
|
298
|
-
new_access_token = token_data.get("access_token")
|
|
299
|
-
|
|
300
|
-
# 测试场景(仅用于本地测试,会打开浏览器)
|
|
301
|
-
google = GoogleLogin(api_key="your_api_key")
|
|
302
|
-
user_info, success = await google.login()
|
|
303
212
|
```
|
|
304
213
|
"""
|
|
305
214
|
|
|
@@ -310,7 +219,7 @@ class GoogleLogin(AsyncClient):
|
|
|
310
219
|
"""
|
|
311
220
|
description:
|
|
312
221
|
获取Google OAuth授权URL(公共方法,供服务端调用)
|
|
313
|
-
|
|
222
|
+
|
|
314
223
|
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
315
224
|
获取到state后,使用wait_for_login方法等待登录结果。
|
|
316
225
|
return:
|
|
@@ -327,53 +236,11 @@ class GoogleLogin(AsyncClient):
|
|
|
327
236
|
return None, False
|
|
328
237
|
return data, True
|
|
329
238
|
|
|
330
|
-
async def
|
|
331
|
-
"""
|
|
332
|
-
description:
|
|
333
|
-
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
|
|
334
|
-
|
|
335
|
-
注意:此方法仅用于本地测试场景,会自动打开浏览器。
|
|
336
|
-
在生产环境的服务端场景中,应该使用get_auth_url和wait_for_login方法。
|
|
337
|
-
parameters:
|
|
338
|
-
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
339
|
-
return:
|
|
340
|
-
user_info(dict): 用户信息字典
|
|
341
|
-
success(bool): 是否成功
|
|
342
|
-
"""
|
|
343
|
-
auth_data, success = await self.get_auth_url()
|
|
344
|
-
if not success or not auth_data:
|
|
345
|
-
return {}, False
|
|
346
|
-
|
|
347
|
-
auth_url = auth_data.get("auth_url")
|
|
348
|
-
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
349
|
-
|
|
350
|
-
if not auth_url or not state:
|
|
351
|
-
return {}, False
|
|
352
|
-
|
|
353
|
-
webbrowser.open(auth_url, new=2)
|
|
354
|
-
|
|
355
|
-
return await self.wait_for_login(state, timeout)
|
|
356
|
-
|
|
357
|
-
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
358
|
-
if not auth_url:
|
|
359
|
-
return None
|
|
360
|
-
try:
|
|
361
|
-
parsed = urllib.parse.urlparse(auth_url)
|
|
362
|
-
query = urllib.parse.parse_qs(parsed.query)
|
|
363
|
-
values = query.get("state")
|
|
364
|
-
if values:
|
|
365
|
-
return values[0]
|
|
366
|
-
except Exception:
|
|
367
|
-
return None
|
|
368
|
-
return None
|
|
369
|
-
|
|
370
|
-
async def wait_for_login(
|
|
371
|
-
self, state: str, timeout: int = 180
|
|
372
|
-
) -> Tuple[Dict, bool]:
|
|
239
|
+
async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
373
240
|
"""
|
|
374
241
|
description:
|
|
375
242
|
等待Google登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
376
|
-
|
|
243
|
+
|
|
377
244
|
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
378
245
|
此方法会持续轮询直到登录成功或超时。
|
|
379
246
|
parameters:
|
|
@@ -426,7 +293,7 @@ class GoogleLogin(AsyncClient):
|
|
|
426
293
|
class WechatLogin(AsyncClient):
|
|
427
294
|
"""
|
|
428
295
|
微信 OAuth2 登录客户端
|
|
429
|
-
|
|
296
|
+
|
|
430
297
|
支持两种登录方式:
|
|
431
298
|
- desktop: PC端扫码登录(使用微信开放平台)
|
|
432
299
|
- mobile: 微信公众号OAuth登录(手机端微信内打开)
|
|
@@ -443,7 +310,8 @@ class WechatLogin(AsyncClient):
|
|
|
443
310
|
# 将auth_url返回给前端,让用户扫码授权
|
|
444
311
|
# 等待登录结果
|
|
445
312
|
user_info, success = await wechat.wait_for_login(state, timeout=180, login_type="desktop")
|
|
446
|
-
|
|
313
|
+
print(user_info, success)
|
|
314
|
+
|
|
447
315
|
# 微信公众号登录(手机端)
|
|
448
316
|
auth_data, success = await wechat.get_auth_url(login_type="mobile")
|
|
449
317
|
if success:
|
|
@@ -452,21 +320,20 @@ class WechatLogin(AsyncClient):
|
|
|
452
320
|
# 将auth_url返回给前端,让用户在微信内打开授权
|
|
453
321
|
# 等待登录结果
|
|
454
322
|
user_info, success = await wechat.wait_for_login(state, timeout=180, login_type="mobile")
|
|
455
|
-
|
|
456
|
-
# 测试场景(仅用于本地测试,会打开浏览器)
|
|
457
|
-
wechat = WechatLogin(api_key="your_api_key")
|
|
458
|
-
user_info, success = await wechat.login(login_type="desktop")
|
|
323
|
+
print(user_info, success)
|
|
459
324
|
```
|
|
460
325
|
"""
|
|
461
326
|
|
|
462
327
|
def __init__(self, api_key: str):
|
|
463
328
|
super().__init__(api_key)
|
|
464
329
|
|
|
465
|
-
async def get_auth_url(
|
|
330
|
+
async def get_auth_url(
|
|
331
|
+
self, login_type: str = "desktop"
|
|
332
|
+
) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
466
333
|
"""
|
|
467
334
|
description:
|
|
468
335
|
获取微信授权URL(公共方法,供服务端调用)
|
|
469
|
-
|
|
336
|
+
|
|
470
337
|
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
471
338
|
获取到state后,使用wait_for_login方法等待登录结果。
|
|
472
339
|
parameters:
|
|
@@ -481,7 +348,7 @@ class WechatLogin(AsyncClient):
|
|
|
481
348
|
else:
|
|
482
349
|
# PC端扫码登录
|
|
483
350
|
endpoint = "/api/unified-login/wechat/auth-url"
|
|
484
|
-
|
|
351
|
+
|
|
485
352
|
data, success = await self._request("POST", endpoint)
|
|
486
353
|
if not success:
|
|
487
354
|
return None, False
|
|
@@ -490,62 +357,13 @@ class WechatLogin(AsyncClient):
|
|
|
490
357
|
return None, False
|
|
491
358
|
return data, True
|
|
492
359
|
|
|
493
|
-
async def login(self, login_type: str = "desktop", timeout: int = 180) -> Tuple[Dict, bool]:
|
|
494
|
-
"""
|
|
495
|
-
description:
|
|
496
|
-
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
|
|
497
|
-
|
|
498
|
-
注意:此方法仅用于本地测试场景,会自动打开浏览器。
|
|
499
|
-
在生产环境的服务端场景中,应该使用get_auth_url和wait_for_login方法。
|
|
500
|
-
parameters:
|
|
501
|
-
login_type(str, optional): 登录类型,desktop表示PC端扫码登录,mobile表示微信公众号登录,默认为desktop
|
|
502
|
-
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
503
|
-
return:
|
|
504
|
-
user_info(dict): 用户信息字典
|
|
505
|
-
success(bool): 是否成功
|
|
506
|
-
"""
|
|
507
|
-
auth_data, success = await self.get_auth_url(login_type)
|
|
508
|
-
if not success or not auth_data:
|
|
509
|
-
return {}, False
|
|
510
|
-
|
|
511
|
-
auth_url = auth_data.get("auth_url")
|
|
512
|
-
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
513
|
-
|
|
514
|
-
if not auth_url or not state:
|
|
515
|
-
return {}, False
|
|
516
|
-
|
|
517
|
-
webbrowser.open(auth_url, new=2)
|
|
518
|
-
|
|
519
|
-
return await self.wait_for_login(state, timeout, login_type)
|
|
520
|
-
|
|
521
|
-
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
522
|
-
"""
|
|
523
|
-
description:
|
|
524
|
-
从授权URL中提取state参数
|
|
525
|
-
parameters:
|
|
526
|
-
auth_url(str, optional): 授权URL
|
|
527
|
-
return:
|
|
528
|
-
state(str, optional): state参数值,如果提取失败则返回None
|
|
529
|
-
"""
|
|
530
|
-
if not auth_url:
|
|
531
|
-
return None
|
|
532
|
-
try:
|
|
533
|
-
parsed = urllib.parse.urlparse(auth_url)
|
|
534
|
-
query = urllib.parse.parse_qs(parsed.query)
|
|
535
|
-
values = query.get("state")
|
|
536
|
-
if values:
|
|
537
|
-
return values[0]
|
|
538
|
-
except Exception:
|
|
539
|
-
return None
|
|
540
|
-
return None
|
|
541
|
-
|
|
542
360
|
async def wait_for_login(
|
|
543
361
|
self, state: str, timeout: int = 180, login_type: str = "desktop"
|
|
544
362
|
) -> Tuple[Dict, bool]:
|
|
545
363
|
"""
|
|
546
364
|
description:
|
|
547
365
|
等待微信登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
548
|
-
|
|
366
|
+
|
|
549
367
|
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
550
368
|
此方法会持续轮询直到登录成功或超时。
|
|
551
369
|
parameters:
|
|
@@ -599,10 +417,7 @@ class GitHubLogin(AsyncClient):
|
|
|
599
417
|
# 将auth_url返回给前端,让用户点击授权
|
|
600
418
|
# 2. 等待登录结果(在服务端轮询)
|
|
601
419
|
user_info, success = await github.wait_for_login(state, timeout=180)
|
|
602
|
-
|
|
603
|
-
# 测试场景(仅用于本地测试,会打开浏览器)
|
|
604
|
-
github = GitHubLogin(api_key="your_api_key")
|
|
605
|
-
user_info, success = await github.login()
|
|
420
|
+
print(user_info, success)
|
|
606
421
|
```
|
|
607
422
|
"""
|
|
608
423
|
|
|
@@ -613,7 +428,7 @@ class GitHubLogin(AsyncClient):
|
|
|
613
428
|
"""
|
|
614
429
|
description:
|
|
615
430
|
获取GitHub OAuth授权URL(公共方法,供服务端调用)
|
|
616
|
-
|
|
431
|
+
|
|
617
432
|
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
618
433
|
获取到state后,使用wait_for_login方法等待登录结果。
|
|
619
434
|
return:
|
|
@@ -630,61 +445,11 @@ class GitHubLogin(AsyncClient):
|
|
|
630
445
|
return None, False
|
|
631
446
|
return data, True
|
|
632
447
|
|
|
633
|
-
async def
|
|
634
|
-
"""
|
|
635
|
-
description:
|
|
636
|
-
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
|
|
637
|
-
|
|
638
|
-
注意:此方法仅用于本地测试场景,会自动打开浏览器。
|
|
639
|
-
在生产环境的服务端场景中,应该使用get_auth_url和wait_for_login方法。
|
|
640
|
-
parameters:
|
|
641
|
-
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
642
|
-
return:
|
|
643
|
-
user_info(dict): 用户信息字典
|
|
644
|
-
success(bool): 是否成功
|
|
645
|
-
"""
|
|
646
|
-
auth_data, success = await self.get_auth_url()
|
|
647
|
-
if not success or not auth_data:
|
|
648
|
-
return {}, False
|
|
649
|
-
|
|
650
|
-
auth_url = auth_data.get("auth_url")
|
|
651
|
-
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
652
|
-
|
|
653
|
-
if not auth_url or not state:
|
|
654
|
-
return {}, False
|
|
655
|
-
|
|
656
|
-
webbrowser.open(auth_url, new=2)
|
|
657
|
-
|
|
658
|
-
return await self.wait_for_login(state, timeout)
|
|
659
|
-
|
|
660
|
-
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
661
|
-
"""
|
|
662
|
-
description:
|
|
663
|
-
从授权URL中提取state参数
|
|
664
|
-
parameters:
|
|
665
|
-
auth_url(str, optional): 授权URL
|
|
666
|
-
return:
|
|
667
|
-
state(str, optional): state参数值,如果提取失败则返回None
|
|
668
|
-
"""
|
|
669
|
-
if not auth_url:
|
|
670
|
-
return None
|
|
671
|
-
try:
|
|
672
|
-
parsed = urllib.parse.urlparse(auth_url)
|
|
673
|
-
query = urllib.parse.parse_qs(parsed.query)
|
|
674
|
-
values = query.get("state")
|
|
675
|
-
if values:
|
|
676
|
-
return values[0]
|
|
677
|
-
except Exception:
|
|
678
|
-
return None
|
|
679
|
-
return None
|
|
680
|
-
|
|
681
|
-
async def wait_for_login(
|
|
682
|
-
self, state: str, timeout: int = 180
|
|
683
|
-
) -> Tuple[Dict, bool]:
|
|
448
|
+
async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
684
449
|
"""
|
|
685
450
|
description:
|
|
686
451
|
等待GitHub登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
687
|
-
|
|
452
|
+
|
|
688
453
|
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
689
454
|
此方法会持续轮询直到登录成功或超时。
|
|
690
455
|
parameters:
|
|
@@ -731,10 +496,7 @@ class DouyinLogin(AsyncClient):
|
|
|
731
496
|
# 将auth_url返回给前端,让用户点击授权
|
|
732
497
|
# 2. 等待登录结果(在服务端轮询)
|
|
733
498
|
user_info, success = await douyin.wait_for_login(state, timeout=180)
|
|
734
|
-
|
|
735
|
-
# 测试场景(仅用于本地测试,会打开浏览器)
|
|
736
|
-
douyin = DouyinLogin(api_key="your_api_key")
|
|
737
|
-
user_info, success = await douyin.login()
|
|
499
|
+
print(user_info, success)
|
|
738
500
|
```
|
|
739
501
|
"""
|
|
740
502
|
|
|
@@ -745,7 +507,7 @@ class DouyinLogin(AsyncClient):
|
|
|
745
507
|
"""
|
|
746
508
|
description:
|
|
747
509
|
获取抖音OAuth授权URL(公共方法,供服务端调用)
|
|
748
|
-
|
|
510
|
+
|
|
749
511
|
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
750
512
|
获取到state后,使用wait_for_login方法等待登录结果。
|
|
751
513
|
return:
|
|
@@ -762,61 +524,90 @@ class DouyinLogin(AsyncClient):
|
|
|
762
524
|
return None, False
|
|
763
525
|
return data, True
|
|
764
526
|
|
|
765
|
-
async def
|
|
527
|
+
async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
766
528
|
"""
|
|
767
529
|
description:
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
530
|
+
等待抖音登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
531
|
+
|
|
532
|
+
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
533
|
+
此方法会持续轮询直到登录成功或超时。
|
|
772
534
|
parameters:
|
|
773
|
-
|
|
535
|
+
state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
|
|
536
|
+
timeout(int, optional): 超时时间(秒),默认为180
|
|
774
537
|
return:
|
|
775
538
|
user_info(dict): 用户信息字典
|
|
776
539
|
success(bool): 是否成功
|
|
777
540
|
"""
|
|
778
|
-
|
|
779
|
-
if
|
|
780
|
-
return {}, False
|
|
541
|
+
interval = 2
|
|
542
|
+
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
781
543
|
|
|
782
|
-
|
|
783
|
-
|
|
544
|
+
for _ in range(total_checks):
|
|
545
|
+
status, response = await self._request_raw(
|
|
546
|
+
"POST",
|
|
547
|
+
"/api/unified-login/douyin/wait-login",
|
|
548
|
+
json={"state": state},
|
|
549
|
+
)
|
|
784
550
|
|
|
785
|
-
|
|
786
|
-
|
|
551
|
+
if status == 200 and response.get("success") is True:
|
|
552
|
+
return response.get("data", {}), True
|
|
787
553
|
|
|
788
|
-
|
|
554
|
+
if status in (400, 408):
|
|
555
|
+
break
|
|
556
|
+
|
|
557
|
+
await asyncio.sleep(interval)
|
|
558
|
+
|
|
559
|
+
return {}, False
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
class TiktokLogin(AsyncClient):
|
|
563
|
+
"""
|
|
564
|
+
TikTok OAuth2 登录客户端
|
|
565
|
+
|
|
566
|
+
使用示例:
|
|
567
|
+
```
|
|
568
|
+
# 服务端使用场景(推荐)
|
|
569
|
+
tiktok = TiktokLogin(api_key="your_api_key")
|
|
570
|
+
# 1. 获取授权URL
|
|
571
|
+
auth_data, success = await tiktok.get_auth_url()
|
|
572
|
+
if success:
|
|
573
|
+
auth_url = auth_data.get("auth_url")
|
|
574
|
+
state = auth_data.get("state")
|
|
575
|
+
# 将auth_url返回给前端,让用户点击授权
|
|
576
|
+
# 2. 等待登录结果(在服务端轮询)
|
|
577
|
+
user_info, success = await tiktok.wait_for_login(state, timeout=180)
|
|
578
|
+
print(user_info, success)
|
|
579
|
+
```
|
|
580
|
+
"""
|
|
789
581
|
|
|
790
|
-
|
|
582
|
+
def __init__(self, api_key: str):
|
|
583
|
+
super().__init__(api_key)
|
|
791
584
|
|
|
792
|
-
def
|
|
585
|
+
async def get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
793
586
|
"""
|
|
794
587
|
description:
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
588
|
+
获取TikTok OAuth授权URL(公共方法,供服务端调用)
|
|
589
|
+
|
|
590
|
+
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
591
|
+
获取到state后,使用wait_for_login方法等待登录结果。
|
|
798
592
|
return:
|
|
799
|
-
|
|
593
|
+
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
594
|
+
success(bool): 是否成功
|
|
800
595
|
"""
|
|
596
|
+
data, success = await self._request(
|
|
597
|
+
"POST", "/api/unified-login/tiktok/auth-url"
|
|
598
|
+
)
|
|
599
|
+
if not success:
|
|
600
|
+
return None, False
|
|
601
|
+
auth_url = data.get("auth_url")
|
|
801
602
|
if not auth_url:
|
|
802
|
-
return None
|
|
803
|
-
|
|
804
|
-
parsed = urllib.parse.urlparse(auth_url)
|
|
805
|
-
query = urllib.parse.parse_qs(parsed.query)
|
|
806
|
-
values = query.get("state")
|
|
807
|
-
if values:
|
|
808
|
-
return values[0]
|
|
809
|
-
except Exception:
|
|
810
|
-
return None
|
|
811
|
-
return None
|
|
603
|
+
return None, False
|
|
604
|
+
return data, True
|
|
812
605
|
|
|
813
|
-
async def wait_for_login(
|
|
814
|
-
self, state: str, timeout: int = 180
|
|
815
|
-
) -> Tuple[Dict, bool]:
|
|
606
|
+
async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
816
607
|
"""
|
|
817
608
|
description:
|
|
818
|
-
|
|
819
|
-
|
|
609
|
+
等待TikTok登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
610
|
+
|
|
820
611
|
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
821
612
|
此方法会持续轮询直到登录成功或超时。
|
|
822
613
|
parameters:
|
|
@@ -832,7 +623,7 @@ class DouyinLogin(AsyncClient):
|
|
|
832
623
|
for _ in range(total_checks):
|
|
833
624
|
status, response = await self._request_raw(
|
|
834
625
|
"POST",
|
|
835
|
-
"/api/unified-login/
|
|
626
|
+
"/api/unified-login/tiktok/wait-login",
|
|
836
627
|
json={"state": state},
|
|
837
628
|
)
|
|
838
629
|
|
|
@@ -863,13 +654,7 @@ class GitLabLogin(AsyncClient):
|
|
|
863
654
|
# 将auth_url返回给前端,让用户点击授权
|
|
864
655
|
# 2. 等待登录结果(在服务端轮询)
|
|
865
656
|
user_info, success = await gitlab.wait_for_login(state, timeout=180)
|
|
866
|
-
|
|
867
|
-
# 使用refresh_token刷新access_token
|
|
868
|
-
token_data, success = await gitlab.refresh_access_token(user_info.get("refresh_token"))
|
|
869
|
-
|
|
870
|
-
# 测试场景(仅用于本地测试,会打开浏览器)
|
|
871
|
-
gitlab = GitLabLogin(api_key="your_api_key")
|
|
872
|
-
user_info, success = await gitlab.login()
|
|
657
|
+
print(user_info, success)
|
|
873
658
|
```
|
|
874
659
|
"""
|
|
875
660
|
|
|
@@ -880,7 +665,7 @@ class GitLabLogin(AsyncClient):
|
|
|
880
665
|
"""
|
|
881
666
|
description:
|
|
882
667
|
获取GitLab OAuth授权URL(公共方法,供服务端调用)
|
|
883
|
-
|
|
668
|
+
|
|
884
669
|
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
885
670
|
获取到state后,使用wait_for_login方法等待登录结果。
|
|
886
671
|
return:
|
|
@@ -897,53 +682,11 @@ class GitLabLogin(AsyncClient):
|
|
|
897
682
|
return None, False
|
|
898
683
|
return data, True
|
|
899
684
|
|
|
900
|
-
async def
|
|
901
|
-
"""
|
|
902
|
-
description:
|
|
903
|
-
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
|
|
904
|
-
|
|
905
|
-
注意:此方法仅用于本地测试场景,会自动打开浏览器。
|
|
906
|
-
在生产环境的服务端场景中,应该使用get_auth_url和wait_for_login方法。
|
|
907
|
-
parameters:
|
|
908
|
-
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
909
|
-
return:
|
|
910
|
-
user_info(dict): 用户信息字典
|
|
911
|
-
success(bool): 是否成功
|
|
912
|
-
"""
|
|
913
|
-
auth_data, success = await self.get_auth_url()
|
|
914
|
-
if not success or not auth_data:
|
|
915
|
-
return {}, False
|
|
916
|
-
|
|
917
|
-
auth_url = auth_data.get("auth_url")
|
|
918
|
-
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
919
|
-
|
|
920
|
-
if not auth_url or not state:
|
|
921
|
-
return {}, False
|
|
922
|
-
|
|
923
|
-
webbrowser.open(auth_url, new=2)
|
|
924
|
-
|
|
925
|
-
return await self.wait_for_login(state, timeout)
|
|
926
|
-
|
|
927
|
-
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
928
|
-
if not auth_url:
|
|
929
|
-
return None
|
|
930
|
-
try:
|
|
931
|
-
parsed = urllib.parse.urlparse(auth_url)
|
|
932
|
-
query = urllib.parse.parse_qs(parsed.query)
|
|
933
|
-
values = query.get("state")
|
|
934
|
-
if values:
|
|
935
|
-
return values[0]
|
|
936
|
-
except Exception:
|
|
937
|
-
return None
|
|
938
|
-
return None
|
|
939
|
-
|
|
940
|
-
async def wait_for_login(
|
|
941
|
-
self, state: str, timeout: int = 180
|
|
942
|
-
) -> Tuple[Dict, bool]:
|
|
685
|
+
async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
943
686
|
"""
|
|
944
687
|
description:
|
|
945
688
|
等待GitLab登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
946
|
-
|
|
689
|
+
|
|
947
690
|
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
948
691
|
此方法会持续轮询直到登录成功或超时。
|
|
949
692
|
parameters:
|
|
@@ -977,7 +720,7 @@ class GitLabLogin(AsyncClient):
|
|
|
977
720
|
"""
|
|
978
721
|
description:
|
|
979
722
|
使用refresh_token刷新access_token
|
|
980
|
-
|
|
723
|
+
|
|
981
724
|
注意:GitLab 采用 token rotation(令牌轮换)机制,每次刷新时会返回新的 refresh_token,
|
|
982
725
|
旧的 refresh_token 会立即失效。必须保存新的 refresh_token 并替换旧的。
|
|
983
726
|
parameters:
|
|
@@ -1041,9 +784,7 @@ class SMSLogin(AsyncClient):
|
|
|
1041
784
|
)
|
|
1042
785
|
return bool(success)
|
|
1043
786
|
|
|
1044
|
-
async def _wait_for_login(
|
|
1045
|
-
self, phone: str, timeout: int
|
|
1046
|
-
) -> Tuple[Dict, bool]:
|
|
787
|
+
async def _wait_for_login(self, phone: str, timeout: int) -> Tuple[Dict, bool]:
|
|
1047
788
|
"""
|
|
1048
789
|
等待短信登录结果
|
|
1049
790
|
|
|
@@ -1133,9 +874,7 @@ class EmailLogin(AsyncClient):
|
|
|
1133
874
|
)
|
|
1134
875
|
return bool(success)
|
|
1135
876
|
|
|
1136
|
-
async def _wait_for_login(
|
|
1137
|
-
self, email: str, timeout: int
|
|
1138
|
-
) -> Tuple[Dict, bool]:
|
|
877
|
+
async def _wait_for_login(self, email: str, timeout: int) -> Tuple[Dict, bool]:
|
|
1139
878
|
"""
|
|
1140
879
|
等待邮箱登录结果
|
|
1141
880
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/cron/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/feedback/__init__.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/feedback/feedback.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/order/__init__.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/order/order.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/product/__init__.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/product/product.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/project/__init__.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/project/project.py
RENAMED
|
File without changes
|
{pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty/user/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pixelarraythirdparty-1.2.3 → pixelarraythirdparty-1.2.5}/pixelarraythirdparty.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|