qqmusic-api-python 0.5.1__tar.gz → 0.5.3__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.1 → qqmusic_api_python-0.5.3}/.github/workflows/docs.yml +1 -1
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/PKG-INFO +1 -1
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/release-notes.md +33 -1
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/tutorial/pagination.md +28 -1
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/pyproject.toml +3 -0
- qqmusic_api_python-0.5.3/qqmusic_api/__init__.py +52 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/core/__init__.py +20 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/core/exceptions.py +78 -2
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/core/pagination.py +17 -1
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/core/versioning.py +1 -1
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/album.py +7 -1
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/base.py +3 -3
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/request.py +5 -1
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/user.py +1 -1
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/login.py +253 -103
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/login_utils.py +3 -7
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/recommend.py +29 -9
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/search.py +22 -10
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/singer.py +18 -18
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/song.py +4 -3
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/user.py +1 -1
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/utils/common.py +2 -1
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/tests/test_comment.py +11 -5
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/tests/test_login.py +54 -4
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/tests/test_login_utils.py +1 -1
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/tests/test_recommend.py +2 -2
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/tests/test_search.py +15 -12
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/tests/test_singer.py +9 -9
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/tests/test_song.py +2 -2
- qqmusic_api_python-0.5.1/qqmusic_api/__init__.py +0 -32
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.agents/skills/pydantic/SKILL.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.agents/skills/python-standards/SKILL.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.agents/skills/tarsio/SKILL.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.agents/skills/tarsio/references/api-reference.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.agents/skills/uv-package-manager/SKILL.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.github/renovate.json +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.github/workflows/checking.yaml +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.github/workflows/release.yml +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.github/workflows/testing.yml +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.gitignore +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/.markdownlint-cli2.yaml +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/AGENTS.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/LICENSE +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/README.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/assets/qq-music.svg +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/cliff.toml +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/coding.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/contributing.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/index.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/core/client.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/core/exception.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/core/pagination.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/core/request.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/core/versioning.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/model/album.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/model/base.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/model/comment.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/model/login.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/model/lyric.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/model/mv.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/model/recommend.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/model/request.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/model/search.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/model/singer.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/model/song.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/model/songlist.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/model/top.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/model/user.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/modules/album.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/modules/comment.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/modules/login.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/modules/login_utils.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/modules/lyric.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/modules/mv.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/modules/recommend.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/modules/search.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/modules/singer.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/modules/song.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/modules/songlist.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/modules/top.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/reference/modules/user.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/tutorial/client.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/tutorial/credential.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/docs/tutorial/start.md +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/examples/download_song.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/examples/phone_login.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/examples/qrcode_login.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/prek.toml +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/algorithms/__init__.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/algorithms/sign.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/algorithms/tripledes.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/core/client.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/core/request.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/__init__.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/comment.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/login.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/lyric.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/mv.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/recommend.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/search.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/singer.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/song.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/songlist.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/models/top.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/__init__.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/_base.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/album.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/comment.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/lyric.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/mv.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/songlist.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/modules/top.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/utils/__init__.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/utils/device.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/utils/mqtt.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/qqmusic_api/utils/qimei.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/scripts/ag-1.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/tests/conftest.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/tests/test_album.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/tests/test_lyric.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/tests/test_mv.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/tests/test_songlist.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/tests/test_top.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/tests/test_user.py +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/uv.lock +0 -0
- {qqmusic_api_python-0.5.1 → qqmusic_api_python-0.5.3}/zensical.toml +0 -0
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
|
|
2
|
-
##
|
|
2
|
+
## [[0.5.2](https://github.com/L-1124/QQMusicApi/compare/v0.5.1..v0.5.2)] - 2026-04-18
|
|
3
|
+
|
|
4
|
+
### Bug 修复
|
|
5
|
+
|
|
6
|
+
* `UserFavSonglistResponse`jsonpath错误 ([2c54ebb](https://github.com/L-1124/QQMusicApi/commit/2c54ebb8d21a8ba02bb5b865a7ff0aa9aadcbc0d)) by [@L-1124](https://github.com/L-1124)
|
|
7
|
+
* `GetAlbumSongResponse`上游返回单个歌曲信息报错 ([adbdc40](https://github.com/L-1124/QQMusicApi/commit/adbdc4069f964125ca2228d36014f64893f3e69a)) by [@L-1124](https://github.com/L-1124)
|
|
8
|
+
|
|
9
|
+
### 功能更新
|
|
10
|
+
|
|
11
|
+
* **(core)** 为 ResponsePager 添加 next 与 has_more ([0a63a26](https://github.com/L-1124/QQMusicApi/commit/0a63a264433bf37b5854064aa8ef95edba97adab)) by [@L-1124](https://github.com/L-1124)
|
|
12
|
+
|
|
13
|
+
### 文档更新
|
|
14
|
+
|
|
15
|
+
* **(pagination)** 修正分页示例错误 ([c310450](https://github.com/L-1124/QQMusicApi/commit/c31045021034ce130136a17b458091c456998d53)) by [@L-1124](https://github.com/L-1124)
|
|
16
|
+
|
|
17
|
+
### 贡献者
|
|
18
|
+
|
|
19
|
+
* @L-1124
|
|
20
|
+
* @renovate[bot] [#240](https://github.com/L-1124/QQMusicApi/pull/240)
|
|
21
|
+
* @github-actions[bot]
|
|
22
|
+
|
|
23
|
+
## [[0.5.1](https://github.com/L-1124/QQMusicApi/compare/v0.5.0..v0.5.1)] - 2026-04-13
|
|
24
|
+
|
|
25
|
+
### 功能更新
|
|
26
|
+
|
|
27
|
+
* 获取歌曲文件支持传入歌曲类型 ([a2ad367](https://github.com/L-1124/QQMusicApi/commit/a2ad3675ac56a501f8e08e761e12c68bf6155a98)) by [@L-1124](https://github.com/L-1124)
|
|
28
|
+
* [**breaking**] 重构歌曲文件获取逻辑,支持获取特殊类型的歌曲文件 ([5e31a48](https://github.com/L-1124/QQMusicApi/commit/5e31a4888cf3d4818b3cd59861ffc4d883c4ca1a)) by [@L-1124](https://github.com/L-1124)
|
|
29
|
+
* 接口请求支持更方便的下一页、换一批请求 ([98693f8](https://github.com/L-1124/QQMusicApi/commit/98693f86995df7a62ca705a46643afffb3306e4a)) by [@L-1124](https://github.com/L-1124) in [#235](https://github.com/L-1124/QQMusicApi/pull/235)
|
|
30
|
+
|
|
31
|
+
### 功能重构
|
|
32
|
+
|
|
33
|
+
* **(login)** 优化 MQTT 登录链路并替换 HTTP 重试实现 ([244e000](https://github.com/L-1124/QQMusicApi/commit/244e000c08dce6beb3ab46c7d266c361515b4b20)) by [@L-1124](https://github.com/L-1124) in [#237](https://github.com/L-1124/QQMusicApi/pull/237)
|
|
3
34
|
|
|
4
35
|
### 文档更新
|
|
5
36
|
|
|
@@ -10,6 +41,7 @@
|
|
|
10
41
|
### 贡献者
|
|
11
42
|
|
|
12
43
|
* @L-1124
|
|
44
|
+
* @renovate[bot] [#238](https://github.com/L-1124/QQMusicApi/pull/238)
|
|
13
45
|
* @github-actions[bot]
|
|
14
46
|
|
|
15
47
|
## [[0.5.0](https://github.com/L-1124/QQMusicApi/compare/v0.4.1..v0.5.0)] - 2026-04-02
|
|
@@ -19,15 +19,42 @@ from qqmusic_api import Client
|
|
|
19
19
|
async def main() -> None:
|
|
20
20
|
async with Client() as client:
|
|
21
21
|
pager = client.search.search_by_type("周杰伦", num=5).paginate(limit=3)
|
|
22
|
+
page_number = 1
|
|
22
23
|
|
|
23
24
|
async for page in pager:
|
|
24
|
-
print(
|
|
25
|
+
print(f"第 {page_number} 页")
|
|
25
26
|
print(len(page.song))
|
|
27
|
+
page_number += 1
|
|
28
|
+
|
|
29
|
+
asyncio.run(main())
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Pager 手动逐页拉取
|
|
33
|
+
|
|
34
|
+
如果你希望自行控制何时请求下一页, 可以配合 `has_more()` 与 `next()` 使用。`has_more()` 只读取分页器当前状态, 不会主动发起网络请求。
|
|
35
|
+
|
|
36
|
+
> `next()` 与 `async for` 的终止行为一致: 没有更多结果时会抛出 `StopAsyncIteration`。
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import asyncio
|
|
40
|
+
|
|
41
|
+
from qqmusic_api import Client
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def main() -> None:
|
|
45
|
+
async with Client() as client:
|
|
46
|
+
pager = client.comment.get_hot_comments(102065756, page_size=5).paginate(limit=2)
|
|
47
|
+
|
|
48
|
+
while pager.has_more():
|
|
49
|
+
page = await pager.next()
|
|
50
|
+
print(len(page.comments))
|
|
26
51
|
|
|
27
52
|
|
|
28
53
|
asyncio.run(main())
|
|
29
54
|
```
|
|
30
55
|
|
|
56
|
+
在第一页尚未请求前, 只要分页器未耗尽且未达到 `limit`, `has_more()` 就会返回 `True`。当上游响应已经明确没有下一页, 或者你已经达到 `limit`, `has_more()` 会变为 `False`。
|
|
57
|
+
|
|
31
58
|
## Pager 返回值
|
|
32
59
|
|
|
33
60
|
分页器每次迭代返回的,仍然是该接口原本的响应模型。
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""QQMusic API 公开入口."""
|
|
2
|
+
|
|
3
|
+
from .core.client import Client
|
|
4
|
+
from .core.exceptions import (
|
|
5
|
+
ApiError,
|
|
6
|
+
HTTPError,
|
|
7
|
+
LoginAccountBannedError,
|
|
8
|
+
LoginAccountRestrictedError,
|
|
9
|
+
LoginApiError,
|
|
10
|
+
LoginAuthCodeError,
|
|
11
|
+
LoginAuthFailedError,
|
|
12
|
+
LoginBindRequiredError,
|
|
13
|
+
LoginCredentialExpiredError,
|
|
14
|
+
LoginDeviceLimitError,
|
|
15
|
+
LoginError,
|
|
16
|
+
LoginExpiredError,
|
|
17
|
+
LoginRateLimitedError,
|
|
18
|
+
LoginSecurityRequiredError,
|
|
19
|
+
NetworkError,
|
|
20
|
+
NotLoginError,
|
|
21
|
+
RatelimitedError,
|
|
22
|
+
SignInvalidError,
|
|
23
|
+
)
|
|
24
|
+
from .core.versioning import Platform
|
|
25
|
+
from .models.request import Credential
|
|
26
|
+
|
|
27
|
+
__version__ = "0.5.3"
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"ApiError",
|
|
31
|
+
"Client",
|
|
32
|
+
"Credential",
|
|
33
|
+
"HTTPError",
|
|
34
|
+
"LoginAccountBannedError",
|
|
35
|
+
"LoginAccountRestrictedError",
|
|
36
|
+
"LoginApiError",
|
|
37
|
+
"LoginAuthCodeError",
|
|
38
|
+
"LoginAuthFailedError",
|
|
39
|
+
"LoginBindRequiredError",
|
|
40
|
+
"LoginCredentialExpiredError",
|
|
41
|
+
"LoginDeviceLimitError",
|
|
42
|
+
"LoginError",
|
|
43
|
+
"LoginExpiredError",
|
|
44
|
+
"LoginRateLimitedError",
|
|
45
|
+
"LoginSecurityRequiredError",
|
|
46
|
+
"NetworkError",
|
|
47
|
+
"NotLoginError",
|
|
48
|
+
"Platform",
|
|
49
|
+
"RatelimitedError",
|
|
50
|
+
"SignInvalidError",
|
|
51
|
+
"__version__",
|
|
52
|
+
]
|
|
@@ -7,8 +7,18 @@ from .exceptions import (
|
|
|
7
7
|
BaseError,
|
|
8
8
|
CredentialError,
|
|
9
9
|
HTTPError,
|
|
10
|
+
LoginAccountBannedError,
|
|
11
|
+
LoginAccountRestrictedError,
|
|
12
|
+
LoginApiError,
|
|
13
|
+
LoginAuthCodeError,
|
|
14
|
+
LoginAuthFailedError,
|
|
15
|
+
LoginBindRequiredError,
|
|
16
|
+
LoginCredentialExpiredError,
|
|
17
|
+
LoginDeviceLimitError,
|
|
10
18
|
LoginError,
|
|
11
19
|
LoginExpiredError,
|
|
20
|
+
LoginRateLimitedError,
|
|
21
|
+
LoginSecurityRequiredError,
|
|
12
22
|
NetworkError,
|
|
13
23
|
NotLoginError,
|
|
14
24
|
RatelimitedError,
|
|
@@ -29,8 +39,18 @@ __all__ = [
|
|
|
29
39
|
"ClientConfig",
|
|
30
40
|
"CredentialError",
|
|
31
41
|
"HTTPError",
|
|
42
|
+
"LoginAccountBannedError",
|
|
43
|
+
"LoginAccountRestrictedError",
|
|
44
|
+
"LoginApiError",
|
|
45
|
+
"LoginAuthCodeError",
|
|
46
|
+
"LoginAuthFailedError",
|
|
47
|
+
"LoginBindRequiredError",
|
|
48
|
+
"LoginCredentialExpiredError",
|
|
49
|
+
"LoginDeviceLimitError",
|
|
32
50
|
"LoginError",
|
|
33
51
|
"LoginExpiredError",
|
|
52
|
+
"LoginRateLimitedError",
|
|
53
|
+
"LoginSecurityRequiredError",
|
|
34
54
|
"NetworkError",
|
|
35
55
|
"NotLoginError",
|
|
36
56
|
"Platform",
|
|
@@ -8,8 +8,18 @@ __all__ = [
|
|
|
8
8
|
"BaseError",
|
|
9
9
|
"CredentialError",
|
|
10
10
|
"HTTPError",
|
|
11
|
+
"LoginAccountBannedError",
|
|
12
|
+
"LoginAccountRestrictedError",
|
|
13
|
+
"LoginApiError",
|
|
14
|
+
"LoginAuthCodeError",
|
|
15
|
+
"LoginAuthFailedError",
|
|
16
|
+
"LoginBindRequiredError",
|
|
17
|
+
"LoginCredentialExpiredError",
|
|
18
|
+
"LoginDeviceLimitError",
|
|
11
19
|
"LoginError",
|
|
12
20
|
"LoginExpiredError",
|
|
21
|
+
"LoginRateLimitedError",
|
|
22
|
+
"LoginSecurityRequiredError",
|
|
13
23
|
"NetworkError",
|
|
14
24
|
"NotLoginError",
|
|
15
25
|
"RatelimitedError",
|
|
@@ -166,8 +176,74 @@ class LoginError(BaseError):
|
|
|
166
176
|
通常在扫码登录流程中断、超时或网络失败时抛出。
|
|
167
177
|
"""
|
|
168
178
|
|
|
169
|
-
def __init__(
|
|
170
|
-
|
|
179
|
+
def __init__(
|
|
180
|
+
self,
|
|
181
|
+
message: str = "登录失败",
|
|
182
|
+
cause: BaseException | None = None,
|
|
183
|
+
context: dict[str, Any] | None = None,
|
|
184
|
+
):
|
|
185
|
+
super().__init__(message, context=context, cause=cause)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class LoginApiError(LoginError):
|
|
189
|
+
"""登录接口返回的业务错误基类."""
|
|
190
|
+
|
|
191
|
+
def __init__(
|
|
192
|
+
self,
|
|
193
|
+
message: str,
|
|
194
|
+
*,
|
|
195
|
+
code: int,
|
|
196
|
+
data: dict[str, Any] | None = None,
|
|
197
|
+
action_url: str = "",
|
|
198
|
+
cause: BaseException | None = None,
|
|
199
|
+
context: dict[str, Any] | None = None,
|
|
200
|
+
):
|
|
201
|
+
merged_context = dict(context or {})
|
|
202
|
+
merged_context["code"] = code
|
|
203
|
+
if data:
|
|
204
|
+
merged_context["data"] = data
|
|
205
|
+
if action_url:
|
|
206
|
+
merged_context["url"] = action_url
|
|
207
|
+
super().__init__(message, context=merged_context, cause=cause)
|
|
208
|
+
self.code = code
|
|
209
|
+
self.data = data or {}
|
|
210
|
+
self.action_url = action_url
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class LoginCredentialExpiredError(LoginApiError):
|
|
214
|
+
"""登录刷新凭证不可用或登录态过期."""
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class LoginAuthFailedError(LoginApiError):
|
|
218
|
+
"""登录认证失败或上游票据不可用."""
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class LoginSecurityRequiredError(LoginApiError):
|
|
222
|
+
"""登录需要安全验证或人工交互."""
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class LoginAccountRestrictedError(LoginApiError):
|
|
226
|
+
"""账号登录受限."""
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class LoginAccountBannedError(LoginApiError):
|
|
230
|
+
"""账号被封禁导致无法登录."""
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class LoginBindRequiredError(LoginApiError):
|
|
234
|
+
"""登录需要完成账号绑定或设备确认."""
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class LoginDeviceLimitError(LoginApiError):
|
|
238
|
+
"""登录设备数量超限."""
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class LoginAuthCodeError(LoginApiError):
|
|
242
|
+
"""手机验证码错误或已失效."""
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class LoginRateLimitedError(LoginApiError):
|
|
246
|
+
"""登录操作触发频率限制."""
|
|
171
247
|
|
|
172
248
|
|
|
173
249
|
class RequestGroupResultMissingError(ApiError):
|
|
@@ -434,18 +434,34 @@ class ResponsePager(_BaseResponseAdvancer[RequestResultT], AsyncIterator[Request
|
|
|
434
434
|
self._limit = limit
|
|
435
435
|
self._yielded_count = 0
|
|
436
436
|
|
|
437
|
+
def _can_advance(self) -> bool:
|
|
438
|
+
"""返回当前分页器是否还能继续产出下一页."""
|
|
439
|
+
if self._next_request is None:
|
|
440
|
+
return False
|
|
441
|
+
if self._limit is None:
|
|
442
|
+
return True
|
|
443
|
+
return self._yielded_count < self._limit
|
|
444
|
+
|
|
437
445
|
def __aiter__(self) -> AsyncIterator[RequestResultT]:
|
|
438
446
|
"""返回分页器自身, 以支持 `async for` 迭代."""
|
|
439
447
|
return self
|
|
440
448
|
|
|
441
449
|
async def __anext__(self) -> RequestResultT:
|
|
442
450
|
"""获取并返回下一页响应."""
|
|
443
|
-
if
|
|
451
|
+
if not self._can_advance():
|
|
444
452
|
raise StopAsyncIteration
|
|
445
453
|
response = await self._advance()
|
|
446
454
|
self._yielded_count += 1
|
|
447
455
|
return response
|
|
448
456
|
|
|
457
|
+
async def next(self) -> RequestResultT:
|
|
458
|
+
"""获取并返回下一页响应."""
|
|
459
|
+
return await self.__anext__()
|
|
460
|
+
|
|
461
|
+
def has_more(self) -> bool:
|
|
462
|
+
"""返回当前分页器是否还能继续产出下一页."""
|
|
463
|
+
return self._can_advance()
|
|
464
|
+
|
|
449
465
|
def _get_meta(self, request: "PaginatedRequest[RequestResultT]") -> PagerMeta:
|
|
450
466
|
"""读取分页请求对应的连续翻页元数据."""
|
|
451
467
|
return request.get_pager_meta()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Album API 返回模型定义."""
|
|
2
2
|
|
|
3
|
-
from pydantic import Field
|
|
3
|
+
from pydantic import Field, field_validator
|
|
4
4
|
|
|
5
5
|
from .base import Album, Singer, Song
|
|
6
6
|
from .request import Response
|
|
@@ -70,3 +70,9 @@ class GetAlbumSongResponse(Response):
|
|
|
70
70
|
album_mid: str = Field(alias="albumMid")
|
|
71
71
|
total_num: int = Field(alias="totalNum")
|
|
72
72
|
song_list: list[Song] = Field(default_factory=list, json_schema_extra={"jsonpath": "$.songList[*].songInfo"})
|
|
73
|
+
|
|
74
|
+
@field_validator("song_list", mode="before")
|
|
75
|
+
@classmethod
|
|
76
|
+
def _coerce_song_list(cls, value: list[dict] | dict) -> list[dict]:
|
|
77
|
+
"""将上游返回的单个歌曲信息统一规整为列表."""
|
|
78
|
+
return [value] if isinstance(value, dict) else value
|
|
@@ -19,7 +19,7 @@ _PHOTO_NEW_SIZE_SEGMENTS: Final[dict[int, str]] = {
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def _normalize_cover_size(size: CoverSize) -> str:
|
|
22
|
-
"""返回
|
|
22
|
+
"""返回 `photo_new` 模板使用的尺寸片段."""
|
|
23
23
|
try:
|
|
24
24
|
return _PHOTO_NEW_SIZE_SEGMENTS[size]
|
|
25
25
|
except KeyError as exc:
|
|
@@ -27,7 +27,7 @@ def _normalize_cover_size(size: CoverSize) -> str:
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def _build_photo_new_cover_url(kind: str, mid: str, size: CoverSize) -> str:
|
|
30
|
-
"""按
|
|
30
|
+
"""按 `photo_new` 规则拼接封面链接."""
|
|
31
31
|
normalized_mid = mid.strip()
|
|
32
32
|
if not normalized_mid:
|
|
33
33
|
return ""
|
|
@@ -200,7 +200,7 @@ class SongList(Response):
|
|
|
200
200
|
picurl: str = Field(default="", validation_alias=AliasChoices("picurl", "cover", "logo", "picUrl"))
|
|
201
201
|
desc: str = Field(default="", validation_alias=AliasChoices("desc", "description"))
|
|
202
202
|
songnum: int = Field(default=0, validation_alias=AliasChoices("songnum", "songNum", "song_cnt"))
|
|
203
|
-
listennum: int
|
|
203
|
+
listennum: int = Field(default=0, validation_alias=AliasChoices("listennum", "playCnt", "play_cnt"))
|
|
204
204
|
|
|
205
205
|
|
|
206
206
|
class Song(Response):
|
|
@@ -53,6 +53,10 @@ class CommonParams(BaseModel):
|
|
|
53
53
|
format: str | None = Field(default=None)
|
|
54
54
|
in_charset: str | None = Field(default=None, alias="inCharset")
|
|
55
55
|
out_charset: str | None = Field(default=None, alias="outCharset")
|
|
56
|
+
# [Web] 通知开关
|
|
57
|
+
notice: int | None = Field(default=None)
|
|
58
|
+
# [Web] 新设备码开关
|
|
59
|
+
need_new_code: int | None = Field(default=None)
|
|
56
60
|
|
|
57
61
|
|
|
58
62
|
class Credential(BaseModel):
|
|
@@ -95,7 +99,7 @@ class Credential(BaseModel):
|
|
|
95
99
|
if not isinstance(data, dict):
|
|
96
100
|
return data
|
|
97
101
|
|
|
98
|
-
if
|
|
102
|
+
if "loginType" in data or "login_type" in data:
|
|
99
103
|
return data
|
|
100
104
|
|
|
101
105
|
musickey = data.get("musickey", "")
|
|
@@ -126,7 +126,7 @@ class UserFavSonglistResponse(Response):
|
|
|
126
126
|
total: int
|
|
127
127
|
hasmore: int
|
|
128
128
|
hide: bool
|
|
129
|
-
playlists: list[UserFavSonglistItem] = Field(json_schema_extra={"jsonpath": "$.v_list
|
|
129
|
+
playlists: list[UserFavSonglistItem] = Field(json_schema_extra={"jsonpath": "$.v_list"})
|
|
130
130
|
deleted_ids: list[int] = Field(alias="v_delTids")
|
|
131
131
|
failed_ids: list[int] = Field(alias="v_failTids")
|
|
132
132
|
|