nonebot-plugin-osubot 6.22.1__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.
Files changed (97) hide show
  1. nonebot_plugin_osubot/api.py +13 -7
  2. nonebot_plugin_osubot/config.py +10 -10
  3. nonebot_plugin_osubot/draw/bmap.py +20 -22
  4. nonebot_plugin_osubot/draw/bp.py +3 -13
  5. nonebot_plugin_osubot/draw/catch_preview.py +2 -16
  6. nonebot_plugin_osubot/draw/echarts.py +8 -1
  7. nonebot_plugin_osubot/draw/info.py +59 -207
  8. nonebot_plugin_osubot/draw/info_templates/index.html +507 -0
  9. nonebot_plugin_osubot/draw/info_templates/output.css +2 -0
  10. nonebot_plugin_osubot/draw/map.py +9 -11
  11. nonebot_plugin_osubot/draw/osu_preview.py +50 -0
  12. nonebot_plugin_osubot/draw/osu_preview_templates/css/style.css +258 -0
  13. nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/README.md +109 -0
  14. nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/gif.js +3 -0
  15. nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/gif.js.map +1 -0
  16. nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/gif.worker.js +3 -0
  17. nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/gif.worker.js.map +1 -0
  18. nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/beatmap.js +211 -0
  19. nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/hitobject.js +29 -0
  20. nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/point.js +55 -0
  21. nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/scroll.js +45 -0
  22. nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/timingpoint.js +35 -0
  23. nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/LegacyRandom.js +81 -0
  24. nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/PalpableCatchHitObject.js +53 -0
  25. nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/bananashower.js +33 -0
  26. nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/catch.js +211 -0
  27. nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/fruit.js +21 -0
  28. nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/juicestream.js +176 -0
  29. nonebot_plugin_osubot/draw/osu_preview_templates/js/mania/hitnote.js +21 -0
  30. nonebot_plugin_osubot/draw/osu_preview_templates/js/mania/holdnote.js +37 -0
  31. nonebot_plugin_osubot/draw/osu_preview_templates/js/mania/mania.js +164 -0
  32. nonebot_plugin_osubot/draw/osu_preview_templates/js/preview.js +61 -0
  33. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/bezier2.js +33 -0
  34. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/catmullcurve.js +34 -0
  35. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/centripetalcatmullrom.js +30 -0
  36. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/circumstancedcircle.js +47 -0
  37. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/curve.js +25 -0
  38. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/curvetype.js +17 -0
  39. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/equaldistancemulticurve.js +70 -0
  40. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/linearbezier.js +40 -0
  41. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/hitcircle.js +85 -0
  42. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/slider.js +120 -0
  43. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/spinner.js +56 -0
  44. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/standard.js +170 -0
  45. nonebot_plugin_osubot/draw/osu_preview_templates/js/taiko/donkat.js +40 -0
  46. nonebot_plugin_osubot/draw/osu_preview_templates/js/taiko/drumroll.js +34 -0
  47. nonebot_plugin_osubot/draw/osu_preview_templates/js/taiko/shaker.js +58 -0
  48. nonebot_plugin_osubot/draw/osu_preview_templates/js/taiko/taiko.js +120 -0
  49. nonebot_plugin_osubot/draw/osu_preview_templates/js/util.js +61 -0
  50. nonebot_plugin_osubot/draw/osu_preview_templates/pic.html +110 -0
  51. nonebot_plugin_osubot/draw/rating.py +6 -3
  52. nonebot_plugin_osubot/draw/score.py +23 -81
  53. nonebot_plugin_osubot/draw/taiko_preview.py +14 -13
  54. nonebot_plugin_osubot/draw/templates/bpa_chart.html +1 -1
  55. nonebot_plugin_osubot/draw/utils.py +162 -16
  56. nonebot_plugin_osubot/file.py +184 -31
  57. nonebot_plugin_osubot/mania/__init__.py +9 -10
  58. nonebot_plugin_osubot/matcher/__init__.py +2 -0
  59. nonebot_plugin_osubot/matcher/bp_analyze.py +14 -9
  60. nonebot_plugin_osubot/matcher/guess.py +250 -294
  61. nonebot_plugin_osubot/matcher/map_convert.py +21 -13
  62. nonebot_plugin_osubot/matcher/medal.py +1 -1
  63. nonebot_plugin_osubot/matcher/osudl.py +5 -4
  64. nonebot_plugin_osubot/matcher/pr.py +0 -4
  65. nonebot_plugin_osubot/matcher/preview.py +10 -3
  66. nonebot_plugin_osubot/matcher/recommend.py +7 -12
  67. nonebot_plugin_osubot/mods.py +62 -61
  68. nonebot_plugin_osubot/network/first_response.py +1 -1
  69. nonebot_plugin_osubot/osufile/mods/AP.png +0 -0
  70. nonebot_plugin_osubot/osufile/mods/CL.png +0 -0
  71. nonebot_plugin_osubot/osufile/mods/DT.png +0 -0
  72. nonebot_plugin_osubot/osufile/mods/EZ.png +0 -0
  73. nonebot_plugin_osubot/osufile/mods/FI.png +0 -0
  74. nonebot_plugin_osubot/osufile/mods/FL.png +0 -0
  75. nonebot_plugin_osubot/osufile/mods/HD.png +0 -0
  76. nonebot_plugin_osubot/osufile/mods/HR.png +0 -0
  77. nonebot_plugin_osubot/osufile/mods/HT.png +0 -0
  78. nonebot_plugin_osubot/osufile/mods/MR.png +0 -0
  79. nonebot_plugin_osubot/osufile/mods/NC.png +0 -0
  80. nonebot_plugin_osubot/osufile/mods/NF.png +0 -0
  81. nonebot_plugin_osubot/osufile/mods/PF.png +0 -0
  82. nonebot_plugin_osubot/osufile/mods/RX.png +0 -0
  83. nonebot_plugin_osubot/osufile/mods/SD.png +0 -0
  84. nonebot_plugin_osubot/osufile/mods/SO.png +0 -0
  85. nonebot_plugin_osubot/osufile/mods/TD.png +0 -0
  86. nonebot_plugin_osubot/osufile/mods/V2.png +0 -0
  87. nonebot_plugin_osubot/pp.py +7 -0
  88. nonebot_plugin_osubot/schema/__init__.py +0 -2
  89. nonebot_plugin_osubot/schema/beatmapsets.py +42 -0
  90. nonebot_plugin_osubot/schema/draw_info.py +54 -0
  91. nonebot_plugin_osubot/schema/score.py +2 -0
  92. nonebot_plugin_osubot/schema/user.py +1 -0
  93. {nonebot_plugin_osubot-6.22.1.dist-info → nonebot_plugin_osubot-6.26.4.dist-info}/METADATA +18 -17
  94. {nonebot_plugin_osubot-6.22.1.dist-info → nonebot_plugin_osubot-6.26.4.dist-info}/RECORD +95 -52
  95. nonebot_plugin_osubot-6.26.4.dist-info/WHEEL +4 -0
  96. nonebot_plugin_osubot/schema/sayo_beatmap.py +0 -59
  97. nonebot_plugin_osubot-6.22.1.dist-info/WHEEL +0 -4
