Esempio n. 1
0
    async def _weather_conditions_forecast(self, ctx: commands.Context, *,
                                           location: str):
        """Gets local weather forecast for the next three days from [wttr.in](http://wttr.in/).
        See help of the `weather` command for possible location types and options."""
        async with ctx.typing():
            try:
                units_arg = re.search(self.wttr_units_regex, location).group(1)
            except AttributeError:
                units_arg = ""
            if units_arg.lower() == "f":
                units = "u"
            elif units_arg.lower() == "c":
                units = "m"
            else:
                units = ""

            loc = self.wttr_units_regex.sub("", location).strip()

            embed = cmn.embed_factory(ctx)
            embed.title = f"Weather Forecast for {loc}"
            embed.description = "Data from [wttr.in](http://wttr.in/)."
            embed.colour = cmn.colours.good

            loc = loc.replace(" ", "+")
            async with self.session.get(
                    f"http://wttr.in/{loc}_{units}pnFQ.png") as resp:
                if resp.status != 200:
                    raise cmn.BotHTTPError(resp)
                data = io.BytesIO(await resp.read())
            embed.set_image(url="attachment://wttr_forecast.png")
            await ctx.send(embed=embed,
                           file=discord.File(data, "wttr_forecast.png"))
Esempio n. 2
0
    async def get_metar_taf_data(self, airport: str, hours: int,
                                 taf: bool) -> List[str]:
        url = (
            f"https://www.aviationweather.gov/metar/data?ids={airport}&format=raw&hours={hours}"
            f"&taf={'on' if taf else 'off'}&layout=off")
        async with self.session.get(url) as r:
            if r.status != 200:
                raise cmn.BotHTTPError(r)
            page = await r.text()

        # pare down to just the data
        page = page.split("<!-- Data starts here -->")[1].split(
            "<!-- Data ends here -->")[0].strip()
        # split at <hr>s
        data = re.split(r"<hr.*>", page, maxsplit=len(airport))

        parsed = []
        for sec in data:
            if sec.strip():
                for line in sec.split("\n"):
                    line = line.strip()
                    # remove HTML stuff
                    line = line.replace("<code>", "").replace("</code>", "")
                    line = line.replace("<strong>",
                                        "").replace("</strong>", "")
                    line = line.replace("<br/>", "\n").replace("&nbsp;", " ")
                    line = line.strip("\n")
                    parsed.append(line)
        return parsed
Esempio n. 3
0
    async def hamstudy_get_pools(self):
        async with self.session.get("https://hamstudy.org/pools/") as resp:
            if resp.status != 200:
                raise cmn.BotHTTPError(resp)
            else:
                pools_dict = json.loads(await resp.read())

        pools = dict()
        for ls in pools_dict.values():
            for pool in ls:
                pools[pool["id"]] = pool

        return pools
Esempio n. 4
0
 async def _band_conditions(self, ctx: commands.Context):
     """Gets a solar conditions report."""
     async with ctx.typing():
         embed = cmn.embed_factory(ctx)
         embed.title = "Current Solar Conditions"
         embed.colour = cmn.colours.good
         async with self.session.get(
                 "http://www.hamqsl.com/solarsun.php") as resp:
             if resp.status != 200:
                 raise cmn.BotHTTPError(resp)
             data = io.BytesIO(await resp.read())
         embed.set_image(url="attachment://condx.png")
         await ctx.send(embed=embed, file=discord.File(data, "condx.png"))
Esempio n. 5
0
 async def _grayline(self, ctx: commands.Context):
     """Gets a map of the current greyline, where HF propagation is the best."""
     async with ctx.typing():
         embed = cmn.embed_factory(ctx)
         embed.title = "Current Greyline Conditions"
         embed.colour = cmn.colours.good
         async with self.session.get(self.gl_url) as resp:
             if resp.status != 200:
                 raise cmn.BotHTTPError(resp)
             data = io.BytesIO(await resp.read())
         embed.set_image(url="attachment://greyline.jpg")
         await ctx.send(embed=embed,
                        file=discord.File(data, "greyline.jpg"))
