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
@@ -26,21 +26,77 @@ from ..exceptions import NetworkError
26
26
  from ..database.models import UserData
27
27
  from ..mania import generate_preview_pic
28
28
  from ..api import safe_async_get, get_user_scores
29
- from ..file import map_path, download_tmp_osu
29
+ from ..file import map_path, download_osu
30
30
  from ..draw.catch_preview import draw_cath_preview
31
31
 
32
- games: dict[str, NewScore] = {}
33
- pic_games: dict[str, NewScore] = {}
34
- chart_games: dict[str, NewScore] = {}
35
- timers: dict[str, TimerHandle] = {}
36
- pic_timers: dict[str, TimerHandle] = {}
37
- chart_timers: dict[str, TimerHandle] = {}
38
- hint_dic = {"pic": False, "artist": False, "creator": False}
39
- pic_hint_dic = {"artist": False, "creator": False, "audio": False}
40
- chart_hint_dic = {"pic": False, "artist": False, "creator": False, "audio": False}
41
- group_hint = {}
42
- pic_group_hint = {}
43
- chart_group_hint = {}
32
+
33
+ class GameType:
34
+ AUDIO = "audio"
35
+ PIC = "pic"
36
+ CHART = "chart"
37
+
38
+
39
+ class GameManager:
40
+ def __init__(self):
41
+ self.games: dict[str, dict[str, NewScore]] = {GameType.AUDIO: {}, GameType.PIC: {}, GameType.CHART: {}}
42
+ self.timers: dict[str, dict[str, TimerHandle]] = {GameType.AUDIO: {}, GameType.PIC: {}, GameType.CHART: {}}
43
+ self.hint_templates = {
44
+ GameType.AUDIO: {"pic": False, "artist": False, "creator": False},
45
+ GameType.PIC: {"artist": False, "creator": False, "audio": False},
46
+ GameType.CHART: {"pic": False, "artist": False, "creator": False, "audio": False},
47
+ }
48
+ self.group_hints: dict[str, dict[str, dict[str, bool]]] = {
49
+ GameType.AUDIO: {},
50
+ GameType.PIC: {},
51
+ GameType.CHART: {},
52
+ }
53
+
54
+ def get_game(self, game_type: str, session_id: str):
55
+ return self.games[game_type].get(session_id)
56
+
57
+ def set_game(self, game_type: str, session_id: str, score: NewScore):
58
+ self.games[game_type][session_id] = score
59
+
60
+ def pop_game(self, game_type: str, session_id: str):
61
+ return self.games[game_type].pop(session_id, None)
62
+
63
+ def get_timer(self, game_type: str, session_id: str):
64
+ return self.timers[game_type].get(session_id)
65
+
66
+ def set_timer(self, game_type: str, session_id: str, timer: TimerHandle):
67
+ self.timers[game_type][session_id] = timer
68
+
69
+ def pop_timer(self, game_type: str, session_id: str):
70
+ return self.timers[game_type].pop(session_id, None)
71
+
72
+ def get_hint(self, game_type: str, session_id: str):
73
+ return self.group_hints[game_type].get(session_id)
74
+
75
+ def init_hint(self, game_type: str, session_id: str):
76
+ self.group_hints[game_type][session_id] = self.hint_templates[game_type].copy()
77
+
78
+ def reset_hint(self, game_type: str, session_id: str):
79
+ self.group_hints[game_type][session_id] = None
80
+
81
+ def is_game_running(self, game_type: str, session_id: str) -> bool:
82
+ return bool(self.games[game_type].get(session_id))
83
+
84
+
85
+ game_manager = GameManager()
86
+
87
+ # Keep old references for backward compatibility during transition
88
+ games = game_manager.games[GameType.AUDIO]
89
+ pic_games = game_manager.games[GameType.PIC]
90
+ chart_games = game_manager.games[GameType.CHART]
91
+ timers = game_manager.timers[GameType.AUDIO]
92
+ pic_timers = game_manager.timers[GameType.PIC]
93
+ chart_timers = game_manager.timers[GameType.CHART]
94
+ hint_dic = game_manager.hint_templates[GameType.AUDIO]
95
+ pic_hint_dic = game_manager.hint_templates[GameType.PIC]
96
+ chart_hint_dic = game_manager.hint_templates[GameType.CHART]
97
+ group_hint = game_manager.group_hints[GameType.AUDIO]
98
+ pic_group_hint = game_manager.group_hints[GameType.PIC]
99
+ chart_group_hint = game_manager.group_hints[GameType.CHART]
44
100
  guess_audio = on_command("音频猜歌", priority=11, block=True)
45
101
  guess_chart = on_command("谱面猜歌", priority=11, block=True)
