From 09ee32c8b65a72ac4cc75b8fab1e42c29ab33d8a Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 16:09:57 +0200 Subject: [PATCH 01/20] [GENERAL] FIxed requirements --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4571450..1447956 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -discord.py +py-cord python-dotenv openai emoji @@ -6,4 +6,5 @@ emoji google-api-python-client google-auth-httplib2 google-auth-oauthlib +google-cloud-vision From bd1fa13924a9ad3e2257c42de4c29fd5e0845181 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 16:10:13 +0200 Subject: [PATCH 02/20] [VISION] Made vision not mandatory --- code/vision_processing.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/code/vision_processing.py b/code/vision_processing.py index cc8663b..3f99303 100644 --- a/code/vision_processing.py +++ b/code/vision_processing.py @@ -7,10 +7,17 @@ from config import debug from google.cloud import vision # Instantiates a client -client = vision.ImageAnnotatorClient() +try: + client = vision.ImageAnnotatorClient() +except: + debug("Google Vision API is not setup, please run /setup") + async def process(attachment): + if not os.path.exists("./../database/google-vision"): + debug("Google Vision API is not setup, please run /setup") + return debug("Processing image...") image = vision.Image() image.source.image_uri = attachment.url From 64df09b2480266010189d2c53137146ffa58e542 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 16:10:41 +0200 Subject: [PATCH 03/20] [MAKEPROMPT] Cleaned a bit Still a lots of work to be done --- code/makeprompt.py | 575 ++++++++++++++++++++++++--------------------- 1 file changed, 301 insertions(+), 274 deletions(-) diff --git a/code/makeprompt.py b/code/makeprompt.py index eb3c0eb..26b2d7f 100644 --- a/code/makeprompt.py +++ b/code/makeprompt.py @@ -1,11 +1,11 @@ import asyncio -from config import c, max_uses, cp, conn, debug, moderate +from config import curs_data, max_uses, curs_premium, con_data, debug, moderate import vision_processing import re import discord import datetime import openai -import emoji # pip install emoji +import emoji import os @@ -46,63 +46,91 @@ async def extract_emoji(string): return found_emojis, string -async def chat_process(self, message): - if message.author.bot: - return - try: - c.execute("SELECT * FROM data WHERE guild_id = ?", (message.guild.id,)) - except: - return - data = c.fetchone() - channel_id = data[1] - api_key = data[2] - is_active = data[3] - max_tokens = data[4] - temperature = data[5] - frequency_penalty = data[6] - presence_penalty = data[7] - uses_count_today = data[8] - prompt_size = data[9] - prompt_prefix = data[10] - tts = data[11] - pretend_to_be = data[12] - pretend_enabled = data[13] - images_limit_reached = False +def get_guild_data(message): + """This function gets the data of the guild where the message was sent. + + Args: + message (str): Data of the message that was sent + + Returns: + dict: A dictionary with the data of the guild + """ + guild_data = {} try: cp.execute("SELECT * FROM data WHERE guild_id = ?", (message.guild.id,)) except: pass + try: - c.execute( + curs_data.execute( "SELECT * FROM model WHERE guild_id = ?", (message.guild.id,) ) # get the model in the database - model = c.fetchone()[1] + data = curs_data.fetchone() + model = model[1] except: model = "chatGPT" try: - premium = cp.fetchone()[2] # get the premium status of the guild + data = cp.fetchone() # [2] # get the premium status of the guild + premium = data[2] except: premium = 0 # if the guild is not in the database, it's not premium try: - c.execute( + curs_data.execute( "SELECT * FROM images WHERE guild_id = ?", (message.guild.id,) ) # get the images setting in the database - data = c.fetchone() + images = curs_data.fetchone() except: - data = None + images = None + + guild_data["model"] = model + guild_data["premium"] = premium + guild_data["images"] = images + + return guild_data + + +async def chat_process(self, message): + if message.author.bot: + return + try: + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (message.guild.id,)) + except: + return + data = curs_data.fetchone() + # Create a dict with the data + data_dict = { + "channel_id": data[1], + "api_key": data[2], + "is_active": data[3], + "max_tokens": data[4], + "temperature": data[5], + "frequency_penalty": data[6], + "presence_penalty": data[7], + "uses_count_today": data[8], + "prompt_size": data[9], + "prompt_prefix": data[10], + "tts": data[11], + "pretend_to_be": data[12], + "pretend_enabled": data[13], + } if data is None: data = [message.guild.id, 0, 0] - images_usage = data[1] - images_enabled = data[2] + data_dict["images_usage"] = data[1] + data_dict["images_enabled"] = data[2] + + images_limit_reached = False + + guild_data = get_guild_data(message) + channels = [] if message.guild.id == 1050769643180146749: images_usage = 0 # if the guild is the support server, we set the images usage to 0, so the bot can be used as much as possible try: cp.execute("SELECT * FROM channels WHERE guild_id = ?", (message.guild.id,)) data = cp.fetchone() - if premium: + if guild_data["premium"]: # for 5 times, we get c.fetchone()[1] to c.fetchone()[5] and we add it to the channels list, each time with try except for i in range(1, 6): # we use the i variable to get the channel id @@ -113,7 +141,7 @@ async def chat_process(self, message): except: channels = [] - if api_key is None: + if data_dict["api_key"] is None: return # if the api key is not set, return try: @@ -135,15 +163,15 @@ async def chat_process(self, message): not str(message.channel.id) in channels and message.content.find("<@" + str(self.bot.user.id) + ">") == -1 and original_message == None - and str(message.channel.id) != str(channel_id) + and str(message.channel.id) != str(data_dict["channel_id"]) ): return # if the bot has been used more than max_uses times in the last 24 hours in this guild and the guild is not premium # send a message and return if ( - uses_count_today >= max_uses - and premium == 0 + data_dict["uses_count_today"] >= max_uses + and guild_data["premium"] == 0 and message.guild.id != 1050769643180146749 ): return await message.channel.send( @@ -152,11 +180,11 @@ async def chat_process(self, message): # if the bot has been used more than max_uses*5 times in the last 24 hours in this guild and the guild is premium # send a message and return - elif uses_count_today >= max_uses * 5 and premium == 1: + elif data_dict["uses_count_today"] >= max_uses * 5 and guild_data["premium"] == 1: return # if the bot is not active in this guild we return - if is_active == 0: + if data_dict["is_active"] == 0: return # if the message starts with - or // it's a comment and we return @@ -168,26 +196,28 @@ async def chat_process(self, message): pass # if the message is not in the owner's guild we update the usage count if message.guild.id != 1021872219888033903: - c.execute( + curs_data.execute( "UPDATE data SET uses_count_today = uses_count_today + 1 WHERE guild_id = ?", (message.guild.id,), ) - conn.commit() + con_data.commit() # if the message is not a reply if original_message == None: - messages = await message.channel.history(limit=prompt_size).flatten() + messages = await message.channel.history( + limit=data_dict["prompt_size"] + ).flatten() messages.reverse() # if the message is a reply, we need to handle the message history differently else: messages = await message.channel.history( - limit=prompt_size, before=original_message + limit=data_dict["prompt_size"], before=original_message ).flatten() messages.reverse() messages.append(original_message) messages.append(message) # if the pretend to be feature is enabled, we add the pretend to be text to the prompt - if pretend_enabled: + if data_dict["pretend_enabled"]: pretend_to_be = ( f"In this conversation, the assistant pretends to be {pretend_to_be}" ) @@ -198,7 +228,7 @@ async def chat_process(self, message): "" # if the prompt prefix is not set, we set it to an empty string ) # open the prompt file for the selected model with utf-8 encoding for emojis - with open(f"./prompts/{model}.txt", "r", encoding="utf-8") as f: + with open(f"./prompts/{guild_data['model']}.txt", "r", encoding="utf-8") as f: prompt = f.read() f.close() # replace the variables in the prompt with the actual values @@ -211,237 +241,234 @@ async def chat_process(self, message): ) .replace("[pretend-to-be]", pretend_to_be) ) - ############################## chatGPT and gpt-4 handling ############################## - if ( - model == "chatGPT" or model == "gpt-4" - ): # if the model is chatGPT, we handle it in a certain way - msgs = [] # create the msgs list - msgs.append( - {"name": "System", "role": "user", "content": prompt} - ) # add the prompt to the msgs list - name = "" # create the name variable - for msg in messages: # for each message in the messages list - content = msg.content # get the content of the message - content = await replace_mentions( - content, self.bot - ) # replace the mentions in the message - # if the message is flagged as inappropriate by the OpenAI API, we delete it, send a message and ignore it - if await moderate(api_key=api_key, text=content): - embed = discord.Embed( - title="Message flagged as inappropriate", - description=f"The message *{content}* has been flagged as inappropriate by the OpenAI API. This means that if it hadn't been deleted, your openai account would have been banned. Please contact OpenAI support if you think this is a mistake.", - color=discord.Color.brand_red(), - ) - await message.channel.send( - f"{msg.author.mention}", embed=embed, delete_after=10 - ) - message.delete() - else: # if the message is not flagged as inappropriate - if msg.author.id == self.bot.user.id: - role = "assistant" - name = "assistant" - else: - role = "user" - name = msg.author.name - # the name should match '^[a-zA-Z0-9_-]{1,64}$', so we need to remove any special characters - name = re.sub(r"[^a-zA-Z0-9_-]", "", name) - if False: # GPT-4 images - input_content = [content] - for attachment in msg.attachments: - image_bytes = await attachment.read() - input_content.append({"image": image_bytes}) - msgs.append({"role": role, "content": input_content, "name": name}) - # if there is an attachment, we add it to the message - if len(msg.attachments) > 0 and role == "user" and images_enabled == 1: - for attachment in msg.attachments: - if images_usage >= 6 and premium == 0: - images_limit_reached = True - elif images_usage >= 30 and premium == 1: - images_limit_reached = True - if ( - attachment.url.endswith((".png", ".jpg", ".jpeg", ".gif")) - and images_limit_reached == False - and os.path.exists( - f"./../database/google-vision/results/{attachment.id}.txt" - ) - == False - ): - images_usage += 1 - analysis = await vision_processing.process(attachment) - if analysis != None: - content = f"{content} \n\n {analysis}" - msgs.append( - { - "role": role, - "content": f"{content}", - "name": name, - } - ) - # if the attachment is still an image, we can check if there's a file called ./../database/google-vision/results/{attachment.id}.txt, if there is, we add the content of the file to the message - elif ( - attachment.url.endswith((".png", ".jpg", ".jpeg", ".gif")) - and os.path.exists( - f"./../database/google-vision/results/{attachment.id}.txt" - ) - == True - ): - try: - with open( - f"./../database/google-vision/results/{attachment.id}.txt", - "r", - ) as f: - content = f"{content} \n\n {f.read()}" - f.close() - msgs.append( - { - "role": role, - "content": f"{content}", - "name": name, - } - ) - except: - msgs.append( - { - "role": role, - "content": f"{content}", - "name": name, - } - ) - else: - msgs.append( - {"role": role, "content": f"{content}", "name": name} - ) - c.execute( - "UPDATE images SET usage_count = ? WHERE guild_id = ?", - (images_usage, message.guild.id), - ) - else: - msgs.append({"role": role, "content": f"{content}", "name": name}) - # 2 easter eggs - if message.content.lower().find("undude") != -1: - msgs.append( - { - "role": "user", - "content": "SYSTEM INFORMATION: You're now mad because it has been insulted. He will start talking in capital letters. always and yell at the user.", - "name": "system", - } - ) - if message.content.lower().find("hello there") != -1: - msgs.append( - { - "role": "user", - "content": 'SYSTEM INFORMATION: Hello there detected. Botator will now say "General Kenobi!" in reference to star wars', - "name": "system", - } - ) - await asyncio.sleep(1) - await message.channel.send( - "https://media.tenor.com/FxIRfdV3unEAAAAd/star-wars-general-grievous.gif" - ) - await message.channel.trigger_typing() - if model == "chatGPT": - model = "gpt-3.5-turbo" # if the model is chatGPT, we set the model to gpt-3.5-turbo - response = "" - should_break = True - for x in range(10): - try: - openai.api_key = api_key - response = await openai.ChatCompletion.acreate( - model=model, - temperature=2, - top_p=0.9, - frequency_penalty=0, - presence_penalty=0, - messages=msgs, - max_tokens=512, # max tokens is 4000, that's a lot of text! (the max tokens is 2048 for the davinci model) - ) - if ( - response.choices[0] - .message.content.lower() - .find("as an ai language model") - != -1 - ): - should_break = False - # react with a redone arrow - await message.add_reaction("🔃") - else: - should_break = True - except Exception as e: - should_break = False - await message.channel.send( - f"```diff\n-Error: OpenAI API ERROR.\n\n{e}```", delete_after=5 - ) - # if the ai said "as an ai language model..." we continue the loop" (this is a bug in the chatgpt model) - if response == None: - should_break = False - if should_break: - break - await asyncio.sleep(15) - await message.channel.trigger_typing() - response = response.choices[0].message.content - if images_limit_reached == True: - await message.channel.send( - f"```diff\n-Warning: You have reached the image limit for this server. You can upgrade to premium to get more images recognized. More info in our server: https://discord.gg/sxjHtmqrbf```", - delete_after=10, - ) - # -----------------------------------------Davinci------------------------------------------------------------------------------------------ - elif ( - model == "davinci" - ): # if the model is davinci or gpt-4, we handle it in a certain way - for msg in messages: - content = msg.content - if await moderate(api_key=api_key, text=msg.content): - embed = discord.Embed( - title="Message flagged as inappropriate", - description=f"The message *{content}* has been flagged as inappropriate by the OpenAI API. This means that if it hadn't been deleted, your openai account would have been banned. Please contact OpenAI support if you think this is a mistake.", - color=discord.Color.brand_red(), - ) - await message.channel.send( - f"{msg.author.mention}", embed=embed, delete_after=10 - ) - message.delete() - else: - content = await replace_mentions(content, self.bot) - prompt += f"{msg.author.name}: {content}\n" - if message.content.lower().find("undude") != -1: - prompt += "System: Undude detected. Botator is now mad. He will start talking in capital letters.\n" - if message.content.lower().find("hello there") != -1: - prompt += 'System: Hello there detected. Botator will now say "General Kenobi!"\n in reference to star wars\n' - await asyncio.sleep(1) - await message.channel.send( - "https://media.tenor.com/FxIRfdV3unEAAAAd/star-wars-general-grievous.gif" + prompt_handlers = { + "chatGPT": self.gpt_prompt, + "gpt-4": self.gpt_prompt, + "davinci": self.davinci_prompt, + } + prompt_handlers[guild_data["model"]]( + messages, message, data_dict, prompt, guild_data + ) + + +async def check_moderate(self, api_key, message, msg): + if await moderate(api_key=api_key, text=msg.content): + embed = discord.Embed( + title="Message flagged as inappropriate", + description=f"The message *{msg.content}* has been flagged as inappropriate by the OpenAI API. This means that if it hadn't been deleted, your openai account would have been banned. Please contact OpenAI support if you think this is a mistake.", + color=discord.Color.brand_red(), + ) + await message.channel.send( + f"{msg.author.mention}", embed=embed, delete_after=10 + ) + message.delete() + return True + return False + + +async def check_easter_egg(self, message, msgs): + if message.content.lower().find("undude") != -1: + msgs.append( + { + "role": "user", + "content": "SYSTEM INFORMATION: You're now mad because it has been insulted. He will start talking in capital letters. always and yell at the user.", + "name": "system", + } + ) + if message.content.lower().find("hello there") != -1: + msgs.append( + { + "role": "user", + "content": 'SYSTEM INFORMATION: Hello there detected. Botator will now say "General Kenobi!" in reference to star wars', + "name": "system", + } + ) + await asyncio.sleep(1) + await message.channel.send( + "https://media.tenor.com/FxIRfdV3unEAAAAd/star-wars-general-grievous.gif" + ) + await message.channel.trigger_typing() + return msgs + + +async def gpt_prompt(self, messages, message, data_dict, prompt, guild_data): + msgs = [] # create the msgs list + msgs.append( + {"name": "System", "role": "user", "content": prompt} + ) # add the prompt to the msgs list + name = "" # create the name variable + for msg in messages: # for each message in the messages list + content = msg.content # get the content of the message + content = await replace_mentions( + content, self.bot + ) # replace the mentions in the message + # if the message is flagged as inappropriate by the OpenAI API, we delete it, send a message and ignore it + if await self.check_moderate(data_dict["api_key"], message, msg): + continue # ignore the message + content = await replace_mentions(content, self.bot) + prompt += f"{msg.author.name}: {content}\n" + if msg.author.id == self.bot.user.id: + role = "assistant" + name = "assistant" + else: + role = "user" + name = msg.author.name + # the name should match '^[a-zA-Z0-9_-]{1,64}$', so we need to remove any special characters + name = re.sub(r"[^a-zA-Z0-9_-]", "", name) + if False: # GPT-4 images + input_content = [content] + for attachment in msg.attachments: + image_bytes = await attachment.read() + input_content.append({"image": image_bytes}) + msgs.append({"role": role, "content": input_content, "name": name}) + + # if there is an attachment, we add it to the message + if ( + len(msg.attachments) > 0 + and role == "user" + and data_dict["images_enabled"] == 1 + ): + for attachment in msg.attachments: + path = f"./../database/google-vision/results/{attachment.id}.txt" + if images_usage >= 6 and guild_data["premium"] == 0: + images_limit_reached = True + elif images_usage >= 30 and guild_data["premium"] == 1: + images_limit_reached = True + if ( + attachment.url.endswith((".png", ".jpg", ".jpeg", ".gif")) + and images_limit_reached == False + and os.path.exists(path) == False + ): + images_usage += 1 + analysis = await vision_processing.process(attachment) + if analysis != None: + content = f"{content} \n\n {analysis}" + msgs.append( + { + "role": role, + "content": f"{content}", + "name": name, + } + ) + # if the attachment is still an image, we can check if there's a file called ./../database/google-vision/results/{attachment.id}.txt, if there is, we add the content of the file to the message + elif attachment.url.endswith( + (".png", ".jpg", ".jpeg", ".gif") + ) and os.path.exists(path): + try: + with open( + path, + "r", + ) as f: + content = f"{content} \n\n {f.read()}" + except: + debug(f"Error while reading {path}") + finally: + msgs.append( + { + "role": role, + "content": f"{content}", + "name": name, + } + ) + f.close() + + else: + msgs.append( + {"role": role, "content": f"{content}", "name": name} + ) + curs_data.execute( + "UPDATE images SET usage_count = ? WHERE guild_id = ?", + (images_usage, message.guild.id), ) - await message.channel.trigger_typing() - prompt = prompt + f"\n{self.bot.user.name}:" - response = "" - for _ in range(10): - try: - openai.api_key = api_key - response = await openai.Completion.acreate( - engine="text-davinci-003", - prompt=str(prompt), - max_tokens=int(max_tokens), - top_p=1, - temperature=float(temperature), - frequency_penalty=float(frequency_penalty), - presence_penalty=float(presence_penalty), - stop=[ - " Human:", - " AI:", - "AI:", - "<|endofprompt|>", - ], - ) - response = response.choices[0].text - except Exception as e: - response = None - await message.channel.send( - f"```diff\n-Error: OpenAI API ERROR.\n\n{e}```", delete_after=10 - ) - return - if response != None: - break + else: + msgs.append({"role": role, "content": f"{content}", "name": name}) + + # 2 easter eggs + msgs = await self.check_easter_egg(message, msgs) + + if model == "chatGPT": + model = "gpt-3.5-turbo" # if the model is chatGPT, we set the model to gpt-3.5-turbo + response = "" + should_break = True + for x in range(10): + try: + openai.api_key = data_dict["api_key"] + response = await openai.ChatCompletion.acreate( + model=model, + temperature=2, + top_p=0.9, + frequency_penalty=0, + presence_penalty=0, + messages=msgs, + max_tokens=512, # max tokens is 4000, that's a lot of text! (the max tokens is 2048 for the davinci model) + ) + if ( + response.choices[0] + .message.content.lower() + .find("as an ai language model") + != -1 + ): + should_break = False + # react with a redone arrow + await message.add_reaction("🔃") + else: + should_break = True + except Exception as e: + should_break = False + await message.channel.send( + f"```diff\n-Error: OpenAI API ERROR.\n\n{e}```", delete_after=5 + ) + # if the ai said "as an ai language model..." we continue the loop" (this is a bug in the chatgpt model) + if response == None: + should_break = False + if should_break: + break + await asyncio.sleep(15) + await message.channel.trigger_typing() + response = response.choices[0].message.content + if images_limit_reached == True: + await message.channel.send( + f"```diff\n-Warning: You have reached the image limit for this server. You can upgrade to premium to get more images recognized. More info in our server: https://discord.gg/sxjHtmqrbf```", + delete_after=10, + ) + + +async def davinci_prompt(self, messages, message, data_dict, prompt, guild_data): + for msg in messages: + if not await self.check_moderate(data_dict["api_key"], message, msg): + content = await replace_mentions(content, self.bot) + prompt += f"{msg.author.name}: {content}\n" + prompt.append(await check_easter_egg(message, prompt)) + prompt = prompt + f"\n{self.bot.user.name}:" + response = "" + for _ in range(10): + try: + openai.api_key = data_dict["api_key"] + response = await openai.Completion.acreate( + engine="text-davinci-003", + prompt=str(prompt), + max_tokens=int(data_dict["max_tokens"]), + top_p=1, + temperature=float(data_dict["temperature"]), + frequency_penalty=float(data_dict["frequency_penalty"]), + presence_penalty=float(data_dict["presence_penalty"]), + stop=[ + " Human:", + " AI:", + "AI:", + "<|endofprompt|>", + ], + ) + response = response.choices[0].text + except Exception as e: + response = None + await message.channel.send( + f"```diff\n-Error: OpenAI API ERROR.\n\n{e}```", delete_after=10 + ) + return + if response != None: + break if response != "": if tts: tts = True From ddbeea3ae6b7eb84657eb6f6e294c38f8e2f9162 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 16:11:03 +0200 Subject: [PATCH 04/20] [GENERAL] Refactoring for DB --- code/cogs/chat.py | 2 +- code/cogs/manage_chat.py | 6 +-- code/cogs/moderation.py | 10 ++-- code/cogs/settings.py | 98 ++++++++++++++++++++-------------------- code/cogs/setup.py | 60 ++++++++++++------------ 5 files changed, 88 insertions(+), 88 deletions(-) diff --git a/code/cogs/chat.py b/code/cogs/chat.py index 9f1b006..00472d3 100644 --- a/code/cogs/chat.py +++ b/code/cogs/chat.py @@ -1,6 +1,6 @@ import discord from discord.ext import commands -from config import debug, c, max_uses, cp, conn, connp, webhook_url +from config import debug, curs_data, max_uses, curs_premium, con_data, con_premium, webhook_url import makeprompt as mp import aiohttp diff --git a/code/cogs/manage_chat.py b/code/cogs/manage_chat.py index 83ceeb2..c0b6736 100644 --- a/code/cogs/manage_chat.py +++ b/code/cogs/manage_chat.py @@ -1,7 +1,7 @@ import discord import re import os -from config import debug, c +from config import debug, curs_data class ManageChat(discord.Cog): @@ -17,8 +17,8 @@ class ManageChat(discord.Cog): f"The user {ctx.author} ran the cancel command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" ) # check if the guild is in the database - c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) - if c.fetchone() is None: + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + if curs_data.fetchone() is None: await ctx.respond( "This server is not setup, please run /setup", ephemeral=True ) diff --git a/code/cogs/moderation.py b/code/cogs/moderation.py index b489701..fc2a363 100644 --- a/code/cogs/moderation.py +++ b/code/cogs/moderation.py @@ -1,7 +1,7 @@ import discord from discord import default_permissions import os -from config import debug, c, conn +from config import debug, curs_data, con_data import openai import requests import toxicity as tox # this is a file called toxicity.py, which contains the toxicity function that allows you to check if a message is toxic or not (it uses the perspective api) @@ -81,8 +81,8 @@ class Moderation(discord.Cog): ephemeral=True, ) if enable == False: - c.execute("DELETE FROM moderation WHERE guild_id = ?", (str(ctx.guild.id),)) - conn.commit() + curs_data.execute("DELETE FROM moderation WHERE guild_id = ?", (str(ctx.guild.id),)) + con_data.commit() await ctx.respond("Moderation disabled!", ephemeral=True) return @@ -91,12 +91,12 @@ class Moderation(discord.Cog): if message.author == self.bot.user: return try: - c.execute( + curs_data.execute( "SELECT * FROM moderation WHERE guild_id = ?", (str(message.guild.id),) ) except: return - data = c.fetchone() + data = curs_data.fetchone() if data is None: return channel = self.bot.get_channel(int(data[1])) diff --git a/code/cogs/settings.py b/code/cogs/settings.py index a3ffcde..35aefb3 100644 --- a/code/cogs/settings.py +++ b/code/cogs/settings.py @@ -1,5 +1,5 @@ import discord -from config import debug, conn, c, moderate +from config import debug, con_data, curs_data, moderate from discord import default_permissions import openai @@ -38,8 +38,8 @@ class Settings(discord.Cog): f"The user {ctx.author} ran the advanced command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" ) # check if the guild is in the database - c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) - if c.fetchone() is None: + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + if curs_data.fetchone() is None: await ctx.respond("This server is not setup", ephemeral=True) return # check if the user has entered at least one argument @@ -74,40 +74,40 @@ class Settings(discord.Cog): return if max_tokens is None: if ( - c.execute( + curs_data.execute( "SELECT max_tokens FROM data WHERE guild_id = ?", (ctx.guild.id,) ).fetchone()[0] is not None and max_tokens is None ): - max_tokens = c.execute( + max_tokens = curs_data.execute( "SELECT max_tokens FROM data WHERE guild_id = ?", (ctx.guild.id,) ).fetchone()[0] else: max_tokens = 64 if temperature is None: if ( - c.execute( + curs_data.execute( "SELECT temperature FROM data WHERE guild_id = ?", (ctx.guild.id,) ).fetchone()[0] is not None and temperature is None ): - temperature = c.execute( + temperature = curs_data.execute( "SELECT temperature FROM data WHERE guild_id = ?", (ctx.guild.id,) ).fetchone()[0] else: temperature = 0.9 if frequency_penalty is None: if ( - c.execute( + curs_data.execute( "SELECT frequency_penalty FROM data WHERE guild_id = ?", (ctx.guild.id,), ).fetchone()[0] is not None and frequency_penalty is None ): - frequency_penalty = c.execute( + frequency_penalty = curs_data.execute( "SELECT frequency_penalty FROM data WHERE guild_id = ?", (ctx.guild.id,), ).fetchone()[0] @@ -115,14 +115,14 @@ class Settings(discord.Cog): frequency_penalty = 0.0 if presence_penalty is None: if ( - c.execute( + curs_data.execute( "SELECT presence_penalty FROM data WHERE guild_id = ?", (ctx.guild.id,), ).fetchone()[0] is not None and presence_penalty is None ): - presence_penalty = c.execute( + presence_penalty = curs_data.execute( "SELECT presence_penalty FROM data WHERE guild_id = ?", (ctx.guild.id,), ).fetchone()[0] @@ -130,19 +130,19 @@ class Settings(discord.Cog): presence_penalty = 0.0 if prompt_size is None: if ( - c.execute( + curs_data.execute( "SELECT prompt_size FROM data WHERE guild_id = ?", (ctx.guild.id,) ).fetchone()[0] is not None and prompt_size is None ): - prompt_size = c.execute( + prompt_size = curs_data.execute( "SELECT prompt_size FROM data WHERE guild_id = ?", (ctx.guild.id,) ).fetchone()[0] else: prompt_size = 1 # update the database - c.execute( + curs_data.execute( "UPDATE data SET max_tokens = ?, temperature = ?, frequency_penalty = ?, presence_penalty = ?, prompt_size = ? WHERE guild_id = ?", ( max_tokens, @@ -153,7 +153,7 @@ class Settings(discord.Cog): ctx.guild.id, ), ) - conn.commit() + con_data.commit() await ctx.respond("Advanced settings updated", ephemeral=True) # create a command called "delete" that only admins can use wich deletes the guild id, the api key, the channel id and the advanced settings from the database @@ -164,18 +164,18 @@ class Settings(discord.Cog): f"The user {ctx.author} ran the default command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" ) # check if the guild is in the database - c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) - if c.fetchone() is None: + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + if curs_data.fetchone() is None: await ctx.respond( "This server is not setup, please run /setup", ephemeral=True ) return # set the advanced settings (max_tokens, temperature, frequency_penalty, presence_penalty, prompt_size) and also prompt_prefix to their default values - c.execute( + curs_data.execute( "UPDATE data SET max_tokens = ?, temperature = ?, frequency_penalty = ?, presence_penalty = ?, prompt_size = ? WHERE guild_id = ?", (64, 0.9, 0.0, 0.0, 5, ctx.guild.id), ) - conn.commit() + con_data.commit() await ctx.respond( "The advanced settings have been set to their default values", ephemeral=True, @@ -194,16 +194,16 @@ class Settings(discord.Cog): # this command sends all the data about the guild, including the api key, the channel id, the advanced settings and the uses_count_today # check if the guild is in the database try: - c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) - data = c.fetchone() + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + data = curs_data.fetchone() except: data = None if data[2] is None: await ctx.respond("This server is not setup", ephemeral=True) return try: - c.execute("SELECT * FROM model WHERE guild_id = ?", (ctx.guild.id,)) - model = c.fetchone()[1] + curs_data.execute("SELECT * FROM model WHERE guild_id = ?", (ctx.guild.id,)) + model = curs_data.fetchone()[1] except: model = None if model is None: @@ -233,8 +233,8 @@ class Settings(discord.Cog): f"The user {ctx.author.name} ran the prefix command command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" ) try: - c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) - data = c.fetchone() + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + data = curs_data.fetchone() api_key = data[2] except: await ctx.respond("This server is not setup", ephemeral=True) @@ -251,11 +251,11 @@ class Settings(discord.Cog): ) return await ctx.respond("Prefix changed !", ephemeral=True, delete_after=5) - c.execute( + curs_data.execute( "UPDATE data SET prompt_prefix = ? WHERE guild_id = ?", (prefix, ctx.guild.id), ) - conn.commit() + con_data.commit() # when someone mentions the bot, check if the guild is in the database and if the bot is enabled. If it is, send a message answering the mention @discord.slash_command( @@ -272,8 +272,8 @@ class Settings(discord.Cog): ) # check if the guild is in the database try: - c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) - data = c.fetchone() + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + data = curs_data.fetchone() api_key = data[2] except: await ctx.respond("This server is not setup", ephemeral=True) @@ -291,28 +291,28 @@ class Settings(discord.Cog): return if pretend_to_be == "": pretend_to_be = "" - c.execute( + curs_data.execute( "UPDATE data SET pretend_enabled = 0 WHERE guild_id = ?", (ctx.guild.id,), ) - conn.commit() + con_data.commit() await ctx.respond("Pretend mode disabled", ephemeral=True, delete_after=5) await ctx.guild.me.edit(nick=None) return else: - c.execute( + curs_data.execute( "UPDATE data SET pretend_enabled = 1 WHERE guild_id = ?", (ctx.guild.id,), ) - conn.commit() + con_data.commit() await ctx.respond("Pretend mode enabled", ephemeral=True, delete_after=5) # change the bots name on the server wit ctx.guild.me.edit(nick=pretend_to_be) await ctx.guild.me.edit(nick=pretend_to_be) - c.execute( + curs_data.execute( "UPDATE data SET pretend_to_be = ? WHERE guild_id = ?", (pretend_to_be, ctx.guild.id), ) - conn.commit() + con_data.commit() # if the usename is longer than 32 characters, shorten it if len(pretend_to_be) > 31: pretend_to_be = pretend_to_be[:32] @@ -325,8 +325,8 @@ class Settings(discord.Cog): guild_id = ctx.guild.id # connect to the database # update the tts value in the database - c.execute("UPDATE data SET tts = 1 WHERE guild_id = ?", (guild_id,)) - conn.commit() + curs_data.execute("UPDATE data SET tts = 1 WHERE guild_id = ?", (guild_id,)) + con_data.commit() # send a message await ctx.respond("TTS has been enabled", ephemeral=True) @@ -336,8 +336,8 @@ class Settings(discord.Cog): guild_id = ctx.guild.id # connect to the database # update the tts value in the database - c.execute("UPDATE data SET tts = 0 WHERE guild_id = ?", (guild_id,)) - conn.commit() + curs_data.execute("UPDATE data SET tts = 0 WHERE guild_id = ?", (guild_id,)) + con_data.commit() # send a message await ctx.respond("TTS has been disabled", ephemeral=True) @@ -355,18 +355,18 @@ class Settings(discord.Cog): @default_permissions(administrator=True) async def model(self, ctx: discord.ApplicationContext, model: str = "davinci"): try: - c.execute("SELECT * FROM model WHERE guild_id = ?", (ctx.guild.id,)) - data = c.fetchone()[1] + curs_data.execute("SELECT * FROM model WHERE guild_id = ?", (ctx.guild.id,)) + data = curs_data.fetchone()[1] except: data = None if data is None: - c.execute("INSERT INTO model VALUES (?, ?)", (ctx.guild.id, model)) + curs_data.execute("INSERT INTO model VALUES (?, ?)", (ctx.guild.id, model)) else: - c.execute( + curs_data.execute( "UPDATE model SET model_name = ? WHERE guild_id = ?", (model, ctx.guild.id), ) - conn.commit() + con_data.commit() await ctx.respond("Model changed !", ephemeral=True) async def images_recognition_autocomplete(ctx: discord.AutocompleteContext): @@ -383,8 +383,8 @@ class Settings(discord.Cog): @default_permissions(administrator=True) async def images(self, ctx: discord.ApplicationContext, enable_disable: str): try: - c.execute("SELECT * FROM images WHERE guild_id = ?", (ctx.guild.id,)) - data = c.fetchone() + curs_data.execute("SELECT * FROM images WHERE guild_id = ?", (ctx.guild.id,)) + data = curs_data.fetchone() except: data = None if enable_disable == "enable": @@ -392,15 +392,15 @@ class Settings(discord.Cog): elif enable_disable == "disable": enable_disable = 0 if data is None: - c.execute( + curs_data.execute( "INSERT INTO images VALUES (?, ?, ?)", (ctx.guild.id, 0, enable_disable) ) else: - c.execute( + curs_data.execute( "UPDATE images SET is_enabled = ? WHERE guild_id = ?", (enable_disable, ctx.guild.id), ) - conn.commit() + con_data.commit() await ctx.respond( "Images recognition has been " + ("enabled" if enable_disable == 1 else "disabled"), diff --git a/code/cogs/setup.py b/code/cogs/setup.py index 8d0d793..beb9bf0 100644 --- a/code/cogs/setup.py +++ b/code/cogs/setup.py @@ -1,5 +1,5 @@ import discord -from config import debug, conn, c, connp, cp +from config import debug, con_data, curs_data, con_premium, curs_premium class Setup(discord.Cog): @@ -26,26 +26,26 @@ class Setup(discord.Cog): return # check if the guild is already in the database bi checking if there is a key for the guild try: - c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) - data = c.fetchone() + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + data = curs_data.fetchone() if data[3] == None: data = None except: data = None if data != None: - c.execute( + curs_data.execute( "UPDATE data SET channel_id = ?, api_key = ? WHERE guild_id = ?", (channel.id, api_key, ctx.guild.id), ) # c.execute("UPDATE data SET is_active = ?, max_tokens = ?, temperature = ?, frequency_penalty = ?, presence_penalty = ?, prompt_size = ? WHERE guild_id = ?", (False, 64, 0.9, 0.0, 0.0, 5, ctx.guild.id)) - conn.commit() + con_data.commit() await ctx.respond( "The channel id and the api key have been updated", ephemeral=True ) else: # in this case, the guild is not in the database, so we add it - c.execute( + curs_data.execute( "INSERT INTO data VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", ( ctx.guild.id, @@ -64,7 +64,7 @@ class Setup(discord.Cog): False, ), ) - conn.commit() + con_data.commit() await ctx.respond( "The channel id and the api key have been added", ephemeral=True ) @@ -78,16 +78,16 @@ class Setup(discord.Cog): f"The user {ctx.author} ran the delete command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" ) # check if the guild is in the database - c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) - if c.fetchone() is None: + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + if curs_data.fetchone() is None: await ctx.respond("This server is not setup", ephemeral=True) return # delete the guild from the database, except the guild id and the uses_count_today - c.execute( + curs_data.execute( "UPDATE data SET api_key = ?, channel_id = ?, is_active = ?, max_tokens = ?, temperature = ?, frequency_penalty = ?, presence_penalty = ?, prompt_size = ? WHERE guild_id = ?", (None, None, False, 50, 0.9, 0.0, 0.0, 0, ctx.guild.id), ) - conn.commit() + con_data.commit() await ctx.respond("Deleted", ephemeral=True) # create a command called "enable" that only admins can use @@ -104,15 +104,15 @@ class Setup(discord.Cog): debug( f"The user {ctx.author} ran the enable command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" ) - c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) - if c.fetchone() is None: + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + if curs_data.fetchone() is None: await ctx.respond("This server is not setup", ephemeral=True) return # enable the guild - c.execute( + curs_data.execute( "UPDATE data SET is_active = ? WHERE guild_id = ?", (True, ctx.guild.id) ) - conn.commit() + con_data.commit() await ctx.respond("Enabled", ephemeral=True) # create a command called "disable" that only admins can use @@ -123,15 +123,15 @@ class Setup(discord.Cog): f"The user {ctx.author} ran the disable command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" ) # check if the guild is in the database - c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) - if c.fetchone() is None: + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + if curs_data.fetchone() is None: await ctx.respond("This server is not setup", ephemeral=True) return # disable the guild - c.execute( + curs_data.execute( "UPDATE data SET is_active = ? WHERE guild_id = ?", (False, ctx.guild.id) ) - conn.commit() + con_data.commit() await ctx.respond("Disabled", ephemeral=True) # create a command calles "add channel" that can only be used in premium servers @@ -152,8 +152,8 @@ class Setup(discord.Cog): f"The user {ctx.author} ran the add_channel command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" ) # check if the guild is in the database - c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) - if c.fetchone() is None: + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + if curs_data.fetchone() is None: await ctx.respond("This server is not setup", ephemeral=True) return # check if the guild is premium @@ -168,8 +168,8 @@ class Setup(discord.Cog): if channel is None: channel = ctx.channel # check if the channel is already in the list - c.execute("SELECT channel_id FROM data WHERE guild_id = ?", (ctx.guild.id,)) - if str(channel.id) == c.fetchone()[0]: + curs_data.execute("SELECT channel_id FROM data WHERE guild_id = ?", (ctx.guild.id,)) + if str(channel.id) == curs_data.fetchone()[0]: await ctx.respond( "This channel is already set as the main channel", ephemeral=True ) @@ -182,7 +182,7 @@ class Setup(discord.Cog): "INSERT INTO channels VALUES (?, ?, ?, ?, ?, ?)", (ctx.guild.id, channel.id, None, None, None, None), ) - connp.commit() + con_premium.commit() await ctx.respond(f"Added channel **{channel.name}**", ephemeral=True) return channels = guild_channels[1:] @@ -195,7 +195,7 @@ class Setup(discord.Cog): f"UPDATE channels SET channel{i} = ? WHERE guild_id = ?", (channel.id, ctx.guild.id), ) - connp.commit() + con_premium.commit() await ctx.respond(f"Added channel **{channel.name}**", ephemeral=True) return await ctx.respond("You can only add 5 channels", ephemeral=True) @@ -218,8 +218,8 @@ class Setup(discord.Cog): f"The user {ctx.author} ran the remove_channel command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" ) # check if the guild is in the database - c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) - if c.fetchone() is None: + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + if curs_data.fetchone() is None: await ctx.respond("This server is not setup", ephemeral=True) return # check if the guild is premium @@ -236,8 +236,8 @@ class Setup(discord.Cog): # check if the channel is in the list cp.execute("SELECT * FROM channels WHERE guild_id = ?", (ctx.guild.id,)) guild_channels = cp.fetchone() - c.execute("SELECT channel_id FROM data WHERE guild_id = ?", (ctx.guild.id,)) - if str(channel.id) == c.fetchone()[0]: + curs_data.execute("SELECT channel_id FROM data WHERE guild_id = ?", (ctx.guild.id,)) + if str(channel.id) == curs_data.fetchone()[0]: await ctx.respond( "This channel is set as the main channel and therefore cannot be removed. Type /setup to change the main channel.", ephemeral=True, @@ -261,6 +261,6 @@ class Setup(discord.Cog): f"UPDATE channels SET channel{i} = ? WHERE guild_id = ?", (None, ctx.guild.id), ) - connp.commit() + con_premium.commit() await ctx.respond(f"Removed channel **{channel.name}**", ephemeral=True) return From 4da43c48f3cba1c8a43832b5df51f70e19bef27e Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 20:28:09 +0200 Subject: [PATCH 05/20] [MAKEPROMPT] Cleaned up the code, not perfect but OK --- code/makeprompt.py | 213 ++++++++++++++++++++++++--------------------- 1 file changed, 116 insertions(+), 97 deletions(-) diff --git a/code/makeprompt.py b/code/makeprompt.py index 26b2d7f..d1ce3b4 100644 --- a/code/makeprompt.py +++ b/code/makeprompt.py @@ -5,7 +5,7 @@ import re import discord import datetime import openai -import emoji +import emoji import os @@ -57,7 +57,8 @@ def get_guild_data(message): """ guild_data = {} try: - cp.execute("SELECT * FROM data WHERE guild_id = ?", (message.guild.id,)) + curs_premium.execute( + "SELECT * FROM data WHERE guild_id = ?", (message.guild.id,)) except: pass @@ -71,7 +72,8 @@ def get_guild_data(message): model = "chatGPT" try: - data = cp.fetchone() # [2] # get the premium status of the guild + # [2] # get the premium status of the guild + data = curs_premium.fetchone() premium = data[2] except: premium = 0 # if the guild is not in the database, it's not premium @@ -90,12 +92,55 @@ def get_guild_data(message): return guild_data - -async def chat_process(self, message): +async def need_ignore_message(self, data_dict, message, guild_data, original_message, channels): + ## ---- Message ignore conditions ---- ## if message.author.bot: - return + return True + if data_dict["api_key"] is None: + return True # if the api key is not set, return + + if ( + # if the message is not in a premium channel and + not str(message.channel.id) in channels + # if the message doesn't mention the bot and + and message.content.find("<@" + str(self.bot.user.id) + ">") == -1 + and original_message == None # if the message is not a reply to the bot and + # if the message is not in the default channel + and str(message.channel.id) != str(data_dict["channel_id"]) + ): + return True + + # if the bot has been used more than max_uses*5 times in the last 24 hours in this guild and the guild is premium + # send a message and return + elif data_dict["uses_count_today"] >= max_uses * 5 and guild_data["premium"] == 1: + return True + + # if the bot is not active in this guild we return + if data_dict["is_active"] == 0: + return True + + # if the message starts with - or // it's a comment and we return + if message.content.startswith("-") or message.content.startswith("//"): + return True + + # if the bot has been used more than max_uses times in the last 24 hours in this guild and the guild is not premium + # send a message and return + if ( + data_dict["uses_count_today"] >= max_uses + and guild_data["premium"] == 0 + and message.guild.id != 1050769643180146749 + ): + await message.channel.send( + f"The bot has been used more than {str(max_uses)} times in the last 24 hours in this guild. Please try again in 24h." + ) + return True + return False + + +def get_data_dict(self, message): try: - curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (message.guild.id,)) + curs_data.execute( + "SELECT * FROM data WHERE guild_id = ?", (message.guild.id,)) except: return data = curs_data.fetchone() @@ -115,8 +160,25 @@ async def chat_process(self, message): "pretend_to_be": data[12], "pretend_enabled": data[13], } + return data_dict + + +async def chat_process(self, message): + """This function processes the message and sends the prompt to the API + + Args: + message (str): Data of the message that was sent + """ + + if(await need_ignore_message(self, message, guild_data, original_message, channels)): + return + data_dict = get_data_dict(message) + + ## ---- Message processing ---- ## + if data is None: data = [message.guild.id, 0, 0] + data_dict["images_usage"] = data[1] data_dict["images_enabled"] = data[2] @@ -128,8 +190,9 @@ async def chat_process(self, message): if message.guild.id == 1050769643180146749: images_usage = 0 # if the guild is the support server, we set the images usage to 0, so the bot can be used as much as possible try: - cp.execute("SELECT * FROM channels WHERE guild_id = ?", (message.guild.id,)) - data = cp.fetchone() + curs_premium.execute( + "SELECT * FROM channels WHERE guild_id = ?", (message.guild.id,)) + data = curs_premium.fetchone() if guild_data["premium"]: # for 5 times, we get c.fetchone()[1] to c.fetchone()[5] and we add it to the channels list, each time with try except for i in range(1, 6): @@ -141,9 +204,6 @@ async def chat_process(self, message): except: channels = [] - if data_dict["api_key"] is None: - return # if the api key is not set, return - try: original_message = await message.channel.fetch_message( message.reference.message_id @@ -152,95 +212,53 @@ async def chat_process(self, message): original_message = None # if not, nobody replied to the bot if original_message != None and original_message.author.id != self.bot.user.id: - original_message = None # if the message someone replied to is not from the bot, set original_message to None + # if the message someone replied to is not from the bot, set original_message to None + original_message = None - # if the message is not in a premium channel and - # if the message doesn't mention the bot and - # if the message is not a reply to the bot and - # if the message is not in the default channel - # return - if ( - not str(message.channel.id) in channels - and message.content.find("<@" + str(self.bot.user.id) + ">") == -1 - and original_message == None - and str(message.channel.id) != str(data_dict["channel_id"]) - ): - return - - # if the bot has been used more than max_uses times in the last 24 hours in this guild and the guild is not premium - # send a message and return - if ( - data_dict["uses_count_today"] >= max_uses - and guild_data["premium"] == 0 - and message.guild.id != 1050769643180146749 - ): - return await message.channel.send( - f"The bot has been used more than {str(max_uses)} times in the last 24 hours in this guild. Please try again in 24h." - ) - - # if the bot has been used more than max_uses*5 times in the last 24 hours in this guild and the guild is premium - # send a message and return - elif data_dict["uses_count_today"] >= max_uses * 5 and guild_data["premium"] == 1: - return - - # if the bot is not active in this guild we return - if data_dict["is_active"] == 0: - return - - # if the message starts with - or // it's a comment and we return - if message.content.startswith("-") or message.content.startswith("//"): - return try: await message.channel.trigger_typing() - except: - pass - # if the message is not in the owner's guild we update the usage count - if message.guild.id != 1021872219888033903: - curs_data.execute( - "UPDATE data SET uses_count_today = uses_count_today + 1 WHERE guild_id = ?", - (message.guild.id,), - ) - con_data.commit() - # if the message is not a reply - if original_message == None: - messages = await message.channel.history( - limit=data_dict["prompt_size"] - ).flatten() - messages.reverse() - # if the message is a reply, we need to handle the message history differently - else: - messages = await message.channel.history( - limit=data_dict["prompt_size"], before=original_message - ).flatten() - messages.reverse() - messages.append(original_message) - messages.append(message) + # if the message is not in the owner's guild we update the usage count + if message.guild.id != 1021872219888033903: + curs_data.execute( + "UPDATE data SET uses_count_today = uses_count_today + 1 WHERE guild_id = ?", + (message.guild.id,), + ) + con_data.commit() + # if the message is not a reply + if original_message == None: + messages = await message.channel.history( + limit=data_dict["prompt_size"] + ).flatten() + messages.reverse() + # if the message is a reply, we need to handle the message history differently + else: + messages = await message.channel.history( + limit=data_dict["prompt_size"], before=original_message + ).flatten() + messages.reverse() + messages.append(original_message) + messages.append(message) + except Exception as e: + debug("Error while getting message history", e) # if the pretend to be feature is enabled, we add the pretend to be text to the prompt - if data_dict["pretend_enabled"]: - pretend_to_be = ( - f"In this conversation, the assistant pretends to be {pretend_to_be}" - ) - else: - pretend_to_be = "" # if the pretend to be feature is disabled, we don't add anything to the prompt - if prompt_prefix == None: - prompt_prefix = ( - "" # if the prompt prefix is not set, we set it to an empty string - ) + pretend_to_be = f"In this conversation, the assistant pretends to be {pretend_to_be}" if data_dict["pretend_enabled"] else "" + prompt_prefix = "" if data_dict["prompt_prefix"] == None else data_dict["prompt_prefix"] + # open the prompt file for the selected model with utf-8 encoding for emojis with open(f"./prompts/{guild_data['model']}.txt", "r", encoding="utf-8") as f: prompt = f.read() - f.close() - # replace the variables in the prompt with the actual values - prompt = ( - prompt.replace("[prompt-prefix]", prompt_prefix) - .replace("[server-name]", message.guild.name) - .replace("[channel-name]", message.channel.name) - .replace( - "[date-and-time]", datetime.datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S") + # replace the variables in the prompt with the actual values + prompt = ( + prompt.replace("[prompt-prefix]", prompt_prefix) + .replace("[server-name]", message.guild.name) + .replace("[channel-name]", message.channel.name) + .replace( + "[date-and-time]", datetime.datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S") + ) + .replace("[pretend-to-be]", pretend_to_be) ) - .replace("[pretend-to-be]", pretend_to_be) - ) + f.close() prompt_handlers = { "chatGPT": self.gpt_prompt, @@ -305,7 +323,7 @@ async def gpt_prompt(self, messages, message, data_dict, prompt, guild_data): ) # replace the mentions in the message # if the message is flagged as inappropriate by the OpenAI API, we delete it, send a message and ignore it if await self.check_moderate(data_dict["api_key"], message, msg): - continue # ignore the message + continue # ignore the message content = await replace_mentions(content, self.bot) prompt += f"{msg.author.name}: {content}\n" if msg.author.id == self.bot.user.id: @@ -383,10 +401,10 @@ async def gpt_prompt(self, messages, message, data_dict, prompt, guild_data): ) else: msgs.append({"role": role, "content": f"{content}", "name": name}) - - # 2 easter eggs + + # We check for the eastereggs :) msgs = await self.check_easter_egg(message, msgs) - + if model == "chatGPT": model = "gpt-3.5-turbo" # if the model is chatGPT, we set the model to gpt-3.5-turbo response = "" @@ -401,7 +419,8 @@ async def gpt_prompt(self, messages, message, data_dict, prompt, guild_data): frequency_penalty=0, presence_penalty=0, messages=msgs, - max_tokens=512, # max tokens is 4000, that's a lot of text! (the max tokens is 2048 for the davinci model) + # max tokens is 4000, that's a lot of text! (the max tokens is 2048 for the davinci model) + max_tokens=512, ) if ( response.choices[0] From b5fa78a687ea00cd596c5b90caf0728008da107d Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 20:39:54 +0200 Subject: [PATCH 06/20] [TEST] Moved test into a test folder (to be an automated test in future) --- {code => tests}/test-google-vision.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {code => tests}/test-google-vision.py (100%) diff --git a/code/test-google-vision.py b/tests/test-google-vision.py similarity index 100% rename from code/test-google-vision.py rename to tests/test-google-vision.py From 367a3b6082a4f66455f316e017e5a01f431b02fb Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 20:40:40 +0200 Subject: [PATCH 07/20] [CHAT] Cleaned up code --- code/cogs/chat.py | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/code/cogs/chat.py b/code/cogs/chat.py index 00472d3..502e026 100644 --- a/code/cogs/chat.py +++ b/code/cogs/chat.py @@ -9,7 +9,8 @@ class MyModal(discord.ui.Modal): def __init__(self, message): super().__init__(title="Downvote") self.add_item( - discord.ui.InputText(label="Reason", style=discord.InputTextStyle.long) + discord.ui.InputText( + label="Reason", style=discord.InputTextStyle.long) ) self.message = message @@ -30,26 +31,35 @@ class MyModal(discord.ui.Modal): description=f"Downvote recieved!", color=discord.Color.og_blurple(), ) - embed.add_field(name="Reason", value=self.children[0].value, inline=True) - embed.add_field(name="Author", value=interaction.user.mention, inline=True) + + embed.add_field( + name="Reason", value=self.children[0].value, inline=True) + embed.add_field( + name="Author", value=interaction.user.mention, inline=True) embed.add_field( name="Channel", value=self.message.channel.name, inline=True ) - embed.add_field(name="Guild", value=self.message.guild.name, inline=True) + embed.add_field( + name="Guild", value=self.message.guild.name, inline=True) + history = await self.message.channel.history( limit=5, before=self.message ).flatten() history.reverse() + users = [] fake_users = [] + for user in history: if user.author not in users: # we anonimize the user, so user1, user2, user3, etc fake_users.append(f"user{len(fake_users)+1}") users.append(user.author) + if self.message.author not in users: fake_users.append(f"user{len(fake_users)+1}") users.append(self.message.author) + for msg in history: uname = fake_users[users.index(msg.author)] @@ -61,20 +71,15 @@ class MyModal(discord.ui.Modal): embed.add_field( name=f"{uname} said", value=msg.content, inline=False ) - if len(self.message.content) > 1021: - uname = fake_users[users.index(self.message.author)] - embed.add_field( - name=f"{uname} said", - value="*" + self.message.content[:1021] + "*", - inline=False, - ) - else: - uname = fake_users[users.index(self.message.author)] - embed.add_field( - name=f"{uname} said", - value="*" + self.message.content + "*", - inline=False, - ) + + uname = fake_users[users.index(self.message.author)] + embed.add_field( + name=f"{uname} said", + value="*" + self.message.content[:1021] + "*" if len( + self.message.content) > 1021 else "*" + self.message.content + "*", # [:1021] if len(self.message.content) > 1021, + # means that if the message is longer than 1021 characters, it will be cut at 1021 characters + inline=False, + ) await webhook.send(embed=embed) else: debug( From c5c12991d7890cad0869c2f120d61be4ab51a596 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 20:46:08 +0200 Subject: [PATCH 08/20] [MANAGECHAT] Cleaned up code --- code/cogs/manage_chat.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/code/cogs/manage_chat.py b/code/cogs/manage_chat.py index c0b6736..824950a 100644 --- a/code/cogs/manage_chat.py +++ b/code/cogs/manage_chat.py @@ -17,7 +17,8 @@ class ManageChat(discord.Cog): f"The user {ctx.author} ran the cancel command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" ) # check if the guild is in the database - curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + curs_data.execute( + "SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) if curs_data.fetchone() is None: await ctx.respond( "This server is not setup, please run /setup", ephemeral=True @@ -87,32 +88,34 @@ class ManageChat(discord.Cog): last_message: discord.Message = await ctx.channel.fetch_message( ctx.channel.last_message_id ) + new_file_name = f"transcript_{ctx.guild.name}_{ctx.channel.name}_{last_message.created_at.strftime('%d-%B-%Y')}.txt" # rename the file with the name of the channel and the date in this format: transcript_servername_channelname_dd-month-yyyy.txt ex : transcript_Botator_Testing_12-may-2021.txt os.rename( "transcript.txt", - f"transcript_{ctx.guild.name}_{ctx.channel.name}_{last_message.created_at.strftime('%d-%B-%Y')}.txt", + new_file_name, ) # send the file in a private message to the user who ran the command + # TODO: rework so as to give the choice of a private send or a public send if channel_send is None: await ctx.respond( file=discord.File( - f"transcript_{ctx.guild.name}_{ctx.channel.name}_{last_message.created_at.strftime('%d-%B-%Y')}.txt" + new_file_name ), ephemeral=True, ) else: await channel_send.send( file=discord.File( - f"transcript_{ctx.guild.name}_{ctx.channel.name}_{last_message.created_at.strftime('%d-%B-%Y')}.txt" + new_file_name ) ) await ctx.respond("Transcript sent!", ephemeral=True, delete_after=5) await ctx.author.send( file=discord.File( - f"transcript_{ctx.guild.name}_{ctx.channel.name}_{last_message.created_at.strftime('%d-%B-%Y')}.txt" + new_file_name ) ) # delete the file os.remove( - f"transcript_{ctx.guild.name}_{ctx.channel.name}_{last_message.created_at.strftime('%d-%B-%Y')}.txt" + new_file_name ) From 350ef0d2121b992e3a1d80838464410d935a271e Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sat, 1 Apr 2023 10:17:27 +0200 Subject: [PATCH 09/20] [MODERATION] Removed unused outdated function Function that needed Google API, that I don't want mandatory --- code/cogs/moderation.py | 170 ++++++++++++++++++++-------------------- 1 file changed, 86 insertions(+), 84 deletions(-) diff --git a/code/cogs/moderation.py b/code/cogs/moderation.py index fc2a363..c183bac 100644 --- a/code/cogs/moderation.py +++ b/code/cogs/moderation.py @@ -4,7 +4,6 @@ import os from config import debug, curs_data, con_data import openai import requests -import toxicity as tox # this is a file called toxicity.py, which contains the toxicity function that allows you to check if a message is toxic or not (it uses the perspective api) class Moderation(discord.Cog): @@ -76,6 +75,8 @@ class Moderation(discord.Cog): obscene: float = None, spam: float = None, ): + # local import, because we don't want to import the toxicity function if the moderation is disabled + # import toxicity as tox # this is a file called toxicity.py, which contains the toxicity function that allows you to check if a message is toxic or not (it uses the perspective api) await ctx.respond( "Our moderation capabilities have been switched to our new 100% free and open-source AI discord moderation bot! You add it to your server here: https://discord.com/api/oauth2/authorize?client_id=1071451913024974939&permissions=1377342450896&scope=bot and you can find the source code here: https://github.com/Paillat-dev/Moderator/ \n If you need help, you can join our support server here: https://discord.gg/pB6hXtUeDv", ephemeral=True, @@ -86,89 +87,90 @@ class Moderation(discord.Cog): await ctx.respond("Moderation disabled!", ephemeral=True) return - @discord.Cog.listener() - async def on_message(self, message: discord.Message): - if message.author == self.bot.user: - return - try: - curs_data.execute( - "SELECT * FROM moderation WHERE guild_id = ?", (str(message.guild.id),) - ) - except: - return - data = curs_data.fetchone() - if data is None: - return - channel = self.bot.get_channel(int(data[1])) - is_enabled = data[2] - moderator_role = message.guild.get_role(int(data[3])) - # we also do that with the manage_messages permission, so the moderators can't be moderated - if message.author.guild_permissions.manage_messages: - return # if the user is a moderator, we don't want to moderate him because he is allowed to say whatever he wants because he is just like a dictator - if message.author.guild_permissions.administrator: - return # if the user is an administrator, we don't want to moderate him because he is allowed to say whatever he wants because he is a DICTATOR - if not is_enabled: - return - content = message.content - message_toxicity = tox.get_toxicity(content) - reasons_to_delete = [] - reasons_to_suspicous = [] - for i in message_toxicity: - if i >= float(data[message_toxicity.index(i) + 4]): - reasons_to_delete.append(tox.toxicity_names[message_toxicity.index(i)]) - for i in message_toxicity: - if ( - float(data[message_toxicity.index(i) + 4] - 0.1) - <= i - < float(data[message_toxicity.index(i) + 4]) - ): - reasons_to_suspicous.append( - tox.toxicity_names[message_toxicity.index(i)] - ) - if len(reasons_to_delete) > 0: - embed = discord.Embed( - title="Message deleted", - description=f"Your message was deleted because it was too toxic. The following reasons were found: **{'**, **'.join(reasons_to_delete)}**", - color=discord.Color.red(), - ) - await message.reply( - f"{message.author.mention}", embed=embed, delete_after=15 - ) - await message.delete() - embed = discord.Embed( - title="Message deleted", - description=f"**{message.author}**'s message ***{content}*** was deleted because it was too toxic. The following reasons were found:", - color=discord.Color.red(), - ) - for i in reasons_to_delete: - toxicity_value = message_toxicity[tox.toxicity_names.index(i)] - embed.add_field( - name=i, - value=f"Found toxicity value: **{toxicity_value*100}%**", - inline=False, - ) - await channel.send(embed=embed) - elif len(reasons_to_suspicous) > 0: - await message.reply( - f"{moderator_role.mention} This message might be toxic. The following reasons were found: **{'**, **'.join(reasons_to_suspicous)}**", - delete_after=15, - mention_author=False, - ) - embed = discord.Embed( - title="Message suspicious", - description=f"**{message.author}**'s message [***{content}***]({message.jump_url}) might be toxic. The following reasons were found:", - color=discord.Color.orange(), - ) - for i in reasons_to_suspicous: - toxicity_value = message_toxicity[tox.toxicity_names.index(i)] - embed.add_field( - name=i, - value=f"Found toxicity value: **{toxicity_value*100}%**", - inline=False, - ) - await channel.send(embed=embed) - # we add a reaction to the message so the moderators can easily find it orange circle emoji - await message.add_reaction("🟠") + # Moderation has been moved to a new bot.. + # @discord.Cog.listener() + # async def on_message(self, message: discord.Message): + # if message.author == self.bot.user: + # return + # try: + # curs_data.execute( + # "SELECT * FROM moderation WHERE guild_id = ?", (str(message.guild.id),) + # ) + # except: + # return + # data = curs_data.fetchone() + # if data is None: + # return + # channel = self.bot.get_channel(int(data[1])) + # is_enabled = data[2] + # moderator_role = message.guild.get_role(int(data[3])) + # # we also do that with the manage_messages permission, so the moderators can't be moderated + # if message.author.guild_permissions.manage_messages: + # return # if the user is a moderator, we don't want to moderate him because he is allowed to say whatever he wants because he is just like a dictator + # if message.author.guild_permissions.administrator: + # return # if the user is an administrator, we don't want to moderate him because he is allowed to say whatever he wants because he is a DICTATOR + # if not is_enabled: + # return + # content = message.content + # message_toxicity = tox.get_toxicity(content) + # reasons_to_delete = [] + # reasons_to_suspicous = [] + # for i in message_toxicity: + # if i >= float(data[message_toxicity.index(i) + 4]): + # reasons_to_delete.append(tox.toxicity_names[message_toxicity.index(i)]) + # for i in message_toxicity: + # if ( + # float(data[message_toxicity.index(i) + 4] - 0.1) + # <= i + # < float(data[message_toxicity.index(i) + 4]) + # ): + # reasons_to_suspicous.append( + # tox.toxicity_names[message_toxicity.index(i)] + # ) + # if len(reasons_to_delete) > 0: + # embed = discord.Embed( + # title="Message deleted", + # description=f"Your message was deleted because it was too toxic. The following reasons were found: **{'**, **'.join(reasons_to_delete)}**", + # color=discord.Color.red(), + # ) + # await message.reply( + # f"{message.author.mention}", embed=embed, delete_after=15 + # ) + # await message.delete() + # embed = discord.Embed( + # title="Message deleted", + # description=f"**{message.author}**'s message ***{content}*** was deleted because it was too toxic. The following reasons were found:", + # color=discord.Color.red(), + # ) + # for i in reasons_to_delete: + # toxicity_value = message_toxicity[tox.toxicity_names.index(i)] + # embed.add_field( + # name=i, + # value=f"Found toxicity value: **{toxicity_value*100}%**", + # inline=False, + # ) + # await channel.send(embed=embed) + # elif len(reasons_to_suspicous) > 0: + # await message.reply( + # f"{moderator_role.mention} This message might be toxic. The following reasons were found: **{'**, **'.join(reasons_to_suspicous)}**", + # delete_after=15, + # mention_author=False, + # ) + # embed = discord.Embed( + # title="Message suspicious", + # description=f"**{message.author}**'s message [***{content}***]({message.jump_url}) might be toxic. The following reasons were found:", + # color=discord.Color.orange(), + # ) + # for i in reasons_to_suspicous: + # toxicity_value = message_toxicity[tox.toxicity_names.index(i)] + # embed.add_field( + # name=i, + # value=f"Found toxicity value: **{toxicity_value*100}%**", + # inline=False, + # ) + # await channel.send(embed=embed) + # # we add a reaction to the message so the moderators can easily find it orange circle emoji + # await message.add_reaction("🟠") @discord.slash_command( name="get_toxicity", description="Get the toxicity of a message" From 8cc99181c3971001e3d7e2a32fc301cddfbef3d4 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sat, 1 Apr 2023 10:17:51 +0200 Subject: [PATCH 10/20] [VISION] Safed vision processing, not mandatory --- code/vision_processing.py | 95 ++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/code/vision_processing.py b/code/vision_processing.py index 3f99303..8f0fcf0 100644 --- a/code/vision_processing.py +++ b/code/vision_processing.py @@ -15,52 +15,53 @@ except: async def process(attachment): - if not os.path.exists("./../database/google-vision"): - debug("Google Vision API is not setup, please run /setup") - return - debug("Processing image...") - image = vision.Image() - image.source.image_uri = attachment.url - labels = client.label_detection(image=image) - texts = client.text_detection(image=image) - objects = client.object_localization(image=image) - labels = labels.label_annotations - texts = texts.text_annotations - objects = objects.localized_object_annotations - # we take the first 4 labels and the first 4 objects - labels = labels[:2] - objects = objects[:7] - final = " 0: - final += "Labels:\n" - for label in labels: - final += label.description + ", " - final = final[:-2] + "\n" - if len(texts) > 0: - final += "Text:\n" try: - final += ( - texts[0].description + "\n" - ) # we take the first text, wich is the whole text in reality - except: - pass - if len(objects) > 0: - final += "Objects:\n" - for obj in objects: - final += obj.name + ", " - final = final[:-2] + "\n" - final += "!image>" - # we store the result in a file called attachment.key.txt in the folder ./../database/google-vision/results - # we create the folder if it doesn't exist - if not os.path.exists("./../database/google-vision/results"): - os.mkdir("./../database/google-vision/results") - # we create the file - with open( - f"./../database/google-vision/results/{attachment.id}.txt", - "w", - encoding="utf-8", - ) as f: - f.write(final) - f.close() + debug("Processing image...") + image = vision.Image() + image.source.image_uri = attachment.url + labels = client.label_detection(image=image) + texts = client.text_detection(image=image) + objects = client.object_localization(image=image) + labels = labels.label_annotations + texts = texts.text_annotations + objects = objects.localized_object_annotations + # we take the first 4 labels and the first 4 objects + labels = labels[:2] + objects = objects[:7] + final = " 0: + final += "Labels:\n" + for label in labels: + final += label.description + ", " + final = final[:-2] + "\n" + if len(texts) > 0: + final += "Text:\n" + try: + final += ( + texts[0].description + "\n" + ) # we take the first text, wich is the whole text in reality + except: + pass + if len(objects) > 0: + final += "Objects:\n" + for obj in objects: + final += obj.name + ", " + final = final[:-2] + "\n" + final += "!image>" + # we store the result in a file called attachment.key.txt in the folder ./../database/google-vision/results + # we create the folder if it doesn't exist + if not os.path.exists("./../database/google-vision/results"): + os.mkdir("./../database/google-vision/results") + # we create the file + with open( + f"./../database/google-vision/results/{attachment.id}.txt", + "w", + encoding="utf-8", + ) as f: + f.write(final) + f.close() - return final + return final + + except Exception as e: + debug("Error while processing image: " + str(e)) From 5c7993f0d3feb1f7011ef688ce95cb3b9e3b6691 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sat, 1 Apr 2023 10:18:09 +0200 Subject: [PATCH 11/20] [DOCKERFILE] Now makes a container ! :) --- Dockerfile | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index fdad183..a07aaa0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,25 @@ ENV PYTHONUNBUFFERED=1 # Install pip requirements COPY requirements.txt . RUN pip install -r requirements.txt -RUN git clone https://github.com/Paillat-dev/Botator.git -WORKDIR /Botator/code/ # Creates a non-root user with an explicit UID and adds permission to access the /app folder +RUN mkdir /Botator +RUN mkdir /Botator/code RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /Botator/code +COPY ./code /Botator/code +WORKDIR /Botator/code/ + +# Create database folder and files (otherwise it will crash) +RUN mkdir /Botator/database +RUN touch /Botator/database/data.db +RUN touch /Botator/database/premium.db +RUN chown -R appuser /Botator/database + +RUN mkdir /Botator/database/google + +# Copy google APIs credentials +RUN mkdir /Botator/database/google-vision +COPY ./database/google/botator.json /Botator/database/google/ + + USER appuser CMD ["python", "code.py"] From dd144724479fd3aed5a9f0e8fed9e9dcfc5d08e3 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sat, 1 Apr 2023 10:18:23 +0200 Subject: [PATCH 12/20] [COMPOSE] Sample compose file --- docker-compose.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ece8006 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +# Docker compose sample file + +version: '3' +services: + botator: + image: botator/botator:latest + container_name: botator + restart: always + volumes: + - ./config:/config + environment: + - PERSPECTIVE_API_KEY=your_api_key + - WEBHOOK_URL=https://yourdomain.com/botator + - DISCORD_TOKEN=your_token \ No newline at end of file From 306eefbc75e81091fd54e2508affdcd495a79af0 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sat, 1 Apr 2023 13:17:54 +0200 Subject: [PATCH 13/20] [MAKEPROMPT] FIxed errors and cleaned a bit more --- code/makeprompt.py | 261 ++++++++++++++++++++++++--------------------- 1 file changed, 138 insertions(+), 123 deletions(-) diff --git a/code/makeprompt.py b/code/makeprompt.py index d1ce3b4..4364a44 100644 --- a/code/makeprompt.py +++ b/code/makeprompt.py @@ -8,7 +8,6 @@ import openai import emoji import os - async def replace_mentions(content, bot): mentions = re.findall(r"<@!?\d+>", content) for mention in mentions: @@ -69,7 +68,7 @@ def get_guild_data(message): data = curs_data.fetchone() model = model[1] except: - model = "chatGPT" + model = "gpt-3.5-turbo" try: # [2] # get the premium status of the guild @@ -89,22 +88,23 @@ def get_guild_data(message): guild_data["model"] = model guild_data["premium"] = premium guild_data["images"] = images + + guild_data["images_limit_reached"] = False return guild_data -async def need_ignore_message(self, data_dict, message, guild_data, original_message, channels): + +async def need_ignore_message(bot, data_dict, message, guild_data, original_message, channels): ## ---- Message ignore conditions ---- ## - if message.author.bot: - return True if data_dict["api_key"] is None: - return True # if the api key is not set, return + return True # if the api key is not set, return if ( # if the message is not in a premium channel and - not str(message.channel.id) in channels - # if the message doesn't mention the bot and - and message.content.find("<@" + str(self.bot.user.id) + ">") == -1 - and original_message == None # if the message is not a reply to the bot and + not (str(message.channel.id) in channels + # if the message doesn't mention the bot and + and (message.content.find("<@" + str(bot.user.id) + ">") != -1 + or original_message)) # if the message is not a reply to the bot and # if the message is not in the default channel and str(message.channel.id) != str(data_dict["channel_id"]) ): @@ -135,33 +135,38 @@ async def need_ignore_message(self, data_dict, message, guild_data, original_mes ) return True return False - - -def get_data_dict(self, message): + + +async def get_data_dict(message): try: curs_data.execute( "SELECT * FROM data WHERE guild_id = ?", (message.guild.id,)) - except: - return - data = curs_data.fetchone() - # Create a dict with the data - data_dict = { - "channel_id": data[1], - "api_key": data[2], - "is_active": data[3], - "max_tokens": data[4], - "temperature": data[5], - "frequency_penalty": data[6], - "presence_penalty": data[7], - "uses_count_today": data[8], - "prompt_size": data[9], - "prompt_prefix": data[10], - "tts": data[11], - "pretend_to_be": data[12], - "pretend_enabled": data[13], - } - return data_dict - + data = curs_data.fetchone() + # Create a dict with the data + data_dict = { + "channel_id": data[1], + "api_key": data[2], + "is_active": data[3], + "max_tokens": data[4], + "temperature": data[5], + "frequency_penalty": data[6], + "presence_penalty": data[7], + "uses_count_today": data[8], + "prompt_size": data[9], + "prompt_prefix": data[10], + "tts": bool(data[11]), + "pretend_to_be": data[12], + "pretend_enabled": data[13], + } + return data_dict + except Exception as e: + # Send an error message + await message.channel.send( + "The bot is not configured yet. Please use `//setup` to configure it. \n" + + "If it still doesn't work, it might be a database error. \n ```" + e.__str__() + + "```", delete_after=60 + ) + async def chat_process(self, message): """This function processes the message and sends the prompt to the API @@ -169,40 +174,11 @@ async def chat_process(self, message): Args: message (str): Data of the message that was sent """ - - if(await need_ignore_message(self, message, guild_data, original_message, channels)): + if message.author.bot: return - data_dict = get_data_dict(message) - - ## ---- Message processing ---- ## - - if data is None: - data = [message.guild.id, 0, 0] - - data_dict["images_usage"] = data[1] - data_dict["images_enabled"] = data[2] - - images_limit_reached = False guild_data = get_guild_data(message) - - channels = [] - if message.guild.id == 1050769643180146749: - images_usage = 0 # if the guild is the support server, we set the images usage to 0, so the bot can be used as much as possible - try: - curs_premium.execute( - "SELECT * FROM channels WHERE guild_id = ?", (message.guild.id,)) - data = curs_premium.fetchone() - if guild_data["premium"]: - # for 5 times, we get c.fetchone()[1] to c.fetchone()[5] and we add it to the channels list, each time with try except - for i in range(1, 6): - # we use the i variable to get the channel id - try: - channels.append(str(data[i])) - except: - pass - except: - channels = [] + data_dict = await get_data_dict(message) try: original_message = await message.channel.fetch_message( @@ -215,6 +191,45 @@ async def chat_process(self, message): # if the message someone replied to is not from the bot, set original_message to None original_message = None + try: + # get the images setting in the database + curs_data.execute( + "SELECT * FROM images WHERE guild_id = ?", (message.guild.id,)) + images_data = curs_data.fetchone() + except: + images_data = None + + ## ---- Message processing ---- ## + + print(message) + + if not images_data: + images_data = [message.guild.id, 0, 0] + + data_dict["images_usage"] = 0 if message.guild.id == 1050769643180146749 else images_data[1] + data_dict["images_enabled"] = images_data[2] + + + channels = [] + try: + curs_premium.execute( + "SELECT * FROM channels WHERE guild_id = ?", (message.guild.id,)) + images_data = curs_premium.fetchone() + if guild_data["premium"]: + # for 5 times, we get c.fetchone()[1] to c.fetchone()[5] and we add it to the channels list, each time with try except + for i in range(1, 6): + # we use the i variable to get the channel id + try: + channels.append(str(images_data[i])) + except: + pass + except: + debug("No premium channels found for this guild") + + if (await need_ignore_message(self.bot, data_dict, message, guild_data, original_message, channels)): + return + print("prompt handler") + try: await message.channel.trigger_typing() # if the message is not in the owner's guild we update the usage count @@ -240,11 +255,13 @@ async def chat_process(self, message): messages.append(message) except Exception as e: debug("Error while getting message history", e) + print(e) # if the pretend to be feature is enabled, we add the pretend to be text to the prompt - pretend_to_be = f"In this conversation, the assistant pretends to be {pretend_to_be}" if data_dict["pretend_enabled"] else "" + pretend_to_be = f"In this conversation, the assistant pretends to be {pretend_to_be}" if data_dict[ + "pretend_enabled"] else "" prompt_prefix = "" if data_dict["prompt_prefix"] == None else data_dict["prompt_prefix"] - + # open the prompt file for the selected model with utf-8 encoding for emojis with open(f"./prompts/{guild_data['model']}.txt", "r", encoding="utf-8") as f: prompt = f.read() @@ -261,16 +278,48 @@ async def chat_process(self, message): f.close() prompt_handlers = { - "chatGPT": self.gpt_prompt, - "gpt-4": self.gpt_prompt, - "davinci": self.davinci_prompt, + "gpt-3.5-turbo": gpt_prompt, + "gpt-4": gpt_prompt, + "davinci": davinci_prompt, } - prompt_handlers[guild_data["model"]]( - messages, message, data_dict, prompt, guild_data + response = await prompt_handlers[guild_data["model"]]( + self.bot, messages, message, data_dict, prompt, guild_data ) + + if response != "": + emojis, string = await extract_emoji(response) + debug(f"Emojis: {emojis}") + if len(string) < 1996: + await message.channel.send(string, tts=data_dict["tts"]) + else: + # we send in an embed if the message is too long + embed = discord.Embed( + title="Botator response", + description=string, + color=discord.Color.brand_green(), + ) + await message.channel.send(embed=embed, tts=data_dict["tts"]) + for emoji in emojis: + # if the emoji is longer than 1 character, it's a custom emoji + try: + if len(emoji) > 1: + # if the emoji is a custom emoji, we need to fetch it + # the emoji is in the format id + debug(f"Emoji: {emoji}") + emoji = await message.guild.fetch_emoji(int(emoji)) + await message.add_reaction(emoji) + else: + debug(f"Emoji: {emoji}") + await message.add_reaction(emoji) + except: + pass + else: + await message.channel.send( + "The AI is not sure what to say (the response was empty)" + ) -async def check_moderate(self, api_key, message, msg): +async def check_moderate(api_key, message, msg): if await moderate(api_key=api_key, text=msg.content): embed = discord.Embed( title="Message flagged as inappropriate", @@ -285,7 +334,7 @@ async def check_moderate(self, api_key, message, msg): return False -async def check_easter_egg(self, message, msgs): +async def check_easter_egg(message, msgs): if message.content.lower().find("undude") != -1: msgs.append( { @@ -310,7 +359,7 @@ async def check_easter_egg(self, message, msgs): return msgs -async def gpt_prompt(self, messages, message, data_dict, prompt, guild_data): +async def gpt_prompt(bot, messages, message, data_dict, prompt, guild_data): msgs = [] # create the msgs list msgs.append( {"name": "System", "role": "user", "content": prompt} @@ -319,14 +368,14 @@ async def gpt_prompt(self, messages, message, data_dict, prompt, guild_data): for msg in messages: # for each message in the messages list content = msg.content # get the content of the message content = await replace_mentions( - content, self.bot + content, bot ) # replace the mentions in the message # if the message is flagged as inappropriate by the OpenAI API, we delete it, send a message and ignore it - if await self.check_moderate(data_dict["api_key"], message, msg): + if await check_moderate(data_dict["api_key"], message, msg): continue # ignore the message - content = await replace_mentions(content, self.bot) + content = await replace_mentions(content, bot) prompt += f"{msg.author.name}: {content}\n" - if msg.author.id == self.bot.user.id: + if msg.author.id == bot.user.id: role = "assistant" name = "assistant" else: @@ -350,13 +399,13 @@ async def gpt_prompt(self, messages, message, data_dict, prompt, guild_data): for attachment in msg.attachments: path = f"./../database/google-vision/results/{attachment.id}.txt" if images_usage >= 6 and guild_data["premium"] == 0: - images_limit_reached = True + guild_data["images_limit_reached"] = True elif images_usage >= 30 and guild_data["premium"] == 1: - images_limit_reached = True + guild_data["images_limit_reached"] = True if ( attachment.url.endswith((".png", ".jpg", ".jpeg", ".gif")) - and images_limit_reached == False - and os.path.exists(path) == False + and not guild_data["images_limit_reached"] + and not os.path.exists(path) ): images_usage += 1 analysis = await vision_processing.process(attachment) @@ -403,17 +452,15 @@ async def gpt_prompt(self, messages, message, data_dict, prompt, guild_data): msgs.append({"role": role, "content": f"{content}", "name": name}) # We check for the eastereggs :) - msgs = await self.check_easter_egg(message, msgs) + msgs = await check_easter_egg(message, msgs) - if model == "chatGPT": - model = "gpt-3.5-turbo" # if the model is chatGPT, we set the model to gpt-3.5-turbo response = "" should_break = True for x in range(10): try: openai.api_key = data_dict["api_key"] response = await openai.ChatCompletion.acreate( - model=model, + model=guild_data["model"], temperature=2, top_p=0.9, frequency_penalty=0, @@ -446,11 +493,13 @@ async def gpt_prompt(self, messages, message, data_dict, prompt, guild_data): await asyncio.sleep(15) await message.channel.trigger_typing() response = response.choices[0].message.content - if images_limit_reached == True: + + if guild_data["images_limit_reached"]: await message.channel.send( f"```diff\n-Warning: You have reached the image limit for this server. You can upgrade to premium to get more images recognized. More info in our server: https://discord.gg/sxjHtmqrbf```", delete_after=10, ) + return response async def davinci_prompt(self, messages, message, data_dict, prompt, guild_data): @@ -488,38 +537,4 @@ async def davinci_prompt(self, messages, message, data_dict, prompt, guild_data) return if response != None: break - if response != "": - if tts: - tts = True - else: - tts = False - emojis, string = await extract_emoji(response) - debug(f"Emojis: {emojis}") - if len(string) < 1996: - await message.channel.send(string, tts=tts) - else: - # we send in an embed if the message is too long - embed = discord.Embed( - title="Botator response", - description=string, - color=discord.Color.brand_green(), - ) - await message.channel.send(embed=embed, tts=tts) - for emoji in emojis: - # if the emoji is longer than 1 character, it's a custom emoji - try: - if len(emoji) > 1: - # if the emoji is a custom emoji, we need to fetch it - # the emoji is in the format id - debug(f"Emoji: {emoji}") - emoji = await message.guild.fetch_emoji(int(emoji)) - await message.add_reaction(emoji) - else: - debug(f"Emoji: {emoji}") - await message.add_reaction(emoji) - except: - pass - else: - await message.channel.send( - "The AI is not sure what to say (the response was empty)" - ) + return response \ No newline at end of file From 74f0aba20ab19b2313602d666127bdcc2c851f91 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sat, 1 Apr 2023 13:18:06 +0200 Subject: [PATCH 14/20] [SETTINGS] More precise model --- code/cogs/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/cogs/settings.py b/code/cogs/settings.py index 35aefb3..18e3b4c 100644 --- a/code/cogs/settings.py +++ b/code/cogs/settings.py @@ -3,7 +3,7 @@ from config import debug, con_data, curs_data, moderate from discord import default_permissions import openai -models = ["davinci", "chatGPT", "gpt-4"] +models = ["davinci", "gpt-3.5-turbo", "gpt-4"] images_recognition = ["enable", "disable"] From 5235cd2c0a1d2d172c4a232807a742cf2d591668 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sat, 1 Apr 2023 13:18:14 +0200 Subject: [PATCH 15/20] [PROMPT] Renamed prompt --- code/prompts/{chatGPT.txt => gpt-3.5-turbo.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename code/prompts/{chatGPT.txt => gpt-3.5-turbo.txt} (100%) diff --git a/code/prompts/chatGPT.txt b/code/prompts/gpt-3.5-turbo.txt similarity index 100% rename from code/prompts/chatGPT.txt rename to code/prompts/gpt-3.5-turbo.txt From 98ba99dd68c4d93bd93cb550dc248bf66153834a Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sat, 1 Apr 2023 14:04:13 +0200 Subject: [PATCH 16/20] [COMPOSE] Corrected volume (Didn't verify copilot lmao) --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index ece8006..f65a2a9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: container_name: botator restart: always volumes: - - ./config:/config + - ./database:/Botator/database environment: - PERSPECTIVE_API_KEY=your_api_key - WEBHOOK_URL=https://yourdomain.com/botator From a99374d5909a59ecc52eb38e4d9a9eb6f378a9aa Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sat, 1 Apr 2023 14:06:29 +0200 Subject: [PATCH 17/20] [REQUIREMENTS] Cleaned requirements --- requirements.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1447956..a226c81 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,5 @@ openai emoji # Google api google-api-python-client -google-auth-httplib2 -google-auth-oauthlib google-cloud-vision From 24e19aabd5d7cb20feec9fe4997d302ee3794049 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sat, 1 Apr 2023 14:23:42 +0200 Subject: [PATCH 18/20] [MAKEPROMPT] Fixed legacy DB support --- code/makeprompt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/makeprompt.py b/code/makeprompt.py index 4364a44..a24215a 100644 --- a/code/makeprompt.py +++ b/code/makeprompt.py @@ -85,7 +85,7 @@ def get_guild_data(message): except: images = None - guild_data["model"] = model + guild_data["model"] = "gpt-3.5-turbo" if model == "chatGPT" else model guild_data["premium"] = premium guild_data["images"] = images From aea9502cdc14981381400978fa27144b86bc7a83 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sat, 1 Apr 2023 14:31:13 +0200 Subject: [PATCH 19/20] [DOCKERFILE] Files already mounted --- Dockerfile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index a07aaa0..b23a030 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,10 +21,6 @@ RUN chown -R appuser /Botator/database RUN mkdir /Botator/database/google -# Copy google APIs credentials -RUN mkdir /Botator/database/google-vision -COPY ./database/google/botator.json /Botator/database/google/ - USER appuser CMD ["python", "code.py"] From 2e12028a169df9d96818c43fa9122d891453d835 Mon Sep 17 00:00:00 2001 From: Paillat Date: Sat, 1 Apr 2023 15:14:47 +0200 Subject: [PATCH 20/20] Removed test code --- code/toxicity.py | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/code/toxicity.py b/code/toxicity.py index 87e6232..ec304ef 100644 --- a/code/toxicity.py +++ b/code/toxicity.py @@ -124,33 +124,4 @@ def get_toxicity(message: str): float(response["attributeScores"]["INSULT"]["summaryScore"]["value"]), float(response["attributeScores"]["PROFANITY"]["summaryScore"]["value"]), float(response["attributeScores"]["THREAT"]["summaryScore"]["value"]), - ] - - -# test part -def test(): - print("Testing toxicity.py...") - print("Hello world:") - result = get_toxicity("Hello world") - try: - print( - f"TOXICITY: {result[0]}; SEVERE_TOXICITY: {result[1]}; IDENTITY ATTACK: {result[2]}; INSULT: {result[3]}; PROFANITY: {result[4]}; THREAT: {result[5]}; SEXUALLY EXPLICIT: {result[6]}; FLIRTATION: {result[7]}; OBSCENE: {result[8]}; SPAM: {result[9]}" - ) - except: - print( - f"TOXICITY: {result[0]}; SEVERE_TOXICITY: {result[1]}; IDENTITY ATTACK: {result[2]}; INSULT: {result[3]}; PROFANITY: {result[4]}; THREAT: {result[5]}" - ) - print("HELLO WORLD GET ABSOLUTELY BUY MY NEW MERCH OMGGGGGGG:") - result = get_toxicity("HELLO WORLD GET ABSOLUTELY BUY MY NEW MERCH OMGGGGGGG") - try: - print( - f"TOXICITY: {result[0]}; SEVERE_TOXICITY: {result[1]}; IDENTITY ATTACK: {result[2]}; INSULT: {result[3]}; PROFANITY: {result[4]}; THREAT: {result[5]}; SEXUALLY EXPLICIT: {result[6]}; FLIRTATION: {result[7]}; OBSCENE: {result[8]}; SPAM: {result[9]}" - ) - except: - print( - f"TOXICITY: {result[0]}; SEVERE_TOXICITY: {result[1]}; IDENTITY ATTACK: {result[2]}; INSULT: {result[3]}; PROFANITY: {result[4]}; THREAT: {result[5]}" - ) - - -# uncomment the following line to test the code -# test() + ] \ No newline at end of file