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.
Files changed (137) hide show
  1. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.github/dependabot.yml +1 -1
  2. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/PKG-INFO +1 -1
  3. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/pyproject.toml +7 -1
  4. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/api/h5/model.py +101 -11
  5. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/api/__init__.py +16 -3
  6. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/api/feed.py +20 -7
  7. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/api/profile.py +4 -7
  8. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/api/request.py +58 -13
  9. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/api/response.py +55 -8
  10. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/api/conftest.py +19 -3
  11. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/api/test_h5.py +24 -14
  12. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/uv.lock +1 -1
  13. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.github/workflows/auto-pr.yml +0 -0
  14. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.github/workflows/build.yml +0 -0
  15. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.github/workflows/codeql-analysis.yml +0 -0
  16. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.github/workflows/sphinx.yml +0 -0
  17. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.github/workflows/test.yml +0 -0
  18. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.gitignore +0 -0
  19. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/.pre-commit-config.yaml +0 -0
  20. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/CONTRIBUTING.md +0 -0
  21. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/LICENSE +0 -0
  22. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/README.md +0 -0
  23. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/README_en.md +0 -0
  24. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/cliff.toml +0 -0
  25. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/_static/penguin-blob.webp +0 -0
  26. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/_static/teaencoder.ts +0 -0
  27. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/api/h5.rst +0 -0
  28. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/api/index.rst +0 -0
  29. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/api/login.rst +0 -0
  30. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/exception.rst +0 -0
  31. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/index.rst +0 -0
  32. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/messages.rst +0 -0
  33. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/model/api.rst +0 -0
  34. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/model/index.rst +0 -0
  35. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/model/protocol.rst +0 -0
  36. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/aioqzone/reference.rst +0 -0
  37. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/conf.py +0 -0
  38. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/disclaimers.rst +0 -0
  39. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/index.rst +0 -0
  40. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/api/h5.po +0 -0
  41. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/api/index.po +0 -0
  42. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/api/login.po +0 -0
  43. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/api/web.po +0 -0
  44. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/exception.po +0 -0
  45. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/index.po +0 -0
  46. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/messages.po +0 -0
  47. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/model/api.po +0 -0
  48. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/model/index.po +0 -0
  49. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/model/protocol.po +0 -0
  50. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/model/response.po +0 -0
  51. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/aioqzone/reference.po +0 -0
  52. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/disclaimers.po +0 -0
  53. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/index.po +0 -0
  54. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/base.po +0 -0
  55. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/exception.po +0 -0
  56. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/index.po +0 -0
  57. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/jsjson.po +0 -0
  58. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/qr/index.po +0 -0
  59. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/qr/login.po +0 -0
  60. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/qr/type.po +0 -0
  61. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/captcha/capsess.po +0 -0
  62. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/captcha/captcha.po +0 -0
  63. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/captcha/index.po +0 -0
  64. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/captcha/jigsaw.po +0 -0
  65. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/encrypt.po +0 -0
  66. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/index.po +0 -0
  67. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/login.po +0 -0
  68. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/qqqr/up/type.po +0 -0
  69. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/locale/zh_CN/LC_MESSAGES/reference.po +0 -0
  70. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/base.rst +0 -0
  71. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/exception.rst +0 -0
  72. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/index.rst +0 -0
  73. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/jsjson.rst +0 -0
  74. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/qr/index.rst +0 -0
  75. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/qr/login.rst +0 -0
  76. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/qr/type.rst +0 -0
  77. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/up/captcha/capsess.rst +0 -0
  78. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/up/captcha/captcha.rst +0 -0
  79. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/up/captcha/index.rst +0 -0
  80. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/up/encrypt.rst +0 -0
  81. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/up/index.rst +0 -0
  82. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/up/login.rst +0 -0
  83. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/doc/source/qqqr/up/type.rst +0 -0
  84. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/__init__.py +0 -0
  85. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/api/__init__.py +0 -0
  86. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/api/h5/__init__.py +0 -0
  87. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/api/login/__init__.py +0 -0
  88. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/api/login/_base.py +0 -0
  89. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/exception.py +0 -0
  90. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/message.py +0 -0
  91. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/__init__.py +0 -0
  92. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/protocol/__init__.py +0 -0
  93. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/protocol/config.py +0 -0
  94. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/model/protocol/entity.py +0 -0
  95. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/utils/__init__.py +0 -0
  96. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/utils/entity.py +0 -0
  97. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/utils/regex.py +0 -0
  98. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/utils/retry.py +0 -0
  99. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/aioqzone/utils/time.py +0 -0
  100. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/__init__.py +0 -0
  101. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/base.py +0 -0
  102. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/constant.py +0 -0
  103. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/exception.py +0 -0
  104. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/message.py +0 -0
  105. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/py.typed +0 -0
  106. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/qr/__init__.py +0 -0
  107. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/qr/type.py +0 -0
  108. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/type.py +0 -0
  109. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/__init__.py +0 -0
  110. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/_model.py +0 -0
  111. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/__init__.py +0 -0
  112. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/_model.py +0 -0
  113. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/capsess.py +0 -0
  114. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/click/__init__.py +0 -0
  115. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/click/_types.py +0 -0
  116. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/pil_utils.py +0 -0
  117. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/select/__init__.py +0 -0
  118. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/select/_types.py +0 -0
  119. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/slide/__init__.py +0 -0
  120. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/captcha/slide/_types.py +0 -0
  121. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/encrypt.py +0 -0
  122. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/h5.py +0 -0
  123. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/up/web.py +0 -0
  124. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/utils/encrypt.py +0 -0
  125. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/utils/iter.py +0 -0
  126. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/utils/jsjson.py +0 -0
  127. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/src/qqqr/utils/net.py +0 -0
  128. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/api/__init__.py +0 -0
  129. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/api/test_loginman.py +0 -0
  130. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/conftest.py +0 -0
  131. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/login_logic/__init__.py +0 -0
  132. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/login_logic/test_captcha.py +0 -0
  133. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/login_logic/test_qr.py +0 -0
  134. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/login_logic/test_up.py +0 -0
  135. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/utils/test_entity.py +0 -0
  136. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/utils/test_json.py +0 -0
  137. {aioqzone-1.9.4.dev9 → aioqzone-1.9.5.dev4}/test/utils/test_time.py +0 -0
