Contract Work - Discord Bot


Python SQL Discord.py



About the project

This is one of the bigger bots I have made for someone, and it has gone through many iterations since its inception. Initially, the bot was created to generate tickets for new members and to provide an automated ticket system for 1on1 support, but over time the owner used ChatGPT to add features. Although this was great and he was able to add new features, it led to a very cluttered file with a lot of useless code.

In the following sections I will go over the new code and what I changed to make it easier for the owner to add new features in the future and increase the readability and efficiency of the bot.

 

The features of the bot are: 

  • Advisor/1on1 scheduling
    • Automated tool to allow members of the Discord group to schedule sessions with advisors.
    • Commands to allow advisors to make themselves available/unavailable.
  • Welcome tickets
    • Creates tickets for new members to introduce them to the Discord to give them a better and less intimidating initial experience.
  • Daily checks
    • One daily check is to check all 6-day old members and send a message in an admin channel letting them know so they can check on the member.
    • The other daily check is to check if the user has been in the server for 30 days and if so then it will give them marketplace access.
      • There is also a command that will allow staff to manually check and assign the marketplace role as well.

 

 

1. Changes Made to the Old Code 

  • Modularity
    • Made the code more reusable by making the main code much smaller and adding new files to contain these reusable functions.
  • Useless code
    • Removed useless and inefficient code to make the script more legible and efficient.
  • Cleanliness
    • Reduced the size of the main bot code from over 1700 lines to 350, and overall code with all extra files combined was about 1000-1100 lines for a reduction of ~35%.
  • Promoting consistency
    • Moving sensitive keys and some commonly used variables (channel ids, roles, etc.) to a .env file to standardize the code.
  • Failsafe's
    • Not as important to the bots' functions, but a change in button functionality was made so that if the bot went down, any button view would be re-enabled when the bot reconnects to Discord.

 

2. Code

Below you can find the code for the bot.

 

Main file

Much simplified main code that has been shrunk to <350 lines of code. Cotains the commands and the trigger for the welcome tickets and 1on1 tickets which you can find in the next files.

import discord
import asyncio
from discord.ui import Button, View
import logging
import os
import datetime
from discord_webhook import DiscordWebhook
from datetime import timedelta
from discord import app_commands
import psycopg
from dotenv import load_dotenv

import embeds
import welcome
import advisor

load_dotenv()
TOKEN = os.environ.get('Token_Polar_Tools_2024')

intents = discord.Intents.all()
intents.messages = True
intents.message_content = True

# Role and Channel IDs
MANAGEMENT_ROLE = int(os.environ.get('MANAGEMENT_ROLE'))
ADVISOR_ROLE = int(os.environ.get('ADVISOR_ROLE'))
POLAR_TEEN_ROLE = int(os.environ.get('POLAR_TEEN_ROLE'))
POLAR_GUILD_ID = int(os.environ.get('POLAR_GUILD_ID'))
CATEGORY_OPEN_ID = int(os.environ.get('CATEGORY_OPEN_ID'))
POLAR_STAFF_ROLE_ID = int(os.environ.get('POLAR_STAFF_ROLE_ID'))
STAFF_CHAT_CHANNEL = int(os.environ.get('STAFF_CHAT_CHANNEL'))
DASH_LOGS_CHANNEL = int(os.environ.get('DASH_LOGS_CHANNEL'))
BOOK_ADVISOR_CATEGORY = int(os.environ.get('BOOK_ADVISOR_CATEGORY'))
FOLLOW_UP_CHANNEL_REMINDER = int(os.environ.get('FOLLOW_UP_CHANNEL_REMINDER'))

VERIFIED_SELLER_ROLE_ID = int(os.environ.get('VERIFIED_SELLER_ROLE_ID'))
MONTHS_REQUIRED = int(os.environ.get('MONTHS_REQUIRED'))
ROLE_ID = int(os.environ.get('ROLE_ID'))
POLAR_CUB_ROLE_ID = int(os.environ.get('POLAR_CUB_ROLE_ID'))

KIAN_ID = int(os.environ.get('KIAN_ID'))
SOLE_ID = int(os.environ.get('SOLE_ID'))

# Database Credentials
DBTable = os.environ.get('DBTable')
DBHost = os.environ.get('DBHost')
DBUsr = os.environ.get('DBUsr')
DBPass = os.environ.get('DBPass')
DBPort = os.environ.get('DBPort')

logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"), filename='PolarBot.log', filemode='a', format='%(asctime)s - [Line:%(lineno)d - Function:%(funcName)s In:%(filename)s From:%(name)s] \n[%(levelname)s] - %(message)s \n', datefmt='%d-%b-%y %H:%M:%S')

