pixelarraythirdparty 1.1.0__py3-none-any.whl → 1.1.2__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/client.py +19 -0
- pixelarraythirdparty/cron/cron.py +62 -105
- pixelarraythirdparty/order/order.py +119 -199
- pixelarraythirdparty/product/product.py +84 -165
- pixelarraythirdparty/unified_login/__init__.py +2 -14
- pixelarraythirdparty/unified_login/unified_login.py +264 -12
- pixelarraythirdparty/user/user.py +72 -138
- {pixelarraythirdparty-1.1.0.dist-info → pixelarraythirdparty-1.1.2.dist-info}/METADATA +1 -1
- pixelarraythirdparty-1.1.2.dist-info/RECORD +17 -0
- pixelarraythirdparty-1.1.0.dist-info/RECORD +0 -17
- {pixelarraythirdparty-1.1.0.dist-info → pixelarraythirdparty-1.1.2.dist-info}/WHEEL +0 -0
- {pixelarraythirdparty-1.1.0.dist-info → pixelarraythirdparty-1.1.2.dist-info}/licenses/LICENSE +0 -0
- {pixelarraythirdparty-1.1.0.dist-info → pixelarraythirdparty-1.1.2.dist-info}/top_level.txt +0 -0
|
@@ -15,44 +15,33 @@ class ProductManagerAsync(AsyncClient):
|
|
|
15
15
|
sort_order: int,
|
|
16
16
|
):
|
|
17
17
|
"""
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
功能说明:
|
|
18
|
+
description:
|
|
21
19
|
创建新的产品,支持订阅产品和一次性产品,产品信息包括名称、描述、价格、分类等。
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
- subscription_period (str): 订阅周期
|
|
48
|
-
- features (str): 产品特性(JSON格式)
|
|
49
|
-
- sort_order (int): 排序权重
|
|
50
|
-
- created_at (str): 产品创建时间
|
|
51
|
-
- updated_at (str): 产品更新时间
|
|
52
|
-
success (bool): 操作是否成功
|
|
53
|
-
|
|
54
|
-
异常情况:
|
|
55
|
-
- 产品创建失败:返回错误信息"产品创建失败"
|
|
20
|
+
parameters:
|
|
21
|
+
name(str): 产品名称
|
|
22
|
+
description(str): 产品描述
|
|
23
|
+
price(float): 产品价格(元)
|
|
24
|
+
category(str): 产品分类
|
|
25
|
+
status(str): 产品状态,可选值:"ACTIVE"(激活)、"INACTIVE"(停用)
|
|
26
|
+
is_subscription(bool): 是否为订阅产品
|
|
27
|
+
subscription_period(str): 订阅周期,可选值:"MONTHLY"(月付)、"YEARLY"(年付)
|
|
28
|
+
features(str): 产品特性,JSON格式字符串
|
|
29
|
+
sort_order(int): 排序权重
|
|
30
|
+
return:
|
|
31
|
+
data(dict): 产品信息
|
|
32
|
+
- id(int): 产品ID
|
|
33
|
+
- name(str): 产品名称
|
|
34
|
+
- description(str): 产品描述
|
|
35
|
+
- price(float): 产品价格(元)
|
|
36
|
+
- category(str): 产品分类
|
|
37
|
+
- status(str): 产品状态
|
|
38
|
+
- is_subscription(bool): 是否为订阅产品
|
|
39
|
+
- subscription_period(str): 订阅周期
|
|
40
|
+
- features(str): 产品特性(JSON格式)
|
|
41
|
+
- sort_order(int): 排序权重
|
|
42
|
+
- created_at(str): 产品创建时间
|
|
43
|
+
- updated_at(str): 产品更新时间
|
|
44
|
+
success(bool): 操作是否成功
|
|
56
45
|
"""
|
|
57
46
|
data = {
|
|
58
47
|
"name": name,
|
|
@@ -79,42 +68,21 @@ class ProductManagerAsync(AsyncClient):
|
|
|
79
68
|
name: str = None,
|
|
80
69
|
):
|
|
81
70
|
"""
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
功能说明:
|
|
71
|
+
description:
|
|
85
72
|
分页查询产品列表,支持按状态、分类和名称进行筛选。
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
- id (int): 产品ID
|
|
100
|
-
- name (str): 产品名称
|
|
101
|
-
- description (str): 产品描述
|
|
102
|
-
- price (float): 产品价格(元)
|
|
103
|
-
- category (str): 产品分类
|
|
104
|
-
- status (str): 产品状态
|
|
105
|
-
- is_subscription (bool): 是否为订阅产品
|
|
106
|
-
- subscription_period (str): 订阅周期
|
|
107
|
-
- features (str): 产品特性(JSON格式)
|
|
108
|
-
- sort_order (int): 排序权重
|
|
109
|
-
- created_at (str): 产品创建时间
|
|
110
|
-
- updated_at (str): 产品更新时间
|
|
111
|
-
- total (int): 总产品数量
|
|
112
|
-
- page (int): 当前页码
|
|
113
|
-
- page_size (int): 每页数量
|
|
114
|
-
success (bool): 操作是否成功
|
|
115
|
-
|
|
116
|
-
异常情况:
|
|
117
|
-
- 获取产品列表失败:返回错误信息"获取产品列表失败"
|
|
73
|
+
parameters:
|
|
74
|
+
page(int): 页码
|
|
75
|
+
page_size(int): 每页数量
|
|
76
|
+
status(str): 产品状态筛选,可选值:"ACTIVE"(激活)、"INACTIVE"(停用)
|
|
77
|
+
category(str): 产品分类筛选
|
|
78
|
+
name(str): 产品名称搜索,支持模糊匹配
|
|
79
|
+
return:
|
|
80
|
+
data(dict): 产品列表信息
|
|
81
|
+
- products(list): 产品列表
|
|
82
|
+
- total(int): 总产品数量
|
|
83
|
+
- page(int): 当前页码
|
|
84
|
+
- page_size(int): 每页数量
|
|
85
|
+
success(bool): 操作是否成功
|
|
118
86
|
"""
|
|
119
87
|
params = {
|
|
120
88
|
"page": page,
|
|
@@ -133,33 +101,13 @@ class ProductManagerAsync(AsyncClient):
|
|
|
133
101
|
|
|
134
102
|
async def get_product_detail(self, product_id: str):
|
|
135
103
|
"""
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
功能说明:
|
|
104
|
+
description:
|
|
139
105
|
根据产品ID获取产品的详细信息。
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
data (dict): 产品详细信息
|
|
146
|
-
- id (int): 产品ID
|
|
147
|
-
- name (str): 产品名称
|
|
148
|
-
- description (str): 产品描述
|
|
149
|
-
- price (float): 产品价格(元)
|
|
150
|
-
- category (str): 产品分类
|
|
151
|
-
- status (str): 产品状态
|
|
152
|
-
- is_subscription (bool): 是否为订阅产品
|
|
153
|
-
- subscription_period (str): 订阅周期
|
|
154
|
-
- features (str): 产品特性(JSON格式)
|
|
155
|
-
- sort_order (int): 排序权重
|
|
156
|
-
- created_at (str): 产品创建时间
|
|
157
|
-
- updated_at (str): 产品更新时间
|
|
158
|
-
success (bool): 操作是否成功
|
|
159
|
-
|
|
160
|
-
异常情况:
|
|
161
|
-
- 产品不存在:返回错误信息"产品不存在"
|
|
162
|
-
- 获取产品详情失败:返回错误信息"获取产品详情失败"
|
|
106
|
+
parameters:
|
|
107
|
+
product_id(str): 产品ID
|
|
108
|
+
return:
|
|
109
|
+
data(dict): 产品详细信息
|
|
110
|
+
success(bool): 操作是否成功
|
|
163
111
|
"""
|
|
164
112
|
data, success = await self._request("GET", f"/api/products/{product_id}")
|
|
165
113
|
if not success:
|
|
@@ -180,46 +128,34 @@ class ProductManagerAsync(AsyncClient):
|
|
|
180
128
|
sort_order: int,
|
|
181
129
|
):
|
|
182
130
|
"""
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
功能说明:
|
|
131
|
+
description:
|
|
186
132
|
更新指定产品的信息,包括名称、描述、价格、状态等。
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
-
|
|
211
|
-
-
|
|
212
|
-
|
|
213
|
-
- subscription_period (str): 订阅周期
|
|
214
|
-
- features (str): 产品特性(JSON格式)
|
|
215
|
-
- sort_order (int): 排序权重
|
|
216
|
-
- created_at (str): 产品创建时间
|
|
217
|
-
- updated_at (str): 产品更新时间
|
|
218
|
-
success (bool): 操作是否成功
|
|
219
|
-
|
|
220
|
-
异常情况:
|
|
221
|
-
- 产品不存在:返回错误信息"产品不存在"
|
|
222
|
-
- 产品更新失败:返回错误信息"产品更新失败"
|
|
133
|
+
parameters:
|
|
134
|
+
product_id(str): 产品ID
|
|
135
|
+
name(str): 产品名称
|
|
136
|
+
description(str): 产品描述
|
|
137
|
+
price(float): 产品价格(元)
|
|
138
|
+
category(str): 产品分类
|
|
139
|
+
status(str): 产品状态,可选值:"ACTIVE"(激活)、"INACTIVE"(停用)
|
|
140
|
+
is_subscription(bool): 是否为订阅产品
|
|
141
|
+
subscription_period(str): 订阅周期,可选值:"MONTHLY"(月付)、"YEARLY"(年付)
|
|
142
|
+
features(str): 产品特性,JSON格式字符串
|
|
143
|
+
sort_order(int): 排序权重
|
|
144
|
+
return:
|
|
145
|
+
data(dict): 更新后的产品信息
|
|
146
|
+
- id(int): 产品ID
|
|
147
|
+
- name(str): 产品名称
|
|
148
|
+
- description(str): 产品描述
|
|
149
|
+
- price(float): 产品价格(元)
|
|
150
|
+
- category(str): 产品分类
|
|
151
|
+
- status(str): 产品状态
|
|
152
|
+
- is_subscription(bool): 是否为订阅产品
|
|
153
|
+
- subscription_period(str): 订阅周期
|
|
154
|
+
- features(str): 产品特性(JSON格式)
|
|
155
|
+
- sort_order(int): 排序权重
|
|
156
|
+
- created_at(str): 产品创建时间
|
|
157
|
+
- updated_at(str): 产品更新时间
|
|
158
|
+
success(bool): 操作是否成功
|
|
223
159
|
"""
|
|
224
160
|
data = {
|
|
225
161
|
"name": name,
|
|
@@ -241,21 +177,13 @@ class ProductManagerAsync(AsyncClient):
|
|
|
241
177
|
|
|
242
178
|
async def delete_product(self, product_id: str):
|
|
243
179
|
"""
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
功能说明:
|
|
180
|
+
description:
|
|
247
181
|
根据产品ID删除指定的产品记录。
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
data (None): 删除成功时返回None
|
|
254
|
-
success (bool): 操作是否成功
|
|
255
|
-
|
|
256
|
-
异常情况:
|
|
257
|
-
- 产品不存在:返回错误信息"产品不存在"
|
|
258
|
-
- 删除产品失败:返回错误信息"删除产品失败"
|
|
182
|
+
parameters:
|
|
183
|
+
product_id(str): 产品ID
|
|
184
|
+
return:
|
|
185
|
+
data(None): 删除成功时返回None
|
|
186
|
+
success(bool): 操作是否成功
|
|
259
187
|
"""
|
|
260
188
|
data, success = await self._request("DELETE", f"/api/products/{product_id}")
|
|
261
189
|
if not success:
|
|
@@ -264,21 +192,12 @@ class ProductManagerAsync(AsyncClient):
|
|
|
264
192
|
|
|
265
193
|
async def get_product_categories(self):
|
|
266
194
|
"""
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
功能说明:
|
|
195
|
+
description:
|
|
270
196
|
获取所有可用的产品分类列表。
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
返回字段:
|
|
276
|
-
data (dict): 产品分类信息
|
|
277
|
-
- categories (list): 产品分类列表,如["subscription", "service", "addon"]
|
|
278
|
-
success (bool): 操作是否成功
|
|
279
|
-
|
|
280
|
-
异常情况:
|
|
281
|
-
- 获取产品分类失败:返回错误信息"获取产品分类失败"
|
|
197
|
+
return:
|
|
198
|
+
data(dict): 产品分类信息
|
|
199
|
+
- categories(list): 产品分类列表,如["subscription", "service", "addon"]
|
|
200
|
+
success(bool): 操作是否成功
|
|
282
201
|
"""
|
|
283
202
|
data, success = await self._request("GET", "/api/products/categories/list")
|
|
284
203
|
if not success:
|
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
from .unified_login import
|
|
2
|
-
ThirdPartyAuthUrlRequest,
|
|
3
|
-
ThirdPartyAuthUrlResponse,
|
|
4
|
-
ThirdPartyLoginRequest,
|
|
5
|
-
ThirdPartyLoginResponse,
|
|
6
|
-
UnifiedLoginClientAsync,
|
|
7
|
-
)
|
|
1
|
+
from .unified_login import GoogleLogin, WechatLogin, GitHubLogin
|
|
8
2
|
|
|
9
|
-
__all__ = [
|
|
10
|
-
"ThirdPartyAuthUrlRequest",
|
|
11
|
-
"ThirdPartyAuthUrlResponse",
|
|
12
|
-
"ThirdPartyLoginRequest",
|
|
13
|
-
"ThirdPartyLoginResponse",
|
|
14
|
-
"UnifiedLoginClientAsync",
|
|
15
|
-
]
|
|
3
|
+
__all__ = ["GoogleLogin", "WechatLogin", "GitHubLogin"]
|
|
16
4
|
|
|
@@ -1,18 +1,270 @@
|
|
|
1
|
-
|
|
1
|
+
import asyncio
|
|
2
|
+
import urllib.parse
|
|
3
|
+
import webbrowser
|
|
4
|
+
from typing import Dict, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
from pixelarraythirdparty.client import AsyncClient
|
|
2
7
|
|
|
3
8
|
|
|
4
9
|
class GoogleLogin(AsyncClient):
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
"""
|
|
11
|
+
Google OAuth2 登录客户端
|
|
12
|
+
|
|
13
|
+
使用示例:
|
|
14
|
+
```
|
|
15
|
+
google = GoogleLogin(api_key="your_api_key")
|
|
16
|
+
user_info, success = await google.login()
|
|
17
|
+
```
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, api_key: str):
|
|
21
|
+
super().__init__(api_key)
|
|
22
|
+
|
|
23
|
+
async def _get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
24
|
+
data, success = await self._request(
|
|
25
|
+
"POST", "/api/unified-login/google/auth-url"
|
|
26
|
+
)
|
|
27
|
+
if not success:
|
|
28
|
+
return None, False
|
|
29
|
+
auth_url = data.get("auth_url")
|
|
30
|
+
if not auth_url:
|
|
31
|
+
return None, False
|
|
32
|
+
return data, True
|
|
33
|
+
|
|
34
|
+
async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
35
|
+
"""
|
|
36
|
+
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,
|
|
37
|
+
终端端轮询等待登录结果
|
|
38
|
+
|
|
39
|
+
:param timeout: 等待用户完成授权的超时时间(秒)
|
|
40
|
+
"""
|
|
41
|
+
auth_data, success = await self._get_auth_url()
|
|
42
|
+
if not success or not auth_data:
|
|
43
|
+
return {}, False
|
|
44
|
+
|
|
45
|
+
auth_url = auth_data.get("auth_url")
|
|
46
|
+
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
47
|
+
|
|
48
|
+
if not auth_url or not state:
|
|
49
|
+
return {}, False
|
|
50
|
+
|
|
51
|
+
webbrowser.open(auth_url, new=2)
|
|
52
|
+
|
|
53
|
+
return await self._wait_for_google_login(state, timeout)
|
|
54
|
+
|
|
55
|
+
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
56
|
+
if not auth_url:
|
|
57
|
+
return None
|
|
58
|
+
try:
|
|
59
|
+
parsed = urllib.parse.urlparse(auth_url)
|
|
60
|
+
query = urllib.parse.parse_qs(parsed.query)
|
|
61
|
+
values = query.get("state")
|
|
62
|
+
if values:
|
|
63
|
+
return values[0]
|
|
64
|
+
except Exception:
|
|
65
|
+
return None
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
async def _wait_for_google_login(
|
|
69
|
+
self, state: str, timeout: int
|
|
70
|
+
) -> Tuple[Dict, bool]:
|
|
71
|
+
interval = 2
|
|
72
|
+
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
73
|
+
|
|
74
|
+
for _ in range(total_checks):
|
|
75
|
+
status, response = await self._request_raw(
|
|
76
|
+
"POST",
|
|
77
|
+
"/api/unified-login/google/wait-login",
|
|
78
|
+
json={"state": state},
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if status == 200 and response.get("success") is True:
|
|
82
|
+
return response.get("data", {}), True
|
|
83
|
+
|
|
84
|
+
if status in (400, 408):
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
await asyncio.sleep(interval)
|
|
88
|
+
|
|
89
|
+
return {}, False
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class WechatLogin(AsyncClient):
|
|
93
|
+
"""
|
|
94
|
+
微信 OAuth2 登录客户端
|
|
95
|
+
|
|
96
|
+
使用示例:
|
|
97
|
+
```
|
|
98
|
+
wechat = WechatLogin(api_key="your_api_key")
|
|
99
|
+
user_info, success = await wechat.login()
|
|
100
|
+
```
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def __init__(self, api_key: str):
|
|
104
|
+
super().__init__(api_key)
|
|
105
|
+
|
|
106
|
+
async def _get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
107
|
+
data, success = await self._request(
|
|
108
|
+
"POST", "/api/unified-login/wechat/auth-url"
|
|
109
|
+
)
|
|
110
|
+
if not success:
|
|
111
|
+
return None, False
|
|
112
|
+
auth_url = data.get("auth_url")
|
|
113
|
+
if not auth_url:
|
|
114
|
+
return None, False
|
|
115
|
+
return data, True
|
|
116
|
+
|
|
117
|
+
async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
118
|
+
"""
|
|
119
|
+
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,
|
|
120
|
+
终端端轮询等待登录结果
|
|
121
|
+
|
|
122
|
+
:param timeout: 等待用户完成授权的超时时间(秒)
|
|
123
|
+
"""
|
|
124
|
+
auth_data, success = await self._get_auth_url()
|
|
125
|
+
if not success or not auth_data:
|
|
126
|
+
return {}, False
|
|
127
|
+
|
|
128
|
+
auth_url = auth_data.get("auth_url")
|
|
129
|
+
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
130
|
+
|
|
131
|
+
if not auth_url or not state:
|
|
132
|
+
return {}, False
|
|
133
|
+
|
|
134
|
+
webbrowser.open(auth_url, new=2)
|
|
135
|
+
|
|
136
|
+
return await self._wait_for_wechat_login(state, timeout)
|
|
137
|
+
|
|
138
|
+
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
139
|
+
if not auth_url:
|
|
140
|
+
return None
|
|
141
|
+
try:
|
|
142
|
+
parsed = urllib.parse.urlparse(auth_url)
|
|
143
|
+
query = urllib.parse.parse_qs(parsed.query)
|
|
144
|
+
values = query.get("state")
|
|
145
|
+
if values:
|
|
146
|
+
return values[0]
|
|
147
|
+
except Exception:
|
|
148
|
+
return None
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
async def _wait_for_wechat_login(
|
|
152
|
+
self, state: str, timeout: int
|
|
153
|
+
) -> Tuple[Dict, bool]:
|
|
154
|
+
interval = 2
|
|
155
|
+
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
156
|
+
|
|
157
|
+
for _ in range(total_checks):
|
|
158
|
+
status, response = await self._request_raw(
|
|
159
|
+
"POST",
|
|
160
|
+
"/api/unified-login/wechat/wait-login",
|
|
161
|
+
json={"state": state},
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if status == 200 and response.get("success") is True:
|
|
165
|
+
return response.get("data", {}), True
|
|
166
|
+
|
|
167
|
+
if status in (400, 408):
|
|
168
|
+
break
|
|
169
|
+
|
|
170
|
+
await asyncio.sleep(interval)
|
|
171
|
+
|
|
172
|
+
return {}, False
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class GitHubLogin(AsyncClient):
|
|
176
|
+
"""
|
|
177
|
+
GitHub OAuth2 登录客户端
|
|
178
|
+
|
|
179
|
+
使用示例:
|
|
180
|
+
```
|
|
181
|
+
github = GitHubLogin(api_key="your_api_key")
|
|
182
|
+
user_info, success = await github.login()
|
|
183
|
+
```
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def __init__(self, api_key: str):
|
|
187
|
+
super().__init__(api_key)
|
|
188
|
+
|
|
189
|
+
async def _get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
190
|
+
data, success = await self._request(
|
|
191
|
+
"POST", "/api/unified-login/github/auth-url"
|
|
192
|
+
)
|
|
193
|
+
if not success:
|
|
194
|
+
return None, False
|
|
195
|
+
auth_url = data.get("auth_url")
|
|
196
|
+
if not auth_url:
|
|
197
|
+
return None, False
|
|
198
|
+
return data, True
|
|
199
|
+
|
|
200
|
+
async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
201
|
+
"""
|
|
202
|
+
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,
|
|
203
|
+
终端端轮询等待登录结果
|
|
204
|
+
|
|
205
|
+
:param timeout: 等待用户完成授权的超时时间(秒)
|
|
206
|
+
"""
|
|
207
|
+
auth_data, success = await self._get_auth_url()
|
|
208
|
+
if not success or not auth_data:
|
|
209
|
+
return {}, False
|
|
210
|
+
|
|
211
|
+
auth_url = auth_data.get("auth_url")
|
|
212
|
+
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
213
|
+
|
|
214
|
+
if not auth_url or not state:
|
|
215
|
+
return {}, False
|
|
216
|
+
|
|
217
|
+
webbrowser.open(auth_url, new=2)
|
|
218
|
+
|
|
219
|
+
return await self._wait_for_github_login(state, timeout)
|
|
220
|
+
|
|
221
|
+
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
222
|
+
if not auth_url:
|
|
223
|
+
return None
|
|
224
|
+
try:
|
|
225
|
+
parsed = urllib.parse.urlparse(auth_url)
|
|
226
|
+
query = urllib.parse.parse_qs(parsed.query)
|
|
227
|
+
values = query.get("state")
|
|
228
|
+
if values:
|
|
229
|
+
return values[0]
|
|
230
|
+
except Exception:
|
|
231
|
+
return None
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
async def _wait_for_github_login(
|
|
235
|
+
self, state: str, timeout: int
|
|
236
|
+
) -> Tuple[Dict, bool]:
|
|
237
|
+
interval = 2
|
|
238
|
+
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
239
|
+
|
|
240
|
+
for _ in range(total_checks):
|
|
241
|
+
status, response = await self._request_raw(
|
|
242
|
+
"POST",
|
|
243
|
+
"/api/unified-login/github/wait-login",
|
|
244
|
+
json={"state": state},
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if status == 200 and response.get("success") is True:
|
|
248
|
+
return response.get("data", {}), True
|
|
249
|
+
|
|
250
|
+
if status in (400, 408):
|
|
251
|
+
break
|
|
252
|
+
|
|
253
|
+
await asyncio.sleep(interval)
|
|
254
|
+
|
|
255
|
+
return {}, False
|
|
10
256
|
|
|
11
|
-
async def _get_user_info(self, token: str) -> dict:
|
|
12
|
-
pass
|
|
13
257
|
|
|
14
|
-
|
|
15
|
-
|
|
258
|
+
class UnifiedLogin(AsyncClient):
|
|
259
|
+
def __init__(self, api_key: str):
|
|
260
|
+
super().__init__(api_key)
|
|
16
261
|
|
|
17
|
-
async def
|
|
18
|
-
|
|
262
|
+
async def login(self, provider: str, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
263
|
+
if provider == "google":
|
|
264
|
+
return await GoogleLogin(self.api_key).login(timeout)
|
|
265
|
+
elif provider == "wechat":
|
|
266
|
+
return await WechatLogin(self.api_key).login(timeout)
|
|
267
|
+
elif provider == "github":
|
|
268
|
+
return await GitHubLogin(self.api_key).login(timeout)
|
|
269
|
+
else:
|
|
270
|
+
return {}, False
|