aioqzone 0.11.4.dev1__tar.gz → 0.12.1.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.
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/PKG-INFO +3 -5
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/pyproject.toml +5 -11
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/setup.py +6 -7
- aioqzone-0.12.1.dev1/src/aioqzone/api/__init__.py +5 -0
- aioqzone-0.12.1.dev1/src/aioqzone/api/h5/__init__.py +3 -0
- aioqzone-0.12.1.dev1/src/aioqzone/api/h5/model.py +2 -0
- aioqzone-0.12.1.dev1/src/aioqzone/api/h5/raw.py +11 -0
- aioqzone-0.11.4.dev1/src/aioqzone/api/loginman.py → aioqzone-0.12.1.dev1/src/aioqzone/api/loginman/__init__.py +57 -49
- aioqzone-0.11.4.dev1/src/aioqzone/event/login.py → aioqzone-0.12.1.dev1/src/aioqzone/api/loginman/_base.py +2 -45
- aioqzone-0.12.1.dev1/src/aioqzone/api/web/__init__.py +3 -0
- aioqzone-0.11.4.dev1/src/aioqzone/api/__init__.py → aioqzone-0.12.1.dev1/src/aioqzone/api/web/model.py +13 -8
- {aioqzone-0.11.4.dev1/src/aioqzone/api → aioqzone-0.12.1.dev1/src/aioqzone/api/web}/raw.py +13 -9
- aioqzone-0.12.1.dev1/src/aioqzone/event/__init__.py +7 -0
- aioqzone-0.12.1.dev1/src/aioqzone/event/login.py +42 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/exception.py +6 -6
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/type/resp.py +1 -1
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/jssupport/execjs.py +3 -1
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/base.py +27 -21
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/constant.py +9 -1
- aioqzone-0.12.1.dev1/src/qqqr/event/__init__.py +244 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/exception.py +1 -1
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/type.py +6 -6
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/captcha/__init__.py +2 -1
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/captcha/jigsaw.py +2 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/encrypt.py +1 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/utils/net.py +2 -0
- aioqzone-0.11.4.dev1/src/aioqzone/event/__init__.py +0 -5
- aioqzone-0.11.4.dev1/src/qqqr/event/__init__.py +0 -285
- aioqzone-0.11.4.dev1/src/qqqr/type.pyi +0 -18
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/LICENSE +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/README.md +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/__init__.py +0 -0
- {aioqzone-0.11.4.dev1/src/aioqzone/api → aioqzone-0.12.1.dev1/src/aioqzone/api/web}/constant.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/py.typed +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/type/__init__.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/type/entity.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/type/internal.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/utils/__init__.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/utils/html.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/utils/regex.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/utils/time.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/jssupport/__init__.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/jssupport/exception.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/jssupport/jsdom.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/jssupport/jsjson.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/jssupport/py.typed +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/__init__.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/event/login.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/py.typed +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/qr/__init__.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/qr/type.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/ssl.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/__init__.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/captcha/archive/collect_env.min.js +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/captcha/archive/decrypt.js +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/captcha/archive/vdata.js +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/captcha/vm.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/rsa.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/type.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/utils/daug.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/utils/encrypt.py +0 -0
- {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/utils/iter.py +0 -0
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: aioqzone
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.1.dev1
|
|
4
4
|
Summary: Python wrapper for Qzone web login and Qzone http api.
|
|
5
5
|
Home-page: https://github.com/aioqzone/aioqzone
|
|
6
6
|
License: AGPL-3.0
|
|
7
7
|
Keywords: qzone-api,autologin,asyncio-spider
|
|
8
8
|
Author: aioqzone
|
|
9
9
|
Author-email: zzzzss990315@gmail.com
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.8,<4.0
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.8
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -22,8 +21,7 @@ Classifier: Typing :: Typed
|
|
|
22
21
|
Requires-Dist: cssselect (>=1.1.0,<2.0.0)
|
|
23
22
|
Requires-Dist: httpx (>=0.23.0,<0.24.0)
|
|
24
23
|
Requires-Dist: lxml (>=4.9.1,<5.0.0)
|
|
25
|
-
Requires-Dist: numpy (>=1.
|
|
26
|
-
Requires-Dist: numpy (>=1.22.3,<2.0.0) ; python_version >= "3.8" and python_version < "4.0"
|
|
24
|
+
Requires-Dist: numpy (>=1.22.3,<2.0.0)
|
|
27
25
|
Requires-Dist: opencv-python-headless (>=4.7.0,<5.0.0)
|
|
28
26
|
Requires-Dist: pydantic (>=1.10.4,<2.0.0)
|
|
29
27
|
Requires-Dist: pytz (>=2022.1,<2023.0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "aioqzone"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.12.1.dev1"
|
|
4
4
|
description = "Python wrapper for Qzone web login and Qzone http api."
|
|
5
5
|
authors = ["aioqzone <zzzzss990315@gmail.com>"]
|
|
6
6
|
license = "AGPL-3.0"
|
|
@@ -27,16 +27,13 @@ exclude = ["*.js"]
|
|
|
27
27
|
"Bug Tracker" = "https://github.com/aioqzone/aioqzone/issues"
|
|
28
28
|
|
|
29
29
|
[tool.poetry.dependencies]
|
|
30
|
-
python = "^3.
|
|
30
|
+
python = "^3.8"
|
|
31
31
|
httpx = "~0.23.0"
|
|
32
32
|
pydantic = "^1.10.4"
|
|
33
33
|
pytz = "^2022.1"
|
|
34
34
|
lxml = "^4.9.1"
|
|
35
35
|
cssselect = "^1.1.0"
|
|
36
|
-
numpy =
|
|
37
|
-
{ version = "~1.21.6", python = ">=3.7,<3.8" },
|
|
38
|
-
{ version = "^1.22.3", python = "^3.8" },
|
|
39
|
-
]
|
|
36
|
+
numpy = "^1.22.3"
|
|
40
37
|
opencv-python-headless = "^4.7.0"
|
|
41
38
|
rsa = "^4.8"
|
|
42
39
|
|
|
@@ -53,10 +50,7 @@ pytest-asyncio = "~0.20.1"
|
|
|
53
50
|
optional = true
|
|
54
51
|
|
|
55
52
|
[tool.poetry.group.dev.dependencies]
|
|
56
|
-
pre-commit =
|
|
57
|
-
{ version = "^2.20.0", python = ">=3.7,<3.8" },
|
|
58
|
-
{ version = "^3.0.1", python = "^3.8" },
|
|
59
|
-
]
|
|
53
|
+
pre-commit = "^3.0.1"
|
|
60
54
|
black = "^22.12.0"
|
|
61
55
|
isort = "^5.11.2"
|
|
62
56
|
|
|
@@ -92,4 +86,4 @@ line_length = 99
|
|
|
92
86
|
|
|
93
87
|
[tool.black]
|
|
94
88
|
line-length = 99
|
|
95
|
-
target-version = ['
|
|
89
|
+
target-version = ['py38']
|
|
@@ -14,6 +14,9 @@ package_dir = \
|
|
|
14
14
|
packages = \
|
|
15
15
|
['aioqzone',
|
|
16
16
|
'aioqzone.api',
|
|
17
|
+
'aioqzone.api.h5',
|
|
18
|
+
'aioqzone.api.loginman',
|
|
19
|
+
'aioqzone.api.web',
|
|
17
20
|
'aioqzone.event',
|
|
18
21
|
'aioqzone.type',
|
|
19
22
|
'aioqzone.utils',
|
|
@@ -32,18 +35,15 @@ install_requires = \
|
|
|
32
35
|
['cssselect>=1.1.0,<2.0.0',
|
|
33
36
|
'httpx>=0.23.0,<0.24.0',
|
|
34
37
|
'lxml>=4.9.1,<5.0.0',
|
|
38
|
+
'numpy>=1.22.3,<2.0.0',
|
|
35
39
|
'opencv-python-headless>=4.7.0,<5.0.0',
|
|
36
40
|
'pydantic>=1.10.4,<2.0.0',
|
|
37
41
|
'pytz>=2022.1,<2023.0',
|
|
38
42
|
'rsa>=4.8,<5.0']
|
|
39
43
|
|
|
40
|
-
extras_require = \
|
|
41
|
-
{':python_version >= "3.7" and python_version < "3.8"': ['numpy>=1.21.6,<1.22.0'],
|
|
42
|
-
':python_version >= "3.8" and python_version < "4.0"': ['numpy>=1.22.3,<2.0.0']}
|
|
43
|
-
|
|
44
44
|
setup_kwargs = {
|
|
45
45
|
'name': 'aioqzone',
|
|
46
|
-
'version': '0.
|
|
46
|
+
'version': '0.12.1.dev1',
|
|
47
47
|
'description': 'Python wrapper for Qzone web login and Qzone http api.',
|
|
48
48
|
'long_description': '# aioqzone\n\naioqzone封装了一些Qzone接口。\n\n[][home]\n[](https://github.com/aioqzone/aioqzone/actions/workflows/qqqr.yml)\n[][pypi]\n[](https://github.com/psf/black)\n\n[English](README_en.md) | 简体中文\n\n> 1. ⚠️ aioqzone 仍在开发阶段,任何功能和接口都有可能在未来的版本中发生变化。\n> 2. 🆘 **欢迎有意协助开发/维护的中文开发者**。不仅限于本仓库,[aioqzone][org] 所属的任何仓库都需要您的帮助。\n\n## 功能和特点\n\n### Qzone 功能\n\n- [x] 二维码登录\n- [x] 密码登录 (受限)\n- [x] 计算验证码答案\n- [ ] 通过网络环境检测\n- [x] 爬取HTML说说\n- [x] 爬取说说详细内容\n- [x] 爬取空间相册\n- [x] 点赞/取消赞\n- [x] 发布(仅文字)/修改/删除说说\n- [ ] 评论相关\n\n### 为什么选择 aioqzone\n\n- [x] 完整的 IDE 类型支持 (typing)\n- [x] API 结果类型验证 (pydantic)\n- [x] 异步设计\n- [x] 易于二次开发\n- [x] [文档支持](https://aioqzone.github.io/aioqzone)\n\n__在做了:__\n\n- [ ] 完善的测试覆盖\n\n## node 依赖\n\n- `jssupport.jsjson.AstLoader` 不需要借助其他进程;\n- 要使用 `jssupport.execjs` 和 `jssupport.jsjson.NodeLoader`,您(至少)需要安装 `Node.js` >= v14;\n- 要使用 `jssupport.jsdom`,您需要安装 `jsdom` 和 `canvas` 两个 npm 包。\n- 验证码部分需要使用 `canvas`,因此您需要正确配置运行环境内的 font config ([#45](https://github.com/aioqzone/aioqzone/issues/45)).\n\n## 包描述\n\n|包名 |简述 |\n|-----------|-------------------|\n|aioqzone |封装Qzone API |\n|jssupport |执行JS |\n|qqqr |网页登录 |\n\n## 例子\n\n这些仓库提供了一些 aioqzone 的实际使用示例。\n\n### aioqzone 的插件们\n\n- [aioqzone-feed][aioqzone-feed]: 提供了操作 feed 的简单接口\n\n## 许可证\n\n```\nCopyright (C) 2022 aioqzone.\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU Affero General Public License as published\nby the Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU Affero General Public License for more details.\n\nYou should have received a copy of the GNU Affero General Public License\nalong with this program. If not, see <https://www.gnu.org/licenses/>.\n```\n\n- aioqzone 以 [AGPL-3.0](LICENSE) 开源.\n- [免责声明](https://aioqzone.github.io/aioqzone/disclaimers.html)\n\n\n[home]: https://github.com/aioqzone/aioqzone "Python wrapper for Qzone web login and Qzone http api"\n[aioqzone-feed]: https://github.com/aioqzone/aioqzone-feed "aioqzone plugin providing higher level api for processing feed"\n[pypi]: https://pypi.org/project/aioqzone\n[org]: https://github.com/aioqzone\n',
|
|
49
49
|
'author': 'aioqzone',
|
|
@@ -55,8 +55,7 @@ setup_kwargs = {
|
|
|
55
55
|
'packages': packages,
|
|
56
56
|
'package_data': package_data,
|
|
57
57
|
'install_requires': install_requires,
|
|
58
|
-
'
|
|
59
|
-
'python_requires': '>=3.7,<4.0',
|
|
58
|
+
'python_requires': '>=3.8,<4.0',
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from aioqzone.api.loginman import Loginable
|
|
2
|
+
from qqqr.utils.net import ClientAdapter
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class QzoneH5RawAPI:
|
|
6
|
+
host = "https://user.qzone.qq.com"
|
|
7
|
+
|
|
8
|
+
def __init__(self, client: ClientAdapter, loginman: Loginable) -> None:
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.client = client
|
|
11
|
+
self.login = loginman
|
|
@@ -5,21 +5,21 @@ Users can inherit these managers and implement their own caching logic.
|
|
|
5
5
|
|
|
6
6
|
import asyncio
|
|
7
7
|
import logging
|
|
8
|
-
from
|
|
9
|
-
from typing import Dict, List, Optional, Union
|
|
8
|
+
from typing import Dict, Optional, Sequence
|
|
10
9
|
|
|
11
10
|
from httpx import ConnectError, HTTPError
|
|
12
11
|
|
|
12
|
+
from aioqzone.event.login import LoginMethod, QREvent, UPEvent
|
|
13
|
+
from aioqzone.exception import LoginError, SkipLoginInterrupt
|
|
13
14
|
from jssupport.exception import JsImportError, JsRuntimeError, NodeNotFoundError
|
|
14
15
|
from qqqr.constant import QzoneAppid, QzoneProxy, StatusCode
|
|
15
|
-
from qqqr.event
|
|
16
|
+
from qqqr.event import Emittable, EventManager
|
|
16
17
|
from qqqr.exception import HookError, TencentLoginError, UserBreak
|
|
17
18
|
from qqqr.qr import QrLogin
|
|
18
19
|
from qqqr.up import UpLogin
|
|
19
20
|
from qqqr.utils.net import ClientAdapter
|
|
20
21
|
|
|
21
|
-
from
|
|
22
|
-
from ..exception import LoginError, SkipLoginInterrupt
|
|
22
|
+
from ._base import Loginable
|
|
23
23
|
|
|
24
24
|
log = logging.getLogger(__name__)
|
|
25
25
|
JsError = JsRuntimeError, JsImportError, NodeNotFoundError
|
|
@@ -44,7 +44,7 @@ class ConstLoginMan(Loginable):
|
|
|
44
44
|
return self._cookie
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
class UPLoginMan(Loginable[UPEvent]):
|
|
47
|
+
class UPLoginMan(Loginable, Emittable[UPEvent]):
|
|
48
48
|
"""Login manager for username-password login.
|
|
49
49
|
This manager may trigger :meth:`~aioqzone.event.login.LoginEvent.LoginSuccess` and
|
|
50
50
|
:meth:`~aioqzone.event.login.LoginEvent.LoginFailed` hook.
|
|
@@ -116,14 +116,14 @@ class UPLoginMan(Loginable[UPEvent]):
|
|
|
116
116
|
exit(1)
|
|
117
117
|
|
|
118
118
|
|
|
119
|
-
class QRLoginMan(Loginable[QREvent]):
|
|
119
|
+
class QRLoginMan(Loginable, Emittable[QREvent]):
|
|
120
120
|
"""Login manager for QR login.
|
|
121
121
|
This manager may trigger :meth:`~aioqzone.event.login.LoginEvent.LoginSuccess` and
|
|
122
122
|
:meth:`~aioqzone.event.login.LoginEvent.LoginFailed` hook.
|
|
123
123
|
"""
|
|
124
124
|
|
|
125
125
|
def __init__(self, client: ClientAdapter, uin: int, refresh_time: int = 6) -> None:
|
|
126
|
-
|
|
126
|
+
super().__init__(uin)
|
|
127
127
|
self.client = client
|
|
128
128
|
self.refresh = refresh_time
|
|
129
129
|
self.qrlogin = QrLogin(self.client, QzoneAppid, QzoneProxy)
|
|
@@ -189,15 +189,6 @@ class QRLoginMan(Loginable[QREvent]):
|
|
|
189
189
|
self.hook.refresh_flag.clear()
|
|
190
190
|
|
|
191
191
|
|
|
192
|
-
class QrStrategy(str, Enum):
|
|
193
|
-
"""Represents QR strategy."""
|
|
194
|
-
|
|
195
|
-
force = "force"
|
|
196
|
-
prefer = "prefer"
|
|
197
|
-
allow = "allow"
|
|
198
|
-
forbid = "forbid"
|
|
199
|
-
|
|
200
|
-
|
|
201
192
|
class MixedLoginEvent(QREvent, UPEvent):
|
|
202
193
|
def __instancecheck__(self, o: object) -> bool:
|
|
203
194
|
return isinstance(o, QREvent) and isinstance(o, UPEvent)
|
|
@@ -206,48 +197,52 @@ class MixedLoginEvent(QREvent, UPEvent):
|
|
|
206
197
|
return issubclass(cls, QREvent) and issubclass(cls, UPEvent)
|
|
207
198
|
|
|
208
199
|
|
|
209
|
-
class MixedLoginMan(
|
|
210
|
-
"""A login manager that will try methods according to the given :class:`.QrStrategy`.
|
|
200
|
+
class MixedLoginMan(EventManager[QREvent, UPEvent], Loginable):
|
|
201
|
+
"""A login manager that will try methods according to the given :class:`.QrStrategy`.
|
|
202
|
+
|
|
203
|
+
.. versionchanged:: 0.12.0
|
|
204
|
+
|
|
205
|
+
Make it a :class:`EventManager`.
|
|
206
|
+
"""
|
|
211
207
|
|
|
212
208
|
def __init__(
|
|
213
209
|
self,
|
|
214
210
|
client: ClientAdapter,
|
|
215
211
|
uin: int,
|
|
216
|
-
|
|
212
|
+
order: Sequence[LoginMethod],
|
|
217
213
|
pwd: Optional[str] = None,
|
|
218
214
|
refresh_time: int = 6,
|
|
219
215
|
) -> None:
|
|
220
216
|
super().__init__(uin)
|
|
221
|
-
self.
|
|
222
|
-
self.
|
|
223
|
-
if
|
|
217
|
+
self.order = tuple(dict.fromkeys(order))
|
|
218
|
+
self.loginables: Dict[LoginMethod, Loginable] = {}
|
|
219
|
+
if LoginMethod.qr in self.order:
|
|
220
|
+
self.loginables[LoginMethod.qr] = QRLoginMan(
|
|
221
|
+
client=client, uin=uin, refresh_time=refresh_time
|
|
222
|
+
)
|
|
223
|
+
if LoginMethod.up in self.order:
|
|
224
224
|
assert pwd
|
|
225
|
-
self.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
c.register_hook(hook)
|
|
240
|
-
if isinstance(c, UPLoginMan) and isinstance(hook, UPEvent):
|
|
241
|
-
c.register_hook(hook)
|
|
242
|
-
|
|
243
|
-
def ordered_methods(self) -> List[Loginable]:
|
|
225
|
+
self.loginables[LoginMethod.up] = UPLoginMan(client=client, uin=uin, pwd=pwd)
|
|
226
|
+
self.init_hooks()
|
|
227
|
+
|
|
228
|
+
def init_hooks(self):
|
|
229
|
+
if LoginMethod.qr in self.order:
|
|
230
|
+
c = self.loginables[LoginMethod.qr]
|
|
231
|
+
if isinstance(c, Emittable):
|
|
232
|
+
c.register_hook(self.inst_of(QREvent))
|
|
233
|
+
if LoginMethod.up in self.order:
|
|
234
|
+
c = self.loginables[LoginMethod.up]
|
|
235
|
+
if isinstance(c, Emittable):
|
|
236
|
+
c.register_hook(self.inst_of(UPEvent))
|
|
237
|
+
|
|
238
|
+
def ordered_methods(self) -> Sequence[LoginMethod]:
|
|
244
239
|
"""Subclasses can inherit this method to choose a subset of `._order` according to its own policy.
|
|
245
240
|
|
|
246
241
|
:return: a subset of `._order`.
|
|
247
242
|
|
|
248
243
|
.. versionadded:: 0.9.8.dev1
|
|
249
244
|
"""
|
|
250
|
-
return self.
|
|
245
|
+
return list(self.order)
|
|
251
246
|
|
|
252
247
|
async def _new_cookie(self) -> Dict[str, str]:
|
|
253
248
|
"""
|
|
@@ -265,7 +260,8 @@ class MixedLoginMan(Loginable[MixedLoginEvent]):
|
|
|
265
260
|
|
|
266
261
|
user_break = None
|
|
267
262
|
|
|
268
|
-
for
|
|
263
|
+
for m in methods:
|
|
264
|
+
c = self.loginables[m]
|
|
269
265
|
try:
|
|
270
266
|
return await c._new_cookie()
|
|
271
267
|
except (TencentLoginError, _NextMethodInterrupt, HookError) as e:
|
|
@@ -274,18 +270,30 @@ class MixedLoginMan(Loginable[MixedLoginEvent]):
|
|
|
274
270
|
except UserBreak as e:
|
|
275
271
|
user_break = e
|
|
276
272
|
log.debug("Mixed loginman received UserBreak, continue.")
|
|
277
|
-
except SystemExit
|
|
273
|
+
except SystemExit:
|
|
278
274
|
log.debug("Mixed loginman captured System Exit, reraise.")
|
|
279
|
-
raise
|
|
275
|
+
raise
|
|
280
276
|
|
|
281
277
|
if user_break:
|
|
282
278
|
raise UserBreak from user_break
|
|
283
279
|
|
|
284
|
-
if
|
|
280
|
+
if LoginMethod.qr not in methods:
|
|
285
281
|
hint = "您可能被限制账密登陆. 扫码登陆仍然可行."
|
|
286
|
-
elif
|
|
282
|
+
elif LoginMethod.up not in methods:
|
|
287
283
|
hint = "您可能已被限制登陆."
|
|
288
284
|
else:
|
|
289
285
|
hint = "你在睡觉!"
|
|
290
286
|
|
|
291
|
-
raise LoginError(hint,
|
|
287
|
+
raise LoginError(hint, methods_tried=methods)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
strategy_to_order = dict(
|
|
291
|
+
forbid=[LoginMethod.up],
|
|
292
|
+
allow=[LoginMethod.up, LoginMethod.qr],
|
|
293
|
+
prefer=[LoginMethod.qr, LoginMethod.up],
|
|
294
|
+
force=[LoginMethod.qr],
|
|
295
|
+
)
|
|
296
|
+
"""We provide a mapping to transform old "strategy" manner to new "order" manner.
|
|
297
|
+
|
|
298
|
+
.. versionadded:: 0.12.0
|
|
299
|
+
"""
|
|
@@ -1,55 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
|
-
from enum import Enum
|
|
4
3
|
from time import time
|
|
5
|
-
from typing import Dict
|
|
4
|
+
from typing import Dict
|
|
6
5
|
|
|
7
|
-
from qqqr.event import Emittable, Event
|
|
8
|
-
from qqqr.event.login import QrEvent as _qrevt
|
|
9
|
-
from qqqr.event.login import UpEvent as _upevt
|
|
10
6
|
from qqqr.utils.encrypt import gtk
|
|
11
7
|
|
|
12
8
|
|
|
13
|
-
class
|
|
14
|
-
qr = "qr"
|
|
15
|
-
up = "up"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class LoginEvent(Event):
|
|
19
|
-
"""Defines usual events happens during login."""
|
|
20
|
-
|
|
21
|
-
async def LoginFailed(self, meth: LoginMethod, msg: Optional[str] = None):
|
|
22
|
-
"""Will be emitted on login failed.
|
|
23
|
-
|
|
24
|
-
:param meth: indicate what login method this login attempt used
|
|
25
|
-
:param msg: Err msg, defaults to None.
|
|
26
|
-
"""
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
async def LoginSuccess(self, meth: LoginMethod):
|
|
30
|
-
"""Will be emitted after login success.
|
|
31
|
-
|
|
32
|
-
:param meth: indicate what login method this login attempt used
|
|
33
|
-
"""
|
|
34
|
-
pass
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
LgEvt = TypeVar("LgEvt", bound=LoginEvent)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class QREvent(LoginEvent, _qrevt):
|
|
41
|
-
"""Defines usual events happens during QR login."""
|
|
42
|
-
|
|
43
|
-
pass
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class UPEvent(LoginEvent, _upevt):
|
|
47
|
-
"""Defines usual events happens during password login."""
|
|
48
|
-
|
|
49
|
-
pass
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
class Loginable(ABC, Emittable[LgEvt]):
|
|
9
|
+
class Loginable(ABC):
|
|
53
10
|
"""Abstract class represents a login manager.
|
|
54
11
|
It is a :class:`Emittable` class which can emit :class:`LoginEvent`.
|
|
55
12
|
"""
|
|
@@ -6,22 +6,27 @@ from typing import List, Optional
|
|
|
6
6
|
|
|
7
7
|
from pydantic import ValidationError
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
|
|
9
|
+
from aioqzone.type.internal import AlbumData
|
|
10
|
+
from aioqzone.type.resp import *
|
|
11
|
+
|
|
12
|
+
from .raw import QzoneWebRawAPI
|
|
12
13
|
|
|
13
14
|
log = logging.getLogger(__name__)
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
class
|
|
17
|
-
"""A wrapper of :class:`.
|
|
18
|
-
and return a typed response. The value returned is usually
|
|
19
|
-
just a basic type if not needed.
|
|
17
|
+
class QzoneWebAPI(QzoneWebRawAPI):
|
|
18
|
+
"""A wrapper of :class:`.QzoneWebRawAPI`. It will validate the returns from :class:`.QzoneWebRawAPI`,
|
|
19
|
+
and return a typed response. The value returned is usually one or more :class:`BaseModel`, sometimes
|
|
20
|
+
just a basic type if not needed.
|
|
21
|
+
|
|
22
|
+
.. versionchanged:: 0.12.1
|
|
23
|
+
rename to ``QzoneWebAPI``
|
|
24
|
+
"""
|
|
20
25
|
|
|
21
26
|
async def feeds3_html_more(
|
|
22
27
|
self, pagenum: int, count: int = 10, *, aux: Optional[FeedMoreAux] = None
|
|
23
28
|
) -> FeedMoreResp:
|
|
24
|
-
"""This will call :meth:`.
|
|
29
|
+
"""This will call :meth:`.QzoneWebRawAPI.feeds3_html_more`.
|
|
25
30
|
|
|
26
31
|
:param aux: :obj:`~.FeedMoreResp.aux` field of last return (pagenum - 1).
|
|
27
32
|
:return: a validated and typed response with a list of :obj:`~.FeedMoreResp.feeds` and :obj:`~.FeedMoreResp.aux` info.
|
|
@@ -10,29 +10,33 @@ from urllib.parse import parse_qs
|
|
|
10
10
|
|
|
11
11
|
from httpx import HTTPStatusError
|
|
12
12
|
|
|
13
|
-
import aioqzone.api.constant as const
|
|
13
|
+
import aioqzone.api.web.constant as const
|
|
14
|
+
from aioqzone.api.loginman import Loginable
|
|
15
|
+
from aioqzone.exception import CorruptError, QzoneError
|
|
16
|
+
from aioqzone.type.internal import AlbumData, LikeData
|
|
17
|
+
from aioqzone.utils.regex import response_callback
|
|
18
|
+
from aioqzone.utils.time import time_ms
|
|
14
19
|
from jssupport.jsjson import JsonValue, json_loads
|
|
15
20
|
from qqqr.utils.daug import du
|
|
16
21
|
from qqqr.utils.net import ClientAdapter, raise_for_status
|
|
17
22
|
|
|
18
|
-
from ..event.login import Loginable
|
|
19
|
-
from ..exception import CorruptError, QzoneError
|
|
20
|
-
from ..type.internal import AlbumData, LikeData
|
|
21
|
-
from ..utils.regex import response_callback
|
|
22
|
-
from ..utils.time import time_ms
|
|
23
|
-
|
|
24
23
|
logger = logging.getLogger(__name__)
|
|
25
24
|
|
|
26
25
|
StrDict = Dict[str, JsonValue]
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
class
|
|
30
|
-
"""Just a wrapper for Qzone http interface. No type validating.
|
|
28
|
+
class QzoneWebRawAPI:
|
|
29
|
+
"""Just a wrapper for Qzone http interface. No type validating.
|
|
30
|
+
|
|
31
|
+
.. versionchanged:: 0.12.1
|
|
32
|
+
rename to ``QzoneWebRawAPI``
|
|
33
|
+
"""
|
|
31
34
|
|
|
32
35
|
encoding = "utf-8"
|
|
33
36
|
host = "https://user.qzone.qq.com"
|
|
34
37
|
|
|
35
38
|
def __init__(self, client: ClientAdapter, loginman: Loginable) -> None:
|
|
39
|
+
super().__init__()
|
|
36
40
|
self.client = client
|
|
37
41
|
self.login = loginman
|
|
38
42
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Dict, Optional, TypeVar
|
|
3
|
+
|
|
4
|
+
from qqqr.event import Event
|
|
5
|
+
from qqqr.event.login import QrEvent as _qrevt
|
|
6
|
+
from qqqr.event.login import UpEvent as _upevt
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LoginMethod(str, Enum):
|
|
10
|
+
qr = "qr"
|
|
11
|
+
up = "up"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LoginEvent(Event):
|
|
15
|
+
"""Defines usual events happens during login."""
|
|
16
|
+
|
|
17
|
+
async def LoginFailed(self, meth: LoginMethod, msg: Optional[str] = None):
|
|
18
|
+
"""Will be emitted on login failed.
|
|
19
|
+
|
|
20
|
+
:param meth: indicate what login method this login attempt used
|
|
21
|
+
:param msg: Err msg, defaults to None.
|
|
22
|
+
"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
async def LoginSuccess(self, meth: LoginMethod):
|
|
26
|
+
"""Will be emitted after login success.
|
|
27
|
+
|
|
28
|
+
:param meth: indicate what login method this login attempt used
|
|
29
|
+
"""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class QREvent(LoginEvent, _qrevt):
|
|
34
|
+
"""Defines usual events happens during QR login."""
|
|
35
|
+
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class UPEvent(LoginEvent, _upevt):
|
|
40
|
+
"""Defines usual events happens during password login."""
|
|
41
|
+
|
|
42
|
+
pass
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import Optional, Sequence
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class QzoneError(RuntimeError):
|
|
@@ -11,7 +11,7 @@ class QzoneError(RuntimeError):
|
|
|
11
11
|
self.rdict = rdict
|
|
12
12
|
if len(args) > 0 and isinstance(args[0], str):
|
|
13
13
|
self.msg = args[0]
|
|
14
|
-
|
|
14
|
+
super().__init__(self, *args)
|
|
15
15
|
|
|
16
16
|
def __str__(self) -> str:
|
|
17
17
|
return f"Code {self.code}: {self.msg}"
|
|
@@ -20,14 +20,14 @@ class QzoneError(RuntimeError):
|
|
|
20
20
|
class LoginError(RuntimeError):
|
|
21
21
|
"""Login failed for some reasons."""
|
|
22
22
|
|
|
23
|
-
def __init__(self, msg: str,
|
|
23
|
+
def __init__(self, msg: str, methods_tried: Optional[Sequence] = None) -> None:
|
|
24
24
|
msg = "登陆失败: " + msg
|
|
25
|
-
super().__init__(msg,
|
|
25
|
+
super().__init__(msg, methods_tried)
|
|
26
26
|
self.msg = msg
|
|
27
|
-
self.
|
|
27
|
+
self.methods_tried = methods_tried or []
|
|
28
28
|
|
|
29
29
|
def __str__(self) -> str:
|
|
30
|
-
return f"{self.msg} (
|
|
30
|
+
return f"{self.msg} (tried={self.methods_tried})"
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class CorruptError(ValueError):
|
|
@@ -12,7 +12,7 @@ from .entity import HasConEntity, HasContent
|
|
|
12
12
|
|
|
13
13
|
# Below are the response reps of Qzone Apis.
|
|
14
14
|
class FeedRep(BaseModel):
|
|
15
|
-
"""Represents a feed in :meth:`~aioqzone.api.
|
|
15
|
+
"""Represents a feed in :meth:`~aioqzone.api.QzoneWebAPI.feeds3_html_more`.
|
|
16
16
|
The feed content is in html format.
|
|
17
17
|
"""
|
|
18
18
|
|
|
@@ -33,6 +33,7 @@ class Partial:
|
|
|
33
33
|
:param asis: If True, the argument's :meth:`repr` will be saved directly.
|
|
34
34
|
Otherwise we will look for some appropriate "repr" for an argument.
|
|
35
35
|
"""
|
|
36
|
+
super().__init__()
|
|
36
37
|
self.func = name
|
|
37
38
|
self.args = args
|
|
38
39
|
self.asis = asis
|
|
@@ -101,6 +102,7 @@ class ExecJS:
|
|
|
101
102
|
assert self.loop_policy_check(), "loop policy cannot be `WindowsSelectorEventLoopPolicy`"
|
|
102
103
|
|
|
103
104
|
def __init__(self):
|
|
105
|
+
super().__init__()
|
|
104
106
|
self.check_all()
|
|
105
107
|
self.setup = []
|
|
106
108
|
self.run = []
|
|
@@ -172,5 +174,5 @@ class ExecJS:
|
|
|
172
174
|
return await self(prop)
|
|
173
175
|
|
|
174
176
|
|
|
175
|
-
if sys.platform == "win32":
|
|
177
|
+
if sys.platform == "win32" and sys.version_info < (3, 8):
|
|
176
178
|
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|