import asyncio import random from json import loads from pathlib import Path import discord from discord.ext import commands from pydis_core.utils.logging import get_logger from bot.bot import Bot from bot.constants import Colours log = get_logger(__name__) EGGHEAD_QUESTIONS = loads(Path("bot/resources/holidays/easter/egghead_questions.json").read_text("utf8")) EMOJIS = [ "\U0001f1e6", "\U0001f1e7", "\U0001f1e8", "\U0001f1e9", "\U0001f1ea", "\U0001f1eb", "\U0001f1ec", "\U0001f1ed", "\U0001f1ee", "\U0001f1ef", "\U0001f1f0", "\U0001f1f1", "\U0001f1f2", "\U0001f1f3", "\U0001f1f4", "\U0001f1f5", "\U0001f1f6", "\U0001f1f7", "\U0001f1f8", "\U0001f1f9", "\U0001f1fa", "\U0001f1fb", "\U0001f1fc", "\U0001f1fd", "\U0001f1fe", "\U0001f1ff" ] # Regional Indicators A-Z (used for voting) TIMELIMIT = 30 class EggheadQuiz(commands.Cog): """The Egghead quiz cog.""" def __init__(self): self.quiz_messages = {} @commands.command(aliases=("eggheadquiz", "easterquiz")) async def eggquiz(self, ctx: commands.Context) -> None: """ Gives a random quiz question, waits 30 seconds and then outputs the answer. Also informs of the percentages and votes of each option """ random_question = random.choice(EGGHEAD_QUESTIONS) question, answers = random_question["question"], random_question["answers"] answers = [(EMOJIS[i], a) for i, a in enumerate(answers)] correct = EMOJIS[random_question["correct_answer"]] valid_emojis = [emoji for emoji, _ in answers] description = f"You have {TIMELIMIT} seconds to vote.\n\n" description += "\n".join([f"{emoji} -> **{answer}**" for emoji, answer in answers]) q_embed = discord.Embed(title=question, description=description, colour=Colours.pink) msg = await ctx.send(embed=q_embed) for emoji in valid_emojis: await msg.add_reaction(emoji) self.quiz_messages[msg.id] = valid_emojis await asyncio.sleep(TIMELIMIT) del self.quiz_messages[msg.id] msg = await ctx.fetch_message(msg.id) # Refreshes message users = [] for reaction in msg.reactions: async for user in reaction.users(): users.append(user) total_no = len(users) - len(valid_emojis) # - bot's reactions if total_no == 0: return await msg.delete() # To avoid ZeroDivisionError if nobody reacts results = ["**VOTES:**"] for emoji, _ in answers: users = [] for reaction in msg.reactions: if str(reaction.emoji) != emoji: continue async for user in reaction.users(): users.append(user) break num = len(users) - 1 percent = round(100 * num / total_no) s = "" if num == 1 else "s" string = f"{emoji} - {num} vote{s} ({percent}%)" results.append(string) users = [] for reaction in msg.reactions: if str(reaction.emoji) != correct: continue async for user in reaction.users(): users.append(user) # At this point we've added everyone who reacted # with the correct answer, so stop looping over reactions. break mentions = " ".join([ u.mention for u in users if not u.bot ]) content = f"Well done {mentions} for getting it correct!" if mentions else "Nobody got it right..." a_embed = discord.Embed( title=f"The correct answer was {correct}!", description="\n".join(results), colour=Colours.pink ) await ctx.send(content, embed=a_embed) return None @staticmethod async def already_reacted(new_reaction: discord.Reaction, user: discord.Member | discord.User) -> bool: """Returns whether a given user has reacted more than once to a given message.""" message = new_reaction.message for reaction in message.reactions: if reaction.emoji == new_reaction.emoji: continue # can't react with same emoji twice so skip 2nd reaction check async for usr in reaction.users(): if usr.id == user.id: # user also reacted with the emoji, i.e. has already reacted return True return False @commands.Cog.listener() async def on_reaction_add(self, reaction: discord.Reaction, user: discord.Member | discord.User) -> None: """Listener to listen specifically for reactions of quiz messages.""" if user.bot: return None if reaction.message.id not in self.quiz_messages: return None if str(reaction.emoji) not in self.quiz_messages[reaction.message.id]: return await reaction.message.remove_reaction(reaction, user) if await self.already_reacted(reaction, user): return await reaction.message.remove_reaction(reaction, user) return None async def setup(bot: Bot) -> None: """Load the Egghead Quiz Cog.""" await bot.add_cog(EggheadQuiz())