qqmusic-api-python 0.1.4__tar.gz → 0.1.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.
Files changed (46) hide show
  1. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/PKG-INFO +2 -2
  2. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/README.md +1 -1
  3. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/pyproject.toml +6 -9
  4. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/__init__.py +1 -1
  5. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/album.py +15 -4
  6. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/data/api/album.json +3 -1
  7. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/login.py +28 -18
  8. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/singer.py +0 -1
  9. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/song.py +1 -1
  10. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/utils/network.py +2 -2
  11. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/LICENSE +0 -0
  12. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/data/api/login.json +0 -0
  13. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/data/api/mv.json +0 -0
  14. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/data/api/search.json +0 -0
  15. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/data/api/singer.json +0 -0
  16. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/data/api/song.json +0 -0
  17. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/data/api/songlist.json +0 -0
  18. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/data/api/top.json +0 -0
  19. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/data/file_type.json +0 -0
  20. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/data/search_type.json +0 -0
  21. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/exceptions/ApiException.py +0 -0
  22. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/exceptions/CredentialNoMusicidException.py +0 -0
  23. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/exceptions/CredentialNoMusickeyException.py +0 -0
  24. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/exceptions/CredentialNoRefreshkeyException.py +0 -0
  25. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/exceptions/LoginException.py +0 -0
  26. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/exceptions/ResponseCodeException.py +0 -0
  27. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/exceptions/__init__.py +0 -0
  28. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/mv.py +0 -0
  29. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/search.py +0 -0
  30. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/songlist.py +0 -0
  31. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/top.py +0 -0
  32. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/utils/__init__.py +0 -0
  33. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/utils/credential.py +0 -0
  34. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/utils/qimei.py +0 -0
  35. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/utils/sync.py +0 -0
  36. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/qqmusic_api/utils/utils.py +0 -0
  37. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/tests/__init__.py +0 -0
  38. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/tests/conftest.py +0 -0
  39. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/tests/test_album.py +0 -0
  40. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/tests/test_login.py +0 -0
  41. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/tests/test_mv.py +0 -0
  42. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/tests/test_search.py +0 -0
  43. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/tests/test_singer.py +0 -0
  44. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/tests/test_song.py +0 -0
  45. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/tests/test_songlist.py +0 -0
  46. {qqmusic_api_python-0.1.4 → qqmusic_api_python-0.1.5}/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.4
3
+ Version: 0.1.5
4
4
  Summary: QQ音乐API封装库
5
5
  Keywords: music,api,qqmusic,tencentmusic
6
6
  Home-page: https://github.com/luren-dc/QQMusicApi
@@ -39,7 +39,7 @@ Description-Content-Type: text/markdown
39
39
  <a href="https://pdm-project.org">
40
40
  <img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fcdn.jsdelivr.net%2Fgh%2Fpdm-project%2F.github%2Fbadge.json" alt="pdm-managed">
41
41
  </a>
42
- <a href="https://github.com/luren-dc/QQMusicApi/tree/build?tab=License-1-ov-file">
42
+ <a href="https://github.com/luren-dc/QQMusicApi?tab=MIT-1-ov-file">
43
43
  <img src="https://img.shields.io/github/license/luren-dc/PyQQMusicApi" alt="GitHub license">
44
44
  </a>
45
45
  <a href="https://github.com/luren-dc/QQMusicApi/stargazers">
@@ -10,7 +10,7 @@
10
10
  <a href="https://pdm-project.org">
11
11
  <img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fcdn.jsdelivr.net%2Fgh%2Fpdm-project%2F.github%2Fbadge.json" alt="pdm-managed">
12
12
  </a>
13
- <a href="https://github.com/luren-dc/QQMusicApi/tree/build?tab=License-1-ov-file">
13
+ <a href="https://github.com/luren-dc/QQMusicApi?tab=MIT-1-ov-file">
14
14
  <img src="https://img.shields.io/github/license/luren-dc/PyQQMusicApi" alt="GitHub license">
15
15
  </a>
16
16
  <a href="https://github.com/luren-dc/QQMusicApi/stargazers">
@@ -40,7 +40,7 @@ classifiers = [
40
40
  "Topic :: Software Development :: Libraries :: Python Modules",
41
41
  ]
