diff --git a/botbotbot/__init__.py b/botbotbot/__init__.py index aca06b4..24265fc 100644 --- a/botbotbot/__init__.py +++ b/botbotbot/__init__.py @@ -1,13 +1,14 @@ import logging -import pickle import random import tomllib import discord from botbotbot.ai import AIBot +from botbotbot.shuffle import Shuffler from botbotbot.text import TextBot from botbotbot.tts import CambAI +from botbotbot.wordlist import Wordlist def main() -> None: @@ -19,10 +20,7 @@ def main() -> None: with open("config.toml", "rb") as config_file: config = tomllib.load(config_file) - with open("wordlist.pickle", "rb") as word_file: - word_list = pickle.load(word_file) guild_ids = config.get("guild_ids") - delay = config.get("delay", 60) system_prompt = """Tu es une intelligence artificelle qui répond en français. Tu dois faire un commentaire pertinent en lien avec ce qui te sera dit. @@ -50,123 +48,25 @@ def main() -> None: intents.voice_states = True bot = discord.Bot(description=description, intents=intents) + shf = Shuffler() + wl = Wordlist(bot) - text_bot = TextBot(bot, aibot, word_list) + text_bot = TextBot(bot, wl, aibot=aibot, shuffler=shf) text_bot.init_events() - shuffle_tasks = set() - @bot.listen("on_ready") async def on_ready() -> None: logger.info(f"We have logged in as {bot.user}") - @bot.listen("on_reaction_add") - async def add_more_reaction( - reaction: discord.Reaction, user: discord.Member | discord.User - ) -> None: - if random.random() < 50 / 100: - logger.info(f"Copy reaction from {user}") - await reaction.message.add_reaction(reaction.emoji) - - @bot.listen("on_message_edit") - async def react_message_edit( - before: discord.Message, after: discord.Message - ) -> None: - if ( - before.content != after.content - and after.author != bot.user - and random.random() < 50 / 100 - ): - logger.info(f"React to edit from {after.author}") - await after.add_reaction("👀") - - @bot.listen("on_message") - async def rando_shuffle(message: discord.Message) -> None: - if not message.flags.ephemeral and random.random() < 5 / 100 and message.guild: - logger.info(f"Message shuffle after message from {message.author}") - await try_shuffle(message.guild) - - def save_wordlist() -> None: - logger.info("Saving updated wordlist") - with open("wordlist.pickle", "wb") as word_file: - pickle.dump(word_list, word_file) - - @bot.slash_command( - name="bibl", guild_ids=guild_ids, description="Ajouter une phrase" - ) - async def bibl(ctx: discord.ApplicationContext, phrase: str) -> None: - logger.info(f"BIBL {ctx.author} {phrase}") - word_list.append(phrase) - embed = discord.Embed( - title="BIBL", description=phrase, color=discord.Colour.green() - ) - await ctx.respond(embed=embed) - save_wordlist() - logger.info("FIN BIBL") - - @bot.slash_command( - name="tabl", guild_ids=guild_ids, description="Lister les phrases" - ) - async def tabl(ctx: discord.ApplicationContext) -> None: - logger.info(f"TABL {ctx.author}") - embed = discord.Embed( - title="TABL", description="\n".join(word_list), color=discord.Colour.green() - ) - await ctx.respond(embed=embed, ephemeral=True, delete_after=delay) - - @bot.slash_command( - name="enle", guild_ids=guild_ids, description="Enlever une phrase" - ) - async def enle(ctx: discord.ApplicationContext, phrase: str) -> None: - logger.info(f"ENLE {ctx.author} {phrase}") - try: - word_list.remove(phrase) - except ValueError: - embed = discord.Embed( - title="ERRE ENLE", description=phrase, color=discord.Colour.red() - ) - await ctx.respond(embed=embed) - logger.info("ERRE ENLE") - else: - embed = discord.Embed( - title="ENLE", description=f"~~{phrase}~~", color=discord.Colour.green() - ) - await ctx.respond(embed=embed, ephemeral=True, delete_after=delay) - save_wordlist() - logger.info("FIN ENLE") - - async def try_shuffle(guild: discord.Guild) -> bool: - if guild.id in shuffle_tasks: - return False - - shuffle_tasks.add(guild.id) - await shuffle_nicks(guild) - shuffle_tasks.discard(guild.id) - return True - - async def shuffle_nicks(guild: discord.Guild) -> None: - logger.info("Shuffle") - members = guild.members - if guild.owner: - members.remove(guild.owner) - - nicks = [member.nick for member in members] - - random.shuffle(nicks) - for member, nick in zip(members, nicks): - logger.info(f"{member} {nick}") - await member.edit(nick=nick) - logger.info("Shuffle done") - @bot.slash_command( name="alea", guild_ids=guild_ids, description="Modifier les pseudos" ) async def alea(ctx: discord.ApplicationContext) -> None: logger.info(f"ALEA {ctx.author}") await ctx.defer() - if await try_shuffle(ctx.guild): + if await shf.try_shuffle(ctx.guild): embed = discord.Embed(title="ALEA", color=discord.Colour.green()) - await ctx.respond(embed=embed, ephemeral=True, delete_after=delay) + await ctx.respond(embed=embed, ephemeral=True, delete_after=30) logger.info("FIN ALEA") else: embed = discord.Embed(title="ERRE ALEA", color=discord.Colour.red()) @@ -177,10 +77,6 @@ def main() -> None: async def on_voice_state_update( member: discord.Member, before: discord.VoiceState, after: discord.VoiceState ) -> None: - if before.channel is None and random.random() < 5 / 100: - logger.info(f"Voice shuffle from {member}") - await try_shuffle(member.guild) - logger.debug("Voice state update") logger.debug(before.channel) logger.debug(after.channel) @@ -210,6 +106,10 @@ def main() -> None: await vo.play(source, wait_finish=True) await vo.disconnect() + if before.channel is None and random.random() < 5 / 100: + logger.info(f"Voice shuffle from {member}") + await shf.try_shuffle(member.guild) + @bot.slash_command( name="indu", guild_ids=guild_ids, description="Poser une question à MistralAI" ) diff --git a/botbotbot/shuffle.py b/botbotbot/shuffle.py new file mode 100644 index 0000000..cd8786c --- /dev/null +++ b/botbotbot/shuffle.py @@ -0,0 +1,34 @@ +import logging +import random + +import discord + +logger = logging.getLogger(__name__) + + +class Shuffler: + def __init__(self) -> None: + self.shuffle_tasks: set[int] = set() + + async def try_shuffle(self, guild: discord.Guild) -> bool: + if guild.id in self.shuffle_tasks: + return False + + self.shuffle_tasks.add(guild.id) + await self.shuffle_nicks(guild) + self.shuffle_tasks.discard(guild.id) + return True + + async def shuffle_nicks(self, guild: discord.Guild) -> None: + logger.info("Shuffle") + members = guild.members + if guild.owner: + members.remove(guild.owner) + + nicks = [member.nick for member in members] + + random.shuffle(nicks) + for member, nick in zip(members, nicks): + logger.info(f"{member} {nick}") + await member.edit(nick=nick) + logger.info("Shuffle done") diff --git a/botbotbot/text.py b/botbotbot/text.py index a03161f..50a3cd6 100644 --- a/botbotbot/text.py +++ b/botbotbot/text.py @@ -6,6 +6,8 @@ import discord import emoji from botbotbot.ai import AIBot +from botbotbot.shuffle import Shuffler +from botbotbot.wordlist import Wordlist logger = logging.getLogger(__name__) @@ -16,17 +18,22 @@ class TextBot: def __init__( self, bot: discord.Bot, + wordlist: Wordlist, aibot: AIBot | None = None, - wordlist: list[str] = [], + shuffler: Shuffler | None = None, rnd_weights: list[float] = [10, 5, 10], ) -> None: self.bot = bot self.aibot = aibot - self.word_list = wordlist + self.wl = wordlist self._rnd_weights = rnd_weights + self.shf = shuffler def init_events(self) -> None: self.bot.add_listener(self.on_message, "on_message") + self.bot.add_listener(self.add_more_reaction, "on_reaction_add") + self.bot.add_listener(self.react_message_edit, "on_message_edit") + self.bot.add_listener(self.rando_shuffle, "on_message") @property def rnd_weights(self) -> list[float]: @@ -77,8 +84,8 @@ class TextBot: )[0] content = random.choice( ( - f"{mention}, {random.choice(self.word_list)}", - f"{random.choice(self.word_list)}", + f"{mention}, {self.wl.random()}", + f"{self.wl.random()}", ) ) @@ -144,3 +151,31 @@ class TextBot: webhook = await channel.create_webhook(name="BotbotbotHook") await webhook.send(content=content, username=name, avatar_url=avatar_url) + + async def add_more_reaction( + self, reaction: discord.Reaction, user: discord.Member | discord.User + ) -> None: + if random.random() < 20 / 100: + logger.info(f"Copy reaction from {user}") + await reaction.message.add_reaction(reaction.emoji) + + async def react_message_edit( + self, before: discord.Message, after: discord.Message + ) -> None: + if ( + before.content != after.content + and after.author != self.bot.user + and random.random() < 20 / 100 + ): + logger.info(f"React to edit from {after.author}") + await after.add_reaction("👀") + + async def rando_shuffle(self, message: discord.Message) -> None: + if ( + self.shf + and not message.flags.ephemeral + and random.random() < 5 / 100 + and message.guild + ): + logger.info(f"Message shuffle after message from {message.author}") + await self.shf.try_shuffle(message.guild) diff --git a/botbotbot/voice.py b/botbotbot/voice.py new file mode 100644 index 0000000..e69de29 diff --git a/botbotbot/wordlist.py b/botbotbot/wordlist.py new file mode 100644 index 0000000..e142408 --- /dev/null +++ b/botbotbot/wordlist.py @@ -0,0 +1,81 @@ +import logging +import pathlib +import pickle +import random + +import discord + +logger = logging.getLogger(__name__) + + +class Wordlist: + def __init__( + self, bot: discord.Bot, path: pathlib.Path = pathlib.Path("wordlist.pickle") + ): + self.path = path + if self.path.exists(): + with self.path.open("rb") as w_file: + self.word_list: list[str] = pickle.load(w_file) + self.bot = bot + + def init_events(self) -> None: + self.bot.add_application_command( + discord.SlashCommand( + self.bibl, name="bibl", description="Ajouter une phrase" + ) + ) + self.bot.add_application_command( + discord.SlashCommand( + self.tabl, name="tabl", description="Lister les phrases" + ) + ) + self.bot.add_application_command( + discord.SlashCommand( + self.enle, name="enle", description="Enlever une phrase" + ) + ) + + def random(self) -> str: + return random.choice(self.word_list) + + def save(self) -> None: + logger.info("Saving updated wordlist") + with open(self.path, "wb") as w_file: + pickle.dump(self.word_list, w_file) + + async def bibl(self, ctx: discord.ApplicationContext, phrase: str) -> None: + logger.info(f"BIBL {ctx.author} {phrase}") + self.word_list.append(phrase) + embed = discord.Embed( + title="BIBL", description=phrase, color=discord.Colour.green() + ) + await ctx.respond(embed=embed) + self.save() + logger.info("FIN BIBL") + + async def tabl(self, ctx: discord.ApplicationContext) -> None: + logger.info(f"TABL {ctx.author}") + embed = discord.Embed( + title="TABL", + description="\n".join(self.word_list), + color=discord.Colour.green(), + ) + await ctx.respond(embed=embed, ephemeral=True, delete_after=30) + + async def enle(self, ctx: discord.ApplicationContext, phrase: str) -> None: + logger.info(f"ENLE {ctx.author} {phrase}") + try: + self.word_list.remove(phrase) + except ValueError: + embed = discord.Embed( + title="ERRE ENLE", description=phrase, color=discord.Colour.red() + ) + await ctx.respond(embed=embed) + logger.info("ERRE ENLE") + else: + embed = discord.Embed( + title="ENLE", description=f"~~{phrase}~~", color=discord.Colour.green() + ) + await ctx.respond(embed=embed, ephemeral=True, delete_after=30) + self.save() + logger.info("FIN ENLE")