|
2 | 2 | from cogs.logging.logger import CogLogger |
3 | 3 | from utils.db import async_db as db |
4 | 4 | from cogs.Help import HelpPaginator |
| 5 | +from typing import Dict, List |
5 | 6 | from utils.betting import parse_bet |
6 | 7 | import discord |
7 | 8 | import random |
@@ -32,6 +33,7 @@ def __init__(self, pages, author, timeout=180): |
32 | 33 | super().__init__(timeout=timeout) |
33 | 34 | self.pages = pages |
34 | 35 | self.author = author |
| 36 | + self.active_jackpots: Dict[int, Dict[int, int]] = {} # {channel_id: {bet_amount: message_id}} |
35 | 37 | self.current_page = 0 |
36 | 38 | self.message = None |
37 | 39 | self.DEFAULT_FISHING_ITEMS = { |
@@ -556,41 +558,50 @@ async def leaderboard(self, ctx, scope: str = "server"): |
556 | 558 | return await self._show_server_leaderboard(ctx) |
557 | 559 |
|
558 | 560 | async def _show_server_leaderboard(self, ctx): |
559 | | - """Show server-specific leaderboard""" |
| 561 | + """Show server-specific leaderboard (optimized version)""" |
560 | 562 | try: |
561 | | - # Use the db instance (which is async_db) |
562 | | - if not await db.ensure_connected(): |
| 563 | + if not await self.db.ensure_connected(): |
563 | 564 | return await ctx.reply(embed=discord.Embed( |
564 | 565 | description="❌ Database connection failed", |
565 | 566 | color=0xff0000 |
566 | 567 | )) |
| 568 | + |
| 569 | + # Get all non-bot member IDs in the server |
| 570 | + member_ids = [str(member.id) for member in ctx.guild.members if not member.bot] |
567 | 571 |
|
| 572 | + if not member_ids: |
| 573 | + return await ctx.reply(embed=discord.Embed( |
| 574 | + description="No users found in this server", |
| 575 | + color=0x2b2d31 |
| 576 | + )) |
| 577 | + |
| 578 | + # Single database query to get all relevant users |
| 579 | + cursor = self.db.db.users.find({ |
| 580 | + "_id": {"$in": member_ids}, |
| 581 | + "$or": [ |
| 582 | + {"wallet": {"$gt": 0}}, |
| 583 | + {"bank": {"$gt": 0}} |
| 584 | + ] |
| 585 | + }) |
| 586 | + |
568 | 587 | users = [] |
569 | | - |
570 | | - # Get all users in the server and check their balances |
571 | | - for member in ctx.guild.members: |
572 | | - if not member.bot: |
573 | | - try: |
574 | | - wallet = await db.get_wallet_balance(member.id, ctx.guild.id) |
575 | | - bank = await db.get_bank_balance(member.id, ctx.guild.id) |
576 | | - total = wallet + bank |
577 | | - if total > 0: # Only include users with money |
578 | | - users.append({ |
579 | | - "member": member, |
580 | | - "total": total |
581 | | - }) |
582 | | - except Exception as e: |
583 | | - print(f"DEBUG: Error getting balance for {member.id}: {e}") |
584 | | - continue |
585 | | - |
| 588 | + async for user_doc in cursor: |
| 589 | + member = ctx.guild.get_member(int(user_doc["_id"])) |
| 590 | + if member: # Only include members still in the server |
| 591 | + total = user_doc.get("wallet", 0) + user_doc.get("bank", 0) |
| 592 | + users.append({ |
| 593 | + "member": member, |
| 594 | + "total": round(total) |
| 595 | + }) |
| 596 | + |
586 | 597 | if not users: |
587 | 598 | embed = discord.Embed( |
588 | 599 | description="No economy data for this server.\n💡 Users need to earn money first (work, daily, etc.)", |
589 | 600 | color=0x2b2d31 |
590 | 601 | ) |
591 | 602 | return await ctx.reply(embed=embed) |
592 | 603 |
|
593 | | - # Sort by total wealth and take top 10 |
| 604 | + # Sort and get top 10 |
594 | 605 | users.sort(key=lambda x: x["total"], reverse=True) |
595 | 606 | users = users[:10] |
596 | 607 |
|
@@ -618,7 +629,7 @@ async def _show_server_leaderboard(self, ctx): |
618 | 629 | await ctx.reply(embed=embed) |
619 | 630 |
|
620 | 631 | except Exception as e: |
621 | | - print(f"DEBUG: Server leaderboard error: {e}") |
| 632 | + print(f"Leaderboard error: {e}") |
622 | 633 | return await ctx.reply(embed=discord.Embed( |
623 | 634 | description="❌ An error occurred while fetching the leaderboard", |
624 | 635 | color=0xff0000 |
@@ -689,7 +700,7 @@ async def _show_global_leaderboard(self, ctx): |
689 | 700 |
|
690 | 701 | formatted_total = "{:,}".format(total_wealth) |
691 | 702 | average_wealth = "{:,}".format(total_wealth // len(content)) if content else "0" |
692 | | - embed.set_footer(text=f"Total Wealth: {formatted_total} {self.currency} • Average: {average_wealth} {self.currency}") |
| 703 | + embed.set_footer(text=f"Total Wealth: ${formatted_total} • Average: ${average_wealth}") |
693 | 704 |
|
694 | 705 | await ctx.reply(embed=embed) |
695 | 706 |
|
@@ -785,6 +796,155 @@ async def buy(self, ctx, item_id: str): |
785 | 796 | else: |
786 | 797 | await ctx.reply(f"❌ {message}") |
787 | 798 |
|
| 799 | + @commands.command(aliases=['jp']) |
| 800 | + async def jackpot(self, ctx, bet_amount: str = "25"): |
| 801 | + """Start a jackpot with custom entry fee. Default: 25 |
| 802 | + Usage: !jackpot [bet] (supports all/max/half/percentages)""" |
| 803 | + |
| 804 | + # Get user's balance |
| 805 | + user_id = ctx.author.id |
| 806 | + wallet = await self.db.get_wallet_balance(user_id) |
| 807 | + bank = await self.db.get_bank_balance(user_id) |
| 808 | + total_balance = wallet + bank |
| 809 | + |
| 810 | + # Parse the bet amount |
| 811 | + parsed_amount, error = parse_bet(bet_amount, total_balance) |
| 812 | + if error: |
| 813 | + return await ctx.reply(f"❌ {error}") |
| 814 | + |
| 815 | + # Check if user can afford the bet |
| 816 | + if wallet < parsed_amount: |
| 817 | + needed = parsed_amount - wallet |
| 818 | + return await ctx.reply( |
| 819 | + f"❌ You need {needed}{self.currency} more in your wallet to join this jackpot!\n" |
| 820 | + f"💡 Use `.withdraw {needed}` to move money from your bank." |
| 821 | + ) |
| 822 | + |
| 823 | + # Handle "all" or "max" with empty bank |
| 824 | + if bet_amount.lower() in ["all", "max"] and bank == 0: |
| 825 | + parsed_amount *= 2 # Double the bet for free |
| 826 | + await ctx.send(f"🎁 Bonus! Since your bank is empty, your bet has been doubled to **{parsed_amount}{self.currency}** for free!") |
| 827 | + |
| 828 | + # Minimum bet check |
| 829 | + if parsed_amount < 10: |
| 830 | + return await ctx.reply(f"Minimum jackpot entry is 10{self.currency}!") |
| 831 | + |
| 832 | + # Check for existing jackpot with this bet amount |
| 833 | + channel_jackpots = self.active_jackpots.get(ctx.channel.id, {}) |
| 834 | + if parsed_amount in channel_jackpots: |
| 835 | + try: |
| 836 | + message = await ctx.channel.fetch_message(channel_jackpots[parsed_amount]) |
| 837 | + return await ctx.reply( |
| 838 | + f"A jackpot with {parsed_amount}{self.currency} entry fee already exists!\n" |
| 839 | + f"{message.jump_url}" |
| 840 | + ) |
| 841 | + except discord.NotFound: |
| 842 | + # Clean up expired jackpot |
| 843 | + del channel_jackpots[parsed_amount] |
| 844 | + |
| 845 | + try: |
| 846 | + # Deduct the bet immediately |
| 847 | + if not await self.db.update_wallet(user_id, -parsed_amount): |
| 848 | + return await ctx.reply("❌ Failed to deduct your bet amount. Please try again.") |
| 849 | + |
| 850 | + embed = discord.Embed( |
| 851 | + description=( |
| 852 | + f"🎰 **JACKPOT STARTED!** 🎰\n" |
| 853 | + f"Hosted by: {ctx.author.mention}\n" |
| 854 | + f"Entry: **{parsed_amount}{self.currency}**\n" |
| 855 | + f"React with 🎉 within **15 seconds** to join!\n\n" |
| 856 | + f"Current pot: **{parsed_amount}{self.currency}** (1 player)" |
| 857 | + ), |
| 858 | + color=discord.Color.gold() |
| 859 | + ) |
| 860 | + jackpot_msg = await ctx.send(embed=embed) |
| 861 | + await jackpot_msg.add_reaction("🎉") |
| 862 | + |
| 863 | + # Store jackpot info |
| 864 | + if ctx.channel.id not in self.active_jackpots: |
| 865 | + self.active_jackpots[ctx.channel.id] = {} |
| 866 | + self.active_jackpots[ctx.channel.id][parsed_amount] = jackpot_msg.id |
| 867 | + |
| 868 | + participants = {ctx.author.id: ctx.author} # Store as dict to avoid duplicates |
| 869 | + await asyncio.sleep(15) |
| 870 | + |
| 871 | + # Clean up jackpot tracking |
| 872 | + if ctx.channel.id in self.active_jackpots and parsed_amount in self.active_jackpots[ctx.channel.id]: |
| 873 | + del self.active_jackpots[ctx.channel.id][parsed_amount] |
| 874 | + if not self.active_jackpots[ctx.channel.id]: |
| 875 | + del self.active_jackpots[ctx.channel.id] |
| 876 | + |
| 877 | + try: |
| 878 | + jackpot_msg = await ctx.channel.fetch_message(jackpot_msg.id) |
| 879 | + reaction = next((r for r in jackpot_msg.reactions if str(r.emoji) == "🎉"), None) |
| 880 | + |
| 881 | + if reaction: |
| 882 | + async for user in reaction.users(): |
| 883 | + if not user.bot and user.id != ctx.author.id: |
| 884 | + # Check each participant's balance |
| 885 | + user_wallet = await self.db.get_wallet_balance(user.id) |
| 886 | + if user_wallet >= parsed_amount: |
| 887 | + if await self.db.update_wallet(user.id, -parsed_amount): |
| 888 | + participants[user.id] = user |
| 889 | + else: |
| 890 | + await ctx.send(f"{user.mention} couldn't join - transaction failed!") |
| 891 | + else: |
| 892 | + await ctx.send(f"{user.mention} couldn't join - insufficient funds!") |
| 893 | + |
| 894 | + if len(participants) == 1: |
| 895 | + # Refund the host if no one joined |
| 896 | + await self.db.update_wallet(ctx.author.id, parsed_amount) |
| 897 | + return await ctx.send(embed=discord.Embed( |
| 898 | + description=f"❌ Only {ctx.author.mention} joined. Refunded {parsed_amount}{self.currency}", |
| 899 | + color=discord.Color.red() |
| 900 | + )) |
| 901 | + |
| 902 | + pot = len(participants) * parsed_amount |
| 903 | + winner_id, winner = random.choice(list(participants.items())) |
| 904 | + |
| 905 | + # Calculate each participant's chance of winning |
| 906 | + win_chance = 100 / len(participants) |
| 907 | + |
| 908 | + # Award the pot to the winner |
| 909 | + if not await self.db.update_wallet(winner_id, pot): |
| 910 | + await ctx.send("❌ Failed to award the jackpot prize! Please contact an admin.") |
| 911 | + # Refund all participants |
| 912 | + for uid in participants: |
| 913 | + await self.db.update_wallet(uid, parsed_amount) |
| 914 | + return |
| 915 | + |
| 916 | + await ctx.send(embed=discord.Embed( |
| 917 | + description=( |
| 918 | + f"🎉 **JACKPOT RESULTS** 🎉\n" |
| 919 | + f"Entry Fee: **{parsed_amount}{self.currency}**\n" |
| 920 | + f"Total entries: **{len(participants)}**\n" |
| 921 | + f"Total pot: **{pot}{self.currency}**\n" |
| 922 | + f"Winner: {winner.mention} (had a **{win_chance:.1f}%** chance)\n\n" |
| 923 | + f"🏆 **{winner.display_name} takes {pot}{self.currency}!** 🏆" |
| 924 | + ), |
| 925 | + color=discord.Color.green() |
| 926 | + )) |
| 927 | + |
| 928 | + except discord.NotFound: |
| 929 | + await ctx.send(embed=discord.Embed( |
| 930 | + description="❌ Jackpot message was deleted. Refunding all participants...", |
| 931 | + color=discord.Color.red() |
| 932 | + )) |
| 933 | + # Refund all participants |
| 934 | + for uid in participants: |
| 935 | + await self.db.update_wallet(uid, parsed_amount) |
| 936 | + |
| 937 | + except Exception as e: |
| 938 | + self.bot.logger.error(f"Jackpot error: {e}") |
| 939 | + # Clean up and attempt to refund if something went wrong |
| 940 | + if ctx.channel.id in self.active_jackpots and parsed_amount in self.active_jackpots[ctx.channel.id]: |
| 941 | + del self.active_jackpots[ctx.channel.id][parsed_amount] |
| 942 | + if not self.active_jackpots[ctx.channel.id]: |
| 943 | + del self.active_jackpots[ctx.channel.id] |
| 944 | + |
| 945 | + await ctx.send("❌ An error occurred during the jackpot. Refunding participants...") |
| 946 | + await self.db.update_wallet(ctx.author.id, parsed_amount) |
| 947 | + |
788 | 948 | @commands.command(aliases=['cf']) |
789 | 949 | @commands.cooldown(1, 3, commands.BucketType.user) |
790 | 950 | async def coinflip(self, ctx, bet_amount: str, choice: str): |
|
0 commit comments