qqmusic-api-python 0.3.4__tar.gz → 0.3.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.
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/PKG-INFO +1 -1
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/pyproject.toml +1 -1
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/__init__.py +3 -2
- qqmusic_api_python-0.3.5/qqmusic_api/comment.py +91 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/login.py +10 -4
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/utils/network.py +8 -9
- qqmusic_api_python-0.3.5/tests/test_comments.py +15 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/tests/test_song.py +1 -1
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/.gitignore +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/LICENSE +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/README.md +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/album.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/exceptions/__init__.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/exceptions/api_exception.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/lyric.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/mv.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/search.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/singer.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/song.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/songlist.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/top.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/user.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/utils/__init__.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/utils/common.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/utils/credential.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/utils/device.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/utils/qimei.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/utils/session.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/utils/sign.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/utils/tripledes.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/tests/test_album.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/tests/test_login.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/tests/test_lyric.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/tests/test_mv.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/tests/test_qimei.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/tests/test_search.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/tests/test_session.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/tests/test_sign.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/tests/test_singer.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/tests/test_songlist.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/tests/test_top.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/tests/test_user.py +0 -0
- {qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/web/README.md +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
from . import album, login, lyric, mv, search, singer, song, songlist, top, user
|
|
3
|
+
from . import album, comment, login, lyric, mv, search, singer, song, songlist, top, user
|
|
4
4
|
from .utils.credential import Credential
|
|
5
5
|
from .utils.session import Session, get_session, set_session
|
|
6
6
|
|
|
7
|
-
__version__ = "0.3.
|
|
7
|
+
__version__ = "0.3.5"
|
|
8
8
|
|
|
9
9
|
logger = logging.getLogger("qqmusicapi")
|
|
10
10
|
|
|
@@ -13,6 +13,7 @@ __all__ = [
|
|
|
13
13
|
"Credential",
|
|
14
14
|
"Session",
|
|
15
15
|
"album",
|
|
16
|
+
"comment",
|
|
16
17
|
"get_session",
|
|
17
18
|
"login",
|
|
18
19
|
"lyric",
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""评论 API"""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from qqmusic_api.utils.network import api_request
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@api_request("music.globalComment.CommentRead", "GetHotCommentList")
|
|
9
|
+
async def get_hot_comments(
|
|
10
|
+
biz_id: str,
|
|
11
|
+
page_num: int = 1,
|
|
12
|
+
page_size: int = 15,
|
|
13
|
+
last_comment_seq_no: str = "",
|
|
14
|
+
):
|
|
15
|
+
"""获取歌曲热评
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
biz_id: 歌曲 ID
|
|
19
|
+
page_num: 页码
|
|
20
|
+
page_size: 每页数量
|
|
21
|
+
last_comment_seq_no: 上一页最后一条评论 ID(可选)
|
|
22
|
+
"""
|
|
23
|
+
params = {
|
|
24
|
+
"BizType": 1,
|
|
25
|
+
"BizId": biz_id,
|
|
26
|
+
"LastCommentSeqNo": last_comment_seq_no,
|
|
27
|
+
"PageSize": page_size,
|
|
28
|
+
"PageNum": page_num,
|
|
29
|
+
"HotType": 1,
|
|
30
|
+
"WithAirborne": 0,
|
|
31
|
+
"PicEnable": 1,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def _processor(data: dict[str, Any]):
|
|
35
|
+
"""处理并返回结构化评论数据:
|
|
36
|
+
|
|
37
|
+
返回结构:
|
|
38
|
+
[
|
|
39
|
+
{
|
|
40
|
+
"Avatar": str, # 用户头像 URL
|
|
41
|
+
"CmId": str, # 评论 ID (后续需要获取全部子评论时需用到)
|
|
42
|
+
"PraiseNum": int, # 点赞数
|
|
43
|
+
"Nick": str, # 昵称
|
|
44
|
+
"Pic": str, # 评论配图 (可能为空)
|
|
45
|
+
"Content": str, # 评论内容
|
|
46
|
+
"SeqNo": str, # 评论序号 ID 可以用于传递给 参数: last_comment_seq_no
|
|
47
|
+
"SubComments": [ # 子评论列表
|
|
48
|
+
{
|
|
49
|
+
"Avatar": str,
|
|
50
|
+
"Nick": str,
|
|
51
|
+
"Content": str,
|
|
52
|
+
"Pic": str,
|
|
53
|
+
"PraiseNum": int,
|
|
54
|
+
"SeqNo": str
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
...
|
|
59
|
+
]
|
|
60
|
+
"""
|
|
61
|
+
comments = data.get("CommentList", {}).get("Comments", [])
|
|
62
|
+
result = []
|
|
63
|
+
|
|
64
|
+
for comment in comments:
|
|
65
|
+
item = {
|
|
66
|
+
"Avatar": comment.get("Avatar"),
|
|
67
|
+
"CmId": comment.get("CmId"),
|
|
68
|
+
"PraiseNum": comment.get("PraiseNum"),
|
|
69
|
+
"Nick": comment.get("Nick"),
|
|
70
|
+
"Pic": comment.get("Pic"),
|
|
71
|
+
"Content": comment.get("Content"),
|
|
72
|
+
"SeqNo": comment.get("SeqNo"),
|
|
73
|
+
"SubComments": [],
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for sub in comment.get("SubComments", []):
|
|
77
|
+
sub_item = {
|
|
78
|
+
"Avatar": sub.get("Avatar"),
|
|
79
|
+
"Nick": sub.get("Nick"),
|
|
80
|
+
"Content": sub.get("Content"),
|
|
81
|
+
"Pic": sub.get("Pic"),
|
|
82
|
+
"PraiseNum": sub.get("PraiseNum"),
|
|
83
|
+
"SeqNo": sub.get("SeqNo"),
|
|
84
|
+
}
|
|
85
|
+
item["SubComments"].append(sub_item)
|
|
86
|
+
|
|
87
|
+
result.append(item)
|
|
88
|
+
|
|
89
|
+
return result
|
|
90
|
+
|
|
91
|
+
return params, _processor
|
|
@@ -12,7 +12,7 @@ from uuid import uuid4
|
|
|
12
12
|
|
|
13
13
|
import httpx
|
|
14
14
|
|
|
15
|
-
from .exceptions.api_exception import CredentialExpiredError, LoginError
|
|
15
|
+
from .exceptions.api_exception import CredentialExpiredError, LoginError, ResponseCodeError
|
|
16
16
|
from .utils.common import hash33
|
|
17
17
|
from .utils.credential import Credential
|
|
18
18
|
from .utils.network import ApiRequest
|
|
@@ -339,7 +339,7 @@ async def _authorize_qq_qr(uin: str, sigx: str) -> Credential:
|
|
|
339
339
|
data={
|
|
340
340
|
"response_type": "code",
|
|
341
341
|
"client_id": "100497308",
|
|
342
|
-
"redirect_uri": "https://y.qq.com/portal/wx_redirect.html?login_type=1&surl=https
|
|
342
|
+
"redirect_uri": "https://y.qq.com/portal/wx_redirect.html?login_type=1&surl=https://y.qq.com/",
|
|
343
343
|
"scope": "get_user_info,get_app_friends",
|
|
344
344
|
"state": "state",
|
|
345
345
|
"switch": "",
|
|
@@ -359,8 +359,8 @@ async def _authorize_qq_qr(uin: str, sigx: str) -> Credential:
|
|
|
359
359
|
raise LoginError("[QQLogin] 获取 code 失败")
|
|
360
360
|
try:
|
|
361
361
|
api = ApiRequest[[], dict[str, Any]](
|
|
362
|
-
"
|
|
363
|
-
"
|
|
362
|
+
"QQConnectLogin.LoginServer",
|
|
363
|
+
"QQLogin",
|
|
364
364
|
common={"tmeLoginType": "2"},
|
|
365
365
|
params={"code": code},
|
|
366
366
|
cacheable=False,
|
|
@@ -368,6 +368,10 @@ async def _authorize_qq_qr(uin: str, sigx: str) -> Credential:
|
|
|
368
368
|
return Credential.from_cookies_dict(await api())
|
|
369
369
|
except CredentialExpiredError:
|
|
370
370
|
raise LoginError("[QQLogin] 无法重复鉴权")
|
|
371
|
+
except ResponseCodeError as e:
|
|
372
|
+
if e.code == 20274:
|
|
373
|
+
raise LoginError("[QQLogin] 设备数量限制")
|
|
374
|
+
raise LoginError(f"[QQLogin] 未知错误: {e.code} - {e.message}")
|
|
371
375
|
|
|
372
376
|
|
|
373
377
|
async def _authorize_wx_qr(code: str) -> Credential:
|
|
@@ -431,6 +435,8 @@ async def phone_authorize(phone: int, auth_code: int, country_code: int = 86) ->
|
|
|
431
435
|
cacheable=False,
|
|
432
436
|
)()
|
|
433
437
|
match resp["code"]:
|
|
438
|
+
case 20274:
|
|
439
|
+
raise LoginError("[PhoneLogin] 设备数量限制")
|
|
434
440
|
case 20271:
|
|
435
441
|
raise LoginError("[PhoneLogin] 验证码错误或已鉴权")
|
|
436
442
|
case 0:
|
|
@@ -38,8 +38,8 @@ def api_request(
|
|
|
38
38
|
process_bool: bool = True,
|
|
39
39
|
cache_ttl: int | None = None,
|
|
40
40
|
cacheable: bool = True,
|
|
41
|
-
exclude_params: list[str] =
|
|
42
|
-
catch_error_code: list[int] =
|
|
41
|
+
exclude_params: list[str] | None = None,
|
|
42
|
+
catch_error_code: list[int] | None = None,
|
|
43
43
|
):
|
|
44
44
|
"""API请求"""
|
|
45
45
|
|
|
@@ -111,7 +111,7 @@ class BaseRequest(ABC):
|
|
|
111
111
|
return common
|
|
112
112
|
|
|
113
113
|
@common.setter
|
|
114
|
-
def
|
|
114
|
+
def common(self, value: dict[str, Any]):
|
|
115
115
|
"""设置公共参数"""
|
|
116
116
|
self._common = value
|
|
117
117
|
|
|
@@ -194,8 +194,8 @@ class ApiRequest(BaseRequest, Generic[_P, _R]):
|
|
|
194
194
|
process_bool: bool = True,
|
|
195
195
|
cache_ttl: int | None = None,
|
|
196
196
|
cacheable: bool = True,
|
|
197
|
-
exclude_params: list[str] =
|
|
198
|
-
catch_error_code: list[int] =
|
|
197
|
+
exclude_params: list[str] | None = None,
|
|
198
|
+
catch_error_code: list[int] | None = None,
|
|
199
199
|
) -> None:
|
|
200
200
|
super().__init__(common, credential, verify, ignore_code)
|
|
201
201
|
self.module = module
|
|
@@ -206,8 +206,8 @@ class ApiRequest(BaseRequest, Generic[_P, _R]):
|
|
|
206
206
|
self.processor: Callable[[dict[str, Any]], Any] = NO_PROCESSOR
|
|
207
207
|
self.cacheable = cacheable
|
|
208
208
|
self.cache_ttl = cache_ttl
|
|
209
|
-
self.exclude_params = exclude_params
|
|
210
|
-
self.catch_error_code = catch_error_code
|
|
209
|
+
self.exclude_params = exclude_params or []
|
|
210
|
+
self.catch_error_code = catch_error_code or []
|
|
211
211
|
|
|
212
212
|
def copy(self) -> "ApiRequest[_P, _R]":
|
|
213
213
|
"""创建当前 ApiRequest 实例的副本"""
|
|
@@ -353,7 +353,7 @@ class RequestGroup(BaseRequest):
|
|
|
353
353
|
|
|
354
354
|
self._requests.append(
|
|
355
355
|
RequestItem(
|
|
356
|
-
id=len(self._requests)
|
|
356
|
+
id=len(self._requests),
|
|
357
357
|
key=unique_key,
|
|
358
358
|
request=request,
|
|
359
359
|
args=args,
|
|
@@ -422,7 +422,6 @@ class RequestGroup(BaseRequest):
|
|
|
422
422
|
|
|
423
423
|
if not self._requests:
|
|
424
424
|
return self._results
|
|
425
|
-
|
|
426
425
|
resp = await self.request()
|
|
427
426
|
await self._process_response(resp)
|
|
428
427
|
return self._results
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from qqmusic_api.comment import get_hot_comments
|
|
4
|
+
|
|
5
|
+
pytestmark = pytest.mark.asyncio(loop_scope="session")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def test_get_comment():
|
|
9
|
+
comment = await get_hot_comments(
|
|
10
|
+
"542574330",
|
|
11
|
+
1,
|
|
12
|
+
10,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
assert comment[0]["Content"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qqmusic_api_python-0.3.4 → qqmusic_api_python-0.3.5}/qqmusic_api/exceptions/api_exception.py
RENAMED
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|