Merge pull request #199 from classabbyamp/5c-newyearlint

New year linting
This commit is contained in:
0x5c 2020-02-15 05:53:48 -05:00 committed by GitHub
commit 27c290cff8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1042 additions and 877 deletions

View File

@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- The `hamstudy` command now uses the syntax `?hamstudy <country> <pool>`. - The `hamstudy` command now uses the syntax `?hamstudy <country> <pool>`.
- Replaced `hamstudyanswer` command with answering by reaction. - Replaced `hamstudyanswer` command with answering by reaction.
- Removed all generic error handling from commands. - Removed all generic error handling from commands.
- Cleaned up the description of multiple commands.
### Fixed ### Fixed
- Fixed ditto marks (") appearing in the ae7q call command. - Fixed ditto marks (") appearing in the ae7q call command.
- Fixed issue where incorrect table was parsed in ae7q call command. - Fixed issue where incorrect table was parsed in ae7q call command.

View File

@ -3,8 +3,8 @@ Common tools for the bot.
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
@ -30,33 +30,42 @@ __all__ = ["colours", "cat", "emojis", "paths", "ImageMetadata", "ImagesGroup",
# --- Common values --- # --- Common values ---
colours = SimpleNamespace(good=0x43B581, colours = SimpleNamespace(
neutral=0x7289DA, good=0x43B581,
bad=0xF04747) neutral=0x7289DA,
bad=0xF04747,
)
# meow # meow
cat = SimpleNamespace(lookup='Information Lookup', cat = SimpleNamespace(
fun='Fun', lookup="Information Lookup",
maps='Mapping', fun="Fun",
ref='Reference', maps="Mapping",
study='Exam Study', ref="Reference",
weather='Land and Space Weather', study="Exam Study",
admin='Bot Control') weather="Land and Space Weather",
admin="Bot Control",
)
emojis = SimpleNamespace(check_mark='', emojis = SimpleNamespace(
x='', check_mark="",
warning='⚠️', x="",
question='', warning="⚠️",
no_entry='', question="",
bangbang='‼️', no_entry="",
a='🇦', bangbang="‼️",
b='🇧', a="🇦",
c='🇨', b="🇧",
d='🇩') c="🇨",
d="🇩",
)
paths = SimpleNamespace(data=Path("./data/"), paths = SimpleNamespace(
resources=Path("./resources/"), data=Path("./data/"),
bandcharts=Path("./resources/img/bandcharts/"), resources=Path("./resources/"),
maps=Path("./resources/img/maps/")) bandcharts=Path("./resources/img/bandcharts/"),
maps=Path("./resources/img/maps/"),
)
# --- Classes --- # --- Classes ---
@ -117,7 +126,7 @@ class GlobalChannelConverter(commands.IDConverter):
async def convert(self, ctx: commands.Context, argument: str): async def convert(self, ctx: commands.Context, argument: str):
bot = ctx.bot bot = ctx.bot
guild = ctx.guild guild = ctx.guild
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument) match = self._get_id_match(argument) or re.match(r"<#([0-9]+)>$", argument)
result = None result = None
if match is None: if match is None:
# not a mention/ID # not a mention/ID
@ -150,7 +159,7 @@ def error_embed_factory(ctx: commands.Context, exception: Exception, debug_mode:
fmtd_ex = traceback.format_exception_only(exception.__class__, exception) fmtd_ex = traceback.format_exception_only(exception.__class__, exception)
embed = embed_factory(ctx) embed = embed_factory(ctx)
embed.title = "⚠️ Error" embed.title = "⚠️ Error"
embed.description = "```\n" + '\n'.join(fmtd_ex) + "```" embed.description = "```\n" + "\n".join(fmtd_ex) + "```"
embed.colour = colours.bad embed.colour = colours.bad
return embed return embed

View File

@ -3,22 +3,24 @@ ae7q extension for qrm
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
---
Test callsigns:
KN8U: active, restricted
AB2EE: expired, restricted
KE8FGB: assigned once, no restrictions
KV4AAA: unassigned, no records
KC4USA: reserved, no call history, *but* has application history
""" """
import discord.ext.commands as commands
# Test callsigns:
# KN8U: active, restricted
# AB2EE: expired, restricted
# KE8FGB: assigned once, no restrictions
# KV4AAA: unassigned, no records
# KC4USA: reserved, no call history, *but* has application history
import aiohttp import aiohttp
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import discord.ext.commands as commands
import common as cmn import common as cmn
@ -29,16 +31,16 @@ class AE7QCog(commands.Cog):
@commands.group(name="ae7q", aliases=["ae"], category=cmn.cat.lookup) @commands.group(name="ae7q", aliases=["ae"], category=cmn.cat.lookup)
async def _ae7q_lookup(self, ctx: commands.Context): async def _ae7q_lookup(self, ctx: commands.Context):
'''Look up a callsign, FRN, or Licensee ID on [ae7q.com](http://ae7q.com/).''' """Looks up a callsign, FRN, or Licensee ID on [ae7q.com](http://ae7q.com/)."""
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await ctx.send_help(ctx.command) await ctx.send_help(ctx.command)
@_ae7q_lookup.command(name="call", aliases=["c"], category=cmn.cat.lookup) @_ae7q_lookup.command(name="call", aliases=["c"], category=cmn.cat.lookup)
async def _ae7q_call(self, ctx: commands.Context, callsign: str): async def _ae7q_call(self, ctx: commands.Context, callsign: str):
'''Look up the history of a callsign on [ae7q.com](http://ae7q.com/).''' """Looks up the history of a callsign on [ae7q.com](http://ae7q.com/)."""
with ctx.typing(): with ctx.typing():
callsign = callsign.upper() callsign = callsign.upper()
desc = '' desc = ""
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)
@ -56,20 +58,20 @@ class AE7QCog(commands.Cog):
if len(table[0]) == 1: if len(table[0]) == 1:
for row in table: for row in table:
desc += " ".join(row.getText().split()) desc += " ".join(row.getText().split())
desc += '\n' desc += "\n"
desc = desc.replace(callsign, f'`{callsign}`') desc = desc.replace(callsign, f"`{callsign}`")
table = tables[1] table = tables[1]
table_headers = table[0].find_all("th") table_headers = table[0].find_all("th")
first_header = ''.join(table_headers[0].strings) if len(table_headers) > 0 else None first_header = "".join(table_headers[0].strings) if len(table_headers) > 0 else None
# catch if the wrong table was selected # catch if the wrong table was selected
if first_header is None or first_header != 'Entity Name': if first_header is None or first_header != "Entity Name":
embed.title = f"AE7Q History for {callsign}" embed.title = f"AE7Q History for {callsign}"
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
embed.url = base_url + callsign embed.url = base_url + callsign
embed.description = desc embed.description = desc
embed.description += f'\nNo records found for `{callsign}`' embed.description += f"\nNo records found for `{callsign}`"
await ctx.send(embed=embed) await ctx.send(embed=embed)
return return
@ -82,18 +84,18 @@ class AE7QCog(commands.Cog):
# add the first three rows of the table to the embed # add the first three rows of the table to the embed
for row in table[0:3]: for row in table[0:3]:
header = f'**{row[0]}** ({row[1]})' # **Name** (Applicant Type) header = f"**{row[0]}** ({row[1]})" # **Name** (Applicant Type)
body = (f'Class: *{row[2]}*\n' body = (f"Class: *{row[2]}*\n"
f'Region: *{row[3]}*\n' f"Region: *{row[3]}*\n"
f'Status: *{row[4]}*\n' f"Status: *{row[4]}*\n"
f'Granted: *{row[5]}*\n' f"Granted: *{row[5]}*\n"
f'Effective: *{row[6]}*\n' f"Effective: *{row[6]}*\n"
f'Cancelled: *{row[7]}*\n' f"Cancelled: *{row[7]}*\n"
f'Expires: *{row[8]}*') f"Expires: *{row[8]}*")
embed.add_field(name=header, value=body, inline=False) embed.add_field(name=header, value=body, inline=False)
if len(table) > 3: if len(table) > 3:
desc += f'\nRecords 1 to 3 of {len(table)}. See ae7q.com for more...' desc += f"\nRecords 1 to 3 of {len(table)}. See ae7q.com for more..."
embed.description = desc embed.description = desc
@ -101,10 +103,10 @@ class AE7QCog(commands.Cog):
@_ae7q_lookup.command(name="trustee", aliases=["t"], category=cmn.cat.lookup) @_ae7q_lookup.command(name="trustee", aliases=["t"], category=cmn.cat.lookup)
async def _ae7q_trustee(self, ctx: commands.Context, callsign: str): async def _ae7q_trustee(self, ctx: commands.Context, callsign: str):
'''Look up the licenses for which a licensee is trustee on [ae7q.com](http://ae7q.com/).''' """Looks up the licenses for which a licensee is trustee on [ae7q.com](http://ae7q.com/)."""
with ctx.typing(): with ctx.typing():
callsign = callsign.upper() callsign = callsign.upper()
desc = '' desc = ""
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)
@ -123,12 +125,12 @@ class AE7QCog(commands.Cog):
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
embed.url = base_url + callsign embed.url = base_url + callsign
embed.description = desc embed.description = desc
embed.description += f'\nNo records found for `{callsign}`' embed.description += f"\nNo records found for `{callsign}`"
await ctx.send(embed=embed) await ctx.send(embed=embed)
return return
table_headers = table[0].find_all("th") table_headers = table[0].find_all("th")
first_header = ''.join(table_headers[0].strings) if len(table_headers) > 0 else None first_header = "".join(table_headers[0].strings) if len(table_headers) > 0 else None
# catch if the wrong table was selected # catch if the wrong table was selected
if first_header is None or not first_header.startswith("With"): if first_header is None or not first_header.startswith("With"):
@ -136,7 +138,7 @@ class AE7QCog(commands.Cog):
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
embed.url = base_url + callsign embed.url = base_url + callsign
embed.description = desc embed.description = desc
embed.description += f'\nNo records found for `{callsign}`' embed.description += f"\nNo records found for `{callsign}`"
await ctx.send(embed=embed) await ctx.send(embed=embed)
return return
@ -149,18 +151,18 @@ class AE7QCog(commands.Cog):
# add the first three rows of the table to the embed # add the first three rows of the table to the embed
for row in table[0:3]: for row in table[0:3]:
header = f'**{row[0]}** ({row[3]})' # **Name** (Applicant Type) header = f"**{row[0]}** ({row[3]})" # **Name** (Applicant Type)
body = (f'Name: *{row[2]}*\n' body = (f"Name: *{row[2]}*\n"
f'Region: *{row[1]}*\n' f"Region: *{row[1]}*\n"
f'Status: *{row[4]}*\n' f"Status: *{row[4]}*\n"
f'Granted: *{row[5]}*\n' f"Granted: *{row[5]}*\n"
f'Effective: *{row[6]}*\n' f"Effective: *{row[6]}*\n"
f'Cancelled: *{row[7]}*\n' f"Cancelled: *{row[7]}*\n"
f'Expires: *{row[8]}*') f"Expires: *{row[8]}*")
embed.add_field(name=header, value=body, inline=False) embed.add_field(name=header, value=body, inline=False)
if len(table) > 3: if len(table) > 3:
desc += f'\nRecords 1 to 3 of {len(table)}. See ae7q.com for more...' desc += f"\nRecords 1 to 3 of {len(table)}. See ae7q.com for more..."
embed.description = desc embed.description = desc
@ -168,11 +170,11 @@ class AE7QCog(commands.Cog):
@_ae7q_lookup.command(name="applications", aliases=["a"], category=cmn.cat.lookup) @_ae7q_lookup.command(name="applications", aliases=["a"], category=cmn.cat.lookup)
async def _ae7q_applications(self, ctx: commands.Context, callsign: str): async def _ae7q_applications(self, ctx: commands.Context, callsign: str):
'''Look up the application history for a callsign on [ae7q.com](http://ae7q.com/).''' """Looks up the application history for a callsign on [ae7q.com](http://ae7q.com/)."""
""" """
with ctx.typing(): with ctx.typing():
callsign = callsign.upper() callsign = callsign.upper()
desc = '' desc = ""
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)
@ -190,14 +192,14 @@ class AE7QCog(commands.Cog):
if len(table[0]) == 1: if len(table[0]) == 1:
for row in table: for row in table:
desc += " ".join(row.getText().split()) desc += " ".join(row.getText().split())
desc += '\n' desc += "\n"
desc = desc.replace(callsign, f'`{callsign}`') desc = desc.replace(callsign, f"`{callsign}`")
# select the last table to get applications # select the last table to get applications
table = tables[-1] table = tables[-1]
table_headers = table[0].find_all("th") table_headers = table[0].find_all("th")
first_header = ''.join(table_headers[0].strings) if len(table_headers) > 0 else None first_header = "".join(table_headers[0].strings) if len(table_headers) > 0 else None
# catch if the wrong table was selected # catch if the wrong table was selected
if first_header is None or not first_header.startswith("Receipt"): if first_header is None or not first_header.startswith("Receipt"):
@ -205,7 +207,7 @@ class AE7QCog(commands.Cog):
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
embed.url = base_url + callsign embed.url = base_url + callsign
embed.description = desc embed.description = desc
embed.description += f'\nNo records found for `{callsign}`' embed.description += f"\nNo records found for `{callsign}`"
await ctx.send(embed=embed) await ctx.send(embed=embed)
return return
@ -218,16 +220,16 @@ class AE7QCog(commands.Cog):
# add the first three rows of the table to the embed # add the first three rows of the table to the embed
for row in table[0:3]: for row in table[0:3]:
header = f'**{row[1]}** ({row[3]})' # **Name** (Callsign) header = f"**{row[1]}** ({row[3]})" # **Name** (Callsign)
body = (f'Received: *{row[0]}*\n' body = (f"Received: *{row[0]}*\n"
f'Region: *{row[2]}*\n' f"Region: *{row[2]}*\n"
f'Purpose: *{row[5]}*\n' f"Purpose: *{row[5]}*\n"
f'Last Action: *{row[7]}*\n' f"Last Action: *{row[7]}*\n"
f'Application Status: *{row[8]}*\n') f"Application Status: *{row[8]}*\n")
embed.add_field(name=header, value=body, inline=False) embed.add_field(name=header, value=body, inline=False)
if len(table) > 3: if len(table) > 3:
desc += f'\nRecords 1 to 3 of {len(table)}. See ae7q.com for more...' desc += f"\nRecords 1 to 3 of {len(table)}. See ae7q.com for more..."
embed.description = desc embed.description = desc
@ -238,7 +240,7 @@ class AE7QCog(commands.Cog):
@_ae7q_lookup.command(name="frn", aliases=["f"], category=cmn.cat.lookup) @_ae7q_lookup.command(name="frn", aliases=["f"], category=cmn.cat.lookup)
async def _ae7q_frn(self, ctx: commands.Context, frn: str): async def _ae7q_frn(self, ctx: commands.Context, frn: str):
'''Look up the history of an FRN on [ae7q.com](http://ae7q.com/).''' """Looks up the history of an FRN on [ae7q.com](http://ae7q.com/)."""
""" """
NOTES: NOTES:
- 2 tables: callsign history and application history - 2 tables: callsign history and application history
@ -260,21 +262,21 @@ class AE7QCog(commands.Cog):
embed.title = f"AE7Q History for FRN {frn}" embed.title = f"AE7Q History for FRN {frn}"
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
embed.url = base_url + frn embed.url = base_url + frn
embed.description = f'No records found for FRN `{frn}`' embed.description = f"No records found for FRN `{frn}`"
await ctx.send(embed=embed) await ctx.send(embed=embed)
return return
table = tables[0] table = tables[0]
table_headers = table[0].find_all("th") table_headers = table[0].find_all("th")
first_header = ''.join(table_headers[0].strings) if len(table_headers) > 0 else None first_header = "".join(table_headers[0].strings) if len(table_headers) > 0 else None
# catch if the wrong table was selected # catch if the wrong table was selected
if first_header is None or not first_header.startswith('With Licensee'): if first_header is None or not first_header.startswith("With Licensee"):
embed.title = f"AE7Q History for FRN {frn}" embed.title = f"AE7Q History for FRN {frn}"
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
embed.url = base_url + frn embed.url = base_url + frn
embed.description = f'No records found for FRN `{frn}`' embed.description = f"No records found for FRN `{frn}`"
await ctx.send(embed=embed) await ctx.send(embed=embed)
return return
@ -287,25 +289,25 @@ class AE7QCog(commands.Cog):
# add the first three rows of the table to the embed # add the first three rows of the table to the embed
for row in table[0:3]: for row in table[0:3]:
header = f'**{row[0]}** ({row[3]})' # **Callsign** (Applicant Type) header = f"**{row[0]}** ({row[3]})" # **Callsign** (Applicant Type)
body = (f'Name: *{row[2]}*\n' body = (f"Name: *{row[2]}*\n"
f'Class: *{row[4]}*\n' f"Class: *{row[4]}*\n"
f'Region: *{row[1]}*\n' f"Region: *{row[1]}*\n"
f'Status: *{row[5]}*\n' f"Status: *{row[5]}*\n"
f'Granted: *{row[6]}*\n' f"Granted: *{row[6]}*\n"
f'Effective: *{row[7]}*\n' f"Effective: *{row[7]}*\n"
f'Cancelled: *{row[8]}*\n' f"Cancelled: *{row[8]}*\n"
f'Expires: *{row[9]}*') f"Expires: *{row[9]}*")
embed.add_field(name=header, value=body, inline=False) embed.add_field(name=header, value=body, inline=False)
if len(table) > 3: if len(table) > 3:
embed.description = f'Records 1 to 3 of {len(table)}. See ae7q.com for more...' embed.description = f"Records 1 to 3 of {len(table)}. See ae7q.com for more..."
await ctx.send(embed=embed) await ctx.send(embed=embed)
@_ae7q_lookup.command(name="licensee", aliases=["l"], category=cmn.cat.lookup) @_ae7q_lookup.command(name="licensee", aliases=["l"], category=cmn.cat.lookup)
async def _ae7q_licensee(self, ctx: commands.Context, licensee_id: str): async def _ae7q_licensee(self, ctx: commands.Context, licensee_id: str):
'''Look up the history of a licensee ID on [ae7q.com](http://ae7q.com/).''' """Looks up the history of a licensee ID on [ae7q.com](http://ae7q.com/)."""
with ctx.typing(): with ctx.typing():
licensee_id = licensee_id.upper() licensee_id = licensee_id.upper()
base_url = "http://ae7q.com/query/data/LicenseeIdHistory.php?ID=" base_url = "http://ae7q.com/query/data/LicenseeIdHistory.php?ID="
@ -323,21 +325,21 @@ class AE7QCog(commands.Cog):
embed.title = f"AE7Q History for Licensee {licensee_id}" embed.title = f"AE7Q History for Licensee {licensee_id}"
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
embed.url = base_url + licensee_id embed.url = base_url + licensee_id
embed.description = f'No records found for Licensee `{licensee_id}`' embed.description = f"No records found for Licensee `{licensee_id}`"
await ctx.send(embed=embed) await ctx.send(embed=embed)
return return
table = tables[0] table = tables[0]
table_headers = table[0].find_all("th") table_headers = table[0].find_all("th")
first_header = ''.join(table_headers[0].strings) if len(table_headers) > 0 else None first_header = "".join(table_headers[0].strings) if len(table_headers) > 0 else None
# catch if the wrong table was selected # catch if the wrong table was selected
if first_header is None or not first_header.startswith('With FCC'): if first_header is None or not first_header.startswith("With FCC"):
embed.title = f"AE7Q History for Licensee {licensee_id}" embed.title = f"AE7Q History for Licensee {licensee_id}"
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
embed.url = base_url + licensee_id embed.url = base_url + licensee_id
embed.description = f'No records found for Licensee `{licensee_id}`' embed.description = f"No records found for Licensee `{licensee_id}`"
await ctx.send(embed=embed) await ctx.send(embed=embed)
return return
@ -350,19 +352,19 @@ class AE7QCog(commands.Cog):
# add the first three rows of the table to the embed # add the first three rows of the table to the embed
for row in table[0:3]: for row in table[0:3]:
header = f'**{row[0]}** ({row[3]})' # **Callsign** (Applicant Type) header = f"**{row[0]}** ({row[3]})" # **Callsign** (Applicant Type)
body = (f'Name: *{row[2]}*\n' body = (f"Name: *{row[2]}*\n"
f'Class: *{row[4]}*\n' f"Class: *{row[4]}*\n"
f'Region: *{row[1]}*\n' f"Region: *{row[1]}*\n"
f'Status: *{row[5]}*\n' f"Status: *{row[5]}*\n"
f'Granted: *{row[6]}*\n' f"Granted: *{row[6]}*\n"
f'Effective: *{row[7]}*\n' f"Effective: *{row[7]}*\n"
f'Cancelled: *{row[8]}*\n' f"Cancelled: *{row[8]}*\n"
f'Expires: *{row[9]}*') f"Expires: *{row[9]}*")
embed.add_field(name=header, value=body, inline=False) embed.add_field(name=header, value=body, inline=False)
if len(table) > 3: if len(table) > 3:
embed.description = f'Records 1 to 3 of {len(table)}. See ae7q.com for more...' embed.description = f"Records 1 to 3 of {len(table)}. See ae7q.com for more..."
await ctx.send(embed=embed) await ctx.send(embed=embed)
@ -372,13 +374,13 @@ async def process_table(table: list):
table_contents = [] table_contents = []
for tr in table: for tr in table:
row = [] row = []
for td in tr.find_all('td'): for td in tr.find_all("td"):
cell_val = td.getText().strip() cell_val = td.getText().strip()
row.append(cell_val if cell_val else '-') row.append(cell_val if cell_val else "-")
# take care of columns that span multiple rows by copying the contents rightward # take care of columns that span multiple rows by copying the contents rightward
if 'colspan' in td.attrs and int(td.attrs['colspan']) > 1: if "colspan" in td.attrs and int(td.attrs["colspan"]) > 1:
for i in range(int(td.attrs['colspan']) - 1): for i in range(int(td.attrs["colspan"]) - 1):
row.append(row[-1]) row.append(row[-1])
# get rid of ditto marks by copying the contents from the previous row # get rid of ditto marks by copying the contents from the previous row

