Release the Cats! - Reorganised extensions and recategorised commands

Fixes #389 - Main issue
Fixes #388 - Accidental, Satmatch deadcode removal
This commit is contained in:
0x5c 2021-03-28 09:50:51 -04:00
parent f6d69f7498
commit 6e54a27f14
No known key found for this signature in database
GPG Key ID: A57F71C3176B9581
17 changed files with 190 additions and 127 deletions

View File

@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Configuration option to use another rTeX instance for `?tex`.
### Changed
- Main name and aliases of `?bandplan`.
- Recategorized the commands.
### Fixed
- Lack of input sanitisation in `?xkcd`.
- Incorrect capitalisation of the categories in the `?help` command.
@ -85,7 +86,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [2.3.1] - 2020-04-02
### Fixed
- Wordlist containing innappropriate words.
- Wordlist containing inappropriate words.
## [2.3.0] - 2020-03-30

View File

@ -48,11 +48,14 @@ class BoltCats(enum.Enum):
# meow
class Cats(enum.Enum):
LOOKUP = "Information Lookup"
CALC = "Calculators"
CODES = "Code References and Tools"
FUN = "Fun"
MAPS = "Mapping"
LOOKUP = "Information Lookup"
REF = "Reference"
STUDY = "Exam Study"
TIME = "Time and Time Zones"
UTILS = "Utilities"
WEATHER = "Land and Space Weather"

View File

