# Remplacez les imports globaux et initialisations import json import random import re from typing import Any, Dict import discord from unidecode import unidecode import database # Supprimez ces lignes globales : # ongoing_riddles = {} # riddles = [] # answers = [] # Gardez seulement le chargement riddles = [] answers = [] API_URL = "".join( [ "https://discord.com/api/oauth2/authorize?", "client_id=1110208055171367014&permissions=274877975552&scope=bot", ] ) MAINTAINER_ID = 151626081458192384 ABOUT = """ Ce bot a été développé par {user} Code Source : https://git.epicsparrow.com/Anselme/perefouras Ajouter ce bot à votre serveur : {url} """ 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]: """Créer un nouvel état d'énigme sans sauvegarde DB.""" return { "index": index, "nbClues": -1, "riddle": riddles[index].strip(), "answer": answers[index], } async def finish_riddle(channel, client) -> None: """Supprimer l'énigme active de la DB et supprimer le suivi en mémoire.""" removed = database.remove_active_riddle(str(channel.id)) # Optionnel : si vous gardez un cache en mémoire pour performance, supprimez-le ici # if hasattr(client, '_cache_ongoing_riddles'): # client._cache_ongoing_riddles.pop(channel.id, None) def clue_string(answer: str, nb_clues: int) -> str: """Generate clue string with revealed letters.""" 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: """Format riddle message for display.""" nb_clues = current_riddle.get("nbClues", -1) answer = current_riddle.get("answer", "") # Si l'énigme est résolue, on récupère le solver depuis l'ID 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) -> 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) -> bool: """Handle debug commands (debug, save, load, broadcast). Returns True if handled.""" message_content = message.content.lower() if message_content == "load fouras": if message.author.id == MAINTAINER_ID: load_riddles() await message.author.send("Réponses et énigmes rechargées") return True if message_content == "debug fouras": if message.author.id == MAINTAINER_ID: # Récupération depuis la DB au lieu d'une variable globale db_riddles = database.get_all_active_riddles() dump = {} for riddle_data in db_riddles: try: channel = await client.fetch_channel(int(riddle_data["channel_id"])) channel_name = await get_channel_name(channel, client) # Reconstruction partielle pour l'affichage riddle_info = { "riddle_index": riddle_data["riddle_index"], "nb_clues": riddle_data["nb_clues"], "message_id": riddle_data["message_id"], "solver_id": riddle_data["solver_id"], "created_at": riddle_data["created_at"], "updated_at": riddle_data["updated_at"], } # On ajoute le texte de l'énigme si disponible if 0 <= riddle_data["riddle_index"] < len(riddles): riddle_info["text"] = ( riddles[riddle_data["riddle_index"]][:50] + "..." ) dump[channel_name] = riddle_info except Exception as e: dump[f"Channel_{riddle_data['channel_id']}"] = {"error": str(e)} await message.author.send( "```json\n{0}```".format(json.dumps(dump, ensure_ascii=False, indent=4)) ) return True 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 async def handle_riddle_commands(message, client) -> bool: """Handle /fouras commands. Returns True if command was processed.""" message_content = message.content.lower() fouras_match = re.match(r"^fouras\s+(\d+)$", message_content) if fouras_match: index = int(fouras_match.group(1)) - 1 if 0 <= index < len(riddles): if random.random() <= 0.03: await message.channel.send("Non") else: riddle_state = new_riddle_state(index) msg = await message.channel.send( format_riddle_message({**riddle_state, "index": index}) ) database.save_active_riddle( channel_id=str(message.channel.id), riddle_index=index, nb_clues=-1, message_id=msg.id, solver_id=None, ) else: await message.channel.send(INVALID_ID.format(len=len(riddles))) return True if message_content == "fouras": if random.random() <= 0.03: await message.channel.send("Non") elif len(riddles) > 0: index = random.randint(0, len(riddles) - 1) riddle_state = new_riddle_state(index) msg = await message.channel.send( format_riddle_message({**riddle_state, "index": index}) ) database.save_active_riddle( channel_id=str(message.channel.id), riddle_index=index, nb_clues=-1, message_id=msg.id, solver_id=None, ) else: print(f'riddles : "{len(riddles)}"') return True if message_content == "about fouras": author_user = await client.fetch_user(MAINTAINER_ID) await message.channel.send(ABOUT.format(user=author_user.mention, url=API_URL)) return True return False async def handle_riddle_solving(message, client) -> bool: """Handle riddle solving logic. Returns True if riddle was solved or modified.""" # Vérification via la DB au lieu de la variable globale riddle_data = database.get_active_riddle(str(message.channel.id)) if not riddle_data: return False # Reconstruire l'état de l'énigme if 0 <= riddle_data["riddle_index"] < len(riddles): current_riddle = { "index": riddle_data["riddle_index"], "nbClues": riddle_data["nb_clues"], "riddle": riddles[riddle_data["riddle_index"]].strip(), "answer": answers[riddle_data["riddle_index"]], } else: return False # Si aucun message n'est associé, on ne peut pas éditer if not riddle_data["message_id"]: return False answer = current_riddle["answer"] # Check if message contains the answer if unidecode(answer.lower()) in unidecode(message.content.lower()): solver_id = str(message.author.id) # Mise à jour DB : marque comme résolue avec le solver 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.channel.send( SUCCESS.format(user=message.author.mention, answer=answer) ) # Récupération du message original pour édition 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 ) ) except discord.errors.NotFound: pass # Suppression du suivi actif await finish_riddle(message.channel, client) return True # Repeat riddle command if message.content.lower() in ["repete", "répète", "repeat"]: try: original_msg = await message.channel.fetch_message( riddle_data["message_id"] ) # msg = await message.channel.send(format_riddle_message(current_riddle)) # database.save_active_riddle( # channel_id=str(message.channel.id), # riddle_index=index, # nb_clues=-1, # message_id=msg.id, # solver_id=None, # ) except discord.errors.NotFound: pass return True # Clue command if message.content.lower() in ["indice", "aide", "help", "clue"]: nb_clues = current_riddle["nbClues"] + 1 current_riddle["nbClues"] = nb_clues if nb_clues >= len(answer): await message.channel.send( "Perdu ! La réponse était : `{0}`".format(answer) ) # Mark as unsolved/revealed in DB database.save_active_riddle( channel_id=str(message.channel.id), riddle_index=riddle_data["riddle_index"], nb_clues=nb_clues, message_id=riddle_data["message_id"], solver_id="revealed", # Marqueur spécial ) await finish_riddle(message.channel, client) else: # Mise à jour du nombre d'indices en DB database.save_active_riddle( channel_id=str(message.channel.id), riddle_index=riddle_data["riddle_index"], nb_clues=nb_clues, message_id=riddle_data["message_id"], solver_id=riddle_data["solver_id"], ) try: original_msg = await message.channel.fetch_message( riddle_data["message_id"] ) await original_msg.edit(content=format_riddle_message(current_riddle)) except discord.errors.NotFound: pass return True return False async def handle_bug_report(message, client) -> bool: """Handle bug report command. Returns True if bug report was sent.""" 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) # Load message history 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=10) ] messages_json = json.dumps(messages, ensure_ascii=False) # Récupération de l'état DB au lieu de la variable globale active_riddles = database.get_all_active_riddles() state_json = json.dumps(active_riddles, 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) -> bool: """Main entry point for message handling.""" database.ensure_db() # Handle debug/admin commands first if await handle_debug_commands(message, client): return True # Handle riddle commands if await handle_riddle_commands(message, client): return True # Handle bug reports if await handle_bug_report(message, client): return True # Handle riddle solving if await handle_riddle_solving(message, client): return True return False