qqmusic-api-python 0.6.8__tar.gz → 0.7.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 (199) hide show
  1. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.dockerignore +0 -2
  2. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/PKG-INFO +1 -1
  3. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/coding.md +1 -1
  4. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/release-notes.md +77 -0
  5. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/tutorial/download.md +2 -2
  6. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/__init__.py +1 -1
  7. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/core/client.py +2 -3
  8. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/album.py +49 -0
  9. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/base.py +5 -9
  10. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/mv.py +33 -1
  11. qqmusic_api_python-0.7.0/qqmusic_api/modules/album.py +117 -0
  12. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/lyric.py +4 -1
  13. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/mv.py +30 -1
  14. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/recommend.py +7 -3
  15. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/song.py +43 -15
  16. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/songlist.py +40 -0
  17. qqmusic_api_python-0.7.0/tests/test_album.py +59 -0
  18. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/tests/test_mv.py +17 -0
  19. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/tests/test_recommend.py +10 -0
  20. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/tests/test_song.py +25 -6
  21. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/tests/test_songlist.py +19 -0
  22. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/Dockerfile +0 -3
  23. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/README.md +22 -1
  24. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/config.example.toml +5 -0
  25. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/requirements.txt +61 -3
  26. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/run.py +0 -3
  27. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/app.py +3 -3
  28. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/core/auth.py +52 -42
  29. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/core/cache.py +25 -12
  30. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/core/config.py +18 -0
  31. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/core/credential_store.py +20 -25
  32. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/core/response.py +1 -3
  33. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/core/security.py +26 -20
  34. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/modules/song.py +27 -18
  35. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/modules/songlist.py +10 -11
  36. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routes/_helpers.py +1 -0
  37. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routes/search.py +3 -3
  38. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routes/song.py +22 -3
  39. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routes/songlist.py +2 -0
  40. qqmusic_api_python-0.7.0/web/src/routes/user.py +186 -0
  41. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routing/docstrings.py +1 -3
  42. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routing/executor.py +42 -20
  43. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routing/router_factory.py +9 -2
  44. qqmusic_api_python-0.7.0/web/tests/conftest.py +12 -0
  45. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/tests/test_web_enums.py +3 -12
  46. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/tests/test_web_routes.py +33 -42
  47. qqmusic_api_python-0.6.8/qqmusic_api/modules/album.py +0 -58
  48. qqmusic_api_python-0.6.8/tests/test_album.py +0 -29
  49. qqmusic_api_python-0.6.8/web/src/routes/user.py +0 -109
  50. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.agents/skills/pydantic/SKILL.md +0 -0
  51. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.agents/skills/python-standards/SKILL.md +0 -0
  52. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.agents/skills/tarsio/SKILL.md +0 -0
  53. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.agents/skills/tarsio/references/api-reference.md +0 -0
  54. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.agents/skills/uv-package-manager/SKILL.md +0 -0
  55. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  56. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  57. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
  58. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  59. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.github/renovate.json +0 -0
  60. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.github/workflows/checking.yaml +0 -0
  61. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.github/workflows/docs.yml +0 -0
  62. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.github/workflows/release.yml +0 -0
  63. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.github/workflows/testing.yml +0 -0
  64. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.gitignore +0 -0
  65. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/.markdownlint-cli2.yaml +0 -0
  66. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/AGENTS.md +0 -0
  67. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/CLAUDE.md +0 -0
  68. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/LICENSE +0 -0
  69. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/README.md +0 -0
  70. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/assets/qq-music.svg +0 -0
  71. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/cliff.toml +0 -0
  72. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/contributing.md +0 -0
  73. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/index.md +0 -0
  74. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/core/client.md +0 -0
  75. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/core/exception.md +0 -0
  76. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/core/pagination.md +0 -0
  77. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/core/request.md +0 -0
  78. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/core/versioning.md +0 -0
  79. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/model/album.md +0 -0
  80. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/model/base.md +0 -0
  81. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/model/comment.md +0 -0
  82. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/model/login.md +0 -0
  83. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/model/lyric.md +0 -0
  84. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/model/mv.md +0 -0
  85. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/model/recommend.md +0 -0
  86. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/model/request.md +0 -0
  87. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/model/search.md +0 -0
  88. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/model/singer.md +0 -0
  89. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/model/song.md +0 -0
  90. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/model/songlist.md +0 -0
  91. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/model/top.md +0 -0
  92. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/model/user.md +0 -0
  93. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/modules/album.md +0 -0
  94. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/modules/comment.md +0 -0
  95. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/modules/login.md +0 -0
  96. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/modules/login_utils.md +0 -0
  97. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/modules/lyric.md +0 -0
  98. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/modules/mv.md +0 -0
  99. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/modules/private_message.md +0 -0
  100. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/modules/recommend.md +0 -0
  101. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/modules/search.md +0 -0
  102. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/modules/singer.md +0 -0
  103. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/modules/song.md +0 -0
  104. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/modules/songlist.md +0 -0
  105. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/modules/top.md +0 -0
  106. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/reference/modules/user.md +0 -0
  107. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/tutorial/client.md +0 -0
  108. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/tutorial/credential.md +0 -0
  109. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/tutorial/error-handling.md +0 -0
  110. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/tutorial/login.md +0 -0
  111. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/tutorial/pagination.md +0 -0
  112. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/tutorial/start.md +0 -0
  113. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/docs/tutorial/web.md +0 -0
  114. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/examples/download_song.py +0 -0
  115. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/examples/get_all_sheets.py +0 -0
  116. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/examples/phone_login.py +0 -0
  117. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/examples/private_message.py +0 -0
  118. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/examples/qrcode_login.py +0 -0
  119. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/examples/upload_file.py +0 -0
  120. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/prek.toml +0 -0
  121. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/pyproject.toml +0 -0
  122. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/algorithms/__init__.py +0 -0
  123. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/algorithms/sign.py +0 -0
  124. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/algorithms/tripledes.py +0 -0
  125. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/core/__init__.py +0 -0
  126. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/core/exceptions.py +0 -0
  127. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/core/pagination.py +0 -0
  128. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/core/request.py +0 -0
  129. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/core/versioning.py +0 -0
  130. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/__init__.py +0 -0
  131. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/_validator.py +0 -0
  132. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/comment.py +0 -0
  133. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/helper.py +0 -0
  134. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/login.py +0 -0
  135. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/lyric.py +0 -0
  136. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/private_message.py +0 -0
  137. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/recommend.py +0 -0
  138. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/request.py +0 -0
  139. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/search.py +0 -0
  140. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/singer.py +0 -0
  141. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/song.py +0 -0
  142. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/songlist.py +0 -0
  143. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/top.py +0 -0
  144. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/models/user.py +0 -0
  145. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/__init__.py +0 -0
  146. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/_base.py +0 -0
  147. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/comment.py +0 -0
  148. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/helper.py +0 -0
  149. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/helper_utils.py +0 -0
  150. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/login.py +0 -0
  151. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/login_utils.py +0 -0
  152. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/private_message.py +0 -0
  153. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/search.py +0 -0
  154. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/singer.py +0 -0
  155. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/top.py +0 -0
  156. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/modules/user.py +0 -0
  157. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/utils/__init__.py +0 -0
  158. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/utils/common.py +0 -0
  159. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/utils/device.py +0 -0
  160. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/utils/mqtt.py +0 -0
  161. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/qqmusic_api/utils/qimei.py +0 -0
  162. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/scripts/ag-1.py +0 -0
  163. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/tests/conftest.py +0 -0
  164. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/tests/test_comment.py +0 -0
  165. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/tests/test_login.py +0 -0
  166. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/tests/test_login_utils.py +0 -0
  167. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/tests/test_lyric.py +0 -0
  168. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/tests/test_private_message.py +0 -0
  169. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/tests/test_search.py +0 -0
  170. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/tests/test_singer.py +0 -0
  171. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/tests/test_top.py +0 -0
  172. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/tests/test_user.py +0 -0
  173. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/uv.lock +0 -0
  174. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/.gitignore +0 -0
  175. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/accounts.example.toml +0 -0
  176. /qqmusic_api_python-0.6.8/web/docker-compose.yml → /qqmusic_api_python-0.7.0/web/docker-compose.example.yml +0 -0
  177. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/core/__init__.py +0 -0
  178. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/core/deps.py +0 -0
  179. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/modules/__init__.py +0 -0
  180. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/modules/comment.py +0 -0
  181. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/modules/login.py +0 -0
  182. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/modules/mv.py +0 -0
  183. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/modules/singer.py +0 -0
  184. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routes/__init__.py +0 -0
  185. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routes/album.py +0 -0
  186. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routes/comment.py +0 -0
  187. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routes/login.py +0 -0
  188. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routes/lyric.py +0 -0
  189. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routes/mv.py +0 -0
  190. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routes/recommend.py +0 -0
  191. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routes/singer.py +0 -0
  192. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routes/top.py +0 -0
  193. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routing/__init__.py +0 -0
  194. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routing/params.py +0 -0
  195. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/src/routing/route_types.py +0 -0
  196. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/tests/test_web_core.py +0 -0
  197. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/tests/test_web_docstrings.py +0 -0
  198. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/web/tests/test_web_route_validation.py +0 -0
  199. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.7.0}/zensical.toml +0 -0
