aioqzone 1.9.6.dev5__tar.gz → 1.9.7.dev1__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.9.6.dev5 → aioqzone-1.9.7.dev1}/PKG-INFO +1 -1
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/pyproject.toml +6 -2
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/api/h5/model.py +1 -1
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/api/login/__init__.py +4 -3
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/api/login/_base.py +1 -1
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/exception.py +1 -1
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/api/response.py +12 -6
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/base.py +4 -2
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/qr/__init__.py +4 -2
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/__init__.py +13 -7
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/web.py +7 -6
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/README.md +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/__init__.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/api/__init__.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/api/h5/__init__.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/message.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/__init__.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/api/__init__.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/api/feed.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/api/profile.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/api/request.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/protocol/__init__.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/protocol/config.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/protocol/entity.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/utils/__init__.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/utils/entity.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/utils/regex.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/utils/retry.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/utils/time.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/__init__.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/constant.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/exception.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/message.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/py.typed +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/qr/type.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/type.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/__init__.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/_model.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/_model.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/capsess.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/click/__init__.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/click/_types.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/pil_utils.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/select/__init__.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/select/_types.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/slide/__init__.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/slide/_types.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/encrypt.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/h5.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/utils/encrypt.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/utils/iter.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/utils/jsjson.py +0 -0
- {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/utils/net.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aioqzone"
|
|
3
|
-
version = "1.9.
|
|
3
|
+
version = "1.9.7.dev1"
|
|
4
4
|
description = "A Python wrapper for Qzone login and H5 APIs."
|
|
5
5
|
authors = [{ name = "aioqzone" }]
|
|
6
6
|
maintainers = [
|
|
@@ -38,7 +38,11 @@ Documentation = "https://aioqzone.github.io/aioqzone"
|
|
|
38
38
|
|
|
39
39
|
[dependency-groups]
|
|
40
40
|
test = ["pytest>=9.0.3", "pytest-asyncio>=1.1,<1.4"]
|
|
41
|
-
dev = [
|
|
41
|
+
dev = [
|
|
42
|
+
"pre-commit",
|
|
43
|
+
"ruff",
|
|
44
|
+
"ty",
|
|
45
|
+
]
|
|
42
46
|
doc = [
|
|
43
47
|
"Sphinx>=8.1.3; python_version=='3.10'",
|
|
44
48
|
"Sphinx~=9.0; python_version>='3.11'",
|
|
@@ -101,6 +101,7 @@ class UpLoginManager(Loginable):
|
|
|
101
101
|
raise TryAgain from e
|
|
102
102
|
raise
|
|
103
103
|
except NotImplementedError:
|
|
104
|
+
# intended: forwarded as NeedSmsVerify
|
|
104
105
|
from qqqr.constant import StatusCode
|
|
105
106
|
|
|
106
107
|
raise TencentLoginError(StatusCode.NeedSmsVerify, "需要手机验证")
|
|
@@ -108,7 +109,7 @@ class UpLoginManager(Loginable):
|
|
|
108
109
|
log.warning(f"密码登录:{type(e).__name__},重试", exc_info=True)
|
|
109
110
|
log.debug(e.args, extra=e.__dict__)
|
|
110
111
|
raise TryAgain from e
|
|
111
|
-
except
|
|
112
|
+
except Exception as e:
|
|
112
113
|
log.fatal("密码登录异常", exc_info=True)
|
|
113
114
|
raise UnexpectedLoginError from e
|
|
114
115
|
|
|
@@ -178,8 +179,8 @@ class QrLoginManager(Loginable):
|
|
|
178
179
|
except (GeneratorExit, ClientError) as e:
|
|
179
180
|
log.warning(f"二维码登录:{type(e).__name__},重试", exc_info=True)
|
|
180
181
|
log.debug(e.args, extra=e.__dict__)
|
|
181
|
-
raise TryAgain
|
|
182
|
-
except
|
|
182
|
+
raise TryAgain from e
|
|
183
|
+
except Exception as e:
|
|
183
184
|
log.fatal("二维码登录异常", exc_info=True)
|
|
184
185
|
raise UnexpectedLoginError from e
|
|
185
186
|
|
|
@@ -212,7 +212,8 @@ class ProfilePagePesp(QzoneResponse):
|
|
|
212
212
|
data = script[m.end() - 1 : m.end() + entire_closing(script[m.end() - 1 :], "[")]
|
|
213
213
|
data = re.sub(r",,\]$", "]", data)
|
|
214
214
|
data = json_loads(data)
|
|
215
|
-
|
|
215
|
+
if not isinstance(data, list):
|
|
216
|
+
raise TryAgain("ProfilePageResponse: profile not returned")
|
|
216
217
|
if len(data) < 2:
|
|
217
218
|
raise TryAgain("ProfilePageResponse: profile not returned")
|
|
218
219
|
|
|
@@ -261,7 +262,8 @@ class AddCommentLegacyResp(QzoneResponse):
|
|
|
261
262
|
raise TryAgain("AddCommentLegacyResponse: script tag not found")
|
|
262
263
|
|
|
263
264
|
m = response_callback.search(script)
|
|
264
|
-
|
|
265
|
+
if not m:
|
|
266
|
+
raise TryAgain("AddCommentLegacyResponse: callback not found")
|
|
265
267
|
return validate_strdict(json_loads(m.group(1)))
|
|
266
268
|
|
|
267
269
|
|
|
@@ -280,7 +282,8 @@ class DeleteCommentResp(QzoneResponse):
|
|
|
280
282
|
raise TryAgain("DeleteCommentResponse: script tag not found")
|
|
281
283
|
|
|
282
284
|
m = response_callback.search(script)
|
|
283
|
-
|
|
285
|
+
if not m:
|
|
286
|
+
raise TryAgain("DeleteCommentResponse: callback not found")
|
|
284
287
|
return validate_strdict(json_loads(m.group(1)))
|
|
285
288
|
|
|
286
289
|
|
|
@@ -306,7 +309,8 @@ class UploadPicResponse(QzoneResponse):
|
|
|
306
309
|
@classmethod
|
|
307
310
|
async def response_to_object(cls, response: ClientResponse) -> StrDict:
|
|
308
311
|
m = response_callback.search(await response.text())
|
|
309
|
-
|
|
312
|
+
if not m:
|
|
313
|
+
raise TryAgain("UploadPicResponse: callback not found")
|
|
310
314
|
return validate_strdict(json_loads(m.group(1)))
|
|
311
315
|
|
|
312
316
|
|
|
@@ -331,10 +335,12 @@ class PhotosPreuploadResponse(QzoneResponse):
|
|
|
331
335
|
@classmethod
|
|
332
336
|
async def response_to_object(cls, response: ClientResponse) -> StrDict:
|
|
333
337
|
m = response_callback.search(await response.text())
|
|
334
|
-
|
|
338
|
+
if not m:
|
|
339
|
+
raise TryAgain("PhotosPreuploadResponse: callback not found")
|
|
335
340
|
|
|
336
341
|
picinfos = json_loads(m.group(1))
|
|
337
|
-
|
|
342
|
+
if not isinstance(picinfos, list):
|
|
343
|
+
raise TryAgain("PhotosPreuploadResponse: picinfos not a list")
|
|
338
344
|
return dict(photos=[PicInfo.from_response_object(info["picinfo"]) for info in picinfos]) # type: ignore
|
|
339
345
|
|
|
340
346
|
|
|
@@ -120,8 +120,10 @@ class LoginBase(ABC, t.Generic[_S]):
|
|
|
120
120
|
async def _get_login_url(
|
|
121
121
|
self, sess: _S, cur_cookies: t.Optional[t.Mapping[str, t.Any]] = None
|
|
122
122
|
) -> t.Dict[str, str]:
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
if not sess.login_url:
|
|
124
|
+
raise ValueError("login_url is required")
|
|
125
|
+
if sess.logined:
|
|
126
|
+
raise RuntimeError("This session is logined.")
|
|
125
127
|
|
|
126
128
|
r = {}
|
|
127
129
|
if cur_cookies is not None:
|
|
@@ -136,7 +136,8 @@ class QrLogin(LoginBase[QrSession], _QrHookMixin):
|
|
|
136
136
|
async with self.client.get(SHOW_QR, params=data) as r:
|
|
137
137
|
qrsig = r.cookies.get("qrsig")
|
|
138
138
|
if not push_qr:
|
|
139
|
-
|
|
139
|
+
if not qrsig:
|
|
140
|
+
raise RuntimeError("qrsig cookie not found")
|
|
140
141
|
return QR(
|
|
141
142
|
png=await r.content.read(),
|
|
142
143
|
sig=qrsig.value,
|
|
@@ -144,7 +145,8 @@ class QrLogin(LoginBase[QrSession], _QrHookMixin):
|
|
|
144
145
|
m = re.search(r"ptui_qrcode_CB\((.*)\)", r := await r.text())
|
|
145
146
|
|
|
146
147
|
log.debug(f"ptqrshow(qr_push) response: {r}")
|
|
147
|
-
|
|
148
|
+
if not m:
|
|
149
|
+
raise RuntimeError("ptui_qrcode_CB not found in response")
|
|
148
150
|
resp = PushQrResp.model_validate_json(m.group(1))
|
|
149
151
|
if qrsig and resp.code == 0:
|
|
150
152
|
log.info("二维码已推送至用户手机端")
|
|
@@ -127,7 +127,8 @@ class Captcha(_CaptchaHookMixin):
|
|
|
127
127
|
r.raise_for_status()
|
|
128
128
|
m = re.search(CALLBACK + r"\((\{.*\})\)", await r.text("utf8"))
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
if not m:
|
|
131
|
+
raise RuntimeError("prehandle callback not found")
|
|
131
132
|
return PrehandleRespValidator.validate_json(m.group(1))
|
|
132
133
|
|
|
133
134
|
sess = TcaptchaSession.factory(sid, await retry_closure())
|
|
@@ -142,6 +143,14 @@ class Captcha(_CaptchaHookMixin):
|
|
|
142
143
|
prehandle = new
|
|
143
144
|
"""alias of :meth:`.new`"""
|
|
144
145
|
|
|
146
|
+
async def _get_tdc_collect(self, sess: TcaptchaSession, client: ClientAdapter) -> str:
|
|
147
|
+
try:
|
|
148
|
+
await sess.get_tdc(client, ip=self.fake_ip)
|
|
149
|
+
except Exception:
|
|
150
|
+
log.debug("get_tdc failed", exc_info=True)
|
|
151
|
+
return ""
|
|
152
|
+
return unquote(str(sess.tdc.getData(None, True)))
|
|
153
|
+
|
|
145
154
|
@retry(
|
|
146
155
|
stop=stop_after_attempt(2),
|
|
147
156
|
retry=retry_if_result(lambda rst: not rst.ticket),
|
|
@@ -158,13 +167,9 @@ class Captcha(_CaptchaHookMixin):
|
|
|
158
167
|
await sess.get_captcha_problem(client)
|
|
159
168
|
return await sess.solve_captcha()
|
|
160
169
|
|
|
161
|
-
async def get_tdc_collect(client: ClientAdapter) -> str:
|
|
162
|
-
await sess.get_tdc(client, ip=self.fake_ip)
|
|
163
|
-
return unquote(str(sess.tdc.getData(None, True)))
|
|
164
|
-
|
|
165
170
|
ans, collect, _ = await asyncio.gather(
|
|
166
171
|
get_solve_captcha(self.client),
|
|
167
|
-
|
|
172
|
+
self._get_tdc_collect(sess, self.client),
|
|
168
173
|
loop.run_in_executor(None, sess.solve_workload),
|
|
169
174
|
)
|
|
170
175
|
if not ans:
|
|
@@ -175,7 +180,8 @@ class Captcha(_CaptchaHookMixin):
|
|
|
175
180
|
data=ans,
|
|
176
181
|
)
|
|
177
182
|
info = sess.tdc.getInfo(None)["info"]
|
|
178
|
-
|
|
183
|
+
if not isinstance(info, str):
|
|
184
|
+
raise TypeError(f"expected str for tdc info, got {type(info).__name__}")
|
|
179
185
|
|
|
180
186
|
data = {
|
|
181
187
|
"collect": collect,
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import logging
|
|
3
2
|
import re
|
|
4
3
|
import typing as t
|
|
5
|
-
from contextlib import suppress
|
|
6
4
|
from random import choice, random
|
|
7
5
|
from time import time_ns
|
|
8
6
|
|
|
@@ -18,7 +16,7 @@ from qqqr.utils.net import ClientAdapter, get_all_cookie
|
|
|
18
16
|
|
|
19
17
|
from ._model import CheckResp, CheckRespValidator, LoginResp, RedirectCookies, VerifyResp
|
|
20
18
|
from .captcha import Captcha
|
|
21
|
-
from .encrypt import
|
|
19
|
+
from .encrypt import TeaEncoder
|
|
22
20
|
|
|
23
21
|
CHECK_URL = "https://ssl.ptlogin2.qq.com/check"
|
|
24
22
|
LOGIN_URL = "https://ssl.ptlogin2.qq.com/login"
|
|
@@ -93,7 +91,7 @@ class UpWebSession(LoginSession):
|
|
|
93
91
|
) from e.last_attempt.exception()
|
|
94
92
|
except NotImplementedError:
|
|
95
93
|
raise
|
|
96
|
-
except
|
|
94
|
+
except Exception as e:
|
|
97
95
|
raise TencentLoginError(StatusCode.NeedCaptcha, "验证过程出现错误") from e
|
|
98
96
|
|
|
99
97
|
log.info("成功通过验证码")
|
|
@@ -278,11 +276,14 @@ class UpWebLogin(LoginBase[UpWebSession], _UpHookMixin):
|
|
|
278
276
|
# fast return so we won't always request smscode which may risk test account.
|
|
279
277
|
raise TencentLoginError(resp.code, "未实现的功能:输入验证码")
|
|
280
278
|
await self.send_sms_code(sess)
|
|
281
|
-
|
|
279
|
+
try:
|
|
282
280
|
hook_results = await self.sms_code_input.results(
|
|
283
281
|
uin=self.uin, phone=resp.msg, nickname=resp.nickname
|
|
284
282
|
)
|
|
285
|
-
|
|
283
|
+
except Exception:
|
|
284
|
+
log.warning("sms_code_input hook failed", exc_info=True)
|
|
285
|
+
hook_results = []
|
|
286
|
+
sess.sms_code = firstn(hook_results, lambda c: c and len(c.strip()) >= 4)
|
|
286
287
|
if sess.sms_code is None:
|
|
287
288
|
raise TencentLoginError(resp.code, "未获得动态(SMS)验证码")
|
|
288
289
|
else:
|
|
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
|