diff --git a/fouras.py b/fouras.py deleted file mode 100644 index fb75dc4..0000000 --- a/fouras.py +++ /dev/null @@ -1,247 +0,0 @@ -from discord.ext import tasks -from datetime import datetime, timedelta, timezone -from dotenv import load_dotenv -import discord -import os -import asyncio -import random -import re -import json -import time -import unidecode - -intents = discord.Intents.default() -intents.members = True -intents.presences = True -intents.guilds = True -intents.messages = True -intents.message_content = True -client = discord.Client(intents=intents) -load_dotenv() -token = os.getenv('DISCORD_TOKEN', "NO_TOKEN") - -client.riddles = [] -client.answers = [] -client.rhyme_keys = {} -client.rhyme_strings = {} -client.cooldown = {} -client.ongoing_riddles = {} - -def load_riddles(): - with open('riddles.txt', 'r', encoding='utf-8') as r_file: - client.riddles = r_file.read().split('\n\n') - with open('answers.txt', 'r', encoding='utf-8') as a_file: - client.answers = [line.strip() for line in a_file.readlines()] - print('Loaded {0} riddles'.format(len(client.riddles))) - -def load_rhymes(): - with open('rhymes.txt', 'r', encoding='utf-8') as f: - data = f.read() - - keys_start = data.index('[KEYS]') + len('[KEYS]\n') - keys_end = data.index('[RHYMES]') - keys_data = data[keys_start:keys_end].split('\n') - keys = {} - for key_data in keys_data: - if key_data: - k, v = key_data.split(":") - keys[k] = v.split(',') - - rhymes_start = data.index('[RHYMES]') + len('[RHYMES]\n') - rhymes_data = data[rhymes_start:].split('\n') - rhymes = {} - for rhyme_data in rhymes_data: - if rhyme_data: - k, v = rhyme_data.split(":") - rhymes[k] = v.split(',') - - client.rhyme_keys = keys - client.rhyme_strings = rhymes - -def new_riddle(channel, index): - current_riddle = dict(index=index, nbClues=-1, riddle=client.riddles[index].strip(), answer=client.answers[index]) - client.ongoing_riddles[channel] = current_riddle - return format_message(current_riddle) - -def finish_riddle(channel): - del client.ongoing_riddles[channel] - -def clue_string(answer, nbClues): - finalString = "_" - for i in range(len(answer) - 1): - finalString += " _" - random.seed(hash(answer)) - nbRevealed = 0 - for i in range(nbClues): - - id = random.randint(0, len(answer) - 1) - while finalString[id * 2] != '_': - id = random.randint(0, len(answer) - 1) - - nbRevealed += 1 - finalString = finalString[:id * 2] + answer[id] + finalString[id * 2 + 1:] - - if nbRevealed == len(answer): - return finalString - return finalString - -def format_message(current_riddle): - nbClues = current_riddle['nbClues'] - answer = current_riddle['answer'] - - formatted_riddle = "> " + current_riddle['riddle'].replace("\n", "\n> ") - formatted_riddle = formatted_riddle.replace("\r", "") - - clue = "" - if nbClues > -1: - if nbClues >= len(answer): - clue = clue + "\nNon trouvée, la solution était : `{0}`".format(answer) - else: - clue = clue + "\nIndice : `{0}`".format(clue_string(answer, nbClues)) - - if 'solver' in current_riddle: - clue = clue + "\n{0} a trouvé la solution, qui était : `{1}`".format(current_riddle['solver'].mention, answer) - - if clue: - return "Énigme {0}:\n{1}\n{2}".format(current_riddle['index'] + 1, formatted_riddle, clue) - else: - return "Énigme {0}:\n{1}".format(current_riddle['index'] + 1, formatted_riddle) - -def get_last_word(ch:str)->str: - truncated = ch - while True: - if len(truncated) < 2 or truncated[-1].isnumeric(): - return '' - if truncated[-1].isalpha() and truncated[-2].isalpha(): - break - truncated = truncated[:-1] - return truncated - -def poil_auquel(ch:str)->str: - for key in client.rhyme_keys: - if ch.endswith(tuple(client.rhyme_keys[key])): - return random.choice(client.rhyme_strings[key]) - return '' - -async def get_channel_name(channel)->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) - - -@client.event -async def on_ready(): - load_riddles() - load_rhymes() - print('Logged in as {0.user}'.format(client)) - -@client.event -async def on_message(message): - # don't answer to self - if message.author == client.user and message.channel in client.ongoing_riddles: - current_riddle = client.ongoing_riddles[message.channel] - if not ('message' in current_riddle): - current_riddle['message'] = message - return - - if isinstance(message.channel, discord.DMChannel) or isinstance(message.channel, discord.TextChannel) or isinstance(message.channel, discord.Thread): - message_content = message.content.lower() - - # command fouras - fouras_match = re.match(r"^fouras\s+(\d+)$", message_content) - if fouras_match: - index = int(fouras_match.group(1)) - 1 - if index >= 0 and index < len(client.riddles): - if random.random() <= 0.03: - await message.channel.send("Non") - else: - await message.channel.send(new_riddle(message.channel, index)) - else: - await message.channel.send("Numéro d'énigme invalide, merci de saisir un numéro entre 1 et {0}".format(len(client.riddles))) - return - if message_content == 'fouras': - if random.random() <= 0.03: - await message.channel.send("Non") - elif len(client.riddles) > 0: - index = random.randint(0, len(client.riddles) - 1) - await message.channel.send(new_riddle(message.channel, index)) - return - - # command reload - if message_content == 'reload_riddles': - load_riddles() - await message.channel.send('Loaded {0} riddles'.format(len(client.riddles))) - return - - if message_content == 'about fouras': - author_user = await client.fetch_user(151626081458192384) - await message.channel.send(f"Ce bot a été développé par {author_user.mention}\nCode Source : https://gitlab.epicsparrow.com/Anselme/perefouras\nAjouter ce bot à votre serveur : https://discord.com/api/oauth2/authorize?client_id=1110208055171367014&permissions=274877975552&scope=bot") - return - - if message_content == 'debug fouras': - dump = {} - cooldowns = {} - for key, value in client.cooldown.items(): - channel_name = await get_channel_name(key) - cooldowns[channel_name] = value - time.time() - dump['poil_au_cooldown'] = cooldowns - for key, value in client.ongoing_riddles.items(): - dump_channel = value - dump_channel.pop("message", None) - dump_channel['answer'] = '||{0}||'.format(dump_channel['answer']) - channel_name = await get_channel_name(key) - dump[channel_name] = dump_channel - await message.author.send(json.dumps(dump, ensure_ascii=False, indent=4)) - return - - # if current channel has ongoing riddle - if message.channel in client.ongoing_riddles: - current_riddle = client.ongoing_riddles[message.channel] - - if 'message' in current_riddle: - answer = current_riddle["answer"] - if unidecode(answer.lower()) in unidecode(message_content): - current_riddle["solver"] = message.author - - await message.channel.send(f"Bravo {message.author.mention} ! La réponse était bien `{answer}`.") - await current_riddle['message'].edit(content=format_message(current_riddle)) - finish_riddle(message.channel) - return - - if message_content == 'repete' or message_content == 'répète' or message_content == 'repeat': - current_riddle.pop('message') - await message.channel.send(format_message(current_riddle)) - return - - # Commande /clue : révèle une lettre au hasard de la réponse attendue - if message_content == 'indice' or message_content == 'aide' or message_content == 'help' or message_content == 'clue': - nbClues = current_riddle["nbClues"] + 1 - current_riddle["nbClues"] = nbClues - if nbClues >= len(answer): - reply = "Perdu ! La réponse était : `{0}`".format(answer) - await message.channel.send(reply) - # else: - # reply = "Indice : `{0}`".format(clue_string(answer, nbClues)) - await current_riddle['message'].edit(content=format_message(current_riddle)) - if nbClues >= len(answer): - finish_riddle(message.channel) - return - - last_word = get_last_word(message_content) - if message.author != client.user and last_word: - poil = poil_auquel(last_word) - cooldown = 0 - if message.channel in client.cooldown: - cooldown = client.cooldown[message.channel] - if poil and time.time() - cooldown > 0: - wait_time = random.randint(0, 900) - if bool(random.getrandbits(1)): - wait_time = random.randint(900, 10800) - client.cooldown[message.channel] = time.time() + wait_time - await message.channel.send(poil) - return - -# Initialise le client -client.run(token) diff --git a/main.py b/main.py new file mode 100644 index 0000000..cc0f81d --- /dev/null +++ b/main.py @@ -0,0 +1,62 @@ +from discord.ext import tasks +from datetime import datetime, timedelta, timezone +from dotenv import load_dotenv +import discord +import os +import asyncio +import random +import re +import json +import time +import unidecode + +from modules.base import BaseModule +from modules import FourasModule, RhymesModule + +MODULES = FourasModule, RhymesModule + +intents = discord.Intents.default() +intents.members = True +intents.presences = True +intents.guilds = True +intents.messages = True +intents.message_content = True +client = discord.Client(intents=intents) +load_dotenv() +token = os.getenv("DISCORD_TOKEN", "NO_TOKEN") + +client.riddles = [] +client.answers = [] +client.rhyme_keys = {} +client.rhyme_strings = {} +client.cooldown = {} +client.ongoing_riddles = {} +client.modules: list[BaseModule] = [] + + +@client.event +async def on_ready(): + client.modules = [m(client) for m in MODULES] + for m in client.modules: + m.load() + print(f"Logged in as {client.user}") + + +@client.event +async def on_message(message): + # don't answer to self + if ( + message.author == client.user and message.channel in client.ongoing_riddles + ): # need to move a part of that block in FourasModule + current_riddle = client.ongoing_riddles[message.channel] + if not ("message" in current_riddle): + current_riddle["message"] = message + return + + if isinstance(message.channel, (discord.DMChannel, discord.TextChannel, discord.Thread)): + for m in client.modules: + await m.handle_message(message) + + +# Initialise le client +client.run(token) diff --git a/modules/__init__.py b/modules/__init__.py new file mode 100644 index 0000000..55aaac1 --- /dev/null +++ b/modules/__init__.py @@ -0,0 +1,2 @@ +from .fouras import FourasModule +from .rhymes import RhymesModule diff --git a/modules/base.py b/modules/base.py new file mode 100644 index 0000000..4e760dd --- /dev/null +++ b/modules/base.py @@ -0,0 +1,22 @@ +ENCODING = "utf-8" +import discord + + +class BaseModule: + _client = None + + def __init__(self, client): + self._client = client + + def load(self): + raise NotImplementedError + + async def handle_message(self, message): + raise NotImplementedError + + async def get_channel_name(self, channel) -> str: + if isinstance(channel, discord.DMChannel): + dm_channel = await self._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) diff --git a/modules/fouras.py b/modules/fouras.py new file mode 100644 index 0000000..28ccac6 --- /dev/null +++ b/modules/fouras.py @@ -0,0 +1,162 @@ +from .base import BaseModule, ENCODING +import random +import re +import time +import json +import unidecode + + +class FourasModule(BaseModule): + def load(self): + with open("riddles.txt", "r", encoding=ENCODING) as r_file: + self._client.riddles = r_file.read().split("\n\n") + with open("answers.txt", "r", encoding=ENCODING) as a_file: + self._client.answers = [line.strip() for line in a_file.readlines()] + print(f"Loaded {len(self._client.riddles)} riddles") + + def new_riddle(self, channel, index): + current_riddle = dict( + index=index, + nbClues=-1, + riddle=self._client.riddles[index].strip(), + answer=self._client.answers[index], + ) + self._client.ongoing_riddles[channel] = current_riddle + return self.format_message(current_riddle) + + def finish_riddle(self, channel): + del self._client.ongoing_riddles[channel] + + def clue_string(self, answer, nbClues): + finalString = "_" + for i in range(len(answer) - 1): + finalString += " _" + random.seed(hash(answer)) + nbRevealed = 0 + for i in range(nbClues): + id = random.randint(0, len(answer) - 1) + while finalString[id * 2] != "_": + id = random.randint(0, len(answer) - 1) + + nbRevealed += 1 + finalString = finalString[: id * 2] + answer[id] + finalString[id * 2 + 1 :] + + if nbRevealed == len(answer): + return finalString + return finalString + + def format_message(self, current_riddle): + nbClues = current_riddle["nbClues"] + answer = current_riddle["answer"] + + formatted_riddle = "> " + current_riddle["riddle"].replace("\n", "\n> ") + formatted_riddle = formatted_riddle.replace("\r", "") + + clue = "" + if nbClues > -1: + if nbClues >= len(answer): + clue = clue + "\nNon trouvée, la solution était : `{0}`".format(answer) + else: + clue = clue + "\nIndice : `{0}`".format(self.clue_string(answer, nbClues)) + + if "solver" in current_riddle: + clue = clue + "\n{0} a trouvé la solution, qui était : `{1}`".format( + current_riddle["solver"].mention, answer + ) + + if clue: + return "Énigme {0}:\n{1}\n{2}".format(current_riddle["index"] + 1, formatted_riddle, clue) + else: + return "Énigme {0}:\n{1}".format(current_riddle["index"] + 1, formatted_riddle) + + async def handle_message(self, message): + message_content = message.content.lower() + + # command fouras + fouras_match = re.match(r"^fouras\s+(\d+)$", message_content) + if fouras_match: + index = int(fouras_match.group(1)) - 1 + if index >= 0 and index < len(self._client.riddles): + if random.random() <= 0.03: + await message.channel.send("Non") + else: + await message.channel.send(self.new_riddle(message.channel, index)) + else: + await message.channel.send( + f"Numéro d'énigme invalide, merci de saisir un numéro entre 1 et {len(self._client.riddles)}" + ) + return + if message_content == "fouras": + if random.random() <= 0.03: + await message.channel.send("Non") + elif len(self._client.riddles) > 0: + index = random.randint(0, len(self._client.riddles) - 1) + await message.channel.send(self.new_riddle(message.channel, index)) + return + + # command reload + if message_content == "reload_riddles": + self.load() + await message.channel.send("Loaded {0} riddles".format(len(self._client.riddles))) + return + + if message_content == "about fouras": + author_user = await self.client.fetch_user(151626081458192384) + await message.channel.send( + f"Ce bot a été développé par {author_user.mention}\nCode Source : https://gitlab.epicsparrow.com/Anselme/perefouras\nAjouter ce bot à votre serveur : https://discord.com/api/oauth2/authorize?client_id=1110208055171367014&permissions=274877975552&scope=bot" + ) + return + + if message_content == "debug fouras": + dump = {} + cooldowns = {} + for key, value in self.client.cooldown.items(): + channel_name = await self.get_channel_name(key) + cooldowns[channel_name] = value - time.time() + dump["poil_au_cooldown"] = cooldowns + for key, value in self._client.ongoing_riddles.items(): + dump_channel = value + dump_channel.pop("message", None) + dump_channel["answer"] = "||{0}||".format(dump_channel["answer"]) + channel_name = await self.get_channel_name(key) + dump[channel_name] = dump_channel + await message.author.send(json.dumps(dump, ensure_ascii=False, indent=4)) + return + + # if current channel has ongoing riddle + if message.channel in self._client.ongoing_riddles: + current_riddle = self._client.ongoing_riddles[message.channel] + + if "message" in current_riddle: + answer = current_riddle["answer"] + if unidecode(answer.lower()) in unidecode(message_content): + current_riddle["solver"] = message.author + + await message.channel.send(f"Bravo {message.author.mention} ! La réponse était bien `{answer}`.") + await current_riddle["message"].edit(content=self.format_message(current_riddle)) + self.finish_riddle(message.channel) + return + + if message_content == "repete" or message_content == "répète" or message_content == "repeat": + current_riddle.pop("message") + await message.channel.send(self.format_message(current_riddle)) + return + + # Commande /clue : révèle une lettre au hasard de la réponse attendue + if ( + message_content == "indice" + or message_content == "aide" + or message_content == "help" + or message_content == "clue" + ): + nbClues = current_riddle["nbClues"] + 1 + current_riddle["nbClues"] = nbClues + if nbClues >= len(answer): + reply = "Perdu ! La réponse était : `{0}`".format(answer) + await message.channel.send(reply) + # else: + # reply = "Indice : `{0}`".format(clue_string(answer, nbClues)) + await current_riddle["message"].edit(content=self.format_message(current_riddle)) + if nbClues >= len(answer): + self.finish_riddle(message.channel) + return diff --git a/modules/rhymes.py b/modules/rhymes.py new file mode 100644 index 0000000..f3a0cb8 --- /dev/null +++ b/modules/rhymes.py @@ -0,0 +1,70 @@ +from .base import BaseModule +import random +import time + +RHYMES_FILE = "rhymes.txt" + + +class RhymesModule(BaseModule): + rhymes_keys: dict = {} + rhyme_strings: dict = {} + cooldown: dict = {} + + def load(self): + with open(RHYMES_FILE, "r", encoding="utf-8") as f: + data = f.read() + + keys_start = data.index("[KEYS]") + len("[KEYS]\n") + keys_end = data.index("[RHYMES]") + keys_data = data[keys_start:keys_end].split("\n") + keys = {} + for key_data in keys_data: + if key_data: + k, v = key_data.split(":") + keys[k] = v.split(",") + + rhymes_start = data.index("[RHYMES]") + len("[RHYMES]\n") + rhymes_data = data[rhymes_start:].split("\n") + rhymes = {} + for rhyme_data in rhymes_data: + if rhyme_data: + k, v = rhyme_data.split(":") + rhymes[k] = v.split(",") + + self.rhyme_keys = keys + self.rhyme_strings = rhymes + + def get_last_word(self, ch: str) -> str: + truncated = ch + while True: + if len(truncated) < 2 or truncated[-1].isnumeric(): + return "" + if truncated[-1].isalpha() and truncated[-2].isalpha(): + break + truncated = truncated[:-1] + return truncated + + def poil_auquel(self, ch: str) -> str: + for key in self.rhyme_keys: + if ch.endswith(tuple(self.rhyme_keys[key])): + return random.choice(self.rhyme_strings[key]) + return "" + + async def handle_message(self, message): + message_content = message.content.lower() + + last_word = self.get_last_word(message_content) + if message.author != self._client.user and last_word: + poil = self.poil_auquel(last_word) + # cooldown = 0 + # if message.channel in self.cooldown: + # cooldown = self.cooldown[message.channel] + cooldown = self.cooldown.get(message.channel, 0) + + if poil and time.time() - cooldown > 0: + wait_time = random.randint(0, 900) + if bool(random.getrandbits(1)): + wait_time = random.randint(900, 10800) + self.cooldown[message.channel] = time.time() + wait_time + await message.channel.send(poil) + return