class Client(discord.Client):
    def __init__(self):
        super().__init__(intents=intents)
        self.synced = False
        asyncio.set_event_loop_policy(
            asyncio.WindowsSelectorEventLoopPolicy()
        )

    async def on_ready(self):
        if not self.synced:
            await self.wait_until_ready()
            await client.sync()
            self.synced = True

            # Sets calendar links to variables
            await advisor.set_calendars(Client, client)

            #sets some variables used in welcome.py
            await welcome.set_vars(Client)

            # Add views to the client
            Client.add_view(welcome.WelcomeViewsMain())
            Client.add_view(welcome.WelcomeViewsSecond())
            Client.add_view(welcome.WelcomeViewsThird())
            Client.add_view(welcome.WelcomeViewsFourth())
            Client.add_view(advisor.AdvisorViewsMain())
            Client.add_view(advisor.AdvisorViewsGeneral())
            Client.add_view(advisor.AdvisorViewsSpecific())
            Client.add_view(advisor.AdvisorViews(category=1))

            print(f'{self.user} has connected to Discord!')

            await asyncio.gather(perform_scan(), check_six_day_old_members())

Client = Client()
client = app_commands.CommandTree(Client)

##############################################################################
#
#
# This section is for misc actions
#
#
##############################################################################
# Gives marketplace access to people in the server for 3+ months.
@client.command(name='historical_scan', description='Perform a historical scan and assign roles to past members')
async def historical_scan_command(interaction: discord.Interaction):
    # Immediately acknowledge the interaction to avoid timeouts
    await interaction.response.defer()

    print('Manually triggered historical scan and role assignment...')
    try:
        await historical_scan()
        await interaction.followup.send('Historical scan completed and roles assigned to past members.')
    except Exception as e:
        print(f"Error in historical_scan_command: {e}")
        # Inform the user if there was an error during execution
        await interaction.followup.send('An error occurred during the historical scan.')

# Scans to see if they have been in the server for X time then give seller role.
async def perform_scan():
    print("entered perform_scan, waiting 180s to perform check.")
    while True:
        await asyncio.sleep(180)
        now = datetime.datetime.now()
        then = now.replace(hour = 0, minute = 1)
        wait_time = (then - now).total_seconds()
        if wait_time < 0:
            wait_time = 86400 + wait_time

        print(str(wait_time))
        await asyncio.sleep(wait_time)

        for guild in Client.guilds:
            for member in guild.members:
                # Check if the member has the target role
                if discord.utils.get(member.roles, id=POLAR_CUB_ROLE_ID):
                    # Your existing checks and role assignment logic
                    if discord.utils.get(member.roles, id=ROLE_ID):
                        join_date = member.joined_at.date()
                        today = datetime.datetime.today().date()

                        time_in_server = today - join_date
                        if time_in_server >= timedelta(days=30 * MONTHS_REQUIRED):
                            print(member.name)
                            role = discord.utils.get(member.guild.roles, id=VERIFIED_SELLER_ROLE_ID)
                            if role in member.roles:
                                pass
                            else:
                                await member.add_roles(role)
                                print(f"Gave {member} the {role.name} role.")
                                sendNewUserWebhook(member.name)

# Historical scan to reassign the seller role.
async def historical_scan():
    for guild in Client.guilds:
        for member in guild.members:
            # Check if the member has the target role
            if discord.utils.get(member.roles, id=POLAR_CUB_ROLE_ID):
                # Your existing checks and role assignment logic
                join_date = member.joined_at.date()
                today = datetime.datetime.today().date()

                time_in_server = today - join_date
                if time_in_server >= timedelta(days=30 * MONTHS_REQUIRED):
                    print(member.name)
                    role = discord.utils.get(member.guild.roles, id=VERIFIED_SELLER_ROLE_ID)
                    if role in member.roles:
                        pass
                    else:
                        await member.add_roles(role)
                        print(f"Gave {member} the {role.name} role.")
                        sendNewUserWebhook(member.name)
    print('Historical scan completed.')

# Sends a message to a certain channel stating user has been given the marketplace role.
def sendNewUserWebhook(discordName): 
    webhook_url = os.environ.get('SENDNEWUSERWEBHOOKURL')
    discordName = str(discordName)
    webhook = DiscordWebhook(url=webhook_url)

    message = f"{discordName} access to marketplace has been given"

    webhook.content = message

    response = webhook.execute()