@@ -7,7 +7,7 @@ updates:
7
7
  interval: weekly
8
8
  day: saturday
9
9
 
10
- - package-ecosystem: pip
10
+ - package-ecosystem: uv
11
11
  directory: "/"
12
12
  schedule:
13
13
  interval: daily
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aioqzone
3
- Version: 1.9.4.dev9
3
+ Version: 1.9.5.dev4
4
4
  Summary: A Python wrapper for Qzone login and H5 APIs.
5
5
  Project-URL: Homepage, https://github.com/aioqzone/aioqzone
6
6
  Project-URL: Repository, https://github.com/aioqzone/aioqzone
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aioqzone"
3
- version = "1.9.4.dev9"
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 = None
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.accept:
60
- headers["Accept"] = api.accept
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, ownuin: int, fid: str, appid: int, content: str, private=False
169
- ) -> AddCommentResp:
170
- """Comment a feed.
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
- :param ownuin: Feed owner uin
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
- accept: t.ClassVar[t.Optional[str]] = None
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
- accept: t.ClassVar[str] = "application/json"
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
- class HasUserInfo(BaseModel):
202
- userinfo: UserInfo
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, HasUserInfo, HasSummary, HasMedia):
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, HasUserInfo):
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
- share_info: ShareInfo = Field(
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, HasUserInfo, HasSummary, HasMedia):
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, HasUserInfo):
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
- share_info: ShareInfo = Field(
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
- class data_wrapper(BaseModel):
88
- data: cls = Field(validation_alias=cls._data_key)
89
-
90
- return data_wrapper.model_validate(obj).data
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("data script not found")
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("data script not found")
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
- return UpLoginManager(
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
- if request.param == "qr":
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
- return man
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
- info = await upload_photos(api)
64
+ picinfo = await upload_photos(api)
65
65
 
66
66
  feed = await api.publish_mood(
67
- MOOD_TEXT, photos=info, sync_weibo=False, ugc_right=UgcRight.self
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 fetched_feed.like.isliked
85
- assert fetched_feed.comment.comments
95
+ # assert detail.like.isliked
96
+ assert detail.comment.comments
86
97
 
87
- comment_dict = {i.commentid: i for i in fetched_feed.comment.comments}
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
 
@@ -116,7 +116,7 @@ wheels = [
116
116
 
117
117
  [[package]]
118
118
  name = "aioqzone"
119
- version = "1.9.4.dev9"
119
+ version = "1.9.5.dev4"
120
120
  source = { editable = "." }
121
121
  dependencies = [
122
122
  { name = "aiohttp" },
File without changes
File without changes
File without changes
File without changes