qqmusic-api-python 0.7.0__tar.gz → 0.7.2__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 (216) hide show
  1. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/workflows/docs.yml +6 -6
  2. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/workflows/release.yml +6 -6
  3. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/workflows/testing.yml +2 -2
  4. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/AGENTS.md +6 -3
  5. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/PKG-INFO +1 -2
  6. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/coding.md +125 -116
  7. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/contributing.md +1 -1
  8. qqmusic_api_python-0.7.2/docs/reference/model/helper.md +3 -0
  9. qqmusic_api_python-0.7.2/docs/reference/model/private_message.md +3 -0
  10. qqmusic_api_python-0.7.2/docs/reference/modules/helper.md +3 -0
  11. qqmusic_api_python-0.7.2/docs/reference/modules/helper_utils.md +3 -0
  12. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/release-notes.md +58 -0
  13. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/tutorial/client.md +46 -4
  14. qqmusic_api_python-0.7.2/docs/tutorial/pagination.md +177 -0
  15. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/prek.toml +5 -5
  16. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/pyproject.toml +3 -2
  17. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/__init__.py +1 -1
  18. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/core/__init__.py +6 -2
  19. qqmusic_api_python-0.7.2/qqmusic_api/core/api_context.py +227 -0
  20. qqmusic_api_python-0.7.2/qqmusic_api/core/client.py +531 -0
  21. qqmusic_api_python-0.7.2/qqmusic_api/core/pagination.py +680 -0
  22. qqmusic_api_python-0.7.2/qqmusic_api/core/request.py +371 -0
  23. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/comment.py +18 -0
  24. qqmusic_api_python-0.7.2/qqmusic_api/models/lyric.py +136 -0
  25. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/request.py +0 -30
  26. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/search.py +193 -0
  27. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/singer.py +28 -1
  28. qqmusic_api_python-0.7.2/qqmusic_api/modules/_base.py +231 -0
  29. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/album.py +18 -14
  30. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/comment.py +108 -69
  31. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/helper.py +2 -8
  32. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/login.py +38 -32
  33. qqmusic_api_python-0.7.2/qqmusic_api/modules/lyric.py +130 -0
  34. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/mv.py +10 -8
  35. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/private_message.py +35 -36
  36. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/recommend.py +28 -42
  37. qqmusic_api_python-0.7.2/qqmusic_api/modules/search.py +299 -0
  38. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/singer.py +68 -46
  39. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/song.py +38 -33
  40. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/songlist.py +13 -14
  41. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/top.py +9 -10
  42. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/user.py +85 -88
  43. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/mqtt.py +4 -2
  44. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/conftest.py +5 -4
  45. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_album.py +4 -4
  46. qqmusic_api_python-0.7.2/tests/test_api_context.py +356 -0
  47. qqmusic_api_python-0.7.2/tests/test_client.py +335 -0
  48. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_comment.py +27 -13
  49. qqmusic_api_python-0.7.2/tests/test_lyric.py +70 -0
  50. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_mv.py +4 -4
  51. qqmusic_api_python-0.7.2/tests/test_pagination.py +490 -0
  52. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_private_message.py +1 -1
  53. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_recommend.py +1 -1
  54. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_search.py +29 -18
  55. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_song.py +10 -6
  56. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/uv.lock +445 -536
  57. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/requirements.txt +435 -489
  58. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/app.py +14 -5
  59. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/auth.py +6 -6
  60. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/cache.py +13 -2
  61. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/security.py +9 -3
  62. qqmusic_api_python-0.7.2/web/src/modules/__init__.py +12 -0
  63. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/modules/comment.py +5 -1
  64. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/modules/login.py +9 -2
  65. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/modules/mv.py +3 -0
  66. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/modules/singer.py +4 -1
  67. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/modules/song.py +12 -2
  68. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/modules/songlist.py +3 -0
  69. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/_helpers.py +17 -1
  70. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/comment.py +8 -9
  71. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/login.py +1 -13
  72. qqmusic_api_python-0.7.2/web/src/routes/lyric.py +48 -0
  73. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/mv.py +0 -3
  74. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/recommend.py +1 -1
  75. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/search.py +10 -4
  76. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/singer.py +14 -5
  77. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/song.py +8 -18
  78. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/songlist.py +0 -3
  79. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/user.py +6 -6
  80. qqmusic_api_python-0.7.2/web/src/routing/adapter_registry.py +56 -0
  81. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routing/docstrings.py +7 -1
  82. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routing/executor.py +9 -6
  83. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routing/params.py +32 -11
  84. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routing/route_types.py +3 -2
  85. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routing/router_factory.py +45 -11
  86. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/tests/test_web_core.py +3 -3
  87. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/tests/test_web_docstrings.py +20 -0
  88. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/tests/test_web_routes.py +7 -5
  89. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/zensical.toml +7 -2
  90. qqmusic_api_python-0.7.0/.agents/skills/tarsio/SKILL.md +0 -247
  91. qqmusic_api_python-0.7.0/.agents/skills/tarsio/references/api-reference.md +0 -246
  92. qqmusic_api_python-0.7.0/docs/tutorial/pagination.md +0 -112
  93. qqmusic_api_python-0.7.0/qqmusic_api/core/client.py +0 -664
  94. qqmusic_api_python-0.7.0/qqmusic_api/core/pagination.py +0 -499
  95. qqmusic_api_python-0.7.0/qqmusic_api/core/request.py +0 -150
  96. qqmusic_api_python-0.7.0/qqmusic_api/models/lyric.py +0 -34
  97. qqmusic_api_python-0.7.0/qqmusic_api/modules/_base.py +0 -351
  98. qqmusic_api_python-0.7.0/qqmusic_api/modules/lyric.py +0 -53
  99. qqmusic_api_python-0.7.0/qqmusic_api/modules/search.py +0 -181
  100. qqmusic_api_python-0.7.0/tests/test_lyric.py +0 -39
  101. qqmusic_api_python-0.7.0/web/src/modules/__init__.py +0 -1
  102. qqmusic_api_python-0.7.0/web/src/routes/lyric.py +0 -10
  103. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.agents/skills/pydantic/SKILL.md +0 -0
  104. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.agents/skills/python-standards/SKILL.md +0 -0
  105. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.agents/skills/uv-package-manager/SKILL.md +0 -0
  106. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.dockerignore +0 -0
  107. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  108. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  109. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
  110. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  111. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/renovate.json +0 -0
  112. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/workflows/checking.yaml +0 -0
  113. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.gitignore +0 -0
  114. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.markdownlint-cli2.yaml +0 -0
  115. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/CLAUDE.md +0 -0
  116. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/LICENSE +0 -0
  117. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/README.md +0 -0
  118. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/assets/qq-music.svg +0 -0
  119. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/cliff.toml +0 -0
  120. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/index.md +0 -0
  121. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/core/client.md +0 -0
  122. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/core/exception.md +0 -0
  123. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/core/pagination.md +0 -0
  124. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/core/request.md +0 -0
  125. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/core/versioning.md +0 -0
  126. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/album.md +0 -0
  127. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/base.md +0 -0
  128. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/comment.md +0 -0
  129. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/login.md +0 -0
  130. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/lyric.md +0 -0
  131. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/mv.md +0 -0
  132. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/recommend.md +0 -0
  133. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/request.md +0 -0
  134. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/search.md +0 -0
  135. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/singer.md +0 -0
  136. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/song.md +0 -0
  137. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/songlist.md +0 -0
  138. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/top.md +0 -0
  139. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/user.md +0 -0
  140. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/album.md +0 -0
  141. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/comment.md +0 -0
  142. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/login.md +0 -0
  143. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/login_utils.md +0 -0
  144. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/lyric.md +0 -0
  145. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/mv.md +0 -0
  146. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/private_message.md +0 -0
  147. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/recommend.md +0 -0
  148. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/search.md +0 -0
  149. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/singer.md +0 -0
  150. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/song.md +0 -0
  151. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/songlist.md +0 -0
  152. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/top.md +0 -0
  153. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/user.md +0 -0
  154. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/tutorial/credential.md +0 -0
  155. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/tutorial/download.md +0 -0
  156. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/tutorial/error-handling.md +0 -0
  157. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/tutorial/login.md +0 -0
  158. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/tutorial/start.md +0 -0
  159. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/tutorial/web.md +0 -0
  160. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/examples/download_song.py +0 -0
  161. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/examples/get_all_sheets.py +0 -0
  162. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/examples/phone_login.py +0 -0
  163. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/examples/private_message.py +0 -0
  164. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/examples/qrcode_login.py +0 -0
  165. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/examples/upload_file.py +0 -0
  166. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/algorithms/__init__.py +0 -0
  167. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/algorithms/sign.py +0 -0
  168. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/algorithms/tripledes.py +0 -0
  169. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/core/exceptions.py +0 -0
  170. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/core/versioning.py +0 -0
  171. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/__init__.py +0 -0
  172. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/_validator.py +0 -0
  173. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/album.py +0 -0
  174. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/base.py +0 -0
  175. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/helper.py +0 -0
  176. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/login.py +0 -0
  177. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/mv.py +0 -0
  178. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/private_message.py +0 -0
  179. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/recommend.py +0 -0
  180. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/song.py +0 -0
  181. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/songlist.py +0 -0
  182. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/top.py +0 -0
  183. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/user.py +0 -0
  184. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/__init__.py +0 -0
  185. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/helper_utils.py +0 -0
  186. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/login_utils.py +0 -0
  187. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/__init__.py +0 -0
  188. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/common.py +0 -0
  189. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/device.py +0 -0
  190. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/qimei.py +0 -0
  191. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/scripts/ag-1.py +0 -0
  192. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_login.py +0 -0
  193. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_login_utils.py +0 -0
  194. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_singer.py +0 -0
  195. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_songlist.py +0 -0
  196. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_top.py +0 -0
  197. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_user.py +0 -0
  198. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/.gitignore +0 -0
  199. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/Dockerfile +0 -0
  200. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/README.md +0 -0
  201. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/accounts.example.toml +0 -0
  202. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/config.example.toml +0 -0
  203. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/docker-compose.example.yml +0 -0
  204. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/run.py +0 -0
  205. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/__init__.py +0 -0
  206. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/config.py +0 -0
  207. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/credential_store.py +0 -0
  208. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/deps.py +0 -0
  209. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/response.py +0 -0
  210. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/__init__.py +0 -0
  211. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/album.py +0 -0
  212. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/top.py +0 -0
  213. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routing/__init__.py +0 -0
  214. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/tests/conftest.py +0 -0
  215. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/tests/test_web_enums.py +0 -0
  216. {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/tests/test_web_route_validation.py +0 -0
@@ -19,22 +19,22 @@ jobs:
19
19
  runs-on: ubuntu-latest
20
20
  steps:
21
21
  - name: Checkout
22
- uses: actions/checkout@v6
22
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
23
23
  with:
24
24
  fetch-depth: 0
25
25
  token: ${{ secrets.GH_TOKEN }}
26
26
  - name: Setup Pages
27
27
  id: pages
28
- uses: actions/configure-pages@v6
28
+ uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6
29
29
  - name: Install uv
30
- uses: astral-sh/setup-uv@v7
30
+ uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7
31
31
  with:
32
32
  enable-cache: true
33
33
  python-version: 3.12
34
34
  - name: Install the project
35
35
  run: uv sync --group docs
36
36
  - name: Generate release-notes
37
- uses: orhun/git-cliff-action@v4
37
+ uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4
38
38
  with:
39
39
  config: cliff.toml
40
40
  args: --verbose --strip all
@@ -55,7 +55,7 @@ jobs:
55
55
  - name: Build with zensical
56
56
  run: uv run zensical build
57
57
  - name: Upload artifact
58
- uses: actions/upload-pages-artifact@v5
58
+ uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
59
59
  with:
60
60
  path: ./site
61
61
 
@@ -68,4 +68,4 @@ jobs:
68
68
  steps:
69
69
  - name: Deploy to GitHub Pages
70
70
  id: deployment
71
- uses: actions/deploy-pages@v5
71
+ uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
@@ -15,13 +15,13 @@ jobs:
15
15
  outputs:
16
16
  release_body: ${{ steps.git-cliff.outputs.content }}
17
17
  steps:
18
- - uses: actions/checkout@v6
18
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
19
19
  with:
20
20
  fetch-depth: 0
21
21
  token: ${{ secrets.GH_TOKEN }}
22
22
  - name: Generate release body
23
23
  id: git-cliff
24
- uses: orhun/git-cliff-action@v4
24
+ uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4
25
25
  with:
26
26
  args: -vv --latest --strip all
27
27
 
@@ -32,11 +32,11 @@ jobs:
32
32
  permissions:
33
33
  id-token: write
34
34
  steps:
35
- - uses: actions/checkout@v6
35
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
36
36
  with:
37
37
  token: ${{ secrets.GH_TOKEN }}
38
38
  - name: Install uv
39
- uses: astral-sh/setup-uv@v7
39
+ uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7
40
40
  with:
41
41
  enable-cache: true
42
42
  - name: Install the project
@@ -51,11 +51,11 @@ jobs:
51
51
  needs: [generate-release-body, publish]
52
52
  steps:
53
53
  - name: Checkout
54
- uses: actions/checkout@v6
54
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
55
55
  with:
56
56
  token: ${{ secrets.GH_TOKEN }}
57
57
  - name: Release
58
- uses: softprops/action-gh-release@v3
58
+ uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3
59
59
  with:
60
60
  body: ${{ needs.generate-release-body.outputs.release_body }}
61
61
  tag_name: ${{ github.ref_name }}
@@ -10,9 +10,9 @@ jobs:
10
10
  test:
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
- - uses: actions/checkout@v6
13
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
14
14
  - name: Install uv
15
- uses: astral-sh/setup-uv@v7
15
+ uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7
16
16
  with:
17
17
  enable-cache: true
18
18
  python-version: "3.10"
@@ -23,9 +23,12 @@
23
23
 
24
24
  ## Testing rules
25
25
 
26
- * **专注 Modules 层**:测试重心放在 `modules` 层,将其视为黑盒。采用基于数据驱动(`@pytest.mark.parametrize`)的函数式测试方法,将输入参数与期望结果的特征断言解耦。
27
- * **真实网络请求(No Mock)**:禁止 Mock 底层网络请求或核心组装逻辑。必须直接与真实的 QQ 音乐 API 交互,以验证接口连通性、参数拼装和数据模型(Models)解析的正确性。
28
- * **优雅处理限流(Rate Limit)**:由于采用真实网络请求,当触发上游 API 的风控或频率限制异常时,必须使用自定义装饰器捕获该异常并调用 `pytest.skip()` 安全跳过,严禁因此导致测试失败。
26
+ * **双重视角测试**:测试按层级分为 Modules 与 Core 两类视角,均以黑盒方式覆盖可观察行为:
27
+ * **Modules 层(集成测试)**:测试重心,视为黑盒,采用基于数据驱动(`@pytest.mark.parametrize`)的函数式测试方法,将输入参数与期望结果的特征断言解耦。
28
+ * **Core 层(单元测试)**:必要的核心逻辑测试,覆盖请求分组、批量合并、参数拼装、异常转换、分页推进等纯逻辑,不发起任何网络请求,保证测试确定性与快速性。
29
+ * **Modules 层真实网络请求(No Mock)**:禁止 Mock 底层网络请求或核心组装逻辑。必须直接与真实的 QQ 音乐 API 交互,以验证接口连通性、参数拼装和数据模型(Models)解析的正确性。
30
+ * **Core 层桩数据(Stub Only)**:仅允许使用自定义桩(如 `DummyResponse`、`MockClient`)驱动被测逻辑,禁止 Mock 被测对象自身的内部行为;不得依赖真实网络。
31
+ * **优雅处理限流(Rate Limit)**:由于 Modules 层采用真实网络请求,当触发上游 API 的风控或频率限制异常时,必须使用自定义装饰器捕获该异常并调用 `pytest.skip()` 安全跳过,严禁因此导致测试失败。
29
32
  * **平铺函数写法**:摒弃测试类(`class TestXXX`),强制采用平铺的独立纯函数(Flat Functions)编写测试用例,通过 fixtures 注入依赖,保证测试的独立性。
30
33
  * 测试用例必须包含单行中文 docstring,且 docstring 内部必须使用英文标点符号。
31
34
  * 优先在现有的测试文件中添加用例,仅在测试全新模块时才允许创建新的测试文件。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qqmusic-api-python
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: QQ音乐API封装库
5
5
  Project-URL: documentation, https://l-1124.github.io/QQMusicApi/
6
6
  Project-URL: homepage, https://l-1124.github.io/QQMusicApi/
@@ -35,7 +35,6 @@ Requires-Dist: niquests[speedups]>=3.18.7
35
35
  Requires-Dist: orjson>=3.10.15
36
36
  Requires-Dist: paho-mqtt>=2.1.0
37
37
  Requires-Dist: pydantic>=2.13.3
38
- Requires-Dist: tarsio>=0.5.1
39
38
  Requires-Dist: typing-extensions>=4.12.2
40
39
  Provides-Extra: upload
41
40
  Requires-Dist: cos-python-sdk-v5>=1.9.44; extra == 'upload'
@@ -11,31 +11,32 @@
11
11
 
12
12
  ```text
13
13
  模块方法
14
- -> self._build_request(...)
15
- -> Request
14
+ -> self._build_cgi(...) / self._build_http(...)
15
+ -> BaseRequest 描述符
16
16
  -> await request
17
17
  -> Client.execute(request)
18
- -> Client.request_api(...) (根据 request.is_jce 分发改用 JCE 或 JSON 协议)
19
- -> Client._build_result(...)
20
- -> 返回原始 dict / TarsDict 或 Pydantic 模型
18
+ -> ApiContext 注入环境与凭证
19
+ -> Session.post(...) / Session.request(...)
20
+ -> Request._parse_response(...)
21
+ -> 返回原始 dict 或 Pydantic 模型
21
22
  ```
22
23
 
23
24
  ### 批量并发请求
24
25
 
25
26
  ```text
26
27
  多个模块方法
27
- -> self._build_request(...)
28
- -> Request 列表
28
+ -> self._build_cgi(...)
29
+ -> BaseRequest 描述符列表
29
30
  -> Client.gather(requests)
30
- -> 按协议、平台、公共参数和凭证分组
31
+ -> 按协议、平台、公共参数和凭证配置键自动分组
31
32
  -> 每组按 batch_size 拆分为批量请求
32
- -> 依次调用 Client.request_api(..., lazy=True) 生成响应任务
33
- -> 使用客户端内部的 multiplexed AsyncSession 并发执行这些任务(self._session.gather)
34
- -> 按 req_n 解析每个响应项
35
- -> 按输入顺序返回结果
33
+ -> 依次发起合并的多参 CGI 请求(req_0, req_1...)
34
+ -> 使用客户端内部的 Session 并发执行这些任务(self._session.gather)
35
+ -> 统一解包解析每个响应项
36
+ -> 按输入顺序返回结果列表
36
37
  ```
37
38
 
38
- `gather` 的分组边界由 `Request._group_key` 决定。只有协议类型、显式平台、公共参数和凭证相同的请求才会合并到同一个批量请求中。
39
+ `gather` 的分组边界由 `BaseRequest._group_key` 决定。只有协议类型、显式平台、公共参数和凭证相同的请求才会安全地合并到同一个批量请求中。
39
40
 
40
41
  ## 编写新的 API
41
42
 
@@ -55,7 +56,7 @@ class FooApi(ApiModule):
55
56
 
56
57
  def get_something(self, id: int):
57
58
  """获取某项数据."""
58
- return self._build_request(
59
+ return self._build_cgi(
59
60
  module="music.foo.Svc",
60
61
  method="GetSomething",
61
62
  param={"id": id},
@@ -66,6 +67,7 @@ class FooApi(ApiModule):
66
67
  # qqmusic_api/core/client.py
67
68
  from functools import cached_property
68
69
 
70
+
69
71
  class Client:
70
72
  @cached_property
71
73
  def foo(self) -> "FooApi":
@@ -75,84 +77,78 @@ class Client:
75
77
 
76
78
  ### 添加新的请求方法
77
79
 
78
- API 方法返回 `Request` 对象,不直接发起请求。使用 `self._build_request(...)` 工厂方法构建:
80
+ API 方法返回 `BaseRequest` 描述符对象,并不立即发起请求。对于标准 CGI 风格的 RPC 请求,使用 `self._build_cgi(...)` 工厂方法构建:
79
81
 
80
82
  ```python
81
83
  def get_detail(self, song_id: int):
82
84
  """获取歌曲详情."""
83
- return self._build_request(
84
- module="music.songDetail", # 接口所属模块
85
- method="GetDetail", # 方法名
86
- param={"songid": song_id}, # 业务参数
85
+ return self._build_cgi(
86
+ module="music.songDetail", # 接口所属模块
87
+ method="GetDetail", # 方法名
88
+ param={"songid": song_id}, # 业务参数
87
89
  )
88
90
  ```
89
91
 
90
- 对于非标准 CGI 接口(如直接 GET 请求),使用 `self._request(...)`:
92
+ 对于非标准 CGI 接口(如直接 GET 请求、获取网页或二维码),使用 `self._build_http(...)`:
91
93
 
92
94
  ```python
93
95
  async def quick_search(self, keyword: str) -> dict[str, Any]:
94
96
  """快速搜索 (直接返回解析后的 JSON 数据)."""
95
- resp = await self._request(
97
+ resp = await self._build_http(
96
98
  "GET",
97
99
  "https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg",
98
100
  params={"key": keyword},
99
101
  )
100
- resp.raise_for_status()
101
- return resp.json()["data"]
102
+ return resp["data"]
102
103
  ```
103
104
 
104
- ### `_build_request` 参数说明
105
-
106
- | 参数 | 类型 | 说明 |
107
- |------------------|-----------------------------|-----------------------------------------------------------|
108
- | `module` | `str` | 接口所属模块名 |
109
- | `method` | `str` | 方法名 |
110
- | `param` | `dict` | 业务参数 |
111
- | `response_model` | `type[BaseModel]` 或 `None` | 响应模型,为 None 时返回原始 dict |
112
- | `comm` | `dict` 或 `None` | 附加的公共参数 |
113
- | `override_comm` | `bool` | 为 True 时 `comm` 完全替代自动生成的参数;为 False 时合并 |
114
- | `credential` | `Credential` 或 `None` | 覆盖本次请求的凭证 |
115
- | `platform` | `Platform` 或 `None` | 覆盖本次请求的平台 |
116
- | `is_jce` | `bool` | 是否作为 JCE (Tars) 二进制协议发送 |
117
- | `preserve_bool` | `bool` | 是否保留布尔值原样(默认转为 0/1 整型) |
118
- | `sign` | `bool` | 是否对请求进行签名 |
119
- | `pager_meta` | `PagerMeta` 或 `None` | 分页元数据,提供后返回 `PaginatedRequest` |
120
- | `refresh_meta` | `RefreshMeta` 或 `None` | 换一批元数据,提供后返回 `RefreshableRequest` |
121
-
122
- ### `client.request` 参数说明
123
-
124
- `client.request` 是底层 HTTP 请求方法,自动装配凭证 Cookies 和平台 User-Agent:
125
-
126
- | 参数 | 类型 | 说明 |
127
- |--------------|------------------------|----------------------------------------------------------------------------------:|
128
- | `method` | `str` | HTTP 方法,如 `"GET"`、`"POST"` |
129
- | `url` | `str` | 请求地址 |
130
- | `credential` | `Credential` 或 `None` | 覆盖本次请求的凭证,默认使用客户端凭证 |
131
- | `platform` | `Platform` 或 `None` | 覆盖本次请求的平台,默认使用客户端平台 |
132
- | `lazy` | `bool` | 是否延迟发送请求(用于批量并发) |
133
- | `**kwargs` | | 透传给底层 `niquests` 的参数(`params`、`json`、`data`、`headers`、`cookies` 等) |
105
+ ### `_build_cgi` 参数说明
106
+
107
+ | 参数 | 类型 | 说明 |
108
+ |------------------|-----------------------------|-------------------------------------------------------------------------------------------------------------|
109
+ | `module` | `str` | 接口所属模块名 |
110
+ | `method` | `str` | 方法名 |
111
+ | `param` | `dict` | 业务参数 |
112
+ | `response_model` | `type[BaseModel]` 或 `None` | 响应模型,为 None 时返回原始 dict |
113
+ | `comm` | `dict` 或 `None` | 附加的公共参数 |
114
+ | `override_comm` | `bool` | 为 True 时 `comm` 完全替代自动生成的参数;为 False 时合并 |
115
+ | `credential` | `Credential` 或 `None` | 覆盖本次请求的凭证 |
116
+ | `platform` | `Platform` 或 `None` | 覆盖本次请求的平台 |
117
+ | `preserve_bool` | `bool` | 是否保留布尔值原样(默认转为 0/1 整型) |
118
+ | `sign` | `bool` | 是否对请求进行签名 |
119
+ | `require_login` | `bool` | 是否在执行时强制校验用户登录态 |
120
+ | `pager_strategy` | `PagerStrategy` 或 `None` | 分页策略,提供后返回 `PaginatedCgiRequest`;可链式调用 `.with_extractor()` 提升为 `ItemPaginatedCgiRequest` |
121
+
122
+ ### `_build_http` 参数说明
123
+
124
+ `_build_http` 用于构建标准 HTTP 请求描述符,自动装配凭证 Cookies 和平台 User-Agent:
125
+
126
+ | 参数 | 类型 | 说明 |
127
+ |-----------------|------------------------|----------------------------------------------------------------------------------:|
128
+ | `method` | `str` | HTTP 方法,如 `"GET"`、`"POST"` |
129
+ | `url` | `str` | 请求地址 |
130
+ | `credential` | `Credential` 或 `None` | 覆盖本次请求的凭证,默认使用客户端凭证 |
131
+ | `disable_parse` | `bool` | 为 True 时不解析 JSON,直接返回原始 `niquests.Response` 对象 |
132
+ | `**kwargs` | | 透传给底层 `niquests` 的参数(`params`、`json`、`data`、`headers`、`cookies` 等) |
134
133
 
135
134
  !!! note
136
135
 
137
- `client.request` 返回的是原始 `niquests.Response` 对象,需要手动解析响应。而 `_build_request` 返回的 `Request` 对象支持 `await`,会自动完成响应验证和模型解析。
136
+ `_build_cgi` 返回 `CgiRequest`,`_build_http` 返回 `HttpRequest`,两者均继承自 `BaseRequest`。它们都支持直接被 `await` 以触发网络请求并自动完成响应验证和模型解析。
138
137
 
139
138
  常见用法:
140
139
 
141
140
  ```python
142
141
  # GET 请求
143
- resp = await client.request("GET", "https://example.com/api", params={"key": "value"})
142
+ req = self._build_http("GET", "https://example.com/api", params={"key": "value"})
144
143
 
145
144
  # POST JSON
146
- resp = await client.request("POST", "https://example.com/api", json={"key": "value"})
147
-
148
- # POST form data
149
- resp = await client.request("POST", "https://example.com/api", data={"key": "value"})
150
-
151
- # 自定义 headers
152
- resp = await client.request("GET", "https://example.com/api", headers={"X-Custom": "value"})
145
+ req = self._build_http("POST", "https://example.com/api", json={"key": "value"})
153
146
 
154
147
  # 覆盖凭证
155
- resp = await client.request("GET", "https://example.com/api", credential=my_credential)
148
+ req = self._build_http("GET", "https://example.com/api", credential=my_credential)
149
+
150
+ # 返回原始 Response 而非解析 JSON
151
+ req = self._build_http("GET", "https://example.com/api", disable_parse=True)
156
152
  ```
157
153
 
158
154
  ## 响应模型
@@ -176,6 +172,17 @@ class MyResponse(Response):
176
172
 
177
173
  `Response` 基类配置了 `frozen=True`(不可变)和 `extra="ignore"`(忽略多余字段)。
178
174
 
175
+ !!! warning "Pydantic 默认值规范"
176
+
177
+ 定义模型时应避免使用 `None` 作为隐式兜底默认值。如果字段可选或为空,应当使用显式的空标量,或通过 `Field(default_factory=...)` 声明:
178
+ ```python
179
+ class Album(Response):
180
+ name: str = ""
181
+ publish_time: str = ""
182
+ # 列表必须使用 default_factory
183
+ singers: list[Singer] = Field(default_factory=list)
184
+ ```
185
+
179
186
  ### JSONPath 字段映射
180
187
 
181
188
  可以通过 `Field(json_schema_extra={"jsonpath": ...})` 声明字段的 JSONPath 映射路径,自动从嵌套响应中提取数据:
@@ -221,12 +228,12 @@ class Singer(Response):
221
228
 
222
229
  ### 需登录的接口
223
230
 
224
- 需要登录的接口通过 `_build_request` 的 `require_login` 参数校验凭证:
231
+ 需要登录的接口通过 `_build_cgi` 的 `require_login` 参数校验凭证:
225
232
 
226
233
  ```python
227
234
  def get_vip_info(self, *, credential: Credential | None = None):
228
235
  """获取 VIP 信息."""
229
- return self._build_request(
236
+ return self._build_cgi(
230
237
  module="VipLogin.VipLoginInter",
231
238
  method="vip_login_base",
232
239
  param={},
@@ -236,22 +243,24 @@ def get_vip_info(self, *, credential: Credential | None = None):
236
243
  )
237
244
  ```
238
245
 
239
- > 若接口需要凭证对象的字段来构建请求参数,
240
- > 仍可显式调用 `_require_login` 获取凭证对象。
246
+ > 若接口需要凭证对象的属性(如 `musicid` 等)来构建请求内联参数,
247
+ > 仍可通过 `credential = credential or self._client.credential` 获取并显式验证其有效性。
241
248
 
242
- ## 翻页与换一批
249
+ ## 连续翻页与批次刷新
243
250
 
244
251
  ### 连续翻页
245
252
 
246
- 通过 `pager_meta` 声明连续翻页能力,返回的请求对象会暴露 `.paginate()`:
253
+ 通过 `pager_strategy` 声明连续翻页能力,建议配合显示 Generic 标注(形如
254
+ `OffsetStrategy[GetSonglistDetailResponse]`)以确保静态类型检查与类型推断的准确性,并通过 `.with_extractor()`
255
+ 链式调用绑定实体数据项的提取逻辑:
247
256
 
248
257
  ```python
249
- from ..core.pagination import OffsetStrategy, PagerMeta, ResponseAdapter
258
+ from ..core.pagination import OffsetStrategy
250
259
 
251
260
 
252
261
  def get_detail(self, songlist_id: int, num: int = 10, page: int = 1):
253
262
  """获取歌单详情."""
254
- return self._build_request(
263
+ return self._build_cgi(
255
264
  module="music.srfDissInfo.DissInfo",
256
265
  method="CgiGetDiss",
257
266
  param={
@@ -260,40 +269,38 @@ def get_detail(self, songlist_id: int, num: int = 10, page: int = 1):
260
269
  "song_num": num,
261
270
  },
262
271
  response_model=GetSonglistDetailResponse,
263
- pager_meta=PagerMeta(
264
- strategy=OffsetStrategy(offset_key="song_begin", page_size_key="song_num"),
265
- adapter=ResponseAdapter(
266
- has_more_flag="hasmore",
267
- total="total",
268
- count=lambda response: len(response.songs),
269
- ),
272
+ pager_strategy=OffsetStrategy[GetSonglistDetailResponse](
273
+ offset_key="song_begin",
274
+ page_size_key="song_num",
275
+ has_more_extractor=lambda response: bool(response.hasmore),
276
+ total_extractor=lambda response: response.total,
277
+ count_extractor=lambda response: len(response.songs),
270
278
  ),
271
- )
279
+ ).with_extractor(lambda response: response.songs)
272
280
  ```
273
281
 
274
- ### 换一批
282
+ ### 批次刷新 (Batch Refresh)
275
283
 
276
- 通过 `refresh_meta` 声明换一批能力,返回的请求对象会暴露 `.refresh()`:
284
+ 批次刷新(Batch Refresh)是一种针对推荐或关联接口、支持游标复位与防循环重复游标的特殊游标分页,同样通过 `pager_strategy` 声明:
277
285
 
278
286
  ```python
279
- from ..core.pagination import BatchRefreshStrategy, RefreshMeta, ResponseAdapter
287
+ from ..core.pagination import BatchRefreshStrategy
288
+ from ..models.base import MV
280
289
 
281
290
 
282
291
  def get_related_mv(self, songid: int, last_mvid: str | None = None):
283
292
  """获取歌曲相关 MV."""
284
- return self._build_request(
293
+ return self._build_cgi(
285
294
  module="MvService.MvInfoProServer",
286
295
  method="GetSongRelatedMv",
287
296
  param={"songid": str(songid), "songtype": 1, "lastmvid": last_mvid or 0},
288
297
  response_model=GetRelatedMvResponse,
289
- refresh_meta=RefreshMeta(
290
- strategy=BatchRefreshStrategy(refresh_key="lastmvid"),
291
- adapter=ResponseAdapter(
292
- has_more_flag="has_more",
293
- cursor=lambda response: response.mv[-1].id if response.mv else None,
294
- ),
298
+ pager_strategy=BatchRefreshStrategy[GetRelatedMvResponse](
299
+ refresh_key="lastmvid",
300
+ cursor_extractor=lambda response: response.mv[-1].id if response.mv else None,
301
+ has_more_extractor=lambda response: bool(response.has_more),
295
302
  ),
296
- )
303
+ ).with_extractor(lambda response: response.mv)
297
304
  ```
298
305
 
299
306
  ### 内置策略速查
@@ -304,26 +311,7 @@ def get_related_mv(self, songid: int, last_mvid: str | None = None):
304
311
  | `OffsetStrategy` | 偏移量滑窗 | `offset_key` + `page_size_key` |
305
312
  | `CursorStrategy` | 响应游标回写 | `cursor_key` |
306
313
  | `MultiFieldContinuationStrategy` | 多字段续翻 | 自定义 `build_next_params` 函数 |
307
- | `BatchRefreshStrategy` | 换一批 | `refresh_key` |
308
-
309
- `pager_meta` 与 `refresh_meta` 不能同时声明。
310
-
311
- ## JCE (Tars) 协议
312
-
313
- 部分接口使用 JCE 二进制协议而非 JSON。通过 `is_jce=True` 启用:
314
-
315
- ```python
316
- def get_something(self):
317
- """获取数据 (JCE 协议)."""
318
- return self._build_request(
319
- module="music.foo.Svc",
320
- method="GetSomething",
321
- param={0: "value"}, # JCE 使用整数 key
322
- is_jce=True,
323
- )
324
- ```
325
-
326
- JCE 协议的响应会自动解码为 `TarsDict`。
314
+ | `BatchRefreshStrategy` | 批次刷新 | `refresh_key` |
327
315
 
328
316
  ## 请求签名
329
317
 
@@ -332,7 +320,7 @@ JCE 协议的响应会自动解码为 `TarsDict`。
332
320
  ```python
333
321
  def get_sheet(self, mid: str):
334
322
  """获取曲谱."""
335
- return self._build_request(
323
+ return self._build_cgi(
336
324
  module="music.mir.SheetMusicSvr",
337
325
  method="GetMoreSheetMusic",
338
326
  param={"songMid": mid},
@@ -348,7 +336,7 @@ def get_sheet(self, mid: str):
348
336
 
349
337
  ```python
350
338
  # 合并到自动生成的 comm 中(默认行为)
351
- self._build_request(
339
+ self._build_cgi(
352
340
  ...,
353
341
  comm={"extra_key": "value"},
354
342
  )
@@ -357,7 +345,7 @@ self._build_request(
357
345
  使用 `override_comm=True` 完全替代自动生成的参数:
358
346
 
359
347
  ```python
360
- self._build_request(
348
+ self._build_cgi(
361
349
  ...,
362
350
  comm={
363
351
  "g_tk": 5381,
@@ -372,6 +360,20 @@ self._build_request(
372
360
  )
373
361
  ```
374
362
 
363
+ ## 异常处理
364
+
365
+ 在抛出或处理异常时,应使用项目统一的基于领域驱动(DDD)风格的异常类(继承自 `BaseApiException` 或 `ApiException`
366
+ )。在包装底层异常时,必须使用原生异常链(`raise ... from exc`)保留堆栈追踪:
367
+
368
+ ```python
369
+ from ..core.exceptions import ApiDataError
370
+
371
+ try:
372
+ ...
373
+ except KeyError as e:
374
+ raise ApiDataError("无法解析歌曲信息") from e
375
+ ```
376
+
375
377
  ## 编写测试
376
378
 
377
379
  测试文件放在 `tests/` 下,按模块命名(如 `test_song.py`)。
@@ -398,8 +400,15 @@ async def test_query_song(client: Client) -> None:
398
400
  ```python
399
401
  @pytest.mark.parametrize("page", [1, 2])
400
402
  async def test_general_search(client: Client, page: int) -> None:
401
- """测试综合搜索翻页."""
402
- result = await client.search.general_search("周杰伦", page=page)
403
+ """测试综合搜索翻页逻辑."""
404
+ try:
405
+ result = await client.search.general_search("周杰伦", page=page)
406
+ except Exception as e:
407
+ # 示例:优雅处理网络风控或限流 (需根据实际异常类型调整)
408
+ if "limit" in str(e).lower() or "risk" in str(e).lower():
409
+ pytest.skip(f"Triggered rate limit or risk control: {e}")
410
+ raise
411
+
403
412
  assert result.song.items is not None
404
413
  ```
405
414
 
@@ -419,7 +428,7 @@ async def test_get_vip_info(authenticated_client: Client) -> None:
419
428
  ```python
420
429
  async def test_search_paginate(client: Client) -> None:
421
430
  """测试搜索分页."""
422
- pager = client.search.search_by_type("周杰伦", num=5).paginate(limit=2)
431
+ pager = client.search.search_by_type("周杰伦", num=5).pager(limit=2)
423
432
 
424
433
  assert pager.has_more() is True
425
434
  first_page = await pager.next()
@@ -80,7 +80,7 @@ uv run mkdocs serve
80
80
 
81
81
  * 注释内容包括:模块注释、类注释、函数注释、参数类型注释、返回值注释
82
82
  * 注释风格遵循 [Google-style docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
83
- * 测试函数应包含单行中文 docstring(英文标点)
83
+ * 测试用例必须包含 **单行中文 docstring**,且内部必须使用 **英文标点符号**。
84
84
 
85
85
  ## 文档规范
86
86
 
@@ -0,0 +1,3 @@
1
+ # helper
2
+
3
+ ::: models.helper
@@ -0,0 +1,3 @@
1
+ # private_message
2
+
3
+ ::: models.private_message
@@ -0,0 +1,3 @@
1
+ # HelperApi
2
+
3
+ ::: modules.helper.HelperApi
@@ -0,0 +1,3 @@
1
+ # HelperUtils
2
+
3
+ ::: modules.helper_utils
@@ -1,4 +1,62 @@
1
1
 
2
+ ## [[0.7.1](https://github.com/L-1124/QQMusicApi/compare/v0.7.0..v0.7.1)] - 2026-08-02
3
+
4
+ ### Bug 修复
5
+
6
+ * **(Song)** 移除 SongApi 中不必要的参数说明 ([e4b02c7](https://github.com/L-1124/QQMusicApi/commit/e4b02c7b0c4ccc8b7310701985bb5c6d62e21f4f)) by [@L-1124](https://github.com/L-1124)
7
+ * **(web)** 使用 context.credential 修复 adapter 凭证读取 KeyError ([261326e](https://github.com/L-1124/QQMusicApi/commit/261326eec051e7f444296b5c461e7412c4b25bb9)) by [@L-1124](https://github.com/L-1124)
8
+ * **(web)** 修复 404 响应不符合 RESTful 规范的问题 ([9658bd5](https://github.com/L-1124/QQMusicApi/commit/9658bd54b45c8eeab75f651ba26b16e1fd1126ce)) by [@L-1124](https://github.com/L-1124)
9
+ * **(web)** 修复 Web 模块代码质量与性能问题 ([329f822](https://github.com/L-1124/QQMusicApi/commit/329f822776376aad7bd6b3ede3e9dd9e5730c77c)) by [@L-1124](https://github.com/L-1124)
10
+
11
+ ### Dep-bump
12
+
13
+ * **(deps)** update dependencies via uv lock --upgrade ([943ed52](https://github.com/L-1124/QQMusicApi/commit/943ed522e8bf6d385e3a0abb77663255ea57ea25)) by [@L-1124](https://github.com/L-1124)
14
+
15
+ ### 功能更新
16
+
17
+ * **(comment)** 支持设置评论业务类型与子类型 ([ea6095c](https://github.com/L-1124/QQMusicApi/commit/ea6095ce89772798154cf85785a2d72ee9a57ac4)) by [@L-1124](https://github.com/L-1124)
18
+ * **(lyric)** 支持获取 AI 歌词词典 ([44e81f0](https://github.com/L-1124/QQMusicApi/commit/44e81f0521d73934a3bedacc3ec64870549c5f9c)) by [@L-1124](https://github.com/L-1124)
19
+ * **(lyric)** 支持获取多风格翻译歌词 ([43d6429](https://github.com/L-1124/QQMusicApi/commit/43d64299a3e57779859f4ba5f876f0b11198e2ab)) by [@L-1124](https://github.com/L-1124)
20
+ * **(lyric)** 支持获取助唱标注歌词及信息 ([3166fea](https://github.com/L-1124/QQMusicApi/commit/3166feabf77e223b82dca6922f4e09dd5eed0a15)) by [@L-1124](https://github.com/L-1124)
21
+ * **(search)** 支持彩铃搜索类型 RINGTONE 与彩铃文件类型 RingSongFileType ([6c2118c](https://github.com/L-1124/QQMusicApi/commit/6c2118ccdd6e1e4eed9dd49d82b12bf63664630d)) by [@L-1124](https://github.com/L-1124)
22
+ * **(search)** 类型搜索支持选择器 ([e097442](https://github.com/L-1124/QQMusicApi/commit/e097442d2a7e86f7f77bef016416554591370f41)) by [@L-1124](https://github.com/L-1124)
23
+ * **(web)** 引入 AuthPolicy.OPTIONAL 并对齐 SDK 凭证策略 ([ea29202](https://github.com/L-1124/QQMusicApi/commit/ea29202766ebc9a309c7fb3db495c246323289f7)) by [@L-1124](https://github.com/L-1124)
24
+ * **(web)** 优化枚举参数在 OpenAPI 文档中的说明展示 ([075a1ed](https://github.com/L-1124/QQMusicApi/commit/075a1ed9d964d374b4d34759b7d5f1a3fed937fc)) by [@L-1124](https://github.com/L-1124)
25
+ * **(web)** 支持 list[BaseModel] 类型的 JSON Query 参数解析 ([9dd26ee](https://github.com/L-1124/QQMusicApi/commit/9dd26eeff3871c1a4990c58b23d497737e547ed2)) by [@L-1124](https://github.com/L-1124)
26
+
27
+ ### 功能重构
28
+
29
+ * **(core)** [**breaking**] 重构并统一分页体系 ([cb41e16](https://github.com/L-1124/QQMusicApi/commit/cb41e16e62e9967010663a94db846dd205d45d1b)) by [@L-1124](https://github.com/L-1124) in [#299](https://github.com/L-1124/QQMusicApi/pull/299)
30
+ * **(core)** [**breaking**] 移除未使用的 JCE 协议支持 ([dca6c7c](https://github.com/L-1124/QQMusicApi/commit/dca6c7cb623942f02d7cad44b986a8c7fbc91858)) by [@L-1124](https://github.com/L-1124)
31
+ * **(lyric)** [**breaking**] 统一 song_id 为 songid ([e6d2a0b](https://github.com/L-1124/QQMusicApi/commit/e6d2a0baae21a3528115635b43a24781b9671017)) by [@L-1124](https://github.com/L-1124)
32
+ * **(web)** 引入 Adapter 注册表机制解耦路由声明 ([8b573c6](https://github.com/L-1124/QQMusicApi/commit/8b573c6a4da79e691762ccf9d81390730260b13c)) by [@L-1124](https://github.com/L-1124)
33
+
34
+ ### 文档更新
35
+
36
+ * **(api)** 补充缺失的 API 模块和数据模型文档映射 ([8fe6095](https://github.com/L-1124/QQMusicApi/commit/8fe6095b154b35406fa2587e585d1f82373720ac)) by [@L-1124](https://github.com/L-1124)
37
+
38
+ ### 贡献者
39
+
40
+ * @L-1124
41
+ * @mirkosalvato1-ctrl
42
+ * @github-actions[bot]
43
+
44
+ ## [[0.7.0](https://github.com/L-1124/QQMusicApi/compare/v0.6.9..v0.7.0)] - 2026-07-22
45
+
46
+ ### Bug 修复
47
+
48
+ * fix retry import path ([75d0693](https://github.com/L-1124/QQMusicApi/commit/75d069344a07c6713b4a616ed6304ca6dc1b740a)) by [@L-1124](https://github.com/L-1124)
49
+
50
+ ### 功能更新
51
+
52
+ * **(lyric)** 支持指定特殊歌曲类型(song_type)查询歌词 ([1b0aae0](https://github.com/L-1124/QQMusicApi/commit/1b0aae0db3ee6876b3a77b8d1ce3057b4b3c9cd5)) by [@L-1124](https://github.com/L-1124)
53
+ * **(song)** [**breaking**] 重构歌曲查询支持 SongQueryInfo 与 Web POST 批量路由 ([4cbb2c6](https://github.com/L-1124/QQMusicApi/commit/4cbb2c6e993d58db71494ce31809a144bfc33be4)) by [@L-1124](https://github.com/L-1124)
54
+
55
+ ### 贡献者
56
+
57
+ * @L-1124
58
+ * @github-actions[bot]
59
+
2
60
  ## [[0.6.9](https://github.com/L-1124/QQMusicApi/compare/v0.6.8..v0.6.9)] - 2026-07-12
3
61
 
4
62
  ### Bug 修复