diff --git a/.gitignore b/.gitignore index c7a4223..4365f89 100644 --- a/.gitignore +++ b/.gitignore @@ -162,4 +162,5 @@ cython_debug/ key.txt data.db premium-key.txt -premium.db \ No newline at end of file +premium.db +guildscount.py \ No newline at end of file diff --git a/README.md b/README.md index f16b228..6161351 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ You can always disable the bot by doing **/disable** and delete your api key fro */default* - Set the advanced settings to their default values +*/redo* - Redo the last answer + */help* - Show this command list # Support me @@ -75,9 +77,10 @@ After that you will normally be able to access some new channels in our discord - [ ] Publish a GOOD docker image on dockerhub and add some more instructions about how to selfhost - [ ] Add a log and updates channel option and a way for devs to send messages to that channel on all servers. - [ ] Add moderation. -- [ ] Add DateHour in prompts -- [ ] Add /redo - [ ] Add TOKENS warnings (when setting the bot up, people dosen't understand tha ot uses their tokens) +- [ ] Add a /continue command - you know +- [x] Add DateHour in prompts +- [x] Add /redo - [x] Add uses count reset after 24h - [x] Organize code in COGs - [x] add way to consider the answers to the bot's messages. diff --git a/code/code.py b/code/code.py index 895fc06..55b40b7 100644 --- a/code/code.py +++ b/code/code.py @@ -1,13 +1,16 @@ #coucou c'est fives # wesh wesh ici latouff import discord # pip install pycord +from discord import Intents import asyncio # pip install asyncio import cogs # import the cogs import datetime # pip install datetime -from config import debug, conn, c # import the debug function and the database connection +from config import debug, conn, c # import the debug function and the database connectionimport apsw # pip install apsw. ApSW is a Python interface to SQLite 3 +#add the message content intent to the bot, aka discord.Intents.default() and discord.Intents.message_content intents = discord.Intents.default() intents.message_content = True intents.members = True +import apsw # pip install apsw. ApSW is a Python interface to SQLite 3 bot = discord.Bot(intents=intents, help_command=None) # create the bot bot.add_cog(cogs.Setup(bot)) bot.add_cog(cogs.Settings(bot)) @@ -19,32 +22,14 @@ async def on_ready(): await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name="your messages to answer you")) debug("Bot is ready") -''' -def reset_uses_count_today(): - c.execute("UPDATE data SET uses_count_today = 0") - conn.commit() -#get the current date and save it in the previous_date variable -#if the day number is different from the previous day number, reset the uses count today -def check_day(): - global previous_date - if datetime.datetime.now().day != previous_date.day: - previous_date = datetime.datetime.now() - previous_date = datetime.datetime.now() - return True - else: - previous_date = datetime.datetime.now() - return False -#run check_day every 10 seconds -async def check_day_task(): - while True: - check_day() - await asyncio.sleep(60) -#add a task to the bot that runs check_day every 1 minute -bot.loop.create_task(check_day_task()) -''' #run the bot # Replace the following with your bot's token with open("./key.txt") as f: key = f.read() bot.run(key) +#set the bot's watching status to watcing your messages to answer you +@bot.event +async def on_ready(): + await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name="your messages to answer you")) + debug("Bot is ready") diff --git a/code/cogs/chat.py b/code/cogs/chat.py index f61c0b0..ae62aef 100644 --- a/code/cogs/chat.py +++ b/code/cogs/chat.py @@ -5,6 +5,7 @@ import openai from config import debug, c, max_uses, cp, conn, connp import random import threading +import time class Chat (discord.Cog) : def __init__(self, bot: discord.Bot): super().__init__() @@ -18,48 +19,99 @@ class Chat (discord.Cog) : @discord.slash_command(name="say", description="Say a message") async def say(self, ctx: discord.ApplicationContext, message: str): - debug(f"The user {ctx.author.display_name} ran the say command command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}") + print(f"The user {ctx.author.display_name} ran the say command command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}") await ctx.respond("Message sent !", ephemeral=True) await ctx.send(message) -async def on_message_process(message, self): + @discord.slash_command(name="redo", description="Redo a message") + async def redo(self, ctx: discord.ApplicationContext): + #first delete the last message but only if it was sent by the bot + # get the last message + history = await ctx.channel.history(limit=2).flatten() + message_to_delete = history[0] + message_to_redo = history[1] + if message_to_delete.author.id == self.bot.user.id: + await message_to_delete.delete() + else: + await ctx.respond("The last message wasn't sent by the bot", ephemeral=True) + return + #get the message to redo aka the last message, because the old last message has been deleted + #get the message before the last message, because the new last message is the bot thinking message, so the message before the last message is the message to redo + if message_to_redo.author.id == self.bot.user.id: + await ctx.respond("The message to redo was sent by the bot", ephemeral=True) + return + loop = asyncio.get_event_loop() + thread = threading.Thread(target=asyncio.run_coroutine_threadsafe, args=(on_message_process(message_to_redo, self), loop)) + thread.start() + await ctx.respond("Message redone !", delete_after=1) + +async def on_message_process(message: discord.Message, self: Chat): #my code - #debug the thread id - debug(f"Thread id: {threading.get_ident()}") + #print the thread id + print(f"Thread id: {threading.get_ident()}") + print("hello") if message.author.bot: + print("The message was sent by a bot") return + print("The message was sent by a human") #check if the guild is in the database c.execute("SELECT * FROM data WHERE guild_id = ?", (message.guild.id,)) if c.fetchone() is None: + print("The guild is not in the database") return + print("The guild is in the database") #check if the bot is enabled c.execute("SELECT is_active FROM data WHERE guild_id = ?", (message.guild.id,)) if c.fetchone()[0] == False: + print("The bot is disabled") return + print("The bot is enabled") #check if the message has been sent in the channel set in the database c.execute("SELECT channel_id FROM data WHERE guild_id = ?", (message.guild.id,)) + #check if the message begins with --, if it does, ignore it, it's a comment + if message.content.startswith("-"): + print("The message is a comment") + return + #select channels from the premium table + try : + cp.execute("SELECT * FROM channels WHERE guild_id = ?", (message.guild.id,)) + channels = cp.fetchone()[1:] + except : + channels = [] + print("No premium channels") + print("here2") try : original_message = await message.channel.fetch_message(message.reference.message_id) except : original_message = None if original_message != None and original_message.author.id != self.bot.user.id: original_message = None - if str(message.channel.id) != str(c.fetchone()[0]): + print("The message is a reply, but the reply is not to the bot") + print("here") + try : + cp.execute("SELECT premium FROM data WHERE guild_id = ?", (message.guild.id,)) + premium = cp.fetchone()[0] + except : + premium = 0 + print("No premium") + if str(message.channel.id) != str(c.fetchone()[0]) : #check if the message is a mention or if the message replies to the bot if original_message != None: - debug("wrong channel, but reply") + print("wrong channel, but reply") elif message.content.find("<@"+str(self.bot.user.id)+">") != -1: - debug("wrong channel, but mention") + print("wrong channel, but mention") + elif str(message.channel.id) in channels and premium == 1: + print("in a channel that is in the database and premium") else : - debug("The message has been sent in the wrong channel") + print("The message has been sent in the wrong channel") return #check if the bot hasn't been used more than 5000 times in the last 24 hours (uses_count_today) c.execute("SELECT uses_count_today FROM data WHERE guild_id = ?", (message.guild.id,)) uses = c.fetchone()[0] - try: cp.execute("SELECT premium FROM data WHERE guild_id = ?", (message.guild.id,)) premium = cp.fetchone()[0] except: premium = 0 + print("here1") if uses >= 500 and premium == 0: - debug(f"The bot has been used more than {max_uses} times in the last 24 hours in this guild. Please try again in 24h.") + print(f"The bot has been used more than {max_uses} times in the last 24 hours in this guild. Please try again in 24h.") await message.channel.send("The bot has been used more than 500 times in the last 24 hours in this guild. Please try again in 24h.") return #add 1 to the uses_count_today @@ -86,21 +138,19 @@ async def on_message_process(message, self): #get the channel id from the database c.execute("SELECT channel_id FROM data WHERE guild_id = ?", (message.guild.id,)) for msg in messages: - if msg.author.bot: - prompt += f"Botator: {msg.content}\n" - else: - #replace the mentions of each user with their name - #first get all the mentions in the message - mentions = re.findall(r"<@!?\d+>", msg.content) - #then replace each mention with the name of the user - for mention in mentions: - #get the user id - id = mention[2:-1] - #get the user - user = await self.bot.fetch_user(id) - #replace the mention with the name - msg.content = msg.content.replace(mention, msg.guild.get_member(user.id).display_name + f"<@{id}>") - prompt += f"{msg.author.display_name}: {msg.content}\n" + mentions = re.findall(r"<@!?\d+>", msg.content) + #then replace each mention with the name of the user + for mention in mentions: + #get the user id + id = mention[2:-1] + #get the user + user = await self.bot.fetch_user(id) + #replace the mention with the name + msg.content = msg.content.replace(mention, msg.guild.get_member(user.id).display_name + f"<@{id}>") + #get the gmt time the message was sent + gmt_time = message.created_at.strftime("%Y-%m-%d %H:%M:%S") + print(f"{gmt_time}") + prompt += f"{msg.author.display_name} ({gmt_time} GMT-0): {msg.content}\n" #get the prompt_prefix from the database #check if the bot is in pretend mode c.execute("SELECT pretend_enabled FROM data WHERE guild_id = ?", (message.guild.id,)) @@ -145,8 +195,8 @@ async def on_message_process(message, self): prompt += "System: Fives3dprint detected. Botator will be very nice and cute with fives3dprint.\n" prompt += "Botator:" prompt = prompt + f"\n" - debug("Sending request to the api") - #debug(prompt) + print("Sending request to the api") + #print(prompt) openai.api_key = api_key response = openai.Completion.create( engine="text-davinci-003", @@ -164,11 +214,11 @@ async def on_message_process(message, self): #if tts is enabled, send the message with tts enabled if tts == 1: await message.channel.send(response["choices"][0]["text"], tts=True) - debug("The response has been sent with tts enabled") + print("The response has been sent with tts enabled") #if tts is disabled, send the message with tts disabled else: await message.channel.send(response["choices"][0]["text"]) - debug("The response has been sent with tts disabled") + print("The response has been sent with tts disabled") else: await message.channel.send("The AI is not sure what to say (the response was empty)") - debug("The response was empty") + print("The response was empty") diff --git a/code/cogs/help.py b/code/cogs/help.py index c8df450..939f48c 100644 --- a/code/cogs/help.py +++ b/code/cogs/help.py @@ -17,6 +17,7 @@ class Help (discord.Cog) : 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) diff --git a/code/cogs/settings.py b/code/cogs/settings.py index 466fa87..0bf5ee6 100644 --- a/code/cogs/settings.py +++ b/code/cogs/settings.py @@ -152,7 +152,7 @@ class Settings (discord.Cog) : conn.commit() await ctx.respond("Pretend mode enabled", ephemeral=True) #change the bots name on the server wit ctx.guild.me.edit(nick=pretend_to_be) - ctx.guild.me.edit(nick=pretend_to_be) + await ctx.guild.me.edit(nick=pretend_to_be) c.execute("UPDATE data SET pretend_to_be = ? WHERE guild_id = ?", (pretend_to_be, ctx.guild.id)) conn.commit() await ctx.guild.me.edit(nick=pretend_to_be) diff --git a/code/cogs/setup.py b/code/cogs/setup.py index 362df86..b2d3520 100644 --- a/code/cogs/setup.py +++ b/code/cogs/setup.py @@ -1,5 +1,5 @@ import discord -from config import debug, conn, c +from config import debug, conn, c, connp, cp class Setup (discord.Cog) : def __init__(self, bot: discord.Bot): @@ -77,4 +77,93 @@ class Setup (discord.Cog) : #disable the guild c.execute("UPDATE data SET is_active = ? WHERE guild_id = ?", (False, ctx.guild.id)) conn.commit() - await ctx.respond("Disabled", ephemeral=True) \ No newline at end of file + await ctx.respond("Disabled", ephemeral=True) + + #create a command calles "add channel" that can only be used in premium servers + @discord.slash_command(name="add_channel", description="Add a channel to the list of channels. Premium only.") + @discord.option(name="channel", description="The channel to add", type=discord.TextChannel, required=False) + async def add_channel(self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None): + debug(f"The user {ctx.author} ran the add_channel command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}") + #check if the guild is in the database + c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + if c.fetchone() is None: + await ctx.respond("This server is not setup", ephemeral=True) + return + #check if the guild is premium + try : + cp.execute("SELECT premium FROM data WHERE guild_id = ?", (ctx.guild.id,)) + premium = cp.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 already in the list + c.execute("SELECT channel_id FROM data WHERE guild_id = ?", (ctx.guild.id,)) + if str(channel.id) == c.fetchone()[0]: + await ctx.respond("This channel is already set as the main channel", ephemeral=True) + return + cp.execute("SELECT * FROM channels WHERE guild_id = ?", (ctx.guild.id,)) + guild_channels = cp.fetchone() + if guild_channels is None: + # if the channel is not in the list, add it + cp.execute("INSERT INTO channels VALUES (?, ?, ?, ?, ?, ?)", (ctx.guild.id, channel.id, None, None, None, None)) + connp.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: + cp.execute(f"UPDATE channels SET channel{i} = ? WHERE guild_id = ?", (channel.id, ctx.guild.id)) + connp.commit() + await ctx.respond(f"Added channel **{channel.name}**", ephemeral=True) + return + await ctx.respond("You can only add 5 channels", ephemeral=True) + + #create a command called "remove channel" that can only be used in premium servers + @discord.slash_command(name="remove_channel", description="Remove a channel from the list of channels. Premium only.") + @discord.option(name="channel", description="The channel to remove", type=discord.TextChannel, required=False) + async def remove_channel(self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None): + debug(f"The user {ctx.author} ran the remove_channel command in the channel {ctx.channel} of the guild {ctx.guild}, named {ctx.guild.name}") + #check if the guild is in the database + c.execute("SELECT * FROM data WHERE guild_id = ?", (ctx.guild.id,)) + if c.fetchone() is None: + await ctx.respond("This server is not setup", ephemeral=True) + return + #check if the guild is premium + try : + cp.execute("SELECT premium FROM data WHERE guild_id = ?", (ctx.guild.id,)) + premium = cp.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 + cp.execute("SELECT * FROM channels WHERE guild_id = ?", (ctx.guild.id,)) + guild_channels = cp.fetchone() + c.execute("SELECT channel_id FROM data WHERE guild_id = ?", (ctx.guild.id,)) + if str(channel.id) == c.fetchone()[0]: + await ctx.respond("This channel is set as the main channel and therefore cannot be removed. Type /setup to change the main channel.", ephemeral=True) + 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): + cp.execute(f"UPDATE channels SET channel{i} = ? WHERE guild_id = ?", (None, ctx.guild.id)) + connp.commit() + await ctx.respond(f"Removed channel **{channel.name}**", ephemeral=True) + return \ No newline at end of file diff --git a/code/config.py b/code/config.py index 4a4189e..44204b5 100644 --- a/code/config.py +++ b/code/config.py @@ -14,4 +14,6 @@ cp = connp.cursor() # Create table called "data" if it does not exist with the following columns: guild_id, channel_id, api_key, is_active, max_tokens, temperature, frequency_penalty, presence_penalty, uses_count_today, prompt_size c.execute('''CREATE TABLE IF NOT EXISTS data (guild_id text, channel_id text, api_key text, is_active boolean, max_tokens integer, temperature real, frequency_penalty real, presence_penalty real, uses_count_today integer, prompt_size integer, prompt_prefix text, tts boolean, pretend_to_be text, pretend_enabled boolean)''') -cp.execute('''CREATE TABLE IF NOT EXISTS data (user_id text, guild_id text, premium boolean)''') \ No newline at end of file +cp.execute('''CREATE TABLE IF NOT EXISTS data (user_id text, guild_id text, premium boolean)''') +# create table called "channels" if it does not exist with the following columns: guild_id, channel1, channel2, channel3, channel4, channel5 +cp.execute('''CREATE TABLE IF NOT EXISTS channels (guild_id text, channel0 text, channel1 text, channel2 text, channel3 text, channel4 text)''') \ No newline at end of file