mirror of
https://github.com/miaowware/qrm2.git
synced 2024-11-26 17:58:40 -05:00
Merge branch 'master' into help-checks
This commit is contained in:
commit
8686b0ef96
@ -7,12 +7,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
- Added Trustee field to qrz command for club callsigns.
|
- Added Trustee field to qrz command for club callsigns.
|
||||||
|
- Added alias for `ae7q call` command (`ae7q c`).
|
||||||
|
- Added ae7q lookup by FRN and Licensee ID, and for trustee records (`ae7q frn, licensee, trustee`).
|
||||||
### Changed
|
### Changed
|
||||||
- Changelog command to accept a version as argument.
|
- Changelog command to accept a version as argument.
|
||||||
- The qrz command can now link to a QRZ page instead of embedding the data with the `--link` flag.
|
- The qrz command can now link to a QRZ page instead of embedding the data with the `--link` flag.
|
||||||
|
- All currently-available pools can now be accessed by the `hamstudy` command.
|
||||||
|
- The `hamstudy` command now uses the syntax `?hamstudy <country> <pool>`.
|
||||||
|
- Replaced `hamstudyanswer` command with answering by reaction.
|
||||||
### 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.
|
||||||
|
- Fixed warning emoji reaction on messages starting with "??".
|
||||||
|
|
||||||
|
|
||||||
## [2.1.0] - 2020-01-04
|
## [2.1.0] - 2020-01-04
|
||||||
|
@ -28,7 +28,7 @@ $ run.sh
|
|||||||
|
|
||||||
## Copyright
|
## Copyright
|
||||||
|
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This program is released under the terms of the GNU General Public License,
|
This program is released under the terms of the GNU General Public License,
|
||||||
version 2. See `COPYING` for full license text.
|
version 2. See `COPYING` for full license text.
|
||||||
|
42
common.py
42
common.py
@ -1,23 +1,19 @@
|
|||||||
"""
|
"""
|
||||||
Common tools for the bot.
|
Common tools for the bot.
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
---
|
|
||||||
|
|
||||||
`colours`: Colours used by embeds.
|
|
||||||
|
|
||||||
`cat`: Category names for the HelpCommand.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
from pathlib import Path
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
@ -26,7 +22,8 @@ import discord.ext.commands as commands
|
|||||||
import data.options as opt
|
import data.options as opt
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["colours", "cat", "emojis", "embed_factory", "error_embed_factory", "add_react", "check_if_owner"]
|
__all__ = ["colours", "cat", "emojis", "paths", "ImageMetadata", "ImagesGroup",
|
||||||
|
"embed_factory", "error_embed_factory", "add_react", "check_if_owner"]
|
||||||
|
|
||||||
|
|
||||||
# --- Common values ---
|
# --- Common values ---
|
||||||
@ -48,7 +45,11 @@ emojis = SimpleNamespace(check_mark='✅',
|
|||||||
warning='⚠️',
|
warning='⚠️',
|
||||||
question='❓',
|
question='❓',
|
||||||
no_entry='⛔',
|
no_entry='⛔',
|
||||||
bangbang='‼️')
|
bangbang='‼️',
|
||||||
|
a='🇦',
|
||||||
|
b='🇧',
|
||||||
|
c='🇨',
|
||||||
|
d='🇩')
|
||||||
|
|
||||||
paths = SimpleNamespace(data=Path("./data/"),
|
paths = SimpleNamespace(data=Path("./data/"),
|
||||||
resources=Path("./resources/"),
|
resources=Path("./resources/"),
|
||||||
@ -95,6 +96,29 @@ class ImagesGroup(collections.abc.Mapping):
|
|||||||
return str(self._images)
|
return str(self._images)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Converters ---
|
||||||
|
|
||||||
|
class GlobalChannelConverter(commands.IDConverter):
|
||||||
|
"""Converter to get any bot-acessible channel by ID/mention (global), or name (in current guild only)."""
|
||||||
|
async def convert(self, ctx: commands.Context, argument: str):
|
||||||
|
bot = ctx.bot
|
||||||
|
guild = ctx.guild
|
||||||
|
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument)
|
||||||
|
result = None
|
||||||
|
if match is None:
|
||||||
|
# not a mention/ID
|
||||||
|
if guild:
|
||||||
|
result = discord.utils.get(guild.text_channels, name=argument)
|
||||||
|
else:
|
||||||
|
raise commands.BadArgument(f"""Channel named "{argument}" not found in this guild.""")
|
||||||
|
else:
|
||||||
|
channel_id = int(match.group(1))
|
||||||
|
result = bot.get_channel(channel_id)
|
||||||
|
if not isinstance(result, (discord.TextChannel, discord.abc.PrivateChannel)):
|
||||||
|
raise commands.BadArgument(f"""Channel "{argument}" not found.""")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
# --- Helper functions ---
|
# --- Helper functions ---
|
||||||
|
|
||||||
def embed_factory(ctx: commands.Context) -> discord.Embed:
|
def embed_factory(ctx: commands.Context) -> discord.Embed:
|
||||||
|
446
exts/ae7q.py
446
exts/ae7q.py
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
ae7q extension for qrm
|
ae7q extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
@ -10,13 +10,13 @@ Test callsigns:
|
|||||||
KN8U: active, restricted
|
KN8U: active, restricted
|
||||||
AB2EE: expired, restricted
|
AB2EE: expired, restricted
|
||||||
KE8FGB: assigned once, no restrictions
|
KE8FGB: assigned once, no restrictions
|
||||||
NA2AAA: unassigned, no records
|
KV4AAA: unassigned, no records
|
||||||
KC4USA: reserved but has call history
|
KC4USA: reserved, no call history, *but* has application history
|
||||||
WF4EMA: "
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
import common as cmn
|
import common as cmn
|
||||||
@ -25,7 +25,7 @@ import common as cmn
|
|||||||
class AE7QCog(commands.Cog):
|
class AE7QCog(commands.Cog):
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.session = bot.qrm.session
|
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
|
||||||
|
|
||||||
@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):
|
||||||
@ -33,110 +33,380 @@ class AE7QCog(commands.Cog):
|
|||||||
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", 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 for a callsign on [ae7q.com](http://ae7q.com/).'''
|
'''Look up the history of a callsign on [ae7q.com](http://ae7q.com/).'''
|
||||||
callsign = callsign.upper()
|
with ctx.typing():
|
||||||
desc = ''
|
callsign = callsign.upper()
|
||||||
base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
|
desc = ''
|
||||||
embed = cmn.embed_factory(ctx)
|
base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
|
||||||
|
embed = cmn.embed_factory(ctx)
|
||||||
|
|
||||||
async with self.session.get(base_url + callsign) as resp:
|
async with self.session.get(base_url + callsign) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
embed.title = "Error in AE7Q call command"
|
embed.title = "Error in AE7Q call command"
|
||||||
embed.description = 'Could not load AE7Q'
|
embed.description = 'Could not load AE7Q'
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
return
|
return
|
||||||
page = await resp.text()
|
page = await resp.text()
|
||||||
|
|
||||||
soup = BeautifulSoup(page, features="html.parser")
|
soup = BeautifulSoup(page, features="html.parser")
|
||||||
tables = soup.select("table.Database")
|
tables = [[row for row in table.find_all("tr")] for table in soup.select("table.Database")]
|
||||||
|
|
||||||
for table in tables:
|
table = tables[0]
|
||||||
rows = table.find_all("tr")
|
|
||||||
if len(rows) > 1 and len(rows[0]) > 1:
|
# find the first table in the page, and use it to make a description
|
||||||
break
|
if len(table[0]) == 1:
|
||||||
if desc == '':
|
for row in table:
|
||||||
for row in rows:
|
|
||||||
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}`')
|
||||||
rows = None
|
table = tables[1]
|
||||||
|
|
||||||
first_header = ''.join(rows[0].find_all("th")[0].strings)
|
table_headers = table[0].find_all("th")
|
||||||
|
first_header = ''.join(table_headers[0].strings) if len(table_headers) > 0 else None
|
||||||
|
|
||||||
if rows is None or first_header != 'Entity Name':
|
# catch if the wrong table was selected
|
||||||
|
if first_header is None or first_header != 'Entity Name':
|
||||||
|
embed.title = f"AE7Q History for {callsign}"
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.url = base_url + callsign
|
||||||
|
embed.description = desc
|
||||||
|
embed.description += f'\nNo records found for `{callsign}`'
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
table = await process_table(table[1:])
|
||||||
|
|
||||||
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f"AE7Q History for {callsign}"
|
embed.title = f"AE7Q History for {callsign}"
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.good
|
||||||
embed.url = f"{base_url}{callsign}"
|
embed.url = base_url + callsign
|
||||||
|
|
||||||
|
# add the first three rows of the table to the embed
|
||||||
|
for row in table[0:3]:
|
||||||
|
header = f'**{row[0]}** ({row[1]})' # **Name** (Applicant Type)
|
||||||
|
body = (f'Class: *{row[2]}*\n'
|
||||||
|
f'Region: *{row[3]}*\n'
|
||||||
|
f'Status: *{row[4]}*\n'
|
||||||
|
f'Granted: *{row[5]}*\n'
|
||||||
|
f'Effective: *{row[6]}*\n'
|
||||||
|
f'Cancelled: *{row[7]}*\n'
|
||||||
|
f'Expires: *{row[8]}*')
|
||||||
|
embed.add_field(name=header, value=body, inline=False)
|
||||||
|
|
||||||
|
if len(table) > 3:
|
||||||
|
desc += f'\nRecords 1 to 3 of {len(table)}. See ae7q.com for more...'
|
||||||
|
|
||||||
embed.description = desc
|
embed.description = desc
|
||||||
embed.description += f'\nNo records found for `{callsign}`'
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
return
|
|
||||||
|
|
||||||
table_contents = [] # store your table here
|
@_ae7q_lookup.command(name="trustee", aliases=["t"], category=cmn.cat.lookup)
|
||||||
for tr in rows:
|
async def _ae7q_trustee(self, ctx: commands.Context, callsign: str):
|
||||||
if rows.index(tr) == 0:
|
'''Look up the licenses for which a licensee is trustee on [ae7q.com](http://ae7q.com/).'''
|
||||||
# first_header = ''.join(tr.find_all("th")[0].strings)
|
with ctx.typing():
|
||||||
# if first_header == 'Entity Name':
|
callsign = callsign.upper()
|
||||||
# print('yooooo')
|
desc = ''
|
||||||
continue
|
base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
|
||||||
row_cells = []
|
embed = cmn.embed_factory(ctx)
|
||||||
for td in tr.find_all('td'):
|
|
||||||
if td.getText().strip() != '':
|
|
||||||
row_cells.append(td.getText().strip())
|
|
||||||
else:
|
|
||||||
row_cells.append('-')
|
|
||||||
if 'colspan' in td.attrs and int(td.attrs['colspan']) > 1:
|
|
||||||
for i in range(int(td.attrs['colspan']) - 1):
|
|
||||||
row_cells.append(row_cells[-1])
|
|
||||||
for i, cell in enumerate(row_cells):
|
|
||||||
if cell == '"':
|
|
||||||
row_cells[i] = table_contents[-1][i]
|
|
||||||
if len(row_cells) > 1:
|
|
||||||
table_contents += [row_cells]
|
|
||||||
|
|
||||||
embed = cmn.embed_factory(ctx)
|
async with self.session.get(base_url + callsign) as resp:
|
||||||
embed.title = f"AE7Q Records for {callsign}"
|
if resp.status != 200:
|
||||||
embed.colour = cmn.colours.good
|
embed.title = "Error in AE7Q trustee command"
|
||||||
embed.url = f"{base_url}{callsign}"
|
embed.description = 'Could not load AE7Q'
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
page = await resp.text()
|
||||||
|
|
||||||
for row in table_contents[0:3]:
|
soup = BeautifulSoup(page, features="html.parser")
|
||||||
header = f'**{row[0]}** ({row[1]})'
|
tables = [[row for row in table.find_all("tr")] for table in soup.select("table.Database")]
|
||||||
body = (f'Class: *{row[2]}*\n'
|
|
||||||
f'Region: *{row[3]}*\n'
|
|
||||||
f'Status: *{row[4]}*\n'
|
|
||||||
f'Granted: *{row[5]}*\n'
|
|
||||||
f'Effective: *{row[6]}*\n'
|
|
||||||
f'Cancelled: *{row[7]}*\n'
|
|
||||||
f'Expires: *{row[8]}*')
|
|
||||||
embed.add_field(name=header, value=body, inline=False)
|
|
||||||
|
|
||||||
embed.description = desc
|
try:
|
||||||
if len(table_contents) > 3:
|
table = tables[2] if len(tables[0][0]) == 1 else tables[1]
|
||||||
embed.description += f'\nRecords 1 to 3 of {len(table_contents)}. See ae7q.com for more...'
|
except IndexError:
|
||||||
|
embed.title = f"AE7Q Trustee History for {callsign}"
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.url = base_url + callsign
|
||||||
|
embed.description = desc
|
||||||
|
embed.description += f'\nNo records found for `{callsign}`'
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
table_headers = table[0].find_all("th")
|
||||||
|
first_header = ''.join(table_headers[0].strings) if len(table_headers) > 0 else None
|
||||||
|
|
||||||
# TODO: write commands for other AE7Q response types?
|
# catch if the wrong table was selected
|
||||||
# @_ae7q_lookup.command(name="trustee")
|
if first_header is None or not first_header.startswith("With"):
|
||||||
# async def _ae7q_trustee(self, ctx: commands.Context, callsign: str):
|
embed.title = f"AE7Q Trustee History for {callsign}"
|
||||||
# pass
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.url = base_url + callsign
|
||||||
|
embed.description = desc
|
||||||
|
embed.description += f'\nNo records found for `{callsign}`'
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
# @_ae7q_lookup.command(name="applications", aliases=['apps'])
|
table = await process_table(table[2:])
|
||||||
# async def _ae7q_applications(self, ctx: commands.Context, callsign: str):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
# @_ae7q_lookup.command(name="frn")
|
embed = cmn.embed_factory(ctx)
|
||||||
# async def _ae7q_frn(self, ctx: commands.Context, frn: str):
|
embed.title = f"AE7Q Trustee History for {callsign}"
|
||||||
# base_url = "http://ae7q.com/query/data/FrnHistory.php?FRN="
|
embed.colour = cmn.colours.good
|
||||||
# pass
|
embed.url = base_url + callsign
|
||||||
|
|
||||||
# @_ae7q_lookup.command(name="licensee", aliases=["lic"])
|
# add the first three rows of the table to the embed
|
||||||
# async def _ae7q_licensee(self, ctx: commands.Context, frn: str):
|
for row in table[0:3]:
|
||||||
# base_url = "http://ae7q.com/query/data/LicenseeIdHistory.php?ID="
|
header = f'**{row[0]}** ({row[3]})' # **Name** (Applicant Type)
|
||||||
# pass
|
body = (f'Name: *{row[2]}*\n'
|
||||||
|
f'Region: *{row[1]}*\n'
|
||||||
|
f'Status: *{row[4]}*\n'
|
||||||
|
f'Granted: *{row[5]}*\n'
|
||||||
|
f'Effective: *{row[6]}*\n'
|
||||||
|
f'Cancelled: *{row[7]}*\n'
|
||||||
|
f'Expires: *{row[8]}*')
|
||||||
|
embed.add_field(name=header, value=body, inline=False)
|
||||||
|
|
||||||
|
if len(table) > 3:
|
||||||
|
desc += f'\nRecords 1 to 3 of {len(table)}. See ae7q.com for more...'
|
||||||
|
|
||||||
|
embed.description = desc
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@_ae7q_lookup.command(name="applications", aliases=["a"], category=cmn.cat.lookup)
|
||||||
|
async def _ae7q_applications(self, ctx: commands.Context, callsign: str):
|
||||||
|
'''Look up the application history for a callsign on [ae7q.com](http://ae7q.com/).'''
|
||||||
|
"""
|
||||||
|
with ctx.typing():
|
||||||
|
callsign = callsign.upper()
|
||||||
|
desc = ''
|
||||||
|
base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
|
||||||
|
embed = cmn.embed_factory(ctx)
|
||||||
|
|
||||||
|
async with self.session.get(base_url + callsign) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
embed.title = "Error in AE7Q applications command"
|
||||||
|
embed.description = 'Could not load AE7Q'
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
page = await resp.text()
|
||||||
|
|
||||||
|
soup = BeautifulSoup(page, features="html.parser")
|
||||||
|
tables = [[row for row in table.find_all("tr")] for table in soup.select("table.Database")]
|
||||||
|
|
||||||
|
table = tables[0]
|
||||||
|
|
||||||
|
# find the first table in the page, and use it to make a description
|
||||||
|
if len(table[0]) == 1:
|
||||||
|
for row in table:
|
||||||
|
desc += " ".join(row.getText().split())
|
||||||
|
desc += '\n'
|
||||||
|
desc = desc.replace(callsign, f'`{callsign}`')
|
||||||
|
|
||||||
|
# select the last table to get applications
|
||||||
|
table = tables[-1]
|
||||||
|
|
||||||
|
table_headers = table[0].find_all("th")
|
||||||
|
first_header = ''.join(table_headers[0].strings) if len(table_headers) > 0 else None
|
||||||
|
|
||||||
|
# catch if the wrong table was selected
|
||||||
|
if first_header is None or not first_header.startswith("Receipt"):
|
||||||
|
embed.title = f"AE7Q Application History for {callsign}"
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.url = base_url + callsign
|
||||||
|
embed.description = desc
|
||||||
|
embed.description += f'\nNo records found for `{callsign}`'
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
table = await process_table(table[1:])
|
||||||
|
|
||||||
|
embed = cmn.embed_factory(ctx)
|
||||||
|
embed.title = f"AE7Q Application History for {callsign}"
|
||||||
|
embed.colour = cmn.colours.good
|
||||||
|
embed.url = base_url + callsign
|
||||||
|
|
||||||
|
# add the first three rows of the table to the embed
|
||||||
|
for row in table[0:3]:
|
||||||
|
header = f'**{row[1]}** ({row[3]})' # **Name** (Callsign)
|
||||||
|
body = (f'Received: *{row[0]}*\n'
|
||||||
|
f'Region: *{row[2]}*\n'
|
||||||
|
f'Purpose: *{row[5]}*\n'
|
||||||
|
f'Last Action: *{row[7]}*\n'
|
||||||
|
f'Application Status: *{row[8]}*\n')
|
||||||
|
embed.add_field(name=header, value=body, inline=False)
|
||||||
|
|
||||||
|
if len(table) > 3:
|
||||||
|
desc += f'\nRecords 1 to 3 of {len(table)}. See ae7q.com for more...'
|
||||||
|
|
||||||
|
embed.description = desc
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@_ae7q_lookup.command(name="frn", aliases=["f"], category=cmn.cat.lookup)
|
||||||
|
async def _ae7q_frn(self, ctx: commands.Context, frn: str):
|
||||||
|
'''Look up the history of an FRN on [ae7q.com](http://ae7q.com/).'''
|
||||||
|
"""
|
||||||
|
NOTES:
|
||||||
|
- 2 tables: callsign history and application history
|
||||||
|
- If not found: no tables
|
||||||
|
"""
|
||||||
|
with ctx.typing():
|
||||||
|
base_url = "http://ae7q.com/query/data/FrnHistory.php?FRN="
|
||||||
|
embed = cmn.embed_factory(ctx)
|
||||||
|
|
||||||
|
async with self.session.get(base_url + frn) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
embed.title = "Error in AE7Q frn command"
|
||||||
|
embed.description = 'Could not load AE7Q'
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
page = await resp.text()
|
||||||
|
|
||||||
|
soup = BeautifulSoup(page, features="html.parser")
|
||||||
|
tables = [[row for row in table.find_all("tr")] for table in soup.select("table.Database")]
|
||||||
|
|
||||||
|
if not len(tables):
|
||||||
|
embed.title = f"AE7Q History for FRN {frn}"
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.url = base_url + frn
|
||||||
|
embed.description = f'No records found for FRN `{frn}`'
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
table = tables[0]
|
||||||
|
|
||||||
|
table_headers = table[0].find_all("th")
|
||||||
|
first_header = ''.join(table_headers[0].strings) if len(table_headers) > 0 else None
|
||||||
|
|
||||||
|
# catch if the wrong table was selected
|
||||||
|
if first_header is None or not first_header.startswith('With Licensee'):
|
||||||
|
embed.title = f"AE7Q History for FRN {frn}"
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.url = base_url + frn
|
||||||
|
embed.description = f'No records found for FRN `{frn}`'
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
table = await process_table(table[2:])
|
||||||
|
|
||||||
|
embed = cmn.embed_factory(ctx)
|
||||||
|
embed.title = f"AE7Q History for FRN {frn}"
|
||||||
|
embed.colour = cmn.colours.good
|
||||||
|
embed.url = base_url + frn
|
||||||
|
|
||||||
|
# add the first three rows of the table to the embed
|
||||||
|
for row in table[0:3]:
|
||||||
|
header = f'**{row[0]}** ({row[3]})' # **Callsign** (Applicant Type)
|
||||||
|
body = (f'Name: *{row[2]}*\n'
|
||||||
|
f'Class: *{row[4]}*\n'
|
||||||
|
f'Region: *{row[1]}*\n'
|
||||||
|
f'Status: *{row[5]}*\n'
|
||||||
|
f'Granted: *{row[6]}*\n'
|
||||||
|
f'Effective: *{row[7]}*\n'
|
||||||
|
f'Cancelled: *{row[8]}*\n'
|
||||||
|
f'Expires: *{row[9]}*')
|
||||||
|
embed.add_field(name=header, value=body, inline=False)
|
||||||
|
|
||||||
|
if len(table) > 3:
|
||||||
|
embed.description = f'Records 1 to 3 of {len(table)}. See ae7q.com for more...'
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@_ae7q_lookup.command(name="licensee", aliases=["l"], category=cmn.cat.lookup)
|
||||||
|
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/).'''
|
||||||
|
with ctx.typing():
|
||||||
|
licensee_id = licensee_id.upper()
|
||||||
|
base_url = "http://ae7q.com/query/data/LicenseeIdHistory.php?ID="
|
||||||
|
embed = cmn.embed_factory(ctx)
|
||||||
|
|
||||||
|
async with self.session.get(base_url + licensee_id) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
embed.title = "Error in AE7Q licensee command"
|
||||||
|
embed.description = 'Could not load AE7Q'
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
page = await resp.text()
|
||||||
|
|
||||||
|
soup = BeautifulSoup(page, features="html.parser")
|
||||||
|
tables = [[row for row in table.find_all("tr")] for table in soup.select("table.Database")]
|
||||||
|
|
||||||
|
if not len(tables):
|
||||||
|
embed.title = f"AE7Q History for Licensee {licensee_id}"
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.url = base_url + licensee_id
|
||||||
|
embed.description = f'No records found for Licensee `{licensee_id}`'
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
table = tables[0]
|
||||||
|
|
||||||
|
table_headers = table[0].find_all("th")
|
||||||
|
first_header = ''.join(table_headers[0].strings) if len(table_headers) > 0 else None
|
||||||
|
|
||||||
|
# catch if the wrong table was selected
|
||||||
|
if first_header is None or not first_header.startswith('With FCC'):
|
||||||
|
embed.title = f"AE7Q History for Licensee {licensee_id}"
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.url = base_url + licensee_id
|
||||||
|
embed.description = f'No records found for Licensee `{licensee_id}`'
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
table = await process_table(table[2:])
|
||||||
|
|
||||||
|
embed = cmn.embed_factory(ctx)
|
||||||
|
embed.title = f"AE7Q History for Licensee {licensee_id}"
|
||||||
|
embed.colour = cmn.colours.good
|
||||||
|
embed.url = base_url + licensee_id
|
||||||
|
|
||||||
|
# add the first three rows of the table to the embed
|
||||||
|
for row in table[0:3]:
|
||||||
|
header = f'**{row[0]}** ({row[3]})' # **Callsign** (Applicant Type)
|
||||||
|
body = (f'Name: *{row[2]}*\n'
|
||||||
|
f'Class: *{row[4]}*\n'
|
||||||
|
f'Region: *{row[1]}*\n'
|
||||||
|
f'Status: *{row[5]}*\n'
|
||||||
|
f'Granted: *{row[6]}*\n'
|
||||||
|
f'Effective: *{row[7]}*\n'
|
||||||
|
f'Cancelled: *{row[8]}*\n'
|
||||||
|
f'Expires: *{row[9]}*')
|
||||||
|
embed.add_field(name=header, value=body, inline=False)
|
||||||
|
|
||||||
|
if len(table) > 3:
|
||||||
|
embed.description = f'Records 1 to 3 of {len(table)}. See ae7q.com for more...'
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
|
async def process_table(table: list):
|
||||||
|
"""Processes tables (*not* including headers) and returns the processed table"""
|
||||||
|
table_contents = []
|
||||||
|
for tr in table:
|
||||||
|
row = []
|
||||||
|
for td in tr.find_all('td'):
|
||||||
|
cell_val = td.getText().strip()
|
||||||
|
row.append(cell_val if cell_val else '-')
|
||||||
|
|
||||||
|
# take care of columns that span multiple rows by copying the contents rightward
|
||||||
|
if 'colspan' in td.attrs and int(td.attrs['colspan']) > 1:
|
||||||
|
for i in range(int(td.attrs['colspan']) - 1):
|
||||||
|
row.append(row[-1])
|
||||||
|
|
||||||
|
# get rid of ditto marks by copying the contents from the previous row
|
||||||
|
for i, cell in enumerate(row):
|
||||||
|
if cell == "\"":
|
||||||
|
row[i] = table_contents[-1][i]
|
||||||
|
# add row to table
|
||||||
|
table_contents += [row]
|
||||||
|
return table_contents
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: commands.Bot):
|
def setup(bot: commands.Bot):
|
||||||
|
22
exts/base.py
22
exts/base.py
@ -1,23 +1,25 @@
|
|||||||
"""
|
"""
|
||||||
Base extension for qrm
|
Base extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import random
|
||||||
import re
|
import re
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import random
|
from typing import Union
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
|
|
||||||
import info
|
import info
|
||||||
|
import common as cmn
|
||||||
|
|
||||||
import data.options as opt
|
import data.options as opt
|
||||||
import common as cmn
|
|
||||||
|
|
||||||
|
|
||||||
class QrmHelpCommand(commands.HelpCommand):
|
class QrmHelpCommand(commands.HelpCommand):
|
||||||
@ -172,15 +174,15 @@ class BaseCog(commands.Cog):
|
|||||||
"(https://github.com/classabbyamp/discord-qrm2/issues)!")
|
"(https://github.com/classabbyamp/discord-qrm2/issues)!")
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@commands.command(name="bruce")
|
|
||||||
async def _b_issue(self, ctx: commands.Context):
|
|
||||||
"""Shows how to create an issue for the bot."""
|
|
||||||
await ctx.invoke(self._issue)
|
|
||||||
|
|
||||||
@commands.command(name="echo", aliases=["e"], category=cmn.cat.admin)
|
@commands.command(name="echo", aliases=["e"], category=cmn.cat.admin)
|
||||||
@commands.check(cmn.check_if_owner)
|
@commands.check(cmn.check_if_owner)
|
||||||
async def _echo(self, ctx: commands.Context, channel: commands.TextChannelConverter, *, msg: str):
|
async def _echo(self, ctx: commands.Context,
|
||||||
"""Send a message in a channel as qrm. Only works within a server or DM to server, not between servers."""
|
channel: Union[cmn.GlobalChannelConverter, commands.UserConverter], *, msg: str):
|
||||||
|
"""Send a message in a channel as qrm. Accepts channel/user IDs/mentions.
|
||||||
|
Channel names are current-guild only.
|
||||||
|
Does not work with the ID of the bot user."""
|
||||||
|
if isinstance(channel, discord.ClientUser):
|
||||||
|
raise commands.BadArgument("Can't send to the bot user!")
|
||||||
await channel.send(msg)
|
await channel.send(msg)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Fun extension for qrm
|
Fun extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Grid extension for qrm
|
Grid extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Ham extension for qrm
|
Ham extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Image extension for qrm
|
Image extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
@ -9,6 +9,8 @@ General Public License, version 2.
|
|||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
|
|
||||||
@ -16,18 +18,20 @@ 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'
|
||||||
|
'&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
|
||||||
self.bandcharts = cmn.ImagesGroup(cmn.paths.bandcharts / "meta.json")
|
self.bandcharts = cmn.ImagesGroup(cmn.paths.bandcharts / "meta.json")
|
||||||
self.maps = cmn.ImagesGroup(cmn.paths.maps / "meta.json")
|
self.maps = cmn.ImagesGroup(cmn.paths.maps / "meta.json")
|
||||||
self.session = bot.qrm.session
|
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.'''
|
'''Posts an image of Frequency Allocations.'''
|
||||||
arg = region.lower()
|
async with ctx.typing():
|
||||||
|
arg = region.lower()
|
||||||
with ctx.typing():
|
|
||||||
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'
|
||||||
@ -37,25 +41,24 @@ class ImageCog(commands.Cog):
|
|||||||
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)
|
||||||
else:
|
return
|
||||||
metadata: cmn.ImageMetadata = self.bandcharts[arg]
|
metadata: cmn.ImageMetadata = self.bandcharts[arg]
|
||||||
img = discord.File(cmn.paths.bandcharts / metadata.filename,
|
img = discord.File(cmn.paths.bandcharts / metadata.filename,
|
||||||
filename=metadata.filename)
|
filename=metadata.filename)
|
||||||
if metadata.description:
|
if metadata.description:
|
||||||
embed.description = metadata.description
|
embed.description = metadata.description
|
||||||
if metadata.source:
|
if metadata.source:
|
||||||
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 an image of a ham-relevant map.'''
|
||||||
arg = map_id.lower()
|
async with ctx.typing():
|
||||||
|
arg = map_id.lower()
|
||||||
with ctx.typing():
|
|
||||||
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'
|
||||||
@ -65,36 +68,35 @@ class ImageCog(commands.Cog):
|
|||||||
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)
|
||||||
else:
|
return
|
||||||
metadata: cmn.ImageMetadata = self.maps[arg]
|
metadata: cmn.ImageMetadata = self.maps[arg]
|
||||||
img = discord.File(cmn.paths.maps / metadata.filename,
|
img = discord.File(cmn.paths.maps / metadata.filename,
|
||||||
filename=metadata.filename)
|
filename=metadata.filename)
|
||||||
if metadata.description:
|
if metadata.description:
|
||||||
embed.description = metadata.description
|
embed.description = metadata.description
|
||||||
if metadata.source:
|
if metadata.source:
|
||||||
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.'''
|
'''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'
|
async with ctx.typing():
|
||||||
'&imgsize=320&dynimg=y&opt=-p&lat=&lon=&alt=&tle=&date=0&utc=&jd=')
|
|
||||||
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(gl_url) as resp:
|
async with self.session.get(self.gl_url) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
embed.description = 'Could not download file...'
|
embed.description = 'Could not download file...'
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
else:
|
await ctx.send(embed=embed)
|
||||||
data = io.BytesIO(await resp.read())
|
return
|
||||||
embed.set_image(url=f'attachment://greyline.jpg')
|
data = io.BytesIO(await resp.read())
|
||||||
await ctx.send(embed=embed, file=discord.File(data, 'greyline.jpg'))
|
embed.set_image(url=f'attachment://greyline.jpg')
|
||||||
|
await ctx.send(embed=embed, file=discord.File(data, 'greyline.jpg'))
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: commands.Bot):
|
def setup(bot: commands.Bot):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Lookup extension for qrm
|
Lookup extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Morse Code extension for qrm
|
Morse Code extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
QRZ extension for qrm
|
QRZ extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
@ -21,7 +21,7 @@ import data.keys as keys
|
|||||||
class QRZCog(commands.Cog):
|
class QRZCog(commands.Cog):
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.session = bot.qrm.session
|
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
|
||||||
self._qrz_session_init.start()
|
self._qrz_session_init.start()
|
||||||
|
|
||||||
@commands.command(name="call", aliases=["qrz"], category=cmn.cat.lookup)
|
@commands.command(name="call", aliases=["qrz"], category=cmn.cat.lookup)
|
||||||
|
196
exts/study.py
196
exts/study.py
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Study extension for qrm
|
Study extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
@ -9,55 +9,106 @@ General Public License, version 2.
|
|||||||
|
|
||||||
import random
|
import random
|
||||||
import json
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
|
|
||||||
import common as cmn
|
import common as cmn
|
||||||
|
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'}
|
||||||
|
|
||||||
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 = bot.qrm.session
|
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, level: str = None):
|
async def _random_question(self, ctx: commands.Context, country: str = '', level: str = ''):
|
||||||
'''Gets a random question from the Technician, General, and/or Extra question pools.'''
|
'''Gets a random question from [HamStudy's](https://hamstudy.org) question pools.'''
|
||||||
tech_pool = 'E2_2018'
|
|
||||||
gen_pool = 'E3_2019'
|
|
||||||
extra_pool = 'E4_2016'
|
|
||||||
|
|
||||||
embed = cmn.embed_factory(ctx)
|
|
||||||
with ctx.typing():
|
with ctx.typing():
|
||||||
selected_pool = None
|
embed = cmn.embed_factory(ctx)
|
||||||
try:
|
|
||||||
level = level.lower()
|
|
||||||
except AttributeError: # no level given (it's None)
|
|
||||||
pass
|
|
||||||
|
|
||||||
if level in ['t', 'technician', 'tech']:
|
country = country.lower()
|
||||||
selected_pool = tech_pool
|
level = level.lower()
|
||||||
|
|
||||||
if level in ['g', 'gen', 'general']:
|
if country in study.pool_names.keys():
|
||||||
selected_pool = gen_pool
|
if level in study.pool_names[country].keys():
|
||||||
|
pool_name = study.pool_names[country][level]
|
||||||
|
|
||||||
if level in ['e', 'ae', 'extra']:
|
elif level in ("random", "r"):
|
||||||
selected_pool = extra_pool
|
# select a random level in that country
|
||||||
|
pool_name = random.choice(list(study.pool_names[country].values()))
|
||||||
|
|
||||||
if (level is None) or (level == 'all'): # no pool given or user wants all, so pick a random pool
|
else:
|
||||||
selected_pool = random.choice([tech_pool, gen_pool, extra_pool])
|
# show list of possible pools
|
||||||
if (level is not None) and (selected_pool is None): # unrecognized pool given by user
|
embed.title = "Pool Not Found!"
|
||||||
embed.title = 'Error in HamStudy command'
|
embed.description = "Possible arguments are:"
|
||||||
embed.description = ('The question pool you gave was unrecognized. '
|
embed.colour = cmn.colours.bad
|
||||||
'There are many ways to call up certain question pools - try ?rq t, g, or e. '
|
for cty in study.pool_names:
|
||||||
'\n\nNote that currently only the US question pools are available.')
|
levels = '`, `'.join(study.pool_names[cty].keys())
|
||||||
|
embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**",
|
||||||
|
value=f"Levels: `{levels}`", inline=False)
|
||||||
|
embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`")
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
elif country in ("random", "r"):
|
||||||
|
# select a random country and level
|
||||||
|
country = random.choice(list(study.pool_names.keys()))
|
||||||
|
pool_name = random.choice(list(study.pool_names[country].values()))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# show list of possible pools
|
||||||
|
embed.title = "Pool Not Found!"
|
||||||
|
embed.description = "Possible arguments are:"
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
|
for cty in study.pool_names:
|
||||||
|
levels = '`, `'.join(study.pool_names[cty].keys())
|
||||||
|
embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**",
|
||||||
|
value=f"Levels: `{levels}`", inline=False)
|
||||||
|
embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`")
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
return
|
return
|
||||||
|
|
||||||
async with self.session.get(f'https://hamstudy.org/pools/{selected_pool}') as resp:
|
pools = await self.hamstudy_get_pools()
|
||||||
|
|
||||||
|
pool_matches = [p for p in pools.keys() if "_".join(p.split("_")[:-1]) == pool_name]
|
||||||
|
|
||||||
|
if len(pool_matches) > 0:
|
||||||
|
if len(pool_matches) == 1:
|
||||||
|
pool = pool_matches[0]
|
||||||
|
else:
|
||||||
|
# look at valid_from and expires dates to find the correct one
|
||||||
|
for p in pool_matches:
|
||||||
|
valid_from = datetime.fromisoformat(pools[p]["valid_from"][:-1] + "+00:00")
|
||||||
|
expires = datetime.fromisoformat(pools[p]["expires"][:-1] + "+00:00")
|
||||||
|
|
||||||
|
if valid_from < datetime.utcnow() < expires:
|
||||||
|
pool = p
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# show list of possible pools
|
||||||
|
embed.title = "Pool Not Found!"
|
||||||
|
embed.description = "Possible arguments are:"
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
for cty in study.pool_names:
|
||||||
|
levels = '`, `'.join(study.pool_names[cty].keys())
|
||||||
|
embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**",
|
||||||
|
value=f"Levels: `{levels}`", inline=False)
|
||||||
|
embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`")
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
pool_meta = pools[pool]
|
||||||
|
|
||||||
|
async with self.session.get(f'https://hamstudy.org/pools/{pool}') as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
embed.title = 'Error in HamStudy command'
|
embed.title = 'Error in HamStudy command'
|
||||||
embed.description = 'Could not load questions'
|
embed.description = 'Could not load questions'
|
||||||
@ -71,47 +122,66 @@ class StudyCog(commands.Cog):
|
|||||||
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 = question['id']
|
embed.title = f"{study.pool_emojis[country]} {pool_meta['class']} {question['id']}"
|
||||||
embed.description = self.source
|
embed.description = self.source
|
||||||
embed.colour = cmn.colours.good
|
|
||||||
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:', value='**A:** ' + question['answers']['A']
|
embed.add_field(name='Answers:',
|
||||||
+ '\n**B:** ' + question['answers']['B']
|
value=(f"**{cmn.emojis.a}** {question['answers']['A']}"
|
||||||
+ '\n**C:** ' + question['answers']['C']
|
f"\n**{cmn.emojis.b}** {question['answers']['B']}"
|
||||||
+ '\n**D:** ' + question['answers']['D'], inline=False)
|
f"\n**{cmn.emojis.c}** {question['answers']['C']}"
|
||||||
embed.add_field(name='Answer:', value='Type _?rqa_ for answer', inline=False)
|
f"\n**{cmn.emojis.d}** {question['answers']['D']}"),
|
||||||
|
inline=False)
|
||||||
|
embed.add_field(name='To Answer:',
|
||||||
|
value=('Answer with reactions below. If not answered within 10 minutes,'
|
||||||
|
' the answer will be revealed.'),
|
||||||
|
inline=False)
|
||||||
if 'image' in question:
|
if 'image' in question:
|
||||||
image_url = f'https://hamstudy.org/_1330011/images/{selected_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)
|
||||||
self.lastq[ctx.message.channel.id] = (question['id'], question['answer'])
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
@commands.command(name="hamstudyanswer", aliases=['rqa', 'randomquestionanswer', 'randomqa', 'hamstudya'],
|
q_msg = await ctx.send(embed=embed)
|
||||||
category=cmn.cat.study)
|
|
||||||
async def _q_answer(self, ctx: commands.Context, answer: str = None):
|
await cmn.add_react(q_msg, cmn.emojis.a)
|
||||||
'''Returns the answer to question last asked (Optional argument: your answer).'''
|
await cmn.add_react(q_msg, cmn.emojis.b)
|
||||||
with ctx.typing():
|
await cmn.add_react(q_msg, cmn.emojis.c)
|
||||||
correct_ans = self.lastq[ctx.message.channel.id][1]
|
await cmn.add_react(q_msg, cmn.emojis.d)
|
||||||
q_num = self.lastq[ctx.message.channel.id][0]
|
|
||||||
embed = cmn.embed_factory(ctx)
|
def check(reaction, user):
|
||||||
if answer is not None:
|
return (user.id != self.bot.user.id
|
||||||
answer = answer.upper()
|
and reaction.message.id == q_msg.id
|
||||||
if answer == correct_ans:
|
and str(reaction.emoji) in self.choices.keys())
|
||||||
result = f'Correct! The answer to {q_num} was **{correct_ans}**.'
|
|
||||||
embed.title = f'{q_num} Answer'
|
try:
|
||||||
embed.description = f'{self.source}\n\n{result}'
|
reaction, user = await self.bot.wait_for('reaction_add', timeout=600.0, check=check)
|
||||||
embed.colour = cmn.colours.good
|
except asyncio.TimeoutError:
|
||||||
else:
|
embed.remove_field(2)
|
||||||
result = f'Incorrect. The answer to {q_num} was **{correct_ans}**, not **{answer}**.'
|
embed.add_field(name="Answer:", value=f"Timed out! The correct answer was **{question['answer']}**.")
|
||||||
embed.title = f'{q_num} Answer'
|
await q_msg.edit(embed=embed)
|
||||||
embed.description = f'{self.source}\n\n{result}'
|
else:
|
||||||
embed.colour = cmn.colours.bad
|
if self.choices[str(reaction.emoji)] == question['answer']:
|
||||||
|
embed.remove_field(2)
|
||||||
|
embed.add_field(name="Answer:", value=f"Correct! The answer was **{question['answer']}**.")
|
||||||
|
embed.colour = cmn.colours.good
|
||||||
|
await q_msg.edit(embed=embed)
|
||||||
else:
|
else:
|
||||||
result = f'The correct answer to {q_num} was **{correct_ans}**.'
|
embed.remove_field(2)
|
||||||
embed.title = f'{q_num} Answer'
|
embed.add_field(name="Answer:", value=f"Incorrect! The correct answer was **{question['answer']}**.")
|
||||||
embed.description = f'{self.source}\n\n{result}'
|
embed.colour = cmn.colours.bad
|
||||||
embed.colour = cmn.colours.neutral
|
await q_msg.edit(embed=embed)
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
async def hamstudy_get_pools(self):
|
||||||
|
async with self.session.get('https://hamstudy.org/pools/') as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
raise ConnectionError
|
||||||
|
else:
|
||||||
|
pools_dict = json.loads(await resp.read())
|
||||||
|
|
||||||
|
pools = dict()
|
||||||
|
for ls in pools_dict.values():
|
||||||
|
for pool in ls:
|
||||||
|
pools[pool["id"]] = pool
|
||||||
|
|
||||||
|
return pools
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: commands.Bot):
|
def setup(bot: commands.Bot):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Weather extension for qrm
|
Weather extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
@ -10,6 +10,8 @@ General Public License, version 2.
|
|||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
|
|
||||||
@ -21,12 +23,12 @@ class WeatherCog(commands.Cog):
|
|||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.session = bot.qrm.session
|
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.'''
|
'''Posts an image of HF Band Conditions.'''
|
||||||
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
|
||||||
@ -34,10 +36,11 @@ class WeatherCog(commands.Cog):
|
|||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
embed.description = 'Could not download file...'
|
embed.description = 'Could not download file...'
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
else:
|
await ctx.send(embed=embed)
|
||||||
data = io.BytesIO(await resp.read())
|
return
|
||||||
embed.set_image(url=f'attachment://condx.png')
|
data = io.BytesIO(await resp.read())
|
||||||
await ctx.send(embed=embed, file=discord.File(data, 'condx.png'))
|
embed.set_image(url=f'attachment://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):
|
||||||
@ -59,7 +62,7 @@ class WeatherCog(commands.Cog):
|
|||||||
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/).
|
'''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.'''
|
See help for weather command for possible location types. Add a `-c` or `-f` to use Celcius or Fahrenheit.'''
|
||||||
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:
|
||||||
@ -83,16 +86,17 @@ See help for weather command for possible location types. Add a `-c` or `-f` to
|
|||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
embed.description = 'Could not download file...'
|
embed.description = 'Could not download file...'
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
else:
|
await ctx.send(embed=embed)
|
||||||
data = io.BytesIO(await resp.read())
|
return
|
||||||
embed.set_image(url=f'attachment://wttr_forecast.png')
|
data = io.BytesIO(await resp.read())
|
||||||
await ctx.send(embed=embed, file=discord.File(data, 'wttr_forecast.png'))
|
embed.set_image(url=f'attachment://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/).
|
'''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.'''
|
See help for weather command for possible location types. Add a `-c` or `-f` to use Celcius or Fahrenheit.'''
|
||||||
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:
|
||||||
@ -116,10 +120,11 @@ See help for weather command for possible location types. Add a `-c` or `-f` to
|
|||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
embed.description = 'Could not download file...'
|
embed.description = 'Could not download file...'
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
else:
|
await ctx.send(embed=embed)
|
||||||
data = io.BytesIO(await resp.read())
|
return
|
||||||
embed.set_image(url=f'attachment://wttr_now.png')
|
data = io.BytesIO(await resp.read())
|
||||||
await ctx.send(embed=embed, file=discord.File(data, 'wttr_now.png'))
|
embed.set_image(url=f'attachment://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):
|
||||||
|
2
info.py
2
info.py
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Static info about the bot.
|
Static info about the bot.
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
|
38
main.py
38
main.py
@ -2,7 +2,7 @@
|
|||||||
"""
|
"""
|
||||||
qrm, a bot for Discord
|
qrm, a bot for Discord
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
@ -11,17 +11,19 @@ General Public License, version 2.
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
import asyncio
|
||||||
from datetime import time, datetime
|
from datetime import time, datetime
|
||||||
import random
|
import random
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
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 common as cmn
|
||||||
|
|
||||||
import info
|
import info
|
||||||
import data.options as opt
|
import data.options as opt
|
||||||
import data.keys as keys
|
import data.keys as keys
|
||||||
@ -38,13 +40,21 @@ debug_mode = opt.debug # Separate assignement in-case we define an override (te
|
|||||||
|
|
||||||
# --- Bot setup ---
|
# --- Bot setup ---
|
||||||
|
|
||||||
|
# Loop/aiohttp stuff
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
connector = loop.run_until_complete(conn.new_connector())
|
||||||
|
|
||||||
bot = commands.Bot(command_prefix=opt.prefix,
|
bot = commands.Bot(command_prefix=opt.prefix,
|
||||||
description=info.description,
|
description=info.description,
|
||||||
help_command=commands.MinimalHelpCommand())
|
help_command=commands.MinimalHelpCommand(),
|
||||||
|
loop=loop,
|
||||||
|
connector=connector)
|
||||||
|
|
||||||
|
# Simple way to access bot-wide stuff in extensions.
|
||||||
bot.qrm = SimpleNamespace()
|
bot.qrm = SimpleNamespace()
|
||||||
bot.qrm.session = aiohttp.ClientSession(headers={'User-Agent': f'discord-qrm2/{info.release}'})
|
|
||||||
|
|
||||||
|
# Let's store stuff here.
|
||||||
|
bot.qrm.connector = connector
|
||||||
bot.qrm.debug_mode = debug_mode
|
bot.qrm.debug_mode = debug_mode
|
||||||
|
|
||||||
|
|
||||||
@ -54,7 +64,6 @@ bot.qrm.debug_mode = debug_mode
|
|||||||
@commands.check(cmn.check_if_owner)
|
@commands.check(cmn.check_if_owner)
|
||||||
async def _restart_bot(ctx: commands.Context):
|
async def _restart_bot(ctx: commands.Context):
|
||||||
"""Restarts the bot."""
|
"""Restarts the bot."""
|
||||||
await bot.qrm.session.close()
|
|
||||||
global exit_code
|
global exit_code
|
||||||
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
|
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
|
||||||
print(f"[**] Restarting! Requested by {ctx.author}.")
|
print(f"[**] Restarting! Requested by {ctx.author}.")
|
||||||
@ -66,7 +75,6 @@ async def _restart_bot(ctx: commands.Context):
|
|||||||
@commands.check(cmn.check_if_owner)
|
@commands.check(cmn.check_if_owner)
|
||||||
async def _shutdown_bot(ctx: commands.Context):
|
async def _shutdown_bot(ctx: commands.Context):
|
||||||
"""Shuts down the bot."""
|
"""Shuts down the bot."""
|
||||||
await bot.qrm.session.close()
|
|
||||||
global exit_code
|
global exit_code
|
||||||
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
|
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
|
||||||
print(f"[**] Shutting down! Requested by {ctx.author}.")
|
print(f"[**] Shutting down! Requested by {ctx.author}.")
|
||||||
@ -74,7 +82,7 @@ async def _shutdown_bot(ctx: commands.Context):
|
|||||||
await bot.logout()
|
await bot.logout()
|
||||||
|
|
||||||
|
|
||||||
@bot.group(name="extctl", category=cmn.cat.admin)
|
@bot.group(name="extctl", aliases=["ex"], category=cmn.cat.admin)
|
||||||
@commands.check(cmn.check_if_owner)
|
@commands.check(cmn.check_if_owner)
|
||||||
async def _extctl(ctx: commands.Context):
|
async def _extctl(ctx: commands.Context):
|
||||||
"""Extension control commands.
|
"""Extension control commands.
|
||||||
@ -84,7 +92,7 @@ async def _extctl(ctx: commands.Context):
|
|||||||
await ctx.invoke(cmd)
|
await ctx.invoke(cmd)
|
||||||
|
|
||||||
|
|
||||||
@_extctl.command(name="list", category=cmn.cat.admin)
|
@_extctl.command(name="list", aliases=["ls"], category=cmn.cat.admin)
|
||||||
@commands.check(cmn.check_if_owner)
|
@commands.check(cmn.check_if_owner)
|
||||||
async def _extctl_list(ctx: commands.Context):
|
async def _extctl_list(ctx: commands.Context):
|
||||||
"""Lists Extensions."""
|
"""Lists Extensions."""
|
||||||
@ -94,7 +102,7 @@ async def _extctl_list(ctx: commands.Context):
|
|||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
@_extctl.command(name="load", category=cmn.cat.admin)
|
@_extctl.command(name="load", aliases=["ld"], category=cmn.cat.admin)
|
||||||
@commands.check(cmn.check_if_owner)
|
@commands.check(cmn.check_if_owner)
|
||||||
async def _extctl_load(ctx: commands.Context, extension: str):
|
async def _extctl_load(ctx: commands.Context, extension: str):
|
||||||
try:
|
try:
|
||||||
@ -105,7 +113,7 @@ async def _extctl_load(ctx: commands.Context, extension: str):
|
|||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
@_extctl.command(name="reload", aliases=["relaod"], category=cmn.cat.admin)
|
@_extctl.command(name="reload", aliases=["rl", "r", "relaod"], category=cmn.cat.admin)
|
||||||
@commands.check(cmn.check_if_owner)
|
@commands.check(cmn.check_if_owner)
|
||||||
async def _extctl_reload(ctx: commands.Context, extension: str):
|
async def _extctl_reload(ctx: commands.Context, extension: str):
|
||||||
if ctx.invoked_with == "relaod":
|
if ctx.invoked_with == "relaod":
|
||||||
@ -120,7 +128,7 @@ async def _extctl_reload(ctx: commands.Context, extension: str):
|
|||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
@_extctl.command(name="unload", category=cmn.cat.admin)
|
@_extctl.command(name="unload", aliases=["ul"], category=cmn.cat.admin)
|
||||||
@commands.check(cmn.check_if_owner)
|
@commands.check(cmn.check_if_owner)
|
||||||
async def _extctl_unload(ctx: commands.Context, extension: str):
|
async def _extctl_unload(ctx: commands.Context, extension: str):
|
||||||
try:
|
try:
|
||||||
@ -160,8 +168,11 @@ async def on_command_error(ctx: commands.Context, err: commands.CommandError):
|
|||||||
if isinstance(err, commands.UserInputError):
|
if isinstance(err, commands.UserInputError):
|
||||||
await cmn.add_react(ctx.message, cmn.emojis.warning)
|
await cmn.add_react(ctx.message, cmn.emojis.warning)
|
||||||
await ctx.send_help(ctx.command)
|
await ctx.send_help(ctx.command)
|
||||||
elif isinstance(err, commands.CommandNotFound) and not ctx.invoked_with.startswith("?"):
|
elif isinstance(err, commands.CommandNotFound):
|
||||||
await cmn.add_react(ctx.message, cmn.emojis.question)
|
if ctx.invoked_with.startswith(("?", "!")):
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
await cmn.add_react(ctx.message, cmn.emojis.question)
|
||||||
elif isinstance(err, commands.CheckFailure):
|
elif isinstance(err, commands.CheckFailure):
|
||||||
# Add handling of other subclasses of CheckFailure as needed.
|
# Add handling of other subclasses of CheckFailure as needed.
|
||||||
if isinstance(err, commands.NotOwner):
|
if isinstance(err, commands.NotOwner):
|
||||||
@ -250,6 +261,7 @@ except ConnectionResetError as ex:
|
|||||||
raise
|
raise
|
||||||
raise SystemExit("ConnectionResetError: {}".format(ex))
|
raise SystemExit("ConnectionResetError: {}".format(ex))
|
||||||
|
|
||||||
|
|
||||||
# --- Exit ---
|
# --- Exit ---
|
||||||
# Codes for the wrapper shell script:
|
# Codes for the wrapper shell script:
|
||||||
# 0 - Clean exit, don't restart
|
# 0 - Clean exit, don't restart
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Information about callsigns for the vanity prefixes command in hamcog.
|
Information about callsigns for the vanity prefixes command in hamcog.
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
A listing of morse code symbols
|
A listing of morse code symbols
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
A listing of NATO Phonetics
|
A listing of NATO Phonetics
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
A listing of Q Codes
|
A listing of Q Codes
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 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 the GNU
|
||||||
General Public License, version 2.
|
General Public License, version 2.
|
||||||
|
49
resources/study.py
Normal file
49
resources/study.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
"""
|
||||||
|
A listing of hamstudy command resources
|
||||||
|
---
|
||||||
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
|
This file is part of discord-qrmbot and is released under the terms of 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': '🇺🇸',
|
||||||
|
'ca': '🇨🇦',
|
||||||
|
'us_c': '🇺🇸 🏢'}
|
3
utils/__init__.py
Normal file
3
utils/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Various utilities for the bot.
|
||||||
|
"""
|
16
utils/connector.py
Normal file
16
utils/connector.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
Wrapper to handle aiohttp connector creation.
|
||||||
|
---
|
||||||
|
Copyright (C) 2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
|
This file is part of discord-qrm2 and is released under the terms of the GNU
|
||||||
|
General Public License, version 2.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
|
||||||
|
async def new_connector(*args, **kwargs) -> aiohttp.TCPConnector:
|
||||||
|
"""*Yes, it's just a coro to instantiate a class.*"""
|
||||||
|
return aiohttp.TCPConnector(*args, **kwargs)
|
Loading…
Reference in New Issue
Block a user