qqmusic-api-python 0.6.6__tar.gz → 0.6.7__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 (192) hide show
  1. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/AGENTS.md +0 -1
  2. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/PKG-INFO +1 -1
  3. qqmusic_api_python-0.6.7/docs/coding.md +428 -0
  4. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/release-notes.md +48 -0
  5. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/tutorial/client.md +13 -6
  6. qqmusic_api_python-0.6.7/docs/tutorial/download.md +124 -0
  7. qqmusic_api_python-0.6.7/docs/tutorial/error-handling.md +69 -0
  8. qqmusic_api_python-0.6.7/docs/tutorial/login.md +171 -0
  9. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/tutorial/start.md +16 -12
  10. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/__init__.py +9 -1
  11. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/core/__init__.py +8 -0
  12. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/core/exceptions.py +69 -1
  13. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/base.py +29 -29
  14. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/comment.py +4 -0
  15. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/login.py +27 -5
  16. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/private_message.py +4 -1
  17. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/search.py +2 -0
  18. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/singer.py +2 -0
  19. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/song.py +2 -0
  20. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/utils/device.py +3 -0
  21. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/utils/qimei.py +12 -4
  22. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/test_login.py +49 -5
  23. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/README.md +2 -2
  24. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/requirements.txt +195 -90
  25. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routes/song.py +2 -0
  26. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/zensical.toml +8 -3
  27. qqmusic_api_python-0.6.6/docs/coding.md +0 -342
  28. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.agents/skills/pydantic/SKILL.md +0 -0
  29. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.agents/skills/python-standards/SKILL.md +0 -0
  30. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.agents/skills/tarsio/SKILL.md +0 -0
  31. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.agents/skills/tarsio/references/api-reference.md +0 -0
  32. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.agents/skills/uv-package-manager/SKILL.md +0 -0
  33. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.dockerignore +0 -0
  34. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  35. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  36. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
  37. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  38. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.github/renovate.json +0 -0
  39. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.github/workflows/checking.yaml +0 -0
  40. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.github/workflows/docs.yml +0 -0
  41. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.github/workflows/release.yml +0 -0
  42. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.github/workflows/testing.yml +0 -0
  43. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.gitignore +0 -0
  44. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/.markdownlint-cli2.yaml +0 -0
  45. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/CLAUDE.md +0 -0
  46. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/LICENSE +0 -0
  47. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/README.md +0 -0
  48. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/assets/qq-music.svg +0 -0
  49. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/cliff.toml +0 -0
  50. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/contributing.md +0 -0
  51. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/index.md +0 -0
  52. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/core/client.md +0 -0
  53. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/core/exception.md +0 -0
  54. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/core/pagination.md +0 -0
  55. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/core/request.md +0 -0
  56. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/core/versioning.md +0 -0
  57. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/model/album.md +0 -0
  58. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/model/base.md +0 -0
  59. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/model/comment.md +0 -0
  60. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/model/login.md +0 -0
  61. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/model/lyric.md +0 -0
  62. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/model/mv.md +0 -0
  63. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/model/recommend.md +0 -0
  64. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/model/request.md +0 -0
  65. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/model/search.md +0 -0
  66. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/model/singer.md +0 -0
  67. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/model/song.md +0 -0
  68. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/model/songlist.md +0 -0
  69. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/model/top.md +0 -0
  70. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/model/user.md +0 -0
  71. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/modules/album.md +0 -0
  72. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/modules/comment.md +0 -0
  73. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/modules/login.md +0 -0
  74. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/modules/login_utils.md +0 -0
  75. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/modules/lyric.md +0 -0
  76. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/modules/mv.md +0 -0
  77. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/modules/private_message.md +0 -0
  78. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/modules/recommend.md +0 -0
  79. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/modules/search.md +0 -0
  80. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/modules/singer.md +0 -0
  81. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/modules/song.md +0 -0
  82. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/modules/songlist.md +0 -0
  83. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/modules/top.md +0 -0
  84. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/reference/modules/user.md +0 -0
  85. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/tutorial/credential.md +0 -0
  86. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/tutorial/pagination.md +0 -0
  87. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/docs/tutorial/web.md +0 -0
  88. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/examples/download_song.py +0 -0
  89. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/examples/get_all_sheets.py +0 -0
  90. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/examples/phone_login.py +0 -0
  91. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/examples/private_message.py +0 -0
  92. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/examples/qrcode_login.py +0 -0
  93. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/prek.toml +0 -0
  94. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/pyproject.toml +0 -0
  95. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/algorithms/__init__.py +0 -0
  96. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/algorithms/sign.py +0 -0
  97. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/algorithms/tripledes.py +0 -0
  98. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/core/client.py +0 -0
  99. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/core/pagination.py +0 -0
  100. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/core/request.py +0 -0
  101. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/core/versioning.py +0 -0
  102. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/__init__.py +0 -0
  103. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/_validator.py +0 -0
  104. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/album.py +0 -0
  105. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/comment.py +0 -0
  106. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/login.py +0 -0
  107. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/lyric.py +0 -0
  108. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/mv.py +0 -0
  109. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/private_message.py +0 -0
  110. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/recommend.py +0 -0
  111. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/request.py +0 -0
  112. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/search.py +0 -0
  113. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/singer.py +0 -0
  114. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/song.py +0 -0
  115. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/songlist.py +0 -0
  116. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/top.py +0 -0
  117. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/models/user.py +0 -0
  118. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/__init__.py +0 -0
  119. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/_base.py +0 -0
  120. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/album.py +0 -0
  121. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/login_utils.py +0 -0
  122. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/lyric.py +0 -0
  123. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/mv.py +0 -0
  124. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/recommend.py +0 -0
  125. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/songlist.py +0 -0
  126. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/top.py +0 -0
  127. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/modules/user.py +0 -0
  128. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/utils/__init__.py +0 -0
  129. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/utils/common.py +0 -0
  130. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/qqmusic_api/utils/mqtt.py +0 -0
  131. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/scripts/ag-1.py +0 -0
  132. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/conftest.py +0 -0
  133. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/test_album.py +0 -0
  134. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/test_comment.py +0 -0
  135. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/test_login_utils.py +0 -0
  136. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/test_lyric.py +0 -0
  137. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/test_mv.py +0 -0
  138. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/test_private_message.py +0 -0
  139. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/test_recommend.py +0 -0
  140. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/test_search.py +0 -0
  141. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/test_singer.py +0 -0
  142. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/test_song.py +0 -0
  143. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/test_songlist.py +0 -0
  144. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/test_top.py +0 -0
  145. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/tests/test_user.py +0 -0
  146. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/uv.lock +0 -0
  147. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/.gitignore +0 -0
  148. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/Dockerfile +0 -0
  149. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/accounts.example.toml +0 -0
  150. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/config.example.toml +0 -0
  151. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/docker-compose.yml +0 -0
  152. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/run.py +0 -0
  153. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/app.py +0 -0
  154. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/core/__init__.py +0 -0
  155. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/core/auth.py +0 -0
  156. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/core/cache.py +0 -0
  157. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/core/config.py +0 -0
  158. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/core/credential_store.py +0 -0
  159. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/core/deps.py +0 -0
  160. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/core/response.py +0 -0
  161. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/core/security.py +0 -0
  162. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/modules/__init__.py +0 -0
  163. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/modules/comment.py +0 -0
  164. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/modules/login.py +0 -0
  165. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/modules/mv.py +0 -0
  166. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/modules/singer.py +0 -0
  167. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/modules/song.py +0 -0
  168. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/modules/songlist.py +0 -0
  169. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routes/__init__.py +0 -0
  170. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routes/_helpers.py +0 -0
  171. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routes/album.py +0 -0
  172. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routes/comment.py +0 -0
  173. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routes/login.py +0 -0
  174. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routes/lyric.py +0 -0
  175. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routes/mv.py +0 -0
  176. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routes/recommend.py +0 -0
  177. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routes/search.py +0 -0
  178. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routes/singer.py +0 -0
  179. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routes/songlist.py +0 -0
  180. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routes/top.py +0 -0
  181. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routes/user.py +0 -0
  182. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routing/__init__.py +0 -0
  183. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routing/docstrings.py +0 -0
  184. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routing/executor.py +0 -0
  185. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routing/params.py +0 -0
  186. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routing/route_types.py +0 -0
  187. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/src/routing/router_factory.py +0 -0
  188. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/tests/test_web_core.py +0 -0
  189. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/tests/test_web_docstrings.py +0 -0
  190. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/tests/test_web_enums.py +0 -0
  191. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/tests/test_web_route_validation.py +0 -0
  192. {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.7}/web/tests/test_web_routes.py +0 -0
