qqmusic-api-python 0.7.2__tar.gz → 0.8.0__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 (252) hide show
  1. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.gitignore +1 -1
  2. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/PKG-INFO +3 -3
  3. qqmusic_api_python-0.8.0/docs/coding.md +560 -0
  4. qqmusic_api_python-0.8.0/docs/reference/model/sound_power.md +3 -0
  5. qqmusic_api_python-0.8.0/docs/reference/modules/sound_power.md +3 -0
  6. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/release-notes.md +40 -0
  7. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/tutorial/client.md +75 -2
  8. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/tutorial/credential.md +9 -0
  9. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/tutorial/pagination.md +9 -0
  10. qqmusic_api_python-0.8.0/examples/sound_power.py +153 -0
  11. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/pyproject.toml +1 -1
  12. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/__init__.py +3 -1
  13. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/core/__init__.py +29 -0
  14. qqmusic_api_python-0.8.0/qqmusic_api/core/client.py +292 -0
  15. qqmusic_api_python-0.8.0/qqmusic_api/core/endpoint.py +363 -0
  16. qqmusic_api_python-0.8.0/qqmusic_api/core/engine.py +378 -0
  17. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/core/exceptions.py +8 -0
  18. qqmusic_api_python-0.8.0/qqmusic_api/core/executor.py +400 -0
  19. qqmusic_api_python-0.8.0/qqmusic_api/core/request.py +233 -0
  20. qqmusic_api_python-0.8.0/qqmusic_api/core/response.py +259 -0
  21. qqmusic_api_python-0.8.0/qqmusic_api/core/transport.py +556 -0
  22. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/core/versioning.py +39 -73
  23. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/__init__.py +2 -1
  24. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/request.py +1 -0
  25. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/singer.py +21 -0
  26. qqmusic_api_python-0.8.0/qqmusic_api/models/sound_power.py +219 -0
  27. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/__init__.py +2 -0
  28. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/_base.py +92 -81
  29. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/album.py +51 -31
  30. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/comment.py +69 -42
  31. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/login.py +54 -52
  32. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/login_utils.py +2 -3
  33. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/lyric.py +43 -36
  34. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/mv.py +29 -17
  35. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/recommend.py +59 -34
  36. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/search.py +59 -43
  37. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/singer.py +91 -50
  38. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/song.py +165 -104
  39. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/songlist.py +31 -18
  40. qqmusic_api_python-0.8.0/qqmusic_api/modules/sound_power.py +191 -0
  41. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/top.py +21 -13
  42. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/user.py +149 -87
  43. qqmusic_api_python-0.8.0/qqmusic_api/utils/android_session.py +190 -0
  44. qqmusic_api_python-0.8.0/qqmusic_api/utils/device.py +435 -0
  45. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/utils/mqtt.py +2 -2
  46. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/utils/qimei.py +130 -63
  47. qqmusic_api_python-0.8.0/tests/kernel_contract.py +184 -0
  48. qqmusic_api_python-0.8.0/tests/test_android_session.py +128 -0
  49. qqmusic_api_python-0.8.0/tests/test_client.py +244 -0
  50. qqmusic_api_python-0.8.0/tests/test_device.py +86 -0
  51. qqmusic_api_python-0.8.0/tests/test_endpoint.py +498 -0
  52. qqmusic_api_python-0.8.0/tests/test_engine.py +337 -0
  53. qqmusic_api_python-0.8.0/tests/test_executor.py +891 -0
  54. qqmusic_api_python-0.8.0/tests/test_login_engine.py +246 -0
  55. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_pagination.py +10 -3
  56. qqmusic_api_python-0.8.0/tests/test_qimei.py +173 -0
  57. qqmusic_api_python-0.8.0/tests/test_response.py +281 -0
  58. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_singer.py +11 -0
  59. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_song.py +7 -0
  60. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_songlist.py +2 -1
  61. qqmusic_api_python-0.8.0/tests/test_sound_power.py +85 -0
  62. qqmusic_api_python-0.8.0/tests/test_transport.py +381 -0
  63. qqmusic_api_python-0.8.0/tests/typing/test_build_request.py +177 -0
  64. qqmusic_api_python-0.8.0/tests/typing/test_client_typing.py +173 -0
  65. qqmusic_api_python-0.8.0/tests/typing/test_endpoint_typing.py +252 -0
  66. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/uv.lock +1 -1
  67. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/README.md +1 -1
  68. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/config.example.toml +6 -0
  69. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/requirements.txt +32 -32
  70. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/app.py +58 -60
  71. qqmusic_api_python-0.8.0/web/src/core/auth.py +94 -0
  72. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/core/cache.py +38 -1
  73. qqmusic_api_python-0.8.0/web/src/core/coalesce.py +100 -0
  74. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/core/config.py +3 -0
  75. qqmusic_api_python-0.8.0/web/src/core/credential_pool.py +249 -0
  76. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/core/credential_store.py +47 -13
  77. qqmusic_api_python-0.8.0/web/src/core/deps.py +83 -0
  78. qqmusic_api_python-0.8.0/web/src/core/error_mapping.py +64 -0
  79. qqmusic_api_python-0.8.0/web/src/modules/__init__.py +10 -0
  80. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/modules/comment.py +5 -1
  81. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/modules/login.py +8 -6
  82. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/modules/mv.py +12 -2
  83. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/modules/singer.py +8 -1
  84. qqmusic_api_python-0.8.0/web/src/modules/song.py +174 -0
  85. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/modules/songlist.py +8 -2
  86. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/routes/__init__.py +2 -0
  87. qqmusic_api_python-0.8.0/web/src/routes/_helpers.py +139 -0
  88. qqmusic_api_python-0.8.0/web/src/routes/album.py +15 -0
  89. qqmusic_api_python-0.8.0/web/src/routes/comment.py +42 -0
  90. qqmusic_api_python-0.8.0/web/src/routes/lyric.py +30 -0
  91. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/routes/mv.py +3 -5
  92. qqmusic_api_python-0.8.0/web/src/routes/recommend.py +30 -0
  93. qqmusic_api_python-0.8.0/web/src/routes/search.py +39 -0
  94. qqmusic_api_python-0.8.0/web/src/routes/singer.py +75 -0
  95. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/routes/song.py +16 -43
  96. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/routes/songlist.py +5 -14
  97. qqmusic_api_python-0.8.0/web/src/routes/sound_power.py +60 -0
  98. qqmusic_api_python-0.8.0/web/src/routes/top.py +15 -0
  99. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/routes/user.py +17 -79
  100. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/routing/adapter_registry.py +1 -1
  101. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/routing/docstrings.py +7 -9
  102. qqmusic_api_python-0.8.0/web/src/routing/executor.py +269 -0
  103. qqmusic_api_python-0.8.0/web/src/routing/modules.py +69 -0
  104. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/routing/params.py +2 -2
  105. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/routing/route_types.py +38 -3
  106. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/routing/router_factory.py +37 -34
  107. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/tests/test_web_core.py +43 -5
  108. qqmusic_api_python-0.8.0/web/tests/test_web_engine_services.py +699 -0
  109. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/tests/test_web_enums.py +24 -1
  110. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/tests/test_web_route_validation.py +16 -0
  111. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/tests/test_web_routes.py +128 -2
  112. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/zensical.toml +2 -0
  113. qqmusic_api_python-0.7.2/docs/coding.md +0 -440
  114. qqmusic_api_python-0.7.2/qqmusic_api/core/api_context.py +0 -227
  115. qqmusic_api_python-0.7.2/qqmusic_api/core/client.py +0 -531
  116. qqmusic_api_python-0.7.2/qqmusic_api/core/request.py +0 -371
  117. qqmusic_api_python-0.7.2/qqmusic_api/utils/device.py +0 -200
  118. qqmusic_api_python-0.7.2/tests/test_api_context.py +0 -356
  119. qqmusic_api_python-0.7.2/tests/test_client.py +0 -335
  120. qqmusic_api_python-0.7.2/web/src/core/auth.py +0 -233
  121. qqmusic_api_python-0.7.2/web/src/core/deps.py +0 -66
  122. qqmusic_api_python-0.7.2/web/src/modules/__init__.py +0 -12
  123. qqmusic_api_python-0.7.2/web/src/modules/song.py +0 -195
  124. qqmusic_api_python-0.7.2/web/src/routes/_helpers.py +0 -159
  125. qqmusic_api_python-0.7.2/web/src/routes/album.py +0 -18
  126. qqmusic_api_python-0.7.2/web/src/routes/comment.py +0 -78
  127. qqmusic_api_python-0.7.2/web/src/routes/lyric.py +0 -48
  128. qqmusic_api_python-0.7.2/web/src/routes/recommend.py +0 -45
  129. qqmusic_api_python-0.7.2/web/src/routes/search.py +0 -46
  130. qqmusic_api_python-0.7.2/web/src/routes/singer.py +0 -104
  131. qqmusic_api_python-0.7.2/web/src/routes/top.py +0 -18
  132. qqmusic_api_python-0.7.2/web/src/routing/executor.py +0 -178
  133. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.agents/skills/pydantic/SKILL.md +0 -0
  134. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.agents/skills/python-standards/SKILL.md +0 -0
  135. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.agents/skills/uv-package-manager/SKILL.md +0 -0
  136. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.dockerignore +0 -0
  137. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  138. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  139. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
  140. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  141. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.github/renovate.json +0 -0
  142. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.github/workflows/checking.yaml +0 -0
  143. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.github/workflows/docs.yml +0 -0
  144. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.github/workflows/release.yml +0 -0
  145. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.github/workflows/testing.yml +0 -0
  146. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/.markdownlint-cli2.yaml +0 -0
  147. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/AGENTS.md +0 -0
  148. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/CLAUDE.md +0 -0
  149. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/LICENSE +0 -0
  150. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/README.md +0 -0
  151. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/assets/qq-music.svg +0 -0
  152. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/cliff.toml +0 -0
  153. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/contributing.md +0 -0
  154. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/index.md +0 -0
  155. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/core/client.md +0 -0
  156. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/core/exception.md +0 -0
  157. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/core/pagination.md +0 -0
  158. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/core/request.md +0 -0
  159. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/core/versioning.md +0 -0
  160. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/album.md +0 -0
  161. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/base.md +0 -0
  162. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/comment.md +0 -0
  163. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/helper.md +0 -0
  164. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/login.md +0 -0
  165. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/lyric.md +0 -0
  166. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/mv.md +0 -0
  167. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/private_message.md +0 -0
  168. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/recommend.md +0 -0
  169. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/request.md +0 -0
  170. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/search.md +0 -0
  171. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/singer.md +0 -0
  172. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/song.md +0 -0
  173. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/songlist.md +0 -0
  174. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/top.md +0 -0
  175. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/model/user.md +0 -0
  176. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/album.md +0 -0
  177. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/comment.md +0 -0
  178. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/helper.md +0 -0
  179. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/helper_utils.md +0 -0
  180. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/login.md +0 -0
  181. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/login_utils.md +0 -0
  182. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/lyric.md +0 -0
  183. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/mv.md +0 -0
  184. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/private_message.md +0 -0
  185. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/recommend.md +0 -0
  186. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/search.md +0 -0
  187. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/singer.md +0 -0
  188. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/song.md +0 -0
  189. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/songlist.md +0 -0
  190. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/top.md +0 -0
  191. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/reference/modules/user.md +0 -0
  192. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/tutorial/download.md +0 -0
  193. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/tutorial/error-handling.md +0 -0
  194. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/tutorial/login.md +0 -0
  195. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/tutorial/start.md +0 -0
  196. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/docs/tutorial/web.md +0 -0
  197. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/examples/download_song.py +0 -0
  198. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/examples/get_all_sheets.py +0 -0
  199. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/examples/phone_login.py +0 -0
  200. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/examples/private_message.py +0 -0
  201. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/examples/qrcode_login.py +0 -0
  202. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/examples/upload_file.py +0 -0
  203. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/prek.toml +0 -0
  204. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/algorithms/__init__.py +0 -0
  205. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/algorithms/sign.py +0 -0
  206. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/algorithms/tripledes.py +0 -0
  207. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/core/pagination.py +0 -0
  208. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/_validator.py +0 -0
  209. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/album.py +0 -0
  210. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/base.py +0 -0
  211. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/comment.py +0 -0
  212. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/helper.py +0 -0
  213. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/login.py +0 -0
  214. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/lyric.py +0 -0
  215. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/mv.py +0 -0
  216. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/private_message.py +0 -0
  217. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/recommend.py +0 -0
  218. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/search.py +0 -0
  219. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/song.py +0 -0
  220. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/songlist.py +0 -0
  221. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/top.py +0 -0
  222. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/models/user.py +0 -0
  223. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/helper.py +0 -0
  224. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/helper_utils.py +0 -0
  225. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/modules/private_message.py +0 -0
  226. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/utils/__init__.py +0 -0
  227. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/qqmusic_api/utils/common.py +0 -0
  228. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/scripts/ag-1.py +0 -0
  229. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/conftest.py +0 -0
  230. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_album.py +0 -0
  231. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_comment.py +0 -0
  232. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_login.py +0 -0
  233. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_login_utils.py +0 -0
  234. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_lyric.py +0 -0
  235. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_mv.py +0 -0
  236. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_private_message.py +0 -0
  237. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_recommend.py +0 -0
  238. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_search.py +0 -0
  239. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_top.py +0 -0
  240. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/tests/test_user.py +0 -0
  241. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/.gitignore +0 -0
  242. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/Dockerfile +0 -0
  243. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/accounts.example.toml +0 -0
  244. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/docker-compose.example.yml +0 -0
  245. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/run.py +0 -0
  246. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/core/__init__.py +0 -0
  247. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/core/response.py +0 -0
  248. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/core/security.py +0 -0
  249. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/routes/login.py +0 -0
  250. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/src/routing/__init__.py +0 -0
  251. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/tests/conftest.py +0 -0
  252. {qqmusic_api_python-0.7.2 → qqmusic_api_python-0.8.0}/web/tests/test_web_docstrings.py +0 -0
