diff --git a/rubyapi/rubyapi.py b/rubyapi/rubyapi.py index 03b30cf..6a493d1 100644 --- a/rubyapi/rubyapi.py +++ b/rubyapi/rubyapi.py @@ -1,4 +1,5 @@ from typing import Optional +import aiohttp import discord from redbot.core import commands, Config from redbot.core.bot import Red @@ -11,15 +12,21 @@ class RubyAPI(commands.Cog): def __init__(self, bot: Red): self.bot = bot self.config = Config.get_conf(self, identifier=867530999, force_registration=True) + self.session = aiohttp.ClientSession() default_guild = { "interaction_url": "https://ruby.valerie.lol/api/interactions", "verify_url": "https://ruby.valerie.lol/verify-user", - "enabled": False + "enabled": False, + "api_key": None } self.config.register_guild(**default_guild) + def cog_unload(self): + if self.session: + self.bot.loop.create_task(self.session.close()) + @commands.group(name="rubyapi") @commands.admin_or_permissions(manage_guild=True) async def rubyapi(self, ctx: commands.Context): @@ -33,6 +40,16 @@ class RubyAPI(commands.Cog): status = "enabled" if toggle else "disabled" await ctx.send(f"Ruby API integration has been {status}.") + @rubyapi.command(name="setkey") + async def set_api_key(self, ctx: commands.Context, api_key: str): + """Set the API key for authentication.""" + await self.config.guild(ctx.guild).api_key.set(api_key) + await ctx.send("API key has been set. I'll delete your message for security.") + try: + await ctx.message.delete() + except discord.HTTPException: + pass + @rubyapi.command(name="setinteraction") async def set_interaction_url(self, ctx: commands.Context, url: str): """Set the interaction endpoint URL.""" @@ -59,16 +76,35 @@ class RubyAPI(commands.Cog): enabled = "Yes" if guild_config["enabled"] else "No" interaction_url = guild_config["interaction_url"] verify_url = guild_config["verify_url"] + has_api_key = "Yes" if guild_config["api_key"] else "No" message = ( "Ruby API Settings:\n" f"Enabled: {enabled}\n" + f"API Key Set: {has_api_key}\n" f"Interaction URL: {interaction_url}\n" f"Verify URL: {verify_url}" ) await ctx.send(box(message)) + async def _make_api_request(self, url: str, method: str = "GET", **kwargs): + """Make an API request with proper headers and error handling.""" + headers = kwargs.pop("headers", {}) + if api_key := kwargs.pop("api_key", None): + headers["Authorization"] = f"Bearer {api_key}" + + try: + async with self.session.request(method, url, headers=headers, **kwargs) as resp: + if resp.status == 429: # Rate limited + retry_after = float(resp.headers.get("Retry-After", 5)) + return {"error": "rate_limited", "retry_after": retry_after} + + data = await resp.json() + return data + except aiohttp.ClientError as e: + return {"error": f"API request failed: {str(e)}"} + @commands.command() async def verifyuser(self, ctx: commands.Context, user: Optional[discord.Member] = None): """Verify a user's linked roles.""" @@ -77,10 +113,44 @@ class RubyAPI(commands.Cog): user = user or ctx.author verify_url = await self.config.guild(ctx.guild).verify_url() + api_key = await self.config.guild(ctx.guild).api_key() - # Here you would implement the actual API call to verify the user - # For demonstration, we'll just show a message - await ctx.send(f"Verification would be performed for {user.mention} using {verify_url}") + if not api_key: + return await ctx.send("API key has not been set. Please set it using `[p]rubyapi setkey`") + + payload = { + "user_id": str(user.id), + "guild_id": str(ctx.guild.id) + } + + async with ctx.typing(): + result = await self._make_api_request( + verify_url, + method="POST", + json=payload, + api_key=api_key + ) + + if "error" in result: + return await ctx.send(f"Error verifying user: {result['error']}") + + if result.get("verified", False): + roles_to_add = [] + for role_id in result.get("roles", []): + if role := ctx.guild.get_role(int(role_id)): + roles_to_add.append(role) + + if roles_to_add: + try: + await user.add_roles(*roles_to_add, reason="Linked roles verification") + role_names = ", ".join(role.name for role in roles_to_add) + await ctx.send(f"Verified {user.mention}! Added roles: {role_names}") + except discord.HTTPException as e: + await ctx.send(f"Error adding roles: {str(e)}") + else: + await ctx.send(f"Verified {user.mention}, but no roles needed to be added.") + else: + await ctx.send(f"Could not verify {user.mention}. Please make sure their accounts are properly linked.") @commands.Cog.listener() async def on_interaction(self, interaction: discord.Interaction): @@ -90,8 +160,38 @@ class RubyAPI(commands.Cog): if not await self.config.guild(interaction.guild).enabled(): return - - # Here you would implement the actual interaction handling - # For demonstration, we'll just log the interaction + interaction_url = await self.config.guild(interaction.guild).interaction_url() - # You would typically make an API request to the interaction_url here \ No newline at end of file + api_key = await self.config.guild(interaction.guild).api_key() + + if not api_key: + return # Silent return if API key is not set + + # Forward the interaction to the API + payload = { + "type": interaction.type.value, + "guild_id": str(interaction.guild_id), + "channel_id": str(interaction.channel_id), + "data": interaction.data, + "member": { + "user": { + "id": str(interaction.user.id), + "username": interaction.user.name, + "discriminator": interaction.user.discriminator, + }, + "roles": [str(role.id) for role in interaction.user.roles], + } if interaction.user else None + } + + result = await self._make_api_request( + interaction_url, + method="POST", + json=payload, + api_key=api_key + ) + + # Handle the API response if needed + if "error" in result: + # Log the error but don't respond to the interaction + # as it might have been handled elsewhere + print(f"Error forwarding interaction: {result['error']}") \ No newline at end of file