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 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 answer.lower() in 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 == 'connard de fouras': 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 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)