@@ -22,5 +22,3 @@ dist
22
22
  .prek_cache
23
23
  *.toml
24
24
  !pyproject.toml
25
- !web/config.toml
26
- !web/accounts.toml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qqmusic-api-python
3
- Version: 0.6.8
3
+ Version: 0.7.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/
@@ -388,7 +388,7 @@ from qqmusic_api import Client
388
388
 
389
389
  async def test_query_song(client: Client) -> None:
390
390
  """测试根据 ID 查询歌曲."""
391
- result = await client.song.query_song(["003w2xz20QlUZt"])
391
+ result = await client.song.query_song([SongQueryInfo(mid="003w2xz20QlUZt")])
392
392
  assert result.tracks
393
393
  assert result.tracks[0].name
394
394
  ```
@@ -1,4 +1,81 @@
1
1
 
2
+ ## [[0.6.9](https://github.com/L-1124/QQMusicApi/compare/v0.6.8..v0.6.9)] - 2026-07-12
3
+
4
+ ### Bug 修复
5
+
6
+ * **(web)** 修复凭据刷新异常捕获过宽与 ETag 缓存机制形同虚设的问题 ([305a063](https://github.com/L-1124/QQMusicApi/commit/305a0637f6261b3de3710c504dc8b22ec0ec52b8)) by [@L-1124](https://github.com/L-1124)
7
+ * **(web)** 修复配置账号由于重置逻辑缺陷被永久标记失效与流式响应并发槽位泄露 ([56f3034](https://github.com/L-1124/QQMusicApi/commit/56f30347a5182380e14febb1b6b1a80debc74ea3)) by [@L-1124](https://github.com/L-1124)
8
+ * **(web)** 避免将 credential 对象盲目混入参数导致 SDK 签名报错 ([fc8b32c](https://github.com/L-1124/QQMusicApi/commit/fc8b32ca1d8d20f16c52f37272798b3688f2848b)) by [@L-1124](https://github.com/L-1124)
9
+ * **(web)** 修复 KeyedLock finalizer 泄漏与 Redis 序列化 Pydantic 模型问题 ([5440a5e](https://github.com/L-1124/QQMusicApi/commit/5440a5e83b1d110d29f4e1b2eab74f5a8f509847)) by [@L-1124](https://github.com/L-1124)
10
+ * **(web)** 修正 songlist 创建与删除路由的 HTTP 方法 ([43c9ce6](https://github.com/L-1124/QQMusicApi/commit/43c9ce6fd0779895afe70a5c7062d7ead0d69957)) by [@L-1124](https://github.com/L-1124)
11
+ * **(web)** 修复凭据刷新锁与限流器的内存泄漏 ([c5ed6a3](https://github.com/L-1124/QQMusicApi/commit/c5ed6a315e7de680b60b63199350fca276781a46)) by [@L-1124](https://github.com/L-1124)
12
+ * **(web)** 修复 search 路由声明中 Any 导致的类型检查错误 ([d1aea06](https://github.com/L-1124/QQMusicApi/commit/d1aea0631aeee866cc8b09d1d6a086b07f24e7a9)) by [@L-1124](https://github.com/L-1124)
13
+ * **(web)** 修复返回 bool 类型时生成的文档和数据格式问题 ([af8550d](https://github.com/L-1124/QQMusicApi/commit/af8550d08038dc8f66be39e28c9d3a905abf7e63)) by [@L-1124](https://github.com/L-1124)
14
+
15
+ ### 功能更新
16
+
17
+ * **(web)** 添加 UserApi 的DisLike和favor api ([b1aa67d](https://github.com/L-1124/QQMusicApi/commit/b1aa67d5e6608d722b337c53274b4d91963a008d)) by [@L-1124](https://github.com/L-1124)
18
+ * 新增新碟上架 / MV 分类 / 收藏专辑 / 收藏歌曲接口与推荐新歌地区筛选 ([520c92b](https://github.com/L-1124/QQMusicApi/commit/520c92bd4d3546a20358c0a6daf47f67af8813d8)) by [@jinzhongjia](https://github.com/jinzhongjia) in [#284](https://github.com/L-1124/QQMusicApi/pull/284)
19
+
20
+ ### 功能重构
21
+
22
+ * **(web)** 收敛配置修改与硬编码路径到配置层 ([0e5950c](https://github.com/L-1124/QQMusicApi/commit/0e5950c9fec28783f081c7d37754de130dcd1950)) by [@L-1124](https://github.com/L-1124)
23
+ * **(web)** 抽取统一的 refresh_and_store 方法消除重复,并补全重试刷新失败时的无效标记逻辑 ([a4a0729](https://github.com/L-1124/QQMusicApi/commit/a4a072984f2dce212885bc240640c9a859d9b0ed)) by [@L-1124](https://github.com/L-1124)
24
+ * **(web)** 移除 songlist 的动态 getattr 分发以恢复类型安全与修复潜在凭据 KeyError ([c1030e1](https://github.com/L-1124/QQMusicApi/commit/c1030e1d867eed8e42957dbf797f548a754796e5)) by [@L-1124](https://github.com/L-1124)
25
+
26
+ ### 性能优化
27
+
28
+ * **(web)** 启用 SQLite WAL 模式并延迟反序列化随机凭据 ([7d01994](https://github.com/L-1124/QQMusicApi/commit/7d019942ae3812fe286d90eb668dc52318ccabe7)) by [@L-1124](https://github.com/L-1124)
29
+ * **(web)** 缓存路由参数解析以避免重复的 SDK 签名内省 ([61cc5a5](https://github.com/L-1124/QQMusicApi/commit/61cc5a5494afb750149d6689e252cb429e9d109d)) by [@L-1124](https://github.com/L-1124)
30
+ * **(web)** 抽取 app 为会话级 fixture 以大幅降低测试耗时 ([6d13b50](https://github.com/L-1124/QQMusicApi/commit/6d13b50ad42c1bf8f56c596a2fd810cadc23208d)) by [@L-1124](https://github.com/L-1124)
31
+ * **(web)** 移除缓存层与响应中不必要的双重序列化 ([bbc865c](https://github.com/L-1124/QQMusicApi/commit/bbc865c8aa52da6a027064510e94c8fcf8d5ab76)) by [@L-1124](https://github.com/L-1124)
32
+
33
+ ### 贡献者
34
+
35
+ * @L-1124
36
+ * @jinzhongjia [#284](https://github.com/L-1124/QQMusicApi/pull/284)
37
+ * @github-actions[bot]
38
+
39
+ ## [[0.6.8](https://github.com/L-1124/QQMusicApi/compare/v0.6.7..v0.6.8)] - 2026-07-01
40
+
41
+ ### Bug 修复
42
+
43
+ * **(web)** 禁用 FastAPI 默认按别名响应,使 JSON 输出与 Python 原生变量名保持一致 ([32459a5](https://github.com/L-1124/QQMusicApi/commit/32459a54c03d8a0a738f1917f9eb19fc6f9ac4ef)) by [@L-1124](https://github.com/L-1124)
44
+
45
+ ### 功能更新
46
+
47
+ * **(core)** 新增 SignatureRequiredError 错误码 2000 处理 ([a73313a](https://github.com/L-1124/QQMusicApi/commit/a73313a91549a156358ad9311d0e20d7401b861b)) by [@L-1124](https://github.com/L-1124)
48
+ * **(core)** _ensure_session 添加 24h 保活机制 ([2290a32](https://github.com/L-1124/QQMusicApi/commit/2290a32304bcd9052365f60c67f2d4f6b00e4d7e)) by [@L-1124](https://github.com/L-1124)
49
+ * **(helper)** 新增辅助功能接口 ([eb0c40c](https://github.com/L-1124/QQMusicApi/commit/eb0c40c1461826955a1eb005afb723438730142a)) by [@L-1124](https://github.com/L-1124)
50
+ * **(login)** 支持登出API ([73f2a7e](https://github.com/L-1124/QQMusicApi/commit/73f2a7ee9ad1d48cbec2d6aa9dbf844f44b573c2)) by [@L-1124](https://github.com/L-1124)
51
+ * **(lyric)** [**breaking**] 彻底移除 crypt 字段,隐式处理加密状态 ([80edc40](https://github.com/L-1124/QQMusicApi/commit/80edc40e41331f6b5c11146d5daf6eccf344181f)) by [@L-1124](https://github.com/L-1124)
52
+ * **(lyric)** [**breaking**] 移除 decrypt 方法,改为默认解密 ([24949c4](https://github.com/L-1124/QQMusicApi/commit/24949c4df8c63b02b35dd69c30e2a8f40d234dfb)) by [@L-1124](https://github.com/L-1124)
53
+ * **(song)** 添加特殊歌曲文件类型 TRY_OGG_640 ([fef7e8c](https://github.com/L-1124/QQMusicApi/commit/fef7e8ceb220aa477ea7d8498f8ebfd240f72cc6)) by [@L-1124](https://github.com/L-1124)
54
+ * **(user)** 新增收藏/取消收藏歌单 API ([7865434](https://github.com/L-1124/QQMusicApi/commit/7865434782dd205815222fe716f52bac9f84aeec)) by [@jinzhongjia](https://github.com/jinzhongjia) in [#281](https://github.com/L-1124/QQMusicApi/pull/281)
55
+ * **(user)** 新增 FeedbackBlack 黑名单 API ([4340554](https://github.com/L-1124/QQMusicApi/commit/4340554aaaafaef175eead7ebced596538bc65e2)) by [@L-1124](https://github.com/L-1124)
56
+ * **(web)** 启用 FastAPI 内置 Swagger UI 和 ReDoc,更新文档地址说明 ([a3245ad](https://github.com/L-1124/QQMusicApi/commit/a3245ad3d3863c7f7eec94352d169a32ba4478c6)) by [@L-1124](https://github.com/L-1124)
57
+ * **(web)** 重构 Dockerfile 为多阶段构建,修复非 root 用户 uv 缓存权限问题 ([00e16a3](https://github.com/L-1124/QQMusicApi/commit/00e16a37b62e989de37232e8af7e77167a28e823)) by [@L-1124](https://github.com/L-1124)
58
+
59
+ ### 功能重构
60
+
61
+ * **(core)** 移除 _session_cache 冗余缓存,简化会话验证逻辑 ([0b12b9d](https://github.com/L-1124/QQMusicApi/commit/0b12b9d041a8be24ad84d60bd2ceaed76b81fe92)) by [@L-1124](https://github.com/L-1124)
62
+ * **(models)** [**breaking**] 将所有模型中的 alias 统一替换为 validation_alias,隔离序列化字段名 ([c611b91](https://github.com/L-1124/QQMusicApi/commit/c611b91d607d36327662a80d0e126a6958b21bd5)) by [@L-1124](https://github.com/L-1124)
63
+ * **(modules)** 移除冗余的 _require_login 调用,统一使用 require_login 参数 ([1a666f4](https://github.com/L-1124/QQMusicApi/commit/1a666f45ae968c235066c07e8b3e82096c3fc668)) by [@L-1124](https://github.com/L-1124)
64
+ * **(modules)** 将 _require_login 收敛到 _build_request 的 require_login 参数中 ([d1a92ca](https://github.com/L-1124/QQMusicApi/commit/d1a92ca07d5a98f3f443453e5fc77b8f8c8dc44e)) by [@L-1124](https://github.com/L-1124)
65
+
66
+ ### 文档更新
67
+
68
+ * **(coding)** 更新需登录接口的编码规范,使用 require_login 参数替代 _require_login ([9e56215](https://github.com/L-1124/QQMusicApi/commit/9e562152ec8039b3045ae6c51006401c93b36a36)) by [@L-1124](https://github.com/L-1124)
69
+ * **(dislike)** 更新不喜欢列表项和响应数据的文档说明 ([5dbf711](https://github.com/L-1124/QQMusicApi/commit/5dbf711e46a69f99940f048504446b9e074fd21f)) by [@L-1124](https://github.com/L-1124)
70
+ * **(login)** 更新二维码和手机验证码登录状态的文档注释 ([f31d4d0](https://github.com/L-1124/QQMusicApi/commit/f31d4d0132cb61a9935ebea92657fcfa6591926c)) by [@L-1124](https://github.com/L-1124)
71
+ * **(readme)** 添加 DeepWiki 相关链接 ([fcea758](https://github.com/L-1124/QQMusicApi/commit/fcea758b80a824cb513a8bdfc8b247da786f5a1e)) by [@L-1124](https://github.com/L-1124)
72
+
73
+ ### 贡献者
74
+
75
+ * @L-1124
76
+ * @jinzhongjia [#281](https://github.com/L-1124/QQMusicApi/pull/281)
77
+ * @github-actions[bot]
78
+
2
79
  ## [[0.6.7](https://github.com/L-1124/QQMusicApi/compare/v0.6.6..v0.6.7)] - 2026-06-17
3
80
 
4
81
  ### 功能更新
@@ -34,7 +34,7 @@ asyncio.run(main())
34
34
  通过 `query_song` 获取歌曲信息后,可从 `pay` 字段判断是否需要付费:
35
35
 
36
36
  ```python
