pixelarraythirdparty 1.2.2__py3-none-any.whl → 1.2.4__py3-none-any.whl
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/__init__.py +1 -1
- pixelarraythirdparty/unified_login/unified_login.py +177 -364
- {pixelarraythirdparty-1.2.2.dist-info → pixelarraythirdparty-1.2.4.dist-info}/METADATA +1 -1
- {pixelarraythirdparty-1.2.2.dist-info → pixelarraythirdparty-1.2.4.dist-info}/RECORD +7 -7
- {pixelarraythirdparty-1.2.2.dist-info → pixelarraythirdparty-1.2.4.dist-info}/WHEEL +0 -0
- {pixelarraythirdparty-1.2.2.dist-info → pixelarraythirdparty-1.2.4.dist-info}/licenses/LICENSE +0 -0
- {pixelarraythirdparty-1.2.2.dist-info → pixelarraythirdparty-1.2.4.dist-info}/top_level.txt +0 -0
pixelarraythirdparty/__init__.py
CHANGED
|
@@ -9,43 +9,29 @@ from pixelarraythirdparty.client import AsyncClient
|
|
|
9
9
|
class OAuth2Login(AsyncClient):
|
|
10
10
|
"""
|
|
11
11
|
统一的 OAuth2 登录客户端基类
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
支持所有基于 OAuth2 的第三方登录(Google、微信、GitHub、GitLab、抖音等)
|
|
14
14
|
通过配置不同的端点来支持不同的提供商
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
使用示例:
|
|
17
17
|
```
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
api_key="your_api_key",
|
|
21
|
-
provider="google"
|
|
22
|
-
)
|
|
23
|
-
user_info, success = await google.login()
|
|
24
|
-
|
|
25
|
-
# 微信PC端扫码登录
|
|
26
|
-
wechat = OAuth2Login(
|
|
27
|
-
api_key="your_api_key",
|
|
28
|
-
provider="wechat",
|
|
29
|
-
login_type="desktop"
|
|
30
|
-
)
|
|
31
|
-
user_info, success = await wechat.login()
|
|
32
|
-
|
|
33
|
-
# 微信手机端登录
|
|
34
|
-
wechat_mobile = OAuth2Login(
|
|
18
|
+
# 服务端使用场景(推荐)
|
|
19
|
+
oauth2_login = OAuth2Login(
|
|
35
20
|
api_key="your_api_key",
|
|
36
|
-
provider="
|
|
37
|
-
login_type="mobile"
|
|
21
|
+
provider="google" # 支持的提供商:google, wechat, github, gitlab, douyin
|
|
38
22
|
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
23
|
+
# 1. 获取授权URL
|
|
24
|
+
auth_data, success = await oauth2_login.get_auth_url()
|
|
25
|
+
if success:
|
|
26
|
+
auth_url = auth_data.get("auth_url")
|
|
27
|
+
state = auth_data.get("state")
|
|
28
|
+
# 将auth_url返回给前端,让用户点击授权
|
|
29
|
+
# 2. 等待登录结果(在服务端轮询)
|
|
30
|
+
user_info, success = await oauth2_login.wait_for_login(state, timeout=180)
|
|
31
|
+
print(user_info, success)
|
|
46
32
|
```
|
|
47
33
|
"""
|
|
48
|
-
|
|
34
|
+
|
|
49
35
|
# 提供商端点映射
|
|
50
36
|
PROVIDER_ENDPOINTS = {
|
|
51
37
|
"google": {
|
|
@@ -75,13 +61,8 @@ class OAuth2Login(AsyncClient):
|
|
|
75
61
|
"wait_login": "/api/unified-login/douyin/wait-login",
|
|
76
62
|
},
|
|
77
63
|
}
|
|
78
|
-
|
|
79
|
-
def __init__(
|
|
80
|
-
self,
|
|
81
|
-
api_key: str,
|
|
82
|
-
provider: str,
|
|
83
|
-
login_type: Optional[str] = None
|
|
84
|
-
):
|
|
64
|
+
|
|
65
|
+
def __init__(self, api_key: str, provider: str, login_type: Optional[str] = None):
|
|
85
66
|
"""
|
|
86
67
|
description:
|
|
87
68
|
初始化OAuth2登录客户端
|
|
@@ -93,19 +74,19 @@ class OAuth2Login(AsyncClient):
|
|
|
93
74
|
super().__init__(api_key)
|
|
94
75
|
self.provider = provider.lower()
|
|
95
76
|
self.login_type = login_type
|
|
96
|
-
|
|
77
|
+
|
|
97
78
|
# 验证提供商是否支持
|
|
98
79
|
if self.provider not in self.PROVIDER_ENDPOINTS:
|
|
99
80
|
raise ValueError(
|
|
100
81
|
f"不支持的提供商: {provider}。"
|
|
101
82
|
f"支持的提供商: {', '.join(self.PROVIDER_ENDPOINTS.keys())}"
|
|
102
83
|
)
|
|
103
|
-
|
|
84
|
+
|
|
104
85
|
# 微信特殊处理:根据login_type选择不同的端点
|
|
105
86
|
if self.provider == "wechat":
|
|
106
87
|
if login_type == "mobile":
|
|
107
88
|
self.provider = "wechat-official"
|
|
108
|
-
|
|
89
|
+
|
|
109
90
|
def _get_endpoint(self, endpoint_type: str) -> str:
|
|
110
91
|
"""
|
|
111
92
|
description:
|
|
@@ -118,15 +99,16 @@ class OAuth2Login(AsyncClient):
|
|
|
118
99
|
endpoints = self.PROVIDER_ENDPOINTS.get(self.provider, {})
|
|
119
100
|
endpoint = endpoints.get(endpoint_type)
|
|
120
101
|
if not endpoint:
|
|
121
|
-
raise ValueError(
|
|
122
|
-
f"提供商 {self.provider} 不支持 {endpoint_type} 端点"
|
|
123
|
-
)
|
|
102
|
+
raise ValueError(f"提供商 {self.provider} 不支持 {endpoint_type} 端点")
|
|
124
103
|
return endpoint
|
|
125
|
-
|
|
126
|
-
async def
|
|
104
|
+
|
|
105
|
+
async def get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
127
106
|
"""
|
|
128
107
|
description:
|
|
129
|
-
获取OAuth授权URL
|
|
108
|
+
获取OAuth授权URL(公共方法,供服务端调用)
|
|
109
|
+
|
|
110
|
+
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
111
|
+
获取到state后,使用wait_for_login方法等待登录结果。
|
|
130
112
|
return:
|
|
131
113
|
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
132
114
|
success(bool): 是否成功
|
|
@@ -139,37 +121,17 @@ class OAuth2Login(AsyncClient):
|
|
|
139
121
|
if not auth_url:
|
|
140
122
|
return None, False
|
|
141
123
|
return data, True
|
|
142
|
-
|
|
143
|
-
def
|
|
144
|
-
"""
|
|
145
|
-
description:
|
|
146
|
-
从授权URL中提取state参数
|
|
147
|
-
parameters:
|
|
148
|
-
auth_url(str, optional): 授权URL
|
|
149
|
-
return:
|
|
150
|
-
state(str, optional): state参数值,如果提取失败则返回None
|
|
151
|
-
"""
|
|
152
|
-
if not auth_url:
|
|
153
|
-
return None
|
|
154
|
-
try:
|
|
155
|
-
parsed = urllib.parse.urlparse(auth_url)
|
|
156
|
-
query = urllib.parse.parse_qs(parsed.query)
|
|
157
|
-
values = query.get("state")
|
|
158
|
-
if values:
|
|
159
|
-
return values[0]
|
|
160
|
-
except Exception:
|
|
161
|
-
return None
|
|
162
|
-
return None
|
|
163
|
-
|
|
164
|
-
async def _wait_for_login(
|
|
165
|
-
self, state: str, timeout: int
|
|
166
|
-
) -> Tuple[Dict, bool]:
|
|
124
|
+
|
|
125
|
+
async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
167
126
|
"""
|
|
168
127
|
description:
|
|
169
|
-
|
|
128
|
+
等待登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
129
|
+
|
|
130
|
+
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
131
|
+
此方法会持续轮询直到登录成功或超时。
|
|
170
132
|
parameters:
|
|
171
|
-
state(str):
|
|
172
|
-
timeout(int):
|
|
133
|
+
state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
|
|
134
|
+
timeout(int, optional): 超时时间(秒),默认为180
|
|
173
135
|
return:
|
|
174
136
|
user_info(dict): 用户信息字典
|
|
175
137
|
success(bool): 是否成功
|
|
@@ -177,53 +139,29 @@ class OAuth2Login(AsyncClient):
|
|
|
177
139
|
interval = 2
|
|
178
140
|
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
179
141
|
endpoint = self._get_endpoint("wait_login")
|
|
180
|
-
|
|
142
|
+
|
|
181
143
|
for _ in range(total_checks):
|
|
182
144
|
status, response = await self._request_raw(
|
|
183
145
|
"POST",
|
|
184
146
|
endpoint,
|
|
185
147
|
json={"state": state},
|
|
186
148
|
)
|
|
187
|
-
|
|
149
|
+
|
|
188
150
|
if status == 200 and response.get("success") is True:
|
|
189
151
|
return response.get("data", {}), True
|
|
190
|
-
|
|
152
|
+
|
|
191
153
|
if status in (400, 408):
|
|
192
154
|
break
|
|
193
|
-
|
|
155
|
+
|
|
194
156
|
await asyncio.sleep(interval)
|
|
195
|
-
|
|
157
|
+
|
|
196
158
|
return {}, False
|
|
197
|
-
|
|
198
|
-
async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
199
|
-
"""
|
|
200
|
-
description:
|
|
201
|
-
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
|
|
202
|
-
parameters:
|
|
203
|
-
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
204
|
-
return:
|
|
205
|
-
user_info(dict): 用户信息字典
|
|
206
|
-
success(bool): 是否成功
|
|
207
|
-
"""
|
|
208
|
-
auth_data, success = await self._get_auth_url()
|
|
209
|
-
if not success or not auth_data:
|
|
210
|
-
return {}, False
|
|
211
|
-
|
|
212
|
-
auth_url = auth_data.get("auth_url")
|
|
213
|
-
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
214
|
-
|
|
215
|
-
if not auth_url or not state:
|
|
216
|
-
return {}, False
|
|
217
|
-
|
|
218
|
-
webbrowser.open(auth_url, new=2)
|
|
219
|
-
|
|
220
|
-
return await self._wait_for_login(state, timeout)
|
|
221
|
-
|
|
159
|
+
|
|
222
160
|
async def refresh_access_token(self, refresh_token: str) -> Tuple[Dict, bool]:
|
|
223
161
|
"""
|
|
224
162
|
description:
|
|
225
163
|
使用refresh_token刷新access_token(仅支持Google和GitLab)
|
|
226
|
-
|
|
164
|
+
|
|
227
165
|
注意:GitLab 采用 token rotation(令牌轮换)机制,每次刷新时会返回新的 refresh_token,
|
|
228
166
|
旧的 refresh_token 会立即失效。必须保存新的 refresh_token 并替换旧的。
|
|
229
167
|
Google 也可能在某些情况下返回新的 refresh_token。
|
|
@@ -237,7 +175,7 @@ class OAuth2Login(AsyncClient):
|
|
|
237
175
|
endpoint = self._get_endpoint("refresh_token")
|
|
238
176
|
except ValueError:
|
|
239
177
|
return {}, False
|
|
240
|
-
|
|
178
|
+
|
|
241
179
|
data, success = await self._request(
|
|
242
180
|
"POST",
|
|
243
181
|
endpoint,
|
|
@@ -254,27 +192,32 @@ class GoogleLogin(AsyncClient):
|
|
|
254
192
|
|
|
255
193
|
使用示例:
|
|
256
194
|
```
|
|
195
|
+
# 服务端使用场景(推荐)
|
|
257
196
|
google = GoogleLogin(api_key="your_api_key")
|
|
258
|
-
|
|
197
|
+
# 1. 获取授权URL
|
|
198
|
+
auth_data, success = await google.get_auth_url()
|
|
259
199
|
if success:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
200
|
+
auth_url = auth_data.get("auth_url")
|
|
201
|
+
state = auth_data.get("state")
|
|
202
|
+
# 将auth_url返回给前端,让用户点击授权
|
|
203
|
+
# 2. 等待登录结果(在服务端轮询)
|
|
204
|
+
user_info, success = await google.wait_for_login(state, timeout=180)
|
|
205
|
+
if success:
|
|
206
|
+
access_token = user_info.get("access_token")
|
|
207
|
+
refresh_token = user_info.get("refresh_token")
|
|
268
208
|
```
|
|
269
209
|
"""
|
|
270
210
|
|
|
271
211
|
def __init__(self, api_key: str):
|
|
272
212
|
super().__init__(api_key)
|
|
273
213
|
|
|
274
|
-
async def
|
|
214
|
+
async def get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
275
215
|
"""
|
|
276
216
|
description:
|
|
277
|
-
获取Google OAuth授权URL
|
|
217
|
+
获取Google OAuth授权URL(公共方法,供服务端调用)
|
|
218
|
+
|
|
219
|
+
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
220
|
+
获取到state后,使用wait_for_login方法等待登录结果。
|
|
278
221
|
return:
|
|
279
222
|
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
280
223
|
success(bool): 是否成功
|
|
@@ -289,52 +232,16 @@ class GoogleLogin(AsyncClient):
|
|
|
289
232
|
return None, False
|
|
290
233
|
return data, True
|
|
291
234
|
|
|
292
|
-
async def
|
|
235
|
+
async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
293
236
|
"""
|
|
294
237
|
description:
|
|
295
|
-
|
|
296
|
-
parameters:
|
|
297
|
-
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
298
|
-
return:
|
|
299
|
-
user_info(dict): 用户信息字典
|
|
300
|
-
success(bool): 是否成功
|
|
301
|
-
"""
|
|
302
|
-
auth_data, success = await self._get_auth_url()
|
|
303
|
-
if not success or not auth_data:
|
|
304
|
-
return {}, False
|
|
305
|
-
|
|
306
|
-
auth_url = auth_data.get("auth_url")
|
|
307
|
-
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
308
|
-
|
|
309
|
-
if not auth_url or not state:
|
|
310
|
-
return {}, False
|
|
238
|
+
等待Google登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
311
239
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return await self._wait_for_google_login(state, timeout)
|
|
315
|
-
|
|
316
|
-
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
317
|
-
if not auth_url:
|
|
318
|
-
return None
|
|
319
|
-
try:
|
|
320
|
-
parsed = urllib.parse.urlparse(auth_url)
|
|
321
|
-
query = urllib.parse.parse_qs(parsed.query)
|
|
322
|
-
values = query.get("state")
|
|
323
|
-
if values:
|
|
324
|
-
return values[0]
|
|
325
|
-
except Exception:
|
|
326
|
-
return None
|
|
327
|
-
return None
|
|
328
|
-
|
|
329
|
-
async def _wait_for_google_login(
|
|
330
|
-
self, state: str, timeout: int
|
|
331
|
-
) -> Tuple[Dict, bool]:
|
|
332
|
-
"""
|
|
333
|
-
description:
|
|
334
|
-
等待Google登录结果,轮询检查登录状态
|
|
240
|
+
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
241
|
+
此方法会持续轮询直到登录成功或超时。
|
|
335
242
|
parameters:
|
|
336
|
-
state(str):
|
|
337
|
-
timeout(int):
|
|
243
|
+
state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
|
|
244
|
+
timeout(int, optional): 超时时间(秒),默认为180
|
|
338
245
|
return:
|
|
339
246
|
user_info(dict): 用户信息字典
|
|
340
247
|
success(bool): 是否成功
|
|
@@ -382,28 +289,49 @@ class GoogleLogin(AsyncClient):
|
|
|
382
289
|
class WechatLogin(AsyncClient):
|
|
383
290
|
"""
|
|
384
291
|
微信 OAuth2 登录客户端
|
|
385
|
-
|
|
292
|
+
|
|
386
293
|
支持两种登录方式:
|
|
387
294
|
- desktop: PC端扫码登录(使用微信开放平台)
|
|
388
295
|
- mobile: 微信公众号OAuth登录(手机端微信内打开)
|
|
389
296
|
|
|
390
297
|
使用示例:
|
|
391
298
|
```
|
|
299
|
+
# 服务端使用场景(推荐)
|
|
392
300
|
wechat = WechatLogin(api_key="your_api_key")
|
|
393
301
|
# PC端扫码登录
|
|
394
|
-
|
|
302
|
+
auth_data, success = await wechat.get_auth_url(login_type="desktop")
|
|
303
|
+
if success:
|
|
304
|
+
auth_url = auth_data.get("auth_url")
|
|
305
|
+
state = auth_data.get("state")
|
|
306
|
+
# 将auth_url返回给前端,让用户扫码授权
|
|
307
|
+
# 等待登录结果
|
|
308
|
+
user_info, success = await wechat.wait_for_login(state, timeout=180, login_type="desktop")
|
|
309
|
+
print(user_info, success)
|
|
310
|
+
|
|
395
311
|
# 微信公众号登录(手机端)
|
|
396
|
-
|
|
312
|
+
auth_data, success = await wechat.get_auth_url(login_type="mobile")
|
|
313
|
+
if success:
|
|
314
|
+
auth_url = auth_data.get("auth_url")
|
|
315
|
+
state = auth_data.get("state")
|
|
316
|
+
# 将auth_url返回给前端,让用户在微信内打开授权
|
|
317
|
+
# 等待登录结果
|
|
318
|
+
user_info, success = await wechat.wait_for_login(state, timeout=180, login_type="mobile")
|
|
319
|
+
print(user_info, success)
|
|
397
320
|
```
|
|
398
321
|
"""
|
|
399
322
|
|
|
400
323
|
def __init__(self, api_key: str):
|
|
401
324
|
super().__init__(api_key)
|
|
402
325
|
|
|
403
|
-
async def
|
|
326
|
+
async def get_auth_url(
|
|
327
|
+
self, login_type: str = "desktop"
|
|
328
|
+
) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
404
329
|
"""
|
|
405
330
|
description:
|
|
406
|
-
获取微信授权URL
|
|
331
|
+
获取微信授权URL(公共方法,供服务端调用)
|
|
332
|
+
|
|
333
|
+
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
334
|
+
获取到state后,使用wait_for_login方法等待登录结果。
|
|
407
335
|
parameters:
|
|
408
336
|
login_type(str, optional): 登录类型,desktop表示PC端扫码登录,mobile表示微信公众号登录,默认为desktop
|
|
409
337
|
return:
|
|
@@ -416,7 +344,7 @@ class WechatLogin(AsyncClient):
|
|
|
416
344
|
else:
|
|
417
345
|
# PC端扫码登录
|
|
418
346
|
endpoint = "/api/unified-login/wechat/auth-url"
|
|
419
|
-
|
|
347
|
+
|
|
420
348
|
data, success = await self._request("POST", endpoint)
|
|
421
349
|
if not success:
|
|
422
350
|
return None, False
|
|
@@ -425,61 +353,18 @@ class WechatLogin(AsyncClient):
|
|
|
425
353
|
return None, False
|
|
426
354
|
return data, True
|
|
427
355
|
|
|
428
|
-
async def
|
|
429
|
-
""
|
|
430
|
-
description:
|
|
431
|
-
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
|
|
432
|
-
parameters:
|
|
433
|
-
login_type(str, optional): 登录类型,desktop表示PC端扫码登录,mobile表示微信公众号登录,默认为desktop
|
|
434
|
-
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
435
|
-
return:
|
|
436
|
-
user_info(dict): 用户信息字典
|
|
437
|
-
success(bool): 是否成功
|
|
438
|
-
"""
|
|
439
|
-
auth_data, success = await self._get_auth_url(login_type)
|
|
440
|
-
if not success or not auth_data:
|
|
441
|
-
return {}, False
|
|
442
|
-
|
|
443
|
-
auth_url = auth_data.get("auth_url")
|
|
444
|
-
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
445
|
-
|
|
446
|
-
if not auth_url or not state:
|
|
447
|
-
return {}, False
|
|
448
|
-
|
|
449
|
-
webbrowser.open(auth_url, new=2)
|
|
450
|
-
|
|
451
|
-
return await self._wait_for_wechat_login(state, timeout, login_type)
|
|
452
|
-
|
|
453
|
-
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
454
|
-
"""
|
|
455
|
-
description:
|
|
456
|
-
从授权URL中提取state参数
|
|
457
|
-
parameters:
|
|
458
|
-
auth_url(str, optional): 授权URL
|
|
459
|
-
return:
|
|
460
|
-
state(str, optional): state参数值,如果提取失败则返回None
|
|
461
|
-
"""
|
|
462
|
-
if not auth_url:
|
|
463
|
-
return None
|
|
464
|
-
try:
|
|
465
|
-
parsed = urllib.parse.urlparse(auth_url)
|
|
466
|
-
query = urllib.parse.parse_qs(parsed.query)
|
|
467
|
-
values = query.get("state")
|
|
468
|
-
if values:
|
|
469
|
-
return values[0]
|
|
470
|
-
except Exception:
|
|
471
|
-
return None
|
|
472
|
-
return None
|
|
473
|
-
|
|
474
|
-
async def _wait_for_wechat_login(
|
|
475
|
-
self, state: str, timeout: int, login_type: str = "desktop"
|
|
356
|
+
async def wait_for_login(
|
|
357
|
+
self, state: str, timeout: int = 180, login_type: str = "desktop"
|
|
476
358
|
) -> Tuple[Dict, bool]:
|
|
477
359
|
"""
|
|
478
360
|
description:
|
|
479
|
-
|
|
361
|
+
等待微信登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
362
|
+
|
|
363
|
+
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
364
|
+
此方法会持续轮询直到登录成功或超时。
|
|
480
365
|
parameters:
|
|
481
|
-
state(str):
|
|
482
|
-
timeout(int):
|
|
366
|
+
state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
|
|
367
|
+
timeout(int, optional): 超时时间(秒),默认为180
|
|
483
368
|
login_type(str, optional): 登录类型,desktop表示PC端扫码登录,mobile表示微信公众号登录,默认为desktop
|
|
484
369
|
return:
|
|
485
370
|
user_info(dict): 用户信息字典
|
|
@@ -518,18 +403,30 @@ class GitHubLogin(AsyncClient):
|
|
|
518
403
|
|
|
519
404
|
使用示例:
|
|
520
405
|
```
|
|
406
|
+
# 服务端使用场景(推荐)
|
|
521
407
|
github = GitHubLogin(api_key="your_api_key")
|
|
522
|
-
|
|
408
|
+
# 1. 获取授权URL
|
|
409
|
+
auth_data, success = await github.get_auth_url()
|
|
410
|
+
if success:
|
|
411
|
+
auth_url = auth_data.get("auth_url")
|
|
412
|
+
state = auth_data.get("state")
|
|
413
|
+
# 将auth_url返回给前端,让用户点击授权
|
|
414
|
+
# 2. 等待登录结果(在服务端轮询)
|
|
415
|
+
user_info, success = await github.wait_for_login(state, timeout=180)
|
|
416
|
+
print(user_info, success)
|
|
523
417
|
```
|
|
524
418
|
"""
|
|
525
419
|
|
|
526
420
|
def __init__(self, api_key: str):
|
|
527
421
|
super().__init__(api_key)
|
|
528
422
|
|
|
529
|
-
async def
|
|
423
|
+
async def get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
530
424
|
"""
|
|
531
425
|
description:
|
|
532
|
-
获取GitHub OAuth授权URL
|
|
426
|
+
获取GitHub OAuth授权URL(公共方法,供服务端调用)
|
|
427
|
+
|
|
428
|
+
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
429
|
+
获取到state后,使用wait_for_login方法等待登录结果。
|
|
533
430
|
return:
|
|
534
431
|
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
535
432
|
success(bool): 是否成功
|
|
@@ -544,60 +441,16 @@ class GitHubLogin(AsyncClient):
|
|
|
544
441
|
return None, False
|
|
545
442
|
return data, True
|
|
546
443
|
|
|
547
|
-
async def
|
|
444
|
+
async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
548
445
|
"""
|
|
549
446
|
description:
|
|
550
|
-
|
|
551
|
-
parameters:
|
|
552
|
-
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
553
|
-
return:
|
|
554
|
-
user_info(dict): 用户信息字典
|
|
555
|
-
success(bool): 是否成功
|
|
556
|
-
"""
|
|
557
|
-
auth_data, success = await self._get_auth_url()
|
|
558
|
-
if not success or not auth_data:
|
|
559
|
-
return {}, False
|
|
560
|
-
|
|
561
|
-
auth_url = auth_data.get("auth_url")
|
|
562
|
-
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
563
|
-
|
|
564
|
-
if not auth_url or not state:
|
|
565
|
-
return {}, False
|
|
447
|
+
等待GitHub登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
566
448
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
return await self._wait_for_github_login(state, timeout)
|
|
570
|
-
|
|
571
|
-
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
572
|
-
"""
|
|
573
|
-
description:
|
|
574
|
-
从授权URL中提取state参数
|
|
575
|
-
parameters:
|
|
576
|
-
auth_url(str, optional): 授权URL
|
|
577
|
-
return:
|
|
578
|
-
state(str, optional): state参数值,如果提取失败则返回None
|
|
579
|
-
"""
|
|
580
|
-
if not auth_url:
|
|
581
|
-
return None
|
|
582
|
-
try:
|
|
583
|
-
parsed = urllib.parse.urlparse(auth_url)
|
|
584
|
-
query = urllib.parse.parse_qs(parsed.query)
|
|
585
|
-
values = query.get("state")
|
|
586
|
-
if values:
|
|
587
|
-
return values[0]
|
|
588
|
-
except Exception:
|
|
589
|
-
return None
|
|
590
|
-
return None
|
|
591
|
-
|
|
592
|
-
async def _wait_for_github_login(
|
|
593
|
-
self, state: str, timeout: int
|
|
594
|
-
) -> Tuple[Dict, bool]:
|
|
595
|
-
"""
|
|
596
|
-
description:
|
|
597
|
-
等待GitHub登录结果,轮询检查登录状态
|
|
449
|
+
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
450
|
+
此方法会持续轮询直到登录成功或超时。
|
|
598
451
|
parameters:
|
|
599
|
-
state(str):
|
|
600
|
-
timeout(int):
|
|
452
|
+
state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
|
|
453
|
+
timeout(int, optional): 超时时间(秒),默认为180
|
|
601
454
|
return:
|
|
602
455
|
user_info(dict): 用户信息字典
|
|
603
456
|
success(bool): 是否成功
|
|
@@ -629,18 +482,30 @@ class DouyinLogin(AsyncClient):
|
|
|
629
482
|
|
|
630
483
|
使用示例:
|
|
631
484
|
```
|
|
485
|
+
# 服务端使用场景(推荐)
|
|
632
486
|
douyin = DouyinLogin(api_key="your_api_key")
|
|
633
|
-
|
|
487
|
+
# 1. 获取授权URL
|
|
488
|
+
auth_data, success = await douyin.get_auth_url()
|
|
489
|
+
if success:
|
|
490
|
+
auth_url = auth_data.get("auth_url")
|
|
491
|
+
state = auth_data.get("state")
|
|
492
|
+
# 将auth_url返回给前端,让用户点击授权
|
|
493
|
+
# 2. 等待登录结果(在服务端轮询)
|
|
494
|
+
user_info, success = await douyin.wait_for_login(state, timeout=180)
|
|
495
|
+
print(user_info, success)
|
|
634
496
|
```
|
|
635
497
|
"""
|
|
636
498
|
|
|
637
499
|
def __init__(self, api_key: str):
|
|
638
500
|
super().__init__(api_key)
|
|
639
501
|
|
|
640
|
-
async def
|
|
502
|
+
async def get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
641
503
|
"""
|
|
642
504
|
description:
|
|
643
|
-
获取抖音OAuth授权URL
|
|
505
|
+
获取抖音OAuth授权URL(公共方法,供服务端调用)
|
|
506
|
+
|
|
507
|
+
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
508
|
+
获取到state后,使用wait_for_login方法等待登录结果。
|
|
644
509
|
return:
|
|
645
510
|
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
646
511
|
success(bool): 是否成功
|
|
@@ -655,60 +520,16 @@ class DouyinLogin(AsyncClient):
|
|
|
655
520
|
return None, False
|
|
656
521
|
return data, True
|
|
657
522
|
|
|
658
|
-
async def
|
|
523
|
+
async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
659
524
|
"""
|
|
660
525
|
description:
|
|
661
|
-
|
|
662
|
-
parameters:
|
|
663
|
-
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
664
|
-
return:
|
|
665
|
-
user_info(dict): 用户信息字典
|
|
666
|
-
success(bool): 是否成功
|
|
667
|
-
"""
|
|
668
|
-
auth_data, success = await self._get_auth_url()
|
|
669
|
-
if not success or not auth_data:
|
|
670
|
-
return {}, False
|
|
526
|
+
等待抖音登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
671
527
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
if not auth_url or not state:
|
|
676
|
-
return {}, False
|
|
677
|
-
|
|
678
|
-
webbrowser.open(auth_url, new=2)
|
|
679
|
-
|
|
680
|
-
return await self._wait_for_douyin_login(state, timeout)
|
|
681
|
-
|
|
682
|
-
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
683
|
-
"""
|
|
684
|
-
description:
|
|
685
|
-
从授权URL中提取state参数
|
|
686
|
-
parameters:
|
|
687
|
-
auth_url(str, optional): 授权URL
|
|
688
|
-
return:
|
|
689
|
-
state(str, optional): state参数值,如果提取失败则返回None
|
|
690
|
-
"""
|
|
691
|
-
if not auth_url:
|
|
692
|
-
return None
|
|
693
|
-
try:
|
|
694
|
-
parsed = urllib.parse.urlparse(auth_url)
|
|
695
|
-
query = urllib.parse.parse_qs(parsed.query)
|
|
696
|
-
values = query.get("state")
|
|
697
|
-
if values:
|
|
698
|
-
return values[0]
|
|
699
|
-
except Exception:
|
|
700
|
-
return None
|
|
701
|
-
return None
|
|
702
|
-
|
|
703
|
-
async def _wait_for_douyin_login(
|
|
704
|
-
self, state: str, timeout: int
|
|
705
|
-
) -> Tuple[Dict, bool]:
|
|
706
|
-
"""
|
|
707
|
-
description:
|
|
708
|
-
等待抖音登录结果,轮询检查登录状态
|
|
528
|
+
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
529
|
+
此方法会持续轮询直到登录成功或超时。
|
|
709
530
|
parameters:
|
|
710
|
-
state(str):
|
|
711
|
-
timeout(int):
|
|
531
|
+
state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
|
|
532
|
+
timeout(int, optional): 超时时间(秒),默认为180
|
|
712
533
|
return:
|
|
713
534
|
user_info(dict): 用户信息字典
|
|
714
535
|
success(bool): 是否成功
|
|
@@ -740,15 +561,34 @@ class GitLabLogin(AsyncClient):
|
|
|
740
561
|
|
|
741
562
|
使用示例:
|
|
742
563
|
```
|
|
564
|
+
# 服务端使用场景(推荐)
|
|
743
565
|
gitlab = GitLabLogin(api_key="your_api_key")
|
|
744
|
-
|
|
566
|
+
# 1. 获取授权URL
|
|
567
|
+
auth_data, success = await gitlab.get_auth_url()
|
|
568
|
+
if success:
|
|
569
|
+
auth_url = auth_data.get("auth_url")
|
|
570
|
+
state = auth_data.get("state")
|
|
571
|
+
# 将auth_url返回给前端,让用户点击授权
|
|
572
|
+
# 2. 等待登录结果(在服务端轮询)
|
|
573
|
+
user_info, success = await gitlab.wait_for_login(state, timeout=180)
|
|
574
|
+
print(user_info, success)
|
|
745
575
|
```
|
|
746
576
|
"""
|
|
747
577
|
|
|
748
578
|
def __init__(self, api_key: str):
|
|
749
579
|
super().__init__(api_key)
|
|
750
580
|
|
|
751
|
-
async def
|
|
581
|
+
async def get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
582
|
+
"""
|
|
583
|
+
description:
|
|
584
|
+
获取GitLab OAuth授权URL(公共方法,供服务端调用)
|
|
585
|
+
|
|
586
|
+
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
587
|
+
获取到state后,使用wait_for_login方法等待登录结果。
|
|
588
|
+
return:
|
|
589
|
+
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
590
|
+
success(bool): 是否成功
|
|
591
|
+
"""
|
|
752
592
|
data, success = await self._request(
|
|
753
593
|
"POST", "/api/unified-login/gitlab/auth-url"
|
|
754
594
|
)
|
|
@@ -759,43 +599,20 @@ class GitLabLogin(AsyncClient):
|
|
|
759
599
|
return None, False
|
|
760
600
|
return data, True
|
|
761
601
|
|
|
762
|
-
async def
|
|
602
|
+
async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
763
603
|
"""
|
|
764
|
-
|
|
765
|
-
|
|
604
|
+
description:
|
|
605
|
+
等待GitLab登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
766
606
|
|
|
767
|
-
|
|
607
|
+
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
608
|
+
此方法会持续轮询直到登录成功或超时。
|
|
609
|
+
parameters:
|
|
610
|
+
state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
|
|
611
|
+
timeout(int, optional): 超时时间(秒),默认为180
|
|
612
|
+
return:
|
|
613
|
+
user_info(dict): 用户信息字典
|
|
614
|
+
success(bool): 是否成功
|
|
768
615
|
"""
|
|
769
|
-
auth_data, success = await self._get_auth_url()
|
|
770
|
-
if not success or not auth_data:
|
|
771
|
-
return {}, False
|
|
772
|
-
|
|
773
|
-
auth_url = auth_data.get("auth_url")
|
|
774
|
-
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
775
|
-
|
|
776
|
-
if not auth_url or not state:
|
|
777
|
-
return {}, False
|
|
778
|
-
|
|
779
|
-
webbrowser.open(auth_url, new=2)
|
|
780
|
-
|
|
781
|
-
return await self._wait_for_gitlab_login(state, timeout)
|
|
782
|
-
|
|
783
|
-
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
784
|
-
if not auth_url:
|
|
785
|
-
return None
|
|
786
|
-
try:
|
|
787
|
-
parsed = urllib.parse.urlparse(auth_url)
|
|
788
|
-
query = urllib.parse.parse_qs(parsed.query)
|
|
789
|
-
values = query.get("state")
|
|
790
|
-
if values:
|
|
791
|
-
return values[0]
|
|
792
|
-
except Exception:
|
|
793
|
-
return None
|
|
794
|
-
return None
|
|
795
|
-
|
|
796
|
-
async def _wait_for_gitlab_login(
|
|
797
|
-
self, state: str, timeout: int
|
|
798
|
-
) -> Tuple[Dict, bool]:
|
|
799
616
|
interval = 2
|
|
800
617
|
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
801
618
|
|
|
@@ -820,7 +637,7 @@ class GitLabLogin(AsyncClient):
|
|
|
820
637
|
"""
|
|
821
638
|
description:
|
|
822
639
|
使用refresh_token刷新access_token
|
|
823
|
-
|
|
640
|
+
|
|
824
641
|
注意:GitLab 采用 token rotation(令牌轮换)机制,每次刷新时会返回新的 refresh_token,
|
|
825
642
|
旧的 refresh_token 会立即失效。必须保存新的 refresh_token 并替换旧的。
|
|
826
643
|
parameters:
|
|
@@ -884,9 +701,7 @@ class SMSLogin(AsyncClient):
|
|
|
884
701
|
)
|
|
885
702
|
return bool(success)
|
|
886
703
|
|
|
887
|
-
async def _wait_for_login(
|
|
888
|
-
self, phone: str, timeout: int
|
|
889
|
-
) -> Tuple[Dict, bool]:
|
|
704
|
+
async def _wait_for_login(self, phone: str, timeout: int) -> Tuple[Dict, bool]:
|
|
890
705
|
"""
|
|
891
706
|
等待短信登录结果
|
|
892
707
|
|
|
@@ -976,9 +791,7 @@ class EmailLogin(AsyncClient):
|
|
|
976
791
|
)
|
|
977
792
|
return bool(success)
|
|
978
793
|
|
|
979
|
-
async def _wait_for_login(
|
|
980
|
-
self, email: str, timeout: int
|
|
981
|
-
) -> Tuple[Dict, bool]:
|
|
794
|
+
async def _wait_for_login(self, email: str, timeout: int) -> Tuple[Dict, bool]:
|
|
982
795
|
"""
|
|
983
796
|
等待邮箱登录结果
|
|
984
797
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
pixelarraythirdparty/__init__.py,sha256=
|
|
1
|
+
pixelarraythirdparty/__init__.py,sha256=ZPQ3uKaT_Scaf4p_JVcXXb90OdZ01QLEkXh57BGGnuY,582
|
|
2
2
|
pixelarraythirdparty/client.py,sha256=Ym_IZ6cdoZJoMKW61ZRTfQ81-Hay5dpLTgExoANZwI4,3171
|
|
3
3
|
pixelarraythirdparty/cron/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
pixelarraythirdparty/cron/cron.py,sha256=nv4e2hX_UkEJ-kbEARbInU2J6aREyYZ61dZ-4b9UWJI,3146
|
|
@@ -11,11 +11,11 @@ pixelarraythirdparty/product/product.py,sha256=5fgv2Ck860epYXxipY83vePziubCIlocF
|
|
|
11
11
|
pixelarraythirdparty/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
pixelarraythirdparty/project/project.py,sha256=a8swjckyn4y3NlIx0-tMbcRwDY9woOijGi7lb1wN7Ok,2455
|
|
13
13
|
pixelarraythirdparty/unified_login/__init__.py,sha256=tzy3nmRv-qZID-6kYRFarqQVrmkUsEI7D6de_PFu7Tg,327
|
|
14
|
-
pixelarraythirdparty/unified_login/unified_login.py,sha256=
|
|
14
|
+
pixelarraythirdparty/unified_login/unified_login.py,sha256=chpB8Yj6yoPrh821LRe4BbDlLkK4Kn9n5X6FBiINUp0,29007
|
|
15
15
|
pixelarraythirdparty/user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
pixelarraythirdparty/user/user.py,sha256=SqufSAVMxQElz-NqtlOZs_dG7UtbuE-ygLAyml9oKpY,5761
|
|
17
|
-
pixelarraythirdparty-1.2.
|
|
18
|
-
pixelarraythirdparty-1.2.
|
|
19
|
-
pixelarraythirdparty-1.2.
|
|
20
|
-
pixelarraythirdparty-1.2.
|
|
21
|
-
pixelarraythirdparty-1.2.
|
|
17
|
+
pixelarraythirdparty-1.2.4.dist-info/licenses/LICENSE,sha256=O-g1dUr0U50rSIvmWE9toiVkSgFpVt72_MHITbWvAqA,1067
|
|
18
|
+
pixelarraythirdparty-1.2.4.dist-info/METADATA,sha256=2m92kvoPsCInrZsLpS094MxadQzGipcKcYtlEqa6GHY,993
|
|
19
|
+
pixelarraythirdparty-1.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
20
|
+
pixelarraythirdparty-1.2.4.dist-info/top_level.txt,sha256=dzG2Ut8j7noUqj_0ZQjcIDAeHYCh_9WtlxjAxtoyufo,21
|
|
21
|
+
pixelarraythirdparty-1.2.4.dist-info/RECORD,,
|
|
File without changes
|
{pixelarraythirdparty-1.2.2.dist-info → pixelarraythirdparty-1.2.4.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|