# Checks for 6d old members and sends a message to a new channel letting admins know.
async def check_six_day_old_members():
    print("entered check_six_day_old_members, waiting 180s to perform check.")
    while True:
        await asyncio.sleep(180)
        now = datetime.datetime.now()
        then = now.replace(hour=12, minute=0)
        wait_time = (then - now).total_seconds()
        if wait_time < 0:
            wait_time = 86400 + wait_time

        print(str(wait_time))
        await asyncio.sleep(wait_time)

        # Starts the process once wait over
        channel = Client.get_channel(FOLLOW_UP_CHANNEL_REMINDER)
        reminder_role_id = MANAGEMENT_ROLE  # Replace with your reminder role ID
        memberstring = f"Reminder <@&{reminder_role_id}>, the following users joined 6 days ago:\n"

        # Adjust time window for checking
        now = datetime.datetime.now()
        dateminussix = now.day - 6
        far = now.replace(day=dateminussix, hour=23, minute=59, second=59)
        close = now.replace(day=dateminussix, hour=0)

        # Set to track processed users
        processed_users = set()

        ch = Client.get_channel(DASH_LOGS_CHANNEL)
        async for msg in ch.history(after=close, before=far):
            embeds = msg.embeds
            for embed in embeds:
                # Check if embed and embed.title are not None
                if embed and embed.title and "membership was purchased" in embed.title.lower():
                    for field in embed.fields:
                        if field.name.lower() == "discord id":
                            memberid = field.value.lower()

                            # Safely convert 'memberid' to an integer
                            try:
                                member_id_int = int(memberid)
                            except ValueError:
                                print(f"Invalid member ID: {memberid}")
                                continue  # Skip to the next loop iteration if the member ID is invalid

                            # Check if user is still in the server and not already processed
                            member = channel.guild.get_member(member_id_int)
                            if member and memberid not in processed_users:
                                memberstring += f"<@!{memberid}>\n"
                                processed_users.add(memberid)

        if len(processed_users) > 0:
            await channel.send(memberstring)
        else:
            await channel.send("No new users to remind today.")
        print("Check for 6-day old members completed.")

##############################################################################
#
#
# This section is for advisor ticket stuff
#
#
##############################################################################
# Testing Command
@client.command(name = "advisortest")
async def advisortest(interaction: discord.Interaction):

    if interaction.user.id == KIAN_ID or interaction.user.id == SOLE_ID:

        await advisor.initial_embed(interaction.channel)

    else:
        interaction.response.send_message("You can't use this command.")

# used to write availability to db
async def get_db_values(interaction, status, value):
    advisor_role_id = ADVISOR_ROLE  # The specific role ID

    advisor_role = discord.utils.get(interaction.user.roles, id=advisor_role_id)

    if advisor_role:
        await interaction.response.defer()  # Defer the response
        user_found = False

        # Database interaction code
        async with await psycopg.AsyncConnection.connect(dbname=DBTable, user=DBUsr, password=DBPass, host=DBHost, port=DBPort) as conn:
            try:
                logging.info("Opened database successfully")
                async with conn.cursor() as curr:
                    # Check if user is in the database
                    query_check_user = """SELECT EXISTS(SELECT 1 FROM oneonone WHERE id = %s)"""
                    await curr.execute(query_check_user, (interaction.user.id,))
                    user_found = await curr.fetchone()[0]

                    if user_found:
                        # User exists, update their availability
                        postgreSQL_upload_Query = """UPDATE oneonone SET available = %s WHERE id = %s"""
                        data = (value, interaction.user.id)
                        await curr.execute(postgreSQL_upload_Query, data)
                        await conn.commit()
                        logging.info("User status updated and closed")

                    # Close the connection
                    await conn.close()

            except Exception as e:
                logging.error(e)

        if user_found:
            await interaction.followup.send(f"You are now marked as {status} for 1 on 1s.")
        else:
            await interaction.followup.send("You are not registered for 1 on 1s or not found in the database.")
    else:
        await interaction.response.send_message("You don't have access to this command.")

    await advisor.set_calendars(Client, client)

# Allows advisors to make themselves unavailable
@client.command(name="unavailable", description="Become Unavailable to 1 on 1s")
async def unavailable(interaction: discord.Interaction):
    await get_db_values(interaction, "unavailable", 0)

# Allows advisors to make themselves available
@client.command(name="available", description="Become Available to 1 on 1s")
async def available(interaction: discord.Interaction):
    await get_db_values(interaction, "available", 1)

# Checks if a created channel is a new advisor ticket and initializes it
@Client.event
async def on_guild_channel_create(ch):
    if ch.category_id == BOOK_ADVISOR_CATEGORY:
        await advisor.initial_embed(ch)

##############################################################################
#
#
# This section is for welcome ticket stuff
#
#
##############################################################################
# Testing Command
@client.command(name = "welcometest")
async def welcometest(interaction: discord.Interaction):

    if interaction.user.id == KIAN_ID or interaction.user.id == SOLE_ID:

        # Get the member object associated with the interaction's user ID
        guild = Client.get_guild(POLAR_GUILD_ID)
        member = guild.get_member(interaction.user.id)

        await welcome.initial_embed(member, guild, POLAR_CUB_ROLE_ID, CATEGORY_OPEN_ID, POLAR_STAFF_ROLE_ID, STAFF_CHAT_CHANNEL)

    else:
        interaction.response.send_message("You can't use this command.")

