aioqzone 1.9.1.dev3__tar.gz → 1.9.2.dev2__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 (55) hide show
  1. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/PKG-INFO +5 -5
  2. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/README.md +1 -1
  3. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/pyproject.toml +5 -3
  4. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/model/api/feed.py +0 -1
  5. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/model/api/response.py +25 -12
  6. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/up/_model.py +6 -3
  7. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/up/captcha/__init__.py +4 -4
  8. aioqzone-1.9.2.dev2/src/qqqr/up/captcha/_model.py +77 -0
  9. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/up/captcha/capsess.py +15 -13
  10. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/up/captcha/select/_types.py +3 -3
  11. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/up/captcha/slide/_types.py +1 -1
  12. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/up/h5.py +2 -8
  13. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/up/web.py +2 -8
  14. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/utils/jsjson.py +4 -3
  15. aioqzone-1.9.1.dev3/src/qqqr/up/captcha/_model.py +0 -73
  16. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/LICENSE +0 -0
  17. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/__init__.py +0 -0
  18. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/api/__init__.py +0 -0
  19. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/api/h5/__init__.py +0 -0
  20. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/api/h5/model.py +0 -0
  21. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/api/login/__init__.py +0 -0
  22. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/api/login/_base.py +0 -0
  23. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/exception.py +0 -0
  24. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/message.py +0 -0
  25. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/model/__init__.py +0 -0
  26. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/model/api/__init__.py +0 -0
  27. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/model/api/profile.py +0 -0
  28. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/model/api/request.py +0 -0
  29. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/model/protocol/__init__.py +0 -0
  30. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/model/protocol/config.py +0 -0
  31. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/model/protocol/entity.py +0 -0
  32. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/utils/__init__.py +0 -0
  33. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/utils/entity.py +0 -0
  34. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/utils/regex.py +0 -0
  35. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/utils/retry.py +0 -0
  36. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/aioqzone/utils/time.py +0 -0
  37. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/__init__.py +0 -0
  38. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/base.py +0 -0
  39. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/constant.py +0 -0
  40. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/exception.py +0 -0
  41. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/message.py +0 -0
  42. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/py.typed +0 -0
  43. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/qr/__init__.py +0 -0
  44. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/qr/type.py +0 -0
  45. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/type.py +0 -0
  46. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/up/__init__.py +0 -0
  47. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/up/captcha/click/__init__.py +0 -0
  48. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/up/captcha/click/_types.py +0 -0
  49. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/up/captcha/pil_utils.py +0 -0
  50. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/up/captcha/select/__init__.py +0 -0
  51. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/up/captcha/slide/__init__.py +0 -0
  52. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/up/encrypt.py +0 -0
  53. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/utils/encrypt.py +0 -0
  54. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/utils/iter.py +0 -0
  55. {aioqzone-1.9.1.dev3 → aioqzone-1.9.2.dev2}/src/qqqr/utils/net.py +0 -0
@@ -1,12 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: aioqzone
3
- Version: 1.9.1.dev3
3
+ Version: 1.9.2.dev2
4
4
  Summary: A Python wrapper for Qzone login and H5 APIs.
5
- Home-page: https://github.com/aioqzone/aioqzone
6
5
  License: AGPL-3.0
7
6
  Keywords: qzone-api,autologin,asyncio-spider
8
7
  Author: aioqzone
9
- Author-email: zzzzss990315@gmail.com
8
+ Author-email: 22952836+JamzumSum@users.noreply.github.com
10
9
  Requires-Python: >=3.9,<4.0
11
10
  Classifier: Development Status :: 4 - Beta
12
11
  Classifier: Intended Audience :: Developers
@@ -33,6 +32,7 @@ Requires-Dist: tylisten (>=2.1.4,<3.0.0)
33
32
  Project-URL: Bug Tracker, https://github.com/aioqzone/aioqzone/issues
34
33
  Project-URL: Documentation, https://aioqzone.github.io/aioqzone
35
34
  Project-URL: Discussion, https://t.me/aioqzone_chatroom