@@ -49,6 +49,5 @@
49
49
 
50
50
  ## Agent behavior
51
51
 
52
- * 每次回答都以 `皇上启奏:` 开头。
53
52
  * **核心规约**:遵循 `docs/contributing.md` 中的详细规约。**在执行任务前,必须完整阅读该指南以确保合规。**
54
53
  * 仅在明确要求时,才能 `git commit` 或 `git push`。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qqmusic-api-python
3
- Version: 0.6.6
3
+ Version: 0.6.7
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/
@@ -0,0 +1,428 @@
1
+ # API 编写指南
2
+
3
+ `qqmusic_api` 采用 `Client + ApiModule + Request` 的结构:
4
+
5
+ * `Client` 负责网络发送、平台信息和凭证。
6
+ * `ApiModule` 负责声明接口参数,并返回可 `await` 的 `Request`。
7
+
8
+ ## 调用流程图
9
+
10
+ ### 单请求
11
+
12
+ ```text
13
+ 模块方法
14
+ -> self._build_request(...)
15
+ -> Request
16
+ -> await request
17
+ -> Client.execute(request)
18
+ -> Client.request_api(...) (根据 request.is_jce 分发改用 JCE 或 JSON 协议)
19
+ -> Client._build_result(...)
20
+ -> 返回原始 dict / TarsDict 或 Pydantic 模型
21
+ ```
22
+
23
+ ### 批量并发请求
24
+
25
+ ```text
26
+ 多个模块方法
27
+ -> self._build_request(...)
28
+ -> Request 列表
29
+ -> Client.gather(requests)
30
+ -> 按协议、平台、公共参数和凭证分组
31
+ -> 每组按 batch_size 拆分为批量请求
32
+ -> 依次调用 Client.request_api(..., lazy=True) 生成响应任务
33
+ -> 使用客户端内部的 multiplexed AsyncSession 并发执行这些任务(self._session.gather)
34
+ -> 按 req_n 解析每个响应项
35
+ -> 按输入顺序返回结果
36
+ ```
37
+
38
+ `gather` 的分组边界由 `Request._group_key` 决定。只有协议类型、显式平台、公共参数和凭证相同的请求才会合并到同一个批量请求中。
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
+
52
+
53
+ class FooApi(ApiModule):
54
+ """Foo 相关 API."""
55
+
56
+ def get_something(self, id: int):
57
+ """获取某项数据."""
58
+ return self._build_request(
59
+ module="music.foo.Svc",
60
+ method="GetSomething",
61
+ param={"id": id},
62
+ )
63
+ ```
64
+
65
+ ```python
66
+ # qqmusic_api/core/client.py
67
+ from functools import cached_property
68
+
69
+ class Client:
70
+ @cached_property
71
+ def foo(self) -> "FooApi":
72
+ from ..modules.foo import FooApi
73
+ return FooApi(self)
74
+ ```
75
+
76
+ ### 添加新的请求方法
77
+
78
+ API 方法返回 `Request` 对象,不直接发起请求。使用 `self._build_request(...)` 工厂方法构建:
79
+
80
+ ```python
81
+ def get_detail(self, song_id: int):
82
+ """获取歌曲详情."""
83
+ return self._build_request(
84
+ module="music.songDetail", # 接口所属模块
85
+ method="GetDetail", # 方法名
86
+ param={"songid": song_id}, # 业务参数
87
+ )
88
+ ```
89
+
90
+ 对于非标准 CGI 接口(如直接 GET 请求),使用 `self._request(...)`:
91
+
92
+ ```python
93
+ async def quick_search(self, keyword: str) -> dict[str, Any]:
94
+ """快速搜索 (直接返回解析后的 JSON 数据)."""
95
+ resp = await self._request(
96
+ "GET",
97
+ "https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg",
98
+ params={"key": keyword},
99
+ )
100
+ resp.raise_for_status()
101
+ return resp.json()["data"]
102
+ ```
103
+
104
+ ### `_build_request` 参数说明
105
+
106
+ | 参数 | 类型 | 说明 |
107
+ |------------------|-----------------------------|-----------------------------------------------------------|
108
+ | `module` | `str` | 接口所属模块名 |
109
+ | `method` | `str` | 方法名 |
110
+ | `param` | `dict` | 业务参数 |
111
+ | `response_model` | `type[BaseModel]` 或 `None` | 响应模型,为 None 时返回原始 dict |
112
+ | `comm` | `dict` 或 `None` | 附加的公共参数 |
113
+ | `override_comm` | `bool` | 为 True 时 `comm` 完全替代自动生成的参数;为 False 时合并 |
114
+ | `credential` | `Credential` 或 `None` | 覆盖本次请求的凭证 |
115
+ | `platform` | `Platform` 或 `None` | 覆盖本次请求的平台 |
116
+ | `is_jce` | `bool` | 是否作为 JCE (Tars) 二进制协议发送 |
117
+ | `preserve_bool` | `bool` | 是否保留布尔值原样(默认转为 0/1 整型) |
118
+ | `sign` | `bool` | 是否对请求进行签名 |
119
+ | `pager_meta` | `PagerMeta` 或 `None` | 分页元数据,提供后返回 `PaginatedRequest` |
120
+ | `refresh_meta` | `RefreshMeta` 或 `None` | 换一批元数据,提供后返回 `RefreshableRequest` |
121
+
122
+ ### `client.request` 参数说明
123
+
124
+ `client.request` 是底层 HTTP 请求方法,自动装配凭证 Cookies 和平台 User-Agent:
125
+
126
+ | 参数 | 类型 | 说明 |
127
+ |--------------|------------------------|----------------------------------------------------------------------------------:|
128
+ | `method` | `str` | HTTP 方法,如 `"GET"`、`"POST"` |
129
+ | `url` | `str` | 请求地址 |
130
+ | `credential` | `Credential` 或 `None` | 覆盖本次请求的凭证,默认使用客户端凭证 |
131
+ | `platform` | `Platform` 或 `None` | 覆盖本次请求的平台,默认使用客户端平台 |
132
+ | `lazy` | `bool` | 是否延迟发送请求(用于批量并发) |
133
+ | `**kwargs` | | 透传给底层 `niquests` 的参数(`params`、`json`、`data`、`headers`、`cookies` 等) |
134
+
135
+ !!! note
136
+
137
+ `client.request` 返回的是原始 `niquests.Response` 对象,需要手动解析响应。而 `_build_request` 返回的 `Request` 对象支持 `await`,会自动完成响应验证和模型解析。
138
+
139
+ 常见用法:
140
+
141
+ ```python
142
+ # GET 请求
143
+ resp = await client.request("GET", "https://example.com/api", params={"key": "value"})
144
+
145
+ # POST JSON
146
+ resp = await client.request("POST", "https://example.com/api", json={"key": "value"})
147
+
148
+ # POST form data
149
+ resp = await client.request("POST", "https://example.com/api", data={"key": "value"})
150
+
151
+ # 自定义 headers
152
+ resp = await client.request("GET", "https://example.com/api", headers={"X-Custom": "value"})
153
+
154
+ # 覆盖凭证
155
+ resp = await client.request("GET", "https://example.com/api", credential=my_credential)
156
+ ```
157
+
158
+ ## 响应模型
159
+
160
+ ### 基础用法
161
+
162
+ 每个响应模型都应继承 `models.request.Response`:
163
+
164
+ ```python
165
+ from pydantic import Field
166
+
167
+ from .request import Response
168
+
169
+
170
+ class MyResponse(Response):
171
+ """我的响应模型."""
172
+
173
+ name: str
174
+ count: int
175
+ ```
176
+
177
+ `Response` 基类配置了 `frozen=True`(不可变)和 `extra="ignore"`(忽略多余字段)。
178
+
179
+ ### JSONPath 字段映射
180
+
181
+ 可以通过 `Field(json_schema_extra={"jsonpath": ...})` 声明字段的 JSONPath 映射路径,自动从嵌套响应中提取数据:
182
+
183
+ ```python
184
+ class SonglistMeta(Response):
185
+ """歌单元数据示例."""
186
+
187
+ id: int = Field(json_schema_extra={"jsonpath": "$.result.tid"})
188
+ dirid: int = Field(json_schema_extra={"jsonpath": "$.result.dirId"})
189
+ name: str = Field(json_schema_extra={"jsonpath": "$.result.dirName"})
190
+ ```
191
+
192
+ 对于列表字段,使用 `[*]` 通配符:
193
+
194
+ ```python
195
+ class CommentListResponse(Response):
196
+ """评论列表响应."""
197
+
198
+ comments: list[Comment] = Field(
199
+ default_factory=list,
200
+ json_schema_extra={"jsonpath": "$.commentlist[*]"},
201
+ )
202
+ ```
203
+
204
+ ### 字段别名
205
+
206
+ Pydantic 的 `validation_alias` 支持多别名兼容:
207
+
208
+ ```python
209
+ class Singer(Response):
210
+ """歌手信息."""
211
+
212
+ id: int = Field(
213
+ default=-1,
214
+ validation_alias=AliasChoices("id", "singerID", "singerId", "SingerID"),
215
+ )
216
+ mid: str = Field(
217
+ default="",
218
+ validation_alias=AliasChoices("mid", "singerMid", "singerMID"),
219
+ )
220
+ ```
221
+
222
+ ### 需登录的接口
223
+
224
+ 需要登录的接口应通过 `_require_login` 校验凭证:
225
+
226
+ ```python
227
+ def get_vip_info(self, *, credential: Credential | None = None):
228
+ """获取 VIP 信息."""
229
+ target_credential = self._require_login(credential)
230
+ return self._build_request(
231
+ module="VipLogin.VipLoginInter",
232
+ method="vip_login_base",
233
+ param={},
234
+ credential=target_credential,
235
+ response_model=UserVipInfoResponse,
236
+ )
237
+ ```
238
+
239
+ ## 翻页与换一批
240
+
241
+ ### 连续翻页
242
+
243
+ 通过 `pager_meta` 声明连续翻页能力,返回的请求对象会暴露 `.paginate()`:
244
+
245
+ ```python
246
+ from ..core.pagination import OffsetStrategy, PagerMeta, ResponseAdapter
247
+
248
+
249
+ def get_detail(self, songlist_id: int, num: int = 10, page: int = 1):
250
+ """获取歌单详情."""
251
+ return self._build_request(
252
+ module="music.srfDissInfo.DissInfo",
253
+ method="CgiGetDiss",
254
+ param={
255
+ "disstid": songlist_id,
256
+ "song_begin": num * (page - 1),
257
+ "song_num": num,
258
+ },
259
+ response_model=GetSonglistDetailResponse,
260
+ pager_meta=PagerMeta(
261
+ strategy=OffsetStrategy(offset_key="song_begin", page_size_key="song_num"),
262
+ adapter=ResponseAdapter(
263
+ has_more_flag="hasmore",
264
+ total="total",
265
+ count=lambda response: len(response.songs),
266
+ ),
267
+ ),
268
+ )
269
+ ```
270
+
271
+ ### 换一批
272
+
273
+ 通过 `refresh_meta` 声明换一批能力,返回的请求对象会暴露 `.refresh()`:
274
+
275
+ ```python
276
+ from ..core.pagination import BatchRefreshStrategy, RefreshMeta, ResponseAdapter
277
+
278
+
279
+ def get_related_mv(self, songid: int, last_mvid: str | None = None):
280
+ """获取歌曲相关 MV."""
281
+ return self._build_request(
282
+ module="MvService.MvInfoProServer",
283
+ method="GetSongRelatedMv",
284
+ param={"songid": str(songid), "songtype": 1, "lastmvid": last_mvid or 0},
285
+ response_model=GetRelatedMvResponse,
286
+ refresh_meta=RefreshMeta(
287
+ strategy=BatchRefreshStrategy(refresh_key="lastmvid"),
288
+ adapter=ResponseAdapter(
289
+ has_more_flag="has_more",
290
+ cursor=lambda response: response.mv[-1].id if response.mv else None,
291
+ ),
292
+ ),
293
+ )
294
+ ```
295
+
296
+ ### 内置策略速查
297
+
298
+ | 策略 | 适用场景 | 关键参数 |
299
+ |----------------------------------|-------------:|---------------------------------|
300
+ | `PageStrategy` | 页码递增 | `page_key` |
301
+ | `OffsetStrategy` | 偏移量滑窗 | `offset_key` + `page_size_key` |
302
+ | `CursorStrategy` | 响应游标回写 | `cursor_key` |
303
+ | `MultiFieldContinuationStrategy` | 多字段续翻 | 自定义 `build_next_params` 函数 |
304
+ | `BatchRefreshStrategy` | 换一批 | `refresh_key` |
305
+
306
+ `pager_meta` 与 `refresh_meta` 不能同时声明。
307
+
308
+ ## JCE (Tars) 协议
309
+
310
+ 部分接口使用 JCE 二进制协议而非 JSON。通过 `is_jce=True` 启用:
311
+
312
+ ```python
313
+ def get_something(self):
314
+ """获取数据 (JCE 协议)."""
315
+ return self._build_request(
316
+ module="music.foo.Svc",
317
+ method="GetSomething",
318
+ param={0: "value"}, # JCE 使用整数 key
319
+ is_jce=True,
320
+ )
321
+ ```
322
+
323
+ JCE 协议的响应会自动解码为 `TarsDict`。
324
+
325
+ ## 请求签名
326
+
327
+ 部分接口需要对请求体进行签名。通过 `sign=True` 启用:
328
+
329
+ ```python
330
+ def get_sheet(self, mid: str):
331
+ """获取曲谱."""
332
+ return self._build_request(
333
+ module="music.mir.SheetMusicSvr",
334
+ method="GetMoreSheetMusic",
335
+ param={"songMid": mid},
336
+ sign=True,
337
+ )
338
+ ```
339
+
340
+ 签名后请求会发送到 `musics.fcg` 而非 `musicu.fcg`,并在 URL 参数中附加 `_`(时间戳)和 `sign`。
341
+
342
+ ## 公共参数 `comm`
343
+
344
+ 默认情况下,`comm` 参数由 `VersionPolicy.build_comm()` 自动生成。可以通过 `comm` 附加额外参数:
345
+
346
+ ```python
347
+ # 合并到自动生成的 comm 中(默认行为)
348
+ self._build_request(
349
+ ...,
350
+ comm={"extra_key": "value"},
351
+ )
352
+ ```
353
+
354
+ 使用 `override_comm=True` 完全替代自动生成的参数:
355
+
356
+ ```python
357
+ self._build_request(
358
+ ...,
359
+ comm={
360
+ "g_tk": 5381,
361
+ "uin": "",
362
+ "format": "json",
363
+ "inCharset": "utf-8",
364
+ "outCharset": "utf-8",
365
+ "notice": 0,
366
+ "needNewCode": 1,
367
+ },
368
+ override_comm=True,
369
+ )
370
+ ```
371
+
372
+ ## 编写测试
373
+
374
+ 测试文件放在 `tests/` 下,按模块命名(如 `test_song.py`)。
375
+
376
+ ### 基本格式
377
+
378
+ ```python
379
+ """歌曲模块测试."""
380
+
381
+ import pytest
382
+
383
+ from qqmusic_api import Client
384
+
385
+
386
+ async def test_query_song(client: Client) -> None:
387
+ """测试根据 ID 查询歌曲."""
388
+ result = await client.song.query_song(["003w2xz20QlUZt"])
389
+ assert result.tracks
390
+ assert result.tracks[0].name
391
+ ```
392
+
393
+ ### 使用 parametrize
394
+
395
+ ```python
396
+ @pytest.mark.parametrize("page", [1, 2])
397
+ async def test_general_search(client: Client, page: int) -> None:
398
+ """测试综合搜索翻页."""
399
+ result = await client.search.general_search("周杰伦", page=page)
400
+ assert result.song.items is not None
401
+ ```
402
+
403
+ ### 需要登录的测试
404
+
405
+ 使用 `authenticated_client` fixture:
406
+
407
+ ```python
408
+ async def test_get_vip_info(authenticated_client: Client) -> None:
409
+ """测试获取 VIP 信息."""
410
+ result = await authenticated_client.user.get_vip_info()
411
+ assert result.vip_flag is not None
412
+ ```
413
+
414
+ ### 测试分页
415
+
416
+ ```python
417
+ async def test_search_paginate(client: Client) -> None:
418
+ """测试搜索分页."""
419
+ pager = client.search.search_by_type("周杰伦", num=5).paginate(limit=2)
420
+
421
+ assert pager.has_more() is True
422
+ first_page = await pager.next()
423
+ assert pager.has_more() is True
424
+ second_page = await pager.next()
425
+
426
+ assert first_page.song
427
+ assert second_page.song
428
+ ```
@@ -1,4 +1,52 @@
1
1
 
2
+ ## What's Changed
3
+
4
+ ### 功能更新
5
+
6
+ * **(web)** 添加 has_sheet 路由 ([9e7a143](https://github.com/L-1124/QQMusicApi/commit/9e7a1439ff597ae43a4ef99d88ec08ee32889b9a)) by [@L-1124](https://github.com/L-1124)
7
+
8
+ ### 文档更新
9
+
10
+ * 完善快速开始文档并新增多篇核心教程 ([d6143cd](https://github.com/L-1124/QQMusicApi/commit/d6143cdc047ebeb4963b061680a21103ba3d58d4)) by [@L-1124](https://github.com/L-1124)
11
+
12
+ ### 贡献者
13
+
14
+ * @L-1124
15
+ * @github-actions[bot]
16
+
17
+ ## [[0.6.6](https://github.com/L-1124/QQMusicApi/compare/v0.6.5..v0.6.6)] - 2026-06-13
18
+
19
+ ### Bug 修复
20
+
21
+ * **(song)** 修复 get_sheet 获取无曲谱歌曲时触发异常的问题 ([f47a643](https://github.com/L-1124/QQMusicApi/commit/f47a643e956ef52a532b0017ea8365cb2537593d)) by [@L-1124](https://github.com/L-1124)
22
+ * **(web)** 修复评论适配器与凭证锁内存泄漏,优化基础设施 ([66a6cc4](https://github.com/L-1124/QQMusicApi/commit/66a6cc4901b1acd99b0c4541f88a1d8e0a9b0888)) by [@L-1124](https://github.com/L-1124)
23
+
24
+ ### Revert
25
+
26
+ * **(models)** 回退 BaseModel 额外字段处理策略为 extra='ignore' ([f6691e0](https://github.com/L-1124/QQMusicApi/commit/f6691e078b87a45f5178d7a018f9a1bd0d5e391a)) by [@L-1124](https://github.com/L-1124)
27
+
28
+ ### 功能更新
29
+
30
+ * **(client)** 支持 zzc 请求签名 ([eb4eb66](https://github.com/L-1124/QQMusicApi/commit/eb4eb66f5c20c2adbe430bbfb11140d0431d48d5)) by [@L-1124](https://github.com/L-1124)
31
+ * **(core)** 支持在使用 allow_error_codes 放行时强制解析模型 ([fac2a71](https://github.com/L-1124/QQMusicApi/commit/fac2a71bfb2f2ea700d59215a439fc65b40461f2)) by [@L-1124](https://github.com/L-1124)
32
+ * **(core)** 支持通过 override_comm 彻底替代通用请求载荷 ([72edb4f](https://github.com/L-1124/QQMusicApi/commit/72edb4f3cc96a063643f2d58b92d41722ad95e8f)) by [@L-1124](https://github.com/L-1124)
33
+ * **(song)** 添加新的曲谱相关 API和相关示例 ([9c461db](https://github.com/L-1124/QQMusicApi/commit/9c461dbdff15acf804ae047a7bc827cd6b1e7dd5)) by [@L-1124](https://github.com/L-1124)
34
+ * **(song)** 支持 HasSheetMusic 接口检查歌曲是否有曲谱 ([94723ff](https://github.com/L-1124/QQMusicApi/commit/94723ffda911f74d680f7f661e9436bba74c3612)) by [@L-1124](https://github.com/L-1124)
35
+ * **(web)** 补充 requirements.txt ([d806214](https://github.com/L-1124/QQMusicApi/commit/d80621477635f6e0ad3589116cbb97c75e31dc6c)) by [@L-1124](https://github.com/L-1124)
36
+
37
+ ### 性能优化
38
+
39
+ * **(qrc)** 优化自定义 3DES 解密性能并补充测试 ([0b808a5](https://github.com/L-1124/QQMusicApi/commit/0b808a5537643f2c3bd0ef94e5c72263ec378bb8)) by [@L-1124](https://github.com/L-1124)
40
+
41
+ ### 构建配置
42
+
43
+ * **(web)** 在 Dockerfile 运行命令中添加 --no-sync 防止容器运行期同步 dev 依赖 ([cf1fbc5](https://github.com/L-1124/QQMusicApi/commit/cf1fbc5523c83b21734d82763a89aef450825674)) by [@L-1124](https://github.com/L-1124)
44
+
45
+ ### 贡献者
46
+
47
+ * @L-1124
48
+ * @github-actions[bot]
49
+
2
50
  ## [[0.6.5](https://github.com/L-1124/QQMusicApi/compare/v0.6.4..v0.6.5)] - 2026-06-12
3
51
 
4
52
  ### Bug 修复
@@ -76,10 +76,6 @@ client = Client(credential=credential)
76
76
 
77
77
  默认的请求平台是 `android`,如果需要可以在初始化时覆盖:
78
78
 
79
- !!! note
80
-
81
- 部分 API 的请求平台是固定的,无法覆盖。
82
-
83
79
  ```python