@@ -1,5 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
+ from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent
3
4
  from nonebot.rule import ArgumentParser
4
5
  from nonebot.internal.adapter import Message
5
6
  from nonebot_plugin_alconna import UniMessage
@@ -7,8 +8,10 @@ from nonebot import on_command, on_shell_command
7
8
  from nonebot.exception import ParserExit, ActionFailed
8
9
  from nonebot.params import CommandArg, ShellCommandArgv
9
10
 
10
- from ..api import get_sayo_map_info
11
+ from ..api import get_beatmapsets_info, osu_api
11
12
  from ..mania import Options, convert_mania_map
13
+ from ..schema import Beatmap
14
+ from ..file import upload_file_stream_batch
12
15
 
13
16
  parser = ArgumentParser("convert", description="变换mania谱面")
14
17
  parser.add_argument("--set", type=int, help="要转换的谱面的setid")
@@ -37,9 +40,11 @@ async def _(argv: list[str] = ShellCommandArgv()):
37
40
  return
38
41
  options = Options(**vars(args))
39
42
  if options.map:
40
- sayo_map_info = await get_sayo_map_info(options.map, 1)
41
- options.set = sayo_map_info.data.sid
42
- options.sayo_info = sayo_map_info
43
+ map_data = await osu_api("map", map_id=options.map)
44
+ mapinfo = Beatmap(**map_data)
45
+ beatmapsets_info = await get_beatmapsets_info(mapinfo.beatmapset_id)
46
+ options.set = mapinfo.beatmapset_id
47
+ options.beatmapsets = beatmapsets_info
43
48
  if not options.set:
44
49
  await UniMessage.text("请提供需要转换的谱面setid").finish(reply_to=True)