42
42
  dynamic = []
43
- version = "0.1.4"
43
+ version = "0.1.5"
44
44
 
45
45
  [project.license]
46
46
  text = "MIT"
@@ -79,14 +79,6 @@ linting = [
79
79
  "ruff>=0.5.4",
80
80
  ]
81
81
 
82
- [tool.pdm.build]
83
- source-includes = [
84
- "qqmusic_api",
85
- "README.md",
86
- "LICENSE",
87
- "tests",
88
- ]
89
-
90
82
  [tool.pdm.scripts]
91
83
  docs = "mkdocs serve"
92
84
 
@@ -129,6 +121,11 @@ convention = "google"
129
121
  "tests/*" = [
130
122
  "D",
131
123
  ]
124
+ "qqmusic_api/login.py" = [
125
+ "F405",
126
+ "F403",
127
+ "D102",
128
+ ]
132
129
  "qqmusic_api/__init__.py" = [
133
130
  "F405",
134
131
  "F403",
@@ -2,7 +2,7 @@ from . import album, login, mv, search, singer, song, songlist, top
2
2
  from .utils.credential import Credential
3
3
  from .utils.network import get_aiohttp_session, set_aiohttp_session
4
4
 
5
- __version__ = "0.1.4"
5
+ __version__ = "0.1.5"
6
6
 
7
7
  __all__ = [
8
8
  "album",
@@ -1,5 +1,7 @@
1
1
  """专辑相关 API"""
2
2
 
3
+ from typing import Optional
4
+
3
5
  from .song import Song
4
6
  from .utils.network import Api
5
7
  from .utils.utils import get_api
@@ -14,16 +16,25 @@ class Album:
14
16
  mid: 专辑 mid
15
17
  """
16
18
 
17
- def __init__(self, mid: str):
19
+ def __init__(
20
+ self,
21
+ mid: Optional[str] = None,
22
+ id: Optional[int] = None,
23
+ ):
18
24
  """初始化专辑类
19
25
 
20
26
  Args:
21
27
  mid: 专辑 mid
28
+ id: 专辑 id
22
29
  """
30
+ # ID 检查
31
+ if mid is None and id is None:
32
+ raise ValueError("mid or id must be provided")
23
33
  self.mid = mid
34
+ self.id = id
24
35
 
25
36
  def __repr__(self) -> str:
26
- return f"Album(mid={self.mid})"
37
+ return f"Album(mid={self.mid}, id={self.id})"
27
38
 
28
39
  async def get_detail(self) -> dict:
29
40
  """获取专辑详细信息
@@ -31,7 +42,7 @@ class Album:
31
42
  Returns:
32
43
  专辑详细信息
33
44
  """
34
- return await Api(**API["detail"]).update_params(albumMid=self.mid).result
45
+ return await Api(**API["detail"]).update_params(albumMid=self.mid, albumId=self.id).result
35
46
 
36
47
  async def get_song(self) -> list[Song]:
37
48
  """获取专辑歌曲
@@ -39,5 +50,5 @@ class Album:
39
50
  Returns:
40
51
  歌曲列表
41
52
  """
42
- result = await Api(**API["song"]).update_params(albumMid=self.mid, begin=0, num=0).result
53
+ result = await Api(**API["song"]).update_params(albumMid=self.mid, albumId=self.id, begin=0, num=0).result
43
54
  return [Song.from_dict(song["songInfo"]) for song in result["songList"]]
@@ -3,7 +3,8 @@
3
3
  "module": "music.musichallAlbum.AlbumInfoServer",
4
4
  "method": "GetAlbumDetail",
5
5
  "params": {
6
- "albumMid": "str 专辑 mid"
6
+ "albumMid": "str 专辑 mid",
7
+ "albumId": "str 专辑 id"
7
8
  },
8
9
  "comment": "获取专辑信息"
9
10
  },
@@ -12,6 +13,7 @@
12
13
  "method": "GetAlbumSongList",
13
14
  "params": {
14
15
  "albumMid": "str 专辑 mid",
16
+ "albumId": "str 专辑 id",
15
17
  "begin": "int 开启位置",
16
18
  "num": "int 返回数量"
17
19
  },
@@ -2,6 +2,7 @@
2
2
 
3
3
  import random
4
4
  import re
5
+ import sys
5
6
  import time
6
7
  import uuid
7
8
  from abc import ABC, abstractmethod
@@ -9,7 +10,11 @@ from enum import Enum
9
10
  from typing import Optional
10
11
 
11
12
  import aiohttp
12
- from typing_extensions import override
13
+
14
+ if sys.version_info >= (3, 12):
15
+ from typing import override
16
+ else:
17
+ from typing_extensions import override
13
18
 
14
19
  from .exceptions import LoginException, ResponseCodeException
15
20
  from .utils.credential import Credential
@@ -51,7 +56,7 @@ class PhoneLoginEvents(Enum):
51
56
  OTHER = 5
52
57
 
53
58
 
54
- class Login:
59
+ class Login(ABC):
55
60
  """登录基类
56
61
 
57
62
  Attributes:
@@ -64,7 +69,7 @@ class Login:
64
69
  self.credential: Optional[Credential] = None
65
70
 
66
71
 
67
- class QRCodeLogin(Login, ABC):
72
+ class QRCodeLogin(Login):
68
73
  """二维码登录基类
69
74
 
70
75
  Attributes:
@@ -76,14 +81,19 @@ class QRCodeLogin(Login, ABC):
76
81
  self.musicid = ""
77
82
  self._state: Optional[QrCodeLoginEvents] = None
78
83
  self._qrcode_data: Optional[bytes] = None
84
+ self._session = aiohttp.ClientSession()
85
+
86
+ async def close(self):
87
+ """关闭登录会话"""
88
+ if not self._session.closed:
89
+ await self._session.close()
79
90
 
80
91
  async def __aenter__(self):
81
- self.session = aiohttp.ClientSession()
92
+ await self._session.__aenter__()
82
93
  return self
83
94
 
84
95
  async def __aexit__(self, exc_type, exc_val, exc_tb):
85
- if not self.session.closed:
86
- await self.session.close()
96
+ await self.close()
87
97
 
88
98
  @abstractmethod
89
99
  async def get_qrcode(self) -> bytes:
@@ -128,7 +138,7 @@ class QQLogin(QRCodeLogin):
128
138
  QrCodeLoginEvents.DONE,
129
139
  ]:
130
140
  return self._qrcode_data
131
- async with self.session.get(
141
+ async with self._session.get(
132
142
  "https://xui.ptlogin2.qq.com/cgi-bin/xlogin",
133
143
  params={
134
144
  "appid": "716027609",
@@ -146,7 +156,7 @@ class QQLogin(QRCodeLogin):
146
156
  },
147
157
  ) as res:
148
158
  self.sig = res.cookies.get("pt_login_sig").value
149
- async with self.session.get(
159
+ async with self._session.get(
150
160
  "https://ssl.ptlogin2.qq.com/ptqrshow",
151
161
  params={
152
162
  "appid": "716027609",
@@ -168,7 +178,7 @@ class QQLogin(QRCodeLogin):
168
178
  async def get_qrcode_state(self):
169
179
  if not self._qrcode_data:
170
180
  raise LoginException("请先获取二维码")
171
- async with self.session.get(
181
+ async with self._session.get(
172
182
  "https://ssl.ptlogin2.qq.com/ptqrlogin",
173
183
  params={
174
184
  "u1": "https://graph.qq.com/oauth2.0/login_jump",
@@ -216,7 +226,7 @@ class QQLogin(QRCodeLogin):
216
226
  if self._state != QrCodeLoginEvents.DONE:
217
227
  raise LoginException("未完成二维码认证")
218
228
  # TODO: 优化获取 p_skey
219
- async with self.session.get(self.auth_url, allow_redirects=False) as res: # type: ignore
229
+ async with self._session.get(self.auth_url, allow_redirects=False) as res: # type: ignore
220
230
  from http import cookies
221
231
 
222
232
  set_cookie_header = res.headers.getall("Set-Cookie", [])
@@ -225,10 +235,10 @@ class QQLogin(QRCodeLogin):
225
235
  cookie.load(header)
226
236
  for key, morsel in cookie.items():
227
237
  if morsel.value:
228
- self.session.cookie_jar.update_cookies(cookie)
238
+ self._session.cookie_jar.update_cookies(cookie)
229
239
 
230
- skey = self.session.cookie_jar.filter_cookies(self.auth_url).get("p_skey").value # type: ignore
231
- async with self.session.post(
240
+ skey = self._session.cookie_jar.filter_cookies(self.auth_url).get("p_skey").value # type: ignore
241
+ async with self._session.post(
232
242
  "https://graph.qq.com/oauth2.0/authorize",
233
243
  params={
234
244
  "Referer": "https://graph.qq.com/oauth2.0/show?which=Login&display=pc&response_type=code&client_id"
@@ -277,7 +287,7 @@ class WXLogin(QRCodeLogin):
277
287
  QrCodeLoginEvents.DONE,
278
288
  ]:
279
289
  return self._qrcode_data
280
- async with self.session.get(
290
+ async with self._session.get(
281
291
  "https://open.weixin.qq.com/connect/qrconnect",
282
292
  params={
283
293
  "appid": "wx48db31d50e334801",
@@ -289,7 +299,7 @@ class WXLogin(QRCodeLogin):
289
299
  },
290
300
  ) as res:
291
301
  self.__uuid = re.findall(r"uuid=(.+?)\"", await res.text())[0]
292
- async with self.session.get(
302
+ async with self._session.get(
293
303
  f"https://open.weixin.qq.com/connect/qrcode/{self.__uuid}",
294
304
  headers={
295
305
  "referer": "https://open.weixin.qq.com/connect/qrconnect?appid=wx48db31d50e334801"
@@ -308,7 +318,7 @@ class WXLogin(QRCodeLogin):
308
318
  async def get_qrcode_state(self):
309
319
  if not self._qrcode_data:
310
320
  raise LoginException("请先获取二维码")
311
- async with self.session.get(
321
+ async with self._session.get(
312
322
  "https://lp.open.weixin.qq.com/connect/l/qrconnect",
313
323
  headers={
314
324
  "referer": "https://open.weixin.qq.com/",
@@ -338,7 +348,7 @@ class WXLogin(QRCodeLogin):
338
348
  "https://y.qq.com/portal/wx_redirect.html?login_type=2"
339
349
  f"&surl=https://y.qq.com/&code={self.musicid}&state=STATE"
340
350
  )
341
- await self.session.get(
351
+ await self._session.get(
342
352
  "https://lp.open.weixin.qq.com/connect/l/qrconnect",
343
353
  params={
344
354
  "uuid": self.__uuid,
@@ -354,7 +364,7 @@ class WXLogin(QRCodeLogin):
354
364
  return self.credential
355
365
  if self._state != QrCodeLoginEvents.DONE:
356
366
  raise LoginException("未完成二维码认证")
357
- await self.session.get(self.auth_url, allow_redirects=False) # type: ignore
367
+ await self._session.get(self.auth_url, allow_redirects=False) # type: ignore
358
368
  res = (
359
369
  await Api(**API["WX_login"])
360
370
  .update_params(strAppid="wx48db31d50e334801", code=self.musicid)
@@ -106,7 +106,6 @@ class TabType(Enum):
106
106
  VIDEO = ("video", "VideoTab")
107
107
 
108
108
  def __init__(self, tabID: str, tabName: str) -> None:
109
- super().__init__()
110
109
  self.tabID = tabID
111
110
  self.tabName = tabName
112
111
 
@@ -93,7 +93,7 @@ class Song:
93
93
  """
94
94
  # ID 检查
95
95
  if mid is None and id is None:
96
- raise TypeError("mid or id must be provided")
96
+ raise ValueError("mid or id must be provided")
97
97
  self._mid = mid
98
98
  self._id = id
99
99
  self._info: Optional[dict] = None
@@ -261,7 +261,7 @@ def __clean() -> None:
261
261
  if s0 is not None and not s0.closed:
262
262
  await s0.close()
263
263
 
264
- if loop.is_closed():
264
+ if not loop.is_closed():
265
265
  loop.run_until_complete(__clean_task())
266
266
  else:
267
- loop.create_task(__clean_task())
267
+ asyncio.run(__clean_task())