qqmusic-api-python 0.1.0__py3-none-any.whl
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/__init__.py +4 -0
- qqmusic_api/api/album.py +40 -0
- qqmusic_api/api/login.py +437 -0
- qqmusic_api/api/mv.py +115 -0
- qqmusic_api/api/search.py +168 -0
- qqmusic_api/api/song.py +389 -0
- qqmusic_api/api/songlist.py +81 -0
- qqmusic_api/api/top.py +98 -0
- qqmusic_api/data/api/album.json +20 -0
- qqmusic_api/data/api/login.json +69 -0
- qqmusic_api/data/api/mv.json +26 -0
- qqmusic_api/data/api/search.json +73 -0
- qqmusic_api/data/api/singer.json +1 -0
- qqmusic_api/data/api/song.json +105 -0
- qqmusic_api/data/api/songlist.json +17 -0
- qqmusic_api/data/api/top.json +20 -0
- qqmusic_api/data/file_type.json +50 -0
- qqmusic_api/data/search_type.json +11 -0
- qqmusic_api/exceptions.py +121 -0
- qqmusic_api/settings.py +22 -0
- qqmusic_api/utils/__init__.py +0 -0
- qqmusic_api/utils/common.py +140 -0
- qqmusic_api/utils/credential.py +92 -0
- qqmusic_api/utils/network.py +249 -0
- qqmusic_api/utils/qimei.py +267 -0
- qqmusic_api_python-0.1.0.dist-info/LICENSE +21 -0
- qqmusic_api_python-0.1.0.dist-info/METADATA +99 -0
- qqmusic_api_python-0.1.0.dist-info/RECORD +29 -0
- qqmusic_api_python-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
3
|
+
import random
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# 定义全局变量
|
|
8
|
+
UUID_CHARS = "0123456789ABCDEF"
|
|
9
|
+
CACHE_DIR = Path(__file__).resolve().parent.parent.parent / ".cache"
|
|
10
|
+
DATA_DIR = Path(__file__).resolve().parent.parent / "data"
|
|
11
|
+
API_DIR = DATA_DIR / "api"
|
|
12
|
+
|
|
13
|
+
# 确保缓存目录存在
|
|
14
|
+
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_cache_file(*args) -> str:
|
|
18
|
+
return str(CACHE_DIR.joinpath(*args))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_api(field: str) -> dict:
|
|
22
|
+
path = API_DIR / f"{field.lower()}.json"
|
|
23
|
+
if path.exists():
|
|
24
|
+
with path.open() as f:
|
|
25
|
+
data = json.load(f)
|
|
26
|
+
return data
|
|
27
|
+
else:
|
|
28
|
+
return {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def random_string(length: int, chars: str = UUID_CHARS) -> str:
|
|
32
|
+
return "".join(random.choices(chars, k=length))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def calc_md5(*multi_string) -> str:
|
|
36
|
+
md5 = hashlib.md5()
|
|
37
|
+
for s in multi_string:
|
|
38
|
+
md5.update(s if isinstance(s, bytes) else s.encode())
|
|
39
|
+
return md5.hexdigest()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def hash33(s: str, h: int = 0) -> int:
|
|
43
|
+
for c in s:
|
|
44
|
+
h = (h << 5) + h + ord(c)
|
|
45
|
+
return 2147483647 & h
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def random_uuid() -> str:
|
|
49
|
+
uuid_format = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
|
|
50
|
+
return "".join(random.choice(UUID_CHARS) if c in "xy" else c for c in uuid_format)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def random_searchID() -> str:
|
|
54
|
+
e = random.randint(1, 20)
|
|
55
|
+
t = e * 18014398509481984
|
|
56
|
+
n = random.randint(0, 4194304) * 4294967296
|
|
57
|
+
a = time.time()
|
|
58
|
+
r = round(a * 1000) % (24 * 60 * 60 * 1000)
|
|
59
|
+
return str(t + n + r)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def parse_song_info(song_info: dict) -> dict:
|
|
63
|
+
# 解析专辑信息
|
|
64
|
+
album = {
|
|
65
|
+
"id": song_info["album"]["id"],
|
|
66
|
+
"mid": song_info["album"]["mid"],
|
|
67
|
+
"name": song_info["album"]["name"],
|
|
68
|
+
"time_public": song_info["album"].get("time_public", ""),
|
|
69
|
+
}
|
|
70
|
+
# 解析MV信息
|
|
71
|
+
mv = {
|
|
72
|
+
"id": song_info["mv"]["id"],
|
|
73
|
+
"name": song_info["mv"].get("name", ""),
|
|
74
|
+
"vid": song_info["mv"]["vid"],
|
|
75
|
+
}
|
|
76
|
+
# 解析歌手信息
|
|
77
|
+
singer = [
|
|
78
|
+
{
|
|
79
|
+
"id": s["id"],
|
|
80
|
+
"mid": s["mid"],
|
|
81
|
+
"name": s["name"],
|
|
82
|
+
"type": s.get("type"),
|
|
83
|
+
"uin": s.get("uin"),
|
|
84
|
+
}
|
|
85
|
+
for s in song_info["singer"]
|
|
86
|
+
]
|
|
87
|
+
# 解析歌曲信息
|
|
88
|
+
info = {
|
|
89
|
+
"id": song_info["id"],
|
|
90
|
+
"mid": song_info["mid"],
|
|
91
|
+
"name": song_info["name"],
|
|
92
|
+
"title": song_info["title"],
|
|
93
|
+
"subTitle": song_info.get("subtitle", ""),
|
|
94
|
+
"language": song_info["language"],
|
|
95
|
+
"time_public": song_info.get("time_public", ""),
|
|
96
|
+
"tag": song_info.get("tag", ""),
|
|
97
|
+
"type": song_info["type"],
|
|
98
|
+
"album": album,
|
|
99
|
+
"mv": mv,
|
|
100
|
+
"singer": singer,
|
|
101
|
+
}
|
|
102
|
+
# 解析文件信息
|
|
103
|
+
file = {
|
|
104
|
+
"media_mid": song_info["file"]["media_mid"],
|
|
105
|
+
"new_0": song_info["file"]["size_new"][0],
|
|
106
|
+
"new_1": song_info["file"]["size_new"][1],
|
|
107
|
+
"new_2": song_info["file"]["size_new"][2],
|
|
108
|
+
"flac": song_info["file"]["size_flac"],
|
|
109
|
+
"ogg_192": song_info["file"]["size_192ogg"],
|
|
110
|
+
"ogg_96": song_info["file"]["size_96ogg"],
|
|
111
|
+
"mp3_320": song_info["file"]["size_320mp3"],
|
|
112
|
+
"mp3_128": song_info["file"]["size_128mp3"],
|
|
113
|
+
"aac_192": song_info["file"]["size_192aac"],
|
|
114
|
+
"aac_96": song_info["file"]["size_96aac"],
|
|
115
|
+
"aac_48": song_info["file"]["size_48aac"],
|
|
116
|
+
}
|
|
117
|
+
# 组装结果
|
|
118
|
+
result = {
|
|
119
|
+
"info": info,
|
|
120
|
+
"file": file,
|
|
121
|
+
"lyric": {
|
|
122
|
+
"match": song_info.get("lyric", ""),
|
|
123
|
+
"content": song_info.get("content", ""),
|
|
124
|
+
},
|
|
125
|
+
"pay": song_info.get("pay", {}),
|
|
126
|
+
"grp": [parse_song_info(song) for song in song_info.get("grp", [])],
|
|
127
|
+
"vs": song_info.get("vs", []),
|
|
128
|
+
}
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def filter_data(data: dict) -> dict:
|
|
133
|
+
keys = [""]
|
|
134
|
+
for key in keys:
|
|
135
|
+
data.pop(key, "")
|
|
136
|
+
return data
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def singer_to_str(data: dict) -> str:
|
|
140
|
+
return "&".join([singer["name"] for singer in data["singer"]])
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from ..exceptions import (
|
|
2
|
+
CredentialNoMusicidException,
|
|
3
|
+
CredentialNoMusickeyException,
|
|
4
|
+
CredientialCanNotRefreshException,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Credential:
|
|
9
|
+
def __init__(
|
|
10
|
+
self, musicid: str = "", musickey: str = "", refresh_key: str = "", **kwagrs
|
|
11
|
+
):
|
|
12
|
+
self.musicid = musicid
|
|
13
|
+
self.musickey = musickey
|
|
14
|
+
self.refresh_key = refresh_key
|
|
15
|
+
self.login_type = 1 if "W_X" in musickey else 2
|
|
16
|
+
|
|
17
|
+
for key, value in kwagrs.items():
|
|
18
|
+
setattr(self, key, value)
|
|
19
|
+
|
|
20
|
+
def get_dict(self) -> dict:
|
|
21
|
+
cookies = {}
|
|
22
|
+
for key, value in self.__dict__.items():
|
|
23
|
+
if key not in cookies and value is not None:
|
|
24
|
+
cookies[key] = value
|
|
25
|
+
return cookies
|
|
26
|
+
|
|
27
|
+
def has_musicid(self) -> bool:
|
|
28
|
+
"""
|
|
29
|
+
是否提供 musicid
|
|
30
|
+
"""
|
|
31
|
+
return bool(self.musicid)
|
|
32
|
+
|
|
33
|
+
def has_musickey(self) -> bool:
|
|
34
|
+
"""
|
|
35
|
+
是否提供 musickey
|
|
36
|
+
"""
|
|
37
|
+
return bool(self.musickey)
|
|
38
|
+
|
|
39
|
+
def can_refresh(self) -> bool:
|
|
40
|
+
"""
|
|
41
|
+
是否能刷新 Credential
|
|
42
|
+
"""
|
|
43
|
+
return bool(self.refresh_key)
|
|
44
|
+
|
|
45
|
+
def raise_for_cannot_refresh(self):
|
|
46
|
+
"""
|
|
47
|
+
无法刷新 Credential 时则抛出异常
|
|
48
|
+
"""
|
|
49
|
+
if not self.can_refresh():
|
|
50
|
+
raise CredientialCanNotRefreshException()
|
|
51
|
+
|
|
52
|
+
def raise_for_no_musicid(self):
|
|
53
|
+
"""
|
|
54
|
+
没有提供 musicid 时抛出异常
|
|
55
|
+
"""
|
|
56
|
+
if not self.has_musicid():
|
|
57
|
+
raise CredentialNoMusicidException()
|
|
58
|
+
|
|
59
|
+
def raise_for_no_musickey(self):
|
|
60
|
+
"""
|
|
61
|
+
没有提供 musickey 时抛出异常
|
|
62
|
+
"""
|
|
63
|
+
if not self.has_musickey():
|
|
64
|
+
raise CredentialNoMusickeyException()
|
|
65
|
+
|
|
66
|
+
async def refresh(self):
|
|
67
|
+
"""
|
|
68
|
+
刷新 cookies
|
|
69
|
+
"""
|
|
70
|
+
from ..api.login import refresh_cookies
|
|
71
|
+
|
|
72
|
+
c = await refresh_cookies(self)
|
|
73
|
+
self.musicid = c.musicid
|
|
74
|
+
self.musickey = c.musickey
|
|
75
|
+
self.refresh_key = c.refresh_key
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def from_cookies(cls, cookies: dict = {}) -> "Credential":
|
|
79
|
+
"""
|
|
80
|
+
从 cookies 新建 Credential
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
cookies : Cookies.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Credential: 凭据类
|
|
87
|
+
"""
|
|
88
|
+
c = cls()
|
|
89
|
+
c.musicid = cookies["musicid"]
|
|
90
|
+
c.musickey = cookies["musickey"]
|
|
91
|
+
c.refresh_key = cookies["refresh_key"]
|
|
92
|
+
return c
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import atexit
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import aiohttp
|
|
8
|
+
|
|
9
|
+
from .. import settings
|
|
10
|
+
from ..exceptions import ClientException, NetworkException
|
|
11
|
+
from .credential import Credential
|
|
12
|
+
|
|
13
|
+
HEADERS = {
|
|
14
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.54",
|
|
15
|
+
"Referer": "https://y.qq.com",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
__session_pool: dict[asyncio.AbstractEventLoop, aiohttp.ClientSession] = {}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_aiohttp_session() -> aiohttp.ClientSession:
|
|
22
|
+
"""
|
|
23
|
+
获取当前模块的 aiohttp.ClientSession 对象,用于自定义请求
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
aiohttp.ClientSession
|
|
27
|
+
"""
|
|
28
|
+
loop = asyncio.get_event_loop()
|
|
29
|
+
session = __session_pool.get(loop, None)
|
|
30
|
+
if session is None:
|
|
31
|
+
session = aiohttp.ClientSession(
|
|
32
|
+
loop=loop, connector=aiohttp.TCPConnector(), trust_env=True
|
|
33
|
+
)
|
|
34
|
+
__session_pool[loop] = session
|
|
35
|
+
|
|
36
|
+
return session
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class Api:
|
|
41
|
+
"""
|
|
42
|
+
用于请求的 Api 类
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
url: 请求地址. Defaults to API_URL
|
|
46
|
+
method: 请求方法
|
|
47
|
+
module: 请求模块. Defaults to ""
|
|
48
|
+
comment: 注释. Defaults to ""
|
|
49
|
+
verify: 是否验证凭据. Defaults to False
|
|
50
|
+
json_body: 是否使用 json 作为载荷. Defaults to False
|
|
51
|
+
data: 请求载荷. Defaults to {}
|
|
52
|
+
params: 请求参数. Defaults to {}
|
|
53
|
+
headers: 请求头. Defaults to {}
|
|
54
|
+
credential: 凭据. Defaults to Credential()
|
|
55
|
+
extra_common: 额外参数. Defaults to {}
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
method: str
|
|
59
|
+
module: str = ""
|
|
60
|
+
url: str = settings.API_URL
|
|
61
|
+
comment: str = ""
|
|
62
|
+
verify: bool = False
|
|
63
|
+
json_body: bool = False
|
|
64
|
+
data: dict = field(default_factory=dict)
|
|
65
|
+
params: dict = field(default_factory=dict)
|
|
66
|
+
headers: dict = field(default_factory=dict)
|
|
67
|
+
credential: Credential = field(default_factory=Credential)
|
|
68
|
+
extra_common: dict = field(default_factory=dict)
|
|
69
|
+
|
|
70
|
+
def __post_init__(self) -> None:
|
|
71
|
+
if not self.module:
|
|
72
|
+
self.method = self.method.upper()
|
|
73
|
+
else:
|
|
74
|
+
self.json_body = True
|
|
75
|
+
self.original_data = self.data.copy()
|
|
76
|
+
self.original_params = self.params.copy()
|
|
77
|
+
self.data = {k: "" for k in self.data.keys()}
|
|
78
|
+
self.params = {k: "" for k in self.params.keys()}
|
|
79
|
+
self.headers = {k: "" for k in self.headers.keys()}
|
|
80
|
+
self.extra_common = {k: "" for k in self.extra_common.keys()}
|
|
81
|
+
self.__result: str | dict | None = None
|
|
82
|
+
|
|
83
|
+
def __setattr__(self, __name: str, __value: Any) -> None:
|
|
84
|
+
"""
|
|
85
|
+
每次更新参数都要把 __result 清除
|
|
86
|
+
"""
|
|
87
|
+
if self.initialized and __name != "_Api__result":
|
|
88
|
+
self.__result = None
|
|
89
|
+
return super().__setattr__(__name, __value)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def initialized(self):
|
|
93
|
+
return "_Api__result" in self.__dict__
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
async def result(self) -> dict | None:
|
|
97
|
+
"""
|
|
98
|
+
获取请求结果
|
|
99
|
+
"""
|
|
100
|
+
if self.__result is None:
|
|
101
|
+
self.__result = await self.request()
|
|
102
|
+
return self.__result
|
|
103
|
+
|
|
104
|
+
def update_params(self, **kwargs) -> "Api":
|
|
105
|
+
"""
|
|
106
|
+
毫无亮点的更新 params
|
|
107
|
+
"""
|
|
108
|
+
self.params = kwargs
|
|
109
|
+
self.__result = None
|
|
110
|
+
return self
|
|
111
|
+
|
|
112
|
+
def update_data(self, **kwargs) -> "Api":
|
|
113
|
+
"""
|
|
114
|
+
毫无亮点的更新 data
|
|
115
|
+
"""
|
|
116
|
+
self.data = kwargs
|
|
117
|
+
self.__result = None
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
def update_headers(self, **kwargs) -> "Api":
|
|
121
|
+
"""
|
|
122
|
+
毫无亮点的更新 headers
|
|
123
|
+
"""
|
|
124
|
+
self.headers = kwargs
|
|
125
|
+
self.__result = None
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def update_extra_common(self, **kwargs) -> "Api":
|
|
129
|
+
"""
|
|
130
|
+
毫无亮点的更新 extra_common
|
|
131
|
+
"""
|
|
132
|
+
self.extra_common = kwargs
|
|
133
|
+
self.__result = None
|
|
134
|
+
return self
|
|
135
|
+
|
|
136
|
+
def __prepare_params_data(self) -> None:
|
|
137
|
+
"""
|
|
138
|
+
准备请求参数
|
|
139
|
+
"""
|
|
140
|
+
new_params, new_data = {}, {}
|
|
141
|
+
for key, value in self.params.items():
|
|
142
|
+
if isinstance(value, bool):
|
|
143
|
+
new_params[key] = int(value)
|
|
144
|
+
elif value is not None:
|
|
145
|
+
new_params[key] = value
|
|
146
|
+
for key, value in self.data.items():
|
|
147
|
+
if isinstance(value, bool):
|
|
148
|
+
new_params[key] = int(value)
|
|
149
|
+
elif value is not None:
|
|
150
|
+
new_data[key] = value
|
|
151
|
+
self.params, self.data = new_params, new_data
|
|
152
|
+
|
|
153
|
+
def __prepare_api_data(self) -> None:
|
|
154
|
+
"""
|
|
155
|
+
准备API请求数据
|
|
156
|
+
"""
|
|
157
|
+
common = {
|
|
158
|
+
"ct": "11",
|
|
159
|
+
"cv": settings.QQMUSIC_VERSION_CODE,
|
|
160
|
+
"v": settings.QQMUSIC_VERSION_CODE,
|
|
161
|
+
"tmeAppID": "qqmusic",
|
|
162
|
+
"QIMEI36": settings.QIMEI36,
|
|
163
|
+
"uid": settings.UID,
|
|
164
|
+
"format": "json",
|
|
165
|
+
"inCharset": "utf-8",
|
|
166
|
+
"outCharset": "utf-8",
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if self.verify:
|
|
170
|
+
self.credential.raise_for_no_musickey()
|
|
171
|
+
self.credential.raise_for_no_musicid()
|
|
172
|
+
|
|
173
|
+
common["qq"] = self.credential.musicid
|
|
174
|
+
common["authst"] = self.credential.musickey
|
|
175
|
+
common["tmeLoginType"] = str(self.credential.login_type)
|
|
176
|
+
|
|
177
|
+
common.update(self.extra_common)
|
|
178
|
+
|
|
179
|
+
data = {
|
|
180
|
+
"comm": common,
|
|
181
|
+
"request": {
|
|
182
|
+
"module": self.module,
|
|
183
|
+
"method": self.method,
|
|
184
|
+
"param": self.params,
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
self.data = data
|
|
188
|
+
|
|
189
|
+
def __prepare_request(self) -> dict:
|
|
190
|
+
"""
|
|
191
|
+
准备请求配置参数
|
|
192
|
+
"""
|
|
193
|
+
config = {
|
|
194
|
+
"url": self.url,
|
|
195
|
+
"method": self.method,
|
|
196
|
+
"data": self.data,
|
|
197
|
+
"params": self.params,
|
|
198
|
+
"headers": HEADERS.copy() if len(self.headers) == 0 else self.headers,
|
|
199
|
+
}
|
|
200
|
+
if self.json_body:
|
|
201
|
+
config["headers"]["Content-Type"] = "application/json"
|
|
202
|
+
config["data"] = json.dumps(config["data"], ensure_ascii=False).encode()
|
|
203
|
+
if self.module:
|
|
204
|
+
config["method"] = "POST"
|
|
205
|
+
config["params"] = ""
|
|
206
|
+
return config
|
|
207
|
+
|
|
208
|
+
async def request(self) -> dict | None:
|
|
209
|
+
"""
|
|
210
|
+
向接口发送请求
|
|
211
|
+
"""
|
|
212
|
+
if self.module:
|
|
213
|
+
self.__prepare_api_data()
|
|
214
|
+
self.__prepare_params_data()
|
|
215
|
+
config = self.__prepare_request()
|
|
216
|
+
session = get_aiohttp_session()
|
|
217
|
+
try:
|
|
218
|
+
async with session.request(**config) as resp:
|
|
219
|
+
try:
|
|
220
|
+
resp.raise_for_status()
|
|
221
|
+
except aiohttp.ClientResponseError as e:
|
|
222
|
+
raise NetworkException(e.status, e.message)
|
|
223
|
+
return self.__process_response(resp, await resp.text())
|
|
224
|
+
except aiohttp.client_exceptions.ClientConnectorError:
|
|
225
|
+
raise ClientException()
|
|
226
|
+
|
|
227
|
+
def __process_response(
|
|
228
|
+
self, resp: aiohttp.ClientResponse, resp_text: str
|
|
229
|
+
) -> dict | None:
|
|
230
|
+
content_length = resp.headers.get("content-length")
|
|
231
|
+
if content_length and int(content_length) == 0:
|
|
232
|
+
return None
|
|
233
|
+
try:
|
|
234
|
+
resp_data = json.loads(resp_text)
|
|
235
|
+
if self.module:
|
|
236
|
+
request_data = resp_data["request"]
|
|
237
|
+
return request_data["data"]
|
|
238
|
+
return resp_data
|
|
239
|
+
except Exception:
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@atexit.register
|
|
244
|
+
def __clean() -> None:
|
|
245
|
+
"""
|
|
246
|
+
程序退出清理操作。
|
|
247
|
+
"""
|
|
248
|
+
for session in __session_pool.values():
|
|
249
|
+
asyncio.run(session.close())
|