qqmusic-api-python 0.3.5__tar.gz → 0.3.6__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.3.6}/.gitignore +2 -1
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/PKG-INFO +4 -4
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/README.md +1 -1
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/pyproject.toml +5 -6
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/__init__.py +1 -1
- qqmusic_api_python-0.3.6/qqmusic_api/comment.py +183 -0
- qqmusic_api_python-0.3.6/qqmusic_api/recommend.py +52 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/song.py +1 -0
- qqmusic_api_python-0.3.6/qqmusic_api/utils/sign.py +36 -0
- qqmusic_api_python-0.3.5/tests/test_comments.py → qqmusic_api_python-0.3.6/tests/test_comment.py +5 -1
- qqmusic_api_python-0.3.6/tests/test_recommend.py +25 -0
- 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 → qqmusic_api_python-0.3.6}/LICENSE +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/album.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/exceptions/__init__.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/exceptions/api_exception.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/login.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/lyric.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/mv.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/search.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/singer.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/songlist.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/top.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/user.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/utils/__init__.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/utils/common.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/utils/credential.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/utils/device.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/utils/network.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/utils/qimei.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/utils/session.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/utils/tripledes.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/tests/test_album.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/tests/test_login.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/tests/test_lyric.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/tests/test_mv.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/tests/test_qimei.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/tests/test_search.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/tests/test_session.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/tests/test_sign.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/tests/test_singer.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/tests/test_song.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/tests/test_songlist.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/tests/test_top.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/tests/test_user.py +0 -0
- {qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/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.
|
|
3
|
+
Version: 0.3.6
|
|
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,12 @@ 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
26
|
Requires-Dist: httpx[http2]>=0.27.0
|
|
27
27
|
Requires-Dist: orjson>=3.10.15
|
|
28
28
|
Requires-Dist: typing-extensions>=4.12.2
|
|
@@ -100,7 +100,7 @@ asyncio.run(main())
|
|
|
100
100
|
|
|
101
101
|
## [Web API](./web/README.md)
|
|
102
102
|
|
|
103
|
-
##
|
|
103
|
+
## License
|
|
104
104
|
|
|
105
105
|
本项目基于 **[MIT License](https://github.com/luren-dc/QQMusicApi?tab=MIT-1-ov-file)** 许可证发行。
|
|
106
106
|
|
|
@@ -5,7 +5,7 @@ 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",
|
|
@@ -26,7 +26,7 @@ classifiers = [
|
|
|
26
26
|
"Framework :: aiohttp",
|
|
27
27
|
"Programming Language :: Python",
|
|
28
28
|
"Programming Language :: Python :: 3 :: Only",
|
|
29
|
-
"Programming Language :: Python :: 3.
|
|
29
|
+
"Programming Language :: Python :: 3.10",
|
|
30
30
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
31
31
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
32
32
|
]
|
|
@@ -55,7 +55,7 @@ path = "qqmusic_api/__init__.py"
|
|
|
55
55
|
packages = ["qqmusic_api"]
|
|
56
56
|
|
|
57
57
|
[tool.hatch.build.targets.sdist]
|
|
58
|
-
include = ["/qqmusic_api", "/tests", "
|
|
58
|
+
include = ["/qqmusic_api", "/tests", "LICENSE", "README.md"]
|
|
59
59
|
|
|
60
60
|
[dependency-groups]
|
|
61
61
|
testing = [
|
|
@@ -81,7 +81,7 @@ web = [
|
|
|
81
81
|
[tool.commitizen]
|
|
82
82
|
name = "cz_gitmoji"
|
|
83
83
|
|
|
84
|
-
[tool.
|
|
84
|
+
[tool.pyright]
|
|
85
85
|
venvPath = "."
|
|
86
86
|
venv = ".venv"
|
|
87
87
|
include = ["qqmusic_api"]
|
|
@@ -104,10 +104,9 @@ extend-select = [
|
|
|
104
104
|
"ASYNC",
|
|
105
105
|
"C4",
|
|
106
106
|
"FURB",
|
|
107
|
-
"R",
|
|
108
107
|
"PERF",
|
|
109
108
|
]
|
|
110
|
-
ignore = ["D105", "D107", "D205", "D415"]
|
|
109
|
+
ignore = ["D105", "D107", "D205", "D415", "RUF029"]
|
|
111
110
|
pydocstyle = { convention = "google" }
|
|
112
111
|
|
|
113
112
|
[tool.ruff.lint.per-file-ignores]
|
|
@@ -4,7 +4,7 @@ from . import album, comment, login, lyric, mv, search, singer, song, songlist,
|
|
|
4
4
|
from .utils.credential import Credential
|
|
5
5
|
from .utils.session import Session, get_session, set_session
|
|
6
6
|
|
|
7
|
-
__version__ = "0.3.
|
|
7
|
+
__version__ = "0.3.6"
|
|
8
8
|
|
|
9
9
|
logger = logging.getLogger("qqmusicapi")
|
|
10
10
|
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""评论 API"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, cast
|
|
4
|
+
|
|
5
|
+
from .utils.network import api_request
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@api_request("music.globalComment.CommentCountSrv", "GetCmCount")
|
|
9
|
+
async def get_comment_count(biz_id: str):
|
|
10
|
+
"""获取歌曲评论数量
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
biz_id: 歌曲 ID
|
|
14
|
+
"""
|
|
15
|
+
return {
|
|
16
|
+
"request": {
|
|
17
|
+
"biz_id": biz_id,
|
|
18
|
+
"biz_type": 1,
|
|
19
|
+
"biz_sub_type": 2,
|
|
20
|
+
},
|
|
21
|
+
}, lambda data: cast(dict[str, Any], data.get("response", {}))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _processor(data: dict[str, Any]):
|
|
25
|
+
"""处理并返回结构化评论数据:
|
|
26
|
+
|
|
27
|
+
返回结构:
|
|
28
|
+
[
|
|
29
|
+
{
|
|
30
|
+
"Avatar": str, # 用户头像 URL
|
|
31
|
+
"CmId": str, # 评论 ID (后续需要获取全部子评论时需用到)
|
|
32
|
+
"PraiseNum": int, # 点赞数
|
|
33
|
+
"Nick": str, # 昵称
|
|
34
|
+
"Pic": str, # 评论配图 (可能为空)
|
|
35
|
+
"Content": str, # 评论内容
|
|
36
|
+
"SeqNo": str, # 评论序号 ID 可以用于传递给 参数: last_comment_seq_no
|
|
37
|
+
"SubComments": [ # 子评论列表
|
|
38
|
+
{
|
|
39
|
+
"Avatar": str,
|
|
40
|
+
"Nick": str,
|
|
41
|
+
"Content": str,
|
|
42
|
+
"Pic": str,
|
|
43
|
+
"PraiseNum": int,
|
|
44
|
+
"SeqNo": str
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
...
|
|
49
|
+
]
|
|
50
|
+
"""
|
|
51
|
+
comments = data.get("CommentList", {}).get("Comments", [])
|
|
52
|
+
result = []
|
|
53
|
+
|
|
54
|
+
for comment in comments:
|
|
55
|
+
item = {
|
|
56
|
+
"Avatar": comment.get("Avatar"),
|
|
57
|
+
"CmId": comment.get("CmId"),
|
|
58
|
+
"PraiseNum": comment.get("PraiseNum"),
|
|
59
|
+
"Nick": comment.get("Nick"),
|
|
60
|
+
"Pic": comment.get("Pic"),
|
|
61
|
+
"Content": comment.get("Content"),
|
|
62
|
+
"SeqNo": comment.get("SeqNo"),
|
|
63
|
+
"SubComments": [],
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for sub in comment.get("SubComments", []):
|
|
67
|
+
sub_item = {
|
|
68
|
+
"Avatar": sub.get("Avatar"),
|
|
69
|
+
"Nick": sub.get("Nick"),
|
|
70
|
+
"Content": sub.get("Content"),
|
|
71
|
+
"Pic": sub.get("Pic"),
|
|
72
|
+
"PraiseNum": sub.get("PraiseNum"),
|
|
73
|
+
"SeqNo": sub.get("SeqNo"),
|
|
74
|
+
}
|
|
75
|
+
item["SubComments"].append(sub_item)
|
|
76
|
+
|
|
77
|
+
result.append(item)
|
|
78
|
+
|
|
79
|
+
return result
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@api_request("music.globalComment.CommentRead", "GetHotCommentList")
|
|
83
|
+
async def get_hot_comments(
|
|
84
|
+
biz_id: str,
|
|
85
|
+
page_num: int = 1,
|
|
86
|
+
page_size: int = 15,
|
|
87
|
+
last_comment_seq_no: str = "",
|
|
88
|
+
):
|
|
89
|
+
"""获取歌曲热评
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
biz_id: 歌曲 ID
|
|
93
|
+
page_num: 页码
|
|
94
|
+
page_size: 每页数量
|
|
95
|
+
last_comment_seq_no: 上一页最后一条评论 ID(可选)
|
|
96
|
+
"""
|
|
97
|
+
params = {
|
|
98
|
+
"BizType": 1,
|
|
99
|
+
"BizId": biz_id,
|
|
100
|
+
"LastCommentSeqNo": last_comment_seq_no,
|
|
101
|
+
"PageSize": page_size,
|
|
102
|
+
"PageNum": page_num - 1,
|
|
103
|
+
"HotType": 1,
|
|
104
|
+
"WithAirborne": 0,
|
|
105
|
+
"PicEnable": 1,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return params, _processor
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@api_request("music.globalComment.CommentRead", "GetNewCommentList")
|
|
112
|
+
async def get_new_comments(
|
|
113
|
+
biz_id: str,
|
|
114
|
+
page_num: int = 1,
|
|
115
|
+
page_size: int = 15,
|
|
116
|
+
last_comment_seq_no: str = "",
|
|
117
|
+
):
|
|
118
|
+
"""获取歌曲最新评论
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
biz_id: 歌曲 ID
|
|
122
|
+
page_num: 页码
|
|
123
|
+
page_size: 每页数量
|
|
124
|
+
last_comment_seq_no: 上一页最后一条评论 ID(可选)
|
|
125
|
+
"""
|
|
126
|
+
params = {
|
|
127
|
+
# "LastRspVer": "",
|
|
128
|
+
# "LastTotalVer": "1755832873618224522",
|
|
129
|
+
"PageSize": page_size,
|
|
130
|
+
"PageNum": page_num - 1,
|
|
131
|
+
"HashTagID": "",
|
|
132
|
+
"BizType": 1,
|
|
133
|
+
# "LastCommentId": "",
|
|
134
|
+
"PicEnable": 1,
|
|
135
|
+
"LastCommentSeqNo": last_comment_seq_no,
|
|
136
|
+
"SelfSeeEnable": 1,
|
|
137
|
+
# "LastTotal": 325,
|
|
138
|
+
# "CmListUIVer": 1,
|
|
139
|
+
"BizId": biz_id,
|
|
140
|
+
"AudioEnable": 1,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return params, _processor
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@api_request("music.globalComment.CommentRead", "GetRecCommentList")
|
|
147
|
+
async def get_recommend_comments(
|
|
148
|
+
biz_id: str,
|
|
149
|
+
page_num: int = 1,
|
|
150
|
+
page_size: int = 15,
|
|
151
|
+
last_comment_seq_no: str = "",
|
|
152
|
+
):
|
|
153
|
+
"""获取歌曲推荐评论
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
biz_id: 歌曲 ID
|
|
157
|
+
page_num: 页码
|
|
158
|
+
page_size: 每页数量
|
|
159
|
+
last_comment_seq_no: 上一页最后一条评论 ID(可选)
|
|
160
|
+
"""
|
|
161
|
+
params = {
|
|
162
|
+
# "FromParentCmId": "",
|
|
163
|
+
# "LastRspVer": "1755834843787200911",
|
|
164
|
+
# "LastTotalVer": "1755834843679664122",
|
|
165
|
+
# "RecOffset": 0,
|
|
166
|
+
# "LastHotScore": "",
|
|
167
|
+
# "FromCommentId": "",
|
|
168
|
+
# "HashTagID": "",
|
|
169
|
+
# "CommentIds": [],
|
|
170
|
+
# "LastRecScore": "",
|
|
171
|
+
# "LastTotal": 325,
|
|
172
|
+
"PageSize": page_size,
|
|
173
|
+
"PageNum": page_num - 1,
|
|
174
|
+
"BizType": 1,
|
|
175
|
+
"PicEnable": 1,
|
|
176
|
+
"Flag": 1,
|
|
177
|
+
"LastCommentSeqNo": last_comment_seq_no,
|
|
178
|
+
"CmListUIVer": 1,
|
|
179
|
+
"BizId": biz_id,
|
|
180
|
+
"AudioEnable": 1,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return params, _processor
|
|
@@ -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
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""QQ音乐 sign"""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from base64 import b64encode
|
|
5
|
+
from hashlib import sha1
|
|
6
|
+
|
|
7
|
+
import orjson as json
|
|
8
|
+
|
|
9
|
+
PART_1_INDEXES = [23, 14, 6, 36, 16, 40, 7, 19]
|
|
10
|
+
PART_2_INDEXES = [16, 1, 32, 12, 19, 27, 8, 5]
|
|
11
|
+
SCRAMBLE_VALUES = [89, 39, 179, 150, 218, 82, 58, 252, 177, 52, 186, 123, 120, 64, 242, 133, 143, 161, 121, 179]
|
|
12
|
+
|
|
13
|
+
# JavaScript quirks emulation
|
|
14
|
+
PART_1_INDEXES = filter(lambda x: x < 40, PART_1_INDEXES)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def sign(request: dict) -> str:
|
|
18
|
+
"""QQ音乐 请求签名
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
request: 请求数据
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
签名结果
|
|
25
|
+
"""
|
|
26
|
+
hash = sha1(json.dumps(request)).hexdigest().upper()
|
|
27
|
+
|
|
28
|
+
part1 = "".join(hash[i] for i in PART_1_INDEXES)
|
|
29
|
+
part2 = "".join(hash[i] for i in PART_2_INDEXES)
|
|
30
|
+
|
|
31
|
+
part3 = bytearray(20)
|
|
32
|
+
for i, v in enumerate(SCRAMBLE_VALUES):
|
|
33
|
+
value = v ^ int(hash[i * 2 : i * 2 + 2], 16)
|
|
34
|
+
part3[i] = value
|
|
35
|
+
b64_part = re.sub(rb"[\\/+=]", b"", b64encode(part3)).decode("utf-8")
|
|
36
|
+
return f"zzc{part1}{b64_part}{part2}".lower()
|
qqmusic_api_python-0.3.5/tests/test_comments.py → qqmusic_api_python-0.3.6/tests/test_comment.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from qqmusic_api.comment import get_hot_comments
|
|
3
|
+
from qqmusic_api.comment import get_comment_count, get_hot_comments
|
|
4
4
|
|
|
5
5
|
pytestmark = pytest.mark.asyncio(loop_scope="session")
|
|
6
6
|
|
|
@@ -13,3 +13,7 @@ async def test_get_comment():
|
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
assert comment[0]["Content"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def test_get_comment_count():
|
|
19
|
+
assert await get_comment_count("103540151")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from qqmusic_api import recommend
|
|
4
|
+
|
|
5
|
+
pytestmark = pytest.mark.asyncio(loop_scope="session")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def test_get_home_feed():
|
|
9
|
+
assert await recommend.get_home_feed()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def test_get_guess_recommend():
|
|
13
|
+
assert await recommend.get_guess_recommend()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def test_get_radar_recommend():
|
|
17
|
+
assert await recommend.get_radar_recommend()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def test_get_recommend_songlist():
|
|
21
|
+
assert await recommend.get_recommend_songlist()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def test_get_recommend_newsong():
|
|
25
|
+
assert await recommend.get_recommend_newsong()
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
"""评论 API"""
|
|
2
|
-
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from qqmusic_api.utils.network import api_request
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@api_request("music.globalComment.CommentRead", "GetHotCommentList")
|
|
9
|
-
async def get_hot_comments(
|
|
10
|
-
biz_id: str,
|
|
11
|
-
page_num: int = 1,
|
|
12
|
-
page_size: int = 15,
|
|
13
|
-
last_comment_seq_no: str = "",
|
|
14
|
-
):
|
|
15
|
-
"""获取歌曲热评
|
|
16
|
-
|
|
17
|
-
Args:
|
|
18
|
-
biz_id: 歌曲 ID
|
|
19
|
-
page_num: 页码
|
|
20
|
-
page_size: 每页数量
|
|
21
|
-
last_comment_seq_no: 上一页最后一条评论 ID(可选)
|
|
22
|
-
"""
|
|
23
|
-
params = {
|
|
24
|
-
"BizType": 1,
|
|
25
|
-
"BizId": biz_id,
|
|
26
|
-
"LastCommentSeqNo": last_comment_seq_no,
|
|
27
|
-
"PageSize": page_size,
|
|
28
|
-
"PageNum": page_num,
|
|
29
|
-
"HotType": 1,
|
|
30
|
-
"WithAirborne": 0,
|
|
31
|
-
"PicEnable": 1,
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
def _processor(data: dict[str, Any]):
|
|
35
|
-
"""处理并返回结构化评论数据:
|
|
36
|
-
|
|
37
|
-
返回结构:
|
|
38
|
-
[
|
|
39
|
-
{
|
|
40
|
-
"Avatar": str, # 用户头像 URL
|
|
41
|
-
"CmId": str, # 评论 ID (后续需要获取全部子评论时需用到)
|
|
42
|
-
"PraiseNum": int, # 点赞数
|
|
43
|
-
"Nick": str, # 昵称
|
|
44
|
-
"Pic": str, # 评论配图 (可能为空)
|
|
45
|
-
"Content": str, # 评论内容
|
|
46
|
-
"SeqNo": str, # 评论序号 ID 可以用于传递给 参数: last_comment_seq_no
|
|
47
|
-
"SubComments": [ # 子评论列表
|
|
48
|
-
{
|
|
49
|
-
"Avatar": str,
|
|
50
|
-
"Nick": str,
|
|
51
|
-
"Content": str,
|
|
52
|
-
"Pic": str,
|
|
53
|
-
"PraiseNum": int,
|
|
54
|
-
"SeqNo": str
|
|
55
|
-
}
|
|
56
|
-
]
|
|
57
|
-
},
|
|
58
|
-
...
|
|
59
|
-
]
|
|
60
|
-
"""
|
|
61
|
-
comments = data.get("CommentList", {}).get("Comments", [])
|
|
62
|
-
result = []
|
|
63
|
-
|
|
64
|
-
for comment in comments:
|
|
65
|
-
item = {
|
|
66
|
-
"Avatar": comment.get("Avatar"),
|
|
67
|
-
"CmId": comment.get("CmId"),
|
|
68
|
-
"PraiseNum": comment.get("PraiseNum"),
|
|
69
|
-
"Nick": comment.get("Nick"),
|
|
70
|
-
"Pic": comment.get("Pic"),
|
|
71
|
-
"Content": comment.get("Content"),
|
|
72
|
-
"SeqNo": comment.get("SeqNo"),
|
|
73
|
-
"SubComments": [],
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
for sub in comment.get("SubComments", []):
|
|
77
|
-
sub_item = {
|
|
78
|
-
"Avatar": sub.get("Avatar"),
|
|
79
|
-
"Nick": sub.get("Nick"),
|
|
80
|
-
"Content": sub.get("Content"),
|
|
81
|
-
"Pic": sub.get("Pic"),
|
|
82
|
-
"PraiseNum": sub.get("PraiseNum"),
|
|
83
|
-
"SeqNo": sub.get("SeqNo"),
|
|
84
|
-
}
|
|
85
|
-
item["SubComments"].append(sub_item)
|
|
86
|
-
|
|
87
|
-
result.append(item)
|
|
88
|
-
|
|
89
|
-
return result
|
|
90
|
-
|
|
91
|
-
return params, _processor
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
"""QQ音乐 sign"""
|
|
2
|
-
|
|
3
|
-
import base64
|
|
4
|
-
|
|
5
|
-
import orjson as json
|
|
6
|
-
|
|
7
|
-
from .common import calc_md5
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _head(b: bytes) -> list:
|
|
11
|
-
p = [21, 4, 9, 26, 16, 20, 27, 30]
|
|
12
|
-
return [b[x] for x in p]
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def _tail(b: bytes) -> list:
|
|
16
|
-
p = [18, 11, 3, 2, 1, 7, 6, 25]
|
|
17
|
-
return [b[x] for x in p]
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _middle(b: bytes) -> list:
|
|
21
|
-
zd = {
|
|
22
|
-
"0": 0,
|
|
23
|
-
"1": 1,
|
|
24
|
-
"2": 2,
|
|
25
|
-
"3": 3,
|
|
26
|
-
"4": 4,
|
|
27
|
-
"5": 5,
|
|
28
|
-
"6": 6,
|
|
29
|
-
"7": 7,
|
|
30
|
-
"8": 8,
|
|
31
|
-
"9": 9,
|
|
32
|
-
"A": 10,
|
|
33
|
-
"B": 11,
|
|
34
|
-
"C": 12,
|
|
35
|
-
"D": 13,
|
|
36
|
-
"E": 14,
|
|
37
|
-
"F": 15,
|
|
38
|
-
}
|
|
39
|
-
ol = [212, 45, 80, 68, 195, 163, 163, 203, 157, 220, 254, 91, 204, 79, 104, 6]
|
|
40
|
-
res = []
|
|
41
|
-
j = 0
|
|
42
|
-
for i in range(0, len(b), 2):
|
|
43
|
-
one = zd[chr(b[i])]
|
|
44
|
-
two = zd[chr(b[i + 1])]
|
|
45
|
-
r = one * 16 ^ two
|
|
46
|
-
res.append(r ^ ol[j])
|
|
47
|
-
j += 1
|
|
48
|
-
return res
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def sign(request: dict) -> str:
|
|
52
|
-
"""QQ音乐 请求签名
|
|
53
|
-
|
|
54
|
-
Args:
|
|
55
|
-
request: 请求数据
|
|
56
|
-
|
|
57
|
-
Returns:
|
|
58
|
-
签名结果
|
|
59
|
-
"""
|
|
60
|
-
md5_str = calc_md5(json.dumps(request)).upper().encode("utf-8")
|
|
61
|
-
|
|
62
|
-
h = _head(md5_str)
|
|
63
|
-
e = _tail(md5_str)
|
|
64
|
-
ls = _middle(md5_str)
|
|
65
|
-
m = base64.b64encode(bytes(ls)).decode("utf-8")
|
|
66
|
-
|
|
67
|
-
res = "zzb" + "".join(map(chr, h)) + m + "".join(map(chr, e))
|
|
68
|
-
return res.lower().replace("/", "").replace("+", "").replace("=", "")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qqmusic_api_python-0.3.5 → qqmusic_api_python-0.3.6}/qqmusic_api/exceptions/api_exception.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|