aioqzone 1.8.2.dev1__tar.gz → 1.8.3.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.8.2.dev1 → aioqzone-1.8.3.dev1}/PKG-INFO +3 -3
  2. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/README.md +1 -1
  3. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/pyproject.toml +4 -4
  4. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/api/__init__.py +2 -0
  5. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/api/profile.py +1 -0
  6. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/protocol/__init__.py +1 -0
  7. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/protocol/config.py +2 -0
  8. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/utils/regex.py +1 -0
  9. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/utils/retry.py +1 -2
  10. aioqzone-1.8.3.dev1/src/qqqr/__init__.py +4 -0
  11. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/qr/__init__.py +57 -11
  12. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/capsess.py +1 -2
  13. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/web.py +10 -5
  14. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/utils/iter.py +4 -4
  15. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/utils/jsjson.py +11 -7
  16. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/utils/net.py +2 -2
  17. aioqzone-1.8.2.dev1/src/qqqr/__init__.py +0 -4
  18. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/LICENSE +0 -0
  19. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/__init__.py +0 -0
  20. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/api/__init__.py +0 -0
  21. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/api/h5/__init__.py +0 -0
  22. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/api/h5/model.py +0 -0
  23. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/api/login/__init__.py +0 -0
  24. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/api/login/_base.py +0 -0
  25. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/exception.py +0 -0
  26. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/message.py +0 -0
  27. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/__init__.py +0 -0
  28. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/api/feed.py +0 -0
  29. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/api/request.py +0 -0
  30. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/api/response.py +0 -0
  31. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/model/protocol/entity.py +0 -0
  32. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/utils/__init__.py +0 -0
  33. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/utils/entity.py +0 -0
  34. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/aioqzone/utils/time.py +0 -0
  35. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/base.py +0 -0
  36. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/constant.py +0 -0
  37. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/exception.py +0 -0
  38. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/message.py +0 -0
  39. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/py.typed +0 -0
  40. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/qr/type.py +0 -0
  41. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/type.py +0 -0
  42. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/__init__.py +0 -0
  43. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/_model.py +0 -0
  44. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/__init__.py +0 -0
  45. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/_model.py +0 -0
  46. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/pil_utils.py +0 -0
  47. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/select/__init__.py +0 -0
  48. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/select/_types.py +0 -0
  49. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/slide/__init__.py +0 -0
  50. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/captcha/slide/_types.py +0 -0
  51. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/encrypt.py +0 -0
  52. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/up/h5.py +0 -0
  53. {aioqzone-1.8.2.dev1 → aioqzone-1.8.3.dev1}/src/qqqr/utils/encrypt.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aioqzone
3
- Version: 1.8.2.dev1
3
+ Version: 1.8.3.dev1
4
4
  Summary: A Python wrapper for Qzone login and H5 APIs.
5
5
  Home-page: https://github.com/aioqzone/aioqzone
6
6
  License: AGPL-3.0
@@ -23,7 +23,7 @@ Provides-Extra: slide-captcha
23
23
  Requires-Dist: aiohttp (>=3.8.5,<4.0.0)
24
24
  Requires-Dist: exceptiongroup (>=1.1.1,<2.0.0)
25
25
  Requires-Dist: pillow (>=10.0.1,<11.0.0)
26
- Requires-Dist: pychaosvm (>=0.3.4,<0.4.0)
26
+ Requires-Dist: pychaosvm (>=0.3.4,<0.5.0)
27
27
  Requires-Dist: pydantic (>=2.0.3,<3.0.0)
28
28
  Requires-Dist: pydantic-settings (>=2.0.2,<3.0.0)
29
29
  Requires-Dist: rsa (>=4.8,<5.0)
