diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6711fb9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.env +lib/ +lib64/ +includes/ +pyvenv.cfg +ledgers.db +bin/ diff --git a/main.py b/main.py new file mode 100644 index 0000000..c3ee26f --- /dev/null +++ b/main.py @@ -0,0 +1,202 @@ +import discord +from discord import app_commands +import os +from dotenv import load_dotenv +import sqlite3 +import datetime + +load_dotenv() + +intents = discord.Intents.default() +client = discord.Client(intents=intents) +tree = app_commands.CommandTree(client) + +token = os.getenv('TOKEN') + +@tree.command(name="register_channel", description="sets up a checkout ledger in the current channel") +async def register_channel(interaction: discord.Interaction) -> None: + db_init(interaction.channel_id) + await interaction.response.send_message("database initialised, bot is ready for use in this channel", ephemeral=True) + +@tree.command(name="checkout", description="marks the specified file as being 'checked out'") +@app_commands.describe(file="the file to check out") +async def checkout(interaction: discord.Interaction, file: str) -> None: + if db_table_exists(interaction.channel_id): + if db_file_exists(interaction.channel_id, file=file) != True: + await interaction.response.send_message(embed=get_embed(file=file, user=interaction.user)) + message = await interaction.original_response() + db_add_row(channel_id=interaction.channel_id,file=file,message_id=message.id,user=interaction.user) + else: + message_id = db_get_message_id_by_file(channel_id=interaction.channel_id, file=file) + await interaction.response.send_message(f"the requested file has already been checked out by another user [here](https://discordapp.com/channels/{interaction.guild_id}/{interaction.channel_id}/{message_id})...", ephemeral=True) + else: + await interaction.response.send_message("the current channel does not have a ledger, either run `/register_channel` or move to a different channel and try again...", ephemeral=True) + +@tree.command(name="checkin", description="allows the user to select a checked out file to check back in") +async def checkin(interaction: discord.Interaction) -> None: + if db_table_exists(interaction.channel_id): + if db_user_exists(interaction.channel_id, interaction.user): + files_by_user = db_get_files_by_user(channel_id=interaction.channel_id, user=interaction.user) + if len(files_by_user) == 1: + await interaction.response.send_modal(CheckinConfirmationModal(channel_id=interaction.channel_id,file=files_by_user[0])) + else: + await interaction.response.send_modal(CheckinModal(channel_id=interaction.channel_id,user=interaction.user)) + else: + await interaction.response.send_message("you haven't checked out any files in this channel yet!", ephemeral=True) + else: + await interaction.response.send_message("the current channel does not have a ledger, either run `/register_channel` or move to a different channel and try again...", ephemeral=True) + +@client.event +async def on_ready(): + await tree.sync() + print("Ready!") + +def db_get_message_id_by_file(channel_id: int, file: str) -> int: + conn = sqlite3.connect("ledgers.db") + cursor = conn.cursor() + args = (file,) + message_id = cursor.execute(""" + SELECT "message_id" FROM "%s" WHERE "file" = ?; + """ %(str(channel_id)), args).fetchall() + conn.close() + if len(message_id) > 0: + return int(message_id[0][0]) + else: + return 0 + +def db_get_files_by_user(channel_id: int, user: discord.User) -> list: + conn = sqlite3.connect("ledgers.db") + cursor = conn.cursor() + args = (str(user.id),) + db_output = cursor.execute(""" + SELECT "file" FROM "%s" WHERE "user_id" = ?; + """ %(str(channel_id)), args).fetchall() + conn.close() + filtered_output = list() + for x in db_output: + filtered_output.append(x[0]) + return filtered_output + +def db_add_row(channel_id: int, file: str, message_id: int, user: discord.User): + conn = sqlite3.connect("ledgers.db") + cursor = conn.cursor() + args = (file, str(message_id), str(user.id)) + cursor.execute(""" + INSERT INTO "%s"(file,message_id,user_id) + VALUES(?,?,?) + """ %(str(channel_id)), args) + conn.commit() + conn.close() + +def db_delete_row_by_file(channel_id: int, file: str): + conn = sqlite3.connect("ledgers.db") + cursor = conn.cursor() + args = (file,) + cursor.execute(""" + DELETE FROM "%s" WHERE "file" = ?; + """ %(str(channel_id)), args) + conn.commit() + conn.close() + +def get_embed(file: str, user: discord.User) -> discord.Embed: + my_embed = discord.Embed( + title="File checked out!", + color=discord.Color.dark_blue(), + timestamp=datetime.datetime.now() + ) + my_embed.add_field(name="File", value=file) + my_embed.add_field(name="By", value=f"<@{user.id}>") + return my_embed + +def db_file_exists(channel_id: int, file: str) -> bool: + conn = sqlite3.connect("ledgers.db") + cursor = conn.cursor() + args = (file,) + rowItems = cursor.execute(""" + SELECT * FROM "%s" WHERE "file" = ?; + """ %(str(channel_id)), args).fetchall() + conn.close() + return rowItems != [] + +def db_user_exists(channel_id: int, user: discord.User) -> bool: + conn = sqlite3.connect("ledgers.db") + cursor = conn.cursor() + args = (str(user.id),) + rows = cursor.execute(""" + SELECT * FROM "%s" WHERE "user_id" = ?; + """ %(str(channel_id)), args).fetchall() + conn.close() + return rows != [] + +def db_table_exists(channel_id: int) -> bool: + conn = sqlite3.connect("ledgers.db") + cursor = conn.cursor() + listOfTables = cursor.execute(""" + SELECT name FROM sqlite_master WHERE type='table' AND name='%s'; + """ %(str(channel_id))).fetchall() + conn.close() + return listOfTables != [] + +def db_init(channel_id: int): + conn = sqlite3.connect("ledgers.db") + cursor = conn.cursor() + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS "%s" ( + file TEXT PRIMARY KEY, + message_id TEXT UNIQUE, + user_id TEXT + ) + """ %(str(channel_id))) + + conn.commit() + conn.close() + +class CheckinModal(discord.ui.Modal, title='Check a file back in!'): + options_list = list() + channel_id = 0 + + def __init__(self, channel_id: int, user: discord.User) -> None: + self.channel_id = channel_id + files_list = db_get_files_by_user(channel_id,user) + # print(files_list) + self.options_list.clear() + for x in files_list: + self.options_list.append(discord.SelectOption(label=x)) + super().__init__() + + select = discord.ui.Label( + text='Which file are you done with?', + component=discord.ui.Select( + placeholder="Select a file...", + options=options_list + ) + ) + + async def on_submit(self, interaction): + channel = await client.fetch_channel(self.channel_id) + message = await channel.fetch_message(db_get_message_id_by_file(channel_id=self.channel_id,file=self.select.component.values[0])) + await message.delete() + db_delete_row_by_file(channel_id=self.channel_id,file=self.select.component.values[0]) + return await interaction.response.send_message(f"Successfully checked in file '{self.select.component.values[0]}'!", ephemeral=True) + +class CheckinConfirmationModal(discord.ui.Modal, title='Please confirm this is correct...'): + text = discord.ui.TextDisplay("hello world") + channel_id = 0 + file = "" + def __init__(self, channel_id: int, file: str) -> None: + self.text.content = f"Do you want to check in the file '{file}'?" + self.channel_id = channel_id + self.file = file + super().__init__() + + async def on_submit(self, interaction): + channel = await client.fetch_channel(self.channel_id) + message_id = db_get_message_id_by_file(channel_id=self.channel_id,file=self.file) + if message_id != 0: + message = await channel.fetch_message(message_id) + await message.delete() + db_delete_row_by_file(channel_id=self.channel_id,file=self.file) + return await interaction.response.send_message(f"Successfully checked in file '{self.file}'!", ephemeral=True) + +client.run(token) \ No newline at end of file