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.
Files changed (52) hide show
  1. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/PKG-INFO +5 -5
  2. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/README.md +3 -2
  3. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/pyproject.toml +6 -6
  4. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/__init__.py +6 -4
  5. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/album.py +27 -9
  6. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/login.json +9 -20
  7. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/search.json +9 -10
  8. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/song.json +2 -1
  9. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/login.py +84 -69
  10. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/mv.py +2 -5
  11. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/search.py +31 -49
  12. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/singer.py +24 -42
  13. qqmusic_api_python-0.1.6/qqmusic_api/song.py +284 -0
  14. qqmusic_api_python-0.1.6/qqmusic_api/songlist.py +56 -0
  15. qqmusic_api_python-0.1.6/qqmusic_api/top.py +52 -0
  16. qqmusic_api_python-0.1.6/qqmusic_api/utils/credential.py +131 -0
  17. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/utils/network.py +25 -28
  18. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/utils/qimei.py +3 -4
  19. qqmusic_api_python-0.1.6/qqmusic_api/utils/utils.py +52 -0
  20. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/conftest.py +1 -1
  21. qqmusic_api_python-0.1.6/tests/test_qimei.py +5 -0
  22. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_singer.py +0 -7
  23. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_song.py +3 -7
  24. qqmusic_api_python-0.1.5/qqmusic_api/song.py +0 -415
  25. qqmusic_api_python-0.1.5/qqmusic_api/songlist.py +0 -85
  26. qqmusic_api_python-0.1.5/qqmusic_api/top.py +0 -105
  27. qqmusic_api_python-0.1.5/qqmusic_api/utils/credential.py +0 -89
  28. qqmusic_api_python-0.1.5/qqmusic_api/utils/utils.py +0 -130
  29. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/LICENSE +0 -0
  30. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/album.json +0 -0
  31. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/mv.json +0 -0
  32. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/singer.json +0 -0
  33. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/songlist.json +0 -0
  34. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/api/top.json +0 -0
  35. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/file_type.json +0 -0
  36. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/data/search_type.json +0 -0
  37. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/exceptions/ApiException.py +0 -0
  38. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/exceptions/CredentialNoMusicidException.py +0 -0
  39. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/exceptions/CredentialNoMusickeyException.py +0 -0
  40. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/exceptions/CredentialNoRefreshkeyException.py +0 -0
  41. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/exceptions/LoginException.py +0 -0
  42. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/exceptions/ResponseCodeException.py +0 -0
  43. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/exceptions/__init__.py +0 -0
  44. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/utils/__init__.py +0 -0
  45. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/qqmusic_api/utils/sync.py +0 -0
  46. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/__init__.py +0 -0
  47. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_album.py +0 -0
  48. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_login.py +0 -0
  49. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_mv.py +0 -0
  50. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_search.py +0 -0
  51. {qqmusic_api_python-0.1.5 → qqmusic_api_python-0.1.6}/tests/test_songlist.py +0 -0
  52. {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.5
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.5"
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 get_aiohttp_session, set_aiohttp_session
3
+ from .utils.network import get_session, set_session
4
+ from .utils.sync import sync
4
5
 
5
- __version__ = "0.1.5"
6
+ __version__ = "0.1.6"
6
7
 
7
8
  __all__ = [
8
9
  "album",
9
10
  "Credential",
10
- "get_aiohttp_session",
11
+ "get_session",
11
12
  "login",
12
13
  "mv",
13
14
  "search",
14
- "set_aiohttp_session",
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: 专辑 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 __repr__(self) -> str:
37
- return f"Album(mid={self.mid}, id={self.id})"
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
- return await Api(**API["detail"]).update_params(albumMid=self.mid, albumId=self.id).result
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[Song]:
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 [Song.from_dict(song["songInfo"]) for song in result["songList"]]
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
- "highlight": "int 是否高亮搜索词",
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
- "search_id": "int 随机生成",
33
+ "searchid": "int 随机生成",
34
34
  "search_type": "int 100",
35
35
  "query": "str 搜索词",
36
- "highlight": "int 是否高亮搜索词",
37
36
  "grp": "int 是否返回歌曲其他版本",
38
- "page_id": "int 页数"
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
- "search_id": "int 随机生成",
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
  },
@@ -50,7 +50,8 @@
50
50
  "method": "GetSongRelatedMv",
51
51
  "params": {
52
52
  "songid": "int 歌曲 ID",
53
- "songmid": "str 歌曲 mid"
53
+ "songtype": "int 1",
54
+ "lastmvid": "用于刷新列表"
54
55
  },
55
56
  "comment": "获取相关MV"
56
57
  },
@@ -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 aiohttp
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 = aiohttp.ClientSession()
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.closed:
89
- await self._session.close()
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
- async with self._session.get(
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
- ) as res:
158
- self.sig = res.cookies.get("pt_login_sig").value
159
- async with self._session.get(
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
- ) as res:
173
- self.ptqrtoken = hash33(res.cookies.get("qrsig").value)
174
- self._qrcode_data = await res.read()
175
- return self._qrcode_data
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
- async with self._session.get(
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.ptqrtoken,
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.sig,
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
- ) as res:
203
- data = await res.text()
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
- # TODO: 优化获取 p_skey
229
- async with self._session.get(self.auth_url, allow_redirects=False) as res: # type: ignore
230
- from http import cookies
231
-
232
- set_cookie_header = res.headers.getall("Set-Cookie", [])
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
- params={
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
- allow_redirects=False,
267
- ) as res:
268
- location = res.headers.get("Location", "")
269
- code = re.findall(r"(?<=code=)(.+?)(?=&)", location)[0]
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
- async with self._session.get(
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
- ) as res:
301
- self.__uuid = re.findall(r"uuid=(.+?)\"", await res.text())[0]
302
- async with self._session.get(
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
- ) as res:
314
- self._qrcode_data = await res.read()
315
- return self._qrcode_data
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
- async with self._session.get(
322
- "https://lp.open.weixin.qq.com/connect/l/qrconnect",
323
- headers={
324
- "referer": "https://open.weixin.qq.com/",
325
- },
326
- params={
327
- "uuid": self.__uuid,
328
- "_": str(int(round(time.time() * 1000))),
329
- },
330
- ) as res:
331
- data = await res.text()
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 await res.text():
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, allow_redirects=False) # type: ignore
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
- return Credential.from_cookies(res)
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
- Return:
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
- res = (
465
- await Api(**API["refresh"])
466
- .update_params(**params)
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 query_by_id
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 query_by_id(song_id)
72
+ return await query_song(song_id)
76
73
 
77
74
  async def get_url(self) -> dict:
78
75
  """获取 MV 播放链接