45
50
  if options.nln and options.fln:
@@ -64,7 +69,7 @@ change = on_command("倍速", priority=11, block=True)
64
69
 
65
70
 
66
71
  @change.handle()
67
- async def _(msg: Message = CommandArg()):
72
+ async def _(bot: Bot, event: GroupMessageEvent, msg: Message = CommandArg()):
68
73
  args = msg.extract_plain_text().strip().split()
69
74
  argv = ["--map"]
70
75
  if not args:
@@ -85,16 +90,19 @@ async def _(msg: Message = CommandArg()):
85
90
  args = parser.parse_args(argv)
86
91
  options = Options(**vars(args))
87
92
  if options.map:
88
- sayo_map_info = await get_sayo_map_info(options.map, 1)
89
- options.set = sayo_map_info.data.sid
90
- options.sayo_info = sayo_map_info
93
+ map_data = await osu_api("map", map_id=options.map)
94
+ mapinfo = Beatmap(**map_data)
95
+ beatmapsets_info = await get_beatmapsets_info(mapinfo.beatmapset_id)
96
+ options.set = mapinfo.beatmapset_id
97
+ options.beatmapsets = beatmapsets_info
91
98
  osz_path = await convert_mania_map(options)
92
99
  if not osz_path:
93
100
  await UniMessage.text("未找到该地图,请检查是否搞混了mapID与setID").finish(reply_to=True)
94
101
  file_path = osz_path.absolute()
102
+ server_osz_path = await upload_file_stream_batch(bot, file_path)
103
+
95
104
  try:
96
- with open(file_path, "rb") as f:
97
- await UniMessage.file(raw=f.read()).send()
105
+ await bot.call_api("upload_group_file", group_id=event.group_id, file=server_osz_path, name=osz_path.name)
98
106
  except ActionFailed:
99
107
  await UniMessage.text("上传文件失败,可能是群空间满或没有权限导致的").send(reply_to=True)
100
108
  finally:
@@ -108,7 +116,7 @@ generate_full_ln = on_command("反键", priority=11, block=True)
108
116
 
109
117
 
110
118
  @generate_full_ln.handle()
111
- async def _(msg: Message = CommandArg()):
119
+ async def _(bot: Bot, event: GroupMessageEvent, msg: Message = CommandArg()):
112
120
  args = msg.extract_plain_text().strip().split()
113
121
  if not args:
114
122
  await UniMessage.text("请输入需要转ln的地图setID").finish(reply_to=True)
@@ -128,9 +136,9 @@ async def _(msg: Message = CommandArg()):
128
136
  if not osz_path:
129
137
  await UniMessage.text("未找到该地图,请检查是否搞混了mapID与setID").finish(reply_to=True)
130
138
  file_path = osz_path.absolute()
139
+ server_osz_path = await upload_file_stream_batch(bot, file_path)
131
140
  try:
132
- with open(file_path, "rb") as f:
133
- await UniMessage.file(raw=f.read()).send()
141
+ await bot.call_api("upload_group_file", group_id=event.group_id, file=server_osz_path, name=osz_path.name)
134
142
  except ActionFailed:
135
143
  await UniMessage.text("上传文件失败,可能是群空间满或没有权限导致的").send(reply_to=True)
136
144
  finally:
@@ -69,6 +69,6 @@ async def _(msg: Message = CommandArg()):
69
69
  for beatmap in medal_data["beatmaps"]:
70
70
  msg += (
71
71
  f"{beatmap['SongTitle']} [{beatmap['DifficultyName']}]\n{beatmap['Difficulty']}⭐\n"
72
- + f"https://osu.ppy.sh/b/{beatmap['BeatmapID']}"
72
+ + f"https://osu.ppy.sh/b/{beatmap['BeatmapID']}\n"
73
73
  )
74
74
  await msg.send()
@@ -1,22 +1,23 @@
1
1
  from nonebot import on_command
2
+ from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent
2
3
  from nonebot.params import CommandArg
3
4
  from nonebot.internal.adapter import Message
4
5
  from nonebot_plugin_alconna import UniMessage
5
6
 
6
- from ..file import download_map
7
+ from ..file import download_map, upload_file_stream_batch
7
8
 
8
9
  osudl = on_command("osudl", priority=11, block=True)
9
10
 
10
11
 
11
12
  @osudl.handle()
12
- async def _osudl(setid: Message = CommandArg()):
13
+ async def _osudl(bot: Bot, event: GroupMessageEvent, setid: Message = CommandArg()):
13
14
  setid = setid.extract_plain_text().strip()
