qqmusic-api-python 0.3.5__tar.gz → 0.4.0__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.3.5 → qqmusic_api_python-0.4.0}/.gitignore +2 -1
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/PKG-INFO +16 -15
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/README.md +12 -12
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/pyproject.toml +6 -6
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/__init__.py +9 -1
- qqmusic_api_python-0.4.0/qqmusic_api/comment.py +147 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/login.py +119 -7
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/mv.py +1 -1
- qqmusic_api_python-0.4.0/qqmusic_api/recommend.py +52 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/search.py +4 -4
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/singer.py +1 -1
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/song.py +6 -10
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/songlist.py +5 -5
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/user.py +12 -12
- qqmusic_api_python-0.4.0/qqmusic_api/utils/mqtt.py +347 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/utils/network.py +20 -69
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/utils/session.py +0 -30
- qqmusic_api_python-0.4.0/qqmusic_api/utils/sign.py +36 -0
- qqmusic_api_python-0.4.0/tests/test_comment.py +38 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/tests/test_login.py +10 -2
- qqmusic_api_python-0.4.0/tests/test_recommend.py +25 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/tests/test_song.py +3 -1
- qqmusic_api_python-0.3.5/qqmusic_api/comment.py +0 -91
- qqmusic_api_python-0.3.5/qqmusic_api/utils/sign.py +0 -68
- qqmusic_api_python-0.3.5/tests/test_comments.py +0 -15
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/LICENSE +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/album.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/exceptions/__init__.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/exceptions/api_exception.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/lyric.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/top.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/utils/__init__.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/utils/common.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/utils/credential.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/utils/device.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/utils/qimei.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/qqmusic_api/utils/tripledes.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/tests/test_album.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/tests/test_lyric.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/tests/test_mv.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/tests/test_qimei.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/tests/test_search.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/tests/test_session.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/tests/test_sign.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/tests/test_singer.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/tests/test_songlist.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/tests/test_top.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/tests/test_user.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.4.0}/web/README.md +0 -0
|
@@ -30,6 +30,7 @@ share/python-wheels/
|
|
|
30
30
|
.installed.cfg
|
|
31
31
|
*.egg
|
|
32
32
|
MANIFEST
|
|
33
|
+
tools
|
|
33
34
|
|
|
34
35
|
# PyInstaller
|
|
35
36
|
# Usually these files are written by a python script from a template
|
|
@@ -164,7 +165,7 @@ cython_debug/
|
|
|
164
165
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
165
166
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
166
167
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
167
|
-
|
|
168
|
+
.idea/
|
|
168
169
|
|
|
169
170
|
### Python Patch ###
|
|
170
171
|
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qqmusic-api-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: QQ音乐API封装库
|
|
5
5
|
Project-URL: homepage, https://luren-dc.github.io/QQMusicApi/
|
|
6
6
|
Project-URL: repository, https://github.com/luren-dc/QQMusicApi
|
|
@@ -17,12 +17,13 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
17
17
|
Classifier: Natural Language :: Chinese (Simplified)
|
|
18
18
|
Classifier: Programming Language :: Python
|
|
19
19
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
21
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
24
|
Requires-Dist: aiocache>=0.12.3
|
|
25
|
-
Requires-Dist: cryptography<
|
|
25
|
+
Requires-Dist: cryptography<45.0.6,>=45.0.5
|
|
26
|
+
Requires-Dist: httpx-ws>=0.8.2
|
|
26
27
|
Requires-Dist: httpx[http2]>=0.27.0
|
|
27
28
|
Requires-Dist: orjson>=3.10.15
|
|
28
29
|
Requires-Dist: typing-extensions>=4.12.2
|
|
@@ -30,19 +31,19 @@ Description-Content-Type: text/markdown
|
|
|
30
31
|
|
|
31
32
|
<div align="center">
|
|
32
33
|
<a>
|
|
33
|
-
<img src="https://socialify.git.ci/
|
|
34
|
+
<img src="https://socialify.git.ci/l-1124/QQMusicApi/image?description=1&font=Source%20Code%20Pro&language=1&logo=https%3A%2F%2Fy.qq.com%2Fmediastyle%2Fmod%2Fmobile%2Fimg%2Flogo.svg&name=1&pattern=Overlapping%20Hexagons&theme=Auto">
|
|
34
35
|
</a>
|
|
35
36
|
<a href="https://www.python.org">
|
|
36
37
|
<img src="https://img.shields.io/badge/Python-3.10|3.11|3.12|3.13-blue" alt="Python">
|
|
37
38
|
</a>
|
|
38
|
-
<a href="https://github.com/
|
|
39
|
-
<img src="https://img.shields.io/github/license/
|
|
39
|
+
<a href="https://github.com/l-1124/QQMusicApi?tab=MIT-1-ov-file">
|
|
40
|
+
<img src="https://img.shields.io/github/license/l-1124/QQMusicApi" alt="GitHub license">
|
|
40
41
|
</a>
|
|
41
|
-
<a href="https://github.com/
|
|
42
|
-
<img src="https://img.shields.io/github/stars/
|
|
42
|
+
<a href="https://github.com/l-1124/QQMusicApi/stargazers">
|
|
43
|
+
<img src="https://img.shields.io/github/stars/l-1124/QQMusicApi?color=yellow&label=Github%20Stars" alt="STARS">
|
|
43
44
|
</a>
|
|
44
|
-
<a href="https://github.com/
|
|
45
|
-
<img src="https://github.com/
|
|
45
|
+
<a href="https://github.com/l-1124/QQMusicApi/actions/workflows/testing.yml">
|
|
46
|
+
<img src="https://github.com/l-1124/QQMusicApi/actions/workflows/testing.yml/badge.svg?branch=main" alt="Testing">
|
|
46
47
|
</a>
|
|
47
48
|
</div>
|
|
48
49
|
|
|
@@ -53,9 +54,9 @@ Description-Content-Type: text/markdown
|
|
|
53
54
|
>
|
|
54
55
|
> **音乐平台不易,请尊重版权,支持正版。**
|
|
55
56
|
|
|
56
|
-
**文档**: <a href="https://
|
|
57
|
+
**文档**: <a href="https://l-1124.github.io/QQMusicApi" target="_blank">https://l-1124.github.io/QQMusicApi</a>
|
|
57
58
|
|
|
58
|
-
**源代码**: <a href="https://github.com/
|
|
59
|
+
**源代码**: <a href="https://github.com/l-1124/QQMusicApi" target="_blank">https://github.com/l-1124/QQMusicApi</a>
|
|
59
60
|
|
|
60
61
|
## 介绍
|
|
61
62
|
|
|
@@ -100,9 +101,9 @@ asyncio.run(main())
|
|
|
100
101
|
|
|
101
102
|
## [Web API](./web/README.md)
|
|
102
103
|
|
|
103
|
-
##
|
|
104
|
+
## License
|
|
104
105
|
|
|
105
|
-
本项目基于 **[MIT License](https://github.com/
|
|
106
|
+
本项目基于 **[MIT License](https://github.com/l-1124/QQMusicApi?tab=MIT-1-ov-file)** 许可证发行。
|
|
106
107
|
|
|
107
108
|
## 免责声明
|
|
108
109
|
|
|
@@ -110,4 +111,4 @@ asyncio.run(main())
|
|
|
110
111
|
|
|
111
112
|
## 贡献者
|
|
112
113
|
|
|
113
|
-
[](https://github.com/l-1124/QQMusicApi/graphs/contributors)
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<a>
|
|
3
|
-
<img src="https://socialify.git.ci/
|
|
3
|
+
<img src="https://socialify.git.ci/l-1124/QQMusicApi/image?description=1&font=Source%20Code%20Pro&language=1&logo=https%3A%2F%2Fy.qq.com%2Fmediastyle%2Fmod%2Fmobile%2Fimg%2Flogo.svg&name=1&pattern=Overlapping%20Hexagons&theme=Auto">
|
|
4
4
|
</a>
|
|
5
5
|
<a href="https://www.python.org">
|
|
6
6
|
<img src="https://img.shields.io/badge/Python-3.10|3.11|3.12|3.13-blue" alt="Python">
|
|
7
7
|
</a>
|
|
8
|
-
<a href="https://github.com/
|
|
9
|
-
<img src="https://img.shields.io/github/license/
|
|
8
|
+
<a href="https://github.com/l-1124/QQMusicApi?tab=MIT-1-ov-file">
|
|
9
|
+
<img src="https://img.shields.io/github/license/l-1124/QQMusicApi" alt="GitHub license">
|
|
10
10
|
</a>
|
|
11
|
-
<a href="https://github.com/
|
|
12
|
-
<img src="https://img.shields.io/github/stars/
|
|
11
|
+
<a href="https://github.com/l-1124/QQMusicApi/stargazers">
|
|
12
|
+
<img src="https://img.shields.io/github/stars/l-1124/QQMusicApi?color=yellow&label=Github%20Stars" alt="STARS">
|
|
13
13
|
</a>
|
|
14
|
-
<a href="https://github.com/
|
|
15
|
-
<img src="https://github.com/
|
|
14
|
+
<a href="https://github.com/l-1124/QQMusicApi/actions/workflows/testing.yml">
|
|
15
|
+
<img src="https://github.com/l-1124/QQMusicApi/actions/workflows/testing.yml/badge.svg?branch=main" alt="Testing">
|
|
16
16
|
</a>
|
|
17
17
|
</div>
|
|
18
18
|
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
>
|
|
24
24
|
> **音乐平台不易,请尊重版权,支持正版。**
|
|
25
25
|
|
|
26
|
-
**文档**: <a href="https://
|
|
26
|
+
**文档**: <a href="https://l-1124.github.io/QQMusicApi" target="_blank">https://l-1124.github.io/QQMusicApi</a>
|
|
27
27
|
|
|
28
|
-
**源代码**: <a href="https://github.com/
|
|
28
|
+
**源代码**: <a href="https://github.com/l-1124/QQMusicApi" target="_blank">https://github.com/l-1124/QQMusicApi</a>
|
|
29
29
|
|
|
30
30
|
## 介绍
|
|
31
31
|
|
|
@@ -70,9 +70,9 @@ asyncio.run(main())
|
|
|
70
70
|
|
|
71
71
|
## [Web API](./web/README.md)
|
|
72
72
|
|
|
73
|
-
##
|
|
73
|
+
## License
|
|
74
74
|
|
|
75
|
-
本项目基于 **[MIT License](https://github.com/
|
|
75
|
+
本项目基于 **[MIT License](https://github.com/l-1124/QQMusicApi?tab=MIT-1-ov-file)** 许可证发行。
|
|
76
76
|
|
|
77
77
|
## 免责声明
|
|
78
78
|
|
|
@@ -80,4 +80,4 @@ asyncio.run(main())
|
|
|
80
80
|
|
|
81
81
|
## 贡献者
|
|
82
82
|
|
|
83
|
-
[](https://github.com/l-1124/QQMusicApi/graphs/contributors)
|
|
@@ -5,11 +5,12 @@ authors = [
|
|
|
5
5
|
{ name = "Luren", email = "68656403+luren-dc@users.noreply.github.com" },
|
|
6
6
|
]
|
|
7
7
|
dependencies = [
|
|
8
|
-
"cryptography>=
|
|
8
|
+
"cryptography>=45.0.5,<45.0.6",
|
|
9
9
|
"typing-extensions>=4.12.2",
|
|
10
10
|
"httpx[http2]>=0.27.0",
|
|
11
11
|
"aiocache>=0.12.3",
|
|
12
12
|
"orjson>=3.10.15",
|
|
13
|
+
"httpx-ws>=0.8.2",
|
|
13
14
|
]
|
|
14
15
|
requires-python = ">=3.10"
|
|
15
16
|
readme = "README.md"
|
|
@@ -26,7 +27,7 @@ classifiers = [
|
|
|
26
27
|
"Framework :: aiohttp",
|
|
27
28
|
"Programming Language :: Python",
|
|
28
29
|
"Programming Language :: Python :: 3 :: Only",
|
|
29
|
-
"Programming Language :: Python :: 3.
|
|
30
|
+
"Programming Language :: Python :: 3.10",
|
|
30
31
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
31
32
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
32
33
|
]
|
|
@@ -55,7 +56,7 @@ path = "qqmusic_api/__init__.py"
|
|
|
55
56
|
packages = ["qqmusic_api"]
|
|
56
57
|
|
|
57
58
|
[tool.hatch.build.targets.sdist]
|
|
58
|
-
include = ["/qqmusic_api", "/tests", "
|
|
59
|
+
include = ["/qqmusic_api", "/tests", "LICENSE", "README.md"]
|
|
59
60
|
|
|
60
61
|
[dependency-groups]
|
|
61
62
|
testing = [
|
|
@@ -81,7 +82,7 @@ web = [
|
|
|
81
82
|
[tool.commitizen]
|
|
82
83
|
name = "cz_gitmoji"
|
|
83
84
|
|
|
84
|
-
[tool.
|
|
85
|
+
[tool.pyright]
|
|
85
86
|
venvPath = "."
|
|
86
87
|
venv = ".venv"
|
|
87
88
|
include = ["qqmusic_api"]
|
|
@@ -104,10 +105,9 @@ extend-select = [
|
|
|
104
105
|
"ASYNC",
|
|
105
106
|
"C4",
|
|
106
107
|
"FURB",
|
|
107
|
-
"R",
|
|
108
108
|
"PERF",
|
|
109
109
|
]
|
|
110
|
-
ignore = ["D105", "D107", "D205", "D415"]
|
|
110
|
+
ignore = ["D105", "D107", "D205", "D415", "RUF029"]
|
|
111
111
|
pydocstyle = { convention = "google" }
|
|
112
112
|
|
|
113
113
|
[tool.ruff.lint.per-file-ignores]
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
2
4
|
|
|
3
5
|
from . import album, comment, login, lyric, mv, search, singer, song, songlist, top, user
|
|
4
6
|
from .utils.credential import Credential
|
|
5
7
|
from .utils.session import Session, get_session, set_session
|
|
6
8
|
|
|
7
|
-
__version__ = "0.
|
|
9
|
+
__version__ = "0.4.0"
|
|
8
10
|
|
|
9
11
|
logger = logging.getLogger("qqmusicapi")
|
|
10
12
|
|
|
13
|
+
# Change to the "Selector" event loop if platform is Windows
|
|
14
|
+
if sys.platform.lower() == "win32" or os.name.lower() == "nt":
|
|
15
|
+
from asyncio import WindowsSelectorEventLoopPolicy, set_event_loop_policy
|
|
16
|
+
|
|
17
|
+
set_event_loop_policy(WindowsSelectorEventLoopPolicy())
|
|
18
|
+
|
|
11
19
|
|
|
12
20
|
__all__ = [
|
|
13
21
|
"Credential",
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""评论 API"""
|
|
2
|
+
|
|
3
|
+
from .utils.network import NO_PROCESSOR, api_request
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@api_request("music.globalComment.CommentCountSrv", "GetCmCount")
|
|
7
|
+
async def get_comment_count(biz_id: str):
|
|
8
|
+
"""获取歌曲评论数量
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
biz_id: 歌曲 ID
|
|
12
|
+
"""
|
|
13
|
+
return {
|
|
14
|
+
"request": {
|
|
15
|
+
"biz_id": biz_id,
|
|
16
|
+
"biz_type": 1,
|
|
17
|
+
"biz_sub_type": 2,
|
|
18
|
+
},
|
|
19
|
+
}, NO_PROCESSOR
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@api_request("music.globalComment.CommentRead", "GetHotCommentList")
|
|
23
|
+
async def get_hot_comments(
|
|
24
|
+
biz_id: str,
|
|
25
|
+
page_num: int = 1,
|
|
26
|
+
page_size: int = 15,
|
|
27
|
+
last_comment_seq_no: str = "",
|
|
28
|
+
):
|
|
29
|
+
"""获取歌曲热评
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
biz_id: 歌曲 ID
|
|
33
|
+
page_num: 页码
|
|
34
|
+
page_size: 每页数量
|
|
35
|
+
last_comment_seq_no: 上一页最后一条评论 ID(可选)
|
|
36
|
+
"""
|
|
37
|
+
params = {
|
|
38
|
+
"BizType": 1,
|
|
39
|
+
"BizId": biz_id,
|
|
40
|
+
"LastCommentSeqNo": last_comment_seq_no,
|
|
41
|
+
"PageSize": page_size,
|
|
42
|
+
"PageNum": page_num - 1,
|
|
43
|
+
"HotType": 1,
|
|
44
|
+
"WithAirborne": 0,
|
|
45
|
+
"PicEnable": 1,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return params, NO_PROCESSOR
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@api_request("music.globalComment.CommentRead", "GetNewCommentList")
|
|
52
|
+
async def get_new_comments(
|
|
53
|
+
biz_id: str,
|
|
54
|
+
page_num: int = 1,
|
|
55
|
+
page_size: int = 15,
|
|
56
|
+
last_comment_seq_no: str = "",
|
|
57
|
+
):
|
|
58
|
+
"""获取歌曲最新评论
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
biz_id: 歌曲 ID
|
|
62
|
+
page_num: 页码
|
|
63
|
+
page_size: 每页数量
|
|
64
|
+
last_comment_seq_no: 上一页最后一条评论 ID(可选)
|
|
65
|
+
"""
|
|
66
|
+
params = {
|
|
67
|
+
# "LastRspVer": "",
|
|
68
|
+
# "LastTotalVer": "1755832873618224522",
|
|
69
|
+
"PageSize": page_size,
|
|
70
|
+
"PageNum": page_num - 1,
|
|
71
|
+
"HashTagID": "",
|
|
72
|
+
"BizType": 1,
|
|
73
|
+
# "LastCommentId": "",
|
|
74
|
+
"PicEnable": 1,
|
|
75
|
+
"LastCommentSeqNo": last_comment_seq_no,
|
|
76
|
+
"SelfSeeEnable": 1,
|
|
77
|
+
# "LastTotal": 325,
|
|
78
|
+
# "CmListUIVer": 1,
|
|
79
|
+
"BizId": biz_id,
|
|
80
|
+
"AudioEnable": 1,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return params, NO_PROCESSOR
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@api_request("music.globalComment.CommentRead", "GetRecCommentList")
|
|
87
|
+
async def get_recommend_comments(
|
|
88
|
+
biz_id: str,
|
|
89
|
+
page_num: int = 1,
|
|
90
|
+
page_size: int = 15,
|
|
91
|
+
last_comment_seq_no: str = "",
|
|
92
|
+
):
|
|
93
|
+
"""获取歌曲推荐评论
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
biz_id: 歌曲 ID
|
|
97
|
+
page_num: 页码
|
|
98
|
+
page_size: 每页数量
|
|
99
|
+
last_comment_seq_no: 上一页最后一条评论 ID(可选)
|
|
100
|
+
"""
|
|
101
|
+
params = {
|
|
102
|
+
# "FromParentCmId": "",
|
|
103
|
+
# "LastRspVer": "1755834843787200911",
|
|
104
|
+
# "LastTotalVer": "1755834843679664122",
|
|
105
|
+
# "RecOffset": 0,
|
|
106
|
+
# "LastHotScore": "",
|
|
107
|
+
# "FromCommentId": "",
|
|
108
|
+
# "HashTagID": "",
|
|
109
|
+
# "CommentIds": [],
|
|
110
|
+
# "LastRecScore": "",
|
|
111
|
+
# "LastTotal": 325,
|
|
112
|
+
"PageSize": page_size,
|
|
113
|
+
"PageNum": page_num - 1,
|
|
114
|
+
"BizType": 1,
|
|
115
|
+
"PicEnable": 1,
|
|
116
|
+
"Flag": 1,
|
|
117
|
+
"LastCommentSeqNo": last_comment_seq_no,
|
|
118
|
+
"CmListUIVer": 1,
|
|
119
|
+
"BizId": biz_id,
|
|
120
|
+
"AudioEnable": 1,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return params, NO_PROCESSOR
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@api_request("music.globalComment.SongTsComment", "GetSongTsCmList")
|
|
127
|
+
async def get_moment_comments(
|
|
128
|
+
biz_id: str,
|
|
129
|
+
page_size: int = 15,
|
|
130
|
+
last_comment_seq_no: str = "",
|
|
131
|
+
):
|
|
132
|
+
"""获取时刻评论
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
biz_id: 歌曲 ID
|
|
136
|
+
page_size: 每页数量
|
|
137
|
+
last_comment_seq_no: 上一页最后一条评论ID
|
|
138
|
+
"""
|
|
139
|
+
params = {
|
|
140
|
+
"LastPos": last_comment_seq_no,
|
|
141
|
+
"HashTagID": "",
|
|
142
|
+
"SeekTs": -1,
|
|
143
|
+
"Size": page_size,
|
|
144
|
+
"BizType": 1,
|
|
145
|
+
"BizId": biz_id,
|
|
146
|
+
}
|
|
147
|
+
return params, NO_PROCESSOR
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import mimetypes
|
|
4
4
|
import random
|
|
5
5
|
import re
|
|
6
|
+
from collections.abc import AsyncGenerator
|
|
6
7
|
from dataclasses import dataclass
|
|
7
8
|
from enum import Enum
|
|
8
9
|
from pathlib import Path
|
|
@@ -15,6 +16,8 @@ import httpx
|
|
|
15
16
|
from .exceptions.api_exception import CredentialExpiredError, LoginError, ResponseCodeError
|
|
16
17
|
from .utils.common import hash33
|
|
17
18
|
from .utils.credential import Credential
|
|
19
|
+
from .utils.mqtt import Client as MqttClient
|
|
20
|
+
from .utils.mqtt import MqttRedirectError, PropertyId
|
|
18
21
|
from .utils.network import ApiRequest
|
|
19
22
|
from .utils.session import get_session
|
|
20
23
|
|
|
@@ -30,7 +33,6 @@ async def check_expired(credential: Credential) -> bool:
|
|
|
30
33
|
"GetLoginUserInfo",
|
|
31
34
|
params={},
|
|
32
35
|
credential=credential,
|
|
33
|
-
cacheable=False,
|
|
34
36
|
)
|
|
35
37
|
|
|
36
38
|
try:
|
|
@@ -65,7 +67,6 @@ async def refresh_cookies(credential: Credential) -> bool:
|
|
|
65
67
|
common={"tmeLoginType": str(credential.login_type)},
|
|
66
68
|
params=params,
|
|
67
69
|
credential=credential,
|
|
68
|
-
cacheable=False,
|
|
69
70
|
)
|
|
70
71
|
|
|
71
72
|
try:
|
|
@@ -124,10 +125,12 @@ class QRLoginType(Enum):
|
|
|
124
125
|
|
|
125
126
|
+ QQ: QQ登录
|
|
126
127
|
+ WX: 微信登录
|
|
128
|
+
+ MOBILE: 手机客户端登录
|
|
127
129
|
"""
|
|
128
130
|
|
|
129
131
|
QQ = "qq"
|
|
130
132
|
WX = "wx"
|
|
133
|
+
MOBILE = "mobile"
|
|
131
134
|
|
|
132
135
|
|
|
133
136
|
@dataclass()
|
|
@@ -138,7 +141,7 @@ class QR:
|
|
|
138
141
|
data: 二维码图像数据
|
|
139
142
|
qr_type: 二维码类型
|
|
140
143
|
mimetype: 二维码图像类型
|
|
141
|
-
|
|
144
|
+
identifier: 标识符
|
|
142
145
|
"""
|
|
143
146
|
|
|
144
147
|
data: bytes
|
|
@@ -164,10 +167,13 @@ class QR:
|
|
|
164
167
|
return file_path
|
|
165
168
|
|
|
166
169
|
|
|
170
|
+
# 修改 qqmusic_api/login.py 中的 get_qrcode 函数
|
|
167
171
|
async def get_qrcode(login_type: QRLoginType) -> QR:
|
|
168
172
|
"""获取登录二维码"""
|
|
169
173
|
if login_type == QRLoginType.WX:
|
|
170
174
|
return await _get_wx_qr()
|
|
175
|
+
if login_type == QRLoginType.MOBILE:
|
|
176
|
+
return await _get_mobile_qr()
|
|
171
177
|
return await _get_qq_qr()
|
|
172
178
|
|
|
173
179
|
|
|
@@ -218,6 +224,27 @@ async def _get_wx_qr() -> QR:
|
|
|
218
224
|
return QR(qrcode_data, QRLoginType.WX, "image/jpeg", uuid)
|
|
219
225
|
|
|
220
226
|
|
|
227
|
+
async def _get_mobile_qr() -> QR:
|
|
228
|
+
import base64
|
|
229
|
+
|
|
230
|
+
res = await ApiRequest[[], dict[str, Any]](
|
|
231
|
+
"music.login.LoginServer",
|
|
232
|
+
"CreateQRCode",
|
|
233
|
+
params={
|
|
234
|
+
"tmeAppID": "qqmusic",
|
|
235
|
+
"ct": 11,
|
|
236
|
+
"cv": 13020508,
|
|
237
|
+
},
|
|
238
|
+
)()
|
|
239
|
+
|
|
240
|
+
return QR(
|
|
241
|
+
data=base64.b64decode(res["qrcode"].split(",")[-1]),
|
|
242
|
+
qr_type=QRLoginType.MOBILE,
|
|
243
|
+
mimetype="image/png",
|
|
244
|
+
identifier=res["qrcodeID"],
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
221
248
|
async def check_qrcode(qrcode: QR) -> tuple[QRCodeLoginEvents, Credential | None]:
|
|
222
249
|
"""检查二维码状态"""
|
|
223
250
|
if qrcode.qr_type == QRLoginType.WX:
|
|
@@ -302,6 +329,95 @@ async def _check_wx_qr(qrcode: QR) -> tuple[QRCodeLoginEvents, Credential | None
|
|
|
302
329
|
return event, None
|
|
303
330
|
|
|
304
331
|
|
|
332
|
+
async def check_mobile_qr(qrcode: QR) -> AsyncGenerator[tuple[QRCodeLoginEvents, Credential | None], None]: # noqa: C901
|
|
333
|
+
"""检查手机客户端登录二维码状态
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
qrcode: 二维码对象
|
|
337
|
+
"""
|
|
338
|
+
if qrcode.qr_type != QRLoginType.MOBILE:
|
|
339
|
+
raise ValueError(f"不支持{qrcode.qr_type}类型的二维码")
|
|
340
|
+
client_id = f"{int(time() * 1000)}{random.randint(1000, 9999)}"
|
|
341
|
+
client = MqttClient(client_id=client_id, host="mu.y.qq.com", port=443, path="/ws/handshake", keep_alive=45)
|
|
342
|
+
async with client:
|
|
343
|
+
max_redirects = 3
|
|
344
|
+
for attempt in range(max_redirects + 1):
|
|
345
|
+
try:
|
|
346
|
+
await client.connect(
|
|
347
|
+
properties={
|
|
348
|
+
PropertyId.AUTH_METHOD: "pass",
|
|
349
|
+
PropertyId.USER_PROPERTY: [
|
|
350
|
+
("tmeAppID", "qqmusic"),
|
|
351
|
+
("business", "management"),
|
|
352
|
+
("hashTag", qrcode.identifier),
|
|
353
|
+
("clientTag", "management.user"),
|
|
354
|
+
("userID", qrcode.identifier),
|
|
355
|
+
],
|
|
356
|
+
},
|
|
357
|
+
headers={
|
|
358
|
+
"Origin": "https://y.qq.com",
|
|
359
|
+
"Referer": "https://y.qq.com/",
|
|
360
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
|
|
361
|
+
},
|
|
362
|
+
)
|
|
363
|
+
break
|
|
364
|
+
except MqttRedirectError as e:
|
|
365
|
+
if attempt == max_redirects:
|
|
366
|
+
raise LoginError("[MobileLogin] 重定向次数过多") from e
|
|
367
|
+
|
|
368
|
+
client.path = f"/ws/handshake/{e.new_address}"
|
|
369
|
+
continue
|
|
370
|
+
await client.subscribe(
|
|
371
|
+
f"management.qrcode_login/{qrcode.identifier}",
|
|
372
|
+
properties={
|
|
373
|
+
PropertyId.USER_PROPERTY: [
|
|
374
|
+
("authorization", "tmelogin"),
|
|
375
|
+
("pubsub", "unicast"),
|
|
376
|
+
]
|
|
377
|
+
},
|
|
378
|
+
)
|
|
379
|
+
yield QRCodeLoginEvents.SCAN, None
|
|
380
|
+
async for msg in client.messages():
|
|
381
|
+
event_type = msg.properties.get("type")
|
|
382
|
+
|
|
383
|
+
match event_type:
|
|
384
|
+
case "scanned":
|
|
385
|
+
yield QRCodeLoginEvents.CONF, None
|
|
386
|
+
|
|
387
|
+
case "cookies":
|
|
388
|
+
data = msg.json
|
|
389
|
+
if data:
|
|
390
|
+
cookies: dict[str, Any] = {
|
|
391
|
+
k: v.get("value") if isinstance(v, dict) else v for k, v in data.get("cookies", {}).items()
|
|
392
|
+
}
|
|
393
|
+
res = await ApiRequest[[], dict[str, Any]](
|
|
394
|
+
"music.login.LoginServer",
|
|
395
|
+
"Login",
|
|
396
|
+
params={
|
|
397
|
+
"musicid": int(cookies.get("qqmusic_uin", 0)),
|
|
398
|
+
"qrCodeID": qrcode.identifier,
|
|
399
|
+
"token": cookies.get("qqmusic_key", ""),
|
|
400
|
+
},
|
|
401
|
+
common={"tmeLoginType": "6"},
|
|
402
|
+
)()
|
|
403
|
+
|
|
404
|
+
yield QRCodeLoginEvents.DONE, Credential.from_cookies_dict(res)
|
|
405
|
+
else:
|
|
406
|
+
yield QRCodeLoginEvents.OTHER, None
|
|
407
|
+
break
|
|
408
|
+
case "canceled":
|
|
409
|
+
yield QRCodeLoginEvents.REFUSE, None
|
|
410
|
+
break
|
|
411
|
+
case "timeout":
|
|
412
|
+
yield QRCodeLoginEvents.TIMEOUT, None
|
|
413
|
+
break
|
|
414
|
+
case "loginFailed":
|
|
415
|
+
yield QRCodeLoginEvents.OTHER, None
|
|
416
|
+
break
|
|
417
|
+
case _:
|
|
418
|
+
pass
|
|
419
|
+
|
|
420
|
+
|
|
305
421
|
async def _authorize_qq_qr(uin: str, sigx: str) -> Credential:
|
|
306
422
|
session = get_session()
|
|
307
423
|
resp = await session.get(
|
|
@@ -363,7 +479,6 @@ async def _authorize_qq_qr(uin: str, sigx: str) -> Credential:
|
|
|
363
479
|
"QQLogin",
|
|
364
480
|
common={"tmeLoginType": "2"},
|
|
365
481
|
params={"code": code},
|
|
366
|
-
cacheable=False,
|
|
367
482
|
)
|
|
368
483
|
return Credential.from_cookies_dict(await api())
|
|
369
484
|
except CredentialExpiredError:
|
|
@@ -381,7 +496,6 @@ async def _authorize_wx_qr(code: str) -> Credential:
|
|
|
381
496
|
"Login",
|
|
382
497
|
common={"tmeLoginType": "1"},
|
|
383
498
|
params={"code": code, "strAppid": "wx48db31d50e334801"},
|
|
384
|
-
cacheable=False,
|
|
385
499
|
)
|
|
386
500
|
return Credential.from_cookies_dict(await api())
|
|
387
501
|
except CredentialExpiredError:
|
|
@@ -405,7 +519,6 @@ async def send_authcode(phone: int, country_code: int = 86) -> tuple[PhoneLoginE
|
|
|
405
519
|
"areaCode": str(country_code),
|
|
406
520
|
},
|
|
407
521
|
ignore_code=True,
|
|
408
|
-
cacheable=False,
|
|
409
522
|
)()
|
|
410
523
|
|
|
411
524
|
match resp["code"]:
|
|
@@ -432,7 +545,6 @@ async def phone_authorize(phone: int, auth_code: int, country_code: int = 86) ->
|
|
|
432
545
|
common={"tmeLoginMethod": "3", "tmeLoginType": "0"},
|
|
433
546
|
params={"code": str(auth_code), "phoneNo": str(phone), "areaCode": str(country_code), "loginMode": 1},
|
|
434
547
|
ignore_code=True,
|
|
435
|
-
cacheable=False,
|
|
436
548
|
)()
|
|
437
549
|
match resp["code"]:
|
|
438
550
|
case 20274:
|
|
@@ -43,7 +43,7 @@ async def get_detail(vids: list[str]):
|
|
|
43
43
|
}, NO_PROCESSOR
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
@api_request("music.stream.MvUrlProxy", "GetMvUrls"
|
|
46
|
+
@api_request("music.stream.MvUrlProxy", "GetMvUrls")
|
|
47
47
|
async def get_mv_urls(vids: list[str]):
|
|
48
48
|
"""获取 MV 播放链接
|
|
49
49
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""推荐相关 API"""
|
|
2
|
+
|
|
3
|
+
from .utils.network import NO_PROCESSOR, api_request
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@api_request("music.recommend.RecommendFeed", "get_recommend_feed")
|
|
7
|
+
async def get_home_feed():
|
|
8
|
+
"""获取主页推荐"""
|
|
9
|
+
return {
|
|
10
|
+
"direction": 0,
|
|
11
|
+
"page": 1,
|
|
12
|
+
"s_num": 0,
|
|
13
|
+
}, NO_PROCESSOR
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@api_request("music.radioProxy.MbTrackRadioSvr", "get_radio_track")
|
|
17
|
+
async def get_guess_recommend():
|
|
18
|
+
"""获取猜你喜欢"""
|
|
19
|
+
return {
|
|
20
|
+
"id": 99,
|
|
21
|
+
"num": 5,
|
|
22
|
+
"from": 0,
|
|
23
|
+
"scene": 0,
|
|
24
|
+
"song_ids": [],
|
|
25
|
+
"ext": {"bluetooth": ""},
|
|
26
|
+
"should_count_down": 1,
|
|
27
|
+
}, NO_PROCESSOR
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@api_request("music.recommend.TrackRelationServer", "GetRadarSong")
|
|
31
|
+
async def get_radar_recommend():
|
|
32
|
+
"""获取雷达推荐"""
|
|
33
|
+
return {
|
|
34
|
+
"Page": 1,
|
|
35
|
+
# "LastToastTime": 1755782480,
|
|
36
|
+
"ReqType": 0,
|
|
37
|
+
"FavSongs": [],
|
|
38
|
+
"EntranceSongs": [],
|
|
39
|
+
# "ext": {"bluetooth": ""},
|
|
40
|
+
}, NO_PROCESSOR
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@api_request("music.playlist.PlaylistSquare", "GetRecommendFeed")
|
|
44
|
+
async def get_recommend_songlist():
|
|
45
|
+
"""获取推荐歌单"""
|
|
46
|
+
return {"From": 0, "Size": 25}, NO_PROCESSOR
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@api_request("newsong.NewSongServer", "get_new_song_info")
|
|
50
|
+
async def get_recommend_newsong():
|
|
51
|
+
"""获取推荐新歌"""
|
|
52
|
+
return {"type": 5}, NO_PROCESSOR
|