qqmusic-api-python 0.6.8__tar.gz → 0.6.9__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.6.9}/.dockerignore +0 -2
  2. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/PKG-INFO +1 -1
  3. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/release-notes.md +40 -0
  4. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/__init__.py +1 -1
  5. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/album.py +49 -0
  6. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/base.py +5 -9
  7. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/mv.py +33 -1
  8. qqmusic_api_python-0.6.9/qqmusic_api/modules/album.py +117 -0
  9. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/mv.py +30 -1
  10. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/recommend.py +7 -3
  11. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/songlist.py +40 -0
  12. qqmusic_api_python-0.6.9/tests/test_album.py +59 -0
  13. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/tests/test_mv.py +17 -0
  14. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/tests/test_recommend.py +10 -0
  15. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/tests/test_songlist.py +19 -0
  16. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/Dockerfile +0 -3
  17. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/README.md +22 -1
  18. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/config.example.toml +5 -0
  19. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/requirements.txt +61 -3
  20. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/run.py +0 -3
  21. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/app.py +3 -3
  22. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/core/auth.py +52 -42
  23. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/core/cache.py +25 -12
  24. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/core/config.py +18 -0
  25. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/core/credential_store.py +20 -25
  26. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/core/response.py +1 -3
  27. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/core/security.py +26 -20
  28. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/modules/songlist.py +10 -11
  29. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routes/search.py +3 -3
  30. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routes/songlist.py +2 -0
  31. qqmusic_api_python-0.6.9/web/src/routes/user.py +186 -0
  32. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routing/docstrings.py +1 -3
  33. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routing/executor.py +42 -20
  34. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routing/router_factory.py +9 -2
  35. qqmusic_api_python-0.6.9/web/tests/conftest.py +12 -0
  36. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/tests/test_web_routes.py +25 -28
  37. qqmusic_api_python-0.6.8/qqmusic_api/modules/album.py +0 -58
  38. qqmusic_api_python-0.6.8/tests/test_album.py +0 -29
  39. qqmusic_api_python-0.6.8/web/src/routes/user.py +0 -109
  40. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.agents/skills/pydantic/SKILL.md +0 -0
  41. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.agents/skills/python-standards/SKILL.md +0 -0
  42. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.agents/skills/tarsio/SKILL.md +0 -0
  43. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.agents/skills/tarsio/references/api-reference.md +0 -0
  44. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.agents/skills/uv-package-manager/SKILL.md +0 -0
  45. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  46. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  47. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
  48. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  49. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.github/renovate.json +0 -0
  50. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.github/workflows/checking.yaml +0 -0
  51. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.github/workflows/docs.yml +0 -0
  52. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.github/workflows/release.yml +0 -0
  53. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.github/workflows/testing.yml +0 -0
  54. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.gitignore +0 -0
  55. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/.markdownlint-cli2.yaml +0 -0
  56. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/AGENTS.md +0 -0
  57. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/CLAUDE.md +0 -0
  58. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/LICENSE +0 -0
  59. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/README.md +0 -0
  60. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/assets/qq-music.svg +0 -0
  61. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/cliff.toml +0 -0
  62. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/coding.md +0 -0
  63. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/contributing.md +0 -0
  64. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/index.md +0 -0
  65. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/core/client.md +0 -0
  66. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/core/exception.md +0 -0
  67. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/core/pagination.md +0 -0
  68. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/core/request.md +0 -0
  69. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/core/versioning.md +0 -0
  70. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/model/album.md +0 -0
  71. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/model/base.md +0 -0
  72. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/model/comment.md +0 -0
  73. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/model/login.md +0 -0
  74. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/model/lyric.md +0 -0
  75. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/model/mv.md +0 -0
  76. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/model/recommend.md +0 -0
  77. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/model/request.md +0 -0
  78. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/model/search.md +0 -0
  79. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/model/singer.md +0 -0
  80. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/model/song.md +0 -0
  81. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/model/songlist.md +0 -0
  82. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/model/top.md +0 -0
  83. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/model/user.md +0 -0
  84. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/modules/album.md +0 -0
  85. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/modules/comment.md +0 -0
  86. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/modules/login.md +0 -0
  87. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/modules/login_utils.md +0 -0
  88. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/modules/lyric.md +0 -0
  89. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/modules/mv.md +0 -0
  90. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/modules/private_message.md +0 -0
  91. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/modules/recommend.md +0 -0
  92. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/modules/search.md +0 -0
  93. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/modules/singer.md +0 -0
  94. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/modules/song.md +0 -0
  95. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/modules/songlist.md +0 -0
  96. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/modules/top.md +0 -0
  97. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/reference/modules/user.md +0 -0
  98. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/tutorial/client.md +0 -0
  99. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/tutorial/credential.md +0 -0
  100. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/tutorial/download.md +0 -0
  101. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/tutorial/error-handling.md +0 -0
  102. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/tutorial/login.md +0 -0
  103. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/tutorial/pagination.md +0 -0
  104. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/tutorial/start.md +0 -0
  105. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/docs/tutorial/web.md +0 -0
  106. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/examples/download_song.py +0 -0
  107. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/examples/get_all_sheets.py +0 -0
  108. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/examples/phone_login.py +0 -0
  109. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/examples/private_message.py +0 -0
  110. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/examples/qrcode_login.py +0 -0
  111. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/examples/upload_file.py +0 -0
  112. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/prek.toml +0 -0
  113. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/pyproject.toml +0 -0
  114. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/algorithms/__init__.py +0 -0
  115. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/algorithms/sign.py +0 -0
  116. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/algorithms/tripledes.py +0 -0
  117. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/core/__init__.py +0 -0
  118. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/core/client.py +0 -0
  119. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/core/exceptions.py +0 -0
  120. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/core/pagination.py +0 -0
  121. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/core/request.py +0 -0
  122. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/core/versioning.py +0 -0
  123. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/__init__.py +0 -0
  124. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/_validator.py +0 -0
  125. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/comment.py +0 -0
  126. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/helper.py +0 -0
  127. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/login.py +0 -0
  128. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/lyric.py +0 -0
  129. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/private_message.py +0 -0
  130. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/recommend.py +0 -0
  131. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/request.py +0 -0
  132. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/search.py +0 -0
  133. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/singer.py +0 -0
  134. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/song.py +0 -0
  135. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/songlist.py +0 -0
  136. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/top.py +0 -0
  137. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/models/user.py +0 -0
  138. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/__init__.py +0 -0
  139. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/_base.py +0 -0
  140. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/comment.py +0 -0
  141. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/helper.py +0 -0
  142. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/helper_utils.py +0 -0
  143. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/login.py +0 -0
  144. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/login_utils.py +0 -0
  145. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/lyric.py +0 -0
  146. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/private_message.py +0 -0
  147. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/search.py +0 -0
  148. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/singer.py +0 -0
  149. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/song.py +0 -0
  150. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/top.py +0 -0
  151. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/modules/user.py +0 -0
  152. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/utils/__init__.py +0 -0
  153. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/utils/common.py +0 -0
  154. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/utils/device.py +0 -0
  155. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/utils/mqtt.py +0 -0
  156. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/qqmusic_api/utils/qimei.py +0 -0
  157. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/scripts/ag-1.py +0 -0
  158. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/tests/conftest.py +0 -0
  159. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/tests/test_comment.py +0 -0
  160. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/tests/test_login.py +0 -0
  161. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/tests/test_login_utils.py +0 -0
  162. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/tests/test_lyric.py +0 -0
  163. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/tests/test_private_message.py +0 -0
  164. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/tests/test_search.py +0 -0
  165. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/tests/test_singer.py +0 -0
  166. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/tests/test_song.py +0 -0
  167. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/tests/test_top.py +0 -0
  168. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/tests/test_user.py +0 -0
  169. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/uv.lock +0 -0
  170. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/.gitignore +0 -0
  171. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/accounts.example.toml +0 -0
  172. /qqmusic_api_python-0.6.8/web/docker-compose.yml → /qqmusic_api_python-0.6.9/web/docker-compose.example.yml +0 -0
  173. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/core/__init__.py +0 -0
  174. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/core/deps.py +0 -0
  175. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/modules/__init__.py +0 -0
  176. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/modules/comment.py +0 -0
  177. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/modules/login.py +0 -0
  178. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/modules/mv.py +0 -0
  179. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/modules/singer.py +0 -0
  180. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/modules/song.py +0 -0
  181. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routes/__init__.py +0 -0
  182. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routes/_helpers.py +0 -0
  183. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routes/album.py +0 -0
  184. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routes/comment.py +0 -0
  185. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routes/login.py +0 -0
  186. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routes/lyric.py +0 -0
  187. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routes/mv.py +0 -0
  188. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routes/recommend.py +0 -0
  189. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routes/singer.py +0 -0
  190. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routes/song.py +0 -0
  191. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routes/top.py +0 -0
  192. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routing/__init__.py +0 -0
  193. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routing/params.py +0 -0
  194. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/src/routing/route_types.py +0 -0
  195. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/tests/test_web_core.py +0 -0
  196. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/tests/test_web_docstrings.py +0 -0
  197. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/tests/test_web_enums.py +0 -0
  198. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/web/tests/test_web_route_validation.py +0 -0
  199. {qqmusic_api_python-0.6.8 → qqmusic_api_python-0.6.9}/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.6.9
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/
@@ -1,4 +1,44 @@
1
1
 