Esempio n. 6
0
    async def tex(self, ctx: commands.Context, *, expr: str):
        """Renders a LaTeX expression.

        In paragraph mode by default. To render math, add `$` around math expressions.
        """
        payload = {
            "format": "png",
            "code": self.template.replace("#CONTENT#", expr),
            "quality": 50
        }

        with ctx.typing():
            # ask rTeX to render our expression
            async with self.session.post(urljoin(opt.rtex_instance, "api/v2"),
                                         json=payload) as r:
                if r.status != 200:
                    raise cmn.BotHTTPError(r)

                render_result = await r.json()
                if render_result["status"] != "success":
                    embed = cmn.embed_factory(ctx)
                    embed.title = "LaTeX Rendering Failed!"
                    embed.description = (
                        "Here are some common reasons:\n"
                        "• Did you forget to use math mode? Surround math expressions with `$`,"
                        " like `$x^3$`.\n"
                        "• Are you using a command from a package? It might not be available.\n"
                        "• Are you including the document headers? We already did that for you."
                    )
                    embed.colour = cmn.colours.bad
                    await ctx.send(embed=embed)
                    return

            # if rendering went well, download the file given in the response
            async with self.session.get(
                    urljoin(opt.rtex_instance,
                            "api/v2/" + render_result["filename"])) as r:
                png_buffer = BytesIO(await r.read())

            embed = cmn.embed_factory(ctx)
            embed.title = "LaTeX Expression"
            embed.description = "Rendered by [rTeX](https://rtex.probablyaweb.site/)."
            embed.set_image(url="attachment://tex.png")
            await ctx.send(file=discord.File(png_buffer, "tex.png"),
                           embed=embed)