14
15
  if not setid or not setid.isdigit():
15
16
  await UniMessage.text("请输入正确的地图ID").send(reply_to=True)
16
17
  osz_path = await download_map(int(setid))
17
- file_path = osz_path.absolute()
18
+ server_osz_path = await upload_file_stream_batch(bot, osz_path)
18
19
  try:
19
- await UniMessage.file(path=file_path).send()
20
+ await bot.call_api("upload_group_file", group_id=event.group_id, file=server_osz_path, name=osz_path.name)
20
21
  except Exception:
21
22
  await UniMessage.text("上传文件失败,可能是群空间满或没有权限导致的").send(reply_to=True)
22
23
  finally:
@@ -43,8 +43,6 @@ async def _recent(event: Event, state: T_State):
43
43
  f"在查找用户:{state['username']} {NGM[state['mode']]}模式"
44
44
  f" {lazer_mode}{mods} 最近{state['range']}成绩时 {str(e)}"
45
45
  ).finish(reply_to=True)
46
- if not player.lazer_mode:
47
- scores = [i for i in scores if any(mod.acronym == "CL" for mod in i.mods)]
48
46
  for score in scores:
49
47
  cal_score_info(player.lazer_mode, score)
50
48
  pic = await draw_pfm("relist", state["user"], scores, scores, mode, state["source"], is_lazer=player.lazer_mode)
@@ -99,8 +97,6 @@ async def _pr(event: Event, state: T_State):
99
97
  f"在查找用户:{state['username']} {NGM[state['mode']]}模式"
100
98
  f" {lazer_mode}{mods} 最近{state['range']}成绩时 {str(e)}"
101
99
  ).finish(reply_to=True)
102
- if not player.lazer_mode:
103
- scores = [i for i in scores if any(mod.acronym == "CL" for mod in i.mods)]
104
100
  for score_info in scores:
105
101
  cal_score_info(player.lazer_mode, score_info)
