First Commit

This commit is contained in:
2025-09-16 15:00:16 +02:00
commit c8980f785f
188 changed files with 43407 additions and 0 deletions
+509
View File
@@ -0,0 +1,509 @@
import discord
from discord.ext import commands, tasks
from utils.sql_commands import DatabaseManager
from utils.bank_functions import bank_data, update_money
from random import choice
from datetime import datetime, timedelta
import logging
TICKET_TYPES = {
"standard": {"price": 500, "weight": 1},
"premium": {"price": 2000, "weight": 5},
}
LOTTERY_INTERVAL_HOURS = 24 # Draw every 24 hours
MAX_TICKETS_PER_USER = 10
ROLLOVER_BONUS = 500
class Lottery(commands.Cog):
def __init__(self, client):
self.client = client
self.db = DatabaseManager()
self.lottery_draw.start()
def cog_unload(self):
self.lottery_draw.cancel()
def get_jackpot(self) -> int:
try:
result = self.db.fetch_one("SELECT jackpot FROM lottery_state WHERE id = 1")
return int(result["jackpot"]) if result else 0
except Exception as e:
logging.error(f"Error fetching jackpot: {e}")
return 0
def set_jackpot(self, amount: int) -> None:
try:
self.db.execute_query(
"UPDATE lottery_state SET jackpot = %s WHERE id = 1", (amount,)
)
except Exception as e:
logging.error(f"Error setting jackpot: {e}")
def add_to_jackpot(self, amount: int) -> None:
try:
self.db.execute_query(
"UPDATE lottery_state SET jackpot = jackpot + %s WHERE id = 1",
(amount,),
)
except Exception as e:
logging.error(f"Error adding to jackpot: {e}")
def get_last_draw_time(self) -> datetime:
try:
result = self.db.fetch_one(
"SELECT last_draw FROM lottery_draw_time WHERE id = 1"
)
last_draw = result["last_draw"] if result and result["last_draw"] else None
if last_draw is None:
# Set to now if missing and update DB
now = datetime.utcnow()
self.set_last_draw_time(now)
return now
if isinstance(last_draw, str):
try:
last_draw = datetime.fromisoformat(last_draw)
except Exception:
last_draw = datetime.strptime(last_draw, "%Y-%m-%d %H:%M:%S")
return last_draw
except Exception as e:
logging.error(f"Error fetching last draw time: {e}")
now = datetime.utcnow()
self.set_last_draw_time(now)
return now
def set_last_draw_time(self, draw_time: datetime) -> None:
try:
self.db.execute_query(
"UPDATE lottery_draw_time SET last_draw = %s WHERE id = 1", (draw_time,)
)
except Exception as e:
logging.error(f"Error setting last draw time: {e}")
def notify_lottery_result(self, user_id: int):
try:
result = self.db.fetch_one(
"SELECT * FROM lottery_results WHERE WINNER_ID = %s AND CLAIMED = 0 ORDER BY DRAW_TIME DESC LIMIT 1",
(user_id,),
)
if not result:
return None
# Remove the result after notifying
self.db.execute_query(
"DELETE FROM lottery_results WHERE ID = %s", (result["ID"],)
)
return (True, result["AMOUNT"])
except Exception as e:
logging.error(f"Error notifying lottery result: {e}")
return None
@commands.command(
name="buyticket",
help=f"Buy one or more lottery tickets for yourself or a group.",
)
async def buy_ticket(
self,
ctx: commands.Context,
ticket_type: str = "standard",
amount: int = 1,
group_id: str | None = None,
member: discord.Member | None = None,
):
ticket_type = ticket_type.lower()
if ticket_type not in TICKET_TYPES:
await ctx.reply(
f"Invalid ticket type. Choose from: {', '.join(TICKET_TYPES)}"
)
return
if amount < 1 or amount > MAX_TICKETS_PER_USER:
await ctx.reply(
f"You can only buy between 1 and {MAX_TICKETS_PER_USER} tickets at once."
)
return
user = member or ctx.author
price = TICKET_TYPES[ticket_type]["price"] * amount
try:
# Notify about last draw if relevant (only if user is the winner)
notify = self.notify_lottery_result(user.id)
if notify:
await ctx.reply(
f"🎉 You won the last lottery! Jackpot: {notify[1]:,} coins.",
mention_author=False,
)
user_data = await bank_data(user)
wallet = int(user_data.get("WALLET", 0))
if wallet < price:
await ctx.reply(
f"You need at least {price} coins to buy {amount} {ticket_type} ticket(s).",
mention_author=False,
)
return
if group_id:
group_exists = self.db.fetch_one(
"SELECT 1 FROM lottery_groups WHERE group_id = %s", (group_id,)
)
in_group = self.db.fetch_one(
"SELECT 1 FROM lottery_group_members WHERE group_id = %s AND user_id = %s",
(group_id, user.id),
)
if not group_exists or not in_group:
await ctx.reply(
"You must be a member of the group to buy tickets for it."
)
return
user_ticket_count = self.db.fetch_one(
"SELECT COUNT(*) as count FROM lottery_tickets WHERE USERID = %s",
(user.id,),
)["count"]
if user_ticket_count + amount > MAX_TICKETS_PER_USER:
await ctx.reply(
f"You can only have {MAX_TICKETS_PER_USER} tickets per draw. You currently have {user_ticket_count}."
)
return
# Deduct ticket price
await update_money(user, wallet=-price)
self.add_to_jackpot(price)
# Store tickets in DB (one row per ticket for higher odds with multiple tickets)
for _ in range(amount):
self.db.execute_query(
"INSERT INTO lottery_tickets (USERID, TIMESTAMP, TICKET_TYPE, group_id) VALUES (%s, %s, %s, %s)",
(user.id, datetime.utcnow(), ticket_type, group_id),
)
jackpot = self.get_jackpot()
await ctx.reply(
f"🎟️ {amount} {ticket_type.capitalize()} ticket(s) bought! Jackpot is now {jackpot:,} coins.",
mention_author=False,
)
except Exception as e:
logging.error(f"Error in buy_ticket: {e}")
await ctx.reply(
"An error occurred while buying your ticket(s).", mention_author=False
)
@commands.command(name="lotterystats", help="Show current lottery stats.")
async def lottery_stats(self, ctx: commands.Context):
try:
# Show last winner and their prize
last_result = self.db.fetch_one(
"SELECT * FROM lottery_results ORDER BY DRAW_TIME DESC LIMIT 1"
)
winner_text = "No draws yet."
if last_result:
winner = self.client.get_user(
last_result["WINNER_ID"]
) or await self.client.fetch_user(last_result["WINNER_ID"])
winner_text = f"Last winner: {winner.mention if winner else 'Unknown'} ({last_result['AMOUNT']:,} coins)"
ticket_count = self.db.fetch_one(
"SELECT COUNT(*) as count FROM lottery_tickets"
)["count"]
jackpot = self.get_jackpot()
# Check if the user has an unclaimed win
unclaimed = self.db.fetch_one(
"SELECT * FROM lottery_results WHERE WINNER_ID = %s AND CLAIMED = 0 ORDER BY DRAW_TIME DESC LIMIT 1",
(ctx.author.id,),
)
winner_note = ""
if unclaimed:
# Mark as claimed and pay out
self.db.execute_query(
"UPDATE lottery_results SET CLAIMED = 1 WHERE ID = %s",
(unclaimed["ID"],),
)
await update_money(ctx.author, wallet=unclaimed["AMOUNT"])
winner_note = f"\n🎉 **You have won {unclaimed['AMOUNT']:,} coins! Your prize has been paid out.**"
await ctx.reply(
f"{winner_text}\n"
f"🎰 There are currently **{ticket_count}** tickets in the pool.\n"
f"💰 Jackpot: **{jackpot:,}** coins.\n"
f"Next draw in: {self.time_until_draw()}"
f"{winner_note}",
mention_author=False,
)
except Exception as e:
logging.error(f"Error in lottery_stats: {e}")
await ctx.reply(
"An error occurred while fetching lottery stats.", mention_author=False
)
def time_until_draw(self) -> str:
now = datetime.utcnow()
last_draw = self.get_last_draw_time()
next_draw = last_draw + timedelta(hours=LOTTERY_INTERVAL_HOURS)
remaining = next_draw - now
if remaining.total_seconds() < 0:
return "Drawing soon!"
hours, remainder = divmod(int(remaining.total_seconds()), 3600)
minutes, seconds = divmod(remainder, 60)
return f"{hours}h {minutes}m {seconds}s"
@tasks.loop(minutes=1)
async def lottery_draw(self):
now = datetime.utcnow()
last_draw = self.get_last_draw_time()
if (now - last_draw).total_seconds() < LOTTERY_INTERVAL_HOURS * 3600:
return # Not time yet
await self._run_lottery_draw()
@commands.command(name="testdraw", help="Manually trigger the lottery draw.")
@commands.is_owner()
async def testdraw(self, ctx: commands.Context):
await ctx.send("Starting lottery draw...")
await self._run_lottery_draw()
async def _run_lottery_draw(self):
try:
await self.client.wait_until_ready()
tickets = self.db.fetch_all(
"SELECT USERID, TICKET_TYPE FROM lottery_tickets"
)
jackpot = self.get_jackpot()
if not tickets or jackpot <= 0:
self.set_jackpot(self.get_jackpot() + ROLLOVER_BONUS)
self.set_last_draw_time(datetime.utcnow())
return
# Build weighted ticket list
weighted_tickets = []
for t in tickets:
luck_row = self.db.fetch_one(
"SELECT LUCK FROM lottery_luck WHERE USERID = %s", (t["USERID"],)
)
luck = luck_row["LUCK"] if luck_row else 0
ticket_type_weight = TICKET_TYPES[t["TICKET_TYPE"]]["weight"]
weight = ticket_type_weight * (1 + luck)
weighted_tickets.extend([t["USERID"]] * weight)
winner_id = choice(weighted_tickets)
# Find group for winning ticket BEFORE deleting tickets
group_id = self.db.fetch_one(
"SELECT group_id FROM lottery_tickets WHERE USERID = %s LIMIT 1",
(winner_id,),
)["group_id"]
self.db.execute_query("DELETE FROM lottery_tickets") # Reset for next round
if group_id:
members = self.db.fetch_all(
"SELECT user_id FROM lottery_group_members WHERE group_id = %s",
(group_id,),
)
member_ids = [m["user_id"] for m in members]
split_prize = (jackpot // 2) // len(member_ids)
for uid in member_ids:
member = self.client.get_user(uid) or await self.client.fetch_user(
uid
)
await update_money(member, wallet=split_prize)
# Store group win in results with WIN_TYPE='group'
self.db.execute_query(
"INSERT INTO lottery_results (WINNER_ID, AMOUNT, DRAW_TIME, CLAIMED, WIN_TYPE) VALUES (%s, %s, %s, 0, %s)",
(group_id, jackpot // 2, datetime.utcnow(), "group"),
)
else:
# Store solo win in results with WIN_TYPE='user'
self.db.execute_query(
"INSERT INTO lottery_results (WINNER_ID, AMOUNT, DRAW_TIME, CLAIMED, WIN_TYPE) VALUES (%s, %s, %s, 0, %s)",
(winner_id, jackpot // 2, datetime.utcnow(), "user"),
)
# Carry over half the jackpot (rounded down)
carryover = jackpot // 2
self.set_jackpot(carryover)
self.set_last_draw_time(datetime.utcnow())
# Reset winner's luck, increment others'
self.db.execute_query(
"UPDATE lottery_luck SET LUCK = 0 WHERE USERID = %s", (winner_id,)
)
self.db.execute_query(
"UPDATE lottery_luck SET LUCK = LUCK + 1 WHERE USERID != %s",
(winner_id,),
)
except Exception as e:
logging.error(f"Error in _run_lottery_draw: {e}")
@lottery_draw.before_loop
async def before_lottery_draw(self):
await self.client.wait_until_ready()
@commands.command(name="lotteryhistory")
async def lottery_history(self, ctx):
results = self.db.fetch_all(
"SELECT * FROM lottery_results ORDER BY DRAW_TIME DESC LIMIT 10"
)
if not results:
await ctx.reply("No lottery draws yet.")
return
lines = []
for res in results:
if res.get("WIN_TYPE") == "group":
lines.append(
f"{res['DRAW_TIME'].strftime('%Y-%m-%d')}: Group `{res['WINNER_ID']}` won {res['AMOUNT']:,} coins"
)
else:
winner = self.client.get_user(
res["WINNER_ID"]
) or await self.client.fetch_user(res["WINNER_ID"])
lines.append(
f"{res['DRAW_TIME'].strftime('%Y-%m-%d')}: {winner.mention if winner else 'Unknown'} won {res['AMOUNT']:,} coins"
)
await ctx.reply("\n".join(lines))
@commands.command(name="lotteryleaderboard")
async def lottery_leaderboard(self, ctx):
winners = self.db.fetch_all(
"SELECT WINNER_ID, COUNT(*) as wins, SUM(AMOUNT) as total FROM lottery_results GROUP BY WINNER_ID ORDER BY wins DESC LIMIT 10"
)
if not winners:
await ctx.reply("No winners yet.")
return
lines = []
for w in winners:
user = self.client.get_user(w["WINNER_ID"]) or await self.client.fetch_user(
w["WINNER_ID"]
)
lines.append(
f"{user.mention if user else 'Unknown'}: {w['wins']} wins, {w['total']:,} coins"
)
await ctx.reply("\n".join(lines))
@commands.command(name="initluck")
@commands.is_owner()
async def init_luck(self, ctx):
"""Initialize or reset the luck of a user."""
user = ctx.author
self.db.execute_query(
"INSERT IGNORE INTO lottery_luck (USERID, LUCK) VALUES (%s, 0)", (user.id,)
)
await ctx.reply(f"Your luck has been initialized/reset.")
@commands.command(name="creategroup")
async def create_group(self, ctx, group_id: str):
exists = self.db.fetch_one(
"SELECT 1 FROM lottery_groups WHERE group_id = %s", (group_id,)
)
if exists:
await ctx.reply("A group with that ID already exists.")
return
self.db.execute_query(
"INSERT INTO lottery_groups (group_id, creator_id) VALUES (%s, %s)",
(group_id, ctx.author.id),
)
self.db.execute_query(
"INSERT INTO lottery_group_members (group_id, user_id) VALUES (%s, %s)",
(group_id, ctx.author.id),
)
await ctx.reply(f"Group `{group_id}` created and you have joined it.")
@commands.command(name="joingroup")
async def join_group(self, ctx, group_id: str):
exists = self.db.fetch_one(
"SELECT 1 FROM lottery_groups WHERE group_id = %s", (group_id,)
)
if not exists:
await ctx.reply("That group does not exist.")
return
already = self.db.fetch_one(
"SELECT 1 FROM lottery_group_members WHERE group_id = %s AND user_id = %s",
(group_id, ctx.author.id),
)
if already:
await ctx.reply("You are already in this group.")
return
self.db.execute_query(
"INSERT INTO lottery_group_members (group_id, user_id) VALUES (%s, %s)",
(group_id, ctx.author.id),
)
await ctx.reply(f"You have joined group `{group_id}`.")
@commands.command(name="leavegroup")
async def leave_group(self, ctx, group_id: str):
self.db.execute_query(
"DELETE FROM lottery_group_members WHERE group_id = %s AND user_id = %s",
(group_id, ctx.author.id),
)
await ctx.reply(f"You have left group `{group_id}`.")
@commands.command(name="deletegroup")
async def delete_group(self, ctx, group_id: str):
# Check if group exists and if user is creator
group = self.db.fetch_one(
"SELECT creator_id FROM lottery_groups WHERE group_id = %s", (group_id,)
)
if not group:
await ctx.reply("That group does not exist.")
return
if group["creator_id"] != ctx.author.id:
await ctx.reply("Only the group creator can delete this group.")
return
# Check if group is empty (no members except possibly the creator)
members = self.db.fetch_all(
"SELECT user_id FROM lottery_group_members WHERE group_id = %s", (group_id,)
)
if len(members) > 1 or (
len(members) == 1 and members[0]["user_id"] != ctx.author.id
):
await ctx.reply(
"You can only delete a group if it is empty (no members except you)."
)
return
# Delete group and membership
self.db.execute_query(
"DELETE FROM lottery_group_members WHERE group_id = %s", (group_id,)
)
self.db.execute_query(
"DELETE FROM lottery_groups WHERE group_id = %s", (group_id,)
)
await ctx.reply(f"Group `{group_id}` has been deleted.")
@commands.command(name="refundtickets")
async def refund_tickets(self, ctx, amount: int = 1):
user = ctx.author
tickets = self.db.fetch_all(
"SELECT * FROM lottery_tickets WHERE USERID = %s", (user.id,)
)
if not tickets:
await ctx.reply("You have no tickets to refund for this draw.")
return
if amount is None:
amount = len(tickets)
if amount < 1 or amount > len(tickets):
await ctx.reply(
f"You can only refund between 1 and {len(tickets)} tickets."
)
return
# Refund only the specified amount
tickets_to_refund = tickets[:amount]
total_refund = 0
ticket_ids = []
for ticket in tickets_to_refund:
price = TICKET_TYPES[ticket["TICKET_TYPE"]]["price"]
refund = int(price * 0.8)
total_refund += refund
ticket_ids.append(ticket["ID"])
# Remove only the refunded tickets
format_strings = ",".join(["%s"] * len(ticket_ids))
self.db.execute_query(
f"DELETE FROM lottery_tickets WHERE ID IN ({format_strings})",
tuple(ticket_ids),
)
await update_money(user, wallet=total_refund)
await ctx.reply(
f"{amount} ticket(s) refunded for {total_refund:,} coins (80% of purchase price)."
)
async def setup(client):
await client.add_cog(Lottery(client))