From c1cf6eba687d02067f17e1c7e2c529c3b5ec77d9 Mon Sep 17 00:00:00 2001 From: Bugsy <124417333+bugsbirb@users.noreply.github.com> Date: Sun, 2 Feb 2025 15:23:41 +0000 Subject: [PATCH] Roblox + ERM intergrations --- Cogs/Configuration/Components/Infractions.py | 285 +++++++++--------- Cogs/Configuration/Components/Modmail.py | 4 +- Cogs/Configuration/Components/integrations.py | 225 ++++++++++++++ Cogs/Configuration/Configuration.py | 14 + Cogs/Events/on_infraction.py | 16 +- Cogs/Events/on_infraction_edit.py | 2 +- Cogs/Events/on_promotion.py | 2 +- Cogs/Modules/infractions.py | 2 +- Cogs/Modules/integrations.py | 122 ++++++++ Cogs/Modules/staff.py | 2 +- main.py | 3 +- utils/roblox.py | 67 ++++ 12 files changed, 599 insertions(+), 145 deletions(-) create mode 100644 Cogs/Configuration/Components/integrations.py create mode 100644 Cogs/Modules/integrations.py create mode 100644 utils/roblox.py diff --git a/Cogs/Configuration/Components/Infractions.py b/Cogs/Configuration/Components/Infractions.py index 33aad14..794e9f9 100644 --- a/Cogs/Configuration/Components/Infractions.py +++ b/Cogs/Configuration/Components/Infractions.py @@ -17,6 +17,7 @@ Customisation = DB["Customisation"] + class InfractionOption(discord.ui.Select): def __init__(self, author: discord.Member): super().__init__( @@ -565,7 +566,7 @@ async def on_submit(self, interaction: discord.Interaction): return await interaction.response.send_message( embed=embed, ephemeral=True ) - view = discord.ui.View() + view = Done() view.add_item(InfractionTypesAction(self.author, self.name.value)) return await interaction.response.edit_message( content=f"{tick} **{interaction.user.display_name}**, you are now editing the infraction type.", @@ -674,198 +675,210 @@ def __init__(self, author, name): emoji="<:erm:1203823601107861504>", description="Requires ERM API", ), + discord.SelectOption( + label="Kick Group Member", emoji="<:robloxWhite:1200584000390053899>" + ), + discord.SelectOption( + label="Change Group Role", emoji="<:robloxWhite:1200584000390053899>" + ), ] super().__init__( - placeholder="Infraction Type Action", + placeholder="Select Infraction Actions", min_values=1, - max_values=3, + max_values=1, options=options, row=0, ) async def callback(self, interaction: discord.Interaction): - options = self.values - view = discord.ui.View() - filter = {"guild_id": interaction.guild.id, "name": self.name} - if "Staff Database Removal" in options: - await infractiontypeactions.update_one( - filter, {"$set": {"name": self.name, "dbremoval": True}}, upsert=True + if interaction.user.id != self.author.id: + return await interaction.response.send_message( + embed=discord.Embed( + description=f"{redx} This is not your panel!", + color=discord.Color.red(), + ), + ephemeral=True, ) + from utils.roblox import GroupRoles - if ( - "Send to channel" not in options - and "Give Roles" not in options - and "Remove Roles" not in options - and "Staff Database Removal" not in options - and "Void Shift" not in options - and "Kick Group Member" not in options - ): - await interaction.response.edit_message( - content=f"{tick} successfully setup Infraction type.", view=None - ) - return - if "Void Shift" in options: - await infractiontypeactions.update_one( - filter, {"$set": {"name": self.name, "voidshift": True}}, upsert=True - ) - if ( - "Send to channel" not in options - and "Give Roles" not in options - and "Remove Roles" not in options - and "Staff Database Removal" not in options - and "Kick Group Member" not in options - ): - await interaction.response.edit_message( - content=f"{tick} successfully setup Infraction type.", view=None + filter = {"guild_id": interaction.guild.id, "name": self.name} + update_data = {"name": self.name} + view = discord.ui.View() + + action_mapping = { + "Send to channel": TypeChannel, + "Give Roles": GiveRoles, + "Remove Roles": RemoveRoles, + "Change Group Role": ChangeGroupRole, + } + + option = self.values[0] + if option == "Change Group Role": + Roles = await GroupRoles(interaction) + if Roles == 0: + return await interaction.response.send_message( + f"{crisis} **{interaction.user.display_name},** you haven't setup roblox groups.", + ephemeral=True ) - return - if "Kick Group Member" in options: - await infractiontypeactions.update_one( - filter, {"$set": {"name": self.name, "kickgroup": True}}, upsert=True - ) - if ( - "Send to channel" not in options - and "Give Roles" not in options - and "Remove Roles" not in options - and "Staff Database Removal" not in options - and "Void Shift" not in options - ): - await interaction.response.edit_message( - content=f"{tick} successfully setup Infraction type.", view=None + if Roles == 1: + return await interaction.response.send_message( + f"{crisis} **{interaction.user.display_name},** the servers auth token has either expired or someone reset it.", + ephemeral=True + ) - return + Roles = Roles.get("groupRoles") + options = [] + for role in Roles: + options.append( + discord.SelectOption( + label=f"{role.get('displayName')}", + value=role.get("path"), + ) + ) + view.add_item(ChangeGroupRole(self.author, self.name, options)) + + if option in action_mapping and option != "Change Group Role": + view.add_item(action_mapping[option](self.author, self.name)) + else: + update_data[option.lower().replace(" ", "")] = True + + await infractiontypeactions.update_one( + filter, {"$set": update_data}, upsert=True + ) + await interaction.response.edit_message( + content=f"{tick} Infraction type successfully updated.", + view=view if view.children else None, + ) + - if ( - "Send to channel" not in options - and "Give Roles" not in options - and "Remove Roles" not in options - and "Staff Database Removal" not in options +class Done(discord.ui.View): + class Done(discord.ui.Button): + def __init__( + self, label: str, style: discord.ButtonStyle, author: discord.Member ): + super().__init__(label=label, style=style, row=3) + self.author = author + + async def callback(self, interaction: discord.Interaction): + if interaction.user.id != self.author.id: + return await interaction.response.send_message( + embed=discord.Embed( + description=f"{redx} This is not your panel!", + color=discord.Color.red(), + ), + ephemeral=True, + ) await interaction.response.edit_message( - content=f"{tick} successfully setup Infraction type.", view=None + content=f"{tick} **{interaction.user.display_name},** succesfully updated infraction type.", + view=None, ) - return - - if "Send to channel" in options: - view.add_item(TypeChannel(self.author, self.name, options)) - await interaction.response.edit_message(view=view) - return - elif "Give Roles" in options: - view.add_item(GiveRoles(self.author, self.name, options)) - await interaction.response.edit_message(view=view) - return - - elif "Remove Roles" in options: - view.add_item(Removeroles(self.author, self.name, options)) - await interaction.response.edit_message(view=view) - return class TypeChannel(discord.ui.ChannelSelect): - def __init__(self, author, name, selected): + def __init__(self, author, name): super().__init__( - placeholder="Infractions Type Channel", + placeholder="Select a Channel", channel_types=[discord.ChannelType.text, discord.ChannelType.news], ) - self.author = author - self.name = name - self.selected = selected + self.author, self.name = author, name async def callback(self, interaction: discord.Interaction): if interaction.user.id != self.author.id: - embed = discord.Embed( - description=f"{redx} **{interaction.user.display_name},** this is not your panel!", - color=discord.Colour.brand_red(), + return await interaction.response.send_message( + embed=discord.Embed( + description=f"{redx} This is not your panel!", + color=discord.Color.red(), + ), + ephemeral=True, ) - return await interaction.response.send_message(embed=embed, ephemeral=True) - ChannelID = self.values[0] filter = {"guild_id": interaction.guild.id, "name": self.name} - await infractiontypeactions.update_one( - filter, {"$set": {"name": self.name, "channel": ChannelID.id}}, upsert=True + filter, {"$set": {"channel": self.values[0].id}}, upsert=True + ) + await interaction.response.edit_message( + content=f"{tick} Channel set.", + view=None, ) - if "Give Roles" in self.selected: - view = discord.ui.View() - view.add_item(GiveRoles(self.author, self.name, self.selected)) - await interaction.response.edit_message( - content=f"{tick} successfully set channel, now set the given roles!", - view=view, - ) - elif "Remove Roles" in self.selected: - view = discord.ui.View() - view.add_item(Removeroles(self.author, self.name, self.selected)) - await interaction.response.edit_message( - content=f"{tick} successfully set channel, now set the removed roles!", - view=view, - ) - else: - await interaction.response.edit_message( - content=f"{tick} successfully setup Infraction type.", view=None +class RemoveRoles(discord.ui.RoleSelect): + def __init__(self, author, name): + super().__init__(placeholder="Select Removed Roles", max_values=10) + self.author, self.name = author, name + + async def callback(self, interaction: discord.Interaction): + if interaction.user.id != self.author.id: + return await interaction.response.send_message( + embed=discord.Embed( + description=f"{redx} This is not your panel!", + color=discord.Color.red(), + ), + ephemeral=True, ) + filter = {"guild_id": interaction.guild.id, "name": self.name} + await infractiontypeactions.update_one( + filter, + {"$set": {"removedroles": [role.id for role in self.values]}}, + upsert=True, + ) + await interaction.response.edit_message( + content=f"{tick} Removed roles set.", view=None + ) + class GiveRoles(discord.ui.RoleSelect): - def __init__(self, author, name, selected): - super().__init__(placeholder="Given Roles", max_values=10) - self.author = author - self.name = name - self.selected = selected + def __init__(self, author, name): + super().__init__(placeholder="Select Given Roles", max_values=10) + self.author, self.name = author, name async def callback(self, interaction: discord.Interaction): if interaction.user.id != self.author.id: - embed = discord.Embed( - description=f"{redx} **{interaction.user.display_name},** this is not your panel!", - color=discord.Colour.brand_red(), + return await interaction.response.send_message( + embed=discord.Embed( + description=f"{redx} This is not your panel!", + color=discord.Color.red(), + ), + ephemeral=True, ) - return await interaction.response.send_message(embed=embed, ephemeral=True) - SelectedRoleIds = [role.id for role in self.values] + filter = {"guild_id": interaction.guild.id, "name": self.name} await infractiontypeactions.update_one( filter, - {"$set": {"name": self.name, "givenroles": SelectedRoleIds}}, + {"$set": {"givenroles": [role.id for role in self.values]}}, upsert=True, ) - - if "Remove Roles" in self.selected: - view = discord.ui.View() - view.add_item(Removeroles(self.author, self.name, self.selected)) - await interaction.response.edit_message( - content=f"{tick} successfully set channel, now set the removed roles!", - view=view, - ) - else: - await interaction.response.edit_message( - content=f"{tick} successfully setup Infraction type.", view=None - ) + await interaction.response.edit_message( + content=f"{tick} Given roles set.", view=None + ) -class Removeroles(discord.ui.RoleSelect): - def __init__(self, author, name, selected): - super().__init__(placeholder="Removed Roles", max_values=10) - self.author = author - self.name = name - self.selected = selected +class ChangeGroupRole(discord.ui.Select): + def __init__(self, author, name, options): + super().__init__( + options=options, placeholder="What should we change their role to?" + ) + self.author, self.name = author, name async def callback(self, interaction: discord.Interaction): if interaction.user.id != self.author.id: - embed = discord.Embed( - description=f"{redx} **{interaction.user.display_name},** this is not your panel!", - color=discord.Colour.brand_red(), + return await interaction.response.send_message( + embed=discord.Embed( + description=f"{redx} This is not your panel!", + color=discord.Color.red(), + ), + ephemeral=True, ) - return await interaction.response.send_message(embed=embed, ephemeral=True) - Selected = [role.id for role in self.values] + filter = {"guild_id": interaction.guild.id, "name": self.name} await infractiontypeactions.update_one( filter, - {"$set": {"name": self.name, "removedroles": Selected}}, + {"$set": {"grouprole": self.values[0]}}, upsert=True, ) - await interaction.response.edit_message( - content=f"{tick} successfully setup Infraction type.", view=None + content=f"{tick} Group role changed.", view=None ) diff --git a/Cogs/Configuration/Components/Modmail.py b/Cogs/Configuration/Components/Modmail.py index 60ff237..499f788 100644 --- a/Cogs/Configuration/Components/Modmail.py +++ b/Cogs/Configuration/Components/Modmail.py @@ -31,7 +31,7 @@ def __init__(self, author: discord.User, type): discord.SelectOption( label="Modmail Categories", description="Allows users to make different categories in the modmail system. ", - emoji="<:intergrations:1272191311234990131>", + emoji="<:integrations:1272191311234990131>", ), ] @@ -53,7 +53,7 @@ def __init__(self, author: discord.User, type): discord.SelectOption( label="Modmail Categories", description="Allows users to make different categories in the modmail system. ", - emoji="<:intergrations:1272191311234990131>", + emoji="<:integrations:1272191311234990131>", ), ] diff --git a/Cogs/Configuration/Components/integrations.py b/Cogs/Configuration/Components/integrations.py new file mode 100644 index 0000000..0b31149 --- /dev/null +++ b/Cogs/Configuration/Components/integrations.py @@ -0,0 +1,225 @@ +import discord +from motor.motor_asyncio import AsyncIOMotorClient +import os +from utils.erm import GetIdentifier +from utils.emojis import * +import asyncio + +MONGO_URL = os.getenv('MONGO_URL') + +mongo = AsyncIOMotorClient(MONGO_URL) +db = mongo["astro"] +integrations = db["integrations"] +Tokens = db["integrations"] +PendingUsers = db["Pending"] + + +class Integrations(discord.ui.Select): + def __init__(self, author: discord.Member): + super().__init__( + options=[ + discord.SelectOption( + label="Roblox Groups", emoji="<:link:1206670134064717904>" + ), + discord.SelectOption( + label="ERM", + emoji="<:erm:1203823601107861504>", + ), + ] + ) + self.author = author + + async def callback(self, interaction): + if interaction.user.id != self.author.id: + embed = discord.Embed( + description=f"{redx} **{interaction.user.display_name},** this is not your panel!", + color=discord.Colour.brand_red(), + ) + return await interaction.followup.send(embed=embed, ephemeral=True) + await interaction.response.defer() + if self.values[0] == "ERM": + + code = await GetIdentifier() + result = await integrations.find_one( + {"server": interaction.guild.id, "erm": {"$exists": True}} + ) + if result and result.get("erm", None): + code = result.get("erm") + embed = discord.Embed( + title="<:erm:1203823601107861504> ERM API Integration", + color=discord.Colour.brand_red(), + description=( + "To complete the integration with ERM, please authorize the application by clicking the 'Authorize' button below. " + "Once you have authorized, press the 'Done' button to finalize the integration. " + "If you encounter any issues, refer to the [documentation](https://docs.astrobirb.dev/) for assistance." + ), + ) + + view = KeyButton( + interaction.user, + f"https://discord.com/oauth2/authorize?client_id=1090291300881932378&redirect_uri=https%3A%2F%2Fapi.ermbot.xyz%2Fapi%2FAuth%2FCallback&response_type=code&scope=identify%20guilds&prompt=none&state={code}", + code, + ) + await interaction.followup.send(embed=embed, view=view, ephemeral=True) + elif self.values[0] == "Roblox Groups": + + embed = discord.Embed( + title="<:robloxWhite:1200584000390053899> Roblox Groups", + color=discord.Colour.dark_embed(), + description="Please authorize the application to access your Roblox groups. Ensure that the person authorizing has the highest role in the group if you want to manage roles.\n-# If this is still here after the pop up appears just dismiss this.", + ) + view = discord.ui.View() + view.add_item( + discord.ui.Button( + label="Authorise", + style=discord.ButtonStyle.link, + url=f"https://authorize.roblox.com/?client_id=2093535171040358934&response_type=code&redirect_uri=https%3A%2F%2Fverify.astrobirb.dev%2Fauth&scope=group%3Awrite+openid+profile&o=&state={interaction.user.id}|{interaction.guild.id}&step=accountConfirm", + ) + ) + await PendingUsers.update_one( + {"user": str(interaction.user.id)}, + {"$set": {"server": str(interaction.guild.id)}}, + upsert=True, + ) + await interaction.followup.send(embed=embed, view=view, ephemeral=True) + await Tokens.delete_one({"discord_id": str(interaction.user.id)}) + + try: + await asyncio.wait_for( + wait_for_token_verification(interaction), timeout=180 + ) + if await Tokens.find_one({"discord_id": str(interaction.user.id)}): + + await interaction.followup.send( + embed=discord.Embed( + description="You are now verified! Please enter your Roblox Group ID to proceed. Make sure the person authorizing has the highest role in the group if you want to manage roles.", + color=discord.Color.brand_green(), + ).set_author(name="Roblox Group"), + view=GroupModalButton(interaction.user), + ephemeral=True, + ) + await PendingUsers.delete_one({"user": interaction.user.id}) + return + except asyncio.TimeoutError: + await interaction.edit_original_response( + content="Verification timed out." + ) + return + + +async def wait_for_token_verification(interaction: discord.Interaction): + attempts = 0 + while attempts < 60: + if await Tokens.find_one({"discord_id": str(interaction.user.id)}): + return True + attempts += 1 + print("Checking token...") + await asyncio.sleep(3) + return False + + +class GroupModalButton(discord.ui.View): + def __init__(self, author): + super().__init__(timeout=None) + self.author = author + + @discord.ui.button(label="Enter Group ID", style=discord.ButtonStyle.primary) + async def GroupID( + self, interaction: discord.Interaction, button: discord.ui.Button + ): + if interaction.user.id != self.author.id: + embed = discord.Embed( + description=f"{redx} **{interaction.user.display_name},** this is not your panel!", + color=discord.Colour.brand_red(), + ) + return await interaction.response.send_message(embed=embed, ephemeral=True) + await interaction.response.send_modal(GroupID()) + + +class GroupID(discord.ui.Modal): + def __init__(self): + super().__init__(title="Enter Roblox Group ID") + self.group_id = discord.ui.TextInput( + label="Group ID", placeholder="Enter your Roblox Group ID here" + ) + self.add_item(self.group_id) + + async def on_submit(self, interaction: discord.Interaction): + embed = discord.Embed( + description=f"Sucessfully linked the group with this server.", + color=discord.Colour.brand_green(), + ) + await Tokens.update_one( + {"server": str(interaction.guild.id)}, + {"$set": {"group": str(self.group_id.value)}}, + upsert=True, + ) + await interaction.response.edit_message(embed=embed, view=None) + + +class KeyButton(discord.ui.View): + def __init__(self, author, link, key): + super().__init__(timeout=None) + self.author = author + self.add_item( + discord.ui.Button( + label="Authorise", style=discord.ButtonStyle.link, url=link + ) + ) + self.key = key + + @discord.ui.button( + label="Done", + style=discord.ButtonStyle.green, + emoji="<:Permissions:1207365901956026368>", + ) + async def apikey(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user.id != self.author.id: + embed = discord.Embed( + description=f"{redx} **{interaction.user.display_name},** this is not your panel!", + color=discord.Colour.brand_red(), + ) + return await interaction.response.send_message(embed=embed, ephemeral=True) + await integrations.update_one( + {"server": interaction.guild.id}, + {"$set": {"erm": self.key}}, + upsert=True, + ) + embed = discord.Embed( + description=f"{greencheck} **API Key has been successfully updated!**", + color=discord.Colour.brand_green(), + ) + await interaction.response.edit_message(embed=embed, view=None) + + +async def integrationsEmbed(interaction: discord.Interaction, embed: discord.Embed): + embed.set_author(name=f"{interaction.guild.name}", icon_url=interaction.guild.icon) + embed.set_thumbnail(url=interaction.guild.icon) + embed.description = ( + "> Integrations are an easy way to connect external providers to the bot. " + "You can find out more at [the documentation](https://docs.astrobirb.dev/)." + ) + + ERM = await integrations.find_one( + {"server": str(interaction.guild.id), "erm": {"$exists": True}} + ) + Groups = await Tokens.find_one({"server": str(interaction.guild.id)}) + + embed.add_field( + name=f"<:erm:1203823601107861504> ERM", + value=f"> **Configured:** {bool(ERM)}", + inline=False, + ) + + group_id = Groups.get("group") if Groups else "N/A" + embed.add_field( + name="<:robloxWhite:1200584000390053899> Roblox Groups", + value=f"> **Group ID**: {group_id}\n> **Configured:** {bool(Groups)}", + inline=False, + ) + embed.add_field( + name="<:Modules:1296530049381568522> Functions", + value="> * Infraction Types\n> -# We are still looking to add more purposes to integrations", + ) + + return embed diff --git a/Cogs/Configuration/Configuration.py b/Cogs/Configuration/Configuration.py index 9e4cc60..bd4f31b 100644 --- a/Cogs/Configuration/Configuration.py +++ b/Cogs/Configuration/Configuration.py @@ -335,6 +335,15 @@ async def callback(self, interaction: discord.Interaction): interaction.user, ) ) + elif selection == "Integrations": + from Cogs.Configuration.Components.integrations import ( + integrationsEmbed, + Integrations, + ) + + view = discord.ui.View() + view.add_item(Integrations(interaction.user)) + embed = await integrationsEmbed(interaction, embed=embed) view.add_item(ConfigMenu(Options(Config), interaction.user)) await interaction.edit_original_response(embed=embed, view=view) @@ -381,6 +390,11 @@ def Options(Config: dict = None): description="Manage your server's subscriptions", emoji="<:subscription:1334962057073655858>", ), + discord.SelectOption( + label="Integrations", + description="Use External APIs.", + emoji="<:link:1206670134064717904>", + ), ] ModuleAddons = [ diff --git a/Cogs/Events/on_infraction.py b/Cogs/Events/on_infraction.py index f60c6d8..4be9ba3 100644 --- a/Cogs/Events/on_infraction.py +++ b/Cogs/Events/on_infraction.py @@ -6,6 +6,7 @@ import logging from utils.erm import voidShift from Cogs.Configuration.Components.EmbedBuilder import DisplayEmbed +import traceback logger = logging.getLogger(__name__) @@ -14,7 +15,7 @@ db = client["astro"] infractions = db["infractions"] Customisation = db["Customisation"] -intergrations = db["intergrations"] +integrations = db["integrations"] staffdb = db["staff database"] consent = db["consent"] Suspension = db["Suspensions"] @@ -254,8 +255,10 @@ async def on_infraction( pass async def InfractionTypes(self, data, staff: discord.Member): + if not data: return + print('ok') try: channel = False if data.get("givenroles"): @@ -272,6 +275,15 @@ async def InfractionTypes(self, data, staff: discord.Member): await staff.add_roles(*roles) except (discord.Forbidden, discord.HTTPException): pass + if data.get("changegrouprole") and data.get("grouprole"): + from utils.roblox import UpdateMembership + + try: + await UpdateMembership( + user=staff, server=staff.guild.id, role=data.get("grouprole") + ) + except Exception as e: + traceback.format_exc(e) if data.get("removedroles"): roles = data.get("removedroles") @@ -288,7 +300,7 @@ async def InfractionTypes(self, data, staff: discord.Member): except (discord.Forbidden, discord.HTTPException): pass if data.get("voidshift"): - result = await intergrations.find_one( + result = await integrations.find_one( {"guild_id": staff.guild.id, "erm": {"$exists": True}} ) if result: diff --git a/Cogs/Events/on_infraction_edit.py b/Cogs/Events/on_infraction_edit.py index e2ecb83..98aced8 100644 --- a/Cogs/Events/on_infraction_edit.py +++ b/Cogs/Events/on_infraction_edit.py @@ -16,7 +16,7 @@ db = client["astro"] infractions = db["infractions"] Customisation = db["Customisation"] -intergrations = db["intergrations"] +integrations = db["integrations"] staffdb = db["staff database"] consent = db["consent"] Suspension = db["Suspensions"] diff --git a/Cogs/Events/on_promotion.py b/Cogs/Events/on_promotion.py index 0bc71fe..02927c5 100644 --- a/Cogs/Events/on_promotion.py +++ b/Cogs/Events/on_promotion.py @@ -16,7 +16,7 @@ db = client["astro"] promotions = db["promotions"] Customisation = db["Customisation"] -intergrations = db["intergrations"] +integrations = db["integrations"] staffdb = db["staff database"] consent = db["consent"] promotionroles = db["promotion roles"] diff --git a/Cogs/Modules/infractions.py b/Cogs/Modules/infractions.py index 38b417a..2a4926b 100644 --- a/Cogs/Modules/infractions.py +++ b/Cogs/Modules/infractions.py @@ -45,7 +45,7 @@ infractiontypeactions = db["infractiontypeactions"] options = db["module options"] staffdb = db["staff database"] -intergrations = db["integrations"] +integrations = db["integrations"] reasons = db["reasons"] config = db["Config"] diff --git a/Cogs/Modules/integrations.py b/Cogs/Modules/integrations.py new file mode 100644 index 0000000..7bcd733 --- /dev/null +++ b/Cogs/Modules/integrations.py @@ -0,0 +1,122 @@ +import discord +from discord.ext import commands +import platform +from typing import Literal +import psutil +import os +from utils.emojis import * +from utils.ui import YesOrNo +import asyncio +from motor.motor_asyncio import AsyncIOMotorClient +from discord import app_commands + +MONGO_URL = os.getenv('MONGO_URL') +client = AsyncIOMotorClient(MONGO_URL) +db = client["astro"] +infractions = db["infractions"] +Suggestions = db["suggestions"] +loa_collection = db["loa"] +Tokens = db["integrations"] +PendingUsers = db["Pending"] + + +class Link(commands.Cog): + def __init__(self, client: commands.Bot): + self.client = client + + integrations = app_commands.Group( + name="integrations", + description="integrations", + ) + + @integrations.command( + description="Link an integration to your discord account (e.g., Roblox)" + ) + async def link(self, interaction: discord.Interaction, service: Literal["Roblox"]): + AlreadyRegistered = await Tokens.find_one( + {"discord_id": str(interaction.user.id)} + ) + msg = None + await interaction.response.defer(ephemeral=True) + if AlreadyRegistered: + view = YesOrNo() + msg = await interaction.edit_original_response( + embed=discord.Embed( + title="Already Linked", + description="You are already linked. Do you want to reverify?", + color=discord.Color.dark_embed(), + ), + view=view, + ) + await view.wait() + if view.value is None: + await interaction.followup.send( + "You took too long to respond.", ephemeral=True + ) + return + if not view.value: + return await interaction.followup.send( + f"{tick} cancelled.", ephemeral=True + ) + + await PendingUsers.update_one( + {"user": interaction.user.id}, + {"$set": {"user": str(interaction.user.id)}}, + upsert=True, + ) + embed = discord.Embed() + embed.set_author( + name="Verify With Roblox", + icon_url="https://cdn.discordapp.com/emojis/1206670134064717904.webp?size=96", + ) + embed.description = "Press the button link below." + + view = discord.ui.View() + view.add_item( + discord.ui.Button( + label="Authorise", + style=discord.ButtonStyle.link, + url=f"https://authorize.roblox.com/?client_id=2093535171040358934&response_type=code&redirect_uri=https%3A%2F%2Fverify.astrobirb.dev%2Fauth&scope=openid+profile&o=&state={interaction.user.id}&step=accountConfirm", + ) + ) + + if AlreadyRegistered: + await interaction.edit_original_response(embed=embed, view=view) + else: + msg = await interaction.followup.send( + embed=embed, view=view, ephemeral=True + ) + + await Tokens.delete_one({"discord_id": str(interaction.user.id)}) + + try: + await asyncio.wait_for( + self.wait_for_token_verification(interaction), timeout=180 + ) + if await Tokens.find_one({"discord_id": str(interaction.user.id)}): + if msg: + await interaction.edit_original_response( + content=f"{tick} Successfully verified!", embed=None, view=None + ) + await PendingUsers.delete_one({"user": interaction.user.id}) + return + except asyncio.TimeoutError: + if msg: + await interaction.edit_original_response( + content="Verification timed out." + ) + return + + async def wait_for_token_verification(self, interaction: discord.Interaction): + attempts = 0 + while attempts < 60: + if await Tokens.find_one({"discord_id": str(interaction.user.id)}): + return True + attempts += 1 + print("Checking token...") + await asyncio.sleep(3) + return False + + +async def setup(client: commands.Bot) -> None: + await client.add_cog(Link(client)) diff --git a/Cogs/Modules/staff.py b/Cogs/Modules/staff.py index cfd0ff7..dbcd529 100644 --- a/Cogs/Modules/staff.py +++ b/Cogs/Modules/staff.py @@ -39,7 +39,7 @@ options = db["module options"] stafflist = db["Staff List"] activelists = db["Active Staff List"] -intergrations = db["integrations"] +integrations = db["integrations"] advancedpermissions = db["Advanced Permissions"] premiums = db["premium"] Configuration = db["Config"] diff --git a/main.py b/main.py index 1833ddf..fddd84e 100644 --- a/main.py +++ b/main.py @@ -165,7 +165,8 @@ def __init__(self): "Cogs.Tasks.qotd", 'Cogs.Events.on_feedback', 'Cogs.Events.on_suggestion', - 'Cogs.Events.on_suggest_update' + 'Cogs.Events.on_suggest_update', + 'Cogs.Modules.integrations' ] if not environment == "custom": diff --git a/utils/roblox.py b/utils/roblox.py new file mode 100644 index 0000000..0cfb731 --- /dev/null +++ b/utils/roblox.py @@ -0,0 +1,67 @@ +import aiohttp +from motor.motor_asyncio import AsyncIOMotorClient +import discord +import os +from dotenv import load_dotenv + +load_dotenv() +MONGO_URL = os.getenv("MONGO_URL") +client = AsyncIOMotorClient(MONGO_URL) +db = client["astro"] +infractions = db["infractions"] +Suggestions = db["suggestions"] +loa_collection = db["loa"] +Tokens = db["integrations"] +PendingUsers = db["Pending"] + + +async def GetUser(token: str): + print("i got here fine") + url = "https://apis.roblox.com/oauth/v1/userinfo" + headers = {"Authorization": f"Bearer {token}"} + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=headers) as response: + return await response.json() + + +async def UpdateMembership(user: discord.User, server: int, role): + result = await Tokens.find_one({"server": str(server)}) + if not result: + return 0 + token = result.get("token") + group = result.get("group") + if not (token and group): + return 2 + UserResult = await Tokens.find_one({"discord_id": str(user.id)}) + if not UserResult: + return 1 + RobloxResult = await GetUser(UserResult.get("token")) + if not RobloxResult: + return + + url = f"https://apis.roblox.com/cloud/v2/groups/{group}/memberships/{RobloxResult.get('sub', 0)}" + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + data = {"role": f"{role}"} + + async with aiohttp.ClientSession() as session: + async with session.patch(url, headers=headers, json=data) as response: + if response.ok: + print( + f'[Updated Membership] {RobloxResult.get("preferred_username")} role has succesfully been changed.' + ) + return await response.json() + + +async def GroupRoles(interaction: discord.Interaction): + result = await Tokens.find_one({"server": str(interaction.guild.id)}) + if not result: + return 0 + token = result.get("token") + group = result.get("group") + url = f"https://apis.roblox.com/cloud/v2/groups/{group}/roles?maxPageSize=50" + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=headers) as response: + if response.status == 401: + return 1 + return await response.json()