84
80
  import asyncio
85
81
 
@@ -91,10 +87,21 @@ async def main():
91
87
  ...
92
88
 
93
89
 
94
- asyncio.run(_main())
95
-
90
+ asyncio.run(main())
96
91
  ```
97
92
 
93
+ 支持的平台:
94
+
95
+ | 平台 | `Platform` 值 | 说明 |
96
+ | ------ | -------------- | ------ |
97
+ | Android | `Platform.ANDROID` | 默认,大部分接口使用 |
98
+ | Desktop | `Platform.DESKTOP` | QQ 音乐桌面端 |
99
+ | Web | `Platform.WEB` | QQ 音乐网页端 |
100
+
101
+ !!! note
102
+
103
+ 部分接口的请求平台是固定的,传入 `platform` 参数不会生效。例如 `get_detail` 固定使用 Web 平台,`send_authcode` 固定使用 Android 平台。
104
+
98
105
  ## 设备信息
99
106
 
100
107
  可通过 `device_path` 参数指定设备信息文件的路径进行持久化存储:
@@ -0,0 +1,124 @@
1
+ # 下载歌曲
2
+
3
+ 获取歌曲文件链接需要登录凭证。基本流程:先获取 CDN 节点,再请求文件地址。
4
+
5
+ ## 基本用法
6
+
7
+ ```python
8
+ import asyncio
9
+ import random
10
+
11
+ from qqmusic_api import Client, Credential
12
+ from qqmusic_api.modules.song import SongFileInfo
13
+
14
+
15
+ async def main() -> None:
16
+ credential = Credential(musicid=123456, musickey="Q_H_L_xxx")
17
+ async with Client(credential=credential) as client:
18
+ cdn_dispatch = await client.song.get_cdn_dispatch()
19
+ cdn = random.choice(cdn_dispatch.sip)
20
+
21
+ urls = await client.song.get_song_urls(
22
+ [SongFileInfo(mid="003w2xz20QlUZt")]
23
+ )
24
+ for info in urls.data:
25
+ if info.purl:
26
+ print(f"{info.mid}: {cdn}{info.purl}")
27
+
28
+
29
+ asyncio.run(main())
30
+ ```
31
+
32
+ ## 检查下载权限
33
+
34
+ 通过 `query_song` 获取歌曲信息后,可从 `pay` 字段判断是否需要付费:
35
+
36
+ ```python
37
+ song = await client.song.query_song(["003w2xz20QlUZt"])
38
+ track = song.tracks[0]
39
+
40
+ # pay_play: 1=需要付费播放, 0=免费
41
+ # pay_down: 1=需要付费下载, 0=免费
42
+ # pay_month: 1=需要绿钻/付费包
43
+ if track.pay.pay_month == 1:
44
+ print("需要绿钻或付费包权限")
45
+ elif track.pay.pay_down == 1:
46
+ print("需要单独付费下载")
47
+ else:
48
+ print("可免费下载")
49
+ ```
50
+
51
+ 请求文件链接后,`UrlinfoItem.result` 会返回授权结果码:
52
+
53
+ | result | 含义 |
54
+ |----------|----------------------------|
55
+ | `0` | 成功,`purl` 可用 |
56
+ | `104003` | 无权限(未登录或等级不够) |
57
+ | `104004` | VKey 获取失败 |
58
+ | `104013` | 播放设备受限 |
59
+
60
+ ```python
61
+ urls = await client.song.get_song_urls([SongFileInfo(mid="003w2xz20QlUZt")])
62
+ for info in urls.data:
63
+ if info.result == 0 and info.purl:
64
+ url = cdn + info.purl
65
+ print(f"可下载: {url}")
66
+ else:
67
+ print(f"无法下载 (result={info.result})")
68
+ ```
69
+
70
+ ## 检查文件是否存在
71
+
72
+ `query_song` 返回的 `file` 字段包含各音质的文件大小,值为 `0` 表示该音质不存在:
73
+
74
+ ```python
75
+ song = await client.song.query_song(["003w2xz20QlUZt"])
76
+ track = song.tracks[0]
77
+
78
+ file = track.file
79
+ if file.size_flac > 0:
80
+ print(f"FLAC 无损可用, 大小: {file.size_flac / 1024 / 1024:.1f} MB")
81
+ if file.size_320mp3 > 0:
82
+ print(f"MP3 320k 可用, 大小: {file.size_320mp3 / 1024 / 1024:.1f} MB")
83
+ if file.size_128mp3 > 0:
84
+ print(f"MP3 128k 可用, 大小: {file.size_128mp3 / 1024 / 1024:.1f} MB")
85
+ ```
86
+
87
+ ## 同时获取多种音质
88
+
89
+ `get_song_urls` 支持在一次请求中为同一首歌请求不同音质。为每首歌构造多个 `SongFileInfo`,指定不同的 `file_type`:
90
+
91
+ ```python
92
+ from qqmusic_api.modules.song import SongFileInfo, SongFileType
93
+
94
+ song_mid = "003w2xz20QlUZt"
95
+ media_mid = "003w2xz20QlUZt" # 从 track.file.media_mid 获取
96
+
97
+ urls = await client.song.get_song_urls(
98
+ [
99
+ SongFileInfo(mid=song_mid, file_type=SongFileType.MP3_128, media_mid=media_mid),
100
+ SongFileInfo(mid=song_mid, file_type=SongFileType.FLAC, media_mid=media_mid),
101
+ SongFileInfo(mid=song_mid, file_type=SongFileType.OGG_320, media_mid=media_mid),
102
+ ]
103
+ )
104
+
105
+ for info in urls.data:
106
+ if info.result == 0 and info.purl:
107
+ print(f"{info.filename}: {cdn}{info.purl}")
108
+ ```
109
+
110
+ 常用的 `SongFileType`:
111
+
112
+ | 枚举值 | 音质 | 格式 |
113
+ |-----------|-----------|-------------------|
114
+ | `MP3_128` | 标准音质 | MP3 128k |
115
+ | `MP3_320` | HQ 高品质 | MP3 320k |
116
+ | `OGG_192` | HQ 高品质 | OGG 192k |
117
+ | `OGG_320` | HQ 高品质 | OGG 320k |
118
+ | `FLAC` | SQ 无损 | FLAC |
119
+ | `OGG_640` | SQ 无损 | OGG 640k |
120
+ | `MASTER` | 臻品母带 | FLAC 24Bit/192kHz |
121
+
122
+ !!! tip
123
+
124
+ `ACC_96`音质部分 VIP 歌曲即使未登录或无会员,也可能通过该音质获取到播放链接(试听级别)。