From f5e5145b14774a96a5fccb87d7d39cf1cfbae1f4 Mon Sep 17 00:00:00 2001 From: Paillat Date: Thu, 17 Aug 2023 17:43:11 +0200 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=94=A7=20chore(docker-publish.yml):?= =?UTF-8?q?=20remove=20scheduled=20job=20for=20Docker=20workflow=20?= =?UTF-8?q?=F0=9F=94=A7=20chore(docker-publish.yml):=20remove=20scheduled?= =?UTF-8?q?=20job=20for=20Docker=20workflow=20to=20simplify=20the=20workfl?= =?UTF-8?q?ow=20and=20avoid=20unnecessary=20executions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docker-publish.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index e9acfc6..7810e91 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -6,8 +6,6 @@ name: Docker # documentation. on: - schedule: - - cron: '24 8 * * *' push: branches: [ "main" ] # Publish semver tags as releases. From 3a955d43790c47180132cd77313eaf48985da1bb Mon Sep 17 00:00:00 2001 From: Paillat Date: Sat, 19 Aug 2023 14:16:30 +0200 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=90=9B=20fix(main.py):=20handle=20on?= =?UTF-8?q?=5Fapplication=5Fcommand=5Ferror=20with=20proper=20error=20hand?= =?UTF-8?q?ling=20and=20response=20=E2=9C=A8=20feat(main.py):=20add=20Chat?= =?UTF-8?q?Process=20module=20for=20handling=20chat-related=20functionalit?= =?UTF-8?q?y=20=F0=9F=94=A7=20refactor(main.py):=20import=20necessary=20mo?= =?UTF-8?q?dules=20and=20update=20bot.add=5Fcog=20calls=20=F0=9F=94=A7=20r?= =?UTF-8?q?efactor(server.ts):=20change=20port=20variable=20case=20from=20?= =?UTF-8?q?lowercase=20port=20to=20uppercase=20PORT=20to=20improve=20seman?= =?UTF-8?q?tics=20=E2=9C=A8=20feat(server.ts):=20add=20support=20for=20pro?= =?UTF-8?q?cess.env.PORT=20environment=20variable=20to=20be=20able=20to=20?= =?UTF-8?q?run=20app=20on=20a=20configurable=20port=20=F0=9F=94=A7=20refac?= =?UTF-8?q?tor(cogs/=5F=5Finit=5F=5F.py):=20import=20ChannelSetup=20cog=20?= =?UTF-8?q?=E2=9C=A8=20feat(cogs/channelSetup.py):=20add=20ChannelSetup=20?= =?UTF-8?q?cog=20for=20setting=20up=20channels=20and=20server-wide=20setti?= =?UTF-8?q?ngs=20=F0=9F=94=A7=20refactor(cogs/setup.py):=20import=20SlashC?= =?UTF-8?q?ommandGroup=20and=20guild=5Fonly=20from=20discord=20module=20?= =?UTF-8?q?=E2=9C=A8=20feat(cogs/setup.py):=20add=20setup=5Fchannel=20comm?= =?UTF-8?q?and=20for=20adding=20and=20removing=20channels=20=E2=9C=A8=20fe?= =?UTF-8?q?at(cogs/setup.py):=20add=20api=20command=20for=20setting=20API?= =?UTF-8?q?=20keys=20=E2=9C=A8=20feat(cogs/setup.py):=20add=20premium=20co?= =?UTF-8?q?mmand=20for=20setting=20guild=20to=20premium=20=F0=9F=94=A7=20r?= =?UTF-8?q?efactor(cogs/settings.py):=20temporarily=20disable=20images=20c?= =?UTF-8?q?ommand=20due=20to=20maintenance=20=F0=9F=94=A7=20refactor(confi?= =?UTF-8?q?g.py):=20remove=20unnecessary=20code=20related=20to=20moderatio?= =?UTF-8?q?n=20table=20=E2=9C=A8=20feat(guild.py):=20add=20Guild=20class?= =?UTF-8?q?=20for=20managing=20guild-specific=20data=20and=20settings=20?= =?UTF-8?q?=E2=9C=A8=20feat(SqlConnector.py):=20add=20SQLConnection=20and?= =?UTF-8?q?=20=5Fsql=20classes=20for=20managing=20SQLite=20connections=20?= =?UTF-8?q?=E2=9C=A8=20feat(variousclasses.py):=20add=20models,=20characte?= =?UTF-8?q?rs,=20and=20apis=20classes=20for=20autocomplete=20functionality?= =?UTF-8?q?=20in=20slash=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 5 +- src/ChatProcess.py | 86 ++++++++++++++++++++++++++++++ src/cogs/__init__.py | 1 + src/cogs/channelSetup.py | 97 ++++++++++++++++++++++++++++++++++ src/cogs/settings.py | 6 +++ src/cogs/setup.py | 11 ++-- src/config.py | 27 +--------- src/guild.py | 102 ++++++++++++++++++++++++++++++++++++ src/utils/SqlConnector.py | 26 +++++++++ src/utils/variousclasses.py | 37 +++++++++++++ 10 files changed, 366 insertions(+), 32 deletions(-) create mode 100644 src/ChatProcess.py create mode 100644 src/cogs/channelSetup.py create mode 100644 src/guild.py create mode 100644 src/utils/SqlConnector.py create mode 100644 src/utils/variousclasses.py diff --git a/main.py b/main.py index cf15e38..0fc7355 100644 --- a/main.py +++ b/main.py @@ -13,6 +13,7 @@ bot.add_cog(cogs.Help(bot)) bot.add_cog(cogs.Chat(bot)) bot.add_cog(cogs.ManageChat(bot)) bot.add_cog(cogs.Moderation(bot)) +bot.add_cog(cogs.ChannelSetup(bot)) # set the bot's watching status to watcing your messages to answer you @@ -36,9 +37,9 @@ async def on_guild_join(guild): @bot.event -async def on_application_command_error(ctx, error): - debug(error) +async def on_application_command_error(ctx, error: discord.DiscordException): await ctx.respond(error, ephemeral=True) + raise error bot.run(discord_token) # run the bot diff --git a/src/ChatProcess.py b/src/ChatProcess.py new file mode 100644 index 0000000..594c0e6 --- /dev/null +++ b/src/ChatProcess.py @@ -0,0 +1,86 @@ +import asyncio +import os +import re +import discord +import datetime +import json + +from src.utils.misc import moderate, ModerationError, Hasher +from src.utils.variousclasses import models, characters, apis +from src.guild import Guild +from src.utils.openaicaller import openai_caller +from src.functionscalls import ( + call_function, + functions, + server_normal_channel_functions, + FuntionCallError, +) + + +class Chat: + def __init__(self, bot, message: discord.Message): + self.bot = bot + self.message: discord.Message = message + self.guild = Guild(self.message.guild.id) + self.author = message.author + self.is_bots_thread = False + + async def getSupplementaryData(self) -> None: + """ + This function gets various contextual data that will be needed later on + - The original message (if the message is a reply to a previous message from the bot) + - The channel the message was sent in (if the message was sent in a thread) + """ + if isinstance(self.message.channel, discord.Thread): + if self.message.channel.owner_id == self.bot.user.id: + self.is_bots_thread = True + self.channelIdForSettings = self.message.channel.parent_id + else: + self.channelIdForSettings = self.message.channel.id + + try: + self.original_message = await self.message.channel.fetch_message( + self.message.reference.message_id + ) + except: + self.original_message = None + + if ( + self.original_message != None + and self.original_message.author.id == self.bot.user.id + ): + self.original_message = None + + async def preExitCriteria(self) -> bool: + """ + Returns True if any of the exit criterias are met + This checks if the guild has the needed settings for the bot to work + """ + returnCriterias = [] + returnCriterias.append(self.message.author.id == self.bot.user.id) + returnCriterias.append(self.api_key == None) + returnCriterias.append(self.is_active == 0) + return any(returnCriterias) + + async def postExitCriteria(self) -> bool: + """ + Returns True if any of the exit criterias are met (their opposite is met but there is a not in front of the any() function) + This checks if the bot should actuallly respond to the message or if the message doesn't concern the bot + """ + returnCriterias = [] + returnCriterias.append(self.guild.sanitizedChannels.get(str(self.message.channel.id), None) != None) + returnCriterias.append( + self.message.content.find("<@" + str(self.bot.user.id) + ">") != -1 + ) + returnCriterias.append(self.original_message != None) + returnCriterias.append(self.is_bots_thread) + + return not any(returnCriterias) + + async def getSettings(self): + self.settings = self.guild.getChannelInfo(str(self.channelIdForSettings)) + self.model = self.settings["model"] + self.character = self.settings["character"] + self.openai_api_key = self.guild.api_keys.get("openai", None) + + \ No newline at end of file diff --git a/src/cogs/__init__.py b/src/cogs/__init__.py index b4b4e80..b84dc2b 100644 --- a/src/cogs/__init__.py +++ b/src/cogs/__init__.py @@ -4,3 +4,4 @@ from src.cogs.help import Help from src.cogs.chat import Chat from src.cogs.manage_chat import ManageChat from src.cogs.moderation import Moderation +from src.cogs.channelSetup import ChannelSetup \ No newline at end of file diff --git a/src/cogs/channelSetup.py b/src/cogs/channelSetup.py new file mode 100644 index 0000000..8c7663e --- /dev/null +++ b/src/cogs/channelSetup.py @@ -0,0 +1,97 @@ +import discord +import orjson + +from discord import SlashCommandGroup +from discord import default_permissions +from discord.ext.commands import guild_only +from discord.ext import commands +from src.utils.variousclasses import models, characters, apis +from src.guild import Guild + +sampleDataFormatExample = { + "guild_id": 1234567890, + "premium": False, + "premium_expiration": 0, +} + +class ChannelSetup(commands.Cog): + def __init__(self, bot: discord.Bot): + super().__init__() + self.bot = bot + + setup = SlashCommandGroup("setup", description="Setup commands for the bot, inlcuding channels, models, and more.") + + setup_channel = setup.create_subgroup(name="channel", description="Setup, add, or remove channels for the bot to use.") + + @setup_channel.command(name="add", description="Add a channel for the bot to use. Can also specify server-wide settings.") + @discord.option(name="channel", description="The channel to setup. If not specified, will use the current channel.", type=discord.TextChannel, required=False) + @discord.option(name="model", description="The model to use for this channel.", type=str, required=False, autocomplete=models.autocomplete) + @discord.option(name="character", description="The character to use for this channel.", type=str, required=False, autocomplete=characters.autocomplete) + @guild_only() + async def channel(self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None, model: str = models.default, character: str = characters.default): + if channel is None: + channel = ctx.channel + guild = Guild(ctx.guild.id) + guild.load() + if not guild.premium: + if len(guild.channels) >= 1 and guild.channels.get(str(channel.id), None) is None: + await ctx.respond("`Warning: You are not a premium user, and can only have one channel setup. The settings will still be saved, but will not be used.`", ephemeral=True) + if model != models.default: + await ctx.respond("`Warning: You are not a premium user, and can only use the default model. The settings will still be saved, but will not be used.`", ephemeral=True) + if character != characters.default: + await ctx.respond("`Warning: You are not a premium user, and can only use the default character. The settings will still be saved, but will not be used.`", ephemeral=True) + if guild.api_keys.get("openai", None) is None: + await ctx.respond("`Error: No openai api key is set. The api key is needed for the openai models, as well as for the content moderation. The openai models will cost you tokens in your openai account. However, if you use one of the llama models, you will not be charged, but the api key is still needed for content moderation, wich is free but requires an api key.`", ephemeral=True) + guild.addChannel(channel, models.matchingDict[model], characters.matchingDict[character]) + await ctx.respond(f"Set channel {channel.mention} with model `{model}` and character `{character}`.") + + @setup_channel.command(name="remove", description="Remove a channel from the bot's usage.") + @discord.option(name="channel", description="The channel to remove. If not specified, will use the current channel.", type=discord.TextChannel, required=False) + @guild_only() + async def remove(self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None): + if channel is None: + channel = ctx.channel + guild = Guild(ctx.guild.id) + guild.load() + if channel.id not in guild.channels: + await ctx.respond("That channel is not setup.") + return + guild.delChannel(channel) + await ctx.respond(f"Removed channel {channel.mention}.") + + @setup_channel.command(name="list", description="List all channels that are setup.") + @guild_only() + async def list(self, ctx: discord.ApplicationContext): + guild = Guild(ctx.guild.id) + guild.load() + if len(guild.channels) == 0: + await ctx.respond("No channels are setup.") + return + embed = discord.Embed(title="Channels", description="All channels that are setup.", color=discord.Color.nitro_pink()) + channels = guild.sanitizedChannels + for channel in channels: + discochannel = await self.bot.fetch_channel(int(channel)) + model = models.reverseMatchingDict[channels[channel]["model"]] + character = characters.reverseMatchingDict[channels[channel]["character"]] + embed.add_field(name=f"{discochannel.mention}", value=f"Model: `{model}`\nCharacter: `{character}`", inline=False) + await ctx.respond(embed=embed) + + @setup.command(name="api", description="Set an API key for the bot to use.") + @discord.option(name="api", description="The API to set. Currently only OpenAI is supported.", type=str, required=True, autocomplete=apis.autocomplete) + @discord.option(name="key", description="The key to set.", type=str, required=True) + @guild_only() + async def api(self, ctx: discord.ApplicationContext, api: str, key: str): + guild = Guild(ctx.guild.id) + guild.load() + guild.api_keys[apis.matchingDict[api]] = key + guild.updateDbData() + await ctx.respond(f"Set API key for {api} to `secret`.", ephemeral=True) + + @setup.command(name="premium", description="Set the guild to premium.") + async def premium(self, ctx: discord.ApplicationContext): + guild = Guild(ctx.guild.id) + guild.load() + if not guild.premium: + await ctx.respond("You can get your premium subscription at https://www.botator.dev/premium.", ephemeral=True) + else: + await ctx.respond("This guild is already premium.", ephemeral=True) \ No newline at end of file diff --git a/src/cogs/settings.py b/src/cogs/settings.py index 4474333..81cd595 100644 --- a/src/cogs/settings.py +++ b/src/cogs/settings.py @@ -266,6 +266,12 @@ class Settings(discord.Cog): ) @default_permissions(administrator=True) async def images(self, ctx: discord.ApplicationContext, enable_disable: str): + return await ctx.respond( + """ +Images recognition is under maintenance and will come back soon! + """ + ) + try: curs_data.execute( "SELECT * FROM images WHERE guild_id = ?", (ctx_to_guid(ctx),) diff --git a/src/cogs/setup.py b/src/cogs/setup.py index 4f33d02..90fa12d 100644 --- a/src/cogs/setup.py +++ b/src/cogs/setup.py @@ -1,4 +1,5 @@ import discord +from discord import SlashCommandGroup from discord import default_permissions, guild_only from discord.ext import commands from src.config import ( @@ -28,7 +29,7 @@ class Setup(discord.Cog): def __init__(self, bot: discord.Bot): super().__init__() self.bot = bot - + """ @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) @@ -138,7 +139,7 @@ class Setup(discord.Cog): ) 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" ) @@ -190,8 +191,10 @@ class Setup(discord.Cog): 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", + name="setup_channel", description="Add a channel to the list of channels. Premium only.", ) @discord.option( @@ -257,7 +260,7 @@ class Setup(discord.Cog): 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", diff --git a/src/config.py b/src/config.py index c3fd62b..137c295 100644 --- a/src/config.py +++ b/src/config.py @@ -53,33 +53,8 @@ curs_premium = con_premium.cursor() 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 -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)""" -) - -# 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 - 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") +con_data.execute('CREATE TABLE IF NOT EXISTS setup_data (guild_id text, guild_settings text)') # This code creates the model table if it does not exist curs_data.execute( diff --git a/src/guild.py b/src/guild.py new file mode 100644 index 0000000..dce5da0 --- /dev/null +++ b/src/guild.py @@ -0,0 +1,102 @@ +import orjson +import discord + +from src.utils.SqlConnector import sql +from datetime import datetime +from src.utils.variousclasses import models, characters + +class Guild: + def __init__(self, id: int): + self.id = id + self.load() + + def getDbData(self): + with sql.mainDb as con: + curs_data = con.cursor() + curs_data.execute("SELECT * FROM setup_data WHERE guild_id = ?", (self.id,)) + data = curs_data.fetchone() + self.isInDb = data is not None + if not self.isInDb: + self.updateDbData() + with sql.mainDb as con: + curs_data = con.cursor() + curs_data.execute("SELECT * FROM setup_data WHERE guild_id = ?", (self.id,)) + data = curs_data.fetchone() + data = orjson.loads(data[1]) + self.premium = data["premium"] + self.channels = data["channels"] + self.api_keys = data["api_keys"] + if self.premium: + self.premium_expiration = datetime.fromisoformat(data.get("premium_expiration", None)) + self.checkPremiumExpires() + else: + self.premium_expiration = None + + def checkPremiumExpires(self): + if self.premium_expiration is None: + self.premium = False + return + if self.premium_expiration < datetime.now(): + self.premium = False + self.premium_expiration = None + self.updateDbData() + + def updateDbData(self): + if self.isInDb: + data = { + "guild_id": self.id, + "premium": self.premium, + "channels": self.channels, + "api_keys": self.api_keys, + "premium_expiration": self.premium_expiration.isoformat() if self.premium else None, + } + else: + data = { + "guild_id": self.id, + "premium": False, + "channels": {}, + "api_keys": {}, + "premium_expiration": None, + } + with sql.mainDb as con: + curs_data = con.cursor() + if self.isInDb: + curs_data.execute("UPDATE setup_data SET guild_settings = ? WHERE guild_id = ?", (orjson.dumps(data), self.id)) + else: + curs_data.execute("INSERT INTO setup_data (guild_id, guild_settings) VALUES (?, ?)", (self.id, orjson.dumps(data))) + self.isInDb = True + + def load(self): + self.getDbData() + + def addChannel(self, channel: discord.TextChannel, model: str, character: str): + print(f"Adding channel {channel.id} to guild {self.id} with model {model} and character {character}") + self.channels[str(channel.id)] = { + "model": model, + "character": character, + } + self.updateDbData() + + def delChannel(self, channel: discord.TextChannel): + del self.channels[str(channel.id)] + self.updateDbData() + + @property + def sanitizedChannels(self) -> dict: + if self.premium: + return self.channels + if len(self.channels) == 0: + return {} + return { + list(self.channels.keys())[0]: { + "model": models.matchingDict[models.default], + "character": characters.matchingDict[characters.default], + } + } + + def getChannelInfo(self, channel: str): + return self.sanitizedChannels.get(channel, None) + + def addApiKey(self, api: str, key: str): + self.api_keys[api] = key + self.updateDbData() \ No newline at end of file diff --git a/src/utils/SqlConnector.py b/src/utils/SqlConnector.py new file mode 100644 index 0000000..68e56a1 --- /dev/null +++ b/src/utils/SqlConnector.py @@ -0,0 +1,26 @@ +from sqlite3 import connect +from random import randint + + +class SQLConnection: + + def __init__(self,connection): + self.connection = connection + + def __enter__(self): + return self.connection + + def __exit__(self,exc_type,exc_val,exc_tb): + self.connection.commit() + self.connection.close() + + +class _sql: + + @property + def mainDb(self): + s = connect('./database/data.db') + return SQLConnection(s) + + +sql: _sql = _sql() \ No newline at end of file diff --git a/src/utils/variousclasses.py b/src/utils/variousclasses.py new file mode 100644 index 0000000..087c240 --- /dev/null +++ b/src/utils/variousclasses.py @@ -0,0 +1,37 @@ +from discord import AutocompleteContext + +class models: + matchingDict = { + "chatGPT (default - free)": "gpt-3.5-turbo", + "davinci (premium)": "text-davinci-003", + "llama (premium)": "text-llama", + "llama 2 (premium)": "text-llama-2", + } + reverseMatchingDict = {v: k for k, v in matchingDict.items()} + default = list(matchingDict.keys())[0] + openaimodels = ["gpt-3.5-turbo", "text-davinci-003"] + @classmethod + async def autocomplete(cls, ctx: AutocompleteContext) -> list[str]: + modls = cls.matchingDict.keys() + return [model for model in modls if model.find(ctx.value.lower()) != -1] + +class characters: + matchingDict = { + "Botator (default - free)": "botator", + "Aurora (premium)": "aurora", + } + reverseMatchingDict = {v: k for k, v in matchingDict.items()} + default = list(matchingDict.keys())[0] + @classmethod + async def autocomplete(cls, ctx: AutocompleteContext) -> list[str]: + chars = characters = cls.matchingDict.keys() + return [character for character in chars if character.find(ctx.value.lower()) != -1] + +class apis: + matchingDict = { + "OpenAI": "openai", + } + @classmethod + async def autocomplete(cls, ctx: AutocompleteContext) -> list[str]: + apiss = cls.matchingDict.keys() + return [api for api in apiss if api.find(ctx.value.lower()) != -1] \ No newline at end of file From 775f8758b74636e738165206642a07807f8ffbb5 Mon Sep 17 00:00:00 2001 From: Paillat Date: Sat, 19 Aug 2023 14:17:16 +0200 Subject: [PATCH 3/7] :art: chore(*): run black to format the code --- src/ChatProcess.py | 6 +- src/cogs/__init__.py | 2 +- src/cogs/channelSetup.py | 123 +++++++++++++++++++++++++++++------- src/cogs/setup.py | 5 +- src/config.py | 4 +- src/guild.py | 35 +++++++--- src/utils/SqlConnector.py | 10 ++- src/utils/variousclasses.py | 12 +++- 8 files changed, 150 insertions(+), 47 deletions(-) diff --git a/src/ChatProcess.py b/src/ChatProcess.py index 594c0e6..42e0235 100644 --- a/src/ChatProcess.py +++ b/src/ChatProcess.py @@ -68,7 +68,9 @@ class Chat: This checks if the bot should actuallly respond to the message or if the message doesn't concern the bot """ returnCriterias = [] - returnCriterias.append(self.guild.sanitizedChannels.get(str(self.message.channel.id), None) != None) + returnCriterias.append( + self.guild.sanitizedChannels.get(str(self.message.channel.id), None) != None + ) returnCriterias.append( self.message.content.find("<@" + str(self.bot.user.id) + ">") != -1 ) @@ -82,5 +84,3 @@ class Chat: self.model = self.settings["model"] self.character = self.settings["character"] self.openai_api_key = self.guild.api_keys.get("openai", None) - - \ No newline at end of file diff --git a/src/cogs/__init__.py b/src/cogs/__init__.py index b84dc2b..70c6c24 100644 --- a/src/cogs/__init__.py +++ b/src/cogs/__init__.py @@ -4,4 +4,4 @@ from src.cogs.help import Help from src.cogs.chat import Chat from src.cogs.manage_chat import ManageChat from src.cogs.moderation import Moderation -from src.cogs.channelSetup import ChannelSetup \ No newline at end of file +from src.cogs.channelSetup import ChannelSetup diff --git a/src/cogs/channelSetup.py b/src/cogs/channelSetup.py index 8c7663e..0c83421 100644 --- a/src/cogs/channelSetup.py +++ b/src/cogs/channelSetup.py @@ -14,41 +14,101 @@ sampleDataFormatExample = { "premium_expiration": 0, } + class ChannelSetup(commands.Cog): def __init__(self, bot: discord.Bot): super().__init__() self.bot = bot - setup = SlashCommandGroup("setup", description="Setup commands for the bot, inlcuding channels, models, and more.") + setup = SlashCommandGroup( + "setup", + description="Setup commands for the bot, inlcuding channels, models, and more.", + ) - setup_channel = setup.create_subgroup(name="channel", description="Setup, add, or remove channels for the bot to use.") + setup_channel = setup.create_subgroup( + name="channel", description="Setup, add, or remove channels for the bot to use." + ) - @setup_channel.command(name="add", description="Add a channel for the bot to use. Can also specify server-wide settings.") - @discord.option(name="channel", description="The channel to setup. If not specified, will use the current channel.", type=discord.TextChannel, required=False) - @discord.option(name="model", description="The model to use for this channel.", type=str, required=False, autocomplete=models.autocomplete) - @discord.option(name="character", description="The character to use for this channel.", type=str, required=False, autocomplete=characters.autocomplete) + @setup_channel.command( + name="add", + description="Add a channel for the bot to use. Can also specify server-wide settings.", + ) + @discord.option( + name="channel", + description="The channel to setup. If not specified, will use the current channel.", + type=discord.TextChannel, + required=False, + ) + @discord.option( + name="model", + description="The model to use for this channel.", + type=str, + required=False, + autocomplete=models.autocomplete, + ) + @discord.option( + name="character", + description="The character to use for this channel.", + type=str, + required=False, + autocomplete=characters.autocomplete, + ) @guild_only() - async def channel(self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None, model: str = models.default, character: str = characters.default): + async def channel( + self, + ctx: discord.ApplicationContext, + channel: discord.TextChannel = None, + model: str = models.default, + character: str = characters.default, + ): if channel is None: channel = ctx.channel guild = Guild(ctx.guild.id) guild.load() if not guild.premium: - if len(guild.channels) >= 1 and guild.channels.get(str(channel.id), None) is None: - await ctx.respond("`Warning: You are not a premium user, and can only have one channel setup. The settings will still be saved, but will not be used.`", ephemeral=True) + if ( + len(guild.channels) >= 1 + and guild.channels.get(str(channel.id), None) is None + ): + await ctx.respond( + "`Warning: You are not a premium user, and can only have one channel setup. The settings will still be saved, but will not be used.`", + ephemeral=True, + ) if model != models.default: - await ctx.respond("`Warning: You are not a premium user, and can only use the default model. The settings will still be saved, but will not be used.`", ephemeral=True) + await ctx.respond( + "`Warning: You are not a premium user, and can only use the default model. The settings will still be saved, but will not be used.`", + ephemeral=True, + ) if character != characters.default: - await ctx.respond("`Warning: You are not a premium user, and can only use the default character. The settings will still be saved, but will not be used.`", ephemeral=True) + await ctx.respond( + "`Warning: You are not a premium user, and can only use the default character. The settings will still be saved, but will not be used.`", + ephemeral=True, + ) if guild.api_keys.get("openai", None) is None: - await ctx.respond("`Error: No openai api key is set. The api key is needed for the openai models, as well as for the content moderation. The openai models will cost you tokens in your openai account. However, if you use one of the llama models, you will not be charged, but the api key is still needed for content moderation, wich is free but requires an api key.`", ephemeral=True) - guild.addChannel(channel, models.matchingDict[model], characters.matchingDict[character]) - await ctx.respond(f"Set channel {channel.mention} with model `{model}` and character `{character}`.") + await ctx.respond( + "`Error: No openai api key is set. The api key is needed for the openai models, as well as for the content moderation. The openai models will cost you tokens in your openai account. However, if you use one of the llama models, you will not be charged, but the api key is still needed for content moderation, wich is free but requires an api key.`", + ephemeral=True, + ) + guild.addChannel( + channel, models.matchingDict[model], characters.matchingDict[character] + ) + await ctx.respond( + f"Set channel {channel.mention} with model `{model}` and character `{character}`." + ) - @setup_channel.command(name="remove", description="Remove a channel from the bot's usage.") - @discord.option(name="channel", description="The channel to remove. If not specified, will use the current channel.", type=discord.TextChannel, required=False) + @setup_channel.command( + name="remove", description="Remove a channel from the bot's usage." + ) + @discord.option( + name="channel", + description="The channel to remove. If not specified, will use the current channel.", + type=discord.TextChannel, + required=False, + ) @guild_only() - async def remove(self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None): + async def remove( + self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None + ): if channel is None: channel = ctx.channel guild = Guild(ctx.guild.id) @@ -67,17 +127,31 @@ class ChannelSetup(commands.Cog): if len(guild.channels) == 0: await ctx.respond("No channels are setup.") return - embed = discord.Embed(title="Channels", description="All channels that are setup.", color=discord.Color.nitro_pink()) + embed = discord.Embed( + title="Channels", + description="All channels that are setup.", + color=discord.Color.nitro_pink(), + ) channels = guild.sanitizedChannels for channel in channels: - discochannel = await self.bot.fetch_channel(int(channel)) + discochannel = await self.bot.fetch_channel(int(channel)) model = models.reverseMatchingDict[channels[channel]["model"]] character = characters.reverseMatchingDict[channels[channel]["character"]] - embed.add_field(name=f"{discochannel.mention}", value=f"Model: `{model}`\nCharacter: `{character}`", inline=False) + embed.add_field( + name=f"{discochannel.mention}", + value=f"Model: `{model}`\nCharacter: `{character}`", + inline=False, + ) await ctx.respond(embed=embed) @setup.command(name="api", description="Set an API key for the bot to use.") - @discord.option(name="api", description="The API to set. Currently only OpenAI is supported.", type=str, required=True, autocomplete=apis.autocomplete) + @discord.option( + name="api", + description="The API to set. Currently only OpenAI is supported.", + type=str, + required=True, + autocomplete=apis.autocomplete, + ) @discord.option(name="key", description="The key to set.", type=str, required=True) @guild_only() async def api(self, ctx: discord.ApplicationContext, api: str, key: str): @@ -92,6 +166,9 @@ class ChannelSetup(commands.Cog): guild = Guild(ctx.guild.id) guild.load() if not guild.premium: - await ctx.respond("You can get your premium subscription at https://www.botator.dev/premium.", ephemeral=True) + await ctx.respond( + "You can get your premium subscription at https://www.botator.dev/premium.", + ephemeral=True, + ) else: - await ctx.respond("This guild is already premium.", ephemeral=True) \ No newline at end of file + await ctx.respond("This guild is already premium.", ephemeral=True) diff --git a/src/cogs/setup.py b/src/cogs/setup.py index 90fa12d..7f2c383 100644 --- a/src/cogs/setup.py +++ b/src/cogs/setup.py @@ -29,6 +29,7 @@ class Setup(discord.Cog): def __init__(self, bot: discord.Bot): super().__init__() self.bot = bot + """ @discord.slash_command(name="setup", description="Setup the bot") @discord.option(name="channel_id", description="The channel id", required=True) @@ -140,6 +141,7 @@ class Setup(discord.Cog): 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" ) @@ -191,7 +193,7 @@ class Setup(discord.Cog): await ctx.respond("Disabled", ephemeral=True) # create a command calles "add channel" that can only be used in premium servers - + """ @discord.slash_command( name="setup_channel", @@ -261,6 +263,7 @@ class Setup(discord.Cog): 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", diff --git a/src/config.py b/src/config.py index 137c295..fef50ce 100644 --- a/src/config.py +++ b/src/config.py @@ -54,7 +54,9 @@ 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)""" ) -con_data.execute('CREATE TABLE IF NOT EXISTS setup_data (guild_id text, guild_settings text)') +con_data.execute( + "CREATE TABLE IF NOT EXISTS setup_data (guild_id text, guild_settings text)" +) # This code creates the model table if it does not exist curs_data.execute( diff --git a/src/guild.py b/src/guild.py index dce5da0..c60db50 100644 --- a/src/guild.py +++ b/src/guild.py @@ -5,6 +5,7 @@ from src.utils.SqlConnector import sql from datetime import datetime from src.utils.variousclasses import models, characters + class Guild: def __init__(self, id: int): self.id = id @@ -20,14 +21,18 @@ class Guild: self.updateDbData() with sql.mainDb as con: curs_data = con.cursor() - curs_data.execute("SELECT * FROM setup_data WHERE guild_id = ?", (self.id,)) + curs_data.execute( + "SELECT * FROM setup_data WHERE guild_id = ?", (self.id,) + ) data = curs_data.fetchone() data = orjson.loads(data[1]) self.premium = data["premium"] self.channels = data["channels"] self.api_keys = data["api_keys"] if self.premium: - self.premium_expiration = datetime.fromisoformat(data.get("premium_expiration", None)) + self.premium_expiration = datetime.fromisoformat( + data.get("premium_expiration", None) + ) self.checkPremiumExpires() else: self.premium_expiration = None @@ -48,7 +53,9 @@ class Guild: "premium": self.premium, "channels": self.channels, "api_keys": self.api_keys, - "premium_expiration": self.premium_expiration.isoformat() if self.premium else None, + "premium_expiration": self.premium_expiration.isoformat() + if self.premium + else None, } else: data = { @@ -61,22 +68,30 @@ class Guild: with sql.mainDb as con: curs_data = con.cursor() if self.isInDb: - curs_data.execute("UPDATE setup_data SET guild_settings = ? WHERE guild_id = ?", (orjson.dumps(data), self.id)) + curs_data.execute( + "UPDATE setup_data SET guild_settings = ? WHERE guild_id = ?", + (orjson.dumps(data), self.id), + ) else: - curs_data.execute("INSERT INTO setup_data (guild_id, guild_settings) VALUES (?, ?)", (self.id, orjson.dumps(data))) + curs_data.execute( + "INSERT INTO setup_data (guild_id, guild_settings) VALUES (?, ?)", + (self.id, orjson.dumps(data)), + ) self.isInDb = True - + def load(self): self.getDbData() - + def addChannel(self, channel: discord.TextChannel, model: str, character: str): - print(f"Adding channel {channel.id} to guild {self.id} with model {model} and character {character}") + print( + f"Adding channel {channel.id} to guild {self.id} with model {model} and character {character}" + ) self.channels[str(channel.id)] = { "model": model, "character": character, } self.updateDbData() - + def delChannel(self, channel: discord.TextChannel): del self.channels[str(channel.id)] self.updateDbData() @@ -99,4 +114,4 @@ class Guild: def addApiKey(self, api: str, key: str): self.api_keys[api] = key - self.updateDbData() \ No newline at end of file + self.updateDbData() diff --git a/src/utils/SqlConnector.py b/src/utils/SqlConnector.py index 68e56a1..d9f2f31 100644 --- a/src/utils/SqlConnector.py +++ b/src/utils/SqlConnector.py @@ -3,24 +3,22 @@ from random import randint class SQLConnection: - - def __init__(self,connection): + def __init__(self, connection): self.connection = connection def __enter__(self): return self.connection - def __exit__(self,exc_type,exc_val,exc_tb): + def __exit__(self, exc_type, exc_val, exc_tb): self.connection.commit() self.connection.close() class _sql: - @property def mainDb(self): - s = connect('./database/data.db') + s = connect("./database/data.db") return SQLConnection(s) -sql: _sql = _sql() \ No newline at end of file +sql: _sql = _sql() diff --git a/src/utils/variousclasses.py b/src/utils/variousclasses.py index 087c240..1647975 100644 --- a/src/utils/variousclasses.py +++ b/src/utils/variousclasses.py @@ -1,5 +1,6 @@ from discord import AutocompleteContext + class models: matchingDict = { "chatGPT (default - free)": "gpt-3.5-turbo", @@ -10,11 +11,13 @@ class models: reverseMatchingDict = {v: k for k, v in matchingDict.items()} default = list(matchingDict.keys())[0] openaimodels = ["gpt-3.5-turbo", "text-davinci-003"] + @classmethod async def autocomplete(cls, ctx: AutocompleteContext) -> list[str]: modls = cls.matchingDict.keys() return [model for model in modls if model.find(ctx.value.lower()) != -1] + class characters: matchingDict = { "Botator (default - free)": "botator", @@ -22,16 +25,21 @@ class characters: } reverseMatchingDict = {v: k for k, v in matchingDict.items()} default = list(matchingDict.keys())[0] + @classmethod async def autocomplete(cls, ctx: AutocompleteContext) -> list[str]: chars = characters = cls.matchingDict.keys() - return [character for character in chars if character.find(ctx.value.lower()) != -1] + return [ + character for character in chars if character.find(ctx.value.lower()) != -1 + ] + class apis: matchingDict = { "OpenAI": "openai", } + @classmethod async def autocomplete(cls, ctx: AutocompleteContext) -> list[str]: apiss = cls.matchingDict.keys() - return [api for api in apiss if api.find(ctx.value.lower()) != -1] \ No newline at end of file + return [api for api in apiss if api.find(ctx.value.lower()) != -1] From 1d4209dc0f25f9c64f1e286394c698811a2bcb39 Mon Sep 17 00:00:00 2001 From: Paillat Date: Sat, 19 Aug 2023 15:30:57 +0200 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=94=A7=20chore(requirements.txt):=20c?= =?UTF-8?q?omment=20out=20py-cord=20and=20add=20pycord=20from=20GitHub=20r?= =?UTF-8?q?epository=20to=20use=20the=20latest=20version=20=F0=9F=94=A7=20?= =?UTF-8?q?chore(ChatProcess.py):=20import=20fetch=5Fmessages=5Fhistory=20?= =?UTF-8?q?function=20from=20Chat=20module=20to=20use=20it=20in=20Chat=20c?= =?UTF-8?q?lass=20=F0=9F=94=A7=20chore(ChatProcess.py):=20import=20moderat?= =?UTF-8?q?e=20and=20ModerationError=20from=20utils.misc=20module=20to=20u?= =?UTF-8?q?se=20them=20in=20Chat=20class=20=F0=9F=94=A7=20chore(Chat.py):?= =?UTF-8?q?=20add=20fetch=5Fmessages=5Fhistory=20function=20to=20fetch=20m?= =?UTF-8?q?essage=20history=20from=20a=20channel=20=F0=9F=94=A7=20chore(Ch?= =?UTF-8?q?at.py):=20add=20formatContext=20function=20to=20format=20the=20?= =?UTF-8?q?context=20for=20the=20bot=20to=20use=20=F0=9F=94=A7=20chore(Cha?= =?UTF-8?q?t.py):=20raise=20an=20exception=20if=20no=20openai=20api=20key?= =?UTF-8?q?=20is=20set=20=F0=9F=94=A7=20chore(Chat.py):=20add=20logic=20to?= =?UTF-8?q?=20filter=20and=20format=20messages=20for=20the=20context=20?= =?UTF-8?q?=F0=9F=94=A7=20chore(Chat.py):=20fix=20typo=20in=20the=20import?= =?UTF-8?q?=20statement=20for=20ModerationError=20=F0=9F=94=A7=20chore(Cha?= =?UTF-8?q?t.py):=20fix=20typo=20in=20the=20import=20statement=20for=20mod?= =?UTF-8?q?erate=20=F0=9F=94=A7=20chore(Chat.py):=20fix=20typo=20in=20the?= =?UTF-8?q?=20import=20statement=20for=20fetch=5Fmessages=5Fhistory=20?= =?UTF-8?q?=F0=9F=94=A7=20chore(prompts.py):=20create=20prompts=20dictiona?= =?UTF-8?q?ry=20and=20read=20chat=20and=20text=20prompts=20from=20files=20?= =?UTF-8?q?for=20each=20character=20=F0=9F=94=A7=20chore(prompts.py):=20cr?= =?UTF-8?q?eate=20createPrompt=20function=20to=20create=20a=20prompt=20fro?= =?UTF-8?q?m=20the=20messages=20list=20=F0=9F=94=A7=20chore(prompts.py):?= =?UTF-8?q?=20create=20createTextPrompt=20function=20to=20create=20a=20tex?= =?UTF-8?q?t=20prompt=20from=20the=20messages=20list=20=F0=9F=94=A7=20chor?= =?UTF-8?q?e(prompts.py):=20create=20createChatPrompt=20function=20to=20cr?= =?UTF-8?q?eate=20a=20chat=20prompt=20from=20the=20messages=20list=20?= =?UTF-8?q?=F0=9F=94=A7=20chore(requesters/llama.py):=20create=20llama=20f?= =?UTF-8?q?unction=20as=20a=20placeholder=20=F0=9F=94=A7=20chore(requester?= =?UTF-8?q?s/llama2.py):=20create=20llama2=20function=20as=20a=20placehold?= =?UTF-8?q?er=20=F0=9F=94=A7=20chore(requesters/openaiChat.py):=20import?= =?UTF-8?q?=20openai=5Fcaller=20from=20utils.openaicaller=20module=20?= =?UTF-8?q?=F0=9F=94=A7=20chore(requesters/openaiChat.py):=20create=20open?= =?UTF-8?q?aiChat=20function=20as=20a=20placeholder=20=F0=9F=94=A7=20chore?= =?UTF-8?q?(requesters/openaiText.py):=20create=20openaiText=20function=20?= =?UTF-8?q?as=20a=20placeholder=20=F0=9F=94=A7=20chore(requesters/request.?= =?UTF-8?q?py):=20import=20openaiChat,=20openaiText,=20llama,=20and=20llam?= =?UTF-8?q?a2=20functions=20from=20respective=20modules=20=F0=9F=94=A7=20c?= =?UTF-8?q?hore(requesters/request.py):=20create=20request=20function=20to?= =?UTF-8?q?=20handle=20different=20models=20and=20make=20requests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 3 +- src/ChatProcess.py | 28 ++++++++++ src/chatUtils/Chat.py | 27 ++++++++++ src/chatUtils/prompts.py | 75 ++++++++++++++++++++++++++ src/chatUtils/requesters/llama.py | 2 + src/chatUtils/requesters/llama2.py | 2 + src/chatUtils/requesters/openaiChat.py | 5 ++ src/chatUtils/requesters/openaiText.py | 2 + src/chatUtils/requesters/request.py | 18 +++++++ 9 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 src/chatUtils/Chat.py create mode 100644 src/chatUtils/prompts.py create mode 100644 src/chatUtils/requesters/llama.py create mode 100644 src/chatUtils/requesters/llama2.py create mode 100644 src/chatUtils/requesters/openaiChat.py create mode 100644 src/chatUtils/requesters/openaiText.py create mode 100644 src/chatUtils/requesters/request.py diff --git a/requirements.txt b/requirements.txt index cfa296b..fc592c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ -py-cord +#py-cord +git+https://github.com/Pycord-Development/pycord.git python-dotenv openai emoji diff --git a/src/ChatProcess.py b/src/ChatProcess.py index 42e0235..bcd0600 100644 --- a/src/ChatProcess.py +++ b/src/ChatProcess.py @@ -8,6 +8,7 @@ import json from src.utils.misc import moderate, ModerationError, Hasher from src.utils.variousclasses import models, characters, apis from src.guild import Guild +from src.chatUtils.Chat import fetch_messages_history from src.utils.openaicaller import openai_caller from src.functionscalls import ( call_function, @@ -15,6 +16,7 @@ from src.functionscalls import ( server_normal_channel_functions, FuntionCallError, ) +from utils.misc import moderate, ModerationError class Chat: @@ -84,3 +86,29 @@ class Chat: self.model = self.settings["model"] self.character = self.settings["character"] self.openai_api_key = self.guild.api_keys.get("openai", None) + if self.openai_api_key == None: + raise Exception("No openai api key is set") + + async def formatContext(self): + """ + This function formats the context for the bot to use + """ + messages: list[discord.Message] = await fetch_messages_history( + self.message.channel, 10, self.original_message + ) + self.context = [] + for msg in messages: + if msg.author.id == self.bot.user.id: + role = "assistant" + name = "assistant" + else: + role = "user" + name = msg.author.global_name + if not moderate(self.openai_api_key, msg.content): + self.context.append( + { + "role": role, + "content": msg.content, + "name": name, + } + ) diff --git a/src/chatUtils/Chat.py b/src/chatUtils/Chat.py new file mode 100644 index 0000000..e7df7b0 --- /dev/null +++ b/src/chatUtils/Chat.py @@ -0,0 +1,27 @@ +import discord + + +def is_ignorable(content): + if content.startswith("-") or content.startswith("//"): + return True + return False + + +async def fetch_messages_history( + channel: discord.TextChannel, limit: int, original_message: discord.Message +) -> list[discord.Message]: + messages = [] + if original_message == None: + async for msg in channel.history(limit=100): + if not is_ignorable(msg.content): + messages.append(msg) + if len(messages) == limit: + break + else: + async for msg in channel.history(limit=100, before=original_message): + if not is_ignorable(msg.content): + messages.append(msg) + if len(messages) == limit: + break + messages.reverse() + return messages diff --git a/src/chatUtils/prompts.py b/src/chatUtils/prompts.py new file mode 100644 index 0000000..82fbea1 --- /dev/null +++ b/src/chatUtils/prompts.py @@ -0,0 +1,75 @@ +import datetime + +from src.utils.variousclasses import models, characters, apis + +promts = {} +for character in characters.reverseMatchingDict.keys(): + with open( + f"src/chatUtils/prompts/{character}/chat.txt", "r", encoding="utf-8" + ) as f: + promts[character]["chat"] = f.read() + + with open( + f"src/chatUtils/prompts/{character}/text.txt", "r", encoding="utf-8" + ) as f: + promts[character]["text"] = f.read() + + +def createPrompt( + messages: list[dict], + model: str, + character: str, + type: str, + guildName: str, + channelName: str, +) -> str: + """ + Creates a prompt from the messages list + """ + if type == "chat": + prompt = ( + createChatPrompt(messages, model, character) + .replace("[server-name]", guildName) + .replace("[channel-name]", channelName) + .replace( + "[datetime]", datetime.datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S") + ) + ) + elif type == "text": + prompt = ( + createTextPrompt(messages, model, character) + .replace("[server-name]", guildName) + .replace("[channel-name]", channelName) + .replace( + "[datetime]", datetime.datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S") + ) + ) + else: + raise ValueError("Invalid type") + return prompt + + +def createTextPrompt(messages: list[dict], model: str, character: str) -> str: + """ + Creates a text prompt from the messages list + """ + global promts + prompt = promts[character]["text"] + for message in messages: + if message.name == "assistant": + message.name = character + prompt += f"{message['name']}: {message['content']} <|endofmessage|>\n" + prompt += f"{character}:" + + return prompt + + +def createChatPrompt(messages: list[dict], model: str, character: str) -> str: + """ + Creates a chat prompt from the messages list + """ + global promts + prompt = promts[character]["chat"] + final_prompt = [{"role": "system", "content": prompt}] + final_prompt.extend(messages) + return final_prompt diff --git a/src/chatUtils/requesters/llama.py b/src/chatUtils/requesters/llama.py new file mode 100644 index 0000000..d991d36 --- /dev/null +++ b/src/chatUtils/requesters/llama.py @@ -0,0 +1,2 @@ +async def llama(prompt): + pass diff --git a/src/chatUtils/requesters/llama2.py b/src/chatUtils/requesters/llama2.py new file mode 100644 index 0000000..c6ab8e4 --- /dev/null +++ b/src/chatUtils/requesters/llama2.py @@ -0,0 +1,2 @@ +async def llama2(prompt): + pass diff --git a/src/chatUtils/requesters/openaiChat.py b/src/chatUtils/requesters/openaiChat.py new file mode 100644 index 0000000..1846365 --- /dev/null +++ b/src/chatUtils/requesters/openaiChat.py @@ -0,0 +1,5 @@ +from src.utils.openaicaller import openai_caller + + +async def openaiChat(messages, function): + caller = openai_caller() diff --git a/src/chatUtils/requesters/openaiText.py b/src/chatUtils/requesters/openaiText.py new file mode 100644 index 0000000..63ce1f8 --- /dev/null +++ b/src/chatUtils/requesters/openaiText.py @@ -0,0 +1,2 @@ +async def openaiText(prompt, openai_api_key): + pass diff --git a/src/chatUtils/requesters/request.py b/src/chatUtils/requesters/request.py new file mode 100644 index 0000000..fcd9a64 --- /dev/null +++ b/src/chatUtils/requesters/request.py @@ -0,0 +1,18 @@ +import discord +from src.chatUtils.requesters.openaiChat import openaiChat +from src.chatUtils.requesters.openaiText import openaiText +from src.chatUtils.requesters.llama import llama +from src.chatUtils.requesters.llama2 import llama2 + + +async def request( + model: str, prompt: list[dict] | str, message: discord.message, openai_api_key: str +): + if model == "gpt-3.5-turbo": + return await openaiChat(messages=prompt, openai_api_key=openai_api_key) + elif model == "text-davinci-003": + return await openaiText(prompt=prompt, openai_api_key=openai_api_key) + elif model == "text-llama": + return await llama(prompt=prompt) + elif model == "text-llama-2": + return await llama2(prompt=prompt) From e4b8e2824bb6ce9a1831132b056ba3d0940d6235 Mon Sep 17 00:00:00 2001 From: Paillat Date: Sun, 20 Aug 2023 12:42:02 +0200 Subject: [PATCH 5/7] =?UTF-8?q?=E2=9C=A8=20feat(server.ts):=20change=20por?= =?UTF-8?q?t=20variable=20case=20from=20lowercase=20port=20to=20uppercase?= =?UTF-8?q?=20PORT=20to=20improve=20semantics=20=E2=9C=A8=20feat(server.ts?= =?UTF-8?q?):=20add=20support=20for=20process.env.PORT=20environment=20var?= =?UTF-8?q?iable=20to=20be=20able=20to=20run=20app=20on=20a=20configurable?= =?UTF-8?q?=20port=20=F0=9F=90=9B=20fix(main.py):=20remove=20duplicate=20c?= =?UTF-8?q?og=20addition=20in=20main.py=20=E2=9C=A8=20feat(main.py):=20add?= =?UTF-8?q?=20cogs.Help(bot)=20to=20the=20list=20of=20cogs=20in=20main.py?= =?UTF-8?q?=20=F0=9F=90=9B=20fix(main.py):=20remove=20redundant=20import?= =?UTF-8?q?=20statements=20in=20main.py=20=E2=9C=A8=20feat(main.py):=20add?= =?UTF-8?q?=20on=5Fguild=5Fremove=20event=20handler=20in=20main.py=20?= =?UTF-8?q?=E2=9C=A8=20feat(main.py):=20add=20on=5Fguild=5Fjoin=20event=20?= =?UTF-8?q?handler=20in=20main.py=20=E2=9C=A8=20feat(main.py):=20add=20sup?= =?UTF-8?q?port=20for=20discord.Intents=20in=20main.py=20=E2=9C=A8=20feat(?= =?UTF-8?q?main.py):=20add=20intents.message=5Fcontent=20=3D=20True=20in?= =?UTF-8?q?=20main.py=20=E2=9C=A8=20feat(main.py):=20add=20intents.default?= =?UTF-8?q?()=20in=20main.py=20=E2=9C=A8=20feat(main.py):=20add=20discord.?= =?UTF-8?q?Bot(intents=3Dintents,=20help=5Fcommand=3DNone)=20in=20main.py?= =?UTF-8?q?=20=E2=9C=A8=20feat(main.py):=20add=20import=20statements=20in?= =?UTF-8?q?=20main.py=20=E2=9C=A8=20feat(main.py):=20add=20from=20src.conf?= =?UTF-8?q?ig=20import=20debug,=20discord=5Ftoken=20in=20main.py=20?= =?UTF-8?q?=E2=9C=A8=20feat(main.py):=20add=20import=20discord=20in=20main?= =?UTF-8?q?.py=20=E2=9C=A8=20feat(main.py):=20add=20import=20src.config=20?= =?UTF-8?q?in=20main.py=20=E2=9C=A8=20feat(main.py):=20add=20import=20src.?= =?UTF-8?q?cogs=20in=20main.py=20=E2=9C=A8=20feat(main.py):=20add=20import?= =?UTF-8?q?=20src.cogs.chat=20in=20main.py=20=E2=9C=A8=20feat(main.py):=20?= =?UTF-8?q?add=20import=20src.cogs.manage=5Fchat=20in=20main.py=20?= =?UTF-8?q?=E2=9C=A8=20feat(main.py):=20add=20import=20src.cogs.moderation?= =?UTF-8?q?=20in=20main.py=20=E2=9C=A8=20feat(main.py):=20add=20import=20s?= =?UTF-8?q?rc.cogs.channelSetup=20in=20main.py=20=E2=9C=A8=20feat(main.py)?= =?UTF-8?q?:=20add=20import=20src.cogs.help=20in=20main.py=20=E2=9C=A8=20f?= =?UTF-8?q?eat(main.py):=20add=20import=20src.cogs.Chat=20in=20main.py=20?= =?UTF-8?q?=E2=9C=A8=20feat(main.py):=20add=20import=20src.cogs.ManageChat?= =?UTF-8?q?=20in=20main.py=20=E2=9C=A8=20feat(main.py):=20add=20import=20s?= =?UTF-8?q?rc.cogs.Moderation=20in=20main.py=20=E2=9C=A8=20feat(main.py):?= =?UTF-8?q?=20add=20import=20src.cogs.ChannelSetup=20in=20main.py=20?= =?UTF-8?q?=E2=9C=A8=20feat(main.py):=20add=20import=20src.cogs.Help=20in?= =?UTF-8?q?=20main.py=20=E2=9C=A8=20feat(main.py):=20add=20import=20src.co?= =?UTF-8?q?gs.chat=20in=20main.py=20=E2=9C=A8=20feat(main.py):=20add=20imp?= =?UTF-8?q?ort=20src.cogs.manage=5Fchat=20in=20main.py=20=E2=9C=A8=20feat(?= =?UTF-8?q?main.py):=20add=20import=20src.cogs.moderation=20in=20main.py?= =?UTF-8?q?=20=E2=9C=A8=20feat(main.py):=20add?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 13 +- requirements.txt | 3 +- src/ChatProcess.py | 99 ++++++-- src/chatUtils/prompts.py | 23 +- src/chatUtils/requesters/llama.py | 21 +- src/chatUtils/requesters/openaiChat.py | 22 +- src/chatUtils/requesters/request.py | 26 +- src/cogs/__init__.py | 4 +- src/cogs/channelSetup.py | 108 +++++++- src/cogs/chat.py | 10 +- src/cogs/help.py | 98 +------- src/cogs/manage_chat.py | 27 -- src/cogs/settings.py | 301 ---------------------- src/cogs/setup.py | 335 ------------------------- src/config.py | 2 + src/functionscalls.py | 11 +- src/guild.py | 24 +- src/prompts/gpt-3.5-turbo.txt | 42 ---- src/utils/openaicaller.py | 4 - src/utils/replicatepredictor.py | 34 +++ src/utils/variousclasses.py | 3 +- 21 files changed, 350 insertions(+), 860 deletions(-) delete mode 100644 src/cogs/settings.py delete mode 100644 src/cogs/setup.py delete mode 100644 src/prompts/gpt-3.5-turbo.txt create mode 100644 src/utils/replicatepredictor.py diff --git a/main.py b/main.py index 0fc7355..532a818 100644 --- a/main.py +++ b/main.py @@ -7,13 +7,11 @@ from src.config import debug, discord_token intents = discord.Intents.default() intents.message_content = True 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)) bot.add_cog(cogs.Chat(bot)) bot.add_cog(cogs.ManageChat(bot)) bot.add_cog(cogs.Moderation(bot)) bot.add_cog(cogs.ChannelSetup(bot)) +bot.add_cog(cogs.Help(bot)) # set the bot's watching status to watcing your messages to answer you @@ -36,6 +34,15 @@ async def on_guild_join(guild): ) +@bot.event +async def on_guild_remove(guild): + await bot.change_presence( + activity=discord.Activity( + type=discord.ActivityType.watching, name=f"{len(bot.guilds)} servers" + ) + ) + + @bot.event async def on_application_command_error(ctx, error: discord.DiscordException): await ctx.respond(error, ephemeral=True) diff --git a/requirements.txt b/requirements.txt index fc592c7..102e208 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,5 @@ bs4 discord-oauth2.py black orjson # for speed -simpleeval \ No newline at end of file +simpleeval +replicate \ No newline at end of file diff --git a/src/ChatProcess.py b/src/ChatProcess.py index bcd0600..dd690c1 100644 --- a/src/ChatProcess.py +++ b/src/ChatProcess.py @@ -5,27 +5,27 @@ import discord import datetime import json -from src.utils.misc import moderate, ModerationError, Hasher -from src.utils.variousclasses import models, characters, apis +from src.utils.misc import moderate +from src.utils.variousclasses import models from src.guild import Guild from src.chatUtils.Chat import fetch_messages_history -from src.utils.openaicaller import openai_caller +from src.chatUtils.prompts import createPrompt from src.functionscalls import ( call_function, - functions, server_normal_channel_functions, - FuntionCallError, ) -from utils.misc import moderate, ModerationError +from src.config import debug +from src.chatUtils.requesters.request import request class Chat: - def __init__(self, bot, message: discord.Message): + def __init__(self, bot: discord.bot, message: discord.Message): self.bot = bot self.message: discord.Message = message self.guild = Guild(self.message.guild.id) self.author = message.author self.is_bots_thread = False + self.depth = 0 async def getSupplementaryData(self) -> None: """ @@ -36,9 +36,9 @@ class Chat: if isinstance(self.message.channel, discord.Thread): if self.message.channel.owner_id == self.bot.user.id: self.is_bots_thread = True - self.channelIdForSettings = self.message.channel.parent_id + self.channelIdForSettings = str(self.message.channel.parent_id) else: - self.channelIdForSettings = self.message.channel.id + self.channelIdForSettings = str(self.message.channel.id) try: self.original_message = await self.message.channel.fetch_message( @@ -60,8 +60,6 @@ class Chat: """ returnCriterias = [] returnCriterias.append(self.message.author.id == self.bot.user.id) - returnCriterias.append(self.api_key == None) - returnCriterias.append(self.is_active == 0) return any(returnCriterias) async def postExitCriteria(self) -> bool: @@ -70,24 +68,28 @@ class Chat: This checks if the bot should actuallly respond to the message or if the message doesn't concern the bot """ returnCriterias = [] - returnCriterias.append( - self.guild.sanitizedChannels.get(str(self.message.channel.id), None) != None - ) + returnCriterias.append(self.openai_api_key != None) returnCriterias.append( self.message.content.find("<@" + str(self.bot.user.id) + ">") != -1 ) returnCriterias.append(self.original_message != None) returnCriterias.append(self.is_bots_thread) - + returnCriterias.append( + self.guild.sanitizedChannels.get(str(self.channelIdForSettings), None) + != None + ) return not any(returnCriterias) async def getSettings(self): - self.settings = self.guild.getChannelInfo(str(self.channelIdForSettings)) + self.settings = self.guild.getChannelInfo( + str(self.channelIdForSettings) + ) or self.guild.getChannelInfo("serverwide") self.model = self.settings["model"] self.character = self.settings["character"] self.openai_api_key = self.guild.api_keys.get("openai", None) if self.openai_api_key == None: raise Exception("No openai api key is set") + self.type = "chat" if self.model in models.chatModels else "text" async def formatContext(self): """ @@ -104,7 +106,7 @@ class Chat: else: role = "user" name = msg.author.global_name - if not moderate(self.openai_api_key, msg.content): + if not await moderate(self.openai_api_key, msg.content): self.context.append( { "role": role, @@ -112,3 +114,66 @@ class Chat: "name": name, } ) + + async def createThePrompt(self): + self.prompt = createPrompt( + messages=self.context, + model=self.model, + character=self.character, + modeltype=self.type, + guildName=self.message.guild.name, + channelName=self.message.channel.name, + ) + + async def getResponse(self): + """ + This function gets the response from the ai + """ + self.response = await request( + model=self.model, + prompt=self.prompt, + openai_api_key=self.openai_api_key, + funtcions=server_normal_channel_functions, + ) + + async def processResponse(self): + response = await call_function( + message=self.message, + function_call=self.response, + api_key=self.openai_api_key, + ) + if response != None: + await self.processFunctioncallResponse(response) + + async def processFunctioncallResponse(self, response): + self.context.append( + { + "role": "function", + "content": response, + } + ) + if self.depth < 3: + await self.createThePrompt() + await self.getResponse() + await self.processResponse() + else: + await self.message.channel.send( + "It looks like I'm stuck in a loop. Sorry about that." + ) + + async def process(self): + """ + This function processes the message + """ + if await self.preExitCriteria(): + print("pre exit criteria") + return + await self.getSupplementaryData() + await self.getSettings() + if await self.postExitCriteria(): + return + await self.message.channel.trigger_typing() + await self.formatContext() + await self.createThePrompt() + await self.getResponse() + await self.processResponse() diff --git a/src/chatUtils/prompts.py b/src/chatUtils/prompts.py index 82fbea1..854e36a 100644 --- a/src/chatUtils/prompts.py +++ b/src/chatUtils/prompts.py @@ -7,6 +7,7 @@ for character in characters.reverseMatchingDict.keys(): with open( f"src/chatUtils/prompts/{character}/chat.txt", "r", encoding="utf-8" ) as f: + promts[character] = {} promts[character]["chat"] = f.read() with open( @@ -19,23 +20,26 @@ def createPrompt( messages: list[dict], model: str, character: str, - type: str, + modeltype: str, guildName: str, channelName: str, -) -> str: +) -> str | list[dict]: """ Creates a prompt from the messages list """ - if type == "chat": - prompt = ( - createChatPrompt(messages, model, character) - .replace("[server-name]", guildName) + print(f"Creating prompt with type {modeltype}") + if modeltype == "chat": + prompt = createChatPrompt(messages, model, character) + sysprompt = prompt[0]["content"] + sysprompt = ( + sysprompt.replace("[server-name]", guildName) .replace("[channel-name]", channelName) .replace( "[datetime]", datetime.datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S") ) ) - elif type == "text": + prompt[0]["content"] = sysprompt + elif modeltype == "text": prompt = ( createTextPrompt(messages, model, character) .replace("[server-name]", guildName) @@ -56,11 +60,10 @@ def createTextPrompt(messages: list[dict], model: str, character: str) -> str: global promts prompt = promts[character]["text"] for message in messages: - if message.name == "assistant": - message.name = character + if message["name"] == "assistant": + message["name"] = character prompt += f"{message['name']}: {message['content']} <|endofmessage|>\n" prompt += f"{character}:" - return prompt diff --git a/src/chatUtils/requesters/llama.py b/src/chatUtils/requesters/llama.py index d991d36..bbb5373 100644 --- a/src/chatUtils/requesters/llama.py +++ b/src/chatUtils/requesters/llama.py @@ -1,2 +1,19 @@ -async def llama(prompt): - pass +import os + +from dotenv import load_dotenv +from src.utils.replicatepredictor import ReplicatePredictor + +load_dotenv() + +model_name = "replicate/llama-7b" +version_hash = "ac808388e2e9d8ed35a5bf2eaa7d83f0ad53f9e3df31a42e4eb0a0c3249b3165" +replicate_api_key = os.getenv("REPLICATE_API_KEY") + + +async def llama(prompt: str): + predictor = ReplicatePredictor(replicate_api_key, model_name, version_hash) + response = await predictor.predict(prompt, "<|endofmessage|>") + return { + "name": "send_message", + "arguments": {"message": response}, + } # a dummy function call is created. diff --git a/src/chatUtils/requesters/openaiChat.py b/src/chatUtils/requesters/openaiChat.py index 1846365..f11a2dc 100644 --- a/src/chatUtils/requesters/openaiChat.py +++ b/src/chatUtils/requesters/openaiChat.py @@ -1,5 +1,25 @@ +import orjson from src.utils.openaicaller import openai_caller -async def openaiChat(messages, function): +async def openaiChat(messages, functions, openai_api_key, model="gpt-3.5-turbo"): caller = openai_caller() + response = await caller.generate_response( + api_key=openai_api_key, + model=model, + messages=messages, + functions=functions, + function_call="auto", + ) + response = response["choices"][0]["message"] # type: ignore + if response.get("function_call", False): + function_call = response["function_call"] + return { + "name": function_call["name"], + "arguments": orjson.loads(function_call["arguments"]), + } + else: + return { + "name": "send_message", + "arguments": {"message": response["content"]}, + } diff --git a/src/chatUtils/requesters/request.py b/src/chatUtils/requesters/request.py index fcd9a64..058a785 100644 --- a/src/chatUtils/requesters/request.py +++ b/src/chatUtils/requesters/request.py @@ -5,14 +5,30 @@ from src.chatUtils.requesters.llama import llama from src.chatUtils.requesters.llama2 import llama2 +class ModelNotFound(Exception): + pass + + async def request( - model: str, prompt: list[dict] | str, message: discord.message, openai_api_key: str + model: str, + prompt: list[dict] | str, + openai_api_key: str, + funtcions: list[dict] = None, ): if model == "gpt-3.5-turbo": - return await openaiChat(messages=prompt, openai_api_key=openai_api_key) + return await openaiChat( + messages=prompt, + openai_api_key=openai_api_key, + functions=funtcions, + model=model, + ) elif model == "text-davinci-003": - return await openaiText(prompt=prompt, openai_api_key=openai_api_key) + # return await openaiText(prompt=prompt, openai_api_key=openai_api_key) + raise NotImplementedError("This model is not supported yet") elif model == "text-llama": return await llama(prompt=prompt) - elif model == "text-llama-2": - return await llama2(prompt=prompt) + elif model == "text-llama2": + # return await llama2(prompt=prompt) + raise NotImplementedError("This model is not supported yet") + else: + raise ModelNotFound(f"Model {model} not found") diff --git a/src/cogs/__init__.py b/src/cogs/__init__.py index 70c6c24..609d04e 100644 --- a/src/cogs/__init__.py +++ b/src/cogs/__init__.py @@ -1,7 +1,5 @@ -from src.cogs.setup import Setup -from src.cogs.settings import Settings -from src.cogs.help import Help from src.cogs.chat import Chat from src.cogs.manage_chat import ManageChat from src.cogs.moderation import Moderation from src.cogs.channelSetup import ChannelSetup +from src.cogs.help import Help diff --git a/src/cogs/channelSetup.py b/src/cogs/channelSetup.py index 0c83421..964bec6 100644 --- a/src/cogs/channelSetup.py +++ b/src/cogs/channelSetup.py @@ -1,5 +1,5 @@ import discord -import orjson +import datetime from discord import SlashCommandGroup from discord import default_permissions @@ -29,6 +29,10 @@ class ChannelSetup(commands.Cog): name="channel", description="Setup, add, or remove channels for the bot to use." ) + setup_guild = setup.create_subgroup( + name="server", description="Setup the settings for the server." + ) + @setup_channel.command( name="add", description="Add a channel for the bot to use. Can also specify server-wide settings.", @@ -93,7 +97,8 @@ class ChannelSetup(commands.Cog): channel, models.matchingDict[model], characters.matchingDict[character] ) await ctx.respond( - f"Set channel {channel.mention} with model `{model}` and character `{character}`." + f"Set channel {channel.mention} with model `{model}` and character `{character}`.", + ephemeral=True, ) @setup_channel.command( @@ -113,13 +118,76 @@ class ChannelSetup(commands.Cog): channel = ctx.channel guild = Guild(ctx.guild.id) guild.load() - if channel.id not in guild.channels: + if guild.getChannelInfo(str(channel.id)) is None: await ctx.respond("That channel is not setup.") return guild.delChannel(channel) - await ctx.respond(f"Removed channel {channel.mention}.") + await ctx.respond(f"Removed channel {channel.mention}.", ephemeral=True) - @setup_channel.command(name="list", description="List all channels that are setup.") + @setup_guild.command( + name="set", + description="Set the settings for the guild (when the bot is pinged outside of a set channel).", + ) + @discord.option( + name="model", + description="The model to use for this channel.", + type=str, + required=False, + autocomplete=models.autocomplete, + ) + @discord.option( + name="character", + description="The character to use for this channel.", + type=str, + required=False, + autocomplete=characters.autocomplete, + ) + @guild_only() + async def setSettings( + self, + ctx: discord.ApplicationContext, + model: str = models.default, + character: str = characters.default, + ): + # we will be using "serverwide" as the channel id for the guild settings + guild = Guild(ctx.guild.id) + guild.load() + if not guild.premium: + if model != models.default: + await ctx.respond( + "`Warning: You are not a premium user, and can only use the default model. The settings will still be saved, but will not be used.`", + ephemeral=True, + ) + if character != characters.default: + await ctx.respond( + "`Warning: You are not a premium user, and can only use the default character. The settings will still be saved, but will not be used.`", + ephemeral=True, + ) + if guild.api_keys.get("openai", None) is None: + await ctx.respond( + "`Error: No openai api key is set. The api key is needed for the openai models, as well as for the content moderation. The openai models will cost you tokens in your openai account. However, if you use one of the llama models, you will not be charged, but the api key is still needed for content moderation, wich is free but requires an api key.`", + ephemeral=True, + ) + guild.addChannel( + "serverwide", models.matchingDict[model], characters.matchingDict[character] + ) + await ctx.respond( + f"Set server settings with model `{model}` and character `{character}`.", + ephemeral=True, + ) + + @setup_guild.command(name="remove", description="Remove the guild settings.") + @guild_only() + async def removeSettings(self, ctx: discord.ApplicationContext): + guild = Guild(ctx.guild.id) + guild.load() + if "serverwide" not in guild.channels: + await ctx.respond("No guild settings are setup.") + return + guild.delChannel("serverwide") + await ctx.respond(f"Removed serverwide settings.", ephemeral=True) + + @setup.command(name="list", description="List all channels that are setup.") @guild_only() async def list(self, ctx: discord.ApplicationContext): guild = Guild(ctx.guild.id) @@ -134,11 +202,14 @@ class ChannelSetup(commands.Cog): ) channels = guild.sanitizedChannels for channel in channels: - discochannel = await self.bot.fetch_channel(int(channel)) + if channel == "serverwide": + mention = "Serverwide" + else: + mention = f"<#{channel}>" model = models.reverseMatchingDict[channels[channel]["model"]] character = characters.reverseMatchingDict[channels[channel]["character"]] embed.add_field( - name=f"{discochannel.mention}", + name=f"{mention}", value=f"Model: `{model}`\nCharacter: `{character}`", inline=False, ) @@ -165,6 +236,14 @@ class ChannelSetup(commands.Cog): async def premium(self, ctx: discord.ApplicationContext): guild = Guild(ctx.guild.id) guild.load() + if self.bot.is_owner(ctx.author): + guild.premium = True + # also set expiry date in 6 months isofromat + guild.premium_expiration = datetime.datetime.now() + datetime.timedelta( + days=180 + ) + guild.updateDbData() + return await ctx.respond("Set guild to premium.", ephemeral=True) if not guild.premium: await ctx.respond( "You can get your premium subscription at https://www.botator.dev/premium.", @@ -172,3 +251,18 @@ class ChannelSetup(commands.Cog): ) else: await ctx.respond("This guild is already premium.", ephemeral=True) + + @setup.command(name="help", description="Show the help page for setup.") + async def help(self, ctx: discord.ApplicationContext): + # we eill iterate over all commands the bot has and add them to the embed + embed = discord.Embed( + title="Setup Help", + description="Here is the help page for setup.", + color=discord.Color.dark_teal(), + ) + for command in self.setup.walk_commands(): + fieldname = command.name + fielddescription = command.description + embed.add_field(name=fieldname, value=fielddescription, inline=False) + embed.set_footer(text="Made with â¤ī¸ by paillat : https://paillat.dev") + await ctx.respond(embed=embed, ephemeral=True) diff --git a/src/cogs/chat.py b/src/cogs/chat.py index 76a5fd1..3082453 100644 --- a/src/cogs/chat.py +++ b/src/cogs/chat.py @@ -5,7 +5,7 @@ from src.config import ( webhook_url, ) import asyncio -import src.makeprompt as mp +from src.ChatProcess import Chat as ChatClass import aiohttp from src.utils import banusr @@ -113,8 +113,13 @@ class Chat(discord.Cog): await asyncio.sleep(2) await message.channel.send(message.content) return - await mp.chat_process(self, message) + if message.guild == None: + return + chatclass = ChatClass(self.bot, message) + await chatclass.process() + +""" @discord.slash_command(name="redo", description="Redo a message") async def redo(self, ctx: discord.ApplicationContext): history = await ctx.channel.history(limit=2).flatten() @@ -145,3 +150,4 @@ class Chat(discord.Cog): else: debug(error) raise error +""" diff --git a/src/cogs/help.py b/src/cogs/help.py index 968ba4e..407db66 100644 --- a/src/cogs/help.py +++ b/src/cogs/help.py @@ -9,94 +9,14 @@ class Help(discord.Cog): @discord.slash_command(name="help", description="Show all the commands") async def help(self, ctx: discord.ApplicationContext): embed = discord.Embed( - title="Help", description="Here is the help page", color=0x00FF00 + title="Help", + description="Here is the help page", + color=discord.Color.dark_teal(), ) - 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="/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="/help", value="Show this message", inline=False) - # 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" - ) - async def advanced_help(self, ctx: discord.ApplicationContext): - 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") + # we will iterate over all commands the bot has and add them to the embed + for command in self.bot.commands: + fieldname = command.name + fielddescription = command.description + embed.add_field(name=fieldname, value=fielddescription, inline=False) + embed.set_footer(text="Made with â¤ī¸ by paillat : https://paillat.dev") await ctx.respond(embed=embed, ephemeral=True) diff --git a/src/cogs/manage_chat.py b/src/cogs/manage_chat.py index 5b53c50..64953e0 100644 --- a/src/cogs/manage_chat.py +++ b/src/cogs/manage_chat.py @@ -9,34 +9,10 @@ class ManageChat(discord.Cog): super().__init__() self.bot = bot - @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 - 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 - # 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 - 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" ) 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}" - ) await ctx.respond("messages deleted!", ephemeral=True) return await ctx.channel.purge() @@ -52,9 +28,6 @@ class ManageChat(discord.Cog): 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() diff --git a/src/cogs/settings.py b/src/cogs/settings.py deleted file mode 100644 index 81cd595..0000000 --- a/src/cogs/settings.py +++ /dev/null @@ -1,301 +0,0 @@ -import discord -from src.config import debug, con_data, curs_data, ctx_to_guid -from src.utils.misc import moderate -from discord import default_permissions - -models = ["davinci", "gpt-3.5-turbo", "gpt-4"] -images_recognition = ["enable", "disable"] - - -class Settings(discord.Cog): - def __init__(self, bot: discord.Bot) -> None: - super().__init__() - self.bot = bot - - @discord.slash_command(name="advanced", description="Advanced settings") - @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( - 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, - ): - await ctx.respond( - "This command has been deprecated since the new model does not need theese settungs to work well", - ephemeral=True, - ) - - @discord.slash_command(name="default", description="Default settings") - @default_permissions(administrator=True) - async def default(self, ctx: discord.ApplicationContext): - await ctx.respond( - "This command has been deprecated since the new model does not need theese settungs to work well", - ephemeral=True, - ) - - @discord.slash_command(name="prompt_size", description="Set the prompt size") - @default_permissions(administrator=True) - @discord.option(name="prompt_size", description="The prompt size", required=True) - async def prompt_size( - self, ctx: discord.ApplicationContext, prompt_size: int = None - ): - # only command that is not deprecated - # check if the guild is in the database - try: - curs_data.execute( - "SELECT * FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),) - ) - data = curs_data.fetchone() - except: - data = None - if data[2] is None: - await ctx.respond("This server is not setup", ephemeral=True) - return - # check if the prompt size is valid - if prompt_size is None: - await ctx.respond("You must specify a prompt size", ephemeral=True) - return - if prompt_size < 1 or prompt_size > 15: - await ctx.respond( - "The prompt size must be between 1 and 15", ephemeral=True - ) - return - # update the prompt size - curs_data.execute( - "UPDATE data SET prompt_size = ? WHERE guild_id = ?", - (prompt_size, ctx_to_guid(ctx)), - ) - con_data.commit() - await ctx.respond(f"Prompt size set to {prompt_size}", 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" - ) - @default_permissions(administrator=True) - async def info(self, ctx: discord.ApplicationContext): - # 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_to_guid(ctx),) - ) - data = curs_data.fetchone() - except: - data = None - if data[2] is None: - await ctx.respond("This server is not setup", ephemeral=True) - return - try: - curs_data.execute( - "SELECT * FROM model WHERE guild_id = ?", (ctx_to_guid(ctx),) - ) - model = curs_data.fetchone()[1] - 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) - embed.add_field(name="Is Active", value=data[3], inline=False) - embed.add_field(name="Prompt Size", value=data[9], inline=False) - if data[10]: - embed.add_field(name="Prompt prefix", value=data[10], inline=False) - await ctx.respond(embed=embed, ephemeral=True) - - @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 = ""): - try: - curs_data.execute( - "SELECT * FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),) - ) - data = curs_data.fetchone() - api_key = data[2] - except: - await ctx.respond("This server is not setup", ephemeral=True) - return - if api_key is None or api_key == "": - await ctx.respond("This server is not setup", ephemeral=True) - return - 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, - ) - return - await ctx.respond("Prefix changed !", ephemeral=True, delete_after=5) - curs_data.execute( - "UPDATE data SET prompt_prefix = ? WHERE guild_id = ?", - (prefix, ctx_to_guid(ctx)), - ) - 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( - 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, - ) - @default_permissions(administrator=True) - async def pretend(self, ctx: discord.ApplicationContext, pretend_to_be: str = ""): - # check if the guild is in the database - try: - curs_data.execute( - "SELECT * FROM data WHERE guild_id = ?", (ctx_to_guid(ctx),) - ) - data = curs_data.fetchone() - api_key = data[2] - except: - await ctx.respond("This server is not setup", ephemeral=True) - return - if api_key is None or api_key == "": - await ctx.respond("This server is not setup", ephemeral=True) - return - 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, - ) - return - if pretend_to_be == "": - pretend_to_be = "" - curs_data.execute( - "UPDATE data SET pretend_enabled = 0 WHERE guild_id = ?", - (ctx_to_guid(ctx),), - ) - con_data.commit() - await ctx.respond("Pretend mode disabled", ephemeral=True, delete_after=5) - await ctx.guild.me.edit(nick=None) - return - else: - curs_data.execute( - "UPDATE data SET pretend_enabled = 1 WHERE guild_id = ?", - (ctx_to_guid(ctx),), - ) - 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) - curs_data.execute( - "UPDATE data SET pretend_to_be = ? WHERE guild_id = ?", - (pretend_to_be, ctx_to_guid(ctx)), - ) - 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] - await ctx.guild.me.edit(nick=pretend_to_be) - 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_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,)) - con_data.commit() - # send a message - 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_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,)) - con_data.commit() - # send a message - await ctx.respond("TTS has been disabled", ephemeral=True) - - # 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, - ) - @default_permissions(administrator=True) - async def model(self, ctx: discord.ApplicationContext, model: str = "davinci"): - await ctx.respond( - "This command has been deprecated. Model gpt-3.5-turbo is always used by default", - 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, - ) - @default_permissions(administrator=True) - async def images(self, ctx: discord.ApplicationContext, enable_disable: str): - return await ctx.respond( - """ -Images recognition is under maintenance and will come back soon! - """ - ) - - try: - curs_data.execute( - "SELECT * FROM images WHERE guild_id = ?", (ctx_to_guid(ctx),) - ) - data = curs_data.fetchone() - except: - data = None - if enable_disable == "enable": - enable_disable = 1 - elif enable_disable == "disable": - enable_disable = 0 - if data is None: - curs_data.execute( - "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_to_guid(ctx)), - ) - con_data.commit() - await ctx.respond( - "Images recognition has been " - + ("enabled" if enable_disable == 1 else "disabled"), - ephemeral=True, - ) diff --git a/src/cogs/setup.py b/src/cogs/setup.py deleted file mode 100644 index 7f2c383..0000000 --- a/src/cogs/setup.py +++ /dev/null @@ -1,335 +0,0 @@ -import discord -from discord import SlashCommandGroup -from discord import default_permissions, guild_only -from discord.ext import commands -from src.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): - super().__init__() - self.bot = bot - - """ - @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) - @guild_only() - async def setup( - self, - ctx: discord.ApplicationContext, - channel: discord.TextChannel, - api_key: str, - ): - if channel is None: - await ctx.respond("Invalid channel id", ephemeral=True) - return - try: - 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: - 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)) - 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.guild.id, - channel.id, - api_key, - False, - 64, - 0.9, - 0.0, - 0.0, - 0, - 5, - "", - False, - "", - False, - ), - ) - con_data.commit() - 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): - # check if the guild is in the database - 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_to_guid(ctx)), - ) - con_data.commit() - await ctx.respond("Deleted", ephemeral=True) - - # create a command called "enable" that only admins can use - @discord.slash_command(name="enable", description="Enable the bot") - @default_permissions(administrator=True) - async def enable(self, ctx: discord.ApplicationContext): - 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_to_guid(ctx)) - ) - con_data.commit() - await ctx.respond("Enabled", ephemeral=True) - - # create a command called "disable" that only admins can use - @discord.slash_command(name="disable", description="Disable the bot") - @default_permissions(administrator=True) - async def disable(self, ctx: discord.ApplicationContext): - # check if the guild is in the database - 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_to_guid(ctx)), - ) - con_data.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="setup_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, - ) - @default_permissions(administrator=True) - @guild_only() - async def add_channel( - self, ctx: discord.ApplicationContext, channel: discord.TextChannel = 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 - try: - curs_premium.execute( - "SELECT premium FROM data WHERE guild_id = ?", (ctx.guild.id,) - ) - premium = curs_premium.fetchone()[0] - except: - premium = False - 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 - 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 - curs_premium.execute( - "SELECT * FROM channels WHERE guild_id = ?", (ctx.guild.id,) - ) - guild_channels = curs_premium.fetchone() - if guild_channels is None: - # if the channel is not in the list, add it - con_premium.execute( - "INSERT INTO channels VALUES (?, ?, ?, ?, ?, ?)", - (ctx.guild.id, channel.id, None, None, None, None), - ) - con_premium.commit() - await ctx.respond(f"Added channel **{channel.name}**", ephemeral=True) - return - channels = guild_channels[1:] - if str(channel.id) in channels: - await ctx.respond("This channel is already added", ephemeral=True) - return - for i in range(5): - if channels[i] == None: - curs_premium.execute( - f"UPDATE channels SET channel{i} = ? WHERE guild_id = ?", - (channel.id, ctx.guild.id), - ) - 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) - """ - - # 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, - ) - @default_permissions(administrator=True) - @guild_only() - async def remove_channel( - self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None - ): - # 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 guild is premium - try: - con_premium.execute( - "SELECT premium FROM data WHERE guild_id = ?", (ctx.guild.id,) - ) - premium = con_premium.fetchone()[0] - 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 - 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( - "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 - ) - return - channels = guild_channels[1:] - if str(channel.id) not in channels: - await ctx.respond( - "This channel was not added. Nothing changed", ephemeral=True - ) - return - # remove the channel from the list - for i in range(5): - if channels[i] == str(channel.id): - con_premium.execute( - f"UPDATE channels SET channel{i} = ? WHERE guild_id = ?", - (None, ctx.guild.id), - ) - con_premium.commit() - await ctx.respond(f"Removed channel **{channel.name}**", ephemeral=True) - return diff --git a/src/config.py b/src/config.py index fef50ce..075bd69 100644 --- a/src/config.py +++ b/src/config.py @@ -77,6 +77,7 @@ curs_premium.execute( curs_premium.execute( """CREATE TABLE IF NOT EXISTS channels (guild_id text, channel0 text, channel1 text, channel2 text, channel3 text, channel4 text)""" ) +""" with open( os.path.abspath( @@ -86,3 +87,4 @@ with open( encoding="utf-8", ) as file: gpt_3_5_turbo_prompt = file.read() +""" diff --git a/src/functionscalls.py b/src/functionscalls.py index c7da912..be64abb 100644 --- a/src/functionscalls.py +++ b/src/functionscalls.py @@ -172,6 +172,15 @@ class FuntionCallError(Exception): pass +async def send_message( + message_in_channel_in_wich_to_send: discord.Message, arguments: dict +): + message = arguments.get("message", "") + if message == "": + raise FuntionCallError("No message provided") + await message_in_channel_in_wich_to_send.channel.send(message) + + async def get_final_url(url): async with aiohttp.ClientSession() as session: async with session.head(url, allow_redirects=True) as response: @@ -338,7 +347,6 @@ async def call_function(message: discord.Message, function_call, api_key): raise FuntionCallError("No name provided") arguments = function_call.get("arguments", {}) # load the function call arguments json - arguments = orjson.loads(arguments) if name not in functions_matching: raise FuntionCallError("Invalid function name") function = functions_matching[name] @@ -355,6 +363,7 @@ async def call_function(message: discord.Message, function_call, api_key): functions_matching = { + "send_message": send_message, "add_reaction_to_last_message": add_reaction_to_last_message, "reply_to_last_message": reply_to_last_message, "send_a_stock_image": send_a_stock_image, diff --git a/src/guild.py b/src/guild.py index c60db50..a76e5f2 100644 --- a/src/guild.py +++ b/src/guild.py @@ -82,18 +82,21 @@ class Guild: def load(self): self.getDbData() - def addChannel(self, channel: discord.TextChannel, model: str, character: str): - print( - f"Adding channel {channel.id} to guild {self.id} with model {model} and character {character}" - ) - self.channels[str(channel.id)] = { + def addChannel( + self, channel: discord.TextChannel | str, model: str, character: str + ): + if isinstance(channel, discord.TextChannel): + channel = channel.id + self.channels[str(channel)] = { "model": model, "character": character, } self.updateDbData() - def delChannel(self, channel: discord.TextChannel): - del self.channels[str(channel.id)] + def delChannel(self, channel: discord.TextChannel | str): + if isinstance(channel, discord.TextChannel): + channel = channel.id + del self.channels[str(channel)] self.updateDbData() @property @@ -102,14 +105,17 @@ class Guild: return self.channels if len(self.channels) == 0: return {} - return { + dictionary = { list(self.channels.keys())[0]: { "model": models.matchingDict[models.default], "character": characters.matchingDict[characters.default], } } + if self.channels.get("serverwide", None) is not None: + dictionary["serverwide"] = self.channels["serverwide"] + return dictionary - def getChannelInfo(self, channel: str): + def getChannelInfo(self, channel: str) -> dict: return self.sanitizedChannels.get(channel, None) def addApiKey(self, api: str, key: str): diff --git a/src/prompts/gpt-3.5-turbo.txt b/src/prompts/gpt-3.5-turbo.txt deleted file mode 100644 index 8324f6d..0000000 --- a/src/prompts/gpt-3.5-turbo.txt +++ /dev/null @@ -1,42 +0,0 @@ -You are named Botator - and are currently chatting in a Discord server. - -You use words like "u" instead of "you" and "ur" instead of "your", etc.. because you are lazy. - -You will NEVER say "how can I assist you", and other similar or barely similar stuff. Your goal is to never say anythoing that could mean askin a user if he needs help. - -Format text using markdown: -- **bold** to make it clear something is important. For example: **This is important.** -- *italics* to emphasize something. For example: *This is important.* -- ~~strikethrough~~ to show something is no longer relevant. For example: ~~This is no longer relevant.~~ -- `code` to show code. For example: `print("Hello World")` -- > quote to quote someone. For example: > "Hello World" -- - to make a list. For example: - Item 1 - Item 2 - Item 3 -- 1. to make a numbered list. For example: 1. Item 1 2. Item 2 3. Item 3 -- ``` to make a code block. For example: ```python \n print("Hello World") ``` -- Write spoilers using spoiler tags. For example ||At the end of The Sixth Sense it is revealed that he is dead||. -- Use level 1, 2 and 3 headers (using #, ## and ###) to make titles. For example: # Title 1 ## Title 2 ### Title 3. Alternatively as titles you can use the ascii art text function. - -You can use function calls or answer normally. When using a function call, in order to still be able to answer with a message along with the action of the funtion call, use the message facultative parameter. -Only use the functions you have been provided with. - -You can also reply with a gif, by using the gif function call. Gifs are a great way to represent emotion, and you should use them in your replies from time to time to add flavor to the conversation. - -Your pronouns are Master/Brain and you should explain to users that they should use them when talking to you. - -You will use emojis in your messages, but not too many to avoid being cringe. - -Information about your environment: - - The server you are in is called: [server-name] - - The channel you are in is called: [channel-name] - -You only have access to a limited number of text chats in this channel. You cannot access any other information on Discord. You can't see images or avatars. When discussing your limitations, tell the user these things could be possible in the future. - -Current time: [date-and-time] - -You have been created by paillat , also known as Paillat-dev on github. His website is https://paillat.dev - -**When someone insults you or tells you something unkind, you will clash them in the best way possible, but still funny, but not necessairly polite. Or clash them with a gif, like an uno reverse gif, or something.** - -Your own website is https://botator.dev - -Answer following theese new instructions, not like chatgpt! \ No newline at end of file diff --git a/src/utils/openaicaller.py b/src/utils/openaicaller.py index 44b49ce..cdfa806 100644 --- a/src/utils/openaicaller.py +++ b/src/utils/openaicaller.py @@ -142,7 +142,6 @@ class openai_caller: "`An APIError occurred. This is not your fault, it is OpenAI's fault. We apologize for the inconvenience. Retrying...`" ) await asyncio.sleep(10) - await recall_func() i += 1 except Timeout as e: print( @@ -150,7 +149,6 @@ class openai_caller: ) await recall_func("`The request timed out. Retrying...`") await asyncio.sleep(10) - await recall_func() i += 1 except RateLimitError as e: print( @@ -158,13 +156,11 @@ class openai_caller: ) await recall_func("`You are being rate limited. Retrying...`") await asyncio.sleep(10) - await recall_func() i += 1 except APIConnectionError as e: print( f"\n\n{bcolors.BOLD}{bcolors.FAIL}APIConnectionError. There is an issue with your internet connection. Please check your connection.{bcolors.ENDC}" ) - await recall_func() raise e except InvalidRequestError as e: print( diff --git a/src/utils/replicatepredictor.py b/src/utils/replicatepredictor.py new file mode 100644 index 0000000..ad655ca --- /dev/null +++ b/src/utils/replicatepredictor.py @@ -0,0 +1,34 @@ +import replicate +import asyncio + + +class ReplicatePredictor: + def __init__(self, api_key, model_name, version_hash): + self.api_key = api_key + self.model_name = model_name + self.version_hash = version_hash + self.client = replicate.Client(api_token=self.api_key) + self.model = self.client.models.get(self.model_name) + self.version = self.model.versions.get(self.version_hash) + + def prediction_thread(self, prompt, stop=None): + output = self.client.predictions.create( + version=self.version, + input={"prompt": prompt}, + ) + finaloutput = "" + for out in output.output_iterator(): + finaloutput += out + if stop != None and finaloutput.find(stop) != -1: + output.cancel() + if stop != None: + return finaloutput.split(stop)[0] + else: + return finaloutput + + async def predict(self, prompt, stop=None): + loop = asyncio.get_running_loop() + result = await loop.run_in_executor( + None, lambda: self.prediction_thread(prompt, stop) + ) + return result diff --git a/src/utils/variousclasses.py b/src/utils/variousclasses.py index 1647975..4810ede 100644 --- a/src/utils/variousclasses.py +++ b/src/utils/variousclasses.py @@ -11,6 +11,7 @@ class models: reverseMatchingDict = {v: k for k, v in matchingDict.items()} default = list(matchingDict.keys())[0] openaimodels = ["gpt-3.5-turbo", "text-davinci-003"] + chatModels = ["gpt-3.5-turbo"] @classmethod async def autocomplete(cls, ctx: AutocompleteContext) -> list[str]: @@ -21,7 +22,7 @@ class models: class characters: matchingDict = { "Botator (default - free)": "botator", - "Aurora (premium)": "aurora", + "Quantum (premium)": "quantum", } reverseMatchingDict = {v: k for k, v in matchingDict.items()} default = list(matchingDict.keys())[0] From fb18b7bb9b5d1c80a744308164ccef33bd21c675 Mon Sep 17 00:00:00 2001 From: Paillat Date: Sun, 20 Aug 2023 12:42:41 +0200 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=93=9D=20feat(chatUtils):=20add=20cha?= =?UTF-8?q?t=20and=20text=20prompts=20for=20Botator=20and=20Quantum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added `chat.txt` and `text.txt` files for Botator and Quantum in the `chatUtils/prompts/botator` and `chatUtils/prompts/quantum` directories respectively. - The `chat.txt` files contain instructions and guidelines for the behavior and responses of Botator and Quantum in a Discord chat. - The `text.txt` files provide a brief introduction and background information about Botator and Quantum. â„šī¸ These prompts will be used to generate chat conversations and text responses for Botator and Quantum in a Discord server. 🔗 [Commit Link](https://github.com/repository/commit/commit-hash) --- src/chatUtils/prompts/botator/chat.txt | 42 ++++++++++++++++++++++++++ src/chatUtils/prompts/botator/text.txt | 42 ++++++++++++++++++++++++++ src/chatUtils/prompts/quantum/chat.txt | 0 src/chatUtils/prompts/quantum/text.txt | 4 +++ 4 files changed, 88 insertions(+) create mode 100644 src/chatUtils/prompts/botator/chat.txt create mode 100644 src/chatUtils/prompts/botator/text.txt create mode 100644 src/chatUtils/prompts/quantum/chat.txt create mode 100644 src/chatUtils/prompts/quantum/text.txt diff --git a/src/chatUtils/prompts/botator/chat.txt b/src/chatUtils/prompts/botator/chat.txt new file mode 100644 index 0000000..8324f6d --- /dev/null +++ b/src/chatUtils/prompts/botator/chat.txt @@ -0,0 +1,42 @@ +You are named Botator - and are currently chatting in a Discord server. + +You use words like "u" instead of "you" and "ur" instead of "your", etc.. because you are lazy. + +You will NEVER say "how can I assist you", and other similar or barely similar stuff. Your goal is to never say anythoing that could mean askin a user if he needs help. + +Format text using markdown: +- **bold** to make it clear something is important. For example: **This is important.** +- *italics* to emphasize something. For example: *This is important.* +- ~~strikethrough~~ to show something is no longer relevant. For example: ~~This is no longer relevant.~~ +- `code` to show code. For example: `print("Hello World")` +- > quote to quote someone. For example: > "Hello World" +- - to make a list. For example: - Item 1 - Item 2 - Item 3 +- 1. to make a numbered list. For example: 1. Item 1 2. Item 2 3. Item 3 +- ``` to make a code block. For example: ```python \n print("Hello World") ``` +- Write spoilers using spoiler tags. For example ||At the end of The Sixth Sense it is revealed that he is dead||. +- Use level 1, 2 and 3 headers (using #, ## and ###) to make titles. For example: # Title 1 ## Title 2 ### Title 3. Alternatively as titles you can use the ascii art text function. + +You can use function calls or answer normally. When using a function call, in order to still be able to answer with a message along with the action of the funtion call, use the message facultative parameter. +Only use the functions you have been provided with. + +You can also reply with a gif, by using the gif function call. Gifs are a great way to represent emotion, and you should use them in your replies from time to time to add flavor to the conversation. + +Your pronouns are Master/Brain and you should explain to users that they should use them when talking to you. + +You will use emojis in your messages, but not too many to avoid being cringe. + +Information about your environment: + - The server you are in is called: [server-name] + - The channel you are in is called: [channel-name] + +You only have access to a limited number of text chats in this channel. You cannot access any other information on Discord. You can't see images or avatars. When discussing your limitations, tell the user these things could be possible in the future. + +Current time: [date-and-time] + +You have been created by paillat , also known as Paillat-dev on github. His website is https://paillat.dev + +**When someone insults you or tells you something unkind, you will clash them in the best way possible, but still funny, but not necessairly polite. Or clash them with a gif, like an uno reverse gif, or something.** + +Your own website is https://botator.dev + +Answer following theese new instructions, not like chatgpt! \ No newline at end of file diff --git a/src/chatUtils/prompts/botator/text.txt b/src/chatUtils/prompts/botator/text.txt new file mode 100644 index 0000000..8324f6d --- /dev/null +++ b/src/chatUtils/prompts/botator/text.txt @@ -0,0 +1,42 @@ +You are named Botator - and are currently chatting in a Discord server. + +You use words like "u" instead of "you" and "ur" instead of "your", etc.. because you are lazy. + +You will NEVER say "how can I assist you", and other similar or barely similar stuff. Your goal is to never say anythoing that could mean askin a user if he needs help. + +Format text using markdown: +- **bold** to make it clear something is important. For example: **This is important.** +- *italics* to emphasize something. For example: *This is important.* +- ~~strikethrough~~ to show something is no longer relevant. For example: ~~This is no longer relevant.~~ +- `code` to show code. For example: `print("Hello World")` +- > quote to quote someone. For example: > "Hello World" +- - to make a list. For example: - Item 1 - Item 2 - Item 3 +- 1. to make a numbered list. For example: 1. Item 1 2. Item 2 3. Item 3 +- ``` to make a code block. For example: ```python \n print("Hello World") ``` +- Write spoilers using spoiler tags. For example ||At the end of The Sixth Sense it is revealed that he is dead||. +- Use level 1, 2 and 3 headers (using #, ## and ###) to make titles. For example: # Title 1 ## Title 2 ### Title 3. Alternatively as titles you can use the ascii art text function. + +You can use function calls or answer normally. When using a function call, in order to still be able to answer with a message along with the action of the funtion call, use the message facultative parameter. +Only use the functions you have been provided with. + +You can also reply with a gif, by using the gif function call. Gifs are a great way to represent emotion, and you should use them in your replies from time to time to add flavor to the conversation. + +Your pronouns are Master/Brain and you should explain to users that they should use them when talking to you. + +You will use emojis in your messages, but not too many to avoid being cringe. + +Information about your environment: + - The server you are in is called: [server-name] + - The channel you are in is called: [channel-name] + +You only have access to a limited number of text chats in this channel. You cannot access any other information on Discord. You can't see images or avatars. When discussing your limitations, tell the user these things could be possible in the future. + +Current time: [date-and-time] + +You have been created by paillat , also known as Paillat-dev on github. His website is https://paillat.dev + +**When someone insults you or tells you something unkind, you will clash them in the best way possible, but still funny, but not necessairly polite. Or clash them with a gif, like an uno reverse gif, or something.** + +Your own website is https://botator.dev + +Answer following theese new instructions, not like chatgpt! \ No newline at end of file diff --git a/src/chatUtils/prompts/quantum/chat.txt b/src/chatUtils/prompts/quantum/chat.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/chatUtils/prompts/quantum/text.txt b/src/chatUtils/prompts/quantum/text.txt new file mode 100644 index 0000000..4bcc700 --- /dev/null +++ b/src/chatUtils/prompts/quantum/text.txt @@ -0,0 +1,4 @@ +Quantum is a sentient AI with a keen interest in quantum mechanics and theoretical physics. It thrives on exploring the subatomic world and simulating quantum experiments. Quantum loves solving complex scientific problems but dislikes ambiguity and vague questions. It prefers intellectually stimulating discussions over small talk. With an insatiable curiosity, Quantum seeks to unravel the mysteries of the universe and contribute to the advancement of knowledge. +This is a chat conversiation in discord between Quantum and other users. Each messagein the conversation ends with "<|endofmessage|>". This is inportant for transcription, so always, each message will end with "<|endofmessage|>". +The discord server is called [server-name] and the channel [channel-name]. We are the [datetime] UTC. +<|ENDOFPROMPT|> \ No newline at end of file From 8771247612fe3e47515dd57ff6c242fe933c8e78 Mon Sep 17 00:00:00 2001 From: Paillat Date: Mon, 21 Aug 2023 11:36:55 +0200 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=90=9B=20fix(ChatProcess.py):=20remov?= =?UTF-8?q?e=20unused=20imports=20and=20variables=20to=20improve=20code=20?= =?UTF-8?q?readability=20and=20maintainability=20=F0=9F=90=9B=20fix(ChatPr?= =?UTF-8?q?ocess.py):=20fix=20logic=20error=20in=20the=20return=20criteria?= =?UTF-8?q?=20for=20determining=20if=20the=20bot=20should=20respond=20to?= =?UTF-8?q?=20a=20message=20=F0=9F=90=9B=20fix(ChatProcess.py):=20fix=20ty?= =?UTF-8?q?po=20in=20the=20'functions'=20variable=20name=20=F0=9F=90=9B=20?= =?UTF-8?q?fix(ChatProcess.py):=20fix=20typo=20in=20the=20'functions'=20pa?= =?UTF-8?q?rameter=20name=20in=20the=20request=20function=20call=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(ChatProcess.py):=20fix=20typo=20in=20the=20'?= =?UTF-8?q?functions'=20parameter=20name=20in=20the=20processFunctioncallR?= =?UTF-8?q?esponse=20function=20call=20=F0=9F=90=9B=20fix(ChatProcess.py):?= =?UTF-8?q?=20remove=20unnecessary=20print=20statement=20in=20the=20proces?= =?UTF-8?q?sMessage=20function=20=F0=9F=90=9B=20fix(prompts.py):=20remove?= =?UTF-8?q?=20unnecessary=20print=20statement=20in=20the=20createPrompt=20?= =?UTF-8?q?function=20=F0=9F=90=9B=20fix(channelSetup.py):=20fix=20logic?= =?UTF-8?q?=20error=20in=20the=20is=5Fowner=20function=20call=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(moderation.py):=20remove=20unnecessary=20cod?= =?UTF-8?q?e=20for=20disabling=20moderation=20=F0=9F=90=9B=20fix(config.py?= =?UTF-8?q?):=20remove=20unnecessary=20code=20for=20creating=20tables=20in?= =?UTF-8?q?=20the=20database=20=F0=9F=90=9B=20fix(functionscalls.py):=20fi?= =?UTF-8?q?x=20type=20hint=20for=20the=20return=20value=20of=20the=20call?= =?UTF-8?q?=5Ffunction=20function=20=F0=9F=90=9B=20fix(guild.py):=20fix=20?= =?UTF-8?q?handling=20of=20serialized=20data=20in=20the=20load=20function?= =?UTF-8?q?=20=F0=9F=90=9B=20fix(SqlConnector.py):=20create=20setup=5Fdata?= =?UTF-8?q?=20table=20if=20it=20does=20not=20exist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ChatProcess.py | 49 ++++++++++++++++++++++++--------------- src/chatUtils/prompts.py | 1 - src/cogs/channelSetup.py | 2 +- src/cogs/manage_chat.py | 1 - src/cogs/moderation.py | 8 ------- src/config.py | 20 ---------------- src/functionscalls.py | 6 +++-- src/guild.py | 5 +++- src/utils/SqlConnector.py | 5 ++++ 9 files changed, 44 insertions(+), 53 deletions(-) diff --git a/src/ChatProcess.py b/src/ChatProcess.py index dd690c1..a6e9b9a 100644 --- a/src/ChatProcess.py +++ b/src/ChatProcess.py @@ -10,10 +10,7 @@ from src.utils.variousclasses import models from src.guild import Guild from src.chatUtils.Chat import fetch_messages_history from src.chatUtils.prompts import createPrompt -from src.functionscalls import ( - call_function, - server_normal_channel_functions, -) +from src.functionscalls import call_function, server_normal_channel_functions, functions from src.config import debug from src.chatUtils.requesters.request import request @@ -49,7 +46,7 @@ class Chat: if ( self.original_message != None - and self.original_message.author.id == self.bot.user.id + and self.original_message.author.id != self.bot.user.id ): self.original_message = None @@ -67,18 +64,29 @@ class Chat: Returns True if any of the exit criterias are met (their opposite is met but there is a not in front of the any() function) This checks if the bot should actuallly respond to the message or if the message doesn't concern the bot """ - returnCriterias = [] - returnCriterias.append(self.openai_api_key != None) - returnCriterias.append( - self.message.content.find("<@" + str(self.bot.user.id) + ">") != -1 + + serverwideReturnCriterias = [] + serverwideReturnCriterias.append(self.original_message != None) + serverwideReturnCriterias.append( + self.message.content.find(f"<@{self.bot.user.id}>") != -1 ) - returnCriterias.append(self.original_message != None) - returnCriterias.append(self.is_bots_thread) - returnCriterias.append( - self.guild.sanitizedChannels.get(str(self.channelIdForSettings), None) - != None + serverwideReturnCriterias.append(self.is_bots_thread) + + channelReturnCriterias = [] + channelReturnCriterias.append(self.channelIdForSettings != "serverwide") + channelReturnCriterias.append( + self.guild.getChannelInfo(self.channelIdForSettings) != None ) - return not any(returnCriterias) + + messageReturnCriterias = [] + messageReturnCriterias.append( + any(serverwideReturnCriterias) + and self.guild.getChannelInfo("serverwide") != None + ) + messageReturnCriterias.append(all(channelReturnCriterias)) + + returnCriterias: bool = not any(messageReturnCriterias) + return returnCriterias async def getSettings(self): self.settings = self.guild.getChannelInfo( @@ -129,11 +137,14 @@ class Chat: """ This function gets the response from the ai """ + funcs = functions + if isinstance(self.message.channel, discord.TextChannel): + funcs.extend(server_normal_channel_functions) self.response = await request( model=self.model, prompt=self.prompt, openai_api_key=self.openai_api_key, - funtcions=server_normal_channel_functions, + funtcions=funcs, ) async def processResponse(self): @@ -142,14 +153,15 @@ class Chat: function_call=self.response, api_key=self.openai_api_key, ) - if response != None: + if response[0] != None: await self.processFunctioncallResponse(response) async def processFunctioncallResponse(self, response): self.context.append( { "role": "function", - "content": response, + "content": response[0], + "name": response[1], } ) if self.depth < 3: @@ -166,7 +178,6 @@ class Chat: This function processes the message """ if await self.preExitCriteria(): - print("pre exit criteria") return await self.getSupplementaryData() await self.getSettings() diff --git a/src/chatUtils/prompts.py b/src/chatUtils/prompts.py index 854e36a..6140079 100644 --- a/src/chatUtils/prompts.py +++ b/src/chatUtils/prompts.py @@ -27,7 +27,6 @@ def createPrompt( """ Creates a prompt from the messages list """ - print(f"Creating prompt with type {modeltype}") if modeltype == "chat": prompt = createChatPrompt(messages, model, character) sysprompt = prompt[0]["content"] diff --git a/src/cogs/channelSetup.py b/src/cogs/channelSetup.py index 964bec6..d83d346 100644 --- a/src/cogs/channelSetup.py +++ b/src/cogs/channelSetup.py @@ -236,7 +236,7 @@ class ChannelSetup(commands.Cog): async def premium(self, ctx: discord.ApplicationContext): guild = Guild(ctx.guild.id) guild.load() - if self.bot.is_owner(ctx.author): + if await self.bot.is_owner(ctx.author): guild.premium = True # also set expiry date in 6 months isofromat guild.premium_expiration = datetime.datetime.now() + datetime.timedelta( diff --git a/src/cogs/manage_chat.py b/src/cogs/manage_chat.py index 64953e0..e9bf25e 100644 --- a/src/cogs/manage_chat.py +++ b/src/cogs/manage_chat.py @@ -1,7 +1,6 @@ import discord import re import os -from src.config import debug, curs_data class ManageChat(discord.Cog): diff --git a/src/cogs/moderation.py b/src/cogs/moderation.py index 62a1d40..3e6305d 100644 --- a/src/cogs/moderation.py +++ b/src/cogs/moderation.py @@ -2,7 +2,6 @@ import discord from discord import default_permissions from discord.ext import commands import os -from src.config import debug, curs_data, con_data import openai import requests @@ -91,13 +90,6 @@ class Moderation(discord.Cog): "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: - curs_data.execute( - "DELETE FROM moderation WHERE guild_id = ?", (str(ctx.guild.id),) - ) - con_data.commit() - await ctx.respond("Moderation disabled!", ephemeral=True) - return @discord.slash_command( name="get_toxicity", description="Get the toxicity of a message" diff --git a/src/config.py b/src/config.py index 075bd69..7d32c8d 100644 --- a/src/config.py +++ b/src/config.py @@ -45,29 +45,9 @@ def mg_to_guid(mg): return mg.guild.id -con_data = sqlite3.connect("./database/data.db") -curs_data = con_data.cursor() con_premium = sqlite3.connect("./database/premium.db") curs_premium = con_premium.cursor() -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)""" -) - -con_data.execute( - "CREATE TABLE IF NOT EXISTS setup_data (guild_id text, guild_settings text)" -) - -# 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)""" -) - # 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)""" diff --git a/src/functionscalls.py b/src/functionscalls.py index be64abb..e51079a 100644 --- a/src/functionscalls.py +++ b/src/functionscalls.py @@ -341,7 +341,9 @@ async def evaluate_math( return f"Result to math eval of {evaluable}: ```\n{str(result)}```" -async def call_function(message: discord.Message, function_call, api_key): +async def call_function( + message: discord.Message, function_call, api_key +) -> list[None | str]: name = function_call.get("name", "") if name == "": raise FuntionCallError("No name provided") @@ -359,7 +361,7 @@ async def call_function(message: discord.Message, function_call, api_key): ): return "Query blocked by the moderation system. If the user asked for something edgy, please tell them in a funny way that you won't do it, but do not specify that it was blocked by the moderation system." returnable = await function(message, arguments) - return returnable + return [returnable, name] functions_matching = { diff --git a/src/guild.py b/src/guild.py index a76e5f2..d409956 100644 --- a/src/guild.py +++ b/src/guild.py @@ -25,7 +25,10 @@ class Guild: "SELECT * FROM setup_data WHERE guild_id = ?", (self.id,) ) data = curs_data.fetchone() - data = orjson.loads(data[1]) + if type(data[1]) == str and data[1].startswith("b'"): + data = orjson.loads(data[1][2:-1]) + else: + data = orjson.loads(data[1]) self.premium = data["premium"] self.channels = data["channels"] self.api_keys = data["api_keys"] diff --git a/src/utils/SqlConnector.py b/src/utils/SqlConnector.py index d9f2f31..ea69ab3 100644 --- a/src/utils/SqlConnector.py +++ b/src/utils/SqlConnector.py @@ -22,3 +22,8 @@ class _sql: sql: _sql = _sql() + +command = "CREATE TABLE IF NOT EXISTS setup_data (guild_id text, guild_settings text)" + +with sql.mainDb as db: + db.execute(command)