# Remplacez les imports globaux et initialisations import json import os import random import re from typing import Any, Dict import discord from discord import Interaction, app_commands from unidecode import unidecode import database riddles = [] answers = [] API_URL = "".join( [ "https://discord.com/api/oauth2/authorize?", "client_id=1110208055171367014&permissions=274877975552&scope=bot", ] ) AUTHOR_ID = 151626081458192384 MAINTAINER_ID = os.getenv("MAINTAINER_ID", AUTHOR_ID) ABOUT = """ Ce bot a été développé par {user} Code Source : https://git.epicsparrow.com/Anselme/perefouras Ajouter ce bot à votre serveur : {url} """ BUG_REPORT = """ BUG REPORT from {user} (`{user_id}`) in channel {channel} (`{channel_id}`) : > {message} History : ```json\n{history}``` """ SUCCESS = """ Bravo {user} ! La réponse était bien `{answer}`. """ INVALID_ID = """ Numéro d'énigme invalide, merci de saisir un numéro entre 1 et {len} """ RIDDLES_FILE = "resources/riddles.txt" ANSWERS_FILE = "resources/answers.txt" def load_riddles() -> list: global riddles, answers riddles = [] with open(RIDDLES_FILE, "r", encoding="utf-8") as f: riddles = f.read().split("\n\n") answers = [] with open(ANSWERS_FILE, "r", encoding="utf-8") as f: answers = [line.strip() for line in f.readlines()] print(f"Loaded {len(riddles)} riddles") def new_riddle_state(index: int) -> Dict[str, Any]: return { "index": index, "nbClues": -1, "riddle": riddles[index].strip(), "answer": answers[index], } async def finish_riddle(channel, client: discord.Client) -> None: database.remove_active_riddle(str(channel.id)) def clue_string(answer: str, nb_clues: int) -> str: final_string = "_" for _ in range(len(answer) - 1): final_string += " _" random.seed(hash(answer)) nb_revealed = 0 for _ in range(nb_clues): idx = random.randint(0, len(answer) - 1) while final_string[idx * 2] != "_": idx = random.randint(0, len(answer) - 1) nb_revealed += 1 final_string = ( final_string[: idx * 2] + answer[idx] + final_string[idx * 2 + 1 :] ) if nb_revealed == len(answer): return final_string return final_string def format_riddle_message( current_riddle: Dict[str, Any], solved: bool = False, solver_mention: str = "" ) -> str: nb_clues = current_riddle.get("nbClues", -1) answer = current_riddle.get("answer", "") solver = None if solved and solver_mention: solver = type("obj", (object,), {"mention": solver_mention})() formatted_riddle = "> " + current_riddle["riddle"].replace("\n", "\n> ") formatted_riddle = formatted_riddle.replace("\r", "") clue = "" if nb_clues > -1: if nb_clues >= len(answer): clue = "\nNon trouvée, la solution était : `{0}`".format(answer) else: clue = "\nIndice : `{0}`".format(clue_string(answer, nb_clues)) if solved and solver: clue = clue + "\n{0} a trouvé la solution, qui était : `{1}`".format( solver.mention, answer ) if clue: return "Énigme {0}:\n{1}\n> Qui suis-je ?\n{2}".format( current_riddle["index"] + 1, formatted_riddle, clue ) else: return "Énigme {0}:\n{1}\n> Qui suis-je ?".format( current_riddle["index"] + 1, formatted_riddle ) async def get_channel_name(channel, client: discord.Client) -> str: if isinstance(channel, discord.DMChannel): dm_channel = await client.fetch_channel(channel.id) return "[DM={0}]".format(dm_channel.recipient.name) else: return "[Server={0}] => [Channel={1}]".format(channel.guild.name, channel.name) async def handle_debug_commands(message, client: discord.Client) -> bool: broadcast_match = re.match(r"^broadcast\s+(\d+) (.*)", message.content) if broadcast_match and message.author.id == MAINTAINER_ID: index = int(broadcast_match.group(1)) broadcast_message = broadcast_match.group(2) try: channel = await client.fetch_channel(index) if channel: await channel.send(broadcast_message) else: await message.channel.send(f"Invalid channel id : {index}") except discord.errors.NotFound: await message.channel.send(f"Channel not found : {index}") return True return False def get_current_riddle(channel_id): riddle_data = database.get_active_riddle(str(channel_id)) if not riddle_data: return None if 0 <= riddle_data["riddle_index"] < len(riddles): return { "index": riddle_data["riddle_index"], "nbClues": riddle_data["nb_clues"], "riddle": riddles[riddle_data["riddle_index"]].strip(), "answer": answers[riddle_data["riddle_index"]], "data": riddle_data } else: return None async def handle_riddle_solving(message, client: discord.Client) -> bool: current_riddle = get_current_riddle(message.channel.id) if(not current_riddle): return False answer = current_riddle["answer"] riddle_data = current_riddle["data"] if unidecode(answer.lower()) in unidecode(message.content.lower()): solver_id = str(message.author.id) database.save_active_riddle( channel_id=str(message.channel.id), riddle_index=riddle_data["riddle_index"], nb_clues=current_riddle["nbClues"], message_id=riddle_data["message_id"], solver_id=solver_id, ) await message.reply( SUCCESS.format(user=message.author.mention, answer=answer) ) try: original_msg = await message.channel.fetch_message( riddle_data["message_id"] ) await original_msg.edit( content=format_riddle_message( current_riddle, solved=True, solver_mention=message.author.mention ) ) # await message.add_reaction("✅") except discord.errors.NotFound: pass await finish_riddle(message.channel, client) return True return False async def handle_bug_report(message, client: discord.Client) -> bool: if not message.content.lower().startswith("bug"): return False author_user = await client.fetch_user(MAINTAINER_ID) channel_name = await get_channel_name(message.channel, client) messages = [ { "id": msg.id, "content": msg.content, "date": msg.created_at.strftime("%d/%m %H:%M:%S"), } async for msg in message.channel.history(limit=6) ] messages_json = json.dumps(messages, ensure_ascii=False) active_riddle = database.get_active_riddle(message.channel.id) state_json = json.dumps(active_riddle, ensure_ascii=False, indent=2) await author_user.send( BUG_REPORT.format( user=message.author.mention, user_id=message.author.id, channel=channel_name, channel_id=message.channel.id, message=message.content, history=messages_json, state=state_json, ) ) await message.channel.send( f"Rapport de bug envoyé à {author_user.mention}\nMerci de ton feedback !" ) return True async def handle_message(message, client: discord.Client) -> bool: database.ensure_db() if await handle_debug_commands(message, client): return True if await handle_bug_report(message, client): return True if await handle_riddle_solving(message, client): return True return False async def new_riddle(interaction: Interaction, index: int) -> None: if random.random() <= 0.03: await interaction.response.send_message("Non") else: riddle_state = new_riddle_state(index) msg = await interaction.response.send_message( format_riddle_message({**riddle_state, "index": index}) ) database.save_active_riddle( channel_id=str(interaction.channel.id), riddle_index=index, nb_clues=-1, message_id=msg.message_id, solver_id=None ) async def update_riddle_message(channel, current_riddle): try: original_msg = await channel.fetch_message(current_riddle["data"]["message_id"]) await original_msg.edit(content=format_riddle_message(current_riddle)) return True except discord.errors.NotFound: return False def setup_commands(client: discord.Client, tree) -> None: @tree.command( name="fouras", description="Demander une énigme au Père Fouras" ) async def fouras( interaction: discord.Interaction, ): if len(riddles) == 0: await interaction.response.send_message("Désolé, mais j'ai oublié mes énigmes", ephemeral=True) else: index = random.randint(0, len(riddles) - 1) await new_riddle(interaction, index) @tree.command( name="requete", description="Demander une énigme spécifique au Père Fouras" ) @app_commands.describe(index="Numéro de l'énigme") async def requete( interaction: discord.Interaction, index: int, ): index = index - 1 if not (0 <= index < len(riddles)): await interaction.response.send_message(INVALID_ID.format(len=len(riddles)+1), ephemeral=True) return await new_riddle(interaction, index) @tree.command( name="repete", description="Demander au Père Fouras de répéter l'énigme en cours" ) async def repete( interaction: discord.Interaction, ): current_riddle = get_current_riddle(interaction.channel.id) if(not current_riddle): await interaction.response.send_message("Aucune énigme en cours", ephemeral=True) return riddle_data = current_riddle["data"] msg = await interaction.response.send_message( format_riddle_message(current_riddle) ) database.save_active_riddle( channel_id=str(interaction.channel.id), riddle_index=riddle_data["riddle_index"], nb_clues=current_riddle["nbClues"], message_id=msg.message_id, solver_id=riddle_data["solver_id"], ) # @tree.command( # name="about", # description="Qui est le Père Fouras ?" # ) # async def about( # interaction: discord.Interaction, # ): # author_user = await client.fetch_user(AUTHOR_ID) # await interaction.response.send_message(ABOUT.format(user=author_user.mention, url=API_URL), ephemeral=True) @tree.command( name="indice", description="Demander un indice pour l'énigme en cours" ) async def indice( interaction: discord.Interaction, ): current_riddle = get_current_riddle(interaction.channel.id) if(not current_riddle): await interaction.response.send_message("Aucune énigme en cours", ephemeral=True) return answer = current_riddle["answer"] riddle_data = current_riddle["data"] nb_clues = current_riddle["nbClues"] + 1 current_riddle["nbClues"] = nb_clues if nb_clues >= len(answer): await interaction.response.send_message( "Perdu ! La réponse était : `{0}`".format(answer) ) if(not await update_riddle_message(interaction.channel, current_riddle)): return await finish_riddle(interaction.channel, client) else: database.save_active_riddle( channel_id=str(interaction.channel.id), riddle_index=riddle_data["riddle_index"], nb_clues=nb_clues, message_id=riddle_data["message_id"], solver_id=riddle_data["solver_id"], ) if(not await update_riddle_message(interaction.channel, current_riddle)): return await interaction.response.send_message("Nouvel indice : `{0}`".format(clue_string(answer, nb_clues)))