Initial commit: Casino bot
This commit is contained in:
340
src/db.py
Normal file
340
src/db.py
Normal file
@@ -0,0 +1,340 @@
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user