# db - msg_id, user_id, guild_id import asyncio import discord import os import json from dotenv import load_dotenv from discord.ui import Modal, InputText from discord.utils import get from discord.ext import commands from dbutil import MessageDB from dbutil import StartButtonDB from dbutil import GuildAppDB load_dotenv() TOKEN = os.getenv("TOKEN") bot = discord.Bot(intents=discord.Intents.all()) @bot.event async def on_ready(): bot.add_view(ApplicationButtonsView()) bot.add_view(ApplicationStartButtonView()) activity = discord.Activity(name=f"{len(bot.guilds)} guilds", type=discord.ActivityType.listening) await bot.change_presence(activity=activity, status = discord.Status.online) print(f"Logged in as {bot.user}") await bot.sync_commands(force=True) for i in bot.guilds: if str(i.id) not in GuildAppDB.get_all_guilds(): GuildAppDB.create_guild(str(i.id), i.name) print(f"entry for {i.id} created") @bot.event async def on_guild_join(guild): activity = discord.Activity(name=f"{len(bot.guilds)} guilds", type=discord.ActivityType.listening) await bot.change_presence(activity=activity, status = discord.Status.online) GuildAppDB.create_guild(str(guild.id), guild.name) print(f"Joined guild {guild.name}: {guild.id}") @bot.event async def on_guild_remove(guild): activity = discord.Activity(name=f"{len(bot.guilds)} guilds", type=discord.ActivityType.listening) await bot.change_presence(activity=activity, status = discord.Status.online) print(f"Removed from guild {guild.name}: {guild.id}") @commands.has_permissions(administrator=True) @bot.slash_command(description = "Command used to set up the application prompt") async def start_button(ctx): view = discord.ui.View() options = SelectApplicationStartButton(max_values=1, placeholder="Select application") for i in GuildAppDB.get_applications(str(ctx.guild.id)): options.add_option(label=i, value=i) view.add_item(options) await ctx.response.send_message(view=view, ephemeral=True) @start_button.error async def on_application_command_error(ctx, error): if isinstance(error, commands.MissingPermissions): await ctx.respond("You need Administrator permissions to use this command", ephemeral=True) else: raise error application = discord.SlashCommandGroup("application", "The main command to manage applications") @commands.has_permissions(administrator=True) @application.command(description="Create application") async def create(ctx, application): if len(application) < 40: result = GuildAppDB.add_application_entry(str(ctx.guild.id), application) if result == "success": await ctx.response.send_message(f"Successfully created application: {application}", ephemeral=True) # create a new application, modal with name ask else: print(result) else: await ctx.response.send_message(f"please choose shorter name", ephemeral=True) @commands.has_permissions(administrator=True) @application.command(description="Remove application") async def remove(ctx, application): result = GuildAppDB.remove_application_entry(str(ctx.guild.id), application) if result == "success": await ctx.response.send_message(f"Successfully removed application: {application}", ephemeral=True) else: await ctx.response.send_message(f"Application {application} not found") @commands.has_permissions(administrator=True) @application.command(description="List all applications") async def list(ctx): applications = GuildAppDB.get_applications(str(ctx.guild.id)) print(applications) embed = discord.Embed() for i in applications: embed.add_field(name=i, value="", inline=False) await ctx.response.send_message(embed=embed) @commands.has_permissions(administrator=True) @application.command(description="Opens editor for selected application") async def editor(ctx): view = discord.ui.View() options = SelectApplicationOptionsEditor(max_values=1, placeholder="Select application") for i in GuildAppDB.get_applications(str(ctx.guild.id)): options.add_option(label=i, value=i) view.add_item(options) await ctx.response.send_message(view=view, ephemeral=True) @commands.has_permissions(administrator=True) @application.command(description="Select response channel for application") async def response_channel(ctx): view = discord.ui.View() options = SelectApplicationOptionsRespChannel(max_values=1, placeholder="Select application") for i in GuildAppDB.get_applications(str(ctx.guild.id)): options.add_option(label=i, value=i) view.add_item(options) await ctx.response.send_message(view=view, ephemeral=True) bot.add_application_command(application) # add application group commands def get_questions_embed(guild_id, application) -> discord.Embed: embed = discord.Embed(title=f"Application: {application}") questions, length = GuildAppDB.get_questions(str(guild_id), application) for i, que in enumerate(questions): embed.add_field(value=f"**{i+1}. {que}**", name="", inline=False) embed.set_footer(text="Made by @anorak01", icon_url="https://cdn.discordapp.com/avatars/269164865480949760/a1af9962da20d5ddaa136043cf45d015?size=1024") return embed class SelectApplicationStartButton(discord.ui.Select): async def callback(self, interaction: discord.Interaction): self.disabled = True embed = discord.Embed(title="**Start your application!**") embed.add_field(name=f"Click the button below to start your application for: {self.values[0]}", value="", inline=False) embed.set_footer(text="Made by @anorak01", icon_url="https://cdn.discordapp.com/avatars/269164865480949760/a1af9962da20d5ddaa136043cf45d015?size=1024") appStartView = ApplicationStartButtonView() await interaction.response.edit_message(embed=None, content="Application button created", view=None) message = await interaction.channel.send(embed = embed, view=appStartView) StartButtonDB.add_start_msg(str(message.id), str(self.values[0]), str(interaction.guild.id)) class SelectResponseChannelView(discord.ui.View): @discord.ui.select( select_type=discord.ComponentType.channel_select, channel_types=[discord.ChannelType.text], max_values=1 ) async def select_callback(self, select, interaction: discord.Interaction): self.disable_all_items() print(select.values[0].id) GuildAppDB.set_response_channel(interaction.guild.id, ) await interaction.response.edit_message(content=f"Selected channel: {select.values[0].mention}", view=None) class SelectApplicationOptionsEditor(discord.ui.Select): async def callback(self, interaction: discord.Interaction): self.disabled = True editor = ApplicationEditorView(str(interaction.guild.id), self.values[0]) embed = get_questions_embed(str(interaction.guild.id), self.values[0]) await interaction.response.edit_message(embed = embed, view=editor) class SelectApplicationOptionsRespChannel(discord.ui.Select): async def callback(self, interaction: discord.Interaction): self.disabled = True view = discord.ui.View() options = SelectResponseChannel(select_type=discord.ComponentType.channel_select, channel_types=[discord.ChannelType.text], max_values=1) options.set_app_name(self.values[0]) view.add_item(options) await interaction.response.edit_message(view=view) class SelectResponseChannel(discord.ui.Select): def set_app_name(self, app_name): self.app_name = app_name async def callback(self, interaction: discord.Interaction): self.disabled = True GuildAppDB.set_response_channel(str(interaction.guild.id), self.app_name, str(self.values[0].id)) await interaction.response.edit_message(content=f"Selected channel: {self.values[0].mention} for application: {self.app_name}", view=None) class ApplicationEditorView(discord.ui.View): def __init__(self, guild_id, application_name): super().__init__(timeout=180) self.guild_id = guild_id self.application_name = application_name @discord.ui.button( label="New", style=discord.ButtonStyle.green, custom_id="editor:add", row=0 ) async def add_question(self, button: discord.ui.Button, interaction: discord.Interaction): print("add question") modal = AddQuestionModal(self.application_name) await interaction.response.send_modal(modal) @discord.ui.button( label="Remove", style=discord.ButtonStyle.red, custom_id="editor:remove", row=0 ) async def remove_question(self, button, interaction: discord.Interaction): print("remove question") view = ApplicationEditorView(str(interaction.guild.id), self.application_name) options = RemoveQuestionSelect(max_values=1, placeholder="Select question to remove") options.set_app_name(self.application_name) questions, length = GuildAppDB.get_questions(str(interaction.guild.id), self.application_name) for i, que in enumerate(questions): options.add_option(label=f"{str(i+1)}. {que}", value=str(i)) view.add_item(options) await interaction.response.edit_message(view=view) @discord.ui.button( label="Edit", style=discord.ButtonStyle.primary, custom_id="editor:edit", row=0 ) async def edit_question(self, button, interaction: discord.Interaction): print("edit question") view = ApplicationEditorView(str(interaction.guild.id), self.application_name) options = EditQuestionSelect(max_values=1, placeholder="Select question to edit") options.set_app_name(self.application_name) questions, length = GuildAppDB.get_questions(str(interaction.guild.id), self.application_name) for i, que in enumerate(questions): options.add_option(label=f"{str(i+1)}. {que}", value=str(i)) view.add_item(options) await interaction.response.edit_message(view=view) @discord.ui.button( label="Move", style=discord.ButtonStyle.gray, custom_id="editor:move", row=0 ) async def move_question(self, button, interaction: discord.Interaction): print("move question") view = ApplicationEditorView(str(interaction.guild.id), self.application_name) options = MoveQuestionSelect(max_values=1, placeholder="Select question to move") options.set_app_name(self.application_name) questions, length = GuildAppDB.get_questions(str(interaction.guild.id), self.application_name) for i, que in enumerate(questions): options.add_option(label=f"{str(i+1)}. {que}", value=str(i)) view.add_item(options) await interaction.response.edit_message(view=view) class AddQuestionModal(discord.ui.Modal): def __init__(self, app_name): self.app_name = app_name super().__init__(discord.ui.InputText(label=f"New Question: "), title = "") async def callback(self, interaction: discord.Interaction): question = self.children[0].value GuildAppDB.add_question(str(interaction.guild.id), self.app_name, question) embed = get_questions_embed(str(interaction.guild.id), self.app_name) await interaction.response.edit_message(embed=embed) class RemoveQuestionSelect(discord.ui.Select): def set_app_name(self, app_name): self.app_name = app_name async def callback(self, interaction: discord.Interaction): self.disabled = True GuildAppDB.remove_question(str(interaction.guild.id), self.app_name, int(self.values[0])+1) editor = ApplicationEditorView(str(interaction.guild.id), self.app_name) embed = get_questions_embed(str(interaction.guild.id), self.app_name) await interaction.response.edit_message(embed = embed, view = editor) class EditQuestionSelect(discord.ui.Select): def set_app_name(self, app_name): self.app_name = app_name async def callback(self, interaction: discord.Interaction): self.disabled = True editor = ApplicationEditorView(str(interaction.guild.id), self.app_name) modal = EditQuestionModal(self.app_name, int(self.values[0])+1) await interaction.response.send_modal(modal) await interaction.followup.edit_message(view = editor, message_id=interaction.message.id) class EditQuestionModal(discord.ui.Modal): def __init__(self, app_name, question_index): self.app_name = app_name self.question_index = question_index super().__init__(discord.ui.InputText(label=f"Edited Question: "), title = "") async def callback(self, interaction: discord.Interaction): question = self.children[0].value GuildAppDB.edit_question(str(interaction.guild.id), self.app_name, self.question_index, question) embed = get_questions_embed(str(interaction.guild.id), self.app_name) await interaction.response.edit_message(embed=embed) class MoveQuestionSelect(discord.ui.Select): def set_app_name(self, app_name): self.app_name = app_name async def callback(self, interaction: discord.Interaction): self.disabled = True view = ApplicationEditorView(str(interaction.guild.id), self.app_name) options = MoveQuestionSelectNum(max_values=1, placeholder="Select place for move") options.set_app_name(self.app_name) options.set_init_index(int(self.values[0])+1) questions, length = GuildAppDB.get_questions(str(interaction.guild.id), self.app_name) for i in range(length): options.add_option(label=str(i+1), value=str(i+1)) view.add_item(options) await interaction.response.edit_message(view = view) class MoveQuestionSelectNum(discord.ui.Select): def set_app_name(self, app_name): self.app_name = app_name def set_init_index(self, init_index: int): self.init_index = init_index async def callback(self, interaction: discord.Interaction): self.disabled = True editor = ApplicationEditorView(str(interaction.guild.id), self.app_name) GuildAppDB.move_question(str(interaction.guild.id), self.app_name, int(self.init_index), int(self.values[0])) embed = get_questions_embed(str(interaction.guild.id), self.app_name) await interaction.response.edit_message(view = editor, embed=embed) # View with button that starts the application process class ApplicationStartButtonView(discord.ui.View): def __init__(self): super().__init__(timeout=None) @discord.ui.button( label="Start application!", style=discord.ButtonStyle.green, custom_id=f"persistent:start_application", ) async def start_app(self, button: discord.ui.Button, interaction: discord.Interaction): app_name, guild_id = StartButtonDB.get_start_msg(interaction.message.id) print(app_name) print(guild_id) questions, max_questions=GuildAppDB.get_questions(guild_id, app_name) response_channel = GuildAppDB.get_response_channel(guild_id, app_name) user = await interaction.user.create_dm() embedd = discord.Embed(title=f'CreaTopia Application', description="Hey! Your application has started. You have 300 seconds to complete it.") embedd.add_field(value=f'You can cancel the application by answering "cancel" to any of the questions', name="", inline=False) embedd.set_footer(text="Made by @anorak01", icon_url="https://cdn.discordapp.com/avatars/269164865480949760/a1af9962da20d5ddaa136043cf45d015?size=1024") try: await user.send(embed=embedd) except discord.Forbidden: await interaction.response.send_message(content="Can't start application. Please allow direct messages from server members in your privacy settings.", ephemeral=True) return await interaction.response.send_message(content="Application started", ephemeral=True) application = {'userId': interaction.user.id} for i in range(0, max_questions): try: embed = discord.Embed(title=f'Question [{i+1}/{max_questions}]', description=questions[i]) await user.send(embed=embed) response = await bot.wait_for('message', check=lambda m: m.author == interaction.user and m.channel == user, timeout=300) if response.content.startswith("cancel"): await user.send("Your application has been cancelled") return else: application[f'question{i}'] = response.content except asyncio.TimeoutError: await user.send(content="As you haven't replied in 300 seconds, your application has been cancelled") return try: with open('applications.json', 'r') as f: data = json.load(f) except (FileNotFoundError, json.JSONDecodeError): data = [] data.append(application) with open('applications.json', 'w') as f: json.dump(data, f) channel = bot.get_channel(int(response_channel)) embed = discord.Embed(title='Application: ' + interaction.user.display_name) for i in range(0, max_questions): embed.add_field(name=f'{questions[i]}', value=application[f'question{i}'], inline=False) embed.set_footer(text=f"Applicant ID: {interaction.user.id}") appView = ApplicationButtonsView() msg = await channel.send(embed=embed, view=appView) print(msg.id) MessageDB.add_application_msg(msg.id, interaction.user.id, interaction.guild.id) await user.send('Thank you for applying!') # View containing accept and decline buttons for each application class ApplicationButtonsView(discord.ui.View): def __init__(self): super().__init__(timeout=None) @discord.ui.button( label="Accept", style=discord.ButtonStyle.green, custom_id=f"persistent:accept", ) async def accept(self, button: discord.ui.Button, interaction: discord.Interaction): msg_id = str(interaction.message.id) user_id, guild_id = MessageDB.get_application_msg(msg_id) modal = ApplicationModal(title=f"Accepting: {bot.get_user(user_id).display_name}") modal.set_action("acc") modal.add_item(discord.ui.InputText(label=f"Reason: ")) await interaction.response.send_modal(modal) @discord.ui.button( label="Decline", style=discord.ButtonStyle.red, custom_id=f"persistent:decline", ) async def decline(self, button: discord.ui.Button, interaction: discord.Interaction): msg_id = str(interaction.message.id) user_id, guild_id = MessageDB.get_application_msg(msg_id) modal = ApplicationModal(title=f"Declining: {bot.get_user(user_id).display_name}") modal.set_action("dec") modal.add_item(discord.ui.InputText(label=f"Reason: ")) await interaction.response.send_modal(modal) # Modal functioning as a callback for Accepting/Declining application class ApplicationModal(discord.ui.Modal): def set_action(self, action): self.action = action async def callback(self, interaction: discord.Interaction): reason = self.children[0].value msg_id = str(interaction.message.id) user_id, guild_id = MessageDB.get_application_msg(msg_id) if self.action == "acc": user = await bot.get_user(user_id).create_dm() await user.send(f"You have been accepted to the CreatTopia Minecraft server!") await user.send(f"Reason: {reason}") await interaction.response.send_message(content="Application accepted", ephemeral=True) role = get(interaction.message.guild.roles, name="CreatTopian") await discord.utils.get(interaction.message.guild.members, id=int(user_id)).add_roles(role) emb = interaction.message.embeds[0] emb.colour = discord.Colour.green() embed = discord.Embed(title='Accepted') embed.add_field(name=f'Reason:', value=reason, inline=False) embed.colour = discord.Colour.green() await interaction.followup.edit_message(message_id = interaction.message.id, embeds=[emb, embed]) view = discord.ui.View.from_message(interaction.message) view.disable_all_items() await interaction.followup.edit_message(message_id = interaction.message.id, view = view) if self.action == "dec": user = await bot.get_user(user_id).create_dm() await user.send(f"You have been declined access to the CreatTopia Minecraft server.") await user.send(f"Reason: {reason}") await interaction.response.send_message(content="Application declined", ephemeral=True) emb = interaction.message.embeds[0] emb.colour = discord.Colour.red() embed = discord.Embed(title='Declined') embed.add_field(name=f'Reason:', value=reason, inline=False) embed.colour = discord.Colour.red() await interaction.followup.edit_message(message_id = interaction.message.id, embeds=[emb, embed]) view = discord.ui.View.from_message(interaction.message) view.disable_all_items() await interaction.followup.edit_message(message_id = interaction.message.id, view = view) # end bot.run(TOKEN)