46
102
  guess_song_cache = ExpiringDict(1000, 60 * 60 * 24)
@@ -71,62 +127,86 @@ async def get_random_beatmap_set(binded_id, group_id) -> (UnifiedScore, str):
71
127
  return selected_score, osu_name
72
128
 
73
129
 
74
- @guess_audio.handle(parameterless=[split_msg()])
75
- async def _(
76
- state: T_State,
77
- matcher: Matcher,
78
- msg: UniMsg,
79
- session_id: str = SessionId(SessionIdType.GROUP),
80
- ):
81
- if "error" in state:
82
- mode = str(random.randint(0, 3))
83
- await UniMessage.text("由于未绑定OSU账号,本次随机挑选模式进行猜歌\n" + state["error"]).send(reply_to=True)
84
- else:
85
- mode = state["mode"]
86
- group_id = session_id
130
+ async def select_score_from_user(state: T_State, msg: UniMsg, session_id: str):
131
+ """
132
+ Common logic for selecting a score from a specific user or randomly.
133
+ Returns (selected_score, selected_user) or (None, error_msg).
134
+ """
135
+ mode = state.get("mode", str(random.randint(0, 3)))
87
136
  binded_id = await UserData.filter(osu_mode=mode).values_list("user_id", flat=True)
88
- if not binded_id:
89
- await UniMessage.text("还没有人绑定该模式的osu账号呢,绑定了再来试试吧").finish(reply_to=True)
90
- if not guess_song_cache.get(group_id):
91
- guess_song_cache[group_id] = set()
137
+
138
+ if not guess_song_cache.get(session_id):
139
+ guess_song_cache[session_id] = set()
140
+
141
+ # Handle @mention
92
142
  if msg.has(At):
93
143
  qq = msg.get(At)[0].target
94
- user_data = await UserData.get_or_none(user_id=qq)
144
+ user_data = await UserData.get_or_none(user_id=int(qq))
95
145
  if not user_data:
96
- await UniMessage.text("该用户未绑定osu账号").finish(reply_to=True)
146
+ return None, "该用户未绑定osu账号"
97
147
  try:
98
148
  bp_ls = await get_user_scores(user_data.osu_id, NGM[state["mode"]], "best")
99
149
  except NetworkError as e:
100
- await UniMessage.text(f"在查找用户:{state['username']} {NGM[state['mode']]}模式bp时 {str(e)}").finish(
101
- reply_to=True
102
- )
103
- filtered_bp_ls = [i for i in bp_ls if i.beatmapset.id not in guess_song_cache[group_id]]
150
+ return None, f"在查找用户:{state['username']} {NGM[state['mode']]}模式bp时 {str(e)}"
151
+
152
+ filtered_bp_ls = [i for i in bp_ls if i.beatmapset.id not in guess_song_cache[session_id]]
104
153
  if not filtered_bp_ls:
105
- await UniMessage.text(state["username"] + "的bp已经被你们猜过一遍了 -_-").finish(reply_to=True)
154
+ return None, f"{state['username']}的bp已经被你们猜过一遍了 -_-"
155
+
106
156
  selected_score = random.choice(filtered_bp_ls)
107
- guess_song_cache[group_id].add(selected_score.beatmapset.id)
108
- selected_user = user_data.osu_name
109
- elif state["user"]:
157
+ guess_song_cache[session_id].add(selected_score.beatmapset.id)
158
+ return selected_score, user_data.osu_name
159
+
160
+ # Handle specific user from state
161
+ elif state.get("user"):
110
162
  try:
111
163
  bp_ls = await get_user_scores(state["user"], NGM[state["mode"]], "best")
112
164
  except NetworkError as e:
113
- await UniMessage.text(f"在查找用户:{state['username']} {NGM[state['mode']]}模式bp时 {str(e)}").finish(
114
- reply_to=True
115
- )
116
- filtered_bp_ls = [i for i in bp_ls if i.beatmapset.id not in guess_song_cache[group_id]]
165
+ return None, f"在查找用户:{state['username']} {NGM[state['mode']]}模式bp时 {str(e)}"
166
+
167
+ filtered_bp_ls = [i for i in bp_ls if i.beatmapset.id not in guess_song_cache[session_id]]
117
168
  if not filtered_bp_ls:
118
- await UniMessage.text(state["username"] + "的bp已经被你们猜过一遍了 -_-").finish(reply_to=True)
169
+ return None, f"{state['username']}的bp已经被你们猜过一遍了 -_-"
170
+
119
171
  selected_score = random.choice(filtered_bp_ls)