# Used when members join to open a new welcome ticket
@Client.event
async def on_member_join(member):

    guild = Client.get_guild(POLAR_GUILD_ID)

    await welcome.initial_embed(member, guild, POLAR_CUB_ROLE_ID, CATEGORY_OPEN_ID, POLAR_STAFF_ROLE_ID, STAFF_CHAT_CHANNEL)

# Run the bot
Client.run(TOKEN)

 

Advisor/1on1 file

This contains the flow for the advisor session signup. Comprised of Classes for views and functions to generate data/embeds.

import discord
import asyncio
from discord.ui import Button, View
import psycopg as psycopg2
from dotenv import load_dotenv
import os
import logging
import random
import functools

import embeds

load_dotenv()

#calendars
all_calendars = {}

users = None
client = None

# Database Credentials
DBTable = os.environ.get('DBTable')
DBHost = os.environ.get('DBHost')
DBUsr = os.environ.get('DBUsr')
DBPass = os.environ.get('DBPass')
DBPort = os.environ.get('DBPort')

logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"), filename='PolarBot.log', filemode='a', format='%(asctime)s - [Line:%(lineno)d - Function:%(funcName)s In:%(filename)s From:%(name)s] \n[%(levelname)s] - %(message)s \n', datefmt='%d-%b-%y %H:%M:%S')

##############################################################################
#
# Called by the main file script to start the advisor process
#
##############################################################################
async def initial_embed(ch):

    emb = await embeds.initialAdvisorEmbed()

    view = AdvisorViewsMain()

    await asyncio.sleep(3)
    await ch.send(embed=emb, view=view)

##############################################################################
#
# This section is for welcome view classes
#
##############################################################################
class AdvisorViewsMain(View):
    def __init__(self):
        super().__init__(timeout=None)

    # Define Buttons
    @discord.ui.button(label="General Assistance", style=discord.ButtonStyle.blurple, custom_id="General")
    async def general_callback(self, interaction, button):
        view = AdvisorViewsGeneral()
        emb = await embeds.advisorGeneralEmbed()
        await interaction.channel.send(embed=emb, view=view)
        await interaction.response.edit_message(view = None)    
    @discord.ui.button(label="Specific Bot", style=discord.ButtonStyle.blurple, custom_id="Specific")
    async def specific_callback(self, interaction, button):
        view = AdvisorViewsSpecific()
        emb = await embeds.advisorSpecificEmbed()
        await interaction.channel.send(embed=emb, view=view)
        await interaction.response.edit_message(view = None)

class AdvisorViewsGeneral(View):
    def __init__(self):
        super().__init__(timeout=None)

    @discord.ui.button(label="Sneaker Botting", style=discord.ButtonStyle.blurple, custom_id="Sneaker")
    async def sneaker_callback(self, interaction, button):
        await random_select(interaction, "sneakers", 1)
        await interaction.channel.send("If the selected staff members schedule doesn't fit yours, feel free to press the **Sneaker Botting** button again for a new member.")
    @discord.ui.button(label="Retail Botting", style=discord.ButtonStyle.blurple, custom_id="Retail")
    async def retail_callback(self, interaction, button):
        await random_select(interaction, "retail", 1)
        await interaction.channel.send("If the selected staff members schedule doesn't fit yours, feel free to press the **Retail Botting** button again for a new member.")
    @discord.ui.button(label="Both", style=discord.ButtonStyle.blurple, custom_id="Both")
    async def both_callback(self, interaction, button):
        await random_select(interaction, "both", 0)
        await interaction.channel.send("If the selected staff members schedule doesn't fit yours, feel free to press the **Both** button again for a new member.")
    @discord.ui.button(label="Restart", style=discord.ButtonStyle.blurple, custom_id="Restart")
    async def restart_callback(self, interaction, button):
        emb = await embeds.initialAdvisorEmbed()
        view = AdvisorViewsMain()
        await interaction.channel.send(embed=emb, view = view)
        await interaction.response.edit_message(view = None)