Esempio n. 7
0
    async def _random_question(self,
                               ctx: commands.Context,
                               country: str = "",
                               level: str = "",
                               element: str = ""):
        """Gets a random question from [HamStudy's](https://hamstudy.org) question pools."""
        with ctx.typing():
            embed = cmn.embed_factory(ctx)

            country = country.lower()
            level = level.lower()
            element = element.upper()

            if country in study.pool_names.keys():
                if level in study.pool_names[country].keys():
                    pool_name = study.pool_names[country][level]

                elif level in ("random", "r"):
                    # select a random level in that country
                    pool_name = random.choice(
                        list(study.pool_names[country].values()))

                else:
                    # show list of possible pools
                    embed.title = "Pool Not Found!"
                    embed.description = "Possible arguments are:"
                    embed.colour = cmn.colours.bad
                    for cty in study.pool_names:
                        levels = "`, `".join(study.pool_names[cty].keys())
                        embed.add_field(
                            name=
                            f"**Country: `{cty}` {study.pool_emojis[cty]}**",
                            value=f"Levels: `{levels}`",
                            inline=False)
                    embed.add_field(
                        name="**Random**",
                        value=
                        "To select a random pool or country, use `random` or `r`"
                    )
                    await ctx.send(embed=embed)
                    return

            elif country in ("random", "r"):
                # select a random country and level
                country = random.choice(list(study.pool_names.keys()))
                pool_name = random.choice(
                    list(study.pool_names[country].values()))

            else:
                # show list of possible pools
                embed.title = "Pool Not Found!"
                embed.description = "Possible arguments are:"
                embed.colour = cmn.colours.bad
                for cty in study.pool_names:
                    levels = "`, `".join(study.pool_names[cty].keys())
                    embed.add_field(
                        name=f"**Country: `{cty}` {study.pool_emojis[cty]}**",
                        value=f"Levels: `{levels}`",
                        inline=False)
                embed.add_field(
                    name="**Random**",
                    value=
                    "To select a random pool or country, use `random` or `r`")
                await ctx.send(embed=embed)
                return

            pools = await self.hamstudy_get_pools()

            pool_matches = [
                p for p in pools.keys()
                if "_".join(p.split("_")[:-1]) == pool_name
            ]

            if len(pool_matches) > 0:
                if len(pool_matches) == 1:
                    pool = pool_matches[0]
                else:
                    # look at valid_from and expires dates to find the correct one
                    for p in pool_matches:
                        valid_from = datetime.fromisoformat(
                            pools[p]["valid_from"][:-1])
                        expires = datetime.fromisoformat(
                            pools[p]["expires"][:-1])

                        if valid_from < datetime.utcnow() < expires:
                            pool = p
                            break
            else:
                # show list of possible pools
                embed.title = "Pool Not Found!"
                embed.description = "Possible arguments are:"
                embed.colour = cmn.colours.bad
                for cty in study.pool_names:
                    levels = "`, `".join(study.pool_names[cty].keys())
                    embed.add_field(
                        name=f"**Country: `{cty}` {study.pool_emojis[cty]}**",
                        value=f"Levels: `{levels}`",
                        inline=False)
                embed.add_field(
                    name="**Random**",
                    value=
                    "To select a random pool or country, use `random` or `r`")
                await ctx.send(embed=embed)
                return

            pool_meta = pools[pool]

            async with self.session.get(
                    f"https://hamstudy.org/pools/{pool}") as resp:
                if resp.status != 200:
                    raise cmn.BotHTTPError(resp)
                pool = json.loads(await resp.read())["pool"]

            # Select a question
            if element:
                els = [el["id"] for el in pool]
                if element in els:
                    pool_section = pool[els.index(element)]["sections"]
                else:
                    embed.title = "Element Not Found!"
                    embed.description = f"Possible Elements for Country `{country}` and Level `{level}` are:"
                    embed.colour = cmn.colours.bad
                    embed.description += "\n\n" + "`" + "`, `".join(els) + "`"
                    await ctx.send(embed=embed)
                    return
            else:
                pool_section = random.choice(pool)["sections"]
            pool_questions = random.choice(pool_section)["questions"]
            question = random.choice(pool_questions)
            answers = question['answers']
            answers_str = ""
            answers_str_bolded = ""
            for letter, ans in answers.items():
                answers_str += f"{self.choices[letter]} {ans}\n"
                if letter == question["answer"]:
                    answers_str_bolded += f"{self.choices[letter]} **{ans}**\n"
                else:
                    answers_str_bolded += f"{self.choices[letter]} {ans}\n"

            embed.title = f"{study.pool_emojis[country]} {pool_meta['class']} {question['id']}"
            embed.description = self.source
            embed.add_field(name="Question",
                            value=question["text"],
                            inline=False)
            embed.add_field(name="Answers", value=answers_str, inline=False)
            embed.add_field(
                name="To Answer",
                value=
                ("Answer with reactions below. If not answered within 5 minutes,"
                 " the answer will be revealed."),
                inline=False)
            if "image" in question:
                image_url = f"https://hamstudy.org/images/{pool_meta['year']}/{question['image']}"
                embed.set_image(url=image_url)

        q_msg = await ctx.send(embed=embed)

        for i in range(len(answers)):
            await cmn.add_react(q_msg, list(self.choices.values())[i])
        await cmn.add_react(q_msg, cmn.emojis.question)

        def check(reaction, user):
            return (user.id != self.bot.user.id
                    and reaction.message.id == q_msg.id
                    and (str(reaction.emoji) in self.choices.values()
                         or str(reaction.emoji) == cmn.emojis.question))

        try:
            reaction, user = await self.bot.wait_for("reaction_add",
                                                     timeout=300.0,
                                                     check=check)
        except asyncio.TimeoutError:
            embed.set_field_at(1,
                               name="Answers",
                               value=answers_str_bolded,
                               inline=False)
            embed.set_field_at(
                2,
                name="Answer",
                value=
                (f"{cmn.emojis.clock} "
                 f"**Timed out!** The correct answer was {self.choices[question['answer']]}"
                 ))
            embed.colour = cmn.colours.timeout
            await q_msg.edit(embed=embed)
        else:
            if str(reaction.emoji) == cmn.emojis.question:
                embed.set_field_at(1,
                                   name="Answers",
                                   value=answers_str_bolded,
                                   inline=False)
                embed.set_field_at(
                    2,
                    name="Answer",
                    value=
                    f"The correct answer was {self.choices[question['answer']]}",
                    inline=False)
                embed.add_field(name="Answer Requested By",
                                value=str(user),
                                inline=False)
                embed.colour = cmn.colours.timeout
                await q_msg.edit(embed=embed)
            else:
                answers_str_checked = ""
                chosen_ans = self.choices_inv[str(reaction.emoji)]
                for letter, ans in answers.items():
                    answers_str_checked += f"{self.choices[letter]}"
                    if letter == question["answer"] == chosen_ans:
                        answers_str_checked += f"{cmn.emojis.check_mark} **{ans}**\n"
                    elif letter == question["answer"]:
                        answers_str_checked += f" **{ans}**\n"
                    elif letter == chosen_ans:
                        answers_str_checked += f"{cmn.emojis.x} {ans}\n"
                    else:
                        answers_str_checked += f" {ans}\n"

                if self.choices[question["answer"]] == str(reaction.emoji):
                    embed.set_field_at(1,
                                       name="Answers",
                                       value=answers_str_checked,
                                       inline=False)
                    embed.set_field_at(
                        2,
                        name="Answer",
                        value=(
                            f"{cmn.emojis.check_mark} "
                            f"**Correct!** The answer was {reaction.emoji}"))
                    embed.add_field(name="Answered By",
                                    value=str(user),
                                    inline=False)
                    embed.colour = cmn.colours.good
                    await q_msg.edit(embed=embed)
                else:
                    embed.set_field_at(1,
                                       name="Answers",
                                       value=answers_str_checked,
                                       inline=False)
                    embed.set_field_at(
                        2,
                        name="Answer",
                        value=
                        (f"{cmn.emojis.x} **Incorrect!** The correct answer was "
                         f"{self.choices[question['answer']]}, not {reaction.emoji}"
                         ))
                    embed.add_field(name="Answered By",
                                    value=str(user),
                                    inline=False)
                    embed.colour = cmn.colours.bad
                    await q_msg.edit(embed=embed)