35
+ Project-URL: Homepage, https://github.com/aioqzone/aioqzone
36
36
  Project-URL: Repository, https://github.com/aioqzone/aioqzone
37
37
  Description-Content-Type: text/markdown
38
38
 
@@ -98,7 +98,7 @@ __在做了:__
98
98
  ## 许可证
99
99
 
100
100
  ```
101
- Copyright (C) 2022-2024 aioqzone.
101
+ Copyright (C) 2022-2025 aioqzone.
102
102
 
103
103
  This program is free software: you can redistribute it and/or modify
104
104
  it under the terms of the GNU Affero General Public License as published
@@ -60,7 +60,7 @@ __在做了:__
60
60
  ## 许可证
61
61
 
62
62
  ```
63
- Copyright (C) 2022-2024 aioqzone.
63
+ Copyright (C) 2022-2025 aioqzone.
64
64
 
65
65
  This program is free software: you can redistribute it and/or modify
66
66
  it under the terms of the GNU Affero General Public License as published
@@ -1,8 +1,8 @@
1
1
  [tool.poetry]
2
2
  name = "aioqzone"
3
- version = "1.9.1.dev3"
3
+ version = "1.9.2.dev2"
4
4
  description = "A Python wrapper for Qzone login and H5 APIs."
5
- authors = ["aioqzone <zzzzss990315@gmail.com>"]
5
+ authors = ["aioqzone <22952836+JamzumSum@users.noreply.github.com>"]
6
6
  license = "AGPL-3.0"
7
7
  readme = "README.md"
8
8
  homepage = "https://github.com/aioqzone/aioqzone"
@@ -48,7 +48,7 @@ optional = false
48
48
 
49
49
  [tool.poetry.group.test.dependencies]
50
50
  pytest = "^8.2.0"
51
- pytest-asyncio = "~0.21.2"
51
+ pytest-asyncio = "~0.25.2"
52
52
 
53
53
  [tool.poetry.group.dev]
54
54
  optional = true
@@ -87,6 +87,7 @@ build-backend = "poetry.core.masonry.api"
87
87
  pythonpath = 'src'
88
88
  log_cli = 1
89
89
  log_cli_level = 'WARNING'
90
+ asyncio_default_fixture_loop_scope = "module"
90
91
 
91
92
  [tool.isort]
92
93
  profile = "black"
@@ -99,3 +100,4 @@ target-version = ['py39']
99
100
  [tool.pyright]
100
101
  pythonVersion = "3.9"
101
102
  pythonPlatform = "All"
103
+ reportIncompatibleVariableOverride = "information"
@@ -1,4 +1,3 @@
1
- import sys
2
1
  import typing as t
3
2
  from enum import IntEnum
4
3
 
@@ -4,19 +4,28 @@ from contextlib import suppress
4
4
 
5
5
  from aiohttp import ClientResponse
6
6
  from lxml.html import HtmlElement, document_fromstring
7
- from pydantic import AliasChoices, AliasPath, BaseModel, Field, HttpUrl, model_validator
7
+ from pydantic import (
8
+ AliasChoices,
9
+ AliasPath,
10
+ BaseModel,
11
+ Field,
12
+ HttpUrl,
13
+ TypeAdapter,
14
+ model_validator,
15
+ )
8
16
  from tenacity import TryAgain
9
17
  from typing_extensions import Self
10
18
 
11
19
  from aioqzone.exception import QzoneError
12
20
  from aioqzone.utils.regex import entire_closing, response_callback
13
21
  from qqqr.utils.iter import firstn
14
- from qqqr.utils.jsjson import JsonValue, json_loads
22
+ from qqqr.utils.jsjson import json_loads
15
23
 
16
24
  from .feed import FeedData
17
25
  from .profile import ProfileFeedData, QzoneProfile
18
26
 
19
- StrDict = t.Dict[str, JsonValue]
27
+ StrDict = t.Dict[str, t.Any]
28
+
20
29
 