class AdvisorViewsSpecific(View):
    def __init__(self):
        super().__init__(timeout=None)

    @discord.ui.button(label="Sneaker Botting", style=discord.ButtonStyle.blurple, custom_id="Sneaker_Specific")
    async def sneaker_callback(self, interaction, button):
        view = AdvisorViews(1)
        em, em1, mastercount = await generate_embed(1)
        if mastercount > 8:
            emb, emb1 = await embeds.advisorListEmbed(em, em1, mastercount)
            await interaction.channel.send(embed=emb)
            await interaction.channel.send(embed=emb1, view = view)
            await interaction.response.edit_message(view = None)
        else:
            emb = await embeds.advisorListEmbed(em, em1, mastercount)
            await interaction.channel.send(embed=emb, view = view)
            await interaction.response.edit_message(view = None)

    @discord.ui.button(label="Retail Botting", style=discord.ButtonStyle.blurple, custom_id="Retail_Specific")
    async def retail_callback(self, interaction, button):
        view = AdvisorViews(2)
        em, em1, mastercount = await generate_embed(2)
        if mastercount > 8:
            emb, emb1 = await embeds.advisorListEmbed(em, em1, mastercount)
            await interaction.channel.send(embed=emb)
            await interaction.channel.send(embed=emb1, view = view)
            await interaction.response.edit_message(view = None)
        else:
            emb = await embeds.advisorListEmbed(em, em1, mastercount)
            await interaction.channel.send(embed=emb, view = view)
            await interaction.response.edit_message(view = None)

    @discord.ui.button(label="Both", style=discord.ButtonStyle.blurple, custom_id="Both_Specific")
    async def both_callback(self, interaction, button):
        view = AdvisorViews(0)
        em, em1, mastercount = await generate_embed(0)
        if mastercount > 8:
            emb, emb1 = await embeds.advisorListEmbed(em, em1, mastercount)
            await interaction.channel.send(embed=emb)
            await interaction.channel.send(embed=emb1, view = view)
            await interaction.response.edit_message(view = None)
        else:
            emb = await embeds.advisorListEmbed(em, em1, mastercount)
            await interaction.channel.send(embed=emb, view = view)
            await interaction.response.edit_message(view = None)

    @discord.ui.button(label="Restart", style=discord.ButtonStyle.blurple, custom_id="Restart_Specific")
    async def restart_callback(self, interaction, button):
        emb = await embeds.initialAdvisorEmbed()
        view = AdvisorViewsMain()
        await interaction.channel.send(embed=emb, view = view)
        await interaction.response.edit_message(view = None)

class AdvisorViews(View):
    def __init__(self, category):
        super().__init__(timeout=None)
        self.category = category

        self.matching_ids = []
        if self.category == 1:
            for user_id, (cal, cat, avail) in all_calendars.items():
                if (cat == "sneakers" or cat == "both") and avail == 1:
                    self.matching_ids.append(user_id)
        if self.category == 2:
            for user_id, (cal, cat, avail) in all_calendars.items():
                if (cat == "retail" or cat == "both") and avail == 1:
                    self.matching_ids.append(user_id)
        if self.category == 0:
            for user_id, (cal, cat, avail) in all_calendars.items():
                if cat == "both" and avail == 1:
                    self.matching_ids.append(user_id)

        for user_id in self.matching_ids:
            member = discord.utils.get(users, id=int(user_id))
            if member:
                button = discord.ui.Button(
                    style=discord.ButtonStyle.blurple,
                    label=member.display_name,
                    custom_id=f"callback_{user_id}"
                )
                button.callback = functools.partial(self.handle_callback, user_id=user_id)
                self.add_item(button)

        button = discord.ui.Button(
            style=discord.ButtonStyle.blurple,
            label="Restart",
            custom_id="Restart_All"
        )
        button.callback = self.restart_callback
        self.add_item(button)

    async def handle_callback(self, interaction, user_id):
        # Implement your handling logic here
        member = discord.utils.get(users, id=int(user_id))
        if member:
            await interaction.response.send_message(f"Thank you! <@!{user_id}> Will be with you shortly.\n\n" + "Sign up here: " + all_calendars[user_id][0] + ". They will be with you shortly if you need further assistance.")
            await interaction.channel.send("If the selected staff members schedule doesn't fit yours, feel free to press another member or restart for a new category.")

    async def restart_callback(self, interaction):
        emb = await embeds.initialAdvisorEmbed()
        view = AdvisorViewsMain()
        await interaction.response.send_message(embed=emb, view = view)

##############################################################################
#
# Selects a random advisor
#
##############################################################################
async def random_select(interaction, selection, category):
    availstaff = []
    member = 0

    async with await psycopg2.AsyncConnection.connect(dbname = DBTable, user = DBUsr, password = DBPass, host = DBHost, port = DBPort) as conn:
        try:
            logging.info("Opened database successfully")
            async with conn.cursor() as curr:

                postgreSQL_select_Query = "select * from oneonone"
                await curr.execute(postgreSQL_select_Query)
                oneonone = await curr.fetchall()

                for row in oneonone:
                    if category == 1:
                        if (row[1] == 1 and row[2] == selection) or (row[1] == 1 and row[2] == "both"):
                            availstaff.append(row[0])
                    else:
                        if row[1] == 1 and row[2] == "both":
                            availstaff.append(row[0])

                member = random.choice(availstaff)

                for row in oneonone:
                    if row[0] == member:
                        await interaction.channel.send("Based on that information, <@!" + str(member) + "> Will be the most suited to assist you!\n\n" + "Sign up here: " + row[3] + ". They will be with you shortly if you need further assistance.")

                logging.info("Pulled values and closed")
                await conn.close()

        except Exception as e:
            logging.error(e)
            await conn.close()

