From 43a24d614b41507e0c9bd946bfd9e8ee5ca3ec3d Mon Sep 17 00:00:00 2001 From: classabbyamp <5366828+classabbyamp@users.noreply.github.com> Date: Wed, 20 Jan 2021 02:48:38 -0500 Subject: [PATCH 1/9] add METAR and TAF commands (#340) fixes #171 --- CHANGELOG.md | 1 + exts/weather.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71a4f04..3562965 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added - MUF and foF2 maps from [prop.kc2g.com](https://prop.kc2g.com/). +- Commands to show METAR (`?metar`) and TAF (`?taf`) (aeronautical weather conditions). ### Changed - New colour theme for `?greyline`. - Moved great circle distance and bearing calculation from `?ungrid` to `?griddistance`. diff --git a/exts/weather.py b/exts/weather.py index f899fe7..b4871e6 100644 --- a/exts/weather.py +++ b/exts/weather.py @@ -9,7 +9,11 @@ the GNU General Public License, version 2. import re +from typing import List +import aiohttp + +from discord import Embed import discord.ext.commands as commands import common as cmn @@ -20,8 +24,10 @@ class WeatherCog(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot + self.session = aiohttp.ClientSession(connector=bot.qrm.connector) - @commands.command(aliases=["solar", "bandconditions", "cond", "condx", "conditions"], category=cmn.cat.weather) + @commands.command(name="solarweather", aliases=["solar", "bandconditions", "cond", "condx", "conditions"], + category=cmn.cat.weather) async def solarweather(self, ctx: commands.Context): """Gets a solar weather report.""" embed = cmn.embed_factory(ctx) @@ -103,6 +109,73 @@ class WeatherCog(commands.Cog): embed.set_image(url=f"http://wttr.in/{loc}_0{units}pnFQ.png") await ctx.send(embed=embed) + @commands.command(name="metar", category=cmn.cat.weather) + async def metar(self, ctx: commands.Context, airport: str, hours: int = 0): + """Gets current raw METAR (Meteorological Terminal Aviation Routine Weather Report) for an airport. \ + Optionally, a number of hours can be given to show a number of hours of historical METAR data. + + Airports should be given as an \ + [ICAO code](https://en.wikipedia.org/wiki/List_of_airports_by_IATA_and_ICAO_code).""" + await ctx.send(embed=await self.gen_metar_taf_embed(ctx, airport, hours, False)) + + @commands.command(name="taf", category=cmn.cat.weather) + async def taf(self, ctx: commands.Context, airport: str): + """Gets forecasted raw TAF (Terminal Aerodrome Forecast) data for an airport. Includes the latest METAR data. + + Airports should be given as an \ + [ICAO code](https://en.wikipedia.org/wiki/List_of_airports_by_IATA_and_ICAO_code).""" + await ctx.send(embed=await self.gen_metar_taf_embed(ctx, airport, 0, True)) + + async def gen_metar_taf_embed(self, ctx: commands.Context, airport: str, hours: int, taf: bool) -> Embed: + embed = cmn.embed_factory(ctx) + airport = airport.upper() + + if re.fullmatch(r"\w(\w|\d){2,3}", airport): + metar = await self.get_metar_taf_data(airport, hours, taf) + + if taf: + embed.title = f"Current TAF for {airport}" + elif hours > 0: + embed.title = f"METAR for {airport} for the last {hours} hour{'s' if hours > 1 else ''}" + else: + embed.title = f"Current METAR for {airport}" + + embed.description = "Data from [aviationweather.gov](https://www.aviationweather.gov/metar/data)." + embed.colour = cmn.colours.good + + data = "\n".join(metar) + embed.description += f"\n\n```\n{data}\n```" + else: + embed.title = "Invalid airport given!" + embed.colour = cmn.colours.bad + return embed + + 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("")[1].split("")[0].strip() + # split at
s + data = re.split(r"", 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("", "").replace("", "") + line = line.replace("", "").replace("", "") + line = line.replace("
", "\n").replace(" ", " ") + line = line.strip("\n") + parsed.append(line) + return parsed + def setup(bot: commands.Bot): bot.add_cog(WeatherCog(bot)) From b091b22c172587d5dbb862f42c883eed322076b2 Mon Sep 17 00:00:00 2001 From: classabbyamp <5366828+classabbyamp@users.noreply.github.com> Date: Mon, 25 Jan 2021 17:24:13 -0500 Subject: [PATCH 2/9] add resources contrib info to ?info, ?issue, and readme (#343) fixes #307 --- README.md | 2 ++ exts/base.py | 7 +++++-- info.py | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2a849ad..b125ea1 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ $ run.sh Check out the [contribution guidelines](/CONTRIBUTING.md) for more information about how to contribute to this project. +All issues and requests related to resources (including maps, band charts, data) should be added in [miaowware/qrm-resources](https://github.com/miaowware/qrm-resources). + ## Copyright Copyright (C) 2019-2020 Abigail Gold, 0x5c diff --git a/exts/base.py b/exts/base.py index 970373b..bae2853 100644 --- a/exts/base.py +++ b/exts/base.py @@ -170,8 +170,11 @@ class BaseCog(commands.Cog): """Shows how to create a bug report or feature request about the bot.""" embed = cmn.embed_factory(ctx) embed.title = "Found a bug? Have a feature request?" - embed.description = ("Submit an issue on the [issue tracker]" - "(https://github.com/miaowware/qrm2/issues)!") + embed.description = """Submit an issue on the [issue tracker](https://github.com/miaowware/qrm2/issues)! + + All issues and requests related to resources (including maps, band charts, data) \ + should be added in \ + [miaowware/qrm-resources](https://github.com/miaowware/qrm-resources/issues).""" await ctx.send(embed=embed) @commands.command(name="echo", aliases=["e"], category=cmn.cat.admin) diff --git a/info.py b/info.py index 04b38c1..e673c23 100644 --- a/info.py +++ b/info.py @@ -11,6 +11,9 @@ the GNU General Public License, version 2. authors = ("@ClassAbbyAmplifier#2229", "@0x5c#0639") description = """A bot with various useful ham radio-related functions, written in Python.""" license = "Released under the GNU General Public License v2" -contributing = "Check out the source on GitHub, contributions welcome: https://github.com/miaowware/qrm2" +contributing = """Check out the [source on GitHub](https://github.com/miaowware/qrm2). Contributions are welcome! + + All issues and requests related to resources (including maps, band charts, data) should be added \ + in [miaowware/qrm-resources](https://github.com/miaowware/qrm-resources).""" release = "2.5.1" bot_server = "https://discord.gg/Ntbg3J4" From 6dfc05217fe4ccb44b91371d0cfa3d9edf59567c Mon Sep 17 00:00:00 2001 From: classabbyamp <5366828+classabbyamp@users.noreply.github.com> Date: Mon, 25 Jan 2021 17:24:45 -0500 Subject: [PATCH 3/9] add the ability to select an element to hamstudy (#344) fixes #208 --- CHANGELOG.md | 1 + exts/study.py | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3562965..dd32f06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - MUF and foF2 maps from [prop.kc2g.com](https://prop.kc2g.com/). - Commands to show METAR (`?metar`) and TAF (`?taf`) (aeronautical weather conditions). +- The ability to select an element of a pool in `?hamstudy`. ### Changed - New colour theme for `?greyline`. - Moved great circle distance and bearing calculation from `?ungrid` to `?griddistance`. diff --git a/exts/study.py b/exts/study.py index f55dd9a..d4fc5dc 100644 --- a/exts/study.py +++ b/exts/study.py @@ -31,13 +31,14 @@ class StudyCog(commands.Cog): self.session = aiohttp.ClientSession(connector=bot.qrm.connector) @commands.command(name="hamstudy", aliases=["rq", "randomquestion", "randomq"], category=cmn.cat.study) - async def _random_question(self, ctx: commands.Context, country: str = "", level: str = ""): + 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(): @@ -115,7 +116,19 @@ class StudyCog(commands.Cog): pool = json.loads(await resp.read())["pool"] # Select a question - pool_section = random.choice(pool)["sections"] + 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) From eb98e295d2fe10a4c4155649f6a91f68c64f3d9a Mon Sep 17 00:00:00 2001 From: classabbyamp <5366828+classabbyamp@users.noreply.github.com> Date: Mon, 25 Jan 2021 17:25:26 -0500 Subject: [PATCH 4/9] add git commit to info output (#342) fixes #341 --- exts/base.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/exts/base.py b/exts/base.py index bae2853..aee4a03 100644 --- a/exts/base.py +++ b/exts/base.py @@ -11,6 +11,7 @@ the GNU General Public License, version 2. import random import re from typing import Union +import pathlib import discord import discord.ext.commands as commands @@ -101,6 +102,22 @@ class BaseCog(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot self.changelog = parse_changelog() + commit_file = pathlib.Path("git_commit") + dot_git = pathlib.Path(".git") + if commit_file.is_file(): + with commit_file.open() as f: + self.commit = f.readline().strip()[:7] + elif dot_git.is_dir(): + head_file = pathlib.Path(dot_git, "HEAD") + if head_file.is_file(): + with head_file.open() as hf: + head = hf.readline().split(": ")[1].strip() + branch_file = pathlib.Path(dot_git, head) + if branch_file.is_file(): + with branch_file.open() as bf: + self.commit = bf.readline().strip()[:7] + else: + self.commit = "" @commands.command(name="info", aliases=["about"]) async def _info(self, ctx: commands.Context): @@ -110,7 +127,7 @@ class BaseCog(commands.Cog): embed.description = info.description embed.add_field(name="Authors", value=", ".join(info.authors)) embed.add_field(name="License", value=info.license) - embed.add_field(name="Version", value=f"v{info.release}") + embed.add_field(name="Version", value=f"v{info.release} {'(`' + self.commit + '`)' if self.commit else ''}") embed.add_field(name="Contributing", value=info.contributing, inline=False) embed.add_field(name="Official Server", value=info.bot_server, inline=False) embed.set_thumbnail(url=str(self.bot.user.avatar_url)) From 4ab4748b9f242bab6cbe54e248ad808d292c2a48 Mon Sep 17 00:00:00 2001 From: Fran Rogers Date: Fri, 29 Jan 2021 22:55:26 -0500 Subject: [PATCH 5/9] Validate inputs for ?ae7q and ?qrz commands --- exts/ae7q.py | 42 ++++++++++++++++++++++++++++++++++++++++++ exts/qrz.py | 9 +++++++++ 2 files changed, 51 insertions(+) diff --git a/exts/ae7q.py b/exts/ae7q.py index b11aeeb..eacad7e 100644 --- a/exts/ae7q.py +++ b/exts/ae7q.py @@ -16,6 +16,8 @@ the GNU General Public License, version 2. # KC4USA: reserved, no call history, *but* has application history +import re + import aiohttp from bs4 import BeautifulSoup @@ -44,6 +46,14 @@ class AE7QCog(commands.Cog): base_url = "http://ae7q.com/query/data/CallHistory.php?CALL=" embed = cmn.embed_factory(ctx) + if not re.match('[A-Z0-9]+$', callsign): + embed = cmn.embed_factory(ctx) + embed.title = "AE7Q 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) @@ -110,6 +120,14 @@ class AE7QCog(commands.Cog): base_url = "http://ae7q.com/query/data/CallHistory.php?CALL=" embed = cmn.embed_factory(ctx) + if not re.match('[A-Z0-9]+$', callsign): + 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) @@ -178,6 +196,14 @@ class AE7QCog(commands.Cog): base_url = "http://ae7q.com/query/data/CallHistory.php?CALL=" embed = cmn.embed_factory(ctx) + if not re.match('[A-Z0-9]+$', callsign): + embed = cmn.embed_factory(ctx) + embed.title = "AE7Q Application 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) @@ -250,6 +276,14 @@ class AE7QCog(commands.Cog): base_url = "http://ae7q.com/query/data/FrnHistory.php?FRN=" embed = cmn.embed_factory(ctx) + if not re.match('[0-9]+$', frn): + embed = cmn.embed_factory(ctx) + embed.title = "AE7Q History for FRN" + embed.colour = cmn.colours.bad + embed.description = "Not a valid FRN!" + await ctx.send(embed=embed) + return + async with self.session.get(base_url + frn) as resp: if resp.status != 200: raise cmn.BotHTTPError(resp) @@ -313,6 +347,14 @@ class AE7QCog(commands.Cog): base_url = "http://ae7q.com/query/data/LicenseeIdHistory.php?ID=" embed = cmn.embed_factory(ctx) + if not re.match('[A-Z][0-9]+$', licensee_id, re.IGNORECASE): + embed = cmn.embed_factory(ctx) + embed.title = "AE7Q History for Licensee" + embed.colour = cmn.colours.bad + embed.description = "Not a valid licensee ID!" + await ctx.send(embed=embed) + return + async with self.session.get(base_url + licensee_id) as resp: if resp.status != 200: raise cmn.BotHTTPError(resp) diff --git a/exts/qrz.py b/exts/qrz.py index 0e79e4b..776e0fa 100644 --- a/exts/qrz.py +++ b/exts/qrz.py @@ -9,6 +9,7 @@ the GNU General Public License, version 2. from io import BytesIO +import re import aiohttp from lxml import etree @@ -31,6 +32,14 @@ class QRZCog(commands.Cog): """Looks up a callsign on [QRZ.com](https://www.qrz.com/). Add `--link` to only link the QRZ page.""" flags = [f.lower() for f in flags] + if not re.match('[A-Z0-9]+$', callsign, re.IGNORECASE): + embed = cmn.embed_factory(ctx) + embed.title = "QRZ Data for Callsign" + embed.colour = cmn.colours.bad + embed.description = "Not a valid callsign!" + await ctx.send(embed=embed) + return + if keys.qrz_user == "" or keys.qrz_pass == "" or "--link" in flags: await ctx.send(f"http://qrz.com/db/{callsign}") return From 483a0bad195e3379f54d1f0b8d6e747194eefe78 Mon Sep 17 00:00:00 2001 From: Fran Rogers Date: Fri, 29 Jan 2021 23:43:30 -0500 Subject: [PATCH 6/9] Use str.isalnum and str.isdecimal instead of re.match for ?ae7q and ?qrz input validation --- exts/ae7q.py | 12 +++++------- exts/qrz.py | 3 +-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/exts/ae7q.py b/exts/ae7q.py index eacad7e..8cf76c7 100644 --- a/exts/ae7q.py +++ b/exts/ae7q.py @@ -16,8 +16,6 @@ the GNU General Public License, version 2. # KC4USA: reserved, no call history, *but* has application history -import re - import aiohttp from bs4 import BeautifulSoup @@ -46,7 +44,7 @@ class AE7QCog(commands.Cog): base_url = "http://ae7q.com/query/data/CallHistory.php?CALL=" embed = cmn.embed_factory(ctx) - if not re.match('[A-Z0-9]+$', callsign): + if not callsign.isalnum(): embed = cmn.embed_factory(ctx) embed.title = "AE7Q History for Callsign" embed.colour = cmn.colours.bad @@ -120,7 +118,7 @@ class AE7QCog(commands.Cog): base_url = "http://ae7q.com/query/data/CallHistory.php?CALL=" embed = cmn.embed_factory(ctx) - if not re.match('[A-Z0-9]+$', callsign): + if not callsign.isalnum(): embed = cmn.embed_factory(ctx) embed.title = "AE7Q Trustee History for Callsign" embed.colour = cmn.colours.bad @@ -196,7 +194,7 @@ class AE7QCog(commands.Cog): base_url = "http://ae7q.com/query/data/CallHistory.php?CALL=" embed = cmn.embed_factory(ctx) - if not re.match('[A-Z0-9]+$', callsign): + if not callsign.isalnum(): embed = cmn.embed_factory(ctx) embed.title = "AE7Q Application History for Callsign" embed.colour = cmn.colours.bad @@ -276,7 +274,7 @@ class AE7QCog(commands.Cog): base_url = "http://ae7q.com/query/data/FrnHistory.php?FRN=" embed = cmn.embed_factory(ctx) - if not re.match('[0-9]+$', frn): + if not frn.isdecimal(): embed = cmn.embed_factory(ctx) embed.title = "AE7Q History for FRN" embed.colour = cmn.colours.bad @@ -347,7 +345,7 @@ class AE7QCog(commands.Cog): base_url = "http://ae7q.com/query/data/LicenseeIdHistory.php?ID=" embed = cmn.embed_factory(ctx) - if not re.match('[A-Z][0-9]+$', licensee_id, re.IGNORECASE): + if not licensee_id.isalnum(): embed = cmn.embed_factory(ctx) embed.title = "AE7Q History for Licensee" embed.colour = cmn.colours.bad diff --git a/exts/qrz.py b/exts/qrz.py index 776e0fa..169635e 100644 --- a/exts/qrz.py +++ b/exts/qrz.py @@ -9,7 +9,6 @@ the GNU General Public License, version 2. from io import BytesIO -import re import aiohttp from lxml import etree @@ -32,7 +31,7 @@ class QRZCog(commands.Cog): """Looks up a callsign on [QRZ.com](https://www.qrz.com/). Add `--link` to only link the QRZ page.""" flags = [f.lower() for f in flags] - if not re.match('[A-Z0-9]+$', callsign, re.IGNORECASE): + if not callsign.isalnum(): embed = cmn.embed_factory(ctx) embed.title = "QRZ Data for Callsign" embed.colour = cmn.colours.bad From d7e544edcd1321d5bc2883856b527c4ab0f444c8 Mon Sep 17 00:00:00 2001 From: Abigail G Date: Sun, 28 Feb 2021 01:51:19 -0500 Subject: [PATCH 7/9] hamstudy improvements - added orange colour for timeouts - added emojis in the last field - made the correct answer bold after answering/timeout - minor code optimisations --- common.py | 3 +++ exts/study.py | 50 +++++++++++++++++++++++++++++--------------------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/common.py b/common.py index af0e9f9..47caf11 100644 --- a/common.py +++ b/common.py @@ -36,6 +36,7 @@ colours = SimpleNamespace( good=0x43B581, neutral=0x7289DA, bad=0xF04747, + timeout=0xF26522, ) # meow @@ -56,10 +57,12 @@ emojis = SimpleNamespace( question="❓", no_entry="⛔", bangbang="‼️", + clock="⏱", a="🇦", b="🇧", c="🇨", d="🇩", + e="🇪", ) paths = SimpleNamespace( diff --git a/exts/study.py b/exts/study.py index d4fc5dc..1739267 100644 --- a/exts/study.py +++ b/exts/study.py @@ -22,7 +22,7 @@ from resources import study class StudyCog(commands.Cog): - choices = {cmn.emojis.a: "A", cmn.emojis.b: "B", cmn.emojis.c: "C", cmn.emojis.d: "D"} + choices = {"A": cmn.emojis.a, "B": cmn.emojis.b, "C": cmn.emojis.c, "D": cmn.emojis.d, "E": cmn.emojis.e} def __init__(self, bot: commands.Bot): self.bot = bot @@ -131,18 +131,22 @@ class StudyCog(commands.Cog): 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=(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="Answers:", value=answers_str, inline=False) embed.add_field(name="To Answer:", - value=("Answer with reactions below. If not answered within 10 minutes," + value=("Answer with reactions below. If not answered within 5 minutes," " the answer will be revealed."), inline=False) if "image" in question: @@ -151,31 +155,35 @@ class StudyCog(commands.Cog): 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) + for i in range(len(answers)): + await cmn.add_react(q_msg, list(self.choices.values())[i]) 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()) + and str(reaction.emoji) in self.choices.values()) try: - reaction, user = await self.bot.wait_for("reaction_add", timeout=600.0, check=check) + reaction, _ = await self.bot.wait_for("reaction_add", timeout=300.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']}**.") + 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 self.choices[str(reaction.emoji)] == question["answer"]: - embed.remove_field(2) - embed.add_field(name="Answer:", value=f"Correct! The answer was **{question['answer']}**.") + if self.choices[question["answer"]] == str(reaction.emoji): + embed.set_field_at(1, name="Answers:", value=answers_str_bolded, inline=False) + embed.set_field_at(2, name="Answer:", value=(f"{cmn.emojis.check_mark} " + f"**Correct!** The answer was {reaction.emoji}")) 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.set_field_at(1, name="Answers:", value=answers_str_bolded, 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.colour = cmn.colours.bad await q_msg.edit(embed=embed) From 4d64d22ec6f1a3f37671a13fac9c3d8247ca8e55 Mon Sep 17 00:00:00 2001 From: Abigail G Date: Sat, 6 Mar 2021 21:16:00 -0500 Subject: [PATCH 8/9] add emojis, question mark, and changelog fixes #347 --- CHANGELOG.md | 2 ++ exts/study.py | 61 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd32f06..b40efdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - MUF and foF2 maps from [prop.kc2g.com](https://prop.kc2g.com/). - Commands to show METAR (`?metar`) and TAF (`?taf`) (aeronautical weather conditions). - The ability to select an element of a pool in `?hamstudy`. +- The ability to answer ❓ to a HamStudy question to get the answer. ### Changed - New colour theme for `?greyline`. - Moved great circle distance and bearing calculation from `?ungrid` to `?griddistance`. - `?ungrid` to `?latlong`. - Renamed `?cond` to `?solar`. +- Reduced `?hamstudy` timeout to 5 minutes. ### Fixed - Weird image caching situation for `?greyline` on Discord's side. ### Deprecated diff --git a/exts/study.py b/exts/study.py index 1739267..530d6db 100644 --- a/exts/study.py +++ b/exts/study.py @@ -23,6 +23,7 @@ from resources import study class StudyCog(commands.Cog): choices = {"A": cmn.emojis.a, "B": cmn.emojis.b, "C": cmn.emojis.c, "D": cmn.emojis.d, "E": cmn.emojis.e} + choices_inv = {y: x for x, y in choices.items()} def __init__(self, bot: commands.Bot): self.bot = bot @@ -143,9 +144,9 @@ class StudyCog(commands.Cog): 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:", + 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) @@ -157,35 +158,59 @@ class StudyCog(commands.Cog): 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()) + and (str(reaction.emoji) in self.choices.values() or str(reaction.emoji) == cmn.emojis.question)) try: - reaction, _ = await self.bot.wait_for("reaction_add", timeout=300.0, check=check) + 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:", + 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 self.choices[question["answer"]] == str(reaction.emoji): - embed.set_field_at(1, name="Answers:", value=answers_str_bolded, inline=False) - embed.set_field_at(2, name="Answer:", value=(f"{cmn.emojis.check_mark} " - f"**Correct!** The answer was {reaction.emoji}")) - embed.colour = cmn.colours.good + 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: - embed.set_field_at(1, name="Answers:", value=answers_str_bolded, 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.colour = cmn.colours.bad - await q_msg.edit(embed=embed) + 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) async def hamstudy_get_pools(self): async with self.session.get("https://hamstudy.org/pools/") as resp: From be6c78f4deb98780c4b39f44241a15e13d39067a Mon Sep 17 00:00:00 2001 From: Abigail G Date: Sat, 6 Mar 2021 23:55:38 -0500 Subject: [PATCH 9/9] fix issue with pushing docker images to ghcr --- .github/workflows/docker.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6ee0915..53ff245 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -57,7 +57,9 @@ jobs: [[ "$VERSION" != "dev" ]] && docker tag $IMAGE_NAME $IMAGE_ID:latest || true - name: Push images to registry - run: docker push ${{ steps.tag_image.outputs.image_id }} + run: | + [[ "${{ steps.tag_image.outputs.version }}" != "dev" ]] && docker push ${{ steps.tag_image.outputs.image_id }}:latest || true + docker push ${{ steps.tag_image.outputs.image_id }}:${{ steps.tag_image.outputs.version }} - name: Deploy official images id: deploy_images