aioqzone 1.9.4.dev9__tar.gz → 1.9.5.dev4__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-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.github/dependabot.yml +1 -1
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/PKG-INFO +1 -1
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/pyproject.toml +7 -1
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/api/h5/model.py +101 -11
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/api/__init__.py +16 -3
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/api/feed.py +20 -7
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/api/profile.py +4 -7
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/api/request.py +58 -13
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/api/response.py +55 -8
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/api/conftest.py +19 -3
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/api/test_h5.py +24 -14
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/uv.lock +1 -1
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.github/workflows/auto-pr.yml +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.github/workflows/build.yml +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.github/workflows/codeql-analysis.yml +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.github/workflows/sphinx.yml +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.github/workflows/test.yml +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.gitignore +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.pre-commit-config.yaml +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/CONTRIBUTING.md +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/LICENSE +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/README.md +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/README_en.md +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/cliff.toml +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/_static/penguin-blob.webp +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/_static/teaencoder.ts +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/api/h5.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/api/index.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/api/login.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/exception.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/index.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/messages.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/model/api.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/model/index.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/model/protocol.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/reference.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/conf.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/disclaimers.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/index.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/api/h5.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/api/index.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/api/login.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/api/web.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/exception.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/index.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/messages.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/model/api.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/model/index.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/model/protocol.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/model/response.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/reference.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/disclaimers.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/index.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/base.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/exception.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/index.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/jsjson.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/qr/index.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/qr/login.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/qr/type.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/captcha/capsess.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/captcha/captcha.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/captcha/index.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/captcha/jigsaw.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/encrypt.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/index.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/login.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/type.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/reference.po +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/base.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/exception.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/index.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/jsjson.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/qr/index.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/qr/login.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/qr/type.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/up/captcha/capsess.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/up/captcha/captcha.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/up/captcha/index.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/up/encrypt.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/up/index.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/up/login.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/up/type.rst +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/api/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/api/h5/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/api/login/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/api/login/_base.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/exception.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/message.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/protocol/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/protocol/config.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/protocol/entity.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/utils/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/utils/entity.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/utils/regex.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/utils/retry.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/utils/time.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/base.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/constant.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/exception.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/message.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/py.typed +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/qr/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/qr/type.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/type.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/_model.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/_model.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/capsess.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/click/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/click/_types.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/pil_utils.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/select/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/select/_types.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/slide/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/slide/_types.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/encrypt.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/h5.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/web.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/utils/encrypt.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/utils/iter.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/utils/jsjson.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/utils/net.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/api/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/api/test_loginman.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/conftest.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/login_logic/__init__.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/login_logic/test_captcha.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/login_logic/test_qr.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/login_logic/test_up.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/utils/test_entity.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/utils/test_json.py +0 -0
- {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/utils/test_time.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aioqzone"
|
|
3
|
-
version = "1.9.
|
|
3
|
+
version = "1.9.5.dev4"
|
|
4
4
|
description = "A Python wrapper for Qzone login and H5 APIs."
|
|
5
5
|
authors = [{ name = "aioqzone" }]
|
|
6
6
|
maintainers = [
|
|
@@ -79,6 +79,12 @@ asyncio_default_fixture_loop_scope = "module"
|
|
|
79
79
|
[tool.ruff]
|
|
80
80
|
line-length = 99
|
|
81
81
|
|
|
82
|
+
[tool.ruff.lint]
|
|
83
|
+
ignore = [
|
|
84
|
+
"F403", # 'from module import *' used; unable to detect undefined names
|
|
85
|
+
"F405", # name may be undefined, or defined from star imports
|
|
86
|
+
]
|
|
87
|
+
|
|
82
88
|
[tool.pyright]
|
|
83
89
|
pythonVersion = "3.9"
|
|
84
90
|
pythonPlatform = "All"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import typing as t
|
|
2
3
|
from os import PathLike
|
|
3
4
|
|
|
4
|
-
from pydantic import ValidationError
|
|
5
|
+
from pydantic import HttpUrl, ValidationError
|
|
5
6
|
from tenacity import AsyncRetrying, TryAgain, after_log, stop_after_attempt
|
|
6
7
|
|
|
7
8
|
from aioqzone.api.login import Loginable
|
|
@@ -48,16 +49,17 @@ class QzoneH5API:
|
|
|
48
49
|
async def call(self, api: QzoneApi[TyRequest, TyResponse]) -> TyResponse:
|
|
49
50
|
params: t.Dict[str, t.Any] = api.params.build_params(self.login.uin)
|
|
50
51
|
if api.http_method == "GET":
|
|
51
|
-
data =
|
|
52
|
+
data = {}
|
|
52
53
|
else:
|
|
53
|
-
data = params
|
|
54
|
+
data = dict(json=params) if api.is_json else dict(data=params)
|
|
54
55
|
params = {}
|
|
55
56
|
|
|
56
57
|
headers = dict(Referer=api.referer)
|
|
57
58
|
if api.keep_alive:
|
|
58
59
|
headers["Connection"] = "keep-alive"
|
|
59
|
-
if api.
|
|
60
|
-
|
|
60
|
+
# if api.is_json:
|
|
61
|
+
# headers["Content-Type"] = "application/json"
|
|
62
|
+
# headers["Accept"] = "application/json"
|
|
61
63
|
|
|
62
64
|
async for attempt in self._relogin_retry:
|
|
63
65
|
with attempt:
|
|
@@ -75,9 +77,9 @@ class QzoneH5API:
|
|
|
75
77
|
api.http_method,
|
|
76
78
|
api.url,
|
|
77
79
|
params=params,
|
|
78
|
-
data=data,
|
|
79
80
|
headers=headers,
|
|
80
81
|
cookies=self.login.cookie,
|
|
82
|
+
**data,
|
|
81
83
|
) as r:
|
|
82
84
|
r.raise_for_status()
|
|
83
85
|
obj = await api.response.response_to_object(r)
|
|
@@ -164,19 +166,99 @@ class QzoneH5API:
|
|
|
164
166
|
|
|
165
167
|
return await self.call(cls(params=DolikeParam.model_validate(locals())))
|
|
166
168
|
|
|
169
|
+
@t.overload
|
|
167
170
|
async def add_comment(
|
|
168
|
-
self,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
+
self,
|
|
172
|
+
hostuin: int,
|
|
173
|
+
fid: str,
|
|
174
|
+
appid: int,
|
|
175
|
+
content: str,
|
|
176
|
+
*,
|
|
177
|
+
busi_param: t.Optional[dict] = None,
|
|
178
|
+
private: bool = False,
|
|
179
|
+
) -> AddCommentResp: ...
|
|
180
|
+
|
|
181
|
+
@t.overload
|
|
182
|
+
async def add_comment(
|
|
183
|
+
self,
|
|
184
|
+
hostuin: int,
|
|
185
|
+
fid: str,
|
|
186
|
+
appid: int,
|
|
187
|
+
content: str,
|
|
188
|
+
photos: t.Sequence[HttpUrl],
|
|
189
|
+
*,
|
|
190
|
+
feedsType: int = 100,
|
|
191
|
+
private: bool = False,
|
|
192
|
+
abstime: t.Optional[int] = None,
|
|
193
|
+
) -> AddCommentLegacyResp: ...
|
|
171
194
|
|
|
172
|
-
|
|
195
|
+
async def add_comment(
|
|
196
|
+
self,
|
|
197
|
+
hostuin: int,
|
|
198
|
+
fid: str,
|
|
199
|
+
appid: int,
|
|
200
|
+
content: str,
|
|
201
|
+
photos: t.Optional[t.Sequence[HttpUrl]] = None,
|
|
202
|
+
busi_param: t.Optional[dict] = None,
|
|
203
|
+
*,
|
|
204
|
+
feedsType: int = 100,
|
|
205
|
+
private=False,
|
|
206
|
+
abstime: t.Optional[int] = None,
|
|
207
|
+
) -> t.Union[AddCommentResp, AddCommentLegacyResp]:
|
|
208
|
+
"""Comment a feed. If :obj:`photos` is given, the legacy comment api will be used.
|
|
209
|
+
|
|
210
|
+
:param hostuin: Feed owner uin
|
|
173
211
|
:param fid: :term:`fid`
|
|
174
212
|
:param appid: :term:`appid`
|
|
175
213
|
:param content: comment content
|
|
214
|
+
:param photos: photos to be attached, usually returned by :meth:`.preupload_photos`
|
|
215
|
+
:param busi_param: optional encoded params from :obj:`FeedOperation.operation.busi_param`
|
|
216
|
+
:param abstime: required if `appid != 311`
|
|
176
217
|
:param private: is private comment
|
|
218
|
+
|
|
219
|
+
.. seealso:: :meth:`.preupload_photos`, :meth:`.upload_pic`
|
|
220
|
+
|
|
221
|
+
.. versionchanged:: 1.9.5.dev1
|
|
222
|
+
|
|
223
|
+
added support for legacy comment api (with photos).
|
|
177
224
|
"""
|
|
225
|
+
if photos:
|
|
226
|
+
if appid == 311:
|
|
227
|
+
topicId = f"{hostuin}_{fid}__1"
|
|
228
|
+
else:
|
|
229
|
+
assert abstime, "abstime is required if appid != 311"
|
|
230
|
+
topicId = f"{hostuin}_{abstime}"
|
|
231
|
+
return await self.call(
|
|
232
|
+
AddCommentApiLegacy(
|
|
233
|
+
params=AddCommentParamsLegacy.model_validate(locals(), from_attributes=True)
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
busi_param = busi_param or {}
|
|
178
238
|
return await self.call(AddCommentApi(params=AddCommentParams.model_validate(locals())))
|
|
179
239
|
|
|
240
|
+
async def delete_comment(
|
|
241
|
+
self,
|
|
242
|
+
hostUin: int,
|
|
243
|
+
topicId: str,
|
|
244
|
+
commentId: int,
|
|
245
|
+
feedsType: int = 100,
|
|
246
|
+
commentUin: t.Optional[int] = None,
|
|
247
|
+
) -> DeleteCommentResp:
|
|
248
|
+
"""Delete a comment.
|
|
249
|
+
|
|
250
|
+
:param hostUin: Feed owner uin
|
|
251
|
+
:param topicId:
|
|
252
|
+
:param feedsType:
|
|
253
|
+
:param commentId: id of the comment to be deleted
|
|
254
|
+
:param commentUin: uin of the comment owner, default as None, means the login uin
|
|
255
|
+
"""
|
|
256
|
+
if commentUin is None:
|
|
257
|
+
commentUin = self.login.uin
|
|
258
|
+
return await self.call(
|
|
259
|
+
DeleteCommentApi(params=DeleteCommentParams.model_validate(locals()))
|
|
260
|
+
)
|
|
261
|
+
|
|
180
262
|
async def publish_mood(
|
|
181
263
|
self,
|
|
182
264
|
content: str,
|
|
@@ -187,9 +269,11 @@ class QzoneH5API:
|
|
|
187
269
|
"""Publish a feed.
|
|
188
270
|
|
|
189
271
|
:param content: feed content
|
|
190
|
-
:param photos:
|
|
272
|
+
:param photos: photos to be attached, usually returned by :meth:`.preupload_photos`
|
|
191
273
|
:param sync_weibo: sync to weibo, default to false
|
|
192
274
|
:param ugc_right: access right, default to "Available to Everyone".
|
|
275
|
+
|
|
276
|
+
.. seealso:: :meth:`.preupload_photos`, :meth:`.upload_pic`
|
|
193
277
|
"""
|
|
194
278
|
photos = photos or []
|
|
195
279
|
return await self.call(
|
|
@@ -237,6 +321,12 @@ class QzoneH5API:
|
|
|
237
321
|
async def preupload_photos(
|
|
238
322
|
self, upload_pics: t.List[UploadPicResponse], cur_num=0, upload_hd=False
|
|
239
323
|
) -> PhotosPreuploadResponse:
|
|
324
|
+
"""Preupload photos before publishing a feed.
|
|
325
|
+
|
|
326
|
+
:param upload_pics: List of :obj:`.UploadPicResponse`, usually returned by :meth:`.upload_pic`
|
|
327
|
+
|
|
328
|
+
.. seealso:: :meth:`.upload_pic`
|
|
329
|
+
"""
|
|
240
330
|
assert upload_pics
|
|
241
331
|
return await self.call(
|
|
242
332
|
PhotosPreuploadApi(params=PhotosPreuploadParams.model_validate(locals()))
|
|
@@ -19,12 +19,12 @@ class QzoneApi(BaseModel, t.Generic[TyRequest, TyResponse]):
|
|
|
19
19
|
path: t.ClassVar[str]
|
|
20
20
|
|
|
21
21
|
keep_alive: t.ClassVar[bool] = True
|
|
22
|
-
|
|
22
|
+
is_json: t.ClassVar[bool] = False
|
|
23
23
|
referer: str = "https://h5.qzone.qq.com/"
|
|
24
24
|
|
|
25
25
|
attach_token: t.ClassVar[bool] = True
|
|
26
26
|
login_required: t.ClassVar[bool] = True
|
|
27
|
-
params: TyRequest = Field(default_factory=QzoneRequestParams)
|
|
27
|
+
params: TyRequest = Field(default_factory=QzoneRequestParams) # type: ignore
|
|
28
28
|
response: t.ClassVar[t.Type[TyResponse]] # type: ignore
|
|
29
29
|
|
|
30
30
|
@property
|
|
@@ -74,7 +74,7 @@ class GetCountApi(QzoneApi[GetCountParams, FeedCount]):
|
|
|
74
74
|
http_method: t.ClassVar[TyHttpMethod] = "GET"
|
|
75
75
|
host: t.ClassVar[str] = "https://mobile.qzone.qq.com"
|
|
76
76
|
path: t.ClassVar[str] = "/feeds/mfeeds_get_count"
|
|
77
|
-
|
|
77
|
+
is_json: t.ClassVar[bool] = True
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
class LikeApi(QzoneApi[DolikeParam, SingleReturnResp]):
|
|
@@ -91,6 +91,19 @@ class AddCommentApi(QzoneApi[AddCommentParams, AddCommentResp]):
|
|
|
91
91
|
response: t.ClassVar = AddCommentResp
|
|
92
92
|
http_method: t.ClassVar[TyHttpMethod] = "POST"
|
|
93
93
|
path: t.ClassVar[str] = "/webapp/json/qzoneOperation/addComment"
|
|
94
|
+
is_json: t.ClassVar[bool] = True
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class AddCommentApiLegacy(QzoneApi[AddCommentParamsLegacy, AddCommentLegacyResp]):
|
|
98
|
+
response: t.ClassVar = AddCommentLegacyResp
|
|
99
|
+
http_method: t.ClassVar[TyHttpMethod] = "POST"
|
|
100
|
+
path: t.ClassVar[str] = "/proxy/domain/taotao.qzone.qq.com/cgi-bin/emotion_cgi_re_feeds"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class DeleteCommentApi(QzoneApi[DeleteCommentParams, DeleteCommentResp]):
|
|
104
|
+
response: t.ClassVar = DeleteCommentResp
|
|
105
|
+
http_method: t.ClassVar[TyHttpMethod] = "POST"
|
|
106
|
+
path: t.ClassVar[str] = "/proxy/domain/taotao.qzone.qq.com/cgi-bin/emotion_cgi_delcomment_ugc"
|
|
94
107
|
|
|
95
108
|
|
|
96
109
|
class ListFriendApi(QzoneApi):
|
|
@@ -192,14 +192,22 @@ class FeedComment(BaseModel):
|
|
|
192
192
|
|
|
193
193
|
class HasCommon(BaseModel):
|
|
194
194
|
common: FeedCommon = Field(validation_alias="comm")
|
|
195
|
+
userinfo: UserInfo
|
|
195
196
|
|
|
196
197
|
@property
|
|
197
198
|
def abstime(self):
|
|
198
199
|
return self.common.time
|
|
199
200
|
|
|
201
|
+
@property
|
|
202
|
+
def topicId(self):
|
|
203
|
+
"""make topicId. for 311 feeds, it's made of uin and fid; for others, uin and time.
|
|
200
204
|
|
|
201
|
-
|
|
202
|
-
|
|
205
|
+
.. versionadded:: 1.9.5.dev1
|
|
206
|
+
"""
|
|
207
|
+
if self.common.appid == 311:
|
|
208
|
+
fid: str = getattr(self, "fid", self.common.ugcrightkey)
|
|
209
|
+
return f"{self.userinfo.uin}_{fid}__1"
|
|
210
|
+
return f"{self.userinfo.uin}_{self.common.time}"
|
|
203
211
|
|
|
204
212
|
|
|
205
213
|
class HasSummary(BaseModel):
|
|
@@ -227,13 +235,20 @@ class ShareInfo(BaseModel):
|
|
|
227
235
|
# return v
|
|
228
236
|
|
|
229
237
|
|
|
238
|
+
class FeedOperation(BaseModel):
|
|
239
|
+
busi_param: dict = Field(default_factory=dict)
|
|
240
|
+
weixin_url: t.Union[HttpUrl, str] = Field(default="", union_mode="left_to_right")
|
|
241
|
+
qq_url: t.Union[HttpUrl, str] = Field(default="", union_mode="left_to_right")
|
|
242
|
+
share_info: ShareInfo = Field(default_factory=ShareInfo)
|
|
243
|
+
|
|
244
|
+
|
|
230
245
|
class Share(BaseModel):
|
|
231
246
|
common: t.Union[FeedCommon, ContentCommon] = Field(
|
|
232
247
|
validation_alias="cell_comm", union_mode="left_to_right"
|
|
233
248
|
)
|
|
234
249
|
|
|
235
250
|
|
|
236
|
-
class FeedOriginal(HasFid, HasCommon,
|
|
251
|
+
class FeedOriginal(HasFid, HasCommon, HasSummary, HasMedia):
|
|
237
252
|
@model_validator(mode="before")
|
|
238
253
|
def remove_prefix(cls, v: dict[str, t.Any]):
|
|
239
254
|
return {k.removeprefix("cell_"): i for k, i in v.items()}
|
|
@@ -245,14 +260,12 @@ class FeedOriginal(HasFid, HasCommon, HasUserInfo, HasSummary, HasMedia):
|
|
|
245
260
|
return v
|
|
246
261
|
|
|
247
262
|
|
|
248
|
-
class FeedData(HasFid, HasCommon, HasSummary, HasMedia
|
|
263
|
+
class FeedData(HasFid, HasCommon, HasSummary, HasMedia):
|
|
249
264
|
like: LikeInfo = Field(default_factory=LikeInfo)
|
|
250
265
|
|
|
251
266
|
comment: FeedComment = Field(default_factory=FeedComment)
|
|
252
267
|
original: t.Union[FeedOriginal, Share, None] = Field(default=None, union_mode="left_to_right")
|
|
253
|
-
|
|
254
|
-
default_factory=ShareInfo, validation_alias=AliasPath("operation", "share_info")
|
|
255
|
-
)
|
|
268
|
+
operation: FeedOperation = Field(default_factory=FeedOperation)
|
|
256
269
|
|
|
257
270
|
# forward_list
|
|
258
271
|
visitor: Visitor = Field(default_factory=Visitor)
|
|
@@ -10,16 +10,15 @@ from .feed import (
|
|
|
10
10
|
CommentItem,
|
|
11
11
|
FeedComment,
|
|
12
12
|
FeedCommon,
|
|
13
|
+
FeedOperation,
|
|
13
14
|
FeedPic,
|
|
14
15
|
FeedSummary,
|
|
15
16
|
HasFid,
|
|
16
17
|
HasSummary,
|
|
17
|
-
HasUserInfo,
|
|
18
18
|
LikeInfo,
|
|
19
19
|
PhotoUrls,
|
|
20
20
|
RightInfo,
|
|
21
21
|
Share,
|
|
22
|
-
ShareInfo,
|
|
23
22
|
UserInfo,
|
|
24
23
|
Visitor,
|
|
25
24
|
)
|
|
@@ -87,7 +86,7 @@ class ProfileComment(FeedComment):
|
|
|
87
86
|
comments: t.List[ProfileCommentItem] = Field(default_factory=list)
|
|
88
87
|
|
|
89
88
|
|
|
90
|
-
class ProfileFeedOriginal(HasFid, HasCommon,
|
|
89
|
+
class ProfileFeedOriginal(HasFid, HasCommon, HasSummary, HasMedia):
|
|
91
90
|
@model_validator(mode="before")
|
|
92
91
|
def remove_prefix(cls, v: dict[str, t.Any]):
|
|
93
92
|
return {k.removeprefix("cell_"): i for k, i in v.items()}
|
|
@@ -99,15 +98,13 @@ class ProfileFeedOriginal(HasFid, HasCommon, HasUserInfo, HasSummary, HasMedia):
|
|
|
99
98
|
return v
|
|
100
99
|
|
|
101
100
|
|
|
102
|
-
class ProfileFeedData(HasFid, HasCommon, HasSummary, HasMedia
|
|
101
|
+
class ProfileFeedData(HasFid, HasCommon, HasSummary, HasMedia):
|
|
103
102
|
like: ProfileLikeInfo = Field(default_factory=ProfileLikeInfo)
|
|
104
103
|
|
|
105
104
|
comment: ProfileComment = Field(default_factory=ProfileComment)
|
|
106
105
|
original: t.Union[ProfileFeedOriginal, Share, None] = Field(
|
|
107
106
|
default=None, union_mode="left_to_right"
|
|
108
107
|
)
|
|
109
|
-
|
|
110
|
-
default_factory=ShareInfo, validation_alias=AliasPath("operation", "share_info")
|
|
111
|
-
)
|
|
108
|
+
operation: FeedOperation = Field(default_factory=FeedOperation)
|
|
112
109
|
|
|
113
110
|
visitor: Visitor = Field(default_factory=Visitor)
|
|
@@ -5,7 +5,7 @@ from math import floor
|
|
|
5
5
|
from os import PathLike
|
|
6
6
|
from time import time
|
|
7
7
|
|
|
8
|
-
from pydantic import BaseModel, Field, field_serializer, field_validator
|
|
8
|
+
from pydantic import BaseModel, Field, HttpUrl, field_serializer, field_validator
|
|
9
9
|
from typing_extensions import Buffer
|
|
10
10
|
|
|
11
11
|
from aioqzone.utils.time import time_ms
|
|
@@ -23,6 +23,8 @@ __all__ = [
|
|
|
23
23
|
"GetCountParams",
|
|
24
24
|
"DolikeParam",
|
|
25
25
|
"AddCommentParams",
|
|
26
|
+
"AddCommentParamsLegacy",
|
|
27
|
+
"DeleteCommentParams",
|
|
26
28
|
"PublishMoodParams",
|
|
27
29
|
"DeleteUgcParams",
|
|
28
30
|
"UploadPicParams",
|
|
@@ -93,18 +95,6 @@ class DolikeParam(QzoneRequestParams):
|
|
|
93
95
|
format: str = "purejson"
|
|
94
96
|
|
|
95
97
|
|
|
96
|
-
class AddCommentParams(QzoneRequestParams):
|
|
97
|
-
uin_fields = ("uin",)
|
|
98
|
-
ownuin: int
|
|
99
|
-
fid: str = Field(serialization_alias="srcId")
|
|
100
|
-
private: int = Field(serialization_alias="isPrivateComment")
|
|
101
|
-
content: str = Field(min_length=1, max_length=2000)
|
|
102
|
-
appid: int = Field(default=311)
|
|
103
|
-
|
|
104
|
-
bypass_param: dict = Field(default_factory=dict)
|
|
105
|
-
busi_param: dict = Field(default_factory=dict)
|
|
106
|
-
|
|
107
|
-
|
|
108
98
|
class PhotoData(BaseModel):
|
|
109
99
|
albumid: str
|
|
110
100
|
lloc: str
|
|
@@ -135,6 +125,61 @@ class PhotoData(BaseModel):
|
|
|
135
125
|
return cls.model_validate(o, from_attributes=True)
|
|
136
126
|
|
|
137
127
|
|
|
128
|
+
class AddCommentParams(QzoneRequestParams):
|
|
129
|
+
uin_fields = ("uin",)
|
|
130
|
+
hostuin: int = Field(serialization_alias="ownuin")
|
|
131
|
+
fid: str = Field(serialization_alias="srcId")
|
|
132
|
+
private: int = Field(serialization_alias="isPrivateComment")
|
|
133
|
+
content: str = Field(min_length=1, max_length=2000)
|
|
134
|
+
appid: int = Field(default=311)
|
|
135
|
+
|
|
136
|
+
bypass_param: dict = Field(default_factory=dict)
|
|
137
|
+
busi_param: dict = Field(default_factory=dict)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class AddCommentParamsLegacy(QzoneRequestParams):
|
|
141
|
+
uin_fields = ("uin",)
|
|
142
|
+
hostuin: int
|
|
143
|
+
topicId: str
|
|
144
|
+
feedsType: int = 100
|
|
145
|
+
content: str = Field(min_length=1, max_length=2000)
|
|
146
|
+
photos: t.List[HttpUrl] = Field(default_factory=list, serialization_alias="richval")
|
|
147
|
+
private: int
|
|
148
|
+
|
|
149
|
+
# defaults
|
|
150
|
+
inCharset: str = "utf-8"
|
|
151
|
+
outCharset: str = "utf-8"
|
|
152
|
+
plat: str = "qzone"
|
|
153
|
+
source: str = "ic"
|
|
154
|
+
isSignIn: str = ""
|
|
155
|
+
format: str = "fs"
|
|
156
|
+
ref: str = "feeds"
|
|
157
|
+
richtype: str = "1" # 1 if photos else empty
|
|
158
|
+
paramstr: str = "2"
|
|
159
|
+
|
|
160
|
+
@field_serializer("photos")
|
|
161
|
+
def richval(self, photos: t.List[HttpUrl]):
|
|
162
|
+
return " ".join(map(str, photos))
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class DeleteCommentParams(QzoneRequestParams):
|
|
166
|
+
uin_fields = ("uin",)
|
|
167
|
+
hostUin: int
|
|
168
|
+
topicId: str
|
|
169
|
+
feedsType: int = 100
|
|
170
|
+
commentId: int
|
|
171
|
+
commentUin: int
|
|
172
|
+
|
|
173
|
+
# defaults
|
|
174
|
+
inCharset: str = "utf-8"
|
|
175
|
+
outCharset: str = "utf-8"
|
|
176
|
+
plat: str = "qzone"
|
|
177
|
+
source: str = "ic"
|
|
178
|
+
format: str = "fs"
|
|
179
|
+
ref: str = "feeds"
|
|
180
|
+
paramstr: str = "2"
|
|
181
|
+
|
|
182
|
+
|
|
138
183
|
class PublishMoodParams(QzoneRequestParams):
|
|
139
184
|
uin_fields = ("res_uin",)
|
|
140
185
|
content: str = Field(min_length=1, max_length=2000)
|
|
@@ -11,6 +11,7 @@ from pydantic import (
|
|
|
11
11
|
Field,
|
|
12
12
|
HttpUrl,
|
|
13
13
|
TypeAdapter,
|
|
14
|
+
create_model,
|
|
14
15
|
model_validator,
|
|
15
16
|
)
|
|
16
17
|
from tenacity import TryAgain
|
|
@@ -37,6 +38,8 @@ __all__ = [
|
|
|
37
38
|
"FeedCount",
|
|
38
39
|
"SingleReturnResp",
|
|
39
40
|
"AddCommentResp",
|
|
41
|
+
"AddCommentLegacyResp",
|
|
42
|
+
"DeleteCommentResp",
|
|
40
43
|
"PublishMoodResp",
|
|
41
44
|
"DeleteUgcResp",
|
|
42
45
|
"UploadPicResponse",
|
|
@@ -84,10 +87,10 @@ class QzoneResponse(BaseModel):
|
|
|
84
87
|
if cls._data_key is None:
|
|
85
88
|
return cls.model_validate(obj)
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
data
|
|
89
|
-
|
|
90
|
-
return data_wrapper.model_validate(obj)
|
|
90
|
+
data_wrapper = create_model(
|
|
91
|
+
"data_wrapper", data=(cls, Field(validation_alias=cls._data_key))
|
|
92
|
+
)
|
|
93
|
+
return getattr(data_wrapper.model_validate(obj), "data")
|
|
91
94
|
|
|
92
95
|
@classmethod
|
|
93
96
|
async def response_to_object(cls, response: ClientResponse) -> "StrDict":
|
|
@@ -191,16 +194,16 @@ class ProfilePagePesp(QzoneResponse):
|
|
|
191
194
|
'body/script[@type="application/javascript"]'
|
|
192
195
|
)
|
|
193
196
|
if not scripts:
|
|
194
|
-
raise TryAgain("script tag not found")
|
|
197
|
+
raise TryAgain("ProfilePageResponse: script tag not found")
|
|
195
198
|
|
|
196
199
|
texts: t.List[str] = [s.text for s in scripts]
|
|
197
200
|
script = firstn(texts, lambda s: "shine0callback" in s)
|
|
198
201
|
if not script:
|
|
199
|
-
raise TryAgain("
|
|
202
|
+
raise TryAgain("ProfilePageResponse: script tag not found")
|
|
200
203
|
|
|
201
204
|
m = re.search(r'window\.shine0callback.*return "([0-9a-f]+?)";', script)
|
|
202
205
|
if m is None:
|
|
203
|
-
raise TryAgain("
|
|
206
|
+
raise TryAgain("ProfilePageResponse: qzonetoken not found")
|
|
204
207
|
qzonetoken = m.group(1)
|
|
205
208
|
|
|
206
209
|
m = re.search(r"var FrontPage =.*?data\s*:\s*\[", script)
|
|
@@ -211,7 +214,7 @@ class ProfilePagePesp(QzoneResponse):
|
|
|
211
214
|
data = json_loads(data)
|
|
212
215
|
assert isinstance(data, list)
|
|
213
216
|
if len(data) < 2:
|
|
214
|
-
raise TryAgain("profile not returned")
|
|
217
|
+
raise TryAgain("ProfilePageResponse: profile not returned")
|
|
215
218
|
|
|
216
219
|
data = dict(zip(["info", "feedpage"], data))
|
|
217
220
|
data["qzonetoken"] = qzonetoken
|
|
@@ -239,6 +242,48 @@ class AddCommentResp(QzoneResponse):
|
|
|
239
242
|
commentLikekey: HttpUrl
|
|
240
243
|
|
|
241
244
|
|
|
245
|
+
class AddCommentLegacyResp(QzoneResponse):
|
|
246
|
+
_data_key = None
|
|
247
|
+
smooth_policy: dict = Field(
|
|
248
|
+
default_factory=dict, validation_alias=AliasPath("result", "smoothpolicy")
|
|
249
|
+
)
|
|
250
|
+
feeds: str = ""
|
|
251
|
+
|
|
252
|
+
@classmethod
|
|
253
|
+
async def response_to_object(cls, response: ClientResponse):
|
|
254
|
+
html = await response.text()
|
|
255
|
+
scripts: t.List[HtmlElement] = document_fromstring(html).xpath(
|
|
256
|
+
'body/script[@type="text/javascript"]'
|
|
257
|
+
)
|
|
258
|
+
texts: t.List[str] = [s.text for s in scripts]
|
|
259
|
+
script = firstn(texts, lambda s: "frameElement.callback" in s)
|
|
260
|
+
if not script:
|
|
261
|
+
raise TryAgain("AddCommentLegacyResponse: script tag not found")
|
|
262
|
+
|
|
263
|
+
m = response_callback.search(script)
|
|
264
|
+
assert m
|
|
265
|
+
return validate_strdict(json_loads(m.group(1)))
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class DeleteCommentResp(QzoneResponse):
|
|
269
|
+
feeds: str = ""
|
|
270
|
+
|
|
271
|
+
@classmethod
|
|
272
|
+
async def response_to_object(cls, response: ClientResponse):
|
|
273
|
+
html = await response.text()
|
|
274
|
+
scripts: t.List[HtmlElement] = document_fromstring(html).xpath(
|
|
275
|
+
'body/script[@type="text/javascript"]'
|
|
276
|
+
)
|
|
277
|
+
texts: t.List[str] = [s.text for s in scripts]
|
|
278
|
+
script = firstn(texts, lambda s: "frameElement.callback" in s)
|
|
279
|
+
if not script:
|
|
280
|
+
raise TryAgain("DeleteCommentResponse: script tag not found")
|
|
281
|
+
|
|
282
|
+
m = response_callback.search(script)
|
|
283
|
+
assert m
|
|
284
|
+
return validate_strdict(json_loads(m.group(1)))
|
|
285
|
+
|
|
286
|
+
|
|
242
287
|
class PublishMoodResp(QzoneResponse):
|
|
243
288
|
ret: int = 0
|
|
244
289
|
msg: str = ""
|
|
@@ -271,7 +316,9 @@ class PicInfo(QzoneResponse):
|
|
|
271
316
|
pre: HttpUrl
|
|
272
317
|
url: HttpUrl
|
|
273
318
|
sloc: str
|
|
319
|
+
"""id of small picture"""
|
|
274
320
|
lloc: str
|
|
321
|
+
"""id of large picture"""
|
|
275
322
|
width: int
|
|
276
323
|
height: int
|
|
277
324
|
albumid: str
|
|
@@ -21,14 +21,14 @@ if environ.get("CI") is None:
|
|
|
21
21
|
@pytest_asyncio.fixture(loop_scope="module", params=loginman_list)
|
|
22
22
|
async def man(request, client: ClientAdapter, env: test_env):
|
|
23
23
|
if request.param == "up":
|
|
24
|
-
|
|
24
|
+
man = UpLoginManager(
|
|
25
25
|
client,
|
|
26
26
|
config=UpLoginConfig.model_validate(
|
|
27
27
|
dict(uin=env.uin, pwd=env.password, fake_ip="8.8.8.8")
|
|
28
28
|
),
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
elif request.param == "qr":
|
|
32
32
|
from aioqzone.api import QrLoginConfig, QrLoginManager
|
|
33
33
|
|
|
34
34
|
man = QrLoginManager(client, config=QrLoginConfig(uin=env.uin))
|
|
@@ -36,4 +36,20 @@ async def man(request, client: ClientAdapter, env: test_env):
|
|
|
36
36
|
lambda png, times, qr_renew=False: image.open(io.BytesIO(png)).show() if png else None
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
else:
|
|
40
|
+
raise ValueError(f"Unknown login manager: {request.param}")
|
|
41
|
+
|
|
42
|
+
if cookie := environ.get("AIOQZONE_COOKIES"):
|
|
43
|
+
try:
|
|
44
|
+
from yaml import safe_load as load
|
|
45
|
+
except ImportError:
|
|
46
|
+
from json import load
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
|
|
49
|
+
if Path(cookie).exists():
|
|
50
|
+
with open(cookie, encoding="utf-8") as f:
|
|
51
|
+
try:
|
|
52
|
+
man.cookie = load(f) or {}
|
|
53
|
+
except BaseException:
|
|
54
|
+
pass
|
|
55
|
+
return man
|
|
@@ -61,16 +61,11 @@ async def upload_photos(api: QzoneH5API):
|
|
|
61
61
|
|
|
62
62
|
async def qzone_workflow(api: QzoneH5API):
|
|
63
63
|
await flow_wo_check(api)
|
|
64
|
-
|
|
64
|
+
picinfo = await upload_photos(api)
|
|
65
65
|
|
|
66
66
|
feed = await api.publish_mood(
|
|
67
|
-
MOOD_TEXT, photos=
|
|
67
|
+
MOOD_TEXT, photos=picinfo, sync_weibo=False, ugc_right=UgcRight.self
|
|
68
68
|
)
|
|
69
|
-
ownuin, appid = api.login.uin, 311
|
|
70
|
-
unikey = LikeData.persudo_unikey(appid, ownuin, feed.fid)
|
|
71
|
-
|
|
72
|
-
comment = await api.add_comment(ownuin, feed.fid, appid, COMMENT_TEXT)
|
|
73
|
-
await api.internal_dolike_app(appid, unikey, curkey=unikey)
|
|
74
69
|
|
|
75
70
|
feed_flow = await api.get_active_feeds()
|
|
76
71
|
feed_dict = {i.fid: i for i in feed_flow.vFeeds}
|
|
@@ -79,26 +74,41 @@ async def qzone_workflow(api: QzoneH5API):
|
|
|
79
74
|
fetched_feed = feed_dict[feed.fid]
|
|
80
75
|
assert fetched_feed.common.right_info.ugc_right == UgcRight.self
|
|
81
76
|
assert MOOD_TEXT in fetched_feed.summary.summary
|
|
77
|
+
|
|
78
|
+
ownuin, appid = api.login.uin, 311
|
|
79
|
+
unikey = LikeData.persudo_unikey(appid, ownuin, feed.fid)
|
|
80
|
+
|
|
81
|
+
comment = await api.add_comment(
|
|
82
|
+
ownuin, feed.fid, appid, COMMENT_TEXT, busi_param=fetched_feed.operation.busi_param
|
|
83
|
+
)
|
|
84
|
+
comment_pic = await api.add_comment(
|
|
85
|
+
ownuin, feed.fid, appid, COMMENT_TEXT, [i.url for i in picinfo]
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
await api.internal_dolike_app(appid, unikey, curkey=unikey)
|
|
89
|
+
|
|
90
|
+
detail = await api.shuoshuo(
|
|
91
|
+
fetched_feed.fid, fetched_feed.userinfo.uin, fetched_feed.common.appid
|
|
92
|
+
)
|
|
82
93
|
# BUG: dolike returns `succ` but has no effect. the fetched `isliked` is False.
|
|
83
94
|
# So this assertion is disabled temperorily. FIXME!
|
|
84
|
-
# assert
|
|
85
|
-
assert
|
|
95
|
+
# assert detail.like.isliked
|
|
96
|
+
assert detail.comment.comments
|
|
86
97
|
|
|
87
|
-
comment_dict = {i.commentid: i for i in
|
|
98
|
+
comment_dict = {i.commentid: i for i in detail.comment.comments}
|
|
88
99
|
assert comment.commentid in comment_dict
|
|
89
100
|
|
|
90
101
|
fetched_comment = comment_dict[comment.commentid]
|
|
91
102
|
assert fetched_comment.commentLikekey == comment.commentLikekey
|
|
92
103
|
assert COMMENT_TEXT in fetched_comment.content
|
|
93
104
|
|
|
94
|
-
detail = await api.shuoshuo(feed.fid, ownuin, appid)
|
|
95
105
|
assert not detail.hasmore
|
|
96
106
|
assert detail.summary.summary == fetched_feed.summary.summary
|
|
97
|
-
assert detail.like.isliked == fetched_feed.like.isliked
|
|
98
|
-
assert detail.comment.comments
|
|
99
|
-
assert comment.commentid in [i.commentid for i in detail.comment.comments]
|
|
107
|
+
# assert detail.like.isliked == fetched_feed.like.isliked
|
|
100
108
|
|
|
101
109
|
count1 = await api.mfeeds_get_count()
|
|
110
|
+
await api.delete_comment(ownuin, fetched_feed.topicId, comment.commentid)
|
|
111
|
+
|
|
102
112
|
delete = await api.delete_ugc(feed.fid, appid)
|
|
103
113
|
count2 = delete.undeal_info
|
|
104
114
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/api/h5.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/api/web.po
RENAMED
|
File without changes
|
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/index.po
RENAMED
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/disclaimers.po
RENAMED
|
File without changes
|
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/base.po
RENAMED
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/exception.po
RENAMED
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/index.po
RENAMED
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/jsjson.po
RENAMED
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/qr/index.po
RENAMED
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/qr/login.po
RENAMED
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/qr/type.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/encrypt.po
RENAMED
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/index.po
RENAMED
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/login.po
RENAMED
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/type.po
RENAMED
|
File without changes
|
{aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/reference.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|