DiscordApplicationBot/bot.py

479 lines
22 KiB
Python

# 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
from action import Action, ActionInteraction
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}\n\nDon't forget to set the response channel!", ephemeral=True) # create a new application, modal with name ask
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", ephemeral=True)
@commands.has_permissions(administrator=True)
@application.command(description="List all applications")
async def list(ctx):
applications = GuildAppDB.get_applications(str(ctx.guild.id))
embed = discord.Embed(title="**List of applications**")
embed.set_footer(text="Made by @anorak01", icon_url="https://cdn.discordapp.com/avatars/269164865480949760/a1af9962da20d5ddaa136043cf45d015?size=1024")
for i, app in enumerate(applications):
embed.add_field(value=f"**{i+1}. {app}**", name="", inline=False)
await ctx.response.send_message(embed=embed, ephemeral=True)
@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()
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, placeholder="Select channel")
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):
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):
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):
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):
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)
questions, max_questions=GuildAppDB.get_questions(guild_id, app_name)
if questions == "error on get questions: application not found":
await interaction.response.send_message(content="Application no longer exists", ephemeral=True)
return
response_channel = GuildAppDB.get_response_channel(guild_id, app_name)
user = await interaction.user.create_dm()
embedd = discord.Embed(title=f'{interaction.guild.name} application: {app_name}', 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=f"Application: {app_name}\nUser: {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)
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"Your application has been accepted!")
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"Your application has been declined.")
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)