fuo-qqmusic 1.0.7__tar.gz → 1.0.9__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.7 → fuo_qqmusic-1.0.9}/PKG-INFO +2 -2
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/README.md +7 -0
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic/api.py +31 -0
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic/provider.py +27 -1
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic/provider_ui.py +15 -6
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic/schemas.py +1 -1
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic.egg-info/PKG-INFO +2 -2
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic.egg-info/requires.txt +1 -1
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/setup.py +2 -2
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic/__init__.py +0 -0
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic/assets/icon.svg +0 -0
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic/consts.py +0 -0
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic/excs.py +0 -0
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic/login.py +0 -0
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic.egg-info/SOURCES.txt +0 -0
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic.egg-info/dependency_links.txt +0 -0
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic.egg-info/entry_points.txt +0 -0
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/fuo_qqmusic.egg-info/top_level.txt +0 -0
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/setup.cfg +0 -0
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/tests/test_api.py +0 -0
- {fuo_qqmusic-1.0.7 → fuo_qqmusic-1.0.9}/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.
|
|
3
|
+
Version: 1.0.9
|
|
4
4
|
Summary: feeluown qqmusic plugin
|
|
5
5
|
Home-page: https://github.com/feeluown/feeluown-qqmusic
|
|
6
6
|
Author: Cosven
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3.7
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.8
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.9
|
|
15
15
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
-
Requires-Dist: feeluown>=4.1.
|
|
16
|
+
Requires-Dist: feeluown>=4.1.13
|
|
17
17
|
Requires-Dist: requests
|
|
18
18
|
Requires-Dist: marshmallow<4.0.0,>=3.0
|
|
19
19
|
Dynamic: author
|
|
@@ -20,6 +20,13 @@ pip3 install fuo-qqmusic
|
|
|
20
20
|
[操作示例](https://github.com/feeluown/feeluown-qqmusic/issues/6)。
|
|
21
21
|
|
|
22
22
|
## changelog
|
|
23
|
+
### 1.0.9 (2025-07-24)
|
|
24
|
+
- 增加“重新登录”按钮(仅 FeelUOwn>=4.1.14 可用)
|
|
25
|
+
|
|
26
|
+
### 1.0.8 (2025-07-03)
|
|
27
|
+
- 增强 web 登录可靠性
|
|
28
|
+
- 歌单支持添加/删除歌曲
|
|
29
|
+
|
|
23
30
|
### 1.0.7 (2025-05-31)
|
|
24
31
|
- 支持音乐黑名单
|
|
25
32
|
|
|
@@ -287,6 +287,34 @@ class API(object):
|
|
|
287
287
|
resp = requests.get(url, params=params)
|
|
288
288
|
return resp.json()['data']
|
|
289
289
|
|
|
290
|
+
def playlist_remove_songs(self, playlist_id, song_id_list):
|
|
291
|
+
payload = {
|
|
292
|
+
'req_0': {
|
|
293
|
+
'method': 'DelSonglist',
|
|
294
|
+
'module': 'music.musicasset.PlaylistDetailWrite',
|
|
295
|
+
'param': {
|
|
296
|
+
'dirId': playlist_id, # int
|
|
297
|
+
'v_songInfo': [{'songId': int(song_id), 'songType': 0} for song_id in song_id_list]
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
js = self.rpc(payload)
|
|
302
|
+
return js['req_0']['code'] == 0
|
|
303
|
+
|
|
304
|
+
def playlist_add_songs(self, playlist_id, song_id_list):
|
|
305
|
+
payload = {
|
|
306
|
+
'req_0': {
|
|
307
|
+
'method': 'AddSonglist',
|
|
308
|
+
'module': 'music.musicasset.PlaylistDetailWrite',
|
|
309
|
+
'param': {
|
|
310
|
+
'dirId': int(playlist_id), # int
|
|
311
|
+
'v_songInfo': [{'songId': int(song_id), 'songType': 0} for song_id in song_id_list]
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
js = self.rpc(payload)
|
|
316
|
+
return js['req_0']['code'] == 0
|
|
317
|
+
|
|
290
318
|
def playlist_detail(self, pid, offset=0, limit=50):
|
|
291
319
|
url = api_base_url + '/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg'
|
|
292
320
|
params = {
|
|
@@ -319,6 +347,7 @@ class API(object):
|
|
|
319
347
|
resp = requests.get(url, params=params, headers=self._headers,
|
|
320
348
|
cookies=self._cookies, timeout=self._timeout)
|
|
321
349
|
js = resp.json()
|
|
350
|
+
logger.debug(f"user detail response: {js}")
|
|
322
351
|
if js['code'] != 0:
|
|
323
352
|
raise CodeShouldBe0(js)
|
|
324
353
|
return js['data']
|
|
@@ -438,6 +467,7 @@ class API(object):
|
|
|
438
467
|
def rpc(self, payload):
|
|
439
468
|
if 'comm' not in payload:
|
|
440
469
|
payload['comm'] = self.get_common_params()
|
|
470
|
+
logger.debug(f"rpc payload: {payload}")
|
|
441
471
|
data_str = json.dumps(payload, ensure_ascii=False)
|
|
442
472
|
params = {
|
|
443
473
|
'_': int(round(time.time() * 1000)),
|
|
@@ -448,6 +478,7 @@ class API(object):
|
|
|
448
478
|
resp = requests.get(url, params=params, headers=self._headers,
|
|
449
479
|
cookies=self._cookies, timeout=self._timeout)
|
|
450
480
|
js = resp.json()
|
|
481
|
+
logger.debug(f"rpc response json: {js}")
|
|
451
482
|
CodeShouldBe0.check(js)
|
|
452
483
|
return js
|
|
453
484
|
|
|
@@ -4,6 +4,7 @@ from feeluown.excs import ModelNotFound
|
|
|
4
4
|
from feeluown.library import (
|
|
5
5
|
AbstractProvider,
|
|
6
6
|
BriefSongModel,
|
|
7
|
+
BriefPlaylistModel,
|
|
7
8
|
PlaylistModel,
|
|
8
9
|
Collection,
|
|
9
10
|
CollectionType,
|
|
@@ -280,6 +281,22 @@ class QQProvider(AbstractProvider, ProviderV2):
|
|
|
280
281
|
data = self.api.playlist_detail(int(identifier), limit=1000)
|
|
281
282
|
return _deserialize(data, QQPlaylistSchema)
|
|
282
283
|
|
|
284
|
+
def playlist_add_song(self, playlist, song):
|
|
285
|
+
# FIXME: 目前 playlist 相关接口用的都是 diss 结构体,而这里需要一个 dirid。
|
|
286
|
+
# 平台方也提供了 dir 相关的接口,我大胆猜测,diss 是一套老接口。
|
|
287
|
+
playlist._cache.pop("songs", None)
|
|
288
|
+
dirid = self._get_dirid_by_playlist_id(playlist.identifier)
|
|
289
|
+
return self.api.playlist_add_songs(dirid, [song.identifier])
|
|
290
|
+
|
|
291
|
+
def playlist_remove_song(self, playlist, song):
|
|
292
|
+
playlist._cache.pop("songs", None)
|
|
293
|
+
dirid = self._get_dirid_by_playlist_id(playlist.identifier)
|
|
294
|
+
return self.api.playlist_remove_songs(dirid, [song.identifier])
|
|
295
|
+
|
|
296
|
+
def _get_dirid_by_playlist_id(self, playlist_id):
|
|
297
|
+
data = self.api.playlist_detail(int(playlist_id), limit=1)
|
|
298
|
+
return data["dirid"]
|
|
299
|
+
|
|
283
300
|
def playlist_create_songs_rd(self, playlist):
|
|
284
301
|
songs = self._model_cache_get_or_fetch(playlist, "songs")
|
|
285
302
|
return create_reader(songs)
|
|
@@ -381,7 +398,13 @@ class QQProvider(AbstractProvider, ProviderV2):
|
|
|
381
398
|
if user is None:
|
|
382
399
|
return []
|
|
383
400
|
playlists = self._model_cache_get_or_fetch(user, "playlists")
|
|
384
|
-
|
|
401
|
+
fav_pid = self._model_cache_get_or_fetch(user, "fav_pid")
|
|
402
|
+
my_love = BriefPlaylistModel(
|
|
403
|
+
source=SOURCE,
|
|
404
|
+
identifier=str(fav_pid),
|
|
405
|
+
name='我喜欢'
|
|
406
|
+
)
|
|
407
|
+
return [my_love] + playlists
|
|
385
408
|
|
|
386
409
|
def current_user_fav_create_songs_rd(self):
|
|
387
410
|
user = self.get_current_user()
|
|
@@ -415,6 +438,9 @@ class QQProvider(AbstractProvider, ProviderV2):
|
|
|
415
438
|
return create_reader([])
|
|
416
439
|
mid = self._model_cache_get_or_fetch(user, "mid")
|
|
417
440
|
playlists = self.api.user_favorite_playlists(user.identifier, mid)
|
|
441
|
+
# HACK: 给 playlist 加一个 disstid 字段,这样可以兼容 QQPlaylistSchema
|
|
442
|
+
for playlist in playlists:
|
|
443
|
+
playlist["disstid"] = playlist["dissid"]
|
|
418
444
|
return [_deserialize(playlist, QQPlaylistSchema) for playlist in playlists]
|
|
419
445
|
|
|
420
446
|
def has_current_user(self):
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import logging
|
|
3
2
|
import os
|
|
4
3
|
|
|
4
|
+
from PyQt5.QtWidgets import QMenu
|
|
5
|
+
|
|
5
6
|
from feeluown.utils.dispatch import Signal
|
|
6
7
|
from feeluown.utils.aio import run_fn
|
|
7
|
-
from feeluown.consts import DATA_DIR
|
|
8
8
|
from feeluown.gui.widgets.login import CookiesLoginDialog, InvalidCookies
|
|
9
9
|
from feeluown.gui.provider_ui import AbstractProviderUi
|
|
10
10
|
from feeluown.app.gui_app import GuiApp
|
|
11
11
|
|
|
12
12
|
from .provider import provider
|
|
13
|
-
from .excs import QQIOError
|
|
14
13
|
from .login import read_cookies, write_cookies
|
|
15
14
|
|
|
16
15
|
logger = logging.getLogger(__name__)
|
|
@@ -29,7 +28,7 @@ class ProviderUI(AbstractProviderUi):
|
|
|
29
28
|
return os.path.join(os.path.dirname(__file__), 'assets', 'icon.svg')
|
|
30
29
|
|
|
31
30
|
def login_or_go_home(self):
|
|
32
|
-
if provider.
|
|
31
|
+
if not provider.has_current_user():
|
|
33
32
|
# According to #14, we have two ways to login:
|
|
34
33
|
# 1. the default way, as the code shows
|
|
35
34
|
# 2. a way for VIP user(maybe):
|
|
@@ -39,8 +38,9 @@ class ProviderUI(AbstractProviderUi):
|
|
|
39
38
|
#
|
|
40
39
|
# - keys: ['skey']
|
|
41
40
|
url = os.getenv('FUO_QQMUSIC_LOGIN_URL', 'https://y.qq.com')
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
keys_str = os.getenv('FUO_QQMUSIC_LOGIN_COOKIE_KEYS',
|
|
42
|
+
'qqmusic_key,wxuin|qqmusic_key,uin')
|
|
43
|
+
self._dialog = LoginDialog(url, [keys.split(',') for keys in keys_str.split('|')])
|
|
44
44
|
self._dialog.login_succeed.connect(self.on_login_succeed)
|
|
45
45
|
self._dialog.show()
|
|
46
46
|
self._dialog.autologin()
|
|
@@ -52,10 +52,19 @@ class ProviderUI(AbstractProviderUi):
|
|
|
52
52
|
def login_event(self):
|
|
53
53
|
return self._login_event
|
|
54
54
|
|
|
55
|
+
def context_menu_add_items(self, menu: QMenu):
|
|
56
|
+
action = menu.addAction('重新登录')
|
|
57
|
+
action.triggered.connect(self._re_login)
|
|
58
|
+
|
|
55
59
|
def on_login_succeed(self):
|
|
56
60
|
del self._dialog
|
|
57
61
|
self.login_event.emit(self, 1)
|
|
58
62
|
|
|
63
|
+
def _re_login(self):
|
|
64
|
+
provider.auth(None)
|
|
65
|
+
self.login_or_go_home()
|
|
66
|
+
|
|
67
|
+
|
|
59
68
|
|
|
60
69
|
class LoginDialog(CookiesLoginDialog):
|
|
61
70
|
|
|
@@ -271,7 +271,7 @@ class QQAlbumSchema(Schema):
|
|
|
271
271
|
|
|
272
272
|
|
|
273
273
|
class QQPlaylistSchema(Schema):
|
|
274
|
-
identifier = fields.Int(required=True, data_key="
|
|
274
|
+
identifier = fields.Int(required=True, data_key="disstid")
|
|
275
275
|
name = fields.Str(required=True, data_key="dissname")
|
|
276
276
|
cover = fields.Str(required=True, data_key="logo")
|
|
277
277
|
# songs field maybe null, though it can't be null in model
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fuo_qqmusic
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.9
|
|
4
4
|
Summary: feeluown qqmusic plugin
|
|
5
5
|
Home-page: https://github.com/feeluown/feeluown-qqmusic
|
|
6
6
|
Author: Cosven
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3.7
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.8
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.9
|
|
15
15
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
-
Requires-Dist: feeluown>=4.1.
|
|
16
|
+
Requires-Dist: feeluown>=4.1.13
|
|
17
17
|
Requires-Dist: requests
|
|
18
18
|
Requires-Dist: marshmallow<4.0.0,>=3.0
|
|
19
19
|
Dynamic: author
|
|
@@ -5,7 +5,7 @@ from setuptools import setup
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name='fuo_qqmusic',
|
|
8
|
-
version='1.0.
|
|
8
|
+
version='1.0.9',
|
|
9
9
|
description='feeluown qqmusic plugin',
|
|
10
10
|
author='Cosven',
|
|
11
11
|
author_email='yinshaowen241@gmail.com',
|
|
@@ -27,7 +27,7 @@ setup(
|
|
|
27
27
|
'Programming Language :: Python :: 3 :: Only',
|
|
28
28
|
],
|
|
29
29
|
install_requires=[
|
|
30
|
-
'feeluown>=4.1.
|
|
30
|
+
'feeluown>=4.1.13',
|
|
31
31
|
'requests',
|
|
32
32
|
'marshmallow>=3.0,<4.0.0'
|
|
33
33
|
],
|
|
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
|