21
30
  __all__ = [
22
31
  "QzoneResponse",
@@ -37,6 +46,9 @@ __all__ = [
37
46
  "ProfileFeedData",
38
47
  ]
39
48
 
49
+ validate_strdict = TypeAdapter(StrDict).validate_python
50
+ validate_str = TypeAdapter(str).validate_python
51
+
40
52
 
41
53
  class QzoneResponse(BaseModel):
42
54
  _errno_key: t.ClassVar[t.Union[str, AliasPath, AliasChoices, None]] = AliasChoices(
@@ -120,7 +132,7 @@ class IndexPageResp(FeedPageResp):
120
132
  qzonetoken: str = ""
121
133
 
122
134
  @classmethod
123
- async def response_to_object(cls, response: ClientResponse):
135
+ async def response_to_object(cls, response: ClientResponse) -> StrDict:
124
136
  html = await response.text()
125
137
  scripts: t.List[HtmlElement] = document_fromstring(html).xpath(
126
138
  'body/script[@type="application/javascript"]'
@@ -142,9 +154,10 @@ class IndexPageResp(FeedPageResp):
142
154
  if m is None:
143
155
  raise TryAgain("page data not found")
144
156
  data = script[m.end() - 1 : m.end() + entire_closing(script[m.end() - 1 :])]
145
- data = json_loads(data)
157
+ data = validate_strdict(json_loads(data))
158
+
146
159
  with suppress(TypeError):
147
- data["data"]["qzonetoken"] = qzonetoken # type: ignore
160
+ data["data"]["qzonetoken"] = qzonetoken
148
161
 
149
162
  return data
150
163
 
@@ -206,9 +219,9 @@ class ProfilePagePesp(QzoneResponse):
206
219
  @classmethod
207
220
  def from_response_object(cls, obj: "StrDict") -> Self:
208
221
  return cls(
209
- info=QzoneInfo.from_response_object(obj["info"]), # type: ignore
210
- feedpage=ProfileResp.from_response_object(obj["feedpage"]), # type: ignore
211
- qzonetoken=obj["qzonetoken"], # type: ignore
222
+ info=QzoneInfo.from_response_object(validate_strdict(obj["info"])),
223
+ feedpage=ProfileResp.from_response_object(validate_strdict(obj["feedpage"])),
224
+ qzonetoken=validate_str(obj.get("qzonetoken", "")),
212
225
  )
213
226
 
214
227
 
@@ -245,10 +258,10 @@ class UploadPicResponse(QzoneResponse):
245
258
  filemd5: str
246
259
 
247
260
  @classmethod
248
- async def response_to_object(cls, response: ClientResponse):
261
+ async def response_to_object(cls, response: ClientResponse) -> StrDict:
249
262
  m = response_callback.search(await response.text())
250
263
  assert m
251
- return json_loads(m.group(1))
264
+ return validate_strdict(json_loads(m.group(1)))
252
265
 
253
266
 
254
267
  class PicInfo(QzoneResponse):
@@ -268,7 +281,7 @@ class PhotosPreuploadResponse(QzoneResponse):
268
281
  photos: t.List[PicInfo] = Field(default_factory=list)
269
282
 
270
283
  @classmethod
271
- async def response_to_object(cls, response: ClientResponse):
284
+ async def response_to_object(cls, response: ClientResponse) -> StrDict:
272
285
  m = response_callback.search(await response.text())
273
286
  assert m
274
287
 
@@ -2,17 +2,17 @@
2
2
 
3
3
  import typing as t
4
4
 
5
- from pydantic import BaseModel, Field, HttpUrl
5
+ from pydantic import BaseModel, Field, HttpUrl, TypeAdapter
6
6
 
7
7
  from qqqr.type import RedirectCookies
8
8
 
9
9
 
10
- class CheckResp(BaseModel):
10
+ class CheckResp(t.NamedTuple):
11
11
  code: int
12
12
  """code = 0/2/3 hideVC; code = 1 showVC
13
13
  """
14
14
  verifycode: str
15
- salt_repr: str = Field(alias="salt")
15
+ salt_repr: t.Annotated[str, Field(alias="salt")]
16
16
  verifysession: str
17
17
  isRandSalt: int
18
18
  ptdrvs: str
@@ -40,3 +40,6 @@ class VerifyResp(BaseModel):
40
40
  ticket: str
41
41
  errMessage: str
42
42
  sess: str
43
+
44
+
45
+ CheckRespValidator = TypeAdapter(CheckResp)
@@ -16,7 +16,7 @@ from qqqr.message import solve_select_captcha, solve_slide_captcha
16
16
 
17
17
  from ...utils.net import ClientAdapter
18
18
  from .._model import VerifyResp
19
- from ._model import PrehandleResp
19
+ from ._model import PrehandleResp, PrehandleRespValidator
20
20
  from .capsess import BaseTcaptchaSession as TcaptchaSession
21
21
  from .click import ClickCaptchaSession
22
22
  from .select import SelectCaptchaSession
@@ -128,7 +128,7 @@ class Captcha(_CaptchaHookMixin):
128
128
  m = re.search(CALLBACK + r"\((\{.*\})\)", await r.text("utf8"))
129
129
 
130
130
  assert m
131
- return PrehandleResp.model_validate_json(m.group(1))
131
+ return PrehandleRespValidator.validate_json(m.group(1))
132
132
 
133
133
  sess = TcaptchaSession.factory(sid, await retry_closure())
134
134
  if isinstance(sess, SelectCaptchaSession):
@@ -181,9 +181,9 @@ class Captcha(_CaptchaHookMixin):
181
181
  "collect": collect,
182
182
  "tlg": len(collect),
183
183
  "eks": info.strip("'"),
184
- "sess": sess.prehandle.sess,
184
+ "sess": sess.prehandle["sess"],
185
185
  "ans": json.dumps([ans]),
186
- "pow_answer": hex_add(sess.conf.common.pow_cfg.prefix, sess.pow_ans),
186
+ "pow_answer": hex_add(sess.conf["common"]["pow_cfg"]["prefix"], sess.pow_ans),
187
187
  "pow_calc_time": sess.duration,
188
188
  }
189
189
  log.debug(f"verify post data: {data}")
@@ -0,0 +1,77 @@
1
+ import typing as t
2
+
3
+ from pydantic import AliasPath, BaseModel, Field, TypeAdapter
4
+ from typing_extensions import TypedDict
5
+
6
+
7
+ class PowCfg(TypedDict):
8
+ prefix: str
9
+ md5: str
10
+
11
+
12
+ class CommonCaptchaConf(TypedDict):
13
+ pow_cfg: PowCfg
14
+ """Ians, duration = match_md5(pow_cfg)"""
15
+ tdc_path: str
16
+ """relative path to get tdc.js"""
17
+
18
+
19
+ class CommonClickConf(TypedDict):
20
+ data_type: t.Annotated[str, Field(validation_alias=AliasPath("data_type", 0))]
21
+ mark_style: str
22
+
23
+
24
+ class CommonBgElmConf(BaseModel):
25
+ cfg: CommonClickConf = Field(validation_alias="click_cfg")
26
+
27
+
28
+ class CommonRender(BaseModel):
29
+ bg: CommonBgElmConf = Field(validation_alias="bg_elem_cfg")
30
+
31
+
32
+ class Sprite(BaseModel):
33
+ """Represents a sprite from a source material."""
34
+
35
+ size_2d: t.List[int]
36
+ """sprite size (w, h)"""
37
+ sprite_pos: t.List[int]
38
+ """sprite position on material (x, y)"""
39
+
40
+ @property
41
+ def height(self):
42
+ return self.size_2d[1]
43
+
44
+ @property
45
+ def width(self):
46
+ return self.size_2d[0]
47
+
48
+ @property
49
+ def box(self):
50
+ l, t = self.sprite_pos
51
+ return (l, t, l + self.width, l + self.height)
52
+
53
+
54
+ class CaptchaData(TypedDict):
55
+ common: t.Annotated[CommonCaptchaConf, Field(alias="comm_captcha_cfg")]
56
+ render: t.Annotated[dict[str, t.Any], Field(alias="dyn_show_info")]
57
+
58
+
59
+ class PrehandleResp(TypedDict):
60
+ captcha: t.Annotated[t.Optional[CaptchaData], Field(alias="data", default=None)]
61
+ sess: str
62
+
63
+ capclass: t.Annotated[int, Field(default=0)]
64
+ log_js: t.Annotated[str, Field(default="")]
65
+ randstr: t.Annotated[str, Field(default="")]
66
+ sid: t.Annotated[str, Field(default="")]
67
+ src_1: t.Annotated[str, Field(default="")]
68
+ src_2: t.Annotated[str, Field(default="")]
69
+ src_3: t.Annotated[str, Field(default="")]
70
+ state: t.Annotated[int, Field(default=0)]
71
+ subcapclass: t.Annotated[int, Field(default=0)]
72
+ ticket: t.Annotated[str, Field(default="")]
73
+ uip: t.Annotated[str, Field(default="")]
74
+ """ipv4 / ipv6"""
75
+
76
+
77
+ PrehandleRespValidator = TypeAdapter(PrehandleResp)
@@ -36,8 +36,8 @@ class BaseTcaptchaSession(ABC):
36
36
  self.mouse_track = asyncio.get_event_loop().create_future()
37
37
 
38
38
  def parse_captcha_data(self):
39
- assert self.prehandle.captcha
40
- self.conf = self.prehandle.captcha
39
+ assert self.prehandle["captcha"]
40
+ self.conf = self.prehandle["captcha"]
41
41
 
42
42
  def solve_workload(self, *, timeout: float = 30.0):
43
43
  """
@@ -49,9 +49,9 @@ class BaseTcaptchaSession(ABC):
49
49
  :return: None
50
50
  """
51
51
 
52
- pow_cfg = self.conf.common.pow_cfg
53
- nonce = str(pow_cfg.prefix).encode()
54
- target = pow_cfg.md5.lower()
52
+ pow_cfg = self.conf["common"]["pow_cfg"]
53
+ nonce = str(pow_cfg["prefix"]).encode()
54
+ target = pow_cfg["md5"].lower()
55
55
 
56
56
  start = time()
57
57
  cnt = 0
@@ -70,7 +70,9 @@ class BaseTcaptchaSession(ABC):
70
70
 
71
71
  def _tdx_js_url(self):
72
72
  assert self.conf
73
- return URL("https://t.captcha.qq.com").with_path(self.conf.common.tdc_path, encoded=True)
73
+ return URL("https://t.captcha.qq.com").with_path(
74
+ self.conf["common"]["tdc_path"], encoded=True
75
+ )
74
76
 
75
77
  def _vmslide_js_url(self):
76
78
  raise NotImplementedError
@@ -87,7 +89,7 @@ class BaseTcaptchaSession(ABC):
87
89
  r.raise_for_status()
88
90
  self.tdc = prepare(
89
91
  await r.text("utf8"),
90
- ip=ip or self.prehandle.uip,
92
+ ip=ip or self.prehandle["uip"],
91
93
  ua=ua or client.headers["User-Agent"],
92
94
  mouse_track=await self.mouse_track,
93
95
  )
@@ -102,18 +104,18 @@ class BaseTcaptchaSession(ABC):
102
104
 
103
105
  @classmethod
104
106
  def factory(cls, session: str, prehandle: PrehandleResp):
105
- assert prehandle.captcha
107
+ assert prehandle["captcha"]
106
108
 
107
109
  try:
108
- render = CommonRender.model_validate(prehandle.captcha.render)
110
+ render = CommonRender.model_validate(prehandle["captcha"]["render"])
109
111
  except ValidationError:
110
- log.error(prehandle.captcha.render)
112
+ log.error(prehandle["captcha"]["render"])
111
113
  raise
112
114
 
113
- if render.bg.cfg.data_type == "DynAnswerType_UC":
115
+ if render.bg.cfg["data_type"] == "DynAnswerType_UC":
114
116
  from .select import SelectCaptchaSession as cls
115
- elif render.bg.cfg.data_type == "DynAnswerType_POS":
116
- log.error(prehandle.captcha.render)
117
+ elif render.bg.cfg["data_type"] == "DynAnswerType_POS":
118
+ log.error(prehandle["captcha"]["render"])
117
119
  raise NotImplementedError("“依次点击”类验证码正在施工")
118
120
  from .click import ClickCaptchaSession as cls
119
121
  else:
@@ -3,7 +3,7 @@ import logging
3
3
  import typing as t
4
4
  from contextlib import suppress
5
5
 
6
- from pydantic import AliasPath, BaseModel, Field, ValidationError, model_validator
6
+ from pydantic import AliasPath, BaseModel, Field, model_validator
7
7
 
8
8
  from qqqr.message import solve_select_captcha
9
9
  from qqqr.utils.iter import first
@@ -61,8 +61,8 @@ class SelectCaptchaSession(BaseTcaptchaSession):
61
61
 
62
62
  def parse_captcha_data(self):
63
63
  super().parse_captcha_data()
64
- self.render = SelectRender.model_validate(self.conf.render)
65
- self.data_type = self.render.bg.cfg.data_type
64
+ self.render = SelectRender.model_validate(self.conf["render"])
65
+ self.data_type = self.render.bg.cfg["data_type"]
66
66
 
67
67
  async def get_captcha_problem(self, client: ClientAdapter):
68
68
  async with client.get(self._cdn_join(self.render.bg.img_url)) as r:
@@ -99,7 +99,7 @@ class SlideCaptchaSession(BaseTcaptchaSession):
99
99
 
100
100
  def parse_captcha_data(self):
101
101
  super().parse_captcha_data()
102
- self.render = SlideRender.model_validate(self.conf.render)
102
+ self.render = SlideRender.model_validate(self.conf["render"])
103
103
 
104
104
  self.cdn_urls = (
105
105
  self._cdn_join(self.render.bg.img_url),
@@ -4,7 +4,7 @@ from random import random
4
4
 
5
5
  from yarl import URL
6
6
 
7
- from ._model import CheckResp
7
+ from ._model import CheckRespValidator
8
8
  from .web import UpWebLogin, UpWebSession
9
9
 
10
10
  log = logging.getLogger(__name__)
@@ -43,13 +43,7 @@ class UpH5Login(UpWebLogin):
43
43
  r.raise_for_status()
44
44
  rl = re.findall(r"'(.*?)'[,\)]", await r.text())
45
45
 
46
- rdict = dict(
47
- zip(
48
- ["code", "verifycode", "salt", "verifysession", "isRandSalt", "ptdrvs", "session"],
49
- rl,
50
- )
51
- )
52
- sess.set_check_result(CheckResp.model_validate(rdict))
46
+ sess.set_check_result(CheckRespValidator.validate_python(rl))
53
47
 
54
48
  async def _make_login_param(self, sess: UpWebSession):
55
49
  const = {
@@ -16,7 +16,7 @@ from qqqr.type import APPID, PT_QR_APP, Proxy
16
16
  from qqqr.utils.iter import firstn
17
17
  from qqqr.utils.net import ClientAdapter, get_all_cookie
18
18
 
19
- from ._model import CheckResp, LoginResp, RedirectCookies, VerifyResp
19
+ from ._model import CheckResp, CheckRespValidator, LoginResp, RedirectCookies, VerifyResp
20
20
  from .captcha import Captcha
21
21
  from .encrypt import PasswdEncoder, TeaEncoder
22
22
 
@@ -160,13 +160,7 @@ class UpWebLogin(LoginBase[UpWebSession], _UpHookMixin):
160
160
  r.raise_for_status()
161
161
  rl = re.findall(r"'(.*?)'[,\)]", await r.text())
162
162
 
163
- rdict = dict(
164
- zip(
165
- ["code", "verifycode", "salt", "verifysession", "isRandSalt", "ptdrvs", "session"],
166
- rl,
167
- )
168
- )
169
- sess.set_check_result(CheckResp.model_validate(rdict))
163
+ sess.set_check_result(CheckRespValidator.validate_python(rl))
170
164
 
171
165
  async def send_sms_code(self, sess: UpWebSession):
172
166
  """Send verify sms (to get dynamic code)
@@ -4,9 +4,10 @@ from textwrap import dedent
4
4
  from typing import Dict, List, Union
5
5
 
6
6
  logger = logging.getLogger(__name__)
7
- JsonDict = Dict[Union[str, int], "JsonValue"]
7
+ BaseTypes = Union[int, float, bool, str]
8
+ JsonDict = Dict[BaseTypes, "JsonValue"]
8
9
  JsonList = List["JsonValue"]
9
- JsonValue = Union[bool, int, str, JsonDict, JsonList]
10
+ JsonValue = Union[BaseTypes, JsonDict, JsonList]
10
11
 
11
12
 
12
13
  class AstLoader:
@@ -27,7 +28,7 @@ class AstLoader:
27
28
  def visit_Name(self, node: ast.Name):
28
29
  if node.id in self.const:
29
30
  return self.const[node.id]
30
- return ast.Str(s=node.id)
31
+ return ast.Constant(value=node.id)
31
32
 
32
33
  @classmethod
33
34
  def json_loads(cls, js: str, filename: str = "stdin") -> JsonValue:
@@ -1,73 +0,0 @@
1
- import typing as t
2
-
3
- from pydantic import AliasPath, BaseModel, Field
4
-
5
-
6
- class PowCfg(BaseModel):
7
- prefix: str
8
- md5: str
9
-
10
-
11
- class CommonCaptchaConf(BaseModel):
12
- pow_cfg: PowCfg
13
- """Ians, duration = match_md5(pow_cfg)"""
14
- tdc_path: str
15
- """relative path to get tdc.js"""
16
-
17
-
18
- class CommonClickConf(BaseModel):
19
- data_type: str = Field(validation_alias=AliasPath("data_type", 0))
20
- mark_style: str
21
-
22
-
23
- class CommonBgElmConf(BaseModel):
24
- cfg: CommonClickConf = Field(validation_alias="click_cfg")
25
-
26
-
27
- class CommonRender(BaseModel):
28
- bg: CommonBgElmConf = Field(validation_alias="bg_elem_cfg")
29
-
30
-
31
- class Sprite(BaseModel):
32
- """Represents a sprite from a source material."""
33
-
34
- size_2d: t.List[int]
35
- """sprite size (w, h)"""
36
- sprite_pos: t.List[int]
37
- """sprite position on material (x, y)"""
38
-
39
- @property
40
- def height(self):
41
- return self.size_2d[1]
42
-
43
- @property
44
- def width(self):
45
- return self.size_2d[0]
46
-
47
- @property
48
- def box(self):
49
- l, t = self.sprite_pos
50
- return (l, t, l + self.width, l + self.height)
51
-
52
-
53
- class CaptchaData(BaseModel):
54
- common: CommonCaptchaConf = Field(alias="comm_captcha_cfg")
55
- render: dict[str, t.Any] = Field(alias="dyn_show_info")
56
-
57
-
58
- class PrehandleResp(BaseModel):
59
- captcha: t.Optional[CaptchaData] = Field(alias="data", default=None)
60
- sess: str
61
-
62
- capclass: int = 0
63
- log_js: str = ""
64
- randstr: str = ""
65
- sid: str = ""
66
- src_1: str = ""
67
- src_2: str = ""
68
- src_3: str = ""
69
- state: int = 0
70
- subcapclass: int = 0
71
- ticket: str = ""
72
- uip: str = ""
73
- """ipv4 / ipv6"""
File without changes