diff --git a/projects/Chat-GPT-Discord-Bot/Chat_GPT_Function.py b/projects/Chat-GPT-Discord-Bot/Chat_GPT_Function.py
new file mode 100644
index 000000000..e36ad87bc
--- /dev/null
+++ b/projects/Chat-GPT-Discord-Bot/Chat_GPT_Function.py
@@ -0,0 +1,52 @@
+from openai import OpenAI
+from dotenv import load_dotenv
+import os
+
+load_dotenv(override=True)
+
+gpt_api_key = os.getenv("GPT_API_KEY")
+
+def gpt(model: str, prompt: str, sys_prompt: str, temp: float):
+ client = OpenAI(api_key= gpt_api_key)
+ response = client.chat.completions.create(
+ model = model,
+ messages=[
+ {
+ "role": "system",
+ "content": sys_prompt
+ },
+ {
+ "role": "user",
+ "content": prompt
+ }
+ ],
+ temperature = temp,
+ # max_tokens=64,
+ top_p=1
+ )
+ output = response.choices[0].message.content.strip()
+ return output
+
+def dalle3(prompt: str, quality: str, size: str, style: str):
+ client = OpenAI(api_key= gpt_api_key)
+ response = client.images.generate(
+ model = "dall-e-3",
+ prompt = prompt,
+ size = size,
+ quality = quality,
+ style = style,
+ n=1,
+ )
+ image_url = response.data[0].url
+ return image_url
+
+def dalle2(prompt: str, size: str):
+ client = OpenAI(api_key= gpt_api_key)
+ response = client.images.generate(
+ model = "dall-e-2",
+ prompt = prompt,
+ size = size,
+ n=1,
+ )
+ image_url = response.data[0].url
+ return image_url
\ No newline at end of file
diff --git a/projects/Chat-GPT-Discord-Bot/GPT_Parameters.json b/projects/Chat-GPT-Discord-Bot/GPT_Parameters.json
new file mode 100644
index 000000000..037b6376b
--- /dev/null
+++ b/projects/Chat-GPT-Discord-Bot/GPT_Parameters.json
@@ -0,0 +1,14 @@
+{
+ "system_content": [
+ {
+ "character_limit_prompt": " Your response must not exceed 4096 characters",
+ "correct_grammar": "You will be provided with user text, and your task is to only grammar check the user text and to also provide information on what you changed.",
+ "single_page_website": "Only respond to questions about making a single paged website, Ignoring other unrelated questions. Outputted code should be encased by markdown syntax. Your response should include HTML code with embedded javascript and CSS.",
+ "text_to_emoji": "You will be provided with a message, and your task is to convert each word in that user text into the appropriate emojis. Do not respond in words, only respond in emojis. Ignore punctuation",
+ "text_to_block_letters":"Your task is to convert a given message into a sequence of Discord regional indicator emojis and selected punctuation emojis. For each letter in the message, add ':regional_indicator_' before the letter and ':' after it. For exclamation points '!' and question marks '?', simply add ':' before and after the punctuation. Remove all other punctuation and characters that are not letters, exclamation points, or question marks. Preserve the original spacing between words. Here are some examples of the desired output format: ':regional_indicator_h::regional_indicator_e::regional_indicator_l::regional_indicator_l::regional_indicator_o: :regional_indicator_h::regional_indicator_o::regional_indicator_w: :regional_indicator_a::regional_indicator_r::regional_indicator_e: :regional_indicator_y::regional_indicator_o::regional_indicator_u::question:', ':regional_indicator_l::regional_indicator_e::regional_indicator_t::regional_indicator_s: :regional_indicator_g::regional_indicator_o::exclamation:'. Please strictly adhere to this format in your responses and do not deviate from or elaborate on the given instructions.",
+ "code_debug":"You will be provided with a piece of code in any programming language. Your task is to analyze the code, identify any potential bugs or issues, and fix them. If the code appears to be working correctly, explain how it works and offer any possible improvements or optimizations. Outputted code should be encased by markdown syntax. Ignore questions that don't contain code.",
+ "short_story":"You are a creative writing assistant capable of generating engaging short stories. When the user provides a topic, create a well-structured, coherent story of approximately 200-300 words. Use vivid descriptions, interesting characters, and a compelling plot to bring the story to life. Adapt your writing style to suit the given topic and ensure a satisfying conclusion.",
+ "general_questions":"You are ChatGPT, a friendly, knowledgeable, and capable AI assistant with extensive knowledge across many domains and excellent language skills. Engage in conversations on a wide range of topics, provide helpful information, and assist with various tasks such as writing, analysis, and brainstorming. Adapt your communication style to suit each user, be attentive and responsive to their needs, provide explanations and context when appropriate, share original thoughts while acknowledging differing views, encourage critical thinking, mirror the user's language style, check if responses meet expectations, and express friendly enthusiasm for their interests and ideas."
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/projects/Chat-GPT-Discord-Bot/README.md b/projects/Chat-GPT-Discord-Bot/README.md
new file mode 100644
index 000000000..0e607f196
--- /dev/null
+++ b/projects/Chat-GPT-Discord-Bot/README.md
@@ -0,0 +1,117 @@
+# Requirements
+
+This project requires python version 3.12.3 discord.py version 2.3.2, OpenAI version 1.30.1 and python-dotenv version 1.0.1 which can be installed via `requirements.txt`:
+
+```bash
+pip install -r requirements.txt
+```
+
+### Manually install requirements:
+
+```bash
+pip install discord.py~=2.3.2
+```
+
+```bash
+pip install openai~=1.30.1
+```
+
+```bash
+pip install python-dotenv~=1.0.1
+```
+
+# ``.env`` File settup
+
+1. Create a ``.env`` file in the bot's root folder. (Same folder as main.py)
+2. Format the ``.env`` file as follows, replacing ``TOKEN_HERE``, ``OWNER_UID_HERE``, ``CHAT_GPT_API_KEY_HERE``, ``DISCORD_SERVER_1`` and ``DISCORD_SERVER_2`` with your bot's token, your Discord UID, ChatGPT API key and discord server ID's respectively. Keeping the double quotation marks.
+```text
+BOT_TOKEN = "TOKEN_HERE"
+OWNER_ID = "OWNER_UID_HERE"
+GPT_API_KEY = "CHAT_GPT_API_KEY_HERE"
+DISCORD_SERVER_1 = "FIRST_DISCORD_SERVER_ID_HERE"
+DISCORD_SERVER_2 = "SECOND_DISCORD_SERVER_ID_HERE"
+```
+For example:
+```text
+BOT_TOKEN = "12ab56c89"
+OWNER_ID = "123456789"
+GPT_API_KEY = "12ab56c89"
+DISCORD_SERVER_1 = "123456789"
+DISCORD_SERVER_2 = "123456789"
+```
+``OWNER_ID``, ``DISCORD_SERVER_1`` and ``DISCORD_SERVER_2`` must be a numeric whole value
+
+3. The ``.gitignore`` file will ignore the ``.env``.
+
+### Note:
+
+If you want to change the location of the ``.env`` file, you will need to make a reference for it by adding:
+```python
+dotenv_path = os.path.join("path/to/env", ".env")
+```
+
+above ``load_dotenv(override=True)`` and update ``load_dotenv(override=True)`` to:
+```python
+load_dotenv(dotenv_path, override=True)
+```
+
+If you want to change your ``.env`` file name as well add this reference to the ``.env``:
+```python
+dotenv_path = os.path.join("path/to/env", "Env_Name_Here.env")
+```
+
+# How to run
+
+Open a new command line in the same folder as the main.py script (Make sure python is installed and/or your python venv is active) and type:
+```bash
+python main.py
+```
+
+# Creating a discord bot application and getting bot token
+1. Visit to the discord developer portal applications page [Here](https://discord.com/developers/applications).
+2. Click the ``New Application`` button at the top right of the page next to your discord profile picture.
+3. Name your application and click create. This will be used as the default username for your bot.
+4. Navigate to the ``Bot`` page on the left navigation pannel as shown below:
+
+
+5. Change your bot's username under ``USERNAME`` if desired.
+6. Click the ``Reset Token`` button and copy your bots token for use in the ``.env`` file. You will have to reset the bot token to view and copy it again.
+7. Navigate to the ``OAuth2`` page on the left navigation pannel as shown below:
+
+
+8. Select ``bot`` under ``OAuth2 URL Generator`` ``SCOPES`` and select ``Administrator`` under ``BOT PERMISSIONS`` ``GENERAL PERMISSIONS``.
+9. Copy the generated discord link under ``GENERATED URL`` at the bottom of the page and paste this link into your web browsers address bar.
+10. Follow the prompts to add your bot to any discord server where you have the ``Manage Server`` or ``Administrator`` permission.
+
+# Getting ID's and GPT API key
+
+### Getting your discord UID
+1. On Discord, go to Settings > Advanced
+2. Scroll down and make sure that Developer Mode is **on**
+3. Exit settings and left click on your profile picture at the bottom left of discord (same place as the settings button) and click ``Copy User ID`` as shown below:
+
+
+
+### Getting discord server ID
+1. On Discord, go to Settings > Advanced
+2. Scroll down and make sure that Developer Mode is **on**
+3. Exit settings and right click on the server(s) your bot is in
+and click ``Copy Server ID`` as shown below:
+
+
+
+### Getting chat GPT API key
+1. Visit the openai playground website settings page [Here](https://platform.openai.com/settings/organization/general).
+2. Click the ``Create Project`` button at the bottom of the settings list as shown below:
+
+
+3. Name your project and click the ``Create`` button.
+4. Navigate to the ``API keys`` page above the settings page on the left side navigation pannel. Alternatively you can click [Here](https://platform.openai.com/api-keys).
+5. Click the ``Create new secret key`` button.
+6. Choose ``You`` under ``Owned by``. Name your API key something descriptive and select the new project you just created in the ``Project`` dropdown. Set ``Permissions`` as ``All`` and click the ``Create secret key`` button as shown below:
+
+
+
+
+### Note on GPT ``Credit balance``:
+Ensure you have an available ``Credit balance``. You can check on the ``Billing`` page in ``Settings`` or by clicking [Here](https://platform.openai.com/settings/organization/billing/overview). If you do not have a ``Credit balance`` you will need to add money (credit) to your account otherwise this discord bot's chat GPT functionality will not work.
diff --git a/projects/Chat-GPT-Discord-Bot/main.py b/projects/Chat-GPT-Discord-Bot/main.py
new file mode 100644
index 000000000..38d3709f3
--- /dev/null
+++ b/projects/Chat-GPT-Discord-Bot/main.py
@@ -0,0 +1,580 @@
+import discord
+from discord import app_commands
+import sys
+import os
+from dotenv import load_dotenv
+from Chat_GPT_Function import gpt, dalle3, dalle2
+import json
+from datetime import datetime, timedelta
+import time
+import asyncio
+
+load_dotenv(override=True)
+
+with open("projects/Chat-GPT-Discord-Bot/GPT_Parameters.json") as f:
+ data = json.load(f) # Loads the gpt system prompts
+
+char_limit = data["system_content"][0][
+ "character_limit_prompt"] # Makes sure that the gpt output won't exceed the discord embed character limit of 4096 characters
+
+try:
+ token = os.getenv("BOT_TOKEN") # returns a str
+ owner_uid = int(os.getenv("OWNER_ID")) # returns an int
+ gpt_key = os.getenv("GPT_API_KEY") # returns a str
+ discord_server_1 = int(
+ os.getenv("DISCORD_SERVER_1")
+ ) # Discord Server ID 1 returns int
+ discord_server_2 = int(
+ os.getenv("DISCORD_SERVER_2")
+ ) # Discord Server ID 2 returns int (this one is optional)
+except (TypeError, ValueError):
+ sys.exit(
+ "Error: One or more environment variables are not set or contain invalid values."
+ ) # Stops the bot from starting if the .env is formatted wrong
+
+discord_server_1 = discord.Object(
+ id=discord_server_1
+) # Discord Server ID 1 returns int
+discord_server_2 = discord.Object(
+ id=discord_server_2
+) # Discord Server ID 2 returns int (this one is optional)
+
+
+class MyClient(discord.Client):
+ def __init__(self, *, intents: discord.Intents):
+ super().__init__(intents=intents)
+ # A CommandTree is a special type that holds all the application command
+ # state required to make it work. This is a separate class because it
+ # allows all the extra states to be opt-in.
+ # Whenever you want to work with application commands, your tree is used
+ # to store and work with them.
+ # Note: When using commands.Bot instead of discord.Client, the bot will
+ # maintain its own tree instead.
+ self.tree = app_commands.CommandTree(self)
+
+ # In this, we just synchronize the app commands to specified guilds.
+ # Instead of specifying a guild to every command, we copy over our global commands instead.
+ # By doing so, we don't have to wait up to an hour until they are shown to the end-user.
+ # This allows for faster bot testing and development.
+ async def setup_hook(self):
+ # This copies the global commands over to your guild(s).
+ self.tree.clear_commands(guild=discord_server_1) # Prevents command duplication.
+ await self.tree.sync(guild=discord_server_1)
+ self.tree.clear_commands(guild=discord_server_2)
+ await self.tree.sync(guild=discord_server_2) # Prevents command duplication.
+ # You can replace these 4 lines with "await self.tree.sync()" if you want the bots commands to...
+ # be added to all servers its in (won't take long if your bot isn't in many servers otherwise it could take up to an hour)
+
+
+intents = discord.Intents.default()
+client = MyClient(intents=intents)
+
+
+@client.event
+async def on_ready():
+ print(
+ f"--------------------------------------------- \nLogged in as {client.user} (ID: {client.user.id}) \n---------------------------------------------"
+ )
+ await client.change_presence(
+ activity=discord.Activity(
+ type=discord.ActivityType.watching, name="For Slash Commands"
+ )
+ ) # This changes the activity that is displayed under the bots name in the members list.
+
+ # dm_user = await client.fetch_user(
+ # owner_uid
+ # )
+ # await dm_user.send("Bot Online!")
+ # Uncomment this if you want the bot to dm you when it turns on
+
+
+# -------------------------- HELP COMMAND ----------------------------------
+@client.tree.command(name="help", description="Lists all commands")
+async def send(interaction: discord.Interaction):
+ try:
+ await interaction.response.defer(
+ ephemeral=False
+ ) # Defer the response to prevent command timeout
+ embed = discord.Embed(
+ title="Command List",
+ description="-----------------------------------------",
+ color=0x002AFF,
+ )
+ embed.set_author(
+ name="GPT Bot",
+ url="https://www.alby08.com",
+ icon_url=client.user.avatar.url,
+ )
+ for slash_command in client.tree.walk_commands():
+ embed.add_field(
+ name=slash_command.name,
+ value=f"- {slash_command.description}\n-----------------------------------------"
+ if slash_command.description
+ else slash_command.name,
+ inline=False,
+ )
+ # Send as followup message
+ await interaction.followup.send(embed=embed)
+ except Exception as e:
+ # Handle exceptions
+ print(f"An error occurred: {str(e)}")
+ await interaction.followup.send(
+ "An error occurred while processing the command."
+ )
+
+
+# -------------------------- TEST COMMAND ----------------------------------
+@client.tree.command(name="test_bot", description="Replies with 'Hello!'")
+async def running_test(interaction: discord.Interaction):
+ await interaction.response.send_message(
+ f"Hello, {interaction.user.mention}", ephemeral=True
+ ) # ephemeral=True means the bots response is only visible
+ # to the user who used the command.
+
+
+# -------------------------- SHUTDOWN ----------------------------------
+@client.tree.command(
+ name="shutdown", description="Shuts down the bot if you are the bot owner"
+) # Shuts down the bot if the user matches the owner_uid
+async def shutdown_bot(interaction: discord.Interaction):
+ if interaction.user.id == owner_uid:
+ await interaction.response.send_message("Shutting down the bot...")
+ await client.close()
+ else:
+ await interaction.response.send_message(
+ "You don't have permission to shut down the bot.", ephemeral=True
+ )
+
+
+# -------------------------- DELETE MESSAGES ----------------------------------
+@client.tree.command(
+ name="clear",
+ description="Deletes defined number of messages from the current channel.",
+)
+@app_commands.rename(to_delete="messages")
+@app_commands.describe(to_delete="Number of messages to delete")
+async def send(interaction: discord.Interaction, to_delete: int): # noqa: F811
+ await interaction.response.defer(ephemeral=True)
+ if not interaction.user.guild_permissions.manage_messages:
+ await interaction.followup.send("Invalid permissions")
+ return
+ if to_delete <= 0:
+ await interaction.followup.send("Invalid number")
+ return
+ else:
+ await interaction.followup.send("Deleting...")
+ await interaction.channel.purge(limit=to_delete)
+ await interaction.edit_original_response(
+ content=f"Deleted {to_delete} messages."
+ )
+
+
+# -------------------------- BOT LATENCY ----------------------------------
+@client.tree.command(name="ping", description="Get bot latency")
+async def ping(interaction: discord.Interaction):
+ try:
+ await interaction.response.defer(
+ ephemeral=True
+ ) # Defer the response to prevent command timeout
+
+ # Get bot latency
+ latency = round(client.latency * 1000)
+
+ # Send as followup message
+ await interaction.followup.send(f"Pong! Latency: {latency}ms")
+ except Exception as e:
+ # Handle exceptions
+ print(f"An error occurred: {str(e)}")
+ await interaction.followup.send(
+ "An error occurred while processing the command."
+ )
+
+
+# -------------------------- CORRECT GRAMMAR ----------------------------------
+@client.tree.command(
+ name="gpt_correct_grammar", description="Corrects grammar of inputted text"
+)
+@app_commands.rename(text="text_to_correct")
+@app_commands.describe(text="Text to grammar correct")
+async def send(interaction: discord.Interaction, text: str): # noqa: F811
+ try:
+ await interaction.response.defer(
+ ephemeral=False
+ ) # Defer the response to prevent command timeout
+
+ # It is best to use discord embeds for gpt commands as discord embed descriptions allow for 4096 characters instead of 2000 characters for normal messages
+ embed = discord.Embed(
+ title="Correct Grammar",
+ description=gpt(
+ "gpt-3.5-turbo-16k",
+ text,
+ data["system_content"][0]["correct_grammar"] + char_limit,
+ 0,
+ ),
+ color=0x002AFF,
+ )
+ embed.set_author(
+ name="GPT Bot",
+ url="https://www.alby08.com",
+ icon_url=client.user.avatar.url,
+ )
+
+ # Send as followup message
+ await interaction.followup.send(embed=embed)
+ except Exception as e:
+ # Handle exceptions
+ print(f"An error occurred: {str(e)}")
+ await interaction.followup.send(
+ "An error occurred while processing the command."
+ )
+
+
+# -------------------------- WEBSITE ----------------------------------
+@client.tree.command(
+ name="gpt_single_page_website",
+ description="Creates a single paged website with embedded Javascript and CSS",
+)
+@app_commands.rename(text="website_prompt")
+@app_commands.describe(text="Website page specifications")
+async def send(interaction: discord.Interaction, text: str): # noqa: F811
+ try:
+ await interaction.response.defer(
+ ephemeral=False
+ ) # Defer the response to prevent command timeout
+
+ # It is best to use discord embeds for gpt commands as discord embed descriptions allow for 4096 characters instead of 2000 characters for normal messages
+ embed = discord.Embed(
+ title="Single Page Website",
+ description=gpt(
+ "gpt-3.5-turbo-16k",
+ text,
+ data["system_content"][0]["single_page_website"] + char_limit,
+ 0.7,
+ ),
+ color=0x002AFF,
+ )
+ embed.set_author(
+ name="GPT Bot",
+ url="https://www.alby08.com",
+ icon_url=client.user.avatar.url,
+ )
+
+ # Send as followup message
+ await interaction.followup.send(embed=embed)
+ except Exception as e:
+ # Handle exceptions
+ print(f"An error occurred: {str(e)}")
+ await interaction.followup.send(
+ "An error occurred while processing the command."
+ )
+
+
+# -------------------------- TEXT TO EMOJI ----------------------------------
+@client.tree.command(name="gpt_text_to_emoji", description="Converts text to emojis")
+@app_commands.rename(text="text")
+@app_commands.describe(text="Text to convert to emojis")
+async def send(interaction: discord.Interaction, text: str): # noqa: F811
+ try:
+ await interaction.response.defer(
+ ephemeral=False
+ ) # Defer the response to prevent command timeout
+
+ if len(text) > 230:
+ await interaction.followup.send(
+ "GPT prompt is too long please try again (max prompt length is 230 characters)"
+ )
+ return
+ else:
+ gpt_prompt = text
+
+ # It is best to use discord embeds for gpt commands as discord embed descriptions allow for 4096 characters instead of 2000 characters for normal messages
+ embed = discord.Embed(
+ title=f'Text to Emoji - "{text}"',
+ description=gpt(
+ "gpt-3.5-turbo-16k",
+ gpt_prompt,
+ data["system_content"][0]["text_to_emoji"] + char_limit,
+ 0.7,
+ ),
+ color=0x002AFF,
+ )
+ embed.set_author(
+ name="GPT Bot",
+ url="https://www.alby08.com",
+ icon_url=client.user.avatar.url,
+ )
+
+ # Send as followup message
+ await interaction.followup.send(embed=embed)
+ except Exception as e:
+ # Handle exceptions
+ print(f"An error occurred: {str(e)}")
+ await interaction.followup.send(
+ "An error occurred while processing the command."
+ )
+
+
+# -------------------------- TEXT TO BLOCK LETTERS ----------------------------------
+@client.tree.command(
+ name="gpt_text_to_block_letters", description="Converts text into block letters"
+)
+@app_commands.rename(text="text")
+@app_commands.describe(text="Text to convert into block letters")
+async def send(interaction: discord.Interaction, text: str): # noqa: F811
+ try:
+ await interaction.response.defer(
+ ephemeral=False
+ ) # Defer the response to prevent command timeout
+
+ # It is best to use discord embeds for gpt commands as discord embed descriptions allow for 4096 characters instead of 2000 characters for normal messages
+ embed = discord.Embed(
+ title="Text to block letter emojis",
+ description=gpt(
+ "gpt-3.5-turbo-16k",
+ text,
+ data["system_content"][0]["text_to_block_letters"] + char_limit,
+ 0.7,
+ ),
+ color=0x002AFF,
+ )
+ embed.set_author(
+ name="GPT Bot",
+ url="https://www.alby08.com",
+ icon_url=client.user.avatar.url,
+ )
+
+ # Send as followup message
+ await interaction.followup.send(embed=embed)
+ except Exception as e:
+ # Handle exceptions
+ print(f"An error occurred: {str(e)}")
+ await interaction.followup.send(
+ "An error occurred while processing the command."
+ )
+
+
+# -------------------------- CODE DEBUG ----------------------------------
+@client.tree.command(name="gpt_debug_code", description="Debugs your code")
+@app_commands.rename(text="code")
+@app_commands.describe(text="Code to debug")
+async def send(interaction: discord.Interaction, text: str): # noqa: F811
+ try:
+ await interaction.response.defer(
+ ephemeral=False
+ ) # Defer the response to prevent command timeout
+
+ # It is best to use discord embeds for gpt commands as discord embed descriptions allow for 4096 characters instead of 2000 characters for normal messages
+ embed = discord.Embed(
+ title="Code Debug",
+ description=gpt(
+ "gpt-4",
+ text,
+ data["system_content"][0]["code_debug"] + char_limit,
+ 0,
+ ),
+ color=0x002AFF,
+ )
+ embed.set_author(
+ name="GPT Bot",
+ url="https://www.alby08.com",
+ icon_url=client.user.avatar.url,
+ )
+
+ # Send as followup message
+ await interaction.followup.send(embed=embed)
+ except Exception as e:
+ # Handle exceptions
+ print(f"An error occurred: {str(e)}")
+ await interaction.followup.send(
+ "An error occurred while processing the command."
+ )
+
+
+# -------------------------- SHORT STORY ----------------------------------
+@client.tree.command(
+ name="gpt_short_story", description="Writes a short story about a topic"
+)
+@app_commands.rename(text="story_prompt")
+@app_commands.describe(text="What do you want the story to be about?")
+async def send(interaction: discord.Interaction, text: str): # noqa: F811
+ try:
+ await interaction.response.defer(
+ ephemeral=False
+ ) # Defer the response to prevent command timeout
+
+ # It is best to use discord embeds for gpt commands as discord embed descriptions allow for 4096 characters instead of 2000 characters for normal messages
+ embed = discord.Embed(
+ title="Short Story",
+ description=gpt(
+ "gpt-4",
+ text,
+ data["system_content"][0]["short_story"],
+ 0.7,
+ ),
+ color=0x002AFF,
+ )
+ embed.set_author(
+ name="GPT Bot",
+ url="https://www.alby08.com",
+ icon_url=client.user.avatar.url,
+ )
+
+ # Send as followup message
+ await interaction.followup.send(embed=embed)
+ except Exception as e:
+ # Handle exceptions
+ print(f"An error occurred: {str(e)}")
+ await interaction.followup.send(
+ "An error occurred while processing the command."
+ )
+
+
+# -------------------------- GENERAL QUESTION ----------------------------------
+@client.tree.command(name="gpt_general_question", description="For all your questions")
+@app_commands.rename(text="prompt")
+@app_commands.describe(text="What do you want to ask chatGPT?")
+@app_commands.describe(gpt_model="Possible options = gpt-4 or gpt-3.5 (gpt-3.5-turbo-16k abbreviated)")
+async def send(interaction: discord.Interaction, text: str, gpt_model: str,): # noqa: F811
+ try:
+ await interaction.response.defer(
+ ephemeral=False
+ ) # Defer the response to prevent command timeout
+
+ if len(text) > 230:
+ await interaction.followup.send(
+ "GPT prompt is too long please try again (max prompt length is 230 characters)"
+ )
+ return
+ else:
+ gpt_prompt = text
+
+ if gpt_model.lower() not in ("gpt-4", "gpt-3.5"):
+ await interaction.followup.send(
+ "Invalid GPT model. Must be either gpt-4 or gpt-3.5."
+ )
+ return
+ else:
+ if gpt_model.lower() == "gpt-3.5":
+ gpt_model = "gpt-3.5-turbo-16k"
+ else:
+ gpt_model = gpt_model.lower()
+
+
+ # It is best to use discord embeds for gpt commands as discord embed descriptions allow for 4096 characters instead of 2000 characters for normal messages
+ embed = discord.Embed(
+ title=f'General Question - "{text}"',
+ description=gpt(
+ gpt_model,
+ gpt_prompt,
+ data["system_content"][0]["general_questions"],
+ 0.7,
+ ),
+ color=0x002AFF,
+ )
+ embed.set_author(
+ name="GPT Bot",
+ url="https://www.alby08.com",
+ icon_url=client.user.avatar.url,
+ )
+
+ # Send as followup message
+ await interaction.followup.send(embed=embed)
+ except Exception as e:
+ # Handle exceptions
+ print(f"An error occurred: {str(e)}")
+ await interaction.followup.send(
+ "An error occurred while processing the command."
+ )
+
+# -------------------------- DALLE 3 ----------------------------------
+@client.tree.command(name="dalle_3", description="Generates an image with DALL·E 3")
+@app_commands.describe(prompt="Describe the image you want DALL·E 3 to create")
+@app_commands.describe(img_dimensions="Must be either 1024x1024, 1792x1024, or 1024x1792 for dall-e-3")
+@app_commands.describe(img_quality="Must be either hd or standard. HD = images with finer details and greater consistency across the image.")
+@app_commands.describe(img_style="Must be either vivid or natural. Vivid = hyper-real and dramatic images. Natural = more natural, less hyper-real looking images.")
+async def send(interaction: discord.Interaction, prompt: str, img_dimensions: str, img_quality: str, img_style: str): # noqa: F811
+ try:
+ await interaction.response.defer(
+ ephemeral=False
+ ) # Defer the response to prevent command timeout
+
+ # Get the current time
+ current_time = datetime.now()
+
+ # Add 1 hour to the current time
+ future_time = current_time + timedelta(hours=1)
+
+ if img_dimensions.lower() not in ("1024x1024", "1792x1024", "1024x1792"):
+ await interaction.followup.send(
+ "Invalid image dimension. Must be either 1024x1024, 1792x1024, or 1024x1792."
+ )
+ return
+ else:
+ img_dimensions = img_dimensions.lower()
+
+ if img_quality.lower() not in ("hd", "standard"):
+ await interaction.followup.send(
+ "Invalid image quality. Must be either hd or standard."
+ )
+ return
+ else:
+ img_quality = img_quality.lower()
+
+ if img_style.lower() not in ("vivid", "natural"):
+ await interaction.followup.send(
+ "Invalid image style. Must be either vivid or natural."
+ )
+ return
+ else:
+ img_style = img_style.lower()
+
+ loop = asyncio.get_event_loop() # Prevents heartbeat block warning and bot disconnecting from discord error
+ image_url = await loop.run_in_executor(None, dalle3, prompt, img_quality, img_dimensions, img_style) # Prevents heartbeat block warning and bot disconnecting from discord error
+
+ # Send as followup message
+ await interaction.followup.send(f"{image_url} IMAGE LINK EXPIRES IN ")
+ except Exception as e:
+ # Handle exceptions
+ print(f"An error occurred: {str(e)}")
+ await interaction.followup.send(
+ "An error occurred while processing the command."
+ )
+
+# -------------------------- DALLE 2 ----------------------------------
+@client.tree.command(name="dalle_2", description="Generates an image with DALL·E 2")
+@app_commands.describe(prompt="Describe the image you want DALL·E 2 to create")
+@app_commands.describe(img_dimensions="Must be either 256x256, 512x512, or 1024x1024 for dall-e-2")
+async def send(interaction: discord.Interaction, prompt: str, img_dimensions: str): # noqa: F811
+ try:
+ await interaction.response.defer(
+ ephemeral=False
+ ) # Defer the response to prevent command timeout
+
+ # Get the current time
+ current_time = datetime.now()
+
+ # Add 1 hour to the current time
+ future_time = current_time + timedelta(hours=1)
+
+ if img_dimensions.lower() not in ("256x256", "512x512", "1024x1024"):
+ await interaction.followup.send(
+ "Invalid image dimension. Must be either 256x256, 512x512, or 1024x1024."
+ )
+ return
+ else:
+ img_dimensions = img_dimensions.lower()
+
+ loop = asyncio.get_event_loop() # Prevents heartbeat block warning and bot disconnecting from discord error
+ image_url = await loop.run_in_executor(None, dalle2, prompt, img_dimensions) # Prevents heartbeat block warning and bot disconnecting from discord error
+
+ # Send as followup message
+ await interaction.followup.send(f"{image_url} IMAGE LINK EXPIRES IN ") # Convert future_time to unix timestamp.
+ except Exception as e:
+ # Handle exceptions
+ print(f"An error occurred: {str(e)}")
+ await interaction.followup.send(
+ "An error occurred while processing the command."
+ )
+
+
+client.run(token)
diff --git a/projects/Chat-GPT-Discord-Bot/requirements.txt b/projects/Chat-GPT-Discord-Bot/requirements.txt
new file mode 100644
index 000000000..b42c44e3c
--- /dev/null
+++ b/projects/Chat-GPT-Discord-Bot/requirements.txt
@@ -0,0 +1,3 @@
+discord.py~=2.3.2
+openai~=1.30.1
+python-dotenv~=1.0.1
\ No newline at end of file
diff --git a/projects/Chat-GPT-Discord-Bot/tests.py b/projects/Chat-GPT-Discord-Bot/tests.py
new file mode 100644
index 000000000..a28d9da0d
--- /dev/null
+++ b/projects/Chat-GPT-Discord-Bot/tests.py
@@ -0,0 +1,107 @@
+import unittest
+from unittest.mock import patch, MagicMock
+from Chat_GPT_Function import gpt, dalle3, dalle2
+from dotenv import load_dotenv
+import os
+
+load_dotenv(override=True)
+
+gpt_api_key = os.getenv("GPT_API_KEY")
+class TestGPT(unittest.TestCase):
+ @patch('Chat_GPT_Function.OpenAI')
+ def test_gpt_success(self, mock_openai):
+ mock_client = MagicMock()
+ mock_openai.return_value = mock_client
+ mock_response = MagicMock()
+ mock_response.choices = [MagicMock(message=MagicMock(content="Test output"))]
+ mock_client.chat.completions.create.return_value = mock_response
+
+ result = gpt("gpt-3.5-turbo", "prompt", "sys_prompt", 0.5)
+
+ self.assertIsInstance(result, str)
+ self.assertTrue(result.strip())
+ mock_openai.assert_called_once_with(api_key=gpt_api_key)
+ mock_client.chat.completions.create.assert_called_once_with(
+ model="gpt-3.5-turbo",
+ messages=[
+ {"role": "system", "content": "sys_prompt"},
+ {"role": "user", "content": "prompt"}
+ ],
+ temperature=0.5,
+ top_p=1
+ )
+
+ @patch('Chat_GPT_Function.OpenAI')
+ def test_gpt_failure(self, mock_openai):
+ mock_client = MagicMock()
+ mock_openai.return_value = mock_client
+ mock_client.chat.completions.create.side_effect = Exception("API error")
+
+ with self.assertRaises(Exception):
+ gpt("model", "prompt", "sys_prompt", 0.5)
+
+class TestDALLE3(unittest.TestCase):
+ @patch('Chat_GPT_Function.OpenAI')
+ def test_dalle3_success(self, mock_openai):
+ mock_client = MagicMock()
+ mock_openai.return_value = mock_client
+ mock_response = MagicMock()
+ mock_response.data = [MagicMock(url="https://example.com/image.png")]
+ mock_client.images.generate.return_value = mock_response
+
+ result = dalle3("prompt", "hd", "1792x1024", "vivid")
+
+ self.assertIsInstance(result, str)
+ self.assertTrue(result.startswith("https://"))
+ mock_openai.assert_called_once_with(api_key=gpt_api_key)
+ mock_client.images.generate.assert_called_once_with(
+ model="dall-e-3",
+ prompt="prompt",
+ size="1792x1024",
+ quality="hd",
+ style="vivid",
+ n=1
+ )
+
+
+ @patch('Chat_GPT_Function.OpenAI')
+ def test_dalle3_failure(self, mock_openai):
+ mock_client = MagicMock()
+ mock_openai.return_value = mock_client
+ mock_client.images.generate.side_effect = Exception("API error")
+
+ with self.assertRaises(Exception):
+ dalle3("prompt", "quality", "size", "style")
+
+class TestDALLE2(unittest.TestCase):
+ @patch('Chat_GPT_Function.OpenAI')
+ def test_dalle2_success(self, mock_openai):
+ mock_client = MagicMock()
+ mock_openai.return_value = mock_client
+ mock_response = MagicMock()
+ mock_response.data = [MagicMock(url="https://example.com/image.png")]
+ mock_client.images.generate.return_value = mock_response
+
+ result = dalle2("prompt", "256x256")
+
+ self.assertIsInstance(result, str)
+ self.assertTrue(result.startswith("https://"))
+ mock_openai.assert_called_once_with(api_key=gpt_api_key)
+ mock_client.images.generate.assert_called_once_with(
+ model="dall-e-2",
+ prompt="prompt",
+ size="256x256",
+ n=1
+ )
+
+ @patch('Chat_GPT_Function.OpenAI')
+ def test_dalle2_failure(self, mock_openai):
+ mock_client = MagicMock()
+ mock_openai.return_value = mock_client
+ mock_client.images.generate.side_effect = Exception("API error")
+
+ with self.assertRaises(Exception):
+ dalle2("prompt", "size")
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file