##############################################################################
#
# Gets the calendars of staff
#
##############################################################################
async def set_calendars(u, c):
    global all_calendars
    global users
    global client

    users = u.users
    client = c

    async with await psycopg2.AsyncConnection.connect(dbname = DBTable, user = DBUsr, password = DBPass, host = DBHost, port = DBPort) as conn:
        try:
            logging.info("Opened database successfully")
            async with conn.cursor() as curr:

                postgreSQL_select_Query = "select * from oneonone"
                await curr.execute(postgreSQL_select_Query)
                oneonone = await curr.fetchall()

                count = 1

                for row in oneonone:
                    user_id = row[0]
                    calendar = row[3]
                    category = row[2]
                    avail = row[1]
                    all_calendars[user_id] = (calendar, category, avail)

                logging.info("Pulled values and closed")
                await conn.close()

        except Exception as e:
            logging.error(e)
            await conn.close()

##############################################################################
#
# Gets the staff list for the embed from the db
#
##############################################################################
async def generate_embed(category):
    em = ""
    em1 = ""
    mastercount = 1

    async with await psycopg2.AsyncConnection.connect(dbname = DBTable, user = DBUsr, password = DBPass, host = DBHost, port = DBPort) as conn:
        try:
            logging.info("Opened database successfully")
            async with conn.cursor() as curr:

                postgreSQL_select_Query = "select * from oneonone"
                await curr.execute(postgreSQL_select_Query)
                oneonone = await curr.fetchall()

                count = 1

                for row in oneonone:
                    if category == 1:
                        if (row[1] == 1 and row[2] == "sneakers") or (row[1] == 1 and row[2] == "both"):
                            if count <= 7:
                                em = em + row[6] + "\n"
                                count = count + 1
                                mastercount = mastercount + 1
                            else:
                                em1 = em1 + row[6] + "\n"
                                count = count + 1
                                mastercount = mastercount + 1
                    elif category == 2:
                        if (row[1] == 1 and row[2] == "retail") or (row[1] == 1 and row[2] == "both"):
                            if count <= 7:
                                em = em + row[7] + "\n"
                                count = count + 1
                                mastercount = mastercount + 1
                            else:
                                em1 = em1 + row[7] + "\n"
                                count = count + 1
                                mastercount = mastercount + 1
                    else:
                        if (row[1] == 1 and row[2] == "both"):
                            if count <= 7:
                                em = em + row[8] + "\n"
                                count = count + 1
                                mastercount = mastercount + 1
                            else:
                                em1 = em1 + row[8] + "\n"
                                count = count + 1
                                mastercount = mastercount + 1

                logging.info("Pulled values and closed")
                await conn.close()

        except Exception as e:
            logging.error(e)
            await conn.close()

    return em, em1, mastercount

 

Welcome Tickets file

This contains the flow for welcome tickets. Similar to the advisor file, it is comprised of Classes for views and functions to start the flow and send data.

import discord
from discord.ui import Button, View
import asyncio

import embeds

Client = None

# Sets client var on bot start
async def set_vars(c):
    global Client

    Client = c

##############################################################################
#
# This section is for welcome view classes
#
##############################################################################
class WelcomeViewsMain(View):
    def __init__(self):
        super().__init__(timeout=None)

    # Define Buttons
    @discord.ui.button(label="Beginner", style=discord.ButtonStyle.blurple, custom_id="Beginner")
    async def beginner_callback(self, interaction, button):
        view = WelcomeViewsSecond()
        emb = await embeds.customizeEmbed()
        await interaction.channel.send(embed=emb, view=view)
        await interaction.response.edit_message(view = None)    
    @discord.ui.button(label="Experienced", style=discord.ButtonStyle.blurple, custom_id="Experienced")
    async def experienced_callback(self, interaction, button):
        view = WelcomeViewsSecond()
        emb = await embeds.customizeEmbed()
        await interaction.channel.send(embed=emb, view=view)
        await interaction.response.edit_message(view = None)

