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.
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/PKG-INFO +5 -14
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/README.md +0 -9
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/pyproject.toml +21 -14
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/h5/raw.py +7 -5
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/loginman/__init__.py +0 -6
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/web/raw.py +30 -18
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/html.py +8 -7
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/qr/__init__.py +1 -2
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/up/captcha/__init__.py +39 -44
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/up/encrypt.py +2 -42
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/up/h5.py +2 -2
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/up/web.py +24 -17
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/utils/iter.py +5 -1
- {aioqzone-0.12.13.dev2/src/jssupport → aioqzone-0.13.0.dev2/src/qqqr/utils}/jsjson.py +0 -35
- aioqzone-0.12.13.dev2/src/jssupport/__init__.py +0 -5
- aioqzone-0.12.13.dev2/src/jssupport/exception.py +0 -27
- aioqzone-0.12.13.dev2/src/jssupport/execjs.py +0 -174
- aioqzone-0.12.13.dev2/src/jssupport/jsdom.py +0 -71
- aioqzone-0.12.13.dev2/src/jssupport/py.typed +0 -0
- aioqzone-0.12.13.dev2/src/qqqr/up/captcha/archive/collect_env.min.js +0 -1
- aioqzone-0.12.13.dev2/src/qqqr/up/captcha/archive/decrypt.js +0 -168
- aioqzone-0.12.13.dev2/src/qqqr/up/captcha/archive/vdata.js +0 -158
- aioqzone-0.12.13.dev2/src/qqqr/up/captcha/vm.py +0 -73
- aioqzone-0.12.13.dev2/src/qqqr/utils/daug.py +0 -25
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/LICENSE +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/__init__.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/__init__.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/h5/__init__.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/h5/model.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/loginman/_base.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/web/__init__.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/web/constant.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/api/web/model.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/event/__init__.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/event/login.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/exception.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/py.typed +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/type/__init__.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/type/entity.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/type/internal.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/type/resp/__init__.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/type/resp/h5.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/type/resp/web.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/__init__.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/catch.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/entity.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/regex.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/time.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/__init__.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/base.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/constant.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/event/__init__.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/event/evt.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/event/evtmgr.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/event/login.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/exception.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/py.typed +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/qr/type.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/ssl.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/type.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/up/__init__.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/up/captcha/jigsaw.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/up/type.py +0 -0
- {aioqzone-0.12.13.dev2 → aioqzone-0.13.0.dev2}/src/qqqr/utils/encrypt.py +0 -0
- {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.
|
|
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
|
-
|
|
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.
|
|
26
|
-
Requires-Dist: lxml (>=4.9.1,<5.0.0)
|
|
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
|
[][home]
|
|
42
|
-
[](https://github.com/aioqzone/aioqzone/actions/workflows/qqqr.yml)
|
|
43
42
|
[][pypi]
|
|
44
43
|
[](https://github.com/psf/black)
|
|
45
44
|
[](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
|
[][home]
|
|
6
|
-
[](https://github.com/aioqzone/aioqzone/actions/workflows/qqqr.yml)
|
|
7
6
|
[][pypi]
|
|
8
7
|
[](https://github.com/psf/black)
|
|
9
8
|
[](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.
|
|
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 = "
|
|
31
|
+
httpx = "^0.24.0"
|
|
33
32
|
pydantic = "^1.10.4"
|
|
34
33
|
rsa = "^4.8"
|
|
35
|
-
|
|
34
|
+
lxml = "^4.9.1"
|
|
35
|
+
cssselect = "^1.1.0"
|
|
36
|
+
exceptiongroup = { version = "^1.1.1", python = "<3.11"}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
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.
|
|
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 = "^
|
|
65
|
+
Sphinx = "^7.0.1"
|
|
68
66
|
autodoc-pydantic = "*"
|
|
69
67
|
sphinx-autodoc-typehints = "^1.19.5"
|
|
70
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
21
|
-
from qqqr.utils.
|
|
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 =
|
|
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 =
|
|
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,
|
|
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:
|
|
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=
|
|
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=
|
|
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:
|
|
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=
|
|
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,
|
|
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,
|
|
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:
|
|
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=
|
|
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:
|
|
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=
|
|
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:
|
|
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=
|
|
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.
|
|
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
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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=
|
|
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
|
|
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
|
|
60
|
-
self.
|
|
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=
|
|
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
|
-
|
|
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.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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 =
|
|
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],
|
|
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":
|
|
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:
|
|
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)
|