@ -26,7 +26,7 @@ class QrmHelpCommand(commands.HelpCommand):
super().__init__(command_attrs={
"help": "Shows help about qrm or a command",
"aliases": ["h"],
# TODO "category": cmn.BoltCats.INFO
"category": cmn.BoltCats.INFO
})
self.verify_checks = True
self.context: commands.Context
@ -140,7 +140,7 @@ class BaseCog(commands.Cog):
self.bot_invite = (f"https://discordapp.com/oauth2/authorize?client_id={self.bot.user.id}"
f"&scope=bot&permissions={opt.invite_perms}")
@commands.command(name="info", aliases=["about"]) # TODO , category=cmn.BoltCats.INFO)
@commands.command(name="info", aliases=["about"], category=cmn.BoltCats.INFO)
async def _info(self, ctx: commands.Context):
"""Shows info about qrm."""
embed = cmn.embed_factory(ctx)
@ -158,7 +158,7 @@ class BaseCog(commands.Cog):
embed.set_thumbnail(url=str(self.bot.user.avatar_url))
await ctx.send(embed=embed)
@commands.command(name="ping", aliases=["beep"]) # TODO , category=cmn.BoltCats.INFO)
@commands.command(name="ping", aliases=["beep"], category=cmn.BoltCats.INFO)
async def _ping(self, ctx: commands.Context):
"""Shows the current latency to the discord endpoint."""
embed = cmn.embed_factory(ctx)
@ -171,7 +171,7 @@ class BaseCog(commands.Cog):
embed.description = f"Current ping is {self.bot.latency*1000:.1f} ms"
await ctx.send(content, embed=embed)
@commands.command(name="changelog", aliases=["clog"]) # TODO , category=cmn.BoltCats.INFO)
@commands.command(name="changelog", aliases=["clog"], category=cmn.BoltCats.INFO)
async def _changelog(self, ctx: commands.Context, version: str = "latest"):
"""Shows what has changed in a bot version. Defaults to the latest version."""
embed = cmn.embed_factory(ctx)
@ -207,7 +207,7 @@ class BaseCog(commands.Cog):
await ctx.send(embed=embed)
@commands.command(name="issue") # TODO , category=cmn.BoltCats.INFO)
@commands.command(name="issue", category=cmn.BoltCats.INFO)
async def _issue(self, ctx: commands.Context):
"""Shows how to create a bug report or feature request about the bot."""
embed = cmn.embed_factory(ctx)
@ -219,7 +219,7 @@ class BaseCog(commands.Cog):
[miaowware/qrm-resources](https://github.com/miaowware/qrm-resources/issues)."""
await ctx.send(embed=embed)
@commands.command(name="donate", aliases=["tip"]) # TODO , category=cmn.BoltCats.INFO)
@commands.command(name="donate", aliases=["tip"], category=cmn.BoltCats.INFO)
async def _donate(self, ctx: commands.Context):
"""Shows ways to help support development of the bot via donations."""
embed = cmn.embed_factory(ctx)
@ -230,7 +230,7 @@ class BaseCog(commands.Cog):
embed.add_field(name=title, value=url, inline=False)
await ctx.send(embed=embed)
@commands.command(name="invite", enabled=opt.enable_invite_cmd) # TODO , category=cmn.BoltCats.INFO)
@commands.command(name="invite", enabled=opt.enable_invite_cmd, category=cmn.BoltCats.INFO)
async def _invite(self, ctx: commands.Context):
"""Generates a link to invite the bot to a server."""
if not (await self.bot.application_info()).bot_public:

View File

@ -1,7 +1,8 @@
"""
QRZ extension for qrm
Callsign Lookup extension for qrm
---
Copyright (C) 2019-2020 Abigail Gold, 0x5c
Copyright (C) 2019-2020 Abigail Gold, 0x5c (as qrz.py)
Copyright (C) 2021 Abigail Gold, 0x5c
This file is part of qrm2 and is released under the terms of
the GNU General Public License, version 2.

View File

@ -1,7 +1,8 @@
"""
Ham extension for qrm
Codes extension for qrm
---
Copyright (C) 2019-2021 Abigail Gold, 0x5c
Copyright (C) 2019-2021 Abigail Gold, 0x5c (as ham.py)
Copyright (C) 2021 Abigail Gold, 0x5c
This file is part of qrm2 and is released under the terms of
the GNU General Public License, version 2.
@ -9,18 +10,15 @@ the GNU General Public License, version 2.
import json
from datetime import datetime
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.pfxs = callsign_info.options
with open(cmn.paths.resources / "phonetics.1.json") as file:
d = json.load(file)
self.phonetics: dict[str, str] = d["phonetics"]
@ -28,7 +26,7 @@ class HamCog(commands.Cog):
with open(cmn.paths.resources / "qcodes.1.json") as file:
self.qcodes: dict = json.load(file)
@commands.command(name="qcode", aliases=["q"], category=cmn.Cats.REF)
@commands.command(name="qcode", aliases=["q"], category=cmn.Cats.CODES)
async def _qcode_lookup(self, ctx: commands.Context, qcode: str):
"""Looks up the meaning of a Q Code."""
qcode = qcode.upper()
@ -42,7 +40,7 @@ class HamCog(commands.Cog):
embed.colour = cmn.colours.bad
await ctx.send(embed=embed)
@commands.command(name="phonetics", aliases=["ph", "phoneticize", "phoneticise", "phone"], category=cmn.Cats.REF)
@commands.command(name="phonetics", aliases=["ph", "phoneticize", "phoneticise", "phone"], category=cmn.Cats.CODES)
async def _phonetics_lookup(self, ctx: commands.Context, *, msg: str):
"""Returns NATO phonetics for a word or phrase."""
result = ""
@ -58,51 +56,7 @@ class HamCog(commands.Cog):
embed.colour = cmn.colours.good
await ctx.send(embed=embed)
@commands.command(name="utc", aliases=["z"], category=cmn.Cats.REF)
async def _utc_lookup(self, ctx: commands.Context):
"""Returns the current time in UTC."""
now = datetime.utcnow()
result = "**" + now.strftime("%Y-%m-%d %H:%M") + "Z**"
embed = cmn.embed_factory(ctx)
embed.title = "The current time is:"
embed.description = result
embed.colour = cmn.colours.good
await ctx.send(embed=embed)
@commands.command(name="prefixes", aliases=["vanity", "pfx", "vanities", "prefix"], category=cmn.Cats.REF)
async def _vanity_prefixes(self, ctx: commands.Context, country: str = ""):
"""Lists valid callsign prefixes for different countries."""
country = country.lower()
embed = cmn.embed_factory(ctx)
if country not in self.pfxs:
desc = "Possible arguments are:\n"
for key, val in self.pfxs.items():
desc += f"`{key}`: {val.title}{(' ' + val.emoji if val.emoji else '')}\n"
embed.title = f"{country} Not Found!"
embed.description = desc
embed.colour = cmn.colours.bad
await ctx.send(embed=embed)
return
else:
data = self.pfxs[country]
embed.title = data.title + (" " + data.emoji if data.emoji else "")
embed.description = data.desc
embed.colour = cmn.colours.good
for name, val in data.calls.items():
embed.add_field(name=name, value=val, inline=False)
await ctx.send(embed=embed)
@commands.command(name="contests", aliases=["cc", "tests"], category=cmn.Cats.REF)
async def _contests(self, ctx: commands.Context):
embed = cmn.embed_factory(ctx)
embed.title = "Contest Calendar"
embed.description = ("*We are currently rewriting the old, Chrome-based `contests` command. In the meantime, "
"use [the website](https://www.contestcalendar.com/weeklycont.php).*")
embed.colour = cmn.colours.good
await ctx.send(embed=embed)
@commands.command(name="phoneticweight", aliases=["pw"], category=cmn.Cats.REF)
@commands.command(name="phoneticweight", aliases=["pw"], category=cmn.Cats.CODES)
async def _weight(self, ctx: commands.Context, *, msg: str):
"""Calculates the phonetic weight of a callsign or message."""
embed = cmn.embed_factory(ctx)

28
exts/contests.py Normal file
View File

@ -0,0 +1,28 @@
"""
Contest Calendar extension for qrm
---
Copyright (C) 2021 Abigail Gold, 0x5c
This file is part of qrm2 and is released under the terms of
the GNU General Public License, version 2.
"""
import discord.ext.commands as commands
import common as cmn
class ContestCalendarCog(commands.Cog):
@commands.command(name="contests", aliases=["cc", "tests"], category=cmn.Cats.LOOKUP)
async def _contests(self, ctx: commands.Context):
embed = cmn.embed_factory(ctx)
embed.title = "Contest Calendar"
embed.description = ("*We are currently rewriting the old, Chrome-based `contests` command. In the meantime, "
"use [the website](https://www.contestcalendar.com/weeklycont.php).*")
embed.colour = cmn.colours.good
await ctx.send(embed=embed)
def setup(bot: commands.Bot):
bot.add_cog(ContestCalendarCog(bot))

View File

@ -67,7 +67,7 @@ class DbConvCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.command(name="dbconv", aliases=["dbc"], category=cmn.Cats.REF)
@commands.command(name="dbconv", aliases=["dbc"], category=cmn.Cats.CALC)
async def _db_conv(self, ctx: commands.Context,
value: Optional[float] = None,
unit_from: Optional[UnitConverter] = None,

View File

@ -1,7 +1,8 @@
"""
Lookup extension for qrm
DXCC Prefix Lookup extension for qrm
---
Copyright (C) 2019-2020 Abigail Gold, 0x5c
Copyright (C) 2019-2020 Abigail Gold, 0x5c (as lookup.py)
Copyright (C) 2021 Abigail Gold, 0x5c
This file is part of qrm2 and is released under the terms of
the GNU General Public License, version 2.
@ -21,7 +22,7 @@ import common as cmn
cty_path = Path("./data/cty.json")
class LookupCog(commands.Cog):
class DXCCCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
try:
@ -29,18 +30,6 @@ class LookupCog(commands.Cog):
except OSError:
self.cty = BigCty()
# TODO: See #107
# @commands.command(name="sat", category=cmn.Cats.Lookup)
# 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."""
# now = datetime.utcnow().strftime("%Y-%m-%d%%20%H:%M")
# if grid2 is None or grid2 == "":
# await ctx.send(f"http://www.satmatch.com/satellite/{sat_name}/obs1/{grid1}"
# f"?search_start_time={now}&duration_hrs=24")
# else:
# await ctx.send(f"http://www.satmatch.com/satellite/{sat_name}/obs1/{grid1}"
# f"/obs2/{grid2}?search_start_time={now}&duration_hrs=24")
@commands.command(name="dxcc", aliases=["dx"], category=cmn.Cats.LOOKUP)
async def _dxcc_lookup(self, ctx: commands.Context, query: str):
"""Gets DXCC info about a callsign prefix."""
@ -82,6 +71,6 @@ def run_update(cty_obj, dump_loc):
def setup(bot: commands.Bot):
lookupcog = LookupCog(bot)
bot.add_cog(lookupcog)
lookupcog._update_cty.start()
dxcccog = DXCCCog(bot)
bot.add_cog(dxcccog)
dxcccog._update_cty.start()

View File

@ -19,7 +19,7 @@ class GridCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.command(name="grid", category=cmn.Cats.MAPS)
@commands.command(name="grid", category=cmn.Cats.CALC)
async def _grid_sq_lookup(self, ctx: commands.Context, lat: float, lon: float):
("""Calculates the grid square for latitude and longitude coordinates."""
"""\n\nCoordinates should be in decimal format, with negative being latitude South and longitude West."""
@ -33,7 +33,7 @@ class GridCog(commands.Cog):
embed.colour = cmn.colours.good
await ctx.send(embed=embed)
@commands.command(name="latlong", aliases=["latlon", "loc", "ungrid"], category=cmn.Cats.MAPS)
@commands.command(name="latlong", aliases=["latlon", "loc", "ungrid"], category=cmn.Cats.CALC)
async def _location_lookup(self, ctx: commands.Context, grid: str):
("""Calculates the latitude and longitude for the center of a grid locator."""
"""\n\nTo calculate the grid locator from a latitude and longitude, use `grid`"""
@ -49,7 +49,7 @@ class GridCog(commands.Cog):
"latlong` to see other names for this command.*"))
await ctx.send(embed=embed)
@commands.command(name="griddistance", aliases=["griddist", "distance", "dist"], category=cmn.Cats.MAPS)
@commands.command(name="griddistance", aliases=["griddist", "distance", "dist"], category=cmn.Cats.CALC)
async def _dist_lookup(self, ctx: commands.Context, grid1: str, grid2: str):
"""Calculates the great circle distance and azimuthal bearing between two grid locators."""
g1 = gridtools.Grid(grid1)

View File

@ -9,7 +9,6 @@ the GNU General Public License, version 2.
import aiohttp
from datetime import datetime
import discord.ext.commands as commands
@ -19,8 +18,6 @@ import data.options as opt
class ImageCog(commands.Cog):
gl_baseurl = "https://www.fourmilab.ch/cgi-bin/uncgi/Earth?img=ETOPO1_day-m.evif&dynimg=y&opt=-p"
def __init__(self, bot: commands.Bot):
self.bot = bot
self.bandcharts = cmn.ImagesGroup(cmn.paths.resources / "bandcharts.1.json")
@ -32,21 +29,11 @@ class ImageCog(commands.Cog):
"""Gets the frequency allocations chart for a given country."""
await ctx.send(embed=create_embed(ctx, "Bandchart", self.bandcharts, chart_id))
@commands.command(name="map", category=cmn.Cats.MAPS)
@commands.command(name="map", category=cmn.Cats.REF)
async def _map(self, ctx: commands.Context, map_id: str = ""):
"""Posts a ham-relevant map."""
await ctx.send(embed=create_embed(ctx, "Map", self.maps, map_id))
@commands.command(name="grayline", aliases=["greyline", "grey", "gray", "gl"], category=cmn.Cats.MAPS)
async def _grayline(self, ctx: commands.Context):
"""Gets a map of the current greyline, where HF propagation is the best."""
embed = cmn.embed_factory(ctx)
embed.title = "Current Greyline Conditions"
embed.colour = cmn.colours.good
date_params = f"&date=1&utc={datetime.utcnow():%Y-%m-%d+%H:%M:%S}"
embed.set_image(url=self.gl_baseurl + date_params)
await ctx.send(embed=embed)
def create_embed(ctx: commands.Context, not_found_name: str, db: cmn.ImagesGroup, img_id: str):
"""Creates an embed for the image and its metadata, or list available images in the group."""

View File

@ -1,7 +1,8 @@
"""
Weather extension for qrm
Land Weather extension for qrm
---
Copyright (C) 2019-2020 Abigail Gold, 0x5c
Copyright (C) 2019-2020 Abigail Gold, 0x5c (as weather.py)
Copyright (C) 2021 Abigail Gold, 0x5c
This file is part of qrm2 and is released under the terms of
the GNU General Public License, version 2.
@ -26,20 +27,6 @@ class WeatherCog(commands.Cog):
self.bot = bot
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
@commands.command(name="solarweather", aliases=["solar", "bandconditions", "cond", "condx", "conditions"],
category=cmn.Cats.WEATHER)
async def solarweather(self, ctx: commands.Context):
"""Gets a solar weather report."""
embed = cmn.embed_factory(ctx)
embed.title = "☀️ Current Solar Weather"
if ctx.invoked_with in ["bandconditions", "cond", "condx", "conditions"]:
embed.add_field(name="⚠️ Deprecated Command Alias",
value=(f"This command has been renamed to `{ctx.prefix}solar`!\n"
"The alias you used will be removed in the next version."))
embed.colour = cmn.colours.good
embed.set_image(url="http://www.hamqsl.com/solarsun.php")
await ctx.send(embed=embed)
@commands.group(name="weather", aliases=["wttr"], case_insensitive=True, category=cmn.Cats.WEATHER)
async def _weather_conditions(self, ctx: commands.Context):
"""Gets local weather conditions from [wttr.in](http://wttr.in/).

View File

@ -23,7 +23,7 @@ class MorseCog(commands.Cog):
self.morse: dict[str, str] = d["morse"]
self.ascii: dict[str, int] = d["ascii"]
@commands.command(name="morse", aliases=["cw"], category=cmn.Cats.REF)
@commands.command(name="morse", aliases=["cw"], category=cmn.Cats.CODES)
async def _morse(self, ctx: commands.Context, *, msg: str):
"""Converts ASCII to international morse code."""
result = ""
@ -39,7 +39,7 @@ class MorseCog(commands.Cog):
embed.colour = cmn.colours.good
await ctx.send(embed=embed)
@commands.command(name="unmorse", aliases=["demorse", "uncw", "decw"], category=cmn.Cats.REF)
@commands.command(name="unmorse", aliases=["demorse", "uncw", "decw"], category=cmn.Cats.CODES)
async def _unmorse(self, ctx: commands.Context, *, msg: str):
"""Converts international morse code to ASCII."""
result = ""
@ -59,7 +59,7 @@ class MorseCog(commands.Cog):
embed.colour = cmn.colours.good
await ctx.send(embed=embed)
@commands.command(name="cwweight", aliases=["weight", "cww"], category=cmn.Cats.REF)
@commands.command(name="cwweight", aliases=["weight", "cww"], category=cmn.Cats.CODES)
async def _weight(self, ctx: commands.Context, *, msg: str):
"""Calculates the CW weight of a callsign or message."""
embed = cmn.embed_factory(ctx)

48
exts/prefixes.py Normal file
View File

@ -0,0 +1,48 @@
"""
Prefixes Lookup extension for qrm
---
Copyright (C) 2021 Abigail Gold, 0x5c
This file is part of qrm2 and is released under the terms of
the GNU General Public License, version 2.
"""
import discord.ext.commands as commands
import common as cmn
from resources import callsign_info
class PrefixesCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.pfxs = callsign_info.options
@commands.command(name="prefixes", aliases=["vanity", "pfx", "vanities", "prefix"], category=cmn.Cats.REF)
async def _vanity_prefixes(self, ctx: commands.Context, country: str = ""):
"""Lists valid callsign prefixes for different countries."""
country = country.lower()
embed = cmn.embed_factory(ctx)
if country not in self.pfxs:
desc = "Possible arguments are:\n"
for key, val in self.pfxs.items():
desc += f"`{key}`: {val.title}{(' ' + val.emoji if val.emoji else '')}\n"
embed.title = f"{country} Not Found!"
embed.description = desc
embed.colour = cmn.colours.bad
await ctx.send(embed=embed)
return
else:
data = self.pfxs[country]
embed.title = data.title + (" " + data.emoji if data.emoji else "")
embed.description = data.desc
embed.colour = cmn.colours.good
for name, val in data.calls.items():
embed.add_field(name=name, value=val, inline=False)
await ctx.send(embed=embed)
def setup(bot: commands.Bot):
bot.add_cog(PrefixesCog(bot))

View File

@ -12,6 +12,7 @@ from io import BytesIO
import aiohttp
import cairosvg
from datetime import datetime
import discord
import discord.ext.commands as commands
@ -22,6 +23,8 @@ import common as cmn
class PropagationCog(commands.Cog):
muf_url = "https://prop.kc2g.com/renders/current/mufd-normal-now.svg"
fof2_url = "https://prop.kc2g.com/renders/current/fof2-normal-now.svg"
gl_baseurl = "https://www.fourmilab.ch/cgi-bin/uncgi/Earth?img=ETOPO1_day-m.evif&dynimg=y&opt=-p"
n0nbh_sun_url = "http://www.hamqsl.com/solarsun.php"
def __init__(self, bot):
self.bot = bot
@ -55,6 +58,30 @@ class PropagationCog(commands.Cog):
embed.set_image(url="attachment://fof2_map.png")
await ctx.send(file=file, embed=embed)
@commands.command(name="grayline", aliases=["greyline", "grey", "gray", "gl"], category=cmn.Cats.WEATHER)
async def grayline(self, ctx: commands.Context):
"""Gets a map of the current greyline, where HF propagation is the best."""
embed = cmn.embed_factory(ctx)
embed.title = "Current Greyline Conditions"
embed.colour = cmn.colours.good
date_params = f"&date=1&utc={datetime.utcnow():%Y-%m-%d+%H:%M:%S}"
embed.set_image(url=self.gl_baseurl + date_params)
await ctx.send(embed=embed)
@commands.command(name="solarweather", aliases=["solar", "bandconditions", "cond", "condx", "conditions"],
category=cmn.Cats.WEATHER)
async def solarweather(self, ctx: commands.Context):
"""Gets a solar weather report."""
embed = cmn.embed_factory(ctx)
embed.title = "☀️ Current Solar Weather"
if ctx.invoked_with in ["bandconditions", "cond", "condx", "conditions"]:
embed.add_field(name="⚠️ Deprecated Command Alias",
value=(f"This command has been renamed to `{ctx.prefix}solar`!\n"
"The alias you used will be removed in the next version."))
embed.colour = cmn.colours.good
embed.set_image(url=self.n0nbh_sun_url)
await ctx.send(embed=embed)
def setup(bot: commands.Bot):
bot.add_cog(PropagationCog(bot))

View File

@ -26,7 +26,7 @@ class TexCog(commands.Cog):
with open(cmn.paths.resources / "template.1.tex") as latex_template:
self.template = latex_template.read()
@commands.command(name="tex", aliases=["latex"], category=cmn.Cats.FUN)
@commands.command(name="tex", aliases=["latex"], category=cmn.Cats.UTILS)
async def tex(self, ctx: commands.Context, *, expr: str):
"""Renders a LaTeX expression."""
payload = {

35
exts/time.py Normal file
View File

@ -0,0 +1,35 @@
"""
Time extension for qrm
---
Copyright (C) 2021 classabbyamp, 0x5c
This file is part of qrm2 and is released under the terms of
the GNU General Public License, version 2.
"""
from datetime import datetime
import discord.ext.commands as commands
import common as cmn
class TimeCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.command(name="utc", aliases=["z"], category=cmn.Cats.TIME)
async def _utc_lookup(self, ctx: commands.Context):
"""Returns the current time in UTC."""
now = datetime.utcnow()
result = "**" + now.strftime("%Y-%m-%d %H:%M") + "Z**"
embed = cmn.embed_factory(ctx)
embed.title = "The current time is:"
embed.description = result
embed.colour = cmn.colours.good
await ctx.send(embed=embed)
def setup(bot: commands.Bot):
bot.add_cog(TimeCog(bot))

View File

@ -31,20 +31,23 @@ owners_uids = (200102491231092736, 564766093051166729)
# The extensions to load when running the bot.
exts = [
"ae7q",
"base",
"ae7q",
"callsign",
"codes",
"contests",
"dbconv",
"dxcc",
"fun",
"grid",
"ham",
"image",
"lookup",
"land_weather",
"morse",
"qrz",
"prefixes",
"propagation",
"study",
"tex",
"weather",
"dbconv",
"propagation",
"time",
]
# URL to the resources