From 9bc2f84feaafd181a0a90c2b8b46714d5800cba4 Mon Sep 17 00:00:00 2001 From: Slipstream Date: Fri, 13 Jun 2025 21:35:38 +0000 Subject: [PATCH] Enhance giveaways with OAuth Nitro checks --- cogs/giveaways_cog.py | 109 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 90 insertions(+), 19 deletions(-) diff --git a/cogs/giveaways_cog.py b/cogs/giveaways_cog.py index 7d356ea..1c8184c 100644 --- a/cogs/giveaways_cog.py +++ b/cogs/giveaways_cog.py @@ -9,16 +9,35 @@ import json import os import aiofiles # Import aiofiles import aiofiles.os +import discord_oauth GIVEAWAY_DATA_FILE = "data/giveaways.json" DATA_DIR = "data" # --- Helper Functions --- +async def get_nitro_status_oauth(user: discord.User | discord.Member) -> bool | None: + """Return True if user has Nitro according to OAuth info, False if not, None if unknown.""" + token = await discord_oauth.get_token(str(user.id)) + if not token: + return None + try: + user_info = await discord_oauth.get_user_info(token) + except Exception: + return None + return user_info.get("premium_type", 0) in (1, 2) + + +# --- Additional Helper Functions --- async def is_user_nitro_like( user: discord.User | discord.Member, bot: commands.Bot = None ) -> bool: - """Checks if a user has an animated avatar or a banner, indicating Nitro.""" + """Heuristically check if a user has Nitro, falling back to OAuth if available.""" + + nitro_oauth = await get_nitro_status_oauth(user) + if nitro_oauth is not None: + return nitro_oauth + # Fetch the full user object to get banner information if bot: try: @@ -59,10 +78,23 @@ class GiveawayEnterButton(ui.Button["GiveawayEnterView"]): await interaction.message.edit(view=self.view) return - if giveaway["is_nitro_giveaway"]: - if not await is_user_nitro_like(interaction.user, bot=self.cog.bot): + if giveaway["is_nitro_giveaway"] or giveaway.get("exclude_nitro_users"): + nitro_status = await get_nitro_status_oauth(interaction.user) + if nitro_status is None: await interaction.response.send_message( - "This is a Nitro-exclusive giveaway. You don't appear to have Nitro (animated avatar or banner).", + "Please authenticate with /auth so I can verify your Nitro status before entering.", + ephemeral=True, + ) + return + if giveaway["is_nitro_giveaway"] and not nitro_status: + await interaction.response.send_message( + "This is a Nitro-exclusive giveaway, and your account does not appear to have Nitro.", + ephemeral=True, + ) + return + if giveaway.get("exclude_nitro_users") and nitro_status: + await interaction.response.send_message( + "Nitro users are excluded from this giveaway.", ephemeral=True, ) return @@ -150,11 +182,16 @@ class GiveawayRerollButton(ui.Button["GiveawayEndView"]): except discord.NotFound: continue # Skip if user cannot be found if user and not user.bot: - # Apply Nitro check again if it was a nitro giveaway - if giveaway_data.get( - "is_nitro_giveaway", False - ) and not is_user_nitro_like(user): - continue + if giveaway_data.get("is_nitro_giveaway") or giveaway_data.get( + "exclude_nitro_users" + ): + nitro_status = await get_nitro_status_oauth(user) + if nitro_status is None: + continue + if giveaway_data.get("is_nitro_giveaway") and not nitro_status: + continue + if giveaway_data.get("exclude_nitro_users") and nitro_status: + continue entrants_users.append(user) if not entrants_users: @@ -228,6 +265,7 @@ class GiveawaysCog(commands.Cog, name="Giveaways"): # "creator_id": int, # "participants": set(), # Store user_ids. Stored as list in JSON. # "is_nitro_giveaway": bool, + # "exclude_nitro_users": bool, # "ended": bool # } # Ensure data directory exists before loading/saving @@ -325,6 +363,7 @@ class GiveawaysCog(commands.Cog, name="Giveaways"): ) gw_data["participants"] = set(gw_data.get("participants", [])) gw_data.setdefault("is_nitro_giveaway", False) + gw_data.setdefault("exclude_nitro_users", False) gw_data.setdefault( "ended", gw_data["end_time"] <= now ) # Set ended if time has passed @@ -432,7 +471,8 @@ class GiveawaysCog(commands.Cog, name="Giveaways"): prize="What is the prize?", duration="How long should the giveaway last? (e.g., 10m, 1h, 2d, 1w)", winners="How many winners? (default: 1)", - nitro_giveaway="Is this a Nitro-only giveaway? (checks for animated avatar/banner)", + nitro_giveaway="Is this a Nitro-only giveaway? (OAuth verification)", + exclude_nitro="Exclude Nitro users from entering?", ) @app_commands.checks.has_permissions(manage_guild=True) async def create_giveaway_slash( @@ -442,6 +482,7 @@ class GiveawaysCog(commands.Cog, name="Giveaways"): duration: str, winners: int = 1, nitro_giveaway: bool = False, + exclude_nitro: bool = False, ): """Slash command to create a giveaway using buttons.""" parsed_duration = self.parse_duration(duration) @@ -469,6 +510,8 @@ class GiveawaysCog(commands.Cog, name="Giveaways"): ) if nitro_giveaway: embed.description += "\n*This is a Nitro-exclusive giveaway!*" + if exclude_nitro: + embed.description += "\n*Users with Nitro are excluded from entering.*" embed.set_footer( text=f"Giveaway started by {interaction.user.display_name}. Entries: 0" ) # Initial entry count @@ -488,6 +531,7 @@ class GiveawaysCog(commands.Cog, name="Giveaways"): "creator_id": interaction.user.id, "participants": set(), "is_nitro_giveaway": nitro_giveaway, + "exclude_nitro_users": exclude_nitro, "ended": False, } self.active_giveaways.append(giveaway_data) @@ -559,10 +603,16 @@ class GiveawaysCog(commands.Cog, name="Giveaways"): if user_to_check.bot: continue - if giveaway_data["is_nitro_giveaway"] and not is_user_nitro_like( - user_to_check + if giveaway_data["is_nitro_giveaway"] or giveaway_data.get( + "exclude_nitro_users" ): - continue # Skip non-nitro users for nitro giveaways + nitro_status = await get_nitro_status_oauth(user_to_check) + if nitro_status is None: + continue + if giveaway_data["is_nitro_giveaway"] and not nitro_status: + continue + if giveaway_data.get("exclude_nitro_users") and nitro_status: + continue entrants_users.append(user_to_check) winners_list = [] @@ -685,10 +735,16 @@ class GiveawaysCog(commands.Cog, name="Giveaways"): user_id ) or await self.bot.fetch_user(user_id) if user and not user.bot: - if giveaway_info.get( - "is_nitro_giveaway", False - ) and not is_user_nitro_like(user): - continue + if giveaway_info.get("is_nitro_giveaway") or giveaway_info.get( + "exclude_nitro_users" + ): + nitro_status = await get_nitro_status_oauth(user) + if nitro_status is None: + continue + if giveaway_info.get("is_nitro_giveaway") and not nitro_status: + continue + if giveaway_info.get("exclude_nitro_users") and nitro_status: + continue entrants.add(user) if not entrants: await interaction.followup.send( @@ -704,8 +760,23 @@ class GiveawaysCog(commands.Cog, name="Giveaways"): reaction_found = True async for user in reaction.users(): if not user.bot: - # For manual reaction roll, we might not know if it was nitro_giveaway - # Consider adding a parameter to manual_roll for this if needed + if giveaway_info and ( + giveaway_info.get("is_nitro_giveaway") + or giveaway_info.get("exclude_nitro_users") + ): + nitro_status = await get_nitro_status_oauth(user) + if nitro_status is None: + continue + if ( + giveaway_info.get("is_nitro_giveaway") + and not nitro_status + ): + continue + if ( + giveaway_info.get("exclude_nitro_users") + and nitro_status + ): + continue entrants.add(user) break if not reaction_found: