Add API key management and session handling to RubyAPI cog. Implement commands to set the API key and verify users, enhancing interaction with external API. Introduce error handling for API requests and ensure proper session closure on cog unload.
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run

This commit is contained in:
Valerie 2025-05-25 04:18:39 -04:00
parent 09726661f3
commit 5a5814bed2

View file

@ -1,4 +1,5 @@
from typing import Optional from typing import Optional
import aiohttp
import discord import discord
from redbot.core import commands, Config from redbot.core import commands, Config
from redbot.core.bot import Red from redbot.core.bot import Red
@ -11,15 +12,21 @@ class RubyAPI(commands.Cog):
def __init__(self, bot: Red): def __init__(self, bot: Red):
self.bot = bot self.bot = bot
self.config = Config.get_conf(self, identifier=867530999, force_registration=True) self.config = Config.get_conf(self, identifier=867530999, force_registration=True)
self.session = aiohttp.ClientSession()
default_guild = { default_guild = {
"interaction_url": "https://ruby.valerie.lol/api/interactions", "interaction_url": "https://ruby.valerie.lol/api/interactions",
"verify_url": "https://ruby.valerie.lol/verify-user", "verify_url": "https://ruby.valerie.lol/verify-user",
"enabled": False "enabled": False,
"api_key": None
} }
self.config.register_guild(**default_guild) 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.group(name="rubyapi")
@commands.admin_or_permissions(manage_guild=True) @commands.admin_or_permissions(manage_guild=True)
async def rubyapi(self, ctx: commands.Context): async def rubyapi(self, ctx: commands.Context):
@ -33,6 +40,16 @@ class RubyAPI(commands.Cog):
status = "enabled" if toggle else "disabled" status = "enabled" if toggle else "disabled"
await ctx.send(f"Ruby API integration has been {status}.") 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") @rubyapi.command(name="setinteraction")
async def set_interaction_url(self, ctx: commands.Context, url: str): async def set_interaction_url(self, ctx: commands.Context, url: str):
"""Set the interaction endpoint URL.""" """Set the interaction endpoint URL."""
@ -59,16 +76,35 @@ class RubyAPI(commands.Cog):
enabled = "Yes" if guild_config["enabled"] else "No" enabled = "Yes" if guild_config["enabled"] else "No"
interaction_url = guild_config["interaction_url"] interaction_url = guild_config["interaction_url"]
verify_url = guild_config["verify_url"] verify_url = guild_config["verify_url"]
has_api_key = "Yes" if guild_config["api_key"] else "No"
message = ( message = (
"Ruby API Settings:\n" "Ruby API Settings:\n"
f"Enabled: {enabled}\n" f"Enabled: {enabled}\n"
f"API Key Set: {has_api_key}\n"
f"Interaction URL: {interaction_url}\n" f"Interaction URL: {interaction_url}\n"
f"Verify URL: {verify_url}" f"Verify URL: {verify_url}"
) )
await ctx.send(box(message)) 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() @commands.command()
async def verifyuser(self, ctx: commands.Context, user: Optional[discord.Member] = None): async def verifyuser(self, ctx: commands.Context, user: Optional[discord.Member] = None):
"""Verify a user's linked roles.""" """Verify a user's linked roles."""
@ -77,10 +113,44 @@ class RubyAPI(commands.Cog):
user = user or ctx.author user = user or ctx.author
verify_url = await self.config.guild(ctx.guild).verify_url() 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 if not api_key:
# For demonstration, we'll just show a message return await ctx.send("API key has not been set. Please set it using `[p]rubyapi setkey`")
await ctx.send(f"Verification would be performed for {user.mention} using {verify_url}")
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() @commands.Cog.listener()
async def on_interaction(self, interaction: discord.Interaction): async def on_interaction(self, interaction: discord.Interaction):
@ -91,7 +161,37 @@ class RubyAPI(commands.Cog):
if not await self.config.guild(interaction.guild).enabled(): if not await self.config.guild(interaction.guild).enabled():
return 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() interaction_url = await self.config.guild(interaction.guild).interaction_url()
# You would typically make an API request to the interaction_url here 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']}")