This repository was archived by the owner on Aug 28, 2024. It is now read-only.
forked from Ljzd-PRO/nonebot-plugin-mystool
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuser_data.py
More file actions
354 lines (293 loc) · 11 KB
/
user_data.py
File metadata and controls
354 lines (293 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
"""
### 用户数据相关
"""
from typing import List, Union, Optional, Any, Dict, Set, TYPE_CHECKING, AbstractSet, \
Mapping, Literal
from httpx import Cookies
from pydantic import BaseModel, validator
from .data_model import BaseModelWithSetter, Good, Address, GameRecord, BaseModelWithUpdate
if TYPE_CHECKING:
IntStr = Union[int, str]
DictStrAny = Dict[str, Any]
AbstractSetIntStr = AbstractSet[IntStr]
MappingIntStrAny = Mapping[IntStr, Any]
class BBSCookies(BaseModelWithSetter, BaseModelWithUpdate):
"""
米游社Cookies数据
# 测试 is_correct() 方法
>>> assert BBSCookies().is_correct() is False
>>> assert BBSCookies(stuid="123", stoken="123", cookie_token="123").is_correct() is True
# 测试 bbs_uid getter
>>> bbs_cookies = BBSCookies()
>>> assert not bbs_cookies.bbs_uid
>>> assert BBSCookies(stuid="123").bbs_uid == "123"
# 测试 bbs_uid setter
>>> bbs_cookies.bbs_uid = "123"
>>> assert bbs_cookies.bbs_uid == "123"
# 检查构造函数内所用的 stoken setter
>>> bbs_cookies = BBSCookies(stoken="abcd1234")
>>> assert bbs_cookies.stoken_v1 and not bbs_cookies.stoken_v2
>>> bbs_cookies = BBSCookies(stoken="v2_abcd1234==")
>>> assert bbs_cookies.stoken_v2 and not bbs_cookies.stoken_v1
>>> assert bbs_cookies.stoken == "v2_abcd1234=="
# 检查 stoken setter
>>> bbs_cookies = BBSCookies(stoken="abcd1234")
>>> bbs_cookies.stoken = "v2_abcd1234=="
>>> assert bbs_cookies.stoken_v2 == "v2_abcd1234=="
>>> assert bbs_cookies.stoken_v1 == "abcd1234"
# 检查 .dict 方法能否生成包含 stoken_2 类型的 stoken 的字典
>>> bbs_cookies = BBSCookies()
>>> bbs_cookies.stoken_v1 = "abcd1234"
>>> bbs_cookies.stoken_v2 = "v2_abcd1234=="
>>> assert bbs_cookies.dict(v2_stoken=True)["stoken"] == "v2_abcd1234=="
# 检查是否有多余的字段
>>> bbs_cookies = BBSCookies(stuid="123")
>>> assert all(bbs_cookies.dict())
>>> assert all(map(lambda x: x not in bbs_cookies, ["stoken_v1", "stoken_v2"]))
# 测试 update 方法
>>> bbs_cookies = BBSCookies(stuid="123")
>>> assert bbs_cookies.update({"stuid": "456", "stoken": "abc"}) is bbs_cookies
>>> assert bbs_cookies.stuid == "456"
>>> assert bbs_cookies.stoken == "abc"
>>> bbs_cookies = BBSCookies(stuid="123")
>>> new_cookies = BBSCookies(stuid="456", stoken="abc")
>>> assert bbs_cookies.update(new_cookies) is bbs_cookies
>>> assert bbs_cookies.stuid == "456"
>>> assert bbs_cookies.stoken == "abc"
"""
stuid: Optional[str]
"""米游社UID"""
ltuid: Optional[str]
"""米游社UID"""
account_id: Optional[str]
"""米游社UID"""
login_uid: Optional[str]
"""米游社UID"""
stoken_v1: Optional[str]
"""保存stoken_v1,方便后续使用"""
stoken_v2: Optional[str]
"""保存stoken_v2,方便后续使用"""
cookie_token: Optional[str]
login_ticket: Optional[str]
ltoken: Optional[str]
mid: Optional[str]
def __init__(self, **data: Any):
super().__init__(**data)
stoken = data.get("stoken")
if stoken:
self.stoken = stoken
def is_correct(self) -> bool:
"""判断是否为正确的Cookies"""
if self.bbs_uid and self.stoken and self.cookie_token:
return True
else:
return False
@property
def bbs_uid(self):
"""
获取米游社UID
"""
uid = None
for value in [self.stuid, self.ltuid, self.account_id, self.login_uid]:
if value:
uid = value
break
return uid or None
@bbs_uid.setter
def bbs_uid(self, value: str):
self.stuid = value
self.ltuid = value
self.account_id = value
self.login_uid = value
@property
def stoken(self):
"""
获取stoken
:return: 优先返回 self.stoken_v1
"""
if self.stoken_v1:
return self.stoken_v1
elif self.stoken_v2:
return self.stoken_v2
else:
return None
@stoken.setter
def stoken(self, value):
if value.startswith("v2_"):
self.stoken_v2 = value
else:
self.stoken_v1 = value
def update(self, cookies: Union[Dict[str, str], Cookies, "BBSCookies"]):
"""
更新Cookies
"""
if not isinstance(cookies, BBSCookies):
self.stoken = cookies.get("stoken") or self.stoken
self.bbs_uid = cookies.get("bbs_uid") or self.bbs_uid
cookies.pop("stoken", None)
cookies.pop("bbs_uid", None)
return super().update(cookies)
def dict(self, *,
include: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']] = None,
exclude: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']] = None,
by_alias: bool = False,
skip_defaults: Optional[bool] = None, exclude_unset: bool = False, exclude_defaults: bool = False,
exclude_none: bool = False, v2_stoken: bool = False,
cookie_type: bool = False) -> 'DictStrAny':
"""
获取Cookies字典
v2_stoken: stoken 字段是否使用 stoken_v2
cookie_type: 是否返回符合Cookie类型的字典(没有自定义的stoken_v1、stoken_v2键)
"""
# 保证 stuid, ltuid 等字段存在
self.bbs_uid = self.bbs_uid
cookies_dict = super().dict(include=include, exclude=exclude, by_alias=by_alias, skip_defaults=skip_defaults,
exclude_unset=exclude_unset, exclude_defaults=exclude_defaults,
exclude_none=exclude_none)
if v2_stoken:
cookies_dict["stoken"] = self.stoken_v2
if cookie_type:
# 去除自定义的 stoken_v1, stoken_v2 字段
cookies_dict.pop("stoken_v1")
cookies_dict.pop("stoken_v2")
# 去除空的字段
empty_key = set()
for key, value in cookies_dict.items():
if not value:
empty_key.add(key)
[cookies_dict.pop(key) for key in empty_key]
return cookies_dict
class UserAccount(BaseModelWithSetter):
"""
米游社账户数据
>>> user_account = UserAccount(cookies=BBSCookies())
>>> assert isinstance(user_account, UserAccount)
>>> user_account.bbs_uid = "123"
>>> assert user_account.bbs_uid == "123"
"""
phone_number: Optional[str]
"""手机号"""
cookies: BBSCookies
"""Cookies"""
address: Optional[Address]
"""收货地址"""
device_id_ios: str
"""iOS设备用 deviceID"""
device_id_android: str
"""安卓设备用 deviceID"""
enable_mission: bool = True
'''是否开启米游币任务计划'''
enable_game_sign: bool = True
'''是否开启米游社游戏签到计划'''
enable_resin: bool = True
'''是否开启原神树脂提醒'''
platform: Literal["ios", "android"] = "ios"
'''设备平台'''
mission_games: Set[type] = {}
'''在哪些板块执行米游币任务计划'''
def __init__(self, **data: Any):
if not data.get("device_id_ios") or not data.get("device_id_android"):
from .utils import generate_device_id
if not data.get("device_id_ios"):
data.setdefault("device_id_ios", generate_device_id())
if not data.get("device_id_android"):
data.setdefault("device_id_android", generate_device_id())
from . import myb_missions_api
mission_games_param: Union[List[str], Set[type], None] = data.pop(
"mission_games") if "mission_games" in data else None
super().__init__(**data)
if isinstance(mission_games_param, list):
self.mission_games = set(map(lambda x: getattr(myb_missions_api, x), mission_games_param))
elif isinstance(mission_games_param, set):
self.mission_games = mission_games_param
elif mission_games_param is None:
self.mission_games = {myb_missions_api.BBSMission}
class Config:
json_encoders = {type: lambda v: v.__name__}
@validator("mission_games")
def mission_games_validator(cls, v):
from .myb_missions_api import BaseMission
if not all(issubclass(game, BaseMission) for game in v):
raise ValueError("UserAccount.mission_games 必须是 BaseMission 的子类")
@property
def bbs_uid(self):
"""
获取米游社UID
"""
return self.cookies.bbs_uid
@bbs_uid.setter
def bbs_uid(self, value: str):
self.cookies.bbs_uid = value
class ExchangePlan(BaseModel):
"""
兑换计划数据类
"""
good: Good
"""商品"""
address: Optional[Address]
"""地址ID"""
account: UserAccount
"""米游社账号"""
game_record: Optional[GameRecord]
"""商品对应的游戏的玩家账号"""
def __hash__(self):
return hash(
(
self.good.goods_id,
self.good.time,
self.address.id if self.address else None,
self.account.bbs_uid,
self.game_record.game_role_id if self.game_record else None
)
)
class CustomDict(dict):
_hash: int
def __hash__(self):
return self._hash
def dict(
self,
*,
include: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']] = None,
exclude: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']] = None,
by_alias: bool = False,
skip_defaults: Optional[bool] = None,
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
) -> 'DictStrAny':
"""
重写 dict 方法,使其返回的 dict 可以被 hash
"""
normal_dict = super().dict(include=include, exclude=exclude, by_alias=by_alias, skip_defaults=skip_defaults,
exclude_unset=exclude_unset, exclude_defaults=exclude_defaults,
exclude_none=exclude_none)
hashable_dict = ExchangePlan.CustomDict(normal_dict)
hashable_dict._hash = hash(self)
return hashable_dict
class ExchangeResult(BaseModel):
"""
兑换结果数据类
"""
result: bool
"""兑换结果"""
return_data: dict
"""返回数据"""
plan: ExchangePlan
"""兑换计划"""
class UserData(BaseModelWithSetter):
"""
用户数据类
"""
exchange_plans: Union[Set[ExchangePlan], List[ExchangePlan]] = set()
"""兑换计划列表"""
accounts: Dict[str, UserAccount] = {}
"""储存一些已绑定的账号数据"""
enable_notice: bool = True
"""是否开启通知"""
def __init__(self, **data: Any):
super().__init__(**data)
exchange_plans = self.exchange_plans
self.exchange_plans = set()
for plan in exchange_plans:
plan = ExchangePlan.parse_obj(plan)
self.exchange_plans.add(plan)