Ruby-Cogs/rubyapi/rubyapi.py

197 lines
No EOL
7.8 KiB
Python

from typing import Optional
import aiohttp
import discord
from redbot.core import commands, Config
from redbot.core.bot import Red
from redbot.core.utils.chat_formatting import box
class RubyAPI(commands.Cog):
"""Ruby API integration for interactions and linked roles verification."""
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,
"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):
"""Ruby API configuration commands."""
pass
@rubyapi.command(name="enable")
async def enable_integration(self, ctx: commands.Context, toggle: bool):
"""Enable or disable Ruby API integration."""
await self.config.guild(ctx.guild).enabled.set(toggle)
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."""
if not url.startswith(("http://", "https://")):
return await ctx.send("Please provide a valid URL starting with http:// or https://")
await self.config.guild(ctx.guild).interaction_url.set(url)
await ctx.send("Interaction endpoint URL has been updated.")
@rubyapi.command(name="setverify")
async def set_verify_url(self, ctx: commands.Context, url: str):
"""Set the verification endpoint URL."""
if not url.startswith(("http://", "https://")):
return await ctx.send("Please provide a valid URL starting with http:// or https://")
await self.config.guild(ctx.guild).verify_url.set(url)
await ctx.send("Verification endpoint URL has been updated.")
@rubyapi.command(name="settings")
async def show_settings(self, ctx: commands.Context):
"""Show current Ruby API settings."""
guild_config = await self.config.guild(ctx.guild).all()
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."""
if not await self.config.guild(ctx.guild).enabled():
return await ctx.send("Ruby API integration is not enabled in this server.")
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()
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):
"""Handle incoming Discord interactions."""
if not interaction.guild:
return
if not await self.config.guild(interaction.guild).enabled():
return
interaction_url = await self.config.guild(interaction.guild).interaction_url()
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']}")