120
- guess_song_cache[group_id].add(selected_score.beatmapset.id)
121
- selected_user = state["username"]
172
+ guess_song_cache[session_id].add(selected_score.beatmapset.id)
173
+ return selected_score, state["username"]
174
+
175
+ # Random selection
122
176
  else:
123
- selected_score, selected_user = await get_random_beatmap_set(binded_id, group_id)
177
+ return await get_random_beatmap_set(binded_id, session_id)
178
+
179
+
180
+ @guess_audio.handle(parameterless=[split_msg()])
181
+ async def _(
182
+ state: T_State,
183
+ matcher: Matcher,
184
+ msg: UniMsg,
185
+ session_id: str = SessionId(SessionIdType.GROUP),
186
+ ):
187
+ if "error" in state:
188
+ mode = str(random.randint(0, 3))
189
+ await UniMessage.text("由于未绑定OSU账号,本次随机挑选模式进行猜歌\n" + state["error"]).send(reply_to=True)
190
+ else:
191
+ mode = state["mode"]
192
+
193
+ binded_id = await UserData.filter(osu_mode=mode).values_list("user_id", flat=True)
194
+ if not binded_id:
195
+ await UniMessage.text("还没有人绑定该模式的osu账号呢,绑定了再来试试吧").finish(reply_to=True)
196
+
197
+ result = await select_score_from_user(state, msg, session_id)
198
+ if result[0] is None:
199
+ await UniMessage.text(result[1]).finish(reply_to=True)
200
+ selected_score, selected_user = result
201
+
124
202
  if not selected_score:
125
203
  await UniMessage.text("好像没有可以猜的歌了,今天的猜歌就到此结束吧!").finish(reply_to=True)
126
- if games.get(group_id, None):
204
+
205
+ if games.get(session_id, None):
127
206
  await UniMessage.text("现在还有进行中的猜歌呢,请等待当前猜歌结束").finish(reply_to=True)
128
- games[group_id] = selected_score
129
- set_timeout(matcher, group_id)
207
+
208
+ games[session_id] = selected_score
209
+ set_timeout(matcher, session_id)
130
210
  await UniMessage.text(
131
211
  f"开始音频猜歌游戏,猜猜下面音频的曲名吧,该曲抽选自{selected_user} {NGM[mode]} 模式的bp"
132
212
  ).send(reply_to=True)
