forked from sass/tipibot
add per-user locks, fix game conditons, startup config validation
This commit is contained in:
83
bot.py
83
bot.py
@@ -1774,15 +1774,6 @@ class HeistLobbyView(discord.ui.View):
|
||||
|
||||
@discord.ui.button(label=S.HEIST_UI["btn_join"], style=discord.ButtonStyle.danger)
|
||||
async def join(self, interaction: discord.Interaction, _: discord.ui.Button):
|
||||
if any(p.id == interaction.user.id for p in self.participants):
|
||||
await interaction.response.send_message(S.HEIST_UI["already_joined"], ephemeral=True)
|
||||
return
|
||||
if len(self.participants) >= _HEIST_MAX_PLAYERS:
|
||||
await interaction.response.send_message(S.ERR["heist_full"], ephemeral=True)
|
||||
return
|
||||
if interaction.user.id in _active_games:
|
||||
await interaction.response.send_message(S.ERR["already_in_game"], ephemeral=True)
|
||||
return
|
||||
res = await economy.do_heist_check(interaction.user.id)
|
||||
if not res["ok"]:
|
||||
if res["reason"] == "banned":
|
||||
@@ -1796,6 +1787,17 @@ class HeistLobbyView(discord.ui.View):
|
||||
S.CD_MSG["heist"].format(ts=_cd_ts(res["remaining"])), ephemeral=True
|
||||
)
|
||||
return
|
||||
# These checks must come after the await above: a double-click could
|
||||
# otherwise pass them twice and join the lobby twice.
|
||||
if any(p.id == interaction.user.id for p in self.participants):
|
||||
await interaction.response.send_message(S.HEIST_UI["already_joined"], ephemeral=True)
|
||||
return
|
||||
if len(self.participants) >= _HEIST_MAX_PLAYERS:
|
||||
await interaction.response.send_message(S.ERR["heist_full"], ephemeral=True)
|
||||
return
|
||||
if interaction.user.id in _active_games:
|
||||
await interaction.response.send_message(S.ERR["already_in_game"], ephemeral=True)
|
||||
return
|
||||
self.participants.append(interaction.user)
|
||||
_active_games.add(interaction.user.id)
|
||||
await interaction.response.edit_message(embed=self._lobby_embed())
|
||||
@@ -1923,31 +1925,42 @@ async def cmd_heist(interaction: discord.Interaction):
|
||||
if _active_heist is not None:
|
||||
await interaction.response.send_message(S.ERR["heist_active"], ephemeral=True)
|
||||
return
|
||||
_heist_cd = await economy.get_heist_global_cd()
|
||||
if time.time() < _heist_cd:
|
||||
await interaction.response.send_message(
|
||||
S.CD_MSG["heist_global"].format(ts=_cd_ts(datetime.timedelta(seconds=_heist_cd - time.time()))),
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
if interaction.user.id in _active_games:
|
||||
await interaction.response.send_message(S.ERR["already_in_game"], ephemeral=True)
|
||||
return
|
||||
res = await economy.do_heist_check(interaction.user.id)
|
||||
if not res["ok"]:
|
||||
if res["reason"] == "banned":
|
||||
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
||||
elif res["reason"] == "jailed":
|
||||
await interaction.response.send_message(
|
||||
S.CD_MSG["jailed"].format(ts=_cd_ts(res["remaining"])), ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
# Claim the lobby singleton and the game slot before the awaits below -
|
||||
# concurrent /heist invocations would otherwise all pass the checks above
|
||||
# and the last lobby would silently overwrite the others.
|
||||
view = HeistLobbyView(interaction.user)
|
||||
_active_heist = view
|
||||
_active_games.add(interaction.user.id)
|
||||
await interaction.response.send_message(embed=view._lobby_embed(), view=view)
|
||||
view.message = await interaction.original_response()
|
||||
opened = False
|
||||
try:
|
||||
_heist_cd = await economy.get_heist_global_cd()
|
||||
if time.time() < _heist_cd:
|
||||
await interaction.response.send_message(
|
||||
S.CD_MSG["heist_global"].format(ts=_cd_ts(datetime.timedelta(seconds=_heist_cd - time.time()))),
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
res = await economy.do_heist_check(interaction.user.id)
|
||||
if not res["ok"]:
|
||||
if res["reason"] == "banned":
|
||||
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
||||
elif res["reason"] == "jailed":
|
||||
await interaction.response.send_message(
|
||||
S.CD_MSG["jailed"].format(ts=_cd_ts(res["remaining"])), ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
await interaction.response.send_message(embed=view._lobby_embed(), view=view)
|
||||
view.message = await interaction.original_response()
|
||||
opened = True
|
||||
finally:
|
||||
if not opened:
|
||||
if _active_heist is view:
|
||||
_active_heist = None
|
||||
_active_games.discard(interaction.user.id)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -2898,9 +2911,13 @@ async def cmd_rps(interaction: discord.Interaction, panus: str = "0", vastane: d
|
||||
)
|
||||
embed.set_footer(text=S.RPS_UI["challenge_footer"])
|
||||
challenge_view = RpsChallengeView(game)
|
||||
await interaction.response.send_message(embed=embed, view=challenge_view)
|
||||
_active_games.add(interaction.user.id)
|
||||
game.server_message = await interaction.original_response()
|
||||
try:
|
||||
await interaction.response.send_message(embed=embed, view=challenge_view)
|
||||
game.server_message = await interaction.original_response()
|
||||
except Exception:
|
||||
_active_games.discard(interaction.user.id)
|
||||
raise
|
||||
return
|
||||
|
||||
# ── vs Bot mode ──────────────────────────────────────────────────────
|
||||
@@ -3360,9 +3377,13 @@ async def cmd_blackjack(interaction: discord.Interaction, panus: str):
|
||||
if interaction.user.id in _active_games:
|
||||
await interaction.response.send_message(S.ERR["already_in_game"], ephemeral=True)
|
||||
return
|
||||
# Claim the game slot before the bet await - a double-invoke in that window
|
||||
# would otherwise pass the check twice and deduct two bets.
|
||||
_active_games.add(interaction.user.id)
|
||||
|
||||
res = await economy.do_blackjack_bet(interaction.user.id, bet)
|
||||
if not res["ok"]:
|
||||
_active_games.discard(interaction.user.id)
|
||||
if res["reason"] == "banned":
|
||||
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
||||
elif res["reason"] == "jailed":
|
||||
@@ -3374,7 +3395,6 @@ async def cmd_blackjack(interaction: discord.Interaction, panus: str):
|
||||
S.ERR["broke"].format(bal=_coin(_data["balance"])), ephemeral=True
|
||||
)
|
||||
return
|
||||
_active_games.add(interaction.user.id)
|
||||
|
||||
deck = _bj_deck()
|
||||
player_hand: list = []
|
||||
@@ -3790,6 +3810,7 @@ def _asyncio_exception_handler(loop: asyncio.AbstractEventLoop, context: dict) -
|
||||
if __name__ == "__main__":
|
||||
if not config.DISCORD_TOKEN:
|
||||
raise SystemExit("DISCORD_TOKEN pole seadistatud. Kopeeri .env.example failiks .env ja täida see.")
|
||||
economy.validate_config() # fail at boot on constants/strings drift, not at command time
|
||||
|
||||
async def _main() -> None:
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
Reference in New Issue
Block a user