37
- song = await client.song.query_song(["003w2xz20QlUZt"])
37
+ song = await client.song.query_song([SongQueryInfo(mid="003w2xz20QlUZt")])
38
38
  track = song.tracks[0]
39
39
 
40
40
  # pay_play: 1=需要付费播放, 0=免费
@@ -72,7 +72,7 @@ for info in urls.data:
72
72
  `query_song` 返回的 `file` 字段包含各音质的文件大小,值为 `0` 表示该音质不存在:
73
73
 
74
74
  ```python
75
- song = await client.song.query_song(["003w2xz20QlUZt"])
75
+ song = await client.song.query_song([SongQueryInfo(mid="003w2xz20QlUZt")])
76
76
  track = song.tracks[0]
77
77
 
78
78
  file = track.file
@@ -22,7 +22,7 @@ from .core.exceptions import (
22
22
  from .core.versioning import Platform
23
23
  from .models.request import Credential
24
24
 
25
- __version__ = "0.6.8"
25
+ __version__ = "0.7.0"
26
26
 
27
27
  __all__ = [
28
28
  "ApiDataError",
@@ -7,12 +7,11 @@ from typing import TYPE_CHECKING, Any, Literal, cast, overload
7
7
 
8
8
  import anyio
9
9
  import orjson as json
10
- from niquests import AsyncSession, AsyncTokenBucketLimiter, PreparedRequest
10
+ from niquests import AsyncSession, AsyncTokenBucketLimiter, PreparedRequest, RetryConfiguration
11
11
  from niquests.exceptions import RequestException
12
12
  from niquests.models import Response
13
13
  from niquests.typing import AsyncHookType, ProxyType, TLSClientCertType, TLSVerifyType
14
14
  from tarsio import TarsDict
15
- from urllib3.util.retry import Retry
16
15
 
17
16
  from ..algorithms import zzc_sign
18
17
  from ..models.request import Credential, JceRequest, JceRequestItem, JceResponse, JceResponseItem, RequestItem
@@ -86,7 +85,7 @@ class Client:
86
85
  multiplexed=True,
87
86
  hooks=AsyncTokenBucketLimiter(rate=rate or 10, capacity=capacity or 50),
88
87
  happy_eyeballs=True,
89
- retries=Retry(
88
+ retries=RetryConfiguration(
90
89
  total=connect_retries or 2,
91
90
  connect=connect_retries or 2,
92
91
  read=0,
@@ -70,3 +70,52 @@ class GetAlbumSongResponse(Response):
70
70
  album_mid: str = Field(validation_alias="albumMid")
71
71
  total_num: int = Field(validation_alias="totalNum")
72
72
  song_list: list[Song] = Field(default_factory=list, json_schema_extra={"jsonpath": "$.songList[*].songInfo"})
73
+
74
+
75
+ class NewAlbumItem(Album):
76
+ """新碟上架列表中的单张专辑摘要.
77
+
78
+ Attributes:
79
+ singers: 专辑署名歌手列表.
80
+ release_time: 发行日期, 格式通常为 YYYY-MM-DD.
81
+ type: 专辑类型.
82
+ area: 地区标识.
83
+ genre: 流派标识.
84
+ language: 语种标识.
85
+ """
86
+
87
+ singers: list[Singer] = Field(default_factory=list)
88
+ release_time: str = ""
89
+ type: int = 0
90
+ area: int = 0
91
+ genre: int = 0
92
+ language: int = 0
93
+
94
+
95
+ class GetNewAlbumResponse(Response):
96
+ """新碟上架接口的响应体.
97
+
98
+ Attributes:
99
+ total: 该地区下新碟总数.
100
+ albums: 当前页新碟列表.
101
+ """
102
+
103
+ total: int = 0
104
+ albums: list[NewAlbumItem] = Field(default_factory=list)
105
+
106
+
107
+ class AlbumFavWriteResponse(Response):
108
+ """收藏 / 取消收藏专辑的写操作响应.
109
+
110
+ Attributes:
111
+ result: 操作结果码, 0 表示成功.
112
+ failed_album_id: 操作失败的专辑 ID 列表, 全部成功时为空.
113
+ """
114
+
115
+ result: int = 0
116
+ failed_album_id: list[int] = Field(default_factory=list, validation_alias="v_failedAlbumId")
117
+
118
+ @property
119
+ def success(self) -> bool:
120
+ """是否操作成功 (result 为 0 且无失败项)."""
121
+ return self.result == 0 and not self.failed_album_id
@@ -18,21 +18,17 @@ _PHOTO_NEW_SIZE_SEGMENTS: Final[dict[int, str]] = {
18
18
  }
19
19
 
20
20
 
21
- def _normalize_cover_size(size: CoverSize) -> str:
22
- """返回 `photo_new` 模板使用的尺寸片段."""
23
- try:
24
- return _PHOTO_NEW_SIZE_SEGMENTS[size]
25
- except KeyError as exc:
26
- raise ValueError("not supported size") from exc
27
-
28
-
29
21
  def _build_photo_new_cover_url(kind: str, mid: str, size: CoverSize) -> str:
30
22
  """按 `photo_new` 规则拼接封面链接."""
31
23
  normalized_mid = mid.strip()
32
24
  if not normalized_mid:
33
25
  return ""
34
26
 
35
- return f"https://y.gtimg.cn/music/photo_new/{kind}{_normalize_cover_size(size)}M000{normalized_mid}.jpg"
27
+ try:
28
+ cover_size = _PHOTO_NEW_SIZE_SEGMENTS[size]
29
+ return f"https://y.gtimg.cn/music/photo_new/{kind}{cover_size}M000{normalized_mid}.jpg"
30
+ except KeyError as exc:
31
+ raise ValueError("not supported size") from exc
36
32
 
37
33
 
38
34
  class Singer(Response):
@@ -4,7 +4,7 @@ from typing import Any
4
4
 
5
5
  from pydantic import Field
6
6
 
7
- from .base import MV
7
+ from .base import MV, Singer
8
8
  from .request import Response
9
9
 
10
10
 
@@ -116,3 +116,35 @@ class GetMvUrlsResponse(Response):
116
116
  """
117
117
 
118
118
  data: dict[str, MvUrlSet] = Field(json_schema_extra={"jsonpath": "$"})
119
+
120
+
121
+ class MvListItem(MV):
122
+ """MV 分类列表中的单个 MV 摘要.
123
+
124
+ Attributes:
125
+ singers: 歌手列表.
126
+ subtitle: MV 副标题.
127
+ playcnt: 播放量.
128
+ pubdate: 发布时间戳.
129
+ duration: 时长 (秒).
130
+ picurl: 封面图片地址.
131
+ """
132
+
133
+ singers: list[Singer] = Field(default_factory=list)
134
+ subtitle: str = ""
135
+ playcnt: int = 0
136
+ pubdate: int = 0
137
+ duration: int = 0
138
+ picurl: str = ""
139
+
140
+
141
+ class GetMvListResponse(Response):
142
+ """MV 分类列表接口的响应体.
143
+
144
+ Attributes:
145
+ total: 该分类条件下的 MV 总数.
146
+ items: 当前页 MV 列表.
147
+ """
148
+
149
+ total: int = 0
150
+ items: list[MvListItem] = Field(default_factory=list, validation_alias="list")
@@ -0,0 +1,117 @@
1
+ """专辑相关 API."""
2
+
3
+ from typing import Any
4
+
5
+ from ..core.pagination import OffsetStrategy, PagerMeta, ResponseAdapter
6
+ from ..models.album import (
7
+ AlbumFavWriteResponse,
8
+ GetAlbumDetailResponse,
9
+ GetAlbumSongResponse,
10
+ GetNewAlbumResponse,
11
+ )
12
+ from ..models.request import Credential
13
+ from ._base import ApiModule
14
+
15
+
16
+ class AlbumApi(ApiModule):
17
+ """专辑相关 API."""
18
+
19
+ def get_detail(self, value: int | str):
20
+ """获取专辑详细信息.
21
+
22
+ Args:
23
+ value: 专辑 ID 或 MID.
24
+ """
25
+ param: dict[str, Any] = {}
26
+ if isinstance(value, int) or (isinstance(value, str) and value.isdecimal()):
27
+ param["albumId"] = int(value)
28
+ else:
29
+ param["albumMId"] = value
30
+
31
+ return self._build_request(
32
+ module="music.musichallAlbum.AlbumInfoServer",
33
+ method="GetAlbumDetail",
34
+ param=param,
35
+ response_model=GetAlbumDetailResponse,
36
+ )
37
+
38
+ def get_song(self, value: int | str, num: int = 10, page: int = 1):
39
+ """获取专辑歌曲列表.
40
+
41
+ Args:
42
+ value: 专辑 ID 或 MID.
43
+ num: 返回结果数量.
44
+ page: 页码.
45
+ """
46
+ param: dict[str, Any] = {
47
+ "begin": num * (page - 1),
48
+ "num": num,
49
+ }
50
+ if isinstance(value, int) or (isinstance(value, str) and value.isdecimal()):
51
+ param["albumId"] = int(value)
52
+ else:
53
+ param["albumMid"] = value
54
+
55
+ return self._build_request(
56
+ module="music.musichallAlbum.AlbumSongList",
57
+ method="GetAlbumSongList",
58
+ param=param,
59
+ response_model=GetAlbumSongResponse,
60
+ pager_meta=PagerMeta(
61
+ strategy=OffsetStrategy(offset_key="begin", page_size_key="num"),
62
+ adapter=ResponseAdapter(total="total_num", count=lambda response: len(response.song_list)),
63
+ ),
64
+ )
65
+
66
+ def get_new_album(self, area: int = 1, num: int = 20, page: int = 1):
67
+ """获取新碟上架列表.
68
+
69
+ Args:
70
+ area: 地区. 1=内地, 2=港台, 3=欧美, 4=韩国, 5=日本, 6=其他.
71
+ num: 每页返回的专辑数量.
72
+ page: 页码, 从 1 开始.
73
+ """
74
+ return self._build_request(
75
+ module="newalbum.NewAlbumServer",
76
+ method="get_new_album_info",
77
+ param={"area": area, "num": num, "start": num * (page - 1)},
78
+ response_model=GetNewAlbumResponse,
79
+ pager_meta=PagerMeta(
80
+ strategy=OffsetStrategy(offset_key="start", page_size_key="num"),
81
+ adapter=ResponseAdapter(total="total", count=lambda response: len(response.albums)),
82
+ ),
83
+ )
84
+
85
+ def fav_album(self, album_id: int | list[int], *, credential: Credential | None = None):
86
+ """收藏专辑到当前登录用户.
87
+
88
+ Args:
89
+ album_id: 专辑 ID, 支持单个或列表.
90
+ credential: 登录凭证.
91
+ """
92
+ ids = [album_id] if isinstance(album_id, int) else album_id
93
+ return self._build_request(
94
+ module="music.musicasset.AlbumFavWrite",
95
+ method="FavAlbum",
96
+ param={"v_albumId": ids},
97
+ credential=credential,
98
+ require_login=True,
99
+ response_model=AlbumFavWriteResponse,
100
+ )
101
+
102
+ def del_fav_album(self, album_id: int | list[int], *, credential: Credential | None = None):
103
+ """取消收藏专辑.
104
+
105
+ Args:
106
+ album_id: 专辑 ID, 支持单个或列表.
107
+ credential: 登录凭证.
108
+ """
109
+ ids = [album_id] if isinstance(album_id, int) else album_id
110
+ return self._build_request(
111
+ module="music.musicasset.AlbumFavWrite",
112
+ method="CancelFavAlbum",
113
+ param={"v_albumId": ids},
114
+ credential=credential,
115
+ require_login=True,
116
+ response_model=AlbumFavWriteResponse,
117
+ )
@@ -16,6 +16,7 @@ class LyricApi(ApiModule):
16
16
  qrc: bool = False,
17
17
  trans: bool = False,
18
18
  roma: bool = False,
19
+ song_type: int = 1,
19
20
  ):
20
21
  """获取歌词原始数据.
21
22
 
@@ -24,6 +25,7 @@ class LyricApi(ApiModule):
24
25
  qrc: 是否获取逐字歌词 (逐字歌词可能需要特定权限).
25
26
  trans: 是否获取翻译.
26
27
  roma: 是否获取罗马音.
28
+ song_type: 歌曲类型.
27
29
  """
28
30
  params: dict[str, Any] = {
29
31
  "crypt": 1,
@@ -34,9 +36,10 @@ class LyricApi(ApiModule):
34
36
  "roma_t": 0,
35
37
  "trans": trans,
36
38
  "trans_t": 0,
37
- "type": 1,
39
+ "type": song_type,
38
40
  }
39
41
  params.update(self._build_query_common_params())
42
+
40
43
  if isinstance(value, int) or (isinstance(value, str) and value.isdecimal()):
41
44
  params["songId"] = int(value)
42
45
  else:
@@ -1,6 +1,7 @@
1
1
  """MV 相关 API."""
2
2
 
3
- from ..models.mv import GetMvDetailResponse, GetMvUrlsResponse
3
+ from ..core.pagination import OffsetStrategy, PagerMeta, ResponseAdapter
4
+ from ..models.mv import GetMvDetailResponse, GetMvListResponse, GetMvUrlsResponse
4
5
  from ..utils.common import get_guid
5
6
  from ._base import ApiModule
6
7
 
@@ -68,3 +69,31 @@ class MvApi(ApiModule):
68
69
  },
69
70
  response_model=GetMvUrlsResponse,
70
71
  )
72
+
73
+ def get_mv_list(
74
+ self,
75
+ area: int = 15,
76
+ version: int = 7,
77
+ order: int = 0,
78
+ num: int = 10,
79
+ page: int = 1,
80
+ ):
81
+ """获取 MV 分类列表.
82
+
83
+ Args:
84
+ area: 地区 ID. 15=全部, 8=内地, 5=港台, 6=欧美, 7=韩国, 4=日本.
85
+ version: 类型 ID. 7=全部, 8=MV, 13=现场, 14=翻唱, 15=舞蹈, 16=影视, 17=综艺, 18=儿歌.
86
+ order: 排序方式. 0=最新, 1=最热.
87
+ num: 每页返回数量.
88
+ page: 页码, 从 1 开始.
89
+ """
90
+ return self._build_request(
91
+ module="MvService.MvInfoProServer",
92
+ method="GetAllocMvInfo",
93
+ param={"area": area, "version": version, "order": order, "start": num * (page - 1), "size": num},
94
+ response_model=GetMvListResponse,
95
+ pager_meta=PagerMeta(
96
+ strategy=OffsetStrategy(offset_key="start", page_size_key="size"),
97
+ adapter=ResponseAdapter(total="total", count=lambda response: len(response.items)),
98
+ ),
99
+ )
@@ -146,9 +146,13 @@ class RecommendApi(ApiModule):
146
146
  ),
147
147
  )
148
148
 
149
- def get_recommend_newsong(self):
150
- """获取推荐新歌."""
151
- data = {"type": 5}
149
+ def get_recommend_newsong(self, type: int = 5): # noqa: A002
150
+ """获取推荐新歌.
151
+
152
+ Args:
153
+ type: 地区/语种筛选. 1=内地, 2=欧美, 3=日本, 4=韩国, 5=最新, 6=港台.
154
+ """
155
+ data = {"type": type}
152
156
  return self._build_request(
153
157
  "newsong.NewSongServer",
154
158
  "get_new_song_info",
@@ -177,36 +177,64 @@ class SongFileInfo(NamedTuple):
177
177
  media_mid: str | None = None
178
178
 
179
179
 
180
+ class SongQueryInfo(NamedTuple):
181
+ """歌曲查询信息.
182
+
183
+ Attributes:
184
+ id: 歌曲 ID.
185
+ mid: 歌曲 MID.
186
+ song_type: 歌曲类型.
187
+ """
188
+
189
+ id: int | None = None
190
+ mid: str | None = None
191
+ song_type: int | None = None
192
+
193
+
180
194
  class SongApi(ApiModule):
181
195
  """歌曲相关 API 模块类."""
182
196
 
183
197
  _GET_SONG_URLS_MAX_MID = 100
184
198
  _SONG_URL_FALLBACK_DOMAIN = "https://isure.stream.qqmusic.qq.com/"
185
199
 
186
- def query_song(self, value: list[int] | list[str]):
187
- """根据 id 或 mid 获取歌曲信息.
200
+ def query_song(
201
+ self,
202
+ song_info: list[SongQueryInfo],
203
+ ):
204
+ """批量获取歌曲信息.
188
205
 
189
206
  Args:
190
- value: 歌曲 ID 列表或 MID 列表.
207
+ song_info: SongQueryInfo 列表.
191
208
 
192
209
  Raises:
193
- ValueError: 如果 `value` 为空.
210
+ ValueError: 如果 `song_info` 为空, 或参数不匹配.
194
211
  """
195
- if not value:
196
- raise ValueError("value 不能为空")
212
+ if not song_info:
213
+ raise ValueError("song_info 不能为空")
214
+
215
+ ids, mids, types = [], [], []
216
+ for item in song_info:
217
+ if (item.id is None) == (item.mid is None):
218
+ raise ValueError("SongQueryInfo 必须提供 id 或 mid 且不能同时提供")
219
+
220
+ if item.id is not None:
221
+ ids.append(item.id)
222
+ else:
223
+ mids.append(item.mid)
224
+ types.append(item.song_type or 0)
225
+
197
226
  params: dict[str, Any] = {
198
- "types": [0 for _ in range(len(value))],
199
- "modify_stamp": [0 for _ in range(len(value))],
200
227
  "ctx": 0,
201
228
  "client": 1,
229
+ "types": types,
230
+ "modify_stamp": [0] * len(types),
202
231
  }
203
- numeric_values = [isinstance(item, int) or (isinstance(item, str) and item.isdecimal()) for item in value]
204
- if all(numeric_values):
205
- params["ids"] = [int(v) for v in value]
206
- elif any(numeric_values):
207
- raise ValueError("value 不能混合歌曲 ID 与 MID")
208
- else:
209
- params["mids"] = [str(v) for v in value]
232
+
233
+ if ids:
234
+ params["ids"] = ids
235
+ if mids:
236
+ params["mids"] = mids
237
+
210
238
  return self._build_request(
211
239
  module="music.trackInfo.UniformRuleCtrl",
212
240
  method="CgiGetTrackInfo",
@@ -175,3 +175,43 @@ class SonglistApi(ApiModule):
175
175
  if e.code == 80092:
176
176
  return False
177
177
  raise
178
+
179
+ async def like_song(
180
+ self,
181
+ song_info: list[tuple[int, int]],
182
+ *,
183
+ credential: Credential | None = None,
184
+ ) -> bool:
185
+ """收藏歌曲到 "我喜欢" 歌单.
186
+
187
+ Note:
188
+ "我喜欢" 歌单的目录 ID 固定为 201, 本方法即以 dirid=201 调用 `add_songs`.
189
+
190
+ Args:
191
+ song_info: 歌曲信息列表, 每项为 `(song_id, song_type)`.
192
+ credential: 登录凭证.
193
+
194
+ Returns:
195
+ 操作成功与否 (歌曲已在 "我喜欢" 中也返回 True).
196
+ """
197
+ return await self.add_songs(201, song_info, credential=credential)
198
+
199
+ async def unlike_song(
200
+ self,
201
+ song_info: list[tuple[int, int]],
202
+ *,
203
+ credential: Credential | None = None,
204
+ ) -> bool:
205
+ """从 "我喜欢" 歌单移除歌曲.
206
+
207
+ Note:
208
+ "我喜欢" 歌单的目录 ID 固定为 201, 本方法即以 dirid=201 调用 `del_songs`.
209
+
210
+ Args:
211
+ song_info: 歌曲信息列表, 每项为 `(song_id, song_type)`.
212
+ credential: 登录凭证.
213
+
214
+ Returns:
215
+ 操作成功与否 (歌曲不在 "我喜欢" 中也返回 True).
216
+ """
217
+ return await self.del_songs(201, song_info, credential=credential)