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.
Files changed (53) hide show
  1. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/PKG-INFO +1 -1
  2. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/pyproject.toml +6 -2
  3. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/api/h5/model.py +1 -1
  4. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/api/login/__init__.py +4 -3
  5. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/api/login/_base.py +1 -1
  6. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/exception.py +1 -1
  7. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/api/response.py +12 -6
  8. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/base.py +4 -2
  9. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/qr/__init__.py +4 -2
  10. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/__init__.py +13 -7
  11. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/web.py +7 -6
  12. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/README.md +0 -0
  13. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/__init__.py +0 -0
  14. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/api/__init__.py +0 -0
  15. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/api/h5/__init__.py +0 -0
  16. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/message.py +0 -0
  17. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/__init__.py +0 -0
  18. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/api/__init__.py +0 -0
  19. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/api/feed.py +0 -0
  20. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/api/profile.py +0 -0
  21. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/api/request.py +0 -0
  22. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/protocol/__init__.py +0 -0
  23. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/protocol/config.py +0 -0
  24. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/model/protocol/entity.py +0 -0
  25. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/utils/__init__.py +0 -0
  26. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/utils/entity.py +0 -0
  27. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/utils/regex.py +0 -0
  28. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/utils/retry.py +0 -0
  29. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/aioqzone/utils/time.py +0 -0
  30. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/__init__.py +0 -0
  31. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/constant.py +0 -0
  32. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/exception.py +0 -0
  33. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/message.py +0 -0
  34. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/py.typed +0 -0
  35. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/qr/type.py +0 -0
  36. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/type.py +0 -0
  37. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/__init__.py +0 -0
  38. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/_model.py +0 -0
  39. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/_model.py +0 -0
  40. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/capsess.py +0 -0
  41. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/click/__init__.py +0 -0
  42. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/click/_types.py +0 -0
  43. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/pil_utils.py +0 -0
  44. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/select/__init__.py +0 -0
  45. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/select/_types.py +0 -0
  46. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/slide/__init__.py +0 -0
  47. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/captcha/slide/_types.py +0 -0
  48. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/encrypt.py +0 -0
  49. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/up/h5.py +0 -0
  50. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/utils/encrypt.py +0 -0
  51. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/utils/iter.py +0 -0
  52. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/utils/jsjson.py +0 -0
  53. {aioqzone-1.9.6.dev5 → aioqzone-1.9.7.dev1}/src/qqqr/utils/net.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aioqzone
3
- Version: 1.9.6.dev5
3
+ Version: 1.9.7.dev1
4
4
  Summary: A Python wrapper for Qzone login and H5 APIs.
5
5
  Keywords: qzone-api,autologin,asyncio-spider
6
6
  Author: aioqzone
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aioqzone"
3
- version = "1.9.6.dev5"
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 = ["pre-commit", "ruff"]
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'",
@@ -89,7 +89,7 @@ class QzoneH5API:
89
89
  log.debug(f"Error when validating {obj}, {api=}")
90
90
  raise
91
91
  else:
92
- raise AssertionError
92
+ raise RuntimeError("max retry exceeded")
93
93
 
94
94
  async def _retry_sleep(self, *_) -> None:
95
95
  await self.login.new_cookie()
@@ -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 BaseException as e:
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 BaseException as e:
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
 
@@ -57,7 +57,7 @@ class Loginable(ABC):
57
57
  async with self.lock:
58
58
  try:
59
59
  self.cookie = await self._new_cookie()
60
- except BaseException as e:
60
+ except Exception as e:
61
61
  self.ch_login_notify.add_awaitable(self.login_failed.emit(self.uin, e))
62
62
  return False
63
63
  else:
@@ -8,7 +8,7 @@ class QzoneError(RuntimeError):
8
8
  self.robj = robj
9
9
  if len(args) > 0 and isinstance(args[0], str):
10
10
  self.msg = args[0]
11
- super().__init__(self, *args)
11
+ super().__init__(*args)
12
12
 
13
13
  def __str__(self) -> str:
14
14
  return f"QzoneCode {self.code}: {self.msg}"
@@ -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
- assert isinstance(data, list)
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
- assert m
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
- assert m
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
- assert m
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
- assert m
338
+ if not m:
339
+ raise TryAgain("PhotosPreuploadResponse: callback not found")
335
340
 
336
341
  picinfos = json_loads(m.group(1))
337
- assert isinstance(picinfos, list)
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
- assert sess.login_url
124
- assert not sess.logined, "This session is logined."
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
- assert qrsig
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
- assert m
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
- assert m
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
- get_tdc_collect(self.client),
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
- assert isinstance(info, str)
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 PasswdEncoder, TeaEncoder
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 BaseException as e:
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
- with suppress(BaseException):
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
- sess.sms_code = firstn(hook_results, lambda c: c and len(c.strip()) >= 4)
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