test / bot /cogs /fun.py
mtaaz's picture
Upload 94 files
91c7f83 verified
"""
Fun cog: Games, trivia, memes, and entertainment commands.
Enhanced with rich emoji decorations, beautiful formatting, and multi-language support.
"""
import html
import random
import aiohttp
import discord
from discord.ext import commands
from bot.cogs.ai_suite import ImperialMotaz
from bot.theme import (
fancy_header, NEON_CYAN, NEON_PINK, NEON_PURPLE, NEON_LIME, NEON_ORANGE, NEON_BLUE,
panel_divider, gaming_embed, success_embed, error_embed, info_embed,
double_line, triple_line, shimmer, pick_neon_color
)
from bot.i18n import get_cmd_desc
from bot.emojis import (
ui, E_GAMEHUB, E_ARROW_PINK, E_ARROW_GREEN, E_ARROW_PURPLE, E_STAR,
E_TROPHY, E_FIRE, E_SPARKLE, E_DIZZY, E_BOOM, E_ZAP, E_DIAMOND, E_CROWN, E_GEM
)
from bot.utils.shared import fetch_gaming_news, fetch_free_games, store_icon, FreeGameClaimView
TRIVIA_BANK: dict[str, dict[str, list[dict[str, object]]]] = {
"en": {
"gaming": [
{"q": "Which studio created The Witcher 3?", "c": ["CD Projekt Red", "Rockstar", "Ubisoft", "Bethesda"], "a": 0},
{"q": "What is the default block in Minecraft?", "c": ["Stone", "Dirt", "Grass Block", "Sand"], "a": 2},
{"q": "In which game would you find Master Chief?", "c": ["Halo", "Destiny", "Call of Duty", "Gears of War"], "a": 0},
{"q": "What year was Fortnite released?", "c": ["2015", "2017", "2018", "2019"], "a": 1},
{"q": "Which company makes the PlayStation?", "c": ["Microsoft", "Nintendo", "Sony", "Sega"], "a": 2},
],
"movies": [
{"q": "Who directed Inception?", "c": ["Christopher Nolan", "James Cameron", "Denis Villeneuve", "Ridley Scott"], "a": 0},
{"q": "In the MCU, who is Peter Parker?", "c": ["Iron Man", "Spider-Man", "Doctor Strange", "Hawkeye"], "a": 1},
{"q": "What is the highest-grossing film of all time?", "c": ["Avengers: Endgame", "Avatar", "Titanic", "Star Wars"], "a": 1},
{"q": "Who played the Joker in The Dark Knight?", "c": ["Joaquin Phoenix", "Jack Nicholson", "Heath Ledger", "Jared Leto"], "a": 2},
],
"series": [
{"q": "Which series features Walter White?", "c": ["Narcos", "Breaking Bad", "Dark", "Sherlock"], "a": 1},
{"q": "In Game of Thrones, what is Jon Snow's house at first?", "c": ["Lannister", "Baratheon", "Stark", "Targaryen"], "a": 2},
{"q": "What is the name of the coffee shop in Friends?", "c": ["Central Park", "Central Perk", "Coffee House", "The Brew"], "a": 1},
{"q": "How many seasons does Stranger Things have?", "c": ["3", "4", "5", "6"], "a": 2},
],
},
"ar": {
"gaming": [
{"q": "ุฃูŠ ุดุฑูƒุฉ ุทูˆุฑุช ู„ุนุจุฉ The Witcher 3ุŸ", "c": ["CD Projekt Red", "Rockstar", "Ubisoft", "Bethesda"], "a": 0},
{"q": "ู…ุง ู‡ูˆ ุงู„ุจู„ูˆูƒ ุงู„ุงูุชุฑุงุถูŠ ููŠ MinecraftุŸ", "c": ["Stone", "Dirt", "Grass Block", "Sand"], "a": 2},
{"q": "ููŠ ุฃูŠ ู„ุนุจุฉ ุชุฌุฏ Master ChiefุŸ", "c": ["Halo", "Destiny", "Call of Duty", "Gears of War"], "a": 0},
{"q": "ู…ุชู‰ ุตุฏุฑุช FortniteุŸ", "c": ["2015", "2017", "2018", "2019"], "a": 1},
],
"movies": [
{"q": "ู…ู† ู…ุฎุฑุฌ ููŠู„ู… InceptionุŸ", "c": ["ูƒุฑูŠุณุชูˆูุฑ ู†ูˆู„ุงู†", "ุฌูŠู…ุณ ูƒุงู…ูŠุฑูˆู†", "ุฏูŠู†ูŠุณ ููŠู„ู†ูˆู", "ุฑูŠุฏู„ูŠ ุณูƒูˆุช"], "a": 0},
{"q": "ููŠ ุนุงู„ู… ู…ุงุฑูู„ุŒ ุจูŠุชุฑ ุจุงุฑูƒุฑ ู‡ูˆุŸ", "c": ["Iron Man", "Spider-Man", "Doctor Strange", "Hawkeye"], "a": 1},
{"q": "ู…ุง ู‡ูˆ ุฃุนู„ู‰ ููŠู„ู… ุฅูŠุฑุงุฏุงุช ููŠ ุงู„ุชุงุฑูŠุฎุŸ", "c": ["Avengers: Endgame", "Avatar", "Titanic", "Star Wars"], "a": 1},
],
"series": [
{"q": "ุฃูŠ ู…ุณู„ุณู„ ูŠุธู‡ุฑ ููŠู‡ Walter WhiteุŸ", "c": ["Narcos", "Breaking Bad", "Dark", "Sherlock"], "a": 1},
{"q": "ููŠ Game of ThronesุŒ ุฌูˆู† ุณู†ูˆ ูƒุงู† ุถู…ู† ุฃูŠ ุจูŠุช ุจุงู„ุจุฏุงูŠุฉุŸ", "c": ["Lannister", "Baratheon", "Stark", "Targaryen"], "a": 2},
],
},
}
class TriviaAnswerButton(discord.ui.Button["TriviaView"]):
def __init__(self, label: str, index: int) -> None:
super().__init__(label=label[:80], style=discord.ButtonStyle.secondary)
self.index = index
async def callback(self, interaction: discord.Interaction) -> None:
await self.view.on_answer(interaction, self.index)
class FreeGameClaimView(discord.ui.View):
def __init__(self, url: str) -> None:
super().__init__(timeout=None)
self.add_item(
discord.ui.Button(
label=f"{ui('gift')} Claim Your Prize {ui('gift')}",
style=discord.ButtonStyle.link,
url=url,
)
)
class TriviaView(discord.ui.View):
def __init__(self, owner_id: int, correct_index: int, lang: str, cog: "Fun", guild_id: int | None) -> None:
super().__init__(timeout=None)
self.owner_id = owner_id
self.correct_index = correct_index
self.lang = lang
self.answered = False
self.cog = cog
self.guild_id = guild_id
async def on_answer(self, interaction: discord.Interaction, picked_index: int) -> None:
if interaction.user.id != self.owner_id:
msg = "โŒ ู‡ุฐุง ุงู„ุณุคุงู„ ู„ุตุงุญุจ ุงู„ุฃู…ุฑ ูู‚ุท." if self.lang == "ar" else "โŒ This question is for the command author only."
await interaction.response.send_message(msg, ephemeral=True)
return
if self.answered:
msg = "โš ๏ธ ุชู…ุช ุงู„ุฅุฌุงุจุฉ ุจุงู„ูุนู„." if self.lang == "ar" else "โš ๏ธ Already answered."
await interaction.response.send_message(msg, ephemeral=True)
return
self.answered = True
is_correct = picked_index == self.correct_index
for idx, child in enumerate(self.children):
if isinstance(child, discord.ui.Button):
child.disabled = True
if idx == self.correct_index:
child.style = discord.ButtonStyle.success
elif idx == picked_index and not is_correct:
child.style = discord.ButtonStyle.danger
if is_correct:
msg = f"โœ… ุฅุฌุงุจุฉ ุตุญูŠุญุฉ! {E_STAR}" if self.lang == "ar" else f"โœ… Correct answer! {E_STAR}"
else:
msg = "โŒ ุฅุฌุงุจุฉ ุฎุงุทุฆุฉ." if self.lang == "ar" else "โŒ Wrong answer."
await interaction.response.edit_message(view=self)
if is_correct and interaction.guild and self.guild_id:
reward = random.randint(20, 45)
await self.cog.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",
self.guild_id,
interaction.user.id,
reward,
)
if self.lang == "ar":
msg += f" ๐Ÿ’ฐ **+{reward}** ุนู…ู„ุฉ!"
else:
msg += f" ๐Ÿ’ฐ **+{reward}** coins!"
await interaction.followup.send(msg, ephemeral=True)
GAME_HUB_GAMES = [
{"id": "chess", "name": "Chess", "emoji": "โ™Ÿ๏ธ", "description": "Strategic board game"},
{"id": "checkers", "name": "Checkers", "emoji": "๐Ÿ”ด", "description": "Classic draughts game"},
{"id": "connect4", "name": "Connect 4", "emoji": "๐Ÿ”ด", "description": "Drop and connect"},
{"id": "othello", "name": "Othello", "emoji": "โšช", "description": "Reversi strategy"},
{"id": "tictactoe", "name": "TicTacToe", "emoji": "โŽ", "description": "X and O battle"},
{"id": "rps", "name": "Rock Paper Scissors", "emoji": "โœ‚๏ธ", "description": "Quick decision game"},
{"id": "trivia", "name": "Trivia Challenge", "emoji": "๐Ÿง ", "description": "Knowledge contest"},
{"id": "guess", "name": "Number Guess", "emoji": "๐Ÿ”ข", "description": "Guess 1-10"},
]
class GameHubSelect(discord.ui.Select):
def __init__(self, cog: "Fun", invitee_id: int | None, lang: str = "en") -> None:
self.cog = cog
self.invitee_id = invitee_id
self.lang = lang
options = []
for game in GAME_HUB_GAMES:
if lang == "ar":
desc = game["description"]
else:
desc = game["description"]
options.append(
discord.SelectOption(
label=game["name"],
value=game["id"],
description=desc,
emoji=game["emoji"]
)
)
super().__init__(
placeholder="๐ŸŽฎ Choose a game",
min_values=1,
max_values=1,
options=options,
custom_id="gamehub_select",
)
async def callback(self, interaction: discord.Interaction) -> None:
if not interaction.guild:
await interaction.response.send_message("Server only.", ephemeral=True)
return
guild = interaction.guild
game_id = self.values[0]
invitee = guild.get_member(self.invitee_id) if self.invitee_id else None
guild_id = guild.id
lang = await self.cog.bot.get_guild_language(guild_id)
category = discord.utils.get(guild.categories, name="๐Ÿ•น๏ธ Game Rooms")
if category is None:
category = await guild.create_category("๐Ÿ•น๏ธ Game Rooms", reason="GameHub temporary rooms")
overwrites = {
guild.default_role: discord.PermissionOverwrite(view_channel=False),
interaction.user: discord.PermissionOverwrite(view_channel=True, send_messages=True, read_message_history=True),
}
if invitee:
overwrites[invitee] = discord.PermissionOverwrite(view_channel=True, send_messages=True, read_message_history=True)
if guild.me:
overwrites[guild.me] = discord.PermissionOverwrite(view_channel=True, send_messages=True, manage_channels=True)
room = await guild.create_text_channel(
name=f"game-{game_id}-{interaction.user.name[:8].lower()}",
category=category,
overwrites=overwrites,
reason=f"GameHub room for {interaction.user}",
)
board_cog = self.cog.bot.get_cog("BoardGames")
try:
if game_id == "tictactoe":
view = TicTacToeView(interaction.user, invitee)
first = interaction.user.mention
second = invitee.mention if invitee else "๐Ÿค– Bot"
if lang == "ar":
msg = await room.send(f"โŽ ุชูŠูƒ ุชุงูƒ ุชูˆ: {first} (X) ุถุฏ {second} (O)\n๐ŸŽฎ ุงู„ุฏูˆุฑ: {interaction.user.mention}", view=view)
else:
msg = await room.send(f"โŽ TicTacToe: {first} (X) vs {second} (O)\n๐ŸŽฎ Turn: {interaction.user.mention}", view=view)
if invitee is None:
await view._maybe_bot(msg)
elif game_id == "trivia":
# Start trivia game
item = random.choice(TRIVIA_BANK.get(lang, TRIVIA_BANK["en"])["gaming"])
view = TriviaView(owner_id=interaction.user.id, correct_index=int(item["a"]), lang=lang, cog=self.cog, guild_id=guild_id)
for idx, choice in enumerate(item["c"]):
view.add_item(TriviaAnswerButton(str(choice), idx))
if lang == "ar":
q_title = "๐Ÿง  ุณุคุงู„ ุชุฑุงฺคูŠุง!"
else:
q_title = "๐Ÿง  Trivia Time!"
embed = discord.Embed(
title=q_title,
description=f"{panel_divider('blue')}\nโ“ {str(item['q'])}\n{panel_divider('blue')}",
color=NEON_CYAN,
)
await room.send(embed=embed, view=view)
elif game_id == "rps":
# RPS game in channel
if lang == "ar":
await room.send(f"โœ‚๏ธ ุญุฌุฑ ูˆุฑู‚ุฉ ู…ู‚ุต!\nุงุณุชุฎุฏู… `rps rock/paper/scissors` ู„ู„ุนุจ!")
else:
await room.send(f"โœ‚๏ธ Rock Paper Scissors!\nUse `rps rock/paper/scissors` to play!")
elif game_id == "guess":
# Number guessing game
secret = random.randint(1, 10)
if lang == "ar":
await room.send(f"๐Ÿ”ข ุฎู…ู† ุงู„ุฑู‚ู… ู…ู† 1 ุฅู„ู‰ 10!\nุงุณุชุฎุฏู… `/guess <ุฑู‚ู…>` ู„ู„ุนุจ!")
else:
await room.send(f"๐Ÿ”ข Guess a number from 1 to 10!\nUse `/guess <number>` to play!")
elif board_cog and game_id in {"chess", "checkers", "connect4", "othello"}:
fake_ctx = type("_Ctx", (), {"guild": guild, "channel": room, "author": interaction.user, "reply": room.send})
opponent = invitee
ok = await board_cog.start_game_session(fake_ctx, game_id, opponent)
if not ok:
if lang == "ar":
await room.send("โš ๏ธ ุชุนุฐุฑ ุชุดุบูŠู„ ุงู„ู„ุนุจุฉ. ุงุฎุชุฑ ู„ุนุจุฉ ุฃุฎุฑู‰.")
else:
await room.send("โš ๏ธ Could not initialize selected board game. Choose another one.")
else:
game_name = next((g["name"] for g in GAME_HUB_GAMES if g["id"] == game_id), game_id.title())
if lang == "ar":
await room.send(f"๐ŸŽฎ **{game_name}** ุงู„ุบุฑูุฉ ุฌุงู‡ุฒุฉ. ุงู„ุนุจ ุงู„ุขู†! {E_FIRE}")
else:
await room.send(f"๐ŸŽฎ **{game_name}** room is ready. Play now! {E_FIRE}")
except Exception:
if lang == "ar":
await room.send("โš ๏ธ ุชุนุฐุฑ ุชุดุบูŠู„ ู‡ุฐู‡ ุงู„ู„ุนุจุฉ. ุฌุฑุจ ุฅู†ุดุงุก ุบุฑูุฉ ุฃุฎุฑู‰.")
else:
await room.send("โš ๏ธ Could not start this game. Try creating another room.")
if lang == "ar":
invite_text = (
f"๐ŸŽฎ **{game_id.title()}** ุงู„ุบุฑูุฉ ุฌุงู‡ุฒุฉ!\n"
f"{panel_divider('purple')}\n"
f"๐Ÿ‘ค **ุงู„ู…ุถูŠู:** {interaction.user.mention}"
)
close_text = "๐Ÿ”’ ุฃุบู„ู‚ ุงู„ุบุฑูุฉ ุนู†ุฏ ุงู„ุงู†ุชู‡ุงุก ๐Ÿ‘‡"
else:
invite_text = (
f"๐ŸŽฎ **{game_id.title()}** Room Ready!\n"
f"{panel_divider('purple')}\n"
f"๐Ÿ‘ค **Host:** {interaction.user.mention}"
)
close_text = "๐Ÿ”’ Close the room when done ๐Ÿ‘‡"
if invitee:
if lang == "ar":
invite_text += f"\n๐ŸŽฏ **ู…ุฏุนูˆ:** {invitee.mention}"
else:
invite_text += f"\n๐ŸŽฏ **Invited:** {invitee.mention}"
invite_text += f"\n\n{close_text}"
await room.send(invite_text, view=RoomControlView(interaction.user.id))
if lang == "ar":
response_msg = f"โœ… ุชู… ุฅู†ุดุงุก ุงู„ุบุฑูุฉ: {room.mention} ๐ŸŽฎ"
if invitee:
response_msg += f" | ุชู…ุช ุฏุนูˆุฉ {invitee.mention}"
else:
response_msg = f"โœ… Room created: {room.mention} ๐ŸŽฎ"
if invitee:
response_msg += f" | Invited {invitee.mention}"
await interaction.response.send_message(response_msg, ephemeral=True)
class GameHubView(discord.ui.View):
def __init__(self, cog: "Fun", invitee_id: int | None = None, lang: str = "en") -> None:
super().__init__(timeout=None)
self.add_item(GameHubSelect(cog, invitee_id, lang))
class RoomControlView(discord.ui.View):
def __init__(self, owner_id: int) -> None:
super().__init__(timeout=None)
self.owner_id = owner_id
@discord.ui.button(label="End Game & Close Room", style=discord.ButtonStyle.danger, emoji=ui("lock"), custom_id="room_close")
async def close_room(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
if not isinstance(interaction.channel, discord.TextChannel):
await interaction.response.send_message("Text channel only.", ephemeral=True)
return
if interaction.user.id != self.owner_id and not interaction.user.guild_permissions.manage_channels:
await interaction.response.send_message("โŒ Only room owner or admins can close this room.", ephemeral=True)
return
await interaction.response.send_message("๐Ÿ”’ Closing room...", ephemeral=True)
try:
await interaction.channel.delete(reason=f"Game room closed by {interaction.user}")
except Exception:
pass
@discord.ui.button(label="Invite Friend", style=discord.ButtonStyle.success, emoji=ui("plus"), custom_id="room_invite")
async def invite_friend(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
if interaction.user.id != self.owner_id:
await interaction.response.send_message("โŒ Only room owner can invite.", ephemeral=True)
return
await interaction.response.send_message("โž• Use `/gamehub @user` to invite someone to a new game room!", ephemeral=True)
class TicTacToeButton(discord.ui.Button["TicTacToeView"]):
def __init__(self, index: int) -> None:
super().__init__(label=" ", style=discord.ButtonStyle.secondary, row=index // 3)
self.index = index
async def callback(self, interaction: discord.Interaction) -> None:
if self.view:
await self.view.play(interaction, self.index)
class TicTacToeView(discord.ui.View):
def __init__(self, player_x: discord.Member, player_o: discord.Member | None) -> None:
super().__init__(timeout=None)
self.player_x = player_x
self.player_o = player_o
self.turn_x = True
self.board = ["" for _ in range(9)]
for i in range(9):
self.add_item(TicTacToeButton(i))
def _check(self, mark: str) -> bool:
lines = [(0,1,2),(3,4,5),(6,7,8),(0,3,6),(1,4,7),(2,5,8),(0,4,8),(2,4,6)]
return any(self.board[a]==self.board[b]==self.board[c]==mark for a,b,c in lines)
def _free(self) -> list[int]:
return [i for i,v in enumerate(self.board) if not v]
async def _maybe_bot(self, message: discord.Message) -> None:
if self.player_o is not None or not self._free() or not self.turn_x:
return
move = random.choice(self._free())
self.board[move] = "O"
btn = self.children[move]
if isinstance(btn, discord.ui.Button):
btn.label = "O"
btn.style = discord.ButtonStyle.danger
btn.disabled = True
self.turn_x = True
if self._check("O"):
for child in self.children:
if isinstance(child, discord.ui.Button):
child.disabled = True
await message.edit(content=f"๐Ÿค– Bot won TicTacToe! {E_BOOM}", view=self)
return
if not self._free():
await message.edit(content="๐Ÿค TicTacToe draw!", view=self)
return
await message.edit(content=f"โŽ Turn: {self.player_x.mention}", view=self)
async def play(self, interaction: discord.Interaction, index: int) -> None:
if self.board[index]:
await interaction.response.send_message("This cell is already used.", ephemeral=True)
return
current = self.player_x if self.turn_x else self.player_o
if self.turn_x and interaction.user.id != self.player_x.id:
await interaction.response.send_message("It is X player's turn.", ephemeral=True)
return
if not self.turn_x and self.player_o and interaction.user.id != self.player_o.id:
await interaction.response.send_message("It is O player's turn.", ephemeral=True)
return
mark = "X" if self.turn_x else "O"
self.board[index] = mark
btn = self.children[index]
if isinstance(btn, discord.ui.Button):
btn.label = mark
btn.style = discord.ButtonStyle.success if mark == "X" else discord.ButtonStyle.danger
btn.disabled = True
if self._check(mark):
for child in self.children:
if isinstance(child, discord.ui.Button):
child.disabled = True
winner = interaction.user.mention if self.player_o else (self.player_x.mention if mark == "X" else "๐Ÿค– Bot")
await interaction.response.edit_message(content=f"๐Ÿ† TicTacToe Winner: {winner}! {E_TROPHY}", view=self)
return
if not self._free():
await interaction.response.edit_message(content="๐Ÿค TicTacToe draw!", view=self)
return
self.turn_x = not self.turn_x
nxt = self.player_x.mention if self.turn_x else (self.player_o.mention if self.player_o else "๐Ÿค– Bot")
await interaction.response.edit_message(content=f"๐ŸŽฎ Turn: {nxt}", view=self)
if not self.turn_x and self.player_o is None and interaction.message:
await self._maybe_bot(interaction.message)
class Fun(commands.Cog):
"""Games and entertainment 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(GameHubView(self))
async def _fetch_trivia_api(self) -> dict[str, object] | None:
url = "https://opentdb.com/api.php?amount=50"
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=15) as response:
data = await response.json(content_type=None)
results = data.get("results") if isinstance(data, dict) else None
if not isinstance(results, list) or not results:
return None
item = random.choice(results)
question = html.unescape(str(item.get("question", "")).strip())
correct = html.unescape(str(item.get("correct_answer", "")).strip())
wrong = [html.unescape(str(c).strip()) for c in item.get("incorrect_answers", []) if str(c).strip()]
if not question or not correct or not wrong:
return None
choices = wrong[:]
choices.append(correct)
random.shuffle(choices)
answer_index = choices.index(correct)
return {"q": question, "c": choices[:4], "a": answer_index}
@commands.hybrid_command(name="8ball", description=get_cmd_desc("commands.tools.8ball_desc"))
async def eightball(self, ctx: commands.Context, *, question: str) -> None:
guild_id = ctx.guild.id if ctx.guild else None
lang = await self.bot.get_guild_language(guild_id)
if lang == "ar":
answers = [
"ู†ุนู…! โœ…", "ู„ุง โŒ", "ุฑุจู…ุง ๐Ÿค”", "ุจุงู„ุชุฃูƒูŠุฏ! ๐Ÿ’ฏ",
"ู„ูŠุณ ุงู„ุขู† โฐ", "ุงุณุฃู„ ู„ุงุญู‚ุงู‹ ๐Ÿ”ฎ", "100% ู†ุนู…! ๐Ÿ”ฅ", "ุบูŠุฑ ูˆุงุถุญ ๐ŸŒซ๏ธ"
]
embed = discord.Embed(
title="๐ŸŽฑ ุงู„ูƒุฑุฉ ุงู„ุณุญุฑูŠุฉ",
description=(
f"{panel_divider('pink')}\n"
f"โ“ **ุงู„ุณุคุงู„:** {question}\n"
f"๐ŸŽฑ **ุงู„ุฌูˆุงุจ:** {random.choice(answers)}\n"
f"{panel_divider('pink')}"
),
color=NEON_PINK,
)
else:
answers = [
"Yes! โœ…", "No โŒ", "Maybe ๐Ÿค”", "Definitely! ๐Ÿ’ฏ",
"Not now โฐ", "Ask later ๐Ÿ”ฎ", "100% Yes! ๐Ÿ”ฅ", "Unclear ๐ŸŒซ๏ธ"
]
embed = discord.Embed(
title="๐ŸŽฑ Magic 8 Ball",
description=(
f"{panel_divider('pink')}\n"
f"โ“ **Question:** {question}\n"
f"๐ŸŽฑ **Answer:** {random.choice(answers)}\n"
f"{panel_divider('pink')}"
),
color=NEON_PINK,
)
await ctx.reply(embed=embed)
@commands.hybrid_command(name="meme", description=get_cmd_desc("commands.tools.meme_desc"))
async def meme(self, ctx: commands.Context) -> None:
guild_id = ctx.guild.id if ctx.guild else None
lang = await self.bot.get_guild_language(guild_id)
url = "https://meme-api.com/gimme"
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=10) as response:
data = await response.json()
except Exception:
if lang == "ar":
await ctx.reply("โš ๏ธ ุชุนุฐุฑ ุชุญู…ูŠู„ ุงู„ู…ูŠู… ุงู„ุขู†. ุฌุฑุจ ู…ุฑุฉ ุฃุฎุฑู‰! ๐Ÿ”„")
else:
await ctx.reply("โš ๏ธ Could not load meme right now. Try again in a minute! ๐Ÿ”„")
return
embed = discord.Embed(
title=f"๐Ÿ˜‚ {data.get('title', 'Random Meme')}",
color=NEON_CYAN,
url=data.get("postLink")
)
if data.get("url"):
embed.set_image(url=data["url"])
embed.set_footer(text=f"๐Ÿ‘ {data.get('ups', 0)} | r/{data.get('subreddit', 'meme')}")
await ctx.reply(embed=embed)
@commands.hybrid_command(name="trivia", description=get_cmd_desc("commands.tools.trivia_desc"), hidden=True, with_app_command=False)
async def trivia(self, ctx: commands.Context, category: str = "", source: str = "local") -> None:
guild_id = ctx.guild.id if ctx.guild else None
lang = await self.bot.get_guild_language(guild_id)
normalized_source = source.strip().lower() or "local"
if normalized_source not in {"local", "api"}:
if lang == "ar":
await ctx.reply("โŒ ุงู„ู…ุตุฏุฑ ูŠุฌุจ ุฃู† ูŠูƒูˆู† `local` ุฃูˆ `api`")
else:
await ctx.reply("โŒ Source must be `local` or `api`")
return
normalized = category.strip().lower()
item: dict[str, object] | None = None
if lang == "ar":
topic_text = "ุงู„ุฃู„ุนุงุจ โ€ข ุงู„ุฃูู„ุงู… โ€ข ุงู„ู…ุณู„ุณู„ุงุช"
else:
topic_text = "Gaming โ€ข Movies โ€ข Series"
if normalized_source == "api":
try:
item = await self._fetch_trivia_api()
topic_text = "OpenTDB (50 questions pool)"
except Exception:
item = None
if item is None:
aliases = {
"gaming": "gaming", "games": "gaming", "game": "gaming", "ู‚ูŠู…ุฒ": "gaming", "ุงู„ุนุงุจ": "gaming",
"movies": "movies", "movie": "movies", "ุงูู„ุงู…": "movies", "ุฃูู„ุงู…": "movies",
"series": "series", "show": "series", "shows": "series", "ู…ุณู„ุณู„ุงุช": "series",
}
normalized = aliases.get(normalized, normalized) if normalized else random.choice(["gaming", "movies", "series"])
if normalized not in {"gaming", "movies", "series"}:
if lang == "ar":
await ctx.reply("โŒ ู‚ุณู… ุบูŠุฑ ู…ุฏุนูˆู…. ุงุณุชุฎุฏู…: gaming ุฃูˆ movies ุฃูˆ series")
else:
await ctx.reply("โŒ Unsupported category. Use: gaming, movies, or series")
return
# Get trivia from appropriate language bank
trivia_bank = TRIVIA_BANK.get(lang, TRIVIA_BANK["en"])
if normalized not in trivia_bank:
trivia_bank = TRIVIA_BANK["en"]
item = random.choice(trivia_bank[normalized])
view = TriviaView(owner_id=ctx.author.id, correct_index=int(item["a"]), lang=lang, cog=self, guild_id=guild_id)
for idx, choice in enumerate(item["c"]):
view.add_item(TriviaAnswerButton(str(choice), idx))
if lang == "ar":
title = "๐Ÿง  ุณุคุงู„ ุชุฑุงฺคูŠุง!"
cat_label = "๐Ÿ“ ุงู„ู‚ุณู…"
topics_label = "๐Ÿ“š ุงู„ู…ูˆุงุถูŠุน"
else:
title = "๐Ÿง  Trivia Time!"
cat_label = "๐Ÿ“ Category"
topics_label = "๐Ÿ“š Topics"
embed = discord.Embed(
title=title,
description=f"{panel_divider('blue')}\nโ“ {str(item['q'])}\n{panel_divider('blue')}",
color=NEON_CYAN,
)
cat_value = normalized.title() if normalized else ("General" if normalized_source == "api" else "Random")
embed.add_field(name=cat_label, value=cat_value, inline=True)
embed.add_field(name=topics_label, value=topic_text, inline=True)
await ctx.reply(embed=embed, view=view)
async def _fetch_gaming_news(self) -> list[dict[str, str]]:
return await fetch_gaming_news()
async def _fetch_free_games(self) -> list[dict[str, str]]:
return await fetch_free_games()
@staticmethod
def _store_icon(platform: str) -> str:
return store_icon(platform)
def _free_game_embed(self, item: dict[str, str]) -> tuple[discord.Embed, discord.ui.View]:
title = item["title"][:120]
link = item["link"]
embed = ImperialMotaz.craft_embed(
title=f"[FREE GAME] | {title}",
description=(
f"**{item.get('description', 'Limited-time free game offer!')}**\n\n"
"Claim it now from the official store page before it expires."
),
color=NEON_PINK,
footer="Free Games Tracker",
)
embed.url = link
embed.add_field(name="Platform", value=f"ใ€Œ {item.get('platform', 'Unknown')} ใ€", inline=True)
embed.add_field(name="Type", value=f"ใ€Œ {item.get('game_type', 'Game')} ใ€", inline=True)
embed.add_field(name="Original Price", value=f"ใ€Œ {item.get('original_price', 'N/A')} ใ€", inline=True)
embed.add_field(name="Ends On", value=f"ใ€Œ {item.get('end_date', 'N/A')} ใ€", inline=True)
embed.add_field(name="Link", value=f"[Open Giveaway]({link})", inline=False)
if item.get("image"):
embed.set_image(url=item["image"])
embed.set_thumbnail(url=self._store_icon(item.get("platform", "")))
return embed, FreeGameClaimView(link)
@commands.hybrid_command(name="gaming_news", description=get_cmd_desc("commands.tools.gaming_news_desc"))
async def gaming_news(self, ctx: commands.Context) -> None:
guild_id = ctx.guild.id if ctx.guild else None
lang = await self.bot.get_guild_language(guild_id)
try:
items = await self._fetch_gaming_news()
except Exception:
if lang == "ar":
await ctx.reply("โŒ ุชุนุฐุฑ ุฌู„ุจ ุฃุฎุจุงุฑ ุงู„ุฃู„ุนุงุจ ุงู„ุขู†.")
else:
await ctx.reply("โŒ Could not fetch gaming news right now.")
return
if not items:
if lang == "ar":
await ctx.reply("ู„ุง ุชูˆุฌุฏ ุฃุฎุจุงุฑ ู…ุชุงุญุฉ ุงู„ุขู†.")
else:
await ctx.reply("No gaming news available right now.")
return
if lang == "ar":
title = "๐Ÿ•น๏ธ ุขุฎุฑ ุฃุฎุจุงุฑ ุงู„ุฃู„ุนุงุจ ุนุงู„ู…ูŠู‹ุง"
else:
title = "๐Ÿ•น๏ธ Latest Gaming News Worldwide"
embed = discord.Embed(
title=title,
description=f"{panel_divider('green')}",
color=NEON_CYAN,
)
for item in items:
date_hint = f"\n๐Ÿ—“๏ธ {item['pub_date'][:16]}" if item.get("pub_date") else ""
if lang == "ar":
embed.add_field(name=item["title"][:256], value=f"๐Ÿ“ฐ [ุงู‚ุฑุฃ ุงู„ู…ู‚ุงู„]({item['link']}){date_hint}", inline=False)
else:
embed.add_field(name=item["title"][:256], value=f"๐Ÿ“ฐ [Read Article]({item['link']}){date_hint}", inline=False)
await ctx.reply(embed=embed)
@commands.hybrid_command(name="freegames", description=get_cmd_desc("commands.tools.freegames_desc"), with_app_command=False)
async def freegames(self, ctx: commands.Context) -> None:
guild_id = ctx.guild.id if ctx.guild else None
lang = await self.bot.get_guild_language(guild_id)
try:
items = await self._fetch_free_games()
except Exception:
if lang == "ar":
await ctx.reply("โŒ ุชุนุฐุฑ ุฌู„ุจ ุงู„ุนุฑูˆุถ ุงู„ุขู†.")
else:
await ctx.reply("โŒ Could not fetch free game offers right now.")
return
if not items:
if lang == "ar":
await ctx.reply("ู„ุง ุชูˆุฌุฏ ุนุฑูˆุถ ู…ุฌุงู†ูŠุฉ ู†ุดุทุฉ ุงู„ุขู†.")
else:
await ctx.reply("No active free game offers right now.")
return
for item in items:
embed, view = self._free_game_embed(item)
await ctx.reply(embed=embed, view=view)
@commands.hybrid_command(name="free_games", description=get_cmd_desc("commands.tools.free_games_desc"))
async def free_games(self, ctx: commands.Context) -> None:
await self.freegames(ctx)
@commands.hybrid_group(name="gamehub", fallback="panel", description="Interactive game hub with temporary private rooms")
async def gamehub(self, ctx: commands.Context, invite: discord.Member | None = None) -> None:
if not ctx.guild:
await ctx.reply("Server only.")
return
guild_id = ctx.guild.id
lang = await self.bot.get_guild_language(guild_id)
if invite and invite.bot:
if lang == "ar":
await ctx.reply("โŒ ุงุฏุนู ุนุถูˆุงู‹ ุญู‚ูŠู‚ูŠุงู‹ุŒ ู„ูŠุณ ุจูˆุช.")
else:
await ctx.reply("โŒ Invite a real member, not a bot.")
return
if invite and invite.id == ctx.author.id:
if lang == "ar":
await ctx.reply("โŒ ุงุฏุนู ุนุถูˆุงู‹ ุขุฎุฑ.")
else:
await ctx.reply("โŒ Invite another member.")
return
games_list = " โ€ข ".join([f"{g['emoji']} {g['name']}" for g in GAME_HUB_GAMES])
if lang == "ar":
title = "๐ŸŽฎ ู…ุฑูƒุฒ ุงู„ุฃู„ุนุงุจ"
desc = (
f"{panel_divider('pink')}\n"
f"๐Ÿ•น๏ธ ุงุฎุชุฑ ู„ุนุจุฉ ุฃุฏู†ุงู‡ ู„ุฅู†ุดุงุก **ุบุฑูุฉ ุฎุงุตุฉ**.\n"
f"๐Ÿ‘ฅ ุงุฏุนู ุตุฏูŠู‚ุงู‹ ุฃูˆ ุงู„ุนุจ ุถุฏ ุงู„ุจูˆุช.\n"
f"๐Ÿ—‘๏ธ ุงู„ุบุฑูุฉ ุชูุญุฐู ุชู„ู‚ุงุฆูŠุงู‹ ุนู†ุฏ ุฅุบู„ุงู‚ู‡ุง.\n"
f"{panel_divider('pink')}\n\n"
f"**ุงู„ุฃู„ุนุงุจ:** {games_list}"
)
invited_label = "๐ŸŽฏ ุงู„ุนุถูˆ ุงู„ู…ุฏุนูˆ"
else:
title = "๐ŸŽฎ Game Hub"
desc = (
f"{panel_divider('pink')}\n"
f"๐Ÿ•น๏ธ Select a game below to create a **private room**.\n"
f"๐Ÿ‘ฅ Invite a friend or play solo vs the bot.\n"
f"๐Ÿ—‘๏ธ The room auto-deletes when you close it.\n"
f"{panel_divider('pink')}\n\n"
f"**Games:** {games_list}"
)
invited_label = "๐ŸŽฏ Invited Member"
embed = discord.Embed(
title=title,
description=desc,
color=NEON_CYAN,
)
if invite:
embed.add_field(name=invited_label, value=invite.mention, inline=False)
await ctx.reply(embed=embed, view=GameHubView(self, invite.id if invite else None, lang))
@gamehub.command(name="xo", description="TicTacToe vs member or bot")
async def gamehub_xo(self, ctx: commands.Context, opponent: discord.Member | None = None) -> None:
await self.xo(ctx, opponent=opponent)
@gamehub.command(name="mario", description="Mario/Pac-Man arcade challenge")
async def gamehub_mario(self, ctx: commands.Context) -> None:
await self.mario(ctx)
@gamehub.command(name="dice", description="Roll dice")
async def gamehub_dice(self, ctx: commands.Context, sides: int = 6) -> None:
await self.dice(ctx, sides=sides)
@gamehub.command(name="slots", description="Slot machine game")
async def gamehub_slots(self, ctx: commands.Context, bet: int = 50) -> None:
await self.slots(ctx, bet=bet)
@gamehub.command(name="trivia", description="Gaming/Movies/Series trivia")
async def gamehub_trivia(self, ctx: commands.Context, category: str = "gaming", difficulty: str = "medium") -> None:
await self.trivia(ctx, category=category)
@commands.hybrid_command(name="xo", description=get_cmd_desc("commands.tools.xo_desc"), hidden=True, with_app_command=False)
async def xo(self, ctx: commands.Context, opponent: discord.Member | None = None) -> None:
guild_id = ctx.guild.id if ctx.guild else None
lang = await self.bot.get_guild_language(guild_id)
if opponent and opponent.bot:
opponent = None
view = TicTacToeView(ctx.author, opponent)
second = opponent.mention if opponent else "๐Ÿค– Bot"
if lang == "ar":
msg = await ctx.reply(
f"โŽ ุชูŠูƒ ุชุงูƒ ุชูˆ: {ctx.author.mention} (X) ุถุฏ {second} (O)\n"
f"๐ŸŽฎ ุงู„ุฏูˆุฑ: {ctx.author.mention}",
view=view
)
else:
msg = await ctx.reply(
f"โŽ TicTacToe: {ctx.author.mention} (X) vs {second} (O)\n"
f"๐ŸŽฎ Turn: {ctx.author.mention}",
view=view
)
if opponent is None:
await view._maybe_bot(msg)
@commands.hybrid_command(name="choose", hidden=True, with_app_command=False, description=get_cmd_desc("commands.tools.choose_desc"))
async def choose(self, ctx: commands.Context, *, options: str) -> None:
guild_id = ctx.guild.id if ctx.guild else None
lang = await self.bot.get_guild_language(guild_id)
parts = [p.strip() for p in options.split(",") if p.strip()]
if len(parts) < 2:
if lang == "ar":
await ctx.reply("โŒ ุฃุฏุฎู„ ุฎูŠุงุฑูŠู† ุนู„ู‰ ุงู„ุฃู‚ู„ ู…ูุตูˆู„ูŠู† ุจููˆุงุตู„.")
else:
await ctx.reply("โŒ Provide at least 2 options separated by commas.")
return
choice = random.choice(parts)
if lang == "ar":
embed = discord.Embed(
title="๐ŸŽฒ ุงุฎุชูŠุงุฑ ุนุดูˆุงุฆูŠ",
description=(
f"{panel_divider('purple')}\n"
f"๐Ÿ“ ุงู„ุฎูŠุงุฑุงุช: {', '.join(parts)}\n"
f"๐ŸŽฏ **ุงู„ู…ุฎุชุงุฑ:** {choice}\n"
f"{panel_divider('purple')}"
),
color=NEON_PINK,
)
else:
embed = discord.Embed(
title="๐ŸŽฒ Random Choice",
description=(
f"{panel_divider('purple')}\n"
f"๐Ÿ“ Options: {', '.join(parts)}\n"
f"๐ŸŽฏ **Selected:** {choice}\n"
f"{panel_divider('purple')}"
),
color=NEON_PINK,
)
await ctx.reply(embed=embed)
@commands.hybrid_command(name="mario", description=get_cmd_desc("commands.tools.mario_desc"), hidden=True, with_app_command=False)
async def mario(self, ctx: commands.Context) -> None:
guild_id = ctx.guild.id if ctx.guild else None
lang = await self.bot.get_guild_language(guild_id)
coins = random.randint(5, 120)
level = random.randint(1, 8)
if lang == "ar":
embed = discord.Embed(
title="๐Ÿ„ ุชุญุฏูŠ ุงู„ุขุฑูƒูŠุฏ",
description=(
f"{panel_divider('orange')}\n"
f"๐Ÿ„ {ctx.author.mention} ุฌู…ุน **{coins}** ุนู…ู„ุฉ!\n"
f"โœ… ุชุฌุงูˆุฒ ุงู„ู…ุฑุญู„ุฉ **{level}**!\n"
f"{panel_divider('orange')}"
),
color=NEON_ORANGE,
)
embed.add_field(name="๐ŸŽ ู…ูƒุงูุฃุฉ", value="ุชู… ูุชุญ combo ุจุงูƒ ู…ุงู†! <:animatedarrowyellow:1477261257592668271>", inline=False)
else:
embed = discord.Embed(
title="๐Ÿ„ Arcade Challenge",
description=(
f"{panel_divider('orange')}\n"
f"๐Ÿ„ {ctx.author.mention} collected **{coins}** coins!\n"
f"โœ… Cleared level **{level}**!\n"
f"{panel_divider('orange')}"
),
color=NEON_ORANGE,
)
embed.add_field(name="๐ŸŽ Bonus", value="Pac-Man combo unlocked! <:animatedarrowyellow:1477261257592668271>", inline=False)
embed.set_thumbnail(url="https://upload.wikimedia.org/wikipedia/en/a/a9/MarioNSMBUDeluxe.png")
await ctx.reply(embed=embed)
@commands.hybrid_command(name="dice", description=get_cmd_desc("commands.tools.dice_desc"), hidden=True, with_app_command=False)
async def dice(self, ctx: commands.Context, sides: int = 6) -> None:
"""Roll a dice with specified sides."""
guild_id = ctx.guild.id if ctx.guild else None
lang = await self.bot.get_guild_language(guild_id)
sides = max(2, min(sides, 100))
result = random.randint(1, sides)
if lang == "ar":
embed = gaming_embed(
"๐ŸŽฒ ุฑู…ูŠ ุงู„ู†ุฑุฏ",
f"๐ŸŽฒ ุฑู…ูŠุช ู†ุฑุฏ ุจู€ **{sides}** ุฃูˆุฌู‡\n๐ŸŽฏ ุงู„ู†ุชูŠุฌุฉ: **{result}**"
)
else:
embed = gaming_embed(
"๐ŸŽฒ Dice Roll",
f"๐ŸŽฒ Rolled a **{sides}**-sided dice\n๐ŸŽฏ Result: **{result}**"
)
await ctx.reply(embed=embed)
@commands.hybrid_command(name="slots", description=get_cmd_desc("commands.tools.slots_desc"), hidden=True, with_app_command=False)
async def slots(self, ctx: commands.Context, bet: int = 10) -> None:
"""Play a slot machine game."""
guild_id = ctx.guild.id if ctx.guild else None
lang = await self.bot.get_guild_language(guild_id)
bet = max(10, bet)
# Check balance
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 wallet < bet:
if lang == "ar":
await ctx.reply(f"โŒ ู„ูŠุณ ู„ุฏูŠูƒ ุฑุตูŠุฏ ูƒุงููŠ. ู…ุญูุธุชูƒ: **{wallet}** ุนู…ู„ุฉ")
else:
await ctx.reply(f"โŒ Insufficient balance. Your wallet: **{wallet}** coins")
return
# Slot symbols
symbols = ["๐Ÿ’", "๐Ÿ‹", "๐ŸŠ", "๐Ÿ‡", "โœ…", "๐Ÿ’Ž", "7๏ธโƒฃ"]
weights = [25, 20, 18, 15, 12, 7, 3] # Weighted probabilities
# Spin
results = random.choices(symbols, weights=weights, k=3)
# Calculate winnings
if results[0] == results[1] == results[2]:
# Jackpot!
if results[0] == "7๏ธโƒฃ":
multiplier = 10
elif results[0] == "๐Ÿ’Ž":
multiplier = 7
elif results[0] == "โœ…":
multiplier = 5
else:
multiplier = 3
winnings = bet * multiplier
elif results[0] == results[1] or results[1] == results[2] or results[0] == results[2]:
winnings = bet * 2
else:
winnings = 0
# Update balance
if winnings > 0:
await self._add_coins(guild_id, ctx.author.id, winnings - bet)
else:
await self._add_coins(guild_id, ctx.author.id, -bet)
# Build display
slot_display = " | ".join(results)
if winnings >= bet * 5:
result_text = f"๐ŸŽฐ **{'ุฌุงุฆุฒุฉ ูƒุจุฑู‰!' if lang == 'ar' else 'JACKPOT!'}** {E_FIRE}"
elif winnings > 0:
result_text = f"โœ… **{'ูุฒุช!' if lang == 'ar' else 'You won!'}**"
else:
result_text = f"โŒ **{'ุญุธ ุฃูˆูุฑ' if lang == 'ar' else 'Better luck next time'}**"
if lang == "ar":
embed = gaming_embed(
"๐ŸŽฐ ู…ุงูƒูŠู†ุฉ ุงู„ู‚ู…ุงุฑ",
f"โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\nโ”‚ {slot_display} โ”‚\nโ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n\n{result_text}\n๐Ÿ’ฐ **ุงู„ุฑุงุจุญ:** {winnings} ุนู…ู„ุฉ"
)
else:
embed = gaming_embed(
"๐ŸŽฐ Slot Machine",
f"โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\nโ”‚ {slot_display} โ”‚\nโ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n\n{result_text}\n๐Ÿ’ฐ **Won:** {winnings} coins"
)
await ctx.reply(embed=embed)
async def _add_coins(self, guild_id: int, user_id: int, amount: int) -> None:
"""Helper to add/remove coins from user balance."""
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 + ?",
guild_id,
user_id,
amount if amount > 0 else 0,
amount,
)
async def setup(bot: commands.Bot) -> None:
await bot.add_cog(Fun(bot))