qqmusic-api-python 0.1.5__tar.gz → 0.1.6__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.1.5 → qqmusic_api_python-0.1.6}/PKG-INFO +5 -5
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/README.md +3 -2
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/pyproject.toml +6 -6
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/__init__.py +6 -4
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/album.py +27 -9
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/login.json +9 -20
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/search.json +9 -10
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/song.json +2 -1
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/login.py +84 -69
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/mv.py +2 -5
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/search.py +31 -49
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/singer.py +24 -42
- qqmusic_api_python-0.1.6/qqmusic_api/song.py +284 -0
- qqmusic_api_python-0.1.6/qqmusic_api/songlist.py +56 -0
- qqmusic_api_python-0.1.6/qqmusic_api/top.py +52 -0
- qqmusic_api_python-0.1.6/qqmusic_api/utils/credential.py +131 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/utils/network.py +25 -28
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/utils/qimei.py +3 -4
- qqmusic_api_python-0.1.6/qqmusic_api/utils/utils.py +52 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/conftest.py +1 -1
- qqmusic_api_python-0.1.6/tests/test_qimei.py +5 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_singer.py +0 -7
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_song.py +3 -7
- qqmusic_api_python-0.1.5/qqmusic_api/song.py +0 -415
- qqmusic_api_python-0.1.5/qqmusic_api/songlist.py +0 -85
- qqmusic_api_python-0.1.5/qqmusic_api/top.py +0 -105
- qqmusic_api_python-0.1.5/qqmusic_api/utils/credential.py +0 -89
- qqmusic_api_python-0.1.5/qqmusic_api/utils/utils.py +0 -130
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/LICENSE +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/album.json +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/mv.json +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/singer.json +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/songlist.json +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/top.json +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/file_type.json +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/search_type.json +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/exceptions/ApiException.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/exceptions/CredentialNoMusicidException.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/exceptions/CredentialNoMusickeyException.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/exceptions/CredentialNoRefreshkeyException.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/exceptions/LoginException.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/exceptions/ResponseCodeException.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/exceptions/__init__.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/utils/__init__.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/utils/sync.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/__init__.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_album.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_login.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_mv.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_search.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_songlist.py +0 -0
- {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_top.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: qqmusic-api-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: QQ音乐API封装库
|
|
5
5
|
Keywords: music,api,qqmusic,tencentmusic
|
|
6
6
|
Home-page: https://github.com/luren-dc/QQMusicApi
|
|
@@ -22,9 +22,8 @@ Project-URL: Repository, https://github.com/luren-dc/QQMusicApi
|
|
|
22
22
|
Project-URL: Documentation, https://github.com/luren-dc/QQMusicApi
|
|
23
23
|
Requires-Python: <4.0,>=3.9
|
|
24
24
|
Requires-Dist: cryptography<42.0.0,>=41.0.2
|
|
25
|
-
Requires-Dist: requests<3.0.0,>=2.31.0
|
|
26
|
-
Requires-Dist: aiohttp<4.0.0,>=3.9.5
|
|
27
25
|
Requires-Dist: typing-extensions>=4.12.2
|
|
26
|
+
Requires-Dist: httpx>=0.27.0
|
|
28
27
|
Description-Content-Type: text/markdown
|
|
29
28
|
|
|
30
29
|
<div align="center">
|
|
@@ -71,9 +70,10 @@ Description-Content-Type: text/markdown
|
|
|
71
70
|
|
|
72
71
|
## 依赖
|
|
73
72
|
|
|
74
|
-
- [AIOHTTP](https://docs.aiohttp.org/)
|
|
75
|
-
- [Requests](https://requests.readthedocs.io/)
|
|
73
|
+
- ~~[AIOHTTP](https://docs.aiohttp.org/)~~
|
|
74
|
+
- ~~[Requests](https://requests.readthedocs.io/)~~
|
|
76
75
|
- [Cryptography](https://cryptography.io/)
|
|
76
|
+
- [HTTPX](https://github.com/encode/httpx/)
|
|
77
77
|
|
|
78
78
|
## 快速上手
|
|
79
79
|
|
|
@@ -42,9 +42,10 @@
|
|
|
42
42
|
|
|
43
43
|
## 依赖
|
|
44
44
|
|
|
45
|
-
- [AIOHTTP](https://docs.aiohttp.org/)
|
|
46
|
-
- [Requests](https://requests.readthedocs.io/)
|
|
45
|
+
- ~~[AIOHTTP](https://docs.aiohttp.org/)~~
|
|
46
|
+
- ~~[Requests](https://requests.readthedocs.io/)~~
|
|
47
47
|
- [Cryptography](https://cryptography.io/)
|
|
48
|
+
- [HTTPX](https://github.com/encode/httpx/)
|
|
48
49
|
|
|
49
50
|
## 快速上手
|
|
50
51
|
|
|
@@ -12,9 +12,8 @@ authors = [
|
|
|
12
12
|
]
|
|
13
13
|
dependencies = [
|
|
14
14
|
"cryptography<42.0.0,>=41.0.2",
|
|
15
|
-
"requests<3.0.0,>=2.31.0",
|
|
16
|
-
"aiohttp<4.0.0,>=3.9.5",
|
|
17
15
|
"typing-extensions>=4.12.2",
|
|
16
|
+
"httpx>=0.27.0",
|
|
18
17
|
]
|
|
19
18
|
requires-python = "<4.0,>=3.9"
|
|
20
19
|
readme = "README.md"
|
|
@@ -40,7 +39,7 @@ classifiers = [
|
|
|
40
39
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
41
40
|
]
|
|
42
41
|
dynamic = []
|
|
43
|
-
version = "0.1.
|
|
42
|
+
version = "0.1.6"
|
|
44
43
|
|
|
45
44
|
[project.license]
|
|
46
45
|
text = "MIT"
|
|
@@ -73,7 +72,6 @@ docs = [
|
|
|
73
72
|
]
|
|
74
73
|
mypy = [
|
|
75
74
|
"mypy>=1.11.0",
|
|
76
|
-
"types-requests>=2.32.0.20240712",
|
|
77
75
|
]
|
|
78
76
|
linting = [
|
|
79
77
|
"ruff>=0.5.4",
|
|
@@ -121,9 +119,11 @@ convention = "google"
|
|
|
121
119
|
"tests/*" = [
|
|
122
120
|
"D",
|
|
123
121
|
]
|
|
122
|
+
"examples/*" = [
|
|
123
|
+
"D",
|
|
124
|
+
"T",
|
|
125
|
+
]
|
|
124
126
|
"qqmusic_api/login.py" = [
|
|
125
|
-
"F405",
|
|
126
|
-
"F403",
|
|
127
127
|
"D102",
|
|
128
128
|
]
|
|
129
129
|
"qqmusic_api/__init__.py" = [
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
from . import album, login, mv, search, singer, song, songlist, top
|
|
2
2
|
from .utils.credential import Credential
|
|
3
|
-
from .utils.network import
|
|
3
|
+
from .utils.network import get_session, set_session
|
|
4
|
+
from .utils.sync import sync
|
|
4
5
|
|
|
5
|
-
__version__ = "0.1.
|
|
6
|
+
__version__ = "0.1.6"
|
|
6
7
|
|
|
7
8
|
__all__ = [
|
|
8
9
|
"album",
|
|
9
10
|
"Credential",
|
|
10
|
-
"
|
|
11
|
+
"get_session",
|
|
11
12
|
"login",
|
|
12
13
|
"mv",
|
|
13
14
|
"search",
|
|
14
|
-
"
|
|
15
|
+
"set_session",
|
|
15
16
|
"singer",
|
|
16
17
|
"song",
|
|
17
18
|
"songlist",
|
|
19
|
+
"sync",
|
|
18
20
|
"top",
|
|
19
21
|
]
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
5
|
-
from .song import Song
|
|
6
5
|
from .utils.network import Api
|
|
7
6
|
from .utils.utils import get_api
|
|
8
7
|
|
|
@@ -14,27 +13,44 @@ class Album:
|
|
|
14
13
|
|
|
15
14
|
Attributes:
|
|
16
15
|
mid: 专辑 mid
|
|
16
|
+
id: 专辑 id
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
def __init__(
|
|
20
20
|
self,
|
|
21
|
+
*,
|
|
21
22
|
mid: Optional[str] = None,
|
|
22
23
|
id: Optional[int] = None,
|
|
23
24
|
):
|
|
24
|
-
"""
|
|
25
|
+
"""/// admonition | 注意
|
|
26
|
+
歌曲 mid 和 id,两者至少提供一个
|
|
27
|
+
///
|
|
25
28
|
|
|
26
29
|
Args:
|
|
27
30
|
mid: 专辑 mid
|
|
28
|
-
id:
|
|
31
|
+
id: 专辑 id
|
|
29
32
|
"""
|
|
30
|
-
# ID 检查
|
|
31
33
|
if mid is None and id is None:
|
|
32
34
|
raise ValueError("mid or id must be provided")
|
|
33
35
|
self.mid = mid
|
|
34
36
|
self.id = id
|
|
37
|
+
self._info: Optional[dict] = None
|
|
35
38
|
|
|
36
|
-
def
|
|
37
|
-
|
|
39
|
+
async def get_mid(self) -> str:
|
|
40
|
+
"""获取专辑 mid
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
专辑 mid
|
|
44
|
+
"""
|
|
45
|
+
return (await self.get_detail())["basicInfo"]["albumMid"]
|
|
46
|
+
|
|
47
|
+
async def get_id(self) -> int:
|
|
48
|
+
"""获取专辑 id
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
专辑 id
|
|
52
|
+
"""
|
|
53
|
+
return (await self.get_detail())["basicInfo"]["albumID"]
|
|
38
54
|
|
|
39
55
|
async def get_detail(self) -> dict:
|
|
40
56
|
"""获取专辑详细信息
|
|
@@ -42,13 +58,15 @@ class Album:
|
|
|
42
58
|
Returns:
|
|
43
59
|
专辑详细信息
|
|
44
60
|
"""
|
|
45
|
-
|
|
61
|
+
if not self._info:
|
|
62
|
+
self._info = await Api(**API["detail"]).update_params(albumMid=self.mid, albumId=self.id).result
|
|
63
|
+
return self._info
|
|
46
64
|
|
|
47
|
-
async def get_song(self) -> list[
|
|
65
|
+
async def get_song(self) -> list[dict]:
|
|
48
66
|
"""获取专辑歌曲
|
|
49
67
|
|
|
50
68
|
Returns:
|
|
51
69
|
歌曲列表
|
|
52
70
|
"""
|
|
53
71
|
result = await Api(**API["song"]).update_params(albumMid=self.mid, albumId=self.id, begin=0, num=0).result
|
|
54
|
-
return [
|
|
72
|
+
return [song["songInfo"] for song in result["songList"]]
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
"module": "QQConnectLogin.LoginServer",
|
|
4
4
|
"method": "QQLogin",
|
|
5
5
|
"params": {
|
|
6
|
-
"code": "str 鉴权码"
|
|
6
|
+
"code": "str 鉴权码",
|
|
7
|
+
"musicid": "int",
|
|
8
|
+
"musickey": "str",
|
|
9
|
+
"refresh_key": "str"
|
|
7
10
|
},
|
|
8
11
|
"extra_common": {
|
|
9
12
|
"tmeLoginType": "str 2"
|
|
@@ -15,7 +18,11 @@
|
|
|
15
18
|
"method": "Login",
|
|
16
19
|
"params": {
|
|
17
20
|
"strAppid": "wx48db31d50e334801",
|
|
18
|
-
"code": "str 鉴权码"
|
|
21
|
+
"code": "str 鉴权码",
|
|
22
|
+
"str_musicid": "str",
|
|
23
|
+
"musickey": "str",
|
|
24
|
+
"refresh_key": "str",
|
|
25
|
+
"loginMode": "int 2"
|
|
19
26
|
},
|
|
20
27
|
"extra_common": {
|
|
21
28
|
"tmeLoginType": "str 1"
|
|
@@ -47,23 +54,5 @@
|
|
|
47
54
|
"tmeLoginMethod": "str 3"
|
|
48
55
|
},
|
|
49
56
|
"comment": "发送验证码"
|
|
50
|
-
},
|
|
51
|
-
"refresh": {
|
|
52
|
-
"module": "music.login.LoginServer",
|
|
53
|
-
"method": "Login",
|
|
54
|
-
"params": {
|
|
55
|
-
"openid": "str",
|
|
56
|
-
"access_token": "str",
|
|
57
|
-
"refresh_token": "str",
|
|
58
|
-
"expired_in": "int",
|
|
59
|
-
"musicid": "str 必须",
|
|
60
|
-
"musickey": "str",
|
|
61
|
-
"refresh_key": "str 必须",
|
|
62
|
-
"loginMode": "int 2"
|
|
63
|
-
},
|
|
64
|
-
"extra_common": {
|
|
65
|
-
"tmeLoginMethod": "str 可填2"
|
|
66
|
-
},
|
|
67
|
-
"comment": "刷新cookies"
|
|
68
57
|
}
|
|
69
58
|
}
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
"module": "tencent_music_soso_smartbox_cgi.SmartBoxCgi",
|
|
12
12
|
"method": "GetSmartBoxResult",
|
|
13
13
|
"params": {
|
|
14
|
+
"search_id": "int 随机生成",
|
|
14
15
|
"query": "str 搜索词",
|
|
15
16
|
"num_per_page": "int 每页返回数量",
|
|
16
|
-
"
|
|
17
|
-
"page_idx": "int 页数"
|
|
17
|
+
"page_idx": "int 1"
|
|
18
18
|
},
|
|
19
19
|
"comment": "获取搜索词补全"
|
|
20
20
|
},
|
|
@@ -30,12 +30,13 @@
|
|
|
30
30
|
"module": "music.adaptor.SearchAdaptor",
|
|
31
31
|
"method": "do_search_v2",
|
|
32
32
|
"params": {
|
|
33
|
-
"
|
|
33
|
+
"searchid": "int 随机生成",
|
|
34
34
|
"search_type": "int 100",
|
|
35
35
|
"query": "str 搜索词",
|
|
36
|
-
"highlight": "int 是否高亮搜索词",
|
|
37
36
|
"grp": "int 是否返回歌曲其他版本",
|
|
38
|
-
"
|
|
37
|
+
"highlight": "int 是否高亮搜索词",
|
|
38
|
+
"page_id": "int 页数",
|
|
39
|
+
"page_num": "int 15"
|
|
39
40
|
},
|
|
40
41
|
"comment": "综合搜索"
|
|
41
42
|
},
|
|
@@ -46,10 +47,9 @@
|
|
|
46
47
|
"search_id": "int 随机生成",
|
|
47
48
|
"search_type": "int 搜索类型",
|
|
48
49
|
"query": "str 搜索词",
|
|
49
|
-
"highlight": "int 是否高亮搜索词",
|
|
50
|
-
"page_id": "int 页数",
|
|
51
50
|
"page_num": "int 页数",
|
|
52
51
|
"num_per_page": "int 每页返回数量",
|
|
52
|
+
"highlight": "int 是否高亮搜索词",
|
|
53
53
|
"selectors": "dict 选择器"
|
|
54
54
|
},
|
|
55
55
|
"comment": "桌面端搜索"
|
|
@@ -58,13 +58,12 @@
|
|
|
58
58
|
"module": "music.search.SearchCgiService",
|
|
59
59
|
"method": "DoSearchForQQMusicMobile",
|
|
60
60
|
"params": {
|
|
61
|
-
"
|
|
61
|
+
"searchid": "int 随机生成",
|
|
62
62
|
"search_type": "int 搜索类型",
|
|
63
63
|
"query": "str 搜索词",
|
|
64
|
-
"highlight": "int 是否高亮搜索词",
|
|
65
|
-
"page_id": "int 页数",
|
|
66
64
|
"page_num": "int 页数",
|
|
67
65
|
"grp": "int 是否返回歌曲其他版本",
|
|
66
|
+
"highlight": "int 是否高亮搜索词",
|
|
68
67
|
"num_per_page": "int 每页返回数量",
|
|
69
68
|
"selectors": "dict 选择器"
|
|
70
69
|
},
|
|
@@ -9,7 +9,7 @@ from abc import ABC, abstractmethod
|
|
|
9
9
|
from enum import Enum
|
|
10
10
|
from typing import Optional
|
|
11
11
|
|
|
12
|
-
import
|
|
12
|
+
import httpx
|
|
13
13
|
|
|
14
14
|
if sys.version_info >= (3, 12):
|
|
15
15
|
from typing import override
|
|
@@ -60,7 +60,7 @@ class Login(ABC):
|
|
|
60
60
|
"""登录基类
|
|
61
61
|
|
|
62
62
|
Attributes:
|
|
63
|
-
auth_url:
|
|
63
|
+
auth_url: 验证链接,用于鉴权和滑块验证
|
|
64
64
|
credential: 用户凭证
|
|
65
65
|
"""
|
|
66
66
|
|
|
@@ -73,7 +73,8 @@ class QRCodeLogin(Login):
|
|
|
73
73
|
"""二维码登录基类
|
|
74
74
|
|
|
75
75
|
Attributes:
|
|
76
|
-
musicid:
|
|
76
|
+
musicid: 登录账号
|
|
77
|
+
credential: 用户凭证
|
|
77
78
|
"""
|
|
78
79
|
|
|
79
80
|
def __init__(self) -> None:
|
|
@@ -81,12 +82,17 @@ class QRCodeLogin(Login):
|
|
|
81
82
|
self.musicid = ""
|
|
82
83
|
self._state: Optional[QrCodeLoginEvents] = None
|
|
83
84
|
self._qrcode_data: Optional[bytes] = None
|
|
84
|
-
self._session =
|
|
85
|
+
self._session = httpx.AsyncClient(
|
|
86
|
+
timeout=20,
|
|
87
|
+
headers={
|
|
88
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/116.0.1938.54",
|
|
89
|
+
},
|
|
90
|
+
)
|
|
85
91
|
|
|
86
92
|
async def close(self):
|
|
87
93
|
"""关闭登录会话"""
|
|
88
|
-
if not self._session.
|
|
89
|
-
await self._session.
|
|
94
|
+
if not self._session.is_closed:
|
|
95
|
+
await self._session.aclose()
|
|
90
96
|
|
|
91
97
|
async def __aenter__(self):
|
|
92
98
|
await self._session.__aenter__()
|
|
@@ -126,10 +132,13 @@ class QRCodeLogin(Login):
|
|
|
126
132
|
"""
|
|
127
133
|
|
|
128
134
|
|
|
129
|
-
# TODO: 优化扫码登录流程
|
|
130
135
|
class QQLogin(QRCodeLogin):
|
|
131
136
|
"""QQ登录"""
|
|
132
137
|
|
|
138
|
+
def __init__(self) -> None:
|
|
139
|
+
super().__init__()
|
|
140
|
+
self._session.headers.update({"Referer": "https://xui.ptlogin2.qq.com/"})
|
|
141
|
+
|
|
133
142
|
@override
|
|
134
143
|
async def get_qrcode(self):
|
|
135
144
|
if self._qrcode_data and self._state not in [
|
|
@@ -138,7 +147,7 @@ class QQLogin(QRCodeLogin):
|
|
|
138
147
|
QrCodeLoginEvents.DONE,
|
|
139
148
|
]:
|
|
140
149
|
return self._qrcode_data
|
|
141
|
-
|
|
150
|
+
res = await self._session.get(
|
|
142
151
|
"https://xui.ptlogin2.qq.com/cgi-bin/xlogin",
|
|
143
152
|
params={
|
|
144
153
|
"appid": "716027609",
|
|
@@ -154,9 +163,10 @@ class QQLogin(QRCodeLogin):
|
|
|
154
163
|
"theme": "2",
|
|
155
164
|
"verify_theme": "",
|
|
156
165
|
},
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
|
|
166
|
+
)
|
|
167
|
+
self._sig = res.cookies["pt_login_sig"]
|
|
168
|
+
|
|
169
|
+
res = await self._session.get(
|
|
160
170
|
"https://ssl.ptlogin2.qq.com/ptqrshow",
|
|
161
171
|
params={
|
|
162
172
|
"appid": "716027609",
|
|
@@ -169,20 +179,20 @@ class QQLogin(QRCodeLogin):
|
|
|
169
179
|
"daid": "383",
|
|
170
180
|
"pt_3rd_aid": "100497308",
|
|
171
181
|
},
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
182
|
+
)
|
|
183
|
+
self._ptqrtoken = hash33(res.cookies["qrsig"])
|
|
184
|
+
self._qrcode_data = res.read()
|
|
185
|
+
return self._qrcode_data
|
|
176
186
|
|
|
177
187
|
@override
|
|
178
188
|
async def get_qrcode_state(self):
|
|
179
189
|
if not self._qrcode_data:
|
|
180
190
|
raise LoginException("请先获取二维码")
|
|
181
|
-
|
|
191
|
+
res = await self._session.get(
|
|
182
192
|
"https://ssl.ptlogin2.qq.com/ptqrlogin",
|
|
183
193
|
params={
|
|
184
194
|
"u1": "https://graph.qq.com/oauth2.0/login_jump",
|
|
185
|
-
"ptqrtoken": self.
|
|
195
|
+
"ptqrtoken": self._ptqrtoken,
|
|
186
196
|
"ptredirect": "0",
|
|
187
197
|
"h": "1",
|
|
188
198
|
"t": "1",
|
|
@@ -192,15 +202,16 @@ class QQLogin(QRCodeLogin):
|
|
|
192
202
|
"action": f"0-0-{int(time.time() * 1000)}",
|
|
193
203
|
"js_ver": "20102616",
|
|
194
204
|
"js_type": "1",
|
|
195
|
-
"login_sig": self.
|
|
205
|
+
"login_sig": self._sig,
|
|
196
206
|
"pt_uistyle": "40",
|
|
197
207
|
"aid": "716027609",
|
|
198
208
|
"daid": "383",
|
|
199
209
|
"pt_3rd_aid": "100497308",
|
|
200
210
|
"has_onekey": "1",
|
|
201
211
|
},
|
|
202
|
-
)
|
|
203
|
-
|
|
212
|
+
)
|
|
213
|
+
data = res.text
|
|
214
|
+
|
|
204
215
|
text_to_state = {
|
|
205
216
|
"二维码未失效": QrCodeLoginEvents.SCAN,
|
|
206
217
|
"二维码认证中": QrCodeLoginEvents.CONF,
|
|
@@ -208,12 +219,16 @@ class QQLogin(QRCodeLogin):
|
|
|
208
219
|
"本次登录已被拒绝": QrCodeLoginEvents.REFUSE,
|
|
209
220
|
"登录成功": QrCodeLoginEvents.DONE,
|
|
210
221
|
}
|
|
222
|
+
|
|
211
223
|
state = QrCodeLoginEvents.OTHER
|
|
224
|
+
|
|
212
225
|
for text, value in text_to_state.items():
|
|
213
226
|
if text in data:
|
|
214
227
|
state = value
|
|
215
228
|
break
|
|
229
|
+
|
|
216
230
|
self._state = state
|
|
231
|
+
|
|
217
232
|
if state == QrCodeLoginEvents.DONE:
|
|
218
233
|
self.musicid = re.findall(r"&uin=(.+?)&service", data)[0]
|
|
219
234
|
self.auth_url = re.findall(r"'(https:.*?)'", data)[0]
|
|
@@ -223,24 +238,15 @@ class QQLogin(QRCodeLogin):
|
|
|
223
238
|
async def authorize(self):
|
|
224
239
|
if self.credential:
|
|
225
240
|
return self.credential
|
|
226
|
-
if self._state != QrCodeLoginEvents.DONE:
|
|
241
|
+
if self._state != QrCodeLoginEvents.DONE or self.auth_url is None:
|
|
227
242
|
raise LoginException("未完成二维码认证")
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
for header in set_cookie_header:
|
|
234
|
-
cookie: cookies.SimpleCookie = cookies.SimpleCookie()
|
|
235
|
-
cookie.load(header)
|
|
236
|
-
for key, morsel in cookie.items():
|
|
237
|
-
if morsel.value:
|
|
238
|
-
self._session.cookie_jar.update_cookies(cookie)
|
|
239
|
-
|
|
240
|
-
skey = self._session.cookie_jar.filter_cookies(self.auth_url).get("p_skey").value # type: ignore
|
|
241
|
-
async with self._session.post(
|
|
243
|
+
res = await self._session.get(self.auth_url, follow_redirects=True)
|
|
244
|
+
|
|
245
|
+
skey = self._session.cookies["p_skey"]
|
|
246
|
+
|
|
247
|
+
res = await self._session.post(
|
|
242
248
|
"https://graph.qq.com/oauth2.0/authorize",
|
|
243
|
-
|
|
249
|
+
headers={
|
|
244
250
|
"Referer": "https://graph.qq.com/oauth2.0/show?which=Login&display=pc&response_type=code&client_id"
|
|
245
251
|
"=100497308&redirect_uri=https://y.qq.com/portal/wx_redirect.html?login_type=1&surl=https"
|
|
246
252
|
"://y.qq.com/portal/profile.html#stat=y_new.top.user_pic&stat=y_new.top.pop.logout"
|
|
@@ -263,10 +269,10 @@ class QQLogin(QRCodeLogin):
|
|
|
263
269
|
"auth_time": str(int(time.time())),
|
|
264
270
|
"ui": uuid.uuid4(),
|
|
265
271
|
},
|
|
266
|
-
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
|
|
272
|
+
follow_redirects=False,
|
|
273
|
+
)
|
|
274
|
+
location = res.headers.get("Location", "")
|
|
275
|
+
code = re.findall(r"(?<=code=)(.+?)(?=&)", location)[0]
|
|
270
276
|
res = await Api(**API["QQ_login"]).update_params(code=code).update_extra_common(tmeLoginType="2").result
|
|
271
277
|
self.credential = Credential.from_cookies(res)
|
|
272
278
|
return self.credential
|
|
@@ -287,7 +293,7 @@ class WXLogin(QRCodeLogin):
|
|
|
287
293
|
QrCodeLoginEvents.DONE,
|
|
288
294
|
]:
|
|
289
295
|
return self._qrcode_data
|
|
290
|
-
|
|
296
|
+
res = await self._session.get(
|
|
291
297
|
"https://open.weixin.qq.com/connect/qrconnect",
|
|
292
298
|
params={
|
|
293
299
|
"appid": "wx48db31d50e334801",
|
|
@@ -297,9 +303,9 @@ class WXLogin(QRCodeLogin):
|
|
|
297
303
|
"state": "STATE",
|
|
298
304
|
"href": "https://y.qq.com/mediastyle/music_v17/src/css/popup_wechat.css#wechat_redirect",
|
|
299
305
|
},
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
|
|
306
|
+
)
|
|
307
|
+
self.__uuid = re.findall(r"uuid=(.+?)\"", res.text)[0]
|
|
308
|
+
res = await self._session.get(
|
|
303
309
|
f"https://open.weixin.qq.com/connect/qrcode/{self.__uuid}",
|
|
304
310
|
headers={
|
|
305
311
|
"referer": "https://open.weixin.qq.com/connect/qrconnect?appid=wx48db31d50e334801"
|
|
@@ -310,25 +316,30 @@ class WXLogin(QRCodeLogin):
|
|
|
310
316
|
"&href=https%3A%2F%2Fy.qq.com%2Fmediastyle%2Fmusic_v17%2Fsrc%2Fcss%2Fpopup_wechat.css"
|
|
311
317
|
"%23wechat_redirect"
|
|
312
318
|
},
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
|
|
319
|
+
)
|
|
320
|
+
self._qrcode_data = res.read()
|
|
321
|
+
return self._qrcode_data
|
|
316
322
|
|
|
317
323
|
@override
|
|
318
324
|
async def get_qrcode_state(self):
|
|
319
325
|
if not self._qrcode_data:
|
|
320
326
|
raise LoginException("请先获取二维码")
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
"
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
res = await self._session.get(
|
|
330
|
+
"https://lp.open.weixin.qq.com/connect/l/qrconnect",
|
|
331
|
+
headers={
|
|
332
|
+
"referer": "https://open.weixin.qq.com/",
|
|
333
|
+
},
|
|
334
|
+
params={
|
|
335
|
+
"uuid": self.__uuid,
|
|
336
|
+
"_": str(int(round(time.time() * 1000))),
|
|
337
|
+
},
|
|
338
|
+
)
|
|
339
|
+
except httpx.ReadTimeout:
|
|
340
|
+
return QrCodeLoginEvents.SCAN
|
|
341
|
+
|
|
342
|
+
data = res.text
|
|
332
343
|
|
|
333
344
|
text_to_state = {
|
|
334
345
|
"408": QrCodeLoginEvents.SCAN,
|
|
@@ -338,10 +349,12 @@ class WXLogin(QRCodeLogin):
|
|
|
338
349
|
}
|
|
339
350
|
state = QrCodeLoginEvents.OTHER
|
|
340
351
|
for text, value in text_to_state.items():
|
|
341
|
-
if text in
|
|
352
|
+
if text in data:
|
|
342
353
|
state = value
|
|
343
354
|
break
|
|
355
|
+
|
|
344
356
|
self._state = state
|
|
357
|
+
|
|
345
358
|
if state == QrCodeLoginEvents.DONE:
|
|
346
359
|
self.musicid = re.findall(r"wx_code='(.+?)';", data)[0]
|
|
347
360
|
self.auth_url = (
|
|
@@ -364,7 +377,7 @@ class WXLogin(QRCodeLogin):
|
|
|
364
377
|
return self.credential
|
|
365
378
|
if self._state != QrCodeLoginEvents.DONE:
|
|
366
379
|
raise LoginException("未完成二维码认证")
|
|
367
|
-
await self._session.get(self.auth_url,
|
|
380
|
+
await self._session.get(self.auth_url, follow_redirects=False) # type: ignore
|
|
368
381
|
res = (
|
|
369
382
|
await Api(**API["WX_login"])
|
|
370
383
|
.update_params(strAppid="wx48db31d50e334801", code=self.musicid)
|
|
@@ -391,6 +404,7 @@ class PhoneLogin(Login):
|
|
|
391
404
|
Raises:
|
|
392
405
|
ValueError: 非法手机号
|
|
393
406
|
"""
|
|
407
|
+
super().__init__()
|
|
394
408
|
if not re.compile(r"^1[3-9]\d{9}$").match(phone):
|
|
395
409
|
raise ValueError("非法手机号")
|
|
396
410
|
self.phone = phone
|
|
@@ -428,6 +442,8 @@ class PhoneLogin(Login):
|
|
|
428
442
|
Raises:
|
|
429
443
|
LoginException: 鉴权失败
|
|
430
444
|
"""
|
|
445
|
+
if self.credential:
|
|
446
|
+
return self.credential
|
|
431
447
|
if not authcode:
|
|
432
448
|
raise ValueError("authcode 为空")
|
|
433
449
|
params = {"code": str(authcode), "phoneNo": self.phone, "loginMode": 1}
|
|
@@ -443,7 +459,8 @@ class PhoneLogin(Login):
|
|
|
443
459
|
raise LoginException("验证码过期或错误")
|
|
444
460
|
else:
|
|
445
461
|
raise LoginException("未知情况,请提交 issue")
|
|
446
|
-
|
|
462
|
+
self.credential = Credential.from_cookies(res)
|
|
463
|
+
return self.credential
|
|
447
464
|
|
|
448
465
|
|
|
449
466
|
async def refresh_cookies(credential: Credential) -> Credential:
|
|
@@ -452,19 +469,17 @@ async def refresh_cookies(credential: Credential) -> Credential:
|
|
|
452
469
|
Args:
|
|
453
470
|
credential: 用户凭证
|
|
454
471
|
|
|
455
|
-
|
|
472
|
+
Returns:
|
|
456
473
|
新的用户凭证
|
|
457
474
|
"""
|
|
458
475
|
credential.raise_for_cannot_refresh()
|
|
459
476
|
params = {
|
|
460
477
|
"refresh_key": credential.refresh_key,
|
|
478
|
+
"refresh_token": credential.refresh_token,
|
|
479
|
+
"musickey": credential.musickey,
|
|
461
480
|
"musicid": credential.musicid,
|
|
462
|
-
"loginMode": 2,
|
|
463
481
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
.update_extra_common(tmeLoginType=str(credential.login_type))
|
|
468
|
-
.result
|
|
469
|
-
)
|
|
482
|
+
|
|
483
|
+
api = API["WX_login"] if credential.login_type == 1 else API["QQ_login"]
|
|
484
|
+
res = await Api(**api).update_params(**params).update_extra_common(tmeLoginType=str(credential.login_type)).result
|
|
470
485
|
return Credential.from_cookies(res)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import random
|
|
4
4
|
|
|
5
|
-
from .song import
|
|
5
|
+
from .song import query_song
|
|
6
6
|
from .utils.network import Api
|
|
7
7
|
from .utils.utils import get_api
|
|
8
8
|
|
|
@@ -24,9 +24,6 @@ class MV:
|
|
|
24
24
|
"""
|
|
25
25
|
self.vid = vid
|
|
26
26
|
|
|
27
|
-
def __repr__(self) -> str:
|
|
28
|
-
return f"MV(vid={self.vid})"
|
|
29
|
-
|
|
30
27
|
async def get_detail(self) -> dict:
|
|
31
28
|
"""获取 MV 详细信息
|
|
32
29
|
|
|
@@ -72,7 +69,7 @@ class MV:
|
|
|
72
69
|
"required": ["related_songs"],
|
|
73
70
|
}
|
|
74
71
|
song_id = (await Api(**API["detail"]).update_params(**param).result)[self.vid]["related_songs"]
|
|
75
|
-
return await
|
|
72
|
+
return await query_song(song_id)
|
|
76
73
|
|
|
77
74
|
async def get_url(self) -> dict:
|
|
78
75
|
"""获取 MV 播放链接
|