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.
Files changed (62) hide show
  1. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/PKG-INFO +3 -5
  2. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/pyproject.toml +5 -11
  3. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/setup.py +6 -7
  4. aioqzone-0.12.1.dev1/src/aioqzone/api/__init__.py +5 -0
  5. aioqzone-0.12.1.dev1/src/aioqzone/api/h5/__init__.py +3 -0
  6. aioqzone-0.12.1.dev1/src/aioqzone/api/h5/model.py +2 -0
  7. aioqzone-0.12.1.dev1/src/aioqzone/api/h5/raw.py +11 -0
  8. aioqzone-0.11.4.dev1/src/aioqzone/api/loginman.py → aioqzone-0.12.1.dev1/src/aioqzone/api/loginman/__init__.py +57 -49
  9. aioqzone-0.11.4.dev1/src/aioqzone/event/login.py → aioqzone-0.12.1.dev1/src/aioqzone/api/loginman/_base.py +2 -45
  10. aioqzone-0.12.1.dev1/src/aioqzone/api/web/__init__.py +3 -0
  11. aioqzone-0.11.4.dev1/src/aioqzone/api/__init__.py → aioqzone-0.12.1.dev1/src/aioqzone/api/web/model.py +13 -8
  12. {aioqzone-0.11.4.dev1/src/aioqzone/api → aioqzone-0.12.1.dev1/src/aioqzone/api/web}/raw.py +13 -9
  13. aioqzone-0.12.1.dev1/src/aioqzone/event/__init__.py +7 -0
  14. aioqzone-0.12.1.dev1/src/aioqzone/event/login.py +42 -0
  15. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/exception.py +6 -6
  16. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/type/resp.py +1 -1
  17. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/jssupport/execjs.py +3 -1
  18. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/base.py +27 -21
  19. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/constant.py +9 -1
  20. aioqzone-0.12.1.dev1/src/qqqr/event/__init__.py +244 -0
  21. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/exception.py +1 -1
  22. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/type.py +6 -6
  23. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/captcha/__init__.py +2 -1
  24. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/captcha/jigsaw.py +2 -0
  25. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/encrypt.py +1 -0
  26. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/utils/net.py +2 -0
  27. aioqzone-0.11.4.dev1/src/aioqzone/event/__init__.py +0 -5
  28. aioqzone-0.11.4.dev1/src/qqqr/event/__init__.py +0 -285
  29. aioqzone-0.11.4.dev1/src/qqqr/type.pyi +0 -18
  30. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/LICENSE +0 -0
  31. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/README.md +0 -0
  32. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/__init__.py +0 -0
  33. {aioqzone-0.11.4.dev1/src/aioqzone/api → aioqzone-0.12.1.dev1/src/aioqzone/api/web}/constant.py +0 -0
  34. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/py.typed +0 -0
  35. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/type/__init__.py +0 -0
  36. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/type/entity.py +0 -0
  37. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/type/internal.py +0 -0
  38. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/utils/__init__.py +0 -0
  39. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/utils/html.py +0 -0
  40. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/utils/regex.py +0 -0
  41. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/aioqzone/utils/time.py +0 -0
  42. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/jssupport/__init__.py +0 -0
  43. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/jssupport/exception.py +0 -0
  44. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/jssupport/jsdom.py +0 -0
  45. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/jssupport/jsjson.py +0 -0
  46. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/jssupport/py.typed +0 -0
  47. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/__init__.py +0 -0
  48. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/event/login.py +0 -0
  49. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/py.typed +0 -0
  50. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/qr/__init__.py +0 -0
  51. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/qr/type.py +0 -0
  52. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/ssl.py +0 -0
  53. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/__init__.py +0 -0
  54. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/captcha/archive/collect_env.min.js +0 -0
  55. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/captcha/archive/decrypt.js +0 -0
  56. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/captcha/archive/vdata.js +0 -0
  57. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/captcha/vm.py +0 -0
  58. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/rsa.py +0 -0
  59. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/up/type.py +0 -0
  60. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/utils/daug.py +0 -0
  61. {aioqzone-0.11.4.dev1 → aioqzone-0.12.1.dev1}/src/qqqr/utils/encrypt.py +0 -0
  62. {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.11.4.dev1
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.7,<4.0
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.21.6,<1.22.0) ; python_version >= "3.7" and python_version < "3.8"
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.11.4.dev1"
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.7"
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 = ['py37']
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.11.4.dev1',
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[![python](https://img.shields.io/pypi/pyversions/aioqzone?logo=python&logoColor=white)][home]\n[![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)\n[![version](https://img.shields.io/pypi/v/aioqzone?logo=python)][pypi]\n[![black](https://img.shields.io/badge/code%20style-black-000000.svg)](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
- 'extras_require': extras_require,
59
- 'python_requires': '>=3.7,<4.0',
58
+ 'python_requires': '>=3.8,<4.0',
60
59
  }
61
60
 
62
61
 
@@ -0,0 +1,5 @@
1
+ # from .h5 import QzoneH5API
2
+ from .loginman import Loginable, MixedLoginMan
3
+ from .web import QzoneWebAPI
4
+
5
+ __all__ = ["Loginable", "MixedLoginMan", "QzoneWebAPI"]
@@ -0,0 +1,3 @@
1
+ from .model import QzoneH5API
2
+
3
+ __all__ = ["QzoneH5API"]
@@ -0,0 +1,2 @@
1
+ class QzoneH5API:
2
+ pass
@@ -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 enum import Enum
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.login import QrEvent, UpEvent
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 ..event.login import Loginable, LoginMethod, QREvent, UPEvent
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
- Loginable.__init__(self, uin)
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(Loginable[MixedLoginEvent]):
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
- strategy: QrStrategy,
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.strategy = strategy
222
- self._order: List[Loginable] = []
223
- if strategy != QrStrategy.force:
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._order.append(UPLoginMan(client, uin, pwd))
226
- if strategy != QrStrategy.forbid:
227
- self._order.append(QRLoginMan(client, uin, refresh_time))
228
- if strategy == QrStrategy.prefer:
229
- self._order = self._order[::-1]
230
-
231
- # use a unified task store
232
- self._tasks = self._order[0]._tasks
233
- for i in self._order:
234
- i._tasks = self._tasks
235
-
236
- def register_hook(self, hook: Union[MixedLoginEvent, QrEvent, UpEvent]):
237
- for c in self._order:
238
- if isinstance(c, QRLoginMan) and isinstance(hook, QREvent):
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._order
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 c in methods:
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 as e:
273
+ except SystemExit:
278
274
  log.debug("Mixed loginman captured System Exit, reraise.")
279
- raise e
275
+ raise
280
276
 
281
277
  if user_break:
282
278
  raise UserBreak from user_break
283
279
 
284
- if self.strategy == "forbid":
280
+ if LoginMethod.qr not in methods:
285
281
  hint = "您可能被限制账密登陆. 扫码登陆仍然可行."
286
- elif self.strategy != "force":
282
+ elif LoginMethod.up not in methods:
287
283
  hint = "您可能已被限制登陆."
288
284
  else:
289
285
  hint = "你在睡觉!"
290
286
 
291
- raise LoginError(hint, self.strategy)
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, Optional, TypeVar
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 LoginMethod(str, Enum):
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
  """
@@ -0,0 +1,3 @@
1
+ from .model import QzoneWebAPI
2
+
3
+ __all__ = ["QzoneWebAPI"]
@@ -6,22 +6,27 @@ from typing import List, Optional
6
6
 
7
7
  from pydantic import ValidationError
8
8
 
9
- from ..type.internal import AlbumData
10
- from ..type.resp import *
11
- from .raw import QzoneApi
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 DummyQapi(QzoneApi):
17
- """A wrapper of :class:`.QzoneApi`. It will validate the returns from :class:`.QzoneApi`,
18
- and return a typed response. The value returned is usually a :class:`BaseModel`, sometimes
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:`.QzoneApi.feeds3_html_more`.
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 QzoneApi:
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,7 @@
1
+ "aioqzone interface defination"
2
+
3
+ from qqqr.event import Emittable, Event, EventManager
4
+
5
+ from .login import LoginEvent, LoginMethod, QREvent, UPEvent
6
+
7
+ __all__ = ["Emittable", "Event", "EventManager", "LoginMethod", "LoginEvent", "QREvent", "UPEvent"]
@@ -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
- RuntimeError.__init__(self, *args)
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, strategy: Optional[str] = None) -> None:
23
+ def __init__(self, msg: str, methods_tried: Optional[Sequence] = None) -> None:
24
24
  msg = "登陆失败: " + msg
25
- super().__init__(msg, strategy)
25
+ super().__init__(msg, methods_tried)
26
26
  self.msg = msg
27
- self.strategy = strategy
27
+ self.methods_tried = methods_tried or []
28
28
 
29
29
  def __str__(self) -> str:
30
- return f"{self.msg} (strategy={self.strategy})"
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.DummyQapi.feeds3_html_more`.
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())