106
102
  pic = await draw_pfm(
@@ -5,9 +5,10 @@ from nonebot_plugin_alconna import UniMessage
5
5
  from ..utils import NGM
6
6
  from ..api import osu_api
7
7
  from .utils import split_msg
8
- from ..file import download_tmp_osu
8
+ from ..file import download_osu
9
9
  from ..exceptions import NetworkError
10
10
  from ..mania import generate_preview_pic
11
+ from ..draw.osu_preview import draw_osu_preview
11
12
  from ..draw.catch_preview import draw_cath_preview
12
13
  from ..draw.taiko_preview import parse_map, map_to_image
13
14
 
@@ -24,7 +25,7 @@ async def _(state: T_State):
24
25
  except NetworkError as e:
25
26
  await UniMessage.text(f"查找map_id:{osu_id} 信息时 {str(e)}").finish(reply_to=True)
26
27
  if state["mode"] == "3":
27
- osu = await download_tmp_osu(osu_id)
28
+ osu = await download_osu(data["beatmapset_id"], int(osu_id))
28
29
  if state["_prefix"]["command"][0] == "完整预览":
29
30
  pic = await generate_preview_pic(osu, True)
30
31
  else:
@@ -34,10 +35,16 @@ async def _(state: T_State):
34
35
  pic = await draw_cath_preview(int(osu_id), data["beatmapset_id"], state["mods"])
35
36
  await UniMessage.image(raw=pic).finish(reply_to=True)
36
37
  elif state["mode"] == "1":
37
- osu = await download_tmp_osu(osu_id)
38
+ osu = await download_osu(data["beatmapset_id"], int(osu_id))
38
39
  beatmap = parse_map(osu)
39
40
  pic = map_to_image(beatmap)
40
41
  await UniMessage.image(raw=pic).finish(reply_to=True)
42
+ elif state["mode"] == "0":
43
+ pic = await draw_osu_preview(int(osu_id), data["beatmapset_id"])
44
+ msg = UniMessage.image(raw=pic) + UniMessage.text(
45
+ f"点击预览:\nhttps://beatmap.try-z.net/?b={osu_id}\nhttps://beatmap.try-z.net/dev/?b={osu_id}"
46
+ )
47
+ await msg.finish(reply_to=True)
41
48
  elif not (0 <= int(state["mode"]) <= 3):
42
49
  await UniMessage.text("模式应为0-3!\n0: std\n1:taiko\n2:ctb\n3: mania").finish()
43
50
  else:
@@ -2,14 +2,14 @@ import re
2
2
  from random import shuffle
3
3
 
4
4
  from nonebot import on_command
5
- from nonebot.log import logger
6
5
  from nonebot.typing import T_State
7
6
  from expiringdict import ExpiringDict
8
7
  from nonebot.internal.matcher import Matcher
9
8
  from nonebot_plugin_alconna import UniMessage
10
9
 
11
10
  from .utils import split_msg
12
- from ..api import get_recommend, update_recommend, get_sayo_map_info
11
+ from ..api import get_recommend, update_recommend, osu_api
12
+ from ..schema import Beatmap
13
13
 
14
14
  recommend = on_command("推荐", priority=11, block=True, aliases={"recommend", "推荐铺面", "推荐谱面"})
15
15
  recommend_cache = ExpiringDict(1000, 60 * 60 * 12)
@@ -41,23 +41,18 @@ async def handle_recommend(state: T_State, matcher: type[Matcher]):
41
41
  break
42
42
  else:
43
43
  await matcher.finish("今天已经没有可以推荐的图啦,明天再来吧")
44
+ return None
44
45
  bid = int(re.findall("https://osu.ppy.sh/beatmaps/(.*)", recommend_map.mapLink)[0])
45
- map_info = await get_sayo_map_info(bid, 1)
46
- sid = map_info.data.sid
47
- for i in map_info.data.bid_data:
48
- if i.bid == bid:
49
- bg = i.bg
50
- break
51
- else:
52
- bg = ""
53
- logger.error(f"出现问题: 有问题的是{bid}, {sid}")
46
+ map_data = await osu_api("map", map_id=bid)
47
+ map_info = Beatmap(**map_data)
48
+ sid = map_info.beatmapset_id
54
49
  s = (
55
50
  f"推荐的铺面是{recommend_map.mapName} ⭐{round(recommend_map.difficulty, 2)}\n{''.join(recommend_map.mod)}\n"
56
51
  f"预计pp为{round(recommend_map.predictPP, 2)}\n提升概率为{round(recommend_map.passPercent * 100, 2)}%\n"
57
52
  f"{recommend_map.mapLink}\nhttps://kitsu.moe/api/d/{sid}\n"
58
53
  f"https://txy1.sayobot.cn/beatmaps/download/novideo/{sid}"
59
54
  )
60
- pic_url = f"https://dl.sayobot.cn/beatmaps/files/{sid}/{bg}"
55
+ pic_url = f"https://osu.direct/api/media/background/{bid}"
61
56
  return pic_url, s
62
57
 
63
58
 
@@ -1,61 +1,62 @@
1
- from .schema.score import Mod, UnifiedScore
2
-
3
- mods_dic = {
4
- "CL": 0,
5
- "NO": 0,
6
- "NF": 1 << 0,
7
- "EZ": 1 << 1,
8
- "TD": 1 << 2,
9
- "HD": 1 << 3,
10
- "HR": 1 << 4,
11
- "SD": 1 << 5,
12
- "DT": 1 << 6,
13
- "RX": 1 << 7,
14
- "HT": 1 << 8,
15
- "NC": 1 << 9,
16
- "FL": 1 << 10,
17
- "AT": 1 << 11,
18
- "SO": 1 << 12,
19
- "RX2": 1 << 13,
20
- "PF": 1 << 14,
21
- "4K": 1 << 15,
22
- "5K": 1 << 16,
23
- "6K": 1 << 17,
24
- "7K": 1 << 18,
25
- "8K": 1 << 19,
26
- "FI": 1 << 20,
27
- "RD": 1 << 21,
28
- "Cinema": 1 << 22,
29
- "TG": 1 << 23,
30
- "9K": 1 << 24,
31
- "KC": 1 << 25,
32
- "1K": 1 << 26,
33
- "3K": 1 << 27,
34
- "2K": 1 << 28,
35
- "V2": 1 << 29,
36
- "MR": 1 << 30,
37
- }
38
-
39
-
40
- def get_mods(mods: int) -> list[Mod]:
41
- dic = mods_dic.copy()
42
- dic.pop("CL")
43
- dic.pop("NO")
44
- return [Mod(acronym=mod) for mod, bit in dic.items() if mods & bit] + [Mod(acronym="CL")]
45
-
46
-
47
- def get_mods_list(score_ls: list[UnifiedScore], mods: list[str]) -> list[int]:
48
- if not mods:
49
- return list(range(len(score_ls)))
50
- mods_index_ls = []
51
- for i, score in enumerate(score_ls):
52
- if score.mods and set(mods).issubset(j.acronym for j in score.mods):
53
- mods_index_ls.append(i)
54
- return mods_index_ls
55
-
56
-
57
- def calc_mods(mods: list[Mod]) -> int:
58
- num = 0
59
- for mod in mods:
60
- num ^= mods_dic.get(mod.acronym, 0)
61
- return num
1
+ from .schema.score import Mod, UnifiedScore
2
+
3
+ mods_dic = {
4
+ "CL": 0,
5
+ "NO": 0,
6
+ "NF": 1 << 0,
7
+ "EZ": 1 << 1,
8
+ "TD": 1 << 2,
9
+ "HD": 1 << 3,
10
+ "HR": 1 << 4,
11
+ "SD": 1 << 5,
12
+ "DT": 1 << 6,
13
+ "RX": 1 << 7,
14
+ "HT": 1 << 8,
15
+ "NC": 1 << 9,
16
+ "FL": 1 << 10,
17
+ "AT": 1 << 11,
18
+ "SO": 1 << 12,
19
+ "RX2": 1 << 13,
20
+ "PF": 1 << 14,
21
+ "4K": 1 << 15,
22
+ "5K": 1 << 16,
23
+ "6K": 1 << 17,
24
+ "7K": 1 << 18,
25
+ "8K": 1 << 19,
26
+ "FI": 1 << 20,
27
+ "RD": 1 << 21,
28
+ "Cinema": 1 << 22,
29
+ "TG": 1 << 23,
30
+ "9K": 1 << 24,
31
+ "KC": 1 << 25,
32
+ "1K": 1 << 26,
33
+ "3K": 1 << 27,
34
+ "2K": 1 << 28,
35
+ "V2": 1 << 29,
36
+ "MR": 1 << 30,
37
+ }
38
+
39
+
40
+ def get_mods(mods: int) -> list[Mod]:
41
+ # Avoid copying the dictionary by iterating directly and filtering
42
+ result = [Mod(acronym=mod) for mod, bit in mods_dic.items() if mod not in ("CL", "NO") and mods & bit]
43
+ return result + [Mod(acronym="CL")]
44
+
45
+
46
+ def get_mods_list(score_ls: list[UnifiedScore], mods: list[str]) -> list[int]:
47
+ if not mods:
48
+ return list(range(len(score_ls)))
49
+ # Optimize: create the set once instead of on every iteration
50
+ mods_set = set(mods)
51
+ mods_index_ls = []
52
+ for i, score in enumerate(score_ls):
53
+ if score.mods and mods_set.issubset(j.acronym for j in score.mods):
54
+ mods_index_ls.append(i)
55
+ return mods_index_ls
56
+
57
+
58
+ def calc_mods(mods: list[Mod]) -> int:
59
+ num = 0
60
+ for mod in mods:
61
+ num ^= mods_dic.get(mod.acronym, 0)
62
+ return num
@@ -25,6 +25,6 @@ async def get_first_response(urls: list[str]):
25
25
  for task in done:
26
26
  response = task.result()
27
27
  if response is not None and response.status_code == 200:
28
- return response.content
28
+ return response
29
29
  tasks = [task for task in tasks if not task.done()]
30
30
  return None
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -3,6 +3,7 @@ import importlib.metadata
3
3
 
4
4
  from rosu_pp_py import Beatmap, Strains, GameMode, Performance, PerformanceAttributes
5
5
 
6
+ from .exceptions import NetworkError
6
7
  from .schema.score import UnifiedScore
7
8
 
8
9
  is_v2 = importlib.metadata.version("pydantic").startswith("2")
@@ -10,6 +11,8 @@ is_v2 = importlib.metadata.version("pydantic").startswith("2")
10
11
 
11
12
  def cal_pp(score: UnifiedScore, path: str, is_lazer: bool) -> PerformanceAttributes:
12
13
  beatmap = Beatmap(path=path)
14
+ if beatmap.is_suspicious():
15
+ raise NetworkError("这似乎不是一个正常谱面 OAO")
13
16
  convert_mode(score, beatmap)
14
17
  c = Performance(
15
18
  accuracy=score.accuracy,
@@ -31,6 +34,8 @@ def cal_pp(score: UnifiedScore, path: str, is_lazer: bool) -> PerformanceAttribu
31
34
 
32
35
  def get_if_pp_ss_pp(score: UnifiedScore, path: str, is_lazer: bool) -> tuple:
33
36
  beatmap = Beatmap(path=path)
37
+ if beatmap.is_suspicious():
38
+ return "nan", "nan"
34
39
  convert_mode(score, beatmap)
35
40
  total = beatmap.n_objects
36
41
  passed = score.statistics.great + score.statistics.miss + score.statistics.ok + score.statistics.meh
@@ -62,6 +67,8 @@ def get_if_pp_ss_pp(score: UnifiedScore, path: str, is_lazer: bool) -> tuple:
62
67
 
63
68
  def get_ss_pp(path: str, mods: int, is_lazer) -> PerformanceAttributes:
64
69
  beatmap = Beatmap(path=path)
70
+ if beatmap.is_suspicious():
71
+ raise NetworkError("这似乎不是一个正常谱面 OAO")
65
72
  c = Performance(accuracy=100, mods=mods, lazer=is_lazer)
66
73
  ss_pp_info = c.calculate(beatmap)
67
74
  return ss_pp_info
@@ -1,7 +1,6 @@
1
1
  from .user import User, Badge
2
2
  from .match import Game, Match
3
3
  from .alphaosu import RecommendData
4
- from .sayo_beatmap import SayoBeatmap
5
4
  from .score import Score, NewScore, BeatmapUserScore
6
5
  from .beatmap import Beatmap, Beatmapset, SeasonalBackgrounds
7
6
 
@@ -14,7 +13,6 @@ __all__ = [
14
13
  "Beatmap",
15
14
  "Beatmapset",
16
15
  "SeasonalBackgrounds",
17
- "SayoBeatmap",
18
16
  "RecommendData",
19
17
  "Match",
20
18
  "Game",
@@ -0,0 +1,42 @@
1
+ from typing import Optional
2
+ from .basemodel import Base
3
+
4
+
5
+ class BidData(Base):
6
+ id: int
7
+ mode_int: int
8
+ version: str
9
+ total_length: int
10
+ cs: float
11
+ ar: float
12
+ accuracy: float
13
+ drain: float
14
+ difficulty_rating: float
15
+ count_circles: int
16
+ count_sliders: int
17
+ count_spinners: int
18
+ max_combo: Optional[int] = None
19
+ playcount: int
20
+ passcount: int
21
+
22
+
23
+ class BeatmapSets(Base):
24
+ id: int
25
+ ranked: int
26
+ title: str
27
+ artist: str
28
+ title_unicode: str
29
+ artist_unicode: str
30
+ creator: str
31
+ user_id: int
32
+ source: str
33
+ last_updated: str
34
+ ranked_date: Optional[str]
35
+ bpm: float
36
+ favourite_count: int
37
+ video: bool
38
+ storyboard: bool
39
+ tags: str
40
+ language_id: int
41
+ genre_id: int
42
+ beatmaps: list[BidData]
@@ -0,0 +1,54 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from .user import Badge, Level, GradeCounts
6
+
7
+
8
+ class Statistics(BaseModel):
9
+ """用户统计数据"""
10
+
11
+ level: Level
12
+ global_rank: Optional[int] = None # 全球排名
13
+ global_rank_percent: Optional[float] = None # 全球排名百分比
14
+ country_rank: Optional[int] = None # 国家/地区排名
15
+ pp: float
16
+ grade_counts: GradeCounts
17
+ ranked_score: int
18
+ hit_accuracy: float
19
+ play_count: int
20
+ total_score: int
21
+ total_hits: int
22
+ play_time: int
23
+ maximum_combo: int
24
+ replays_watched_by_others: int
25
+
26
+
27
+ class Team(BaseModel):
28
+ """用户战队信息(为绘图用途专门创建的模型,若无数据则为 null)"""
29
+
30
+ name: str
31
+ flag_url: str
32
+
33
+ # 启用 extra="allow" 可以允许接收更多未定义的字段
34
+ class Config:
35
+ extra = "allow"
36
+
37
+
38
+ class DrawUser(BaseModel):
39
+ """Osu! 用户信息根模型"""
40
+
41
+ id: int
42
+ username: str
43
+ country_code: str # e.g., "AU"
44
+ team: Optional[Team] = None
45
+ footer: str # e.g., "2025/11/07 14:11:45 | 数据对比于4天前"
46
+ mode: str # e.g., "STD"
47
+ badges: Optional[list[Badge]] = None
48
+ statistics: Optional[Statistics] = None
49
+ rank_change: Optional[str] = None
50
+ country_rank_change: Optional[str] = None
51
+ pp_change: Optional[str] = None
52
+ acc_change: Optional[str] = None
53
+ pc_change: Optional[str] = None
54
+ hits_change: Optional[str] = None
@@ -114,6 +114,7 @@ class UnifiedBeatmap(Base):
114
114
  hp: float
115
115
  stars: float
116
116
  user_id: Optional[int] = None
117
+ convert: Optional[bool] = False
117
118
 
118
119
 
119
120
  class UnifiedScore(Base):
@@ -129,3 +130,4 @@ class UnifiedScore(Base):
129
130
  beatmap: Optional[UnifiedBeatmap] = None
130
131
  passed: bool
131
132
  pp: Optional[float] = None
133
+ beatmapset: Optional[Beatmapset] = None
@@ -48,6 +48,7 @@ class UserStatistics(Base):
48
48
  country_rank: Optional[int] = None
49
49
  badges: Optional[list[Badge]] = None
50
50
  variants: Optional[list[Variant]] = None
51
+ global_rank_percent: Optional[float] = None
51
52
 
52
53
 
53
54
  class UserCompact(Base):
@@ -1,26 +1,28 @@
1
- Metadata-Version: 2.1
2
- Name: nonebot-plugin-osubot
3
- Version: 6.22.1
1
+ Metadata-Version: 2.3
2
+ Name: nonebot_plugin_osubot
3
+ Version: 6.26.4
4
4
  Summary: OSUbot in NoneBot2
5
- License: AGPL-3.0
5
+ Author: yaowan233
6
6
  Author-email: yaowan233 <572473053@qq.com>
7
- Requires-Python: >=3.9.8,<3.13
7
+ License: AGPL-3.0
8
+ Requires-Dist: nonebot2>=2.3.0
9
+ Requires-Dist: pydantic>=1.10.0,!=2.5.0,!=2.5.1,<3.0.0
10
+ Requires-Dist: nonebot-plugin-alconna>=0.46.4
11
+ Requires-Dist: nonebot-plugin-session~=0.3
12
+ Requires-Dist: pillow>=9.2.0
8
13
  Requires-Dist: expiringdict>=1.2.2
14
+ Requires-Dist: nonebot-plugin-apscheduler>=0.4.0
15
+ Requires-Dist: tortoise-orm>=0.20.0
16
+ Requires-Dist: nonebot-plugin-tortoise-orm>=0.1.4
17
+ Requires-Dist: rosu-pp-py==3.1.0
18
+ Requires-Dist: reamber>=0.2.1
9
19
  Requires-Dist: httpx>=0.23.3
20
+ Requires-Dist: typing-extensions>=4.11.0
10
21
  Requires-Dist: matplotlib>=3.7.1
11
- Requires-Dist: nonebot-plugin-alconna>=0.46.4
12
- Requires-Dist: nonebot-plugin-apscheduler>=0.4.0
13
22
  Requires-Dist: nonebot-plugin-htmlrender>=0.3.1
14
- Requires-Dist: nonebot-plugin-session~=0.3
15
- Requires-Dist: nonebot-plugin-tortoise-orm>=0.1.4
16
23
  Requires-Dist: nonebot-plugin-waiter>=0.6.1
17
- Requires-Dist: nonebot2>=2.3.0
18
- Requires-Dist: pillow>=9.2.0
19
- Requires-Dist: pydantic!=2.5.0,!=2.5.1,<3.0.0,>=1.10.0
20
- Requires-Dist: reamber>=0.2.1
21
- Requires-Dist: rosu-pp-py==3.0.0
22
- Requires-Dist: tortoise-orm>=0.20.0
23
- Requires-Dist: typing_extensions>=4.11.0
24
+ Requires-Dist: nonebot-adapter-onebot>=2.4.6
25
+ Requires-Python: >=3.9.8, <3.13
24
26
  Project-URL: Homepage, https://github.com/yaowan233/nonebot-plugin-osubot
25
27
  Project-URL: Repository, https://github.com/yaowan233/nonebot-plugin-osubot
26
28
  Description-Content-Type: text/markdown
@@ -130,4 +132,3 @@ _✨ NoneBot osubot ✨_
130
132
  如果遇到任何问题,欢迎提各种issue来反馈bug
131
133
  你也可以加群(228986744)来进行反馈!
132
134
  ![1665504476458_temp_qrcode_share_9993](https://user-images.githubusercontent.com/30517062/195143643-5c212f4e-5ee2-49fd-8e71-4f360eef2d46.png)
133
-