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