fuo-qqmusic 1.0.6__tar.gz → 1.0.7__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.

Files changed (21) hide show
  1. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/PKG-INFO +2 -2
  2. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/README.md +3 -0
  3. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic/api.py +81 -0
  4. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic/provider.py +39 -0
  5. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic.egg-info/PKG-INFO +2 -2
  6. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic.egg-info/SOURCES.txt +1 -0
  7. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic.egg-info/requires.txt +1 -1
  8. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/setup.py +2 -2
  9. fuo_qqmusic-1.0.7/tests/test_api.py +56 -0
  10. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic/__init__.py +0 -0
  11. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic/assets/icon.svg +0 -0
  12. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic/consts.py +0 -0
  13. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic/excs.py +0 -0
  14. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic/login.py +0 -0
  15. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic/provider_ui.py +0 -0
  16. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic/schemas.py +0 -0
  17. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic.egg-info/dependency_links.txt +0 -0
  18. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic.egg-info/entry_points.txt +0 -0
  19. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/fuo_qqmusic.egg-info/top_level.txt +0 -0
  20. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/setup.cfg +0 -0
  21. {fuo_qqmusic-1.0.6 → fuo_qqmusic-1.0.7}/tests/test_provider.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fuo_qqmusic
3
- Version: 1.0.6
3
+ Version: 1.0.7
4
4
  Summary: feeluown qqmusic plugin
5
5
  Home-page: https://github.com/feeluown/feeluown-qqmusic
6
6
  Author: Cosven
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.9
15
15
  Classifier: Programming Language :: Python :: 3 :: Only
16
16
  Requires-Dist: feeluown>=4.1.3
17
17
  Requires-Dist: requests
18
- Requires-Dist: marshmallow>=3.0
18
+ Requires-Dist: marshmallow<4.0.0,>=3.0
19
19
  Dynamic: author
20
20
  Dynamic: author-email
21
21
  Dynamic: classifier
