From f1b3f90e8cf5dc2b91b0cd3da1663a9fc5167851 Mon Sep 17 00:00:00 2001
From: TropiiDev <tropii@fstropii.com>
Date: Thu, 3 Oct 2024 21:05:03 -0400
Subject: [PATCH] Inital Commit

---
 .gitignore          |   3 +
 bot.py              |  71 ++++++++++++++++
 commands/levels.py  | 148 +++++++++++++++++++++++++++++++++
 commands/ping.py    |  23 ++++++
 commands/sync.py    |  20 +++++
 commands/tickets.py | 195 ++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 460 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 bot.py
 create mode 100644 commands/levels.py
 create mode 100644 commands/ping.py
 create mode 100644 commands/sync.py
 create mode 100644 commands/tickets.py

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3e20d53
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.venv/
+.env
+.idea/
\ No newline at end of file
diff --git a/bot.py b/bot.py
new file mode 100644
index 0000000..6dfbddc
--- /dev/null
+++ b/bot.py
@@ -0,0 +1,71 @@
+# pip install imports
+import discord
+from discord.ext import commands
+
+from dotenv import load_dotenv
+import asyncio
+# import pymongo
+
+# sys imports
+import os
+import logging
+import logging.handlers
+
+load_dotenv()
+
+# sentry.io
+import sentry_sdk
+sentry_sdk.init(
+    dsn=os.getenv("sentry_key"),
+
+    # Set traces_sample_rate to 1.0 to capture 100%
+    # of transactions for performance monitoring.
+    # We recommend adjusting this value in production.
+    traces_sample_rate=1.0
+)
+
+# startup stuff
+load_dotenv()
+
+intents = discord.Intents().default()
+intents.message_content = True
+intents.guilds = True
+intents.members = True
+
+
+# create the bot
+class MyBot(commands.Bot):
+    def __init__(self):
+        # super().__init__(command_prefix=get_server_prefix, intents = intents) - prefix setup
+        super().__init__(command_prefix="!", intents=intents)
+        self.synced = False
+        # self.remove_command("help")     
+
+    async def setup_hook(self):
+        print(f'\33[32mLogged in as {self.user} (ID: {self.user.id})')
+        print('------')
+
+    async def load_extensions(self):
+        for name in os.listdir('./commands'):
+            if name.endswith('.py'):
+                await self.load_extension(f'commands.{name[:-3]}')
+
+    # async def on_ready(self):
+    #    await self.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=""))
+
+
+# making var for commands in this file
+bot = MyBot()
+tree = bot.tree
+
+
+# start bot
+async def main():
+    async with bot:
+        await bot.load_extensions()
+        await bot.start(os.getenv("token"))
+        await asyncio.sleep(0.1)
+    await asyncio.sleep(0.1)
+
+
+asyncio.run(main())
\ No newline at end of file
diff --git a/commands/levels.py b/commands/levels.py
new file mode 100644
index 0000000..246b543
--- /dev/null
+++ b/commands/levels.py
@@ -0,0 +1,148 @@
+import discord
+import pymongo
+import os
+import random
+from discord import app_commands
+from discord.ext import commands
+import asyncio
+
+client = pymongo.MongoClient(os.getenv("mongo_url"))
+db = client.levels
+
+
+class Levels(commands.Cog):
+    def __init__(self, bot):
+        self.bot = bot
+
+    @commands.Cog.listener()
+    async def on_ready(self):
+        print("Levels Online")
+
+    levels = app_commands.Group(name="levels", description="All the leveling commands!")
+
+    @commands.Cog.listener()
+    async def on_message(self, message):
+        # get the guilds coll
+        guilds_coll = db.guilds
+
+        if message.author.bot:
+            return
+
+        if message.content.startswith(self.bot.command_prefix):
+            return
+
+        if message.guild is None:
+            return
+
+        if guilds_coll.find_one({"_id": message.guild.id}) is None or guilds_coll.find_one({"_id": message.guild.id})['disabled'] == True:
+            return
+
+        xp_inc = guilds_coll.find_one({"_id": message.guild.id})["xp_inc"]
+
+        # check if user has an account in the db
+        coll = db.accounts
+        users = coll.find_one({"_id": message.author.id})
+
+        if users is None:
+            coll.insert_one({"_id": message.author.id, "xp": xp_inc, "level": 1, "guild": message.guild.id})
+        else:
+            coll.update_one({"_id": message.author.id}, {"$inc": {"xp": xp_inc}})
+
+        user = coll.find_one({"_id": message.author.id})
+
+        if user['xp'] >= user['level'] * 100:
+            coll.update_one({"_id": message.author.id}, {"$inc": {"level": 1}})
+
+            guild_channel = db.guilds.find_one({"_id": message.guild.id})['channel']
+            if guild_channel is None:
+                await message.guild.owner.send(f"There is an issue with leveling! User: {message.author.id} has leveled up but no log channel is set..")
+            else:
+                channel = message.guild.get_channel(guild_channel)
+
+                await channel.send(f"{message.author.display_name} has reached level {user['level']}!")
+
+        if user['level'] == 50 or user['level'] == 100:
+            await message.guild.owner.send(f"{message.author.display_name} has reached level 50 or 100!")
+
+    @levels.command(name="setup", description='Set the levels channel')
+    @commands.has_permissions(manage_messages=True)
+    async def setup(self, interaction: discord.Interaction, channel: discord.TextChannel = None, xp: int = None):
+        if channel == None:
+            channel = interaction.channel
+
+        if xp is None:
+            xp = 5
+
+        await interaction.response.send_message(f"Starting the setup process")
+
+        coll = db.guilds
+        coll.insert_one({"_id": interaction.guild.id, "channel": channel.id, "xp_inc": xp, "disabled": False})
+
+        await interaction.followup.send(f"The channel set for levels logging is <#{channel.id}>\nThe xp increment is {xp}")
+
+    @levels.command(name='edit', description='Edit the current config')
+    @commands.has_permissions(manage_messages=True)
+    async def edit(self, interaction: discord.Interaction, channel: discord.TextChannel = None, xp: int = None, disabled: bool = None):
+        if channel == None:
+            channel = interaction.channel
+
+        if xp is None:
+            xp = 5
+
+        if disabled is None:
+            disabled = False
+
+        await interaction.response.send_message(f"Editing the current config...")
+
+        coll = db.guilds
+        guild = coll.find_one({"_id": interaction.guild.id})
+
+        if guild is None:
+            await interaction.followup.send("The guilds is not currently setup for leveling")
+            return
+
+        coll.update_one({"_id": interaction.guild.id}, {"$set": {"channel": channel.id, "xp_inc": xp, "disabled": disabled}})
+
+        await interaction.followup.send(f"Current channel: <#{channel.id}>\nXP is: {xp}\nAre levels disabled? {disabled}")
+
+    @levels.command(name="rank", description='Show your current level and xp!')
+    async def rank(self, interaction: discord.Interaction, member: discord.Member = None):
+        if member is None:
+            member = interaction.user
+
+        user_coll = db.accounts
+        guild_coll = db.guilds
+
+        user = user_coll.find_one({"_id": member.id})
+        guild = guild_coll.find_one({"_id": interaction.guild.id})
+
+        if user is None:
+            await interaction.response.send_message("This user hasn't sent a message! They don't have a rank.")
+            return
+
+        em = discord.Embed(title="Rank", description=f"{member.display_name}'s current rank is...", color=member.color)
+        em.add_field(name="Level", value=user['level'], inline=False)
+        em.add_field(name='XP', value=user['xp'], inline=False)
+        em.add_field(name='XP required to level up', value=f"{(user['level'] * 100) - user['xp']}", inline=False)
+        em.add_field(name='Disabled?', value=guild['disabled'])
+
+        await interaction.response.send_message(embed=em)
+
+    # @levels.command(name='leaderboard', description='View the servers leaderboard')
+    # async def leaderboard(self, interaction: discord.Interaction):
+    #     guild_coll = db.guilds
+    #     users_coll = db.accounts
+    #
+    #     users = users_coll.find({})
+    #
+    #     for user in users:
+    #         pass
+    #
+    #     em = discord.Embed(title='Leaderboard', description='View the people with the highest XP')
+    #
+    #
+    #     await interaction.response.send_message("This command is currently under development...")
+
+
+async def setup(bot):
+    await bot.add_cog(Levels(bot))
diff --git a/commands/ping.py b/commands/ping.py
new file mode 100644
index 0000000..c780e26
--- /dev/null
+++ b/commands/ping.py
@@ -0,0 +1,23 @@
+import discord
+import os
+from dotenv import load_dotenv
+from discord import app_commands
+from discord.ext import commands
+
+load_dotenv()
+
+class ping(commands.Cog):
+    def __init__(self, bot):
+        self.bot = bot
+
+    @commands.Cog.listener()
+    async def on_ready(self):
+        print("Ping Online")
+
+    @app_commands.command(name="ping", description="Pong!")
+    async def ping(self, interaction: discord.Interaction):
+        print("Ping Ping Ping")
+        await interaction.response.send_message(f":ping_pong:**Pong!**\nLatency: {round(self.bot.latency * 1000)}ms")
+
+async def setup(bot):
+    await bot.add_cog(ping(bot))
\ No newline at end of file
diff --git a/commands/sync.py b/commands/sync.py
new file mode 100644
index 0000000..27f34b5
--- /dev/null
+++ b/commands/sync.py
@@ -0,0 +1,20 @@
+import discord
+from discord.ext import commands
+
+
+class Sync(commands.Cog):
+    def __init__(self, bot):
+        self.bot = bot
+
+    @commands.Cog.listener()
+    async def on_ready(self):
+        print("Sync Online")
+
+    @commands.command(name="sync", description="Syncs all of the slash commands")
+    @commands.is_owner()
+    async def sync(self, ctx):
+        await self.bot.tree.sync()
+        await ctx.send("Synced!")
+
+async def setup(bot):
+    await bot.add_cog(Sync(bot))
\ No newline at end of file
diff --git a/commands/tickets.py b/commands/tickets.py
new file mode 100644
index 0000000..689fc43
--- /dev/null
+++ b/commands/tickets.py
@@ -0,0 +1,195 @@
+import discord
+import os
+import random
+import pymongo
+
+from discord import app_commands
+from discord.ext import commands
+
+client = pymongo.MongoClient(os.getenv('mongo_url'))
+db = client.tickets
+
+class Tickets(commands.Cog):
+    def __init__(self, bot):
+        self.bot = bot
+
+    @commands.Cog.listener()
+    async def on_ready(self):
+        print("Tickets Online")
+
+    ticket = app_commands.Group(name="ticket", description="Have an issue? Create a ticket now")
+
+    @ticket.command(name="setup", description="Setup tickets")
+    @commands.has_permissions(administrator=True)
+    async def setup(self, interaction: discord.Interaction):
+        await interaction.response.send_message("Starting the setup process")
+
+        coll = db.guilds
+        guild = coll.find_one({"_id": interaction.guild.id})
+
+        if guild is not None:
+            await interaction.followup.send("Ticket has already been setup for this server")
+            return
+
+        if not discord.utils.get(interaction.guild.categories, name="OPENED TICKETS"):
+            await interaction.guild.create_category(name="OPENED TICKETS")
+        if not discord.utils.get(interaction.guild.categories, name="CLOSED TICKETS"):
+            await interaction.guild.create_category(name="CLOSED TICKETS")
+        if not discord.utils.get(interaction.guild.roles, name="Ticket Master"):
+            await interaction.guild.create_role(name="Ticket Master")
+        if not discord.utils.get(interaction.guild.channels, name="ticket-logs"):
+            await interaction.guild.create_text_channel(name="ticket-logs")
+
+        log_channel = discord.utils.get(interaction.guild.channels, name="ticket-logs")
+
+        coll.insert_one({"_id": interaction.guild.id, "ticket_count": 0, "opened_tickets": [], "closed_tickets": [], "log_channel": log_channel.id})
+        await interaction.followup.send("Tickets have been setup. Users can use `/ticket create` in any channel.")
+
+        await log_channel.send("This channel will now be used for ticket logging.")
+
+    @ticket.command(name="create", description="Have an issue? Create a ticket!")
+    async def create(self, interaction: discord.Interaction, reason: str):
+        # db setup
+        coll = db.tickets
+        guild_coll = db.guilds
+
+        guild = guild_coll.find_one({"_id": interaction.guild.id})
+
+        if guild is None:
+            await interaction.response.send_message("Tickets is not setup for this server...")
+            return
+
+        # category stuff & check if user already has a ticket opened
+        category: discord.CategoryChannel = discord.utils.get(interaction.guild.categories, name="OPENED TICKETS")
+        for ch in category.text_channels:
+            if ch.topic == f"{interaction.user.id} DO NOT CHANGE THE TOPIC OF THIS CHANNEL!":
+                await interaction.response.send_message(
+                    "You already have a ticket open! Ticket located in {0}".format(ch.mention), ephemeral=True)
+                return
+
+        # send message to let the user know the bot is doing things
+        await interaction.response.send_message("Creating a ticket...")
+
+        # get the ticket number
+        ticket_num = guild['ticket_count'] + 1
+
+        # channel settings & create channel
+        r1: discord.Role = discord.utils.get(interaction.guild.roles, name="Ticket Master")
+        overwrites = {
+            interaction.guild.default_role: discord.PermissionOverwrite(read_messages=False),
+            r1: discord.PermissionOverwrite(read_messages=True, send_messages=True, manage_messages=True),
+            interaction.user: discord.PermissionOverwrite(read_messages=True, send_messages=True),
+            interaction.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True)
+        }
+        channel = await category.create_text_channel(
+            name=f"ticket-{ticket_num}",
+            topic=f"{interaction.user.id} DO NOT CHANGE THE TOPIC OF THIS CHANNEL!",
+            overwrites=overwrites
+        )
+
+        # insert a collection into the db with info & update the ticket count in db
+        coll.insert_one({"_id": interaction.user.id, "ticket_number": ticket_num, "reason": reason, "opened": True, "channel_id": channel.id})
+        guild_coll.update_one({"_id": interaction.guild.id}, {"$inc": {"ticket_count": 1}})
+        guild_coll.update_one({"_id": interaction.guild.id}, {"$push": {"opened_tickets": ticket_num}})
+
+        # tell the user that their channel was created
+        await interaction.followup.send(f"{interaction.user.mention} your ticket has been created. Visit it @ {channel.mention}")
+
+        # create the embed for the ticket & send it in the channel
+        ticket_em = discord.Embed(title="Ticket", description=f"{reason}\n\nPlease do not ping any staff. To close this ticket use `/ticket close`", color=interaction.user.color)
+        await channel.send(f"Opened by {interaction.user.mention}\n\n{r1.mention}", embed=ticket_em)
+
+        # send a message in the ticket log channel
+        ticket_log_id = guild_coll.find_one({"_id": interaction.guild.id})['log_channel']
+        ticket_log = interaction.guild.get_channel(ticket_log_id)
+
+        await ticket_log.send(f"A ticket has been opened by {interaction.user.display_name}. The ticket ID is {ticket_num}. Ticket located at {channel.mention}")
+
+    @ticket.command(name="close", description="Close a ticket")
+    async def close(self, interaction: discord.Interaction):
+        channel = interaction.channel
+
+        # check if the channel is actually a ticket
+        if not channel.name.startswith("ticket-"):
+            await interaction.response.send_message("This command can only be used in a ticket...")
+            return
+
+        # get the ticket number
+        split_channel_name = channel.name.split("ticket-")
+        ticket = split_channel_name[1]
+
+        # send a message to the user letting them know the ticket is being closed
+        await interaction.response.send_message("Attempting to close this ticket...")
+
+        # get the author of the ticket
+        ticket_author = channel.topic.split(" ")[0]
+
+        # edit the channel
+        category: discord.CategoryChannel = discord.utils.get(interaction.guild.categories, name="CLOSED TICKETS")
+        r1: discord.Role = discord.utils.get(interaction.guild.roles, name="Ticket Master")
+        overwrites = {
+            interaction.guild.default_role: discord.PermissionOverwrite(read_messages=False),
+            r1: discord.PermissionOverwrite(read_messages=True, send_messages=True, manage_messages=True),
+            interaction.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True)
+        }
+
+        await interaction.channel.edit(category=category, overwrites=overwrites)
+
+        # update the db
+        ticket_coll = db.tickets
+        guild_coll = db.guilds
+
+        ticket_coll.update_one({"_id": int(ticket_author)}, {"$set": {"opened": False}})
+        guild_coll.update_one({"_id": interaction.guild.id}, {"$pull": {"opened_tickets": int(ticket)}})
+        guild_coll.update_one({"_id": interaction.guild.id}, {"$push": {"closed_tickets": int(ticket)}})
+
+        # tell the user the ticket was closer
+        await interaction.followup.send("This ticket was closed")
+
+        # send a message in the log channel
+        ticket_log_id = guild_coll.find_one({"_id": interaction.guild.id})['log_channel']
+        ticket_log = interaction.guild.get_channel(ticket_log_id)
+
+        await ticket_log.send(f"A ticket has been closed by {interaction.user.display_name}. Ticket ID: {int(ticket)}")
+
+    @ticket.command(name="delete", description="Delete a ticket")
+    @commands.has_permissions(manage_messages=True)
+    async def delete(self, interaction: discord.Interaction, ticket: int = None):
+        channel = interaction.channel
+
+        if not channel.name.startswith('ticket-') and ticket is None:
+            await interaction.response.send_message("Please either use this command in a ticket or specify a ticket #.")
+            return
+
+
+        if ticket is None and channel.name.startswith("ticket-"):
+            ticket = int(channel.name.split('ticket-')[1])
+
+        # loop through all the channels in the category and check if the name matches the ticket #
+        category = discord.utils.get(interaction.guild.categories, name="CLOSED TICKETS")
+        for ch in category.text_channels:
+            if ch.name == f"ticket-{ticket}":
+                await interaction.response.send_message("Deleting the ticket..")
+
+                # Delete the channel
+                await ch.delete(reason="Deleted Ticket")
+
+                # update the db
+                ticket_coll = db.tickets
+                guild_coll = db.guilds
+                ticket_author = int(ch.topic.split(" ")[0])
+
+                ticket_coll.delete_one({"_id": ticket_author})
+                guild_coll.update_one({"_id": interaction.guild.id}, {"$pull": {"closed_tickets": ticket}})
+
+                # send a message to the log channel
+                ticket_log_id = guild_coll.find_one({"_id": interaction.guild.id})['log_channel']
+                ticket_log = interaction.guild.get_channel(ticket_log_id)
+
+                await ticket_log.send(f"A ticket has been deleted by {interaction.user.display_name}. Ticket ID is {ticket}")
+                return
+
+        await interaction.response.send_message("A ticket could not be found with that ID. It either does not exist or is not closed.")
+
+async def setup(bot):
+    await bot.add_cog(Tickets(bot))
\ No newline at end of file