@@ -14,7 +14,7 @@ device.json
14
14
  .ccb/
15
15
  docs/superpowers/
16
16
  .omo/
17
-
17
+ .omp/
18
18
  # Distribution / packaging
19
19
  .Python
20
20
  build/
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: qqmusic-api-python
3
- Version: 0.7.2
3
+ Version: 0.8.0
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,7 @@ 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: typing-extensions>=4.12.2
38
+ Requires-Dist: typing-extensions>=4.16.0
39
39
  Provides-Extra: upload
40
40
  Requires-Dist: cos-python-sdk-v5>=1.9.44; extra == 'upload'
41
41
  Description-Content-Type: text/markdown
@@ -0,0 +1,560 @@
1
+ # API 编写指南
2
+
3
+ `qqmusic_api` 采用 `Client + ApiModule + Request` 的结构:
4
+
5
+ * `ApiModule` 通过 `@cgi_endpoint` / `@http_endpoint` 声明接口契约,方法体只负责返回 `CgiRequestData` / `HttpRequestData` 装载运行时参数。
6
+ * `Request`(`CgiRequest` / `HttpRequest` 及分页子类)是由上述声明装配出的惰性描述符,被 `await` 时才真正执行。
7
+ * `Client` 是使用者门面,持有默认凭证与平台,并把 `Request` 交由 `RequestEngine` 执行。
8
+
9
+ ## 调用流程图
10
+
11
+ ### 单请求
12
+
13
+ ```text
14
+ 模块方法
15
+ -> 装配为 BaseRequest 描述符 (CgiRequest / HttpRequest 及分页子类)
16
+ -> await request: RequestExecutor 解析身份并交由 RequestEngine 分派
17
+ -> CgiExecutor / HttpExecutor 装配物理请求
18
+ -> Transport.request(prepared) 发送并释放响应
19
+ -> core/response.py 统一解析
20
+ -> 返回原始 dict 或 Pydantic 模型
21
+ ```
22
+
23
+ ### 批量并发请求
24
+
25
+ ```text
26
+ 多个模块方法
27
+ -> 各自产出 BaseRequest 描述符
28
+ -> Client.gather(requests)
29
+ -> RequestEngine.gather(calls, batch_size=...)
30
+ -> 按请求类型 (CGI / HTTP) 分区并行执行
31
+ -> CGI 条目按快照身份自动分组, 每组按 batch_size 拆分后经 send_many 批量发送
32
+ -> 统一解包解析每个响应项, 逐项归属错误
33
+ -> 按输入顺序还原结果列表
34
+ ```
35
+
36
+ `gather` 的分组边界由执行器按 **快照身份** 计算 (生效平台, 完整凭证, 规范化公共参数, 覆盖模式与签名)。只有这些线上环境完全一致的请求才会安全地合并到同一个批量请求中。
37
+
38
+ 服务端接入不经过 `Client`: 由 `RequestEngine` 配合 `ScopedRequestExecutor` 按请求绑定身份, 单请求流程一致。
39
+
40
+ ## 编写新的 API
41
+
42
+ ### 添加新模块
43
+
44
+ 1. 在 `qqmusic_api/modules/` 下创建新文件,例如 `foo.py`。
45
+ 2. 定义模块类,继承 `ApiModule`。
46
+ 3. 在 `Client` 中注册为 `@cached_property`。
47
+
48
+ ```python
49
+ # qqmusic_api/modules/foo.py
50
+ from ._base import ApiModule
51
+ from ..core.endpoint import cgi_endpoint, CgiRequestData
52
+
53
+
54
+ class FooApi(ApiModule):
55
+ """Foo 相关 API."""
56
+
57
+ @cgi_endpoint(
58
+ key="foo.get_something",
59
+ module="music.foo.Svc",
60
+ method="GetSomething",
61
+ )
62
+ def get_something(self, id: int) -> CgiRequestData:
63
+ """获取某项数据."""
64
+ return CgiRequestData(param={"id": id})
65
+ ```
66
+
67
+ ```python
68
+ # qqmusic_api/core/client.py
69
+ from functools import cached_property
70
+
71
+
72
+ class Client:
73
+ @cached_property
74
+ def foo(self) -> "FooApi":
75
+ from ..modules.foo import FooApi
76
+
77
+ return FooApi(self)
78
+ ```
79
+
80
+ ### 添加新的请求方法
81
+
82
+ API 方法返回 `BaseRequest` 描述符对象,并不立即发起请求。可以通过声明式装饰器(`@cgi_endpoint` / `@http_endpoint`)或原生构建方法(`self._build_cgi` / `self._build_http`)构建:
83
+
84
+ #### 1. CGI 接口 (RPC 风格)
85
+
86
+ 对于标准 CGI 风格的 RPC 请求,可以使用 `@cgi_endpoint` 装饰器声明端点契约,并在方法体中返回 `CgiRequestData` 装载运行时参数:
87
+
88
+ ```python
89
+ from ..core.endpoint import cgi_endpoint, CgiRequestData
90
+
91
+
92
+ @cgi_endpoint(
93
+ key="song.get_detail", # 端点唯一标识
94
+ module="music.songDetail", # 接口所属模块
95
+ method="GetDetail", # 方法名
96
+ )
97
+ def get_detail(self, song_id: int) -> CgiRequestData:
98
+ """获取歌曲详情."""
99
+ return CgiRequestData(
100
+ param={"songid": song_id}, # 业务参数
101
+ )
102
+ ```
103
+
104
+ 也可以使用 `self._build_cgi(...)` 构建:
105
+
106
+ ```python
107
+ def get_detail(self, song_id: int):
108
+ """获取歌曲详情."""
109
+ return self._build_cgi(
110
+ module="music.songDetail", # 接口所属模块
111
+ method="GetDetail", # 方法名
112
+ param={"songid": song_id}, # 业务参数
113
+ )
114
+ ```
115
+
116
+ #### 2. HTTP 接口 (标准 HTTP)
117
+
118
+ 对于标准 HTTP 请求(如直接 GET 请求、获取网页或二维码),可以使用 `@http_endpoint` 装饰器声明,并在方法体中返回 `HttpRequestData`:
119
+
120
+ ```python
121
+ from ..core.endpoint import http_endpoint, HttpRequestData
122
+
123
+
124
+ @http_endpoint(
125
+ key="search.quick_search",
126
+ method="GET",
127
+ url="https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg",
128
+ )
129
+ def quick_search(self, keyword: str) -> HttpRequestData:
130
+ """快速搜索 (直接返回解析后的 JSON 数据)."""
131
+ return HttpRequestData(
132
+ params={"key": keyword},
133
+ )
134
+ ```
135
+
136
+ 也可以使用 `self._build_http(...)` 构建:
137
+
138
+ ```python
139
+ async def quick_search(self, keyword: str) -> dict[str, Any]:
140
+ """快速搜索 (直接返回解析后的 JSON 数据)."""
141
+ resp = await self._build_http(
142
+ "GET",
143
+ "https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg",
144
+ params={"key": keyword},
145
+ )
146
+ return resp["data"]
147
+ ```
148
+
149
+ ### `@cgi_endpoint` 声明参数说明
150
+
151
+ `@cgi_endpoint` 装饰器用于在定义期静态声明端点的核心契约:
152
+
153
+ | 参数 | 类型 | 说明 |
154
+ | ------------------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------- |
155
+ | `key` | `str` | 端点唯一标识符(如 `"song.get_detail"`) |
156
+ | `module` | `str` | 接口所属 CGI 模块名 |
157
+ | `method` | `str` | CGI 方法名 |
158
+ | `response_model` | `type[BaseModel]` 或 `None` | 响应模型,为 None 时返回原始 dict |
159
+ | `item_type` | `type` 或 `None` | (仅分页)数据项模型,声明后推导为 `ItemPaginatedCgiRequest` |
160
+ | `pager` | `bool` | (仅分页)是否为分页端点,若 `item_type` 已填则无需填此项 |
161
+ | `sign` | `bool` | 是否对请求进行签名 |
162
+ | `require_login` | `bool` | 是否在执行时强制校验用户登录态 |
163
+ | `platform` | `Platform` 或 `None` | 强制指定该接口使用的目标平台 |
164
+
165
+ ### `CgiRequestData` 运行时参数说明
166
+
167
+ 在被 `@cgi_endpoint` 装饰的函数体内返回,用于承载每次调用的动态参数:
168
+
169
+ | 参数 | 类型 | 说明 |
170
+ | ------------------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------- |
171
+ | `param` | `dict` | 请求体的业务参数 `param` 字段 |
172
+ | `comm` | `dict` 或 `None` | 附加的公共参数 |
173
+ | `override_comm` | `bool` | 为 True 时 `comm` 完全替代自动生成的参数;为 False 时合并 |
174
+ | `credential` | `Credential` 或 `None` | 覆盖本次请求的凭证 |
175
+ | `platform` | `Platform` 或 `None` | 覆盖本次请求的平台 |
176
+ | `preserve_bool` | `bool` 或 `None` | 是否保留布尔值原样(默认转为 0/1 整型) |
177
+ | `pager_strategy` | `PagerStrategy` 或 `None` | 分页策略,必须与装饰器的分页声明匹配 |
178
+ | `items_extractor` | `Callable` 或 `None` | (仅声明了 item_type 时需要)从单页响应对象中提取目标列表的闭包 |
179
+
180
+ ### `@http_endpoint` 声明参数说明
181
+
182
+ `@http_endpoint` 装饰器用于静态声明 HTTP 接口契约:
183
+
184
+ | 参数 | 类型 | 说明 |
185
+ | ---------------- | -------------------------------- | -------------------------------------------------------------------- |
186
+ | `key` | `str` | 端点唯一标识符(如 `"search.quick_search"`) |
187
+ | `method` | `str` | HTTP 方法,如 `"GET"`、`"POST"` |
188
+ | `url` | `str` | 请求地址模板,支持 `{param}` 占位符配合 `path_params` 渲染 |
189
+ | `response_model` | `type[ResponseModel]` 或 `None` | 响应模型类型 |
190
+ | `raw` | `bool` 或 `None` | 为 True 时返回 `RawPayload`(当模型为 `RawPayload` 时默认为 True) |
191
+
192
+ ### `HttpRequestData` 运行时参数说明
193
+
194
+ 在被 `@http_endpoint` 装饰的函数体内返回,用于承载每次 HTTP 调用的动态参数:
195
+
196
+ | 参数 | 类型 | 说明 |
197
+ | ------------- | --------------------------- | --------------------------------------------------------- |
198
+ | `path_params` | `dict[str, Any]` 或 `None` | URL 路径参数,用于格式化 `url` 中的占位符 |
199
+ | `params` | `Any` 或 `None` | URL 查询参数(Query string) |
200
+ | `headers` | `Any` 或 `None` | HTTP 请求头 |
201
+ | `cookies` | `Any` 或 `None` | HTTP 请求 Cookies |
202
+ | `json` | `Any` 或 `None` | 请求体 JSON 数据 |
203
+ | `data` | `Any` 或 `None` | 原始请求体数据 |
204
+ | `credential` | `Credential` 或 `None` | 覆盖本次请求的凭证 |
205
+ | `options` | `dict[str, Any]` | 透传给底层客户端的可选参数(如 `timeout`、`files` 等) |
206
+
207
+ ### `_build_cgi` 参数说明
208
+
209
+ `_build_cgi` 用于构建 CGI 风格 RPC 请求描述符:
210
+
211
+ | 参数 | 类型 | 说明 |
212
+ | ------------------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------- |
213
+ | `module` | `str` | 接口所属模块名 |
214
+ | `method` | `str` | 方法名 |
215
+ | `param` | `dict` | 业务参数 |
216
+ | `response_model` | `type[BaseModel]` 或 `None` | 响应模型,为 None 时返回原始 dict |
217
+ | `comm` | `dict` 或 `None` | 附加的公共参数 |
218
+ | `override_comm` | `bool` | 为 True 时 `comm` 完全替代自动生成的参数;为 False 时合并 |
219
+ | `credential` | `Credential` 或 `None` | 覆盖本次请求的凭证 |
220
+ | `platform` | `Platform` 或 `None` | 覆盖本次请求的平台 |
221
+ | `preserve_bool` | `bool` | 是否保留布尔值原样(默认转为 0/1 整型) |
222
+ | `sign` | `bool` | 是否对请求进行签名 |
223
+ | `require_login` | `bool` | 是否在执行时强制校验用户登录态 |
224
+ | `pager_strategy` | `PagerStrategy` 或 `None` | 分页策略,提供后返回 `PaginatedCgiRequest`;可链式调用 `.with_extractor()` 提升为 `ItemPaginatedCgiRequest` |
225
+
226
+ ### `_build_http` 参数说明
227
+
228
+ `_build_http` 用于构建标准 HTTP 请求描述符,遵循 httpx 风格参数规范,自动装配凭证 Cookies 和平台 User-Agent:
229
+
230
+ | 参数 | 类型 | 说明 |
231
+ | ---------------- | -------------------------------- | ------------------------------------------------------------------- |
232
+ | `method` | `str` | HTTP 方法,如 `"GET"`、`"POST"` |
233
+ | `url` | `str` | 请求地址 |
234
+ | `params` | `Mapping` 或 `None` | URL 查询参数 |
235
+ | `json` | `Any` 或 `None` | 请求体 JSON 数据 |
236
+ | `data` | `bytes` / `str` / `None` | 原始请求体数据(非 JSON 场景) |
237
+ | `headers` | `Mapping` 或 `None` | HTTP 请求头 |
238
+ | `cookies` | `Mapping` 或 `None` | HTTP 请求 Cookies |
239
+ | `credential` | `Credential` 或 `None` | 覆盖本次请求的凭证,默认使用客户端凭证 |
240
+ | `response_model` | `type[ResponseModel]` 或 `None` | 响应模型类型 |
241
+ | `raw` | `bool` | 校验 HTTP 状态并返回 `RawPayload`(值语义,无需释放) |
242
+ | `**options` | | 透传给底层客户端的参数(`timeout`、`allow_redirects`、`files` 等) |
243
+
244
+ 常见用法:
245
+
246
+ ```python
247
+ # GET 请求
248
+ req = self._build_http("GET", "https://example.com/api", params={"key": "value"})
249
+
250
+ # POST JSON
251
+ req = self._build_http("POST", "https://example.com/api", json={"key": "value"})
252
+
253
+ # 覆盖凭证
254
+ req = self._build_http("GET", "https://example.com/api", credential=my_credential)
255
+
256
+ # 返回 RawPayload 而非解析 JSON
257
+ req = self._build_http("GET", "https://example.com/api", raw=True)
258
+ ```
259
+
260
+ ## 响应模型
261
+
262
+ ### 基础用法
263
+
264
+ 每个响应模型都应继承 `models.request.Response`:
265
+
266
+ ```python
267
+ from pydantic import Field
268
+
269
+ from .request import Response
270
+
271
+
272
+ class MyResponse(Response):
273
+ """我的响应模型."""
274
+
275
+ name: str
276
+ count: int
277
+ ```
278
+
279
+ `Response` 基类配置了 `frozen=True`(不可变)和 `extra="ignore"`(忽略多余字段)。
280
+
281
+ !!! warning "Pydantic 默认值规范"
282
+
283
+ 定义模型时应避免使用 `None` 作为隐式兜底默认值。如果字段可选或为空,应当使用显式的空标量,或通过 `Field(default_factory=...)` 声明:
284
+ ```python
285
+ class Album(Response):
286
+ name: str = ""
287
+ publish_time: str = ""
288
+ # 列表必须使用 default_factory
289
+ singers: list[Singer] = Field(default_factory=list)
290
+ ```
291
+
292
+ ### JSONPath 字段映射
293
+
294
+ 可以通过 `Field(json_schema_extra={"jsonpath": ...})` 声明字段的 JSONPath 映射路径,自动从嵌套响应中提取数据:
295
+
296
+ ```python
297
+ class SonglistMeta(Response):
298
+ """歌单元数据示例."""
299
+
300
+ id: int = Field(json_schema_extra={"jsonpath": "$.result.tid"})
301
+ dirid: int = Field(json_schema_extra={"jsonpath": "$.result.dirId"})
302
+ name: str = Field(json_schema_extra={"jsonpath": "$.result.dirName"})
303
+ ```
304
+
305
+ 对于列表字段,使用 `[*]` 通配符:
306
+
307
+ ```python
308
+ class CommentListResponse(Response):
309
+ """评论列表响应."""
310
+
311
+ comments: list[Comment] = Field(
312
+ default_factory=list,
313
+ json_schema_extra={"jsonpath": "$.commentlist[*]"},
314
+ )
315
+ ```
316
+
317
+ ### 字段别名
318
+
319
+ Pydantic 的 `validation_alias` 支持多别名兼容:
320
+
321
+ ```python
322
+ class Singer(Response):
323
+ """歌手信息."""
324
+
325
+ id: int = Field(
326
+ default=-1,
327
+ validation_alias=AliasChoices("id", "singerID", "singerId", "SingerID"),
328
+ )
329
+ mid: str = Field(
330
+ default="",
331
+ validation_alias=AliasChoices("mid", "singerMid", "singerMID"),
332
+ )
333
+ ```
334
+
335
+ ### 需登录接口
336
+
337
+ 需要登录的接口通过 `@cgi_endpoint` 的 `require_login=True` 参数校验凭证:
338
+
339
+ ```python
340
+ @cgi_endpoint(
341
+ key="user.get_vip_info",
342
+ module="VipLogin.VipLoginInter",
343
+ method="vip_login_base",
344
+ response_model=UserVipInfoResponse,
345
+ require_login=True,
346
+ )
347
+ def get_vip_info(self, *, credential: Credential | None = None) -> CgiRequestData:
348
+ """获取 VIP 信息."""
349
+ return CgiRequestData(credential=credential)
350
+ ```
351
+
352
+ > 若接口需要凭证对象的属性(如 `musicid` 等)来构建请求内联参数,
353
+ > 仍可通过 `credential = credential or self._client.credential` 获取并显式验证其有效性。
354
+
355
+ ## 连续翻页与批次刷新
356
+
357
+ ### 连续翻页
358
+
359
+ 通过返回的 `CgiRequestData(pager_strategy=...)` 声明连续翻页能力,建议为策略配合显式的 Generic 标注(形如 `OffsetStrategy[GetSonglistDetailResponse]`)以确保静态类型推断。
360
+ 若要提取特定类型的数据条目流,请在 `@cgi_endpoint(item_type=...)` 中声明目标类型,并在 `CgiRequestData` 中同时提供 `items_extractor`:
361
+
362
+ ```python
363
+ from ..core.pagination import OffsetStrategy
364
+
365
+
366
+ @cgi_endpoint(
367
+ key="songlist.get_detail",
368
+ module="music.srfDissInfo.DissInfo",
369
+ method="CgiGetDiss",
370
+ response_model=GetSonglistDetailResponse,
371
+ item_type=Song,
372
+ )
373
+ def get_detail(self, songlist_id: int, num: int = 10, page: int = 1) -> CgiRequestData:
374
+ """获取歌单详情."""
375
+ return CgiRequestData(
376
+ param={
377
+ "disstid": songlist_id,
378
+ "song_begin": num * (page - 1),
379
+ "song_num": num,
380
+ },
381
+ pager_strategy=OffsetStrategy[GetSonglistDetailResponse](
382
+ offset_key="song_begin",
383
+ page_size_key="song_num",
384
+ has_more_extractor=lambda response: bool(response.hasmore),
385
+ total_extractor=lambda response: response.total,
386
+ count_extractor=lambda response: len(response.songs),
387
+ ),
388
+ items_extractor=lambda response: response.songs,
389
+ )
390
+ ```
391
+
392
+ ### 批次刷新 (Batch Refresh)
393
+
394
+ 批次刷新(Batch Refresh)是一种针对推荐或关联接口、支持游标复位与防循环重复游标的特殊游标分页,同样通过 `pager_strategy` 声明:
395
+
396
+ ```python
397
+ from ..core.pagination import BatchRefreshStrategy
398
+ from ..models.base import MV
399
+
400
+
401
+ @cgi_endpoint(
402
+ key="song.get_related_mv",
403
+ module="MvService.MvInfoProServer",
404
+ method="GetSongRelatedMv",
405
+ response_model=GetRelatedMvResponse,
406
+ item_type=RelatedMv,
407
+ )
408
+ def get_related_mv(self, songid: int, last_mvid: str | None = None) -> CgiRequestData:
409
+ """获取歌曲相关 MV."""
410
+ return CgiRequestData(
411
+ param={"songid": str(songid), "songtype": 1, "lastmvid": last_mvid or 0},
412
+ pager_strategy=BatchRefreshStrategy[GetRelatedMvResponse](
413
+ refresh_key="lastmvid",
414
+ cursor_extractor=lambda response: response.mv[-1].id if response.mv else None,
415
+ has_more_extractor=lambda response: bool(response.has_more),
416
+ ),
417
+ items_extractor=lambda response: response.mv,
418
+ )
419
+ ```
420
+
421
+ ### 内置策略速查
422
+
423
+ | 策略 | 适用场景 | 关键参数 |
424
+ | ---------------------------------- | -------------: | --------------------------------- |
425
+ | `PageStrategy` | 页码递增 | `page_key` |
426
+ | `OffsetStrategy` | 偏移量滑窗 | `offset_key` + `page_size_key` |
427
+ | `CursorStrategy` | 响应游标回写 | `cursor_key` |
428
+ | `MultiFieldContinuationStrategy` | 多字段续翻 | 自定义 `build_next_params` 函数 |
429
+ | `BatchRefreshStrategy` | 批次刷新 | `refresh_key` |
430
+
431
+ ## 请求签名
432
+
433
+ 部分接口需要对请求体进行签名。通过 `@cgi_endpoint` 的 `sign=True` 启用:
434
+
435
+ ```python
436
+ @cgi_endpoint(
437
+ key="song.get_sheet",
438
+ module="music.mir.SheetMusicSvr",
439
+ method="GetMoreSheetMusic",
440
+ sign=True,
441
+ )
442
+ def get_sheet(self, mid: str) -> CgiRequestData:
443
+ """获取曲谱."""
444
+ return CgiRequestData(
445
+ param={"songMid": mid},
446
+ )
447
+ ```
448
+
449
+ 签名后请求会发送到 `musics.fcg` 而非 `musicu.fcg`,并在 URL 参数中附加 `_`(时间戳)和 `sign`。
450
+
451
+ ## 公共参数 `comm`
452
+
453
+ 默认情况下,`comm` 参数由 `VersionPolicy.build_comm()` 自动生成。可以通过 `comm` 附加额外参数:
454
+
455
+ ```python
456
+ # 合并到自动生成的 comm 中(默认行为)
457
+ CgiRequestData(
458
+ ...,
459
+ comm={"extra_key": "value"},
460
+ )
461
+ ```
462
+
463
+ 发送前,所有 `comm` 值都会转换为字符串。合并模式下可将值设为 `None` 或空字符串,删除自动生成的同名参数。
464
+
465
+ 使用 `override_comm=True` 完全替代自动生成的参数:
466
+
467
+ ```python
468
+ CgiRequestData(
469
+ ...,
470
+ comm={
471
+ "g_tk": 5381,
472
+ "uin": "",
473
+ "format": "json",
474
+ "inCharset": "utf-8",
475
+ "outCharset": "utf-8",
476
+ "notice": 0,
477
+ "needNewCode": 1,
478
+ },
479
+ override_comm=True,
480
+ )
481
+ ```
482
+
483
+ ## 异常处理
484
+
485
+ 在抛出或处理异常时,应使用项目统一的基于领域驱动(DDD)风格的异常类(继承自 `BaseApiException` 或 `ApiException`
486
+ )。在包装底层异常时,必须使用原生异常链(`raise ... from exc`)保留堆栈追踪:
487
+
488
+ ```python
489
+ from ..core.exceptions import ApiDataError
490
+
491
+ try:
492
+ ...
493
+ except KeyError as e:
494
+ raise ApiDataError("无法解析歌曲信息") from e
495
+ ```
496
+
497
+ ## 编写测试
498
+
499
+ 测试文件放在 `tests/` 下,按模块命名(如 `test_song.py`)。
500
+
501
+ ### 基本格式
502
+
503
+ ```python
504
+ """歌曲模块测试."""
505
+
506
+ import pytest
507
+
508
+ from qqmusic_api import Client
509
+
510
+
511
+ async def test_query_song(client: Client) -> None:
512
+ """测试根据 ID 查询歌曲."""
513
+ result = await client.song.query_song([SongQueryInfo(mid="003w2xz20QlUZt")])
514
+ assert result.tracks
515
+ assert result.tracks[0].name
516
+ ```
517
+
518
+ ### 使用 parametrize
519
+
520
+ ```python
521
+ @pytest.mark.parametrize("page", [1, 2])
522
+ async def test_general_search(client: Client, page: int) -> None:
523
+ """测试综合搜索翻页逻辑."""
524
+ try:
525
+ result = await client.search.general_search("周杰伦", page=page)
526
+ except Exception as e:
527
+ # 示例:优雅处理网络风控或限流 (需根据实际异常类型调整)
528
+ if "limit" in str(e).lower() or "risk" in str(e).lower():
529
+ pytest.skip(f"Triggered rate limit or risk control: {e}")
530
+ raise
531
+
532
+ assert result.song.items is not None
533
+ ```
534
+
535
+ ### 需要登录的测试
536
+
537
+ 使用 `authenticated_client` fixture:
538
+
539
+ ```python
540
+ async def test_get_vip_info(authenticated_client: Client) -> None:
541
+ """测试获取 VIP 信息."""
542
+ result = await authenticated_client.user.get_vip_info()
543
+ assert result.vip_flag is not None
544
+ ```
545
+
546
+ ### 测试分页
547
+
548
+ ```python
549
+ async def test_search_paginate(client: Client) -> None:
550
+ """测试搜索分页."""
551
+ pager = client.search.search_by_type("周杰伦", num=5).pager(limit=2)
552
+
553
+ assert pager.has_more() is True
554
+ first_page = await pager.next()
555
+ assert pager.has_more() is True
556
+ second_page = await pager.next()
557
+
558
+ assert first_page.song
559
+ assert second_page.song
560
+ ```
@@ -0,0 +1,3 @@
1
+ # sound_power
2
+
3
+ ::: models.sound_power
@@ -0,0 +1,3 @@
1
+ # SoundPowerApi
2
+
3
+ ::: modules.sound_power.SoundPowerApi
@@ -1,4 +1,44 @@
1
1
 