2
+ ## [[0.6.8](https://github.com/L-1124/QQMusicApi/compare/v0.6.7..v0.6.8)] - 2026-07-01
3
+
4
+ ### Bug 修复
5
+
6
+ * **(web)** 禁用 FastAPI 默认按别名响应,使 JSON 输出与 Python 原生变量名保持一致 ([32459a5](https://github.com/L-1124/QQMusicApi/commit/32459a54c03d8a0a738f1917f9eb19fc6f9ac4ef)) by [@L-1124](https://github.com/L-1124)
7
+
8
+ ### 功能更新
9
+
10
+ * **(core)** 新增 SignatureRequiredError 错误码 2000 处理 ([a73313a](https://github.com/L-1124/QQMusicApi/commit/a73313a91549a156358ad9311d0e20d7401b861b)) by [@L-1124](https://github.com/L-1124)
11
+ * **(core)** _ensure_session 添加 24h 保活机制 ([2290a32](https://github.com/L-1124/QQMusicApi/commit/2290a32304bcd9052365f60c67f2d4f6b00e4d7e)) by [@L-1124](https://github.com/L-1124)
12
+ * **(helper)** 新增辅助功能接口 ([eb0c40c](https://github.com/L-1124/QQMusicApi/commit/eb0c40c1461826955a1eb005afb723438730142a)) by [@L-1124](https://github.com/L-1124)
13
+ * **(login)** 支持登出API ([73f2a7e](https://github.com/L-1124/QQMusicApi/commit/73f2a7ee9ad1d48cbec2d6aa9dbf844f44b573c2)) by [@L-1124](https://github.com/L-1124)
14
+ * **(lyric)** [**breaking**] 彻底移除 crypt 字段,隐式处理加密状态 ([80edc40](https://github.com/L-1124/QQMusicApi/commit/80edc40e41331f6b5c11146d5daf6eccf344181f)) by [@L-1124](https://github.com/L-1124)
15
+ * **(lyric)** [**breaking**] 移除 decrypt 方法,改为默认解密 ([24949c4](https://github.com/L-1124/QQMusicApi/commit/24949c4df8c63b02b35dd69c30e2a8f40d234dfb)) by [@L-1124](https://github.com/L-1124)
16
+ * **(song)** 添加特殊歌曲文件类型 TRY_OGG_640 ([fef7e8c](https://github.com/L-1124/QQMusicApi/commit/fef7e8ceb220aa477ea7d8498f8ebfd240f72cc6)) by [@L-1124](https://github.com/L-1124)
17
+ * **(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)
18
+ * **(user)** 新增 FeedbackBlack 黑名单 API ([4340554](https://github.com/L-1124/QQMusicApi/commit/4340554aaaafaef175eead7ebced596538bc65e2)) by [@L-1124](https://github.com/L-1124)
19
+ * **(web)** 启用 FastAPI 内置 Swagger UI 和 ReDoc,更新文档地址说明 ([a3245ad](https://github.com/L-1124/QQMusicApi/commit/a3245ad3d3863c7f7eec94352d169a32ba4478c6)) by [@L-1124](https://github.com/L-1124)
20
+ * **(web)** 重构 Dockerfile 为多阶段构建,修复非 root 用户 uv 缓存权限问题 ([00e16a3](https://github.com/L-1124/QQMusicApi/commit/00e16a37b62e989de37232e8af7e77167a28e823)) by [@L-1124](https://github.com/L-1124)
21
+
22
+ ### 功能重构
23
+
24
+ * **(core)** 移除 _session_cache 冗余缓存,简化会话验证逻辑 ([0b12b9d](https://github.com/L-1124/QQMusicApi/commit/0b12b9d041a8be24ad84d60bd2ceaed76b81fe92)) by [@L-1124](https://github.com/L-1124)
25
+ * **(models)** [**breaking**] 将所有模型中的 alias 统一替换为 validation_alias,隔离序列化字段名 ([c611b91](https://github.com/L-1124/QQMusicApi/commit/c611b91d607d36327662a80d0e126a6958b21bd5)) by [@L-1124](https://github.com/L-1124)
26
+ * **(modules)** 移除冗余的 _require_login 调用,统一使用 require_login 参数 ([1a666f4](https://github.com/L-1124/QQMusicApi/commit/1a666f45ae968c235066c07e8b3e82096c3fc668)) by [@L-1124](https://github.com/L-1124)
27
+ * **(modules)** 将 _require_login 收敛到 _build_request 的 require_login 参数中 ([d1a92ca](https://github.com/L-1124/QQMusicApi/commit/d1a92ca07d5a98f3f443453e5fc77b8f8c8dc44e)) by [@L-1124](https://github.com/L-1124)
28
+
29
+ ### 文档更新
30
+
31
+ * **(coding)** 更新需登录接口的编码规范,使用 require_login 参数替代 _require_login ([9e56215](https://github.com/L-1124/QQMusicApi/commit/9e562152ec8039b3045ae6c51006401c93b36a36)) by [@L-1124](https://github.com/L-1124)
32
+ * **(dislike)** 更新不喜欢列表项和响应数据的文档说明 ([5dbf711](https://github.com/L-1124/QQMusicApi/commit/5dbf711e46a69f99940f048504446b9e074fd21f)) by [@L-1124](https://github.com/L-1124)
33
+ * **(login)** 更新二维码和手机验证码登录状态的文档注释 ([f31d4d0](https://github.com/L-1124/QQMusicApi/commit/f31d4d0132cb61a9935ebea92657fcfa6591926c)) by [@L-1124](https://github.com/L-1124)
34
+ * **(readme)** 添加 DeepWiki 相关链接 ([fcea758](https://github.com/L-1124/QQMusicApi/commit/fcea758b80a824cb513a8bdfc8b247da786f5a1e)) by [@L-1124](https://github.com/L-1124)
35
+
36
+ ### 贡献者
37
+
38
+ * @L-1124
39
+ * @jinzhongjia [#281](https://github.com/L-1124/QQMusicApi/pull/281)
40
+ * @github-actions[bot]
41
+
2
42
  ## [[0.6.7](https://github.com/L-1124/QQMusicApi/compare/v0.6.6..v0.6.7)] - 2026-06-17
3
43
 
4
44
  ### 功能更新
@@ -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.6.9"
26
26
 
27
27
  __all__ = [
28
28
  "ApiDataError",
@@ -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
+ )
@@ -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",
@@ -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)
@@ -0,0 +1,59 @@
1
+ """专辑模块测试."""
2
+
3
+ from typing import Literal
4
+
5
+ import pytest
6
+
7
+ from qqmusic_api import Client, CredentialInvalidError
8
+
9
+ CoverSize = Literal[150, 300, 500, 800] | None
10
+
11
+
12
+ async def test_get_detail_by_id(client: Client) -> None:
13
+ """测试通过专辑 ID 获取专辑详情."""
14
+ result = await client.album.get_detail(100)
15
+ assert result is not None
16
+
17
+
18
+ @pytest.mark.parametrize(
19
+ ("mid", "num", "page"),
20
+ [
21
+ ("001uKKpF1RuJSd", 30, 1),
22
+ ("001gR6jO1L4MWq", 5, 1),
23
+ ("0041WVfh2vtlJE", 10, 2),
24
+ ],
25
+ )
26
+ async def test_get_song(client: Client, mid: str, num: int, page: int) -> None:
27
+ """测试获取专辑歌曲列表."""
28
+ result = await client.album.get_song(mid, num=num, page=page)
29
+ assert result is not None
30
+
31
+
32
+ @pytest.mark.parametrize("area", [1, 2, 3])
33
+ async def test_get_new_album(client: Client, area: int) -> None:
34
+ """测试按地区获取新碟上架列表."""
35
+ result = await client.album.get_new_album(area=area, num=5, page=1)
36
+ assert result.albums
37
+ assert result.albums[0].mid
38
+ assert result.albums[0].name
39
+
40
+
41
+ async def test_get_new_album_pagination(client: Client) -> None:
42
+ """测试新碟上架分页返回不同数据."""
43
+ pager = client.album.get_new_album(area=1, num=5, page=1).paginate(limit=2)
44
+ first = await pager.next()
45
+ second = await pager.next()
46
+ assert first.albums[0].mid != second.albums[0].mid
47
+
48
+
49
+ async def test_fav_album_roundtrip(authenticated_client: Client) -> None:
50
+ """测试收藏与取消收藏专辑可逆往返."""
51
+ album = (await authenticated_client.album.get_new_album(area=1, num=1, page=1)).albums[0]
52
+ assert (await authenticated_client.album.fav_album(album.id)).success
53
+ assert (await authenticated_client.album.del_fav_album(album.id)).success
54
+
55
+
56
+ async def test_fav_album_requires_login(client: Client) -> None:
57
+ """测试收藏专辑需要登录凭证."""
58
+ with pytest.raises(CredentialInvalidError):
59
+ await client.album.fav_album(100)
@@ -25,3 +25,20 @@ async def test_get_mv_urls_multiple(client: Client) -> None:
25
25
  """测试批量获取 MV 播放链接."""
26
26
  result = await client.mv.get_mv_urls(["013xscuH0xlbie", "013xscuH0xlbie"])
27
27
  assert result is not None
28
+
29
+
30
+ async def test_get_mv_list(client: Client) -> None:
31
+ """测试获取 MV 分类列表."""
32
+ result = await client.mv.get_mv_list(area=15, version=7, order=0, num=5, page=1)
33
+ assert result.total > 0
34
+ assert result.items
35
+ assert result.items[0].vid
36
+ assert result.items[0].title
37
+
38
+
39
+ async def test_get_mv_list_pagination(client: Client) -> None:
40
+ """测试 MV 分类列表分页返回不同数据."""
41
+ pager = client.mv.get_mv_list(num=5, page=1).paginate(limit=2)
42
+ first = await pager.next()
43
+ second = await pager.next()
44
+ assert first.items[0].vid != second.items[0].vid
@@ -1,5 +1,7 @@
1
1
  """推荐模块测试."""
2
2
 
3
+ import pytest
4
+
3
5
  from qqmusic_api import Client
4
6
 
5
7
 
@@ -35,3 +37,11 @@ async def test_get_recommend_newsong(client: Client) -> None:
35
37
  result = await client.recommend.get_recommend_newsong()
36
38
  assert result.songs
37
39
  assert result.songs[0].mid
40
+
41
+
42
+ @pytest.mark.parametrize(("area_type", "lan"), [(1, "内地"), (2, "欧美"), (6, "港台")])
43
+ async def test_get_recommend_newsong_by_type(client: Client, area_type: int, lan: str) -> None:
44
+ """测试按地区类型获取推荐新歌."""
45
+ result = await client.recommend.get_recommend_newsong(type=area_type)
46
+ assert result.lan == lan
47
+ assert result.songs
@@ -63,3 +63,22 @@ async def test_del_songs_requires_login(client: Client) -> None:
63
63
  """测试删除歌单歌曲需要登录凭证."""
64
64
  with pytest.raises(CredentialInvalidError):
65
65
  await client.songlist.del_songs(dirid=1, song_info=[(100, 0)])
66
+
67
+
68
+ async def test_like_song_requires_login(client: Client) -> None:
69
+ """测试收藏歌曲到我喜欢需要登录凭证."""
70
+ with pytest.raises(CredentialInvalidError):
71
+ await client.songlist.like_song([(100, 0)])
72
+
73
+
74
+ async def test_unlike_song_requires_login(client: Client) -> None:
75
+ """测试从我喜欢移除歌曲需要登录凭证."""
76
+ with pytest.raises(CredentialInvalidError):
77
+ await client.songlist.unlike_song([(100, 0)])
78
+
79
+
80
+ async def test_like_song_roundtrip(authenticated_client: Client) -> None:
81
+ """测试收藏与取消收藏歌曲可逆往返."""
82
+ song = (await authenticated_client.recommend.get_recommend_newsong(type=1)).songs[0]
83
+ assert await authenticated_client.songlist.like_song([(song.id, song.type)])
84
+ assert await authenticated_client.songlist.unlike_song([(song.id, song.type)])
@@ -38,7 +38,4 @@ WORKDIR /app
38
38
 
39
39
  EXPOSE 8080
40
40
 
41
- HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
42
- CMD python -c "from urllib.request import urlopen; urlopen('http://localhost:8080/')" || exit 1
43
-
44
41
  CMD ["python", "web/run.py"]
@@ -45,17 +45,38 @@ uv run --no-sync web/run.py
45
45
  docker build -t qqmusic-api-web -f web/Dockerfile .
46
46
 
47
47
  # 运行容器
48
- docker run -p 8080:8080 \
48
+ docker run -d -p 8080:8080 --name qqmusic-api-web \
49
49
  -v ./web/data:/app/web/data \
50
+ -v ./web/accounts.toml:/app/web/accounts.toml:ro \
51
+ -v ./web/config.toml:/app/web/config.toml:ro \
50
52
  qqmusic-api-web
51
53
  ```
52
54
 
53
55
  使用 docker-compose:
54
56
 
55
57
  ```bash
58
+ # 首次使用请复制示例文件并编辑:
59
+ cp web/docker-compose.example.yml web/docker-compose.yml
60
+ # 然后启动:
56
61
  docker compose -f web/docker-compose.yml up -d
57
62
  ```
58
63
 
64
+ ### wslc 部署
65
+
66
+ [`wslc`](https://learn.microsoft.com/zh-cn/windows/wsl/) 是 Windows 自带的 WSL 容器管理工具。
67
+
68
+ ```bash
69
+ # 构建镜像
70
+ wslc build -t qqmusic-api-web -f web/Dockerfile .
71
+
72
+ # 运行容器
73
+ wslc run -d -p 8080:8080 --name qqmusic-api-web \
74
+ -v ./web/data:/app/web/data \
75
+ -v ./web/accounts.toml:/app/web/accounts.toml:ro \
76
+ -v ./web/config.toml:/app/web/config.toml:ro \
77
+ qqmusic-api-web
78
+ ```
79
+
59
80
  挂载说明:
60
81
 
61
82
  * `web/data/` — 设备信息、凭据状态库、日志等持久化数据。
@@ -43,6 +43,11 @@ backend = "memory"
43
43
  # redis_prefix = "qqapi:"
44
44
 
45
45
 
46
+ [client]
47
+ # SDK Client 设备信息文件路径
48
+ # device_path = "web/data/device.json"
49
+
50
+
46
51
  [credential]
47
52
  # 是否启用全局默认登录凭证。Cookie 中显式传入的登录凭证始终优先。
48
53
  enabled = false