perefouras/fouras.py
2026-08-06 16:20:08 +02:00

399 lines
12 KiB
Python

# 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.channel.send(
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
)
)
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)))