class WelcomeViewsSecond(View):
    def __init__(self):
        super().__init__(timeout=None)

    # Define Buttons
    @discord.ui.button(emoji="๐Ÿ‘•", style=discord.ButtonStyle.grey, custom_id="1131999372054372422")
    async def streetwear_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="<:concert_ticket:1204732881654652988>", style=discord.ButtonStyle.grey, custom_id="1159321009321676830")
    async def ticket_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿฅท", style=discord.ButtonStyle.grey, custom_id="1159321108990930985")
    async def lowkey_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐ŸŽŸ๏ธ", style=discord.ButtonStyle.grey, custom_id="854616226901917696")
    async def raffle_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿ›’", style=discord.ButtonStyle.grey, custom_id="851932213666512956")
    async def aco_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿงฑ", style=discord.ButtonStyle.grey, custom_id="673336259954868235")
    async def brick_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿ‘ž", style=discord.ButtonStyle.grey, custom_id="851900087180328981")
    async def all_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="<:swoosh:1123399875778838640>", style=discord.ButtonStyle.grey, custom_id="1163924926206595092")
    async def nike_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿ“…", style=discord.ButtonStyle.grey, custom_id="1081022606502801469")
    async def future_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿ”†", style=discord.ButtonStyle.grey, custom_id="1201049328274391160")
    async def retail_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐ŸŽ‰", style=discord.ButtonStyle.grey, custom_id="1032024081589096499")
    async def groupbuy_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(label="Next", style=discord.ButtonStyle.blurple, custom_id="next1")
    async def next1_callback(self, interaction, button):
        try:
            view = WelcomeViewsThird()
            emb = await embeds.rolesEmbed()
            await interaction.channel.send(embed=emb, view=view)
            await interaction.response.edit_message(view = None)
        except Exception as e:
            await interaction.response.send_message("Please try clicking the button again, thanks!")
            print(f"Error with category assigning embed in ticket {interaction.channel.name}, with error {e}")

    async def handle_click(self, interaction: discord.Interaction, button: discord.ui.Button):
        # get role from the role id
        role = interaction.guild.get_role(int(button.custom_id))

        # if member has the role, remove it
        if role in interaction.user.roles:
            await interaction.user.remove_roles(role)
            # send confirmation message
            await interaction.response.send_message(f"Your {role} role has been removed", ephemeral=True)

        # if the member does not have the role, add it
        else:
            await interaction.user.add_roles(role)
            # send confirmation message
            await interaction.response.send_message(f"You have been given the {role} role", ephemeral=True)

class WelcomeViewsThird(View):
    def __init__(self):
        super().__init__(timeout=None)

    # Define Buttons
    @discord.ui.button(emoji="๐Ÿ”ง", style=discord.ButtonStyle.grey, custom_id="949367855386886174")
    async def streetwear_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿ–๏ธ", style=discord.ButtonStyle.grey, custom_id="949367724415537173")
    async def ticket_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(label="๐Ÿš™", style=discord.ButtonStyle.grey, custom_id="1022664146820538388")
    async def lowkey_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿ”", style=discord.ButtonStyle.grey, custom_id="1159325608799633499")
    async def raffle_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐ŸŽ‰", style=discord.ButtonStyle.grey, custom_id="774721210999242782")
    async def aco_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿช", style=discord.ButtonStyle.grey, custom_id="1159702777354792970")
    async def brick_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿ’ฝ", style=discord.ButtonStyle.grey, custom_id="1159751441397850152")
    async def all_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿƒ", style=discord.ButtonStyle.grey, custom_id="1159751529012678667")
    async def nike_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿงข", style=discord.ButtonStyle.grey, custom_id="1159322883231854703")
    async def future_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿงฉ", style=discord.ButtonStyle.grey, custom_id="1184724467193229332")
    async def retail_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿงธ", style=discord.ButtonStyle.grey, custom_id="1159321511199526952")
    async def groupbuy_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="๐Ÿ“•", style=discord.ButtonStyle.grey, custom_id="1159927284963217508")
    async def comic_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(emoji="โ›ณ", style=discord.ButtonStyle.grey, custom_id="1202409861502210088")
    async def golf_callback(self, button, interaction):
        await self.handle_click(button, interaction)
    @discord.ui.button(label="Next", style=discord.ButtonStyle.blurple, custom_id="496425184065224724")
    async def next_callback(self, interaction, button):
        try:
            view = WelcomeViewsFourth()
            emb = await embeds.checklistEmbed()
            await interaction.channel.send(embed=emb, view=view)
            await interaction.response.edit_message(view = None)

            # DM of the same embed
            await interaction.user.send(embed=emb)

            # Grants the final role
            role = interaction.guild.get_role(int(button.custom_id))
            await interaction.user.add_roles(role)
            await interaction.channel.send(f"You have been given the {role} role, welcome to Polar Chefs!")
        except Exception as e:
            await interaction.response.send_message("Please try clicking the button again, thanks!")
            print(f"Error with role assigning embed in ticket {interaction.channel.name}, with error {e}")
        
    async def handle_click(self, interaction: discord.Interaction, button: discord.ui.Button):
        # get role from the role id
        role = interaction.guild.get_role(int(button.custom_id))

        # if member has the role, remove it
        if role in interaction.user.roles:
            await interaction.user.remove_roles(role)
            # send confirmation message
            await interaction.response.send_message(f"Your {role} role has been removed", ephemeral=True)

        # if the member does not have the role, add it
        else:
            await interaction.user.add_roles(role)
            # send confirmation message
            await interaction.response.send_message(f"You have been given the {role} role", ephemeral=True)