@@ -43,7 +43,7 @@ aioqzone封装了一些Qzone接口。
43
43
  [![python](https://img.shields.io/pypi/pyversions/aioqzone?logo=python&logoColor=white)][home]
44
44
  [![version](https://img.shields.io/pypi/v/aioqzone?logo=python)][pypi]
45
45
  [![style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
46
- [![discuss](https://img.shields.io/badge/dynamic/xml?style=social&logo=telegram&label=Discuss&query=%2F%2Fdiv%5B%40class%3D%22tgme_page_extra%22%5D&url=https%3A%2F%2Ft.me%2Faioqzone_chatroom)](https://t.me/aioqzone_chatrooom)
46
+ [![discuss](https://img.shields.io/badge/dynamic/xml?style=social&logo=telegram&label=Discuss&query=%2F%2Fdiv%5B%40class%3D%22tgme_page_extra%22%5D&url=https%3A%2F%2Ft.me%2Faioqzone_chatroom)](https://t.me/aioqzone_chatroom)
47
47
 
48
48
  [English](README_en.md) | 简体中文
49
49
 
@@ -5,7 +5,7 @@ aioqzone封装了一些Qzone接口。
5
5
  [![python](https://img.shields.io/pypi/pyversions/aioqzone?logo=python&logoColor=white)][home]
6
6
  [![version](https://img.shields.io/pypi/v/aioqzone?logo=python)][pypi]
7
7
  [![style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
8
- [![discuss](https://img.shields.io/badge/dynamic/xml?style=social&logo=telegram&label=Discuss&query=%2F%2Fdiv%5B%40class%3D%22tgme_page_extra%22%5D&url=https%3A%2F%2Ft.me%2Faioqzone_chatroom)](https://t.me/aioqzone_chatrooom)
8
+ [![discuss](https://img.shields.io/badge/dynamic/xml?style=social&logo=telegram&label=Discuss&query=%2F%2Fdiv%5B%40class%3D%22tgme_page_extra%22%5D&url=https%3A%2F%2Ft.me%2Faioqzone_chatroom)](https://t.me/aioqzone_chatroom)
9
9
 
10
10
  [English](README_en.md) | 简体中文
11
11
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "aioqzone"
3
- version = "1.8.2.dev1"
3
+ version = "1.8.3.dev1"
4
4
  description = "A Python wrapper for Qzone login and H5 APIs."
5
5
  authors = ["aioqzone <zzzzss990315@gmail.com>"]
6
6
  license = "AGPL-3.0"
@@ -36,7 +36,7 @@ tenacity = "^8.2.3"
36
36
  exceptiongroup = "^1.1.1"
37
37
  tylisten = "^2.1.3"
38
38
  pillow = "^10.0.1"
39
- pychaosvm = { version = "~0.3.4", source = "aioqzone-index" }
39
+ pychaosvm = { version = ">=0.3.4,<0.5.0", source = "aioqzone-index" }
40
40
  slide-tc = {version = "~0.1.1", optional = true, source = "aioqzone-index" }
41
41
 
42
42
  [tool.poetry.extras]
@@ -47,8 +47,8 @@ slide-captcha = ["slide-tc"]
47
47
  optional = false
48
48
 
49
49
  [tool.poetry.group.test.dependencies]
50
- pytest = "^7.4.0"
51
- pytest-asyncio = "~0.21.0"
50
+ pytest = "^8.2.0"
51
+ pytest-asyncio = "~0.21.2"
52
52
 
53
53
  [tool.poetry.group.dev]
54
54
  optional = true
@@ -12,6 +12,8 @@ TyHttpMethod = t.Union[t.Literal["GET"], t.Literal["POST"]]
12
12
 
13
13
 
14
14
  class QzoneApi(BaseModel, t.Generic[TyRequest, TyResponse]):
15
+ """The base class for all Qzone APIs below."""
16
+
15
17
  host: t.ClassVar[str] = "https://h5.qzone.qq.com"
16
18
  http_method: t.ClassVar[TyHttpMethod]
17
19
  path: t.ClassVar[str]
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Qzone uses different feed schemes for ``/mqzone/profile``. This module patches :mod:`.feed`.
3
3
  """
4
+
4
5
  import typing as t
5
6
 
6
7
  from pydantic import AliasPath, BaseModel, Field, HttpUrl, field_validator, model_validator
@@ -1,6 +1,7 @@
1
1
  """This module defines types that are used internally by aioqzone and its plugins.
2
2
  These types are not designed to represent responses from Qzone.
3
3
  """
4
+
4
5
  from pydantic import BaseModel
5
6
 
6
7
  from .config import *
@@ -26,3 +26,5 @@ class QrLoginConfig(LoginConfig):
26
26
  """Maximum QR code refresh times."""
27
27
  poll_freq: float = 3
28
28
  """QR status polling interval."""
29
+ no_push: bool = False
30
+ """Do not try to push the QR code to user's client."""
@@ -1,6 +1,7 @@
1
1
  """
2
2
  some patterns for matching html and so on.
3
3
  """
4
+
4
5
  import re
5
6
 
6
7
  # use this to match qzone api response
@@ -14,8 +14,7 @@ class RetryIfCode(retry_if_exception, Generic[_E]):
14
14
 
15
15
  @classmethod
16
16
  @abstractmethod
17
- def get_code(cls, exc: _E) -> int:
18
- ...
17
+ def get_code(cls, exc: _E) -> int: ...
19
18
 
20
19
  def __init__(self, *code: int) -> None:
21
20
  super().__init__(lambda exc: isinstance(exc, self._exc_cls) and self.get_code(exc) in code)
@@ -0,0 +1,4 @@
1
+ """
2
+ QQQR is an API-level simulation of Qzone web login process. Currently this package
3
+ includes QR login and password login. A captcha verifier is also contained to pass TDC.
4
+ """
@@ -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,6 +117,11 @@ 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:
120
+ """This method will call ``ptqrshow`` api and wrap the response QR bytes into :class:`QR`.
121
+
122
+ :param push_qr: push QR to mobile client.
123
+ :return: a :class:`QR` object.
124
+ """
91
125
  data = {
92
126
  "appid": self.app.appid,
93
127
  "daid": self.app.daid,
@@ -125,7 +159,7 @@ class QrLogin(LoginBase[QrSession], _QrHookMixin):
125
159
  async def poll(self, sess: QrSession) -> PollResp:
126
160
  """Poll QR status.
127
161
 
128
- :raise `httpx.HTTPStatusError`: if response status code != 200
162
+ :raise `aiohttp.ClientResponseError`: if response status code != 200
129
163
 
130
164
  :return: a poll response object
131
165
  """
@@ -166,26 +200,37 @@ class QrLogin(LoginBase[QrSession], _QrHookMixin):
166
200
  *,
167
201
  refresh_times: int = 6,
168
202
  poll_freq: float = 3,
203
+ no_push=False,
169
204
  ):
170
- """Loop until cookie is returned or max `refresh_times` exceeds.
171
- - This method will emit :meth:`QrEvent.QrFetched` event if a new qrcode is fetched.
172
- - If qr is not scanned after `refresh_times`, it will raise :exc:`asyncio.TimeoutError`.
173
- - If :obj:`QrEvent.refresh_flag` is set, it will refresh qrcode at once without increasing expire counter.
174
- - If :obj:`QrEvent.cancel_flag` is set, it will raise :exc:`UserBreak` before next polling.
205
+ """Loop until cookie is returned or max :obj:`refresh_times` exceeds.
206
+
207
+ - This method will emit :obj:`.qr_fetched` event if a new qrcode is fetched.
208
+
209
+ - If the QR code is not scanned after :obj:`refresh_times`,
210
+ it will raise :exc:`~qqqr.exception.UserTimeout`.
211
+
212
+ - If :obj:`.refresh` is set, it will refresh qrcode at once without increasing expire counter.
213
+
214
+ - If :obj:`.cancel` is set, it will raise :exc:`~qqqr.exception.UserBreak` before next polling.
175
215
 
176
216
  :meta public:
177
217
  :param refresh_times: max qr expire times.
178
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.
220
+
221
+ :raise `UserTimeout`: if the QR code is not scanned after :obj:`refresh_times` expires.
222
+ :raise `UserBreak`: if :obj:`.cancel` is set.
223
+
224
+ .. versionchanged:: 1.8.3
179
225
 
180
- :raise `UserTimeout`: if qr is not scanned after `refresh_times` expires.
181
- :raise `UserBreak`: if :obj:`QrEvent.cancel_flag` is set.
226
+ Added :obj:`no_push` param.
182
227
  """
183
228
  self.refresh.clear()
184
229
  self.cancel.clear()
185
230
 
186
231
  cnt_expire = 0
187
232
  renew = False
188
- sess = await self.new()
233
+ sess = await self.new(no_push)
189
234
 
190
235
  while cnt_expire < refresh_times:
191
236
  # BUG: should we wrap hook errors here?
@@ -202,6 +247,7 @@ class QrLogin(LoginBase[QrSession], _QrHookMixin):
202
247
  await asyncio.sleep(poll_freq)
203
248
  stat = await self.poll(sess)
204
249
  if stat.code == StatusCode.Expired:
250
+ sess.current_qr.expired = True
205
251
  cnt_expire += 1
206
252
  break
207
253
  elif stat.code == StatusCode.Authenticated:
@@ -88,8 +88,7 @@ class BaseTcaptchaSession(ABC):
88
88
  )
89
89
 
90
90
  @abstractmethod
91
- async def get_captcha_problem(self, client: ClientAdapter):
92
- ...
91
+ async def get_captcha_problem(self, client: ClientAdapter): ...
93
92
 
94
93
  @abstractmethod
95
94
  async def solve_captcha(self) -> str:
@@ -99,6 +99,7 @@ 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):
@@ -132,17 +133,21 @@ class UpWebLogin(LoginBase[UpWebSession], _UpHookMixin):
132
133
  self.client, self.app.appid, str(self.login_page_url), fake_ip=fake_ip
133
134
  )
134
135
 
135
- async def new(self):
136
- """Create a :class:`UpWebSession`. This will call `check` api of Qzone, and receive result
137
- about whether this login needs a captcha, sms verification, etc.
136
+ async def new(self) -> UpWebSession:
137
+ """Create a :class:`UpWebSession`. This will trigger a GET to ``xlogin`` url.
138
138
 
139
- :raise `httpx.HTTPStatusError`:
139
+ :raise `aiohttp.ClientResponseError`: if response status != 200
140
140
 
141
141
  :return: a up login session
142
142
  """
143
143
  return UpWebSession(await self._pt_login_sig())
144
144
 
145
145
  async def check(self, sess: UpWebSession):
146
+ """This will call ``check`` api of Qzone, and receive result about
147
+ whether this login needs a captcha, sms verification, etc.
148
+
149
+ :param sess: Session got from :meth:`~UpWebLogin.new`.
150
+ """
146
151
  data = {
147
152
  "regmaster": "",
148
153
  "pt_tea": 2,
@@ -217,7 +222,7 @@ class UpWebLogin(LoginBase[UpWebSession], _UpHookMixin):
217
222
  data.update(const)
218
223
  return data
219
224
 
220
- async def try_login(self, sess: UpWebSession):
225
+ async def try_login(self, sess: UpWebSession) -> LoginResp:
221
226
  """
222
227
  Check if current session meets the login condition.
223
228
  It takes a session object and returns response of this try.
@@ -5,8 +5,9 @@ D = t.TypeVar("D")
5
5
 
6
6
 
7
7
  @t.overload
8
- def first(it: t.Iterable[T], pred: t.Optional[t.Callable[[T], t.Optional[object]]] = None) -> T:
9
- ...
8
+ def first(
9
+ it: t.Iterable[T], pred: t.Optional[t.Callable[[T], t.Optional[object]]] = None
10
+ ) -> T: ...
10
11
 
11
12
 
12
13
  @t.overload
@@ -15,8 +16,7 @@ def first(
15
16
  pred: t.Optional[t.Callable[[T], t.Optional[object]]] = None,
16
17
  *,
17
18
  default: D,
18
- ) -> t.Union[T, D]:
19
- ...
19
+ ) -> t.Union[T, D]: ...
20
20
 
21
21
 
22
22
  def first(
@@ -10,9 +10,13 @@ JsonValue = Union[bool, int, str, JsonDict, JsonList]
10
10
 
11
11
 
12
12
  class AstLoader:
13
- """`AstLoader` uses standard :mod:`ast` module to parse the js/json"""
13
+ """:class:`AstLoader` uses standard :mod:`ast` module to parse the js/json"""
14
14
 
15
15
  class RewriteUndef(ast.NodeTransformer):
16
+ """
17
+ :meta private:
18
+ """
19
+
16
20
  const = {
17
21
  "undefined": ast.Constant(value=None),
18
22
  "null": ast.Constant(value=None),
@@ -28,12 +32,12 @@ class AstLoader:
28
32
  @classmethod
29
33
  def json_loads(cls, js: str, filename: str = "stdin") -> JsonValue:
30
34
  """
31
- The json_loads function loads a JSON object from a js/json string. It uses standard
35
+ The :meth:`~AstLoader.json_loads` function loads a JSON object from a js/json string. It uses standard
32
36
  :mod:`ast` module to parse the js/json.
33
37
 
34
38
  :param js: Used to Pass the js/json string to be parsed.
35
39
  :param filename: Used to Specify the name of the file that is being read. This is only for debug use.
36
- :return: A jsonvalue object.
40
+ :return: A :obj:`JsonValue` object.
37
41
  """
38
42
  js = dedent(js).replace(r"\/", "/")
39
43
  node = ast.parse(js, mode="eval")
@@ -43,14 +47,14 @@ class AstLoader:
43
47
 
44
48
 
45
49
  def json_loads(js: str) -> JsonValue:
46
- """The json_loads function converts a string representation of JS/JSON data into a Python object.
50
+ """The :meth:`json_loads` function converts a string representation of JS/JSON data into a Python object.
47
51
  Current implementation is using :external+python:mod:`ast`.
48
52
 
49
- If you need more parameters or another implementation, call `xxxLoader.json_loads` instead.
50
-
51
53
  .. seealso:: :meth:`.AstLoader.json_loads`
52
54
 
55
+ If you need more parameters or another implementation, call ``xxxLoader.json_loads`` instead.
56
+
53
57
  :param js: Used to Pass the JS/JSON string.
54
- :return: A jsonvalue object.
58
+ :return: A :obj:`JsonValue` object.
55
59
  """
56
60
  return AstLoader.json_loads(js)
@@ -9,12 +9,12 @@ __all__ = ["raise_for_status", "get_all_cookie", "ClientAdapter"]
9
9
 
10
10
 
11
11
  def raise_for_status(response: Response, *accept_code: int):
12
- """A checker more strict than :meth:`~httpx.Response.raise_for_status`.
12
+ """A checker more strict than :meth:`~aiohttp.ClientResponse.raise_for_status`.
13
13
 
14
14
  :param response: Client response to check.
15
15
  :param accept_code: Overwrite codes that can be accepted, If not given, default is `(200, )`
16
16
 
17
- :raise `httpx.HTTPStatusError`: if status not in :obj:`accept_code`
17
+ :raise `aiohttp.ClientResponseError`: if status not in :obj:`accept_code`
18
18
  """
19
19
  response.raise_for_status
20
20
  accept_code = accept_code or (200,)
@@ -1,4 +0,0 @@
1
- """
2
- QQQR is a simulation of Qzone web login process. Including QR login and password login.
3
- A captcha verifier is also contained to pass TDC.
4
- """
File without changes