Esempio n. 8
0
    async def _ae7q_call(self, ctx: commands.Context, callsign: str):
        """Looks up the history of a callsign on [ae7q.com](http://ae7q.com/)."""
        with ctx.typing():
            callsign = callsign.upper()
            desc = ""
            base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
            embed = cmn.embed_factory(ctx)

            async with self.session.get(base_url + callsign) as resp:
                if resp.status != 200:
                    raise cmn.BotHTTPError(resp)
                page = await resp.text()

            soup = BeautifulSoup(page, features="html.parser")
            tables = [[row for row in table.find_all("tr")]
                      for table in soup.select("table.Database")]

            table = tables[0]

            # find the first table in the page, and use it to make a description
            if len(table[0]) == 1:
                for row in table:
                    desc += " ".join(row.getText().split())
                    desc += "\n"
                desc = desc.replace(callsign, f"`{callsign}`")
                table = tables[1]

            table_headers = table[0].find_all("th")
            first_header = "".join(
                table_headers[0].strings) if len(table_headers) > 0 else None

            # catch if the wrong table was selected
            if first_header is None or first_header != "Entity Name":
                embed.title = f"AE7Q History for {callsign}"
                embed.colour = cmn.colours.bad
                embed.url = base_url + callsign
                embed.description = desc
                embed.description += f"\nNo records found for `{callsign}`"
                await ctx.send(embed=embed)
                return

            table = await process_table(table[1:])

            embed = cmn.embed_factory(ctx)
            embed.title = f"AE7Q History for {callsign}"
            embed.colour = cmn.colours.good
            embed.url = base_url + callsign

            # add the first three rows of the table to the embed
            for row in table[0:3]:
                header = f"**{row[0]}** ({row[1]})"  # **Name** (Applicant Type)
                body = (f"Class: *{row[2]}*\n"
                        f"Region: *{row[3]}*\n"
                        f"Status: *{row[4]}*\n"
                        f"Granted: *{row[5]}*\n"
                        f"Effective: *{row[6]}*\n"
                        f"Cancelled: *{row[7]}*\n"
                        f"Expires: *{row[8]}*")
                embed.add_field(name=header, value=body, inline=False)

            if len(table) > 3:
                desc += f"\nRecords 1 to 3 of {len(table)}. See ae7q.com for more..."

            embed.description = desc

            await ctx.send(embed=embed)
