qqmusic-api-python 0.6.6__tar.gz → 0.6.8__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.
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.gitignore +1 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/AGENTS.md +21 -1
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/PKG-INFO +6 -1
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/README.md +3 -0
- qqmusic_api_python-0.6.8/docs/coding.md +431 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/release-notes.md +54 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/tutorial/client.md +13 -6
- qqmusic_api_python-0.6.8/docs/tutorial/download.md +124 -0
- qqmusic_api_python-0.6.8/docs/tutorial/error-handling.md +69 -0
- qqmusic_api_python-0.6.8/docs/tutorial/login.md +171 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/tutorial/start.md +16 -12
- qqmusic_api_python-0.6.8/examples/upload_file.py +33 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/pyproject.toml +9 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/__init__.py +9 -1
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/core/__init__.py +8 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/core/client.py +27 -8
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/core/exceptions.py +77 -1
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/album.py +7 -7
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/base.py +29 -29
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/comment.py +56 -56
- qqmusic_api_python-0.6.8/qqmusic_api/models/helper.py +177 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/login.py +14 -2
- qqmusic_api_python-0.6.8/qqmusic_api/models/lyric.py +34 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/mv.py +2 -2
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/private_message.py +32 -30
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/recommend.py +14 -14
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/search.py +13 -13
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/singer.py +51 -51
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/song.py +49 -49
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/songlist.py +4 -4
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/top.py +29 -27
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/user.py +133 -93
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/__init__.py +2 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/_base.py +15 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/comment.py +6 -2
- qqmusic_api_python-0.6.8/qqmusic_api/modules/helper.py +84 -0
- qqmusic_api_python-0.6.8/qqmusic_api/modules/helper_utils.py +280 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/login.py +42 -7
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/private_message.py +34 -31
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/search.py +2 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/singer.py +2 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/song.py +4 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/songlist.py +8 -8
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/user.py +174 -14
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/utils/device.py +5 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/utils/qimei.py +12 -4
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/test_login.py +49 -5
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/test_lyric.py +1 -7
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/test_user.py +25 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/uv.lock +112 -0
- qqmusic_api_python-0.6.8/web/Dockerfile +44 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/README.md +10 -3
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/app.py +2 -2
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routes/song.py +2 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routing/router_factory.py +1 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/zensical.toml +8 -3
- qqmusic_api_python-0.6.6/docs/coding.md +0 -342
- qqmusic_api_python-0.6.6/qqmusic_api/models/lyric.py +0 -41
- qqmusic_api_python-0.6.6/web/Dockerfile +0 -23
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.agents/skills/pydantic/SKILL.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.agents/skills/python-standards/SKILL.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.agents/skills/tarsio/SKILL.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.agents/skills/tarsio/references/api-reference.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.agents/skills/uv-package-manager/SKILL.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.dockerignore +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.github/renovate.json +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.github/workflows/checking.yaml +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.github/workflows/docs.yml +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.github/workflows/release.yml +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.github/workflows/testing.yml +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/.markdownlint-cli2.yaml +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/CLAUDE.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/LICENSE +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/assets/qq-music.svg +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/cliff.toml +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/contributing.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/index.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/core/client.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/core/exception.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/core/pagination.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/core/request.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/core/versioning.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/model/album.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/model/base.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/model/comment.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/model/login.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/model/lyric.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/model/mv.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/model/recommend.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/model/request.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/model/search.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/model/singer.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/model/song.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/model/songlist.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/model/top.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/model/user.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/modules/album.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/modules/comment.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/modules/login.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/modules/login_utils.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/modules/lyric.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/modules/mv.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/modules/private_message.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/modules/recommend.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/modules/search.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/modules/singer.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/modules/song.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/modules/songlist.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/modules/top.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/reference/modules/user.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/tutorial/credential.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/tutorial/pagination.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/docs/tutorial/web.md +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/examples/download_song.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/examples/get_all_sheets.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/examples/phone_login.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/examples/private_message.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/examples/qrcode_login.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/prek.toml +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/algorithms/__init__.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/algorithms/sign.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/algorithms/tripledes.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/core/pagination.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/core/request.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/core/versioning.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/__init__.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/_validator.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/models/request.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/album.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/login_utils.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/lyric.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/mv.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/recommend.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/modules/top.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/utils/__init__.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/utils/common.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/qqmusic_api/utils/mqtt.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/scripts/ag-1.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/conftest.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/test_album.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/test_comment.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/test_login_utils.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/test_mv.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/test_private_message.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/test_recommend.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/test_search.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/test_singer.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/test_song.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/test_songlist.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/tests/test_top.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/.gitignore +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/accounts.example.toml +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/config.example.toml +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/docker-compose.yml +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/requirements.txt +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/run.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/core/__init__.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/core/auth.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/core/cache.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/core/config.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/core/credential_store.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/core/deps.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/core/response.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/core/security.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/modules/__init__.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/modules/comment.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/modules/login.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/modules/mv.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/modules/singer.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/modules/song.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/modules/songlist.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routes/__init__.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routes/_helpers.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routes/album.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routes/comment.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routes/login.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routes/lyric.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routes/mv.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routes/recommend.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routes/search.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routes/singer.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routes/songlist.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routes/top.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routes/user.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routing/__init__.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routing/docstrings.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routing/executor.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routing/params.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/src/routing/route_types.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/tests/test_web_core.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/tests/test_web_docstrings.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/tests/test_web_enums.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/tests/test_web_route_validation.py +0 -0
- {qqmusic_api_python-0.6.6 → qqmusic_api_python-0.6.8}/web/tests/test_web_routes.py +0 -0
|
@@ -47,8 +47,28 @@
|
|
|
47
47
|
* 新增页面必须同步更新 `zensical.toml` 的 `nav`。
|
|
48
48
|
* 文档构建工具实际使用 `zensical`,配置文件是 `zensical.toml`。
|
|
49
49
|
|
|
50
|
+
## Release workflow
|
|
51
|
+
|
|
52
|
+
1. 更新 `qqmusic_api/__init__.py` 中的 `__version__` 为待发布版本。
|
|
53
|
+
2. 提交版本号变更:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git add qqmusic_api/__init__.py
|
|
57
|
+
git commit -m "🧹 chore(release): x.x.x"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
3. 在**该提交上**打 tag 并推送:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
git tag v0.x.x
|
|
64
|
+
git push origin v0.x.x
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
4. CI 自动执行:
|
|
68
|
+
* **release.yml**:`uv build` → `uv publish`(PyPI)→ 创建 GitHub Release(git-cliff 自动生成 release body)
|
|
69
|
+
* **docs.yml**(release published 后触发):更新 `docs/release-notes.md` → 导出 `web/requirements.txt` → commit → 构建文档站 → 部署 GitHub Pages
|
|
70
|
+
|
|
50
71
|
## Agent behavior
|
|
51
72
|
|
|
52
|
-
* 每次回答都以 `皇上启奏:` 开头。
|
|
53
73
|
* **核心规约**:遵循 `docs/contributing.md` 中的详细规约。**在执行任务前,必须完整阅读该指南以确保合规。**
|
|
54
74
|
* 仅在明确要求时,才能 `git commit` 或 `git push`。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qqmusic-api-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.8
|
|
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/
|
|
@@ -37,6 +37,8 @@ Requires-Dist: paho-mqtt>=2.1.0
|
|
|
37
37
|
Requires-Dist: pydantic>=2.13.3
|
|
38
38
|
Requires-Dist: tarsio>=0.5.1
|
|
39
39
|
Requires-Dist: typing-extensions>=4.12.2
|
|
40
|
+
Provides-Extra: upload
|
|
41
|
+
Requires-Dist: cos-python-sdk-v5>=1.9.44; extra == 'upload'
|
|
40
42
|
Description-Content-Type: text/markdown
|
|
41
43
|
|
|
42
44
|
<div align="center">
|
|
@@ -56,6 +58,9 @@ Description-Content-Type: text/markdown
|
|
|
56
58
|
<a href="https://github.com/l-1124/QQMusicApi/actions/workflows/testing.yml">
|
|
57
59
|
<img src="https://github.com/l-1124/QQMusicApi/actions/workflows/testing.yml/badge.svg?branch=main" alt="Testing">
|
|
58
60
|
</a>
|
|
61
|
+
<a href="https://deepwiki.com/luren-dc/QQMusicApi">
|
|
62
|
+
<img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki">
|
|
63
|
+
</a>
|
|
59
64
|
</div>
|
|
60
65
|
|
|
61
66
|
---
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
<a href="https://github.com/l-1124/QQMusicApi/actions/workflows/testing.yml">
|
|
16
16
|
<img src="https://github.com/l-1124/QQMusicApi/actions/workflows/testing.yml/badge.svg?branch=main" alt="Testing">
|
|
17
17
|
</a>
|
|
18
|
+
<a href="https://deepwiki.com/luren-dc/QQMusicApi">
|
|
19
|
+
<img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki">
|
|
20
|
+
</a>
|
|
18
21
|
</div>
|
|
19
22
|
|
|
20
23
|
---
|
|
@@ -0,0 +1,431 @@
|
|
|
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
|
+
需要登录的接口通过 `_build_request` 的 `require_login` 参数校验凭证:
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
def get_vip_info(self, *, credential: Credential | None = None):
|
|
228
|
+
"""获取 VIP 信息."""
|
|
229
|
+
return self._build_request(
|
|
230
|
+
module="VipLogin.VipLoginInter",
|
|
231
|
+
method="vip_login_base",
|
|
232
|
+
param={},
|
|
233
|
+
credential=credential,
|
|
234
|
+
response_model=UserVipInfoResponse,
|
|
235
|
+
require_login=True,
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
> 若接口需要凭证对象的字段来构建请求参数,
|
|
240
|
+
> 仍可显式调用 `_require_login` 获取凭证对象。
|
|
241
|
+
|
|
242
|
+
## 翻页与换一批
|
|
243
|
+
|
|
244
|
+
### 连续翻页
|
|
245
|
+
|
|
246
|
+
通过 `pager_meta` 声明连续翻页能力,返回的请求对象会暴露 `.paginate()`:
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
from ..core.pagination import OffsetStrategy, PagerMeta, ResponseAdapter
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def get_detail(self, songlist_id: int, num: int = 10, page: int = 1):
|
|
253
|
+
"""获取歌单详情."""
|
|
254
|
+
return self._build_request(
|
|
255
|
+
module="music.srfDissInfo.DissInfo",
|
|
256
|
+
method="CgiGetDiss",
|
|
257
|
+
param={
|
|
258
|
+
"disstid": songlist_id,
|
|
259
|
+
"song_begin": num * (page - 1),
|
|
260
|
+
"song_num": num,
|
|
261
|
+
},
|
|
262
|
+
response_model=GetSonglistDetailResponse,
|
|
263
|
+
pager_meta=PagerMeta(
|
|
264
|
+
strategy=OffsetStrategy(offset_key="song_begin", page_size_key="song_num"),
|
|
265
|
+
adapter=ResponseAdapter(
|
|
266
|
+
has_more_flag="hasmore",
|
|
267
|
+
total="total",
|
|
268
|
+
count=lambda response: len(response.songs),
|
|
269
|
+
),
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### 换一批
|
|
275
|
+
|
|
276
|
+
通过 `refresh_meta` 声明换一批能力,返回的请求对象会暴露 `.refresh()`:
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
from ..core.pagination import BatchRefreshStrategy, RefreshMeta, ResponseAdapter
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def get_related_mv(self, songid: int, last_mvid: str | None = None):
|
|
283
|
+
"""获取歌曲相关 MV."""
|
|
284
|
+
return self._build_request(
|
|
285
|
+
module="MvService.MvInfoProServer",
|
|
286
|
+
method="GetSongRelatedMv",
|
|
287
|
+
param={"songid": str(songid), "songtype": 1, "lastmvid": last_mvid or 0},
|
|
288
|
+
response_model=GetRelatedMvResponse,
|
|
289
|
+
refresh_meta=RefreshMeta(
|
|
290
|
+
strategy=BatchRefreshStrategy(refresh_key="lastmvid"),
|
|
291
|
+
adapter=ResponseAdapter(
|
|
292
|
+
has_more_flag="has_more",
|
|
293
|
+
cursor=lambda response: response.mv[-1].id if response.mv else None,
|
|
294
|
+
),
|
|
295
|
+
),
|
|
296
|
+
)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### 内置策略速查
|
|
300
|
+
|
|
301
|
+
| 策略 | 适用场景 | 关键参数 |
|
|
302
|
+
|----------------------------------|-------------:|---------------------------------|
|
|
303
|
+
| `PageStrategy` | 页码递增 | `page_key` |
|
|
304
|
+
| `OffsetStrategy` | 偏移量滑窗 | `offset_key` + `page_size_key` |
|
|
305
|
+
| `CursorStrategy` | 响应游标回写 | `cursor_key` |
|
|
306
|
+
| `MultiFieldContinuationStrategy` | 多字段续翻 | 自定义 `build_next_params` 函数 |
|
|
307
|
+
| `BatchRefreshStrategy` | 换一批 | `refresh_key` |
|
|
308
|
+
|
|
309
|
+
`pager_meta` 与 `refresh_meta` 不能同时声明。
|
|
310
|
+
|
|
311
|
+
## JCE (Tars) 协议
|
|
312
|
+
|
|
313
|
+
部分接口使用 JCE 二进制协议而非 JSON。通过 `is_jce=True` 启用:
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
def get_something(self):
|
|
317
|
+
"""获取数据 (JCE 协议)."""
|
|
318
|
+
return self._build_request(
|
|
319
|
+
module="music.foo.Svc",
|
|
320
|
+
method="GetSomething",
|
|
321
|
+
param={0: "value"}, # JCE 使用整数 key
|
|
322
|
+
is_jce=True,
|
|
323
|
+
)
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
JCE 协议的响应会自动解码为 `TarsDict`。
|
|
327
|
+
|
|
328
|
+
## 请求签名
|
|
329
|
+
|
|
330
|
+
部分接口需要对请求体进行签名。通过 `sign=True` 启用:
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
def get_sheet(self, mid: str):
|
|
334
|
+
"""获取曲谱."""
|
|
335
|
+
return self._build_request(
|
|
336
|
+
module="music.mir.SheetMusicSvr",
|
|
337
|
+
method="GetMoreSheetMusic",
|
|
338
|
+
param={"songMid": mid},
|
|
339
|
+
sign=True,
|
|
340
|
+
)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
签名后请求会发送到 `musics.fcg` 而非 `musicu.fcg`,并在 URL 参数中附加 `_`(时间戳)和 `sign`。
|
|
344
|
+
|
|
345
|
+
## 公共参数 `comm`
|
|
346
|
+
|
|
347
|
+
默认情况下,`comm` 参数由 `VersionPolicy.build_comm()` 自动生成。可以通过 `comm` 附加额外参数:
|
|
348
|
+
|
|
349
|
+
```python
|
|
350
|
+
# 合并到自动生成的 comm 中(默认行为)
|
|
351
|
+
self._build_request(
|
|
352
|
+
...,
|
|
353
|
+
comm={"extra_key": "value"},
|
|
354
|
+
)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
使用 `override_comm=True` 完全替代自动生成的参数:
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
self._build_request(
|
|
361
|
+
...,
|
|
362
|
+
comm={
|
|
363
|
+
"g_tk": 5381,
|
|
364
|
+
"uin": "",
|
|
365
|
+
"format": "json",
|
|
366
|
+
"inCharset": "utf-8",
|
|
367
|
+
"outCharset": "utf-8",
|
|
368
|
+
"notice": 0,
|
|
369
|
+
"needNewCode": 1,
|
|
370
|
+
},
|
|
371
|
+
override_comm=True,
|
|
372
|
+
)
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## 编写测试
|
|
376
|
+
|
|
377
|
+
测试文件放在 `tests/` 下,按模块命名(如 `test_song.py`)。
|
|
378
|
+
|
|
379
|
+
### 基本格式
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
"""歌曲模块测试."""
|
|
383
|
+
|
|
384
|
+
import pytest
|
|
385
|
+
|
|
386
|
+
from qqmusic_api import Client
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
async def test_query_song(client: Client) -> None:
|
|
390
|
+
"""测试根据 ID 查询歌曲."""
|
|
391
|
+
result = await client.song.query_song(["003w2xz20QlUZt"])
|
|
392
|
+
assert result.tracks
|
|
393
|
+
assert result.tracks[0].name
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### 使用 parametrize
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
@pytest.mark.parametrize("page", [1, 2])
|
|
400
|
+
async def test_general_search(client: Client, page: int) -> None:
|
|
401
|
+
"""测试综合搜索翻页."""
|
|
402
|
+
result = await client.search.general_search("周杰伦", page=page)
|
|
403
|
+
assert result.song.items is not None
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### 需要登录的测试
|
|
407
|
+
|
|
408
|
+
使用 `authenticated_client` fixture:
|
|
409
|
+
|
|
410
|
+
```python
|
|
411
|
+
async def test_get_vip_info(authenticated_client: Client) -> None:
|
|
412
|
+
"""测试获取 VIP 信息."""
|
|
413
|
+
result = await authenticated_client.user.get_vip_info()
|
|
414
|
+
assert result.vip_flag is not None
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### 测试分页
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
async def test_search_paginate(client: Client) -> None:
|
|
421
|
+
"""测试搜索分页."""
|
|
422
|
+
pager = client.search.search_by_type("周杰伦", num=5).paginate(limit=2)
|
|
423
|
+
|
|
424
|
+
assert pager.has_more() is True
|
|
425
|
+
first_page = await pager.next()
|
|
426
|
+
assert pager.has_more() is True
|
|
427
|
+
second_page = await pager.next()
|
|
428
|
+
|
|
429
|
+
assert first_page.song
|
|
430
|
+
assert second_page.song
|
|
431
|
+
```
|
|
@@ -1,4 +1,58 @@
|
|
|
1
1
|
|
|
2
|
+
## [[0.6.7](https://github.com/L-1124/QQMusicApi/compare/v0.6.6..v0.6.7)] - 2026-06-17
|
|
3
|
+
|
|
4
|
+
### 功能更新
|
|
5
|
+
|
|
6
|
+
* **(login)** 新增登录异常子类和错误码映射 ([0ef9645](https://github.com/L-1124/QQMusicApi/commit/0ef9645806c9778cfa6214dd60fa56644568c276)) by [@jinzhongjia](https://github.com/jinzhongjia) in [#269](https://github.com/L-1124/QQMusicApi/pull/269)
|
|
7
|
+
* **(qimei)** 增加 24 小时过期重新获取逻辑 ([05aed29](https://github.com/L-1124/QQMusicApi/commit/05aed29096dff31b7d0cbb26ccc66ec50da38bdc)) by [@L-1124](https://github.com/L-1124)
|
|
8
|
+
* **(web)** 添加 has_sheet 路由 ([9e7a143](https://github.com/L-1124/QQMusicApi/commit/9e7a1439ff597ae43a4ef99d88ec08ee32889b9a)) by [@L-1124](https://github.com/L-1124)
|
|
9
|
+
|
|
10
|
+
### 文档更新
|
|
11
|
+
|
|
12
|
+
* **(coding)** 重写 API 编写指南,补充开发细节 ([9db46de](https://github.com/L-1124/QQMusicApi/commit/9db46def73ec6c2d9826c4a15bc2eb7ba9ed0f37)) by [@L-1124](https://github.com/L-1124)
|
|
13
|
+
* 为固定请求平台的 API 补充 docstring 说明 ([aba712e](https://github.com/L-1124/QQMusicApi/commit/aba712e56a7485b4184941480c05f370cd53c548)) by [@L-1124](https://github.com/L-1124)
|
|
14
|
+
* 完善快速开始文档并新增多篇核心教程 ([d6143cd](https://github.com/L-1124/QQMusicApi/commit/d6143cdc047ebeb4963b061680a21103ba3d58d4)) by [@L-1124](https://github.com/L-1124)
|
|
15
|
+
|
|
16
|
+
### 贡献者
|
|
17
|
+
|
|
18
|
+
* @L-1124
|
|
19
|
+
* @jinzhongjia [#269](https://github.com/L-1124/QQMusicApi/pull/269)
|
|
20
|
+
* @github-actions[bot]
|
|
21
|
+
* @renovate[bot]
|
|
22
|
+
|
|
23
|
+
## [[0.6.6](https://github.com/L-1124/QQMusicApi/compare/v0.6.5..v0.6.6)] - 2026-06-13
|
|
24
|
+
|
|
25
|
+
### Bug 修复
|
|
26
|
+
|
|
27
|
+
* **(song)** 修复 get_sheet 获取无曲谱歌曲时触发异常的问题 ([f47a643](https://github.com/L-1124/QQMusicApi/commit/f47a643e956ef52a532b0017ea8365cb2537593d)) by [@L-1124](https://github.com/L-1124)
|
|
28
|
+
* **(web)** 修复评论适配器与凭证锁内存泄漏,优化基础设施 ([66a6cc4](https://github.com/L-1124/QQMusicApi/commit/66a6cc4901b1acd99b0c4541f88a1d8e0a9b0888)) by [@L-1124](https://github.com/L-1124)
|
|
29
|
+
|
|
30
|
+
### Revert
|
|
31
|
+
|
|
32
|
+
* **(models)** 回退 BaseModel 额外字段处理策略为 extra='ignore' ([f6691e0](https://github.com/L-1124/QQMusicApi/commit/f6691e078b87a45f5178d7a018f9a1bd0d5e391a)) by [@L-1124](https://github.com/L-1124)
|
|
33
|
+
|
|
34
|
+
### 功能更新
|
|
35
|
+
|
|
36
|
+
* **(client)** 支持 zzc 请求签名 ([eb4eb66](https://github.com/L-1124/QQMusicApi/commit/eb4eb66f5c20c2adbe430bbfb11140d0431d48d5)) by [@L-1124](https://github.com/L-1124)
|
|
37
|
+
* **(core)** 支持在使用 allow_error_codes 放行时强制解析模型 ([fac2a71](https://github.com/L-1124/QQMusicApi/commit/fac2a71bfb2f2ea700d59215a439fc65b40461f2)) by [@L-1124](https://github.com/L-1124)
|
|
38
|
+
* **(core)** 支持通过 override_comm 彻底替代通用请求载荷 ([72edb4f](https://github.com/L-1124/QQMusicApi/commit/72edb4f3cc96a063643f2d58b92d41722ad95e8f)) by [@L-1124](https://github.com/L-1124)
|
|
39
|
+
* **(song)** 添加新的曲谱相关 API和相关示例 ([9c461db](https://github.com/L-1124/QQMusicApi/commit/9c461dbdff15acf804ae047a7bc827cd6b1e7dd5)) by [@L-1124](https://github.com/L-1124)
|
|
40
|
+
* **(song)** 支持 HasSheetMusic 接口检查歌曲是否有曲谱 ([94723ff](https://github.com/L-1124/QQMusicApi/commit/94723ffda911f74d680f7f661e9436bba74c3612)) by [@L-1124](https://github.com/L-1124)
|
|
41
|
+
* **(web)** 补充 requirements.txt ([d806214](https://github.com/L-1124/QQMusicApi/commit/d80621477635f6e0ad3589116cbb97c75e31dc6c)) by [@L-1124](https://github.com/L-1124)
|
|
42
|
+
|
|
43
|
+
### 性能优化
|
|
44
|
+
|
|
45
|
+
* **(qrc)** 优化自定义 3DES 解密性能并补充测试 ([0b808a5](https://github.com/L-1124/QQMusicApi/commit/0b808a5537643f2c3bd0ef94e5c72263ec378bb8)) by [@L-1124](https://github.com/L-1124)
|
|
46
|
+
|
|
47
|
+
### 构建配置
|
|
48
|
+
|
|
49
|
+
* **(web)** 在 Dockerfile 运行命令中添加 --no-sync 防止容器运行期同步 dev 依赖 ([cf1fbc5](https://github.com/L-1124/QQMusicApi/commit/cf1fbc5523c83b21734d82763a89aef450825674)) by [@L-1124](https://github.com/L-1124)
|
|
50
|
+
|
|
51
|
+
### 贡献者
|
|
52
|
+
|
|
53
|
+
* @L-1124
|
|
54
|
+
* @github-actions[bot]
|
|
55
|
+
|
|
2
56
|
## [[0.6.5](https://github.com/L-1124/QQMusicApi/compare/v0.6.4..v0.6.5)] - 2026-06-12
|
|
3
57
|
|
|
4
58
|
### 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(
|
|
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` 参数指定设备信息文件的路径进行持久化存储:
|