qqmusic-api-python 0.1.0__py3-none-any.whl
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/__init__.py +4 -0
- qqmusic_api/api/album.py +40 -0
- qqmusic_api/api/login.py +437 -0
- qqmusic_api/api/mv.py +115 -0
- qqmusic_api/api/search.py +168 -0
- qqmusic_api/api/song.py +389 -0
- qqmusic_api/api/songlist.py +81 -0
- qqmusic_api/api/top.py +98 -0
- qqmusic_api/data/api/album.json +20 -0
- qqmusic_api/data/api/login.json +69 -0
- qqmusic_api/data/api/mv.json +26 -0
- qqmusic_api/data/api/search.json +73 -0
- qqmusic_api/data/api/singer.json +1 -0
- qqmusic_api/data/api/song.json +105 -0
- qqmusic_api/data/api/songlist.json +17 -0
- qqmusic_api/data/api/top.json +20 -0
- qqmusic_api/data/file_type.json +50 -0
- qqmusic_api/data/search_type.json +11 -0
- qqmusic_api/exceptions.py +121 -0
- qqmusic_api/settings.py +22 -0
- qqmusic_api/utils/__init__.py +0 -0
- qqmusic_api/utils/common.py +140 -0
- qqmusic_api/utils/credential.py +92 -0
- qqmusic_api/utils/network.py +249 -0
- qqmusic_api/utils/qimei.py +267 -0
- qqmusic_api_python-0.1.0.dist-info/LICENSE +21 -0
- qqmusic_api_python-0.1.0.dist-info/METADATA +99 -0
- qqmusic_api_python-0.1.0.dist-info/RECORD +29 -0
- qqmusic_api_python-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from qqmusic_api.api.song import Song
|
|
4
|
+
|
|
5
|
+
from ..utils.common import get_api, parse_song_info, random_searchID
|
|
6
|
+
from ..utils.network import Api
|
|
7
|
+
|
|
8
|
+
API = get_api("search")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SearchType(Enum):
|
|
12
|
+
"""
|
|
13
|
+
搜索类型
|
|
14
|
+
|
|
15
|
+
+ SONG: 歌曲
|
|
16
|
+
+ SINGER: 歌手
|
|
17
|
+
+ ALBUM: 专辑
|
|
18
|
+
+ SONGLIST: 歌单
|
|
19
|
+
+ MV: MV
|
|
20
|
+
+ LYRIC: 歌词
|
|
21
|
+
+ USER: 用户
|
|
22
|
+
+ AUDIO_SONG: 节目专辑
|
|
23
|
+
+ AUDIO: 节目
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
SONG = 0
|
|
27
|
+
SINGER = 1
|
|
28
|
+
ALBUM = 2
|
|
29
|
+
SONGLIST = 3
|
|
30
|
+
MV = 4
|
|
31
|
+
LYRIC = 7
|
|
32
|
+
USER = 8
|
|
33
|
+
AUDIO_ALBUM = 15
|
|
34
|
+
AUDIO = 18
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def hotkey() -> list[dict]:
|
|
38
|
+
"""
|
|
39
|
+
获取热搜词
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
list: 热搜词列表,k为热搜词,n为搜索量
|
|
43
|
+
"""
|
|
44
|
+
params = {"search_id": random_searchID()}
|
|
45
|
+
res = await Api(**API["hotkey"]).update_params(**params).result
|
|
46
|
+
data = res.get("vec_hotkey", [])
|
|
47
|
+
return [{"k": hotkey["query"], "n": hotkey["score"]} for hotkey in data]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def complete(keyword: str, highlight: bool = False) -> list[str]:
|
|
51
|
+
"""
|
|
52
|
+
搜索词补全
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
keyword: 关键词
|
|
56
|
+
highlight: 是否高亮关键词. Defaluts to False
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
list: 补全结果
|
|
60
|
+
"""
|
|
61
|
+
params = {
|
|
62
|
+
"search_id": random_searchID(),
|
|
63
|
+
"query": keyword,
|
|
64
|
+
"num_per_page": 0,
|
|
65
|
+
"page_idx": 0,
|
|
66
|
+
}
|
|
67
|
+
res = await Api(**API["complete"]).update_params(**params).result
|
|
68
|
+
data = res["items"]
|
|
69
|
+
if highlight:
|
|
70
|
+
return [item["hint_hilight"] for item in data]
|
|
71
|
+
else:
|
|
72
|
+
return [item["hint"] for item in data]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def quick_search(keyword: str) -> dict:
|
|
76
|
+
"""
|
|
77
|
+
快速搜索
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
keyword: 关键词
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
dict: 包含专辑,歌手,歌曲的简略信息
|
|
84
|
+
"""
|
|
85
|
+
res = await Api(**API["quick_search"]).update_params(key=keyword).result
|
|
86
|
+
return res["data"]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def general_search(keyword: str, page: int = 1, highlight: bool = False) -> dict:
|
|
90
|
+
"""
|
|
91
|
+
综合搜索
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
keyword: 关键词
|
|
95
|
+
page: 页码. Defaluts to 1
|
|
96
|
+
highlight: 是否高亮关键词. Defaluts to False
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
dict: 包含直接结果,歌曲,歌手,专辑,歌单,mv等.
|
|
100
|
+
"""
|
|
101
|
+
params = {
|
|
102
|
+
"search_id": random_searchID(),
|
|
103
|
+
"search_type": 100,
|
|
104
|
+
"query": keyword,
|
|
105
|
+
"page_id": page,
|
|
106
|
+
"highlight": highlight,
|
|
107
|
+
"grp": 1,
|
|
108
|
+
}
|
|
109
|
+
res = await Api(**API["general_search"]).update_params(**params).result
|
|
110
|
+
data = res["body"]
|
|
111
|
+
return {
|
|
112
|
+
"direct": data["direct_result"]["direct_group"],
|
|
113
|
+
"song": [parse_song_info(d) for d in data["item_song"]["items"]],
|
|
114
|
+
"singer": data["singer"]["items"],
|
|
115
|
+
"album": data["item_album"]["items"],
|
|
116
|
+
"songlist": data["item_songlist"]["items"],
|
|
117
|
+
"mv": data["item_mv"]["items"],
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def search_by_type(
|
|
122
|
+
keyword: str,
|
|
123
|
+
search_type: SearchType = SearchType.SONG,
|
|
124
|
+
num: int = 10,
|
|
125
|
+
page: int = 1,
|
|
126
|
+
selectors: dict = {},
|
|
127
|
+
highlight: bool = False,
|
|
128
|
+
) -> list[Song]:
|
|
129
|
+
"""
|
|
130
|
+
搜索
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
keyword: 关键词
|
|
134
|
+
search_type: 搜索类型. Defaluts to SearchType.SONG
|
|
135
|
+
num: 返回数量. Defaluts to 10
|
|
136
|
+
page: 页码. Defaluts to 1
|
|
137
|
+
selectors: 选择器. Defaluts to {}
|
|
138
|
+
highlight: 是否高亮关键词. Defaluts to False
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
list: 搜索结果.
|
|
142
|
+
"""
|
|
143
|
+
params = {
|
|
144
|
+
"searchid": random_searchID(),
|
|
145
|
+
"query": keyword,
|
|
146
|
+
"search_type": search_type.value,
|
|
147
|
+
"num_per_page": num,
|
|
148
|
+
"page_num": page,
|
|
149
|
+
"highlight": highlight,
|
|
150
|
+
"page_id": page,
|
|
151
|
+
"selectors": selectors,
|
|
152
|
+
}
|
|
153
|
+
res = await Api(**API["desktop_search_by_type"]).update_params(**params).result
|
|
154
|
+
types = {
|
|
155
|
+
SearchType.SONG: "song",
|
|
156
|
+
SearchType.SINGER: "singer",
|
|
157
|
+
SearchType.ALBUM: "album",
|
|
158
|
+
SearchType.SONGLIST: "songlist",
|
|
159
|
+
SearchType.MV: "mv",
|
|
160
|
+
SearchType.LYRIC: "song",
|
|
161
|
+
SearchType.USER: "user",
|
|
162
|
+
SearchType.AUDIO_ALBUM: "album",
|
|
163
|
+
SearchType.AUDIO: "song",
|
|
164
|
+
}
|
|
165
|
+
data = res["body"][types[search_type]]["list"]
|
|
166
|
+
if search_type in [SearchType.SONG, SearchType.LYRIC, SearchType.AUDIO]:
|
|
167
|
+
return [Song.from_dict(song) for song in data]
|
|
168
|
+
return data
|
qqmusic_api/api/song.py
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from ..exceptions import ArgsException
|
|
5
|
+
from ..utils.common import get_api, parse_song_info, random_string
|
|
6
|
+
from ..utils.credential import Credential
|
|
7
|
+
from ..utils.network import Api
|
|
8
|
+
|
|
9
|
+
API = get_api("song")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SongFileType(Enum):
|
|
13
|
+
"""
|
|
14
|
+
歌曲文件类型
|
|
15
|
+
|
|
16
|
+
+ NEW_0: 臻品母带2.0
|
|
17
|
+
+ NEW_1: 臻品全景声
|
|
18
|
+
+ NEW_2: 臻品音质2.0
|
|
19
|
+
+ FLAC: 无损音频压缩格式
|
|
20
|
+
+ OGG_192: OGG 格式,192kbps
|
|
21
|
+
+ OGG_96: OGG 格式,96kbps
|
|
22
|
+
+ MP3_320: MP3 格式,320kbps
|
|
23
|
+
+ MP3_128: MP3 格式,128kbps
|
|
24
|
+
+ ACC_192: AAC 格式,192kbps
|
|
25
|
+
+ ACC_96: AAC 格式,96kbps
|
|
26
|
+
+ ACC_48: AAC 格式,48kbps
|
|
27
|
+
+ TRY: 试听文件
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
NEW_0 = ("AI00", ".flac")
|
|
31
|
+
NEW_1 = ("Q000", ".flac")
|
|
32
|
+
NEW_2 = ("Q001", ".flac")
|
|
33
|
+
FLAC = ("F000", ".flac")
|
|
34
|
+
OGG_192 = ("O600", ".ogg")
|
|
35
|
+
OGG_96 = ("O400", ".ogg")
|
|
36
|
+
MP3_320 = ("M800", ".mp3")
|
|
37
|
+
MP3_128 = ("M500", ".mp3")
|
|
38
|
+
ACC_192 = ("C600", ".m4a")
|
|
39
|
+
ACC_96 = ("C400", ".m4a")
|
|
40
|
+
ACC_48 = ("C200", ".m4a")
|
|
41
|
+
TRY = ("RS02", ".mp3")
|
|
42
|
+
|
|
43
|
+
def __init__(self, start_code: str, extension: str):
|
|
44
|
+
self.__start_code = start_code
|
|
45
|
+
self.__extension = extension
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def s(self) -> str:
|
|
49
|
+
return self.__start_code
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def e(self) -> str:
|
|
53
|
+
return self.__extension
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class UrlType(Enum):
|
|
57
|
+
"""
|
|
58
|
+
歌曲文件链接类型
|
|
59
|
+
+ PLAY: 播放链接
|
|
60
|
+
+ DOWNLOAD: 下载链接
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
PLAY = "play_url"
|
|
64
|
+
DOWNLOAD = "download_url"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Song:
|
|
68
|
+
"""
|
|
69
|
+
歌曲类
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
mid: Optional[str] = None,
|
|
75
|
+
id: Optional[int] = None,
|
|
76
|
+
credential: Optional[Credential] = None,
|
|
77
|
+
):
|
|
78
|
+
"""
|
|
79
|
+
Args:
|
|
80
|
+
mid: 歌曲 mid. 歌曲 id 和歌曲 mid 必须提供其中之一
|
|
81
|
+
id: 歌曲 id. 歌曲 id 和歌曲 mid 必须提供其中之一
|
|
82
|
+
credential: Credential 类. Defaluts to None
|
|
83
|
+
"""
|
|
84
|
+
# ID 检查
|
|
85
|
+
if mid is None and id is None:
|
|
86
|
+
raise ArgsException("请至少提供 mid 和 id 中的其中一个参数。")
|
|
87
|
+
self._mid = mid
|
|
88
|
+
self._id = id
|
|
89
|
+
self.credential = Credential() if credential is None else credential
|
|
90
|
+
self._info: Optional[dict] = None
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def from_dict(cls, info: dict):
|
|
94
|
+
"""
|
|
95
|
+
从字典新建 Song
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
info: 歌曲字典
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Song: 歌曲类
|
|
102
|
+
"""
|
|
103
|
+
info = parse_song_info(info)
|
|
104
|
+
s = cls(id=info["info"]["id"], mid=info["info"]["mid"])
|
|
105
|
+
s._info = info
|
|
106
|
+
return s
|
|
107
|
+
|
|
108
|
+
async def __get_info(self):
|
|
109
|
+
"""
|
|
110
|
+
获取歌曲必要信息
|
|
111
|
+
"""
|
|
112
|
+
if not self._info:
|
|
113
|
+
if self._mid:
|
|
114
|
+
self._info = (await query_by_mid([self._mid]))[0]
|
|
115
|
+
elif self._id:
|
|
116
|
+
self._info = (await query_by_id([self._id]))[0]
|
|
117
|
+
return self._info
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
async def mid(self) -> str:
|
|
121
|
+
"""
|
|
122
|
+
获取歌曲 mid
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
str: mid
|
|
126
|
+
"""
|
|
127
|
+
if not self._mid:
|
|
128
|
+
self._mid = (await self.__get_info())["info"]["mid"]
|
|
129
|
+
return str(self._mid)
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
async def id(self) -> int:
|
|
133
|
+
"""
|
|
134
|
+
获取歌曲 id
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
int: id
|
|
138
|
+
"""
|
|
139
|
+
if not self._id:
|
|
140
|
+
self._id = (await self.__get_info())["info"]["id"]
|
|
141
|
+
return int(self._id)
|
|
142
|
+
|
|
143
|
+
async def __prepare_param(self, is_mid: bool = False, is_id: bool = False) -> dict:
|
|
144
|
+
"""
|
|
145
|
+
准备请求参数
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
is_mid: 是否强制使用 mid. Defaults to False
|
|
149
|
+
is_id: 是否强制使用 id. Defaults to False
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
dict: 请求参数
|
|
153
|
+
"""
|
|
154
|
+
if is_mid and is_id:
|
|
155
|
+
raise ArgsException("参数错误")
|
|
156
|
+
if is_mid:
|
|
157
|
+
return {"songmid": await self.mid}
|
|
158
|
+
elif is_id:
|
|
159
|
+
return {"songid": await self.id}
|
|
160
|
+
elif self._mid:
|
|
161
|
+
return {"songmid": self._mid}
|
|
162
|
+
elif self._id:
|
|
163
|
+
return {"songid": self._id}
|
|
164
|
+
else:
|
|
165
|
+
return {}
|
|
166
|
+
|
|
167
|
+
async def get_info(self) -> dict:
|
|
168
|
+
"""
|
|
169
|
+
获取歌曲基本信息
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
dict: 基本信息
|
|
173
|
+
"""
|
|
174
|
+
return (await self.__get_info())["info"]
|
|
175
|
+
|
|
176
|
+
async def get_detail(self) -> dict:
|
|
177
|
+
"""
|
|
178
|
+
获取歌曲详细信息
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
dict: 详细信息
|
|
182
|
+
"""
|
|
183
|
+
param = await self.__prepare_param()
|
|
184
|
+
if "songmid" in param:
|
|
185
|
+
param["song_mid"] = param.pop("songmid")
|
|
186
|
+
if "songid" in param:
|
|
187
|
+
param["song_id"] = param.pop("songid")
|
|
188
|
+
return await Api(**API["detail"]).update_params(**param).result
|
|
189
|
+
|
|
190
|
+
async def get_similar_song(self) -> list[dict]:
|
|
191
|
+
"""
|
|
192
|
+
获取歌曲相似歌曲
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
list: 歌曲信息
|
|
196
|
+
"""
|
|
197
|
+
param = await self.__prepare_param(is_id=True)
|
|
198
|
+
res = await Api(**API["similar"]).update_params(**param).result
|
|
199
|
+
return [parse_song_info(song["track"]) for song in res["vecSong"]]
|
|
200
|
+
|
|
201
|
+
async def get_labels(self) -> list[dict]:
|
|
202
|
+
"""
|
|
203
|
+
获取歌曲标签
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
list: 标签信息
|
|
207
|
+
"""
|
|
208
|
+
param = await self.__prepare_param(is_id=True)
|
|
209
|
+
return (await Api(**API["labels"]).update_params(**param).result)["labels"]
|
|
210
|
+
|
|
211
|
+
async def get_related_playlist(self) -> list[dict]:
|
|
212
|
+
"""
|
|
213
|
+
获取歌曲相关歌单
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
list: 歌单信息
|
|
217
|
+
"""
|
|
218
|
+
param = await self.__prepare_param(is_id=True)
|
|
219
|
+
return (await Api(**API["playlist"]).update_params(**param).result)[
|
|
220
|
+
"vecPlaylist"
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
async def get_related_mv(self) -> list[dict]:
|
|
224
|
+
"""
|
|
225
|
+
获取歌曲相关MV
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
list: MV信息
|
|
229
|
+
"""
|
|
230
|
+
param = await self.__prepare_param()
|
|
231
|
+
return (await Api(**API["mv"]).update_params(**param).result)["list"]
|
|
232
|
+
|
|
233
|
+
async def get_other_version(self) -> list[dict]:
|
|
234
|
+
"""
|
|
235
|
+
获取歌曲其他版本
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
list: 歌曲信息
|
|
239
|
+
"""
|
|
240
|
+
param = await self.__prepare_param()
|
|
241
|
+
res = await Api(**API["other"]).update_params(**param).result
|
|
242
|
+
return [parse_song_info(song) for song in res["versionList"]]
|
|
243
|
+
|
|
244
|
+
async def get_sheet(self) -> list[dict]:
|
|
245
|
+
"""
|
|
246
|
+
获取歌曲相关曲谱
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
list: 曲谱信息
|
|
250
|
+
"""
|
|
251
|
+
param = await self.__prepare_param(is_mid=True)
|
|
252
|
+
param["scoreType"] = -1
|
|
253
|
+
return (await Api(**API["sheet"]).update_params(**param).result)["result"]
|
|
254
|
+
|
|
255
|
+
async def get_producer(self) -> list[dict]:
|
|
256
|
+
"""
|
|
257
|
+
获取歌曲制作信息
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
list: 人员信息
|
|
261
|
+
"""
|
|
262
|
+
param = await self.__prepare_param()
|
|
263
|
+
return (await Api(**API["producer"]).update_params(**param).result)["Lst"]
|
|
264
|
+
|
|
265
|
+
async def get_url(
|
|
266
|
+
self,
|
|
267
|
+
file_type: SongFileType = SongFileType.MP3_128,
|
|
268
|
+
url_type: UrlType = UrlType.PLAY,
|
|
269
|
+
) -> dict[str, str]:
|
|
270
|
+
"""
|
|
271
|
+
获取歌曲文件链接
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
file_type: 歌曲文件类型. Defaults to SongFileType.MP3_128
|
|
275
|
+
url_type: 歌曲链接类型. Defaults to UrlType.PLAY
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
dict: 链接字典
|
|
279
|
+
"""
|
|
280
|
+
return await get_song_urls([await self.mid], file_type, url_type)
|
|
281
|
+
|
|
282
|
+
async def get_file_size(self, file_type: Optional[SongFileType] = None) -> dict:
|
|
283
|
+
"""
|
|
284
|
+
获取歌曲文件大小
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
file_type: 指定文件类型. Defaults to None
|
|
288
|
+
|
|
289
|
+
Return:
|
|
290
|
+
dict: 文件大小
|
|
291
|
+
"""
|
|
292
|
+
size = (await self.__get_info())["file"]
|
|
293
|
+
if file_type:
|
|
294
|
+
name = file_type.name.lower()
|
|
295
|
+
size = {name: size[name]}
|
|
296
|
+
return size
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
async def query_by_id(id: list[int]) -> list[dict]:
|
|
300
|
+
"""
|
|
301
|
+
根据 mid 获取歌曲信息
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
mid: 歌曲 mid 列表
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
list: 歌曲信息
|
|
308
|
+
"""
|
|
309
|
+
param = {
|
|
310
|
+
"ids": id,
|
|
311
|
+
"mids": [],
|
|
312
|
+
"types": [0 for _ in range(len(id))],
|
|
313
|
+
"modify_stamp": [0 for _ in range(len(id))],
|
|
314
|
+
"ctx": 0,
|
|
315
|
+
"client": 1,
|
|
316
|
+
}
|
|
317
|
+
res = await Api(**API["query"]).update_params(**param).result
|
|
318
|
+
tracks = res["tracks"]
|
|
319
|
+
return [parse_song_info(song) for song in tracks]
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
async def query_by_mid(mid: list[str]) -> list[dict]:
|
|
323
|
+
"""
|
|
324
|
+
根据 mid 获取歌曲信息
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
mid: 歌曲 mid 列表
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
list: 歌曲信息
|
|
331
|
+
"""
|
|
332
|
+
param = {
|
|
333
|
+
"ids": [],
|
|
334
|
+
"mids": mid,
|
|
335
|
+
"types": [0 for _ in range(len(mid))],
|
|
336
|
+
"modify_stamp": [0 for _ in range(len(mid))],
|
|
337
|
+
"ctx": 0,
|
|
338
|
+
"client": 1,
|
|
339
|
+
}
|
|
340
|
+
res = await Api(**API["query"]).update_params(**param).result
|
|
341
|
+
tracks = res["tracks"]
|
|
342
|
+
return [parse_song_info(song) for song in tracks]
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
async def get_song_urls(
|
|
346
|
+
mid: list[str],
|
|
347
|
+
file_type: SongFileType = SongFileType.MP3_128,
|
|
348
|
+
url_type: UrlType = UrlType.PLAY,
|
|
349
|
+
credential: Optional[Credential] = None,
|
|
350
|
+
) -> dict[str, str]:
|
|
351
|
+
"""
|
|
352
|
+
获取歌曲文件链接
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
mid: 歌曲 mid
|
|
356
|
+
file_type: 歌曲文件类型. Defaults to SongFileType.MP3_128
|
|
357
|
+
url_type: 歌曲链接类型. Defaults to UrlType.PLAY
|
|
358
|
+
credential: Credential 类. Defaluts to None
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
dict: 链接字典
|
|
362
|
+
"""
|
|
363
|
+
if credential is None:
|
|
364
|
+
credential = Credential()
|
|
365
|
+
# 分割 id,单次最大请求100
|
|
366
|
+
mid_list = [mid[i : i + 100] for i in range(0, len(mid), 100)]
|
|
367
|
+
# 选择文件域名
|
|
368
|
+
domain = (
|
|
369
|
+
"https://isure.stream.qqmusic.qq.com/"
|
|
370
|
+
if url_type == UrlType.PLAY
|
|
371
|
+
else "https://dl.stream.qqmusic.qq.com/"
|
|
372
|
+
)
|
|
373
|
+
api = Api(**API[url_type.value], credential=credential)
|
|
374
|
+
urls = {}
|
|
375
|
+
for mid in mid_list:
|
|
376
|
+
# 构造请求参数
|
|
377
|
+
file_name = [f"{file_type.s}{_}{_}{file_type.e}" for _ in mid]
|
|
378
|
+
param = {
|
|
379
|
+
"filename": file_name,
|
|
380
|
+
"guid": random_string(32, "abcdef1234567890"),
|
|
381
|
+
"songmid": mid,
|
|
382
|
+
"songtype": [1 for _ in range(len(mid))],
|
|
383
|
+
}
|
|
384
|
+
res = await api.update_params(**param).result
|
|
385
|
+
data = res["midurlinfo"]
|
|
386
|
+
for info in data:
|
|
387
|
+
song_url = domain + info["wifiurl"] if info["wifiurl"] else ""
|
|
388
|
+
urls[info["songmid"]] = song_url
|
|
389
|
+
return urls
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from qqmusic_api.api.song import Song
|
|
4
|
+
|
|
5
|
+
from ..utils.common import get_api
|
|
6
|
+
from ..utils.network import Api
|
|
7
|
+
|
|
8
|
+
API = get_api("songlist")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Songlist:
|
|
12
|
+
"""
|
|
13
|
+
歌单类
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, id: int):
|
|
17
|
+
"""
|
|
18
|
+
Args:
|
|
19
|
+
id: 歌单 ID
|
|
20
|
+
"""
|
|
21
|
+
self.id = id
|
|
22
|
+
self._info: Optional[dict] = None
|
|
23
|
+
|
|
24
|
+
async def __get_info(self):
|
|
25
|
+
if not self._info:
|
|
26
|
+
param = {
|
|
27
|
+
"disstid": self.id,
|
|
28
|
+
"dirid": 0,
|
|
29
|
+
"tag": True,
|
|
30
|
+
"song_num": 0,
|
|
31
|
+
"userinfo": True,
|
|
32
|
+
"orderlist": True,
|
|
33
|
+
}
|
|
34
|
+
result = (
|
|
35
|
+
await Api(**API["detail"])
|
|
36
|
+
.update_params(**param)
|
|
37
|
+
.update_headers(Referer="")
|
|
38
|
+
.result
|
|
39
|
+
)
|
|
40
|
+
self._info = result # type: ignore
|
|
41
|
+
return self._info
|
|
42
|
+
|
|
43
|
+
async def get_detail(self) -> dict:
|
|
44
|
+
"""
|
|
45
|
+
获取歌单详细信息
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
dict: 歌单信息
|
|
49
|
+
"""
|
|
50
|
+
result = await self.__get_info()
|
|
51
|
+
return result["dirinfo"]
|
|
52
|
+
|
|
53
|
+
async def get_song(self) -> list[Song]:
|
|
54
|
+
"""
|
|
55
|
+
获取歌单歌曲
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
list: 歌单歌曲
|
|
59
|
+
"""
|
|
60
|
+
result = await self.__get_info()
|
|
61
|
+
return [Song.from_dict(song) for song in result["songlist"]]
|
|
62
|
+
|
|
63
|
+
async def get_song_tag(self) -> list[dict]:
|
|
64
|
+
"""
|
|
65
|
+
获取歌单歌曲标签
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
list: 歌单歌曲标签
|
|
69
|
+
"""
|
|
70
|
+
result = await self.__get_info()
|
|
71
|
+
return result["songtag"]
|
|
72
|
+
|
|
73
|
+
async def get_song_mid(self) -> list[dict]:
|
|
74
|
+
"""
|
|
75
|
+
获取歌单歌曲全部 mid
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
list: 歌单歌曲 mid
|
|
79
|
+
"""
|
|
80
|
+
result = await self.__get_info()
|
|
81
|
+
return [song["mid"] for song in result["songlist"]]
|