Esempio n. 9
0
    async def _ae7q_licensee(self, ctx: commands.Context, licensee_id: str):
        """Looks up the history of a licensee ID on [ae7q.com](http://ae7q.com/)."""
        with ctx.typing():
            licensee_id = licensee_id.upper()
            base_url = "http://ae7q.com/query/data/LicenseeIdHistory.php?ID="
            embed = cmn.embed_factory(ctx)

            async with self.session.get(base_url + licensee_id) as resp:
                if resp.status != 200:
                    raise cmn.BotHTTPError(resp)
                page = await resp.text()

            soup = BeautifulSoup(page, features="html.parser")
            tables = [[row for row in table.find_all("tr")]
                      for table in soup.select("table.Database")]

            if not len(tables):
                embed.title = f"AE7Q History for Licensee {licensee_id}"
                embed.colour = cmn.colours.bad
                embed.url = base_url + licensee_id
                embed.description = f"No records found for Licensee `{licensee_id}`"
                await ctx.send(embed=embed)
                return

            table = tables[0]

            table_headers = table[0].find_all("th")
            first_header = "".join(
                table_headers[0].strings) if len(table_headers) > 0 else None

            # catch if the wrong table was selected
            if first_header is None or not first_header.startswith("With FCC"):
                embed.title = f"AE7Q History for Licensee {licensee_id}"
                embed.colour = cmn.colours.bad
                embed.url = base_url + licensee_id
                embed.description = f"No records found for Licensee `{licensee_id}`"
                await ctx.send(embed=embed)
                return

            table = await process_table(table[2:])

            embed = cmn.embed_factory(ctx)
            embed.title = f"AE7Q History for Licensee {licensee_id}"
            embed.colour = cmn.colours.good
            embed.url = base_url + licensee_id

            # add the first three rows of the table to the embed
            for row in table[0:3]:
                header = f"**{row[0]}** ({row[3]})"  # **Callsign** (Applicant Type)
                body = (f"Name: *{row[2]}*\n"
                        f"Class: *{row[4]}*\n"
                        f"Region: *{row[1]}*\n"
                        f"Status: *{row[5]}*\n"
                        f"Granted: *{row[6]}*\n"
                        f"Effective: *{row[7]}*\n"
                        f"Cancelled: *{row[8]}*\n"
                        f"Expires: *{row[9]}*")
                embed.add_field(name=header, value=body, inline=False)

            if len(table) > 3:
                embed.description = f"Records 1 to 3 of {len(table)}. See ae7q.com for more..."

            await ctx.send(embed=embed)
