Files
SupaCasino/src/db.py

341 lines
14 KiB
Python

import os, sqlite3
from datetime import date, datetime, timedelta
DB_PATH = os.getenv("DB_PATH", "/app/data/blackjack.db")
def _connect():
return sqlite3.connect(DB_PATH)
def init_db():
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = _connect()
cur = conn.cursor()
# base
cur.execute("""
CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY,
cash INTEGER DEFAULT 1000,
games_played INTEGER DEFAULT 0,
games_won INTEGER DEFAULT 0,
last_daily DATE DEFAULT NULL
)
""")
# add new columns if missing
cur.execute("PRAGMA table_info(users)")
cols = {row[1] for row in cur.fetchall()}
add_cols = []
# shared / economy
if "free_spins" not in cols: add_cols.append(("free_spins", "INTEGER DEFAULT 0"))
if "last_topup" not in cols: add_cols.append(("last_topup", "TEXT DEFAULT NULL"))
if "last_daily_ts" not in cols:add_cols.append(("last_daily_ts", "TEXT DEFAULT NULL"))
# blackjack
if "games_played_blackjack" not in cols: add_cols.append(("games_played_blackjack", "INTEGER DEFAULT 0"))
if "games_won_blackjack" not in cols: add_cols.append(("games_won_blackjack", "INTEGER DEFAULT 0"))
# slots
if "games_played_slots" not in cols: add_cols.append(("games_played_slots", "INTEGER DEFAULT 0"))
if "games_won_slots" not in cols: add_cols.append(("games_won_slots", "INTEGER DEFAULT 0"))
if "slots_biggest_win" not in cols: add_cols.append(("slots_biggest_win", "INTEGER DEFAULT 0"))
# roulette
if "games_played_roulette" not in cols: add_cols.append(("games_played_roulette", "INTEGER DEFAULT 0"))
if "roulette_biggest_win" not in cols: add_cols.append(("roulette_biggest_win", "INTEGER DEFAULT 0"))
if "roulette_net" not in cols: add_cols.append(("roulette_net", "INTEGER DEFAULT 0"))
# coin flip
if "games_played_coinflip" not in cols: add_cols.append(("games_played_coinflip", "INTEGER DEFAULT 0"))
if "games_won_coinflip" not in cols: add_cols.append(("games_won_coinflip", "INTEGER DEFAULT 0"))
if "coinflip_biggest_win" not in cols: add_cols.append(("coinflip_biggest_win", "INTEGER DEFAULT 0"))
if "coinflip_net" not in cols: add_cols.append(("coinflip_net", "INTEGER DEFAULT 0"))
# towers
if "games_played_towers" not in cols: add_cols.append(("games_played_towers", "INTEGER DEFAULT 0"))
if "games_won_towers" not in cols: add_cols.append(("games_won_towers", "INTEGER DEFAULT 0"))
# baccarat (NEW)
if "games_played_baccarat" not in cols: add_cols.append(("games_played_baccarat", "INTEGER DEFAULT 0"))
if "games_won_baccarat" not in cols: add_cols.append(("games_won_baccarat", "INTEGER DEFAULT 0"))
if "baccarat_net" not in cols: add_cols.append(("baccarat_net", "INTEGER DEFAULT 0"))
if "baccarat_biggest_win" not in cols: add_cols.append(("baccarat_biggest_win", "INTEGER DEFAULT 0"))
for name, decl in add_cols:
cur.execute(f"ALTER TABLE users ADD COLUMN {name} {decl}")
conn.commit()
conn.close()
def ensure_user(user_id: int):
conn = _connect(); cur = conn.cursor()
cur.execute("SELECT 1 FROM users WHERE user_id=?", (user_id,))
if cur.fetchone() is None:
cur.execute("INSERT INTO users (user_id) VALUES (?)", (user_id,))
conn.commit()
conn.close()
def get_wallet(user_id: int):
ensure_user(user_id)
conn = _connect(); cur = conn.cursor()
cur.execute("SELECT cash, free_spins FROM users WHERE user_id=?", (user_id,))
cash, free_spins = cur.fetchone()
conn.close()
return cash, free_spins
def add_cash(user_id: int, delta: int):
ensure_user(user_id)
conn = _connect(); cur = conn.cursor()
cur.execute("UPDATE users SET cash = cash + ? WHERE user_id=?", (delta, user_id))
conn.commit(); conn.close()
def add_free_spins(user_id: int, spins: int):
ensure_user(user_id)
if spins <= 0: return
conn = _connect(); cur = conn.cursor()
cur.execute("UPDATE users SET free_spins = free_spins + ? WHERE user_id=?", (spins, user_id))
conn.commit(); conn.close()
def consume_free_spin(user_id: int) -> bool:
ensure_user(user_id)
conn = _connect(); cur = conn.cursor()
cur.execute("SELECT free_spins FROM users WHERE user_id=?", (user_id,))
fs = cur.fetchone()[0]
if fs > 0:
cur.execute("UPDATE users SET free_spins = free_spins - 1 WHERE user_id=?", (user_id,))
conn.commit(); conn.close()
return True
conn.close()
return False
# legacy aggregate
def record_game(user_id: int, won: bool):
ensure_user(user_id)
conn = _connect(); cur = conn.cursor()
if won:
cur.execute("UPDATE users SET games_played=games_played+1, games_won=games_won+1 WHERE user_id=?", (user_id,))
else:
cur.execute("UPDATE users SET games_played=games_played+1 WHERE user_id=?", (user_id,))
conn.commit(); conn.close()
# per-game
def record_blackjack(user_id: int, won: bool):
ensure_user(user_id)
conn = _connect(); cur = conn.cursor()
if won:
cur.execute("""
UPDATE users SET
games_played_blackjack = games_played_blackjack + 1,
games_won_blackjack = games_won_blackjack + 1
WHERE user_id = ?
""", (user_id,))
else:
cur.execute("""
UPDATE users SET
games_played_blackjack = games_played_blackjack + 1
WHERE user_id = ?
""", (user_id,))
conn.commit(); conn.close()
record_game(user_id, won)
def record_slots(user_id: int, win_amount: int):
ensure_user(user_id)
won = (win_amount > 0)
conn = _connect(); cur = conn.cursor()
if won:
cur.execute("""
UPDATE users SET
games_played_slots = games_played_slots + 1,
games_won_slots = games_won_slots + 1,
slots_biggest_win = CASE WHEN ? > slots_biggest_win THEN ? ELSE slots_biggest_win END
WHERE user_id = ?
""", (win_amount, win_amount, user_id))
else:
cur.execute("""
UPDATE users SET
games_played_slots = games_played_slots + 1
WHERE user_id = ?
""", (user_id,))
conn.commit(); conn.close()
record_game(user_id, won)
def record_roulette(user_id: int, total_bet: int, total_return: int):
ensure_user(user_id)
net = total_return - total_bet
conn = _connect(); cur = conn.cursor()
cur.execute("""
UPDATE users
SET games_played_roulette = games_played_roulette + 1,
roulette_net = roulette_net + ?,
roulette_biggest_win = CASE WHEN ? > roulette_biggest_win THEN ? ELSE roulette_biggest_win END
WHERE user_id = ?
""", (net, total_return, total_return, user_id))
conn.commit(); conn.close()
def record_coinflip(user_id: int, bet: int, return_amount: int, won: bool):
ensure_user(user_id)
net = return_amount - bet
conn = _connect(); cur = conn.cursor()
if won:
cur.execute("""
UPDATE users
SET games_played_coinflip = games_played_coinflip + 1,
games_won_coinflip = games_won_coinflip + 1,
coinflip_net = coinflip_net + ?,
coinflip_biggest_win = CASE WHEN ? > coinflip_biggest_win THEN ? ELSE coinflip_biggest_win END
WHERE user_id = ?
""", (net, return_amount, return_amount, user_id))
else:
cur.execute("""
UPDATE users
SET games_played_coinflip = games_played_coinflip + 1,
coinflip_net = coinflip_net + ?
WHERE user_id = ?
""", (net, user_id))
conn.commit(); conn.close()
record_game(user_id, won)
def record_towers(user_id: int, won: bool):
"""Call with won=True on cashout/full clear; won=False on bomb."""
ensure_user(user_id)
conn = _connect(); cur = conn.cursor()
if won:
cur.execute("""
UPDATE users SET
games_played_towers = games_played_towers + 1,
games_won_towers = games_won_towers + 1
WHERE user_id = ?
""", (user_id,))
else:
cur.execute("""
UPDATE users SET
games_played_towers = games_played_towers + 1
WHERE user_id = ?
""", (user_id,))
conn.commit(); conn.close()
record_game(user_id, won)
# NEW: Baccarat
def record_baccarat(user_id: int, total_bet: int, total_return: int):
"""
Record a single baccarat hand.
- total_bet: sum of Player+Banker+Tie stakes debited
- total_return: total chips returned (including stake(s) on pushes/wins)
"""
ensure_user(user_id)
net = total_return - total_bet
won = net > 0
conn = _connect(); cur = conn.cursor()
cur.execute("""
UPDATE users
SET games_played_baccarat = games_played_baccarat + 1,
games_won_baccarat = games_won_baccarat + CASE WHEN ? > 0 THEN 1 ELSE 0 END,
baccarat_net = baccarat_net + ?,
baccarat_biggest_win = CASE WHEN ? > baccarat_biggest_win THEN ? ELSE baccarat_biggest_win END
WHERE user_id = ?
""", (net, net, total_return, total_return, user_id))
conn.commit(); conn.close()
record_game(user_id, won)
def top_cash(limit=10):
conn = _connect(); cur = conn.cursor()
cur.execute("SELECT user_id, cash FROM users ORDER BY cash DESC LIMIT ?", (limit,))
rows = cur.fetchall()
conn.close()
return rows
def user_counts():
conn = _connect(); cur = conn.cursor()
cur.execute("SELECT COUNT(*) FROM users")
n = cur.fetchone()[0]
conn.close()
return n
def get_full_stats(user_id: int):
ensure_user(user_id)
conn = _connect(); cur = conn.cursor()
cur.execute("""
SELECT cash, free_spins, games_played, games_won,
games_played_blackjack, games_won_blackjack,
games_played_slots, games_won_slots,
games_played_roulette, roulette_net,
games_played_coinflip, games_won_coinflip, coinflip_net,
games_played_towers, games_won_towers,
games_played_baccarat, games_won_baccarat, baccarat_net
FROM users WHERE user_id=?
""", (user_id,))
row = cur.fetchone(); conn.close()
keys = [
"cash","free_spins","games_played","games_won",
"gp_bj","gw_bj",
"gp_slots","gw_slots",
"gp_roulette","roulette_net",
"gp_coin","gw_coin","coin_net",
"gp_towers","gw_towers",
"gp_bac","gw_bac","bac_net",
]
return dict(zip(keys, row))
def can_claim_topup(user_id: int, cooldown_minutes: int = 5) -> tuple[bool, int]:
ensure_user(user_id)
conn = _connect(); cur = conn.cursor()
cur.execute("SELECT last_topup FROM users WHERE user_id=?", (user_id,))
row = cur.fetchone(); conn.close()
last = row[0] if row else None
if not last: return True, 0
try: last_dt = datetime.fromisoformat(last)
except Exception: return True, 0
now = datetime.utcnow()
wait = timedelta(minutes=cooldown_minutes)
if now - last_dt >= wait: return True, 0
left = int((wait - (now - last_dt)).total_seconds())
return False, left
def claim_topup(user_id: int, amount: int):
ensure_user(user_id)
now_str = datetime.utcnow().isoformat(timespec="seconds")
conn = _connect(); cur = conn.cursor()
cur.execute("UPDATE users SET cash = cash + ?, last_topup = ? WHERE user_id = ?", (amount, now_str, user_id))
conn.commit(); conn.close()
def daily_cooldown(user_id: int, hours: int = 24) -> tuple[bool, int]:
ensure_user(user_id)
conn = _connect(); cur = conn.cursor()
cur.execute("SELECT last_daily_ts FROM users WHERE user_id=?", (user_id,))
row = cur.fetchone(); conn.close()
last = row[0] if row else None
if not last: return True, 0
try: last_dt = datetime.fromisoformat(last)
except Exception: return True, 0
now = datetime.utcnow()
wait = timedelta(hours=hours)
if now - last_dt >= wait: return True, 0
secs_left = int((wait - (now - last_dt)).total_seconds())
return False, secs_left
def claim_daily(user_id: int, cash_bonus: int, spin_bonus: int):
ensure_user(user_id)
today = str(date.today())
now_str = datetime.utcnow().isoformat(timespec="seconds")
conn = _connect(); cur = conn.cursor()
cur.execute("""
UPDATE users
SET cash = cash + ?, free_spins = free_spins + ?, last_daily = ?, last_daily_ts = ?
WHERE user_id = ?
""", (cash_bonus, spin_bonus, today, now_str, user_id))
conn.commit(); conn.close()
def transfer_cash(from_user_id: int, to_user_id: int, amount: int):
ensure_user(from_user_id); ensure_user(to_user_id)
if amount is None or amount <= 0: return False, "INVALID_AMOUNT", None, None
if from_user_id == to_user_id: return False, "SAME_USER", None, None
conn = _connect(); cur = conn.cursor()
try:
cur.execute("BEGIN IMMEDIATE")
cur.execute("SELECT cash FROM users WHERE user_id=?", (from_user_id,))
row = cur.fetchone()
if not row: conn.rollback(); return False, "INSUFFICIENT_FUNDS", None, None
from_cash = row[0]
if from_cash < amount: conn.rollback(); return False, "INSUFFICIENT_FUNDS", from_cash, None
cur.execute("UPDATE users SET cash = cash - ? WHERE user_id=?", (amount, from_user_id))
cur.execute("UPDATE users SET cash = cash + ? WHERE user_id=?", (amount, to_user_id))
cur.execute("SELECT cash FROM users WHERE user_id=?", (from_user_id,)); new_from = cur.fetchone()[0]
cur.execute("SELECT cash FROM users WHERE user_id=?", (to_user_id,)); new_to = cur.fetchone()[0]
conn.commit(); return True, None, new_from, new_to
except Exception:
conn.rollback(); raise
finally:
conn.close()