qqmusic-api-python 0.3.1__tar.gz → 0.3.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.3.1 → qqmusic_api_python-0.3.3}/PKG-INFO +5 -3
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/README.md +4 -2
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/__init__.py +1 -1
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/mv.py +1 -1
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/singer.py +15 -27
- qqmusic_api_python-0.3.3/qqmusic_api/songlist.py +146 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/user.py +11 -11
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/utils/network.py +17 -11
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/utils/qimei.py +2 -2
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/utils/session.py +9 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/tests/test_songlist.py +4 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/tests/test_user.py +16 -0
- qqmusic_api_python-0.3.1/qqmusic_api/songlist.py +0 -49
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/.gitignore +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/LICENSE +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/pyproject.toml +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/album.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/exceptions/__init__.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/exceptions/api_exception.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/login.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/lyric.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/search.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/song.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/top.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/utils/__init__.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/utils/common.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/utils/credential.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/utils/device.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/utils/sign.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/qqmusic_api/utils/tripledes.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/tests/test_album.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/tests/test_login.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/tests/test_lyric.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/tests/test_mv.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/tests/test_qimei.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/tests/test_search.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/tests/test_session.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/tests/test_sign.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/tests/test_singer.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/tests/test_song.py +0 -0
- {qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/tests/test_top.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qqmusic-api-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
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
|
|
@@ -69,8 +69,10 @@ Description-Content-Type: text/markdown
|
|
|
69
69
|
|
|
70
70
|
## 依赖
|
|
71
71
|
|
|
72
|
-
-
|
|
73
|
-
-
|
|
72
|
+
- Cryptography
|
|
73
|
+
- HTTPX
|
|
74
|
+
- aiocache
|
|
75
|
+
- orjson
|
|
74
76
|
|
|
75
77
|
## 快速上手
|
|
76
78
|
|
|
@@ -4,7 +4,7 @@ from . import album, login, lyric, mv, search, singer, song, songlist, top, user
|
|
|
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.3"
|
|
8
8
|
|
|
9
9
|
logger = logging.getLogger("qqmusicapi")
|
|
10
10
|
|
|
@@ -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", exclude_params=["guid"])
|
|
47
47
|
async def get_mv_urls(vids: list[str]):
|
|
48
48
|
"""获取 MV 播放链接
|
|
49
49
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from typing import Any, Literal, cast
|
|
5
5
|
|
|
6
|
-
from .utils.network import RequestGroup, api_request
|
|
6
|
+
from .utils.network import NO_PROCESSOR, RequestGroup, api_request
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class AreaType(Enum):
|
|
@@ -133,7 +133,7 @@ async def get_singer_list(
|
|
|
133
133
|
)
|
|
134
134
|
|
|
135
135
|
|
|
136
|
-
@api_request("music.musichallSinger.SingerList", "GetSingerListIndex")
|
|
136
|
+
@api_request("music.musichallSinger.SingerList", "GetSingerListIndex", catch_error_code=[104500])
|
|
137
137
|
async def get_singer_list_index(
|
|
138
138
|
area: int | AreaType = AreaType.ALL,
|
|
139
139
|
sex: int | SexType = SexType.ALL,
|
|
@@ -164,10 +164,7 @@ async def get_singer_list_index(
|
|
|
164
164
|
"index": index,
|
|
165
165
|
"sin": sin,
|
|
166
166
|
"cur_page": cur_page,
|
|
167
|
-
},
|
|
168
|
-
dict[str, Any],
|
|
169
|
-
data,
|
|
170
|
-
)
|
|
167
|
+
}, NO_PROCESSOR
|
|
171
168
|
|
|
172
169
|
|
|
173
170
|
async def get_singer_list_index_all(
|
|
@@ -175,7 +172,7 @@ async def get_singer_list_index_all(
|
|
|
175
172
|
sex: int | SexType = SexType.ALL,
|
|
176
173
|
genre: int | GenreType = GenreType.ALL,
|
|
177
174
|
index: int | IndexType = IndexType.ALL,
|
|
178
|
-
):
|
|
175
|
+
) -> list[dict[str, Any]]:
|
|
179
176
|
"""获取所有歌手列表
|
|
180
177
|
|
|
181
178
|
Args:
|
|
@@ -206,7 +203,7 @@ async def get_singer_list_index_all(
|
|
|
206
203
|
|
|
207
204
|
for data in await rg.execute():
|
|
208
205
|
singer_list.extend(data["singerlist"])
|
|
209
|
-
return
|
|
206
|
+
return singer_list
|
|
210
207
|
|
|
211
208
|
|
|
212
209
|
@api_request("music.UnifiedHomepage.UnifiedHomepageSrv", "GetHomepageHeader")
|
|
@@ -216,7 +213,7 @@ async def get_info(mid: str):
|
|
|
216
213
|
Args:
|
|
217
214
|
mid: 歌手 mid
|
|
218
215
|
"""
|
|
219
|
-
return {"SingerMid": mid},
|
|
216
|
+
return {"SingerMid": mid}, NO_PROCESSOR
|
|
220
217
|
|
|
221
218
|
|
|
222
219
|
@api_request("music.UnifiedHomepage.UnifiedHomepageSrv", "GetHomepageTabDetail")
|
|
@@ -305,13 +302,10 @@ async def get_songs_list(mid: str, number: int = 10, begin: int = 0):
|
|
|
305
302
|
"order": 1,
|
|
306
303
|
"number": number,
|
|
307
304
|
"begin": begin,
|
|
308
|
-
},
|
|
309
|
-
dict[str, Any],
|
|
310
|
-
data,
|
|
311
|
-
)
|
|
305
|
+
}, NO_PROCESSOR
|
|
312
306
|
|
|
313
307
|
|
|
314
|
-
async def get_songs_list_all(mid: str):
|
|
308
|
+
async def get_songs_list_all(mid: str) -> list[dict[str, Any]]:
|
|
315
309
|
"""获取歌手所有歌曲列表
|
|
316
310
|
|
|
317
311
|
Args:
|
|
@@ -332,7 +326,7 @@ async def get_songs_list_all(mid: str):
|
|
|
332
326
|
for res in response:
|
|
333
327
|
songs.extend([song["songInfo"] for song in res["songList"]])
|
|
334
328
|
|
|
335
|
-
return
|
|
329
|
+
return songs
|
|
336
330
|
|
|
337
331
|
|
|
338
332
|
@api_request("music.musichallAlbum.AlbumListServer", "GetAlbumList")
|
|
@@ -349,13 +343,10 @@ async def get_album_list(mid: str, number: int = 10, begin: int = 0):
|
|
|
349
343
|
"order": 1,
|
|
350
344
|
"number": number,
|
|
351
345
|
"begin": begin,
|
|
352
|
-
},
|
|
353
|
-
dict[str, Any],
|
|
354
|
-
data,
|
|
355
|
-
)
|
|
346
|
+
}, NO_PROCESSOR
|
|
356
347
|
|
|
357
348
|
|
|
358
|
-
async def get_album_list_all(mid: str):
|
|
349
|
+
async def get_album_list_all(mid: str) -> list[dict[str, Any]]:
|
|
359
350
|
"""获取歌手所有专辑列表
|
|
360
351
|
|
|
361
352
|
Args:
|
|
@@ -376,7 +367,7 @@ async def get_album_list_all(mid: str):
|
|
|
376
367
|
for res in response:
|
|
377
368
|
albums.extend(res["albumList"])
|
|
378
369
|
|
|
379
|
-
return
|
|
370
|
+
return albums
|
|
380
371
|
|
|
381
372
|
|
|
382
373
|
@api_request("MvService.MvInfoProServer", "GetSingerMvList")
|
|
@@ -393,13 +384,10 @@ async def get_mv_list(mid: str, number: int = 10, begin: int = 0):
|
|
|
393
384
|
"order": 1,
|
|
394
385
|
"count": number,
|
|
395
386
|
"start": begin,
|
|
396
|
-
},
|
|
397
|
-
dict[str, Any],
|
|
398
|
-
data,
|
|
399
|
-
)
|
|
387
|
+
}, NO_PROCESSOR
|
|
400
388
|
|
|
401
389
|
|
|
402
|
-
async def get_mv_list_all(mid: str):
|
|
390
|
+
async def get_mv_list_all(mid: str) -> list[dict[str, Any]]:
|
|
403
391
|
"""获取歌手所有专辑列表
|
|
404
392
|
|
|
405
393
|
Args:
|
|
@@ -420,4 +408,4 @@ async def get_mv_list_all(mid: str):
|
|
|
420
408
|
for res in response:
|
|
421
409
|
mvs.extend(res["list"])
|
|
422
410
|
|
|
423
|
-
return
|
|
411
|
+
return mvs
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""歌单相关 API"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, cast
|
|
4
|
+
|
|
5
|
+
from .utils.credential import Credential
|
|
6
|
+
from .utils.network import RequestGroup, api_request
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@api_request("music.srfDissInfo.DissInfo", "CgiGetDiss")
|
|
10
|
+
async def get_detail(
|
|
11
|
+
songlist_id: int,
|
|
12
|
+
dirid: int = 0,
|
|
13
|
+
num: int = 10,
|
|
14
|
+
page: int = 1,
|
|
15
|
+
onlysong: bool = False,
|
|
16
|
+
tag: bool = True,
|
|
17
|
+
userinfo: bool = True,
|
|
18
|
+
):
|
|
19
|
+
"""获取歌单详细信息和歌曲
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
songlist_id: 歌单 ID
|
|
23
|
+
dirid: 歌单 dirid
|
|
24
|
+
num: 返回数量
|
|
25
|
+
page: 页码
|
|
26
|
+
onlysong: 是否仅返回歌曲信息(优先级最大)
|
|
27
|
+
tag: 是否返回歌单的标签信息
|
|
28
|
+
userinfo: 是否返回歌单创建者的用户信息
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def _processsor(data: dict[str, Any]):
|
|
32
|
+
return {
|
|
33
|
+
"dirinfo": data.get("dirinfo", {}),
|
|
34
|
+
"total_song_num": data.get("total_song_num", 0),
|
|
35
|
+
"songlist_size": data.get("songlist_size", 0),
|
|
36
|
+
"songlist": data.get("songlist", []),
|
|
37
|
+
"songtag": data.get("songtag", []),
|
|
38
|
+
"orderlist": data.get("orderlist", []),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
"disstid": songlist_id,
|
|
43
|
+
"dirid": dirid,
|
|
44
|
+
"tag": tag,
|
|
45
|
+
"song_begin": num * (page - 1),
|
|
46
|
+
"song_num": num,
|
|
47
|
+
"userinfo": userinfo,
|
|
48
|
+
"orderlist": True,
|
|
49
|
+
"onlysonglist": onlysong,
|
|
50
|
+
}, _processsor
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def get_songlist(
|
|
54
|
+
songlist_id: int,
|
|
55
|
+
dirid: int = 0,
|
|
56
|
+
) -> list[dict[str, Any]]:
|
|
57
|
+
"""获取歌单中所有歌曲列表
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
songlist_id: 歌单 ID
|
|
61
|
+
dirid: 歌单 dirid
|
|
62
|
+
"""
|
|
63
|
+
response = await get_detail(songlist_id=songlist_id, dirid=dirid, num=100, onlysong=True)
|
|
64
|
+
|
|
65
|
+
total = response["total_song_num"]
|
|
66
|
+
songs = response["songlist"]
|
|
67
|
+
if total <= 100:
|
|
68
|
+
return cast(list[dict[str, Any]], songs)
|
|
69
|
+
|
|
70
|
+
rg = RequestGroup()
|
|
71
|
+
for p, num in enumerate(range(100, total, 100), start=2):
|
|
72
|
+
rg.add_request(get_detail, songlist_id=songlist_id, dirid=dirid, num=100, page=p, onlysong=True)
|
|
73
|
+
|
|
74
|
+
response = await rg.execute()
|
|
75
|
+
for res in response:
|
|
76
|
+
songs.extend(res["songlist"])
|
|
77
|
+
|
|
78
|
+
return songs
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@api_request("music.musicasset.PlaylistBaseWrite", "AddPlaylist", verify=True, cacheable=False)
|
|
82
|
+
async def create(dirname: str, credential: Credential | None = None):
|
|
83
|
+
"""添加歌单, 重名会在名称后面添加时间戳
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
dirname: 歌单名称
|
|
87
|
+
credential: 凭证
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
创建的歌单基本信息
|
|
91
|
+
"""
|
|
92
|
+
return {
|
|
93
|
+
"dirName": dirname,
|
|
94
|
+
}, lambda data: cast(dict[str, Any], data["result"])
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@api_request("music.musicasset.PlaylistBaseWrite", "DelPlaylist", verify=True, cacheable=False)
|
|
98
|
+
async def delete(dirid: int, credential: Credential | None = None):
|
|
99
|
+
"""删除歌单
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
dirid: 歌单id
|
|
103
|
+
credential: 凭证
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
是否删除成功若不存在则返回False
|
|
107
|
+
"""
|
|
108
|
+
return {
|
|
109
|
+
"dirId": dirid,
|
|
110
|
+
}, lambda data: data["result"]["dirId"] == dirid
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@api_request("music.musicasset.PlaylistDetailWrite", "AddSonglist", verify=True, cacheable=False)
|
|
114
|
+
async def add_songs(dirid: int = 1, song_ids: list[int] = [], credential: Credential | None = None):
|
|
115
|
+
"""添加歌曲到歌单
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
dirid: 歌单 dirid
|
|
119
|
+
song_ids: 歌曲 ID 列表
|
|
120
|
+
credential: 凭证
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
是否添加成功, 歌曲已存在返回False
|
|
124
|
+
"""
|
|
125
|
+
return {
|
|
126
|
+
"dirId": dirid,
|
|
127
|
+
"v_songInfo": [{"songType": 0, "songId": songid} for songid in song_ids],
|
|
128
|
+
}, lambda data: bool(data["result"]["updateTime"])
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@api_request("music.musicasset.PlaylistDetailWrite", "DelSonglist", verify=True, cacheable=False)
|
|
132
|
+
async def del_songs(dirid: int = 1, song_ids: list[int] = [], credential: Credential | None = None):
|
|
133
|
+
"""删除歌单歌曲
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
dirid: 歌单 dirid
|
|
137
|
+
song_ids: 歌曲 ID 列表
|
|
138
|
+
credential: 凭证
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
是否删除成功, 歌曲不存在返回False
|
|
142
|
+
"""
|
|
143
|
+
return {
|
|
144
|
+
"dirId": dirid,
|
|
145
|
+
"v_songInfo": [{"songType": 0, "songId": songid} for songid in song_ids],
|
|
146
|
+
}, lambda data: bool(data["result"]["updateTime"])
|
|
@@ -32,7 +32,7 @@ async def get_musicid(euin: str):
|
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
@api_request("music.UnifiedHomepage.UnifiedHomepageSrv", "GetHomepageHeader")
|
|
35
|
+
@api_request("music.UnifiedHomepage.UnifiedHomepageSrv", "GetHomepageHeader", cacheable=False)
|
|
36
36
|
async def get_homepage(euin: str, *, credential: Credential | None = None):
|
|
37
37
|
"""获取用户主页信息(包含音乐基因、歌单等)
|
|
38
38
|
|
|
@@ -49,7 +49,7 @@ async def get_vip_info(*, credential: Credential | None = None):
|
|
|
49
49
|
return {}, NO_PROCESSOR
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
@api_request("music.concern.RelationList", "GetFollowSingerList", verify=True)
|
|
52
|
+
@api_request("music.concern.RelationList", "GetFollowSingerList", verify=True, cacheable=False)
|
|
53
53
|
async def get_follow_singers(euin: str, page: int = 1, num: int = 10, *, credential: Credential | None = None):
|
|
54
54
|
"""获取关注歌手列表
|
|
55
55
|
|
|
@@ -65,7 +65,7 @@ async def get_follow_singers(euin: str, page: int = 1, num: int = 10, *, credent
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
@api_request("music.concern.RelationList", "GetFansList", verify=True)
|
|
68
|
+
@api_request("music.concern.RelationList", "GetFansList", verify=True, cacheable=False)
|
|
69
69
|
async def get_fans(euin: str, page: int = 1, num: int = 10, *, credential: Credential | None = None):
|
|
70
70
|
"""获取粉丝列表
|
|
71
71
|
|
|
@@ -81,7 +81,7 @@ async def get_fans(euin: str, page: int = 1, num: int = 10, *, credential: Crede
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
@api_request("music.homepage.Friendship", "GetFriendList", verify=True)
|
|
84
|
+
@api_request("music.homepage.Friendship", "GetFriendList", verify=True, cacheable=False)
|
|
85
85
|
async def get_friend(page: int = 1, num: int = 10, *, credential: Credential | None = None):
|
|
86
86
|
"""获取好友列表
|
|
87
87
|
|
|
@@ -96,7 +96,7 @@ async def get_friend(page: int = 1, num: int = 10, *, credential: Credential | N
|
|
|
96
96
|
}, lambda data: {"total": data.get("Total", 0), "list": data.get("List", [])}
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
@api_request("music.concern.RelationList", "GetFollowUserList", verify=True)
|
|
99
|
+
@api_request("music.concern.RelationList", "GetFollowUserList", verify=True, cacheable=False)
|
|
100
100
|
async def get_follow_user(euin: str, page: int = 1, num: int = 10, *, credential: Credential | None = None):
|
|
101
101
|
"""获取关注用户列表
|
|
102
102
|
|
|
@@ -112,7 +112,7 @@ async def get_follow_user(euin: str, page: int = 1, num: int = 10, *, credential
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
|
|
115
|
-
@api_request("music.musicasset.PlaylistBaseRead", "GetPlaylistByUin")
|
|
115
|
+
@api_request("music.musicasset.PlaylistBaseRead", "GetPlaylistByUin", cacheable=False)
|
|
116
116
|
async def get_created_songlist(uin: str, *, credential: Credential | None = None):
|
|
117
117
|
"""获取创建的歌单
|
|
118
118
|
|
|
@@ -123,7 +123,7 @@ async def get_created_songlist(uin: str, *, credential: Credential | None = None
|
|
|
123
123
|
return {"uin": uin}, lambda data: cast(list[dict[str, Any]], data.get("v_playlist", []))
|
|
124
124
|
|
|
125
125
|
|
|
126
|
-
@api_request("music.srfDissInfo.DissInfo", "CgiGetDiss")
|
|
126
|
+
@api_request("music.srfDissInfo.DissInfo", "CgiGetDiss", cacheable=False)
|
|
127
127
|
async def get_fav_song(euin: str, page: int = 1, num: int = 10, *, credential: Credential | None = None):
|
|
128
128
|
"""获取收藏歌曲
|
|
129
129
|
|
|
@@ -156,7 +156,7 @@ async def get_fav_song(euin: str, page: int = 1, num: int = 10, *, credential: C
|
|
|
156
156
|
}, _processsor
|
|
157
157
|
|
|
158
158
|
|
|
159
|
-
@api_request("music.musicasset.PlaylistFavRead", "CgiGetPlaylistFavInfo")
|
|
159
|
+
@api_request("music.musicasset.PlaylistFavRead", "CgiGetPlaylistFavInfo", cacheable=False)
|
|
160
160
|
async def get_fav_songlist(euin: str, page: int = 1, num: int = 10, *, credential: Credential | None = None):
|
|
161
161
|
"""获取收藏歌单
|
|
162
162
|
|
|
@@ -169,7 +169,7 @@ async def get_fav_songlist(euin: str, page: int = 1, num: int = 10, *, credentia
|
|
|
169
169
|
return {"uin": euin, "offset": (page - 1) * num, "size": num}, NO_PROCESSOR
|
|
170
170
|
|
|
171
171
|
|
|
172
|
-
@api_request("music.musicasset.AlbumFavRead", "CgiGetAlbumFavInfo")
|
|
172
|
+
@api_request("music.musicasset.AlbumFavRead", "CgiGetAlbumFavInfo", cacheable=False)
|
|
173
173
|
async def get_fav_album(euin: str, page: int = 1, num: int = 10, *, credential: Credential | None = None):
|
|
174
174
|
"""获取收藏专辑
|
|
175
175
|
|
|
@@ -182,7 +182,7 @@ async def get_fav_album(euin: str, page: int = 1, num: int = 10, *, credential:
|
|
|
182
182
|
return {"euin": euin, "offset": (page - 1) * num, "size": num}, NO_PROCESSOR
|
|
183
183
|
|
|
184
184
|
|
|
185
|
-
@api_request("music.musicasset.MVFavRead", "getMyFavMV_v2", verify=True)
|
|
185
|
+
@api_request("music.musicasset.MVFavRead", "getMyFavMV_v2", verify=True, cacheable=False)
|
|
186
186
|
async def get_fav_mv(euin: str, page: int = 1, num: int = 10, *, credential: Credential | None = None):
|
|
187
187
|
"""获取收藏 MV
|
|
188
188
|
|
|
@@ -195,7 +195,7 @@ async def get_fav_mv(euin: str, page: int = 1, num: int = 10, *, credential: Cre
|
|
|
195
195
|
return {"encuin": euin, "pagesize": num, "num": page - 1}, NO_PROCESSOR
|
|
196
196
|
|
|
197
197
|
|
|
198
|
-
@api_request("music.recommend.UserProfileSettingSvr", "GetProfileReport")
|
|
198
|
+
@api_request("music.recommend.UserProfileSettingSvr", "GetProfileReport", cacheable=False)
|
|
199
199
|
async def get_music_gene(euin: str, *, credential: Credential | None = None):
|
|
200
200
|
"""获取音乐基因数据
|
|
201
201
|
|
|
@@ -13,7 +13,7 @@ from typing_extensions import override
|
|
|
13
13
|
from ..exceptions import CredentialExpiredError, ResponseCodeError, SignInvalidError
|
|
14
14
|
from .common import calc_md5
|
|
15
15
|
from .credential import Credential
|
|
16
|
-
from .session import get_session
|
|
16
|
+
from .session import Session, get_session
|
|
17
17
|
from .sign import sign
|
|
18
18
|
|
|
19
19
|
_P = ParamSpec("_P")
|
|
@@ -39,6 +39,7 @@ def api_request(
|
|
|
39
39
|
cache_ttl: int | None = None,
|
|
40
40
|
cacheable: bool = True,
|
|
41
41
|
exclude_params: list[str] = [],
|
|
42
|
+
catch_error_code: list[int] = [],
|
|
42
43
|
):
|
|
43
44
|
"""API请求"""
|
|
44
45
|
|
|
@@ -55,6 +56,7 @@ def api_request(
|
|
|
55
56
|
cacheable=cacheable,
|
|
56
57
|
cache_ttl=cache_ttl,
|
|
57
58
|
exclude_params=exclude_params,
|
|
59
|
+
catch_error_code=catch_error_code,
|
|
58
60
|
)
|
|
59
61
|
|
|
60
62
|
return decorator
|
|
@@ -80,17 +82,21 @@ class BaseRequest(ABC):
|
|
|
80
82
|
verify: bool = False,
|
|
81
83
|
ignore_code: bool = False,
|
|
82
84
|
) -> None:
|
|
83
|
-
self.session = get_session()
|
|
84
85
|
self._common = common or {}
|
|
85
86
|
self._credential = credential
|
|
86
87
|
self.verify = verify
|
|
87
88
|
self.ignore_code = ignore_code
|
|
88
89
|
self.cache = self.session._cache
|
|
89
90
|
|
|
91
|
+
@property
|
|
92
|
+
def session(self) -> Session:
|
|
93
|
+
"""获取请求会话"""
|
|
94
|
+
return get_session()
|
|
95
|
+
|
|
90
96
|
@property
|
|
91
97
|
def credential(self) -> Credential:
|
|
92
98
|
"""获取请求凭证"""
|
|
93
|
-
return self._credential or
|
|
99
|
+
return self._credential or self.session.credential or Credential()
|
|
94
100
|
|
|
95
101
|
@credential.setter
|
|
96
102
|
def credential(self, value: Credential):
|
|
@@ -110,11 +116,11 @@ class BaseRequest(ABC):
|
|
|
110
116
|
self._common = value
|
|
111
117
|
|
|
112
118
|
def _build_common_params(self, credential: Credential) -> dict[str, Any]:
|
|
113
|
-
config =
|
|
119
|
+
config = self.session.api_config
|
|
114
120
|
common = {
|
|
115
121
|
"cv": config["version_code"],
|
|
116
122
|
"v": config["version_code"],
|
|
117
|
-
"QIMEI36":
|
|
123
|
+
"QIMEI36": self.session.qimei,
|
|
118
124
|
}
|
|
119
125
|
common.update(self.COMMON_DEFAULTS)
|
|
120
126
|
if credential.has_musicid() and credential.has_musickey():
|
|
@@ -189,7 +195,7 @@ class ApiRequest(BaseRequest, Generic[_P, _R]):
|
|
|
189
195
|
cache_ttl: int | None = None,
|
|
190
196
|
cacheable: bool = True,
|
|
191
197
|
exclude_params: list[str] = [],
|
|
192
|
-
|
|
198
|
+
catch_error_code: list[int] = [],
|
|
193
199
|
) -> None:
|
|
194
200
|
super().__init__(common, credential, verify, ignore_code)
|
|
195
201
|
self.module = module
|
|
@@ -201,6 +207,7 @@ class ApiRequest(BaseRequest, Generic[_P, _R]):
|
|
|
201
207
|
self.cacheable = cacheable
|
|
202
208
|
self.cache_ttl = cache_ttl
|
|
203
209
|
self.exclude_params = exclude_params
|
|
210
|
+
self.catch_error_code = catch_error_code
|
|
204
211
|
|
|
205
212
|
def copy(self) -> "ApiRequest[_P, _R]":
|
|
206
213
|
"""创建当前 ApiRequest 实例的副本"""
|
|
@@ -217,14 +224,14 @@ class ApiRequest(BaseRequest, Generic[_P, _R]):
|
|
|
217
224
|
cacheable=self.cacheable,
|
|
218
225
|
cache_ttl=self.cache_ttl,
|
|
219
226
|
exclude_params=self.exclude_params.copy(),
|
|
227
|
+
catch_error_code=self.catch_error_code,
|
|
220
228
|
)
|
|
221
229
|
req.processor = self.processor
|
|
222
230
|
return req
|
|
223
231
|
|
|
224
232
|
@override
|
|
225
233
|
def build_request_data(self) -> dict[str, Any]:
|
|
226
|
-
|
|
227
|
-
return {"comm": common, f"{self.module}.{self.method}": self.data}
|
|
234
|
+
return {"comm": self.common, f"{self.module}.{self.method}": self.data}
|
|
228
235
|
|
|
229
236
|
@property
|
|
230
237
|
def data(self) -> dict[str, Any]:
|
|
@@ -274,7 +281,7 @@ class ApiRequest(BaseRequest, Generic[_P, _R]):
|
|
|
274
281
|
code,
|
|
275
282
|
)
|
|
276
283
|
|
|
277
|
-
if code == 0:
|
|
284
|
+
if code == 0 or code in self.catch_error_code:
|
|
278
285
|
return
|
|
279
286
|
|
|
280
287
|
if code == 2000:
|
|
@@ -388,9 +395,8 @@ class RequestGroup(BaseRequest):
|
|
|
388
395
|
@override
|
|
389
396
|
def build_request_data(self):
|
|
390
397
|
"""构建请求"""
|
|
391
|
-
common = self._build_common_params(self.credential)
|
|
392
398
|
merged_data = {req["key"]: req["request"].data for req in self._requests}
|
|
393
|
-
data = {"comm": common}
|
|
399
|
+
data = {"comm": self.common}
|
|
394
400
|
data.update(merged_data)
|
|
395
401
|
return data
|
|
396
402
|
|
|
@@ -112,7 +112,7 @@ def random_payload_by_device(device: Device, version: str) -> dict:
|
|
|
112
112
|
"packageId": "com.tencent.qqmusic",
|
|
113
113
|
"deviceType": "Phone",
|
|
114
114
|
"sdkName": "",
|
|
115
|
-
"reserved": json.dumps(reserved),
|
|
115
|
+
"reserved": json.dumps(reserved).decode(),
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
|
|
@@ -158,7 +158,7 @@ def get_qimei(version: str) -> QimeiResult:
|
|
|
158
158
|
device.qimei = data["q36"]
|
|
159
159
|
save_device(device)
|
|
160
160
|
return QimeiResult(q16=data["q16"], q36=data["q36"])
|
|
161
|
-
except
|
|
161
|
+
except httpx.HTTPError:
|
|
162
162
|
if device.qimei:
|
|
163
163
|
return QimeiResult(q16="", q36=device.qimei)
|
|
164
164
|
logger.exception("获取 QIMEI 失败,使用默认 QIMEI")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""请求 Session 管理"""
|
|
2
2
|
|
|
3
3
|
import contextvars
|
|
4
|
+
import logging
|
|
4
5
|
from typing import TypedDict
|
|
5
6
|
|
|
6
7
|
import httpx
|
|
@@ -11,6 +12,10 @@ from aiocache.serializers import BaseSerializer
|
|
|
11
12
|
from .credential import Credential
|
|
12
13
|
from .qimei import get_qimei
|
|
13
14
|
|
|
15
|
+
# 配置日志记录器
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
_session_counter = 0
|
|
18
|
+
|
|
14
19
|
|
|
15
20
|
class ApiConfig(TypedDict):
|
|
16
21
|
"""API 配置"""
|
|
@@ -102,6 +107,7 @@ def get_session() -> Session:
|
|
|
102
107
|
"""获取当前上下文的 Session"""
|
|
103
108
|
session = _session_context.get()
|
|
104
109
|
if session is None:
|
|
110
|
+
logger.info("创建新的默认Session")
|
|
105
111
|
session = Session()
|
|
106
112
|
_session_context.set(session)
|
|
107
113
|
return session
|
|
@@ -109,12 +115,15 @@ def get_session() -> Session:
|
|
|
109
115
|
|
|
110
116
|
def set_session(session: Session) -> None:
|
|
111
117
|
"""设置当前上下文的 Session"""
|
|
118
|
+
logger.info("设置新的Session到上下文")
|
|
112
119
|
_session_context.set(session)
|
|
113
120
|
|
|
114
121
|
|
|
115
122
|
def clear_session() -> None:
|
|
116
123
|
"""清除当前上下文的 Session"""
|
|
124
|
+
logger.info("清除当前上下文的Session")
|
|
117
125
|
try:
|
|
118
126
|
_session_context.set(None)
|
|
119
127
|
except LookupError:
|
|
128
|
+
logger.warning("尝试清除不存在的Session上下文")
|
|
120
129
|
pass
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
+
from qqmusic_api import songlist
|
|
3
4
|
from qqmusic_api.exceptions import CredentialExpiredError
|
|
4
5
|
from qqmusic_api.user import (
|
|
5
6
|
Credential,
|
|
@@ -68,3 +69,18 @@ class TestUserAPI:
|
|
|
68
69
|
"""测试获取好友列表"""
|
|
69
70
|
with pytest.raises(CredentialExpiredError):
|
|
70
71
|
assert await get_friend(page=1, num=10, credential=self.VALID_CREDENTIAL)
|
|
72
|
+
|
|
73
|
+
async def test_create_songlist(self):
|
|
74
|
+
"""测试创建歌单"""
|
|
75
|
+
with pytest.raises(CredentialExpiredError):
|
|
76
|
+
result = await songlist.create(dirname="test", credential=self.VALID_CREDENTIAL)
|
|
77
|
+
|
|
78
|
+
if not result or "dirId" not in result or not result["dirId"]:
|
|
79
|
+
pytest.fail(f"创建歌单失败, result 无效: {result}")
|
|
80
|
+
|
|
81
|
+
dir_id = result["dirId"]
|
|
82
|
+
assert await songlist.add_songs(
|
|
83
|
+
dirid=dir_id, song_ids=[438910555, 9063002], credential=self.VALID_CREDENTIAL
|
|
84
|
+
)
|
|
85
|
+
assert await songlist.del_songs(dirid=dir_id, song_ids=[438910555], credential=self.VALID_CREDENTIAL)
|
|
86
|
+
assert await songlist.delete(dirid=dir_id, credential=self.VALID_CREDENTIAL)
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"""歌单相关 API"""
|
|
2
|
-
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from .utils.network import api_request
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@api_request("music.srfDissInfo.DissInfo", "CgiGetDiss")
|
|
9
|
-
async def get_detail(
|
|
10
|
-
songlist_id: int,
|
|
11
|
-
dirid: int = 0,
|
|
12
|
-
num: int = 10,
|
|
13
|
-
page: int = 1,
|
|
14
|
-
onlysong: bool = False,
|
|
15
|
-
tag: bool = True,
|
|
16
|
-
userinfo: bool = True,
|
|
17
|
-
):
|
|
18
|
-
"""获取歌单详细信息和歌曲
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
songlist_id: 歌单 ID
|
|
22
|
-
dirid: 歌单 dirid
|
|
23
|
-
num: 返回数量
|
|
24
|
-
page: 页码
|
|
25
|
-
onlysong: 是否仅返回歌曲信息(优先级最大)
|
|
26
|
-
tag: 是否返回歌单的标签信息
|
|
27
|
-
userinfo: 是否返回歌单创建者的用户信息
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
def _processsor(data: dict[str, Any]):
|
|
31
|
-
return {
|
|
32
|
-
"dirinfo": data.get("dirinfo", {}),
|
|
33
|
-
"total_song_num": data.get("total_song_num", 0),
|
|
34
|
-
"songlist_size": data.get("songlist_size", 0),
|
|
35
|
-
"songlist": data.get("songlist", []),
|
|
36
|
-
"songtag": data.get("songtag", []),
|
|
37
|
-
"orderlist": data.get("orderlist", []),
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
"disstid": songlist_id,
|
|
42
|
-
"dirid": dirid,
|
|
43
|
-
"tag": tag,
|
|
44
|
-
"song_begin": num * (page - 1),
|
|
45
|
-
"song_num": num,
|
|
46
|
-
"userinfo": userinfo,
|
|
47
|
-
"orderlist": True,
|
|
48
|
-
"onlysonglist": onlysong,
|
|
49
|
-
}, _processsor
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qqmusic_api_python-0.3.1 → qqmusic_api_python-0.3.3}/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
|