forked from sass/tipibot
feat(economy): add vanity shop and harden economy money-safety
Vanity shop (/vanity): cosmetic badges/titles as a pure whale coin sink - purchases burn coins (not credited to the house) and equip a badge shown on /profile. New vanity_owned/vanity_active fields, schema sync, and tests. Money-safety and robustness fixes from a codebase review: - _parse_amount now rejects negative amounts. Every bet/give/request flows through it, so a negative value can no longer mint coins on a loss/transfer path that trusts the caller's sign (all call sites already guarded <= 0; this closes the source). - do_blackjack_payout no longer raises on a DB failure. The stake was already deducted in do_blackjack_bet, so it now logs critical with the owed amount (for admin reconciliation) and returns db_error; all payout call sites render a clear "payout failed" notice instead of crashing the interaction. - Instant "kohv" consumable now cancels the pending reminder DMs for the cooldowns it wipes (via new INSTANT_RESET_COMMANDS), so no stale/duplicate reminders fire. - Renamed the misleadingly-named _refund_user_safe -> _debit_house_safe (it debits the house) and dropped its ignored first arg. - Added __all__ to vanity.py and consumables.py so `import *` no longer leaks incidental imports into the economy namespace. - Documented Kõrvaklapid's +25 coin daily bonus in README and DEV_NOTES. Tests: blackjack payout DB-failure safety and INSTANT_RESET_COMMANDS lockstep. 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:
11
bot.py
11
bot.py
@@ -688,8 +688,10 @@ register_prestige_commands(
|
||||
|
||||
def _parse_amount(value: str, balance: int) -> tuple[int | None, str | None]:
|
||||
"""Parse an amount string; 'all' resolves to the user's full balance.
|
||||
Accepts plain integers and valid thousand-separated numbers (1,000 / 1.000 / 1 000).
|
||||
Rejects decimals and ambiguous inputs like 1,1 or 1.5.
|
||||
Accepts plain non-negative integers and valid thousand-separated numbers
|
||||
(1,000 / 1.000 / 1 000). Rejects decimals, ambiguous inputs like 1,1 or 1.5,
|
||||
and negative amounts (a negative bet/give would mint coins on loss/transfer
|
||||
paths that trust the caller's sign).
|
||||
Returns (amount, None) on success or (None, error_msg) on failure."""
|
||||
v = value.strip()
|
||||
if v.lower() == "all":
|
||||
@@ -698,9 +700,12 @@ def _parse_amount(value: str, balance: int) -> tuple[int | None, str | None]:
|
||||
if re.fullmatch(r'\d{1,3}([,. ]\d{3})*', v):
|
||||
v = re.sub(r'[,. ]', '', v)
|
||||
try:
|
||||
return int(v), None
|
||||
amount = int(v)
|
||||
except ValueError:
|
||||
return None, S.ERR["invalid_amount"]
|
||||
if amount < 0:
|
||||
return None, S.ERR["invalid_amount"]
|
||||
return amount, None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user