View File

@ -3,8 +3,8 @@ Base extension for qrm
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
@ -24,7 +24,7 @@ import data.options as opt
class QrmHelpCommand(commands.HelpCommand): class QrmHelpCommand(commands.HelpCommand):
def __init__(self): def __init__(self):
super().__init__(command_attrs={'help': 'Shows help about qrm or a command', 'aliases': ['h']}) super().__init__(command_attrs={"help": "Shows help about qrm or a command", "aliases": ["h"]})
self.verify_checks = True self.verify_checks = True
async def get_bot_mapping(self): async def get_bot_mapping(self):
@ -32,7 +32,7 @@ class QrmHelpCommand(commands.HelpCommand):
mapping = {} mapping = {}
for cmd in await self.filter_commands(bot.commands, sort=True): for cmd in await self.filter_commands(bot.commands, sort=True):
cat = cmd.__original_kwargs__.get('category', None) cat = cmd.__original_kwargs__.get("category", None)
if cat in mapping: if cat in mapping:
mapping[cat].append(cmd) mapping[cat].append(cmd)
else: else:
@ -42,27 +42,27 @@ class QrmHelpCommand(commands.HelpCommand):
async def get_command_signature(self, command): async def get_command_signature(self, command):
parent = command.full_parent_name parent = command.full_parent_name
if command.aliases != []: if command.aliases != []:
aliases = ', '.join(command.aliases) aliases = ", ".join(command.aliases)
fmt = command.name fmt = command.name
if parent: if parent:
fmt = f'{parent} {fmt}' fmt = f"{parent} {fmt}"
alias = fmt alias = fmt
return f'{opt.prefix}{alias} {command.signature}\n *Aliases:* {aliases}' return f"{opt.prefix}{alias} {command.signature}\n *Aliases:* {aliases}"
alias = command.name if not parent else f'{parent} {command.name}' alias = command.name if not parent else f"{parent} {command.name}"
return f'{opt.prefix}{alias} {command.signature}' return f"{opt.prefix}{alias} {command.signature}"
async def send_error_message(self, error): async def send_error_message(self, error):
embed = cmn.embed_factory(self.context) embed = cmn.embed_factory(self.context)
embed.title = 'qrm Help Error' embed.title = "qrm Help Error"
embed.description = error embed.description = error
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
await self.context.send(embed=embed) await self.context.send(embed=embed)
async def send_bot_help(self, mapping): async def send_bot_help(self, mapping):
embed = cmn.embed_factory(self.context) embed = cmn.embed_factory(self.context)
embed.title = 'qrm Help' embed.title = "qrm Help"
embed.description = (f'For command-specific help and usage, use `{opt.prefix}help [command name]`' embed.description = (f"For command-specific help and usage, use `{opt.prefix}help [command name]`."
'. Many commands have shorter aliases.') " Many commands have shorter aliases.")
mapping = await mapping mapping = await mapping
for cat, cmds in mapping.items(): for cat, cmds in mapping.items():
@ -70,9 +70,9 @@ class QrmHelpCommand(commands.HelpCommand):
continue continue
names = sorted([cmd.name for cmd in cmds]) names = sorted([cmd.name for cmd in cmds])
if cat is not None: if cat is not None:
embed.add_field(name=cat.title(), value=', '.join(names), inline=False) embed.add_field(name=cat.title(), value=", ".join(names), inline=False)
else: else:
embed.add_field(name='Other', value=', '.join(names), inline=False) embed.add_field(name="Other", value=", ".join(names), inline=False)
await self.context.send(embed=embed) await self.context.send(embed=embed)
async def send_command_help(self, command): async def send_command_help(self, command):
@ -112,28 +112,28 @@ class BaseCog(commands.Cog):
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}")
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))
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.command(name="ping", aliases=['beep']) @commands.command(name="ping", aliases=["beep"])
async def _ping(self, ctx: commands.Context): async def _ping(self, ctx: commands.Context):
"""Show the current latency to the discord endpoint.""" """Shows the current latency to the discord endpoint."""
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
content = '' content = ""
if ctx.invoked_with == "beep": if ctx.invoked_with == "beep":
embed.title = "**Boop!**" embed.title = "**Boop!**"
else: else:
content = ctx.message.author.mention if random.random() < 0.05 else '' content = ctx.message.author.mention if random.random() < 0.05 else ""
embed.title = "🏓 **Pong!**" embed.title = "🏓 **Pong!**"
embed.description = f'Current ping is {self.bot.latency*1000:.1f} ms' embed.description = f"Current ping is {self.bot.latency*1000:.1f} ms"
await ctx.send(content, embed=embed) await ctx.send(content, embed=embed)
@commands.command(name="changelog", aliases=["clog"]) @commands.command(name="changelog", aliases=["clog"])
async def _changelog(self, ctx: commands.Context, version: str = 'latest'): async def _changelog(self, ctx: commands.Context, version: str = "latest"):
"""Show what has changed in a bot version.""" """Shows what has changed in a bot version. Defaults to the latest version."""
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = "qrm Changelog" embed.title = "qrm Changelog"
embed.description = ("For a full listing, visit [Github](https://" embed.description = ("For a full listing, visit [Github](https://"
@ -144,32 +144,32 @@ class BaseCog(commands.Cog):
version = version.lower() version = version.lower()
if version == 'latest': if version == "latest":
version = info.release version = info.release
if version == 'unreleased': if version == "unreleased":
version = 'Unreleased' version = "Unreleased"
try: try:
log = changelog[version] log = changelog[version]
except KeyError: except KeyError:
embed.title += ": Version Not Found" embed.title += ": Version Not Found"
embed.description += '\n\n**Valid versions:** latest, ' embed.description += "\n\n**Valid versions:** latest, "
embed.description += ', '.join(vers) embed.description += ", ".join(vers)
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
await ctx.send(embed=embed) await ctx.send(embed=embed)
return return
if 'date' in log: if "date" in log:
embed.description += f'\n\n**v{version}** ({log["date"]})' embed.description += f"\n\n**v{version}** ({log['date']})"
else: else:
embed.description += f'\n\n**v{version}**' embed.description += f"\n\n**v{version}**"
embed = await format_changelog(log, embed) embed = await format_changelog(log, embed)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.command(name="issue") @commands.command(name="issue")
async def _issue(self, ctx: commands.Context): async def _issue(self, ctx: commands.Context):
"""Shows how to create an issue for 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]"
@ -180,7 +180,7 @@ class BaseCog(commands.Cog):
@commands.check(cmn.check_if_owner) @commands.check(cmn.check_if_owner)
async def _echo(self, ctx: commands.Context, async def _echo(self, ctx: commands.Context,
channel: Union[cmn.GlobalChannelConverter, commands.UserConverter], *, msg: str): channel: Union[cmn.GlobalChannelConverter, commands.UserConverter], *, msg: str):
"""Send a message in a channel as qrm. Accepts channel/user IDs/mentions. """Sends a message in a channel as qrm. Accepts channel/user IDs/mentions.
Channel names are current-guild only. Channel names are current-guild only.
Does not work with the ID of the bot user.""" Does not work with the ID of the bot user."""
if isinstance(channel, discord.ClientUser): if isinstance(channel, discord.ClientUser):
@ -190,36 +190,36 @@ class BaseCog(commands.Cog):
def parse_changelog(): def parse_changelog():
changelog = OrderedDict() changelog = OrderedDict()
ver = '' ver = ""
heading = '' heading = ""
with open('CHANGELOG.md') as changelog_file: with open("CHANGELOG.md") as changelog_file:
for line in changelog_file.readlines(): for line in changelog_file.readlines():
if line.strip() == '': if line.strip() == "":
continue continue
if re.match(r'##[^#]', line): if re.match(r"##[^#]", line):
ver_match = re.match(r'\[(.+)\](?: - )?(\d{4}-\d{2}-\d{2})?', line.lstrip('#').strip()) ver_match = re.match(r"\[(.+)\](?: - )?(\d{4}-\d{2}-\d{2})?", line.lstrip("#").strip())
if ver_match is not None: if ver_match is not None:
ver = ver_match.group(1) ver = ver_match.group(1)
changelog[ver] = dict() changelog[ver] = dict()
if ver_match.group(2): if ver_match.group(2):
changelog[ver]['date'] = ver_match.group(2) changelog[ver]["date"] = ver_match.group(2)
elif re.match(r'###[^#]', line): elif re.match(r"###[^#]", line):
heading = line.lstrip('#').strip() heading = line.lstrip("#").strip()
changelog[ver][heading] = [] changelog[ver][heading] = []
elif ver != '' and heading != '': elif ver != "" and heading != "":
if line.startswith('-'): if line.startswith("-"):
changelog[ver][heading].append(line.lstrip('-').strip()) changelog[ver][heading].append(line.lstrip("-").strip())
return changelog return changelog
async def format_changelog(log: dict, embed: discord.Embed): async def format_changelog(log: dict, embed: discord.Embed):
for header, lines in log.items(): for header, lines in log.items():
formatted = '' formatted = ""
if header != 'date': if header != "date":
for line in lines: for line in lines:
formatted += f'- {line}\n' formatted += f"- {line}\n"
embed.add_field(name=f'**{header}**', value=formatted, inline=False) embed.add_field(name=f"**{header}**", value=formatted, inline=False)
return embed return embed

View File

@ -3,10 +3,11 @@ Fun extension for qrm
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
import random import random
import discord.ext.commands as commands import discord.ext.commands as commands
@ -17,37 +18,37 @@ import common as cmn
class FunCog(commands.Cog): class FunCog(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
with open('resources/words') as words_file: with open("resources/words") as words_file:
self.words = words_file.read().lower().splitlines() self.words = words_file.read().lower().splitlines()
@commands.command(name="xkcd", aliases=['x'], category=cmn.cat.fun) @commands.command(name="xkcd", aliases=["x"], category=cmn.cat.fun)
async def _xkcd(self, ctx: commands.Context, number: str): async def _xkcd(self, ctx: commands.Context, number: str):
'''Look up an xkcd by number.''' """Looks up an xkcd comic by number."""
await ctx.send('http://xkcd.com/' + number) await ctx.send("http://xkcd.com/" + number)
@commands.command(name="tar", category=cmn.cat.fun) @commands.command(name="tar", category=cmn.cat.fun)
async def _tar(self, ctx: commands.Context): async def _tar(self, ctx: commands.Context):
'''Returns an xkcd about tar.''' """Returns xkcd: tar."""
await ctx.send('http://xkcd.com/1168') await ctx.send("http://xkcd.com/1168")
@commands.command(name="xd", hidden=True, category=cmn.cat.fun) @commands.command(name="xd", hidden=True, category=cmn.cat.fun)
async def _xd(self, ctx: commands.Context): async def _xd(self, ctx: commands.Context):
'''ecks dee''' """ecks dee"""
await ctx.send('ECKS DEE :smirk:') await ctx.send("ECKS DEE :smirk:")
@commands.command(name="funetics", aliases=['fun'], category=cmn.cat.fun) @commands.command(name="funetics", aliases=["fun"], category=cmn.cat.fun)
async def _funetics_lookup(self, ctx: commands.Context, *, msg: str): async def _funetics_lookup(self, ctx: commands.Context, *, msg: str):
'''Get fun phonetics for a word or phrase.''' """Generates fun/wacky phonetics for a word or phrase."""
with ctx.typing(): with ctx.typing():
result = '' result = ""
for char in msg.lower(): for char in msg.lower():
if char.isalpha(): if char.isalpha():
result += random.choice([word for word in self.words if word[0] == char]) result += random.choice([word for word in self.words if word[0] == char])
else: else:
result += char result += char
result += ' ' result += " "
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f'Funetics for {msg}' embed.title = f"Funetics for {msg}"
embed.description = result.title() embed.description = result.title()
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
await ctx.send(embed=embed) await ctx.send(embed=embed)

View File

@ -3,10 +3,11 @@ Grid extension for qrm
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
import math import math
import discord.ext.commands as commands import discord.ext.commands as commands
@ -20,57 +21,57 @@ class GridCog(commands.Cog):
@commands.command(name="grid", category=cmn.cat.maps) @commands.command(name="grid", category=cmn.cat.maps)
async def _grid_sq_lookup(self, ctx: commands.Context, lat: str, lon: str): async def _grid_sq_lookup(self, ctx: commands.Context, lat: str, lon: str):
'''Calculates the grid square for latitude and longitude coordinates, ("""Calculates the grid square for latitude and longitude coordinates, """
with negative being latitude South and longitude West.''' """with negative being latitude South and longitude West.""")
with ctx.typing(): with ctx.typing():
grid = "**" grid = "**"
latf = float(lat) + 90 latf = float(lat) + 90
lonf = float(lon) + 180 lonf = float(lon) + 180
if 0 <= latf <= 180 and 0 <= lonf <= 360: if 0 <= latf <= 180 and 0 <= lonf <= 360:
grid += chr(ord('A') + int(lonf / 20)) grid += chr(ord("A") + int(lonf / 20))
grid += chr(ord('A') + int(latf / 10)) grid += chr(ord("A") + int(latf / 10))
grid += chr(ord('0') + int((lonf % 20)/2)) grid += chr(ord("0") + int((lonf % 20)/2))
grid += chr(ord('0') + int((latf % 10)/1)) grid += chr(ord("0") + int((latf % 10)/1))
grid += chr(ord('a') + int((lonf - (int(lonf/2)*2)) / (5/60))) grid += chr(ord("a") + int((lonf - (int(lonf/2)*2)) / (5/60)))
grid += chr(ord('a') + int((latf - (int(latf/1)*1)) / (2.5/60))) grid += chr(ord("a") + int((latf - (int(latf/1)*1)) / (2.5/60)))
grid += "**" grid += "**"
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f'Maidenhead Grid Locator for {float(lat):.6f}, {float(lon):.6f}' embed.title = f"Maidenhead Grid Locator for {float(lat):.6f}, {float(lon):.6f}"
embed.description = grid embed.description = grid
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
else: else:
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f'Error generating grid square for {lat}, {lon}.' embed.title = f"Error generating grid square for {lat}, {lon}."
embed.description = ("Coordinates out of range.\n" embed.description = """Coordinates out of range.
"The valid ranges are:\n" The valid ranges are:
"- Latitude: `-90` to `+90`\n" - Latitude: `-90` to `+90`
"- Longitude: `-180` to `+180`") - Longitude: `-180` to `+180`"""
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.command(name="ungrid", aliases=['loc'], category=cmn.cat.maps) @commands.command(name="ungrid", aliases=["loc"], category=cmn.cat.maps)
async def _location_lookup(self, ctx: commands.Context, grid: str, grid2: str = None): async def _location_lookup(self, ctx: commands.Context, grid: str, grid2: str = None):
'''Calculates the latitude and longitude for the center of a grid square. """Calculates the latitude and longitude for the center of a grid square.
If two grid squares are given, the distance and azimuth between them is calculated.''' If two grid squares are given, the distance and azimuth between them is calculated."""
with ctx.typing(): with ctx.typing():
if grid2 is None or grid2 == '': if grid2 is None or grid2 == "":
try: try:
grid = grid.upper() grid = grid.upper()
loc = get_coords(grid) loc = get_coords(grid)
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f'Latitude and Longitude for {grid}' embed.title = f"Latitude and Longitude for {grid}"
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
if len(grid) >= 6: if len(grid) >= 6:
embed.description = f'**{loc[0]:.5f}, {loc[1]:.5f}**' embed.description = f"**{loc[0]:.5f}, {loc[1]:.5f}**"
embed.url = f'https://www.openstreetmap.org/#map=13/{loc[0]:.5f}/{loc[1]:.5f}' embed.url = f"https://www.openstreetmap.org/#map=13/{loc[0]:.5f}/{loc[1]:.5f}"
else: else:
embed.description = f'**{loc[0]:.1f}, {loc[1]:.1f}**' embed.description = f"**{loc[0]:.1f}, {loc[1]:.1f}**"
embed.url = f'https://www.openstreetmap.org/#map=10/{loc[0]:.1f}/{loc[1]:.1f}' embed.url = f"https://www.openstreetmap.org/#map=10/{loc[0]:.1f}/{loc[1]:.1f}"
except Exception as e: except Exception as e:
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f'Error generating latitude and longitude for grid {grid}.' embed.title = f"Error generating latitude and longitude for grid {grid}."
embed.description = str(e) embed.description = str(e)
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
else: else:
@ -101,12 +102,12 @@ If two grid squares are given, the distance and azimuth between them is calculat
bearing = (math.degrees(math.atan2(y_dist, x_dist)) + 360) % 360 bearing = (math.degrees(math.atan2(y_dist, x_dist)) + 360) % 360
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f'Great Circle Distance and Bearing from {grid} to {grid2}' embed.title = f"Great Circle Distance and Bearing from {grid} to {grid2}"
embed.description = f'**Distance:** {d:.1f} km ({d_mi:.1f} mi)\n**Bearing:** {bearing:.1f}°' embed.description = f"**Distance:** {d:.1f} km ({d_mi:.1f} mi)\n**Bearing:** {bearing:.1f}°"
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
except Exception as e: except Exception as e:
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f'Error generating great circle distance and bearing from {grid} and {grid2}.' embed.title = f"Error generating great circle distance and bearing from {grid} and {grid2}."
embed.description = str(e) embed.description = str(e)
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
await ctx.send(embed=embed) await ctx.send(embed=embed)
@ -114,23 +115,23 @@ If two grid squares are given, the distance and azimuth between them is calculat
def get_coords(grid: str): def get_coords(grid: str):
if len(grid) < 3: if len(grid) < 3:
raise ValueError('The grid locator must be at least 4 characters long.') raise ValueError("The grid locator must be at least 4 characters long.")
if not grid[0:2].isalpha() or not grid[2:4].isdigit(): if not grid[0:2].isalpha() or not grid[2:4].isdigit():
if len(grid) <= 4: if len(grid) <= 4:
raise ValueError('The grid locator must be of the form AA##.') raise ValueError("The grid locator must be of the form AA##.")
if len(grid) >= 6 and not grid[5:7].isalpha(): if len(grid) >= 6 and not grid[5:7].isalpha():
raise ValueError('The grid locator must be of the form AA##AA.') raise ValueError("The grid locator must be of the form AA##AA.")
lon = ((ord(grid[0]) - ord('A')) * 20) - 180 lon = ((ord(grid[0]) - ord("A")) * 20) - 180
lat = ((ord(grid[1]) - ord('A')) * 10) - 90 lat = ((ord(grid[1]) - ord("A")) * 10) - 90
lon += ((ord(grid[2]) - ord('0')) * 2) lon += ((ord(grid[2]) - ord("0")) * 2)
lat += ((ord(grid[3]) - ord('0')) * 1) lat += ((ord(grid[3]) - ord("0")) * 1)
if len(grid) >= 6: if len(grid) >= 6:
# have subsquares # have subsquares
lon += ((ord(grid[4])) - ord('A')) * (5/60) lon += ((ord(grid[4])) - ord("A")) * (5/60)
lat += ((ord(grid[5])) - ord('A')) * (2.5/60) lat += ((ord(grid[5])) - ord("A")) * (2.5/60)
# move to center of subsquare # move to center of subsquare
lon += (2.5/60) lon += (2.5/60)
lat += (1.25/60) lat += (1.25/60)

View File

@ -3,10 +3,11 @@ Ham extension for qrm
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
from datetime import datetime from datetime import datetime
import discord.ext.commands as commands import discord.ext.commands as commands
@ -21,9 +22,9 @@ class HamCog(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
@commands.command(name="qcode", aliases=['q'], category=cmn.cat.ref) @commands.command(name="qcode", aliases=["q"], category=cmn.cat.ref)
async def _qcode_lookup(self, ctx: commands.Context, qcode: str): async def _qcode_lookup(self, ctx: commands.Context, qcode: str):
'''Look up a Q Code.''' """Looks up the meaning of a Q Code."""
with ctx.typing(): with ctx.typing():
qcode = qcode.upper() qcode = qcode.upper()
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
@ -32,49 +33,49 @@ class HamCog(commands.Cog):
embed.description = qcodes.qcodes[qcode] embed.description = qcodes.qcodes[qcode]
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
else: else:
embed.title = f'Q Code {qcode} not found' embed.title = f"Q Code {qcode} not found"
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.command(name="phonetics", aliases=['ph', 'phoneticize', 'phoneticise', 'phone'], category=cmn.cat.ref) @commands.command(name="phonetics", aliases=["ph", "phoneticize", "phoneticise", "phone"], category=cmn.cat.ref)
async def _phonetics_lookup(self, ctx: commands.Context, *, msg: str): async def _phonetics_lookup(self, ctx: commands.Context, *, msg: str):
'''Get phonetics for a word or phrase.''' """Returns NATO phonetics for a word or phrase."""
with ctx.typing(): with ctx.typing():
result = '' result = ""
for char in msg.lower(): for char in msg.lower():
if char.isalpha(): if char.isalpha():
result += phonetics.phonetics[char] result += phonetics.phonetics[char]
else: else:
result += char result += char
result += ' ' result += " "
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f'Phonetics for {msg}' embed.title = f"Phonetics for {msg}"
embed.description = result.title() embed.description = result.title()
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.command(name="utc", aliases=['z'], category=cmn.cat.ref) @commands.command(name="utc", aliases=["z"], category=cmn.cat.ref)
async def _utc_lookup(self, ctx: commands.Context): async def _utc_lookup(self, ctx: commands.Context):
'''Gets the current time in UTC.''' """Returns the current time in UTC."""
with ctx.typing(): with ctx.typing():
now = datetime.utcnow() now = datetime.utcnow()
result = '**' + now.strftime('%Y-%m-%d %H:%M') + 'Z**' result = "**" + now.strftime("%Y-%m-%d %H:%M") + "Z**"
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = 'The current time is:' embed.title = "The current time is:"
embed.description = result embed.description = result
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.command(name="prefixes", aliases=["vanity", "pfx", "vanities", "prefix"], category=cmn.cat.ref) @commands.command(name="prefixes", aliases=["vanity", "pfx", "vanities", "prefix"], category=cmn.cat.ref)
async def _vanity_prefixes(self, ctx: commands.Context, country: str = None): async def _vanity_prefixes(self, ctx: commands.Context, country: str = None):
'''Lists valid prefixes for countries.''' """Lists valid callsign prefixes for different countries."""
if country is None: if country is None:
await ctx.send_help(ctx.command) await ctx.send_help(ctx.command)
return return
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
if country.lower() not in callsign_info.options: if country.lower() not in callsign_info.options:
embed.title = f'{country} not found!', embed.title = f"{country} not found!",
embed.description = f'Valid countries: {", ".join(callsign_info.options.keys())}', embed.description = f"Valid countries: {', '.join(callsign_info.options.keys())}",
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
else: else:
embed.title = callsign_info.options[country.lower()][0] embed.title = callsign_info.options[country.lower()][0]

View File

@ -3,10 +3,11 @@ Image extension for qrm
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
import io import io
import aiohttp import aiohttp
@ -18,8 +19,8 @@ import common as cmn
class ImageCog(commands.Cog): class ImageCog(commands.Cog):
gl_url = ('http://www.fourmilab.ch/cgi-bin/uncgi/Earth?img=NOAAtopo.evif' gl_url = ("http://www.fourmilab.ch/cgi-bin/uncgi/Earth?img=NOAAtopo.evif"
'&imgsize=320&dynimg=y&opt=-p&lat=&lon=&alt=&tle=&date=0&utc=&jd=') "&imgsize=320&dynimg=y&opt=-p&lat=&lon=&alt=&tle=&date=0&utc=&jd=")
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
@ -27,17 +28,17 @@ class ImageCog(commands.Cog):
self.maps = cmn.ImagesGroup(cmn.paths.maps / "meta.json") self.maps = cmn.ImagesGroup(cmn.paths.maps / "meta.json")
self.session = aiohttp.ClientSession(connector=bot.qrm.connector) self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
@commands.command(name="bandplan", aliases=['plan', 'bands'], category=cmn.cat.ref) @commands.command(name="bandplan", aliases=["plan", "bands"], category=cmn.cat.ref)
async def _bandplan(self, ctx: commands.Context, region: str = ''): async def _bandplan(self, ctx: commands.Context, region: str = ""):
'''Posts an image of Frequency Allocations.''' """Gets the frequency allocations chart for a given country."""
async with ctx.typing(): async with ctx.typing():
arg = region.lower() arg = region.lower()
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
if arg not in self.bandcharts: if arg not in self.bandcharts:
desc = 'Possible arguments are:\n' desc = "Possible arguments are:\n"
for key, img in self.bandcharts.items(): for key, img in self.bandcharts.items():
desc += f'`{key}`: {img.name}{(" " + img.emoji if img.emoji else "")}\n' desc += f"`{key}`: {img.name}{(' ' + img.emoji if img.emoji else '')}\n"
embed.title = f'Bandplan Not Found!' embed.title = "Bandplan Not Found!"
embed.description = desc embed.description = desc
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
await ctx.send(embed=embed) await ctx.send(embed=embed)
@ -51,20 +52,20 @@ class ImageCog(commands.Cog):
embed.add_field(name="Source", value=metadata.source) embed.add_field(name="Source", value=metadata.source)
embed.title = metadata.long_name + (" " + metadata.emoji if metadata.emoji else "") embed.title = metadata.long_name + (" " + metadata.emoji if metadata.emoji else "")
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
embed.set_image(url='attachment://' + metadata.filename) embed.set_image(url="attachment://" + metadata.filename)
await ctx.send(embed=embed, file=img) await ctx.send(embed=embed, file=img)
@commands.command(name="map", category=cmn.cat.maps) @commands.command(name="map", category=cmn.cat.maps)
async def _map(self, ctx: commands.Context, map_id: str = ''): async def _map(self, ctx: commands.Context, map_id: str = ""):
'''Posts an image of a ham-relevant map.''' """Posts a ham-relevant map."""
async with ctx.typing(): async with ctx.typing():
arg = map_id.lower() arg = map_id.lower()
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
if arg not in self.maps: if arg not in self.maps:
desc = 'Possible arguments are:\n' desc = "Possible arguments are:\n"
for key, img in self.maps.items(): for key, img in self.maps.items():
desc += f'`{key}`: {img.name}{(" " + img.emoji if img.emoji else "")}\n' desc += f"`{key}`: {img.name}{(' ' + img.emoji if img.emoji else '')}\n"
embed.title = 'Map Not Found!' embed.title = "Map Not Found!"
embed.description = desc embed.description = desc
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
await ctx.send(embed=embed) await ctx.send(embed=embed)
@ -78,22 +79,22 @@ class ImageCog(commands.Cog):
embed.add_field(name="Source", value=metadata.source) embed.add_field(name="Source", value=metadata.source)
embed.title = metadata.long_name + (" " + metadata.emoji if metadata.emoji else "") embed.title = metadata.long_name + (" " + metadata.emoji if metadata.emoji else "")
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
embed.set_image(url='attachment://' + metadata.filename) embed.set_image(url="attachment://" + metadata.filename)
await ctx.send(embed=embed, file=img) await ctx.send(embed=embed, file=img)
@commands.command(name="grayline", aliases=['greyline', 'grey', 'gray', 'gl'], category=cmn.cat.maps) @commands.command(name="grayline", aliases=["greyline", "grey", "gray", "gl"], category=cmn.cat.maps)
async def _grayline(self, ctx: commands.Context): async def _grayline(self, ctx: commands.Context):
'''Posts a map of the current greyline, where HF propagation is the best.''' """Gets a map of the current greyline, where HF propagation is the best."""
async with ctx.typing(): async with ctx.typing():
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = 'Current Greyline Conditions' embed.title = "Current Greyline Conditions"
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
async with self.session.get(self.gl_url) as resp: async with self.session.get(self.gl_url) as resp:
if resp.status != 200: if resp.status != 200:
raise cmn.BotHTTPError(resp) raise cmn.BotHTTPError(resp)
data = io.BytesIO(await resp.read()) data = io.BytesIO(await resp.read())
embed.set_image(url=f'attachment://greyline.jpg') embed.set_image(url="attachment://greyline.jpg")
await ctx.send(embed=embed, file=discord.File(data, 'greyline.jpg')) await ctx.send(embed=embed, file=discord.File(data, "greyline.jpg"))
def setup(bot: commands.Bot): def setup(bot: commands.Bot):

View File

@ -3,15 +3,17 @@ Lookup extension for qrm
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
import threading import threading
from discord.ext import commands, tasks
from ctyparser import BigCty from ctyparser import BigCty
from discord.ext import commands, tasks
import common as cmn import common as cmn
@ -19,48 +21,48 @@ class LookupCog(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
try: try:
self.cty = BigCty('./data/cty.json') self.cty = BigCty("./data/cty.json")
except OSError: except OSError:
self.cty = BigCty() self.cty = BigCty()
# TODO: See #107 # TODO: See #107
# @commands.command(name="sat", category=cmn.cat.lookup) # @commands.command(name="sat", category=cmn.cat.lookup)
# async def _sat_lookup(self, ctx: commands.Context, sat_name: str, grid1: str, grid2: str = None): # async def _sat_lookup(self, ctx: commands.Context, sat_name: str, grid1: str, grid2: str = None):
# '''Links to info about satellite passes on satmatch.com.''' # """Links to info about satellite passes on satmatch.com."""
# now = datetime.utcnow().strftime('%Y-%m-%d%%20%H:%M') # now = datetime.utcnow().strftime("%Y-%m-%d%%20%H:%M")
# if grid2 is None or grid2 == '': # if grid2 is None or grid2 == "":
# await ctx.send(f'http://www.satmatch.com/satellite/{sat_name}/obs1/{grid1}' # await ctx.send(f"http://www.satmatch.com/satellite/{sat_name}/obs1/{grid1}"
# f'?search_start_time={now}&duration_hrs=24') # f"?search_start_time={now}&duration_hrs=24")
# else: # else:
# await ctx.send(f'http://www.satmatch.com/satellite/{sat_name}/obs1/{grid1}' # await ctx.send(f"http://www.satmatch.com/satellite/{sat_name}/obs1/{grid1}"
# f'/obs2/{grid2}?search_start_time={now}&duration_hrs=24') # f"/obs2/{grid2}?search_start_time={now}&duration_hrs=24")
@commands.command(name="dxcc", aliases=['dx'], category=cmn.cat.lookup) @commands.command(name="dxcc", aliases=["dx"], category=cmn.cat.lookup)
async def _dxcc_lookup(self, ctx: commands.Context, query: str): async def _dxcc_lookup(self, ctx: commands.Context, query: str):
'''Gets info about a DXCC prefix.''' """Gets DXCC info about a callsign prefix."""
with ctx.typing(): with ctx.typing():
query = query.upper() query = query.upper()
full_query = query full_query = query
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f'DXCC Info for ' embed.title = "DXCC Info for "
embed.description = f'*Last Updated: {self.cty.formatted_version}*' embed.description = f"*Last Updated: {self.cty.formatted_version}*"
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
while query: while query:
if query in self.cty.keys(): if query in self.cty.keys():
data = self.cty[query] data = self.cty[query]
embed.add_field(name="Entity", value=data['entity']) embed.add_field(name="Entity", value=data["entity"])
embed.add_field(name="CQ Zone", value=data['cq']) embed.add_field(name="CQ Zone", value=data["cq"])
embed.add_field(name="ITU Zone", value=data['itu']) embed.add_field(name="ITU Zone", value=data["itu"])
embed.add_field(name="Continent", value=data['continent']) embed.add_field(name="Continent", value=data["continent"])
embed.add_field(name="Time Zone", embed.add_field(name="Time Zone",
value=f'+{data["tz"]}' if data['tz'] > 0 else str(data['tz'])) value=f"+{data['tz']}" if data["tz"] > 0 else str(data["tz"]))
embed.title += query embed.title += query
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
break break
else: else:
query = query[:-1] query = query[:-1]
else: else:
embed.title += full_query + ' not found' embed.title += full_query + " not found"
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
await ctx.send(embed=embed) await ctx.send(embed=embed)

View File

@ -3,10 +3,11 @@ Morse Code extension for qrm
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
import discord.ext.commands as commands import discord.ext.commands as commands
import common as cmn import common as cmn
@ -17,63 +18,63 @@ class MorseCog(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
@commands.command(name="morse", aliases=['cw'], category=cmn.cat.ref) @commands.command(name="morse", aliases=["cw"], category=cmn.cat.ref)
async def _morse(self, ctx: commands.Context, *, msg: str): async def _morse(self, ctx: commands.Context, *, msg: str):
"""Converts ASCII to international morse code.""" """Converts ASCII to international morse code."""
with ctx.typing(): with ctx.typing():
result = '' result = ""
for char in msg.upper(): for char in msg.upper():
try: try:
result += morse.morse[char] result += morse.morse[char]
except KeyError: except KeyError:
result += '<?>' result += "<?>"
result += ' ' result += " "
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f'Morse Code for {msg}' embed.title = f"Morse Code for {msg}"
embed.description = '**' + result + '**' embed.description = "**" + result + "**"
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.command(name="unmorse", aliases=['demorse', 'uncw', 'decw'], category=cmn.cat.ref) @commands.command(name="unmorse", aliases=["demorse", "uncw", "decw"], category=cmn.cat.ref)
async def _unmorse(self, ctx: commands.Context, *, msg: str): async def _unmorse(self, ctx: commands.Context, *, msg: str):
'''Converts international morse code to ASCII.''' """Converts international morse code to ASCII."""
with ctx.typing(): with ctx.typing():
result = '' result = ""
msg0 = msg msg0 = msg
msg = msg.split('/') msg = msg.split("/")
msg = [m.split() for m in msg] msg = [m.split() for m in msg]
for word in msg: for word in msg:
for char in word: for char in word:
try: try:
result += morse.ascii[char] result += morse.ascii[char]
except KeyError: except KeyError:
result += '<?>' result += "<?>"
result += ' ' result += " "
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f'ASCII for {msg0}' embed.title = f"ASCII for {msg0}"
embed.description = result embed.description = result
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.command(name="cwweight", aliases=["weight", 'cww'], category=cmn.cat.ref) @commands.command(name="cwweight", aliases=["weight", "cww"], category=cmn.cat.ref)
async def _weight(self, ctx: commands.Context, *, msg: str): async def _weight(self, ctx: commands.Context, *, msg: str):
'''Calculates the CW Weight of a callsign or message.''' """Calculates the CW weight of a callsign or message."""
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
with ctx.typing(): with ctx.typing():
msg = msg.upper() msg = msg.upper()
weight = 0 weight = 0
for char in msg: for char in msg:
try: try:
cw_char = morse.morse[char].replace('-', '==') cw_char = morse.morse[char].replace("-", "==")
weight += len(cw_char) * 2 + 2 weight += len(cw_char) * 2 + 2
except KeyError: except KeyError:
embed.title = 'Error in calculation of CW weight' embed.title = "Error in calculation of CW weight"
embed.description = f'Unknown character `{char}` in message' embed.description = f"Unknown character `{char}` in message"
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
await ctx.send(embed=embed) await ctx.send(embed=embed)
return return
embed.title = f'CW Weight of {msg}' embed.title = f"CW Weight of {msg}"
embed.description = f'The CW weight is **{weight}**' embed.description = f"The CW weight is **{weight}**"
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
await ctx.send(embed=embed) await ctx.send(embed=embed)

View File

@ -3,18 +3,21 @@ QRZ extension for qrm
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
from collections import OrderedDict from collections import OrderedDict
from io import BytesIO from io import BytesIO
from discord.ext import commands, tasks
import aiohttp import aiohttp
from lxml import etree from lxml import etree
from discord.ext import commands, tasks
import common as cmn import common as cmn
import data.keys as keys import data.keys as keys
@ -26,11 +29,11 @@ class QRZCog(commands.Cog):
@commands.command(name="call", aliases=["qrz"], category=cmn.cat.lookup) @commands.command(name="call", aliases=["qrz"], category=cmn.cat.lookup)
async def _qrz_lookup(self, ctx: commands.Context, callsign: str, *flags): async def _qrz_lookup(self, ctx: commands.Context, callsign: str, *flags):
'''Look 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 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
try: try:
@ -38,38 +41,38 @@ class QRZCog(commands.Cog):
except ConnectionError: except ConnectionError:
await self.get_session() await self.get_session()
url = f'http://xmldata.qrz.com/xml/current/?s={self.key};callsign={callsign}' url = f"http://xmldata.qrz.com/xml/current/?s={self.key};callsign={callsign}"
async with self.session.get(url) as resp: async with self.session.get(url) as resp:
if resp.status != 200: if resp.status != 200:
raise ConnectionError(f'Unable to connect to QRZ (HTTP Error {resp.status})') raise ConnectionError(f"Unable to connect to QRZ (HTTP Error {resp.status})")
with BytesIO(await resp.read()) as resp_file: with BytesIO(await resp.read()) as resp_file:
resp_xml = etree.parse(resp_file).getroot() resp_xml = etree.parse(resp_file).getroot()
resp_xml_session = resp_xml.xpath('/x:QRZDatabase/x:Session', namespaces={'x': 'http://xmldata.qrz.com'}) resp_xml_session = resp_xml.xpath("/x:QRZDatabase/x:Session", namespaces={"x": "http://xmldata.qrz.com"})
resp_session = {el.tag.split('}')[1]: el.text for el in resp_xml_session[0].getiterator()} resp_session = {el.tag.split("}")[1]: el.text for el in resp_xml_session[0].getiterator()}
if 'Error' in resp_session: if "Error" in resp_session:
if 'Session Timeout' in resp_session['Error']: if "Session Timeout" in resp_session["Error"]:
await self.get_session() await self.get_session()
await self._qrz_lookup(ctx, callsign) await self._qrz_lookup(ctx, callsign)
return return
if 'Not found' in resp_session['Error']: if "Not found" in resp_session["Error"]:
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f"QRZ Data for {callsign.upper()}" embed.title = f"QRZ Data for {callsign.upper()}"
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
embed.description = 'No data found!' embed.description = "No data found!"
await ctx.send(embed=embed) await ctx.send(embed=embed)
return return
raise ValueError(resp_session['Error']) raise ValueError(resp_session["Error"])
resp_xml_data = resp_xml.xpath('/x:QRZDatabase/x:Callsign', namespaces={'x': 'http://xmldata.qrz.com'}) resp_xml_data = resp_xml.xpath("/x:QRZDatabase/x:Callsign", namespaces={"x": "http://xmldata.qrz.com"})
resp_data = {el.tag.split('}')[1]: el.text for el in resp_xml_data[0].getiterator()} resp_data = {el.tag.split("}")[1]: el.text for el in resp_xml_data[0].getiterator()}
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f"QRZ Data for {resp_data['call']}" embed.title = f"QRZ Data for {resp_data['call']}"
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
embed.url = f'http://www.qrz.com/db/{resp_data["call"]}' embed.url = f"http://www.qrz.com/db/{resp_data['call']}"
if 'image' in resp_data: if "image" in resp_data:
embed.set_thumbnail(url=resp_data['image']) embed.set_thumbnail(url=resp_data["image"])
data = qrz_process_info(resp_data) data = qrz_process_info(resp_data)
@ -81,14 +84,14 @@ class QRZCog(commands.Cog):
async def get_session(self): async def get_session(self):
"""Session creation and caching.""" """Session creation and caching."""
self.key = await qrz_login(keys.qrz_user, keys.qrz_pass, self.session) self.key = await qrz_login(keys.qrz_user, keys.qrz_pass, self.session)
with open('data/qrz_session', 'w') as qrz_file: with open("data/qrz_session", "w") as qrz_file:
qrz_file.write(self.key) qrz_file.write(self.key)
@tasks.loop(count=1) @tasks.loop(count=1)
async def _qrz_session_init(self): async def _qrz_session_init(self):
"""Helper task to allow obtaining a session at cog instantiation.""" """Helper task to allow obtaining a session at cog instantiation."""
try: try:
with open('data/qrz_session') as qrz_file: with open("data/qrz_session") as qrz_file:
self.key = qrz_file.readline().strip() self.key = qrz_file.readline().strip()
await qrz_test_session(self.key, self.session) await qrz_test_session(self.key, self.session)
except (FileNotFoundError, ConnectionError): except (FileNotFoundError, ConnectionError):
@ -96,86 +99,86 @@ class QRZCog(commands.Cog):
async def qrz_login(user: str, passwd: str, session: aiohttp.ClientSession): async def qrz_login(user: str, passwd: str, session: aiohttp.ClientSession):
url = f'http://xmldata.qrz.com/xml/current/?username={user};password={passwd};agent=discord-qrm2' url = f"http://xmldata.qrz.com/xml/current/?username={user};password={passwd};agent=discord-qrm2"
async with session.get(url) as resp: async with session.get(url) as resp:
if resp.status != 200: if resp.status != 200:
raise ConnectionError(f'Unable to connect to QRZ (HTTP Error {resp.status})') raise ConnectionError(f"Unable to connect to QRZ (HTTP Error {resp.status})")
with BytesIO(await resp.read()) as resp_file: with BytesIO(await resp.read()) as resp_file:
resp_xml = etree.parse(resp_file).getroot() resp_xml = etree.parse(resp_file).getroot()
resp_xml_session = resp_xml.xpath('/x:QRZDatabase/x:Session', namespaces={'x': 'http://xmldata.qrz.com'}) resp_xml_session = resp_xml.xpath("/x:QRZDatabase/x:Session", namespaces={"x": "http://xmldata.qrz.com"})
resp_session = {el.tag.split('}')[1]: el.text for el in resp_xml_session[0].getiterator()} resp_session = {el.tag.split("}")[1]: el.text for el in resp_xml_session[0].getiterator()}
if 'Error' in resp_session: if "Error" in resp_session:
raise ConnectionError(resp_session['Error']) raise ConnectionError(resp_session["Error"])
if resp_session['SubExp'] == 'non-subscriber': if resp_session["SubExp"] == "non-subscriber":
raise ConnectionError('Invalid QRZ Subscription') raise ConnectionError("Invalid QRZ Subscription")
return resp_session['Key'] return resp_session["Key"]
async def qrz_test_session(key: str, session: aiohttp.ClientSession): async def qrz_test_session(key: str, session: aiohttp.ClientSession):
url = f'http://xmldata.qrz.com/xml/current/?s={key}' url = f"http://xmldata.qrz.com/xml/current/?s={key}"
async with session.get(url) as resp: async with session.get(url) as resp:
if resp.status != 200: if resp.status != 200:
raise ConnectionError(f'Unable to connect to QRZ (HTTP Error {resp.status})') raise ConnectionError(f"Unable to connect to QRZ (HTTP Error {resp.status})")
with BytesIO(await resp.read()) as resp_file: with BytesIO(await resp.read()) as resp_file:
resp_xml = etree.parse(resp_file).getroot() resp_xml = etree.parse(resp_file).getroot()
resp_xml_session = resp_xml.xpath('/x:QRZDatabase/x:Session', namespaces={'x': 'http://xmldata.qrz.com'}) resp_xml_session = resp_xml.xpath("/x:QRZDatabase/x:Session", namespaces={"x": "http://xmldata.qrz.com"})
resp_session = {el.tag.split('}')[1]: el.text for el in resp_xml_session[0].getiterator()} resp_session = {el.tag.split("}")[1]: el.text for el in resp_xml_session[0].getiterator()}
if 'Error' in resp_session: if "Error" in resp_session:
raise ConnectionError(resp_session['Error']) raise ConnectionError(resp_session["Error"])
def qrz_process_info(data: dict): def qrz_process_info(data: dict):
if 'name' in data: if "name" in data:
if 'fname' in data: if "fname" in data:
name = data['fname'] + ' ' + data['name'] name = data["fname"] + " " + data["name"]
else: else:
name = data['name'] name = data["name"]
else: else:
name = None name = None
if 'state' in data: if "state" in data:
state = f', {data["state"]}' state = f", {data['state']}"
else: else:
state = '' state = ""
address = data.get('addr1', '') + '\n' + data.get('addr2', '') + state + ' ' + data.get('zip', '') address = data.get("addr1", "") + "\n" + data.get("addr2", "") + state + " " + data.get("zip", "")
address = address.strip() address = address.strip()
if address == '': if address == "":
address = None address = None
if 'eqsl' in data: if "eqsl" in data:
eqsl = 'Yes' if data['eqsl'] == 1 else 'No' eqsl = "Yes" if data["eqsl"] == 1 else "No"
else: else:
eqsl = 'Unknown' eqsl = "Unknown"
if 'mqsl' in data: if "mqsl" in data:
mqsl = 'Yes' if data['mqsl'] == 1 else 'No' mqsl = "Yes" if data["mqsl"] == 1 else "No"
else: else:
mqsl = 'Unknown' mqsl = "Unknown"
if 'lotw' in data: if "lotw" in data:
lotw = 'Yes' if data['lotw'] == 1 else 'No' lotw = "Yes" if data["lotw"] == 1 else "No"
else: else:
lotw = 'Unknown' lotw = "Unknown"
return OrderedDict([('Name', name), return OrderedDict([("Name", name),
('Country', data.get('country', None)), ("Country", data.get("country", None)),
('Address', address), ("Address", address),
('Grid Square', data.get('grid', None)), ("Grid Square", data.get("grid", None)),
('County', data.get('county', None)), ("County", data.get("county", None)),
('CQ Zone', data.get('cqzone', None)), ("CQ Zone", data.get("cqzone", None)),
('ITU Zone', data.get('ituzone', None)), ("ITU Zone", data.get("ituzone", None)),
('IOTA Designator', data.get('iota', None)), ("IOTA Designator", data.get("iota", None)),
('Expires', data.get('expdate', None)), ("Expires", data.get("expdate", None)),
('Aliases', data.get('aliases', None)), ("Aliases", data.get("aliases", None)),
('Previous Callsign', data.get('p_call', None)), ("Previous Callsign", data.get("p_call", None)),
('License Class', data.get('class', None)), ("License Class", data.get("class", None)),
('Trustee', data.get('trustee', None)), ("Trustee", data.get("trustee", None)),
('eQSL?', eqsl), ("eQSL?", eqsl),
('Paper QSL?', mqsl), ("Paper QSL?", mqsl),
('LotW?', lotw), ("LotW?", lotw),
('QSL Info', data.get('qslmgr', None)), ("QSL Info", data.get("qslmgr", None)),
('CQ Zone', data.get('cqzone', None)), ("CQ Zone", data.get("cqzone", None)),
('ITU Zone', data.get('ituzone', None)), ("ITU Zone", data.get("ituzone", None)),
('IOTA Designator', data.get('iota', None)), ("IOTA Designator", data.get("iota", None)),
('Born', data.get('born', None))]) ("Born", data.get("born", None))])
def setup(bot): def setup(bot):

View File

@ -3,10 +3,11 @@ Study extension for qrm
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
import random import random
import json import json
from datetime import datetime from datetime import datetime
@ -21,17 +22,17 @@ 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 = {cmn.emojis.a: "A", cmn.emojis.b: "B", cmn.emojis.c: "C", cmn.emojis.d: "D"}
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
self.lastq = dict() self.lastq = dict()
self.source = 'Data courtesy of [HamStudy.org](https://hamstudy.org/)' self.source = "Data courtesy of [HamStudy.org](https://hamstudy.org/)"
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 = ""):
'''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)
@ -52,7 +53,7 @@ class StudyCog(commands.Cog):
embed.description = "Possible arguments are:" embed.description = "Possible arguments are:"
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
for cty in study.pool_names: for cty in study.pool_names:
levels = '`, `'.join(study.pool_names[cty].keys()) levels = "`, `".join(study.pool_names[cty].keys())
embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**", embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**",
value=f"Levels: `{levels}`", inline=False) value=f"Levels: `{levels}`", inline=False)
embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`") embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`")
@ -70,7 +71,7 @@ class StudyCog(commands.Cog):
embed.description = "Possible arguments are:" embed.description = "Possible arguments are:"
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
for cty in study.pool_names: for cty in study.pool_names:
levels = '`, `'.join(study.pool_names[cty].keys()) levels = "`, `".join(study.pool_names[cty].keys())
embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**", embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**",
value=f"Levels: `{levels}`", inline=False) value=f"Levels: `{levels}`", inline=False)
embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`") embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`")
@ -99,7 +100,7 @@ class StudyCog(commands.Cog):
embed.description = "Possible arguments are:" embed.description = "Possible arguments are:"
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
for cty in study.pool_names: for cty in study.pool_names:
levels = '`, `'.join(study.pool_names[cty].keys()) levels = "`, `".join(study.pool_names[cty].keys())
embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**", embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**",
value=f"Levels: `{levels}`", inline=False) value=f"Levels: `{levels}`", inline=False)
embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`") embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`")
@ -108,31 +109,31 @@ class StudyCog(commands.Cog):
pool_meta = pools[pool] pool_meta = pools[pool]
async with self.session.get(f'https://hamstudy.org/pools/{pool}') as resp: async with self.session.get(f"https://hamstudy.org/pools/{pool}") as resp:
if resp.status != 200: if resp.status != 200:
raise cmn.BotHTTPError(resp) raise cmn.BotHTTPError(resp)
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'] 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)
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=(f"**{cmn.emojis.a}** {question['answers']['A']}" value=(f"**{cmn.emojis.a}** {question['answers']['A']}"
f"\n**{cmn.emojis.b}** {question['answers']['B']}" f"\n**{cmn.emojis.b}** {question['answers']['B']}"
f"\n**{cmn.emojis.c}** {question['answers']['C']}" f"\n**{cmn.emojis.c}** {question['answers']['C']}"
f"\n**{cmn.emojis.d}** {question['answers']['D']}"), f"\n**{cmn.emojis.d}** {question['answers']['D']}"),
inline=False) inline=False)
embed.add_field(name='To Answer:', 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 10 minutes,"
' the answer will be revealed.'), " the answer will be revealed."),
inline=False) inline=False)
if 'image' in question: if "image" in question:
image_url = f'https://hamstudy.org/_1330011/images/{pool.split("_",1)[1]}/{question["image"]}' image_url = f"https://hamstudy.org/_1330011/images/{pool.split('_',1)[1]}/{question['image']}"
embed.set_image(url=image_url) embed.set_image(url=image_url)
q_msg = await ctx.send(embed=embed) q_msg = await ctx.send(embed=embed)
@ -148,13 +149,13 @@ class StudyCog(commands.Cog):
and str(reaction.emoji) in self.choices.keys()) and str(reaction.emoji) in self.choices.keys())
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=600.0, check=check)
except asyncio.TimeoutError: except asyncio.TimeoutError:
embed.remove_field(2) embed.remove_field(2)
embed.add_field(name="Answer:", value=f"Timed out! The correct answer was **{question['answer']}**.") embed.add_field(name="Answer:", value=f"Timed out! The correct answer was **{question['answer']}**.")
await q_msg.edit(embed=embed) await q_msg.edit(embed=embed)
else: else:
if self.choices[str(reaction.emoji)] == question['answer']: if self.choices[str(reaction.emoji)] == question["answer"]:
embed.remove_field(2) embed.remove_field(2)
embed.add_field(name="Answer:", value=f"Correct! The answer was **{question['answer']}**.") embed.add_field(name="Answer:", value=f"Correct! The answer was **{question['answer']}**.")
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
@ -166,7 +167,7 @@ class StudyCog(commands.Cog):
await q_msg.edit(embed=embed) 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:
if resp.status != 200: if resp.status != 200:
raise cmn.BotHTTPError(resp) raise cmn.BotHTTPError(resp)
else: else:

View File

@ -3,10 +3,11 @@ Weather extension for qrm
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
import io import io
import re import re
@ -25,97 +26,98 @@ class WeatherCog(commands.Cog):
self.bot = bot self.bot = bot
self.session = aiohttp.ClientSession(connector=bot.qrm.connector) self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
@commands.command(name="bandconditions", aliases=['cond', 'condx', 'conditions'], category=cmn.cat.weather) @commands.command(name="bandconditions", aliases=["cond", "condx", "conditions"], category=cmn.cat.weather)
async def _band_conditions(self, ctx: commands.Context): async def _band_conditions(self, ctx: commands.Context):
'''Posts an image of HF Band Conditions.''' """Gets a solar conditions report."""
async with ctx.typing(): async with ctx.typing():
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = 'Current Solar Conditions' embed.title = "Current Solar Conditions"
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
async with self.session.get('http://www.hamqsl.com/solarsun.php') as resp: async with self.session.get("http://www.hamqsl.com/solarsun.php") as resp:
if resp.status != 200: if resp.status != 200:
raise cmn.BotHTTPError(resp) raise cmn.BotHTTPError(resp)
data = io.BytesIO(await resp.read()) data = io.BytesIO(await resp.read())
embed.set_image(url=f'attachment://condx.png') embed.set_image(url="attachment://condx.png")
await ctx.send(embed=embed, file=discord.File(data, 'condx.png')) await ctx.send(embed=embed, file=discord.File(data, "condx.png"))
@commands.group(name="weather", aliases=['wttr'], category=cmn.cat.weather) @commands.group(name="weather", aliases=["wttr"], category=cmn.cat.weather)
async def _weather_conditions(self, ctx: commands.Context): async def _weather_conditions(self, ctx: commands.Context):
'''Posts an image of Local Weather Conditions from [wttr.in](http://wttr.in/). """Gets local weather conditions from [wttr.in](http://wttr.in/).
*Supported location types:* *Supported location types:*
city name: `paris` city name: `paris`
any location: `~Eiffel Tower` any location: `~Eiffel Tower`
Unicode name of any location in any language: `Москва` Unicode name of any location in any language: `Москва`
airport code (3 letters): `muc` airport code (3 letters): `muc`
domain name `@stackoverflow.com` domain name `@stackoverflow.com`
area codes: `12345` area codes: `12345`
GPS coordinates: `-78.46,106.79` GPS coordinates: `-78.46,106.79`
'''
Add a `-c` or `-f` to use Celcius or Fahrenheit: `-c YSC`"""
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await ctx.send_help(ctx.command) await ctx.send_help(ctx.command)
@_weather_conditions.command(name='forecast', aliases=['fc', 'future'], category=cmn.cat.weather) @_weather_conditions.command(name="forecast", aliases=["fc", "future"], category=cmn.cat.weather)
async def _weather_conditions_forecast(self, ctx: commands.Context, *, location: str): async def _weather_conditions_forecast(self, ctx: commands.Context, *, location: str):
'''Posts an image of Local Weather Conditions for the next three days from [wttr.in](http://wttr.in/). """Gets local weather forecast for the next three days from [wttr.in](http://wttr.in/).
See help for weather command for possible location types. Add a `-c` or `-f` to use Celcius or Fahrenheit.''' See help of the `weather` command for possible location types and options."""
async with ctx.typing(): async with ctx.typing():
try: try:
units_arg = re.search(self.wttr_units_regex, location).group(1) units_arg = re.search(self.wttr_units_regex, location).group(1)
except AttributeError: except AttributeError:
units_arg = '' units_arg = ""
if units_arg.lower() == 'f': if units_arg.lower() == "f":
units = 'u' units = "u"
elif units_arg.lower() == 'c': elif units_arg.lower() == "c":
units = 'm' units = "m"
else: else:
units = '' units = ""
loc = self.wttr_units_regex.sub('', location).strip() loc = self.wttr_units_regex.sub("", location).strip()
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f'Weather Forecast for {loc}' embed.title = f"Weather Forecast for {loc}"
embed.description = 'Data from [wttr.in](http://wttr.in/).' embed.description = "Data from [wttr.in](http://wttr.in/)."
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
loc = loc.replace(' ', '+') loc = loc.replace(" ", "+")
async with self.session.get(f'http://wttr.in/{loc}_{units}pnFQ.png') as resp: async with self.session.get(f"http://wttr.in/{loc}_{units}pnFQ.png") as resp:
if resp.status != 200: if resp.status != 200:
raise cmn.BotHTTPError(resp) raise cmn.BotHTTPError(resp)
data = io.BytesIO(await resp.read()) data = io.BytesIO(await resp.read())
embed.set_image(url=f'attachment://wttr_forecast.png') embed.set_image(url="attachment://wttr_forecast.png")
await ctx.send(embed=embed, file=discord.File(data, 'wttr_forecast.png')) await ctx.send(embed=embed, file=discord.File(data, "wttr_forecast.png"))
@_weather_conditions.command(name='now', aliases=['n'], category=cmn.cat.weather) @_weather_conditions.command(name="now", aliases=["n"], category=cmn.cat.weather)
async def _weather_conditions_now(self, ctx: commands.Context, *, location: str): async def _weather_conditions_now(self, ctx: commands.Context, *, location: str):
'''Posts an image of current Local Weather Conditions from [wttr.in](http://wttr.in/). """Gets current local weather conditions from [wttr.in](http://wttr.in/).
See help for weather command for possible location types. Add a `-c` or `-f` to use Celcius or Fahrenheit.''' See help of the `weather` command for possible location types and options."""
async with ctx.typing(): async with ctx.typing():
try: try:
units_arg = re.search(self.wttr_units_regex, location).group(1) units_arg = re.search(self.wttr_units_regex, location).group(1)
except AttributeError: except AttributeError:
units_arg = '' units_arg = ""
if units_arg.lower() == 'f': if units_arg.lower() == "f":
units = 'u' units = "u"
elif units_arg.lower() == 'c': elif units_arg.lower() == "c":
units = 'm' units = "m"
else: else:
units = '' units = ""
loc = self.wttr_units_regex.sub('', location).strip() loc = self.wttr_units_regex.sub("", location).strip()
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = f'Current Weather for {loc}' embed.title = f"Current Weather for {loc}"
embed.description = 'Data from [wttr.in](http://wttr.in/).' embed.description = "Data from [wttr.in](http://wttr.in/)."
embed.colour = cmn.colours.good embed.colour = cmn.colours.good
loc = loc.replace(' ', '+') loc = loc.replace(" ", "+")
async with self.session.get(f'http://wttr.in/{loc}_0{units}pnFQ.png') as resp: async with self.session.get(f"http://wttr.in/{loc}_0{units}pnFQ.png") as resp:
if resp.status != 200: if resp.status != 200:
raise cmn.BotHTTPError(resp) raise cmn.BotHTTPError(resp)
data = io.BytesIO(await resp.read()) data = io.BytesIO(await resp.read())
embed.set_image(url=f'attachment://wttr_now.png') embed.set_image(url="attachment://wttr_now.png")
await ctx.send(embed=embed, file=discord.File(data, 'wttr_now.png')) await ctx.send(embed=embed, file=discord.File(data, "wttr_now.png"))
def setup(bot: commands.Bot): def setup(bot: commands.Bot):

22
info.py
View File

@ -3,26 +3,14 @@ Static info about the bot.
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
---
`authors`: The authors of the bot.
`description`: A description of the bot.
`license`: The license the bot is released under.
`contrubuting`: Info on how to contribute to the bot.
`release`: Current bot version.
`release_timestamp`: When the bot was last released.
""" """
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/classabbyamp/discord-qrm2" contributing = "Check out the source on GitHub, contributions welcome: https://github.com/classabbyamp/discord-qrm2"
release = '2.1.0' release = "2.1.0"
bot_server = 'https://discord.gg/Ntbg3J4' bot_server = "https://discord.gg/Ntbg3J4"

29
main.py
View File

@ -4,16 +4,16 @@ qrm, a bot for Discord
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
import asyncio
import random
import sys import sys
import traceback import traceback
import asyncio from datetime import datetime, time
from datetime import time, datetime
import random
from types import SimpleNamespace from types import SimpleNamespace
import pytz import pytz
@ -21,12 +21,12 @@ import pytz
import discord import discord
from discord.ext import commands, tasks from discord.ext import commands, tasks
import utils.connector as conn
import common as cmn
import info import info
import data.options as opt import common as cmn
import utils.connector as conn
import data.keys as keys import data.keys as keys
import data.options as opt
# --- Settings --- # --- Settings ---
@ -94,7 +94,7 @@ async def _extctl(ctx: commands.Context):
@_extctl.command(name="list", aliases=["ls"]) @_extctl.command(name="list", aliases=["ls"])
async def _extctl_list(ctx: commands.Context): async def _extctl_list(ctx: commands.Context):
"""Lists Extensions.""" """Lists loaded extensions."""
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
embed.title = "Loaded Extensions" embed.title = "Loaded Extensions"
embed.description = "\n".join(["" + x.split(".")[1] for x in bot.extensions.keys()]) embed.description = "\n".join(["" + x.split(".")[1] for x in bot.extensions.keys()])
@ -103,12 +103,14 @@ async def _extctl_list(ctx: commands.Context):
@_extctl.command(name="load", aliases=["ld"]) @_extctl.command(name="load", aliases=["ld"])
async def _extctl_load(ctx: commands.Context, extension: str): async def _extctl_load(ctx: commands.Context, extension: str):
"""Loads an extension."""
bot.load_extension(ext_dir + "." + extension) bot.load_extension(ext_dir + "." + extension)
await cmn.add_react(ctx.message, cmn.emojis.check_mark) await cmn.add_react(ctx.message, cmn.emojis.check_mark)
@_extctl.command(name="reload", aliases=["rl", "r", "relaod"]) @_extctl.command(name="reload", aliases=["rl", "r", "relaod"])
async def _extctl_reload(ctx: commands.Context, extension: str): async def _extctl_reload(ctx: commands.Context, extension: str):
"""Reloads an extension."""
if ctx.invoked_with == "relaod": if ctx.invoked_with == "relaod":
pika = bot.get_emoji(opt.pika) pika = bot.get_emoji(opt.pika)
if pika: if pika:
@ -119,6 +121,7 @@ async def _extctl_reload(ctx: commands.Context, extension: str):
@_extctl.command(name="unload", aliases=["ul"]) @_extctl.command(name="unload", aliases=["ul"])
async def _extctl_unload(ctx: commands.Context, extension: str): async def _extctl_unload(ctx: commands.Context, extension: str):
"""Unloads an extension."""
bot.unload_extension(ext_dir + "." + extension) bot.unload_extension(ext_dir + "." + extension)
await cmn.add_react(ctx.message, cmn.emojis.check_mark) await cmn.add_react(ctx.message, cmn.emojis.check_mark)
@ -167,7 +170,7 @@ async def on_command_error(ctx: commands.Context, err: commands.CommandError):
await cmn.add_react(ctx.message, cmn.emojis.bangbang) await cmn.add_react(ctx.message, cmn.emojis.bangbang)
elif isinstance(err, (commands.CommandInvokeError, commands.ConversionError)): elif isinstance(err, (commands.CommandInvokeError, commands.ConversionError)):
# Emulating discord.py's default beaviour. # Emulating discord.py's default beaviour.
print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr) print("Ignoring exception in command {}:".format(ctx.command), file=sys.stderr)
traceback.print_exception(type(err), err, err.__traceback__, file=sys.stderr) traceback.print_exception(type(err), err, err.__traceback__, file=sys.stderr)
embed = cmn.error_embed_factory(ctx, err.original, bot.qrm.debug_mode) embed = cmn.error_embed_factory(ctx, err.original, bot.qrm.debug_mode)
@ -176,7 +179,7 @@ async def on_command_error(ctx: commands.Context, err: commands.CommandError):
await ctx.send(embed=embed) await ctx.send(embed=embed)
else: else:
# Emulating discord.py's default beaviour. (safest bet) # Emulating discord.py's default beaviour. (safest bet)
print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr) print("Ignoring exception in command {}:".format(ctx.command), file=sys.stderr)
traceback.print_exception(type(err), err, err.__traceback__, file=sys.stderr) traceback.print_exception(type(err), err, err.__traceback__, file=sys.stderr)
await cmn.add_react(ctx.message, cmn.emojis.warning) await cmn.add_react(ctx.message, cmn.emojis.warning)
@ -221,7 +224,7 @@ async def _ensure_activity_fixed():
# --- Run --- # --- Run ---
for ext in opt.exts: for ext in opt.exts:
bot.load_extension(ext_dir + '.' + ext) bot.load_extension(ext_dir + "." + ext)
try: try:

View File

@ -3,53 +3,54 @@ Information about callsigns for the vanity prefixes command in hamcog.
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrmbot and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
from collections import OrderedDict from collections import OrderedDict
us_calls_title = "Valid US Vanity Callsigns" us_calls_title = "Valid US Vanity Callsigns"
us_calls_desc = ('#x# is the number of letters in the prefix and suffix of a callsign. ' us_calls_desc = ("#x# is the number of letters in the prefix and suffix of a callsign. "
'E.g., WY4RC would be a 2x2 callsign, with prefix WY and suffix RC.') "E.g., WY4RC would be a 2x2 callsign, with prefix WY and suffix RC.")
us_calls = OrderedDict([('**Group A** (Extra Only)', ('**Any:** K, N, W (1x2)\n' us_calls = OrderedDict([("**Group A** (Extra Only)", ("**Any:** K, N, W (1x2)\n"
' AA-AL, KA-KZ, NA-NZ, WA-WZ (2x1)\n' " AA-AL, KA-KZ, NA-NZ, WA-WZ (2x1)\n"
' AA-AL (2x2)\n' " AA-AL (2x2)\n"
'*Except*\n' "*Except*\n"
'**Alaska:** AL, KL, NL, WL (2x1)\n' "**Alaska:** AL, KL, NL, WL (2x1)\n"
'**Caribbean:** KP, NP, WP (2x1)\n' "**Caribbean:** KP, NP, WP (2x1)\n"
'**Pacific:** AH, KH, NH, WH (2x1)')), "**Pacific:** AH, KH, NH, WH (2x1)")),
('**Group B** (Advanced and Extra Only)', ('**Any:** KA-KZ, NA-NZ, WA-WZ (2x2)\n' ("**Group B** (Advanced and Extra Only)", ("**Any:** KA-KZ, NA-NZ, WA-WZ (2x2)\n"
'*Except*\n' "*Except*\n"
'**Alaska:** AL (2x2)\n' "**Alaska:** AL (2x2)\n"
'**Caribbean:** KP (2x2)\n' "**Caribbean:** KP (2x2)\n"
'**Pacific:** AH (2x2)')), "**Pacific:** AH (2x2)")),
('**Group C** (Technician, General, Advanced, Extra Only)', ('**Any Region:** K, N, W (1x3)\n' ("**Group C** (Technician, General, Advanced, Extra Only)", ("**Any Region:** K, N, W (1x3)\n"
'*Except*\n' "*Except*\n"
'**Alaska:** KL, NL, WL (2x2)\n' "**Alaska:** KL, NL, WL (2x2)\n"
'**Caribbean:** NP, WP (2x2)\n' "**Caribbean:** NP, WP (2x2)\n"
'**Pacific:** KH, NH, WH (2x2)')), "**Pacific:** KH, NH, WH (2x2)")),
('**Group D** (Any License Class)', ('**Any Region:** KA-KZ, WA-WZ (2x3)\n' ("**Group D** (Any License Class)", ("**Any Region:** KA-KZ, WA-WZ (2x3)\n"
'*Except*\n' "*Except*\n"
'**Alaska:** KL, WL (2x3)\n' "**Alaska:** KL, WL (2x3)\n"
'**Caribbean:** KP, WP (2x3)\n' "**Caribbean:** KP, WP (2x3)\n"
'**Pacific:** KH, WH (2x3)')), "**Pacific:** KH, WH (2x3)")),
('**Unavailable**', ('- KA2AA-KA9ZZ: US Army in Japan\n' ("**Unavailable**", ("- KA2AA-KA9ZZ: US Army in Japan\n"
'- KC4AAA-KC4AAF: NSF in Antartica\n' "- KC4AAA-KC4AAF: NSF in Antartica\n"
'- KC4USA-KC4USZ: US Navy in Antartica\n' "- KC4USA-KC4USZ: US Navy in Antartica\n"
'- KG4AA-KG4ZZ: US Navy in Guantanamo Bay\n' "- KG4AA-KG4ZZ: US Navy in Guantanamo Bay\n"
'- KL9KAA-KL9KHZ: US military in Korea\n' "- KL9KAA-KL9KHZ: US military in Korea\n"
'- KC6AA-KC6ZZ: Former US (Eastern and Western Caroline Islands), ' "- KC6AA-KC6ZZ: Former US (Eastern and Western Caroline Islands), "
'now Federated States of Micronesia (V6) and Republic of Palau (T8)\n' "now Federated States of Micronesia (V6) and Republic of Palau (T8)\n"
'- KX6AA-KX6ZZ: Former US (Marshall Islands), ' "- KX6AA-KX6ZZ: Former US (Marshall Islands), "
'now Republic of the Marshall Islands (V73)\n' "now Republic of the Marshall Islands (V73)\n"
'- Any suffix SOS or QRA-QUZ\n' "- Any suffix SOS or QRA-QUZ\n"
'- Any 2x3 with X as the first suffix letter\n' "- Any 2x3 with X as the first suffix letter\n"
'- Any 2x3 with AF, KF, NF, or WF prefix and suffix EMA: FEMA\n' "- Any 2x3 with AF, KF, NF, or WF prefix and suffix EMA: FEMA\n"
'- Any 2x3 with AA-AL, NA-NZ, WC, WK, WM, WR, or WT prefix: "Group X"\n' "- Any 2x3 with AA-AL, NA-NZ, WC, WK, WM, WR, or WT prefix: \"Group X\"\n"
'- Any 2x1, 2x2, or 2x3 with KP, NP, WP prefix and 0, 6, 7, 8, 9 number\n' "- Any 2x1, 2x2, or 2x3 with KP, NP, WP prefix and 0, 6, 7, 8, 9 number\n"
'- Any 1x1 callsign: Special Event'))]) "- Any 1x1 callsign: Special Event"))])
# format: country: (title, description, text) # format: country: (title, description, text)
options = {'us': (us_calls_title, us_calls_desc, us_calls)} options = {"us": (us_calls_title, us_calls_desc, us_calls)}

View File

@ -3,27 +3,134 @@ A listing of morse code symbols
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrmbot and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
morse = {"A": ".-", "B": "-...", "C": "-.-.", "D": "-..", "E": ".", "F": "..-.", "G": "--.",
"H": "....", "I": "..", "J": ".---", "K": "-.-", "L": ".-..", "M": "--", "N": "-.",
"O": "---", "P": ".--.", "Q": "--.-", "R": ".-.", "S": "...", "T": "-", "U": "..-",
"V": "...-", "W": ".--", "X": "-..-", "Y": "-.--", "Z": "--..", "1": ".----",
"2": "..---", "3": "...--", "4": "....-", "5": ".....", "6": "-....", "7": "--...",
"8": "---..", "9": "----.", "0": "-----", ".": ".-.-.-", ",": "--..--", "?": "..--..",
"'": ".----.", "!": "-.-.--", "/": "-..-.", "(": "-.--.", ")": "-.--.-", "&": ".-...",
":": "---...", ";": "-.-.-.", "=": "-...-", "+": ".-.-.", "-": "-....-", "\"": ".-..-.",
"@": ".--.-.", "Ä": ".-.-", "Å": ".-.-", "Ą": ".-.-", "Æ": ".-.-", "É": "..-..",
"Ñ": "--.--", "Ö": "---.", "Ü": "..--", "Š": "----", " ": "/"}
ascii = {".-": "A", "-...": "B", "-.-.": "C", "-..": "D", ".": "E", "..-.": "F", "--.": "G",
"....": "H", "..": "I", ".---": "J", "-.-": "K", ".-..": "L", "--": "M", "-.": "N", morse = {
"---": "O", ".--.": "P", "--.-": "Q", ".-.": "R", "...": "S", "-": "T", "..-": "U", "A": ".-",
"...-": "V", ".--": "W", "-..-": "X", "-.--": "Y", "--..": "Z", ".----": "1", "B": "-...",
"..---": "2", "...--": "3", "....-": "4", ".....": "5", "-....": "6", "--...": "7", "C": "-.-.",
"---..": "8", "----.": "9", "-----": "0", ".-.-.-": ".", "--..--": ",", "..--..": "?", "D": "-..",
".----.": "'", "-.-.--": "!", "-..-.": "/", "-.--.": "(", "-.--.-": ")", ".-...": "&", "E": ".",
"---...": ":", "-.-.-.": ";", "-...-": "=", ".-.-.": "+", "-....-": "-", ".-..-.": "\"", "F": "..-.",
".--.-.": "@", ".-.-": "Ä", "..-..": "É", "--.--": "Ñ", "---.": "Ö", "..--": "Ü", "G": "--.",
"----": "Š", "/": " "} "H": "....",
"I": "..",
"J": ".---",
"K": "-.-",
"L": ".-..",
"M": "--",
"N": "-.",
"O": "---",
"P": ".--.",
"Q": "--.-",
"R": ".-.",
"S": "...",
"T": "-",
"U": "..-",
"V": "...-",
"W": ".--",
"X": "-..-",
"Y": "-.--",
"Z": "--..",
"1": ".----",
"2": "..---",
"3": "...--",
"4": "....-",
"5": ".....",
"6": "-....",
"7": "--...",
"8": "---..",
"9": "----.",
"0": "-----",
".": ".-.-.-",
",": "--..--",
"?": "..--..",
"'": ".----.",
"!": "-.-.--",
"/": "-..-.",
"(": "-.--.",
")": "-.--.-",
"&": ".-...",
":": "---...",
";": "-.-.-.",
"=": "-...-",
"+": ".-.-.",
"-": "-....-",
"\"": ".-..-.",
"@": ".--.-.",
"Ä": ".-.-",
"Å": ".-.-",
"Ą": ".-.-",
"Æ": ".-.-",
"É": "..-..",
"Ñ": "--.--",
"Ö": "---.",
"Ü": "..--",
"Š": "----",
" ": "/"
}
ascii = {
".-": "A",
"-...": "B",
"-.-.": "C",
"-..": "D",
".": "E",
"..-.": "F",
"--.": "G",
"....": "H",
"..": "I",
".---": "J",
"-.-": "K",
".-..": "L",
"--": "M",
"-.": "N",
"---": "O",
".--.": "P",
"--.-": "Q",
".-.": "R",
"...": "S",
"-": "T",
"..-": "U",
"...-": "V",
".--": "W",
"-..-": "X",
"-.--": "Y",
"--..": "Z",
".----": "1",
"..---": "2",
"...--": "3",
"....-": "4",
".....": "5",
"-....": "6",
"--...": "7",
"---..": "8",
"----.": "9",
"-----": "0",
".-.-.-": ".",
"--..--": ",",
"..--..": "?",
".----.": "'",
"-.-.--": "!",
"-..-.": "/",
"-.--.": "(",
"-.--.-": ")",
".-...": "&",
"---...": ":",
"-.-.-.": ";",
"-...-": "=",
".-.-.": "+",
"-....-": "-",
".-..-.": "\"",
".--.-.": "@",
".-.-": "Ä",
"..-..": "É",
"--.--": "Ñ",
"---.": "Ö",
"..--": "Ü",
"----": "Š",
"/": " "
}

View File

@ -3,12 +3,36 @@ A listing of NATO Phonetics
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrmbot and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
phonetics = {'a': 'alfa', 'b': 'bravo', 'c': 'charlie', 'd': 'delta', 'e': 'echo', 'f': 'foxtrot',
'g': 'golf', 'h': 'hotel', 'i': 'india', 'j': 'juliett', 'k': 'kilo', 'l': 'lima', phonetics = {
'm': 'mike', 'n': 'november', 'o': 'oscar', 'p': 'papa', 'q': 'quebec', 'r': 'romeo', "a": "alfa",
's': 'sierra', 't': 'tango', 'u': 'uniform', 'v': 'victor', 'w': 'whiskey', 'x': 'x-ray', "b": "bravo",
'y': 'yankee', 'z': 'zulu'} "c": "charlie",
"d": "delta",
"e": "echo",
"f": "foxtrot",
"g": "golf",
"h": "hotel",
"i": "india",
"j": "juliett",
"k": "kilo",
"l": "lima",
"m": "mike",
"n": "november",
"o": "oscar",
"p": "papa",
"q": "quebec",
"r": "romeo",
"s": "sierra",
"t": "tango",
"u": "uniform",
"v": "victor",
"w": "whiskey",
"x": "x-ray",
"y": "yankee",
"z": "zulu"
}

View File

@ -3,278 +3,284 @@ A listing of Q Codes
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrmbot and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
# flake8: noqa # flake8: noqa
qcodes = {"QAB": "May I have clearance (for ...) from ... (place) to ... (place) at flight level/altitude ... ? / You are cleared (or ... is cleared) by ... from ... (place) to ... (place) at flight level/altitude ...",
"QAF": "Will you advise me when you are (were) at (over) ... (place)? / I am (was) at (over) ... (place) (at ... hours) at flight level/altitude ...",
"QAG": "Arrange your flight in order to arrive over ... (place) at ... hours.", qcodes = {
"QAH": "What is your height above ... (datum)? / I am at .... flight level/altitude ... --or-- Arrange your flight so as to reach flight level/altitude ... at ... (hours or place).", "QAB": "May I have clearance (for ...) from ... (place) to ... (place) at flight level/altitude ... ? / You are cleared (or ... is cleared) by ... from ... (place) to ... (place) at flight level/altitude ...",
"QAI": "What is the essential traffic respecting my aircraft? / The essential traffic respecting your aircraft is ...", "QAF": "Will you advise me when you are (were) at (over) ... (place)? / I am (was) at (over) ... (place) (at ... hours) at flight level/altitude ...",
"QAK": "Is there any risk of collision? / There is risk of collision.", "QAG": "Arrange your flight in order to arrive over ... (place) at ... hours.",
"QAL": "Are you going to land at ... (place)? I am going to land at ... (place).", "QAH": "What is your height above ... (datum)? / I am at .... flight level/altitude ... --or-- Arrange your flight so as to reach flight level/altitude ... at ... (hours or place).",
"QAM": "What is the latest available meteorological observation for ... (place)? / Meteorological observation made at ... (place) at ... hours was as follows ...", "QAI": "What is the essential traffic respecting my aircraft? / The essential traffic respecting your aircraft is ...",
"QAN": "What is the surface wind direction and speed at ... (place)? / The surface wind direction and speed at ... (place) at ... hours is ... (direction) ... (speed).", "QAK": "Is there any risk of collision? / There is risk of collision.",
"QAO": "What is the wind direction in degrees TRUE and speed at ... (position or zone/s) at each of the ... (figures) ... (units) levels above ... (datum)? / The wind direction and speed at (position or zone/s) at flight level/altitude ... is: ... (vertical distance) ... degrees TRUE ... (speed).", "QAL": "Are you going to land at ... (place)? I am going to land at ... (place).",
"QAP": "Shall I listen for you (or for ...) on ... kHz (... MHz)? / Listen for me (or for ...) on ... kHz (... MHz).", "QAM": "What is the latest available meteorological observation for ... (place)? / Meteorological observation made at ... (place) at ... hours was as follows ...",
"QAQ": "Am I near a prohibited, restricted or danger area?", "QAN": "What is the surface wind direction and speed at ... (place)? / The surface wind direction and speed at ... (place) at ... hours is ... (direction) ... (speed).",
"QAR": "May I stop listening on the watch frequency for ... minutes? / You may stop listening on the watch frequency for ... minutes.", "QAO": "What is the wind direction in degrees TRUE and speed at ... (position or zone/s) at each of the ... (figures) ... (units) levels above ... (datum)? / The wind direction and speed at (position or zone/s) at flight level/altitude ... is: ... (vertical distance) ... degrees TRUE ... (speed).",
"QAU": "Where may I jettison fuel? / I am about to jettison fuel.", "QAP": "Shall I listen for you (or for ...) on ... kHz (... MHz)? / Listen for me (or for ...) on ... kHz (... MHz).",
"QAW": "I am about to carry out overshoot procedure.", "QAQ": "Am I near a prohibited, restricted or danger area?",
"QAY": "Will you advise me when you pass (passed) ... (place) bearing 090 (270) degrees relative to your heading? / I passed ... (place) bearing ... degrees relative to my heading at ... hours.", "QAR": "May I stop listening on the watch frequency for ... minutes? / You may stop listening on the watch frequency for ... minutes.",
"QAZ": "Are you experiencing communication difficulties through flying in a storm? / I am experiencing communication difficulties through flying in a storm.", "QAU": "Where may I jettison fuel? / I am about to jettison fuel.",
"QBA": "What is the horizontal visibility at ... (place)? / The horizontal visibility at ... (place) at ... hours is ... (distance figures and units).", "QAW": "I am about to carry out overshoot procedure.",
"QBB": "What is the amount, type and height above official aerodrome elevation of the base of the cloud [at ... (place)]? / The amount, type and height above official aerodrome elevation of the base of the cloud at ... (place) at ... hours is: ... eights (... type) at ... (figures and units) height above official aerodrome elevation.", "QAY": "Will you advise me when you pass (passed) ... (place) bearing 090 (270) degrees relative to your heading? / I passed ... (place) bearing ... degrees relative to my heading at ... hours.",
"QBC": "Report meteorological conditions as observed from your aircraft [at ... (position or zone)] [(at ... hours)]. / The meteorological conditions as observed from my aircraft at ... (position or zone) at ... hours at ... (figures and units) height above ... (datum) are ...", "QAZ": "Are you experiencing communication difficulties through flying in a storm? / I am experiencing communication difficulties through flying in a storm.",
"QBD": "How much fuel have you remaining (expressed as hours and/or minutes of consumption)? / My fuel endurance is ... (hours and/or minutes).", "QBA": "What is the horizontal visibility at ... (place)? / The horizontal visibility at ... (place) at ... hours is ... (distance figures and units).",
"QBE": "I am about to wind in my aerial.", "QBB": "What is the amount, type and height above official aerodrome elevation of the base of the cloud [at ... (place)]? / The amount, type and height above official aerodrome elevation of the base of the cloud at ... (place) at ... hours is: ... eights (... type) at ... (figures and units) height above official aerodrome elevation.",
"QBF": "Are you flying in cloud? / I am flying in cloud at ... flight level/altitude ... [and I am ascending (descending) to flight level/altitude ...].", "QBC": "Report meteorological conditions as observed from your aircraft [at ... (position or zone)] [(at ... hours)]. / The meteorological conditions as observed from my aircraft at ... (position or zone) at ... hours at ... (figures and units) height above ... (datum) are ...",
"QBG": "Are you flying above cloud? / I am flying above cloud and at flight level/altitude ...", "QBD": "How much fuel have you remaining (expressed as hours and/or minutes of consumption)? / My fuel endurance is ... (hours and/or minutes).",
"QBH": "Are you flying below cloud? / I am flying below cloud and at flight level/altitude ...", "QBE": "I am about to wind in my aerial.",
"QBI": "Is flight under IFR compulsory at ... (place) [or from ... to ... (place)]? / Flight under IFR is compulsory at ... (place) [or from ... to ... (place)].", "QBF": "Are you flying in cloud? / I am flying in cloud at ... flight level/altitude ... [and I am ascending (descending) to flight level/altitude ...].",
"QBJ": "What is the amount, type and height above ... (datum) of the top of the cloud [at ... (position or zone)]? / At ... hours at ... (position or zone) the top of the cloud is: amount ... eights (... type) at ... (figures and units) height above ... (datum).", "QBG": "Are you flying above cloud? / I am flying above cloud and at flight level/altitude ...",
"QBK": "Are you flying with no cloud in your vicinity? / I am flying with no cloud in my vicinity and at flight level/altitude ...", "QBH": "Are you flying below cloud? / I am flying below cloud and at flight level/altitude ...",
"QBM": "Has ... sent any messages for me? / Here is the message sent by ... at ... hours.", "QBI": "Is flight under IFR compulsory at ... (place) [or from ... to ... (place)]? / Flight under IFR is compulsory at ... (place) [or from ... to ... (place)].",
"QBN": "Are you flying between two layers of cloud? / I am flying between two layers of cloud and at flight level/altitude ...", "QBJ": "What is the amount, type and height above ... (datum) of the top of the cloud [at ... (position or zone)]? / At ... hours at ... (position or zone) the top of the cloud is: amount ... eights (... type) at ... (figures and units) height above ... (datum).",
"QBO": "What is the nearest aerodrome at which flight under VFR is permissible and which would be suitable for my landing? / Flying under VFR is permissible at ... (place) which would be suitable for your landing.", "QBK": "Are you flying with no cloud in your vicinity? / I am flying with no cloud in my vicinity and at flight level/altitude ...",
"QBP": "Are you flying in and out of cloud? / I am flying in and out of cloud and at flight level/altitude ...", "QBM": "Has ... sent any messages for me? / Here is the message sent by ... at ... hours.",
"QBS": "Ascend (or descend) to ... (figures and units) height above ... (datum) before encountering instrument meteorological conditions or if visibility falls below ... (distance figures and units) and advise.", "QBN": "Are you flying between two layers of cloud? / I am flying between two layers of cloud and at flight level/altitude ...",
"QBT": "What is the runway visual range at ... (place)? / The runway visual range at ... (place) at ... hours is ... (distance figures and units).", "QBO": "What is the nearest aerodrome at which flight under VFR is permissible and which would be suitable for my landing? / Flying under VFR is permissible at ... (place) which would be suitable for your landing.",
"QBV": "Have you reached flight level/altitude ... [or ... (area or place)]? / I have reached ... flight level/altitude ... [or ... (area or place)].", "QBP": "Are you flying in and out of cloud? / I am flying in and out of cloud and at flight level/altitude ...",
"QBX": "Have you left ... flight level/altitude ... [or ... (area or place)]? / I have left ... flight level/altitude ... [or ... (area or place)].", "QBS": "Ascend (or descend) to ... (figures and units) height above ... (datum) before encountering instrument meteorological conditions or if visibility falls below ... (distance figures and units) and advise.",
"QBZ": "Report your flying conditions in relation to clouds. / The reply to QBZ ? is given by the appropriate answer form of signals QBF, QBG, QBH, QBK, QBN and QBP.", "QBT": "What is the runway visual range at ... (place)? / The runway visual range at ... (place) at ... hours is ... (distance figures and units).",
"QCA": "May I change my flight level/altitude from ... to ... ? / You may change your flight level/altitude from ... to ...", "QBV": "Have you reached flight level/altitude ... [or ... (area or place)]? / I have reached ... flight level/altitude ... [or ... (area or place)].",
"QCB": "Delay is being caused by ...", "QBX": "Have you left ... flight level/altitude ... [or ... (area or place)]? / I have left ... flight level/altitude ... [or ... (area or place)].",
"QCE": "When may I expect approach clearance? / Expect approach clearance at ... hours.", "QBZ": "Report your flying conditions in relation to clouds. / The reply to QBZ ? is given by the appropriate answer form of signals QBF, QBG, QBH, QBK, QBN and QBP.",
"QCF": "Delay indefinite. Expect approach clearance not later than ... hours.", "QCA": "May I change my flight level/altitude from ... to ... ? / You may change your flight level/altitude from ... to ...",
"QCH": "May I taxi to ... (place)? / Cleared to taxi to ... (place).", "QCB": "Delay is being caused by ...",
"QCI": "Make a 360-degree turn immediately (turning to the ...).", "QCE": "When may I expect approach clearance? / Expect approach clearance at ... hours.",
"QCS": "My reception on ... frequency has broken down.", "QCF": "Delay indefinite. Expect approach clearance not later than ... hours.",
"QCX": "What is your full call sign? / My full call sign is ...", "QCH": "May I taxi to ... (place)? / Cleared to taxi to ... (place).",
"QCY": "I am working on a trailing aerial.", "QCI": "Make a 360-degree turn immediately (turning to the ...).",
"QDB": "Have you sent message ... to ... ? / I have sent message ... to ...", "QCS": "My reception on ... frequency has broken down.",
"QDF": "What is your D-Value at ... (position)?", "QCX": "What is your full call sign? / My full call sign is ...",
"QDL": "Do you intend to ask me for a series of bearings? / I intend to ask you for a series of bearings.", "QCY": "I am working on a trailing aerial.",
"QDM": "Will you indicate the MAGNETIC heading for me to steer towards you (or ...) with no wind? / The MAGNETIC heading for you to steer to reach me (or ...) with no wind was ... degrees (at ... hours).", "QDB": "Have you sent message ... to ... ? / I have sent message ... to ...",
"QDP": "Will you accept control (or responsibility) of (for) ... now (or at ... hours)? / I will accept control (or responsibility) of (for) ... now (or at ... hours).", "QDF": "What is your D-Value at ... (position)?",
"QDR": "What is my MAGNETIC bearing from you (or from ...)? / Your MAGNETIC bearing from me (or from ...) was ... degrees (at ... hours).", "QDL": "Do you intend to ask me for a series of bearings? / I intend to ask you for a series of bearings.",
"QDT": "Are you flying in visual meteorological condition? / I am flying in visual meteorological condition.", "QDM": "Will you indicate the MAGNETIC heading for me to steer towards you (or ...) with no wind? / The MAGNETIC heading for you to steer to reach me (or ...) with no wind was ... degrees (at ... hours).",
"QDU": "Cancelling my IFR flight.", "QDP": "Will you accept control (or responsibility) of (for) ... now (or at ... hours)? / I will accept control (or responsibility) of (for) ... now (or at ... hours).",
"QDV": "Are you flying in a flight visibility of less than ... (figures and units)? / I am flying in a flight visibility of less than ... (figures and units) at flight level/altitude ...", "QDR": "What is my MAGNETIC bearing from you (or from ...)? / Your MAGNETIC bearing from me (or from ...) was ... degrees (at ... hours).",
"QEA": "May I cross the runway ahead of me? / You may cross the runway ahead of you.", "QDT": "Are you flying in visual meteorological condition? / I am flying in visual meteorological condition.",
"QEB": "May I turn at the intersection? / Taxi as follows at the intersection ...", "QDU": "Cancelling my IFR flight.",
"QEC": "May I make a 180-degree turn and return down the runway? / You may make a 180-degree turn and return down the runway.", "QDV": "Are you flying in a flight visibility of less than ... (figures and units)? / I am flying in a flight visibility of less than ... (figures and units) at flight level/altitude ...",
"QED": "Shall I follow the pilot vehicle? / Follow the pilot vehicle.", "QEA": "May I cross the runway ahead of me? / You may cross the runway ahead of you.",
"QEF": "Have I reached my parking area? / You have reached your parking area.", "QEB": "May I turn at the intersection? / Taxi as follows at the intersection ...",
"QEG": "May I leave the parking area? / You may leave the parking area.", "QEC": "May I make a 180-degree turn and return down the runway? / You may make a 180-degree turn and return down the runway.",
"QEH": "May I move to the holding position for runway number ... ? / Cleared to the holding position for runway number ...", "QED": "Shall I follow the pilot vehicle? / Follow the pilot vehicle.",
"QEJ": "May I assume position for take-off? / Cleared to hold at take-off position for runway number ...", "QEF": "Have I reached my parking area? / You have reached your parking area.",
"QEK": "Are you ready for immediate take-off? / I am ready for immediate take-off.", "QEG": "May I leave the parking area? / You may leave the parking area.",
"QEL": "May I take-off (and make a ... hand turn after take-off)? / You are cleared to take-off (turn as follows after take-off ...).", "QEH": "May I move to the holding position for runway number ... ? / Cleared to the holding position for runway number ...",
"QEM": "What is the condition of the landing surface at ... (place)? / The condition of the landing surface at ... (place) is ...", "QEJ": "May I assume position for take-off? / Cleared to hold at take-off position for runway number ...",
"QEN": "Shall I hold my position? / Hold your position", "QEK": "Are you ready for immediate take-off? / I am ready for immediate take-off.",
"QEO": "Shall I clear the runway (or landing area)? / Clear the runway (or landing area).", "QEL": "May I take-off (and make a ... hand turn after take-off)? / You are cleared to take-off (turn as follows after take-off ...).",
"QES": "Is a right-hand circuit in force at ... (place)? / A right-hand circuit is in force at ... (place).", "QEM": "What is the condition of the landing surface at ... (place)? / The condition of the landing surface at ... (place) is ...",
"QFA": "What is the meteorological forecast for ... (flight, route, section of route or zone) for the period ... hours until ... hours? / The meteorological forecast for ... (flight, route, section of route or zone) for the period ... hours until ... hours is ...", "QEN": "Shall I hold my position? / Hold your position",
"QFB": "The approach/runway lights are out of order.", "QEO": "Shall I clear the runway (or landing area)? / Clear the runway (or landing area).",
"QFC": "What is the amount, the type and the height above ... (datum) of the base of the cloud at ... (place, position or zone)? / At ... (place, position or zone) the base of the cloud is ... eighths ... type at ... (figures and units) height above ... (datum).", "QES": "Is a right-hand circuit in force at ... (place)? / A right-hand circuit is in force at ... (place).",
"QFD": "Is the ... visual beacon [at ... (place)] in operation?", "QFA": "What is the meteorological forecast for ... (flight, route, section of route or zone) for the period ... hours until ... hours? / The meteorological forecast for ... (flight, route, section of route or zone) for the period ... hours until ... hours is ...",
"QFE": "What should I set on the subscale of my altimeter so that the instrument would indicate its height above the reference elevation being used? / If you set the subscale of your altimeter to read ... millibars, the instrument would indicate its height above aerodrome elevation (above threshold, runway number ...).", "QFB": "The approach/runway lights are out of order.",
"QFF": "[At ... (place)] what is the present atmospheric pressure converted to mean sea level in accordance with meteorological practice? / At ... (place) the atmospheric pressure converted to mean sea level in accordance with meteorological practice is (or was determined at ... hours to be) ... millibars.", "QFC": "What is the amount, the type and the height above ... (datum) of the base of the cloud at ... (place, position or zone)? / At ... (place, position or zone) the base of the cloud is ... eighths ... type at ... (figures and units) height above ... (datum).",
"QFG": "Am I overhead? / You are overhead.", "QFD": "Is the ... visual beacon [at ... (place)] in operation?",
"QFH": "May I descend below the clouds? / You may descend below the clouds.", "QFE": "What should I set on the subscale of my altimeter so that the instrument would indicate its height above the reference elevation being used? / If you set the subscale of your altimeter to read ... millibars, the instrument would indicate its height above aerodrome elevation (above threshold, runway number ...).",
"QFI": "Are the aerodrome lights lit? / The aerodrome lights are lit.", "QFF": "[At ... (place)] what is the present atmospheric pressure converted to mean sea level in accordance with meteorological practice? / At ... (place) the atmospheric pressure converted to mean sea level in accordance with meteorological practice is (or was determined at ... hours to be) ... millibars.",
"QFL": "Will you send up pyrotechnical lights? / I will send up pyrotechnical lights.", "QFG": "Am I overhead? / You are overhead.",
"QFM": "What flight level/altitude should I maintain?", "QFH": "May I descend below the clouds? / You may descend below the clouds.",
"QFO": "May I land immediately? / You may land immediately.", "QFI": "Are the aerodrome lights lit? / The aerodrome lights are lit.",
"QFP": "Will you give me the latest information concerning ... facility [at ... (place)]? / The latest information concerning ... facility [at ... (place)] is as follows ...", "QFL": "Will you send up pyrotechnical lights? / I will send up pyrotechnical lights.",
"QFQ": "Are the approach and runway lights lit? / The approach and runway lights are lit.", "QFM": "What flight level/altitude should I maintain?",
"QFR": "Does my landing gear appear damaged? / Your landing gear appears damaged.", "QFO": "May I land immediately? / You may land immediately.",
"QFS": "Is the radio facility at ... (place) in operation? / The radio facility at ... (place) is in operation (or will be in operation in ... hours).", "QFP": "Will you give me the latest information concerning ... facility [at ... (place)]? / The latest information concerning ... facility [at ... (place)] is as follows ...",
"QFT": "Between what heights above ... (datum) has ice formation been observed [at ... (position or zone)]? / Ice formation has been observed at ... (position or zone) in the type of ... and with an accretion rate of ... between ... (figures and units) and ... (figures and units) heights above ... (datum).", "QFQ": "Are the approach and runway lights lit? / The approach and runway lights are lit.",
"QFU": "What is the magnetic direction (or number) of the runway to be used? / The magnetic direction (or number) of the runway to be used is ...", "QFR": "Does my landing gear appear damaged? / Your landing gear appears damaged.",
"QFV": "Are the floodlights switched on? / The floodlights are switched on.", "QFS": "Is the radio facility at ... (place) in operation? / The radio facility at ... (place) is in operation (or will be in operation in ... hours).",
"QFW": "What is the length of the runway in use in ... (units)? / The length of runway ... now in use is ... (figures and units).", "QFT": "Between what heights above ... (datum) has ice formation been observed [at ... (position or zone)]? / Ice formation has been observed at ... (position or zone) in the type of ... and with an accretion rate of ... between ... (figures and units) and ... (figures and units) heights above ... (datum).",
"QFX": "I am working (or am going to work) on a fixed aerial.", "QFU": "What is the magnetic direction (or number) of the runway to be used? / The magnetic direction (or number) of the runway to be used is ...",
"QFY": "Please report the present meteorological landing conditions [at ... (place)]. / The present meteorological landing conditions at ... (place) are ...", "QFV": "Are the floodlights switched on? / The floodlights are switched on.",
"QFZ": "What is the aerodrome meteorological forecast for ... (place) for the period ... hours until ... hours? / The aerodrome meteorological forecast for ... (place) for the period ... hours until ... hours is ...", "QFW": "What is the length of the runway in use in ... (units)? / The length of runway ... now in use is ... (figures and units).",
"QGC": "There are obstructions to the ... of ... runway ...", "QFX": "I am working (or am going to work) on a fixed aerial.",
"QGD": "Are there on my track any obstructions whose elevation equals or exceeds my altitude? / There are obstructions on your track ... (figures and units) height above ... (datum).", "QFY": "Please report the present meteorological landing conditions [at ... (place)]. / The present meteorological landing conditions at ... (place) are ...",
"QGE": "What is my distance to your station (or to ...)? / Your distance to my station (or to ...) is ... (distance figures and units).", "QFZ": "What is the aerodrome meteorological forecast for ... (place) for the period ... hours until ... hours? / The aerodrome meteorological forecast for ... (place) for the period ... hours until ... hours is ...",
"QGH": "May I land using ... (procedure or facility)? / You may land using ... (procedure or facility).", "QGC": "There are obstructions to the ... of ... runway ...",
"QGK": "What track should I make good? / Make good a track from ... (place) on ... degrees ... (true or magnetic).", "QGD": "Are there on my track any obstructions whose elevation equals or exceeds my altitude? / There are obstructions on your track ... (figures and units) height above ... (datum).",
"QGL": "May I enter the ... (control area or zone) at ... (place)? / You may enter the ... (control area or zone) at ... (place).", "QGE": "What is my distance to your station (or to ...)? / Your distance to my station (or to ...) is ... (distance figures and units).",
"QGM": "Leave the ... (control area or zone).", "QGH": "May I land using ... (procedure or facility)? / You may land using ... (procedure or facility).",
"QGN": "May I be cleared to land [at ... (place)]? / You are cleared to land [at ... (place)].", "QGK": "What track should I make good? / Make good a track from ... (place) on ... degrees ... (true or magnetic).",
"QGO": "Landing is prohibited at ... (place).", "QGL": "May I enter the ... (control area or zone) at ... (place)? / You may enter the ... (control area or zone) at ... (place).",
"QGP": "What is my number for landing? / You are number ... to land.", "QGM": "Leave the ... (control area or zone).",
"QGQ": "May I hold at ... (place)? / Hold at ... (place) at flight level/altitude ... (datum) and await further clearance.", "QGN": "May I be cleared to land [at ... (place)]? / You are cleared to land [at ... (place)].",
"QGT": "Fly for ... minutes on a heading what will enable you to maintain a track reciprocal to your present one.", "QGO": "Landing is prohibited at ... (place).",
"QGU": "Fly for ... minutes on a magnetic heading of ... degrees.", "QGP": "What is my number for landing? / You are number ... to land.",
"QGV": "Do you see me? / Can you see the aerodrome? / Can you see ... (aircraft)? / I see you at ... (cardinal or quadrantal point of direction).", "QGQ": "May I hold at ... (place)? / Hold at ... (place) at flight level/altitude ... (datum) and await further clearance.",
"QGW": "Does my landing gear appear to be down and in place? / Your landing gear appears to be down and in place.", "QGT": "Fly for ... minutes on a heading what will enable you to maintain a track reciprocal to your present one.",
"QGZ": "Hold on ... direction of ... facility.", "QGU": "Fly for ... minutes on a magnetic heading of ... degrees.",
"QHE": "Will you inform me when you are on ... leg of approach? / I am on ...(leg).. of approach.", "QGV": "Do you see me? / Can you see the aerodrome? / Can you see ... (aircraft)? / I see you at ... (cardinal or quadrantal point of direction).",
"QHG": "May I enter traffic circuit at flight level/altitude ...? / Cleared to enter traffic circuit at flight level/altitude ...", "QGW": "Does my landing gear appear to be down and in place? / Your landing gear appears to be down and in place.",
"QHH": "Are you making an emergency landing? / I am making an emergency landing.", "QGZ": "Hold on ... direction of ... facility.",
"QHI": "Are you (or is ...) ... waterborne / on land? ", "QHE": "Will you inform me when you are on ... leg of approach? / I am on ...(leg).. of approach.",
"QHQ": "May I make a ... approach [at ... (place)]? / You may make a ... approach [at ... (place)].", "QHG": "May I enter traffic circuit at flight level/altitude ...? / Cleared to enter traffic circuit at flight level/altitude ...",
"QHZ": "Shall I circle the aerodrome (or go around)? / Circle the aerodrome (or go around).", "QHH": "Are you making an emergency landing? / I am making an emergency landing.",
"QIC": "May I establish communication with ... radio station on ... kHz (or ... MHz.) now (or at ... hours)? / Establish communication with ... radio station on ... kHz. (or MHz.) now (or at ...hours).", "QHI": "Are you (or is ...) ... waterborne / on land? ",
"QIF": "What frequency is ... using? / ... is using ... kHz (or ... MHz.).", "QHQ": "May I make a ... approach [at ... (place)]? / You may make a ... approach [at ... (place)].",
"QJA": "Is my tape / mark and space reversed? / Your tape / mark and space is reversed.", "QHZ": "Shall I circle the aerodrome (or go around)? / Circle the aerodrome (or go around).",
"QJB": "Will you use radio/cable/telegraph/teletype/telephone/receiver/transmitter/reperforator? / I will use ...", "QIC": "May I establish communication with ... radio station on ... kHz (or ... MHz.) now (or at ... hours)? / Establish communication with ... radio station on ... kHz. (or MHz.) now (or at ...hours).",
"QJC": "Will you check your transmitter/autohead/perforator/reperfordator/printer/keyboard/antenna? / I will check my ...", "QIF": "What frequency is ... using? / ... is using ... kHz (or ... MHz.).",
"QJD": "Am I transmitting letters/figures? / You are transmitting ...", "QJA": "Is my tape / mark and space reversed? / Your tape / mark and space is reversed.",
"QJE": "Is my frequency shift wide/narrow/correct? / Your frequency shift is ... (by ... cycles)", "QJB": "Will you use radio/cable/telegraph/teletype/telephone/receiver/transmitter/reperforator? / I will use ...",
"QJF": "My signal as checked by monitor locally/as radiated is satisfactory.", "QJC": "Will you check your transmitter/autohead/perforator/reperfordator/printer/keyboard/antenna? / I will check my ...",
"QJG": "Shall I revert to automatic relay? / Revert to automatic relay.", "QJD": "Am I transmitting letters/figures? / You are transmitting ...",
"QJH": "Shall I run test tape/sentence? / Run test tape/sentence.", "QJE": "Is my frequency shift wide/narrow/correct? / Your frequency shift is ... (by ... cycles)",
"QJI": "Will you transmit a continuous mark/space? / I am transmitting a continuous mark/space.", "QJF": "My signal as checked by monitor locally/as radiated is satisfactory.",
"QJK": "Are you receiving continuous mark/space / mark/space bias? / I am receiving a continuous mark/space / mark/space bias.", "QJG": "Shall I revert to automatic relay? / Revert to automatic relay.",
"QKC": "The sea conditions (at ... position) ... permit alighting but not takeoff / render alihting extremely hazardous.", "QJH": "Shall I run test tape/sentence? / Run test tape/sentence.",
"QKF": "May I be relieved (at ... hours)? / You may expect to be relieved at ... hours [by aircraft/vessel/callsign/name].", "QJI": "Will you transmit a continuous mark/space? / I am transmitting a continuous mark/space.",
"QKG": "Will relief take place when ... (identification) establishes visual/comms contact with survivors? / Relief will take place when ... (identification) establishes visual/comms contact with survivors.", "QJK": "Are you receiving continuous mark/space / mark/space bias? / I am receiving a continuous mark/space / mark/space bias.",
"QKH": "Report details of the parallel sweep (track) search being (or to be) conducted? --or-- In the parallel sweep (track) search being (or to be) conducted, what is (are) the direction/separation/altitude of sweeps employed in the search pattern? / The parallel sweep (track) search is being (or to be) conducted [with direction of sweeps ... degrees ... (true or magnetic) || with ... (distance figures and units) separation between sweeps || flight level/altitude].", "QKC": "The sea conditions (at ... position) ... permit alighting but not takeoff / render alihting extremely hazardous.",
"QKN": "Aircraft plotted (believed to be you) in position ... on track ... degrees at ... hours.", "QKF": "May I be relieved (at ... hours)? / You may expect to be relieved at ... hours [by aircraft/vessel/callsign/name].",
"QKO": "What other units are (or will be) taking part in the operation [... (identification of operation)]? / In the operation [... (identification)], the following units are (or will be) taking part ... (name of units). --or-- ... (name) unit is taking part in operation [... (identification] (with effect from ... hours).", "QKG": "Will relief take place when ... (identification) establishes visual/comms contact with survivors? / Relief will take place when ... (identification) establishes visual/comms contact with survivors.",
"QKP": "Which pattern of search is being followed? / The search pattern is parallel sweep/square search/creeping line ahead/track crawl/contour search/combined search by aircraft and ship/[other].", "QKH": "Report details of the parallel sweep (track) search being (or to be) conducted? --or-- In the parallel sweep (track) search being (or to be) conducted, what is (are) the direction/separation/altitude of sweeps employed in the search pattern? / The parallel sweep (track) search is being (or to be) conducted [with direction of sweeps ... degrees ... (true or magnetic) || with ... (distance figures and units) separation between sweeps || flight level/altitude].",
"QLB": "Will you monitor ... station and report regarding range, quality, etc.? / I have monitored ... station and report (briefly) as follows ...", "QKN": "Aircraft plotted (believed to be you) in position ... on track ... degrees at ... hours.",
"QLE": "What is your expected signal? / The expected signal is low...", "QKO": "What other units are (or will be) taking part in the operation [... (identification of operation)]? / In the operation [... (identification)], the following units are (or will be) taking part ... (name of units). --or-- ... (name) unit is taking part in operation [... (identification] (with effect from ... hours).",
"QLF": "Are you sending with your left foot? Try sending with your left foot!", "QKP": "Which pattern of search is being followed? / The search pattern is parallel sweep/square search/creeping line ahead/track crawl/contour search/combined search by aircraft and ship/[other].",
"QLH": "Will you use simultaneous keying on ... frequency and ... frequency? / I will now key simultaneously on ... frequency and ... frequency.", "QLB": "Will you monitor ... station and report regarding range, quality, etc.? / I have monitored ... station and report (briefly) as follows ...",
"QLV": "Is the ... radio facility still required? / The ... radio facility is still required.", "QLE": "What is your expected signal? / The expected signal is low...",
"QMH": "Shift to transmit and receive on ... kHz (or ... MHz.); if communication is not established within 5 minutes, revert to present frequency.", "QLF": "Are you sending with your left foot? Try sending with your left foot!",
"QMI": "Report the vertical distribution of cloud [at ... (position or zone)] as observed from your aircraft. / The vertical distribution of cloud as observed from my aircraft at ... hours at ... (position or zone) is : *lowest layer observed* ... eights (... type) with base of ... (figures and units) and tops of ... (figures and units) [*and similarly in sequence for each of the layers observed.] height above ... (datum).", "QLH": "Will you use simultaneous keying on ... frequency and ... frequency? / I will now key simultaneously on ... frequency and ... frequency.",
"QMU": "What is the surface temperature at ... (place) and what is the dew point temperature at that place? / The surface temperature at ... (place) at ... hours is ... degrees and the dew point temperature at that time and place is ... degrees.", "QLV": "Is the ... radio facility still required? / The ... radio facility is still required.",
"QMW": "At ... (position or zone) what is (are) the flight level(s)/altitude(s) ... of the zero Celsius isotherm(s)? / At ... (position or zone) the zero Celsius isotherm(s) is (are) at flight level(s)/altitude(s) ...", "QMH": "Shift to transmit and receive on ... kHz (or ... MHz.); if communication is not established within 5 minutes, revert to present frequency.",
"QMX": "What is the air temperature [at ... (position or zone)] (at ... hours) at flight level/altitude ...? / At ... (position or zone) at ... hours the air temperature is ... (degrees and units) at flight level/altitude ... --Note-- Aircraft reporting QMX information will transmit the temperature figures as corrected for airspeed.", "QMI": "Report the vertical distribution of cloud [at ... (position or zone)] as observed from your aircraft. / The vertical distribution of cloud as observed from my aircraft at ... hours at ... (position or zone) is : *lowest layer observed* ... eights (... type) with base of ... (figures and units) and tops of ... (figures and units) [*and similarly in sequence for each of the layers observed.] height above ... (datum).",
"QMZ": "Have you any amendments to the flight forecast in respect of section of route yet to be traversed? / The following amendment(s) should be made to the flight forecast ... [If no amendments, signal QMZ NIL]", "QMU": "What is the surface temperature at ... (place) and what is the dew point temperature at that place? / The surface temperature at ... (place) at ... hours is ... degrees and the dew point temperature at that time and place is ... degrees.",
"QNE": "What indication will my altimeter give on landing at ... (place) at ... hours, my sub-scale being set to 1013.2 millibars (29.92 inches)? / On landing at ... (place) at ... hours, with your sub-scale being set to 1013.2 millibars (29.92 inches), your altimeter will indicate ... (figures and units).", "QMW": "At ... (position or zone) what is (are) the flight level(s)/altitude(s) ... of the zero Celsius isotherm(s)? / At ... (position or zone) the zero Celsius isotherm(s) is (are) at flight level(s)/altitude(s) ...",
"QNH": "What should I set on the subscale of my altimeter so that the instrument would indicate its elevation if my aircraft were on the ground at your station? / If you set the subscale of your altimeter to read ... millibars, the instrument would indicate its elevation if your aircraft were on the ground at my station at ... hours. --Note-- When the setting is given in hundredths of inch the abbreviation INS is used to identify the units.", "QMX": "What is the air temperature [at ... (position or zone)] (at ... hours) at flight level/altitude ...? / At ... (position or zone) at ... hours the air temperature is ... (degrees and units) at flight level/altitude ... --Note-- Aircraft reporting QMX information will transmit the temperature figures as corrected for airspeed.",
"QNI": "May I join the net? / You may check in...", "QMZ": "Have you any amendments to the flight forecast in respect of section of route yet to be traversed? / The following amendment(s) should be made to the flight forecast ... [If no amendments, signal QMZ NIL]",
"QNO": "I am not equipped to give the information (or provide the facility) requested.", "QNE": "What indication will my altimeter give on landing at ... (place) at ... hours, my sub-scale being set to 1013.2 millibars (29.92 inches)? / On landing at ... (place) at ... hours, with your sub-scale being set to 1013.2 millibars (29.92 inches), your altimeter will indicate ... (figures and units).",
"QNR": "I am approaching my point of no return.", "QNH": "What should I set on the subscale of my altimeter so that the instrument would indicate its elevation if my aircraft were on the ground at your station? / If you set the subscale of your altimeter to read ... millibars, the instrument would indicate its elevation if your aircraft were on the ground at my station at ... hours. --Note-- When the setting is given in hundredths of inch the abbreviation INS is used to identify the units.",
"QNT": "What is the maximum speed of the surface wind at ... (place)? / The maximum speed of the surface wind at ... (place) at ... hours is ... (speed figures and units).", "QNI": "May I join the net? / You may check in...",
"QNY": "What is the present weather and the intensity thereof at ... (place, position or zone)? / The present weather and intensity thereof at ... (place, position or zone) at ... hours is ...", "QNO": "I am not equipped to give the information (or provide the facility) requested.",
"QOA": "Can you communicate by radiotelegraphy (500 kHz)? / I can communicate by radiotelegraphy (500 kHz).", "QNR": "I am approaching my point of no return.",
"QOB": "Can you communicate by radiotelephony (2182 kHz)? / I can communicate by radiotelephony (2182 kHz).", "QNT": "What is the maximum speed of the surface wind at ... (place)? / The maximum speed of the surface wind at ... (place) at ... hours is ... (speed figures and units).",
"QOC": "Can you communicate by radiotelephony (channel 16 - frequency 156.80 MHz)? / I can communicate by radiotelephony (channel 16 - frequency 156.80 MHz).", "QNY": "What is the present weather and the intensity thereof at ... (place, position or zone)? / The present weather and intensity thereof at ... (place, position or zone) at ... hours is ...",
"QOD": "Can you communicate with me in Dutch/English/French/German/Greek/Italian/Japanese/Norwegian/Russian/Spanish? / I can communicate with you in ...", "QOA": "Can you communicate by radiotelegraphy (500 kHz)? / I can communicate by radiotelegraphy (500 kHz).",
"QOE": "Have you received the safety signal sent by ... (name and/or call sign)? / I have received the safety signal sent by ... (name and/or call sign).", "QOB": "Can you communicate by radiotelephony (2182 kHz)? / I can communicate by radiotelephony (2182 kHz).",
"QOF": "What is the commercial quality of my signals? / The quality of your signals is not commercial/marginally commercial/commercial.", "QOC": "Can you communicate by radiotelephony (channel 16 - frequency 156.80 MHz)? / I can communicate by radiotelephony (channel 16 - frequency 156.80 MHz).",
"QOG": "How many tapes have you to send? / I have ... tapes to send.", "QOD": "Can you communicate with me in Dutch/English/French/German/Greek/Italian/Japanese/Norwegian/Russian/Spanish? / I can communicate with you in ...",
"QOH": "Shall I send a phasing signal for ... seconds? / Send a phasing signal for ... seconds.", "QOE": "Have you received the safety signal sent by ... (name and/or call sign)? / I have received the safety signal sent by ... (name and/or call sign).",
"QOI": "Shall I send my tape? / Send your tape.", "QOF": "What is the commercial quality of my signals? / The quality of your signals is not commercial/marginally commercial/commercial.",
"QOJ": "Will you listen on ... kHz (or MHz) for signals of emergency position-indicating radiobeacons? / I am listening on ... kHz (or MHz) for signals of emergency position-indicating radiobeacons.", "QOG": "How many tapes have you to send? / I have ... tapes to send.",
"QOK": "Have you received the signals of an emergency position-indicating radiobeacon on ... kHz (or MHz)? / I have received the signals of an emergency position-indicating radiobeacon on ... kHz (or MHz).", "QOH": "Shall I send a phasing signal for ... seconds? / Send a phasing signal for ... seconds.",
"QOL": "Is your vessel fitted for reception of selective calls? If so, what is your selective call number or signal? / My vessel is fitted for the reception of selective calls. My selective call number or signal is ...", "QOI": "Shall I send my tape? / Send your tape.",
"QOM": "On what frequencies can your vessel be reached by a selective call? / My vessel can be reached by a selective call on the following frequency/ies ... (periods of time to be added if necessary).", "QOJ": "Will you listen on ... kHz (or MHz) for signals of emergency position-indicating radiobeacons? / I am listening on ... kHz (or MHz) for signals of emergency position-indicating radiobeacons.",
"QOO": "Can you send on any working frequency? / I can send on any working frequency.", "QOK": "Have you received the signals of an emergency position-indicating radiobeacon on ... kHz (or MHz)? / I have received the signals of an emergency position-indicating radiobeacon on ... kHz (or MHz).",
"QOT": "Do you hear my call; what is the approximate delay in minutes before we may exchange traffic? / I hear your call; the approximate delay is ... minutes.", "QOL": "Is your vessel fitted for reception of selective calls? If so, what is your selective call number or signal? / My vessel is fitted for the reception of selective calls. My selective call number or signal is ...",
"QRA": "What is the name of your vessel (or station)? / The name of my vessel (or station) is ...", "QOM": "On what frequencies can your vessel be reached by a selective call? / My vessel can be reached by a selective call on the following frequency/ies ... (periods of time to be added if necessary).",
"QRB": "How far approximately are you from my station? / The approximate distance between our stations is ... nautical miles (or km).", "QOO": "Can you send on any working frequency? / I can send on any working frequency.",
"QRC": "What is your true bearing? / My true bearing is ____ degrees.", "QOT": "Do you hear my call; what is the approximate delay in minutes before we may exchange traffic? / I hear your call; the approximate delay is ... minutes.",
"QRD": "Where are you bound for? / I am bound for ____.", "QRA": "What is the name of your vessel (or station)? / The name of my vessel (or station) is ...",
"QRE": "What is your estimated time of arrival at ... (or over ...) (place)? / My estimated time of arrival at ... (or over ...) (place) is ... hours.", "QRB": "How far approximately are you from my station? / The approximate distance between our stations is ... nautical miles (or km).",
"QRF": "Where are you bound from? / I am bound from ____.", "QRC": "What is your true bearing? / My true bearing is ____ degrees.",
"QRG": "Will you tell me my exact frequency (or that of ...)? / Your exact frequency (or that of ...) is ... kHz (or MHz).", "QRD": "Where are you bound for? / I am bound for ____.",
"QRH": "Does my frequency vary? / Your frequency varies.", "QRE": "What is your estimated time of arrival at ... (or over ...) (place)? / My estimated time of arrival at ... (or over ...) (place) is ... hours.",
"QRI": "How is the tone of my transmission? / The tone of your transmission is ...", "QRF": "Where are you bound from? / I am bound from ____.",
"QRJ": "How many voice contacts do you want to make? / I want to make ... voice contacts.", "QRG": "Will you tell me my exact frequency (or that of ...)? / Your exact frequency (or that of ...) is ... kHz (or MHz).",
"QRK": "How do you receive me? / I am receiving (1-5).", "QRH": "Does my frequency vary? / Your frequency varies.",
"QRL": "Are you busy? / I am busy (or I am busy with ...). Please do not interfere.", "QRI": "How is the tone of my transmission? / The tone of your transmission is ...",
"QRM": "Are you being interfered with? / I am being interfered with.", "QRJ": "How many voice contacts do you want to make? / I want to make ... voice contacts.",
"QRN": "Are the atmospherics strong? / Atmospherics (noise) are very strong.", "QRK": "How do you receive me? / I am receiving (1-5).",
"QRO": "Shall I increase transmitter power? / Increase transmitter power.", "QRL": "Are you busy? / I am busy (or I am busy with ...). Please do not interfere.",
"QRP": "Shall I decrease transmitter power? / Decrease transmitter power.", "QRM": "Are you being interfered with? / I am being interfered with.",
"QRQ": "Shall I send faster? / Send faster (... wpm)", "QRN": "Are the atmospherics strong? / Atmospherics (noise) are very strong.",
"QRR": "Are you ready for automatic operation? / I am ready for automatic operation. Send at ... words per minute.", "QRO": "Shall I increase transmitter power? / Increase transmitter power.",
"QRS": "Shall I send more slowly? / Send more slowly (... words per minute).", "QRP": "Shall I decrease transmitter power? / Decrease transmitter power.",
"QRT": "Shall I stop sending? / Stop sending.", "QRQ": "Shall I send faster? / Send faster (... wpm)",
"QRU": "Have you anything for me? / I have nothing for you.", "QRR": "Are you ready for automatic operation? / I am ready for automatic operation. Send at ... words per minute.",
"QRV": "Are you ready? / I am ready.", "QRS": "Shall I send more slowly? / Send more slowly (... words per minute).",
"QRW": "Shall I inform ... that you are calling him on ... kHz (or MHz)? / Please inform ... that I am calling him on ... kHz (or MHz).", "QRT": "Shall I stop sending? / Stop sending.",
"QRX": "When will you call me again? / I will call you again at ... hours (on ... kHz (or MHz)).", "QRU": "Have you anything for me? / I have nothing for you.",
"QRY": "What is my turn? / Your turn is Number ... (or according to any other indication).", "QRV": "Are you ready? / I am ready.",
"QRZ": "Who is calling me? / You are being called by ... (on ... kHz (or MHz)).", "QRW": "Shall I inform ... that you are calling him on ... kHz (or MHz)? / Please inform ... that I am calling him on ... kHz (or MHz).",
"QSA": "What is the strength of my signals (or those of ...)? / The strength of your signals (or those of ...) is ...", "QRX": "When will you call me again? / I will call you again at ... hours (on ... kHz (or MHz)).",
"QSB": "Are my signals fading? / Your signals are fading.", "QRY": "What is my turn? / Your turn is Number ... (or according to any other indication).",
"QSC": "Are you a cargo vessel? --or-- Are you a low traffic ship? / I am a cargo vessel. --or-- I am a low traffic ship.", "QRZ": "Who is calling me? / You are being called by ... (on ... kHz (or MHz)).",
"QSD": "Is my keying defective? --or-- Are my signals mutilated? / Your keying is defective. --or-- Your signals are mutilated.", "QSA": "What is the strength of my signals (or those of ...)? / The strength of your signals (or those of ...) is ...",
"QSE": "What is the estimated drift of the survival craft? / The estimated drift of the survival craft is ... (figures and units).", "QSB": "Are my signals fading? / Your signals are fading.",
"QSF": "Have you effected rescue? / I have effected rescue and am proceeding to ... base (with ... persons injured requiring ambulance).", "QSC": "Are you a cargo vessel? --or-- Are you a low traffic ship? / I am a cargo vessel. --or-- I am a low traffic ship.",
"QSG": "Shall I send ... telegrams at a time? / Send ... telegrams at a time.", "QSD": "Is my keying defective? --or-- Are my signals mutilated? / Your keying is defective. --or-- Your signals are mutilated.",
"QSH": "Are you able to home on your direction-finding equipment? / I am able to home on my D/F equipment (on station ...).", "QSE": "What is the estimated drift of the survival craft? / The estimated drift of the survival craft is ... (figures and units).",
"QSI": "I have been unable to break in on your transmission. --or-- Will you inform ... (call sign) that I have been unable to break in on his transmission (on ... kHz (or MHz)).", "QSF": "Have you effected rescue? / I have effected rescue and am proceeding to ... base (with ... persons injured requiring ambulance).",
"QSJ": "What is the charge to be collected to ... including your internal charge? / The charge to be collected to ... including my internal charge is ... francs.", "QSG": "Shall I send ... telegrams at a time? / Send ... telegrams at a time.",
"QSK": "Can you hear me between your signals and if so can I break in on your transmission? / I can hear you between my signals; break in on my transmission.", "QSH": "Are you able to home on your direction-finding equipment? / I am able to home on my D/F equipment (on station ...).",
"QSL": "Can you acknowledge receipt? / I am acknowledging receipt.", "QSI": "I have been unable to break in on your transmission. --or-- Will you inform ... (call sign) that I have been unable to break in on his transmission (on ... kHz (or MHz)).",
"QSM": "Shall I repeat the last telegram (message) which I sent you, or some previous telegram (message)? / Repeat the last telegram (message) which you sent me (or telegram(s) (message(s)) numbers(s) ...).", "QSJ": "What is the charge to be collected to ... including your internal charge? / The charge to be collected to ... including my internal charge is ... francs.",
"QSN": "Did you hear me (or ... (call sign)) on ... kHz (or MHz)? / I did hear you (or ... (call sign)) on ... kHz (or MHz).", "QSK": "Can you hear me between your signals and if so can I break in on your transmission? / I can hear you between my signals; break in on my transmission.",
"QSO": "Can you communicate with ... direct (or by relay)? / I can communicate with ... direct (or by relay through ...).", "QSL": "Can you acknowledge receipt? / I am acknowledging receipt.",
"QSP": "Will you relay to ... free of charge? / I will relay to ... free of charge.", "QSM": "Shall I repeat the last telegram (message) which I sent you, or some previous telegram (message)? / Repeat the last telegram (message) which you sent me (or telegram(s) (message(s)) numbers(s) ...).",
"QSQ": "Have you a doctor on board (or is ... (name of person) on board)? / I have a doctor on board (or ... (name of person) is on board).", "QSN": "Did you hear me (or ... (call sign)) on ... kHz (or MHz)? / I did hear you (or ... (call sign)) on ... kHz (or MHz).",
"QSR": "Shall I repeat the call on the calling frequency? / Repeat your call on the calling frequency; did not hear you (or have interference).", "QSO": "Can you communicate with ... direct (or by relay)? / I can communicate with ... direct (or by relay through ...).",
"QSS": "What working frequency will you use? / I will use the working frequency ... kHz (or MHz) (in the HF bands normally only the last three figures of the frequency need be given).", "QSP": "Will you relay to ... free of charge? / I will relay to ... free of charge.",
"QST": "Here is a broadcast message to all amateurs.", "QSQ": "Have you a doctor on board (or is ... (name of person) on board)? / I have a doctor on board (or ... (name of person) is on board).",
"QSU": "Shall I send or reply on this frequency (or on ... kHz (or MHz)) (with emissions of class ...)? / Send or reply on this frequency (or on ... kHz (or MHz)) (with emissions of class ...).", "QSR": "Shall I repeat the call on the calling frequency? / Repeat your call on the calling frequency; did not hear you (or have interference).",
"QSV": "Shall I send a series of Vs on this frequency (or on ... kHz (or MHz))? / Send a series of Vs on this frequency (or on ... kHz (or MHz)).", "QSS": "What working frequency will you use? / I will use the working frequency ... kHz (or MHz) (in the HF bands normally only the last three figures of the frequency need be given).",
"QSW": "Will you send on this frequency (or on ... kHz (or MHz)) (with emissions of class ...)? / I am going to send on this frequency (or on ... kHz (or MHz)) (with emissions of class ...).", "QST": "Here is a broadcast message to all amateurs.",
"QSX": "Will you listen to ... (call sign(s)) on ... kHz (or MHz)? --or-- Will you listen to ... (call sign(s)) on ... kHz (or MHz), or in the bands ... / channels ... ? / I am listening to ... (call sign(s)) on ... kHz (or MHz). --or-- I am listening to ... (call sign(s)) on ... kHz (or MHz), or in the bands ... / channels ...", "QSU": "Shall I send or reply on this frequency (or on ... kHz (or MHz)) (with emissions of class ...)? / Send or reply on this frequency (or on ... kHz (or MHz)) (with emissions of class ...).",
"QSY": "Shall I change to transmission on another frequency? / Change to transmission on another frequency (or on ... kHz (or MHz)).", "QSV": "Shall I send a series of Vs on this frequency (or on ... kHz (or MHz))? / Send a series of Vs on this frequency (or on ... kHz (or MHz)).",
"QSZ": "Shall I send each word or group more than once? / Send each word or group twice (or ... times).", "QSW": "Will you send on this frequency (or on ... kHz (or MHz)) (with emissions of class ...)? / I am going to send on this frequency (or on ... kHz (or MHz)) (with emissions of class ...).",
"QTA": "Shall I cancel telegram (or message) number ... ? / Cancel telegram (or message) number ...", "QSX": "Will you listen to ... (call sign(s)) on ... kHz (or MHz)? --or-- Will you listen to ... (call sign(s)) on ... kHz (or MHz), or in the bands ... / channels ... ? / I am listening to ... (call sign(s)) on ... kHz (or MHz). --or-- I am listening to ... (call sign(s)) on ... kHz (or MHz), or in the bands ... / channels ...",
"QTB": "Do you agree with my counting of words? / I do not agree with your counting of words; I will repeat the first letter or digit of each word or group.", "QSY": "Shall I change to transmission on another frequency? / Change to transmission on another frequency (or on ... kHz (or MHz)).",
"QTC": "How many telegrams have you to send? / I have ... telegrams for you (or for ...).", "QSZ": "Shall I send each word or group more than once? / Send each word or group twice (or ... times).",
"QTD": "What has the rescue vessel or rescue aircraft recovered? / ... (identification) has recovered ... survivors/wreckage/bodies.", "QTA": "Shall I cancel telegram (or message) number ... ? / Cancel telegram (or message) number ...",
"QTE": "What is my TRUE bearing from you? --or-- What is my TRUE bearing from ... (call sign)? --or-- What is the TRUE bearing of ... (call sign) from ... (call sign)? / Your TRUE bearing from me is ... degrees at ... hours. --or-- Your TRUE bearing from ... (call sign) was ... degrees at ... hours. --or-- The TRUE bearing of ... (call sign) from ... (call sign) was ... degrees at ... hours.", "QTB": "Do you agree with my counting of words? / I do not agree with your counting of words; I will repeat the first letter or digit of each word or group.",
"QTF": "Will you give me the position of my station according to the bearings taken by the direction-finding stations which you control? / The position of your station according to the bearings taken by the D/F stations which I control was ... latitude, ... longitude (or other indication of position), class ... at ... hours.", "QTC": "How many telegrams have you to send? / I have ... telegrams for you (or for ...).",
"QTG": "Will you send two dashes of ten seconds each followed by your call sign (repeated ... times) (on ... kHz (or MHz))? --or-- Will you request ... to send two dashes of ten seconds followed by his call sign (repeated ... times) on ... kHz (or MHz)? / I am going to send two dashes of ten seconds each followed by my call sign (repeated ... times) (on ... kHz (or MHz)). --or-- I have requested ... to send two dashes of ten seconds followed by his call sign (repeated ... times) on ... kHz (or MHz).", "QTD": "What has the rescue vessel or rescue aircraft recovered? / ... (identification) has recovered ... survivors/wreckage/bodies.",
"QTHR": "At the registered location; Chiefly British in voice or writing, Historically - the location in the printed Callbook. Modernly - as given in online government records for my callsign", "QTE": "What is my TRUE bearing from you? --or-- What is my TRUE bearing from ... (call sign)? --or-- What is the TRUE bearing of ... (call sign) from ... (call sign)? / Your TRUE bearing from me is ... degrees at ... hours. --or-- Your TRUE bearing from ... (call sign) was ... degrees at ... hours. --or-- The TRUE bearing of ... (call sign) from ... (call sign) was ... degrees at ... hours.",
"QTH": "What is your position in latitude and longitude (or according to any other indication)? / My position is ... latitude, ... longitude (or according to any other indication).", "QTF": "Will you give me the position of my station according to the bearings taken by the direction-finding stations which you control? / The position of your station according to the bearings taken by the D/F stations which I control was ... latitude, ... longitude (or other indication of position), class ... at ... hours.",
"QTI": "What is your TRUE course? / My TRUE course is ... degrees.", "QTG": "Will you send two dashes of ten seconds each followed by your call sign (repeated ... times) (on ... kHz (or MHz))? --or-- Will you request ... to send two dashes of ten seconds followed by his call sign (repeated ... times) on ... kHz (or MHz)? / I am going to send two dashes of ten seconds each followed by my call sign (repeated ... times) (on ... kHz (or MHz)). --or-- I have requested ... to send two dashes of ten seconds followed by his call sign (repeated ... times) on ... kHz (or MHz).",
"QTJ": "What is your speed? / My speed is ... knots (or ... kilometres per hour or ... statute miles per hour).", "QTHR": "At the registered location; Chiefly British in voice or writing, Historically - the location in the printed Callbook. Modernly - as given in online government records for my callsign",
"QTK": "What is the speed of your aircraft in relation to the surface of the Earth? / The speed of my aircraft in relation to the surface of the Earth is ... knots (or ... kilometres per hour or ... statute miles per hour).", "QTH": "What is your position in latitude and longitude (or according to any other indication)? / My position is ... latitude, ... longitude (or according to any other indication).",
"QTL": "What is your TRUE heading? / My TRUE heading is ... degrees.", "QTI": "What is your TRUE course? / My TRUE course is ... degrees.",
"QTM": "What is your MAGNETIC heading? / My MAGNETIC heading is ... degrees.", "QTJ": "What is your speed? / My speed is ... knots (or ... kilometres per hour or ... statute miles per hour).",
"QTN": "At what time did you depart from ... (place)? / I departed from ... (place) at ... hours.", "QTK": "What is the speed of your aircraft in relation to the surface of the Earth? / The speed of my aircraft in relation to the surface of the Earth is ... knots (or ... kilometres per hour or ... statute miles per hour).",
"QTO": "Have you left dock (or port)? --or-- Are you airborne? / I have left dock (or port). --or-- I am airborne.", "QTL": "What is your TRUE heading? / My TRUE heading is ... degrees.",
"QTP": "Are you going to enter dock (or port)? --or-- Are you going to alight (or land)? / I am going to enter dock (or port). --or-- I am going to alight (or land).", "QTM": "What is your MAGNETIC heading? / My MAGNETIC heading is ... degrees.",
"QTQ": "Can you communicate with my station by means of the International Code of Signals (INTERCO)? / I am going to communicate with your station by means of the International Code of Signals (INTERCO).", "QTN": "At what time did you depart from ... (place)? / I departed from ... (place) at ... hours.",
"QTR": "What is the correct time? / The correct time is ... hours.", "QTO": "Have you left dock (or port)? --or-- Are you airborne? / I have left dock (or port). --or-- I am airborne.",
"QTS": "Will you send your call sign (and/or name) for ... seconds? / I will send my call sign (and/or name) for ... seconds.", "QTP": "Are you going to enter dock (or port)? --or-- Are you going to alight (or land)? / I am going to enter dock (or port). --or-- I am going to alight (or land).",
"QTT": "The identification signal which follows is superimposed on another transmission.", "QTQ": "Can you communicate with my station by means of the International Code of Signals (INTERCO)? / I am going to communicate with your station by means of the International Code of Signals (INTERCO).",
"QTU": "What are the hours during which your station is open? / My station is open from ... to ... hours.", "QTR": "What is the correct time? / The correct time is ... hours.",
"QTV": "Shall I stand guard for you on the frequency of ... kHz (or MHz) (from ... to ... hours)? / Stand guard for me on the frequency of ... kHz (or MHz) (from ... to ... hours).", "QTS": "Will you send your call sign (and/or name) for ... seconds? / I will send my call sign (and/or name) for ... seconds.",
"QTW": "What is the condition of survivors? / Survivors are in ... condition and urgently need ...", "QTT": "The identification signal which follows is superimposed on another transmission.",
"QTX": "Will you keep your station open for further communication with me until further notice (or until ... hours)? / I will keep my station open for further communication with you until further notice (or until ... hours).", "QTU": "What are the hours during which your station is open? / My station is open from ... to ... hours.",
"QTY": "Are you proceeding to the position of incident and if so when do you expect to arrive? / I am proceeding to the position of incident and expect to arrive at ... hours (on ... (date)).", "QTV": "Shall I stand guard for you on the frequency of ... kHz (or MHz) (from ... to ... hours)? / Stand guard for me on the frequency of ... kHz (or MHz) (from ... to ... hours).",
"QTZ": "Are you continuing the search? / I am continuing the search for ... (aircraft, ship, survival craft, survivors or wreckage).", "QTW": "What is the condition of survivors? / Survivors are in ... condition and urgently need ...",
"QUA": "Have you news of ... (call sign)? / Here is news of ... (call sign).", "QTX": "Will you keep your station open for further communication with me until further notice (or until ... hours)? / I will keep my station open for further communication with you until further notice (or until ... hours).",
"QUB": "Can you give me in the following order information concerning: the direction in degrees TRUE and speed of the surface wind; visibility; present weather; and amount, type and height of base of cloud above surface elevation at ... (place of observation)? / Here is the information requested: ... (The units used for speed and distances should be indicated.)", "QTY": "Are you proceeding to the position of incident and if so when do you expect to arrive? / I am proceeding to the position of incident and expect to arrive at ... hours (on ... (date)).",
"QUC": "What is the number (or other indication) of the last message you received from me (or from ... (call sign))? / The number (or other indication) of the last message I received from you (or from ... (call sign)) is ...", "QTZ": "Are you continuing the search? / I am continuing the search for ... (aircraft, ship, survival craft, survivors or wreckage).",
"QUD": "Have you received the urgency signal sent by ... (call sign of mobile station)? / I have received the urgency signal sent by ... (call sign of mobile station) at ... hours.", "QUA": "Have you news of ... (call sign)? / Here is news of ... (call sign).",
"QUE": "Can you speak in ... (language), - with interpreter if necessary; if so, on what frequencies? / I can speak in ... (language) on ... kHz (or MHz).", "QUB": "Can you give me in the following order information concerning: the direction in degrees TRUE and speed of the surface wind; visibility; present weather; and amount, type and height of base of cloud above surface elevation at ... (place of observation)? / Here is the information requested: ... (The units used for speed and distances should be indicated.)",
"QUF": "Have you received the distress signal sent by ... (call sign of mobile station)? / I have received the distress signal sent by ... (call sign of mobile station) at ... hours.", "QUC": "What is the number (or other indication) of the last message you received from me (or from ... (call sign))? / The number (or other indication) of the last message I received from you (or from ... (call sign)) is ...",
"QUG": "Will you be forced to alight (or land)? / I am forced to alight (or land) immediately. --or-- I shall be forced to alight (or land) at ... (position or place) at ... hours.", "QUD": "Have you received the urgency signal sent by ... (call sign of mobile station)? / I have received the urgency signal sent by ... (call sign of mobile station) at ... hours.",
"QUH": "Will you give me the present barometric pressure at sea level? / The present barometric pressure at sea level is ... (units).", "QUE": "Can you speak in ... (language), - with interpreter if necessary; if so, on what frequencies? / I can speak in ... (language) on ... kHz (or MHz).",
"QUI": "Are your navigation lights working? / My navigation lights are working.", "QUF": "Have you received the distress signal sent by ... (call sign of mobile station)? / I have received the distress signal sent by ... (call sign of mobile station) at ... hours.",
"QUJ": "Will you indicate the TRUE track to reach you (or ...)? / The TRUE track to reach me (or ...) is ... degrees at ... hours.", "QUG": "Will you be forced to alight (or land)? / I am forced to alight (or land) immediately. --or-- I shall be forced to alight (or land) at ... (position or place) at ... hours.",
"QUK": "Can you tell me the condition of the sea observed at ... (place or coordinates)? / The sea at ... (place or coordinates) is ...", "QUH": "Will you give me the present barometric pressure at sea level? / The present barometric pressure at sea level is ... (units).",
"QUL": "Can you tell me the swell observed at ... (place or coordinates)? / The swell at ... (place or coordinates) is ...", "QUI": "Are your navigation lights working? / My navigation lights are working.",
"QUM": "May I resume normal working? / Normal working may be resumed.", "QUJ": "Will you indicate the TRUE track to reach you (or ...)? / The TRUE track to reach me (or ...) is ... degrees at ... hours.",
"QUN": "When directed to all stations: Will vessels in my immediate vicinity ... (or in the vicinity of ... latitude, ... longitude) please indicate their position, TRUE course and speed? --or-- When directed to a single station: please indicate their position, TRUE course and speed? / My position, TRUE course and speed are ...", "QUK": "Can you tell me the condition of the sea observed at ... (place or coordinates)? / The sea at ... (place or coordinates) is ...",
"QUO": "Shall I search for aircraft/ship/survival craft in the vicinity of ... latitude, ... longitude (or according to any other indication)? / Please search for aircraft/ship/survival craft in the vicinity of ... latitude, ... longitude (or according to any other indication).", "QUL": "Can you tell me the swell observed at ... (place or coordinates)? / The swell at ... (place or coordinates) is ...",
"QUP": "Will you indicate your position by searchlight/black smoke trail/pyrotechnic lights? / My position is indicated by searchlight/black smoke trail/pyrotechnic lights.", "QUM": "May I resume normal working? / Normal working may be resumed.",
"QUQ": "Shall I train my searchlight nearly vertical on a cloud, occulting if possible and, if your aircraft is seen, deflect the beam up wind and on the water (or land) to facilitate your landing? / Please train your searchlight on a cloud, occulting if possible and, if my aircraft is seen or heard, deflect the beam up wind and on the water (or land) to facilitate my landing.", "QUN": "When directed to all stations: Will vessels in my immediate vicinity ... (or in the vicinity of ... latitude, ... longitude) please indicate their position, TRUE course and speed? --or-- When directed to a single station: please indicate their position, TRUE course and speed? / My position, TRUE course and speed are ...",
"QUR": "Have survivors received survival equipment/been picked up by rescue vessel/been reached by ground rescue party? / Survivors are in possession of survival equipment dropped by ... / have been picked up by rescue vessel/have been reached by ground rescue party.", "QUO": "Shall I search for aircraft/ship/survival craft in the vicinity of ... latitude, ... longitude (or according to any other indication)? / Please search for aircraft/ship/survival craft in the vicinity of ... latitude, ... longitude (or according to any other indication).",
"QUS": "Have you sighted survivors or wreckage? / If so, in what position? / Have sighted ... survivors in water/survivors on rafts/wreckage in position ... latitude, ... longitude (or according to any other indication).", "QUP": "Will you indicate your position by searchlight/black smoke trail/pyrotechnic lights? / My position is indicated by searchlight/black smoke trail/pyrotechnic lights.",
"QUT": "Is position of incident marked? / Position of incident is marked by flame or smoke float/sea marker/sea marker dye/... (specify other marking).", "QUQ": "Shall I train my searchlight nearly vertical on a cloud, occulting if possible and, if your aircraft is seen, deflect the beam up wind and on the water (or land) to facilitate your landing? / Please train your searchlight on a cloud, occulting if possible and, if my aircraft is seen or heard, deflect the beam up wind and on the water (or land) to facilitate my landing.",
"QUU": "Shall I home ship or aircraft to my position? / Home ship or aircraft ... (call sign) ... to your position by transmitting your call sign and long dashes on ... kHz (or MHz)/by transmitting on ... kHz (or MHz) TRUE track to reach you.", "QUR": "Have survivors received survival equipment/been picked up by rescue vessel/been reached by ground rescue party? / Survivors are in possession of survival equipment dropped by ... / have been picked up by rescue vessel/have been reached by ground rescue party.",
"QUW": "Are you in the search area designated as ... (designator or latitude and longitude)? / I am in the ... (designation) search area.", "QUS": "Have you sighted survivors or wreckage? / If so, in what position? / Have sighted ... survivors in water/survivors on rafts/wreckage in position ... latitude, ... longitude (or according to any other indication).",
"QUX": "Do you have any navigational warnings or gale warnings in force? / I have the following navigational warning(s) or gale warning(s) in force: ...", "QUT": "Is position of incident marked? / Position of incident is marked by flame or smoke float/sea marker/sea marker dye/... (specify other marking).",
"QUY": "Is position of survival craft marked? / Position of survival craft was marked at ... hours by flame or smoke float/sea marker/sea marker dye/... (specify other marking).", "QUU": "Shall I home ship or aircraft to my position? / Home ship or aircraft ... (call sign) ... to your position by transmitting your call sign and long dashes on ... kHz (or MHz)/by transmitting on ... kHz (or MHz) TRUE track to reach you.",
"QUZ": "May I resume restricted working? / Distress phase still in force; restricted working may be resumed.", "QUW": "Are you in the search area designated as ... (designator or latitude and longitude)? / I am in the ... (designation) search area.",
"QZZ": "Daily key change about to take place (German WWII usage)."} "QUX": "Do you have any navigational warnings or gale warnings in force? / I have the following navigational warning(s) or gale warning(s) in force: ...",
"QUY": "Is position of survival craft marked? / Position of survival craft was marked at ... hours by flame or smoke float/sea marker/sea marker dye/... (specify other marking).",
"QUZ": "May I resume restricted working? / Distress phase still in force; restricted working may be resumed.",
"QZZ": "Daily key change about to take place (German WWII usage)."
}

View File

@ -3,47 +3,58 @@ A listing of hamstudy command resources
--- ---
Copyright (C) 2019-2020 Abigail Gold, 0x5c Copyright (C) 2019-2020 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrmbot and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """
pool_names = {'us': {'technician': 'E2',
'tech': 'E2',
't': 'E2',
'general': 'E3',
'gen': 'E3',
'g': 'E3',
'extra': 'E4',
'e': 'E4'},
'ca': {'basic': 'CA_B',
'b': 'CA_B',
'advanced': 'CA_A',
'adv': 'CA_A',
'a': 'CA_A',
'basic_fr': 'CA_FB',
'b_fr': 'CA_FB',
'base': 'CA_FB',
'advanced_fr': 'CA_FS',
'adv_fr': 'CA_FS',
'a_fr': 'CA_FS',
'supérieure': 'CA_FS',
'superieure': 'CA_FS',
's': 'CA_FS'},
'us_c': {'c1': 'C1',
'comm1': 'C1',
'c3': 'C3',
'comm3': 'C3',
'c6': 'C6',
'comm6': 'C6',
'c7': 'C7',
'comm7': 'C7',
'c7r': 'C7R',
'comm7r': 'C7R',
'c8': 'C8',
'comm8': 'C8',
'c9': 'C9',
'comm9': 'C9'}}
pool_emojis = {'us': '🇺🇸', pool_names = {
'ca': '🇨🇦', "us": {
'us_c': '🇺🇸 🏢'} "technician": "E2",
"tech": "E2",
"t": "E2",
"general": "E3",
"gen": "E3",
"g": "E3",
"extra": "E4",
"e": "E4",
},
"ca": {
"basic": "CA_B",
"b": "CA_B",
"advanced": "CA_A",
"adv": "CA_A",
"a": "CA_A",
"basic_fr": "CA_FB",
"b_fr": "CA_FB",
"base": "CA_FB",
"advanced_fr": "CA_FS",
"adv_fr": "CA_FS",
"a_fr": "CA_FS",
"supérieure": "CA_FS",
"superieure": "CA_FS",
"s": "CA_FS",
},
"us_c": {
"c1": "C1",
"comm1": "C1",
"c3": "C3",
"comm3": "C3",
"c6": "C6",
"comm6": "C6",
"c7": "C7",
"comm7": "C7",
"c7r": "C7R",
"comm7r": "C7R",
"c8": "C8",
"comm8": "C8",
"c9": "C9",
"comm9": "C9",
},
}
pool_emojis = {
"us": "🇺🇸",
"ca": "🇨🇦",
"us_c": "🇺🇸 🏢",
}

View File

@ -27,7 +27,7 @@ debug = False
owners_uids = (200102491231092736,) owners_uids = (200102491231092736,)
# The extensions to load when running the bot. # The extensions to load when running the bot.
exts = ['ae7q', 'base', 'fun', 'grid', 'ham', 'image', 'lookup', 'morse', 'qrz', 'study', 'weather'] exts = ["ae7q", "base", "fun", "grid", "ham", "image", "lookup", "morse", "qrz", "study", "weather"]
# Either "time", "random", or "fixed" (first item in statuses) # Either "time", "random", or "fixed" (first item in statuses)
status_mode = "fixed" status_mode = "fixed"
@ -37,17 +37,17 @@ statuses = ["with lids on the air", "with fire"]
# Timezone for the status (string) # Timezone for the status (string)
# See https://pythonhosted.org/pytz/ for more info # See https://pythonhosted.org/pytz/ for more info
status_tz = 'US/Eastern' status_tz = "US/Eastern"
# The text to put in the "playing" status, with start and stop times # The text to put in the "playing" status, with start and stop times
time_statuses = [('with lids on 3.840', (00, 00), (6, 00)), time_statuses = [("with lids on 3.840", (00, 00), (6, 00)),
('with lids on 7.200', (6, 00), (10, 00)), ("with lids on 7.200", (6, 00), (10, 00)),
('with lids on 14.313', (10, 00), (18, 00)), ("with lids on 14.313", (10, 00), (18, 00)),
('with lids on 7.200', (18, 00), (20, 00)), ("with lids on 7.200", (18, 00), (20, 00)),
('with lids on 3.840', (20, 00), (23, 59))] ("with lids on 3.840", (20, 00), (23, 59))]
# Emoji IDs and keywords for emoji reactions # Emoji IDs and keywords for emoji reactions
# Use the format {emoji_id (int): ('tuple', 'of', 'lowercase', 'keywords')} # Use the format {emoji_id (int): ("tuple", "of", "lowercase", "keywords")}
msg_reacts = {} msg_reacts = {}
# A :pika: emote's ID, None for no emote :c # A :pika: emote's ID, None for no emote :c

View File

@ -3,8 +3,8 @@ Wrapper to handle aiohttp connector creation.
--- ---
Copyright (C) 2020 Abigail Gold, 0x5c Copyright (C) 2020 Abigail Gold, 0x5c
This file is part of discord-qrm2 and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of
General Public License, version 2. the GNU General Public License, version 2.
""" """