Esempio n. 10
0
    async def _ae7q_trustee(self, ctx: commands.Context, callsign: str):
        """Looks up the licenses for which a licensee is trustee on [ae7q.com](http://ae7q.com/)."""
        with ctx.typing():
            callsign = callsign.upper()
            desc = ""
            base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
            embed = cmn.embed_factory(ctx)

            if not callsign.isalnum():
                embed = cmn.embed_factory(ctx)
                embed.title = "AE7Q Trustee History for Callsign"
                embed.colour = cmn.colours.bad
                embed.description = "Not a valid callsign!"
                await ctx.send(embed=embed)
                return

            async with self.session.get(base_url + callsign) as resp:
                if resp.status != 200:
                    raise cmn.BotHTTPError(resp)
                page = await resp.text()

            soup = BeautifulSoup(page, features="html.parser")
            tables = [[row for row in table.find_all("tr")]
                      for table in soup.select("table.Database")]

            try:
                table = tables[2] if len(tables[0][0]) == 1 else tables[1]
            except IndexError:
                embed.title = f"AE7Q Trustee History for {callsign}"
                embed.colour = cmn.colours.bad
                embed.url = base_url + callsign
                embed.description = desc
                embed.description += f"\nNo records found for `{callsign}`"
                await ctx.send(embed=embed)
                return

            table_headers = table[0].find_all("th")
            first_header = "".join(
                table_headers[0].strings) if len(table_headers) > 0 else None

            # catch if the wrong table was selected
            if first_header is None or not first_header.startswith("With"):
                embed.title = f"AE7Q Trustee History for {callsign}"
                embed.colour = cmn.colours.bad
                embed.url = base_url + callsign
                embed.description = desc
                embed.description += f"\nNo records found for `{callsign}`"
                await ctx.send(embed=embed)
                return

            table = await process_table(table[2:])

            embed = cmn.embed_factory(ctx)
            embed.title = f"AE7Q Trustee History for {callsign}"
            embed.colour = cmn.colours.good
            embed.url = base_url + callsign

            # add the first three rows of the table to the embed
            for row in table[0:3]:
                header = f"**{row[0]}** ({row[3]})"  # **Name** (Applicant Type)
                body = (f"Name: *{row[2]}*\n"
                        f"Region: *{row[1]}*\n"
                        f"Status: *{row[4]}*\n"
                        f"Granted: *{row[5]}*\n"
                        f"Effective: *{row[6]}*\n"
                        f"Cancelled: *{row[7]}*\n"
                        f"Expires: *{row[8]}*")
                embed.add_field(name=header, value=body, inline=False)

            if len(table) > 3:
                desc += f"\nRecords 1 to 3 of {len(table)}. See ae7q.com for more..."

            embed.description = desc

            await ctx.send(embed=embed)
