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()