qqmusic-api-python 0.5.0__tar.gz → 0.5.2__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.5.0 → qqmusic_api_python-0.5.2}/.github/workflows/checking.yaml +5 -1
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.github/workflows/docs.yml +5 -5
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.github/workflows/release.yml +7 -3
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.gitignore +3 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/PKG-INFO +2 -4
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/README.md +0 -3
- qqmusic_api_python-0.5.2/docs/coding.md +381 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/index.md +2 -5
- qqmusic_api_python-0.5.2/docs/reference/core/pagination.md +3 -0
- qqmusic_api_python-0.5.2/docs/reference/modules/login_utils.md +3 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/release-notes.md +290 -244
- qqmusic_api_python-0.5.2/docs/tutorial/pagination.md +112 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/examples/download_song.py +2 -1
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/prek.toml +2 -2
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/pyproject.toml +5 -4
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/__init__.py +1 -1
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/core/client.py +79 -21
- qqmusic_api_python-0.5.2/qqmusic_api/core/pagination.py +496 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/core/request.py +46 -1
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/album.py +7 -1
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/base.py +1 -1
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/user.py +1 -1
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/_base.py +137 -17
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/album.py +5 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/comment.py +42 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/login.py +67 -24
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/login_utils.py +5 -10
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/recommend.py +55 -5
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/search.py +24 -1
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/singer.py +41 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/song.py +97 -46
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/songlist.py +9 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/top.py +8 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/user.py +60 -6
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/utils/__init__.py +0 -2
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/utils/mqtt.py +138 -186
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/tests/conftest.py +3 -3
- qqmusic_api_python-0.5.2/tests/test_album.py +29 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/tests/test_comment.py +27 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/tests/test_login.py +23 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/tests/test_search.py +17 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/tests/test_song.py +24 -8
- qqmusic_api_python-0.5.2/uv.lock +1502 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/zensical.toml +7 -0
- qqmusic_api_python-0.5.0/docs/coding.md +0 -237
- qqmusic_api_python-0.5.0/qqmusic_api/utils/retry.py +0 -116
- qqmusic_api_python-0.5.0/tests/test_album.py +0 -54
- qqmusic_api_python-0.5.0/uv.lock +0 -1487
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.agents/skills/pydantic/SKILL.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.agents/skills/python-standards/SKILL.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.agents/skills/tarsio/SKILL.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.agents/skills/tarsio/references/api-reference.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.agents/skills/uv-package-manager/SKILL.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.github/renovate.json +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.github/workflows/testing.yml +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/.markdownlint-cli2.yaml +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/AGENTS.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/LICENSE +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/assets/qq-music.svg +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/cliff.toml +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/contributing.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/core/client.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/core/exception.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/core/request.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/core/versioning.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/model/album.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/model/base.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/model/comment.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/model/login.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/model/lyric.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/model/mv.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/model/recommend.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/model/request.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/model/search.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/model/singer.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/model/song.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/model/songlist.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/model/top.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/model/user.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/modules/album.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/modules/comment.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/modules/login.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/modules/lyric.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/modules/mv.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/modules/recommend.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/modules/search.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/modules/singer.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/modules/song.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/modules/songlist.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/modules/top.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/reference/modules/user.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/tutorial/client.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/tutorial/credential.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/docs/tutorial/start.md +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/examples/phone_login.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/examples/qrcode_login.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/algorithms/__init__.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/algorithms/sign.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/algorithms/tripledes.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/core/__init__.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/core/exceptions.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/core/versioning.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/__init__.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/comment.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/login.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/lyric.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/mv.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/recommend.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/request.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/search.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/singer.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/song.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/songlist.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/models/top.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/__init__.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/lyric.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/modules/mv.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/utils/common.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/utils/device.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/qqmusic_api/utils/qimei.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/scripts/ag-1.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/tests/test_login_utils.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/tests/test_lyric.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/tests/test_mv.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/tests/test_recommend.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/tests/test_singer.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/tests/test_songlist.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/tests/test_top.py +0 -0
- {qqmusic_api_python-0.5.0 → qqmusic_api_python-0.5.2}/tests/test_user.py +0 -0
|
@@ -8,7 +8,11 @@ jobs:
|
|
|
8
8
|
runs-on: ubuntu-latest
|
|
9
9
|
steps:
|
|
10
10
|
- uses: actions/checkout@v6
|
|
11
|
-
- name:
|
|
11
|
+
- name: Setup uv
|
|
12
|
+
uses: astral-sh/setup-uv@v5
|
|
13
|
+
- name: Install dependencies
|
|
14
|
+
run: uv sync
|
|
15
|
+
- name: Run prek
|
|
12
16
|
uses: j178/prek-action@v2
|
|
13
17
|
with:
|
|
14
18
|
extra-args: --all-files --hook-stage pre-push
|
|
@@ -22,10 +22,10 @@ jobs:
|
|
|
22
22
|
uses: actions/checkout@v6
|
|
23
23
|
with:
|
|
24
24
|
fetch-depth: 0
|
|
25
|
-
token: ${{ secrets.
|
|
25
|
+
token: ${{ secrets.GH_TOKEN }}
|
|
26
26
|
- name: Setup Pages
|
|
27
27
|
id: pages
|
|
28
|
-
uses: actions/configure-pages@
|
|
28
|
+
uses: actions/configure-pages@v6
|
|
29
29
|
- name: Install uv
|
|
30
30
|
uses: astral-sh/setup-uv@v7
|
|
31
31
|
with:
|
|
@@ -49,11 +49,11 @@ jobs:
|
|
|
49
49
|
set +e
|
|
50
50
|
git add docs/release-notes.md
|
|
51
51
|
git commit -m "🧹 chore(release-notes): 更新 release notes"
|
|
52
|
-
git push
|
|
52
|
+
git push origin main
|
|
53
53
|
- name: Build with zensical
|
|
54
54
|
run: uv run zensical build
|
|
55
55
|
- name: Upload artifact
|
|
56
|
-
uses: actions/upload-pages-artifact@
|
|
56
|
+
uses: actions/upload-pages-artifact@v5
|
|
57
57
|
with:
|
|
58
58
|
path: ./site
|
|
59
59
|
|
|
@@ -66,4 +66,4 @@ jobs:
|
|
|
66
66
|
steps:
|
|
67
67
|
- name: Deploy to GitHub Pages
|
|
68
68
|
id: deployment
|
|
69
|
-
uses: actions/deploy-pages@
|
|
69
|
+
uses: actions/deploy-pages@v5
|
|
@@ -18,7 +18,7 @@ jobs:
|
|
|
18
18
|
- uses: actions/checkout@v6
|
|
19
19
|
with:
|
|
20
20
|
fetch-depth: 0
|
|
21
|
-
token: ${{ secrets.
|
|
21
|
+
token: ${{ secrets.GH_TOKEN }}
|
|
22
22
|
- name: Generate release body
|
|
23
23
|
id: git-cliff
|
|
24
24
|
uses: orhun/git-cliff-action@v4
|
|
@@ -33,6 +33,8 @@ jobs:
|
|
|
33
33
|
id-token: write
|
|
34
34
|
steps:
|
|
35
35
|
- uses: actions/checkout@v6
|
|
36
|
+
with:
|
|
37
|
+
token: ${{ secrets.GH_TOKEN }}
|
|
36
38
|
- name: Install uv
|
|
37
39
|
uses: astral-sh/setup-uv@v7
|
|
38
40
|
with:
|
|
@@ -50,9 +52,11 @@ jobs:
|
|
|
50
52
|
steps:
|
|
51
53
|
- name: Checkout
|
|
52
54
|
uses: actions/checkout@v6
|
|
55
|
+
with:
|
|
56
|
+
token: ${{ secrets.GH_TOKEN }}
|
|
53
57
|
- name: Release
|
|
54
|
-
uses: softprops/action-gh-release@
|
|
58
|
+
uses: softprops/action-gh-release@v3
|
|
55
59
|
with:
|
|
56
60
|
body: ${{ needs.generate-release-body.outputs.release_body }}
|
|
57
61
|
tag_name: ${{ github.ref_name }}
|
|
58
|
-
token: ${{ secrets.
|
|
62
|
+
token: ${{ secrets.GH_TOKEN }}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qqmusic-api-python
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
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/
|
|
@@ -30,6 +30,7 @@ Classifier: Typing :: Typed
|
|
|
30
30
|
Requires-Python: >=3.10
|
|
31
31
|
Requires-Dist: anyio>=4.12.1
|
|
32
32
|
Requires-Dist: cryptography>=46.0.3
|
|
33
|
+
Requires-Dist: httpx-retries>=0.4.6
|
|
33
34
|
Requires-Dist: httpx[http2]>=0.27.0
|
|
34
35
|
Requires-Dist: jsonpath-ng>=1.8.0
|
|
35
36
|
Requires-Dist: orjson>=3.10.15
|
|
@@ -61,7 +62,6 @@ Description-Content-Type: text/markdown
|
|
|
61
62
|
---
|
|
62
63
|
|
|
63
64
|
> [!IMPORTANT]
|
|
64
|
-
> 请在使用前阅读仓库中的 `LICENSE` 文件, **并遵守相关平台条款与版权法律**。
|
|
65
65
|
> **音乐平台不易, 请尊重版权, 支持正版**。
|
|
66
66
|
|
|
67
67
|
---
|
|
@@ -84,8 +84,6 @@ Description-Content-Type: text/markdown
|
|
|
84
84
|
|
|
85
85
|
本项目当前采用 **[GNU General Public License v3.0 or later](https://github.com/l-1124/QQMusicApi/blob/main/LICENSE)**。
|
|
86
86
|
|
|
87
|
-
`v0.5.0` 及后续版本均按 GPL 条款分发, 具体内容以仓库中的 `LICENSE` 文件为准。
|
|
88
|
-
|
|
89
87
|
本项目仅用于对技术可行性的探索及研究,请勿将其用于任何商业用途或侵犯版权的行为。
|
|
90
88
|
|
|
91
89
|
## ⚠️ 免责声明
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
22
|
> [!IMPORTANT]
|
|
23
|
-
> 请在使用前阅读仓库中的 `LICENSE` 文件, **并遵守相关平台条款与版权法律**。
|
|
24
23
|
> **音乐平台不易, 请尊重版权, 支持正版**。
|
|
25
24
|
|
|
26
25
|
---
|
|
@@ -43,8 +42,6 @@
|
|
|
43
42
|
|
|
44
43
|
本项目当前采用 **[GNU General Public License v3.0 or later](https://github.com/l-1124/QQMusicApi/blob/main/LICENSE)**。
|
|
45
44
|
|
|
46
|
-
`v0.5.0` 及后续版本均按 GPL 条款分发, 具体内容以仓库中的 `LICENSE` 文件为准。
|
|
47
|
-
|
|
48
45
|
本项目仅用于对技术可行性的探索及研究,请勿将其用于任何商业用途或侵犯版权的行为。
|
|
49
46
|
|
|
50
47
|
## ⚠️ 免责声明
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
# API 编写指南
|
|
2
|
+
|
|
3
|
+
`qqmusic_api` 采用 `Client + ApiModule + Request` 的结构:
|
|
4
|
+
|
|
5
|
+
* `Client` 负责网络发送、平台信息、凭证和批量调度。
|
|
6
|
+
* `ApiModule` 负责声明接口参数,并返回可 `await` 的 `Request`。
|
|
7
|
+
* `RequestGroup` 用于批量执行多个 `Request`。
|
|
8
|
+
|
|
9
|
+
## 调用流程图
|
|
10
|
+
|
|
11
|
+
### 单请求
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
模块方法
|
|
15
|
+
-> self._build_request(...)
|
|
16
|
+
-> Request
|
|
17
|
+
-> await request
|
|
18
|
+
-> Client.execute(request)
|
|
19
|
+
-> 根据 request.is_jce 分发:
|
|
20
|
+
-> Client.request_jce(...)
|
|
21
|
+
-> 或 Client.request_musicu(...)
|
|
22
|
+
-> Client._build_result(...)
|
|
23
|
+
-> 返回原始 dict / TarsDict 或 Pydantic 模型
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 批量请求
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
多个模块方法
|
|
30
|
+
-> 多个 Request
|
|
31
|
+
-> Client.request_group()
|
|
32
|
+
-> RequestGroup.add(...) / extend(...)
|
|
33
|
+
-> 按 is_jce / platform / comm / credential 分组
|
|
34
|
+
-> 按 batch_size 分批
|
|
35
|
+
-> 并发发送批次
|
|
36
|
+
-> 两种消费方式:
|
|
37
|
+
-> execute_iter():
|
|
38
|
+
返回无序流式 RequestGroupResult
|
|
39
|
+
字段包括 index / success / data / error
|
|
40
|
+
-> execute():
|
|
41
|
+
返回按添加顺序回填的 list[Any | Exception]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 编写新的 API
|
|
45
|
+
|
|
46
|
+
API 按功能拆分在 `qqmusic_api/modules/` 下,添加新的 API 只需在对应的模块中添加请求方法即可。
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from typing import Any
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SongApi(ApiModule):
|
|
53
|
+
"""歌曲相关 API 模块."""
|
|
54
|
+
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
def get_detail(self, song_id: int):
|
|
58
|
+
"""获取歌曲详情."""
|
|
59
|
+
return self._build_request(
|
|
60
|
+
module="music.songDetail",
|
|
61
|
+
method="GetDetail",
|
|
62
|
+
param={"songid": song_id},
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
class SearchApi(ApiModule):
|
|
66
|
+
"""搜索相关 API 模块."""
|
|
67
|
+
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
async def quick_search(self, keyword: str) -> dict[str, Any]:
|
|
71
|
+
"""快速搜索 (直接返回解析后的 JSON 数据).
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
keyword: 关键词.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
dict[str, Any]: 搜索结果字典.
|
|
78
|
+
"""
|
|
79
|
+
resp = await self._client.fetch(
|
|
80
|
+
"GET",
|
|
81
|
+
"https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg",
|
|
82
|
+
params={"key": keyword},
|
|
83
|
+
)
|
|
84
|
+
resp.raise_for_status()
|
|
85
|
+
return resp.json()["data"]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `Credential` 和 `Platform` 参数
|
|
89
|
+
|
|
90
|
+
`_build_request` 可以接受 `credential` 和 `platform` 参数,默认会继承当前 `Client` 的设置。
|
|
91
|
+
通常情况下,模块方法不需要暴露这些参数,除非需要支持覆盖凭证或平台。
|
|
92
|
+
不同的 `Platform` 会影响接口返回的数据内容和格式,是否需要登录。
|
|
93
|
+
部分接口的 `Platform` 是固定的。
|
|
94
|
+
|
|
95
|
+
### 响应模型 `response_model`
|
|
96
|
+
|
|
97
|
+
每个响应模型都应继承 `.models.request.Response`。
|
|
98
|
+
可以通过 `Field(json_schema_extra={"jsonpath": ...})` 声明字段的 JSONPath 映射路径,自动从嵌套响应中提取数据,以减少嵌套层级。
|
|
99
|
+
|
|
100
|
+
```py
|
|
101
|
+
from pydantic import Field
|
|
102
|
+
|
|
103
|
+
from .request import Response
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class SonglistMeta(Response):
|
|
107
|
+
"""歌单元数据示例."""
|
|
108
|
+
|
|
109
|
+
id: int = Field(json_schema_extra={"jsonpath": "$.result.tid"})
|
|
110
|
+
dirid: int = Field(json_schema_extra={"jsonpath": "$.result.dirId"})
|
|
111
|
+
name: str = Field(json_schema_extra={"jsonpath": "$.result.dirName"})
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class MyApi(ApiModule):
|
|
115
|
+
"""带 JSONPath 响应模型的示例模块."""
|
|
116
|
+
|
|
117
|
+
def get_songlist_meta(self, disstid: int):
|
|
118
|
+
"""获取歌单元数据."""
|
|
119
|
+
return self._build_request(
|
|
120
|
+
module="music.srfDissInfo.aiDissInfo",
|
|
121
|
+
method="uniform_get_Dissinfo",
|
|
122
|
+
param={"disstid": disstid},
|
|
123
|
+
response_model=SonglistMeta,
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 声明连续翻页与换一批能力
|
|
128
|
+
|
|
129
|
+
当一个接口支持连续翻页时,应在模块方法中通过 `_build_request(..., pager_meta=...)` 显式声明连续翻页能力。声明后,该方法返回的请求对象才会暴露 `.paginate()`。
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from ..core.pagination import OffsetStrategy, PagerMeta, ResponseAdapter
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class SonglistApi(ApiModule):
|
|
136
|
+
"""歌单相关 API."""
|
|
137
|
+
|
|
138
|
+
def get_detail(self, songlist_id: int, num: int = 10, page: int = 1):
|
|
139
|
+
"""获取歌单详情."""
|
|
140
|
+
return self._build_request(
|
|
141
|
+
module="music.srfDissInfo.DissInfo",
|
|
142
|
+
method="CgiGetDiss",
|
|
143
|
+
param={
|
|
144
|
+
"disstid": songlist_id,
|
|
145
|
+
"song_begin": num * (page - 1),
|
|
146
|
+
"song_num": num,
|
|
147
|
+
},
|
|
148
|
+
response_model=GetSonglistDetailResponse,
|
|
149
|
+
pager_meta=PagerMeta(
|
|
150
|
+
strategy=OffsetStrategy(offset_key="song_begin", page_size_key="song_num"),
|
|
151
|
+
adapter=ResponseAdapter(
|
|
152
|
+
has_more_flag="hasmore",
|
|
153
|
+
total="total",
|
|
154
|
+
count=lambda response: len(response.songs),
|
|
155
|
+
),
|
|
156
|
+
),
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
当一个接口支持“换一批”时,应通过 `_build_request(..., refresh_meta=...)` 声明换一批能力。声明后,该方法返回的请求对象会暴露 `.refresh()`,并返回 `ResponseRefresher`。
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from ..core.pagination import BatchRefreshStrategy, RefreshMeta, ResponseAdapter
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class SongApi(ApiModule):
|
|
167
|
+
"""歌曲相关 API."""
|
|
168
|
+
|
|
169
|
+
def get_related_mv(self, songid: int, last_mvid: str | None = None):
|
|
170
|
+
"""获取歌曲相关 MV."""
|
|
171
|
+
return self._build_request(
|
|
172
|
+
module="MvService.MvInfoProServer",
|
|
173
|
+
method="GetSongRelatedMv",
|
|
174
|
+
param={"songid": str(songid), "songtype": 1, "lastmvid": last_mvid or 0},
|
|
175
|
+
response_model=GetRelatedMvResponse,
|
|
176
|
+
refresh_meta=RefreshMeta(
|
|
177
|
+
strategy=BatchRefreshStrategy(refresh_key="lastmvid"),
|
|
178
|
+
adapter=ResponseAdapter(
|
|
179
|
+
has_more_flag="has_more",
|
|
180
|
+
cursor=lambda response: response.mv[-1].id if response.mv else None,
|
|
181
|
+
),
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 内置连续翻页策略
|
|
187
|
+
|
|
188
|
+
#### `PageStrategy`
|
|
189
|
+
|
|
190
|
+
适用于请求参数里有明确页码字段,且下一页只需要把该字段加一的接口。
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from ..core.pagination import PageStrategy, PagerMeta, ResponseAdapter
|
|
194
|
+
|
|
195
|
+
pager_meta = PagerMeta(
|
|
196
|
+
strategy=PageStrategy(page_key="PageNum", page_size=num, start_page=page - 1),
|
|
197
|
+
adapter=ResponseAdapter(has_more_flag="has_more"),
|
|
198
|
+
)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### `OffsetStrategy`
|
|
202
|
+
|
|
203
|
+
适用于请求参数里有 `offset`、`begin`、`song_begin` 这类偏移量字段的接口。
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from ..core.pagination import OffsetStrategy, PagerMeta, ResponseAdapter
|
|
207
|
+
|
|
208
|
+
pager_meta = PagerMeta(
|
|
209
|
+
strategy=OffsetStrategy(offset_key="song_begin", page_size_key="song_num"),
|
|
210
|
+
adapter=ResponseAdapter(
|
|
211
|
+
has_more_flag="hasmore",
|
|
212
|
+
total="total",
|
|
213
|
+
count=lambda response: len(response.songs),
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
如果上游尾页可能返回少量结果或重叠窗口,应优先提供 `count`。
|
|
219
|
+
|
|
220
|
+
#### `CursorStrategy`
|
|
221
|
+
|
|
222
|
+
适用于响应里能直接拿到下一页游标,并且下一次请求只需要回写这一个字段的接口。
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
from ..core.pagination import CursorStrategy, PagerMeta, ResponseAdapter
|
|
226
|
+
|
|
227
|
+
pager_meta = PagerMeta(
|
|
228
|
+
strategy=CursorStrategy(cursor_key="lastmvid"),
|
|
229
|
+
adapter=ResponseAdapter(
|
|
230
|
+
has_more_flag="has_more",
|
|
231
|
+
cursor=lambda response: response.mv[-1].id if response.mv else None,
|
|
232
|
+
),
|
|
233
|
+
)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
#### `MultiFieldContinuationStrategy`
|
|
237
|
+
|
|
238
|
+
适用于下一页请求需要同时更新多个字段的接口,例如页码加额外上下文。
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from ..core.pagination import MultiFieldContinuationStrategy, PagerMeta, ResponseAdapter
|
|
242
|
+
|
|
243
|
+
pager_meta = PagerMeta(
|
|
244
|
+
strategy=MultiFieldContinuationStrategy(
|
|
245
|
+
lambda params, response, adapter: {
|
|
246
|
+
**params,
|
|
247
|
+
"page_id": response.nextpage,
|
|
248
|
+
"page_start": adapter.get_cursor(response),
|
|
249
|
+
},
|
|
250
|
+
context_name="general_search",
|
|
251
|
+
),
|
|
252
|
+
adapter=ResponseAdapter(
|
|
253
|
+
has_more_flag=lambda response: response.nextpage != -1,
|
|
254
|
+
cursor="nextpage_start",
|
|
255
|
+
),
|
|
256
|
+
)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### `BatchRefreshStrategy`
|
|
260
|
+
|
|
261
|
+
适用于“换一批”接口。它不会把结果视为同一个连续窗口,而是根据上一批响应提取新的刷新参数,再请求下一批候选结果。
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
from ..core.pagination import BatchRefreshStrategy, RefreshMeta, ResponseAdapter
|
|
265
|
+
|
|
266
|
+
refresh_meta = RefreshMeta(
|
|
267
|
+
strategy=BatchRefreshStrategy(refresh_key="vecPlaylist"),
|
|
268
|
+
adapter=ResponseAdapter(
|
|
269
|
+
has_more_flag="has_more",
|
|
270
|
+
cursor=lambda response: [playlist.id for playlist in response.songlist] if response.songlist else None,
|
|
271
|
+
),
|
|
272
|
+
)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### `ResponseAdapter`
|
|
276
|
+
|
|
277
|
+
`ResponseAdapter` 用于从响应中提取分页决策所需信息。常见字段包括:
|
|
278
|
+
|
|
279
|
+
* `has_more_flag`: 显式是否还有下一页
|
|
280
|
+
* `total`: 总量
|
|
281
|
+
* `cursor`: 下一页游标
|
|
282
|
+
* `count`: 当前页实际返回数量
|
|
283
|
+
|
|
284
|
+
对偏移量分页,优先提供 `count`,因为上游尾页可能返回少于请求数量的结果,甚至返回重叠窗口;仅依赖请求页大小会导致尾页重复获取。
|
|
285
|
+
|
|
286
|
+
`ResponseAdapter` 的每个字段都用于告诉分页器“应该从哪里读取分页信号”。常见写法如下。
|
|
287
|
+
|
|
288
|
+
#### 只依赖显式 `has_more`
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
adapter = ResponseAdapter(has_more_flag="has_more")
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### 使用总量判断是否还有下一页
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
adapter = ResponseAdapter(total="total_num")
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### 从响应中提取下一页游标
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
adapter = ResponseAdapter(cursor="nextpage_start")
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
也可以在字段需要转换时使用函数:
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
adapter = ResponseAdapter(
|
|
310
|
+
cursor=lambda response: response.mv[-1].id if response.mv else None,
|
|
311
|
+
)
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### 为偏移量分页提供当前页实际数量
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
adapter = ResponseAdapter(
|
|
318
|
+
has_more_flag="hasmore",
|
|
319
|
+
total="total",
|
|
320
|
+
count=lambda response: len(response.songs),
|
|
321
|
+
)
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
如果接口需要多个信号,也可以组合使用:
|
|
325
|
+
|
|
326
|
+
```python
|
|
327
|
+
adapter = ResponseAdapter(
|
|
328
|
+
has_more_flag="has_more",
|
|
329
|
+
total="total",
|
|
330
|
+
cursor="nextpage_start",
|
|
331
|
+
count=lambda response: len(response.items),
|
|
332
|
+
)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## 在 `Client` 中注册模块
|
|
336
|
+
|
|
337
|
+
新增模块后,在 `Client` 中注册该模块属性:
|
|
338
|
+
|
|
339
|
+
```python
|
|
340
|
+
class Client:
|
|
341
|
+
@property
|
|
342
|
+
def my_api(self) -> "MyApi":
|
|
343
|
+
from ..modules.my_api import MyApi
|
|
344
|
+
|
|
345
|
+
return MyApi(self)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## 批量请求 `RequestGroup`
|
|
349
|
+
|
|
350
|
+
使用 `Client.request_group()` 可以批量提交请求。`RequestGroup` 会自动按 `platform`、`credential`、`comm` 和 `is_jce` 分组,并按 `batch_size` 分批发送。
|
|
351
|
+
|
|
352
|
+
`execute()` 会返回与添加顺序一致的完整结果列表:
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
from qqmusic_api import Client
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
async def batch_query(song_ids: list[int]):
|
|
359
|
+
async with Client() as client:
|
|
360
|
+
group = client.request_group()
|
|
361
|
+
for song_id in song_ids:
|
|
362
|
+
group.add(client.song.get_detail(song_id))
|
|
363
|
+
|
|
364
|
+
return await group.execute()
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
`execute_iter()` 会按完成顺序流式返回 [`RequestGroupResult`][core.request.RequestGroupResult],不保证与添加顺序一致:
|
|
368
|
+
|
|
369
|
+
```python
|
|
370
|
+
from qqmusic_api import Client
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
async def batch_query_stream(song_ids: list[int]):
|
|
374
|
+
async with Client() as client:
|
|
375
|
+
group = client.request_group(batch_size=1, max_inflight_batches=4)
|
|
376
|
+
for song_id in song_ids:
|
|
377
|
+
group.add(client.song.get_detail(song_id))
|
|
378
|
+
|
|
379
|
+
async for result in group.execute_iter():
|
|
380
|
+
print(result.index, result.module, result.method, result.success)
|
|
381
|
+
```
|
|
@@ -27,7 +27,6 @@ hide: navigation
|
|
|
27
27
|
|
|
28
28
|
!!! note 重要提示
|
|
29
29
|
|
|
30
|
-
请在使用前阅读仓库中的 `LICENSE` 文件, 并**遵守相关平台条款与版权法律**。
|
|
31
30
|
**音乐平台不易, 请尊重版权, 支持正版**。
|
|
32
31
|
|
|
33
32
|
---
|
|
@@ -59,8 +58,8 @@ from qqmusic_api import Client
|
|
|
59
58
|
async def main():
|
|
60
59
|
"""演示基础调用."""
|
|
61
60
|
async with Client() as client:
|
|
62
|
-
result = await client.search.search_by_type("周杰伦")
|
|
63
|
-
print(f"
|
|
61
|
+
result = await client.search.search_by_type(keyword="周杰伦", num=5)
|
|
62
|
+
print(f"单曲结果数量: {len(result.song)}")
|
|
64
63
|
|
|
65
64
|
|
|
66
65
|
asyncio.run(main())
|
|
@@ -70,8 +69,6 @@ asyncio.run(main())
|
|
|
70
69
|
|
|
71
70
|
本项目当前采用 **[GNU General Public License v3.0 or later](https://github.com/l-1124/QQMusicApi/blob/main/LICENSE)**。
|
|
72
71
|
|
|
73
|
-
`v0.5.0` 及后续版本均按 GPL 条款分发, 具体内容以仓库中的 `LICENSE` 文件为准。
|
|
74
|
-
|
|
75
72
|
本项目仅用于对技术可行性的探索及研究,请勿将其用于任何商业用途或侵犯版权的行为。
|
|
76
73
|
|
|
77
74
|
## ⚠️ 免责声明
|