datadid-sdk-python 1.0.0__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.
- datadid_sdk_python-1.0.0.dist-info/METADATA +479 -0
- datadid_sdk_python-1.0.0.dist-info/RECORD +12 -0
- datadid_sdk_python-1.0.0.dist-info/WHEEL +5 -0
- datadid_sdk_python-1.0.0.dist-info/top_level.txt +1 -0
- src/__init__.py +50 -0
- src/data/__init__.py +0 -0
- src/data/client.py +473 -0
- src/data/types.py +103 -0
- src/did/__init__.py +0 -0
- src/did/client.py +225 -0
- src/did/types.py +49 -0
- src/errors.py +15 -0
src/data/client.py
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from ..errors import DataDIDApiError
|
|
8
|
+
from .types import (
|
|
9
|
+
ActionRecord,
|
|
10
|
+
AuthMeResponse,
|
|
11
|
+
AuthTokens,
|
|
12
|
+
DataClientOptions,
|
|
13
|
+
DiscordInfo,
|
|
14
|
+
EvmLoginResult,
|
|
15
|
+
PiInfo,
|
|
16
|
+
TelegramInfo,
|
|
17
|
+
TwitterInfo,
|
|
18
|
+
UserInfo,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
_PRODUCTION_URL = "https://data-be.metamemo.one"
|
|
22
|
+
_TEST_URL = "https://test-data-be-v2.memolabs.net"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DataClient:
|
|
26
|
+
"""
|
|
27
|
+
Async REST client for the DataDID platform API (data-be).
|
|
28
|
+
|
|
29
|
+
Wraps authentication, user info, and action record endpoints.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, options: DataClientOptions) -> None:
|
|
33
|
+
self._base_url: str = options.base_url.rstrip("/")
|
|
34
|
+
self._access_token: Optional[str] = None
|
|
35
|
+
self._disable_auto_token: bool = options.disable_auto_token
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def production(cls) -> DataClient:
|
|
39
|
+
"""Create a client pointing to production (https://data-be.metamemo.one)."""
|
|
40
|
+
return cls(DataClientOptions(base_url=_PRODUCTION_URL))
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def testnet(cls) -> DataClient:
|
|
44
|
+
"""Create a client pointing to the test server (https://test-data-be-v2.memolabs.net)."""
|
|
45
|
+
return cls(DataClientOptions(base_url=_TEST_URL))
|
|
46
|
+
|
|
47
|
+
def set_access_token(self, token: str) -> None:
|
|
48
|
+
"""Manually set the access token for authenticated requests."""
|
|
49
|
+
self._access_token = token
|
|
50
|
+
|
|
51
|
+
def get_access_token(self) -> Optional[str]:
|
|
52
|
+
"""Return the currently stored access token, or None."""
|
|
53
|
+
return self._access_token
|
|
54
|
+
|
|
55
|
+
# ── Auth endpoints ───────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
async def send_email_code(self, email: str) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Send a login verification code to the given email.
|
|
60
|
+
|
|
61
|
+
POST /v2/login/email/code
|
|
62
|
+
"""
|
|
63
|
+
await self._request("POST", "/v2/login/email/code", {"email": email})
|
|
64
|
+
|
|
65
|
+
async def login_with_email(
|
|
66
|
+
self,
|
|
67
|
+
email: str,
|
|
68
|
+
code: str,
|
|
69
|
+
source: str,
|
|
70
|
+
useragent: Optional[str] = None,
|
|
71
|
+
) -> AuthTokens:
|
|
72
|
+
"""
|
|
73
|
+
Log in with email and verification code.
|
|
74
|
+
|
|
75
|
+
POST /v2/login/email
|
|
76
|
+
"""
|
|
77
|
+
body: dict[str, Any] = {"email": email, "code": code, "source": source}
|
|
78
|
+
if useragent is not None:
|
|
79
|
+
body["useragent"] = useragent
|
|
80
|
+
data = await self._request("POST", "/v2/login/email", body)
|
|
81
|
+
tokens = self._extract_tokens(data)
|
|
82
|
+
self._auto_set_token(tokens.access_token)
|
|
83
|
+
return tokens
|
|
84
|
+
|
|
85
|
+
async def register_with_email(
|
|
86
|
+
self,
|
|
87
|
+
email: str,
|
|
88
|
+
code: str,
|
|
89
|
+
password: str,
|
|
90
|
+
source: str,
|
|
91
|
+
useragent: Optional[str] = None,
|
|
92
|
+
) -> AuthTokens:
|
|
93
|
+
"""
|
|
94
|
+
Register a new user with email, verification code, and password.
|
|
95
|
+
|
|
96
|
+
POST /v2/login/email/register
|
|
97
|
+
"""
|
|
98
|
+
body: dict[str, Any] = {
|
|
99
|
+
"email": email,
|
|
100
|
+
"code": code,
|
|
101
|
+
"password": password,
|
|
102
|
+
"source": source,
|
|
103
|
+
}
|
|
104
|
+
if useragent is not None:
|
|
105
|
+
body["useragent"] = useragent
|
|
106
|
+
data = await self._request("POST", "/v2/login/email/register", body)
|
|
107
|
+
tokens = self._extract_tokens(data)
|
|
108
|
+
self._auto_set_token(tokens.access_token)
|
|
109
|
+
return tokens
|
|
110
|
+
|
|
111
|
+
async def login_with_email_password(self, email: str, password: str) -> AuthTokens:
|
|
112
|
+
"""
|
|
113
|
+
Log in with email and password.
|
|
114
|
+
|
|
115
|
+
POST /v2/login/email/password
|
|
116
|
+
"""
|
|
117
|
+
data = await self._request(
|
|
118
|
+
"POST",
|
|
119
|
+
"/v2/login/email/password",
|
|
120
|
+
{"email": email, "password": password},
|
|
121
|
+
)
|
|
122
|
+
tokens = self._extract_tokens(data)
|
|
123
|
+
self._auto_set_token(tokens.access_token)
|
|
124
|
+
return tokens
|
|
125
|
+
|
|
126
|
+
async def reset_password(self, email: str, code: str, new_password: str) -> None:
|
|
127
|
+
"""
|
|
128
|
+
Reset password with email and verification code.
|
|
129
|
+
|
|
130
|
+
POST /v2/login/email/password/reset
|
|
131
|
+
"""
|
|
132
|
+
await self._request(
|
|
133
|
+
"POST",
|
|
134
|
+
"/v2/login/email/password/reset",
|
|
135
|
+
{"email": email, "code": code, "new_password": new_password},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
async def login_with_telegram(
|
|
139
|
+
self,
|
|
140
|
+
initdata: str,
|
|
141
|
+
source: str,
|
|
142
|
+
useragent: Optional[str] = None,
|
|
143
|
+
) -> AuthTokens:
|
|
144
|
+
"""
|
|
145
|
+
Log in with Telegram init data.
|
|
146
|
+
|
|
147
|
+
POST /v2/login/telegram
|
|
148
|
+
"""
|
|
149
|
+
body: dict[str, Any] = {"initdata": initdata, "source": source}
|
|
150
|
+
if useragent is not None:
|
|
151
|
+
body["useragent"] = useragent
|
|
152
|
+
data = await self._request("POST", "/v2/login/telegram", body)
|
|
153
|
+
tokens = self._extract_tokens(data)
|
|
154
|
+
self._auto_set_token(tokens.access_token)
|
|
155
|
+
return tokens
|
|
156
|
+
|
|
157
|
+
async def login_with_pi(
|
|
158
|
+
self,
|
|
159
|
+
pi_access_token: str,
|
|
160
|
+
source: str,
|
|
161
|
+
useragent: Optional[str] = None,
|
|
162
|
+
) -> AuthTokens:
|
|
163
|
+
"""
|
|
164
|
+
Log in with Pi browser access token.
|
|
165
|
+
|
|
166
|
+
POST /v2/login/pi
|
|
167
|
+
"""
|
|
168
|
+
body: dict[str, Any] = {"source": source}
|
|
169
|
+
if useragent is not None:
|
|
170
|
+
body["useragent"] = useragent
|
|
171
|
+
data = await self._request(
|
|
172
|
+
"POST",
|
|
173
|
+
"/v2/login/pi",
|
|
174
|
+
body,
|
|
175
|
+
extra_headers={"Authorization": pi_access_token},
|
|
176
|
+
)
|
|
177
|
+
tokens = self._extract_tokens(data)
|
|
178
|
+
self._auto_set_token(tokens.access_token)
|
|
179
|
+
return tokens
|
|
180
|
+
|
|
181
|
+
async def get_evm_challenge(
|
|
182
|
+
self,
|
|
183
|
+
address: str,
|
|
184
|
+
chain_id: Optional[int] = None,
|
|
185
|
+
origin: Optional[str] = None,
|
|
186
|
+
) -> str:
|
|
187
|
+
"""
|
|
188
|
+
Get the sign-in challenge message for EVM wallet login.
|
|
189
|
+
|
|
190
|
+
GET /v2/login/evm/challenge
|
|
191
|
+
"""
|
|
192
|
+
params: dict[str, str] = {"address": address}
|
|
193
|
+
if chain_id is not None:
|
|
194
|
+
params["chainid"] = str(chain_id)
|
|
195
|
+
query = "&".join(f"{k}={v}" for k, v in params.items())
|
|
196
|
+
headers: dict[str, str] = {}
|
|
197
|
+
if origin is not None:
|
|
198
|
+
headers["Origin"] = origin
|
|
199
|
+
data = await self._request(
|
|
200
|
+
"GET",
|
|
201
|
+
f"/v2/login/evm/challenge?{query}",
|
|
202
|
+
extra_headers=headers or None,
|
|
203
|
+
)
|
|
204
|
+
return str(data)
|
|
205
|
+
|
|
206
|
+
async def login_with_evm(
|
|
207
|
+
self,
|
|
208
|
+
message: str,
|
|
209
|
+
signature: str,
|
|
210
|
+
source: str,
|
|
211
|
+
useragent: Optional[str] = None,
|
|
212
|
+
) -> EvmLoginResult:
|
|
213
|
+
"""
|
|
214
|
+
Log in with EVM wallet signature.
|
|
215
|
+
|
|
216
|
+
POST /v2/login/evm
|
|
217
|
+
"""
|
|
218
|
+
body: dict[str, Any] = {
|
|
219
|
+
"message": message,
|
|
220
|
+
"signature": signature,
|
|
221
|
+
"source": source,
|
|
222
|
+
}
|
|
223
|
+
if useragent is not None:
|
|
224
|
+
body["useragent"] = useragent
|
|
225
|
+
data = await self._request("POST", "/v2/login/evm", body)
|
|
226
|
+
result = EvmLoginResult(
|
|
227
|
+
access_token=(
|
|
228
|
+
data.get("access_token")
|
|
229
|
+
or data.get("accessToken")
|
|
230
|
+
or data.get("AccessToken")
|
|
231
|
+
or ""
|
|
232
|
+
),
|
|
233
|
+
refresh_token=(
|
|
234
|
+
data.get("refresh_token")
|
|
235
|
+
or data.get("refreshToken")
|
|
236
|
+
or data.get("RefreshToken")
|
|
237
|
+
or ""
|
|
238
|
+
),
|
|
239
|
+
did=data.get("did", ""),
|
|
240
|
+
number=data.get("number", ""),
|
|
241
|
+
)
|
|
242
|
+
self._auto_set_token(result.access_token)
|
|
243
|
+
return result
|
|
244
|
+
|
|
245
|
+
async def refresh_token(self, refresh_token: str) -> str:
|
|
246
|
+
"""
|
|
247
|
+
Get a new access token using the refresh token.
|
|
248
|
+
|
|
249
|
+
POST /v2/login/refresh
|
|
250
|
+
"""
|
|
251
|
+
data = await self._request(
|
|
252
|
+
"POST",
|
|
253
|
+
"/v2/login/refresh",
|
|
254
|
+
extra_headers={"Authorization": refresh_token},
|
|
255
|
+
)
|
|
256
|
+
new_token = (
|
|
257
|
+
data.get("access_token")
|
|
258
|
+
or data.get("accessToken")
|
|
259
|
+
or data.get("AccessToken")
|
|
260
|
+
or ""
|
|
261
|
+
)
|
|
262
|
+
self._auto_set_token(new_token)
|
|
263
|
+
return new_token
|
|
264
|
+
|
|
265
|
+
# ── User endpoints ───────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
async def get_me(self) -> AuthMeResponse:
|
|
268
|
+
"""
|
|
269
|
+
Validate access token and get current user's basic info.
|
|
270
|
+
|
|
271
|
+
GET /v2/auth/me
|
|
272
|
+
"""
|
|
273
|
+
data = await self._authenticated_request("GET", "/v2/auth/me")
|
|
274
|
+
return AuthMeResponse(
|
|
275
|
+
uid=data.get("uid", ""),
|
|
276
|
+
email=data.get("email", ""),
|
|
277
|
+
username=data.get("username", ""),
|
|
278
|
+
role=data.get("role", ""),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
async def get_user_info(self) -> UserInfo:
|
|
282
|
+
"""
|
|
283
|
+
Get the current logged-in user's full info.
|
|
284
|
+
|
|
285
|
+
GET /v2/user/info
|
|
286
|
+
"""
|
|
287
|
+
data = await self._authenticated_request("GET", "/v2/user/info")
|
|
288
|
+
|
|
289
|
+
pi_raw = data.get("pi_info")
|
|
290
|
+
pi_info = (
|
|
291
|
+
PiInfo(
|
|
292
|
+
pi_id=pi_raw.get("pi_id", ""),
|
|
293
|
+
pi_user_name=pi_raw.get("pi_user_name", ""),
|
|
294
|
+
pi_address=pi_raw.get("pi_address", ""),
|
|
295
|
+
)
|
|
296
|
+
if pi_raw
|
|
297
|
+
else None
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
tw_raw = data.get("twitter_info")
|
|
301
|
+
twitter_info = (
|
|
302
|
+
TwitterInfo(
|
|
303
|
+
twitter_id=tw_raw.get("twitter_id", ""),
|
|
304
|
+
twitter_name=tw_raw.get("twitter_name", ""),
|
|
305
|
+
twitter_user_name=tw_raw.get("twitter_user_name", ""),
|
|
306
|
+
)
|
|
307
|
+
if tw_raw
|
|
308
|
+
else None
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
tg_raw = data.get("telegram_info")
|
|
312
|
+
telegram_info = (
|
|
313
|
+
TelegramInfo(
|
|
314
|
+
telegram_id=tg_raw.get("telegram_id", 0),
|
|
315
|
+
telegram_first_name=tg_raw.get("telegram_first_name", ""),
|
|
316
|
+
telegram_last_name=tg_raw.get("telegram_last_name", ""),
|
|
317
|
+
telegram_user_name=tg_raw.get("telegram_user_name", ""),
|
|
318
|
+
telegram_photo=tg_raw.get("telegram_photo", ""),
|
|
319
|
+
)
|
|
320
|
+
if tg_raw
|
|
321
|
+
else None
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
dc_raw = data.get("discord_info")
|
|
325
|
+
discord_info = (
|
|
326
|
+
DiscordInfo(
|
|
327
|
+
discord_id=dc_raw.get("discord_id", ""),
|
|
328
|
+
discord_name=dc_raw.get("discord_name", ""),
|
|
329
|
+
discord_user_name=dc_raw.get("discord_user_name", ""),
|
|
330
|
+
discord_email=dc_raw.get("discord_email", ""),
|
|
331
|
+
)
|
|
332
|
+
if dc_raw
|
|
333
|
+
else None
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
return UserInfo(
|
|
337
|
+
name=data.get("name", ""),
|
|
338
|
+
icon=data.get("icon", ""),
|
|
339
|
+
address=data.get("address", ""),
|
|
340
|
+
did=data.get("did", ""),
|
|
341
|
+
email=data.get("email"),
|
|
342
|
+
pi_info=pi_info,
|
|
343
|
+
twitter_info=twitter_info,
|
|
344
|
+
telegram_info=telegram_info,
|
|
345
|
+
discord_info=discord_info,
|
|
346
|
+
wechat_info=data.get("wechat_info"),
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# ── Action record endpoints ───────────────────────────────────
|
|
350
|
+
|
|
351
|
+
async def get_action_record(self, action_id: int) -> Optional[ActionRecord]:
|
|
352
|
+
"""
|
|
353
|
+
Get the current user's completion record for the given action ID.
|
|
354
|
+
Returns None if no record exists.
|
|
355
|
+
|
|
356
|
+
GET /v2/data/record/:actionID
|
|
357
|
+
"""
|
|
358
|
+
data = await self._authenticated_request("GET", f"/v2/data/record/{action_id}")
|
|
359
|
+
if not data:
|
|
360
|
+
return None
|
|
361
|
+
return ActionRecord(
|
|
362
|
+
action=data.get("action", 0),
|
|
363
|
+
points=data.get("points", 0),
|
|
364
|
+
time=data.get("time", 0),
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
async def add_action_record(self, action_id: int, opts: Any = None) -> None:
|
|
368
|
+
"""
|
|
369
|
+
Add one action completion record for the current user.
|
|
370
|
+
|
|
371
|
+
POST /v2/data/record/add
|
|
372
|
+
"""
|
|
373
|
+
body: dict[str, Any] = {"actionid": action_id}
|
|
374
|
+
if opts is not None:
|
|
375
|
+
body["opts"] = opts
|
|
376
|
+
await self._authenticated_request("POST", "/v2/data/record/add", body)
|
|
377
|
+
|
|
378
|
+
# ── Internal helpers ─────────────────────────────────────────
|
|
379
|
+
|
|
380
|
+
def _auto_set_token(self, token: str) -> None:
|
|
381
|
+
if not self._disable_auto_token and token:
|
|
382
|
+
self._access_token = token
|
|
383
|
+
|
|
384
|
+
@staticmethod
|
|
385
|
+
def _extract_tokens(data: Any) -> AuthTokens:
|
|
386
|
+
return AuthTokens(
|
|
387
|
+
access_token=(
|
|
388
|
+
data.get("access_token")
|
|
389
|
+
or data.get("accessToken")
|
|
390
|
+
or data.get("AccessToken")
|
|
391
|
+
or ""
|
|
392
|
+
),
|
|
393
|
+
refresh_token=(
|
|
394
|
+
data.get("refresh_token")
|
|
395
|
+
or data.get("refreshToken")
|
|
396
|
+
or data.get("RefreshToken")
|
|
397
|
+
or ""
|
|
398
|
+
),
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
async def _authenticated_request(
|
|
402
|
+
self,
|
|
403
|
+
method: str,
|
|
404
|
+
path: str,
|
|
405
|
+
body: Optional[dict[str, Any]] = None,
|
|
406
|
+
) -> Any:
|
|
407
|
+
if not self._access_token:
|
|
408
|
+
raise RuntimeError(
|
|
409
|
+
"No access token set. "
|
|
410
|
+
"Call a login method first, or use set_access_token()."
|
|
411
|
+
)
|
|
412
|
+
return await self._request(
|
|
413
|
+
method,
|
|
414
|
+
path,
|
|
415
|
+
body,
|
|
416
|
+
extra_headers={"Authorization": f"Bearer {self._access_token}"},
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
async def _request(
|
|
420
|
+
self,
|
|
421
|
+
method: str,
|
|
422
|
+
path: str,
|
|
423
|
+
body: Optional[dict[str, Any]] = None,
|
|
424
|
+
extra_headers: Optional[dict[str, str]] = None,
|
|
425
|
+
) -> Any:
|
|
426
|
+
url = f"{self._base_url}{path}"
|
|
427
|
+
headers: dict[str, str] = dict(extra_headers or {})
|
|
428
|
+
if body is not None:
|
|
429
|
+
headers["Content-Type"] = "application/json"
|
|
430
|
+
|
|
431
|
+
async with httpx.AsyncClient() as http:
|
|
432
|
+
response = await http.request(method, url, headers=headers, json=body)
|
|
433
|
+
|
|
434
|
+
try:
|
|
435
|
+
json_body = response.json()
|
|
436
|
+
except Exception:
|
|
437
|
+
if not response.is_success:
|
|
438
|
+
raise DataDIDApiError(
|
|
439
|
+
f"HTTP {response.status_code} {response.reason_phrase}",
|
|
440
|
+
response.status_code,
|
|
441
|
+
)
|
|
442
|
+
return None
|
|
443
|
+
|
|
444
|
+
# Two response formats:
|
|
445
|
+
# 1. { "result": 1, "data": ... } — most endpoints
|
|
446
|
+
# 2. { "success": true, "data": ... } — /v2/auth/me
|
|
447
|
+
if json_body.get("success") is False:
|
|
448
|
+
raise DataDIDApiError(
|
|
449
|
+
json_body.get("error") or "Request failed",
|
|
450
|
+
response.status_code,
|
|
451
|
+
json_body,
|
|
452
|
+
)
|
|
453
|
+
result_val = json_body.get("result")
|
|
454
|
+
if result_val is not None and result_val != 1:
|
|
455
|
+
raise DataDIDApiError(
|
|
456
|
+
json_body.get("message") or f"API returned result={result_val}",
|
|
457
|
+
response.status_code,
|
|
458
|
+
json_body,
|
|
459
|
+
)
|
|
460
|
+
if (
|
|
461
|
+
not response.is_success
|
|
462
|
+
and result_val is None
|
|
463
|
+
and json_body.get("success") is None
|
|
464
|
+
):
|
|
465
|
+
raise DataDIDApiError(
|
|
466
|
+
json_body.get("message")
|
|
467
|
+
or json_body.get("error")
|
|
468
|
+
or f"HTTP {response.status_code}",
|
|
469
|
+
response.status_code,
|
|
470
|
+
json_body,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
return json_body.get("data")
|
src/data/types.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class DataClientOptions:
|
|
7
|
+
"""Options for creating a DataClient."""
|
|
8
|
+
|
|
9
|
+
base_url: str
|
|
10
|
+
"""Base URL of the Data-BE server."""
|
|
11
|
+
|
|
12
|
+
disable_auto_token: bool = False
|
|
13
|
+
"""If True, login methods will NOT auto-set the access token on the client."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class AuthTokens:
|
|
18
|
+
"""Tokens returned by login endpoints."""
|
|
19
|
+
|
|
20
|
+
access_token: str
|
|
21
|
+
refresh_token: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class EvmLoginResult(AuthTokens):
|
|
26
|
+
"""EVM login returns tokens + DID info."""
|
|
27
|
+
|
|
28
|
+
did: str = ""
|
|
29
|
+
number: str = ""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class AuthMeResponse:
|
|
34
|
+
"""User info from GET /v2/auth/me."""
|
|
35
|
+
|
|
36
|
+
uid: str
|
|
37
|
+
email: str
|
|
38
|
+
username: str
|
|
39
|
+
role: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class PiInfo:
|
|
44
|
+
"""Pi account info."""
|
|
45
|
+
|
|
46
|
+
pi_id: str
|
|
47
|
+
pi_user_name: str
|
|
48
|
+
pi_address: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class TwitterInfo:
|
|
53
|
+
"""Twitter account info."""
|
|
54
|
+
|
|
55
|
+
twitter_id: str
|
|
56
|
+
twitter_name: str
|
|
57
|
+
twitter_user_name: str
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class TelegramInfo:
|
|
62
|
+
"""Telegram account info."""
|
|
63
|
+
|
|
64
|
+
telegram_id: int
|
|
65
|
+
telegram_first_name: str
|
|
66
|
+
telegram_last_name: str
|
|
67
|
+
telegram_user_name: str
|
|
68
|
+
telegram_photo: str
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class DiscordInfo:
|
|
73
|
+
"""Discord account info."""
|
|
74
|
+
|
|
75
|
+
discord_id: str
|
|
76
|
+
discord_name: str
|
|
77
|
+
discord_user_name: str
|
|
78
|
+
discord_email: str
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class UserInfo:
|
|
83
|
+
"""Full user info from GET /v2/user/info."""
|
|
84
|
+
|
|
85
|
+
name: str
|
|
86
|
+
icon: str
|
|
87
|
+
address: str
|
|
88
|
+
did: str
|
|
89
|
+
email: Optional[str] = None
|
|
90
|
+
pi_info: Optional[PiInfo] = None
|
|
91
|
+
twitter_info: Optional[TwitterInfo] = None
|
|
92
|
+
telegram_info: Optional[TelegramInfo] = None
|
|
93
|
+
discord_info: Optional[DiscordInfo] = None
|
|
94
|
+
wechat_info: Optional[bool] = None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class ActionRecord:
|
|
99
|
+
"""Action record from GET /v2/data/record/:actionID."""
|
|
100
|
+
|
|
101
|
+
action: int
|
|
102
|
+
points: int
|
|
103
|
+
time: int
|
src/did/__init__.py
ADDED
|
File without changes
|