mirror of https://github.com/miaowware/qrm2.git
Merge branch 'master' into helppfx
This commit is contained in:
commit
950840be60
|
@ -57,7 +57,9 @@ jobs:
|
||||||
[[ "$VERSION" != "dev" ]] && docker tag $IMAGE_NAME $IMAGE_ID:latest || true
|
[[ "$VERSION" != "dev" ]] && docker tag $IMAGE_NAME $IMAGE_ID:latest || true
|
||||||
|
|
||||||
- name: Push images to registry
|
- 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
|
- name: Deploy official images
|
||||||
id: deploy_images
|
id: deploy_images
|
||||||
|
|
|
@ -7,12 +7,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
- MUF and foF2 maps from [prop.kc2g.com](https://prop.kc2g.com/).
|
- 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.
|
||||||
- The list of available prefixes to `?help` when there is more than one.
|
- The list of available prefixes to `?help` when there is more than one.
|
||||||
### Changed
|
### Changed
|
||||||
- New colour theme for `?greyline`.
|
- New colour theme for `?greyline`.
|
||||||
- Moved great circle distance and bearing calculation from `?ungrid` to `?griddistance`.
|
- Moved great circle distance and bearing calculation from `?ungrid` to `?griddistance`.
|
||||||
- `?ungrid` to `?latlong`.
|
- `?ungrid` to `?latlong`.
|
||||||
- Renamed `?cond` to `?solar`.
|
- Renamed `?cond` to `?solar`.
|
||||||
|
- Reduced `?hamstudy` timeout to 5 minutes.
|
||||||
### Fixed
|
### Fixed
|
||||||
- Weird image caching situation for `?greyline` on Discord's side.
|
- Weird image caching situation for `?greyline` on Discord's side.
|
||||||
- The help command was not using the prefix it was invoked with.
|
- The help command was not using the prefix it was invoked with.
|
||||||
|
|
|
@ -32,6 +32,8 @@ $ run.sh
|
||||||
|
|
||||||
Check out the [contribution guidelines](/CONTRIBUTING.md) for more information about how to contribute to this project.
|
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
|
||||||
|
|
||||||
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
|
@ -36,6 +36,7 @@ colours = SimpleNamespace(
|
||||||
good=0x43B581,
|
good=0x43B581,
|
||||||
neutral=0x7289DA,
|
neutral=0x7289DA,
|
||||||
bad=0xF04747,
|
bad=0xF04747,
|
||||||
|
timeout=0xF26522,
|
||||||
)
|
)
|
||||||
|
|
||||||
# meow
|
# meow
|
||||||
|
@ -56,10 +57,12 @@ emojis = SimpleNamespace(
|
||||||
question="❓",
|
question="❓",
|
||||||
no_entry="⛔",
|
no_entry="⛔",
|
||||||
bangbang="‼️",
|
bangbang="‼️",
|
||||||
|
clock="⏱",
|
||||||
a="🇦",
|
a="🇦",
|
||||||
b="🇧",
|
b="🇧",
|
||||||
c="🇨",
|
c="🇨",
|
||||||
d="🇩",
|
d="🇩",
|
||||||
|
e="🇪",
|
||||||
)
|
)
|
||||||
|
|
||||||
paths = SimpleNamespace(
|
paths = SimpleNamespace(
|
||||||
|
|
40
exts/ae7q.py
40
exts/ae7q.py
|
@ -44,6 +44,14 @@ class AE7QCog(commands.Cog):
|
||||||
base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
|
base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
|
|
||||||
|
if not callsign.isalnum():
|
||||||
|
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:
|
async with self.session.get(base_url + callsign) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
raise cmn.BotHTTPError(resp)
|
raise cmn.BotHTTPError(resp)
|
||||||
|
@ -110,6 +118,14 @@ class AE7QCog(commands.Cog):
|
||||||
base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
|
base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
|
||||||
embed = cmn.embed_factory(ctx)
|
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:
|
async with self.session.get(base_url + callsign) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
raise cmn.BotHTTPError(resp)
|
raise cmn.BotHTTPError(resp)
|
||||||
|
@ -178,6 +194,14 @@ class AE7QCog(commands.Cog):
|
||||||
base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
|
base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
|
|
||||||
|
if not callsign.isalnum():
|
||||||
|
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:
|
async with self.session.get(base_url + callsign) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
raise cmn.BotHTTPError(resp)
|
raise cmn.BotHTTPError(resp)
|
||||||
|
@ -250,6 +274,14 @@ class AE7QCog(commands.Cog):
|
||||||
base_url = "http://ae7q.com/query/data/FrnHistory.php?FRN="
|
base_url = "http://ae7q.com/query/data/FrnHistory.php?FRN="
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
|
|
||||||
|
if not frn.isdecimal():
|
||||||
|
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:
|
async with self.session.get(base_url + frn) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
raise cmn.BotHTTPError(resp)
|
raise cmn.BotHTTPError(resp)
|
||||||
|
@ -313,6 +345,14 @@ class AE7QCog(commands.Cog):
|
||||||
base_url = "http://ae7q.com/query/data/LicenseeIdHistory.php?ID="
|
base_url = "http://ae7q.com/query/data/LicenseeIdHistory.php?ID="
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
|
|
||||||
|
if not licensee_id.isalnum():
|
||||||
|
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:
|
async with self.session.get(base_url + licensee_id) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
raise cmn.BotHTTPError(resp)
|
raise cmn.BotHTTPError(resp)
|
||||||
|
|
26
exts/base.py
26
exts/base.py
|
@ -11,6 +11,7 @@ the GNU General Public License, version 2.
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
import pathlib
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
|
@ -103,6 +104,22 @@ class BaseCog(commands.Cog):
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.changelog = parse_changelog()
|
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"])
|
@commands.command(name="info", aliases=["about"])
|
||||||
async def _info(self, ctx: commands.Context):
|
async def _info(self, ctx: commands.Context):
|
||||||
|
@ -112,7 +129,7 @@ class BaseCog(commands.Cog):
|
||||||
embed.description = info.description
|
embed.description = info.description
|
||||||
embed.add_field(name="Authors", value=", ".join(info.authors))
|
embed.add_field(name="Authors", value=", ".join(info.authors))
|
||||||
embed.add_field(name="License", value=info.license)
|
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="Contributing", value=info.contributing, inline=False)
|
||||||
embed.add_field(name="Official Server", value=info.bot_server, inline=False)
|
embed.add_field(name="Official Server", value=info.bot_server, inline=False)
|
||||||
embed.set_thumbnail(url=str(self.bot.user.avatar_url))
|
embed.set_thumbnail(url=str(self.bot.user.avatar_url))
|
||||||
|
@ -172,8 +189,11 @@ class BaseCog(commands.Cog):
|
||||||
"""Shows how to create a bug report or feature request about the bot."""
|
"""Shows how to create a bug report or feature request about the bot."""
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = "Found a bug? Have a feature request?"
|
embed.title = "Found a bug? Have a feature request?"
|
||||||
embed.description = ("Submit an issue on the [issue tracker]"
|
embed.description = """Submit an issue on the [issue tracker](https://github.com/miaowware/qrm2/issues)!
|
||||||
"(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)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@commands.command(name="echo", aliases=["e"], category=cmn.cat.admin)
|
@commands.command(name="echo", aliases=["e"], category=cmn.cat.admin)
|
||||||
|
|
|
@ -31,6 +31,14 @@ class QRZCog(commands.Cog):
|
||||||
"""Looks up a callsign on [QRZ.com](https://www.qrz.com/). Add `--link` to only link the QRZ page."""
|
"""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]
|
flags = [f.lower() for f in flags]
|
||||||
|
|
||||||
|
if not callsign.isalnum():
|
||||||
|
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:
|
if keys.qrz_user == "" or keys.qrz_pass == "" or "--link" in flags:
|
||||||
await ctx.send(f"http://qrz.com/db/{callsign}")
|
await ctx.send(f"http://qrz.com/db/{callsign}")
|
||||||
return
|
return
|
||||||
|
|
102
exts/study.py
102
exts/study.py
|
@ -22,7 +22,8 @@ from resources import study
|
||||||
|
|
||||||
|
|
||||||
class StudyCog(commands.Cog):
|
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}
|
||||||
|
choices_inv = {y: x for x, y in choices.items()}
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
@ -31,13 +32,14 @@ class StudyCog(commands.Cog):
|
||||||
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
|
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
|
||||||
|
|
||||||
@commands.command(name="hamstudy", aliases=["rq", "randomquestion", "randomq"], category=cmn.cat.study)
|
@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."""
|
"""Gets a random question from [HamStudy's](https://hamstudy.org) question pools."""
|
||||||
with ctx.typing():
|
with ctx.typing():
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
|
|
||||||
country = country.lower()
|
country = country.lower()
|
||||||
level = level.lower()
|
level = level.lower()
|
||||||
|
element = element.upper()
|
||||||
|
|
||||||
if country in study.pool_names.keys():
|
if country in study.pool_names.keys():
|
||||||
if level in study.pool_names[country].keys():
|
if level in study.pool_names[country].keys():
|
||||||
|
@ -115,21 +117,37 @@ class StudyCog(commands.Cog):
|
||||||
pool = json.loads(await resp.read())["pool"]
|
pool = json.loads(await resp.read())["pool"]
|
||||||
|
|
||||||
# Select a question
|
# 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"]
|
pool_questions = random.choice(pool_section)["questions"]
|
||||||
question = random.choice(pool_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.title = f"{study.pool_emojis[country]} {pool_meta['class']} {question['id']}"
|
||||||
embed.description = self.source
|
embed.description = self.source
|
||||||
embed.add_field(name="Question:", value=question["text"], inline=False)
|
embed.add_field(name="Question", value=question["text"], inline=False)
|
||||||
embed.add_field(name="Answers:",
|
embed.add_field(name="Answers", value=answers_str, inline=False)
|
||||||
value=(f"**{cmn.emojis.a}** {question['answers']['A']}"
|
embed.add_field(name="To Answer",
|
||||||
f"\n**{cmn.emojis.b}** {question['answers']['B']}"
|
value=("Answer with reactions below. If not answered within 5 minutes,"
|
||||||
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."),
|
" the answer will be revealed."),
|
||||||
inline=False)
|
inline=False)
|
||||||
if "image" in question:
|
if "image" in question:
|
||||||
|
@ -138,33 +156,61 @@ class StudyCog(commands.Cog):
|
||||||
|
|
||||||
q_msg = await ctx.send(embed=embed)
|
q_msg = await ctx.send(embed=embed)
|
||||||
|
|
||||||
await cmn.add_react(q_msg, cmn.emojis.a)
|
for i in range(len(answers)):
|
||||||
await cmn.add_react(q_msg, cmn.emojis.b)
|
await cmn.add_react(q_msg, list(self.choices.values())[i])
|
||||||
await cmn.add_react(q_msg, cmn.emojis.c)
|
await cmn.add_react(q_msg, cmn.emojis.question)
|
||||||
await cmn.add_react(q_msg, cmn.emojis.d)
|
|
||||||
|
|
||||||
def check(reaction, user):
|
def check(reaction, user):
|
||||||
return (user.id != self.bot.user.id
|
return (user.id != self.bot.user.id
|
||||||
and reaction.message.id == q_msg.id
|
and reaction.message.id == q_msg.id
|
||||||
and str(reaction.emoji) in self.choices.keys())
|
and (str(reaction.emoji) in self.choices.values() or str(reaction.emoji) == cmn.emojis.question))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
reaction, user = await self.bot.wait_for("reaction_add", timeout=600.0, check=check)
|
reaction, user = await self.bot.wait_for("reaction_add", timeout=300.0, check=check)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
embed.remove_field(2)
|
embed.set_field_at(1, name="Answers", value=answers_str_bolded, inline=False)
|
||||||
embed.add_field(name="Answer:", value=f"Timed out! The correct answer was **{question['answer']}**.")
|
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)
|
await q_msg.edit(embed=embed)
|
||||||
else:
|
else:
|
||||||
if self.choices[str(reaction.emoji)] == question["answer"]:
|
if str(reaction.emoji) == cmn.emojis.question:
|
||||||
embed.remove_field(2)
|
embed.set_field_at(1, name="Answers", value=answers_str_bolded, inline=False)
|
||||||
embed.add_field(name="Answer:", value=f"Correct! The answer was **{question['answer']}**.")
|
embed.set_field_at(2, name="Answer",
|
||||||
embed.colour = cmn.colours.good
|
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)
|
await q_msg.edit(embed=embed)
|
||||||
else:
|
else:
|
||||||
embed.remove_field(2)
|
answers_str_checked = ""
|
||||||
embed.add_field(name="Answer:", value=f"Incorrect! The correct answer was **{question['answer']}**.")
|
chosen_ans = self.choices_inv[str(reaction.emoji)]
|
||||||
embed.colour = cmn.colours.bad
|
for letter, ans in answers.items():
|
||||||
await q_msg.edit(embed=embed)
|
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 def hamstudy_get_pools(self):
|
||||||
async with self.session.get("https://hamstudy.org/pools/") as resp:
|
async with self.session.get("https://hamstudy.org/pools/") as resp:
|
||||||
|
|
|
@ -9,7 +9,11 @@ the GNU General Public License, version 2.
|
||||||
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
from discord import Embed
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
|
|
||||||
import common as cmn
|
import common as cmn
|
||||||
|
@ -20,8 +24,10 @@ class WeatherCog(commands.Cog):
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = 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):
|
async def solarweather(self, ctx: commands.Context):
|
||||||
"""Gets a solar weather report."""
|
"""Gets a solar weather report."""
|
||||||
embed = cmn.embed_factory(ctx)
|
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")
|
embed.set_image(url=f"http://wttr.in/{loc}_0{units}pnFQ.png")
|
||||||
await ctx.send(embed=embed)
|
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("<!-- 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(" ", " ")
|
||||||
|
line = line.strip("\n")
|
||||||
|
parsed.append(line)
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: commands.Bot):
|
def setup(bot: commands.Bot):
|
||||||
bot.add_cog(WeatherCog(bot))
|
bot.add_cog(WeatherCog(bot))
|
||||||
|
|
5
info.py
5
info.py
|
@ -11,6 +11,9 @@ the GNU General Public License, version 2.
|
||||||
authors = ("@ClassAbbyAmplifier#2229", "@0x5c#0639")
|
authors = ("@ClassAbbyAmplifier#2229", "@0x5c#0639")
|
||||||
description = """A bot with various useful ham radio-related functions, written in Python."""
|
description = """A bot with various useful ham radio-related functions, written in Python."""
|
||||||
license = "Released under the GNU General Public License v2"
|
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"
|
release = "2.5.1"
|
||||||
bot_server = "https://discord.gg/Ntbg3J4"
|
bot_server = "https://discord.gg/Ntbg3J4"
|
||||||
|
|
Loading…
Reference in New Issue