@@ -140,67 +220,48 @@ async def _(
140
220
  await UniMessage.audio(raw=audio.read()).finish()
141
221
 
142
222
 
143
- async def stop_game(matcher: Matcher, cid: str):
144
- timers.pop(cid, None)
145
- if games.get(cid, None):
146
- game = games.pop(cid)
147
- if group_hint.get(cid, None):
148
- group_hint[cid] = None
223
+ async def stop_game_generic(matcher: Matcher, game_type: str, cid: str):
224
+ game_manager.pop_timer(game_type, cid)
225
+ game = game_manager.pop_game(game_type, cid)
226
+ if game:
227
+ game_manager.reset_hint(game_type, cid)
149
228
  msg = f"猜歌超时,游戏结束,正确答案是{game.beatmapset.title_unicode}"
150
229
  if game.beatmapset.title_unicode != game.beatmapset.title:
151
230
  msg += f" [{game.beatmapset.title}]"
152
231
  await matcher.send(msg)
153
232
 
154
233
 
234
+ async def stop_game(matcher: Matcher, cid: str):
235
+ await stop_game_generic(matcher, GameType.AUDIO, cid)
236
+
237
+
155
238
  async def pic_stop_game(matcher: Matcher, cid: str):
156
- pic_timers.pop(cid, None)
157
- if pic_games.get(cid, None):
158
- game = pic_games.pop(cid)
159
- if pic_group_hint.get(cid, None):
160
- pic_group_hint[cid] = None
161
- msg = f"猜歌超时,游戏结束,正确答案是{game.beatmapset.title_unicode}"
162
- if game.beatmapset.title_unicode != game.beatmapset.title:
163
- msg += f" [{game.beatmapset.title}]"
164
- await matcher.send(msg)
239
+ await stop_game_generic(matcher, GameType.PIC, cid)
165
240
 
166
241
 
167
242
  async def chart_stop_game(matcher: Matcher, cid: str):
168
- chart_timers.pop(cid, None)
169
- if chart_games.get(cid, None):
170
- game = chart_games.pop(cid)
171
- if chart_group_hint.get(cid, None):
172
- chart_group_hint[cid] = None
173
- msg = f"猜歌超时,游戏结束,正确答案是{game.beatmapset.title_unicode}"
174
- if game.beatmapset.title_unicode != game.beatmapset.title:
175
- msg += f" [{game.beatmapset.title}]"
176
- await matcher.send(msg)
243
+ await stop_game_generic(matcher, GameType.CHART, cid)
177
244
 
178
245
 
179
- def set_timeout(matcher: Matcher, cid: str, timeout: float = 300):
180
- timer = timers.get(cid, None)
246
+ def set_timeout_generic(matcher: Matcher, game_type: str, stop_func, cid: str, timeout: float = 300):
247
+ timer = game_manager.get_timer(game_type, cid)
181
248
  if timer:
182
249
  timer.cancel()
183
250
  loop = asyncio.get_running_loop()
184
- timer = loop.call_later(timeout, lambda: asyncio.ensure_future(stop_game(matcher, cid)))
185
- timers[cid] = timer
251
+ timer = loop.call_later(timeout, lambda: asyncio.ensure_future(stop_func(matcher, cid)))
252
+ game_manager.set_timer(game_type, cid, timer)
253
+
254
+
255
+ def set_timeout(matcher: Matcher, cid: str, timeout: float = 300):
256
+ set_timeout_generic(matcher, GameType.AUDIO, stop_game, cid, timeout)
186
257
 
187
258
 
188
259
  def pic_set_timeout(matcher: Matcher, cid: str, timeout: float = 300):
189
- timer = pic_timers.get(cid, None)
190
- if timer:
191
- timer.cancel()
192
- loop = asyncio.get_running_loop()
193
- timer = loop.call_later(timeout, lambda: asyncio.ensure_future(pic_stop_game(matcher, cid)))
194
- pic_timers[cid] = timer
260
+ set_timeout_generic(matcher, GameType.PIC, pic_stop_game, cid, timeout)
195
261
 
196
262
 
197
263
  def chart_set_timeout(matcher: Matcher, cid: str, timeout: float = 300):
198
- timer = chart_timers.get(cid, None)
199
- if timer:
200
- timer.cancel()
201
- loop = asyncio.get_running_loop()
202
- timer = loop.call_later(timeout, lambda: asyncio.ensure_future(chart_stop_game(matcher, cid)))
203
- chart_timers[cid] = timer
264
+ set_timeout_generic(matcher, GameType.CHART, chart_stop_game, cid, timeout)
204
265
 
205
266
 
206
267
  def game_running(session_id: str = SessionId(SessionIdType.GROUP)) -> bool:
@@ -215,6 +276,29 @@ def chart_game_running(session_id: str = SessionId(SessionIdType.GROUP)) -> bool
215
276
  return bool(chart_games.get(session_id, None))
216
277
 
217
278
 
279
+ def create_word_matcher_handler(game_type: str, games_dict: dict):
280
+ """Factory function to create word matcher handlers for different game types."""
281
+
282
+ async def handler(event: Event, session_id: str = SessionId(SessionIdType.GROUP)):
283
+ song_name = games_dict[session_id].beatmapset.title
284
+ song_name_unicode = games_dict[session_id].beatmapset.title_unicode
285
+ user_input = event.get_plaintext().lower()
286
+
287
+ # Calculate similarity ratios
288
+ r1 = SequenceMatcher(None, song_name.lower(), user_input).ratio()
289
+ r2 = SequenceMatcher(None, song_name_unicode.lower(), user_input).ratio()
290
+ r3 = SequenceMatcher(None, re.sub(r"[(\[].*[)\]]", "", song_name.lower()), user_input).ratio()
291
+ r4 = SequenceMatcher(None, re.sub(r"[(\[].*[)\]]", "", song_name_unicode.lower()), user_input).ratio()
292
+
293
+ if r1 >= 0.5 or r2 >= 0.5 or r3 >= 0.5 or r4 >= 0.5:
294
+ game_manager.pop_game(game_type, session_id)
295
+ game_manager.reset_hint(game_type, session_id)
296
+ msg = f"恭喜猜出正确答案为{song_name_unicode}"
297
+ await UniMessage.text(msg).send(reply_to=True)
298
+
299
+ return handler
300
+
301
+
218
302
  word_matcher = on_message(Rule(game_running), priority=12)
219
303
  pic_word_matcher = on_message(Rule(pic_game_running), priority=12)
220
304
  chart_word_matcher = on_message(Rule(chart_game_running), priority=12)
@@ -222,98 +306,48 @@ chart_word_matcher = on_message(Rule(chart_game_running), priority=12)
222
306
 
223
307
  @word_matcher.handle()
224
308
  async def _(event: Event, session_id: str = SessionId(SessionIdType.GROUP)):
225
- song_name = games[session_id].beatmapset.title
226
- song_name_unicode = games[session_id].beatmapset.title_unicode
227
- r1 = SequenceMatcher(None, song_name.lower(), event.get_plaintext().lower()).ratio()
228
- r2 = SequenceMatcher(None, song_name_unicode.lower(), event.get_plaintext().lower()).ratio()
229
- r3 = SequenceMatcher(
230
- None,
231
- re.sub(r"[(\[].*[)\]]", "", song_name.lower()),
232
- event.get_plaintext().lower(),
233
- ).ratio()
234
- r4 = SequenceMatcher(
235
- None,
236
- re.sub(r"[(\[].*[)\]]", "", song_name_unicode.lower()),
237
- event.get_plaintext().lower(),
238
- ).ratio()
239
- if r1 >= 0.5 or r2 >= 0.5 or r3 >= 0.5 or r4 >= 0.5:
240
- games.pop(session_id)
241
- if group_hint.get(session_id, None):
242
- group_hint[session_id] = None
243
- msg = f"恭喜猜出正确答案为{song_name_unicode}"
244
- await UniMessage.text(msg).send(reply_to=True)
309
+ await create_word_matcher_handler(GameType.AUDIO, games)(event, session_id)
245
310
 
246
311
 
247
312
  @pic_word_matcher.handle()
248
313
  async def _(event: Event, session_id: str = SessionId(SessionIdType.GROUP)):
249
- song_name = pic_games[session_id].beatmapset.title
250
- song_name_unicode = pic_games[session_id].beatmapset.title_unicode
251
- r1 = SequenceMatcher(None, song_name.lower(), event.get_plaintext().lower()).ratio()
252
- r2 = SequenceMatcher(None, song_name_unicode.lower(), event.get_plaintext().lower()).ratio()
253
- r3 = SequenceMatcher(
254
- None,
255
- re.sub(r"[(\[].*[)\]]", "", song_name.lower()),
256
- event.get_plaintext().lower(),
257
- ).ratio()
258
- r4 = SequenceMatcher(
259
- None,
260
- re.sub(r"[(\[].*[)\]]", "", song_name_unicode.lower()),
261
- event.get_plaintext().lower(),
262
- ).ratio()
263
- if r1 >= 0.5 or r2 >= 0.5 or r3 >= 0.5 or r4 >= 0.5:
264
- pic_games.pop(session_id)
265
- if pic_group_hint.get(session_id, None):
266
- pic_group_hint[session_id] = None
267
- msg = f"恭喜猜出正确答案为{song_name_unicode}"
268
- await UniMessage.text(msg).send(reply_to=True)
314
+ await create_word_matcher_handler(GameType.PIC, pic_games)(event, session_id)
269
315
 
270
316
 
271
- hint = on_command("音频提示", priority=11, block=True, rule=Rule(game_running))
317
+ @chart_word_matcher.handle()
318
+ async def _(event: Event, session_id: str = SessionId(SessionIdType.GROUP)):
319
+ await create_word_matcher_handler(GameType.CHART, chart_games)(event, session_id)
272
320
 
273
321
 
274
- @hint.handle()
275
- async def _(session_id: str = SessionId(SessionIdType.GROUP)):
276
- score = games[session_id]
277
- if not group_hint.get(session_id, None):
278
- group_hint[session_id] = hint_dic.copy()
279
- if all(group_hint[session_id].values()):
322
+ async def handle_hint_generic(game_type: str, games_dict: dict, session_id: str, matcher):
323
+ """Generic hint handler for all game types."""
324
+ score = games_dict[session_id]
325
+
326
+ if not game_manager.get_hint(game_type, session_id):
327
+ game_manager.init_hint(game_type, session_id)
328
+
329
+ hints = game_manager.get_hint(game_type, session_id)
330
+ if all(hints.values()):
280
331
  await UniMessage.text("已无更多提示,加油哦").finish(reply_to=True)
281
- true_keys = []
282
- for key, value in group_hint[session_id].items():
283
- if not value:
284
- true_keys.append(key)
285
- action = random.choice(true_keys)
332
+
333
+ # Get available hint types
334
+ available_hints = [key for key, value in hints.items() if not value]
335
+ action = random.choice(available_hints)
336
+
337
+ # Mark hint as used
338
+ hints[action] = True
339
+
340
+ # Provide hint based on action
286
341
  if action == "pic":
287
- group_hint[session_id]["pic"] = True
288
342
  await UniMessage.image(url=score.beatmapset.covers.cover).finish(reply_to=True)
289
- if action == "artist":
290
- group_hint[session_id]["artist"] = True
343
+ elif action == "artist":
291
344
  msg = f"曲师为:{score.beatmapset.artist_unicode}"
292
345
  if score.beatmapset.artist_unicode != score.beatmapset.artist:
293
346
  msg += f" [{score.beatmapset.artist}]"
294
- await UniMessage.text(msg).finish(reply_to=True)
295
- if action == "creator":
296
- group_hint[session_id]["creator"] = True
297
- await UniMessage.text(f"谱师为:{score.beatmapset.creator}").finish(reply_to=True)
298
-
299
-
300
- pic_hint = on_command("图片提示", priority=11, block=True, rule=Rule(pic_game_running))
301
-
302
-
303
- @pic_hint.handle()
304
- async def _(session_id: str = SessionId(SessionIdType.GROUP)):
305
- score = pic_games[session_id]
306
- if not pic_group_hint.get(session_id, None):
307
- pic_group_hint[session_id] = pic_hint_dic.copy()
308
- if all(pic_group_hint[session_id].values()):
309
- await pic_hint.finish("已无更多提示,加油哦")
310
- true_keys = []
311
- for key, value in pic_group_hint[session_id].items():
312
- if not value:
313
- true_keys.append(key)
314
- action = random.choice(true_keys)
315
- if action == "audio":
316
- pic_group_hint[session_id]["audio"] = True
347
+ await matcher.finish(msg)
348
+ elif action == "creator":
349
+ await matcher.finish(f"谱师为:{score.beatmapset.creator}")
350
+ elif action == "audio":
317
351
  path = map_path / f"{score.beatmapset.id}"
318
352
  if not path.exists():
319
353
  path.mkdir(parents=True, exist_ok=True)
@@ -321,15 +355,30 @@ async def _(session_id: str = SessionId(SessionIdType.GROUP)):
321
355
  if not audio:
322
356
  await UniMessage.text("音频下载失败了 qaq ").finish(reply_to=True)
323
357
  await UniMessage.audio(raw=audio.read()).finish()
324
- if action == "artist":
325
- pic_group_hint[session_id]["artist"] = True
326
- msg = f"曲师为:{score.beatmapset.artist_unicode}"
327
- if score.beatmapset.artist_unicode != score.beatmapset.artist:
328
- msg += f" [{score.beatmapset.artist}]"
329
- await pic_hint.finish(msg)
330
- if action == "creator":
331
- pic_group_hint[session_id]["creator"] = True
332
- await pic_hint.finish(f"谱师为:{score.beatmapset.creator}")
358
+
359
+
360
+ hint = on_command("音频提示", priority=11, block=True, rule=Rule(game_running))
361
+
362
+
363
+ @hint.handle()
364
+ async def _(session_id: str = SessionId(SessionIdType.GROUP)):
365
+ await handle_hint_generic(GameType.AUDIO, games, session_id, hint)
366
+
367
+
368
+ pic_hint = on_command("图片提示", priority=11, block=True, rule=Rule(pic_game_running))
369
+
370
+
371
+ @pic_hint.handle()
372
+ async def _(session_id: str = SessionId(SessionIdType.GROUP)):
373
+ await handle_hint_generic(GameType.PIC, pic_games, session_id, pic_hint)
374
+
375
+
376
+ chart_hint = on_command("谱面提示", priority=11, block=True, rule=Rule(chart_game_running))
377
+
378
+
379
+ @chart_hint.handle()
380
+ async def _(session_id: str = SessionId(SessionIdType.GROUP)):
381
+ await handle_hint_generic(GameType.CHART, chart_games, session_id, chart_hint)
333
382
 
334
383
 
335
384
  guess_pic = on_command("图片猜歌", priority=11, block=True)
@@ -347,47 +396,22 @@ async def _(
347
396
  await UniMessage.text("由于未绑定OSU账号,本次随机选择模式进行猜歌\n" + state["error"]).send(reply_to=True)
348
397
  else:
349
398
  mode = state["mode"]
399
+
350
400
  binded_id = await UserData.filter(osu_mode=mode).values_list("user_id", flat=True)
351
401
  if not binded_id:
352
402
  await guess_pic.finish("还没有人绑定该模式的osu账号呢,绑定了再来试试吧")
353
- if not guess_song_cache.get(session_id):
354
- guess_song_cache[session_id] = set()
355
- if msg.has(At):
356
- qq = msg.get(At)[0].target
357
- user_data = await UserData.get_or_none(user_id=int(qq))
358
- if not user_data:
359
- await UniMessage.text("该用户未绑定osu账号").finish(reply_to=True)
360
- try:
361
- bp_ls = await get_user_scores(user_data.osu_id, NGM[state["mode"]], "best")
362
- except NetworkError as e:
363
- await UniMessage.text(f"在查找用户:{state['username']} {NGM[state['mode']]}模式bp时 {str(e)}").finish(
364
- reply_to=True
365
- )
366
- filtered_bp_ls = [i for i in bp_ls if i.beatmapset.id not in guess_song_cache[session_id]]
367
- if not filtered_bp_ls:
368
- await UniMessage.text(state["username"] + "的bp已经被你们猜过一遍了 -_-").finish(reply_to=True)
369
- selected_score = random.choice(filtered_bp_ls)
370
- guess_song_cache[session_id].add(selected_score.beatmapset.id)
371
- selected_user = user_data.osu_name
372
- elif state["user"]:
373
- try:
374
- bp_ls = await get_user_scores(state["user"], NGM[state["mode"]], "best")
375
- except NetworkError as e:
376
- await UniMessage.text(f"在查找用户:{state['username']} {NGM[state['mode']]}模式bp时 {str(e)}").finish(
377
- reply_to=True
378
- )
379
- filtered_bp_ls = [i for i in bp_ls if i.beatmapset.id not in guess_song_cache[session_id]]
380
- if not filtered_bp_ls:
381
- await UniMessage.text(state["username"] + "的bp已经被你们猜过一遍了 -_-").finish(reply_to=True)
382
- selected_score = random.choice(filtered_bp_ls)
383
- guess_song_cache[session_id].add(selected_score.beatmapset.id)
384
- selected_user = state["username"]
385
- else:
386
- selected_score, selected_user = await get_random_beatmap_set(binded_id, session_id)
403
+
404
+ result = await select_score_from_user(state, msg, session_id)
405
+ if result[0] is None:
406
+ await UniMessage.text(result[1]).finish(reply_to=True)
407
+ selected_score, selected_user = result
408
+
387
409
  if not selected_score:
388
410
  await guess_pic.finish("好像没有可以猜的歌了,今天的猜歌就到此结束吧!")
411
+
389
412
  if pic_games.get(session_id, None):
390
413
  await guess_pic.finish("现在还有进行中的猜歌呢,请等待当前猜歌结束")
414
+
391
415
  pic_games[session_id] = selected_score
392
416
  pic_set_timeout(matcher, session_id)
393
417
  try:
@@ -424,56 +448,32 @@ async def _(
424
448
  await UniMessage.text("由于未绑定OSU账号,本次随机选择模式进行猜歌\n" + state["error"]).send(reply_to=True)
425
449
  else:
426
450
  mode = state["mode"]
451
+
427
452
  if mode == "0":
428
453
  await UniMessage.text("该模式暂不支持猜歌").finish(reply_to=True)
454
+
429
455
  binded_id = await UserData.filter(osu_mode=mode).values_list("user_id", flat=True)
430
456
  if not binded_id:
431
457
  await guess_pic.finish("还没有人绑定该模式的osu账号呢,绑定了再来试试吧")
432
- if not guess_song_cache.get(session_id):
433
- guess_song_cache[session_id] = set()
434
- if msg.has(At):
435
- qq = msg.get(At)[0].target
436
- user_data = await UserData.get_or_none(user_id=int(qq))
437
- if not user_data:
438
- await UniMessage.text("该用户未绑定osu账号").finish(reply_to=True)
439
- try:
440
- bp_ls = await get_user_scores(user_data.osu_id, NGM[state["mode"]], "best")
441
- except NetworkError as e:
442
- await UniMessage.text(f"在查找用户:{state['username']} {NGM[state['mode']]}模式bp时 {str(e)}").finish(
443
- reply_to=True
444
- )
445
- filtered_bp_ls = [i for i in bp_ls if i.beatmapset.id not in guess_song_cache[session_id]]
446
- if not filtered_bp_ls:
447
- await UniMessage.text(state["username"] + "的bp已经被你们猜过一遍了 -_-").finish(reply_to=True)
448
- selected_score = random.choice(filtered_bp_ls)
449
- selected_user = user_data.osu_name
450
- guess_song_cache[session_id].add(selected_score.beatmapset.id)
451
- elif state["user"]:
452
- try:
453
- bp_ls = await get_user_scores(state["user"], NGM[state["mode"]], "best")
454
- except NetworkError as e:
455
- await UniMessage.text(f"在查找用户:{state['username']} {NGM[state['mode']]}模式bp时 {str(e)}").finish(
456
- reply_to=True
457
- )
458
- filtered_bp_ls = [i for i in bp_ls if i.beatmapset.id not in guess_song_cache[session_id]]
459
- if not filtered_bp_ls:
460
- await UniMessage.text(state["username"] + "的bp已经被你们猜过一遍了 -_-").finish(reply_to=True)
461
- selected_score = random.choice(filtered_bp_ls)
462
- selected_user = state["username"]
463
- guess_song_cache[session_id].add(selected_score.beatmapset.id)
464
- else:
465
- selected_score, selected_user = await get_random_beatmap_set(binded_id, session_id)
458
+
459
+ result = await select_score_from_user(state, msg, session_id)
460
+ if result[0] is None:
461
+ await UniMessage.text(result[1]).finish(reply_to=True)
462
+ selected_score, selected_user = result
463
+
466
464
  if not selected_score:
467
465
  await guess_pic.finish("好像没有可以猜的歌了,今天的猜歌就到此结束吧!")
466
+
468
467
  if chart_games.get(session_id, None):
469
468
  await guess_pic.finish("现在还有进行中的猜歌呢,请等待当前猜歌结束")
469
+
470
470
  chart_games[session_id] = selected_score
471
471
  chart_set_timeout(matcher, session_id)
472
472
  if mode == "3":
473
- osu = await download_tmp_osu(selected_score.beatmap.id)
473
+ osu = await download_osu(selected_score.beatmapset.id, selected_score.beatmap.id)
474
474
  pic = await generate_preview_pic(osu)
475
475
  elif mode == "1":
476
- osu = await download_tmp_osu(selected_score.beatmap.id)
476
+ osu = await download_osu(selected_score.beatmapset.id, selected_score.beatmap.id)
477
477
  beatmap = parse_map(osu)
478
478
  pic = map_to_image(beatmap)
479
479
  else:
@@ -487,21 +487,7 @@ async def _(
487
487
 
488
488
  @chart_word_matcher.handle()
489
489
  async def _(event: Event, session_id: str = SessionId(SessionIdType.GROUP)):
490
- song_name = chart_games[session_id].beatmapset.title
491
- song_name_unicode = chart_games[session_id].beatmapset.title_unicode
492
- r1 = SequenceMatcher(None, song_name.lower(), event.get_plaintext().lower()).ratio()
493
- r2 = SequenceMatcher(None, song_name_unicode.lower(), event.get_plaintext().lower()).ratio()
494
- r3 = SequenceMatcher(
495
- None,
496
- re.sub(r"[(\[].*[)\]]", "", song_name.lower()),
497
- event.get_plaintext().lower(),
498
- ).ratio()
499
- if r1 >= 0.5 or r2 >= 0.5 or r3 >= 0.5:
500
- chart_games.pop(session_id)
501
- if chart_group_hint.get(session_id, None):
502
- chart_group_hint[session_id] = None
503
- msg = f"恭喜猜出正确答案为{song_name_unicode}"
504
- await UniMessage.text(msg).send(reply_to=True)
490
+ await create_word_matcher_handler(GameType.CHART, chart_games)(event, session_id)
505
491
 
506
492
 
507
493
  chart_hint = on_command("谱面提示", priority=11, block=True, rule=Rule(chart_game_running))
@@ -509,34 +495,4 @@ chart_hint = on_command("谱面提示", priority=11, block=True, rule=Rule(chart
509
495
 
510
496
  @chart_hint.handle()
511
497
  async def _(session_id: str = SessionId(SessionIdType.GROUP)):
512
- score = chart_games[session_id]
513
- if not chart_group_hint.get(session_id, None):
514
- chart_group_hint[session_id] = chart_hint_dic.copy()
515
- if all(chart_group_hint[session_id].values()):
516
- await chart_hint.finish("已无更多提示,加油哦")
517
- true_keys = []
518
- for key, value in chart_group_hint[session_id].items():
519
- if not value:
520
- true_keys.append(key)
521
- action = random.choice(true_keys)
522
- if action == "audio":
523
- chart_group_hint[session_id]["audio"] = True
524
- path = map_path / f"{score.beatmapset.id}"
525
- if not path.exists():
526
- path.mkdir(parents=True, exist_ok=True)
527
- audio = await safe_async_get(f"https://b.ppy.sh/preview/{score.beatmapset.id}.mp3")
528
- if not audio:
529
- await UniMessage.text("音频下载失败了 qaq ").finish(reply_to=True)
530
- await UniMessage.audio(raw=audio.read()).finish()
531
- if action == "artist":
532
- chart_group_hint[session_id]["artist"] = True
533
- msg = f"曲师为:{score.beatmapset.artist_unicode}"
534
- if score.beatmapset.artist_unicode != score.beatmapset.artist:
535
- msg += f" [{score.beatmapset.artist}]"
536
- await chart_hint.finish(msg)
537
- if action == "creator":
538
- chart_group_hint[session_id]["creator"] = True
539
- await chart_hint.finish(f"谱师为:{score.beatmapset.creator}")
540
- if action == "pic":
541
- chart_group_hint[session_id]["pic"] = True
542
- await UniMessage.image(url=score.beatmapset.covers.cover).finish(reply_to=True)
498
+ await handle_hint_generic(GameType.CHART, chart_games, session_id, chart_hint)