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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fuo_qqmusic
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: feeluown qqmusic plugin
5
5
  Home-page: https://github.com/feeluown/feeluown-qqmusic
6
6
  Author: Cosven
@@ -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_ == 8:
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 rec_list_daily_playlists(self):
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 shelf in feed['v_shelf']:
292
+ for shelf_ in feed['v_shelf']:
261
293
  # I guess 10046 means 'song'.
262
- if shelf['miscellany'].get('jumptype') == 10046:
263
- shelf = 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
- if type_ == SearchType.pl:
369
- data = provider.api.search_playlists(keyword)
370
- playlists = [_deserialize(playlist, _BriefPlaylistSchema) for playlist in data]
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
- else:
373
- type_type_map = {
374
- SearchType.so: 0,
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fuo-qqmusic
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: feeluown qqmusic plugin
5
5
  Home-page: https://github.com/feeluown/feeluown-qqmusic
6
6
  Author: Cosven
@@ -5,7 +5,7 @@ from setuptools import setup
5
5
 
6
6
  setup(
7
7
  name='fuo_qqmusic',
8
- version='1.0.3',
8
+ version='1.0.5',
9
9
  description='feeluown qqmusic plugin',
10
10
  author='Cosven',
11
11
  author_email='yinshaowen241@gmail.com',
File without changes