aioqzone 1.8.2.dev3__tar.gz → 1.8.4.dev3__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.
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/PKG-INFO +1 -1
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/pyproject.toml +1 -1
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/model/api/request.py +6 -3
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/model/api/response.py +21 -16
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/model/protocol/config.py +2 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/utils/regex.py +1 -1
- aioqzone-1.8.4.dev3/src/qqqr/__init__.py +4 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/qr/__init__.py +50 -8
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/up/web.py +11 -11
- aioqzone-1.8.2.dev3/src/qqqr/__init__.py +0 -4
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/LICENSE +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/README.md +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/__init__.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/api/__init__.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/api/h5/__init__.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/api/h5/model.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/api/login/__init__.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/api/login/_base.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/exception.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/message.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/model/__init__.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/model/api/__init__.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/model/api/feed.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/model/api/profile.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/model/protocol/__init__.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/model/protocol/entity.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/utils/__init__.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/utils/entity.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/utils/retry.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/aioqzone/utils/time.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/base.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/constant.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/exception.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/message.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/py.typed +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/qr/type.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/type.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/up/__init__.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/up/_model.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/up/captcha/__init__.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/up/captcha/_model.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/up/captcha/capsess.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/up/captcha/pil_utils.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/up/captcha/select/__init__.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/up/captcha/select/_types.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/up/captcha/slide/__init__.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/up/captcha/slide/_types.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/up/encrypt.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/up/h5.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/utils/encrypt.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/utils/iter.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/utils/jsjson.py +0 -0
- {aioqzone-1.8.2.dev3 → aioqzone-1.8.4.dev3}/src/qqqr/utils/net.py +0 -0
|
@@ -3,8 +3,7 @@ from base64 import b64encode
|
|
|
3
3
|
from math import floor
|
|
4
4
|
from time import time
|
|
5
5
|
|
|
6
|
-
from pydantic import
|
|
7
|
-
from typing_extensions import Annotated
|
|
6
|
+
from pydantic import BaseModel, Field, field_serializer
|
|
8
7
|
|
|
9
8
|
from aioqzone.utils.time import time_ms
|
|
10
9
|
|
|
@@ -164,7 +163,7 @@ class DeleteUgcParams(QzoneRequestParams):
|
|
|
164
163
|
|
|
165
164
|
class UploadPicParams(QzoneRequestParams):
|
|
166
165
|
uin_fields = ("uin",)
|
|
167
|
-
picture:
|
|
166
|
+
picture: bytes
|
|
168
167
|
hd_height: int
|
|
169
168
|
hd_width: int
|
|
170
169
|
hd_quality: int = 70
|
|
@@ -180,6 +179,10 @@ class UploadPicParams(QzoneRequestParams):
|
|
|
180
179
|
Exif_CameraModel: str = ""
|
|
181
180
|
Exif_Time: str = ""
|
|
182
181
|
|
|
182
|
+
@field_serializer("picture", return_type=str)
|
|
183
|
+
def b64_picture(self, picture: t.ByteString) -> str:
|
|
184
|
+
return b64encode(picture).decode()
|
|
185
|
+
|
|
183
186
|
|
|
184
187
|
class PhotosPreuploadParams(QzoneRequestParams):
|
|
185
188
|
uin_fields = ("uin",)
|
|
@@ -39,7 +39,7 @@ __all__ = [
|
|
|
39
39
|
|
|
40
40
|
class QzoneResponse(BaseModel):
|
|
41
41
|
_errno_key: t.ClassVar[t.Union[str, AliasPath, AliasChoices, None]] = AliasChoices(
|
|
42
|
-
"code", "ret", "err"
|
|
42
|
+
"code", "ret", "err", "error"
|
|
43
43
|
)
|
|
44
44
|
_msg_key: t.ClassVar[t.Union[str, AliasPath, AliasChoices, None]] = AliasChoices(
|
|
45
45
|
"message", "msg"
|
|
@@ -55,18 +55,17 @@ class QzoneResponse(BaseModel):
|
|
|
55
55
|
|
|
56
56
|
:return: Self
|
|
57
57
|
"""
|
|
58
|
-
if cls._errno_key and cls._msg_key:
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
class response_header(BaseModel):
|
|
60
|
+
status: int = Field(default=0, validation_alias=cls._errno_key)
|
|
61
|
+
message: str = Field(default="", validation_alias=cls._msg_key)
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
header = response_header.model_validate(obj)
|
|
64
|
+
if header.status != 0:
|
|
65
|
+
if header.message:
|
|
66
|
+
raise QzoneError(header.status, header.message, robj=obj)
|
|
67
|
+
else:
|
|
68
|
+
raise QzoneError(header.status, robj=obj)
|
|
70
69
|
|
|
71
70
|
if cls._data_key is None:
|
|
72
71
|
return cls.model_validate(obj)
|
|
@@ -208,7 +207,7 @@ class ProfilePagePesp(QzoneResponse):
|
|
|
208
207
|
return cls(
|
|
209
208
|
info=QzoneInfo.from_response_object(obj["info"]), # type: ignore
|
|
210
209
|
feedpage=ProfileResp.from_response_object(obj["feedpage"]), # type: ignore
|
|
211
|
-
qzonetoken=obj["qzonetoken"],
|
|
210
|
+
qzonetoken=obj["qzonetoken"], # type: ignore
|
|
212
211
|
)
|
|
213
212
|
|
|
214
213
|
|
|
@@ -239,7 +238,8 @@ class DeleteUgcResp(QzoneResponse):
|
|
|
239
238
|
|
|
240
239
|
|
|
241
240
|
class UploadPicResponse(QzoneResponse):
|
|
242
|
-
|
|
241
|
+
_data_key = None
|
|
242
|
+
|
|
243
243
|
filelen: int
|
|
244
244
|
filemd5: str
|
|
245
245
|
|
|
@@ -250,7 +250,9 @@ class UploadPicResponse(QzoneResponse):
|
|
|
250
250
|
return json_loads(m.group(1))
|
|
251
251
|
|
|
252
252
|
|
|
253
|
-
class PicInfo(
|
|
253
|
+
class PicInfo(QzoneResponse):
|
|
254
|
+
_data_key = None
|
|
255
|
+
|
|
254
256
|
pre: HttpUrl
|
|
255
257
|
url: HttpUrl
|
|
256
258
|
sloc: str
|
|
@@ -261,11 +263,14 @@ class PicInfo(BaseModel):
|
|
|
261
263
|
|
|
262
264
|
|
|
263
265
|
class PhotosPreuploadResponse(QzoneResponse):
|
|
264
|
-
|
|
266
|
+
_data_key = None
|
|
265
267
|
photos: t.List[PicInfo] = Field(default_factory=list)
|
|
266
268
|
|
|
267
269
|
@classmethod
|
|
268
270
|
async def response_to_object(cls, response: ClientResponse):
|
|
269
271
|
m = response_callback.search(await response.text())
|
|
270
272
|
assert m
|
|
271
|
-
|
|
273
|
+
|
|
274
|
+
picinfos = json_loads(m.group(1))
|
|
275
|
+
assert isinstance(picinfos, list)
|
|
276
|
+
return dict(photos=[PicInfo.from_response_object(info["picinfo"]) for info in picinfos])
|
|
@@ -5,7 +5,7 @@ some patterns for matching html and so on.
|
|
|
5
5
|
import re
|
|
6
6
|
|
|
7
7
|
# use this to match qzone api response
|
|
8
|
-
response_callback = re.compile(r"callback\(\s*(\{
|
|
8
|
+
response_callback = re.compile(r"callback\(\s*([\{\[].*[\}\]])\s*\)", re.S | re.I)
|
|
9
9
|
# use this to get unikey & curkey of a html
|
|
10
10
|
uni_cur_key = re.compile(r'data-unikey="([^"]*)"[^d]*data-curkey="([^"]*)"')
|
|
11
11
|
|
|
@@ -28,13 +28,17 @@ PTLOGIN2 = URL("https://ptlogin2.qq.com")
|
|
|
28
28
|
|
|
29
29
|
@dataclass(unsafe_hash=True)
|
|
30
30
|
class QR:
|
|
31
|
+
"""Class :class:`QR` represents a QR code."""
|
|
32
|
+
|
|
31
33
|
png: t.Optional[bytes]
|
|
32
|
-
"""If None, the QR is pushed to user's client."""
|
|
34
|
+
"""QR code content. If None, the QR is pushed to user's client."""
|
|
33
35
|
sig: str
|
|
34
36
|
expired: bool = False
|
|
37
|
+
"""Whether the QR code is expired."""
|
|
35
38
|
|
|
36
39
|
@property
|
|
37
40
|
def pushed(self):
|
|
41
|
+
"""Whether the QR code is pushed to user's client."""
|
|
38
42
|
return self.png is None
|
|
39
43
|
|
|
40
44
|
|
|
@@ -49,9 +53,12 @@ class QrSession(LoginSession):
|
|
|
49
53
|
) -> None:
|
|
50
54
|
super().__init__(login_sig=login_sig, create_time=create_time)
|
|
51
55
|
self.refreshed = refresh_times
|
|
56
|
+
"""QR code refresh times counter."""
|
|
52
57
|
self.current_qr = first_qr
|
|
58
|
+
"""A :class:`QrSession` keeps a :class:`QR` object as current QR code."""
|
|
53
59
|
|
|
54
60
|
def new_qr(self, qr: QR):
|
|
61
|
+
"""Add a new QR code to this session."""
|
|
55
62
|
self.current_qr.expired = True
|
|
56
63
|
self.current_qr = qr
|
|
57
64
|
self.refreshed += 1
|
|
@@ -61,14 +68,36 @@ class _QrHookMixin:
|
|
|
61
68
|
def __init__(self, *args, **kwds) -> None:
|
|
62
69
|
super().__init__(*args, **kwds)
|
|
63
70
|
self.qr_fetched = MT.qr_fetched.with_timeout(60)
|
|
71
|
+
"""This emitter is triggered when a QR code is fetched."""
|
|
64
72
|
self.qr_cancelled = MT.qr_cancelled()
|
|
73
|
+
"""This emitter is triggered when QR login is cancelled."""
|
|
65
74
|
self.cancel = asyncio.Event()
|
|
75
|
+
"""Async-event indicating whether the loop should cancel the QR login."""
|
|
66
76
|
self.refresh = asyncio.Event()
|
|
77
|
+
"""Async-event indicating whether the loop should refresh the QR code immediately."""
|
|
67
78
|
|
|
68
79
|
|
|
69
80
|
class QrLogin(LoginBase[QrSession], _QrHookMixin):
|
|
70
|
-
async def new(self) -> QrSession:
|
|
81
|
+
async def new(self, no_push=False) -> QrSession:
|
|
82
|
+
"""Create a :class:`QrSession`. This method will:
|
|
83
|
+
|
|
84
|
+
1. GET ``xlogin`` url to get ``pt_login_sig`` cookie;
|
|
85
|
+
|
|
86
|
+
#. Try "quick login" (the QR code is pushed to user's client);
|
|
87
|
+
|
|
88
|
+
#. Whether the QR code is pushed or not, a :class:`QR` object is created
|
|
89
|
+
and is hold by the returned :class:`QrSession`.
|
|
90
|
+
|
|
91
|
+
:param no_push: Do not try to push the QR code to user's client.
|
|
92
|
+
:return: a :class:`QrSession`
|
|
93
|
+
|
|
94
|
+
.. versionchanged:: 1.8.3
|
|
95
|
+
|
|
96
|
+
Added :obj:`no_push` param.
|
|
97
|
+
"""
|
|
71
98
|
login_sig = await self._pt_login_sig()
|
|
99
|
+
if no_push:
|
|
100
|
+
return QrSession(await self.show(), login_sig=login_sig)
|
|
72
101
|
|
|
73
102
|
cookie = self.client.cookie_jar.filter_cookies(PTLOGIN2).get("pt_guid_sig")
|
|
74
103
|
push_qr = False
|
|
@@ -88,9 +117,10 @@ class QrLogin(LoginBase[QrSession], _QrHookMixin):
|
|
|
88
117
|
return QrSession(await self.show(push_qr), login_sig=login_sig)
|
|
89
118
|
|
|
90
119
|
async def show(self, push_qr=False) -> QR:
|
|
91
|
-
"""``ptqrshow`` api
|
|
120
|
+
"""This method will call ``ptqrshow`` api and wrap the response QR bytes into :class:`QR`.
|
|
92
121
|
|
|
93
122
|
:param push_qr: push QR to mobile client.
|
|
123
|
+
:return: a :class:`QR` object.
|
|
94
124
|
"""
|
|
95
125
|
data = {
|
|
96
126
|
"appid": self.app.appid,
|
|
@@ -170,26 +200,37 @@ class QrLogin(LoginBase[QrSession], _QrHookMixin):
|
|
|
170
200
|
*,
|
|
171
201
|
refresh_times: int = 6,
|
|
172
202
|
poll_freq: float = 3,
|
|
203
|
+
no_push=False,
|
|
173
204
|
):
|
|
174
|
-
"""Loop until cookie is returned or max
|
|
205
|
+
"""Loop until cookie is returned or max :obj:`refresh_times` exceeds.
|
|
206
|
+
|
|
175
207
|
- This method will emit :obj:`.qr_fetched` event if a new qrcode is fetched.
|
|
176
|
-
|
|
208
|
+
|
|
209
|
+
- If the QR code is not scanned after :obj:`refresh_times`,
|
|
210
|
+
it will raise :exc:`~qqqr.exception.UserTimeout`.
|
|
211
|
+
|
|
177
212
|
- If :obj:`.refresh` is set, it will refresh qrcode at once without increasing expire counter.
|
|
178
|
-
|
|
213
|
+
|
|
214
|
+
- If :obj:`.cancel` is set, it will raise :exc:`~qqqr.exception.UserBreak` before next polling.
|
|
179
215
|
|
|
180
216
|
:meta public:
|
|
181
217
|
:param refresh_times: max qr expire times.
|
|
182
218
|
:param poll_freq: interval between two status polling, in seconds, default as 3.
|
|
219
|
+
:param no_push: Do not try to push the QR code to user's client.
|
|
183
220
|
|
|
184
|
-
:raise `UserTimeout`: if
|
|
221
|
+
:raise `UserTimeout`: if the QR code is not scanned after :obj:`refresh_times` expires.
|
|
185
222
|
:raise `UserBreak`: if :obj:`.cancel` is set.
|
|
223
|
+
|
|
224
|
+
.. versionchanged:: 1.8.3
|
|
225
|
+
|
|
226
|
+
Added :obj:`no_push` param.
|
|
186
227
|
"""
|
|
187
228
|
self.refresh.clear()
|
|
188
229
|
self.cancel.clear()
|
|
189
230
|
|
|
190
231
|
cnt_expire = 0
|
|
191
232
|
renew = False
|
|
192
|
-
sess = await self.new()
|
|
233
|
+
sess = await self.new(no_push)
|
|
193
234
|
|
|
194
235
|
while cnt_expire < refresh_times:
|
|
195
236
|
# BUG: should we wrap hook errors here?
|
|
@@ -206,6 +247,7 @@ class QrLogin(LoginBase[QrSession], _QrHookMixin):
|
|
|
206
247
|
await asyncio.sleep(poll_freq)
|
|
207
248
|
stat = await self.poll(sess)
|
|
208
249
|
if stat.code == StatusCode.Expired:
|
|
250
|
+
sess.current_qr.expired = True
|
|
209
251
|
cnt_expire += 1
|
|
210
252
|
break
|
|
211
253
|
elif stat.code == StatusCode.Authenticated:
|
|
@@ -99,19 +99,15 @@ class _UpHookMixin:
|
|
|
99
99
|
def __init__(self, *args, **kwds) -> None:
|
|
100
100
|
super().__init__(*args, **kwds)
|
|
101
101
|
self.sms_code_input = MT.sms_code_input.with_timeout(60)
|
|
102
|
+
"""This emitter is triggered when SMS verify code is needed during login."""
|
|
102
103
|
|
|
103
104
|
|
|
104
105
|
class UpWebLogin(LoginBase[UpWebSession], _UpHookMixin):
|
|
105
106
|
"""
|
|
106
|
-
.. versionchanged:: 0.12.4
|
|
107
|
-
|
|
108
|
-
`TeaEncoder` is used as the default password encoder. A `legacy_encoder` paramater is added to force
|
|
109
|
-
using the former `NodeEncoder`. It can also be configured by set :envvar:`AIOQZONE_PWDENCODER` to "node".
|
|
110
|
-
Note that the paramater in code, i.e. `legacy_encoder`, takes precedence.
|
|
111
|
-
|
|
112
107
|
.. versionchanged:: 0.13.0.dev1
|
|
113
108
|
|
|
114
|
-
|
|
109
|
+
:class:`~qqqr.up.encrypt.TeaEncoder` is the unique :class:`~qqqr.up.encrypt.PasswdEncoder`.
|
|
110
|
+
``NodeEncoder`` is removed.
|
|
115
111
|
"""
|
|
116
112
|
|
|
117
113
|
def __init__(
|
|
@@ -132,9 +128,8 @@ class UpWebLogin(LoginBase[UpWebSession], _UpHookMixin):
|
|
|
132
128
|
self.client, self.app.appid, str(self.login_page_url), fake_ip=fake_ip
|
|
133
129
|
)
|
|
134
130
|
|
|
135
|
-
async def new(self):
|
|
136
|
-
"""Create a :class:`UpWebSession`. This will
|
|
137
|
-
about whether this login needs a captcha, sms verification, etc.
|
|
131
|
+
async def new(self) -> UpWebSession:
|
|
132
|
+
"""Create a :class:`UpWebSession`. This will trigger a GET to ``xlogin`` url.
|
|
138
133
|
|
|
139
134
|
:raise `aiohttp.ClientResponseError`: if response status != 200
|
|
140
135
|
|
|
@@ -143,6 +138,11 @@ class UpWebLogin(LoginBase[UpWebSession], _UpHookMixin):
|
|
|
143
138
|
return UpWebSession(await self._pt_login_sig())
|
|
144
139
|
|
|
145
140
|
async def check(self, sess: UpWebSession):
|
|
141
|
+
"""This will call ``check`` api of Qzone, and receive result about
|
|
142
|
+
whether this login needs a captcha, sms verification, etc.
|
|
143
|
+
|
|
144
|
+
:param sess: Session got from :meth:`~UpWebLogin.new`.
|
|
145
|
+
"""
|
|
146
146
|
data = {
|
|
147
147
|
"regmaster": "",
|
|
148
148
|
"pt_tea": 2,
|
|
@@ -217,7 +217,7 @@ class UpWebLogin(LoginBase[UpWebSession], _UpHookMixin):
|
|
|
217
217
|
data.update(const)
|
|
218
218
|
return data
|
|
219
219
|
|
|
220
|
-
async def try_login(self, sess: UpWebSession):
|
|
220
|
+
async def try_login(self, sess: UpWebSession) -> LoginResp:
|
|
221
221
|
"""
|
|
222
222
|
Check if current session meets the login condition.
|
|
223
223
|
It takes a session object and returns response of this try.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|