mirror of
https://github.com/miaowware/qrm2.git
synced 2025-06-17 05:22:25 -04:00
Merge pull request #199 from classabbyamp/5c-newyearlint
New year linting
This commit is contained in:
commit
27c290cff8
@ -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.
|
||||||
|
59
common.py
59
common.py
@ -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(
|
||||||
|
good=0x43B581,
|
||||||
neutral=0x7289DA,
|
neutral=0x7289DA,
|
||||||
bad=0xF04747)
|
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(
|
||||||
|
data=Path("./data/"),
|
||||||
resources=Path("./resources/"),
|
resources=Path("./resources/"),
|
||||||
bandcharts=Path("./resources/img/bandcharts/"),
|
bandcharts=Path("./resources/img/bandcharts/"),
|
||||||
maps=Path("./resources/img/maps/"))
|
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
|
||||||
|
|
||||||
|
178
exts/ae7q.py
178
exts/ae7q.py
@ -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
|
||||||
|
98
exts/base.py
98
exts/base.py
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
31
exts/fun.py
31
exts/fun.py
@ -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)
|
||||||
|
77
exts/grid.py
77
exts/grid.py
@ -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)
|
||||||
|
35
exts/ham.py
35
exts/ham.py
@ -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]
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
157
exts/qrz.py
157
exts/qrz.py
@ -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):
|
||||||
|
@ -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:
|
||||||
|
@ -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,25 +26,25 @@ 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: `Москва`
|
||||||
@ -51,71 +52,72 @@ class WeatherCog(commands.Cog):
|
|||||||
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
22
info.py
@ -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
29
main.py
@ -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:
|
||||||
|
@ -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)}
|
||||||
|
@ -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",
|
||||||
|
".-.-.-": ".",
|
||||||
|
"--..--": ",",
|
||||||
|
"..--..": "?",
|
||||||
|
".----.": "'",
|
||||||
|
"-.-.--": "!",
|
||||||
|
"-..-.": "/",
|
||||||
|
"-.--.": "(",
|
||||||
|
"-.--.-": ")",
|
||||||
|
".-...": "&",
|
||||||
|
"---...": ":",
|
||||||
|
"-.-.-.": ";",
|
||||||
|
"-...-": "=",
|
||||||
|
".-.-.": "+",
|
||||||
|
"-....-": "-",
|
||||||
|
".-..-.": "\"",
|
||||||
|
".--.-.": "@",
|
||||||
|
".-.-": "Ä",
|
||||||
|
"..-..": "É",
|
||||||
|
"--.--": "Ñ",
|
||||||
|
"---.": "Ö",
|
||||||
|
"..--": "Ü",
|
||||||
|
"----": "Š",
|
||||||
|
"/": " "
|
||||||
|
}
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
|
@ -3,11 +3,16 @@ 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 ...",
|
|
||||||
|
|
||||||
|
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 ...",
|
"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.",
|
"QAG": "Arrange your flight in order to arrive over ... (place) at ... hours.",
|
||||||
"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).",
|
"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).",
|
||||||
@ -277,4 +282,5 @@ qcodes = {"QAB": "May I have clearance (for ...) from ... (place) to ... (place)
|
|||||||
"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: ...",
|
"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).",
|
"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.",
|
"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)."}
|
"QZZ": "Daily key change about to take place (German WWII usage)."
|
||||||
|
}
|
||||||
|
@ -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": "🇺🇸 🏢",
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user