@@ -20,6 +20,9 @@ pip3 install fuo-qqmusic
20
20
  [操作示例](https://github.com/feeluown/feeluown-qqmusic/issues/6)。
21
21
 
22
22
  ## changelog
23
+ ### 1.0.7 (2025-05-31)
24
+ - 支持音乐黑名单
25
+
23
26
  ### 1.0.6 (2025-05-08)
24
27
  - 支持每日推荐
25
28
  - 支持自动登录
@@ -8,6 +8,7 @@ import math
8
8
  import json
9
9
  import random
10
10
  import time
11
+ from enum import Enum
11
12
 
12
13
  import requests
13
14
  from .excs import QQIOError
@@ -717,5 +718,85 @@ class API(object):
717
718
  return 'http://isure.stream.qqmusic.qq.com/{}'.format(midurlinfo[0]['purl'])
718
719
  return ''
719
720
 
721
+ class DislikeListType(Enum):
722
+ singer = 2
723
+ song = 3
724
+ _style_unsupported = 4 # TODO: 这是什么?似乎是不喜欢的风格列表,不确定,暂时不支持
725
+
726
+ def get_dislike_list(self, page=1, type_=DislikeListType.song, last_id=0):
727
+ payload = {
728
+ "req_0": {
729
+ "module": "music.feedback.FeedbackBlack",
730
+ "method": "GetDislikeList",
731
+ "param": {
732
+ "Cmd": type_.value,
733
+ "Page": page,
734
+ "SongLastid": last_id if type_ == API.DislikeListType.song else 0,
735
+ "SingersLastid": (
736
+ last_id if type_ == API.DislikeListType.singer else 0
737
+ ),
738
+ },
739
+ },
740
+ }
741
+ js = self.rpc(payload)
742
+ if type_ == API.DislikeListType.song:
743
+ return js["req_0"]["data"]["Songs"]
744
+ elif type_ == API.DislikeListType.singer:
745
+ return js["req_0"]["data"]["Singers"]
746
+ else:
747
+ raise QQIOError(f"Unknown dislike list type: {type_}")
748
+
749
+ def add_to_dislike_list(self, items, type_=DislikeListType.song):
750
+ req_param = {
751
+ "Singers": [],
752
+ "Songs": [],
753
+ "Styles": [],
754
+ "OnlyAdd": 1,
755
+ }
756
+ if type_ == API.DislikeListType.song:
757
+ req_param["Songs"] = items
758
+ elif type_ == API.DislikeListType.singer:
759
+ req_param["Singers"] = items
760
+ else:
761
+ raise QQIOError(f"Unknown dislike list type: {type_}")
762
+
763
+ payload = {
764
+ "req_0": {
765
+ "module": "music.feedback.FeedbackBlack",
766
+ "method": "AddDislike",
767
+ "param": req_param,
768
+ },
769
+ }
770
+ js = self.rpc(payload)
771
+ # Response example, {'code': 0, 'data': {'Retcode': 0, 'Msg': '', 'Token': ''}}
772
+ CodeShouldBe0.check(js['req_0'])
773
+ return js['req_0']['data']
774
+
775
+ def remove_from_dislike_list(self, items, type_=DislikeListType.song):
776
+ req_param = {
777
+ "Singers": [],
778
+ "Songs": [],
779
+ "Styles": [],
780
+ "OnlyAdd": 0,
781
+ }
782
+ if type_ == API.DislikeListType.song:
783
+ req_param["Songs"] = items
784
+ elif type_ == API.DislikeListType.singer:
785
+ req_param["Singers"] = items
786
+ else:
787
+ raise QQIOError(f"Unknown dislike list type: {type_}")
788
+
789
+ payload = {
790
+ "req_0": {
791
+ "module": "music.feedback.FeedbackBlack",
792
+ "method": "CancelDislike",
793
+ "param": req_param,
794
+ },
795
+ }
796
+ js = self.rpc(payload)
797
+ CodeShouldBe0.check(js)
798
+ CodeShouldBe0.check(js['req_0'])
799
+ return js['req_0']['data']
800
+
720
801
 
721
802
  api = API()
@@ -23,12 +23,17 @@ from feeluown.library import (
23
23
  SupportsPlaylistGet,
24
24
  SupportsPlaylistSongsReader,
25
25
  SupportsRecACollectionOfSongs,
26
+ SupportsCurrentUserDislikeSongsReader,
27
+ SupportsCurrentUserDislikeAddSong,
28
+ SupportsCurrentUserDislikeRemoveSong,
29
+ SupportsCurrentUserChanged,
26
30
  SimpleSearchResult,
27
31
  SearchType,
28
32
  ModelType,
29
33
  UserModel,
30
34
  )
31
35
  from feeluown.media import Media, Quality
36
+ from feeluown.utils.dispatch import Signal
32
37
  from feeluown.utils.reader import create_reader, SequentialReader
33
38
  from .api import API
34
39
  from .login import read_cookies
@@ -53,6 +58,9 @@ class Supports(
53
58
  SupportsPlaylistSongsReader,
54
59
  SupportsRecACollectionOfSongs,
55
60
  SupportsAlbumSongsReader,
61
+ SupportsCurrentUserDislikeSongsReader,
62
+ SupportsCurrentUserDislikeAddSong,
63
+ SupportsCurrentUserDislikeRemoveSong,
56
64
  Protocol,
57
65
  ):
58
66
  pass
@@ -70,6 +78,7 @@ class QQProvider(AbstractProvider, ProviderV2):
70
78
  def __init__(self):
71
79
  super().__init__()
72
80
  self.api = API()
81
+ self.current_user_changed = Signal()
73
82
 
74
83
  def _(self) -> Supports:
75
84
  return self
@@ -89,6 +98,7 @@ class QQProvider(AbstractProvider, ProviderV2):
89
98
  self.auth(user)
90
99
  else:
91
100
  logger.info(f'Auto login failed: {err}')
101
+ self.current_user_changed.emit(user)
92
102
 
93
103
  def try_get_user_from_cookies(self, cookies) -> Tuple[Optional[UserModel], str]:
94
104
  if not cookies: # is None or empty
@@ -417,6 +427,35 @@ class QQProvider(AbstractProvider, ProviderV2):
417
427
  data_songs = self.api.song_similar(int(song.identifier))
418
428
  return [_deserialize(data_song, QQSongSchema) for data_song in data_songs]
419
429
 
430
+ def current_user_dislike_create_songs_rd(self):
431
+ user = self.get_current_user()
432
+ if user is None:
433
+ return create_reader([])
434
+ # FIXME: 如果用户的黑名单歌曲数量较多的话,这样处理则是不够的
435
+ items = self.api.get_dislike_list(1, API.DislikeListType.song, 0)
436
+ songs = []
437
+ for item in items:
438
+ name = item['Name']
439
+ title, artists_name = name.split(' - ')
440
+ song = BriefSongModel(
441
+ source=SOURCE,
442
+ identifier=item['ID'],
443
+ title=title,
444
+ artists_name=artists_name,
445
+ )
446
+ songs.append(song)
447
+ return create_reader(songs)
448
+
449
+ def current_user_dislike_add_song(self, song):
450
+ items = [{'ID': song.identifier}]
451
+ js = self.api.add_to_dislike_list(items, API.DislikeListType.song)
452
+ return js.get('Retcode') == 0
453
+
454
+ def current_user_dislike_remove_song(self, song):
455
+ items = [{'ID': song.identifier}]
456
+ js = self.api.remove_from_dislike_list(items, API.DislikeListType.song)
457
+ return js.get('Retcode') == 0
458
+
420
459
 
421
460
  def _deserialize(data, schema_cls):
422
461
  schema = schema_cls()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fuo_qqmusic
3
- Version: 1.0.6
3
+ Version: 1.0.7
4
4
  Summary: feeluown qqmusic plugin
5
5
  Home-page: https://github.com/feeluown/feeluown-qqmusic
6
6
  Author: Cosven
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.9
15
15
  Classifier: Programming Language :: Python :: 3 :: Only
16
16
  Requires-Dist: feeluown>=4.1.3
17
17
  Requires-Dist: requests
18
- Requires-Dist: marshmallow>=3.0
18
+ Requires-Dist: marshmallow<4.0.0,>=3.0
19
19
  Dynamic: author
20
20
  Dynamic: author-email
21
21
  Dynamic: classifier
@@ -15,4 +15,5 @@ fuo_qqmusic.egg-info/entry_points.txt
15
15
  fuo_qqmusic.egg-info/requires.txt
16
16
  fuo_qqmusic.egg-info/top_level.txt
17
17
  fuo_qqmusic/assets/icon.svg
18
+ tests/test_api.py
18
19
  tests/test_provider.py
@@ -1,3 +1,3 @@
1
1
  feeluown>=4.1.3
2
2
  requests
3
- marshmallow>=3.0
3
+ marshmallow<4.0.0,>=3.0
@@ -5,7 +5,7 @@ from setuptools import setup
5
5
 
6
6
  setup(
7
7
  name='fuo_qqmusic',
8
- version='1.0.6',
8
+ version='1.0.7',
9
9
  description='feeluown qqmusic plugin',
10
10
  author='Cosven',
11
11
  author_email='yinshaowen241@gmail.com',
@@ -29,7 +29,7 @@ setup(
29
29
  install_requires=[
30
30
  'feeluown>=4.1.3',
31
31
  'requests',
32
- 'marshmallow>=3.0'
32
+ 'marshmallow>=3.0,<4.0.0'
33
33
  ],
34
34
  entry_points={
35
35
  'fuo.plugins_v1': [
@@ -0,0 +1,56 @@
1
+ from fuo_qqmusic.api import API
2
+ import pytest
3
+
4
+
5
+ def parse_cookie_to_dict(cookie_str):
6
+ # 初始化结果字典
7
+ result = {}
8
+
9
+ # 按分号分割字符串,得到每个键值对
10
+ pairs = cookie_str.split(";")
11
+
12
+ for pair in pairs:
13
+ # 去除两端空格
14
+ pair = pair.strip()
15
+
16
+ # 按等号分割键和值
17
+ if "=" in pair:
18
+ key, value = pair.split("=", 1)
19
+ key = key.strip()
20
+ value = value.strip()
21
+
22
+ # 如果值为空字符串,则设置为 None
23
+ if value == "":
24
+ value = None
25
+
26
+ # 存入字典
27
+ result[key] = value
28
+
29
+ return result
30
+
31
+
32
+ cookie_str = "Your cookie string here"
33
+
34
+
35
+ @pytest.mark.skip(reason="need valid cookies")
36
+ def test_api():
37
+ api = API()
38
+ api.set_cookies(parse_cookie_to_dict(cookie_str))
39
+ # You can also use the following code to load cookies
40
+ # from fuo_qqmusic.provider import provider
41
+ # provider.auto_login()
42
+ # api = provider.api
43
+ items = [
44
+ {
45
+ "ID": "238159921",
46
+ "Name": "无人区-Vacuum Track#ADD8E6- - 米缐p.",
47
+ "IdType": 0,
48
+ }
49
+ ]
50
+ print(api.add_to_dislike_list(items, type_=API.DislikeListType.song))
51
+ print(api.get_dislike_list(type_=API.DislikeListType.song))
52
+ print(api.remove_from_dislike_list(items, type_=API.DislikeListType.song))
53
+
54
+
55
+ if __name__ == "__main__":
56
+ test_api()
File without changes