fuo-qqmusic 1.0.3__tar.gz → 1.0.5__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.
Potentially problematic release.
This version of fuo-qqmusic might be problematic. Click here for more details.
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/PKG-INFO +1 -1
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/README.md +8 -0
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/fuo_qqmusic/api.py +8 -3
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/fuo_qqmusic/provider.py +67 -25
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/fuo_qqmusic/schemas.py +63 -0
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/fuo_qqmusic.egg-info/PKG-INFO +1 -1
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/setup.py +1 -1
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/fuo_qqmusic/__init__.py +0 -0
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/fuo_qqmusic/assets/icon.svg +0 -0
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/fuo_qqmusic/consts.py +0 -0
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/fuo_qqmusic/excs.py +0 -0
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/fuo_qqmusic/provider_ui.py +0 -0
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/fuo_qqmusic.egg-info/SOURCES.txt +0 -0
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/fuo_qqmusic.egg-info/dependency_links.txt +0 -0
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/fuo_qqmusic.egg-info/entry_points.txt +0 -0
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/fuo_qqmusic.egg-info/requires.txt +0 -0
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/fuo_qqmusic.egg-info/top_level.txt +0 -0
- {fuo_qqmusic-1.0.3 → fuo_qqmusic-1.0.5}/setup.cfg +0 -0
|
@@ -20,6 +20,14 @@ pip3 install fuo-qqmusic
|
|
|
20
20
|
[操作示例](https://github.com/feeluown/feeluown-qqmusic/issues/6)。
|
|
21
21
|
|
|
22
22
|
## changelog
|
|
23
|
+
### 1.0.5 (2024-06-03)
|
|
24
|
+
- 修复搜索接口,支持搜索歌单、视频、专辑
|
|
25
|
+
- 支持提供歌单的播放次数
|
|
26
|
+
|
|
27
|
+
### 1.0.4 (2024-05-21)
|
|
28
|
+
- 歌手歌曲排序切换为”按热度排序”
|
|
29
|
+
- 修复推荐歌单接口
|
|
30
|
+
|
|
23
31
|
### 1.0.3 (2024-04-21)
|
|
24
32
|
- 适配 feeluown 4.1.3 的新主页功能
|
|
25
33
|
|
|
@@ -124,10 +124,14 @@ class API(object):
|
|
|
124
124
|
# Other supported types: songlist, user, mv, qc, gedantip, zhida.
|
|
125
125
|
if type_ == 0:
|
|
126
126
|
key_ = 'song'
|
|
127
|
-
elif type_ ==
|
|
128
|
-
key_ = 'album'
|
|
129
|
-
elif type_ == 9:
|
|
127
|
+
elif type_ == 1:
|
|
130
128
|
key_ = 'singer'
|
|
129
|
+
elif type_ == 2:
|
|
130
|
+
key_ = 'album'
|
|
131
|
+
elif type_ == 3:
|
|
132
|
+
key_ = 'songlist' # playlist
|
|
133
|
+
elif type_ == 4:
|
|
134
|
+
key_ = 'mv' # video
|
|
131
135
|
else:
|
|
132
136
|
raise QQIOError('invalid search type_:%d', type_)
|
|
133
137
|
payload = {
|
|
@@ -247,6 +251,7 @@ class API(object):
|
|
|
247
251
|
'singerid': artist_id,
|
|
248
252
|
'begin': (page - 1) * page_size,
|
|
249
253
|
'num': page_size,
|
|
254
|
+
'order': 1, # 热门/新,不带这个字段就是按歌曲新旧排序
|
|
250
255
|
# 有 newsong 字段时,服务端会返回含有 file 字段的字典
|
|
251
256
|
'newsong': 1
|
|
252
257
|
}},
|
|
@@ -18,6 +18,7 @@ from feeluown.library import (
|
|
|
18
18
|
SupportsVideoGet,
|
|
19
19
|
SupportsSongLyric,
|
|
20
20
|
SupportsAlbumGet,
|
|
21
|
+
SupportsAlbumSongsReader,
|
|
21
22
|
SupportsArtistGet,
|
|
22
23
|
SupportsPlaylistGet,
|
|
23
24
|
SupportsPlaylistSongsReader,
|
|
@@ -48,6 +49,7 @@ class Supports(
|
|
|
48
49
|
SupportsPlaylistGet,
|
|
49
50
|
SupportsPlaylistSongsReader,
|
|
50
51
|
SupportsRecACollectionOfSongs,
|
|
52
|
+
SupportsAlbumSongsReader,
|
|
51
53
|
Protocol,
|
|
52
54
|
):
|
|
53
55
|
pass
|
|
@@ -224,6 +226,10 @@ class QQProvider(AbstractProvider, ProviderV2):
|
|
|
224
226
|
album = _deserialize(data_album, QQAlbumSchema)
|
|
225
227
|
return album
|
|
226
228
|
|
|
229
|
+
def album_create_songs_rd(self, album):
|
|
230
|
+
album = self.album_get(album.identifier)
|
|
231
|
+
return create_reader(album.songs)
|
|
232
|
+
|
|
227
233
|
def user_get(self, identifier):
|
|
228
234
|
data = self.api.user_detail(identifier)
|
|
229
235
|
data["creator"]["fav_pid"] = data["mymusic"][0]["id"]
|
|
@@ -240,7 +246,7 @@ class QQProvider(AbstractProvider, ProviderV2):
|
|
|
240
246
|
songs = self._model_cache_get_or_fetch(playlist, "songs")
|
|
241
247
|
return create_reader(songs)
|
|
242
248
|
|
|
243
|
-
def
|
|
249
|
+
def __rec_hot_playlists(self):
|
|
244
250
|
user = self.get_current_user()
|
|
245
251
|
if user is None:
|
|
246
252
|
return []
|
|
@@ -253,16 +259,46 @@ class QQProvider(AbstractProvider, ProviderV2):
|
|
|
253
259
|
pl["logo"] = pl["cover"]
|
|
254
260
|
return [_deserialize(playlist, QQPlaylistSchema) for playlist in playlists]
|
|
255
261
|
|
|
262
|
+
def rec_list_daily_playlists(self):
|
|
263
|
+
# TODO: cache API result
|
|
264
|
+
feed = self.api.get_recommend_feed()
|
|
265
|
+
shelf = None
|
|
266
|
+
for shelf_ in feed['v_shelf']:
|
|
267
|
+
# I guess 10046 means 'song'.
|
|
268
|
+
if shelf_['extra_info'].get('moduleID', '').startswith('playlist'):
|
|
269
|
+
shelf = shelf_
|
|
270
|
+
break
|
|
271
|
+
if shelf is None:
|
|
272
|
+
return []
|
|
273
|
+
playlists = []
|
|
274
|
+
for batch in shelf['v_niche']:
|
|
275
|
+
for card in batch['v_card']:
|
|
276
|
+
print(card['title'], card['jumptype'])
|
|
277
|
+
if card['jumptype'] == 10014: # 10014->playlist
|
|
278
|
+
playlists.append(
|
|
279
|
+
PlaylistModel(identifier=str(card['id']),
|
|
280
|
+
source=SOURCE,
|
|
281
|
+
name=card['title'],
|
|
282
|
+
cover=card['cover'],
|
|
283
|
+
description=card['miscellany']['rcmdtemplate'],
|
|
284
|
+
play_count=card['cnt'])
|
|
285
|
+
)
|
|
286
|
+
return playlists
|
|
287
|
+
|
|
256
288
|
def rec_a_collection_of_songs(self):
|
|
257
289
|
# TODO: cache API result
|
|
258
290
|
feed = self.api.get_recommend_feed()
|
|
259
291
|
shelf = None
|
|
260
|
-
for
|
|
292
|
+
for shelf_ in feed['v_shelf']:
|
|
261
293
|
# I guess 10046 means 'song'.
|
|
262
|
-
if
|
|
263
|
-
shelf =
|
|
294
|
+
if int(shelf_['miscellany'].get('jumptype', 0)) == 10046:
|
|
295
|
+
shelf = shelf_
|
|
296
|
+
break
|
|
264
297
|
if shelf is None:
|
|
265
|
-
return '',
|
|
298
|
+
return Collection(name='',
|
|
299
|
+
type_=CollectionType.only_songs,
|
|
300
|
+
models=[],
|
|
301
|
+
description='')
|
|
266
302
|
title = shelf['title_content'] or shelf['title_template']
|
|
267
303
|
song_ids = []
|
|
268
304
|
for batch in shelf['v_niche']:
|
|
@@ -365,26 +401,29 @@ def create_g(func, identifier, schema):
|
|
|
365
401
|
|
|
366
402
|
def search(keyword, **kwargs):
|
|
367
403
|
type_ = SearchType.parse(kwargs["type_"])
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
404
|
+
type_type_map = {
|
|
405
|
+
SearchType.so: 0,
|
|
406
|
+
SearchType.ar: 1,
|
|
407
|
+
SearchType.al: 2,
|
|
408
|
+
SearchType.pl: 3,
|
|
409
|
+
SearchType.vi: 4,
|
|
410
|
+
}
|
|
411
|
+
data = provider.api.search(keyword, type_=type_type_map[type_])
|
|
412
|
+
if type_ == SearchType.so:
|
|
413
|
+
songs = [_deserialize(song, QQSongSchema) for song in data]
|
|
414
|
+
return SimpleSearchResult(q=keyword, songs=songs)
|
|
415
|
+
if type_ == SearchType.ar:
|
|
416
|
+
artists = [_deserialize(artist, SearchArtistSchema) for artist in data]
|
|
417
|
+
return SimpleSearchResult(q=keyword, artists=artists)
|
|
418
|
+
elif type_ == SearchType.al:
|
|
419
|
+
albums = [_deserialize(album, SearchAlbumSchema) for album in data]
|
|
420
|
+
return SimpleSearchResult(q=keyword, albums=albums)
|
|
421
|
+
elif type_ == SearchType.pl:
|
|
422
|
+
playlists = [_deserialize(playlist, SearchPlaylistSchema) for playlist in data]
|
|
371
423
|
return SimpleSearchResult(q=keyword, playlists=playlists)
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
SearchType.al: 8,
|
|
376
|
-
SearchType.ar: 9,
|
|
377
|
-
}
|
|
378
|
-
data = provider.api.search(keyword, type_=type_type_map[type_])
|
|
379
|
-
if type_ == SearchType.so:
|
|
380
|
-
songs = [_deserialize(song, QQSongSchema) for song in data]
|
|
381
|
-
return SimpleSearchResult(q=keyword, songs=songs)
|
|
382
|
-
elif type_ == SearchType.al:
|
|
383
|
-
albums = [_deserialize(album, _BriefAlbumSchema) for album in data]
|
|
384
|
-
return SimpleSearchResult(q=keyword, albums=albums)
|
|
385
|
-
else:
|
|
386
|
-
artists = [_deserialize(artist, _BriefArtistSchema) for artist in data]
|
|
387
|
-
return SimpleSearchResult(q=keyword, artists=artists)
|
|
424
|
+
elif type_ == SearchType.vi:
|
|
425
|
+
models = [_deserialize(model, SearchMVSchema) for model in data]
|
|
426
|
+
return SimpleSearchResult(q=keyword, videos=models)
|
|
388
427
|
|
|
389
428
|
|
|
390
429
|
provider = QQProvider()
|
|
@@ -398,9 +437,12 @@ from .schemas import ( # noqa
|
|
|
398
437
|
_BriefAlbumSchema,
|
|
399
438
|
_UserArtistSchema,
|
|
400
439
|
_BriefArtistSchema,
|
|
401
|
-
_BriefPlaylistSchema,
|
|
402
440
|
QQAlbumSchema,
|
|
403
441
|
QQPlaylistSchema,
|
|
404
442
|
QQUserSchema,
|
|
405
443
|
_UserAlbumSchema,
|
|
444
|
+
SearchAlbumSchema,
|
|
445
|
+
SearchArtistSchema,
|
|
446
|
+
SearchPlaylistSchema,
|
|
447
|
+
SearchMVSchema,
|
|
406
448
|
) # noqa
|
|
@@ -10,6 +10,7 @@ from feeluown.library import (
|
|
|
10
10
|
PlaylistModel,
|
|
11
11
|
BriefPlaylistModel,
|
|
12
12
|
UserModel,
|
|
13
|
+
VideoModel,
|
|
13
14
|
)
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
@@ -143,6 +144,17 @@ class _BriefArtistSchema(Schema):
|
|
|
143
144
|
return create_model(BriefArtistModel, data, ["mid"])
|
|
144
145
|
|
|
145
146
|
|
|
147
|
+
class SearchArtistSchema(_BriefArtistSchema):
|
|
148
|
+
pic_url = fields.Str(data_key="singerPic", required=True)
|
|
149
|
+
|
|
150
|
+
@post_load
|
|
151
|
+
def create_model(self, data, **kwargs):
|
|
152
|
+
data['hot_songs'] = []
|
|
153
|
+
data['description'] = ''
|
|
154
|
+
data['aliases'] = []
|
|
155
|
+
return create_model(ArtistModel, data, ["mid"])
|
|
156
|
+
|
|
157
|
+
|
|
146
158
|
class _BriefAlbumSchema(Schema):
|
|
147
159
|
identifier = fields.Int(data_key="albumID", required=True)
|
|
148
160
|
mid = fields.Str(data_key="albumMID", required=True)
|
|
@@ -153,6 +165,20 @@ class _BriefAlbumSchema(Schema):
|
|
|
153
165
|
return create_model(BriefAlbumModel, data, ["mid"])
|
|
154
166
|
|
|
155
167
|
|
|
168
|
+
class SearchAlbumSchema(_BriefAlbumSchema):
|
|
169
|
+
cover = fields.Str(data_key="albumPic", required=True)
|
|
170
|
+
released = fields.Str(data_key="publicTime", required=True)
|
|
171
|
+
song_count = fields.Int(required=True)
|
|
172
|
+
artists = fields.List(fields.Nested(_SongArtistSchema),
|
|
173
|
+
data_key="singer_list",
|
|
174
|
+
required=True)
|
|
175
|
+
@post_load
|
|
176
|
+
def create_model(self, data, **kwargs):
|
|
177
|
+
data['description'] = ''
|
|
178
|
+
data['songs'] = []
|
|
179
|
+
return create_model(AlbumModel, data, ['mid'])
|
|
180
|
+
|
|
181
|
+
|
|
156
182
|
class _BriefPlaylistSchema(Schema):
|
|
157
183
|
identifier = fields.Int(data_key="dissid", required=True)
|
|
158
184
|
name = fields.Str(data_key="dissname", required=True)
|
|
@@ -164,6 +190,27 @@ class _BriefPlaylistSchema(Schema):
|
|
|
164
190
|
return create_model(PlaylistModel, **data)
|
|
165
191
|
|
|
166
192
|
|
|
193
|
+
class PlaylistUserSchema(Schema):
|
|
194
|
+
identifier = fields.Str(data_key="creator_uin", required=True)
|
|
195
|
+
mid = fields.Str(data_key="encrypt_uin", required=True)
|
|
196
|
+
name = fields.Str(required=True)
|
|
197
|
+
avatar_url = fields.Str(required=True, data_key="avatarUrl")
|
|
198
|
+
|
|
199
|
+
@post_load
|
|
200
|
+
def create_model(self, data, **kwargs):
|
|
201
|
+
return create_model(UserModel, data, ['mid'])
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class SearchPlaylistSchema(_BriefPlaylistSchema):
|
|
205
|
+
creator = fields.Nested("PlaylistUserSchema", required=True)
|
|
206
|
+
description = fields.Str(data_key="introduction", required=True)
|
|
207
|
+
play_count = fields.Int(data_key="listennum", required=True)
|
|
208
|
+
|
|
209
|
+
@post_load
|
|
210
|
+
def create_model(self, data, **kwargs):
|
|
211
|
+
return create_model(PlaylistModel, data)
|
|
212
|
+
|
|
213
|
+
|
|
167
214
|
class QQArtistSchema(Schema):
|
|
168
215
|
"""歌手详情 Schema、歌曲歌手简要信息 Schema"""
|
|
169
216
|
|
|
@@ -294,3 +341,19 @@ class QQUserSchema(Schema):
|
|
|
294
341
|
playlists=playlists,
|
|
295
342
|
)
|
|
296
343
|
return create_model(UserModel, data, ['mid', 'fav_pid', 'playlists'])
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class SearchMVSchema(Schema):
|
|
347
|
+
# 使用 mv_id 字段的话,目前拿不到播放 url,用 v_id 比较合适
|
|
348
|
+
identifier = fields.Str(data_key="v_id", required=True)
|
|
349
|
+
title = fields.Str(data_key="mv_name", required=True)
|
|
350
|
+
artists = fields.List(fields.Nested("_SongArtistSchema"),
|
|
351
|
+
data_key="singer_list",
|
|
352
|
+
required=True)
|
|
353
|
+
duration = fields.Int(required=True)
|
|
354
|
+
cover = fields.Str(data_key="mv_pic_url", required=True)
|
|
355
|
+
play_count = fields.Int(required=True)
|
|
356
|
+
|
|
357
|
+
@post_load
|
|
358
|
+
def create_model(self, data, **kwargs):
|
|
359
|
+
return create_model(VideoModel, data)
|
|
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
|