nonebot-plugin-osubot 6.18.1__py3-none-any.whl → 6.20.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.

Potentially problematic release.


This version of nonebot-plugin-osubot might be problematic. Click here for more details.

@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import random
2
3
  from io import BytesIO
3
4
  from urllib.parse import urlencode
@@ -73,6 +74,58 @@ async def get_headers() -> dict[str, str]:
73
74
  return {"Authorization": f"Bearer {token}", "x-api-version": "20220705"}
74
75
 
75
76
 
77
+ async def fetch_score_batch(
78
+ uid: Union[int, str],
79
+ mode: str,
80
+ scope: str,
81
+ batch_size: int,
82
+ offset: int,
83
+ legacy_only: bool,
84
+ include_failed: bool,
85
+ ) -> list[UnifiedScore]:
86
+ """并发获取单次批次数据"""
87
+ url = (
88
+ f"{api}/users/{uid}/scores/{scope}?mode={mode}&limit={batch_size}"
89
+ f"&offset={offset}&legacy_only={int(legacy_only)}"
90
+ f"&include_fails={int(include_failed)}"
91
+ )
92
+ data = await make_request(url, await get_headers(), "未找到该玩家BP")
93
+ if not data:
94
+ return []
95
+ scores = [NewScore(**i) for i in data]
96
+ return [
97
+ UnifiedScore(
98
+ mods=i.mods,
99
+ ruleset_id=i.ruleset_id,
100
+ rank=i.rank,
101
+ accuracy=i.accuracy * 100,
102
+ total_score=i.total_score,
103
+ ended_at=datetime.strptime(i.ended_at.replace("Z", ""), "%Y-%m-%dT%H:%M:%S") + timedelta(hours=8),
104
+ max_combo=i.max_combo,
105
+ statistics=i.statistics,
106
+ legacy_total_score=i.legacy_total_score,
107
+ passed=i.passed,
108
+ beatmap=UnifiedBeatmap(
109
+ id=i.beatmap_id,
110
+ set_id=i.beatmapset.id,
111
+ artist=i.beatmapset.artist,
112
+ title=i.beatmapset.title,
113
+ version=i.beatmap.version,
114
+ creator=i.beatmapset.creator,
115
+ total_length=i.beatmap.total_length,
116
+ mode=i.beatmap.mode_int,
117
+ bpm=i.beatmap.bpm,
118
+ cs=i.beatmap.cs,
119
+ ar=i.beatmap.ar,
120
+ hp=i.beatmap.drain,
121
+ od=i.beatmap.accuracy,
122
+ stars=i.beatmap.difficulty_rating,
123
+ ),
124
+ )
125
+ for i in scores
126
+ ]
127
+
128
+
76
129
  async def get_user_scores(
77
130
  uid: Union[int, str],
78
131
  mode: str,
@@ -81,47 +134,40 @@ async def get_user_scores(
81
134
  legacy_only: bool = 0,
82
135
  include_failed: bool = True,
83
136
  offset: int = 0,
84
- limit: int = 100,
137
+ limit: int = 200,
85
138
  ) -> list[UnifiedScore]:
86
139
  if source == "osu":
87
- url = (
88
- f"{api}/users/{uid}/scores/{scope}?mode={mode}&limit={limit}&legacy_only={int(legacy_only)}"
89
- f"&offset={offset}&include_fails={int(include_failed)}"
90
- )
91
- data = await make_request(url, await get_headers(), "未找到该玩家BP")
92
- scores = [NewScore(**i) for i in data]
93
- unified_scores = [
94
- UnifiedScore(
95
- mods=i.mods,
96
- ruleset_id=i.ruleset_id,
97
- rank=i.rank,
98
- accuracy=i.accuracy * 100,
99
- total_score=i.total_score,
100
- ended_at=datetime.strptime(i.ended_at.replace("Z", ""), "%Y-%m-%dT%H:%M:%S") + timedelta(hours=8),
101
- max_combo=i.max_combo,
102
- statistics=i.statistics,
103
- legacy_total_score=i.legacy_total_score,
104
- passed=i.passed,
105
- beatmap=UnifiedBeatmap(
106
- id=i.beatmap_id,
107
- set_id=i.beatmapset.id,
108
- artist=i.beatmapset.artist,
109
- title=i.beatmapset.title,
110
- version=i.beatmap.version,
111
- creator=i.beatmapset.creator,
112
- total_length=i.beatmap.total_length,
113
- mode=i.beatmap.mode_int,
114
- bpm=i.beatmap.bpm,
115
- cs=i.beatmap.cs,
116
- ar=i.beatmap.ar,
117
- hp=i.beatmap.drain,
118
- od=i.beatmap.accuracy,
119
- stars=i.beatmap.difficulty_rating,
120
- ),
121
- )
122
- for i in scores
123
- ]
124
- return unified_scores
140
+ if limit <= 0:
141
+ return []
142
+
143
+ # 计算需要多少次请求
144
+ # 计算需要多少批次
145
+ batch_size = 100
146
+ total_batches = (limit + batch_size - 1) // batch_size # ceiling(limit/batch_size)
147
+ all_scores = []
148
+ # 分批并发请求
149
+ for batch_idx in range(0, total_batches, 2):
150
+ current_batches = range(batch_idx, min(batch_idx + 2, total_batches))
151
+
152
+ # 生成 tasks(并发执行)
153
+ tasks = []
154
+ for batch_n in current_batches:
155
+ offset = batch_n * batch_size
156
+ actual_batch_size = min(batch_size, limit - len(all_scores) - offset)
157
+
158
+ if actual_batch_size <= 0:
159
+ continue # 已获取足够数据
160
+
161
+ task = fetch_score_batch(uid, mode, scope, actual_batch_size, offset, legacy_only, include_failed)
162
+ tasks.append(task)
163
+ # 并发请求当前批次
164
+ batch_results = await asyncio.gather(*tasks)
165
+
166
+ for batch_scores in batch_results:
167
+ all_scores.extend(batch_scores)
168
+ if len(all_scores) >= limit:
169
+ return all_scores[:limit] # 提前终止
170
+ return all_scores[:limit]
125
171
 
126
172
  elif source == "ppysb":
127
173
  url = f"https://api.ppy.sb/v1/get_player_scores?scope={scope}&id={uid}&mode={FGM[mode]}&limit={limit}&include_failed={int(include_failed)}"
@@ -27,6 +27,7 @@ from .utils import (
27
27
  get_modeimage,
28
28
  open_user_icon,
29
29
  filter_scores_with_regex,
30
+ trim_text_with_ellipsis,
30
31
  )
31
32
  from .static import (
32
33
  Image,
@@ -398,9 +399,18 @@ async def draw_score_pic(score_info: UnifiedScore, info: UnifiedUser, map_json,
398
399
  anchor="lm",
399
400
  )
400
401
  # 谱面版本,mapper
402
+ if mapinfo.owners:
403
+ owner_names = [owner.username for owner in mapinfo.owners]
404
+ owners_str = ", ".join(owner_names)
405
+ mapper = f"{mapinfo.version} | 谱师: {owners_str}"
406
+
407
+ else:
408
+ mapper = f"{mapinfo.version} | 谱师: {mapinfo.beatmapset.creator}"
409
+ mapper = trim_text_with_ellipsis(mapper, 1000, Torus_SemiBold_20)
410
+
401
411
  draw.text(
402
412
  (225, 90),
403
- f"{mapinfo.version} | 谱师: {mapinfo.beatmapset.creator}",
413
+ mapper,
404
414
  font=Torus_SemiBold_20,
405
415
  anchor="lm",
406
416
  )
@@ -267,18 +267,18 @@ async def update_icon(info: UnifiedUser):
267
267
  user_icon = await get_projectimg(info.avatar_url)
268
268
  with open(path / f"icon.{info.avatar_url.split('.')[-1]}", "wb") as f:
269
269
  f.write(user_icon.getvalue())
270
-
271
- team_path = team_cache_path / f"{info.team.id}.png"
272
- if team_path.exists():
273
- modified_time = team_path.stat().st_mtime
274
- modified_datetime = datetime.datetime.fromtimestamp(modified_time)
275
- time_diff = datetime.datetime.now() - modified_datetime
276
- # 判断文件是否创建超过一天
277
- if time_diff > datetime.timedelta(days=1):
278
- team_path.unlink()
279
- team_icon = await get_projectimg(info.team.flag_url)
280
- with open(team_path, "wb") as f:
281
- f.write(team_icon.getvalue())
270
+ if info.team:
271
+ team_path = team_cache_path / f"{info.team.id}.png"
272
+ if team_path.exists():
273
+ modified_time = team_path.stat().st_mtime
274
+ modified_datetime = datetime.datetime.fromtimestamp(modified_time)
275
+ time_diff = datetime.datetime.now() - modified_datetime
276
+ # 判断文件是否创建超过一天
277
+ if time_diff > datetime.timedelta(days=1):
278
+ team_path.unlink()
279
+ team_icon = await get_projectimg(info.team.flag_url)
280
+ with open(team_path, "wb") as f:
281
+ f.write(team_icon.getvalue())
282
282
 
283
283
 
284
284
  async def update_map(set_id, map_id):
@@ -410,3 +410,31 @@ def filter_scores_with_regex(scores_with_index, conditions):
410
410
  score for score in scores_with_index if matches_condition_with_regex(score, key, operator, value)
411
411
  ]
412
412
  return scores_with_index
413
+
414
+
415
+ def trim_text_with_ellipsis(text, max_width, font):
416
+ # 初始检查:空字符串或无需处理
417
+ bbox = font.getbbox(text)
418
+ text_width = bbox[2] - bbox[0]
419
+ if not text or text_width <= max_width:
420
+ return text
421
+ # 逐字符检查
422
+ ellipsis_symbol = "…"
423
+ ellipsis_width = font.getbbox("…")[2] - font.getbbox("…")[0]
424
+ # 确保最大宽度能至少容纳一个字符+省略号
425
+ if max_width < font.getbbox("A")[2] - font.getbbox("A")[0] + ellipsis_width:
426
+ return ellipsis_symbol
427
+
428
+ truncated_text = ""
429
+ current_width = 0
430
+
431
+ for char in text:
432
+ # 检查当前字符宽度 + 省略号宽度是否超标
433
+ char_width = font.getbbox(char)[2] - font.getbbox(char)[0]
434
+ if current_width + char_width + ellipsis_width > max_width:
435
+ break
436
+ truncated_text += char
437
+ current_width += char_width
438
+
439
+ # 返回截断后的字符串 + 省略号
440
+ return truncated_text + ellipsis_symbol if truncated_text else ellipsis_symbol
@@ -25,8 +25,8 @@ async def _bp(state: T_State):
25
25
  if not best.isdigit():
26
26
  await UniMessage.text("只能接受纯数字的bp参数").finish(reply_to=True)
27
27
  best = int(best)
28
- if best <= 0 or best > 100:
29
- await UniMessage.text("只允许查询bp 1-100 的成绩").finish(reply_to=True)
28
+ if best <= 0 or best > 200:
29
+ await UniMessage.text("只允许查询bp 1-200 的成绩").finish(reply_to=True)
30
30
  try:
31
31
  data = await draw_score(
32
32
  "bp",
@@ -52,11 +52,11 @@ async def _pfm(state: T_State):
52
52
  if "error" in state:
53
53
  await UniMessage.text(state["error"]).finish(reply_to=True)
54
54
  if not state["range"]:
55
- state["range"] = "1-100"
55
+ state["range"] = "1-200"
56
56
  ls = state["range"].split("-")
57
57
  low, high = int(ls[0]), int(ls[1])
58
- if not 0 < low < high <= 100:
59
- await UniMessage.text("仅支持查询bp1-100").finish(reply_to=True)
58
+ if not 0 < low < high <= 200:
59
+ await UniMessage.text("仅支持查询bp1-200").finish(reply_to=True)
60
60
  try:
61
61
  data = await draw_bp(
62
62
  "bp",
@@ -84,11 +84,11 @@ async def _tbp(state: T_State):
84
84
  if "error" in state:
85
85
  await UniMessage.text(state["error"]).finish(reply_to=True)
86
86
  if not state["range"]:
87
- state["range"] = "1-100"
87
+ state["range"] = "1-200"
88
88
  ls = state["range"].split("-")
89
89
  low, high = int(ls[0]), int(ls[1])
90
- if not 0 < low < high <= 100:
91
- await UniMessage.text("仅支持查询bp1-100").finish(reply_to=True)
90
+ if not 0 < low < high <= 200:
91
+ await UniMessage.text("仅支持查询bp1-200").finish(reply_to=True)
92
92
  try:
93
93
  data = await draw_bp(
94
94
  "tbp",
@@ -11,6 +11,11 @@ class Covers(Base):
11
11
  slimcover: str
12
12
 
13
13
 
14
+ class Gds(Base):
15
+ id: int
16
+ username: str
17
+
18
+
14
19
  class BeatmapsetCompact(Base):
15
20
  artist: str
16
21
  artist_unicode: str
@@ -78,6 +83,7 @@ class Beatmap(BeatmapCompact):
78
83
  playcount: int
79
84
  ranked: int
80
85
  url: str
86
+ owners: Optional[list[Gds]] = None
81
87
 
82
88
 
83
89
  class BackgroundsAttributes(Base):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nonebot-plugin-osubot
3
- Version: 6.18.1
3
+ Version: 6.20.0
4
4
  Summary: OSUbot in NoneBot2
5
5
  License: AGPL-3.0
6
6
  Author-email: yaowan233 <572473053@qq.com>
@@ -18,7 +18,7 @@ Requires-Dist: nonebot2>=2.3.0
18
18
  Requires-Dist: pillow>=9.2.0
19
19
  Requires-Dist: pydantic!=2.5.0,!=2.5.1,<3.0.0,>=1.10.0
20
20
  Requires-Dist: reamber>=0.2.1
21
- Requires-Dist: rosu-pp-py==2.0.1
21
+ Requires-Dist: rosu-pp-py==3.0.0
22
22
  Requires-Dist: tortoise-orm>=0.20.0
23
23
  Requires-Dist: typing_extensions>=4.11.0
24
24
  Project-URL: Homepage, https://github.com/yaowan233/nonebot-plugin-osubot
@@ -1,5 +1,5 @@
1
1
  nonebot_plugin_osubot/__init__.py,sha256=Q-mTTnOIdKiKG6JrVm-kqpPrAhOP9lWyiKHNRqA7gpc,1478
2
- nonebot_plugin_osubot/api.py,sha256=lzp86uRbnt_Vd9qg2kIHy26EB7ODFGKkbNA6H2iJNh0,14933
2
+ nonebot_plugin_osubot/api.py,sha256=hBG_Ugmkwwdw99XNO--1Q8bzfibxbcH3aZHBbfOkEvc,16304
3
3
  nonebot_plugin_osubot/beatmap_stats_moder.py,sha256=mNNTufc-gvO4NdYa3TnealSZI4-LBoiTlb599SeLBck,2915
4
4
  nonebot_plugin_osubot/config.py,sha256=Ub2s5Ny09-d1ZwT6x8cirB6zWy0brtO-oZV3W0qEM5Q,311
5
5
  nonebot_plugin_osubot/database/__init__.py,sha256=7CDo9xU_DGLQ6uTj_mU_Px92phg_DMU5mP6WvgOxFLY,101
@@ -42,14 +42,14 @@ nonebot_plugin_osubot/draw/info.py,sha256=i2YcJmSdTpwhZl_nDe7GJv4jQTB8_9oBfpq2Zw
42
42
  nonebot_plugin_osubot/draw/map.py,sha256=4M8xRd0dIbC5g1s8I4eTZ3vRglM6r_TSznFOZ62K2wk,8654
43
43
  nonebot_plugin_osubot/draw/match_history.py,sha256=GBJl6lAA27U7NSMC2isEzD_YsoIPAeG6ijDu7Oflcl0,997
44
44
  nonebot_plugin_osubot/draw/rating.py,sha256=pA7mGLI4IujmYB6kQf_tSkR7mZGpUAVLRLyaAsZhqTM,20397
45
- nonebot_plugin_osubot/draw/score.py,sha256=bEQq01UCdOeIE_m35nKuNYGk84bzfVjAsQZfleQ1yTU,27223
45
+ nonebot_plugin_osubot/draw/score.py,sha256=Y2-BUuKYgYoXk-Uq0PFNg3WaU_SUfSB6Ebo446xslUg,27553
46
46
  nonebot_plugin_osubot/draw/static.py,sha256=2rznsXZTcKggQ5JSpsJg4y6uWP4e-Y40U7v_QAwLT4Q,3969
47
47
  nonebot_plugin_osubot/draw/taiko_preview.py,sha256=tqhuHSdxUJEuXqKHMJDeSLdusuJhSnMMiaG5FbUnaJw,11441
48
48
  nonebot_plugin_osubot/draw/templates/bpa_chart.html,sha256=cnpM0qRvvyCMTRP-mIOABQlaaqxQwG5kLUxlo4h7F7w,7012
49
49
  nonebot_plugin_osubot/draw/templates/echarts.min.js,sha256=IF32ooP8NPIzQg_fs7lVHpwG92JcCPE1TZAEyFSgGZU,1022939
50
50
  nonebot_plugin_osubot/draw/templates/mod_chart.html,sha256=Iz71KM5v9z_Rt2vqJ5WIZumRIHgDfcGIUmWGvVGZ-nQ,1508
51
51
  nonebot_plugin_osubot/draw/templates/pp_rank_line_chart.html,sha256=Gyf-GR8ZBlWQTks0TlB3M8EWUBMVwiUaesFAmDISxLo,1417
52
- nonebot_plugin_osubot/draw/utils.py,sha256=mwP_wTnuRnp9FrlCqUFvCX2p0_Lm5jQ73PgjjhtF6EA,14205
52
+ nonebot_plugin_osubot/draw/utils.py,sha256=LZVDmrQizVwVFX0kDJISYYO68jmZbn5Xz40gV5bj8bo,15276
53
53
  nonebot_plugin_osubot/exceptions.py,sha256=N_FsEk-9Eu2QnuznhdfWn6OoyA1HH73Q7bUaY89gVi0,40
54
54
  nonebot_plugin_osubot/file.py,sha256=GhG54EdVsxFf8_8GZOPh4YGvw9iw5bAX9JFz4Mg4kMg,4379
55
55
  nonebot_plugin_osubot/info/__init__.py,sha256=I7YlMQiuHExEeJWqyzZb2I-Vl2uql3Py2LdhSH2Z9N0,136
@@ -58,7 +58,7 @@ nonebot_plugin_osubot/info/bind.py,sha256=b2ua625hbYym7rpb-kLBB-VDP5YWFdmT5RweM5
58
58
  nonebot_plugin_osubot/mania/__init__.py,sha256=XRPn563jDJiPohFekcoFFCqCJYznb-6uORneioZv4xI,5534
59
59
  nonebot_plugin_osubot/matcher/__init__.py,sha256=yID7QcdQF6_Mouwbej3JwYUBbKSU3VQdrjq6B1Fz9P8,1331
60
60
  nonebot_plugin_osubot/matcher/bind.py,sha256=QQJc2S7XFo5tu4CPloIET6fKaeiQixgb8M7QvULV6E0,2834
61
- nonebot_plugin_osubot/matcher/bp.py,sha256=ULNVgrlRCe9w6N7yC7Yw3Y-OSJQKR8AZfBkB5jToOPQ,3983
61
+ nonebot_plugin_osubot/matcher/bp.py,sha256=GidJfuZ9lJ7LwMq126DDOwMksNUOz4Bkab83OlKg8t8,3983
62
62
  nonebot_plugin_osubot/matcher/bp_analyze.py,sha256=S1dhYiGqtPQapJt4PB5mNKfpPbOpVNAqDIToNEJVpSY,3949
63
63
  nonebot_plugin_osubot/matcher/getbg.py,sha256=Ar2yIST556MYRxzuXCLSDAMAmESRENN1fCty-kdH7PI,699
64
64
  nonebot_plugin_osubot/matcher/guess.py,sha256=iE5oRZ3mDTC_wcjLJTyyAWlmnDzkAgzGN5rvoTIn0uM,24093
@@ -396,13 +396,13 @@ nonebot_plugin_osubot/pp.py,sha256=PSWGLWERr4vVtE9H5D2EBm-_hM5HOU3PQvv1cC4rqmQ,3
396
396
  nonebot_plugin_osubot/schema/__init__.py,sha256=io5BqRRNeBUSWPw5VXQav_TMrDN4dsTVpoMzrU9lTCA,468
397
397
  nonebot_plugin_osubot/schema/alphaosu.py,sha256=7cJLIwl4X-dshYsXMi8hHgcp7Ly6BiI3pqwXENhMaX0,693
398
398
  nonebot_plugin_osubot/schema/basemodel.py,sha256=aZI1rdOHx-kmMXohazq1s5tYdtR-2WRzfYQATmWd6a4,99
399
- nonebot_plugin_osubot/schema/beatmap.py,sha256=_WPLWz6GYs_9igGCHoqWy59p-JfwIZ3MWY6l_f4YGqE,1872
399
+ nonebot_plugin_osubot/schema/beatmap.py,sha256=UnobfZEHq1V2HG-A4j3BECubO-dB1JzTMA2_QIXttNM,1960
400
400
  nonebot_plugin_osubot/schema/match.py,sha256=lR3pGNVR9K_5GZAdOLG6Ki-W3fwJvgMlNhYOzKNE3lg,494
401
401
  nonebot_plugin_osubot/schema/ppysb/__init__.py,sha256=JK2Z4n44gUJPVKdETMJYJ5uIw-4a8T50y6j5n-YrlYM,1375
402
402
  nonebot_plugin_osubot/schema/sayo_beatmap.py,sha256=lS1PQZ-HvHl0VhkzlI0-pNLeJrLYWVqmKAo6xZr5I2U,959
403
403
  nonebot_plugin_osubot/schema/score.py,sha256=zHU-w2e7RzMDL8vdPkX5vggcqalBo83JTvu96abcflo,3124
404
404
  nonebot_plugin_osubot/schema/user.py,sha256=sxNmqymG_kIVuGuzfchSv9UML6NPG70cqo2_h5xDIpM,2250
405
405
  nonebot_plugin_osubot/utils/__init__.py,sha256=pyv8XxBcCOeQVDj1E4dgvktzcefgQXfKBlarsYGx1sg,815
406
- nonebot_plugin_osubot-6.18.1.dist-info/WHEEL,sha256=B19PGBCYhWaz2p_UjAoRVh767nYQfk14Sn4TpIZ-nfU,87
407
- nonebot_plugin_osubot-6.18.1.dist-info/METADATA,sha256=8mVJgIDqkt_Ps9LK9i018HXK-OfPLmJri2RmJp15wZk,4476
408
- nonebot_plugin_osubot-6.18.1.dist-info/RECORD,,
406
+ nonebot_plugin_osubot-6.20.0.dist-info/WHEEL,sha256=B19PGBCYhWaz2p_UjAoRVh767nYQfk14Sn4TpIZ-nfU,87
407
+ nonebot_plugin_osubot-6.20.0.dist-info/METADATA,sha256=h46-68Jkbr_-bhXPbyK_BS-f7kVOO585n8TGDpPYijc,4476
408
+ nonebot_plugin_osubot-6.20.0.dist-info/RECORD,,