448 lines
14 KiB
Python
448 lines
14 KiB
Python
# 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
|