aioqzone 0.12.13.dev2__tar.gz → 0.13.0.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 (65) hide show
  1. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/PKG-INFO +5 -14
  2. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/README.md +0 -9
  3. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/pyproject.toml +21 -14
  4. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/h5/raw.py +7 -5
  5. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/loginman/__init__.py +0 -6
  6. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/web/raw.py +30 -18
  7. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/html.py +8 -7
  8. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/qr/__init__.py +1 -2
  9. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/up/captcha/__init__.py +39 -44
  10. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/up/encrypt.py +2 -42
  11. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/up/h5.py +2 -2
  12. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/up/web.py +24 -17
  13. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/utils/iter.py +5 -1
  14. {aioqzone-0.12.13.dev2/src/jssupport → aioqzone-0.13.0.dev2/src/qqqr/utils}/jsjson.py +0 -35
  15. aioqzone-0.12.13.dev2/src/jssupport/__init__.py +0 -5
  16. aioqzone-0.12.13.dev2/src/jssupport/exception.py +0 -27
  17. aioqzone-0.12.13.dev2/src/jssupport/execjs.py +0 -174
  18. aioqzone-0.12.13.dev2/src/jssupport/jsdom.py +0 -71
  19. aioqzone-0.12.13.dev2/src/jssupport/py.typed +0 -0
  20. aioqzone-0.12.13.dev2/src/qqqr/up/captcha/archive/collect_env.min.js +0 -1
  21. aioqzone-0.12.13.dev2/src/qqqr/up/captcha/archive/decrypt.js +0 -168
  22. aioqzone-0.12.13.dev2/src/qqqr/up/captcha/archive/vdata.js +0 -158
  23. aioqzone-0.12.13.dev2/src/qqqr/up/captcha/vm.py +0 -73
  24. aioqzone-0.12.13.dev2/src/qqqr/utils/daug.py +0 -25
  25. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/LICENSE +0 -0
  26. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/__init__.py +0 -0
  27. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/__init__.py +0 -0
  28. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/h5/__init__.py +0 -0
  29. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/h5/model.py +0 -0
  30. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/loginman/_base.py +0 -0
  31. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/web/__init__.py +0 -0
  32. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/web/constant.py +0 -0
  33. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/web/model.py +0 -0
  34. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/event/__init__.py +0 -0
  35. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/event/login.py +0 -0
  36. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/exception.py +0 -0
  37. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/py.typed +0 -0
  38. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/type/__init__.py +0 -0
  39. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/type/entity.py +0 -0
  40. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/type/internal.py +0 -0
  41. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/type/resp/__init__.py +0 -0
  42. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/type/resp/h5.py +0 -0
  43. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/type/resp/web.py +0 -0
  44. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/__init__.py +0 -0
  45. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/catch.py +0 -0
  46. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/entity.py +0 -0
  47. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/regex.py +0 -0
  48. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/time.py +0 -0
  49. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/__init__.py +0 -0
  50. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/base.py +0 -0
  51. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/constant.py +0 -0
  52. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/event/__init__.py +0 -0
  53. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/event/evt.py +0 -0
  54. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/event/evtmgr.py +0 -0
  55. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/event/login.py +0 -0
  56. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/exception.py +0 -0
  57. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/py.typed +0 -0
  58. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/qr/type.py +0 -0
  59. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/ssl.py +0 -0
  60. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/type.py +0 -0
  61. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/up/__init__.py +0 -0
  62. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/up/captcha/jigsaw.py +0 -0
  63. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/up/type.py +0 -0
  64. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/utils/encrypt.py +0 -0
  65. {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/utils/net.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aioqzone
3
- Version: 0.12.13.dev2
3
+ Version: 0.13.0.dev2
4
4
  Summary: Python wrapper for Qzone login and Qzone HTTP APIs.
5
5
  Home-page: https://github.com/aioqzone/aioqzone
6
6
  License: AGPL-3.0
@@ -19,13 +19,13 @@ Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
20
  Classifier: Typing :: Typed
21
21
  Provides-Extra: captcha
22
- Provides-Extra: lxml
23
- Requires-Dist: cssselect (>=1.1.0,<2.0.0) ; extra == "lxml"
22
+ Requires-Dist: cssselect (>=1.1.0,<2.0.0)
24
23
  Requires-Dist: exceptiongroup (>=1.1.1,<2.0.0) ; python_version < "3.11"
25
- Requires-Dist: httpx (>=0.23.0,<0.25.0)
26
- Requires-Dist: lxml (>=4.9.1,<5.0.0) ; extra == "lxml"
24
+ Requires-Dist: httpx (>=0.24.0,<0.25.0)
25
+ Requires-Dist: lxml (>=4.9.1,<5.0.0)
27
26
  Requires-Dist: numpy (>=1.22.3,<2.0.0) ; extra == "captcha"
28
27
  Requires-Dist: pillow (>=9.4.0,<10.0.0) ; extra == "captcha"
28
+ Requires-Dist: pychaosvm (>=0.2.3,<0.3.0) ; extra == "captcha"
29
29
  Requires-Dist: pydantic (>=1.10.4,<2.0.0)
30
30
  Requires-Dist: rsa (>=4.8,<5.0)
31
31
  Project-URL: Bug Tracker, https://github.com/aioqzone/aioqzone/issues
@@ -39,7 +39,6 @@ Description-Content-Type: text/markdown
39
39
  aioqzone封装了一些Qzone接口。
40
40
 
41
41
  [![python](https://img.shields.io/pypi/pyversions/aioqzone?logo=python&logoColor=white)][home]
42
- [![QQQR](https://github.com/aioqzone/aioqzone/actions/workflows/qqqr.yml/badge.svg?branch=beta&event=schedule)](https://github.com/aioqzone/aioqzone/actions/workflows/qqqr.yml)
43
42
  [![version](https://img.shields.io/pypi/v/aioqzone?logo=python)][pypi]
44
43
  [![style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
45
44
  [![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)
@@ -76,19 +75,11 @@ __在做了:__
76
75
 
77
76
  - [ ] 完善的测试覆盖
78
77
 
79
- ## node 依赖
80
-
81
- - `jssupport.jsjson.AstLoader` 不需要借助其他进程;
82
- - 要使用 `jssupport.execjs` 和 `jssupport.jsjson.NodeLoader`,您(至少)需要安装 `Node.js` >= v14;
83
- - 要使用 `jssupport.jsdom`,您需要安装 `jsdom` 和 `canvas` 两个 npm 包。
84
- - 验证码部分需要使用 `canvas`,因此您需要正确配置运行环境内的 font config ([#45](https://github.com/aioqzone/aioqzone/issues/45)).
85
-
86
78
  ## 包描述
87
79
 
88
80
  |包名 |简述 |
89
81
  |-----------|-------------------|
90
82
  |aioqzone |封装Qzone API |
91
- |jssupport |执行JS |
92
83
  |qqqr |网页登录 |
93
84
 
94
85
  ## 例子
@@ -3,7 +3,6 @@
3
3
  aioqzone封装了一些Qzone接口。
4
4
 
5
5
  [![python](https://img.shields.io/pypi/pyversions/aioqzone?logo=python&logoColor=white)][home]
6
- [![QQQR](https://github.com/aioqzone/aioqzone/actions/workflows/qqqr.yml/badge.svg?branch=beta&event=schedule)](https://github.com/aioqzone/aioqzone/actions/workflows/qqqr.yml)
7
6
  [![version](https://img.shields.io/pypi/v/aioqzone?logo=python)][pypi]
8
7
  [![style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
9
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)
@@ -40,19 +39,11 @@ __在做了:__
40
39
 
41
40
  - [ ] 完善的测试覆盖
42
41
 
43
- ## node 依赖
44
-
45
- - `jssupport.jsjson.AstLoader` 不需要借助其他进程;
46
- - 要使用 `jssupport.execjs` 和 `jssupport.jsjson.NodeLoader`,您(至少)需要安装 `Node.js` >= v14;
47
- - 要使用 `jssupport.jsdom`,您需要安装 `jsdom` 和 `canvas` 两个 npm 包。
48
- - 验证码部分需要使用 `canvas`,因此您需要正确配置运行环境内的 font config ([#45](https://github.com/aioqzone/aioqzone/issues/45)).
49
-
50
42
  ## 包描述
51
43
 
52
44
  |包名 |简述 |
53
45
  |-----------|-------------------|
54
46
  |aioqzone |封装Qzone API |
55
- |jssupport |执行JS |
56
47
  |qqqr |网页登录 |
57
48
 
58
49
  ## 例子
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "aioqzone"
3
- version = "0.12.13.dev2"
3
+ version = "0.13.0.dev2"
4
4
  description = "Python wrapper for Qzone login and Qzone HTTP APIs."
5
5
  authors = ["aioqzone <zzzzss990315@gmail.com>"]
6
6
  license = "AGPL-3.0"
@@ -18,7 +18,6 @@ classifiers = [
18
18
 
19
19
  packages = [
20
20
  { include = "aioqzone", from = "src" },
21
- { include = "jssupport", from = "src" },
22
21
  { include = "qqqr", from = "src" },
23
22
  ]
24
23
  exclude = ["*.js"]
@@ -29,27 +28,26 @@ exclude = ["*.js"]
29
28
 
30
29
  [tool.poetry.dependencies]
31
30
  python = "^3.8"
32
- httpx = ">=0.23.0,<0.25.0"
31
+ httpx = "^0.24.0"
33
32
  pydantic = "^1.10.4"
34
33
  rsa = "^4.8"
35
- exceptiongroup = { version = "^1.1.1", python = "<3.11" }
34
+ lxml = "^4.9.1"
35
+ cssselect = "^1.1.0"
36
+ exceptiongroup = { version = "^1.1.1", python = "<3.11"}
36
37
 
37
- lxml = { version = "^4.9.1", optional = false }
38
- cssselect = { version = "^1.1.0", optional = false }
39
-
40
- numpy = { version = "^1.22.3", optional = false }
41
- pillow = { version = "^9.4.0", optional = false }
38
+ numpy = { version = "^1.22.3", optional = true}
39
+ pillow = { version = "^9.4.0", optional = true}
40
+ pychaosvm = { version = "^0.2.3", optional = true, source = "aioqzone-index" }
42
41
 
43
42
  [tool.poetry.extras]
44
- captcha = ["numpy", "pillow"]
45
- lxml = ["lxml", "cssselect"]
43
+ captcha = ["numpy", "pillow", "pychaosvm"]
46
44
 
47
45
  # dependency groups
48
46
  [tool.poetry.group.test]
49
47
  optional = false
50
48
 
51
49
  [tool.poetry.group.test.dependencies]
52
- pytest = "^7.2.1"
50
+ pytest = "^7.4.0"
53
51
  pytest-asyncio = "~0.21.0"
54
52
 
55
53
  [tool.poetry.group.dev]
@@ -64,12 +62,21 @@ isort = "*"
64
62
  optional = true
65
63
 
66
64
  [tool.poetry.group.docs.dependencies]
67
- Sphinx = "^6.1.3"
65
+ Sphinx = "^7.0.1"
68
66
  autodoc-pydantic = "*"
69
67
  sphinx-autodoc-typehints = "^1.19.5"
70
- sphinx-rtd-theme = "*"
68
+ furo = "*"
71
69
  sphinx-intl = "*"
72
70
 
71
+ [[tool.poetry.source]]
72
+ name = "aioqzone-index"
73
+ url = "https://aioqzone.github.io/aioqzone-index/simple"
74
+ priority = "supplemental"
75
+
76
+ [[tool.poetry.source]]
77
+ name = "PyPI"
78
+ priority = "primary"
79
+
73
80
  [build-system]
74
81
  requires = ["poetry-core>=1.0.0"]
75
82
  build-backend = "poetry.core.masonry.api"
@@ -9,7 +9,8 @@ from aioqzone.api.loginman import Loginable
9
9
  from aioqzone.exception import QzoneError
10
10
  from aioqzone.utils.catch import HTTPStatusErrorDispatch, QzoneErrorDispatch
11
11
  from aioqzone.utils.regex import entire_closing, response_callback
12
- from jssupport.jsjson import JsonValue, json_loads
12
+ from qqqr.utils.iter import firstn
13
+ from qqqr.utils.jsjson import JsonValue, json_loads
13
14
  from qqqr.utils.net import ClientAdapter
14
15
 
15
16
  StrDict = Dict[str, JsonValue]
@@ -102,7 +103,8 @@ class QzoneH5RawAPI:
102
103
  """
103
104
 
104
105
  with QzoneErrorDispatch() as qze, HTTPStatusErrorDispatch() as hse:
105
- qze.dispatch(-3000, suppress=lambda e: "登录" in e.msg) # -3000: 请先登录
106
+ # NOTE: 尽管只有“-3000: 请先登录”明确要求重新登录,但似乎任何原因的-3000错误值都意味着cookie过期。因此移除了对message的校验
107
+ qze.dispatch(-3000)
106
108
  qze.dispatch(-10000)
107
109
  hse.dispatch(302, 403)
108
110
  return await func(*args, **kwds)
@@ -150,13 +152,13 @@ class QzoneH5RawAPI:
150
152
 
151
153
  assert isinstance(r, dict)
152
154
 
153
- err = next(filter(lambda i: i is not None, (r.get(i) for i in errno_key)), None)
155
+ err = firstn((r.get(i) for i in errno_key), lambda i: i is not None)
154
156
  assert err is not None, f"no {errno_key} in {r.keys()}"
155
157
  assert isinstance(err, (int, str))
156
158
  err = int(err)
157
159
 
158
160
  if err != 0:
159
- msg = next(filter(None, (r.get(i) for i in msg_key)), None)
161
+ msg = firstn((r.get(i) for i in msg_key), lambda i: i is not None)
160
162
  if msg:
161
163
  raise QzoneError(err, msg, rdict=r)
162
164
  else:
@@ -183,7 +185,7 @@ class QzoneH5RawAPI:
183
185
  raise RuntimeError("script tag not found")
184
186
 
185
187
  texts: List[str] = [s.text for s in scripts]
186
- script = next(filter(lambda s: "shine0callback" in s, texts), None)
188
+ script = firstn(texts, lambda s: "shine0callback" in s)
187
189
  if not script:
188
190
  raise RuntimeError("data script not found")
189
191
 
@@ -11,7 +11,6 @@ from httpx import ConnectError, HTTPError
11
11
 
12
12
  from aioqzone.event.login import LoginMethod, QREvent, UPEvent
13
13
  from aioqzone.exception import LoginError, SkipLoginInterrupt
14
- from jssupport.exception import JsImportError, JsRuntimeError, NodeNotFoundError
15
14
  from qqqr.constant import QzoneH5Proxy, StatusCode
16
15
  from qqqr.event import Emittable, EventManager
17
16
  from qqqr.exception import HookError, TencentLoginError, UserBreak
@@ -21,7 +20,6 @@ from qqqr.utils.net import ClientAdapter
21
20
  from ._base import Loginable
22
21
 
23
22
  log = logging.getLogger(__name__)
24
- JsError = JsRuntimeError, JsImportError, NodeNotFoundError
25
23
 
26
24
 
27
25
  class _NextMethodInterrupt(RuntimeError):
@@ -95,10 +93,6 @@ class UPLoginMan(Loginable, Emittable[UPEvent]):
95
93
  raise TencentLoginError(
96
94
  StatusCode.NeedSmsVerify, "Dynamic code verify not implemented"
97
95
  ) from e
98
- except JsError as e:
99
- log.error(str(e), exc_info=e)
100
- emit_hook(self.hook.LoginFailed(meth, "JS调用出错"))
101
- raise TencentLoginError(StatusCode.NeedCaptcha, "Failed to pass captcha") from e
102
96
  except HookError as e:
103
97
  log.warning(f"HookError occured in {e.hook}", exc_info=e)
104
98
  emit_hook(self.hook.LoginFailed(meth, str(e)))
@@ -17,8 +17,8 @@ from aioqzone.type.internal import AlbumData, LikeData
17
17
  from aioqzone.utils.catch import HTTPStatusErrorDispatch, QzoneErrorDispatch
18
18
  from aioqzone.utils.regex import response_callback
19
19
  from aioqzone.utils.time import time_ms
20
- from jssupport.jsjson import JsonValue, json_loads
21
- from qqqr.utils.daug import du
20
+ from qqqr.utils.iter import firstn
21
+ from qqqr.utils.jsjson import JsonValue, json_loads
22
22
  from qqqr.utils.net import ClientAdapter, raise_for_status
23
23
 
24
24
  logger = logging.getLogger(__name__)
@@ -156,13 +156,13 @@ class QzoneWebRawAPI:
156
156
  r = json_loads(rtext)
157
157
  assert isinstance(r, dict)
158
158
 
159
- err = next(filter(lambda i: i is not None, (r.get(i) for i in errno_key)), None)
159
+ err = firstn((r.get(i) for i in errno_key), lambda i: i is not None)
160
160
  assert err is not None, f"no {errno_key} in {r.keys()}"
161
161
  assert isinstance(err, (int, str))
162
162
  err = int(err)
163
163
 
164
164
  if err != 0:
165
- msg = next(filter(None, (r.get(i) for i in msg_key)), None)
165
+ msg = firstn((r.get(i) for i in msg_key), lambda i: i is not None)
166
166
  if msg:
167
167
  raise QzoneError(err, msg, rdict=r)
168
168
  else:
@@ -238,10 +238,11 @@ class QzoneWebRawAPI:
238
238
  "usertime": time_ms(),
239
239
  "externparam": external,
240
240
  }
241
+ query.update(default)
241
242
 
242
243
  @self._relogin_retry
243
244
  async def retry_closure():
244
- async with self.host_get(const.feeds3_html_more, du(default, query)) as r:
245
+ async with self.host_get(const.feeds3_html_more, query) as r:
245
246
  r.raise_for_status()
246
247
  rtext = r.text
247
248
 
@@ -288,11 +289,12 @@ class QzoneWebRawAPI:
288
289
  "tid": tid,
289
290
  "feedsType": feedstype,
290
291
  }
291
- logger.debug("emotion_getcomments post data:", body)
292
+ logger.debug(f"emotion_getcomments post data: {body}")
293
+ body.update(default)
292
294
 
293
295
  @self._relogin_retry
294
296
  async def retry_closure():
295
- async with self.host_post(const.emotion_getcomments, data=du(default, body)) as r:
297
+ async with self.host_post(const.emotion_getcomments, data=body) as r:
296
298
  r.raise_for_status()
297
299
  rtext = r.text
298
300
 
@@ -326,10 +328,11 @@ class QzoneWebRawAPI:
326
328
  "need_private_comment": 1,
327
329
  }
328
330
  query = {"uin": owner, "tid": fid}
331
+ query.update(default)
329
332
 
330
333
  @self._relogin_retry
331
334
  async def retry_closure():
332
- async with self.host_get(const.emotion_msgdetail, params=du(default, query)) as r:
335
+ async with self.host_get(const.emotion_msgdetail, params=query) as r:
333
336
  r.raise_for_status()
334
337
  return self._rtext_handler(r.text)
335
338
 
@@ -387,12 +390,14 @@ class QzoneWebRawAPI:
387
390
  "abstime": likedata.abstime,
388
391
  "fid": likedata.fid,
389
392
  }
390
- logger.debug("like_app post data:", body)
393
+ logger.debug(f"like_app post data: {body}")
394
+
395
+ body.update(default)
391
396
  url = const.internal_dolike_app if like else const.internal_unlike_app
392
397
 
393
398
  @self._relogin_retry
394
399
  async def retry_closure():
395
- async with self.host_post(url, data=du(default, body)) as r:
400
+ async with self.host_post(url, data=body) as r:
396
401
  r.raise_for_status()
397
402
  return self._rtext_handler(r.text, errno_key=("code", "ret"))
398
403
 
@@ -454,10 +459,11 @@ class QzoneWebRawAPI:
454
459
  "t": randint(int(1e8), int(1e9 - 1))
455
460
  # The distribution is not consistent with photo.js; but the format is.
456
461
  }
462
+ query.update(default)
457
463
 
458
464
  @self._relogin_retry
459
465
  async def retry_closure():
460
- async with self.host_get(const.floatview_photo_list, du(default, query)) as r:
466
+ async with self.host_get(const.floatview_photo_list, query) as r:
461
467
  r.raise_for_status()
462
468
  return self._rtext_handler(r.text)
463
469
 
@@ -506,9 +512,12 @@ class QzoneWebRawAPI:
506
512
  "need_private_comment": 1,
507
513
  }
508
514
 
515
+ if pos:
516
+ param.update(add)
517
+
509
518
  @self._relogin_retry
510
519
  async def retry_closure():
511
- async with self.host_get(const.emotion_msglist, du(param, add) if pos else param) as r:
520
+ async with self.host_get(const.emotion_msglist, param) as r:
512
521
  r.raise_for_status()
513
522
  rtext = r.text
514
523
  return self._rtext_handler(rtext)
@@ -551,11 +560,12 @@ class QzoneWebRawAPI:
551
560
  "feedversion": 1,
552
561
  "hostuin": self.login.uin,
553
562
  }
554
- logger.debug("emotion_publish post data:", body)
563
+ logger.debug(f"emotion_publish post data: {body}")
564
+ body.update(default)
555
565
 
556
566
  @self._relogin_retry
557
567
  async def retry_closure():
558
- async with self.host_post(const.emotion_publish, data=du(default, body)) as r:
568
+ async with self.host_post(const.emotion_publish, data=body) as r:
559
569
  r.raise_for_status()
560
570
  return self._rtext_handler(r.text)
561
571
 
@@ -649,11 +659,12 @@ class QzoneWebRawAPI:
649
659
  "hostuin": uin or self.login.uin,
650
660
  # 'pic_bo': ''
651
661
  }
652
- logger.debug("emotion_update post data:", body)
662
+ logger.debug(f"emotion_update post data: {body}")
663
+ body.update(default)
653
664
 
654
665
  @self._relogin_retry
655
666
  async def retry_closure():
656
- async with self.host_post(const.emotion_update, data=du(default, body)) as r:
667
+ async with self.host_post(const.emotion_update, data=body) as r:
657
668
  r.raise_for_status()
658
669
  return self._rtext_handler(r.text)
659
670
 
@@ -706,11 +717,12 @@ class QzoneWebRawAPI:
706
717
  private=int(is_private),
707
718
  paramstr=1,
708
719
  )
709
- logger.debug("emotion_re_feeds post data:", data)
720
+ logger.debug(f"emotion_re_feeds post data: {data}")
721
+ data.update(default)
710
722
 
711
723
  @self._relogin_retry
712
724
  async def retry_closure():
713
- async with self.host_post(const.emotion_re_feeds, data=du(default, data)) as r:
725
+ async with self.host_post(const.emotion_re_feeds, data=data) as r:
714
726
  r.raise_for_status()
715
727
  return self._rtext_handler(r.text)
716
728
 
@@ -7,12 +7,13 @@ Use this module to get some data from Qzone html feed.
7
7
  """
8
8
  import logging
9
9
  import re
10
+ from contextlib import suppress
10
11
  from typing import Iterable, List, Optional, Union, cast
11
12
 
12
13
  from lxml.html import HtmlElement, fromstring
13
- from pydantic import BaseModel, HttpUrl
14
+ from pydantic import BaseModel, HttpUrl, ValidationError
14
15
 
15
- from qqqr.utils.daug import di
16
+ from qqqr.utils.iter import firstn
16
17
 
17
18
  from ..type.entity import ConEntity
18
19
  from ..type.internal import AlbumData
@@ -81,7 +82,7 @@ class HtmlContent(BaseModel):
81
82
  img_data = lambda a: {k[5:]: v for k, v in a.attrib.items() if k.startswith("data-")}
82
83
 
83
84
  def load_src(a: Iterable[HtmlElement]) -> Optional[HttpUrl]:
84
- o: Optional[HtmlElement] = next(filter(lambda i: i.tag == "img", a), None)
85
+ o = firstn(a, lambda i: i.tag == "img")
85
86
  if o is None:
86
87
  return
87
88
  src: str = o.get("src", "")
@@ -101,11 +102,11 @@ class HtmlContent(BaseModel):
101
102
 
102
103
  finfo: HtmlElement = mxsafe(root.cssselect("div.f-info"))
103
104
  lia: List[HtmlElement] = root.cssselect("div.f-ct a.img-item")
105
+ (d := img_data(lia[0]))["hostuin"] = hostuin
104
106
 
105
- try:
106
- album = AlbumData.parse_obj(di(img_data(lia[0]), hostuin=hostuin))
107
- except:
108
- album = None
107
+ album = None
108
+ with suppress(ValidationError):
109
+ album = AlbumData.parse_obj(d)
109
110
 
110
111
  pic = [
111
112
  PicRep(
@@ -11,7 +11,6 @@ from ..constant import StatusCode
11
11
  from ..event import Emittable, hook_guard
12
12
  from ..event.login import QrEvent
13
13
  from ..exception import UserBreak
14
- from ..utils.daug import du
15
14
  from ..utils.encrypt import hash33
16
15
 
17
16
  log = logging.getLogger(__name__)
@@ -86,7 +85,7 @@ class QrLogin(LoginBase[QrSession], Emittable[QrEvent]):
86
85
  "daid": self.app.daid,
87
86
  }
88
87
 
89
- async with self.client.get(POLL_QR, params=du(data, const)) as r:
88
+ async with self.client.get(POLL_QR, params=data.update(const) or data) as r:
90
89
  r.raise_for_status()
91
90
  rl = re.findall(r"'(.*?)'[,\)]", r.text)
92
91
 
@@ -3,20 +3,21 @@ import base64
3
3
  import json
4
4
  import logging
5
5
  import re
6
+ from contextlib import suppress
6
7
  from hashlib import md5
7
8
  from ipaddress import IPv4Address
8
9
  from random import random
9
10
  from time import time
10
- from typing import List, Type, TypeVar
11
+ from typing import List
11
12
 
13
+ from chaosvm import prepare
14
+ from chaosvm.proxy.dom import TDC
12
15
  from httpx import URL
13
16
 
14
- from ...utils.daug import du
15
17
  from ...utils.iter import first
16
18
  from ...utils.net import ClientAdapter
17
19
  from ..type import PrehandleResp, VerifyResp
18
20
  from .jigsaw import Jigsaw, imitate_drag
19
- from .vm import CollectEnv
20
21
 
21
22
  PREHANDLE_URL = "https://t.captcha.qq.com/cap_union_prehandle"
22
23
  SHOW_NEW_URL = "https://t.captcha.qq.com/cap_union_new_show"
@@ -27,8 +28,6 @@ time_ms = lambda: int(1e3 * time())
27
28
  rnd6 = lambda: str(random())[2:8]
28
29
  log = logging.getLogger(__name__)
29
30
 
30
- _TDC_TY = TypeVar("_TDC_TY", bound=CollectEnv)
31
-
32
31
 
33
32
  def hex_add(h: str, o: int):
34
33
  if h.endswith("#"):
@@ -56,8 +55,8 @@ class TcaptchaSession:
56
55
  self.cdn_imgs: List[bytes] = []
57
56
  self.piece_sprite = first(self.conf.render.sprites, lambda s: s.move_cfg)
58
57
 
59
- def set_js_env(self, tdc: CollectEnv):
60
- self.tdc = tdc
58
+ def set_drag_track(self, xs: List[float], ys: List[float]):
59
+ self.mouse_track = list(zip(xs, ys))
61
60
 
62
61
  def solve_workload(self, *, timeout: float = 30.0):
63
62
  """
@@ -88,6 +87,9 @@ class TcaptchaSession:
88
87
  def set_captcha_answer(self, left: int, top: int):
89
88
  self.jig_ans = left, top
90
89
 
90
+ def set_js_env(self, tdc: TDC):
91
+ self.tdc = tdc
92
+
91
93
  def _cdn(self, rel_path: str) -> URL:
92
94
  return URL("https://t.captcha.qq.com").join(rel_path)
93
95
 
@@ -163,7 +165,7 @@ class Captcha:
163
165
  "subsid": 1,
164
166
  "callback": CALLBACK,
165
167
  }
166
- async with self.client.get(PREHANDLE_URL, params=du(const, data)) as r:
168
+ async with self.client.get(PREHANDLE_URL, params=data.update(const) or data) as r:
167
169
  r.raise_for_status()
168
170
  m = re.search(CALLBACK + r"\((\{.*\})\)", r.text)
169
171
 
@@ -187,35 +189,11 @@ class Captcha:
187
189
  # BUG: should always bypass client's proxy settings
188
190
  async with self.client.get("https://" + api) as r:
189
191
  cand = r.text.strip()
190
- try:
192
+ with suppress(ValueError):
191
193
  IPv4Address(cand)
192
194
  return cand
193
- except ValueError:
194
- continue
195
195
  return ""
196
196
 
197
- async def get_tdc(self, sess: TcaptchaSession, *, cls: Type[_TDC_TY] = CollectEnv):
198
- """
199
- The get_tdc function is a coroutine that sets an instance of the :class:`TDC` class to `sess`.
200
-
201
- :param sess: captcha session
202
- :param cls: Specify the type of :class:`TDC` instance to be returned, default as :class:`TDC`.
203
- :return: None
204
- """
205
- js_url = sess.tdx_js_url()
206
- tdc = cls(
207
- xlogin_url=self.xlogin_url,
208
- ipv4=await self.get_ipv4(),
209
- ua=self.client.headers["User-Agent"],
210
- # iframe=await self.iframe(),
211
- )
212
-
213
- async with self.client.get(js_url) as r:
214
- r.raise_for_status()
215
- tdc.load_vm(r.text)
216
-
217
- sess.set_js_env(tdc)
218
-
219
197
  async def get_captcha_problem(self, sess: TcaptchaSession):
220
198
  """
221
199
  The get_captcha_problem function is a coroutine that accepts a TcaptchaSession object as an argument.
@@ -263,37 +241,54 @@ class Captcha:
263
241
  sess.set_captcha_answer(left, jig.top)
264
242
 
265
243
  xs, ys = imitate_drag(sess.piece_sprite.init_pos[0], left, jig.top)
266
- sess.tdc.run.append(
267
- "async function main(){await simulate_slide(%s, %s)}" % (str(xs), str(ys))
268
- )
269
- sess.tdc.add_run("main")
244
+ sess.set_drag_track(xs, ys) # type: ignore
245
+
246
+ async def get_tdc(self, sess: TcaptchaSession):
247
+ """
248
+ The get_tdc function is a coroutine that sets an instance of the :class:`TDC` class to `sess`.
249
+
250
+ :param sess: captcha session
251
+ :return: None
252
+ """
253
+ async with self.client.get(sess.tdx_js_url()) as r:
254
+ r.raise_for_status()
255
+ tdc = prepare(
256
+ r.text,
257
+ ip=await self.get_ipv4(),
258
+ ua=self.client.headers["User-Agent"],
259
+ mouse_track=sess.mouse_track,
260
+ )
261
+
262
+ sess.set_js_env(tdc)
270
263
 
271
264
  async def verify(self):
272
265
  sess = await self.new()
273
- await self.get_tdc(sess)
274
-
275
- sess.solve_workload()
276
266
 
277
267
  await self.get_captcha_problem(sess)
268
+ sess.solve_workload()
278
269
  self.solve_captcha(sess)
270
+ await self.get_tdc(sess)
271
+
272
+ assert sess.piece_sprite.move_cfg
273
+ assert sess.piece_sprite.move_cfg.data_type
279
274
 
280
- collect = await sess.tdc.get_data()
275
+ collect = str(sess.tdc.getData(None, True)) # BUG: maybe a String(), convert to str
281
276
 
282
277
  ans = dict(
283
278
  elem_id=1,
284
- type=sess.piece_sprite.move_cfg.data_type[0], # type: ignore
279
+ type=sess.piece_sprite.move_cfg.data_type[0],
285
280
  data="{0},{1}".format(*sess.jig_ans),
286
281
  )
287
282
  data = {
288
283
  "collect": collect,
289
284
  "tlg": len(collect),
290
- "eks": (await sess.tdc.get_info())["info"],
285
+ "eks": sess.tdc.getInfo()["info"],
291
286
  "sess": sess.prehandle.sess,
292
287
  "ans": json.dumps(ans),
293
288
  "pow_answer": hex_add(sess.conf.common.pow_cfg.prefix, sess.pow_ans),
294
289
  "pow_calc_time": sess.duration,
295
290
  }
296
- log.debug("verify post data:", data)
291
+ log.debug(f"verify post data: {data}")
297
292
 
298
293
  async with self.client.post(VERIFY_URL, data=data) as r:
299
294
  r = VerifyResp.parse_raw(r.text)