class WelcomeViewsFourth(View):
    def __init__(self):
        super().__init__(timeout=None)

    # Define Buttons
    @discord.ui.button(label="Seek Help", style=discord.ButtonStyle.grey, custom_id="SeekHelp")
    async def streetwear_callback(self, interaction, button):
        channel_id = interaction.channel.id
        print(f"Seek Help button clicked in channel {channel_id}")

        channel = Client.get_channel(channel_id)
        print(f"Sending help message to channel {channel_id}")
        await channel.send(f"<@&605965629794680832> will be with you shortly.")
        await interaction.response.edit_message(view = None)
        
    @discord.ui.button(label="Close", style=discord.ButtonStyle.grey, custom_id="Close")
    async def ticket_callback(self, interaction, button):
        member = interaction.guild.get_member(interaction.user.id)
        if any(role.id == 605965629794680832 for role in member.roles):
            channel_id = interaction.channel.id
            print(f"Close button clicked in channel {channel_id}")

            channel = Client.get_channel(channel_id)
            print(f"Deleting channel {channel_id}")
            await channel.delete(reason="Onboarding process completed.")
        else:
            await interaction.response.send_message("You don't have access to this button.")

##############################################################################
#
# This section is for other welcome functions
#
##############################################################################
async def initial_embed(member, guild, POLAR_CUB_ROLE_ID, CATEGORY_OPEN_ID, POLAR_STAFF_ROLE_ID, STAFF_CHAT_CHANNEL):

    user = member
    try:
        print(f"Fetched user: {user.name}")
    except Exception as e:
        print(f"Issue fetching users name: {user.name}, error is: {e}")

    role = guild.get_role(POLAR_CUB_ROLE_ID)

    await asyncio.sleep(5)
    
    if role in user.roles:

        cat = discord.utils.get(guild.categories, id=CATEGORY_OPEN_ID)

        overwrites = {
            guild.default_role: discord.PermissionOverwrite(read_messages=False),
            guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True),
            user: discord.PermissionOverwrite(view_channel=True, read_message_history=True, send_messages=True),
            guild.get_role(POLAR_STAFF_ROLE_ID): discord.PermissionOverwrite(view_channel=True, read_message_history=True, send_messages=True, manage_channels=True)
        }

        ch = await guild.create_text_channel(f"Welcome-{user.name}", overwrites=overwrites, category=cat)
        print(f"Created new channel: {ch.name}")

        # Send a message to mention the user
        await ch.send(content=f"<@{user.id}>")

        emb = await embeds.initialWelcomeEmbed(user.name)

        view = WelcomeViewsMain()
    
        await ch.send(embed=emb, view=view)
        await send_welcome_notification(user, ch, guild, STAFF_CHAT_CHANNEL)

#######################################################################
#
# Sends Webhook to Staff Chat Channel
#
#######################################################################

async def send_welcome_notification(user, ch, guild, STAFF_CHAT_CHANNEL):
    try:
        channel = guild.get_channel(STAFF_CHAT_CHANNEL)
        if not channel:
            print(f"Unable to find channel with ID {STAFF_CHAT_CHANNEL}")
            return

        embed = discord.Embed(
            title=f"New Member Alert: {user.name}",
            description=f"‎\n<@{user.id}> has just joined!\n\nTheir onboarding process has begun in {ch.mention}\n\nPlease jump in and say *Hello* **immediately**.",
            color=0x25aee1
        )

        # Set the image for the embed
        embed.set_image(url="https://picsmemes.com/wp-content/uploads/2022/09/hello-meme.jpg")

        await channel.send(embed=embed)
    except Exception as e:
        print(f"Error in send_welcome_notification: {e}")

 

The last file is just for embeds, so I am not going to include it since I set it up so it would only contain embeds used throughout the code to keep the other files clean.

 

3. Future Changes

Some changes that could be added to the code to make it more efficient and cleaner could be:

  • Packing more into queries
    • This can avoid looping through pulled data and just grabbing what is needed in the query.
    • Wasn't initally done since the only data pulled from the database is the advisors which is at most 10-15 rows of data.
  • Useless Code
    • Remove useless code such as conn.close(), as AsyncConnection will close automatically when exited.

Only reason this had yet to be implemented is the owner is satisfied with the bot and it's performance so once he requests new features I will optimize the code more.