Esempio n. 11
0
    async def _random_question(self, ctx: commands.Context, country: str = "", level: str = ""):
        """Gets a random question from [HamStudy's](https://hamstudy.org) question pools."""
        with ctx.typing():
            embed = cmn.embed_factory(ctx)

            country = country.lower()
            level = level.lower()

            if country in study.pool_names.keys():
                if level in study.pool_names[country].keys():
                    pool_name = study.pool_names[country][level]

                elif level in ("random", "r"):
                    # select a random level in that country
                    pool_name = random.choice(list(study.pool_names[country].values()))

                else:
                    # show list of possible pools
                    embed.title = "Pool Not Found!"
                    embed.description = "Possible arguments are:"
                    embed.colour = cmn.colours.bad
                    for cty in study.pool_names:
                        levels = "`, `".join(study.pool_names[cty].keys())
                        embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**",
                                        value=f"Levels: `{levels}`", inline=False)
                    embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`")
                    await ctx.send(embed=embed)
                    return

            elif country in ("random", "r"):
                # select a random country and level
                country = random.choice(list(study.pool_names.keys()))
                pool_name = random.choice(list(study.pool_names[country].values()))

            else:
                # show list of possible pools
                embed.title = "Pool Not Found!"
                embed.description = "Possible arguments are:"
                embed.colour = cmn.colours.bad
                for cty in study.pool_names:
                    levels = "`, `".join(study.pool_names[cty].keys())
                    embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**",
                                    value=f"Levels: `{levels}`", inline=False)
                embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`")
                await ctx.send(embed=embed)
                return

            pools = await self.hamstudy_get_pools()

            pool_matches = [p for p in pools.keys() if "_".join(p.split("_")[:-1]) == pool_name]

            if len(pool_matches) > 0:
                if len(pool_matches) == 1:
                    pool = pool_matches[0]
                else:
                    # look at valid_from and expires dates to find the correct one
                    for p in pool_matches:
                        valid_from = datetime.fromisoformat(pools[p]["valid_from"][:-1])
                        expires = datetime.fromisoformat(pools[p]["expires"][:-1])

                        if valid_from < datetime.utcnow() < expires:
                            pool = p
                            break
            else:
                # show list of possible pools
                embed.title = "Pool Not Found!"
                embed.description = "Possible arguments are:"
                embed.colour = cmn.colours.bad
                for cty in study.pool_names:
                    levels = "`, `".join(study.pool_names[cty].keys())
                    embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**",
                                    value=f"Levels: `{levels}`", inline=False)
                embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`")
                await ctx.send(embed=embed)
                return

            pool_meta = pools[pool]

            async with self.session.get(f"https://hamstudy.org/pools/{pool}") as resp:
                if resp.status != 200:
                    raise cmn.BotHTTPError(resp)
                pool = json.loads(await resp.read())["pool"]

            # Select a question
            pool_section = random.choice(pool)["sections"]
            pool_questions = random.choice(pool_section)["questions"]
            question = random.choice(pool_questions)

            embed.title = f"{study.pool_emojis[country]} {pool_meta['class']} {question['id']}"
            embed.description = self.source
            embed.add_field(name="Question:", value=question["text"], inline=False)
            embed.add_field(name="Answers:",
                            value=(f"**{cmn.emojis.a}** {question['answers']['A']}"
                                   f"\n**{cmn.emojis.b}** {question['answers']['B']}"
                                   f"\n**{cmn.emojis.c}** {question['answers']['C']}"
                                   f"\n**{cmn.emojis.d}** {question['answers']['D']}"),
                            inline=False)
            embed.add_field(name="To Answer:",
                            value=("Answer with reactions below. If not answered within 10 minutes,"
                                   " the answer will be revealed."),
                            inline=False)
            if "image" in question:
                image_url = f"https://hamstudy.org/images/{pool_meta['year']}/{question['image']}"
                embed.set_image(url=image_url)

        q_msg = await ctx.send(embed=embed)

        await cmn.add_react(q_msg, cmn.emojis.a)
        await cmn.add_react(q_msg, cmn.emojis.b)
        await cmn.add_react(q_msg, cmn.emojis.c)
        await cmn.add_react(q_msg, cmn.emojis.d)

        def check(reaction, user):
            return (user.id != self.bot.user.id
                    and reaction.message.id == q_msg.id
                    and str(reaction.emoji) in self.choices.keys())

        try:
            reaction, user = await self.bot.wait_for("reaction_add", timeout=600.0, check=check)
        except asyncio.TimeoutError:
            embed.remove_field(2)
            embed.add_field(name="Answer:", value=f"Timed out! The correct answer was **{question['answer']}**.")
            await q_msg.edit(embed=embed)
        else:
            if self.choices[str(reaction.emoji)] == question["answer"]:
                embed.remove_field(2)
                embed.add_field(name="Answer:", value=f"Correct! The answer was **{question['answer']}**.")
                embed.colour = cmn.colours.good
                await q_msg.edit(embed=embed)
            else:
                embed.remove_field(2)
                embed.add_field(name="Answer:", value=f"Incorrect! The correct answer was **{question['answer']}**.")
                embed.colour = cmn.colours.bad
                await q_msg.edit(embed=embed)