import asyncio import datetime import random import discord from discord import option from discord.ext import commands class RouletteCog(commands.Cog): def __init__(self, bot: discord.Bot): self.bot = bot self.cooldown = 60 * 3 self.active_games = {} def _build_view(self, game: dict) -> discord.ui.View: view = discord.ui.View(timeout=None) if game["status"] == "pending": async def accept_callback(interaction: discord.Interaction): if interaction.user.id != game["target"].id: await interaction.response.send_message( "Only the challenged member can accept this duel.", ephemeral=True, ) return game["status"] = "ready" game["current_player"] = game["author"] if game.get("message"): await game["message"].edit( content=( f"{game['author'].mention} challenged {game['target'].mention} to a Russian roulette duel.\n" f"✅ {game['target'].mention} accepted. {game['current_player'].mention}, click Fire!" ), view=self._build_view(game), ) await interaction.response.defer() async def decline_callback(interaction: discord.Interaction): if interaction.user.id != game["target"].id: await interaction.response.send_message( "Only the challenged member can decline this duel.", ephemeral=True, ) return game["status"] = "declined" self.active_games.pop((game["guild_id"], game["author"].id, game["target"].id), None) if game.get("message"): await game["message"].edit( content=f"{game['target'].mention} declined the challenge.", view=None, ) await interaction.response.defer() accept_button = discord.ui.Button(label="Accept", style=discord.ButtonStyle.green) accept_button.callback = accept_callback view.add_item(accept_button) decline_button = discord.ui.Button(label="Decline", style=discord.ButtonStyle.red) decline_button.callback = decline_callback view.add_item(decline_button) elif game["status"] in {"ready", "playing"}: async def fire_callback(interaction: discord.Interaction): if interaction.user.id != game["current_player"].id: await interaction.response.send_message( f"Wait for {game['current_player'].mention} to fire.", ephemeral=True, ) return if interaction.user.id in game["fired"]: await interaction.response.send_message( "You already fired once. Only one shot per player is allowed.", ephemeral=True, ) return game["fired"].add(interaction.user.id) is_shot = random.randint(1, 6) == 1 if is_shot: game["status"] = "finished" self.active_games.pop((game["guild_id"], game["author"].id, game["target"].id), None) await self._mute_member(interaction.user, 60) if game.get("message"): await game["message"].edit( content=( f"💥 {interaction.user.mention} pulled the trigger and got shot.\n" f"They were muted for 60 seconds." ), view=None, ) else: if len(game["fired"]) >= 2: game["status"] = "finished" self.active_games.pop((game["guild_id"], game["author"].id, game["target"].id), None) if game.get("message"): await game["message"].edit( content=( f"🔫 {interaction.user.mention} survived and both players have fired once.\n" f"No one was shot. The duel ends in a draw." ), view=None, ) else: game["status"] = "playing" game["current_player"] = game["target"] if interaction.user.id == game["author"].id else game["author"] if game.get("message"): await game["message"].edit( content=( f"🔫 {interaction.user.mention} survived.\n" f"Next turn: {game['current_player'].mention}" ), view=self._build_view(game), ) await interaction.response.defer() fire_button = discord.ui.Button(label="Fire", style=discord.ButtonStyle.danger) fire_button.callback = fire_callback view.add_item(fire_button) return view async def _mute_member(self, member: discord.Member, seconds: int = 60): until = discord.utils.utcnow() + datetime.timedelta(seconds=seconds) try: await member.timeout(until=until, reason="Russian roulette loss") except AttributeError: await member.edit(timed_out_until=until, reason="Russian roulette loss") @commands.slash_command( name="roulette", description="Challenge other server members. The loser is getting muted for 1 minute", ) @option( name="target", description="The member to challenge", input_type=discord.Member, required=True, ) async def roulette( self, ctx: discord.ApplicationContext, target: discord.Member, ): author = ctx.author self_test = author.id == target.id if self_test: target = author game = { "author": author, "target": target, "status": "pending", "current_player": None, "guild_id": ctx.guild_id, "message": None, "fired": set(), } self.active_games[(ctx.guild_id, author.id, target.id)] = game await ctx.send_response( f"{author.mention} challenged {target.mention} to a Russian roulette duel.\n" f"The target has 60 seconds to accept or decline.", view=self._build_view(game), ) if self_test: await ctx.send_followup( "**Self-challenge enabled for testing.**\n" "The game will start immediately and you can test the flow.", ephemeral=True, ) try: game["message"] = await ctx.interaction.original_response() except Exception: game["message"] = None async def timeout_handler(): try: await asyncio.sleep(60) except asyncio.CancelledError: return if game.get("status") == "pending": game["status"] = "timeout" self.active_games.pop((ctx.guild_id, author.id, target.id), None) await self._mute_member(target, 60) if game.get("message"): await game["message"].edit( content=f"⏰ {target.mention} did not respond in time and was muted for 60 seconds.", view=None, ) asyncio.create_task(timeout_handler())