2
+ ## [[0.7.3](https://github.com/L-1124/QQMusicApi/compare/v0.7.2..v0.7.3)] - 2026-09-13
3
+
4
+ ### Bug 修复
5
+
6
+ * **(song)** 恢复 get_song_urls 的 mid 数量上限校验 ([c358c77](https://github.com/L-1124/QQMusicApi/commit/c358c77783ef2209a83b734f86a928ba7c91b70b)) by [@L-1124](https://github.com/L-1124)
7
+
8
+ ### 功能更新
9
+
10
+ * **(singer)** 新增歌手名称透明 PNG 接口及演示 ([3740170](https://github.com/L-1124/QQMusicApi/commit/374017047c39dc987418450b6d513bc216499a69)) by [@Lincb522](https://github.com/Lincb522) in [#302](https://github.com/L-1124/QQMusicApi/pull/302)
11
+
12
+ ### 贡献者
13
+
14
+ * @L-1124
15
+ * @Lincb522 [#302](https://github.com/L-1124/QQMusicApi/pull/302)
16
+ * @github-actions[bot]
17
+
18
+ ## [[0.7.2](https://github.com/L-1124/QQMusicApi/compare/v0.7.1..v0.7.2)] - 2026-08-05
19
+
20
+ ### Bug 修复
21
+
22
+ * **(routing)** 修复 add_api_route tags 参数类型不匹配 ([ee98436](https://github.com/L-1124/QQMusicApi/commit/ee984368c19bca4d164475c272b49fa136566e9e)) by [@L-1124](https://github.com/L-1124)
23
+
24
+ ### 功能更新
25
+
26
+ * **(search)** 为 quick_search/complete/get_hotkey 添加响应模型 ([b97549f](https://github.com/L-1124/QQMusicApi/commit/b97549fea31f3de9ce68904df932a6c4d73aff45)) by [@L-1124](https://github.com/L-1124)
27
+ * **(singer)** 支持 get_desc 接口详细数据控制参数 ([8a8c7f1](https://github.com/L-1124/QQMusicApi/commit/8a8c7f14262e5b08dc46073e84e43fc43508f392)) by [@L-1124](https://github.com/L-1124)
28
+
29
+ ### 功能重构
30
+
31
+ * **(core)** [**breaking**] 统一请求调度引擎 ([8b71333](https://github.com/L-1124/QQMusicApi/commit/8b71333835891f851a70354c313c614f558ff8e0)) by [@L-1124](https://github.com/L-1124) in [#301](https://github.com/L-1124/QQMusicApi/pull/301)
32
+
33
+ ### 文档更新
34
+
35
+ * 更新 lyric.py 中的错误 docstring ([8ec78ce](https://github.com/L-1124/QQMusicApi/commit/8ec78ce3f16805ebe470b8308a96fef925cf8e56)) by [@L-1124](https://github.com/L-1124)
36
+
37
+ ### 贡献者
38
+
39
+ * @L-1124
40
+ * @github-actions[bot]
41
+
2
42
  ## [[0.7.1](https://github.com/L-1124/QQMusicApi/compare/v0.7.0..v0.7.1)] - 2026-08-02
3
43
 
4
44
  ### Bug 修复