Custom Help Command (#55)

* subclasses HelpCommand to implement a custom help command.

add cog names for display in the help command.

Globalsettings is no longer a cog. HelpCommand uses the custom attribute
category for grouping commands.

* PEP8 fixes

* improve variable names for help command clarity. Fixes #70

* improve help command formatting

add aliases for weather commands
add help text for ae7q call and qrz

* move global_settings to common

* rename import alias

* fix loading/unloading of help command

* fix info command

* fix options import

* changed canonical command names to be more descriptive

* remove cog names and revert map/bandplan error listing

* add link to hamstudy references
This commit is contained in:
Abigail Gold 2019-12-06 01:19:42 -05:00 committed by GitHub
parent f821a2e2f2
commit ad2c559a0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 257 additions and 140 deletions

View File

@ -21,20 +21,22 @@ import discord.ext.commands as commands
from bs4 import BeautifulSoup
import aiohttp
import common as cmn
class AE7QCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.gs = bot.get_cog("GlobalSettings")
@commands.group(name="ae7q", aliases=["ae"])
@commands.group(name="ae7q", aliases=["ae"], category=cmn.cat.lookup)
async def _ae7q_lookup(self, ctx: commands.Context):
'''Look up a callsign, FRN, or Licensee ID on ae7q.com'''
'''Look up a callsign, FRN, or Licensee ID on [ae7q.com](http://ae7q.com/).'''
if ctx.invoked_subcommand is None:
await ctx.send_help(ctx.command)
@_ae7q_lookup.command(name="call")
@_ae7q_lookup.command(name="call", category=cmn.cat.lookup)
async def _ae7q_call(self, ctx: commands.Context, callsign: str):
'''Look up the history for a callsign on [ae7q.com](http://ae7q.com/).'''
callsign = callsign.upper()
desc = ''
base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
@ -61,7 +63,7 @@ class AE7QCog(commands.Cog):
if rows is None:
embed = discord.Embed(title=f"AE7Q History for {callsign}",
colour=self.gs.colours.bad,
colour=cmn.colours.bad,
url=f"{base_url}{callsign}",
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
@ -91,7 +93,7 @@ class AE7QCog(commands.Cog):
table_contents += [row_cells]
embed = discord.Embed(title=f"AE7Q Records for {callsign}",
colour=self.gs.colours.good,
colour=cmn.colours.good,
url=f"{base_url}{callsign}",
timestamp=datetime.utcnow())
@ -118,20 +120,20 @@ class AE7QCog(commands.Cog):
# TODO: write commands for other AE7Q response types?
# @_ae7q_lookup.command(name="trustee")
# async def _ae7q_trustee(self, ctx, callsign: str):
# async def _ae7q_trustee(self, ctx: commands.Context, callsign: str):
# pass
# @_ae7q_lookup.command(name="applications", aliases=['apps'])
# async def _ae7q_applications(self, ctx, callsign: str):
# async def _ae7q_applications(self, ctx: commands.Context, callsign: str):
# pass
# @_ae7q_lookup.command(name="frn")
# async def _ae7q_frn(self, ctx, frn: str):
# async def _ae7q_frn(self, ctx: commands.Context, frn: str):
# base_url = "http://ae7q.com/query/data/FrnHistory.php?FRN="
# pass
# @_ae7q_lookup.command(name="licensee", aliases=["lic"])
# async def _ae7q_licensee(self, ctx, frn: str):
# async def _ae7q_licensee(self, ctx: commands.Context, frn: str):
# base_url = "http://ae7q.com/query/data/LicenseeIdHistory.php?ID="
# pass

View File

@ -15,26 +15,114 @@ import random
import discord
import discord.ext.commands as commands
import info
from data import options as opt
import common as cmn
class QrmHelpCommand(commands.HelpCommand):
def __init__(self):
super().__init__(command_attrs={'help': 'Shows help about qrm or a command',
'aliases': ['h']})
def get_bot_mapping(self):
bot = self.context.bot
mapping = {}
for cmd in bot.commands:
cat = cmd.__original_kwargs__.get('category', None)
if cat in mapping:
mapping[cat].append(cmd)
else:
mapping[cat] = [cmd]
return mapping
def get_command_signature(self, command):
parent = command.full_parent_name
if command.aliases != []:
aliases = ', '.join(command.aliases)
fmt = command.name
if parent:
fmt = f'{parent} {fmt}'
alias = fmt
return f'{opt.prefix}{alias} {command.signature}\n *Aliases:* {aliases}'
alias = command.name if not parent else f'{parent} {command.name}'
return f'{opt.prefix}{alias} {command.signature}'
async def send_error_message(self, error):
embed = discord.Embed(title='qrm Help Error',
description=error,
colour=cmn.colours.bad,
timestamp=datetime.utcnow()
)
embed.set_footer(text=self.context.author.name,
icon_url=str(self.context.author.avatar_url))
await self.context.send(embed=embed)
async def send_bot_help(self, mapping):
embed = discord.Embed(title='qrm Help',
description=(f'For command-specific help and usage, use `{opt.prefix}help [command name]`'
'. Many commands have shorter aliases.'),
colour=cmn.colours.neutral,
timestamp=datetime.utcnow()
)
embed.set_footer(text=self.context.author.name,
icon_url=str(self.context.author.avatar_url))
for cat, cmds in mapping.items():
cmds = list(filter(lambda x: not x.hidden, cmds))
if cmds == []:
continue
names = sorted([cmd.name for cmd in cmds])
if cat is not None:
embed.add_field(name=cat.title(), value=', '.join(names), inline=False)
else:
embed.add_field(name='Other', value=', '.join(names), inline=False)
await self.context.send(embed=embed)
async def send_command_help(self, command):
embed = discord.Embed(title=self.get_command_signature(command),
description=command.help,
colour=cmn.colours.neutral,
timestamp=datetime.utcnow()
)
embed.set_footer(text=self.context.author.name,
icon_url=str(self.context.author.avatar_url))
await self.context.send(embed=embed)
async def send_group_help(self, group):
embed = discord.Embed(title=self.get_command_signature(group),
description=group.help,
colour=cmn.colours.neutral,
timestamp=datetime.utcnow()
)
embed.set_footer(text=self.context.author.name,
icon_url=str(self.context.author.avatar_url))
for cmd in group.commands:
embed.add_field(name=self.get_command_signature(cmd), value=cmd.help, inline=False)
await self.context.send(embed=embed)
class BaseCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.gs = bot.get_cog("GlobalSettings")
self.changelog = parse_changelog()
@commands.command(name="info", aliases=["about"])
async def _info(self, ctx):
async def _info(self, ctx: commands.Context):
"""Shows info about qrm."""
embed = discord.Embed(title="About qrm",
description=self.gs.info.description,
colour=self.gs.colours.neutral,
description=info.description,
colour=cmn.colours.neutral,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
embed = embed.add_field(name="Authors", value=", ".join(info.authors))
embed = embed.add_field(name="License", value=info.license)
embed = embed.add_field(name="Version", value=f'v{info.release}')
embed = embed.add_field(name="Contributing", value=info.contributing, inline=False)
embed.set_thumbnail(url=str(self.bot.user.avatar_url))
embed = embed.add_field(name="Authors", value=", ".join(self.gs.info.authors))
embed = embed.add_field(name="Contributing", value=self.gs.info.contributing)
embed = embed.add_field(name="License", value=self.gs.info.license)
await ctx.send(embed=embed)
@commands.command(name="ping")
@ -43,7 +131,7 @@ class BaseCog(commands.Cog):
content = ctx.message.author.mention if random.random() < 0.05 else ''
embed = discord.Embed(title="**Pong!**",
description=f'Current ping is {self.bot.latency*1000:.1f} ms',
colour=self.gs.colours.neutral,
colour=cmn.colours.neutral,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
@ -55,7 +143,7 @@ class BaseCog(commands.Cog):
embed = discord.Embed(title="qrm Changelog",
description=("For a full listing, visit [Github](https://"
"github.com/classabbyamp/discord-qrm-bot/blob/master/CHANGELOG.md)."),
colour=self.gs.colours.neutral,
colour=cmn.colours.neutral,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
@ -111,3 +199,9 @@ async def format_changelog(log: dict):
def setup(bot: commands.Bot):
bot.add_cog(BaseCog(bot))
bot._original_help_command = bot.help_command
bot.help_command = QrmHelpCommand()
def teardown(bot: commands.Bot):
bot.help_command = bot._original_help_command

View File

@ -9,23 +9,24 @@ General Public License, version 2.
import discord.ext.commands as commands
import common as cmn
class FunCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.gs = bot.get_cog("GlobalSettings")
@commands.command(name="xkcd", aliases=['x'])
async def _xkcd(self, ctx: commands.Context, num: str):
@commands.command(name="xkcd", aliases=['x'], category=cmn.cat.fun)
async def _xkcd(self, ctx: commands.Context, number: str):
'''Look up an xkcd by number.'''
await ctx.send('http://xkcd.com/' + num)
await ctx.send('http://xkcd.com/' + number)
@commands.command(name="tar")
@commands.command(name="tar", category=cmn.cat.fun)
async def _tar(self, ctx: commands.Context):
'''Returns an xkcd about tar.'''
await ctx.send('http://xkcd.com/1168')
@commands.command(name="xd")
@commands.command(name="xd", hidden=True, category=cmn.cat.fun)
async def _xd(self, ctx: commands.Context):
'''ecks dee'''
await ctx.send('ECKS DEE :smirk:')

View File

@ -13,17 +13,17 @@ from datetime import datetime
import discord
import discord.ext.commands as commands
import common as cmn
class GridCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.gs = bot.get_cog("GlobalSettings")
@commands.command(name="grid")
@commands.command(name="grid", category=cmn.cat.maps)
async def _grid_sq_lookup(self, ctx: commands.Context, lat: str, lon: str):
'''Calculates the grid square for latitude and longitude coordinates.
Usage: `?grid <lat> <lon>`
`lat` and `lon` are decimal coordinates, with negative being latitude South and longitude West.'''
'''Calculates the grid square for latitude and longitude coordinates,
with negative being latitude South and longitude West.'''
with ctx.typing():
grid = "**"
try:
@ -39,7 +39,7 @@ class GridCog(commands.Cog):
grid += "**"
embed = discord.Embed(title=f'Maidenhead Grid Locator for {float(lat):.6f}, {float(lon):.6f}',
description=grid,
colour=self.gs.colours.good,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
@ -48,16 +48,16 @@ class GridCog(commands.Cog):
except ValueError as err:
msg = f'Error generating grid square for {lat}, {lon}.'
embed = discord.Embed(title=msg, description=str(err),
colour=self.gs.colours.bad,
colour=cmn.colours.bad,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
await ctx.send(embed=embed)
@commands.command(name="ungrid", aliases=['loc'])
@commands.command(name="ungrid", aliases=['loc'], category=cmn.cat.maps)
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.
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():
if grid2 is None or grid2 == '':
try:
@ -67,14 +67,14 @@ class GridCog(commands.Cog):
if len(grid) >= 6:
embed = discord.Embed(title=f'Latitude and Longitude for {grid}',
description=f'**{loc[0]:.5f}, {loc[1]:.5f}**',
colour=self.gs.colours.good,
colour=cmn.colours.good,
url=f'https://www.openstreetmap.org/#map=13/{loc[0]:.5f}/{loc[1]:.5f}',
timestamp=datetime.utcnow())
else:
embed = discord.Embed(title=f'Latitude and Longitude for {grid}',
description=f'**{loc[0]:.1f}, {loc[1]:.1f}**',
colour=self.gs.colours.good,
colour=cmn.colours.good,
url=f'https://www.openstreetmap.org/#map=10/{loc[0]:.1f}/{loc[1]:.1f}',
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
@ -82,7 +82,7 @@ class GridCog(commands.Cog):
except Exception as e:
msg = f'Error generating latitude and longitude for grid {grid}.'
embed = discord.Embed(title=msg, description=str(e),
colour=self.gs.colours.bad,
colour=cmn.colours.bad,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
@ -113,14 +113,14 @@ class GridCog(commands.Cog):
des = f'**Distance:** {d:.1f} km ({d_mi:.1f} mi)\n**Bearing:** {bearing:.1f}°'
embed = discord.Embed(title=f'Great Circle Distance and Bearing from {grid} to {grid2}',
description=des,
colour=self.gs.colours.good,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
except Exception as e:
msg = f'Error generating great circle distance and bearing from {grid} and {grid2}.'
embed = discord.Embed(title=msg, description=str(e),
colour=self.gs.colours.bad,
colour=cmn.colours.bad,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))

View File

@ -13,36 +13,36 @@ from datetime import datetime
import discord
import discord.ext.commands as commands
import common as cmn
from resources import callsign_info
class HamCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.gs = bot.get_cog("GlobalSettings")
with open('resources/qcodes.json') as qcode_file:
self.qcodes = json.load(qcode_file)
with open('resources/words') as words_file:
self.words = words_file.read().lower().splitlines()
@commands.command(name="qcode", aliases=['q'])
@commands.command(name="qcode", aliases=['q'], category=cmn.cat.ref)
async def _qcode_lookup(self, ctx: commands.Context, qcode: str):
'''Look up a Q Code.'''
with ctx.typing():
qcode = qcode.upper()
if qcode in self.qcodes:
embed = discord.Embed(title=qcode, description=self.qcodes[qcode],
colour=self.gs.colours.good,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
else:
embed = discord.Embed(title=f'Q Code {qcode} not found',
colour=self.gs.colours.bad,
colour=cmn.colours.bad,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
await ctx.send(embed=embed)
@commands.command(name="phonetics", aliases=['ph', 'phoneticize', 'phoneticise', 'phone'])
@commands.command(name="phonetics", aliases=['ph', 'phoneticize', 'phoneticise', 'phone'], category=cmn.cat.fun)
async def _phonetics_lookup(self, ctx: commands.Context, *, msg: str):
'''Get phonetics for a word or phrase.'''
with ctx.typing():
@ -55,13 +55,13 @@ class HamCog(commands.Cog):
result += ' '
embed = discord.Embed(title=f'Phonetics for {msg}',
description=result.title(),
colour=self.gs.colours.good,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
await ctx.send(embed=embed)
@commands.command(name="utc", aliases=['z'])
@commands.command(name="utc", aliases=['z'], category=cmn.cat.ref)
async def _utc_lookup(self, ctx: commands.Context):
'''Gets the current time in UTC.'''
with ctx.typing():
@ -69,13 +69,13 @@ class HamCog(commands.Cog):
result = '**' + now.strftime('%Y-%m-%d %H:%M') + 'Z**'
embed = discord.Embed(title='The current time is:',
description=result,
colour=self.gs.colours.good,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
await ctx.send(embed=embed)
@commands.command(name="vanities", aliases=["vanity", "pfx", "prefixes", "prefix"])
@commands.command(name="prefixes", aliases=["vanity", "pfx", "vanities", "prefix"])
async def _vanity_prefixes(self, ctx: commands.Context, country: str = None):
'''Lists valid prefixes for countries.'''
if country is None:

View File

@ -15,22 +15,22 @@ import discord.ext.commands as commands
import aiohttp
import common as cmn
class ImageCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.gs = bot.get_cog("GlobalSettings")
@commands.command(name="plan", aliases=['bands'])
async def _bandplan(self, ctx: commands.Context, msg: str = ''):
'''Posts an image of Frequency Allocations.
Optional argument: `cn`, `ca`, `nl`, `us`, `mx`.'''
@commands.command(name="bandplan", aliases=['plan', 'bands'], category=cmn.cat.ref)
async def _bandplan(self, ctx: commands.Context, region: str = ''):
'''Posts an image of Frequency Allocations.'''
name = {'cn': 'Chinese',
'ca': 'Canadian',
'nl': 'Dutch',
'us': 'US',
'mx': 'Mexican'}
arg = msg.lower()
arg = region.lower()
with ctx.typing():
if arg not in name:
@ -39,7 +39,7 @@ class ImageCog(commands.Cog):
desc += f'`{abbrev}`: {title}\n'
embed = discord.Embed(title=f'Bandplan Not Found!',
description=desc,
colour=self.gs.colours.bad,
colour=cmn.colours.bad,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
@ -48,7 +48,7 @@ class ImageCog(commands.Cog):
img = discord.File(f"resources/images/bandchart/{arg}bandchart.png",
filename=f'{arg}bandchart.png')
embed = discord.Embed(title=f'{name[arg]} Amateur Radio Bands',
colour=self.gs.colours.good,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
embed.set_image(url=f'attachment://{arg}bandchart.png')
embed.set_footer(text=ctx.author.name,
@ -56,20 +56,20 @@ class ImageCog(commands.Cog):
await ctx.send(embed=embed, file=img)
@commands.command(name="grayline", aliases=['greyline', 'grey', 'gray', 'gl'])
@commands.command(name="grayline", aliases=['greyline', 'grey', 'gray', 'gl'], category=cmn.cat.maps)
async def _grayline(self, ctx: commands.Context):
'''Posts a map of the current greyline, where HF propagation is the best.'''
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=')
with ctx.typing():
embed = discord.Embed(title='Current Greyline Conditions',
colour=self.gs.colours.good,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
async with aiohttp.ClientSession() as session:
async with session.get(gl_url) as resp:
if resp.status != 200:
embed.description = 'Could not download file...'
embed.colour = self.gs.colours.bad
embed.colour = cmn.colours.bad
else:
data = io.BytesIO(await resp.read())
embed.set_image(url=f'attachment://greyline.jpg')
@ -77,11 +77,9 @@ class ImageCog(commands.Cog):
icon_url=str(ctx.author.avatar_url))
await ctx.send(embed=embed, file=discord.File(data, 'greyline.jpg'))
@commands.command(name="map")
async def _map(self, ctx: commands.Context, msg: str = ''):
'''Posts an image of Frequency Allocations.
Optional argument:`cq` = CQ Zones, `itu` = ITU Zones, `arrl` or `rac` =
ARRL/RAC sections, `cn` = Chinese Callsign Areas, `us` = US Callsign Areas.'''
@commands.command(name="map", category=cmn.cat.maps)
async def _map(self, ctx: commands.Context, map_id: str = ''):
'''Posts an image of a ham-relevant map.'''
map_titles = {"cq": 'Worldwide CQ Zones Map',
"itu": 'Worldwide ITU Zones Map',
"arrl": 'ARRL/RAC Section Map',
@ -89,7 +87,7 @@ class ImageCog(commands.Cog):
"cn": 'Chinese Callsign Areas',
"us": 'US Callsign Areas'}
arg = msg.lower()
arg = map_id.lower()
with ctx.typing():
if arg not in map_titles:
desc = 'Possible arguments are:\n'
@ -97,7 +95,7 @@ class ImageCog(commands.Cog):
desc += f'`{abbrev}`: {title}\n'
embed = discord.Embed(title=f'Map Not Found!',
description=desc,
colour=self.gs.colours.bad,
colour=cmn.colours.bad,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
@ -106,7 +104,7 @@ class ImageCog(commands.Cog):
img = discord.File(f"resources/images/map/{arg}map.png",
filename=f'{arg}map.png')
embed = discord.Embed(title=f'{map_titles[arg]} Map',
colour=self.gs.colours.good,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
embed.set_image(url=f'attachment://{arg}map.png')
embed.set_footer(text=ctx.author.name,

View File

@ -13,16 +13,17 @@ from datetime import datetime
import discord
import discord.ext.commands as commands
import common as cmn
class MorseCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.gs = bot.get_cog("GlobalSettings")
with open('resources/morse.json') as morse_file:
self.ascii2morse = json.load(morse_file)
self.morse2ascii = {v: k for k, v in self.ascii2morse.items()}
@commands.command(name="morse", aliases=['cw'])
@commands.command(name="morse", aliases=['cw'], category=cmn.cat.ref)
async def _morse(self, ctx: commands.Context, *, msg: str):
"""Converts ASCII to international morse code."""
with ctx.typing():
@ -35,13 +36,13 @@ class MorseCog(commands.Cog):
result += ' '
embed = discord.Embed(title=f'Morse Code for {msg}',
description=result,
colour=self.gs.colours.good,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
await ctx.send(embed=embed)
@commands.command(name="unmorse", aliases=['demorse', 'uncw', 'decw'])
@commands.command(name="unmorse", aliases=['demorse', 'uncw', 'decw'], category=cmn.cat.ref)
async def _unmorse(self, ctx: commands.Context, *, msg: str):
'''Converts international morse code to ASCII.'''
with ctx.typing():
@ -58,15 +59,15 @@ class MorseCog(commands.Cog):
result += ' '
embed = discord.Embed(title=f'ASCII for {msg0}',
description=result,
colour=self.gs.colours.good,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
await ctx.send(embed=embed)
@commands.command(name="weight", aliases=["cwweight", 'cww'])
async def _weight(self, ctx: commands.Context, msg: str):
'''Calculates the CW Weight of a callsign.'''
@commands.command(name="cwweight", aliases=["weight", 'cww'], category=cmn.cat.ref)
async def _weight(self, ctx: commands.Context, *, msg: str):
'''Calculates the CW Weight of a callsign or message.'''
with ctx.typing():
msg = msg.upper()
weight = 0
@ -81,7 +82,7 @@ class MorseCog(commands.Cog):
res = f'The CW weight is **{weight}**'
embed = discord.Embed(title=f'CW Weight of {msg}',
description=res,
colour=self.gs.colours.good,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))

View File

@ -16,18 +16,21 @@ from discord.ext import commands, tasks
import aiohttp
from lxml import etree
import common as cmn
import keys
class QRZCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.gs = bot.get_cog("GlobalSettings")
self.session = aiohttp.ClientSession()
self._qrz_session_init.start()
@commands.command(name="qrz", aliases=["call"])
async def _qrz_lookup(self, ctx: commands.Context, call: str):
if self.gs.keys.qrz_user == '' or self.gs.keys.qrz_pass == '':
await ctx.send(f'http://qrz.com/db/{call}')
@commands.command(name="call", aliases=["qrz"], category=cmn.cat.lookup)
async def _qrz_lookup(self, ctx: commands.Context, callsign: str):
'''Look up a callsign on [QRZ.com](https://www.qrz.com/).'''
if keys.qrz_user == '' or keys.qrz_pass == '':
await ctx.send(f'http://qrz.com/db/{callsign}')
return
try:
@ -35,7 +38,7 @@ class QRZCog(commands.Cog):
except ConnectionError:
await self.get_session()
url = f'http://xmldata.qrz.com/xml/current/?s={self.key};callsign={call}'
url = f'http://xmldata.qrz.com/xml/current/?s={self.key};callsign={callsign}'
async with self.session.get(url) as resp:
if resp.status != 200:
raise ConnectionError(f'Unable to connect to QRZ (HTTP Error {resp.status})')
@ -47,11 +50,11 @@ class QRZCog(commands.Cog):
if 'Error' in resp_session:
if 'Session Timeout' in resp_session['Error']:
await self.get_session()
await self._qrz_lookup(ctx, call)
await self._qrz_lookup(ctx, callsign)
return
if 'Not found' in resp_session['Error']:
embed = discord.Embed(title=f"QRZ Data for {call.upper()}",
colour=self.gs.colours.bad,
embed = discord.Embed(title=f"QRZ Data for {callsign.upper()}",
colour=cmn.colours.bad,
description='No data found!',
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
@ -65,7 +68,7 @@ class QRZCog(commands.Cog):
resp_data = {el.tag.split('}')[1]: el.text for el in resp_xml_data[0].getiterator()}
embed = discord.Embed(title=f"QRZ Data for {resp_data['call']}",
colour=self.gs.colours.good,
colour=cmn.colours.good,
url=f'http://www.qrz.com/db/{resp_data["call"]}',
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
@ -82,7 +85,7 @@ class QRZCog(commands.Cog):
async def get_session(self):
"""Session creation and caching."""
self.key = await qrz_login(self.gs.keys.qrz_user, self.gs.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:
qrz_file.write(self.key)

View File

@ -16,14 +16,16 @@ import discord.ext.commands as commands
import aiohttp
import common as cmn
class StudyCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.gs = bot.get_cog("GlobalSettings")
self.lastq = dict()
self.source = 'Data courtesy of [HamStudy.org](https://hamstudy.org/)'
@commands.command(name="rq", aliases=['randomq'])
@commands.command(name="hamstudy", aliases=['rq', 'randomquestion', 'randomq'], category=cmn.cat.study)
async def _random_question(self, ctx: commands.Context, level: str = None):
'''Gets a random question from the Technician, General, and/or Extra question pools.'''
tech_pool = 'E2_2018'
@ -66,7 +68,8 @@ class StudyCog(commands.Cog):
question = random.choice(pool_questions)
embed = discord.Embed(title=question['id'],
colour=self.gs.colours.good,
description=self.source,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url))
@ -83,31 +86,32 @@ class StudyCog(commands.Cog):
self.lastq[ctx.message.channel.id] = (question['id'], question['answer'])
await ctx.send(embed=embed)
@commands.command(name="rqa")
async def _q_answer(self, ctx: commands.Context, ans: str = None):
@commands.command(name="hamstudyanswer", aliases=['rqa', 'randomquestionanswer', 'randomqa', 'hamstudya'],
category=cmn.cat.study)
async def _q_answer(self, ctx: commands.Context, answer: str = None):
'''Returns the answer to question last asked (Optional argument: your answer).'''
with ctx.typing():
correct_ans = self.lastq[ctx.message.channel.id][1]
q_num = self.lastq[ctx.message.channel.id][0]
if ans is not None:
ans = ans.upper()
if ans == correct_ans:
if answer is not None:
answer = answer.upper()
if answer == correct_ans:
result = f'Correct! The answer to {q_num} was **{correct_ans}**.'
embed = discord.Embed(title=f'{q_num} Answer',
description=result,
colour=self.gs.colours.good,
description=f'{self.source}\n\n{result}',
colour=cmn.colours.good,
timestamp=datetime.utcnow())
else:
result = f'Incorrect. The answer to {q_num} was **{correct_ans}**, not **{ans}**.'
result = f'Incorrect. The answer to {q_num} was **{correct_ans}**, not **{answer}**.'
embed = discord.Embed(title=f'{q_num} Answer',
description=result,
colour=self.gs.colours.bad,
description=f'{self.source}\n\n{result}',
colour=cmn.colours.bad,
timestamp=datetime.utcnow())
else:
result = f'The correct answer to {q_num} was **{correct_ans}**.'
embed = discord.Embed(title=f'{q_num} Answer',
description=result,
colour=self.gs.colours.neutral,
description=f'{self.source}\n\n{result}',
colour=cmn.colours.neutral,
timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name,

View File

@ -16,26 +16,27 @@ import discord.ext.commands as commands
import aiohttp
import common as cmn
class WeatherCog(commands.Cog):
wttr_units_regex = re.compile(r"\B-([cCfF])\b")
def __init__(self, bot: commands.Bot):
self.bot = bot
self.gs = bot.get_cog("GlobalSettings")
@commands.command(name="cond", aliases=['condx'])
@commands.command(name="bandconditions", aliases=['cond', 'condx', 'conditions'], category=cmn.cat.weather)
async def _band_conditions(self, ctx: commands.Context):
'''Posts an image of HF Band Conditions.'''
with ctx.typing():
embed = discord.Embed(title='Current Solar Conditions',
colour=self.gs.colours.good,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
async with aiohttp.ClientSession() as session:
async with session.get('http://www.hamqsl.com/solarsun.php') as resp:
if resp.status != 200:
embed.description = 'Could not download file...'
embed.colour = self.gs.colours.bad
embed.colour = cmn.colours.bad
else:
data = io.BytesIO(await resp.read())
embed.set_image(url=f'attachment://condx.png')
@ -43,7 +44,7 @@ class WeatherCog(commands.Cog):
icon_url=str(ctx.author.avatar_url))
await ctx.send(embed=embed, file=discord.File(data, 'condx.png'))
@commands.group(name="weather", aliases=['wttr'])
@commands.group(name="weather", aliases=['wttr'], category=cmn.cat.weather)
async def _weather_conditions(self, ctx: commands.Context):
'''Posts an image of Local Weather Conditions from [wttr.in](http://wttr.in/).
@ -59,13 +60,13 @@ class WeatherCog(commands.Cog):
if ctx.invoked_subcommand is None:
await ctx.send_help(ctx.command)
@_weather_conditions.command(name='forecast')
async def _weather_conditions_forecast(self, ctx: commands.Context, *, args: str):
@_weather_conditions.command(name='forecast', aliases=['fc', 'future'], category=cmn.cat.weather)
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/).
See help for weather command for possible location types. Add a `-c` or `-f` to use Celcius or Fahrenheit.'''
with ctx.typing():
try:
units_arg = re.search(self.wttr_units_regex, args).group(1)
units_arg = re.search(self.wttr_units_regex, location).group(1)
except AttributeError:
units_arg = ''
if units_arg.lower() == 'f':
@ -75,18 +76,18 @@ See help for weather command for possible location types. Add a `-c` or `-f` to
else:
units = ''
loc = self.wttr_units_regex.sub('', args).strip()
loc = self.wttr_units_regex.sub('', location).strip()
embed = discord.Embed(title=f'Weather Forecast for {loc}',
description='Data from [wttr.in](http://wttr.in/).',
colour=self.gs.colours.good,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
loc = loc.replace(' ', '+')
async with aiohttp.ClientSession() as session:
async with session.get(f'http://wttr.in/{loc}_{units}pnFQ.png') as resp:
if resp.status != 200:
embed.description = 'Could not download file...'
embed.colour = self.gs.colours.bad
embed.colour = cmn.colours.bad
else:
data = io.BytesIO(await resp.read())
loc = loc.replace('+', '')
@ -97,13 +98,13 @@ See help for weather command for possible location types. Add a `-c` or `-f` to
icon_url=str(ctx.author.avatar_url))
await ctx.send(embed=embed, file=discord.File(data, f'{loc}_{units}pnFQ.png'))
@_weather_conditions.command(name='now')
async def _weather_conditions_now(self, ctx: commands.Context, *, args: str):
@_weather_conditions.command(name='now', aliases=['n'], category=cmn.cat.weather)
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/).
See help for weather command for possible location types. Add a `-c` or `-f` to use Celcius or Fahrenheit.'''
with ctx.typing():
try:
units_arg = re.search(self.wttr_units_regex, args).group(1)
units_arg = re.search(self.wttr_units_regex, location).group(1)
except AttributeError:
units_arg = ''
if units_arg.lower() == 'f':
@ -113,18 +114,18 @@ See help for weather command for possible location types. Add a `-c` or `-f` to
else:
units = ''
loc = self.wttr_units_regex.sub('', args).strip()
loc = self.wttr_units_regex.sub('', location).strip()
embed = discord.Embed(title=f'Current Weather for {loc}',
description='Data from [wttr.in](http://wttr.in/).',
colour=self.gs.colours.good,
colour=cmn.colours.good,
timestamp=datetime.utcnow())
loc = loc.replace(' ', '+')
async with aiohttp.ClientSession() as session:
async with session.get(f'http://wttr.in/{loc}_0{units}pnFQ.png') as resp:
if resp.status != 200:
embed.description = 'Could not download file...'
embed.colour = self.gs.colours.bad
embed.colour = cmn.colours.bad
else:
data = io.BytesIO(await resp.read())
loc = loc.replace('+', '')

27
common.py Normal file
View File

@ -0,0 +1,27 @@
"""
Common tools for the bot.
---
Copyright (C) 2019 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU
General Public License, version 2.
---
`colours`: Colours used by embeds.
`cat`: Category names for the HelpCommand.
"""
from types import SimpleNamespace
colours = SimpleNamespace(good=0x43B581,
neutral=0x7289DA,
bad=0xF04747)
# meow
cat = SimpleNamespace(lookup='Information Lookup',
fun='Fun',
maps='Mapping',
ref='Reference',
study='Exam Study',
weather='Land and Space Weather')

View File

@ -15,11 +15,13 @@ General Public License, version 2.
`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")
authors = ("@ClassAbbyAmplifier#2229", "@0x5c#0639")
description = """A bot with various useful ham radio-related functions, written in Python."""
license = "Released under the GNU General Public License v2"
contributing = "Check out the source on GitHub, contributions welcome: https://github.com/classabbyamp/discord-qrm-bot"
release_timestamp = "not yet :P"
release = '1.0.0'

22
main.py
View File

@ -8,8 +8,6 @@ This file is part of discord-qrmbot and is released under the terms of the GNU
General Public License, version 2.
"""
from types import SimpleNamespace
import discord
from discord.ext import commands, tasks
@ -26,20 +24,6 @@ exit_code = 1 # The default exit code. ?shutdown and ?restart will change it ac
debug_mode = opt.debug # Separate assignement in-case we define an override (ternary operator goes here)
class GlobalSettings(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.opt = opt
self.keys = keys
self.info = info
self.colours = SimpleNamespace(good=0x43B581,
neutral=0x7289DA,
bad=0xF04747)
self.debug = debug_mode
# --- Bot setup ---
bot = commands.Bot(command_prefix=opt.prefix,
@ -67,7 +51,7 @@ async def check_if_owner(ctx: commands.Context):
# --- Commands ---
@bot.command(name="restart")
@bot.command(name="restart", hidden=True)
@commands.check(check_if_owner)
async def _restart_bot(ctx: commands.Context):
"""Restarts the bot."""
@ -77,7 +61,7 @@ async def _restart_bot(ctx: commands.Context):
await bot.logout()
@bot.command(name="shutdown")
@bot.command(name="shutdown", hidden=True)
@commands.check(check_if_owner)
async def _shutdown_bot(ctx: commands.Context):
"""Shuts down the bot."""
@ -109,7 +93,7 @@ async def _before_ensure_activity():
# --- Run ---
bot.add_cog(GlobalSettings(bot))
# bot.add_cog(GlobalSettings(bot))
for cog in opt.cogs:
bot.load_extension(f"cogs.{cog}")