| """
|
| Engagement cog: Leveling, economy, tournaments, and minigames.
|
| Enhanced with rich emoji decorations, beautiful formatting, and multi-language support.
|
| """
|
|
|
| import datetime as dt
|
| import random
|
|
|
| import discord
|
| from discord.ext import commands
|
|
|
| from bot.theme import (
|
| fancy_header, NEON_CYAN, NEON_LIME, NEON_ORANGE, NEON_PURPLE, NEON_YELLOW, NEON_RED,
|
| progress_bar, panel_divider, xp_progress_bar, coin_display, rank_display,
|
| level_display, economy_embed, success_embed, error_embed, info_embed,
|
| leaderboard_embed, profile_embed, gaming_embed, tournament_embed,
|
| format_leaderboard, money_display, timestamp_display, double_line, triple_line,
|
| pick_neon_color, shimmer, idle_embed_for_guild
|
| )
|
| from bot.i18n import get_cmd_desc
|
| from bot.emojis import (
|
| ui, E_DIAMOND, E_STAR, E_CATJAM, E_ARROW_BLUE, E_ARROW_GREEN,
|
| E_ARROW_PINK, E_ARROW_PURPLE, E_CROWN, E_TROPHY, E_FIRE, E_SPARKLE,
|
| E_MONEY, E_GEM, E_ROCKET, E_HEART, E_STAR2, E_DIZZY, get_custom_emoji
|
| )
|
|
|
|
|
| class EconomyPanelView(discord.ui.View):
|
| def __init__(self, cog: "Engagement", guild_id: int) -> None:
|
| super().__init__(timeout=None)
|
| self.cog = cog
|
| self.guild_id = guild_id
|
| economy_emoji = get_custom_emoji("economy")
|
| admin_emoji = get_custom_emoji("admin")
|
| for item in self.children:
|
| if isinstance(item, discord.ui.Button):
|
| if item.custom_id == "economy:daily":
|
| item.emoji = economy_emoji
|
| item.row = 0
|
| elif item.custom_id == "economy:work":
|
| item.emoji = "💼"
|
| item.row = 0
|
| elif item.custom_id == "economy:bank":
|
| item.emoji = admin_emoji
|
| item.row = 0
|
| elif item.custom_id == "economy:refresh":
|
| item.row = 1
|
|
|
| async def on_error(self, interaction: discord.Interaction, error: Exception, item: discord.ui.Item) -> None:
|
| try:
|
| if not interaction.response.is_done():
|
| await interaction.response.send_message("⚠️ حدث خطأ في التفاعل، أعد المحاولة.", ephemeral=True)
|
| else:
|
| await interaction.followup.send("⚠️ حدث خطأ في التفاعل، أعد المحاولة.", ephemeral=True)
|
| except Exception:
|
| pass
|
|
|
| @discord.ui.button(label="Daily", emoji=ui("calendar"), style=discord.ButtonStyle.success, custom_id="economy:daily", row=0)
|
| async def daily_btn(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| if not interaction.guild:
|
| await interaction.response.send_message(await self.cog.bot.get_text(self.guild_id, "common.server_only"), ephemeral=True)
|
| return
|
|
|
| guild_id = interaction.guild.id
|
| _lang = await self.cog.bot.get_guild_language(guild_id)
|
| await interaction.response.defer(ephemeral=True)
|
|
|
| today = dt.datetime.utcnow().date().isoformat()
|
| row = await self.cog.bot.db.fetchone(
|
| "SELECT claimed_date FROM user_daily_claim WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| interaction.user.id,
|
| )
|
|
|
| if row and row[0] == today:
|
| msg = await self.cog.bot.get_text(guild_id, "economy.daily.already_claimed")
|
| await interaction.followup.send(msg, ephemeral=True)
|
| else:
|
| salary_row = await self.cog.bot.db.fetchone(
|
| "SELECT daily_min, daily_max FROM economy_salaries WHERE guild_id = ?",
|
| guild_id,
|
| )
|
| daily_min = salary_row[0] if salary_row else 100
|
| daily_max = salary_row[1] if salary_row else 250
|
|
|
| reward = random.randint(daily_min, daily_max)
|
| await self.cog._add_coins(guild_id, interaction.user.id, reward)
|
| await self.cog.bot.db.execute(
|
| "INSERT INTO user_daily_claim(guild_id, user_id, claimed_date) VALUES (?, ?, ?) "
|
| "ON CONFLICT(guild_id, user_id) DO UPDATE SET claimed_date = excluded.claimed_date",
|
| guild_id,
|
| interaction.user.id,
|
| today,
|
| )
|
| msg = await self.cog.bot.get_text(guild_id, "economy.daily.reward_received", reward=reward)
|
| await interaction.followup.send(msg, ephemeral=True)
|
|
|
| if interaction.message:
|
| embed = await self._build_embed(interaction)
|
| await interaction.message.edit(embed=embed, view=self)
|
|
|
| @discord.ui.button(label="Work", emoji=ui("briefcase"), style=discord.ButtonStyle.primary, custom_id="economy:work", row=0)
|
| async def work_btn(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| if not interaction.guild:
|
| await interaction.response.send_message(await self.cog.bot.get_text(self.guild_id, "common.server_only"), ephemeral=True)
|
| return
|
|
|
| guild_id = interaction.guild.id
|
| _lang = await self.cog.bot.get_guild_language(guild_id)
|
| await interaction.response.defer(ephemeral=True)
|
|
|
| row = await self.cog.bot.db.fetchone(
|
| "SELECT last_work FROM user_work_cooldown WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| interaction.user.id,
|
| )
|
| last = row[0] if row and row[0] else ""
|
| now = dt.datetime.utcnow()
|
| cooldown_minutes = 45
|
|
|
| if last:
|
| try:
|
| last_dt = dt.datetime.fromisoformat(last)
|
| remaining = (cooldown_minutes * 60) - (now - last_dt).total_seconds()
|
| if remaining > 0:
|
| mins = int(remaining / 60)
|
| msg = await self.cog.bot.get_text(guild_id, "economy.work.cooldown", mins=mins)
|
| await interaction.followup.send(msg, ephemeral=True)
|
| return
|
| except ValueError:
|
| pass
|
|
|
| salary_row = await self.cog.bot.db.fetchone(
|
| "SELECT min_salary, max_salary FROM economy_salaries WHERE guild_id = ?",
|
| guild_id,
|
| )
|
| min_sal = salary_row[0] if salary_row else 50
|
| max_sal = salary_row[1] if salary_row else 150
|
|
|
| reward = random.randint(min_sal, max_sal)
|
| await self.cog._add_coins(guild_id, interaction.user.id, reward)
|
| await self.cog.bot.db.execute(
|
| "INSERT INTO user_work_cooldown(guild_id, user_id, last_work) VALUES (?, ?, ?) "
|
| "ON CONFLICT(guild_id, user_id) DO UPDATE SET last_work = excluded.last_work",
|
| guild_id,
|
| interaction.user.id,
|
| now.isoformat(),
|
| )
|
|
|
| msg = await self.cog.bot.get_text(guild_id, "economy.work.earned", reward=reward)
|
| await interaction.followup.send(msg, ephemeral=True)
|
|
|
| if interaction.message:
|
| embed = await self._build_embed(interaction)
|
| await interaction.message.edit(embed=embed, view=self)
|
|
|
| @discord.ui.button(label="Bank", emoji=ui("bank"), style=discord.ButtonStyle.secondary, custom_id="economy:bank", row=0)
|
| async def bank_btn(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| if not interaction.guild:
|
| await interaction.response.send_message("Server only.", ephemeral=True)
|
| return
|
| guild_id = interaction.guild.id
|
| row = await self.cog.bot.db.fetchone(
|
| "SELECT wallet, bank FROM user_balance WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| interaction.user.id,
|
| )
|
| wallet, bank = row if row else (0, 0)
|
| msg = await self.cog.bot.get_text(guild_id, "economy.bank.snapshot", wallet=wallet, bank=bank)
|
| await interaction.response.send_message(msg, ephemeral=True)
|
|
|
| @discord.ui.button(label="Deposit", emoji=ui("moneybag"), style=discord.ButtonStyle.blurple, custom_id="economy:deposit", row=1)
|
| async def deposit_btn(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| if not interaction.guild:
|
| await interaction.response.send_message("Server only.", ephemeral=True)
|
| return
|
| guild_id = interaction.guild.id
|
| await interaction.response.send_modal(DepositModal(self.cog, guild_id, interaction.user.id))
|
|
|
| @discord.ui.button(label="Withdraw", emoji=ui("moneybag"), style=discord.ButtonStyle.blurple, custom_id="economy:withdraw", row=1)
|
| async def withdraw_btn(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| if not interaction.guild:
|
| await interaction.response.send_message("Server only.", ephemeral=True)
|
| return
|
| guild_id = interaction.guild.id
|
| await interaction.response.send_modal(WithdrawModal(self.cog, guild_id, interaction.user.id))
|
|
|
| @discord.ui.button(label="Gamble", emoji=ui("game"), style=discord.ButtonStyle.danger, custom_id="economy:gamble", row=1)
|
| async def gamble_btn(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| if not interaction.guild:
|
| await interaction.response.send_message("Server only.", ephemeral=True)
|
| return
|
| gambling_cog = interaction.client.get_cog("Gambling")
|
| if gambling_cog is None:
|
| guild_id = interaction.guild.id
|
| await interaction.response.send_modal(GambleModal(self.cog, guild_id, interaction.user.id))
|
| return
|
| from bot.cogs.gambling import GamblingPanelView
|
| embed = discord.Embed(
|
| title="Casino & Gambling Panel",
|
| description=(
|
| "Choose a game:\n\n"
|
| "Blackjack\n"
|
| "Roulette\n"
|
| "RPG Adventure"
|
| ),
|
| color=NEON_ORANGE,
|
| )
|
| await interaction.response.send_message(
|
| embed=embed,
|
| view=GamblingPanelView(gambling_cog, interaction.guild.id, interaction.user.id),
|
| ephemeral=True,
|
| )
|
|
|
| @discord.ui.button(label="Rob", emoji=ui("gun"), style=discord.ButtonStyle.danger, custom_id="economy:rob", row=1)
|
| async def rob_btn(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| if not interaction.guild:
|
| await interaction.response.send_message("Server only.", ephemeral=True)
|
| return
|
| guild_id = interaction.guild.id
|
| await interaction.response.send_modal(RobModal(self.cog, guild_id, interaction.user.id))
|
|
|
| @discord.ui.button(label="RPS", emoji="✂️", style=discord.ButtonStyle.primary, custom_id="economy:rps", row=1)
|
| async def rps_btn(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| if not interaction.guild:
|
| await interaction.response.send_message("Server only.", ephemeral=True)
|
| return
|
| guild_id = interaction.guild.id
|
| await interaction.response.send_message(
|
| "Choose your move:",
|
| view=RPSView(self.cog, guild_id, interaction.user.id),
|
| ephemeral=True,
|
| )
|
|
|
| @discord.ui.button(label="Leaderboard", emoji=ui("trophy"), style=discord.ButtonStyle.primary, custom_id="economy:leaderboard", row=2)
|
| async def leaderboard_btn(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| if not interaction.guild:
|
| await interaction.response.send_message("Server only.", ephemeral=True)
|
| return
|
| guild_id = interaction.guild.id
|
| await interaction.response.defer(ephemeral=True)
|
| rows = await self.cog.bot.db.fetchall(
|
| "SELECT user_id, wallet + bank AS total FROM user_balance WHERE guild_id = ? ORDER BY total DESC LIMIT 10",
|
| guild_id,
|
| )
|
| if not rows:
|
| await interaction.followup.send(
|
| embed=await idle_embed_for_guild(
|
| "Economy Leaderboard Idle",
|
| "No economy data is available yet.",
|
| guild=interaction.guild,
|
| bot=self.cog.bot,
|
| ),
|
| ephemeral=True,
|
| )
|
| return
|
| lines = []
|
| for rank, (uid, total) in enumerate(rows, 1):
|
| medal = {1: "🥇", 2: "🥈", 3: "🥉"}.get(rank, f"{rank}.")
|
| member = interaction.guild.get_member(uid)
|
| name = member.display_name if member else f"<@{uid}>"
|
| lines.append(f"{medal} **{name}** — `{total:,}` coins")
|
| embed = discord.Embed(
|
| title="🏆 Top 10 Richest",
|
| description="\n".join(lines),
|
| color=NEON_YELLOW,
|
| )
|
| await interaction.followup.send(embed=embed, ephemeral=True)
|
|
|
| @discord.ui.button(label="Refresh", emoji=ui("refresh"), style=discord.ButtonStyle.secondary, custom_id="economy:refresh", row=2)
|
| async def refresh_btn(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| if not interaction.guild or not interaction.message:
|
| return
|
| await interaction.response.defer(ephemeral=True)
|
| embed = await self._build_embed(interaction)
|
| await interaction.message.edit(embed=embed, view=self)
|
|
|
| guild_id = interaction.guild.id
|
| msg = await self.cog.bot.get_text(guild_id, "success.panel_refreshed")
|
| await interaction.followup.send(msg, ephemeral=True)
|
|
|
| async def _build_embed(self, interaction: discord.Interaction) -> discord.Embed:
|
| guild_id = interaction.guild.id
|
| _lang = await self.cog.bot.get_guild_language(guild_id)
|
|
|
| row = await self.cog.bot.db.fetchone(
|
| "SELECT wallet, bank FROM user_balance WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| interaction.user.id,
|
| )
|
| wallet, bank = row if row else (0, 0)
|
| xp_row = await self.cog.bot.db.fetchone(
|
| "SELECT xp, level FROM user_xp WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| interaction.user.id,
|
| )
|
| xp, level = xp_row if xp_row else (0, 1)
|
| target = self.cog.xp_target(level)
|
|
|
| divider = await self.cog.bot.get_text(guild_id, "panels.global.divider")
|
| bullet = await self.cog.bot.get_text(guild_id, "panels.global.bullet")
|
| title = await self.cog.bot.get_text(guild_id, "panels.economy.header")
|
| desc = (
|
| f"{divider}\n"
|
| f"{bullet} {await self.cog.bot.get_text(guild_id, 'economy.panel.line_one')}\n"
|
| f"{bullet} {await self.cog.bot.get_text(guild_id, 'economy.panel.line_two')}\n"
|
| f"{divider}"
|
| )
|
| wallet_label = await self.cog.bot.get_text(guild_id, "economy.labels.wallet")
|
| bank_label = await self.cog.bot.get_text(guild_id, "economy.labels.bank")
|
| level_label = await self.cog.bot.get_text(guild_id, "economy.labels.level")
|
| progress_label = await self.cog.bot.get_text(guild_id, "economy.labels.progress")
|
|
|
| embed = discord.Embed(
|
| title=f"{ui('economy')} {title}",
|
| description=desc,
|
| color=NEON_CYAN,
|
| )
|
| embed.add_field(name=f"💰 {wallet_label}", value=f"`{wallet:,}`", inline=True)
|
| embed.add_field(name=f"🏦 {bank_label}", value=f"`{bank:,}`", inline=True)
|
| embed.add_field(name=f"⭐ {level_label}", value=f"**{level}**", inline=True)
|
| embed.add_field(name=f"📊 XP", value=f"`{xp:,}/{target:,}`", inline=True)
|
| embed.add_field(name=f"📈 {progress_label}", value=xp_progress_bar(xp, target, level), inline=False)
|
|
|
| if interaction.guild:
|
| await add_banner_to_embed(embed, interaction.guild)
|
|
|
| embed.set_thumbnail(url=interaction.user.display_avatar.url)
|
| embed.set_footer(text=await self.cog.bot.get_text(guild_id, "economy.panel.footer"))
|
| return embed
|
|
|
|
|
| class DepositModal(discord.ui.Modal, title="💰 Deposit to Bank"):
|
| amount = discord.ui.TextInput(
|
| label="Amount (or 'all')",
|
| placeholder="500 or all",
|
| required=True,
|
| max_length=16,
|
| )
|
|
|
| def __init__(self, cog: "Engagement", guild_id: int, user_id: int) -> None:
|
| super().__init__(timeout=None)
|
| self.cog = cog
|
| self.guild_id = guild_id
|
| self.user_id = user_id
|
|
|
| async def on_submit(self, interaction: discord.Interaction) -> None:
|
| await interaction.response.defer(ephemeral=True)
|
| val = self.amount.value.strip().lower()
|
| row = await self.cog.bot.db.fetchone(
|
| "SELECT wallet, bank FROM user_balance WHERE guild_id = ? AND user_id = ?",
|
| self.guild_id, self.user_id,
|
| )
|
| wallet, bank = row if row else (0, 0)
|
| if val == "all":
|
| amt = wallet
|
| else:
|
| try:
|
| amt = int(val)
|
| except ValueError:
|
| await interaction.followup.send("Invalid amount.", ephemeral=True)
|
| return
|
| if amt <= 0:
|
| await interaction.followup.send("Amount must be positive.", ephemeral=True)
|
| return
|
| if amt > wallet:
|
| await interaction.followup.send("Not enough coins in wallet.", ephemeral=True)
|
| return
|
| await self.cog.bot.db.execute(
|
| "INSERT INTO user_balance(guild_id, user_id, wallet, bank) VALUES (?, ?, ?, ?) "
|
| "ON CONFLICT(guild_id, user_id) DO UPDATE SET wallet = wallet - ?, bank = bank + ?",
|
| self.guild_id, self.user_id, wallet - amt, bank + amt, amt, amt,
|
| )
|
| await interaction.followup.send(f"✅ Deposited `{amt:,}` coins to bank.", ephemeral=True)
|
|
|
|
|
| class WithdrawModal(discord.ui.Modal, title="💸 Withdraw from Bank"):
|
| amount = discord.ui.TextInput(
|
| label="Amount (or 'all')",
|
| placeholder="500 or all",
|
| required=True,
|
| max_length=16,
|
| )
|
|
|
| def __init__(self, cog: "Engagement", guild_id: int, user_id: int) -> None:
|
| super().__init__(timeout=None)
|
| self.cog = cog
|
| self.guild_id = guild_id
|
| self.user_id = user_id
|
|
|
| async def on_submit(self, interaction: discord.Interaction) -> None:
|
| await interaction.response.defer(ephemeral=True)
|
| val = self.amount.value.strip().lower()
|
| row = await self.cog.bot.db.fetchone(
|
| "SELECT wallet, bank FROM user_balance WHERE guild_id = ? AND user_id = ?",
|
| self.guild_id, self.user_id,
|
| )
|
| wallet, bank = row if row else (0, 0)
|
| if val == "all":
|
| amt = bank
|
| else:
|
| try:
|
| amt = int(val)
|
| except ValueError:
|
| await interaction.followup.send("Invalid amount.", ephemeral=True)
|
| return
|
| if amt <= 0:
|
| await interaction.followup.send("Amount must be positive.", ephemeral=True)
|
| return
|
| if amt > bank:
|
| await interaction.followup.send("Not enough coins in bank.", ephemeral=True)
|
| return
|
| await self.cog.bot.db.execute(
|
| "INSERT INTO user_balance(guild_id, user_id, wallet, bank) VALUES (?, ?, ?, ?) "
|
| "ON CONFLICT(guild_id, user_id) DO UPDATE SET wallet = wallet + ?, bank = bank - ?",
|
| self.guild_id, self.user_id, wallet + amt, bank - amt, amt, amt,
|
| )
|
| await interaction.followup.send(f"✅ Withdrew `{amt:,}` coins from bank.", ephemeral=True)
|
|
|
|
|
| class GambleModal(discord.ui.Modal, title="🎲 Gamble Coins"):
|
| amount = discord.ui.TextInput(
|
| label="Bet Amount",
|
| placeholder="100",
|
| required=True,
|
| max_length=16,
|
| )
|
|
|
| def __init__(self, cog: "Engagement", guild_id: int, user_id: int) -> None:
|
| super().__init__(timeout=None)
|
| self.cog = cog
|
| self.guild_id = guild_id
|
| self.user_id = user_id
|
|
|
| async def on_submit(self, interaction: discord.Interaction) -> None:
|
| await interaction.response.defer(ephemeral=True)
|
| try:
|
| amt = int(self.amount.value.strip())
|
| except ValueError:
|
| await interaction.followup.send("Invalid amount.", ephemeral=True)
|
| return
|
| if amt <= 0:
|
| await interaction.followup.send("Bet must be positive.", ephemeral=True)
|
| return
|
| row = await self.cog.bot.db.fetchone(
|
| "SELECT wallet, bank FROM user_balance WHERE guild_id = ? AND user_id = ?",
|
| self.guild_id, self.user_id,
|
| )
|
| wallet, bank = row if row else (0, 0)
|
| total = wallet + bank
|
| if amt > total:
|
| await interaction.followup.send("Not enough coins.", ephemeral=True)
|
| return
|
| win = random.random() < 0.45
|
| if win:
|
| gained = amt
|
| if wallet >= amt:
|
| await self.cog.bot.db.execute(
|
| "UPDATE user_balance SET wallet = wallet + ? WHERE guild_id = ? AND user_id = ?",
|
| gained, self.guild_id, self.user_id,
|
| )
|
| else:
|
| await self.cog.bot.db.execute(
|
| "UPDATE user_balance SET bank = bank + ? WHERE guild_id = ? AND user_id = ?",
|
| gained, self.guild_id, self.user_id,
|
| )
|
| await interaction.followup.send(f"🎉 You won `{gained:,}` coins!", ephemeral=True)
|
| else:
|
| lost = amt
|
| if wallet >= amt:
|
| await self.cog.bot.db.execute(
|
| "UPDATE user_balance SET wallet = wallet - ? WHERE guild_id = ? AND user_id = ?",
|
| lost, self.guild_id, self.user_id,
|
| )
|
| elif wallet > 0:
|
| from_wallet = wallet
|
| from_bank = amt - wallet
|
| await self.cog.bot.db.execute(
|
| "UPDATE user_balance SET wallet = 0, bank = bank - ? WHERE guild_id = ? AND user_id = ?",
|
| from_bank, self.guild_id, self.user_id,
|
| )
|
| else:
|
| await self.cog.bot.db.execute(
|
| "UPDATE user_balance SET bank = bank - ? WHERE guild_id = ? AND user_id = ?",
|
| lost, self.guild_id, self.user_id,
|
| )
|
| await interaction.followup.send(f"💀 You lost `{lost:,}` coins!", ephemeral=True)
|
|
|
|
|
| class RobModal(discord.ui.Modal, title="🔫 Rob a User"):
|
| target = discord.ui.TextInput(
|
| label="User ID or Mention (optional)",
|
| placeholder="Leave empty for random target",
|
| required=False,
|
| max_length=64,
|
| )
|
|
|
| def __init__(self, cog: "Engagement", guild_id: int, user_id: int) -> None:
|
| super().__init__(timeout=None)
|
| self.cog = cog
|
| self.guild_id = guild_id
|
| self.user_id = user_id
|
|
|
| async def on_submit(self, interaction: discord.Interaction) -> None:
|
| await interaction.response.defer(ephemeral=True)
|
| tid: int | None = None
|
| val = self.target.value.strip().replace("<", "").replace(">", "").replace("@", "").replace("!", "")
|
| if val:
|
| try:
|
| tid = int(val)
|
| except ValueError:
|
| await interaction.followup.send("Invalid user ID.", ephemeral=True)
|
| return
|
| elif interaction.guild:
|
| eligible = [
|
| m for m in interaction.guild.members
|
| if (not m.bot) and m.id != self.user_id and m.guild_permissions.send_messages
|
| ]
|
| if eligible:
|
| tid = random.choice(eligible).id
|
| if tid is None:
|
| await interaction.followup.send("No valid robbery target found.", ephemeral=True)
|
| return
|
| if tid == self.user_id:
|
| await interaction.followup.send("You can't rob yourself!", ephemeral=True)
|
| return
|
| row = await self.cog.bot.db.fetchone(
|
| "SELECT wallet, bank FROM user_balance WHERE guild_id = ? AND user_id = ?",
|
| self.guild_id, tid,
|
| )
|
| victim_wallet, victim_bank = row if row else (0, 0)
|
| if victim_wallet <= 0:
|
| await interaction.followup.send("That user has no coins in wallet to rob!", ephemeral=True)
|
| return
|
| steal = random.randint(1, victim_wallet)
|
| success = random.random() < 0.5
|
| if success:
|
| await self.cog.bot.db.execute(
|
| "UPDATE user_balance SET wallet = wallet - ? WHERE guild_id = ? AND user_id = ?",
|
| steal, self.guild_id, tid,
|
| )
|
| await self.cog.bot.db.execute(
|
| "UPDATE user_balance SET wallet = wallet + ? WHERE guild_id = ? AND user_id = ?",
|
| steal, self.guild_id, self.user_id,
|
| )
|
| await interaction.followup.send(f"🎉 You robbed `{steal:,}` coins!", ephemeral=True)
|
| else:
|
| fine = min(victim_wallet, random.randint(10, 100))
|
| await self.cog.bot.db.execute(
|
| "UPDATE user_balance SET wallet = wallet - ? WHERE guild_id = ? AND user_id = ?",
|
| fine, self.guild_id, self.user_id,
|
| )
|
| await interaction.followup.send(f"🚔 You got caught and paid `{fine:,}` coins fine!", ephemeral=True)
|
|
|
|
|
| class RPSView(discord.ui.View):
|
| def __init__(self, cog: "Engagement", guild_id: int, user_id: int) -> None:
|
| super().__init__(timeout=60)
|
| self.cog = cog
|
| self.guild_id = guild_id
|
| self.user_id = user_id
|
|
|
| @discord.ui.button(label="Rock 🪨", style=discord.ButtonStyle.primary)
|
| async def rock(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| await self._resolve(interaction, "rock")
|
|
|
| @discord.ui.button(label="Paper 📄", style=discord.ButtonStyle.primary)
|
| async def paper(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| await self._resolve(interaction, "paper")
|
|
|
| @discord.ui.button(label="Scissors ✂️", style=discord.ButtonStyle.primary)
|
| async def scissors(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| await self._resolve(interaction, "scissors")
|
|
|
| async def _resolve(self, interaction: discord.Interaction, choice: str) -> None:
|
| if interaction.user.id != self.user_id:
|
| await interaction.response.send_message("This isn't your game!", ephemeral=True)
|
| return
|
| bot_choice = random.choice(["rock", "paper", "scissors"])
|
| emoji_map = {"rock": "🪨", "paper": "📄", "scissors": "✂️"}
|
| result_map = {
|
| ("rock", "scissors"): "win",
|
| ("paper", "rock"): "win",
|
| ("scissors", "paper"): "win",
|
| }
|
| key = (choice, bot_choice)
|
| if choice == bot_choice:
|
| result = "tie"
|
| else:
|
| result = result_map.get(key, "lose")
|
| reward = 0
|
| if result == "win":
|
| reward = random.randint(10, 50)
|
| await self.cog.bot.db.execute(
|
| "UPDATE user_balance SET wallet = wallet + ? WHERE guild_id = ? AND user_id = ?",
|
| reward, self.guild_id, self.user_id,
|
| )
|
| msg = f"You: {emoji_map[choice]} | Bot: {emoji_map[bot_choice]}\n"
|
| if result == "win":
|
| msg += f"🎉 You win `{reward:,}` coins!"
|
| elif result == "lose":
|
| msg += "💀 You lose!"
|
| else:
|
| msg += "🤝 It's a tie!"
|
| await interaction.response.send_message(msg, ephemeral=True)
|
| self.stop()
|
|
|
|
|
| class TournamentJoinView(discord.ui.View):
|
| def __init__(self, cog: "Engagement", guild_id: int, name: str) -> None:
|
| super().__init__(timeout=None)
|
| self.cog = cog
|
| self.guild_id = guild_id
|
| self.name = name
|
|
|
| async def on_error(self, interaction: discord.Interaction, error: Exception, item: discord.ui.Item) -> None:
|
| try:
|
| if not interaction.response.is_done():
|
| await interaction.response.send_message("⚠️ Interaction failed, please retry.", ephemeral=True)
|
| else:
|
| await interaction.followup.send("⚠️ Interaction failed, please retry.", ephemeral=True)
|
| except Exception:
|
| pass
|
|
|
| @discord.ui.button(label="Join Tournament", emoji=ui("flag"), style=discord.ButtonStyle.success, custom_id="tournament:join")
|
| async def join(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| if not interaction.guild:
|
| await interaction.response.send_message("Server only.", ephemeral=True)
|
| return
|
|
|
| lang = await self.cog.bot.get_guild_language(self.guild_id)
|
| await self.cog.bot.db.execute(
|
| "INSERT OR IGNORE INTO tournament_participants(guild_id, tournament_name, user_id) VALUES (?, ?, ?)",
|
| self.guild_id,
|
| self.name,
|
| interaction.user.id,
|
| )
|
|
|
| if lang == "ar":
|
| msg = f"✅ انضممت إلى **{self.name}**! {E_TROPHY}"
|
| else:
|
| msg = f"✅ You joined **{self.name}**! {E_TROPHY}"
|
| await interaction.response.send_message(msg, ephemeral=True)
|
|
|
| @discord.ui.button(label="Tournament Info", emoji=ui("game"), style=discord.ButtonStyle.secondary, custom_id="tournament:info")
|
| async def info(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
| lang = await self.cog.bot.get_guild_language(self.guild_id)
|
|
|
| games_rows = await self.cog.bot.db.fetchall(
|
| "SELECT game_name FROM tournament_games WHERE guild_id = ? AND tournament_name = ? ORDER BY game_name ASC",
|
| self.guild_id,
|
| self.name,
|
| )
|
| players_rows = await self.cog.bot.db.fetchall(
|
| "SELECT user_id FROM tournament_participants WHERE guild_id = ? AND tournament_name = ?",
|
| self.guild_id,
|
| self.name,
|
| )
|
| games = ", ".join(game for (game,) in games_rows) if games_rows else ("لم تحدد" if lang == "ar" else "Not set")
|
| status_row = await self.cog.bot.db.fetchone(
|
| "SELECT status FROM tournaments WHERE guild_id = ? AND name = ?",
|
| self.guild_id,
|
| self.name,
|
| )
|
| status = status_row[0] if status_row and status_row[0] else "open"
|
|
|
| if lang == "ar":
|
| title = f"🏆 **{self.name}**"
|
| desc = (
|
| f"{panel_divider('lime')}\n"
|
| f"📊 الحالة: **{status}**\n"
|
| f"👥 اللاعبين: **{len(players_rows)}**\n"
|
| f"🎮 الألعاب: {games}"
|
| )
|
| else:
|
| title = f"🏆 **{self.name}**"
|
| desc = (
|
| f"{panel_divider('lime')}\n"
|
| f"📊 Status: **{status}**\n"
|
| f"👥 Players: **{len(players_rows)}**\n"
|
| f"🎮 Games: {games}"
|
| )
|
| await interaction.response.send_message(f"{title}\n{desc}", ephemeral=True)
|
|
|
|
|
| class Engagement(commands.Cog):
|
| """Leveling and economy with beautiful panels and multi-language support."""
|
|
|
| def __init__(self, bot: commands.Bot) -> None:
|
| self.bot = bot
|
|
|
| async def cog_load(self) -> None:
|
| self.bot.add_view(EconomyPanelView(self, 0))
|
| self.bot.add_view(TournamentJoinView(self, 0, ""))
|
|
|
| def xp_target(self, level: int) -> int:
|
| return max(150, level * 150)
|
|
|
| async def _add_coins(self, guild_id: int, user_id: int, amount: int) -> None:
|
| if amount <= 0:
|
| return
|
| await self.bot.db.execute(
|
| "INSERT INTO user_balance(guild_id, user_id, wallet, bank) VALUES (?, ?, ?, 0) "
|
| "ON CONFLICT(guild_id, user_id) DO UPDATE SET wallet = wallet + excluded.wallet",
|
| guild_id,
|
| user_id,
|
| amount,
|
| )
|
|
|
| @commands.Cog.listener()
|
| async def on_message(self, message: discord.Message) -> None:
|
| if message.author.bot or not message.guild:
|
| return
|
|
|
| gained = random.randint(5, 15)
|
| row = await self.bot.db.fetchone(
|
| "SELECT xp, level FROM user_xp WHERE guild_id = ? AND user_id = ?",
|
| message.guild.id,
|
| message.author.id,
|
| )
|
| if not row:
|
| xp, level = 0, 1
|
| await self.bot.db.execute(
|
| "INSERT INTO user_xp(guild_id, user_id, xp, level) VALUES (?, ?, ?, ?)",
|
| message.guild.id,
|
| message.author.id,
|
| xp,
|
| level,
|
| )
|
| else:
|
| xp, level = row
|
|
|
| xp += gained
|
| target = self.xp_target(level)
|
| if xp >= target:
|
| level += 1
|
| lang = await self.bot.get_guild_language(message.guild.id)
|
| if lang == "ar":
|
| await message.channel.send(f"🎉 {message.author.mention} وصل إلى **المستوى {level}**! {E_STAR2}")
|
| else:
|
| await message.channel.send(f"🎉 {message.author.mention} reached **Level {level}**! {E_STAR2}")
|
|
|
| await self.bot.db.execute(
|
| "UPDATE user_xp SET xp = ?, level = ? WHERE guild_id = ? AND user_id = ?",
|
| xp,
|
| level,
|
| message.guild.id,
|
| message.author.id,
|
| )
|
|
|
| @commands.hybrid_command(name="rank", description=get_cmd_desc("commands.economy.rank_desc"))
|
| async def rank(self, ctx: commands.Context, member: discord.Member | None = None) -> None:
|
| member = member or ctx.author
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| row = await self.bot.db.fetchone(
|
| "SELECT xp, level FROM user_xp WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| member.id,
|
| )
|
| if not row:
|
| if lang == "ar":
|
| await ctx.reply(embed=await idle_embed_for_guild(
|
| "حالة الرتبة الخاملة",
|
| "لا توجد بيانات مستوى بعد. ابدأ الدردشة لكسب XP!",
|
| guild=ctx.guild,
|
| bot=self.bot,
|
| ))
|
| else:
|
| await ctx.reply(embed=await idle_embed_for_guild(
|
| "Rank Idle",
|
| "No rank data yet. Start chatting to earn XP!",
|
| guild=ctx.guild,
|
| bot=self.bot,
|
| ))
|
| return
|
|
|
| xp, level = row
|
| target = self.xp_target(level)
|
|
|
| if lang == "ar":
|
| title = f"{E_STAR} مستوى {member.display_name}"
|
| desc = (
|
| f"{panel_divider('purple')}\n"
|
| f"{level_display(level)}\n"
|
| f"📈 الخبرة: `{xp:,}/{target:,}`\n"
|
| f"📊 التقدم: {xp_progress_bar(xp, target, level)}\n"
|
| f"{panel_divider('purple')}"
|
| )
|
| else:
|
| title = f"{E_STAR} {member.display_name}'s Rank"
|
| desc = (
|
| f"{panel_divider('purple')}\n"
|
| f"{level_display(level)}\n"
|
| f"📈 XP: `{xp:,}/{target:,}`\n"
|
| f"📊 Progress: {xp_progress_bar(xp, target, level)}\n"
|
| f"{panel_divider('purple')}"
|
| )
|
|
|
| embed = discord.Embed(
|
| title=title,
|
| description=desc,
|
| color=NEON_PURPLE,
|
| )
|
| embed.set_thumbnail(url=member.display_avatar.url)
|
| await ctx.reply(embed=embed)
|
|
|
| @commands.hybrid_command(name="leaderboard", description=get_cmd_desc("commands.economy.leaderboard_desc"))
|
| async def leaderboard(self, ctx: commands.Context) -> None:
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| rows = await self.bot.db.fetchall(
|
| "SELECT user_id, SUM(wallet + bank) AS total FROM user_balance GROUP BY user_id ORDER BY total DESC LIMIT 10"
|
| )
|
| if not rows:
|
| if lang == "ar":
|
| await ctx.reply(embed=await idle_embed_for_guild(
|
| "اللوحة الخاملة",
|
| "لا توجد بيانات بعد. ابدأ الدردشة لكسب XP!",
|
| guild=ctx.guild,
|
| bot=self.bot,
|
| ))
|
| else:
|
| await ctx.reply(embed=await idle_embed_for_guild(
|
| "Leaderboard Idle",
|
| "No data yet. Start chatting to earn XP!",
|
| guild=ctx.guild,
|
| bot=self.bot,
|
| ))
|
| return
|
|
|
| lines = []
|
| for index, (uid, total) in enumerate(rows, start=1):
|
| user = ctx.guild.get_member(uid) if ctx.guild else None
|
| name = user.display_name if user else f"User {uid}"
|
| badge = {1: "🥇", 2: "🥈", 3: "🥉"}.get(index, f"#{index}")
|
| if lang == "ar":
|
| lines.append(f"{badge} **{name}** — {money_display(int(total or 0))}")
|
| else:
|
| lines.append(f"{badge} **{name}** — {money_display(int(total or 0))}")
|
|
|
| if lang == "ar":
|
| title = "🏆 لوحة الاقتصاد العالمية"
|
| else:
|
| title = "🏆 Global Economy Leaderboard"
|
|
|
| embed = discord.Embed(
|
| title=title,
|
| description=f"{panel_divider('yellow')}\n" + "\n".join(lines) + f"\n{panel_divider('yellow')}",
|
| color=NEON_YELLOW,
|
| )
|
| await ctx.reply(embed=embed)
|
|
|
| @commands.hybrid_command(name="daily", description=get_cmd_desc("commands.economy.daily_desc"))
|
| async def daily(self, ctx: commands.Context) -> None:
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| today = dt.datetime.utcnow().date().isoformat()
|
| row = await self.bot.db.fetchone(
|
| "SELECT claimed_date FROM user_daily_claim WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| ctx.author.id,
|
| )
|
| if row and row[0] == today:
|
| if lang == "ar":
|
| await ctx.reply("🌅 لقد استلمت مكافأتك اليومية بالفعل. ارجع غدًا!")
|
| else:
|
| await ctx.reply("🌅 You already claimed your daily reward! Come back tomorrow.")
|
| return
|
|
|
|
|
| salary_row = await self.bot.db.fetchone(
|
| "SELECT daily_min, daily_max FROM economy_salaries WHERE guild_id = ?",
|
| guild_id,
|
| )
|
| daily_min = salary_row[0] if salary_row else 100
|
| daily_max = salary_row[1] if salary_row else 250
|
|
|
| reward = random.randint(daily_min, daily_max)
|
| await self._add_coins(guild_id, ctx.author.id, reward)
|
| await self.bot.db.execute(
|
| "INSERT INTO user_daily_claim(guild_id, user_id, claimed_date) VALUES (?, ?, ?) "
|
| "ON CONFLICT(guild_id, user_id) DO UPDATE SET claimed_date = excluded.claimed_date",
|
| guild_id,
|
| ctx.author.id,
|
| today,
|
| )
|
|
|
| if lang == "ar":
|
| embed = success_embed("مكافأة يومية!", f"{E_MONEY} حصلت على **{reward}** عملة!")
|
| else:
|
| embed = success_embed("Daily Reward Claimed!", f"{E_MONEY} You received **{reward}** coins!")
|
| await ctx.reply(embed=embed)
|
|
|
| @commands.hybrid_command(name="balance", description=get_cmd_desc("commands.economy.balance_desc"))
|
| async def balance(self, ctx: commands.Context, member: discord.Member | None = None) -> None:
|
| member = member or ctx.author
|
| guild_id = ctx.guild.id
|
|
|
| row = await self.bot.db.fetchone(
|
| "SELECT wallet, bank FROM user_balance WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| member.id,
|
| )
|
| wallet, bank = row if row else (0, 0)
|
|
|
| embed = discord.Embed(
|
| title=f"{E_GEM} {member.display_name}'s Balance",
|
| description=panel_divider('cyan'),
|
| color=NEON_CYAN,
|
| )
|
| embed.add_field(name="💰 Wallet", value=money_display(wallet), inline=True)
|
| embed.add_field(name="💳 Bank", value=money_display(bank), inline=True)
|
| embed.add_field(name="🧾 Net Worth", value=money_display(wallet + bank), inline=True)
|
| embed.set_thumbnail(url=member.display_avatar.url)
|
| embed.set_footer(text="Use /deposit and /withdraw to manage funds.")
|
| await ctx.reply(embed=embed)
|
|
|
| @commands.hybrid_command(name="economy_admin", hidden=True, with_app_command=False, description=get_cmd_desc("commands.economy.economy_admin_desc"))
|
| @commands.has_permissions(administrator=True)
|
| async def economy_admin(
|
| self,
|
| ctx: commands.Context,
|
| action: str,
|
| member: discord.Member,
|
| amount: int,
|
| ) -> None:
|
| """Hidden admin command to add/remove funds from a user wallet."""
|
| if action.lower() not in {"add", "remove"}:
|
| await ctx.reply("Use `add` or `remove`.")
|
| return
|
| delta = amount if action.lower() == "add" else -amount
|
| await self._add_coins(ctx.guild.id, member.id, delta)
|
| await ctx.reply(f"✅ {action.title()} {money_display(amount)} for {member.mention}.")
|
|
|
| @commands.hybrid_command(name="work", description=get_cmd_desc("commands.economy.work_desc"))
|
| async def work(self, ctx: commands.Context) -> None:
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| row = await self.bot.db.fetchone(
|
| "SELECT last_work FROM user_work_cooldown WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| ctx.author.id,
|
| )
|
| last = row[0] if row and row[0] else ""
|
| now = dt.datetime.utcnow()
|
| cooldown_minutes = 45
|
|
|
| if last:
|
| try:
|
| last_dt = dt.datetime.fromisoformat(last)
|
| remaining = (cooldown_minutes * 60) - (now - last_dt).total_seconds()
|
| if remaining > 0:
|
| mins = int(remaining / 60)
|
| if lang == "ar":
|
| await ctx.reply(f"⏳ ارجع بعد **{mins}** دقيقة.")
|
| else:
|
| await ctx.reply(f"⏳ Come back in **{mins}** minutes.")
|
| return
|
| except ValueError:
|
| pass
|
|
|
|
|
| salary_row = await self.bot.db.fetchone(
|
| "SELECT min_salary, max_salary FROM economy_salaries WHERE guild_id = ?",
|
| guild_id,
|
| )
|
| min_sal = salary_row[0] if salary_row else 50
|
| max_sal = salary_row[1] if salary_row else 150
|
|
|
| reward = random.randint(min_sal, max_sal)
|
| await self._add_coins(guild_id, ctx.author.id, reward)
|
| await self.bot.db.execute(
|
| "INSERT INTO user_work_cooldown(guild_id, user_id, last_work) VALUES (?, ?, ?) "
|
| "ON CONFLICT(guild_id, user_id) DO UPDATE SET last_work = excluded.last_work",
|
| guild_id,
|
| ctx.author.id,
|
| now.isoformat(),
|
| )
|
|
|
| if lang == "ar":
|
| embed = success_embed("تم العمل!", f"💼 عملت بجد وحصلت على **{reward}** عملة! {E_MONEY}")
|
| else:
|
| embed = success_embed("Work Complete!", f"💼 You worked hard and earned **{reward}** coins! {E_MONEY}")
|
| await ctx.reply(embed=embed)
|
|
|
| async def gamble(self, ctx: commands.Context, bet: int) -> None:
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| bet = max(1, bet)
|
| row = await self.bot.db.fetchone(
|
| "SELECT wallet FROM user_balance WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| ctx.author.id,
|
| )
|
| wallet = row[0] if row else 0
|
| if bet < 50:
|
| if lang == "ar":
|
| await ctx.reply("❌ الحد الأدنى للرهان **50** عملة.")
|
| else:
|
| await ctx.reply("❌ Minimum bet is **50** coins.")
|
| return
|
| if wallet < bet:
|
| if lang == "ar":
|
| await ctx.reply(f"❌ لديك فقط **{wallet}** عملة.")
|
| else:
|
| await ctx.reply(f"❌ You only have **{wallet}** coins.")
|
| return
|
|
|
| win = random.random() < 0.45
|
| change = bet if win else -bet
|
| await self._add_coins(guild_id, ctx.author.id, change)
|
|
|
| if win:
|
| if lang == "ar":
|
| embed = success_embed("🎰 فوز!", f"{E_FIRE} راهنت **{bet}** و **فزت!**\n\n💰 **+{bet}** عملة!")
|
| else:
|
| embed = success_embed("🎰 JACKPOT!", f"{E_FIRE} You bet **{bet}** and **WON!**\n\n💰 **+{bet}** coins!")
|
| else:
|
| if lang == "ar":
|
| embed = error_embed("🎰 حظ أوفر!", f"💀 راهنت **{bet}** وخسرت.\n\n💰 **-{bet}** عملة")
|
| else:
|
| embed = error_embed("🎰 Better Luck Next Time!", f"💀 You bet **{bet}** and lost.\n\n💰 **-{bet}** coins")
|
| await ctx.reply(embed=embed)
|
|
|
| @commands.hybrid_command(name="rob", description=get_cmd_desc("commands.economy.rob_desc"), hidden=True, with_app_command=False)
|
| async def rob(self, ctx: commands.Context, target: discord.Member | None = None) -> None:
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| if target is None:
|
| eligible = [
|
| member for member in ctx.guild.members
|
| if (not member.bot) and member.id != ctx.author.id and member.guild_permissions.send_messages
|
| ]
|
| target = random.choice(eligible) if eligible else None
|
| if target is None:
|
| await ctx.reply("❌ No valid robbery targets found.")
|
| return
|
|
|
| if target.id == ctx.author.id or target.bot:
|
| if lang == "ar":
|
| await ctx.reply("❌ هدف غير صالح.")
|
| else:
|
| await ctx.reply("❌ Invalid target.")
|
| return
|
|
|
| row = await self.bot.db.fetchone(
|
| "SELECT wallet FROM user_balance WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| target.id,
|
| )
|
| target_wallet = row[0] if row else 0
|
| if target_wallet < 100:
|
| if lang == "ar":
|
| await ctx.reply("❌ الهدف لديه أقل من **100** عملة، لا يستحق.")
|
| else:
|
| await ctx.reply("❌ Target has less than **100** coins, not worth it.")
|
| return
|
|
|
| success = random.random() < 0.40
|
| if success:
|
| stolen = random.randint(50, min(target_wallet // 2, 500))
|
| await self.bot.db.execute(
|
| "INSERT INTO user_balance(guild_id, user_id, wallet, bank) VALUES (?, ?, 0, 0) "
|
| "ON CONFLICT(guild_id, user_id) DO UPDATE SET wallet = wallet - ?",
|
| guild_id,
|
| target.id,
|
| stolen,
|
| )
|
| await self._add_coins(guild_id, ctx.author.id, stolen)
|
| if lang == "ar":
|
| embed = success_embed("🦝 سرقة ناجحة!", f"سرقت **{stolen}** عملة من {target.mention}! {E_FIRE}")
|
| else:
|
| embed = success_embed("🦝 Heist Successful!", f"You stole **{stolen}** coins from {target.mention}! {E_FIRE}")
|
| else:
|
| fine = random.randint(50, 200)
|
| await self._add_coins(guild_id, ctx.author.id, -fine)
|
| if lang == "ar":
|
| embed = error_embed("🚨 تم القبض عليك!", f"تم القبض عليك وأنت تحاول سرقة {target.mention}!\n\n💸 دفعت **{fine}** عملة كغرامة.")
|
| else:
|
| embed = error_embed("🚨 BUSTED!", f"You got caught trying to rob {target.mention}!\n\n💸 Paid **{fine}** coins as a fine.")
|
| await ctx.reply(embed=embed)
|
|
|
| @commands.hybrid_group(name="economy", fallback="panel", description="Economy panel with quick actions")
|
| async def economy(self, ctx: commands.Context) -> None:
|
| if ctx.interaction and not ctx.interaction.response.is_done():
|
| await ctx.interaction.response.defer()
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| row = await self.bot.db.fetchone(
|
| "SELECT wallet, bank FROM user_balance WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| ctx.author.id,
|
| )
|
| wallet, bank = row if row else (0, 0)
|
| xp_row = await self.bot.db.fetchone(
|
| "SELECT xp, level FROM user_xp WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| ctx.author.id,
|
| )
|
| xp, level = xp_row if xp_row else (0, 1)
|
| target = self.xp_target(level)
|
|
|
| divider = await self.bot.get_text(guild_id, "panels.global.divider")
|
| bullet = await self.bot.get_text(guild_id, "panels.global.bullet")
|
| title = await self.bot.get_text(guild_id, "panels.economy.header")
|
| desc = (
|
| f"{divider}\n"
|
| f"{bullet} {await self.bot.get_text(guild_id, 'economy.panel.line_one')}\n"
|
| f"{bullet} {await self.bot.get_text(guild_id, 'economy.panel.line_two')}\n"
|
| f"{divider}"
|
| )
|
| wallet_label = await self.bot.get_text(guild_id, "economy.labels.wallet")
|
| bank_label = await self.bot.get_text(guild_id, "economy.labels.bank")
|
| level_label = await self.bot.get_text(guild_id, "economy.labels.level")
|
| progress_label = await self.bot.get_text(guild_id, "economy.labels.progress")
|
| footer = await self.bot.get_text(guild_id, "economy.panel.footer")
|
|
|
| embed = discord.Embed(
|
| title=f"{get_custom_emoji('economy')} {await self.bot.get_text(guild_id, 'panels.global.prefix')} {title}",
|
| description=desc,
|
| color=NEON_CYAN,
|
| )
|
| embed.add_field(name=wallet_label, value=f"`{wallet:,}`", inline=True)
|
| embed.add_field(name=bank_label, value=f"`{bank:,}`", inline=True)
|
| embed.add_field(name=level_label, value=f"**{level}** (`{xp:,}/{target:,}`)", inline=True)
|
| embed.add_field(name=progress_label, value=xp_progress_bar(xp, target, level), inline=False)
|
| embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
| embed.set_footer(text=footer)
|
| if ctx.interaction:
|
| await ctx.interaction.followup.send(embed=embed, view=EconomyPanelView(self, guild_id), ephemeral=True)
|
| else:
|
| await ctx.reply(embed=embed, view=EconomyPanelView(self, guild_id))
|
|
|
| @economy.command(name="deposit", description="Move coins from wallet to bank")
|
| async def economy_deposit(self, ctx: commands.Context, amount: int) -> None:
|
| await self.deposit(ctx, amount)
|
|
|
| @economy.command(name="withdraw", description="Move coins from bank to wallet")
|
| async def economy_withdraw(self, ctx: commands.Context, amount: int) -> None:
|
| await self.withdraw(ctx, amount)
|
|
|
| @economy.command(name="gamble", description="Gamble your coins")
|
| async def economy_gamble(self, ctx: commands.Context, bet: int) -> None:
|
| await self.gamble(ctx, bet)
|
|
|
| @economy.command(name="rob", description="Attempt to rob another user")
|
| async def economy_rob(self, ctx: commands.Context, target: discord.Member | None = None) -> None:
|
| await self.rob(ctx, target)
|
|
|
| @commands.hybrid_command(name="profile", description=get_cmd_desc("commands.economy.profile_desc"))
|
| async def profile(self, ctx: commands.Context, member: discord.Member | None = None) -> None:
|
| member = member or ctx.author
|
| guild_id = ctx.guild.id
|
|
|
| xp_row = await self.bot.db.fetchone(
|
| "SELECT xp, level FROM user_xp WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| member.id,
|
| )
|
| bal_row = await self.bot.db.fetchone(
|
| "SELECT wallet, bank FROM user_balance WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| member.id,
|
| )
|
| xp, level = xp_row if xp_row else (0, 1)
|
| wallet, bank = bal_row if bal_row else (0, 0)
|
| target = self.xp_target(level)
|
|
|
| embed = discord.Embed(
|
| title=f"👤 {member.display_name}'s Profile",
|
| description=panel_divider('purple'),
|
| color=NEON_PURPLE,
|
| )
|
| embed.add_field(name=f"{E_STAR} Level", value=level_display(level), inline=True)
|
| embed.add_field(name="📈 XP", value=f"`{xp:,}/{target:,}`", inline=True)
|
| embed.add_field(name="📊 Progress", value=xp_progress_bar(xp, target, level), inline=False)
|
| embed.add_field(name="💰 Wallet", value=f"`{wallet:,}`", inline=True)
|
| embed.add_field(name="💳 Bank", value=f"`{bank:,}`", inline=True)
|
| embed.set_thumbnail(url=member.display_avatar.url)
|
| embed.set_footer(text="Unified profile card • Economy + Leveling")
|
| await ctx.reply(embed=embed)
|
|
|
|
|
|
|
|
|
|
|
| @commands.hybrid_group(name="econadmin", description="Economy management commands")
|
| @commands.has_permissions(manage_guild=True)
|
| async def econadmin(self, ctx: commands.Context) -> None:
|
| """Economy management commands for admins."""
|
| if ctx.invoked_subcommand is None:
|
| guild_id = ctx.guild.id if ctx.guild else None
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| if lang == "ar":
|
| desc = (
|
| f"{panel_divider('cyan')}\n"
|
| f"💰 `/econadmin setbalance @user <amount>` - تعيين رصيد المستخدم\n"
|
| f"💎 `/econadmin addcoins @user <amount>` - إضافة عملات\n"
|
| f"💸 `/econadmin removecoins @user <amount>` - إزالة عملات\n"
|
| f"💼 `/econadmin salary <min> <max>` - تعيين رواتب العمل\n"
|
| f"🌅 `/econadmin daily <min> <max>` - تعيين مكافآت يومية\n"
|
| f"{panel_divider('cyan')}"
|
| )
|
| else:
|
| desc = (
|
| f"{panel_divider('cyan')}\n"
|
| f"💰 `/econadmin setbalance @user <amount>` - Set user balance\n"
|
| f"💎 `/econadmin addcoins @user <amount>` - Add coins to user\n"
|
| f"💸 `/econadmin removecoins @user <amount>` - Remove coins from user\n"
|
| f"💼 `/econadmin salary <min> <max>` - Set work salary range\n"
|
| f"🌅 `/econadmin daily <min> <max>` - Set daily reward range\n"
|
| f"{panel_divider('cyan')}"
|
| )
|
|
|
| embed = discord.Embed(
|
| title=f"⚙️ {'إدارة الاقتصاد' if lang == 'ar' else 'Economy Management'}",
|
| description=desc,
|
| color=NEON_CYAN,
|
| )
|
| await ctx.reply(embed=embed)
|
|
|
| @econadmin.command(name="setbalance")
|
| @commands.has_permissions(manage_guild=True)
|
| async def setbalance(self, ctx: commands.Context, member: discord.Member, amount: int) -> None:
|
| """Set a user's wallet balance."""
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| amount = max(0, amount)
|
| await self.bot.db.execute(
|
| "INSERT INTO user_balance(guild_id, user_id, wallet, bank) VALUES (?, ?, ?, 0) "
|
| "ON CONFLICT(guild_id, user_id) DO UPDATE SET wallet = excluded.wallet",
|
| guild_id,
|
| member.id,
|
| amount,
|
| )
|
|
|
| if lang == "ar":
|
| embed = success_embed("💰 تم تعيين الرصيد", f"تم تعيين رصيد {member.mention} إلى **{amount:,}** عملة.")
|
| else:
|
| embed = success_embed("💰 Balance Set", f"Set {member.mention}'s wallet to **{amount:,}** coins.")
|
| await ctx.reply(embed=embed)
|
|
|
| @econadmin.command(name="addcoins")
|
| @commands.has_permissions(manage_guild=True)
|
| async def addcoins(self, ctx: commands.Context, member: discord.Member, amount: int) -> None:
|
| """Add coins to a user's wallet."""
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| amount = max(1, amount)
|
| await self._add_coins(guild_id, member.id, amount)
|
|
|
| if lang == "ar":
|
| embed = success_embed("💎 عملات مضافة", f"تمت إضافة **{amount:,}** عملة إلى {member.mention}.")
|
| else:
|
| embed = success_embed("💎 Coins Added", f"Added **{amount:,}** coins to {member.mention}.")
|
| await ctx.reply(embed=embed)
|
|
|
| @econadmin.command(name="removecoins")
|
| @commands.has_permissions(manage_guild=True)
|
| async def removecoins(self, ctx: commands.Context, member: discord.Member, amount: int) -> None:
|
| """Remove coins from a user's wallet."""
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| amount = max(1, amount)
|
| row = await self.bot.db.fetchone(
|
| "SELECT wallet FROM user_balance WHERE guild_id = ? AND user_id = ?",
|
| guild_id,
|
| member.id,
|
| )
|
| current = row[0] if row else 0
|
| new_amount = max(0, current - amount)
|
|
|
| await self.bot.db.execute(
|
| "INSERT INTO user_balance(guild_id, user_id, wallet, bank) VALUES (?, ?, ?, 0) "
|
| "ON CONFLICT(guild_id, user_id) DO UPDATE SET wallet = excluded.wallet",
|
| guild_id,
|
| member.id,
|
| new_amount,
|
| )
|
|
|
| if lang == "ar":
|
| embed = success_embed("💸 عملات محذوفة", f"تمت إزالة **{amount:,}** عملة من {member.mention}.\nالرصيد الجديد: **{new_amount:,}**")
|
| else:
|
| embed = success_embed("💸 Coins Removed", f"Removed **{amount:,}** coins from {member.mention}.\nNew balance: **{new_amount:,}**")
|
| await ctx.reply(embed=embed)
|
|
|
| @econadmin.command(name="salary")
|
| @commands.has_permissions(manage_guild=True)
|
| async def salary(self, ctx: commands.Context, min_salary: int, max_salary: int) -> None:
|
| """Set the work salary range."""
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| min_salary = max(1, min_salary)
|
| max_salary = max(min_salary, max_salary)
|
|
|
| await self.bot.db.execute(
|
| "INSERT INTO economy_salaries(guild_id, min_salary, max_salary) VALUES (?, ?, ?) "
|
| "ON CONFLICT(guild_id) DO UPDATE SET min_salary = excluded.min_salary, max_salary = excluded.max_salary",
|
| guild_id,
|
| min_salary,
|
| max_salary,
|
| )
|
|
|
| if lang == "ar":
|
| embed = success_embed("💼 رواتب العمل", f"تم تعيين نطاق الراتب: **{min_salary}** - **{max_salary}** عملة.")
|
| else:
|
| embed = success_embed("💼 Work Salary Set", f"Work salary range set to: **{min_salary}** - **{max_salary}** coins.")
|
| await ctx.reply(embed=embed)
|
|
|
| @econadmin.command(name="daily")
|
| @commands.has_permissions(manage_guild=True)
|
| async def daily_reward(self, ctx: commands.Context, min_reward: int, max_reward: int) -> None:
|
| """Set the daily reward range."""
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| min_reward = max(1, min_reward)
|
| max_reward = max(min_reward, max_reward)
|
|
|
| await self.bot.db.execute(
|
| "INSERT INTO economy_salaries(guild_id, daily_min, daily_max) VALUES (?, ?, ?) "
|
| "ON CONFLICT(guild_id) DO UPDATE SET daily_min = excluded.daily_min, daily_max = excluded.daily_max",
|
| guild_id,
|
| min_reward,
|
| max_reward,
|
| )
|
|
|
| if lang == "ar":
|
| embed = success_embed("🌅 المكافأة اليومية", f"تم تعيين نطاق المكافأة: **{min_reward}** - **{max_reward}** عملة.")
|
| else:
|
| embed = success_embed("🌅 Daily Reward Set", f"Daily reward range set to: **{min_reward}** - **{max_reward}** coins.")
|
| await ctx.reply(embed=embed)
|
|
|
|
|
|
|
|
|
|
|
| @commands.hybrid_command(name="rps", description=get_cmd_desc("commands.economy.rps_desc"))
|
| async def rps(self, ctx: commands.Context, choice: str) -> None:
|
| guild_id = ctx.guild.id if ctx.guild else None
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| picks = {"rock": "🪨", "paper": "📄", "scissors": "✂️"}
|
| arabic_picks = {"حجر": "rock", "ورقة": "paper", "مقص": "scissors"}
|
|
|
| choice = choice.strip().lower()
|
| if choice in arabic_picks:
|
| choice = arabic_picks[choice]
|
| if choice not in picks:
|
| if lang == "ar":
|
| await ctx.reply("🎮 استخدم: `حجر` / `ورقة` / `مقص` أو `rock` / `paper` / `scissors`")
|
| else:
|
| await ctx.reply("🎮 Use: `rock` / `paper` / `scissors`")
|
| return
|
|
|
| bot_pick = random.choice(list(picks))
|
| win = (choice, bot_pick) in {("rock", "scissors"), ("paper", "rock"), ("scissors", "paper")}
|
| draw = choice == bot_pick
|
|
|
| if win:
|
| reward = random.randint(15, 35)
|
| await self._add_coins(guild_id, ctx.author.id, reward)
|
| if lang == "ar":
|
| result = f"✅ **فزت!** {E_FIRE} حصلت على **{reward}** عملة!"
|
| else:
|
| result = f"✅ **You win!** {E_FIRE} Earned **{reward}** coins!"
|
| elif draw:
|
| result = "🤝 **تعادل!** لا خسارة ولا ربح." if lang == "ar" else "🤝 **Draw!** No coins lost or gained."
|
| else:
|
| result = "❌ **خسرت!** حظ أوفر." if lang == "ar" else "❌ **You lost!** Better luck next time."
|
|
|
| embed = gaming_embed("Rock Paper Scissors" if lang != "ar" else "حجر ورقة مقص",
|
| f"🎮 {'أنت' if lang == 'ar' else 'You'}: {picks[choice]} vs Bot: {picks[bot_pick]}\n\n{result}")
|
| await ctx.reply(embed=embed)
|
|
|
| @commands.hybrid_command(name="guess", description=get_cmd_desc("commands.economy.guess_desc"))
|
| async def guess(self, ctx: commands.Context, number: int) -> None:
|
| guild_id = ctx.guild.id if ctx.guild else None
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| number = max(1, min(10, number))
|
| secret = random.randint(1, 10)
|
| if number == secret:
|
| reward = random.randint(20, 50)
|
| await self._add_coins(guild_id, ctx.author.id, reward)
|
| if lang == "ar":
|
| embed = success_embed("🎯 صحيح!", f"الرقم كان **{secret}**!\n\n{E_MONEY} حصلت على **{reward}** عملة!")
|
| else:
|
| embed = success_embed("🎯 Correct!", f"The number was **{secret}**!\n\n{E_MONEY} You earned **{reward}** coins!")
|
| else:
|
| if lang == "ar":
|
| embed = error_embed("😅 خطأ!", f"الرقم كان **{secret}**. حاول مرة أخرى!")
|
| else:
|
| embed = error_embed("😅 Wrong Guess!", f"The number was **{secret}**. Try again!")
|
| await ctx.reply(embed=embed)
|
|
|
| @commands.hybrid_command(name="coinflip", description=get_cmd_desc("commands.economy.coinflip_desc"))
|
| async def coinflip_cmd(self, ctx: commands.Context) -> None:
|
| guild_id = ctx.guild.id if ctx.guild else None
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| result = random.choice(["Heads", "Tails"])
|
| emoji = "👑" if result == "Heads" else "🦅"
|
|
|
| if lang == "ar":
|
| result_ar = "وجه" if result == "Heads" else "ظهر"
|
| embed = gaming_embed("رمي العملة", f"{emoji} العملة سقطت على **{result_ar}**!")
|
| else:
|
| embed = gaming_embed("Coin Flip", f"{emoji} The coin landed on **{result}**!")
|
| await ctx.reply(embed=embed)
|
|
|
|
|
|
|
|
|
|
|
| @commands.hybrid_group(name="tournament", fallback="create", description="Tournament system")
|
| async def tournament(self, ctx: commands.Context, name: str, *, games: str = "chess, checkers, connect4, othello") -> None:
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| selected_games = [g.strip() for g in games.split(",") if g.strip()][:6]
|
| if not selected_games:
|
| selected_games = ["Valorant"]
|
| await self.bot.db.execute(
|
| "INSERT OR REPLACE INTO tournaments(guild_id, name, status, created_by, winner_id) VALUES (?, ?, 'open', ?, NULL)",
|
| guild_id,
|
| name,
|
| ctx.author.id,
|
| )
|
| await self.bot.db.execute(
|
| "DELETE FROM tournament_games WHERE guild_id = ? AND tournament_name = ?",
|
| guild_id,
|
| name,
|
| )
|
| for game in selected_games:
|
| await self.bot.db.execute(
|
| "INSERT OR IGNORE INTO tournament_games(guild_id, tournament_name, game_name) VALUES (?, ?, ?)",
|
| guild_id,
|
| name,
|
| game,
|
| )
|
|
|
| if lang == "ar":
|
| desc = (
|
| f"{panel_divider('lime')}\n"
|
| f"🎮 **الألعاب:** {', '.join(selected_games)}\n"
|
| f"👥 استخدم الزر أدناه للانضمام!\n"
|
| f"⚙️ المشرفون يمكنهم استخدام `/tournament match` للألعاب الحقيقية.\n"
|
| f"{panel_divider('lime')}"
|
| )
|
| title = f"🏁 بطولة جديدة: {name}"
|
| players_text = "0 انضموا حتى الآن — كن الأول!"
|
| status_text = "مفتوح"
|
| else:
|
| desc = (
|
| f"{panel_divider('lime')}\n"
|
| f"🎮 **Games:** {', '.join(selected_games)}\n"
|
| f"👥 Use the button below to join!\n"
|
| f"⚙️ Admins can use `/tournament match` for real games.\n"
|
| f"{panel_divider('lime')}"
|
| )
|
| title = f"🏁 New Tournament: {name}"
|
| players_text = "0 joined yet — be the first!"
|
| status_text = "Open"
|
|
|
| embed = discord.Embed(
|
| title=title,
|
| description=desc,
|
| color=NEON_LIME,
|
| )
|
| embed.add_field(name="👥 " + ("اللاعبين" if lang == "ar" else "Players"), value=players_text, inline=True)
|
| embed.add_field(name="⚙️ " + ("الحالة" if lang == "ar" else "Status"), value=status_text, inline=True)
|
| embed.set_footer(text=f"Tip: use /tournament panel {name} anytime")
|
| await ctx.reply(embed=embed, view=TournamentJoinView(self, guild_id, name))
|
|
|
| @tournament.command(name="close")
|
| @commands.has_permissions(manage_guild=True)
|
| async def tournament_close(self, ctx: commands.Context, *, name: str) -> None:
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| await self.bot.db.execute(
|
| "UPDATE tournaments SET status = 'closed' WHERE guild_id = ? AND name = ?",
|
| guild_id,
|
| name,
|
| )
|
|
|
| if lang == "ar":
|
| await ctx.reply(f"🔒 البطولة **{name}** مغلقة للمشاركين الجدد.")
|
| else:
|
| await ctx.reply(f"🔒 Tournament **{name}** closed for new participants.")
|
|
|
| @tournament.command(name="play")
|
| @commands.has_permissions(manage_guild=True)
|
| async def tournament_play(self, ctx: commands.Context, min_players: int = 2, *, name: str) -> None:
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| rows = await self.bot.db.fetchall(
|
| "SELECT user_id FROM tournament_participants WHERE guild_id = ? AND tournament_name = ?",
|
| guild_id,
|
| name,
|
| )
|
| min_players = max(1, min(64, min_players))
|
| if len(rows) < min_players:
|
| if lang == "ar":
|
| await ctx.reply(f"❌ يحتاج على الأقل **{min_players}** لاعبين.")
|
| else:
|
| await ctx.reply(f"❌ Need at least **{min_players}** players.")
|
| return
|
|
|
| winner_id = random.choice(rows)[0]
|
| games_rows = await self.bot.db.fetchall(
|
| "SELECT game_name FROM tournament_games WHERE guild_id = ? AND tournament_name = ?",
|
| guild_id,
|
| name,
|
| )
|
| chosen_game = random.choice(games_rows)[0] if games_rows else "Random Game"
|
| reward = random.randint(120, 300)
|
| await self._add_coins(guild_id, winner_id, reward)
|
| await self.bot.db.execute(
|
| "UPDATE tournaments SET winner_id = ?, status = 'finished' WHERE guild_id = ? AND name = ?",
|
| winner_id,
|
| guild_id,
|
| name,
|
| )
|
|
|
| if lang == "ar":
|
| embed = success_embed(f"🏆 فائز البطولة!", f"🎉 <@{winner_id}> فاز بـ **{chosen_game}**!\n\n{E_MONEY} الجائزة: **{reward}** عملة!")
|
| else:
|
| embed = success_embed(f"🏆 Tournament Winner!", f"🎉 <@{winner_id}> won **{chosen_game}**!\n\n{E_MONEY} Prize: **{reward}** coins!")
|
| await ctx.reply(embed=embed)
|
|
|
| @tournament.command(name="panel")
|
| async def tournament_panel(self, ctx: commands.Context, *, name: str) -> None:
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| row = await self.bot.db.fetchone(
|
| "SELECT name, status, winner_id FROM tournaments WHERE guild_id = ? AND name = ?",
|
| guild_id,
|
| name,
|
| )
|
| if not row:
|
| if lang == "ar":
|
| await ctx.reply(embed=await idle_embed_for_guild(
|
| "البطولة غير متاحة",
|
| "لا توجد بطولة بهذا الاسم حالياً.",
|
| guild=ctx.guild,
|
| bot=self.bot,
|
| ))
|
| else:
|
| await ctx.reply(embed=await idle_embed_for_guild(
|
| "Tournament Not Found",
|
| "No tournament exists with that name right now.",
|
| guild=ctx.guild,
|
| bot=self.bot,
|
| ))
|
| return
|
|
|
| _, status, winner_id = row
|
| games_rows = await self.bot.db.fetchall(
|
| "SELECT game_name FROM tournament_games WHERE guild_id = ? AND tournament_name = ? ORDER BY game_name ASC",
|
| guild_id,
|
| name,
|
| )
|
| games = ", ".join(game for (game,) in games_rows) if games_rows else ("لم تحدد" if lang == "ar" else "Not set")
|
| participants = await self.bot.db.fetchall(
|
| "SELECT user_id FROM tournament_participants WHERE guild_id = ? AND tournament_name = ?",
|
| guild_id,
|
| name,
|
| )
|
| tournament_lb = await self.bot.db.fetchall(
|
| "SELECT winner_id, COUNT(*) as wins FROM tournaments WHERE guild_id = ? AND winner_id IS NOT NULL GROUP BY winner_id ORDER BY wins DESC LIMIT 5",
|
| guild_id,
|
| )
|
|
|
| if lang == "ar":
|
| desc = f"{panel_divider('orange')}\n🎮 انضم من الزر أدناه وابدأ المنافسة!"
|
| title = f"🎮 لوحة البطولة: {name}"
|
| else:
|
| desc = f"{panel_divider('orange')}\n🎮 Join from the button below and start battling!"
|
| title = f"🎮 Tournament Panel: {name}"
|
|
|
| embed = discord.Embed(
|
| title=title,
|
| description=desc,
|
| color=NEON_LIME,
|
| )
|
| embed.add_field(name="🎯 " + ("الألعاب" if lang == "ar" else "Games Pool"), value=games, inline=False)
|
| embed.add_field(name="👥 " + ("المشاركين" if lang == "ar" else "Joined"), value=str(len(participants)), inline=True)
|
| embed.add_field(name="📌 " + ("الحالة" if lang == "ar" else "Status"), value=str(status or "open"), inline=True)
|
| embed.add_field(name="🥇 " + ("الفائز الحالي" if lang == "ar" else "Current Winner"), value=(f"<@{winner_id}>" if winner_id else ("لا يوجد" if lang == "ar" else "None yet")), inline=True)
|
| if tournament_lb:
|
| lb_lines = [f"{idx}. <@{uid}> — {wins}" for idx, (uid, wins) in enumerate(tournament_lb, start=1)]
|
| embed.add_field(name="🏆 " + ("ليدربورد البطولات" if lang == "ar" else "Tournament Leaderboard"), value="\n".join(lb_lines), inline=False)
|
| embed.add_field(name="🧭 " + ("الأوامر" if lang == "ar" else "Commands"), value="`/tournament games` • `/tournament match` • `/tournament play`", inline=True)
|
| await ctx.reply(embed=embed, view=TournamentJoinView(self, guild_id, name))
|
|
|
| @tournament.command(name="games")
|
| async def tournament_games(self, ctx: commands.Context, *, name: str) -> None:
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| rows = await self.bot.db.fetchall(
|
| "SELECT game_name FROM tournament_games WHERE guild_id = ? AND tournament_name = ? ORDER BY game_name ASC",
|
| guild_id,
|
| name,
|
| )
|
| if not rows:
|
| if lang == "ar":
|
| await ctx.reply(embed=await idle_embed_for_guild(
|
| "قائمة الألعاب فارغة",
|
| "لم يتم إعداد ألعاب لهذه البطولة بعد.",
|
| guild=ctx.guild,
|
| bot=self.bot,
|
| ))
|
| else:
|
| await ctx.reply(embed=await idle_embed_for_guild(
|
| "Tournament Games Idle",
|
| "No games are configured for this tournament yet.",
|
| guild=ctx.guild,
|
| bot=self.bot,
|
| ))
|
| return
|
|
|
| games = "🎮 " + " • ".join(game for (game,) in rows)
|
| await ctx.reply(games)
|
|
|
| @tournament.command(name="match")
|
| @commands.has_permissions(manage_guild=True)
|
| async def tournament_match(
|
| self,
|
| ctx: commands.Context,
|
| name: str,
|
| game: str,
|
| player1: discord.Member,
|
| player2: discord.Member | None = None,
|
| ) -> None:
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| game = game.strip().lower()
|
| board_cog = self.bot.get_cog("BoardGames")
|
| if board_cog is None:
|
| if lang == "ar":
|
| await ctx.reply("❌ مكون الألعاب غير محمل.")
|
| else:
|
| await ctx.reply("❌ BoardGames cog is not loaded.")
|
| return
|
|
|
| row = await self.bot.db.fetchone(
|
| "SELECT status FROM tournaments WHERE guild_id = ? AND name = ?",
|
| guild_id,
|
| name,
|
| )
|
| if not row:
|
| if lang == "ar":
|
| await ctx.reply(embed=await idle_embed_for_guild(
|
| "البطولة غير متاحة",
|
| "لا توجد بطولة بهذا الاسم حالياً.",
|
| guild=ctx.guild,
|
| bot=self.bot,
|
| ))
|
| else:
|
| await ctx.reply(embed=await idle_embed_for_guild(
|
| "Tournament Not Found",
|
| "No tournament exists with that name right now.",
|
| guild=ctx.guild,
|
| bot=self.bot,
|
| ))
|
| return
|
|
|
| game_rows = await self.bot.db.fetchall(
|
| "SELECT game_name FROM tournament_games WHERE guild_id = ? AND tournament_name = ?",
|
| guild_id,
|
| name,
|
| )
|
| allowed = {g.lower() for (g,) in game_rows}
|
| if game_rows and game not in allowed:
|
| if lang == "ar":
|
| await ctx.reply(f"❌ اللعبة `{game}` ليست في قائمة هذه البطولة.")
|
| else:
|
| await ctx.reply(f"❌ Game `{game}` is not in this tournament's game list.")
|
| return
|
|
|
| joined_rows = await self.bot.db.fetchall(
|
| "SELECT user_id FROM tournament_participants WHERE guild_id = ? AND tournament_name = ?",
|
| guild_id,
|
| name,
|
| )
|
| joined = {uid for (uid,) in joined_rows}
|
| if player1.id not in joined or (player2 and player2.id not in joined):
|
| if lang == "ar":
|
| await ctx.reply("❌ كلا اللاعبين يجب أن ينضموا للبطولة أولاً.")
|
| else:
|
| await ctx.reply("❌ Both players must join the tournament first.")
|
| return
|
|
|
| if lang == "ar":
|
| desc = (
|
| f"🎮 بدء مباراة البطولة **{name}**\n"
|
| f"🎯 اللعبة: **{game}**\n"
|
| f"⚔️ {player1.mention} ضد {(player2.mention if player2 else '🤖 بوت')}"
|
| )
|
| else:
|
| desc = (
|
| f"🎮 Starting tournament match **{name}**\n"
|
| f"🎯 Game: **{game}**\n"
|
| f"⚔️ {player1.mention} vs {(player2.mention if player2 else '🤖 Bot')}"
|
| )
|
| await ctx.reply(desc)
|
| await board_cog.start_tournament_duel(ctx, game, player1, player2, tournament_name=name)
|
|
|
| @tournament.command(name="gamehub", description="Show tournament + gamehub quick actions")
|
| async def tournament_gamehub(self, ctx: commands.Context, *, name: str) -> None:
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
| row = await self.bot.db.fetchone(
|
| "SELECT status FROM tournaments WHERE guild_id = ? AND name = ?",
|
| guild_id,
|
| name,
|
| )
|
| if not row:
|
| if lang == "ar":
|
| await ctx.reply(embed=await idle_embed_for_guild(
|
| "البطولة غير متاحة",
|
| "لا توجد بطولة بهذا الاسم حالياً.",
|
| guild=ctx.guild,
|
| bot=self.bot,
|
| ))
|
| else:
|
| await ctx.reply(embed=await idle_embed_for_guild(
|
| "Tournament Not Found",
|
| "No tournament exists with that name right now.",
|
| guild=ctx.guild,
|
| bot=self.bot,
|
| ))
|
| return
|
| if lang == "ar":
|
| msg = (
|
| f"🔗 تم ربط مسار البطولة **{name}** مع أوامر الألعاب.\n"
|
| f"استخدم: `/tournament panel {name}` ثم `/tournament match {name} <game> <player1> [player2]`.\n"
|
| f"يمكنك أيضاً فتح `/gamehub` لبدء ألعاب سريعة خارج البطولة."
|
| )
|
| else:
|
| msg = (
|
| f"🔗 Tournament **{name}** is linked with game commands.\n"
|
| f"Use: `/tournament panel {name}` then `/tournament match {name} <game> <player1> [player2]`.\n"
|
| f"You can also open `/gamehub` for quick non-tournament games."
|
| )
|
| await ctx.reply(msg)
|
|
|
| @commands.hybrid_command(name="tournament_lb", description=get_cmd_desc("commands.economy.tournament_lb_desc"))
|
| async def tournament_lb(self, ctx: commands.Context) -> None:
|
| guild_id = ctx.guild.id
|
| lang = await self.bot.get_guild_language(guild_id)
|
|
|
| rows = await self.bot.db.fetchall(
|
| "SELECT winner_id, COUNT(*) FROM tournaments WHERE guild_id = ? AND winner_id IS NOT NULL GROUP BY winner_id ORDER BY COUNT(*) DESC LIMIT 10",
|
| guild_id,
|
| )
|
| if not rows:
|
| if lang == "ar":
|
| await ctx.reply(embed=await idle_embed_for_guild(
|
| "لوحة البطولات الخاملة",
|
| "لا يوجد فائزون مسجلون حتى الآن.",
|
| guild=ctx.guild,
|
| bot=self.bot,
|
| ))
|
| else:
|
| await ctx.reply(embed=await idle_embed_for_guild(
|
| "Tournament Leaderboard Idle",
|
| "No tournament winners are recorded yet.",
|
| guild=ctx.guild,
|
| bot=self.bot,
|
| ))
|
| return
|
|
|
| lines = []
|
| for idx, (uid, wins) in enumerate(rows, start=1):
|
| member = ctx.guild.get_member(uid)
|
| name = member.display_name if member else str(uid)
|
| badge = {1: "🥇", 2: "🥈", 3: "🥉"}.get(idx, f"#{idx}")
|
| if lang == "ar":
|
| lines.append(f"{badge} **{name}** — {wins} فوز")
|
| else:
|
| lines.append(f"{badge} **{name}** — {wins} wins")
|
|
|
| if lang == "ar":
|
| title = "🏆 أبطال البطولات"
|
| else:
|
| title = "🏆 Tournament Champions"
|
|
|
| embed = discord.Embed(
|
| title=title,
|
| description=f"{panel_divider('lime')}\n" + "\n".join(lines) + f"\n{panel_divider('lime')}",
|
| color=NEON_LIME,
|
| )
|
| await ctx.reply(embed=embed)
|
|
|
|
|
| async def setup(bot: commands.Bot) -> None:
|
| await bot.add_cog(Engagement(bot))
|
|
|