nonebot-plugin-osubot 6.22.3__py3-none-any.whl → 6.26.4__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.
- nonebot_plugin_osubot/api.py +12 -7
- nonebot_plugin_osubot/config.py +10 -10
- nonebot_plugin_osubot/draw/bmap.py +20 -22
- nonebot_plugin_osubot/draw/bp.py +3 -13
- nonebot_plugin_osubot/draw/catch_preview.py +2 -16
- nonebot_plugin_osubot/draw/echarts.py +8 -1
- nonebot_plugin_osubot/draw/info.py +59 -207
- nonebot_plugin_osubot/draw/info_templates/index.html +507 -0
- nonebot_plugin_osubot/draw/info_templates/output.css +2 -0
- nonebot_plugin_osubot/draw/map.py +9 -11
- nonebot_plugin_osubot/draw/osu_preview.py +50 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/css/style.css +258 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/README.md +109 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/gif.js +3 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/gif.js.map +1 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/gif.worker.js +3 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/gif.worker.js.map +1 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/beatmap.js +211 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/hitobject.js +29 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/point.js +55 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/scroll.js +45 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/timingpoint.js +35 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/LegacyRandom.js +81 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/PalpableCatchHitObject.js +53 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/bananashower.js +33 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/catch.js +211 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/fruit.js +21 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/juicestream.js +176 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/mania/hitnote.js +21 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/mania/holdnote.js +37 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/mania/mania.js +164 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/preview.js +61 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/bezier2.js +33 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/catmullcurve.js +34 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/centripetalcatmullrom.js +30 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/circumstancedcircle.js +47 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/curve.js +25 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/curvetype.js +17 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/equaldistancemulticurve.js +70 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/linearbezier.js +40 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/hitcircle.js +85 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/slider.js +120 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/spinner.js +56 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/standard.js +170 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/taiko/donkat.js +40 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/taiko/drumroll.js +34 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/taiko/shaker.js +58 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/taiko/taiko.js +120 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/js/util.js +61 -0
- nonebot_plugin_osubot/draw/osu_preview_templates/pic.html +110 -0
- nonebot_plugin_osubot/draw/rating.py +6 -3
- nonebot_plugin_osubot/draw/score.py +23 -81
- nonebot_plugin_osubot/draw/taiko_preview.py +14 -13
- nonebot_plugin_osubot/draw/templates/bpa_chart.html +1 -1
- nonebot_plugin_osubot/draw/utils.py +162 -16
- nonebot_plugin_osubot/file.py +184 -31
- nonebot_plugin_osubot/mania/__init__.py +9 -10
- nonebot_plugin_osubot/matcher/__init__.py +2 -0
- nonebot_plugin_osubot/matcher/bp_analyze.py +14 -9
- nonebot_plugin_osubot/matcher/guess.py +250 -294
- nonebot_plugin_osubot/matcher/map_convert.py +21 -13
- nonebot_plugin_osubot/matcher/medal.py +1 -1
- nonebot_plugin_osubot/matcher/osudl.py +5 -4
- nonebot_plugin_osubot/matcher/pr.py +0 -4
- nonebot_plugin_osubot/matcher/preview.py +10 -3
- nonebot_plugin_osubot/matcher/recommend.py +7 -12
- nonebot_plugin_osubot/mods.py +62 -61
- nonebot_plugin_osubot/network/first_response.py +1 -1
- nonebot_plugin_osubot/osufile/mods/AP.png +0 -0
- nonebot_plugin_osubot/osufile/mods/CL.png +0 -0
- nonebot_plugin_osubot/osufile/mods/DT.png +0 -0
- nonebot_plugin_osubot/osufile/mods/EZ.png +0 -0
- nonebot_plugin_osubot/osufile/mods/FI.png +0 -0
- nonebot_plugin_osubot/osufile/mods/FL.png +0 -0
- nonebot_plugin_osubot/osufile/mods/HD.png +0 -0
- nonebot_plugin_osubot/osufile/mods/HR.png +0 -0
- nonebot_plugin_osubot/osufile/mods/HT.png +0 -0
- nonebot_plugin_osubot/osufile/mods/MR.png +0 -0
- nonebot_plugin_osubot/osufile/mods/NC.png +0 -0
- nonebot_plugin_osubot/osufile/mods/NF.png +0 -0
- nonebot_plugin_osubot/osufile/mods/PF.png +0 -0
- nonebot_plugin_osubot/osufile/mods/RX.png +0 -0
- nonebot_plugin_osubot/osufile/mods/SD.png +0 -0
- nonebot_plugin_osubot/osufile/mods/SO.png +0 -0
- nonebot_plugin_osubot/osufile/mods/TD.png +0 -0
- nonebot_plugin_osubot/osufile/mods/V2.png +0 -0
- nonebot_plugin_osubot/pp.py +7 -0
- nonebot_plugin_osubot/schema/__init__.py +0 -2
- nonebot_plugin_osubot/schema/beatmapsets.py +42 -0
- nonebot_plugin_osubot/schema/draw_info.py +54 -0
- nonebot_plugin_osubot/schema/score.py +1 -0
- nonebot_plugin_osubot/schema/user.py +1 -0
- {nonebot_plugin_osubot-6.22.3.dist-info → nonebot_plugin_osubot-6.26.4.dist-info}/METADATA +18 -17
- {nonebot_plugin_osubot-6.22.3.dist-info → nonebot_plugin_osubot-6.26.4.dist-info}/RECORD +95 -55
- nonebot_plugin_osubot-6.26.4.dist-info/WHEEL +4 -0
- nonebot_plugin_osubot/data/osu/1849145.osz +0 -0
- nonebot_plugin_osubot/data/osu/1849145.zip +0 -0
- nonebot_plugin_osubot/draw/templates/t.html +0 -58
- nonebot_plugin_osubot/schema/sayo_beatmap.py +0 -59
- nonebot_plugin_osubot-6.22.3.dist-info/WHEEL +0 -4
nonebot_plugin_osubot/file.py
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
1
3
|
import re
|
|
4
|
+
import urllib
|
|
2
5
|
import random
|
|
3
6
|
import asyncio
|
|
7
|
+
import uuid
|
|
4
8
|
from pathlib import Path
|
|
5
9
|
from typing import Union, Optional
|
|
6
10
|
from io import BytesIO, TextIOWrapper
|
|
7
11
|
|
|
12
|
+
from nonebot.adapters.onebot.v11 import Bot
|
|
8
13
|
from nonebot.log import logger
|
|
9
14
|
|
|
10
15
|
from .schema import Badge
|
|
@@ -18,45 +23,67 @@ user_cache_path = Path() / "data" / "osu" / "user"
|
|
|
18
23
|
badge_cache_path = Path() / "data" / "osu" / "badge"
|
|
19
24
|
team_cache_path = Path() / "data" / "osu" / "team"
|
|
20
25
|
api_ls = [
|
|
21
|
-
"https://api.chimu.moe/v1/download/",
|
|
22
26
|
"https://osu.direct/api/d/",
|
|
23
27
|
"https://txy1.sayobot.cn/beatmaps/download/novideo/",
|
|
24
28
|
"https://catboy.best/d/",
|
|
25
29
|
]
|
|
26
30
|
semaphore = asyncio.Semaphore(5)
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
map_path.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
user_cache_path.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
badge_cache_path.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
team_cache_path.mkdir(parents=True, exist_ok=True)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def extract_filename_from_headers(headers: dict[str, str]) -> Optional[str]:
|
|
39
|
+
"""
|
|
40
|
+
从 Content-Disposition 响应头中提取文件名,并处理 URL 编码。
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
headers: 响应头字典。
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
提取到的文件名字符串,如果失败则返回 None。
|
|
47
|
+
"""
|
|
48
|
+
disposition = headers.get("content-disposition", "")
|
|
49
|
+
if not disposition:
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
match_utf8 = re.search(r"filename\*=(?:utf-8''|)(.+?)(?:;|$)", disposition, re.IGNORECASE)
|
|
53
|
+
|
|
54
|
+
if match_utf8:
|
|
55
|
+
# 提取匹配到的文件名部分
|
|
56
|
+
encoded_filename = match_utf8.group(1).strip('"').strip()
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
return urllib.parse.unquote(encoded_filename)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
# 如果解码失败,记录错误并尝试使用原始编码
|
|
62
|
+
print(f"警告: 解码 filename* 失败: {e}. 使用原始编码.")
|
|
63
|
+
return encoded_filename
|
|
64
|
+
|
|
65
|
+
match_normal = re.search(r"filename=\"?(.+?)\"?(\s|;|$)", disposition, re.IGNORECASE)
|
|
66
|
+
if match_normal:
|
|
67
|
+
# 普通 filename 字段也可能包含 URL 编码,进行解码
|
|
68
|
+
filename = match_normal.group(1).strip('"').strip()
|
|
69
|
+
try:
|
|
70
|
+
return urllib.parse.unquote(filename)
|
|
71
|
+
except Exception:
|
|
72
|
+
return filename
|
|
73
|
+
|
|
74
|
+
return None
|
|
36
75
|
|
|
37
76
|
|
|
38
77
|
async def download_map(setid: int) -> Optional[Path]:
|
|
39
|
-
urls = [
|
|
78
|
+
urls = [f"{base_url}{setid}" for base_url in api_ls]
|
|
40
79
|
logger.info(f"开始下载地图: <{setid}>")
|
|
41
80
|
req = await get_first_response(urls)
|
|
42
|
-
filename =
|
|
81
|
+
filename = extract_filename_from_headers(req.headers)
|
|
43
82
|
filepath = map_path.parent / filename
|
|
44
|
-
with open(filepath, "wb") as f:
|
|
45
|
-
f.write(req.
|
|
83
|
+
with open(filepath.absolute(), "wb") as f:
|
|
84
|
+
f.write(req.content)
|
|
46
85
|
logger.info(f"地图: <{setid}> 下载完毕")
|
|
47
|
-
return filepath
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
async def download_tmp_osu(map_id):
|
|
51
|
-
url = f"https://osu.ppy.sh/osu/{map_id}"
|
|
52
|
-
logger.info(f"开始下载谱面: <{map_id}>")
|
|
53
|
-
req = await safe_async_get(url)
|
|
54
|
-
filename = f"{map_id}.osu"
|
|
55
|
-
filepath = map_path / filename
|
|
56
|
-
chunk = req.read()
|
|
57
|
-
with open(filepath, "wb") as f:
|
|
58
|
-
f.write(chunk)
|
|
59
|
-
return filepath
|
|
86
|
+
return filepath.absolute()
|
|
60
87
|
|
|
61
88
|
|
|
62
89
|
@auto_retry
|
|
@@ -71,8 +98,9 @@ async def download_osu(set_id, map_id):
|
|
|
71
98
|
if req := await get_first_response(url):
|
|
72
99
|
filename = f"{map_id}.osu"
|
|
73
100
|
filepath = map_path / str(set_id) / filename
|
|
101
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
74
102
|
with open(filepath, "wb") as f:
|
|
75
|
-
f.write(req)
|
|
103
|
+
f.write(req.content)
|
|
76
104
|
return filepath
|
|
77
105
|
else:
|
|
78
106
|
raise Exception("下载出错,请稍后再试")
|
|
@@ -94,8 +122,7 @@ async def get_projectimg(url: str) -> BytesIO:
|
|
|
94
122
|
|
|
95
123
|
async def get_pfm_img(url: str, cache_path: Path) -> BytesIO:
|
|
96
124
|
cache_dir = cache_path.parent
|
|
97
|
-
|
|
98
|
-
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
99
126
|
if cache_path.exists():
|
|
100
127
|
with cache_path.open("rb") as f:
|
|
101
128
|
return BytesIO(f.read())
|
|
@@ -132,7 +159,133 @@ async def make_badge_cache_file(badge: Badge):
|
|
|
132
159
|
# 保存个人信息界面背景
|
|
133
160
|
async def save_info_pic(user: str, byt: bytes):
|
|
134
161
|
path = user_cache_path / user
|
|
135
|
-
|
|
136
|
-
path.mkdir()
|
|
162
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
137
163
|
with open(path / "info.png", "wb") as f:
|
|
138
164
|
f.write(BytesIO(byt).getvalue())
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def calculate_file_chunks(file_path: str, chunk_size: int = 1024 * 64) -> tuple[list[bytes], str, int]:
|
|
168
|
+
"""
|
|
169
|
+
计算文件分片和 SHA256
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
file_path: 文件路径
|
|
173
|
+
chunk_size: 分片大小(默认64KB)
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
(chunks, sha256_hash, total_size)
|
|
177
|
+
"""
|
|
178
|
+
chunks = []
|
|
179
|
+
hasher = hashlib.sha256()
|
|
180
|
+
total_size = 0
|
|
181
|
+
|
|
182
|
+
with open(file_path, "rb") as f:
|
|
183
|
+
while True:
|
|
184
|
+
chunk = f.read(chunk_size)
|
|
185
|
+
if not chunk:
|
|
186
|
+
break
|
|
187
|
+
chunks.append(chunk)
|
|
188
|
+
hasher.update(chunk)
|
|
189
|
+
total_size += len(chunk)
|
|
190
|
+
|
|
191
|
+
sha256_hash = hasher.hexdigest()
|
|
192
|
+
|
|
193
|
+
return chunks, sha256_hash, total_size
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
MAX_CONCURRENT_UPLOADS = 20
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
async def _upload_chunk(
|
|
200
|
+
bot: "Bot",
|
|
201
|
+
stream_id: str,
|
|
202
|
+
chunk_data: bytes,
|
|
203
|
+
chunk_index: int,
|
|
204
|
+
total_chunks: int,
|
|
205
|
+
total_size: int,
|
|
206
|
+
sha256_hash: str,
|
|
207
|
+
filename: str,
|
|
208
|
+
semaphore: asyncio.Semaphore,
|
|
209
|
+
) -> None:
|
|
210
|
+
"""内部函数,用于异步上传单个文件分片"""
|
|
211
|
+
async with semaphore:
|
|
212
|
+
# 将分片数据编码为 base64
|
|
213
|
+
chunk_base64 = base64.b64encode(chunk_data).decode("utf-8")
|
|
214
|
+
|
|
215
|
+
# 构建参数
|
|
216
|
+
params = {
|
|
217
|
+
"stream_id": stream_id,
|
|
218
|
+
"chunk_data": chunk_base64,
|
|
219
|
+
"chunk_index": chunk_index,
|
|
220
|
+
"total_chunks": total_chunks,
|
|
221
|
+
"file_size": total_size,
|
|
222
|
+
"expected_sha256": sha256_hash,
|
|
223
|
+
"filename": filename,
|
|
224
|
+
"file_retention": 30 * 1000,
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# 发送分片
|
|
228
|
+
response = await bot.call_api("upload_file_stream", **params)
|
|
229
|
+
|
|
230
|
+
logger.info(
|
|
231
|
+
f"分片 {chunk_index + 1}/{total_chunks} 上传成功 "
|
|
232
|
+
f"(接收: {response.get('received_chunks', 0)}/{response.get('total_chunks', 0)})"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
async def upload_file_stream_batch(bot: Bot, file_path: Path, chunk_size: int = 1024 * 64) -> str:
|
|
237
|
+
"""
|
|
238
|
+
一次性批量上传文件流
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
bot: Bot 实例
|
|
242
|
+
file_path: 要上传的文件路径
|
|
243
|
+
chunk_size: 分片大小
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
上传完成后的文件路径
|
|
247
|
+
"""
|
|
248
|
+
if not file_path.exists():
|
|
249
|
+
raise FileNotFoundError(f"文件不存在: {file_path}")
|
|
250
|
+
|
|
251
|
+
# 分析文件
|
|
252
|
+
chunks, sha256_hash, total_size = calculate_file_chunks(str(file_path), chunk_size)
|
|
253
|
+
stream_id = str(uuid.uuid4())
|
|
254
|
+
|
|
255
|
+
logger.info(f"\n开始上传文件: {file_path.name}")
|
|
256
|
+
logger.info(f"流ID: {stream_id}")
|
|
257
|
+
|
|
258
|
+
# 一次性发送所有分片
|
|
259
|
+
total_chunks = len(chunks)
|
|
260
|
+
# 创建信号量,限制最大并发数
|
|
261
|
+
semaphore = asyncio.Semaphore(MAX_CONCURRENT_UPLOADS)
|
|
262
|
+
|
|
263
|
+
# 创建所有分片上传任务
|
|
264
|
+
upload_tasks = []
|
|
265
|
+
for chunk_index, chunk_data in enumerate(chunks):
|
|
266
|
+
task = _upload_chunk(
|
|
267
|
+
bot, stream_id, chunk_data, chunk_index, total_chunks, total_size, sha256_hash, file_path.name, semaphore
|
|
268
|
+
)
|
|
269
|
+
upload_tasks.append(task)
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
await asyncio.gather(*upload_tasks)
|
|
273
|
+
except Exception as e:
|
|
274
|
+
logger.error(f"\n文件分片上传过程中发生错误: {e}")
|
|
275
|
+
# 这里可以选择执行清理逻辑,如通知服务器取消上传
|
|
276
|
+
raise e
|
|
277
|
+
|
|
278
|
+
# 发送完成信号
|
|
279
|
+
logger.info("\n所有分片发送完成,请求文件合并...")
|
|
280
|
+
complete_params = {"stream_id": stream_id, "is_complete": True}
|
|
281
|
+
|
|
282
|
+
response = await bot.call_api("upload_file_stream", **complete_params)
|
|
283
|
+
|
|
284
|
+
if response.get("status") == "file_complete":
|
|
285
|
+
logger.info("✅ 文件上传成功!")
|
|
286
|
+
logger.info(f" - 文件路径: {response.get('file_path')}")
|
|
287
|
+
logger.info(f" - 文件大小: {response.get('file_size')} 字节")
|
|
288
|
+
logger.info(f" - SHA256: {response.get('sha256')}")
|
|
289
|
+
return response.get("file_path")
|
|
290
|
+
else:
|
|
291
|
+
raise Exception(f"文件状态异常: {response}")
|
|
@@ -25,7 +25,7 @@ from reamber.algorithms.playField.parts import (
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
from ..file import download_map
|
|
28
|
-
from ..schema import
|
|
28
|
+
from ..schema.beatmapsets import BeatmapSets
|
|
29
29
|
|
|
30
30
|
osu_path = Path() / "data" / "osu"
|
|
31
31
|
if not osu_path.exists():
|
|
@@ -39,7 +39,7 @@ class Options:
|
|
|
39
39
|
od: Optional[float]
|
|
40
40
|
set: Optional[int]
|
|
41
41
|
map: Optional[int] = None
|
|
42
|
-
|
|
42
|
+
beatmapsets: Optional[BeatmapSets] = None
|
|
43
43
|
nsv: bool = False
|
|
44
44
|
nln: bool = False
|
|
45
45
|
fln: bool = False
|
|
@@ -89,15 +89,14 @@ async def convert_mania_map(options: Options) -> Optional[Path]:
|
|
|
89
89
|
with ZipFile(osz_file.absolute()) as my_zip:
|
|
90
90
|
my_zip.extractall(path)
|
|
91
91
|
os.remove(osz_file)
|
|
92
|
-
if options.
|
|
93
|
-
for
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
if options.beatmapsets:
|
|
93
|
+
for file in path.rglob("*.osu"):
|
|
94
|
+
osu = OsuMap.read_file(str(file.absolute()))
|
|
95
|
+
if osu.beatmap_id == options.map:
|
|
96
|
+
audio_file_name = osu.audio_file_name
|
|
97
|
+
audio_name = osu.audio_file_name[:-4]
|
|
98
|
+
audio_type = osu.audio_file_name[-4:]
|
|
98
99
|
break
|
|
99
|
-
else:
|
|
100
|
-
raise Exception("小夜api有问题啊")
|
|
101
100
|
if options.rate:
|
|
102
101
|
if options.rate > 10:
|
|
103
102
|
options.rate = 10
|
|
@@ -5,6 +5,7 @@ from .getbg import getbg
|
|
|
5
5
|
from .match import match
|
|
6
6
|
from .medal import medal
|
|
7
7
|
from .score import score
|
|
8
|
+
from .osudl import osudl
|
|
8
9
|
from .pr import pr, recent
|
|
9
10
|
from .rating import rating
|
|
10
11
|
from .history import history
|
|
@@ -57,4 +58,5 @@ __all__ = [
|
|
|
57
58
|
"match",
|
|
58
59
|
"rating",
|
|
59
60
|
"group_pp_rank",
|
|
61
|
+
"osudl",
|
|
60
62
|
]
|
|
@@ -34,12 +34,16 @@ async def _(event: Event, state: T_State):
|
|
|
34
34
|
uid = state["user"]
|
|
35
35
|
lazer_mode = "lazer模式下" if state["is_lazer"] else "stable模式下"
|
|
36
36
|
try:
|
|
37
|
-
score_ls = await get_user_scores(
|
|
37
|
+
score_ls = await get_user_scores(
|
|
38
|
+
uid, NGM[state["mode"]], "best", state["source"], legacy_only=not state["is_lazer"]
|
|
39
|
+
)
|
|
38
40
|
except NetworkError as e:
|
|
39
41
|
await UniMessage.text(
|
|
40
42
|
f"在查找用户:{state['username']} {NGM[state['mode']]}模式 {lazer_mode}时 {str(e)}"
|
|
41
43
|
).finish(reply_to=True)
|
|
42
44
|
for score in score_ls:
|
|
45
|
+
if not state["is_lazer"] or state["source"] == "ppysb":
|
|
46
|
+
score.mods = [mod for mod in score.mods if mod.acronym != "CL"]
|
|
43
47
|
for mod in score.mods:
|
|
44
48
|
if mod.acronym == "DT" or mod.acronym == "NC":
|
|
45
49
|
score.beatmap.total_length /= 1.5
|
|
@@ -89,14 +93,15 @@ async def _(event: Event, state: T_State):
|
|
|
89
93
|
pp_data.append({"name": mod, "value": round(pp, 2)})
|
|
90
94
|
mapper_pp = defaultdict(int)
|
|
91
95
|
for num, i in enumerate(score_ls):
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
mapper_pp = mapper_pp[:9]
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
key = i.beatmap.creator if state["source"] == "ppysb" else i.beatmap.user_id
|
|
97
|
+
mapper_pp[key] += i.pp * 0.95**num
|
|
98
|
+
mapper_pp = sorted(mapper_pp.items(), key=lambda x: x[1], reverse=True)[:9]
|
|
99
|
+
if state["source"] == "ppysb":
|
|
100
|
+
mapper_pp_data = [{"name": mapper, "value": round(pp, 2)} for mapper, pp in mapper_pp]
|
|
101
|
+
else:
|
|
102
|
+
users = await get_users([i[0] for i in mapper_pp])
|
|
103
|
+
user_dic = {i.id: i.username for i in users}
|
|
104
|
+
mapper_pp_data = [{"name": user_dic.get(mapper, ""), "value": round(pp, 2)} for mapper, pp in mapper_pp]
|
|
100
105
|
if len(mapper_pp_data) > 20:
|
|
101
106
|
mapper_pp_data = mapper_pp_data[:20]
|
|
102
107
|
name = f"{state['username']} {NGM[state['mode']]} 模式 "
|