fix(economy): handle db_error in commands, dedupe cooldowns, single leaderboard scan

Three robustness/perf fixes from the review:

1. db_error UX: economy core returns {"ok": False, "reason": "db_error"} on a
   PocketBase outage, but no handler expected it - deferred commands hung on
   "thinking..." and others showed a misleading "you're broke". Added a shared
   reply_db_error helper (commands/_replies.py) + S.ERR["db_error"], and wired a
   db_error branch into every handler that can receive it (daily/work/beg/crime/
   rob/give/buy/roulette/slots/blackjack/heist/fish/prestige/vanity/consumables/
   request-funding). Also fixed a latent KeyError in vs-bot RPS that read
   res["balance"] without checking res["ok"].

2. Deduped the item->cooldown mapping that was copied in do_daily/do_work/do_beg,
   do_fish_start, _maybe_remind and _restore_reminders. Single source of truth:
   store.ITEM_COOLDOWNS + effective_cooldown(cmd, items).

3. /leaderboard did six full-collection scans (one per tab). Added
   get_all_leaderboards() which scans once and builds all six views in memory.

Tests: effective_cooldown cases, and get_all_leaderboards matches the individual
queries + scans the collection exactly once.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VbAVsrZuYesea99mPMmPT
This commit is contained in:
Rene Arumetsa
2026-09-04 02:26:07 +03:00
parent 7a28617657
commit eb7346b851
15 changed files with 249 additions and 41 deletions

View File

@@ -12,6 +12,8 @@ from core import economy
from core.emoji import EMOJI as E
import strings as S
from ._replies import reply_db_error
def register_economy_games_commands(
tree: app_commands.CommandTree,
@@ -116,6 +118,9 @@ def register_economy_games_commands(
res = await economy.do_roulette(interaction.user.id, panus_int, värv.value)
if not res["ok"]:
active_games.discard(interaction.user.id)
if res["reason"] == "db_error":
await reply_db_error(interaction)
return
if res["reason"] == "banned":
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
elif res["reason"] == "jailed":
@@ -202,7 +207,14 @@ def register_economy_games_commands(
bet_line = ""
if self.bet > 0:
res = await economy.do_game_bet(interaction.user.id, self.bet, outcome)
if outcome == "win":
if not res.get("ok"):
if res.get("reason") == "db_error":
await reply_db_error(interaction)
return
# Player was jailed/went broke since the duel started - show the
# result without a bet line rather than crashing on res["balance"].
bet_line = ""
elif outcome == "win":
bet_line = S.RPS_UI["bet_win"].format(amount=coin(self.bet), balance=coin(res["balance"]))
elif outcome == "lose":
bet_line = S.RPS_UI["bet_lose"].format(amount=coin(self.bet), balance=coin(res["balance"]))
@@ -652,6 +664,9 @@ def register_economy_games_commands(
res = await economy.do_slots(interaction.user.id, panus_int)
if not res["ok"]:
active_games.discard(interaction.user.id)
if res["reason"] == "db_error":
await reply_db_error(interaction)
return
if res["reason"] == "banned":
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
return
@@ -1017,6 +1032,9 @@ def register_economy_games_commands(
try:
res = await economy.do_blackjack_bet(self.user_id, self.bet)
if not res["ok"]:
if res["reason"] == "db_error":
await reply_db_error(interaction)
return
await interaction.response.send_message(
S.ERR["broke"].format(bal=coin(res.get("balance", 0))), ephemeral=True
)
@@ -1042,6 +1060,9 @@ def register_economy_games_commands(
try:
res = await economy.do_blackjack_bet(self.user_id, self.bet)
if not res["ok"]:
if res["reason"] == "db_error":
await reply_db_error(interaction)
return
await interaction.response.send_message(
S.ERR["broke"].format(bal=coin(res.get("balance", 0))), ephemeral=True
)
@@ -1101,6 +1122,9 @@ def register_economy_games_commands(
res = await economy.do_blackjack_bet(interaction.user.id, bet)
if not res["ok"]:
if res["reason"] == "db_error":
await reply_db_error(interaction)
return
if res["reason"] == "banned":
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
elif res["reason"] == "jailed":