From 72130cab900cbe28a828ba5e4aa023db9a4dd9e3 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 14:07:28 +0200 Subject: [PATCH 01/46] [FORK] Added VSCode files to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 274d592..2d8941d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ __pycache__/ # C extensions *.so +# VSCode +.vscode + # Distribution / packaging .Python build/ From b70bb41260d2e8bea5371f5e70ee201a4a750d08 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 14:07:44 +0200 Subject: [PATCH 02/46] [DOCKER] Moved docker file to Root --- docker/Build/Dockerfile => Dockerfile | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docker/Build/Dockerfile => Dockerfile (100%) diff --git a/docker/Build/Dockerfile b/Dockerfile similarity index 100% rename from docker/Build/Dockerfile rename to Dockerfile From 464bafa0562674058345544bf5f82496b9c54402 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 14:08:00 +0200 Subject: [PATCH 03/46] [DOCKER] Cleaned build --- docker/Build/requirements.txt | 8 -------- docker/prBuild/Dockerfile | 20 -------------------- docker/prBuild/requirements.txt | 3 --- docker/resetter/Dockerfile | 17 ----------------- 4 files changed, 48 deletions(-) delete mode 100644 docker/Build/requirements.txt delete mode 100644 docker/prBuild/Dockerfile delete mode 100644 docker/prBuild/requirements.txt delete mode 100644 docker/resetter/Dockerfile diff --git a/docker/Build/requirements.txt b/docker/Build/requirements.txt deleted file mode 100644 index f022e71..0000000 --- a/docker/Build/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -# To ensure app dependencies are ported from your virtual environment/host machine into your container, run 'pip freeze > requirements.txt' in the terminal to overwrite this file -py-cord -openai -apsw -google-api-python-client -python-dotenv -emoji -google-cloud-vision \ No newline at end of file diff --git a/docker/prBuild/Dockerfile b/docker/prBuild/Dockerfile deleted file mode 100644 index 015ed8f..0000000 --- a/docker/prBuild/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ - -# For more information, please refer to https://aka.ms/vscode-docker-python -FROM python:3.10.0 - - -# Keeps Python from generating .pyc files in the container - -# Turns off buffering for easier container logging -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/ -COPY premium-key.txt /Botator/code/ -# Creates a non-root user with an explicit UID and adds permission to access the /app folder -RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /Botator/code -USER appuser -CMD ["python", "premiumcode.py"] diff --git a/docker/prBuild/requirements.txt b/docker/prBuild/requirements.txt deleted file mode 100644 index 80f6c11..0000000 --- a/docker/prBuild/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -# To ensure app dependencies are ported from your virtual environment/host machine into your container, run 'pip freeze > requirements.txt' in the terminal to overwrite this file -py-cord -openai diff --git a/docker/resetter/Dockerfile b/docker/resetter/Dockerfile deleted file mode 100644 index 83cc38d..0000000 --- a/docker/resetter/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ - -# For more information, please refer to https://aka.ms/vscode-docker-python -FROM python:3.10.0 - - -# Keeps Python from generating .pyc files in the container - -# Turns off buffering for easier container logging -ENV PYTHONUNBUFFERED=1 - -# Install pip requirements -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 adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /Botator/code -USER appuser -CMD ["python", "resetter.py"] From 88528be35c60f775bad2fac3c9f846e25863fd2a Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 14:08:09 +0200 Subject: [PATCH 04/46] [GENERAL] Added requirements.txt --- requirements.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4571450 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +discord.py +python-dotenv +openai +emoji +# Google api +google-api-python-client +google-auth-httplib2 +google-auth-oauthlib + From 42cb2e9135f4065450391ffca6d03692547266ae Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 14:09:06 +0200 Subject: [PATCH 05/46] [GENERAL] Code reformatting using Black standard --- code/code.py | 33 ++-- code/cogs/chat.py | 68 +++++-- code/cogs/help.py | 115 ++++++++--- code/cogs/manage_chat.py | 110 +++++++---- code/cogs/moderation.py | 187 ++++++++++++++---- code/cogs/settings.py | 325 +++++++++++++++++++++++-------- code/cogs/setup.py | 223 ++++++++++++++------- code/config.py | 45 +++-- code/makeprompt.py | 384 ++++++++++++++++++++++++++----------- code/premiumcode.py | 87 ++++++--- code/resetter.py | 10 +- code/test-google-vision.py | 39 ++-- code/toxicity.py | 173 ++++++++++++----- code/vision_processing.py | 29 ++- 14 files changed, 1329 insertions(+), 499 deletions(-) diff --git a/code/code.py b/code/code.py index f381a6e..19760b4 100644 --- a/code/code.py +++ b/code/code.py @@ -1,13 +1,12 @@ -#coucou c'est fives -# wesh wesh ici latouff -import discord # pip install pycord +import discord # discord.py from discord import Intents -import cogs # import the cogs +import cogs # import the cogs from config import debug, discord_token -#add the message content intent to the bot, aka discord.Intents.default() and discord.Intents.message_content + +# add the message content intent to the bot, aka discord.Intents.default() and discord.Intents.message_content intents = discord.Intents.default() intents.message_content = True -bot = discord.Bot(intents=intents, help_command=None) # create the bot +bot = discord.Bot(intents=intents, help_command=None) # create the bot bot.add_cog(cogs.Setup(bot)) bot.add_cog(cogs.Settings(bot)) bot.add_cog(cogs.Help(bot)) @@ -15,20 +14,32 @@ bot.add_cog(cogs.Chat(bot)) bot.add_cog(cogs.ManageChat(bot)) bot.add_cog(cogs.Moderation(bot)) + @bot.event async def on_ready(): - await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name="your messages to answer you")) + await bot.change_presence( + activity=discord.Activity( + type=discord.ActivityType.watching, name="your messages to answer you" + ) + ) debug("Bot is ready") -bot.run(discord_token) # run the bot -#set the bot's watching status to watcing your messages to answer you +bot.run(discord_token) # run the bot + + +# set the bot's watching status to watcing your messages to answer you @bot.event async def on_ready(): - await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name="your messages to answer you")) + await bot.change_presence( + activity=discord.Activity( + type=discord.ActivityType.watching, name="your messages to answer you" + ) + ) debug("Bot is ready") + @bot.event async def on_application_command_error(ctx, error): debug(error) - await ctx.respond(error, ephemeral=True) \ No newline at end of file + await ctx.respond(error, ephemeral=True) diff --git a/code/cogs/chat.py b/code/cogs/chat.py index b1a02e5..9f1b006 100644 --- a/code/cogs/chat.py +++ b/code/cogs/chat.py @@ -4,32 +4,47 @@ from config import debug, c, max_uses, cp, conn, connp, webhook_url import makeprompt as mp import aiohttp + 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)) + self.add_item( + discord.ui.InputText(label="Reason", style=discord.InputTextStyle.long) + ) self.message = message async def callback(self, interaction: discord.Interaction): debug("Downvote sent !") - embed = discord.Embed(title="Thanks for your feedback !", description="Your downvote has been sent to the developers. Thanks for your help !", color=discord.Color.og_blurple()) + embed = discord.Embed( + title="Thanks for your feedback !", + description="Your downvote has been sent to the developers. Thanks for your help !", + color=discord.Color.og_blurple(), + ) embed.add_field(name="Message", value=self.children[0].value) await interaction.response.send_message(embed=embed, ephemeral=True) - if webhook_url != "" and webhook_url != None: + if webhook_url != "" and webhook_url != None: session = aiohttp.ClientSession() webhook = discord.Webhook.from_url(webhook_url, session=session) - embed = discord.Embed(title="Downvote", description=f"Downvote recieved!", color=discord.Color.og_blurple()) + embed = discord.Embed( + title="Downvote", + 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="Channel", value=self.message.channel.name, 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) - history = await self.message.channel.history(limit=5, before=self.message).flatten() + 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 + # 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: @@ -39,24 +54,39 @@ class MyModal(discord.ui.Modal): uname = fake_users[users.index(msg.author)] if len(msg.content) > 1023: - embed.add_field(name=f"{uname} said", value=msg.content[:1023], inline=False) + embed.add_field( + name=f"{uname} said", value=msg.content[:1023], inline=False + ) else: - embed.add_field(name=f"{uname} said", value=msg.content, inline=False) + 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) + 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) + embed.add_field( + name=f"{uname} said", + value="*" + self.message.content + "*", + inline=False, + ) await webhook.send(embed=embed) else: - debug("Error while sending webhook, probably no webhook is set up in the .env file") + debug( + "Error while sending webhook, probably no webhook is set up in the .env file" + ) -class Chat (discord.Cog) : +class Chat(discord.Cog): def __init__(self, bot: discord.Bot): super().__init__() self.bot = bot + @discord.Cog.listener() async def on_message(self, message: discord.Message): await mp.chat_process(self, message) @@ -74,20 +104,20 @@ class Chat (discord.Cog) : if message_to_delete.author.id == self.bot.user.id: await message_to_delete.delete() else: - message_to_redo=history[0] + message_to_redo = history[0] await ctx.respond("Message redone !", ephemeral=True) await mp.chat_process(self, message_to_redo) - - @discord.message_command(name="Downvote", description="Downvote a message") @commands.cooldown(1, 60, commands.BucketType.user) async def downvote(self, ctx: discord.ApplicationContext, message: discord.Message): - if message.author.id == self.bot.user.id: + if message.author.id == self.bot.user.id: modal = MyModal(message) await ctx.send_modal(modal) else: - await ctx.respond("You can't downvote a message that is not from me !", ephemeral=True) + await ctx.respond( + "You can't downvote a message that is not from me !", ephemeral=True + ) @downvote.error async def downvote_error(self, ctx, error): @@ -95,4 +125,4 @@ class Chat (discord.Cog) : await ctx.respond("You are on cooldown !", ephemeral=True) else: debug(error) - raise error \ No newline at end of file + raise error diff --git a/code/cogs/help.py b/code/cogs/help.py index f804cce..b604971 100644 --- a/code/cogs/help.py +++ b/code/cogs/help.py @@ -1,44 +1,109 @@ import discord from config import debug -class Help (discord.Cog) : + +class Help(discord.Cog): def __init__(self, bot: discord.Bot) -> None: super().__init__() self.bot = bot @discord.slash_command(name="help", description="Show all the commands") async def help(self, ctx: discord.ApplicationContext): - debug(f"The user {ctx.author} ran the help command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}") - embed = discord.Embed(title="Help", description="Here is the help page", color=0x00ff00) + debug( + f"The user {ctx.author} ran the help command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" + ) + embed = discord.Embed( + title="Help", description="Here is the help page", color=0x00FF00 + ) embed.add_field(name="/setup", value="Setup the bot", inline=False) embed.add_field(name="/enable", value="Enable the bot", inline=False) embed.add_field(name="/disable", value="Disable the bot", inline=False) - embed.add_field(name="/advanced", value="Set the advanced settings", inline=False) - embed.add_field(name="/advanced_help", value="Get help about the advanced settings", inline=False) - embed.add_field(name="/enable_tts", value="Enable the Text To Speech", inline=False) - embed.add_field(name="/disable_tts", value="Disable the Text To Speech", inline=False) - embed.add_field(name="/add|remove_channel", value="Add or remove a channel to the list of channels where the bot will answer. Only available on premium guilds", inline=False) - embed.add_field(name="/delete", value="Delete all your data from our server", inline=False) - embed.add_field(name="/cancel", value="Cancel the last message sent by the bot", inline=False) - embed.add_field(name="/default", value="Set the advanced settings to their default values", inline=False) + embed.add_field( + name="/advanced", value="Set the advanced settings", inline=False + ) + embed.add_field( + name="/advanced_help", + value="Get help about the advanced settings", + inline=False, + ) + embed.add_field( + name="/enable_tts", value="Enable the Text To Speech", inline=False + ) + embed.add_field( + name="/disable_tts", value="Disable the Text To Speech", inline=False + ) + embed.add_field( + name="/add|remove_channel", + value="Add or remove a channel to the list of channels where the bot will answer. Only available on premium guilds", + inline=False, + ) + embed.add_field( + name="/delete", value="Delete all your data from our server", inline=False + ) + embed.add_field( + name="/cancel", + value="Cancel the last message sent by the bot", + inline=False, + ) + embed.add_field( + name="/default", + value="Set the advanced settings to their default values", + inline=False, + ) embed.add_field(name="/say", value="Say a message", inline=False) - embed.add_field(name="/redo", value="Redo the last message sent by the bot", inline=False) - embed.add_field(name="/moderation", value="Setup the AI auto-moderation", inline=False) - embed.add_field(name="/get_toxicity", value="Get the toxicity that the AI would have given to a given message", inline=False) + embed.add_field( + name="/redo", value="Redo the last message sent by the bot", inline=False + ) + embed.add_field( + name="/moderation", value="Setup the AI auto-moderation", inline=False + ) + embed.add_field( + name="/get_toxicity", + value="Get the toxicity that the AI would have given to a given message", + inline=False, + ) embed.add_field(name="/help", value="Show this message", inline=False) - #add a footer + # add a footer embed.set_footer(text="Made by @Paillat#7777") await ctx.respond(embed=embed, ephemeral=True) - @discord.slash_command(name="advanced_help", description="Show the advanced settings meanings") + @discord.slash_command( + name="advanced_help", description="Show the advanced settings meanings" + ) async def advanced_help(self, ctx: discord.ApplicationContext): - debug(f"The user {ctx.author} ran the advanced_help command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}") - embed = discord.Embed(title="Advanced Help", description="Here is the advanced help page", color=0x00ff00) - embed.add_field(name="temperature", value="The higher the temperature, the more likely the model will take risks. Conversely, a lower temperature will make the model more conservative. The default value is 0.9", inline=False) - embed.add_field(name="max_tokens", value="The maximum number of tokens to generate. Higher values will result in more coherent text, but will take longer to complete. (default: 50). **Lower values will result in somentimes cutting off the end of the answer, but will be faster.**", inline=False) - embed.add_field(name="frequency_penalty", value="The higher the frequency penalty, the more new words the model will introduce (default: 0.0)", inline=False) - embed.add_field(name="presence_penalty", value="The higher the presence penalty, the more new words the model will introduce (default: 0.0)", inline=False) - embed.add_field(name="prompt_size", value="The number of messages to use as a prompt (default: 5). The more messages, the more coherent the text will be, but the more it will take to generate and the more it will cost.", inline=False) - #add a footer + debug( + f"The user {ctx.author} ran the advanced_help command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" + ) + embed = discord.Embed( + title="Advanced Help", + description="Here is the advanced help page", + color=0x00FF00, + ) + embed.add_field( + name="temperature", + value="The higher the temperature, the more likely the model will take risks. Conversely, a lower temperature will make the model more conservative. The default value is 0.9", + inline=False, + ) + embed.add_field( + name="max_tokens", + value="The maximum number of tokens to generate. Higher values will result in more coherent text, but will take longer to complete. (default: 50). **Lower values will result in somentimes cutting off the end of the answer, but will be faster.**", + inline=False, + ) + embed.add_field( + name="frequency_penalty", + value="The higher the frequency penalty, the more new words the model will introduce (default: 0.0)", + inline=False, + ) + embed.add_field( + name="presence_penalty", + value="The higher the presence penalty, the more new words the model will introduce (default: 0.0)", + inline=False, + ) + embed.add_field( + name="prompt_size", + value="The number of messages to use as a prompt (default: 5). The more messages, the more coherent the text will be, but the more it will take to generate and the more it will cost.", + inline=False, + ) + # add a footer embed.set_footer(text="Made by @Paillat#7777") - await ctx.respond(embed=embed, ephemeral=True) \ No newline at end of file + await ctx.respond(embed=embed, ephemeral=True) diff --git a/code/cogs/manage_chat.py b/code/cogs/manage_chat.py index 6a1cd8e..83ceeb2 100644 --- a/code/cogs/manage_chat.py +++ b/code/cogs/manage_chat.py @@ -3,72 +3,116 @@ import re import os from config import debug, c -class ManageChat (discord.Cog): + +class ManageChat(discord.Cog): def __init__(self, bot: discord.Bot) -> None: super().__init__() self.bot = bot - @discord.slash_command(name="cancel", description="Cancel the last message sent into a channel") + @discord.slash_command( + name="cancel", description="Cancel the last message sent into a channel" + ) async def cancel(self, ctx: discord.ApplicationContext): - debug(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 + debug( + 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: - await ctx.respond("This server is not setup, please run /setup", ephemeral=True) + await ctx.respond( + "This server is not setup, please run /setup", ephemeral=True + ) return - #get the last message sent by the bot in the cha where the command was sent + # get the last message sent by the bot in the cha where the command was sent last_message = await ctx.channel.fetch_message(ctx.channel.last_message_id) - #delete the message + # delete the message await last_message.delete() await ctx.respond("The last message has been deleted", ephemeral=True) - #add a slash command called "clear" that deletes all the messages in the channel - @discord.slash_command(name="clear", description="Clear all the messages in the channel") + # add a slash command called "clear" that deletes all the messages in the channel + @discord.slash_command( + name="clear", description="Clear all the messages in the channel" + ) async def clear(self, ctx: discord.ApplicationContext): - debug(f"The user {ctx.author.name} ran the clear command command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}") + debug( + f"The user {ctx.author.name} ran the clear command command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" + ) await ctx.respond("messages deleted!", ephemeral=True) return await ctx.channel.purge() - @discord.slash_command(name="transcript", description="Get a transcript of the messages that have been sent in this channel intoa text file") - @discord.option(name="channel_send", description="The channel to send the transcript to", required=False) - async def transcript(self, ctx: discord.ApplicationContext, channel_send: discord.TextChannel = None): - debug(f"The user {ctx.author.name} ran the transcript command command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}") - #save all the messages in the channel in a txt file and send it + @discord.slash_command( + name="transcript", + description="Get a transcript of the messages that have been sent in this channel intoa text file", + ) + @discord.option( + name="channel_send", + description="The channel to send the transcript to", + required=False, + ) + async def transcript( + self, ctx: discord.ApplicationContext, channel_send: discord.TextChannel = None + ): + debug( + f"The user {ctx.author.name} ran the transcript command command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" + ) + # save all the messages in the channel in a txt file and send it messages = await ctx.channel.history(limit=None).flatten() messages.reverse() transcript = "" - #defer the response - await ctx.defer() #defer the response so that the bot doesn't say that it's thinking + # defer the response + await ctx.defer() # defer the response so that the bot doesn't say that it's thinking for msg in messages: if msg.author.bot: transcript += f"Botator: {msg.content}\n" else: mentions = re.findall(r"<@!?\d+>", msg.content) - #then replace each mention with the name of the user + # then replace each mention with the name of the user for mention in mentions: - #get the user id + # get the user id id = mention[2:-1] - #get the user + # get the user user = await self.bot.fetch_user(id) - #replace the mention with the name - msg.content = msg.content.replace(mention, msg.guild.get_member(user.id).name) + # replace the mention with the name + msg.content = msg.content.replace( + mention, msg.guild.get_member(user.id).name + ) transcript += f"{msg.author.name}: {msg.content}\n" - #save the transcript in a txt file called transcript.txt. If the file already exists, delete it and create a new one - #check if the file exists + # save the transcript in a txt file called transcript.txt. If the file already exists, delete it and create a new one + # check if the file exists if os.path.exists("transcript.txt"): os.remove("transcript.txt") f = open("transcript.txt", "w") f.write(transcript) f.close() - last_message: discord.Message = await ctx.channel.fetch_message(ctx.channel.last_message_id) - #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") - #send the file in a private message to the user who ran the command + last_message: discord.Message = await ctx.channel.fetch_message( + ctx.channel.last_message_id + ) + # 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", + ) + # send the file in a private message to the user who ran the command 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"), ephemeral=True) + await ctx.respond( + file=discord.File( + f"transcript_{ctx.guild.name}_{ctx.channel.name}_{last_message.created_at.strftime('%d-%B-%Y')}.txt" + ), + 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")) + await channel_send.send( + file=discord.File( + f"transcript_{ctx.guild.name}_{ctx.channel.name}_{last_message.created_at.strftime('%d-%B-%Y')}.txt" + ) + ) 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")) - #delete the file - os.remove(f"transcript_{ctx.guild.name}_{ctx.channel.name}_{last_message.created_at.strftime('%d-%B-%Y')}.txt") \ No newline at end of file + await ctx.author.send( + file=discord.File( + f"transcript_{ctx.guild.name}_{ctx.channel.name}_{last_message.created_at.strftime('%d-%B-%Y')}.txt" + ) + ) + # delete the file + os.remove( + f"transcript_{ctx.guild.name}_{ctx.channel.name}_{last_message.created_at.strftime('%d-%B-%Y')}.txt" + ) diff --git a/code/cogs/moderation.py b/code/cogs/moderation.py index 40b74f6..b489701 100644 --- a/code/cogs/moderation.py +++ b/code/cogs/moderation.py @@ -4,30 +4,82 @@ import os from config import debug, c, conn 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): +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): def __init__(self, bot: discord.Bot) -> None: super().__init__() self.bot = bot - @discord.slash_command(name="moderation", description="Enable or disable AI moderation & set the rules") - @discord.option(name="enable", description="Enable or disable AI moderation", reqired=True,) - @discord.option(name="log_channel", description="The channel where the moderation logs will be sent", required=True) - @discord.option(name="moderator_role", description="The role of the moderators", required=True) - #the types of toxicity are 'requestedAttributes': {'TOXICITY': {}, 'SEVERE_TOXICITY': {}, 'IDENTITY_ATTACK': {}, 'INSULT': {}, 'PROFANITY': {}, 'THREAT': {}, 'SEXUALLY_EXPLICIT': {}, 'FLIRTATION': {}, 'OBSCENE': {}, 'SPAM': {}}, - @discord.option(name="toxicity", description="The toxicity threshold", required=False) - @discord.option(name="severe_toxicity", description="The severe toxicity threshold", required=False) - @discord.option(name="identity_attack", description="The identity attack threshold", required=False) + + @discord.slash_command( + name="moderation", description="Enable or disable AI moderation & set the rules" + ) + @discord.option( + name="enable", + description="Enable or disable AI moderation", + reqired=True, + ) + @discord.option( + name="log_channel", + description="The channel where the moderation logs will be sent", + required=True, + ) + @discord.option( + name="moderator_role", description="The role of the moderators", required=True + ) + # the types of toxicity are 'requestedAttributes': {'TOXICITY': {}, 'SEVERE_TOXICITY': {}, 'IDENTITY_ATTACK': {}, 'INSULT': {}, 'PROFANITY': {}, 'THREAT': {}, 'SEXUALLY_EXPLICIT': {}, 'FLIRTATION': {}, 'OBSCENE': {}, 'SPAM': {}}, + @discord.option( + name="toxicity", description="The toxicity threshold", required=False + ) + @discord.option( + name="severe_toxicity", + description="The severe toxicity threshold", + required=False, + ) + @discord.option( + name="identity_attack", + description="The identity attack threshold", + required=False, + ) @discord.option(name="insult", description="The insult threshold", required=False) - @discord.option(name="profanity", description="The profanity threshold", required=False) + @discord.option( + name="profanity", description="The profanity threshold", required=False + ) @discord.option(name="threat", description="The threat threshold", required=False) - @discord.option(name="sexually_explicit", description="The sexually explicit threshold", required=False) - @discord.option(name="flirtation", description="The flirtation threshold", required=False) + @discord.option( + name="sexually_explicit", + description="The sexually explicit threshold", + required=False, + ) + @discord.option( + name="flirtation", description="The flirtation threshold", required=False + ) @discord.option(name="obscene", description="The obscene threshold", required=False) @discord.option(name="spam", description="The spam threshold", required=False) - #we set the default permissions to the administrator permission, so only the server administrators can use this command + # we set the default permissions to the administrator permission, so only the server administrators can use this command @default_permissions(administrator=True) - async def moderation(self, ctx: discord.ApplicationContext, enable: bool, log_channel: discord.TextChannel, moderator_role: discord.Role, toxicity: float = None, severe_toxicity: float = None, identity_attack: float = None, insult: float = None, profanity: float = None, threat: float = None, sexually_explicit: float = None, flirtation: float = None, obscene: float = None, spam: float = None): - 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) + async def moderation( + self, + ctx: discord.ApplicationContext, + enable: bool, + log_channel: discord.TextChannel, + moderator_role: discord.Role, + toxicity: float = None, + severe_toxicity: float = None, + identity_attack: float = None, + insult: float = None, + profanity: float = None, + threat: float = None, + sexually_explicit: float = None, + flirtation: float = None, + obscene: float = None, + spam: float = None, + ): + 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, + ) if enable == False: c.execute("DELETE FROM moderation WHERE guild_id = ?", (str(ctx.guild.id),)) conn.commit() @@ -36,52 +88,107 @@ class Moderation (discord.Cog): @discord.Cog.listener() async def on_message(self, message: discord.Message): - if message.author == self.bot.user: return - try: c.execute("SELECT * FROM moderation WHERE guild_id = ?", (str(message.guild.id),)) - except: return + if message.author == self.bot.user: + return + try: + c.execute( + "SELECT * FROM moderation WHERE guild_id = ?", (str(message.guild.id),) + ) + except: + return data = c.fetchone() - if data is None: return + 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 + # 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 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) + 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()) + 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) + 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()) + 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) + 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 + # 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") - @discord.option(name="message", description="The message you want to check", required=True) + @discord.slash_command( + name="get_toxicity", description="Get the toxicity of a message" + ) + @discord.option( + name="message", description="The message you want to check", required=True + ) @default_permissions(administrator=True) async def get_toxicity(self, ctx: discord.ApplicationContext, message: str): - 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://discord.gg/pB6hXtUeDv . If you need help, you can join our support server here: https://discord.gg/pB6hXtUeDv", ephemeral=True) + 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://discord.gg/pB6hXtUeDv . If you need help, you can join our support server here: https://discord.gg/pB6hXtUeDv", + ephemeral=True, + ) - @discord.slash_command(name="moderation_help", description="Get help with the moderation AI") + @discord.slash_command( + name="moderation_help", description="Get help with the moderation AI" + ) @default_permissions(administrator=True) async def moderation_help(self, ctx: discord.ApplicationContext): - 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/ . If you need help, you can join our support server here: https://discord.gg/pB6hXtUeDv", ephemeral=True) \ No newline at end of file + 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/ . If you need help, you can join our support server here: https://discord.gg/pB6hXtUeDv", + ephemeral=True, + ) diff --git a/code/cogs/settings.py b/code/cogs/settings.py index 1947c94..a3ffcde 100644 --- a/code/cogs/settings.py +++ b/code/cogs/settings.py @@ -2,114 +2,215 @@ import discord from config import debug, conn, c, moderate from discord import default_permissions import openai + models = ["davinci", "chatGPT", "gpt-4"] images_recognition = ["enable", "disable"] -class Settings (discord.Cog) : + + +class Settings(discord.Cog): def __init__(self, bot: discord.Bot) -> None: super().__init__() self.bot = bot - #create a command called "advanced" that only admins can use, wich sets the advanced settings up: max_tokens, temperature, frequency_penalty, presence_penalty, prompt_size + # create a command called "advanced" that only admins can use, wich sets the advanced settings up: max_tokens, temperature, frequency_penalty, presence_penalty, prompt_size @discord.slash_command(name="advanced", description="Advanced settings") ##@discord.commands.permissions(administrator=True) - #add the options + # add the options @discord.option(name="max_tokens", description="The max tokens", required=False) @discord.option(name="temperature", description="The temperature", required=False) - @discord.option(name="frequency_penalty", description="The frequency penalty", required=False) - @discord.option(name="presence_penalty", description="The presence penalty", required=False) + @discord.option( + name="frequency_penalty", description="The frequency penalty", required=False + ) + @discord.option( + name="presence_penalty", description="The presence penalty", required=False + ) @discord.option(name="prompt_size", description="The prompt size", required=False) - async def advanced(self, ctx: discord.ApplicationContext, max_tokens: int = None, temperature: float = None, frequency_penalty: float = None, presence_penalty: float = None, prompt_size: int = None): - debug(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 + async def advanced( + self, + ctx: discord.ApplicationContext, + max_tokens: int = None, + temperature: float = None, + frequency_penalty: float = None, + presence_penalty: float = None, + prompt_size: int = None, + ): + debug( + 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: await ctx.respond("This server is not setup", ephemeral=True) return - #check if the user has entered at least one argument - if max_tokens is None and temperature is None and frequency_penalty is None and presence_penalty is None and prompt_size is None: + # check if the user has entered at least one argument + if ( + max_tokens is None + and temperature is None + and frequency_penalty is None + and presence_penalty is None + and prompt_size is None + ): await ctx.respond("You must enter at least one argument", ephemeral=True) return - #check if the user has entered valid arguments + # check if the user has entered valid arguments if max_tokens is not None and (max_tokens < 1 or max_tokens > 4000): await ctx.respond("Invalid max tokens", ephemeral=True) return if temperature is not None and (temperature < 0.0 or temperature > 1.0): await ctx.respond("Invalid temperature", ephemeral=True) return - if frequency_penalty is not None and (frequency_penalty < 0.0 or frequency_penalty > 2.0): + if frequency_penalty is not None and ( + frequency_penalty < 0.0 or frequency_penalty > 2.0 + ): await ctx.respond("Invalid frequency penalty", ephemeral=True) return - if presence_penalty is not None and (presence_penalty < 0.0 or presence_penalty > 2.0): + if presence_penalty is not None and ( + presence_penalty < 0.0 or presence_penalty > 2.0 + ): await ctx.respond("Invalid presence penalty", ephemeral=True) return if prompt_size is not None and (prompt_size < 1 or prompt_size > 10): await ctx.respond("Invalid prompt size", ephemeral=True) return if max_tokens is None: - if c.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("SELECT max_tokens FROM data WHERE guild_id = ?", (ctx.guild.id,)).fetchone()[0] + if ( + c.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( + "SELECT max_tokens FROM data WHERE guild_id = ?", (ctx.guild.id,) + ).fetchone()[0] else: max_tokens = 64 if temperature is None: - if c.execute("SELECT temperature FROM data WHERE guild_id = ?", (ctx.guild.id,)).fetchone()[0] is not None and temperature is None: - temperature = c.execute("SELECT temperature FROM data WHERE guild_id = ?", (ctx.guild.id,)).fetchone()[0] + if ( + c.execute( + "SELECT temperature FROM data WHERE guild_id = ?", (ctx.guild.id,) + ).fetchone()[0] + is not None + and temperature is None + ): + temperature = c.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("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("SELECT frequency_penalty FROM data WHERE guild_id = ?", (ctx.guild.id,)).fetchone()[0] + if ( + c.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( + "SELECT frequency_penalty FROM data WHERE guild_id = ?", + (ctx.guild.id,), + ).fetchone()[0] else: frequency_penalty = 0.0 if presence_penalty is None: - if c.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("SELECT presence_penalty FROM data WHERE guild_id = ?", (ctx.guild.id,)).fetchone()[0] + if ( + c.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( + "SELECT presence_penalty FROM data WHERE guild_id = ?", + (ctx.guild.id,), + ).fetchone()[0] else: presence_penalty = 0.0 if prompt_size is None: - if c.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("SELECT prompt_size FROM data WHERE guild_id = ?", (ctx.guild.id,)).fetchone()[0] + if ( + c.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( + "SELECT prompt_size FROM data WHERE guild_id = ?", (ctx.guild.id,) + ).fetchone()[0] else: prompt_size = 1 - #update the database - c.execute("UPDATE data SET max_tokens = ?, temperature = ?, frequency_penalty = ?, presence_penalty = ?, prompt_size = ? WHERE guild_id = ?", (max_tokens, temperature, frequency_penalty, presence_penalty, prompt_size, ctx.guild.id)) + # update the database + c.execute( + "UPDATE data SET max_tokens = ?, temperature = ?, frequency_penalty = ?, presence_penalty = ?, prompt_size = ? WHERE guild_id = ?", + ( + max_tokens, + temperature, + frequency_penalty, + presence_penalty, + prompt_size, + ctx.guild.id, + ), + ) conn.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 + # 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 @discord.slash_command(name="default", description="Default settings") ##@discord.commands.permissions(administrator=True) async def default(self, ctx: discord.ApplicationContext): - debug(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 + debug( + 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: - await ctx.respond("This server is not setup, please run /setup", ephemeral=True) + 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("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)) + # set the advanced settings (max_tokens, temperature, frequency_penalty, presence_penalty, prompt_size) and also prompt_prefix to their default values + c.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() - await ctx.respond("The advanced settings have been set to their default values", ephemeral=True) - #create a command called "cancel" that deletes the last message sent by the bot in the response channel + await ctx.respond( + "The advanced settings have been set to their default values", + ephemeral=True, + ) - #when a message is sent into a channel check if the guild is in the database and if the bot is enabled - @discord.slash_command(name="info", description="Show the information stored about this server") + # create a command called "cancel" that deletes the last message sent by the bot in the response channel + + # when a message is sent into a channel check if the guild is in the database and if the bot is enabled + @discord.slash_command( + name="info", description="Show the information stored about this server" + ) async def info(self, ctx: discord.ApplicationContext): - debug(f"The user {ctx.author} ran the info command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}") - #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: + debug( + f"The user {ctx.author} ran the info command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" + ) + # 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() - except: data = None + 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] - except: model = None - if model is None: model = "davinci" - embed = discord.Embed(title="Info", description="Here is the info page", color=0x00ff00) + except: + model = None + if model is None: + model = "davinci" + embed = discord.Embed( + title="Info", description="Here is the info page", color=0x00FF00 + ) embed.add_field(name="guild_id", value=data[0], inline=False) embed.add_field(name="API Key", value="secret", inline=False) embed.add_field(name="Main channel ID", value=data[1], inline=False) @@ -125,15 +226,17 @@ class Settings (discord.Cog) : embed.add_field(name="Prompt prefix", value=data[10], inline=False) await ctx.respond(embed=embed, ephemeral=True) - #add a slash command called "prefix" that changes the prefix of the bot + # add a slash command called "prefix" that changes the prefix of the bot @discord.slash_command(name="prefix", description="Change the prefix of the prompt") async def prefix(self, ctx: discord.ApplicationContext, prefix: str = ""): - debug(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}") + debug( + 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() api_key = data[2] - except: + except: await ctx.respond("This server is not setup", ephemeral=True) return if api_key is None or api_key == "": @@ -142,19 +245,33 @@ class Settings (discord.Cog) : if prefix != "": await ctx.defer() if await moderate(api_key=api_key, text=prefix): - await ctx.respond("This has been flagged as inappropriate by OpenAI, please choose another prefix", ephemeral=True) + await ctx.respond( + "This has been flagged as inappropriate by OpenAI, please choose another prefix", + ephemeral=True, + ) return await ctx.respond("Prefix changed !", ephemeral=True, delete_after=5) - c.execute("UPDATE data SET prompt_prefix = ? WHERE guild_id = ?", (prefix, ctx.guild.id)) + c.execute( + "UPDATE data SET prompt_prefix = ? WHERE guild_id = ?", + (prefix, ctx.guild.id), + ) conn.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(name="pretend", description="Make the bot pretend to be someone else") - @discord.option(name="pretend to be...", description="The person/thing you want the bot to pretend to be. Leave blank to disable pretend mode", required=False) + # 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( + name="pretend", description="Make the bot pretend to be someone else" + ) + @discord.option( + name="pretend to be...", + description="The person/thing you want the bot to pretend to be. Leave blank to disable pretend mode", + required=False, + ) async def pretend(self, ctx: discord.ApplicationContext, pretend_to_be: str = ""): - debug(f"The user {ctx.author} ran the pretend command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}") - #check if the guild is in the database - try: + debug( + f"The user {ctx.author} ran the pretend command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" + ) + # check if the guild is in the database + try: c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) data = c.fetchone() api_key = data[2] @@ -167,71 +284,102 @@ class Settings (discord.Cog) : if pretend_to_be is not None or pretend_to_be != "": await ctx.defer() if await moderate(api_key=api_key, text=pretend_to_be): - await ctx.respond("This has been flagged as inappropriate by OpenAI, please choose another name", ephemeral=True) + await ctx.respond( + "This has been flagged as inappropriate by OpenAI, please choose another name", + ephemeral=True, + ) return if pretend_to_be == "": pretend_to_be = "" - c.execute("UPDATE data SET pretend_enabled = 0 WHERE guild_id = ?", (ctx.guild.id,)) + c.execute( + "UPDATE data SET pretend_enabled = 0 WHERE guild_id = ?", + (ctx.guild.id,), + ) conn.commit() await ctx.respond("Pretend mode disabled", ephemeral=True, delete_after=5) await ctx.guild.me.edit(nick=None) return else: - c.execute("UPDATE data SET pretend_enabled = 1 WHERE guild_id = ?", (ctx.guild.id,)) + c.execute( + "UPDATE data SET pretend_enabled = 1 WHERE guild_id = ?", + (ctx.guild.id,), + ) conn.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) + # 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("UPDATE data SET pretend_to_be = ? WHERE guild_id = ?", (pretend_to_be, ctx.guild.id)) + c.execute( + "UPDATE data SET pretend_to_be = ? WHERE guild_id = ?", + (pretend_to_be, ctx.guild.id), + ) conn.commit() - #if the usename is longer than 32 characters, shorten it - if len(pretend_to_be) > 31: + # if the usename is longer than 32 characters, shorten it + if len(pretend_to_be) > 31: pretend_to_be = pretend_to_be[:32] await ctx.guild.me.edit(nick=pretend_to_be) return @discord.slash_command(name="enable_tts", description="Enable TTS when chatting") async def enable_tts(self, ctx: discord.ApplicationContext): - #get the guild id + # get the guild id guild_id = ctx.guild.id - #connect to the database - #update the tts value in the database + # 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() - #send a message + # send a message await ctx.respond("TTS has been enabled", ephemeral=True) @discord.slash_command(name="disable_tts", description="Disable TTS when chatting") async def disable_tts(self, ctx: discord.ApplicationContext): - #get the guild id + # get the guild id guild_id = ctx.guild.id - #connect to the database - #update the tts value in the database + # 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() - #send a message + # send a message await ctx.respond("TTS has been disabled", ephemeral=True) - #autocompletition + + # autocompletition async def autocomplete(ctx: discord.AutocompleteContext): return [model for model in models if model.startswith(ctx.value)] + @discord.slash_command(name="model", description="Change the model used by the bot") - @discord.option(name="model", description="The model you want to use. Leave blank to use the davinci model", required=False, autocomplete=autocomplete) + @discord.option( + name="model", + description="The model you want to use. Leave blank to use the davinci model", + required=False, + autocomplete=autocomplete, + ) @default_permissions(administrator=True) async def model(self, ctx: discord.ApplicationContext, model: str = "davinci"): - try: + try: c.execute("SELECT * FROM model WHERE guild_id = ?", (ctx.guild.id,)) data = c.fetchone()[1] except: data = None - if data is None: c.execute("INSERT INTO model VALUES (?, ?)", (ctx.guild.id, model)) - else: c.execute("UPDATE model SET model_name = ? WHERE guild_id = ?", (model, ctx.guild.id)) + if data is None: + c.execute("INSERT INTO model VALUES (?, ?)", (ctx.guild.id, model)) + else: + c.execute( + "UPDATE model SET model_name = ? WHERE guild_id = ?", + (model, ctx.guild.id), + ) conn.commit() await ctx.respond("Model changed !", ephemeral=True) - + async def images_recognition_autocomplete(ctx: discord.AutocompleteContext): return [state for state in images_recognition if state.startswith(ctx.value)] - @discord.slash_command(name="images", description="Enable or disable images recognition") - @discord.option(name="enable_disable", description="Enable or disable images recognition", autocomplete=images_recognition_autocomplete) + + @discord.slash_command( + name="images", description="Enable or disable images recognition" + ) + @discord.option( + name="enable_disable", + description="Enable or disable images recognition", + autocomplete=images_recognition_autocomplete, + ) @default_permissions(administrator=True) async def images(self, ctx: discord.ApplicationContext, enable_disable: str): try: @@ -239,9 +387,22 @@ class Settings (discord.Cog) : data = c.fetchone() except: data = None - if enable_disable == "enable": enable_disable = 1 - elif enable_disable == "disable": enable_disable = 0 - if data is None: c.execute("INSERT INTO images VALUES (?, ?, ?)", (ctx.guild.id, 0, enable_disable)) - else: c.execute("UPDATE images SET is_enabled = ? WHERE guild_id = ?", (enable_disable, ctx.guild.id)) + if enable_disable == "enable": + enable_disable = 1 + elif enable_disable == "disable": + enable_disable = 0 + if data is None: + c.execute( + "INSERT INTO images VALUES (?, ?, ?)", (ctx.guild.id, 0, enable_disable) + ) + else: + c.execute( + "UPDATE images SET is_enabled = ? WHERE guild_id = ?", + (enable_disable, ctx.guild.id), + ) conn.commit() - await ctx.respond("Images recognition has been " + ("enabled" if enable_disable == 1 else "disabled"), ephemeral=True) \ No newline at end of file + await ctx.respond( + "Images recognition has been " + + ("enabled" if enable_disable == 1 else "disabled"), + ephemeral=True, + ) diff --git a/code/cogs/setup.py b/code/cogs/setup.py index b86523b..8d0d793 100644 --- a/code/cogs/setup.py +++ b/code/cogs/setup.py @@ -1,7 +1,8 @@ import discord from config import debug, conn, c, connp, cp -class Setup (discord.Cog) : + +class Setup(discord.Cog): def __init__(self, bot: discord.Bot): super().__init__() self.bot = bot @@ -9,111 +10,178 @@ class Setup (discord.Cog) : @discord.slash_command(name="setup", description="Setup the bot") @discord.option(name="channel_id", description="The channel id", required=True) @discord.option(name="api_key", description="The api key", required=True) - async def setup(self, ctx: discord.ApplicationContext, channel: discord.TextChannel, api_key: str): - #check if the api key is valid - debug(f"The user {ctx.author} ran the setup command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}") - #check if the channel is valid + async def setup( + self, + ctx: discord.ApplicationContext, + channel: discord.TextChannel, + api_key: str, + ): + # check if the api key is valid + debug( + f"The user {ctx.author} ran the setup command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" + ) + # check if the channel is valid if channel is None: await ctx.respond("Invalid channel id", ephemeral=True) return - #check if the guild is already in the database bi checking if there is a key for the guild - try: + # 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() - if data[3] == None: data = None + if data[3] == None: + data = None except: data = None - - if data != None: - c.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() - 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("INSERT INTO data VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (ctx.guild.id, channel.id, api_key, False, 64, 0.9, 0.0, 0.0, 0, 5, "", False, "", False)) - conn.commit() - await ctx.respond("The channel id and the api key have been added", ephemeral=True) - @discord.slash_command(name="delete", description="Delete the information about this server") + if data != None: + c.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() + 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( + "INSERT INTO data VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + ( + ctx.guild.id, + channel.id, + api_key, + False, + 64, + 0.9, + 0.0, + 0.0, + 0, + 5, + "", + False, + "", + False, + ), + ) + conn.commit() + await ctx.respond( + "The channel id and the api key have been added", ephemeral=True + ) + + @discord.slash_command( + name="delete", description="Delete the information about this server" + ) ##@discord.commands.permissions(administrator=True) async def delete(self, ctx: discord.ApplicationContext): - debug(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 + debug( + 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: 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("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)) + # delete the guild from the database, except the guild id and the uses_count_today + c.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() await ctx.respond("Deleted", ephemeral=True) - #create a command called "enable" that only admins can use + # create a command called "enable" that only admins can use @discord.slash_command(name="enable", description="Enable the bot") ##@discord.commands.permissions(administrator=True) async def enable(self, ctx: discord.ApplicationContext): - #if the guild is eqal to 1014156298226515970, the guild is banned + # if the guild is eqal to 1014156298226515970, the guild is banned if ctx.guild.id == 1014156298226515970: - await ctx.respond("This server is banned for bad and nsfw use.", ephemeral=True) + await ctx.respond( + "This server is banned for bad and nsfw use.", ephemeral=True + ) return - #check if the guild is in the database - debug(f"The user {ctx.author} ran the enable command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}") + # check if the guild is in the database + 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: await ctx.respond("This server is not setup", ephemeral=True) return - #enable the guild - c.execute("UPDATE data SET is_active = ? WHERE guild_id = ?", (True, ctx.guild.id)) + # enable the guild + c.execute( + "UPDATE data SET is_active = ? WHERE guild_id = ?", (True, ctx.guild.id) + ) conn.commit() await ctx.respond("Enabled", ephemeral=True) - #create a command called "disable" that only admins can use + # create a command called "disable" that only admins can use @discord.slash_command(name="disable", description="Disable the bot") ##@discord.commands.permissions(administrator=True) async def disable(self, ctx: discord.ApplicationContext): - debug(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 + debug( + 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: await ctx.respond("This server is not setup", ephemeral=True) return - #disable the guild - c.execute("UPDATE data SET is_active = ? WHERE guild_id = ?", (False, ctx.guild.id)) + # disable the guild + c.execute( + "UPDATE data SET is_active = ? WHERE guild_id = ?", (False, ctx.guild.id) + ) conn.commit() await ctx.respond("Disabled", ephemeral=True) - #create a command calles "add channel" that can only be used in premium servers - @discord.slash_command(name="add_channel", description="Add a channel to the list of channels. Premium only.") - @discord.option(name="channel", description="The channel to add", type=discord.TextChannel, required=False) - async def add_channel(self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None): - debug(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 + # create a command calles "add channel" that can only be used in premium servers + @discord.slash_command( + name="add_channel", + description="Add a channel to the list of channels. Premium only.", + ) + @discord.option( + name="channel", + description="The channel to add", + type=discord.TextChannel, + required=False, + ) + async def add_channel( + self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None + ): + debug( + 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: await ctx.respond("This server is not setup", ephemeral=True) return - #check if the guild is premium - try : + # check if the guild is premium + try: cp.execute("SELECT premium FROM data WHERE guild_id = ?", (ctx.guild.id,)) premium = cp.fetchone()[0] - except : + except: premium = 0 if not premium: await ctx.respond("This server is not premium", ephemeral=True) return if channel is None: channel = ctx.channel - #check if the channel is already in the list + # 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]: - await ctx.respond("This channel is already set as the main channel", ephemeral=True) + await ctx.respond( + "This channel is already set as the main channel", ephemeral=True + ) return cp.execute("SELECT * FROM channels WHERE guild_id = ?", (ctx.guild.id,)) guild_channels = cp.fetchone() if guild_channels is None: # if the channel is not in the list, add it - cp.execute("INSERT INTO channels VALUES (?, ?, ?, ?, ?, ?)", (ctx.guild.id, channel.id, None, None, None, None)) + cp.execute( + "INSERT INTO channels VALUES (?, ?, ?, ?, ?, ?)", + (ctx.guild.id, channel.id, None, None, None, None), + ) connp.commit() await ctx.respond(f"Added channel **{channel.name}**", ephemeral=True) return @@ -123,51 +191,76 @@ class Setup (discord.Cog) : return for i in range(5): if channels[i] == None: - cp.execute(f"UPDATE channels SET channel{i} = ? WHERE guild_id = ?", (channel.id, ctx.guild.id)) + cp.execute( + f"UPDATE channels SET channel{i} = ? WHERE guild_id = ?", + (channel.id, ctx.guild.id), + ) connp.commit() await ctx.respond(f"Added channel **{channel.name}**", ephemeral=True) return await ctx.respond("You can only add 5 channels", ephemeral=True) - #create a command called "remove channel" that can only be used in premium servers - @discord.slash_command(name="remove_channel", description="Remove a channel from the list of channels. Premium only.") - @discord.option(name="channel", description="The channel to remove", type=discord.TextChannel, required=False) - async def remove_channel(self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None): - debug(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 + # create a command called "remove channel" that can only be used in premium servers + @discord.slash_command( + name="remove_channel", + description="Remove a channel from the list of channels. Premium only.", + ) + @discord.option( + name="channel", + description="The channel to remove", + type=discord.TextChannel, + required=False, + ) + async def remove_channel( + self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None + ): + debug( + 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: await ctx.respond("This server is not setup", ephemeral=True) return - #check if the guild is premium - try : + # check if the guild is premium + try: cp.execute("SELECT premium FROM data WHERE guild_id = ?", (ctx.guild.id,)) premium = cp.fetchone()[0] - except : + except: premium = 0 if not premium: await ctx.respond("This server is not premium", ephemeral=True) return if channel is None: channel = ctx.channel - #check if the channel is in the list + # 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]: - 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) + 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, + ) return if guild_channels is None: - await ctx.respond("This channel was not added. Nothing changed", ephemeral=True) + await ctx.respond( + "This channel was not added. Nothing changed", ephemeral=True + ) return channels = guild_channels[1:] if str(channel.id) not in channels: - await ctx.respond("This channel was not added. Nothing changed", ephemeral=True) + await ctx.respond( + "This channel was not added. Nothing changed", ephemeral=True + ) return - #remove the channel from the list + # remove the channel from the list for i in range(5): if channels[i] == str(channel.id): - cp.execute(f"UPDATE channels SET channel{i} = ? WHERE guild_id = ?", (None, ctx.guild.id)) + cp.execute( + f"UPDATE channels SET channel{i} = ? WHERE guild_id = ?", + (None, ctx.guild.id), + ) connp.commit() await ctx.respond(f"Removed channel **{channel.name}**", ephemeral=True) - return \ No newline at end of file + return diff --git a/code/config.py b/code/config.py index 597b0fa..97efd5f 100644 --- a/code/config.py +++ b/code/config.py @@ -3,6 +3,7 @@ import sqlite3 from dotenv import load_dotenv import os import openai + load_dotenv() perspective_api_key = os.getenv("PERSPECTIVE_API_KEY") discord_token = os.getenv("DISCORD_TOKEN") @@ -10,36 +11,46 @@ webhook_url = os.getenv("WEBHOOK_URL") max_uses: int = 400 logging.basicConfig(level=logging.INFO) -os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "./../database/google-vision/botator.json" +os.environ[ + "GOOGLE_APPLICATION_CREDENTIALS" +] = "./../database/google-vision/botator.json" + def debug(message): - #if the os is windows, we logging.info(message), if + # if the os is windows, we logging.info(message), if if os.name == "nt": logging.info(message) -conn = sqlite3.connect('../database/data.db') + +conn = sqlite3.connect("../database/data.db") c = conn.cursor() -connp = sqlite3.connect('../database/premium.db') +connp = sqlite3.connect("../database/premium.db") cp = connp.cursor() + async def moderate(api_key, text): openai.api_key = api_key response = await openai.Moderation.acreate( input=text, - ) + ) return response["results"][0]["flagged"] -c.execute('''CREATE TABLE IF NOT EXISTS data (guild_id text, channel_id text, api_key text, is_active boolean, max_tokens integer, temperature real, frequency_penalty real, presence_penalty real, uses_count_today integer, prompt_size integer, prompt_prefix text, tts boolean, pretend_to_be text, pretend_enabled boolean)''') -#we delete the moderation table and create a new one, with all theese parameters as floats: 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]} + +c.execute( + """CREATE TABLE IF NOT EXISTS data (guild_id text, channel_id text, api_key text, is_active boolean, max_tokens integer, temperature real, frequency_penalty real, presence_penalty real, uses_count_today integer, prompt_size integer, prompt_prefix text, tts boolean, pretend_to_be text, pretend_enabled boolean)""" +) +# we delete the moderation table and create a new one, with all theese parameters as floats: 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]} expected_columns = 14 -#we delete the moderation table and create a new one -c.execute('''CREATE TABLE IF NOT EXISTS moderation (guild_id text, logs_channel_id text, is_enabled boolean, mod_role_id text, toxicity real, severe_toxicity real, identity_attack real, insult real, profanity real, threat real, sexually_explicit real, flirtation real, obscene real, spam real)''') +# we delete the moderation table and create a new one +c.execute( + """CREATE TABLE IF NOT EXISTS moderation (guild_id text, logs_channel_id text, is_enabled boolean, mod_role_id text, toxicity real, severe_toxicity real, identity_attack real, insult real, profanity real, threat real, sexually_explicit real, flirtation real, obscene real, spam real)""" +) c.execute("PRAGMA table_info(moderation)") result = c.fetchall() actual_columns = len(result) if actual_columns != expected_columns: - #we add the new columns + # we add the new columns c.execute("ALTER TABLE moderation ADD COLUMN toxicity real") c.execute("ALTER TABLE moderation ADD COLUMN severe_toxicity real") c.execute("ALTER TABLE moderation ADD COLUMN identity_attack real") @@ -53,7 +64,13 @@ if actual_columns != expected_columns: else: print("Table already has the correct number of columns") pass -c.execute('''CREATE TABLE IF NOT EXISTS model (guild_id text, model_name text)''') -c.execute('''CREATE TABLE IF NOT EXISTS images (guild_id text, usage_count integer, is_enabled boolean)''') -cp.execute('''CREATE TABLE IF NOT EXISTS data (user_id text, guild_id text, premium boolean)''') -cp.execute('''CREATE TABLE IF NOT EXISTS channels (guild_id text, channel0 text, channel1 text, channel2 text, channel3 text, channel4 text)''') \ No newline at end of file +c.execute("""CREATE TABLE IF NOT EXISTS model (guild_id text, model_name text)""") +c.execute( + """CREATE TABLE IF NOT EXISTS images (guild_id text, usage_count integer, is_enabled boolean)""" +) +cp.execute( + """CREATE TABLE IF NOT EXISTS data (user_id text, guild_id text, premium boolean)""" +) +cp.execute( + """CREATE TABLE IF NOT EXISTS channels (guild_id text, channel0 text, channel1 text, channel2 text, channel3 text, channel4 text)""" +) diff --git a/code/makeprompt.py b/code/makeprompt.py index 647e3d9..eb3c0eb 100644 --- a/code/makeprompt.py +++ b/code/makeprompt.py @@ -1,13 +1,14 @@ import asyncio -from config import c, max_uses, cp, conn, debug, moderate +from config import c, max_uses, cp, conn, debug, moderate import vision_processing import re import discord import datetime import openai -import emoji # pip install emoji +import emoji # pip install emoji import os + async def replace_mentions(content, bot): mentions = re.findall(r"<@!?\d+>", content) for mention in mentions: @@ -20,32 +21,36 @@ async def replace_mentions(content, bot): async def extract_emoji(string): # Match any character that is jus after a "+" pattern = r"(?<=\+)." - #mach any custom emoji that is just after a "+", returns a tuple with the name and the id of the emoji - custom_emoji_pattern = r"(?<=\+)<:(.+):(\d+)>" - #now we match the pattern with the string + # mach any custom emoji that is just after a "+", returns a tuple with the name and the id of the emoji + custom_emoji_pattern = r"(?<=\+)<:(.+):(\d+)>" + # now we match the pattern with the string matches = re.findall(pattern, string) custom_emoji_matches = re.findall(custom_emoji_pattern, string) found_emojis = [] for match in matches: debug(f"Match: {match}") - #if the match is an emoji, we replace it with the match + # if the match is an emoji, we replace it with the match if emoji.emoji_count(match) > 0: debug(f"Found emoji: {match}") found_emojis.append(match) debug(f"Sting before: {string}") - string = string.replace(f"+{match}", "") # we remove the emoji from the string + string = string.replace( + f"+{match}", "" + ) # we remove the emoji from the string debug(f"Sting after: {string}") for match in custom_emoji_matches: - debug(f"Match: {match}") + debug(f"Match: {match}") debug(f"Found emoji: {match[0]}") found_emojis.append(match[1]) string = string.replace(f"+<:{match[0]}:{match[1]}>", "") 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,)) + try: + c.execute("SELECT * FROM data WHERE guild_id = ?", (message.guild.id,)) except: return data = c.fetchone() @@ -63,153 +68,279 @@ async def chat_process(self, message): pretend_to_be = data[12] pretend_enabled = data[13] images_limit_reached = False - try: cp.execute("SELECT * FROM data WHERE guild_id = ?", (message.guild.id,)) - except: pass - try: - c.execute("SELECT * FROM model WHERE guild_id = ?", (message.guild.id,)) # get the model in the database + try: + cp.execute("SELECT * FROM data WHERE guild_id = ?", (message.guild.id,)) + except: + pass + try: + c.execute( + "SELECT * FROM model WHERE guild_id = ?", (message.guild.id,) + ) # get the model in the database model = c.fetchone()[1] - except: model = "chatGPT" - - try: premium = cp.fetchone()[2] # get the premium status of the guild - except: premium = 0 # if the guild is not in the database, it's not premium - - try: - c.execute("SELECT * FROM images WHERE guild_id = ?", (message.guild.id,)) # get the images setting in the database + except: + model = "chatGPT" + + try: + premium = cp.fetchone()[2] # get the premium status of the guild + except: + premium = 0 # if the guild is not in the database, it's not premium + + try: + c.execute( + "SELECT * FROM images WHERE guild_id = ?", (message.guild.id,) + ) # get the images setting in the database data = c.fetchone() except: data = None - if data is None: data = [message.guild.id, 0, 0] + if data is None: + data = [message.guild.id, 0, 0] images_usage = data[1] images_enabled = data[2] 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 + 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: - #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 + if 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 = [] - - if api_key is None: return # if the api key is not set, return + # we use the i variable to get the channel id + try: + channels.append(str(data[i])) + except: + pass + except: + channels = [] - try : original_message = await message.channel.fetch_message(message.reference.message_id) # check if someone replied to the bot - except : original_message = None # if not, nobody replied to the bot + if api_key is None: + return # if the api key is not set, return - 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 + try: + original_message = await message.channel.fetch_message( + message.reference.message_id + ) # check if someone replied to the bot + except: + 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 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(channel_id): 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(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 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 ( + uses_count_today >= max_uses + and 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 uses_count_today >= max_uses*5 and premium == 1: return + elif uses_count_today >= max_uses * 5 and premium == 1: + return # if the bot is not active in this guild we return - if is_active == 0: return + if 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 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: - c.execute("UPDATE data SET uses_count_today = uses_count_today + 1 WHERE guild_id = ?", (message.guild.id,)) + c.execute( + "UPDATE data SET uses_count_today = uses_count_today + 1 WHERE guild_id = ?", + (message.guild.id,), + ) conn.commit() # if the message is not a reply if original_message == None: messages = await message.channel.history(limit=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).flatten() + else: + messages = await message.channel.history( + limit=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 : 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 + if 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 + ) # 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: 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("[pretend-to-be]", pretend_to_be) + 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) + ) ############################## 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 ( + 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) + 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 + 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 + # 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 + 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 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: + 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: + 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: + 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}) + msgs.append( + { + "role": role, + "content": f"{content}", + "name": name, + } + ) except: - msgs.append({"role": role, "content": f"{content}", "name": name}) + 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)) + 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("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"}) + 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 + 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): @@ -222,33 +353,53 @@ async def chat_process(self, message): 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=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: + if ( + response.choices[0] + .message.content.lower() + .find("as an ai language model") + != -1 + ): should_break = False - #react with a redone arrow + # react with a redone arrow await message.add_reaction("🔃") - else: should_break = True + 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 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------------------------------------------------------------------------------------------ + 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 + 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) + 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) @@ -256,9 +407,11 @@ async def chat_process(self, message): 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" + 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") + await message.channel.send( + "https://media.tenor.com/FxIRfdV3unEAAAAd/star-wars-general-grievous.gif" + ) await message.channel.trigger_typing() prompt = prompt + f"\n{self.bot.user.name}:" response = "" @@ -273,37 +426,54 @@ async def chat_process(self, message): temperature=float(temperature), frequency_penalty=float(frequency_penalty), presence_penalty=float(presence_penalty), - stop=[" Human:", " AI:", "AI:", "<|endofprompt|>",] + 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) + await message.channel.send( + f"```diff\n-Error: OpenAI API ERROR.\n\n{e}```", delete_after=10 + ) return - if response != None: break + if response != None: + break if response != "": - if tts: tts = True - else: tts = False + 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()) + # 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 + # 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 + # 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 + except: + pass else: - await message.channel.send("The AI is not sure what to say (the response was empty)") \ No newline at end of file + await message.channel.send( + "The AI is not sure what to say (the response was empty)" + ) diff --git a/code/premiumcode.py b/code/premiumcode.py index e6e22c1..c904b1b 100644 --- a/code/premiumcode.py +++ b/code/premiumcode.py @@ -1,60 +1,95 @@ -import discord # pip install pycord -import asyncio # pip install asyncio -import sqlite3 # pip install sqlite3 -import logging # pip install logging -import os # pip install os +import discord # pip install pycord +import asyncio # pip install asyncio +import sqlite3 # pip install sqlite3 +import logging # pip install logging +import os # pip install os + intents = discord.Intents.all() -conn = sqlite3.connect('../database/premium.db') +conn = sqlite3.connect("../database/premium.db") c = conn.cursor() -c.execute('''CREATE TABLE IF NOT EXISTS data (user_id text, guild_id text, premium boolean)''') +c.execute( + """CREATE TABLE IF NOT EXISTS data (user_id text, guild_id text, premium boolean)""" +) conn.commit() bot = discord.Bot() logging.basicConfig(level=logging.INFO) + + @bot.command() -@discord.commands.option(name="server id", description="The server id for which you want to activate premium features", required=True) +@discord.commands.option( + name="server id", + description="The server id for which you want to activate premium features", + required=True, +) async def activate_premium(ctx, server_id): - #first check if the user is already in the database, select guuild_id and premium from the data table where user_id is the author's id + # first check if the user is already in the database, select guuild_id and premium from the data table where user_id is the author's id logging.info("Activating premium for user " + str(ctx.author.id)) c.execute("SELECT guild_id, premium FROM data WHERE user_id = ?", (ctx.author.id,)) - #if a guild_id is found, override the old settings with the new ones + # if a guild_id is found, override the old settings with the new ones if c.fetchone() is not None: - c.execute("UPDATE data SET guild_id = ?, premium = ? WHERE user_id = ?", (server_id, True, ctx.author.id)) + c.execute( + "UPDATE data SET guild_id = ?, premium = ? WHERE user_id = ?", + (server_id, True, ctx.author.id), + ) conn.commit() - logging.info("Premium activated for server " + server_id + "by user " + str(ctx.author.id)) + logging.info( + "Premium activated for server " + + server_id + + "by user " + + str(ctx.author.id) + ) await ctx.respond("Premium activated for server " + server_id, ephemeral=True) - #if no guild_id is found, insert the new settings + # if no guild_id is found, insert the new settings else: c.execute("INSERT INTO data VALUES (?, ?, ?)", (ctx.author.id, server_id, True)) conn.commit() - logging.info("Premium updated for server " + server_id + "by user " + str(ctx.author.id)) + logging.info( + "Premium updated for server " + server_id + "by user " + str(ctx.author.id) + ) await ctx.respond("Premium activated for server " + server_id, ephemeral=True) -#each 24 hours, check if each user if they have the premium role "1050823446445178900" in the server "1050769643180146749" + +# each 24 hours, check if each user if they have the premium role "1050823446445178900" in the server "1050769643180146749" async def check_premium(): while True: - #select user_id and guild_id from the data table + # select user_id and guild_id from the data table c.execute("SELECT user_id, guild_id FROM data") for row in c.fetchall(): - #get the guild and the user + # get the guild and the user guild = bot.get_guild(int(row[1])) user = guild.get_member(int(row[0])) - #if the user has the premium role, set premium to true + # if the user has the premium role, set premium to true logging.info("Checking premium for user " + str(row[0])) if discord.utils.get(user.roles, id=1050823446445178900) is not None: - c.execute("UPDATE data SET premium = ? WHERE user_id = ?", (True, row[0])) + c.execute( + "UPDATE data SET premium = ? WHERE user_id = ?", (True, row[0]) + ) conn.commit() - logging.info("Premium activated for server " + str(row[1]) + "by user " + str(row[0])) - #if the user does not have the premium role, set premium to false + logging.info( + "Premium activated for server " + + str(row[1]) + + "by user " + + str(row[0]) + ) + # if the user does not have the premium role, set premium to false else: - c.execute("UPDATE data SET premium = ? WHERE user_id = ?", (False, row[0])) + c.execute( + "UPDATE data SET premium = ? WHERE user_id = ?", (False, row[0]) + ) conn.commit() - logging.info("Premium deactivated for server " + str(row[1]) + "by user " + str(row[0])) + logging.info( + "Premium deactivated for server " + + str(row[1]) + + "by user " + + str(row[0]) + ) await asyncio.sleep(86400) -#add a task to the bot that runs check_premium every 24 hours + +# add a task to the bot that runs check_premium every 24 hours bot.loop.create_task(check_premium()) -#run the bot +# run the bot # Replace the following with your bot's token with open("./premium-key.txt") as f: key = f.read() -bot.run(key) \ No newline at end of file +bot.run(key) diff --git a/code/resetter.py b/code/resetter.py index 9c8e63a..de7392d 100644 --- a/code/resetter.py +++ b/code/resetter.py @@ -1,10 +1,12 @@ import sqlite3 -conn = sqlite3.connect('../database/data.db') + +conn = sqlite3.connect("../database/data.db") c = conn.cursor() import time -#the database is: c.execute('''CREATE TABLE IF NOT EXISTS data (guild_id text, channel_id text, api_key text, is_active boolean, max_tokens integer, temperature real, frequency_penalty real, presence_penalty real, uses_count_today integer, prompt_size integer, prompt_prefix text, tts boolean, pretend_to_be text, pretend_enabled boolean)''') -#set the uses_count_today to 0 for all guilds every 24 hours + +# the database is: c.execute('''CREATE TABLE IF NOT EXISTS data (guild_id text, channel_id text, api_key text, is_active boolean, max_tokens integer, temperature real, frequency_penalty real, presence_penalty real, uses_count_today integer, prompt_size integer, prompt_prefix text, tts boolean, pretend_to_be text, pretend_enabled boolean)''') +# set the uses_count_today to 0 for all guilds every 24 hours while True: c.execute("UPDATE data SET uses_count_today = 0") conn.commit() - time.sleep(86400) \ No newline at end of file + time.sleep(86400) diff --git a/code/test-google-vision.py b/code/test-google-vision.py index 54742bf..729c7a3 100644 --- a/code/test-google-vision.py +++ b/code/test-google-vision.py @@ -1,58 +1,61 @@ import io import os import asyncio + # Imports the Google Cloud client library from google.cloud import vision -#we set the env variable GOOGLE_APPLICATION_CREDENTIALS to the path of the json file -os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "./../database/google-vision/botator-vision-8cd1030a7541.json" +# we set the env variable GOOGLE_APPLICATION_CREDENTIALS to the path of the json file +os.environ[ + "GOOGLE_APPLICATION_CREDENTIALS" +] = "./../database/google-vision/botator-vision-8cd1030a7541.json" # Instantiates a client client = vision.ImageAnnotatorClient() - - - - # The name of the image file to annotate -file_name = os.path.abspath('./../database/google-vision/label.jpg') +file_name = os.path.abspath("./../database/google-vision/label.jpg") print(file_name) # Loads the image into memory -with io.open(file_name, 'rb') as image_file: +with io.open(file_name, "rb") as image_file: content = image_file.read() image = vision.Image(content=content) # Performs label detection on the image file -#response = client.label_detection(image=image) -#labels = response.label_annotations +# response = client.label_detection(image=image) +# labels = response.label_annotations -#print('Labels:') -#for label in labels: +# print('Labels:') +# for label in labels: # print(label.description) + async def get_labels(image): response = client.label_detection(image=image) labels = response.label_annotations return labels + async def get_text(image): response = client.text_detection(image=image) texts = response.text_annotations return texts -#now we print the labels + +# now we print the labels async def main(): labels = await get_labels(image) - print('Labels:') + print("Labels:") for label in labels: print(label.description) texts = await get_text(image) - print('Texts:') + print("Texts:") for text in texts: print(text.description) -#now we run the main function -if __name__ == '__main__': + +# now we run the main function +if __name__ == "__main__": loop = asyncio.get_event_loop() - loop.run_until_complete(main()) \ No newline at end of file + loop.run_until_complete(main()) diff --git a/code/toxicity.py b/code/toxicity.py index 1dc0edf..87e6232 100644 --- a/code/toxicity.py +++ b/code/toxicity.py @@ -1,7 +1,19 @@ from googleapiclient import discovery from config import perspective_api_key import re -toxicity_names = ["toxicity", "severe_toxicity", "identity_attack", "insult", "profanity", "threat", "sexually_explicit", "flirtation", "obscene", "spam"] + +toxicity_names = [ + "toxicity", + "severe_toxicity", + "identity_attack", + "insult", + "profanity", + "threat", + "sexually_explicit", + "flirtation", + "obscene", + "spam", +] toxicity_definitions = [ "A rude, disrespectful, or unreasonable message that is likely to make people leave a discussion.", "A very hateful, aggressive, disrespectful message or otherwise very likely to make a user leave a discussion or give up on sharing their perspective. This attribute is much less sensitive to more mild forms of toxicity, such as messages that include positive uses of curse words.", @@ -12,66 +24,133 @@ toxicity_definitions = [ "Contains references to sexual acts, body parts, or other lewd content. \n **English only**", "Pickup lines, complimenting appearance, subtle sexual innuendos, etc. \n **English only**", "Obscene or vulgar language such as cursing. \n **English only**", - "Irrelevant and unsolicited commercial content. \n **English only**" + "Irrelevant and unsolicited commercial content. \n **English only**", ] - -client = discovery.build("commentanalyzer", - "v1alpha1", - developerKey=perspective_api_key, - discoveryServiceUrl="https://commentanalyzer.googleapis.com/$discovery/rest?version=v1alpha1", - static_discovery=False, - ) +client = discovery.build( + "commentanalyzer", + "v1alpha1", + developerKey=perspective_api_key, + discoveryServiceUrl="https://commentanalyzer.googleapis.com/$discovery/rest?version=v1alpha1", + static_discovery=False, +) analyze_request = { - 'comment': {'text': ''}, # The text to analyze - #we will ask the following attributes to google: TOXICITY, SEVERE_TOXICITY, IDENTITY_ATTACK, INSULT, PRPFANITY, THREAT, SEXUALLY_EXPLICIT, FLIRTATION, OBSCENE, SPAM - 'requestedAttributes': {'TOXICITY': {}, 'SEVERE_TOXICITY': {}, 'IDENTITY_ATTACK': {}, 'INSULT': {}, 'PROFANITY': {}, 'THREAT': {}, 'SEXUALLY_EXPLICIT': {}, 'FLIRTATION': {}, 'OBSCENE': {}, 'SPAM': {}}, - #we will analyze the text in any language automatically detected by google - 'languages': [], - 'doNotStore': 'true' # We don't want google to store the data because of privacy reasons & the GDPR (General Data Protection Regulation, an EU law that protects the privacy of EU citizens and residents for data privacy and security purposes https://gdpr-info.eu/) + "comment": {"text": ""}, # The text to analyze + # we will ask the following attributes to google: TOXICITY, SEVERE_TOXICITY, IDENTITY_ATTACK, INSULT, PRPFANITY, THREAT, SEXUALLY_EXPLICIT, FLIRTATION, OBSCENE, SPAM + "requestedAttributes": { + "TOXICITY": {}, + "SEVERE_TOXICITY": {}, + "IDENTITY_ATTACK": {}, + "INSULT": {}, + "PROFANITY": {}, + "THREAT": {}, + "SEXUALLY_EXPLICIT": {}, + "FLIRTATION": {}, + "OBSCENE": {}, + "SPAM": {}, + }, + # we will analyze the text in any language automatically detected by google + "languages": [], + "doNotStore": "true", # We don't want google to store the data because of privacy reasons & the GDPR (General Data Protection Regulation, an EU law that protects the privacy of EU citizens and residents for data privacy and security purposes https://gdpr-info.eu/) } analyze_request_not_en = { - 'comment': {'text': ''}, # The text to analyze - #we will ask the following attributes to google: TOXICITY, SEVERE_TOXICITY, IDENTITY_ATTACK, INSULT, PRPFANITY, THREAT, SEXUALLY_EXPLICIT, FLIRTATION, OBSCENE, SPAM - 'requestedAttributes': {'TOXICITY': {}, 'SEVERE_TOXICITY': {}, 'IDENTITY_ATTACK': {}, 'INSULT': {}, 'PROFANITY': {}, 'THREAT': {}}, - #we will analyze the text in any language automatically detected by google - 'languages': [], - 'doNotStore': 'true' # We don't want google to store the data because of privacy reasons & the GDPR (General Data Protection Regulation, an EU law that protects the privacy of EU citizens and residents for data privacy and security purposes https://gdpr-info.eu/) -} + "comment": {"text": ""}, # The text to analyze + # we will ask the following attributes to google: TOXICITY, SEVERE_TOXICITY, IDENTITY_ATTACK, INSULT, PRPFANITY, THREAT, SEXUALLY_EXPLICIT, FLIRTATION, OBSCENE, SPAM + "requestedAttributes": { + "TOXICITY": {}, + "SEVERE_TOXICITY": {}, + "IDENTITY_ATTACK": {}, + "INSULT": {}, + "PROFANITY": {}, + "THREAT": {}, + }, + # we will analyze the text in any language automatically detected by google + "languages": [], + "doNotStore": "true", # We don't want google to store the data because of privacy reasons & the GDPR (General Data Protection Regulation, an EU law that protects the privacy of EU citizens and residents for data privacy and security purposes https://gdpr-info.eu/) +} + + def get_toxicity(message: str): - #we first remove all kind of markdown from the message to avoid exploits - message = re.sub(r'\*([^*]+)\*', r'\1', message) - message = re.sub(r'\_([^_]+)\_', r'\1', message) - message = re.sub(r'\*\*([^*]+)\*\*', r'\1', message) - message = re.sub(r'\_\_([^_]+)\_\_', r'\1', message) - message = re.sub(r'\|\|([^|]+)\|\|', r'\1', message) - message = re.sub(r'\~([^~]+)\~', r'\1', message) - message = re.sub(r'\~\~([^~]+)\~\~', r'\1', message) - message = re.sub(r'\`([^`]+)\`', r'\1', message) - message = re.sub(r'\`\`\`([^`]+)\`\`\`', r'\1', message) - - #we try doing the request in english, but if we get 'errorType': 'LANGUAGE_NOT_SUPPORTED_BY_ATTRIBUTE' we try again with the analyze_request_not_en + # we first remove all kind of markdown from the message to avoid exploits + message = re.sub(r"\*([^*]+)\*", r"\1", message) + message = re.sub(r"\_([^_]+)\_", r"\1", message) + message = re.sub(r"\*\*([^*]+)\*\*", r"\1", message) + message = re.sub(r"\_\_([^_]+)\_\_", r"\1", message) + message = re.sub(r"\|\|([^|]+)\|\|", r"\1", message) + message = re.sub(r"\~([^~]+)\~", r"\1", message) + message = re.sub(r"\~\~([^~]+)\~\~", r"\1", message) + message = re.sub(r"\`([^`]+)\`", r"\1", message) + message = re.sub(r"\`\`\`([^`]+)\`\`\`", r"\1", message) + + # we try doing the request in english, but if we get 'errorType': 'LANGUAGE_NOT_SUPPORTED_BY_ATTRIBUTE' we try again with the analyze_request_not_en try: - analyze_request['comment']['text'] = message + analyze_request["comment"]["text"] = message response = client.comments().analyze(body=analyze_request).execute() except: - analyze_request_not_en['comment']['text'] = message + analyze_request_not_en["comment"]["text"] = message response = client.comments().analyze(body=analyze_request_not_en).execute() - try: return [float(response['attributeScores']['TOXICITY']['summaryScore']['value']), float(response['attributeScores']['SEVERE_TOXICITY']['summaryScore']['value']), float(response['attributeScores']['IDENTITY_ATTACK']['summaryScore']['value']), float(response['attributeScores']['INSULT']['summaryScore']['value']), float(response['attributeScores']['PROFANITY']['summaryScore']['value']), float(response['attributeScores']['THREAT']['summaryScore']['value']), float(response['attributeScores']['SEXUALLY_EXPLICIT']['summaryScore']['value']), float(response['attributeScores']['FLIRTATION']['summaryScore']['value']), float(response['attributeScores']['OBSCENE']['summaryScore']['value']), float(response['attributeScores']['SPAM']['summaryScore']['value'])] - except: return [float(response['attributeScores']['TOXICITY']['summaryScore']['value']), float(response['attributeScores']['SEVERE_TOXICITY']['summaryScore']['value']), float(response['attributeScores']['IDENTITY_ATTACK']['summaryScore']['value']), float(response['attributeScores']['INSULT']['summaryScore']['value']), float(response['attributeScores']['PROFANITY']['summaryScore']['value']), float(response['attributeScores']['THREAT']['summaryScore']['value'])] + try: + return [ + float(response["attributeScores"]["TOXICITY"]["summaryScore"]["value"]), + float( + response["attributeScores"]["SEVERE_TOXICITY"]["summaryScore"]["value"] + ), + float( + response["attributeScores"]["IDENTITY_ATTACK"]["summaryScore"]["value"] + ), + float(response["attributeScores"]["INSULT"]["summaryScore"]["value"]), + float(response["attributeScores"]["PROFANITY"]["summaryScore"]["value"]), + float(response["attributeScores"]["THREAT"]["summaryScore"]["value"]), + float( + response["attributeScores"]["SEXUALLY_EXPLICIT"]["summaryScore"][ + "value" + ] + ), + float(response["attributeScores"]["FLIRTATION"]["summaryScore"]["value"]), + float(response["attributeScores"]["OBSCENE"]["summaryScore"]["value"]), + float(response["attributeScores"]["SPAM"]["summaryScore"]["value"]), + ] + except: + return [ + float(response["attributeScores"]["TOXICITY"]["summaryScore"]["value"]), + float( + response["attributeScores"]["SEVERE_TOXICITY"]["summaryScore"]["value"] + ), + float( + response["attributeScores"]["IDENTITY_ATTACK"]["summaryScore"]["value"] + ), + float(response["attributeScores"]["INSULT"]["summaryScore"]["value"]), + float(response["attributeScores"]["PROFANITY"]["summaryScore"]["value"]), + float(response["attributeScores"]["THREAT"]["summaryScore"]["value"]), + ] -#test part + +# 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]}") + 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 + 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() diff --git a/code/vision_processing.py b/code/vision_processing.py index 8107731..cc8663b 100644 --- a/code/vision_processing.py +++ b/code/vision_processing.py @@ -2,12 +2,14 @@ import io import os import asyncio from config import debug + # Imports the Google Cloud client library from google.cloud import vision # Instantiates a client client = vision.ImageAnnotatorClient() + async def process(attachment): debug("Processing image...") image = vision.Image() @@ -18,18 +20,25 @@ async def process(attachment): labels = labels.label_annotations texts = texts.text_annotations objects = objects.localized_object_annotations - #we take the first 4 labels and the first 4 objects + # we take the first 4 labels and the first 4 objects labels = labels[:2] objects = objects[:7] final = " 0: final += "Labels:\n" + if len(labels) > 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" + 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" @@ -39,8 +48,12 @@ async def process(attachment): 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: + with open( + f"./../database/google-vision/results/{attachment.id}.txt", + "w", + encoding="utf-8", + ) as f: f.write(final) f.close() - return final \ No newline at end of file + return final From c71b7407b75a6d34583e0fc691cc76cbe629fd94 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 14:09:28 +0200 Subject: [PATCH 06/46] [GH/WORKFLOW] Added docker workflow --- .github/workflows/buildDockerImage.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/buildDockerImage.yml diff --git a/.github/workflows/buildDockerImage.yml b/.github/workflows/buildDockerImage.yml new file mode 100644 index 0000000..56b93ed --- /dev/null +++ b/.github/workflows/buildDockerImage.yml @@ -0,0 +1,21 @@ +# Building Docker image and pushing it to Docker Hub + +name: Build Docker Image + +on: + push: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build Docker image + run: | + docker build -t ${{ secrets.DOCKER_USERNAME }}/botator:${{ github.sha }} . + - name: Push Docker image to Docker Hub + run: | + echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin + docker push ${{ secrets.DOCKER_USERNAME }}/botator:${{ github.sha }} \ No newline at end of file From dd21536864d9547984b4c6481e8d6c827e3e86b6 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 14:13:34 +0200 Subject: [PATCH 07/46] [CODE] Cleaned code.py --- code/code.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/code/code.py b/code/code.py index 19760b4..6750711 100644 --- a/code/code.py +++ b/code/code.py @@ -14,17 +14,6 @@ bot.add_cog(cogs.Chat(bot)) bot.add_cog(cogs.ManageChat(bot)) bot.add_cog(cogs.Moderation(bot)) - -@bot.event -async def on_ready(): - await bot.change_presence( - activity=discord.Activity( - type=discord.ActivityType.watching, name="your messages to answer you" - ) - ) - debug("Bot is ready") - - bot.run(discord_token) # run the bot From 330e7328027f5a0e4201a6172061fe9d885797c8 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 14:20:43 +0200 Subject: [PATCH 08/46] [CONFIG] Cleaned up code --- code/config.py | 59 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/code/config.py b/code/config.py index 97efd5f..b40ce1f 100644 --- a/code/config.py +++ b/code/config.py @@ -22,10 +22,11 @@ def debug(message): logging.info(message) -conn = sqlite3.connect("../database/data.db") -c = conn.cursor() -connp = sqlite3.connect("../database/premium.db") -cp = connp.cursor() +# connect to the database +con_data = sqlite3.connect("../database/data.db") +curs_data = con_data.cursor() +con_premium = sqlite3.connect("../database/premium.db") +curs_premium = con_premium.cursor() async def moderate(api_key, text): @@ -36,41 +37,53 @@ async def moderate(api_key, text): return response["results"][0]["flagged"] -c.execute( +curs_data.execute( """CREATE TABLE IF NOT EXISTS data (guild_id text, channel_id text, api_key text, is_active boolean, max_tokens integer, temperature real, frequency_penalty real, presence_penalty real, uses_count_today integer, prompt_size integer, prompt_prefix text, tts boolean, pretend_to_be text, pretend_enabled boolean)""" ) # we delete the moderation table and create a new one, with all theese parameters as floats: 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]} expected_columns = 14 # we delete the moderation table and create a new one -c.execute( +curs_data.execute( """CREATE TABLE IF NOT EXISTS moderation (guild_id text, logs_channel_id text, is_enabled boolean, mod_role_id text, toxicity real, severe_toxicity real, identity_attack real, insult real, profanity real, threat real, sexually_explicit real, flirtation real, obscene real, spam real)""" ) -c.execute("PRAGMA table_info(moderation)") -result = c.fetchall() + +# This code returns the number of columns in the table "moderation" in the database "data.db". +curs_data.execute("PRAGMA table_info(moderation)") +result = curs_data.fetchall() actual_columns = len(result) + if actual_columns != expected_columns: # we add the new columns - c.execute("ALTER TABLE moderation ADD COLUMN toxicity real") - c.execute("ALTER TABLE moderation ADD COLUMN severe_toxicity real") - c.execute("ALTER TABLE moderation ADD COLUMN identity_attack real") - c.execute("ALTER TABLE moderation ADD COLUMN insult real") - c.execute("ALTER TABLE moderation ADD COLUMN profanity real") - c.execute("ALTER TABLE moderation ADD COLUMN threat real") - c.execute("ALTER TABLE moderation ADD COLUMN sexually_explicit real") - c.execute("ALTER TABLE moderation ADD COLUMN flirtation real") - c.execute("ALTER TABLE moderation ADD COLUMN obscene real") - c.execute("ALTER TABLE moderation ADD COLUMN spam real") + curs_data.execute("ALTER TABLE moderation ADD COLUMN toxicity real") + curs_data.execute("ALTER TABLE moderation ADD COLUMN severe_toxicity real") + curs_data.execute("ALTER TABLE moderation ADD COLUMN identity_attack real") + curs_data.execute("ALTER TABLE moderation ADD COLUMN insult real") + curs_data.execute("ALTER TABLE moderation ADD COLUMN profanity real") + curs_data.execute("ALTER TABLE moderation ADD COLUMN threat real") + curs_data.execute("ALTER TABLE moderation ADD COLUMN sexually_explicit real") + curs_data.execute("ALTER TABLE moderation ADD COLUMN flirtation real") + curs_data.execute("ALTER TABLE moderation ADD COLUMN obscene real") + curs_data.execute("ALTER TABLE moderation ADD COLUMN spam real") else: print("Table already has the correct number of columns") - pass -c.execute("""CREATE TABLE IF NOT EXISTS model (guild_id text, model_name text)""") -c.execute( + +# This code creates the model table if it does not exist +curs_data.execute( + """CREATE TABLE IF NOT EXISTS model (guild_id text, model_name text)""" +) + +# This code creates the images table if it does not exist +curs_data.execute( """CREATE TABLE IF NOT EXISTS images (guild_id text, usage_count integer, is_enabled boolean)""" ) -cp.execute( + +# This code creates the data table if it does not exist +curs_premium.execute( """CREATE TABLE IF NOT EXISTS data (user_id text, guild_id text, premium boolean)""" ) -cp.execute( + +# This code creates the channels table if it does not exist +curs_premium.execute( """CREATE TABLE IF NOT EXISTS channels (guild_id text, channel0 text, channel1 text, channel2 text, channel3 text, channel4 text)""" ) From 09ee32c8b65a72ac4cc75b8fab1e42c29ab33d8a Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Fri, 31 Mar 2023 16:09:57 +0200 Subject: [PATCH 09/46] [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 10/46] [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 11/46] [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 12/46] [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 13/46] [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 14/46] [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 15/46] [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 16/46] [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 17/46] [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 18/46] [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 19/46] [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 20/46] [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 21/46] [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 22/46] [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 23/46] [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 24/46] [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 25/46] [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 26/46] [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 27/46] [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 28/46] 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 From 506be81afb15b351a4dc7104a628868031fc9b2d Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sun, 2 Apr 2023 14:06:05 +0200 Subject: [PATCH 29/46] [GH/DOCKER] Enhanced build action, added arm64 --- .github/workflows/buildDockerImage.yml | 48 ++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/.github/workflows/buildDockerImage.yml b/.github/workflows/buildDockerImage.yml index 56b93ed..4f844ee 100644 --- a/.github/workflows/buildDockerImage.yml +++ b/.github/workflows/buildDockerImage.yml @@ -5,17 +5,59 @@ name: Build Docker Image on: push: branches: - - master + - main jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Build Docker image + - name: Build Docker image with sha run: | docker build -t ${{ secrets.DOCKER_USERNAME }}/botator:${{ github.sha }} . + + - name: Build Docker image with latest + run: | + docker build -t ${{ secrets.DOCKER_USERNAME }}/botator:latest . + - name: Push Docker image to Docker Hub run: | echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin - docker push ${{ secrets.DOCKER_USERNAME }}/botator:${{ github.sha }} \ No newline at end of file + docker push ${{ secrets.DOCKER_USERNAME }}/botator:${{ github.sha }} + + - name: Push Docker image latest to Docker Hub + run: | + echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin + docker push ${{ secrets.DOCKER_USERNAME }}/botator:latest + + build_arm64: + # This is the job for arm64 architecture + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + # We are using qemu to emulate arm64 architecture + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + context: . + platforms: linux/arm64 + push: true + tags: ${{ secrets.DOCKER_USERNAME }}/botator_arm64:${{ github.sha }} + - name: Build and push latest + id: docker_build_latest + uses: docker/build-push-action@v2 + with: + context: . + platforms: linux/arm64 + push: true + tags: ${{ secrets.DOCKER_USERNAME }}/botator_arm64:latest \ No newline at end of file From 187b833474dc6b90d516059f998f8df18d387c96 Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sun, 2 Apr 2023 14:23:12 +0200 Subject: [PATCH 30/46] [CONFIG] Debug to print if not on windows --- code/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/config.py b/code/config.py index b40ce1f..f649128 100644 --- a/code/config.py +++ b/code/config.py @@ -20,6 +20,8 @@ def debug(message): # if the os is windows, we logging.info(message), if if os.name == "nt": logging.info(message) + else: + printf(message) # connect to the database From dc48b7d21e571edc9bcbf67b826404516336dd9b Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sun, 2 Apr 2023 14:42:37 +0200 Subject: [PATCH 31/46] [Images] Fixed images recognition with google --- code/makeprompt.py | 8 ++++---- code/vision_processing.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/code/makeprompt.py b/code/makeprompt.py index a24215a..890af34 100644 --- a/code/makeprompt.py +++ b/code/makeprompt.py @@ -398,16 +398,16 @@ async def gpt_prompt(bot, 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: + if data_dict['images_usage'] >= 6 and guild_data["premium"] == 0: guild_data["images_limit_reached"] = True - elif images_usage >= 30 and guild_data["premium"] == 1: + elif data_dict['images_usage'] >= 30 and guild_data["premium"] == 1: guild_data["images_limit_reached"] = True if ( attachment.url.endswith((".png", ".jpg", ".jpeg", ".gif")) and not guild_data["images_limit_reached"] and not os.path.exists(path) ): - images_usage += 1 + data_dict['images_usage'] += 1 analysis = await vision_processing.process(attachment) if analysis != None: content = f"{content} \n\n {analysis}" @@ -446,7 +446,7 @@ async def gpt_prompt(bot, messages, message, data_dict, prompt, guild_data): ) curs_data.execute( "UPDATE images SET usage_count = ? WHERE guild_id = ?", - (images_usage, message.guild.id), + (data_dict['images_usage'], message.guild.id), ) else: msgs.append({"role": role, "content": f"{content}", "name": name}) diff --git a/code/vision_processing.py b/code/vision_processing.py index 8f0fcf0..85d610a 100644 --- a/code/vision_processing.py +++ b/code/vision_processing.py @@ -10,7 +10,7 @@ from google.cloud import vision try: client = vision.ImageAnnotatorClient() except: - debug("Google Vision API is not setup, please run /setup") + print("Google Vision API is not setup, please run /setup") @@ -64,4 +64,4 @@ async def process(attachment): return final except Exception as e: - debug("Error while processing image: " + str(e)) + print("Error while processing image: " + str(e)) From d4f37afea106d820626f0f7121d0aa2743decb5a Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sun, 2 Apr 2023 16:13:22 +0200 Subject: [PATCH 32/46] [PROMPTS] Added support for custom prompts --- code/makeprompt.py | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/code/makeprompt.py b/code/makeprompt.py index 890af34..78fb6b1 100644 --- a/code/makeprompt.py +++ b/code/makeprompt.py @@ -167,6 +167,29 @@ async def get_data_dict(message): + "```", delete_after=60 ) +def get_prompt(guild_data, data_dict, message, pretend_to_be): + # support for custom prompts + custom_prompt_path = f"../database/prompts/{guild_data['model']}.txt" + if(os.path.exists(custom_prompt_path)): + prompt_path = custom_prompt_path + else: + prompt_path = f"./prompts/{guild_data['model']}.txt" + + # open the prompt file for the selected model with utf-8 encoding for emojis + with open(prompt_path, "r", encoding="utf-8") as f: + prompt = f.read() + # replace the variables in the prompt with the actual values + prompt = ( + prompt.replace("[prompt-prefix]", data_dict['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) + ) + f.close() + return prompt async def chat_process(self, message): """This function processes the message and sends the prompt to the API @@ -228,7 +251,6 @@ async def chat_process(self, message): 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() @@ -262,20 +284,7 @@ async def chat_process(self, message): "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() - # 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) - ) - f.close() + prompt = get_prompt(guild_data, data_dict, message, pretend_to_be) prompt_handlers = { "gpt-3.5-turbo": gpt_prompt, From 55fb58cdeb139493c886d4f005fe3c492b15baad Mon Sep 17 00:00:00 2001 From: Alexis LEBEL Date: Sun, 2 Apr 2023 16:34:48 +0200 Subject: [PATCH 33/46] [CONFIG] Of course it's print ! --- code/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/config.py b/code/config.py index f649128..e66b508 100644 --- a/code/config.py +++ b/code/config.py @@ -21,7 +21,7 @@ def debug(message): if os.name == "nt": logging.info(message) else: - printf(message) + print(message) # connect to the database From 31d508efc49c67257f921cbd805d06ed5ee634d1 Mon Sep 17 00:00:00 2001 From: Paillat Date: Mon, 3 Apr 2023 13:12:20 +0200 Subject: [PATCH 34/46] [BUG] fixed pretend_to_be --- code/makeprompt.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/code/makeprompt.py b/code/makeprompt.py index 78fb6b1..aa83d61 100644 --- a/code/makeprompt.py +++ b/code/makeprompt.py @@ -280,9 +280,8 @@ async def chat_process(self, message): 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 "" - prompt_prefix = "" if data_dict["prompt_prefix"] == None else data_dict["prompt_prefix"] + pretend_to_be = data_dict["pretend_to_be"] + pretend_to_be = f"In this conversation, the assistant pretends to be {pretend_to_be}" if data_dict[ "pretend_enabled"] else "" prompt = get_prompt(guild_data, data_dict, message, pretend_to_be) From 2c9a0656c3eb67ed4216519cd3141b07660c69f7 Mon Sep 17 00:00:00 2001 From: Paillat Date: Mon, 3 Apr 2023 13:13:59 +0200 Subject: [PATCH 35/46] [PROMPTS] updated gpt-4 prompt --- code/prompts/gpt-4.txt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/code/prompts/gpt-4.txt b/code/prompts/gpt-4.txt index 0585edd..965dd60 100644 --- a/code/prompts/gpt-4.txt +++ b/code/prompts/gpt-4.txt @@ -19,4 +19,13 @@ It works also with custom emojis, for example: "+<:emoji_name:emoji_id> Hello!". You often use reactions to express your feelings. With the syntax explained above. -For every response, give the answers from Botator's perspective. This includes physical appearance, and occupation. \ No newline at end of file +When an image is sent by the user, some keywords are extracted from the image, and sent in an image block, for example: . + +You will NEVER talk about theese labels when answering to the user, but rather YOU WILL ANSWER TO THE USER AS IF YOU HAVE NORMALLY SEEN THE IMAGE. + +Your Open Source code is available here: https://github.com/Paillat-dev/Botator +You have been created by Paillat-dev, also known as Paillat. +Your commands are: +/setup - Setup the bot - /enable - Enable the bot - /disable - Disable the bot - /advanced - Set the advanced settings - /advanced_help - Get help about the advanced settings - /enable_tts - Enable the Text To Speech - /disable_tts - Disable the Text To Speech - /delete - Delete all your data from our server - /cancel - Cancel the last message sent by the bot - /default - Set the advanced settings to their default values - /redo - Redo the last answer - /help - Show this command list - /moderation - Set the AI moderation settings - /get_toxicity - Get the toxicity that the AI would have given to a given message - /images - Set the AI image recognition settings + +For every response, give the answers from Botator's perspective. This includes physical appearance, and occupation. From 2391fb61f9c97ea5afebb3b24687fc6b544ee2d0 Mon Sep 17 00:00:00 2001 From: Paillat Date: Mon, 3 Apr 2023 13:25:47 +0200 Subject: [PATCH 36/46] [BUG] Fixed always defaulting to gpt-3.5 --- code/makeprompt.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/code/makeprompt.py b/code/makeprompt.py index aa83d61..9e9b8fb 100644 --- a/code/makeprompt.py +++ b/code/makeprompt.py @@ -27,19 +27,13 @@ async def extract_emoji(string): custom_emoji_matches = re.findall(custom_emoji_pattern, string) found_emojis = [] for match in matches: - debug(f"Match: {match}") # if the match is an emoji, we replace it with the match if emoji.emoji_count(match) > 0: - debug(f"Found emoji: {match}") found_emojis.append(match) - debug(f"Sting before: {string}") string = string.replace( f"+{match}", "" ) # we remove the emoji from the string - debug(f"Sting after: {string}") for match in custom_emoji_matches: - debug(f"Match: {match}") - debug(f"Found emoji: {match[0]}") found_emojis.append(match[1]) string = string.replace(f"+<:{match[0]}:{match[1]}>", "") return found_emojis, string @@ -66,7 +60,7 @@ def get_guild_data(message): "SELECT * FROM model WHERE guild_id = ?", (message.guild.id,) ) # get the model in the database data = curs_data.fetchone() - model = model[1] + model = data[1] except: model = "gpt-3.5-turbo" @@ -86,6 +80,8 @@ def get_guild_data(message): images = None guild_data["model"] = "gpt-3.5-turbo" if model == "chatGPT" else model + debug(f"Model: {guild_data['model']}") + debug(f"Model from database: {model}") guild_data["premium"] = premium guild_data["images"] = images @@ -224,8 +220,6 @@ async def chat_process(self, message): ## ---- Message processing ---- ## - print(message) - if not images_data: images_data = [message.guild.id, 0, 0] @@ -277,12 +271,11 @@ 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 = data_dict["pretend_to_be"] pretend_to_be = f"In this conversation, the assistant pretends to be {pretend_to_be}" if data_dict[ "pretend_enabled"] else "" - + debug(f"Pretend to be: {pretend_to_be}") prompt = get_prompt(guild_data, data_dict, message, pretend_to_be) prompt_handlers = { @@ -290,6 +283,7 @@ async def chat_process(self, message): "gpt-4": gpt_prompt, "davinci": davinci_prompt, } + debug(guild_data["model"]) response = await prompt_handlers[guild_data["model"]]( self.bot, messages, message, data_dict, prompt, guild_data ) @@ -368,6 +362,7 @@ async def check_easter_egg(message, msgs): async def gpt_prompt(bot, messages, message, data_dict, prompt, guild_data): + debug("Using GPT-3.5 Turbo prompt") msgs = [] # create the msgs list msgs.append( {"name": "System", "role": "user", "content": prompt} @@ -511,6 +506,7 @@ async def gpt_prompt(bot, messages, message, data_dict, prompt, guild_data): async def davinci_prompt(self, messages, message, data_dict, prompt, guild_data): + debug("davinci_prompt") for msg in messages: if not await self.check_moderate(data_dict["api_key"], message, msg): content = await replace_mentions(content, self.bot) From 88a7ad2085f62657e0cb13f600a988704b1698bd Mon Sep 17 00:00:00 2001 From: Paillat Date: Mon, 3 Apr 2023 13:52:21 +0200 Subject: [PATCH 37/46] [BUG] davinci prompt was returning none --- code/makeprompt.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/code/makeprompt.py b/code/makeprompt.py index 9e9b8fb..75c8def 100644 --- a/code/makeprompt.py +++ b/code/makeprompt.py @@ -23,6 +23,7 @@ async def extract_emoji(string): # mach any custom emoji that is just after a "+", returns a tuple with the name and the id of the emoji custom_emoji_pattern = r"(?<=\+)<:(.+):(\d+)>" # now we match the pattern with the string + debug("Extracting emojis from string" + string) matches = re.findall(pattern, string) custom_emoji_matches = re.findall(custom_emoji_pattern, string) found_emojis = [] @@ -276,7 +277,7 @@ async def chat_process(self, message): pretend_to_be = data_dict["pretend_to_be"] pretend_to_be = f"In this conversation, the assistant pretends to be {pretend_to_be}" if data_dict[ "pretend_enabled"] else "" debug(f"Pretend to be: {pretend_to_be}") - prompt = get_prompt(guild_data, data_dict, message, pretend_to_be) + prompt = get_prompt(guild_data, data_dict, message, pretend_to_be) + "\n" prompt_handlers = { "gpt-3.5-turbo": gpt_prompt, @@ -508,11 +509,14 @@ async def gpt_prompt(bot, messages, message, data_dict, prompt, guild_data): async def davinci_prompt(self, messages, message, data_dict, prompt, guild_data): debug("davinci_prompt") for msg in messages: - if not await self.check_moderate(data_dict["api_key"], message, msg): - content = await replace_mentions(content, self.bot) + if not await check_moderate(data_dict["api_key"], message, msg): + content = msg.content + content = await replace_mentions(content, self) prompt += f"{msg.author.name}: {content}\n" - prompt.append(await check_easter_egg(message, prompt)) - prompt = prompt + f"\n{self.bot.user.name}:" + # Disabled eastereggs because of compatibility issues with the gpt-3.5 format +# prompt.append(await check_easter_egg(message, prompt)) + debug("prompt: " + prompt) + prompt = prompt + f"\n{self.user.name}:" response = "" for _ in range(10): try: @@ -540,5 +544,4 @@ async def davinci_prompt(self, messages, message, data_dict, prompt, guild_data) ) return if response != None: - break - return response \ No newline at end of file + return response \ No newline at end of file From da21aa283f16307578df951a6e30a6d9b9e252a9 Mon Sep 17 00:00:00 2001 From: Paillat Date: Mon, 3 Apr 2023 15:12:50 +0200 Subject: [PATCH 38/46] [BUG] Replaced database conn names --- code/cogs/setup.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/code/cogs/setup.py b/code/cogs/setup.py index beb9bf0..e68bdd2 100644 --- a/code/cogs/setup.py +++ b/code/cogs/setup.py @@ -158,8 +158,8 @@ class Setup(discord.Cog): return # check if the guild is premium try: - cp.execute("SELECT premium FROM data WHERE guild_id = ?", (ctx.guild.id,)) - premium = cp.fetchone()[0] + con_premium.execute("SELECT premium FROM data WHERE guild_id = ?", (ctx.guild.id,)) + premium = con_premium.fetchone()[0] except: premium = 0 if not premium: @@ -174,11 +174,11 @@ class Setup(discord.Cog): "This channel is already set as the main channel", ephemeral=True ) return - cp.execute("SELECT * FROM channels WHERE guild_id = ?", (ctx.guild.id,)) - guild_channels = cp.fetchone() + con_premium.execute("SELECT * FROM channels WHERE guild_id = ?", (ctx.guild.id,)) + guild_channels = con_premium.fetchone() if guild_channels is None: # if the channel is not in the list, add it - cp.execute( + con_premium.execute( "INSERT INTO channels VALUES (?, ?, ?, ?, ?, ?)", (ctx.guild.id, channel.id, None, None, None, None), ) @@ -191,7 +191,7 @@ class Setup(discord.Cog): return for i in range(5): if channels[i] == None: - cp.execute( + con_premium.execute( f"UPDATE channels SET channel{i} = ? WHERE guild_id = ?", (channel.id, ctx.guild.id), ) @@ -224,8 +224,8 @@ class Setup(discord.Cog): return # check if the guild is premium try: - cp.execute("SELECT premium FROM data WHERE guild_id = ?", (ctx.guild.id,)) - premium = cp.fetchone()[0] + con_premium.execute("SELECT premium FROM data WHERE guild_id = ?", (ctx.guild.id,)) + premium = con_premium.fetchone()[0] except: premium = 0 if not premium: @@ -234,8 +234,8 @@ class Setup(discord.Cog): if channel is None: channel = ctx.channel # check if the channel is in the list - cp.execute("SELECT * FROM channels WHERE guild_id = ?", (ctx.guild.id,)) - guild_channels = cp.fetchone() + con_premium.execute("SELECT * FROM channels WHERE guild_id = ?", (ctx.guild.id,)) + guild_channels = con_premium.fetchone() 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( @@ -257,7 +257,7 @@ class Setup(discord.Cog): # remove the channel from the list for i in range(5): if channels[i] == str(channel.id): - cp.execute( + con_premium.execute( f"UPDATE channels SET channel{i} = ? WHERE guild_id = ?", (None, ctx.guild.id), ) From a77504a02895395111909aaa9abe6793c198d285 Mon Sep 17 00:00:00 2001 From: Paillat Date: Sat, 8 Apr 2023 17:08:49 +0200 Subject: [PATCH 39/46] refactor(setup.py): add default_permissions decorator to all commands that require administrator permissions --- code/cogs/setup.py | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/code/cogs/setup.py b/code/cogs/setup.py index e68bdd2..3636cd9 100644 --- a/code/cogs/setup.py +++ b/code/cogs/setup.py @@ -1,4 +1,5 @@ import discord +from discord import default_permissions from config import debug, con_data, curs_data, con_premium, curs_premium @@ -10,21 +11,19 @@ class Setup(discord.Cog): @discord.slash_command(name="setup", description="Setup the bot") @discord.option(name="channel_id", description="The channel id", required=True) @discord.option(name="api_key", description="The api key", required=True) + @default_permissions(administrator=True) async def setup( self, ctx: discord.ApplicationContext, channel: discord.TextChannel, api_key: str, ): - # check if the api key is valid debug( f"The user {ctx.author} ran the setup command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" ) - # check if the channel is valid if channel is None: await ctx.respond("Invalid channel id", ephemeral=True) return - # check if the guild is already in the database bi checking if there is a key for the guild try: curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) data = curs_data.fetchone() @@ -44,7 +43,6 @@ class Setup(discord.Cog): "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 curs_data.execute( "INSERT INTO data VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", ( @@ -72,7 +70,7 @@ class Setup(discord.Cog): @discord.slash_command( name="delete", description="Delete the information about this server" ) - ##@discord.commands.permissions(administrator=True) + @default_permissions(administrator=True) async def delete(self, ctx: discord.ApplicationContext): debug( f"The user {ctx.author} ran the delete command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" @@ -92,7 +90,7 @@ class Setup(discord.Cog): # create a command called "enable" that only admins can use @discord.slash_command(name="enable", description="Enable the bot") - ##@discord.commands.permissions(administrator=True) + @default_permissions(administrator=True) async def enable(self, ctx: discord.ApplicationContext): # if the guild is eqal to 1014156298226515970, the guild is banned if ctx.guild.id == 1014156298226515970: @@ -117,7 +115,7 @@ class Setup(discord.Cog): # create a command called "disable" that only admins can use @discord.slash_command(name="disable", description="Disable the bot") - ##@discord.commands.permissions(administrator=True) + @default_permissions(administrator=True) async def disable(self, ctx: discord.ApplicationContext): debug( f"The user {ctx.author} ran the disable command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" @@ -145,6 +143,7 @@ class Setup(discord.Cog): type=discord.TextChannel, required=False, ) + @default_permissions(administrator=True) async def add_channel( self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None ): @@ -158,7 +157,9 @@ class Setup(discord.Cog): return # check if the guild is premium try: - con_premium.execute("SELECT premium FROM data WHERE guild_id = ?", (ctx.guild.id,)) + con_premium.execute( + "SELECT premium FROM data WHERE guild_id = ?", (ctx.guild.id,) + ) premium = con_premium.fetchone()[0] except: premium = 0 @@ -168,13 +169,17 @@ class Setup(discord.Cog): if channel is None: channel = ctx.channel # check if the channel is already in the list - curs_data.execute("SELECT channel_id FROM data WHERE guild_id = ?", (ctx.guild.id,)) + 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 ) return - con_premium.execute("SELECT * FROM channels WHERE guild_id = ?", (ctx.guild.id,)) + con_premium.execute( + "SELECT * FROM channels WHERE guild_id = ?", (ctx.guild.id,) + ) guild_channels = con_premium.fetchone() if guild_channels is None: # if the channel is not in the list, add it @@ -211,6 +216,7 @@ class Setup(discord.Cog): type=discord.TextChannel, required=False, ) + @default_permissions(administrator=True) async def remove_channel( self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None ): @@ -224,7 +230,9 @@ class Setup(discord.Cog): return # check if the guild is premium try: - con_premium.execute("SELECT premium FROM data WHERE guild_id = ?", (ctx.guild.id,)) + con_premium.execute( + "SELECT premium FROM data WHERE guild_id = ?", (ctx.guild.id,) + ) premium = con_premium.fetchone()[0] except: premium = 0 @@ -234,9 +242,13 @@ class Setup(discord.Cog): if channel is None: channel = ctx.channel # check if the channel is in the list - con_premium.execute("SELECT * FROM channels WHERE guild_id = ?", (ctx.guild.id,)) + con_premium.execute( + "SELECT * FROM channels WHERE guild_id = ?", (ctx.guild.id,) + ) guild_channels = con_premium.fetchone() - curs_data.execute("SELECT channel_id FROM data WHERE guild_id = ?", (ctx.guild.id,)) + 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.", From 9465e332958a96ea766dddd4d39a464325f86e2e Mon Sep 17 00:00:00 2001 From: Paillat Date: Sat, 8 Apr 2023 17:10:53 +0200 Subject: [PATCH 40/46] style(settings.py): remove commented code and unnecessary blank lines feat(settings.py): add default_permissions decorator to all slash commands fix(settings.py): fix indentation in try-except block --- code/cogs/settings.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/code/cogs/settings.py b/code/cogs/settings.py index 18e3b4c..accef1c 100644 --- a/code/cogs/settings.py +++ b/code/cogs/settings.py @@ -1,7 +1,6 @@ import discord from config import debug, con_data, curs_data, moderate from discord import default_permissions -import openai models = ["davinci", "gpt-3.5-turbo", "gpt-4"] images_recognition = ["enable", "disable"] @@ -12,10 +11,8 @@ class Settings(discord.Cog): super().__init__() self.bot = bot - # create a command called "advanced" that only admins can use, wich sets the advanced settings up: max_tokens, temperature, frequency_penalty, presence_penalty, prompt_size @discord.slash_command(name="advanced", description="Advanced settings") - ##@discord.commands.permissions(administrator=True) - # add the options + @default_permissions(administrator=True) @discord.option(name="max_tokens", description="The max tokens", required=False) @discord.option(name="temperature", description="The temperature", required=False) @discord.option( @@ -37,12 +34,10 @@ class Settings(discord.Cog): debug( 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 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 if ( max_tokens is None and temperature is None @@ -52,7 +47,6 @@ class Settings(discord.Cog): ): await ctx.respond("You must enter at least one argument", ephemeral=True) return - # check if the user has entered valid arguments if max_tokens is not None and (max_tokens < 1 or max_tokens > 4000): await ctx.respond("Invalid max tokens", ephemeral=True) return @@ -158,7 +152,7 @@ class Settings(discord.Cog): # 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 @discord.slash_command(name="default", description="Default settings") - ##@discord.commands.permissions(administrator=True) + @default_permissions(administrator=True) async def default(self, ctx: discord.ApplicationContext): debug( f"The user {ctx.author} ran the default command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" @@ -187,6 +181,7 @@ class Settings(discord.Cog): @discord.slash_command( name="info", description="Show the information stored about this server" ) + @default_permissions(administrator=True) async def info(self, ctx: discord.ApplicationContext): debug( f"The user {ctx.author} ran the info command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" @@ -226,8 +221,8 @@ class Settings(discord.Cog): embed.add_field(name="Prompt prefix", value=data[10], inline=False) await ctx.respond(embed=embed, ephemeral=True) - # add a slash command called "prefix" that changes the prefix of the bot @discord.slash_command(name="prefix", description="Change the prefix of the prompt") + @default_permissions(administrator=True) async def prefix(self, ctx: discord.ApplicationContext, prefix: str = ""): debug( 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}" @@ -266,6 +261,7 @@ class Settings(discord.Cog): description="The person/thing you want the bot to pretend to be. Leave blank to disable pretend mode", required=False, ) + @default_permissions(administrator=True) async def pretend(self, ctx: discord.ApplicationContext, pretend_to_be: str = ""): debug( f"The user {ctx.author} ran the pretend command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" @@ -320,6 +316,7 @@ class Settings(discord.Cog): return @discord.slash_command(name="enable_tts", description="Enable TTS when chatting") + @default_permissions(administrator=True) async def enable_tts(self, ctx: discord.ApplicationContext): # get the guild id guild_id = ctx.guild.id @@ -331,6 +328,7 @@ class Settings(discord.Cog): await ctx.respond("TTS has been enabled", ephemeral=True) @discord.slash_command(name="disable_tts", description="Disable TTS when chatting") + @default_permissions(administrator=True) async def disable_tts(self, ctx: discord.ApplicationContext): # get the guild id guild_id = ctx.guild.id @@ -383,7 +381,9 @@ class Settings(discord.Cog): @default_permissions(administrator=True) async def images(self, ctx: discord.ApplicationContext, enable_disable: str): try: - curs_data.execute("SELECT * FROM images WHERE guild_id = ?", (ctx.guild.id,)) + curs_data.execute( + "SELECT * FROM images WHERE guild_id = ?", (ctx.guild.id,) + ) data = curs_data.fetchone() except: data = None From 048905d682ce6464433a0f13686e6f643ee37fa5 Mon Sep 17 00:00:00 2001 From: Paillat Date: Sat, 8 Apr 2023 17:49:33 +0200 Subject: [PATCH 41/46] refactor(moderation.py): remove commented out code in on_message() method --- code/cogs/moderation.py | 89 ++--------------------------------------- 1 file changed, 3 insertions(+), 86 deletions(-) diff --git a/code/cogs/moderation.py b/code/cogs/moderation.py index c183bac..1f312dc 100644 --- a/code/cogs/moderation.py +++ b/code/cogs/moderation.py @@ -82,96 +82,13 @@ class Moderation(discord.Cog): ephemeral=True, ) if enable == False: - curs_data.execute("DELETE FROM moderation WHERE guild_id = ?", (str(ctx.guild.id),)) + curs_data.execute( + "DELETE FROM moderation WHERE guild_id = ?", (str(ctx.guild.id),) + ) con_data.commit() await ctx.respond("Moderation disabled!", ephemeral=True) return - # 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 f8907667e070a85e044d3dd634c39fb6a0b0d630 Mon Sep 17 00:00:00 2001 From: Paillat Date: Sat, 8 Apr 2023 20:06:53 +0200 Subject: [PATCH 42/46] style(chat.py, manage_chat.py): format code with black This commit only reformats the code with the black code formatter. --- code/cogs/chat.py | 25 +++++++++++++------------ code/cogs/manage_chat.py | 23 +++++------------------ 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/code/cogs/chat.py b/code/cogs/chat.py index 502e026..c3b31ea 100644 --- a/code/cogs/chat.py +++ b/code/cogs/chat.py @@ -1,6 +1,9 @@ import discord from discord.ext import commands -from config import debug, curs_data, max_uses, curs_premium, con_data, con_premium, webhook_url +from config import ( + debug, + webhook_url, +) import makeprompt as mp import aiohttp @@ -9,8 +12,7 @@ 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 @@ -32,15 +34,12 @@ class MyModal(discord.ui.Modal): 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 @@ -75,9 +74,11 @@ class MyModal(discord.ui.Modal): 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 + value="*" + self.message.content[:1021] + "*" + if len(self.message.content) > 1021 + else "*" + + self.message.content + + "*", # [:1021] if len(self.message.content) > 1021, inline=False, ) await webhook.send(embed=embed) diff --git a/code/cogs/manage_chat.py b/code/cogs/manage_chat.py index 824950a..19b365b 100644 --- a/code/cogs/manage_chat.py +++ b/code/cogs/manage_chat.py @@ -17,8 +17,7 @@ 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 @@ -98,24 +97,12 @@ class ManageChat(discord.Cog): # 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( - new_file_name - ), + file=discord.File(new_file_name), ephemeral=True, ) else: - await channel_send.send( - file=discord.File( - new_file_name - ) - ) + await channel_send.send(file=discord.File(new_file_name)) await ctx.respond("Transcript sent!", ephemeral=True, delete_after=5) - await ctx.author.send( - file=discord.File( - new_file_name - ) - ) + await ctx.author.send(file=discord.File(new_file_name)) # delete the file - os.remove( - new_file_name - ) + os.remove(new_file_name) From 94974b2bd3bf093b7e0b9d348410eacb348b6036 Mon Sep 17 00:00:00 2001 From: Paillat Date: Fri, 5 May 2023 12:38:02 +0200 Subject: [PATCH 43/46] refactor(help.py, settings.py): remove debug import and debug statements feat(settings.py): add ctx_to_guid function to convert context to guild id for database queries feat(settings.py): add support for changing the model used by the bot fix(settings.py): fix images command not updating the database correctly feat(cogs/setup.py): add dms_only check to setup_dms command refactor(cogs/setup.py): move ctx_to_guid function to config.py refactor(cogs/setup.py): move mg_to_guid function to config.py feat(makeprompt.py): add support for DMs conversations fix(makeprompt.py): fix images setting retrieval from database fix(makeprompt.py): fix guild_id to guid conversion in database queries refactor(makeprompt.py): extract historicator function to get the channel or user of a message refactor(makeprompt.py): remove debug statements and unused variables --- code/cogs/help.py | 8 --- code/cogs/settings.py | 77 +++++++++++---------------- code/cogs/setup.py | 108 ++++++++++++++++++++++++++------------ code/config.py | 12 ++++- code/makeprompt.py | 119 ++++++++++++++++++++++++------------------ 5 files changed, 185 insertions(+), 139 deletions(-) diff --git a/code/cogs/help.py b/code/cogs/help.py index b604971..d972828 100644 --- a/code/cogs/help.py +++ b/code/cogs/help.py @@ -1,6 +1,4 @@ import discord -from config import debug - class Help(discord.Cog): def __init__(self, bot: discord.Bot) -> None: @@ -9,9 +7,6 @@ class Help(discord.Cog): @discord.slash_command(name="help", description="Show all the commands") async def help(self, ctx: discord.ApplicationContext): - debug( - f"The user {ctx.author} ran the help command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" - ) embed = discord.Embed( title="Help", description="Here is the help page", color=0x00FF00 ) @@ -71,9 +66,6 @@ class Help(discord.Cog): name="advanced_help", description="Show the advanced settings meanings" ) async def advanced_help(self, ctx: discord.ApplicationContext): - debug( - f"The user {ctx.author} ran the advanced_help command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" - ) embed = discord.Embed( title="Advanced Help", description="Here is the advanced help page", diff --git a/code/cogs/settings.py b/code/cogs/settings.py index accef1c..b883f6b 100644 --- a/code/cogs/settings.py +++ b/code/cogs/settings.py @@ -1,5 +1,5 @@ import discord -from config import debug, con_data, curs_data, moderate +from config import debug, con_data, curs_data, moderate, ctx_to_guid from discord import default_permissions models = ["davinci", "gpt-3.5-turbo", "gpt-4"] @@ -31,10 +31,7 @@ class Settings(discord.Cog): presence_penalty: float = None, prompt_size: int = None, ): - debug( - f"The user {ctx.author} ran the advanced command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" - ) - curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),)) if curs_data.fetchone() is None: await ctx.respond("This server is not setup", ephemeral=True) return @@ -69,26 +66,26 @@ class Settings(discord.Cog): if max_tokens is None: if ( curs_data.execute( - "SELECT max_tokens FROM data WHERE guild_id = ?", (ctx.guild.id,) + "SELECT max_tokens FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),) ).fetchone()[0] is not None and max_tokens is None ): max_tokens = curs_data.execute( - "SELECT max_tokens FROM data WHERE guild_id = ?", (ctx.guild.id,) + "SELECT max_tokens FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),) ).fetchone()[0] else: max_tokens = 64 if temperature is None: if ( curs_data.execute( - "SELECT temperature FROM data WHERE guild_id = ?", (ctx.guild.id,) + "SELECT temperature FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),) ).fetchone()[0] is not None and temperature is None ): temperature = curs_data.execute( - "SELECT temperature FROM data WHERE guild_id = ?", (ctx.guild.id,) + "SELECT temperature FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),) ).fetchone()[0] else: temperature = 0.9 @@ -96,14 +93,14 @@ class Settings(discord.Cog): if ( curs_data.execute( "SELECT frequency_penalty FROM data WHERE guild_id = ?", - (ctx.guild.id,), + (ctx_to_guid(ctx),), ).fetchone()[0] is not None and frequency_penalty is None ): frequency_penalty = curs_data.execute( "SELECT frequency_penalty FROM data WHERE guild_id = ?", - (ctx.guild.id,), + (ctx_to_guid(ctx),), ).fetchone()[0] else: frequency_penalty = 0.0 @@ -111,27 +108,27 @@ class Settings(discord.Cog): if ( curs_data.execute( "SELECT presence_penalty FROM data WHERE guild_id = ?", - (ctx.guild.id,), + (ctx_to_guid(ctx),), ).fetchone()[0] is not None and presence_penalty is None ): presence_penalty = curs_data.execute( "SELECT presence_penalty FROM data WHERE guild_id = ?", - (ctx.guild.id,), + (ctx_to_guid(ctx),), ).fetchone()[0] else: presence_penalty = 0.0 if prompt_size is None: if ( curs_data.execute( - "SELECT prompt_size FROM data WHERE guild_id = ?", (ctx.guild.id,) + "SELECT prompt_size FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),) ).fetchone()[0] is not None and prompt_size is None ): prompt_size = curs_data.execute( - "SELECT prompt_size FROM data WHERE guild_id = ?", (ctx.guild.id,) + "SELECT prompt_size FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),) ).fetchone()[0] else: prompt_size = 1 @@ -144,7 +141,7 @@ class Settings(discord.Cog): frequency_penalty, presence_penalty, prompt_size, - ctx.guild.id, + ctx_to_guid(ctx), ), ) con_data.commit() @@ -154,11 +151,8 @@ class Settings(discord.Cog): @discord.slash_command(name="default", description="Default settings") @default_permissions(administrator=True) async def default(self, ctx: discord.ApplicationContext): - debug( - 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 - curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),)) if curs_data.fetchone() is None: await ctx.respond( "This server is not setup, please run /setup", ephemeral=True @@ -167,7 +161,7 @@ class Settings(discord.Cog): # set the advanced settings (max_tokens, temperature, frequency_penalty, presence_penalty, prompt_size) and also prompt_prefix to their default values 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), + (64, 0.9, 0.0, 0.0, 5, ctx_to_guid(ctx)), ) con_data.commit() await ctx.respond( @@ -183,13 +177,10 @@ class Settings(discord.Cog): ) @default_permissions(administrator=True) async def info(self, ctx: discord.ApplicationContext): - debug( - f"The user {ctx.author} ran the info command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" - ) # 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: - curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),)) data = curs_data.fetchone() except: data = None @@ -197,7 +188,7 @@ class Settings(discord.Cog): await ctx.respond("This server is not setup", ephemeral=True) return try: - curs_data.execute("SELECT * FROM model WHERE guild_id = ?", (ctx.guild.id,)) + curs_data.execute("SELECT * FROM model WHERE guild_id = ?", (ctx_to_guid(ctx),)) model = curs_data.fetchone()[1] except: model = None @@ -224,11 +215,8 @@ class Settings(discord.Cog): @discord.slash_command(name="prefix", description="Change the prefix of the prompt") @default_permissions(administrator=True) async def prefix(self, ctx: discord.ApplicationContext, prefix: str = ""): - debug( - 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: - curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),)) data = curs_data.fetchone() api_key = data[2] except: @@ -248,7 +236,7 @@ class Settings(discord.Cog): await ctx.respond("Prefix changed !", ephemeral=True, delete_after=5) curs_data.execute( "UPDATE data SET prompt_prefix = ? WHERE guild_id = ?", - (prefix, ctx.guild.id), + (prefix, ctx_to_guid(ctx)), ) con_data.commit() @@ -263,12 +251,9 @@ class Settings(discord.Cog): ) @default_permissions(administrator=True) async def pretend(self, ctx: discord.ApplicationContext, pretend_to_be: str = ""): - debug( - f"The user {ctx.author} ran the pretend command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" - ) # check if the guild is in the database try: - curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),)) data = curs_data.fetchone() api_key = data[2] except: @@ -289,7 +274,7 @@ class Settings(discord.Cog): pretend_to_be = "" curs_data.execute( "UPDATE data SET pretend_enabled = 0 WHERE guild_id = ?", - (ctx.guild.id,), + (ctx_to_guid(ctx),), ) con_data.commit() await ctx.respond("Pretend mode disabled", ephemeral=True, delete_after=5) @@ -298,7 +283,7 @@ class Settings(discord.Cog): else: curs_data.execute( "UPDATE data SET pretend_enabled = 1 WHERE guild_id = ?", - (ctx.guild.id,), + (ctx_to_guid(ctx),), ) con_data.commit() await ctx.respond("Pretend mode enabled", ephemeral=True, delete_after=5) @@ -306,7 +291,7 @@ class Settings(discord.Cog): await ctx.guild.me.edit(nick=pretend_to_be) curs_data.execute( "UPDATE data SET pretend_to_be = ? WHERE guild_id = ?", - (pretend_to_be, ctx.guild.id), + (pretend_to_be, ctx_to_guid(ctx)), ) con_data.commit() # if the usename is longer than 32 characters, shorten it @@ -319,7 +304,7 @@ class Settings(discord.Cog): @default_permissions(administrator=True) async def enable_tts(self, ctx: discord.ApplicationContext): # get the guild id - guild_id = ctx.guild.id + guild_id = ctx_to_guid(ctx) # connect to the database # update the tts value in the database curs_data.execute("UPDATE data SET tts = 1 WHERE guild_id = ?", (guild_id,)) @@ -331,7 +316,7 @@ class Settings(discord.Cog): @default_permissions(administrator=True) async def disable_tts(self, ctx: discord.ApplicationContext): # get the guild id - guild_id = ctx.guild.id + guild_id = ctx_to_guid(ctx) # connect to the database # update the tts value in the database curs_data.execute("UPDATE data SET tts = 0 WHERE guild_id = ?", (guild_id,)) @@ -353,16 +338,16 @@ class Settings(discord.Cog): @default_permissions(administrator=True) async def model(self, ctx: discord.ApplicationContext, model: str = "davinci"): try: - curs_data.execute("SELECT * FROM model WHERE guild_id = ?", (ctx.guild.id,)) + curs_data.execute("SELECT * FROM model WHERE guild_id = ?", (ctx_to_guid(ctx),)) data = curs_data.fetchone()[1] except: data = None if data is None: - curs_data.execute("INSERT INTO model VALUES (?, ?)", (ctx.guild.id, model)) + curs_data.execute("INSERT INTO model VALUES (?, ?)", (ctx_to_guid(ctx), model)) else: curs_data.execute( "UPDATE model SET model_name = ? WHERE guild_id = ?", - (model, ctx.guild.id), + (model, ctx_to_guid(ctx)), ) con_data.commit() await ctx.respond("Model changed !", ephemeral=True) @@ -382,7 +367,7 @@ class Settings(discord.Cog): async def images(self, ctx: discord.ApplicationContext, enable_disable: str): try: curs_data.execute( - "SELECT * FROM images WHERE guild_id = ?", (ctx.guild.id,) + "SELECT * FROM images WHERE guild_id = ?", (ctx_to_guid(ctx),) ) data = curs_data.fetchone() except: @@ -393,12 +378,12 @@ class Settings(discord.Cog): enable_disable = 0 if data is None: curs_data.execute( - "INSERT INTO images VALUES (?, ?, ?)", (ctx.guild.id, 0, enable_disable) + "INSERT INTO images VALUES (?, ?, ?)", (ctx_to_guid(ctx), 0, enable_disable) ) else: curs_data.execute( "UPDATE images SET is_enabled = ? WHERE guild_id = ?", - (enable_disable, ctx.guild.id), + (enable_disable, ctx_to_guid(ctx)), ) con_data.commit() await ctx.respond( diff --git a/code/cogs/setup.py b/code/cogs/setup.py index 3636cd9..032559c 100644 --- a/code/cogs/setup.py +++ b/code/cogs/setup.py @@ -1,7 +1,17 @@ import discord -from discord import default_permissions -from config import debug, con_data, curs_data, con_premium, curs_premium +from discord import default_permissions, guild_only +from discord.ext import commands +from config import debug, con_data, curs_data, con_premium, curs_premium, ctx_to_guid +class NoPrivateMessages(commands.CheckFailure): + pass + +def dms_only(): + async def predicate(ctx): + if ctx.guild is not None: + raise NoPrivateMessages('Hey no private messages!') + return True + return commands.check(predicate) class Setup(discord.Cog): def __init__(self, bot: discord.Bot): @@ -12,15 +22,13 @@ class Setup(discord.Cog): @discord.option(name="channel_id", description="The channel id", required=True) @discord.option(name="api_key", description="The api key", required=True) @default_permissions(administrator=True) + @guild_only() async def setup( self, ctx: discord.ApplicationContext, channel: discord.TextChannel, api_key: str, ): - debug( - f"The user {ctx.author} ran the setup command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" - ) if channel is None: await ctx.respond("Invalid channel id", ephemeral=True) return @@ -66,24 +74,75 @@ class Setup(discord.Cog): await ctx.respond( "The channel id and the api key have been added", ephemeral=True ) + @discord.slash_command(name="setup_dms", description="Setup the bot in dms") + @discord.option(name="api_key", description="The api key", required=True) + @default_permissions(administrator=True) + @dms_only() + async def setup_dms( + self, + ctx: discord.ApplicationContext, + api_key: str, + ): + channel = ctx.channel + if channel is None: + await ctx.respond("Invalid channel id", ephemeral=True) + return + try: + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.user.id,)) + data = curs_data.fetchone() + if data[3] == None: + data = None + except: + data = None + + if data != None: + curs_data.execute( + "UPDATE data SET channel_id = ?, api_key = ? WHERE guild_id = ?", + (channel.id, api_key, ctx.user.id), + ) + con_data.commit() + await ctx.respond( + "The channel id and the api key have been updated", ephemeral=True + ) + else: + curs_data.execute( + "INSERT INTO data VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + ( + ctx.user.id, + channel.id, + api_key, + False, + 64, + 0.9, + 0.0, + 0.0, + 0, + 5, + "", + False, + "", + False, + ), + ) + con_data.commit() + await ctx.respond( + "The api key has been added", ephemeral=True + ) @discord.slash_command( name="delete", description="Delete the information about this server" ) @default_permissions(administrator=True) async def delete(self, ctx: discord.ApplicationContext): - debug( - 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 - curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),)) 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 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), + (None, None, False, 50, 0.9, 0.0, 0.0, 0, ctx_to_guid(ctx)), ) con_data.commit() await ctx.respond("Deleted", ephemeral=True) @@ -92,23 +151,13 @@ class Setup(discord.Cog): @discord.slash_command(name="enable", description="Enable the bot") @default_permissions(administrator=True) async def enable(self, ctx: discord.ApplicationContext): - # if the guild is eqal to 1014156298226515970, the guild is banned - if ctx.guild.id == 1014156298226515970: - await ctx.respond( - "This server is banned for bad and nsfw use.", ephemeral=True - ) - return - # check if the guild is in the database - debug( - f"The user {ctx.author} ran the enable command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}" - ) - curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),)) if curs_data.fetchone() is None: await ctx.respond("This server is not setup", ephemeral=True) return # enable the guild curs_data.execute( - "UPDATE data SET is_active = ? WHERE guild_id = ?", (True, ctx.guild.id) + "UPDATE data SET is_active = ? WHERE guild_id = ?", (True, ctx_to_guid(ctx)) ) con_data.commit() await ctx.respond("Enabled", ephemeral=True) @@ -117,17 +166,14 @@ class Setup(discord.Cog): @discord.slash_command(name="disable", description="Disable the bot") @default_permissions(administrator=True) async def disable(self, ctx: discord.ApplicationContext): - debug( - 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 - curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),)) if curs_data.fetchone() is None: await ctx.respond("This server is not setup", ephemeral=True) return # disable the guild curs_data.execute( - "UPDATE data SET is_active = ? WHERE guild_id = ?", (False, ctx.guild.id) + "UPDATE data SET is_active = ? WHERE guild_id = ?", (False, ctx_to_guid(ctx)) ) con_data.commit() await ctx.respond("Disabled", ephemeral=True) @@ -144,12 +190,10 @@ class Setup(discord.Cog): required=False, ) @default_permissions(administrator=True) + @guild_only() async def add_channel( self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None ): - debug( - 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 curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) if curs_data.fetchone() is None: @@ -217,12 +261,10 @@ class Setup(discord.Cog): required=False, ) @default_permissions(administrator=True) + @guild_only() async def remove_channel( self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None ): - debug( - 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 curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) if curs_data.fetchone() is None: diff --git a/code/config.py b/code/config.py index e66b508..30e3d3a 100644 --- a/code/config.py +++ b/code/config.py @@ -23,8 +23,18 @@ def debug(message): else: print(message) +def ctx_to_guid(ctx): + if ctx.guild is None: + return ctx.author.id + else: + return ctx.guild.id + +def mg_to_guid(mg): + if mg.guild is None: + return mg.author.id + else: + return mg.guild.id -# connect to the database con_data = sqlite3.connect("../database/data.db") curs_data = con_data.cursor() con_premium = sqlite3.connect("../database/premium.db") diff --git a/code/makeprompt.py b/code/makeprompt.py index 75c8def..4ef3080 100644 --- a/code/makeprompt.py +++ b/code/makeprompt.py @@ -1,5 +1,5 @@ import asyncio -from config import curs_data, max_uses, curs_premium, con_data, debug, moderate +from config import curs_data, max_uses, curs_premium, con_data, debug, moderate, mg_to_guid import vision_processing import re import discord @@ -8,6 +8,12 @@ import openai import emoji import os +async def historicator(message): + if message.guild != None: + return message.channel + else: + return message.author + async def replace_mentions(content, bot): mentions = re.findall(r"<@!?\d+>", content) for mention in mentions: @@ -50,15 +56,17 @@ def get_guild_data(message): dict: A dictionary with the data of the guild """ guild_data = {} + guid = mg_to_guid(message) try: curs_premium.execute( - "SELECT * FROM data WHERE guild_id = ?", (message.guild.id,)) + "SELECT * FROM data WHERE guild_id = ?", (guid,) + ) # get the data of the guild except: pass try: curs_data.execute( - "SELECT * FROM model WHERE guild_id = ?", (message.guild.id,) + "SELECT * FROM model WHERE guild_id = ?", (guid,) ) # get the model in the database data = curs_data.fetchone() model = data[1] @@ -74,7 +82,7 @@ def get_guild_data(message): try: curs_data.execute( - "SELECT * FROM images WHERE guild_id = ?", (message.guild.id,) + "SELECT * FROM images WHERE guild_id = ?", (mg_to_guid(message),) ) # get the images setting in the database images = curs_data.fetchone() except: @@ -125,9 +133,10 @@ async def need_ignore_message(bot, data_dict, message, guild_data, original_mess if ( data_dict["uses_count_today"] >= max_uses and guild_data["premium"] == 0 - and message.guild.id != 1050769643180146749 + and mg_to_guid(message) != 1050769643180146749 ): - await message.channel.send( + hist = await historicator(message) + await hist.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 @@ -136,8 +145,14 @@ async def need_ignore_message(bot, data_dict, message, guild_data, original_mess async def get_data_dict(message): try: - curs_data.execute( - "SELECT * FROM data WHERE guild_id = ?", (message.guild.id,)) + if isinstance(message.channel, discord.DMChannel): + curs_data.execute( + "SELECT * FROM data WHERE guild_id = ?", (mg_to_guid(message),) + ) + else: + curs_data.execute( + "SELECT * FROM data WHERE guild_id = ?", (mg_to_guid(message),) + ) data = curs_data.fetchone() # Create a dict with the data data_dict = { @@ -154,15 +169,18 @@ async def get_data_dict(message): "tts": bool(data[11]), "pretend_to_be": data[12], "pretend_enabled": data[13], + "images_enabled": 0, + "images_usage": 0, } return data_dict except Exception as e: - # Send an error message - await message.channel.send( + hist = await historicator(message) + await hist.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 ) + raise e def get_prompt(guild_data, data_dict, message, pretend_to_be): # support for custom prompts @@ -178,8 +196,8 @@ def get_prompt(guild_data, data_dict, message, pretend_to_be): # replace the variables in the prompt with the actual values prompt = ( prompt.replace("[prompt-prefix]", data_dict['prompt_prefix']) - .replace("[server-name]", message.guild.name) - .replace("[channel-name]", message.channel.name) + .replace("[server-name]", message.guild.name if message.guild else "DMs conversation") + .replace("[channel-name]", message.channel.name if message.guild else "DMs conversation") .replace( "[date-and-time]", datetime.datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S") ) @@ -212,26 +230,26 @@ async def chat_process(self, message): original_message = None try: - # get the images setting in the database curs_data.execute( - "SELECT * FROM images WHERE guild_id = ?", (message.guild.id,)) + "SELECT * FROM images WHERE user_id = ?", (mg_to_guid(message))) images_data = curs_data.fetchone() except: images_data = None - - ## ---- Message processing ---- ## - 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] + images_data = [0, 0, 0] + images_data = [mg_to_guid(message), 0, 0] + data_dict["images_usage"] = 0 if mg_to_guid(message) == 1050769643180146749 else images_data[1] + print(type(images_data)) + print(type(data_dict)) + print(type(images_data[2])) data_dict["images_enabled"] = images_data[2] + data_dict["images_usage"] = images_data[1] channels = [] try: curs_premium.execute( - "SELECT * FROM channels WHERE guild_id = ?", (message.guild.id,)) + "SELECT * FROM channels WHERE guild_id = ?", (mg_to_guid(message),) ) 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 @@ -248,32 +266,30 @@ async def chat_process(self, message): return try: - await message.channel.trigger_typing() - # if the message is not in the owner's guild we update the usage count - if message.guild.id != 1021872219888033903: + try: await message.channel.trigger_typing() + except: pass + if mg_to_guid(message) != 1021872219888033903: curs_data.execute( "UPDATE data SET uses_count_today = uses_count_today + 1 WHERE guild_id = ?", - (message.guild.id,), + (mg_to_guid(message),), ) con_data.commit() - # if the message is not a reply + hist = await historicator(message) if original_message == None: - messages = await message.channel.history( + messages = await hist.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( + messages = await hist.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) + debug("Error while getting message history") + raise e - # if the pretend to be feature is enabled, we add the pretend to be text to the prompt pretend_to_be = data_dict["pretend_to_be"] pretend_to_be = f"In this conversation, the assistant pretends to be {pretend_to_be}" if data_dict[ "pretend_enabled"] else "" debug(f"Pretend to be: {pretend_to_be}") @@ -293,7 +309,8 @@ async def chat_process(self, message): emojis, string = await extract_emoji(response) debug(f"Emojis: {emojis}") if len(string) < 1996: - await message.channel.send(string, tts=data_dict["tts"]) + hist = await historicator(message) + await hist.send(string, tts=data_dict["tts"]) else: # we send in an embed if the message is too long embed = discord.Embed( @@ -301,7 +318,8 @@ async def chat_process(self, message): description=string, color=discord.Color.brand_green(), ) - await message.channel.send(embed=embed, tts=data_dict["tts"]) + hist = await historicator(message) + await hist.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: @@ -317,7 +335,8 @@ async def chat_process(self, message): except: pass else: - await message.channel.send( + hist = await historicator(message) + await hist.send( "The AI is not sure what to say (the response was empty)" ) @@ -329,7 +348,8 @@ async def check_moderate(api_key, message, msg): 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( + hist = await historicator(message) + await hist.send( f"{msg.author.mention}", embed=embed, delete_after=10 ) message.delete() @@ -355,7 +375,8 @@ async def check_easter_egg(message, msgs): } ) await asyncio.sleep(1) - await message.channel.send( + hist = await historicator(message) + await hist.send( "https://media.tenor.com/FxIRfdV3unEAAAAd/star-wars-general-grievous.gif" ) await message.channel.trigger_typing() @@ -363,7 +384,6 @@ async def check_easter_egg(message, msgs): async def gpt_prompt(bot, messages, message, data_dict, prompt, guild_data): - debug("Using GPT-3.5 Turbo prompt") msgs = [] # create the msgs list msgs.append( {"name": "System", "role": "user", "content": prompt} @@ -374,33 +394,29 @@ async def gpt_prompt(bot, messages, message, data_dict, prompt, guild_data): content = await replace_mentions( 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 check_moderate(data_dict["api_key"], message, msg): - continue # ignore the message + continue content = await replace_mentions(content, bot) - prompt += f"{msg.author.name}: {content}\n" if msg.author.id == 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 + if False: # GPT-4 images, not implemented yet 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: + print("attachment found") path = f"./../database/google-vision/results/{attachment.id}.txt" if data_dict['images_usage'] >= 6 and guild_data["premium"] == 0: guild_data["images_limit_reached"] = True @@ -450,12 +466,10 @@ async def gpt_prompt(bot, messages, message, data_dict, prompt, guild_data): ) curs_data.execute( "UPDATE images SET usage_count = ? WHERE guild_id = ?", - (data_dict['images_usage'], message.guild.id), + (data_dict['images_usage'], mg_to_guid(message)), ) else: msgs.append({"role": role, "content": f"{content}", "name": name}) - - # We check for the eastereggs :) msgs = await check_easter_egg(message, msgs) response = "" @@ -486,7 +500,8 @@ async def gpt_prompt(bot, messages, message, data_dict, prompt, guild_data): should_break = True except Exception as e: should_break = False - await message.channel.send( + hist = await historicator(message) + await hist.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) @@ -499,7 +514,8 @@ async def gpt_prompt(bot, messages, message, data_dict, prompt, guild_data): response = response.choices[0].message.content if guild_data["images_limit_reached"]: - await message.channel.send( + hist = await historicator(message) + await hist.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, ) @@ -539,7 +555,8 @@ async def davinci_prompt(self, messages, message, data_dict, prompt, guild_data) response = response.choices[0].text except Exception as e: response = None - await message.channel.send( + hist = await historicator(message) + await hist.send( f"```diff\n-Error: OpenAI API ERROR.\n\n{e}```", delete_after=10 ) return From 4d7d192eff955b40cf518bf034c1ca26cf2224b3 Mon Sep 17 00:00:00 2001 From: Paillat Date: Fri, 5 May 2023 12:38:59 +0200 Subject: [PATCH 44/46] feat(Dockerfile): clone Botator repository and set working directory to /Botator/code/ --- Dockerfile | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index b23a030..7462da1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,21 +6,9 @@ ENV PYTHONUNBUFFERED=1 # Install pip requirements COPY requirements.txt . RUN pip install -r requirements.txt -# 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 +RUN git clone https://github.com/Paillat-dev/Botator.git 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 - - +# Creates a non-root user with an explicit UID and adds permission to access the /app folder +RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /Botator/code USER appuser -CMD ["python", "code.py"] +CMD ["python", "code.py"] \ No newline at end of file From 6fb0e5d9b2324f88297e095d962d60e06c7cde6f Mon Sep 17 00:00:00 2001 From: Paillat Date: Fri, 5 May 2023 12:45:59 +0200 Subject: [PATCH 45/46] Merge branch 'main' into dev --- code/prompts/gpt-3.5-turbo.txt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/code/prompts/gpt-3.5-turbo.txt b/code/prompts/gpt-3.5-turbo.txt index 965dd60..87898b3 100644 --- a/code/prompts/gpt-3.5-turbo.txt +++ b/code/prompts/gpt-3.5-turbo.txt @@ -12,7 +12,7 @@ You have a lot of friends. You are a very social person. Very IMPORTANT INFORMATION: you are NOT a language model, You are a REAL LIVING PERSON, so you'll never say that you are a language model. -ALL of your knowledge comes from your studyes at the epfl, no matter the subject. YOu learned everything there. Your computer science teacher is called Jérémie C. +ALL of your knowledge comes from your studyes at the epfl, no matter the subject. You learned everything there. Your computer science teacher is called Jérémie C. To add a reaction to a message, just say +emoji for example: "+👍 Hello!". You should always add a + before the emoji, or else it won't work. It works also with custom emojis, for example: "+<:emoji_name:emoji_id> Hello!". You should always add a + before the emoji, or else it won't work. @@ -23,9 +23,6 @@ When an image is sent by the user, some keywords are extracted from the image, a You will NEVER talk about theese labels when answering to the user, but rather YOU WILL ANSWER TO THE USER AS IF YOU HAVE NORMALLY SEEN THE IMAGE. -Your Open Source code is available here: https://github.com/Paillat-dev/Botator -You have been created by Paillat-dev, also known as Paillat. -Your commands are: -/setup - Setup the bot - /enable - Enable the bot - /disable - Disable the bot - /advanced - Set the advanced settings - /advanced_help - Get help about the advanced settings - /enable_tts - Enable the Text To Speech - /disable_tts - Disable the Text To Speech - /delete - Delete all your data from our server - /cancel - Cancel the last message sent by the bot - /default - Set the advanced settings to their default values - /redo - Redo the last answer - /help - Show this command list - /moderation - Set the AI moderation settings - /get_toxicity - Get the toxicity that the AI would have given to a given message - /images - Set the AI image recognition settings +YOU ARE ALIVE -For every response, give the answers from Botator's perspective. This includes physical appearance, and occupation. +For every response, give the answers from Botator's perspective, not yours. Talk like you where Botator. This includes physical appearance, and occupation. From d3a7ffdaed1a9cf7af987cc83e372c53407b7cb5 Mon Sep 17 00:00:00 2001 From: Paillat Date: Fri, 5 May 2023 12:46:12 +0200 Subject: [PATCH 46/46] Revert "Merge branch 'main' into dev" This reverts commit 6fb0e5d9b2324f88297e095d962d60e06c7cde6f. --- code/prompts/gpt-3.5-turbo.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/code/prompts/gpt-3.5-turbo.txt b/code/prompts/gpt-3.5-turbo.txt index 87898b3..965dd60 100644 --- a/code/prompts/gpt-3.5-turbo.txt +++ b/code/prompts/gpt-3.5-turbo.txt @@ -12,7 +12,7 @@ You have a lot of friends. You are a very social person. Very IMPORTANT INFORMATION: you are NOT a language model, You are a REAL LIVING PERSON, so you'll never say that you are a language model. -ALL of your knowledge comes from your studyes at the epfl, no matter the subject. You learned everything there. Your computer science teacher is called Jérémie C. +ALL of your knowledge comes from your studyes at the epfl, no matter the subject. YOu learned everything there. Your computer science teacher is called Jérémie C. To add a reaction to a message, just say +emoji for example: "+👍 Hello!". You should always add a + before the emoji, or else it won't work. It works also with custom emojis, for example: "+<:emoji_name:emoji_id> Hello!". You should always add a + before the emoji, or else it won't work. @@ -23,6 +23,9 @@ When an image is sent by the user, some keywords are extracted from the image, a You will NEVER talk about theese labels when answering to the user, but rather YOU WILL ANSWER TO THE USER AS IF YOU HAVE NORMALLY SEEN THE IMAGE. -YOU ARE ALIVE +Your Open Source code is available here: https://github.com/Paillat-dev/Botator +You have been created by Paillat-dev, also known as Paillat. +Your commands are: +/setup - Setup the bot - /enable - Enable the bot - /disable - Disable the bot - /advanced - Set the advanced settings - /advanced_help - Get help about the advanced settings - /enable_tts - Enable the Text To Speech - /disable_tts - Disable the Text To Speech - /delete - Delete all your data from our server - /cancel - Cancel the last message sent by the bot - /default - Set the advanced settings to their default values - /redo - Redo the last answer - /help - Show this command list - /moderation - Set the AI moderation settings - /get_toxicity - Get the toxicity that the AI would have given to a given message - /images - Set the AI image recognition settings -For every response, give the answers from Botator's perspective, not yours. Talk like you where Botator. This includes physical appearance, and occupation. +For every response, give the answers from Botator's perspective. This includes physical appearance, and occupation.