test / bot /cogs /engagement.py
mtaaz's picture
Upload 94 files
91c7f83 verified
"""
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
# Get salary settings
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
# Get salary settings
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)
# ═══════════════════════════════════════════════════════════════════════════════
# ECONOMY MANAGEMENT COMMANDS
# ═══════════════════════════════════════════════════════════════════════════════
@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)
# ═══════════════════════════════════════════════════════════════════════════════
# MINI GAMES
# ═══════════════════════════════════════════════════════════════════════════════
@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)
# ═══════════════════════════════════════════════════════════════════════════════
# TOURNAMENTS
# ═══════════════════════════════════════════════════════════════════════════════
@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))