Merge pull request #359 from miaowware/qrztools-integration

convert qrzcog to use qrztools
This commit is contained in:
0x5c 2021-03-17 03:53:34 -04:00 committed by GitHub
commit bc2515c9fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 88 additions and 142 deletions

View File

@ -13,18 +13,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- The list of available prefixes to `?help` when there is more than one.
- `?donate` command to show ways to support qrm's development.
- `?invite` command to invite qrm to your server.
- Configuration options to disable showing the `?invite` and set default invite permissions (enabled by default).
- Configuration option to show QRZ nickname in place of first name (enabled by default).
### Changed
- New colour theme for `?greyline`.
- Moved great circle distance and bearing calculation from `?ungrid` to `?griddistance`.
- `?ungrid` to `?latlong`.
- Renamed `?cond` to `?solar`.
- Reduced `?hamstudy` timeout to 5 minutes.
- Library used for QRZ lookups.
### Fixed
- Weird image caching situation for `?greyline` on Discord's side.
- The help command was not using the prefix it was invoked with.
### Deprecated
- `?ungrid`.
- Deprecated old `?solar` aliases (`?cond`, etc).
- Deprecated old `?call` alias (`?qrz`).
## [2.5.1] - 2020-12-10

View File

@ -8,182 +8,120 @@ the GNU General Public License, version 2.
"""
from io import BytesIO
from typing import Dict
from datetime import datetime
import aiohttp
from lxml import etree
from qrztools import qrztools, QrzAsync, QrzError
from gridtools import Grid, LatLong
from discord.ext import commands, tasks
from discord.ext import commands
import common as cmn
import data.options as opt
import data.keys as keys
class QRZCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
self._qrz_session_init.start()
self.qrz = None
try:
if keys.qrz_user and keys.qrz_pass:
self.qrz = QrzAsync(keys.qrz_user, keys.qrz_pass, useragent="discord-qrm2",
session=aiohttp.ClientSession(connector=bot.qrm.connector))
# seed the qrz object with the previous session key, in case it already works
try:
with open("data/qrz_session") as qrz_file:
self.qrz.session_key = qrz_file.readline().strip()
except FileNotFoundError:
pass
except AttributeError:
pass
@commands.command(name="call", aliases=["qrz"], category=cmn.cat.lookup)
async def _qrz_lookup(self, ctx: commands.Context, callsign: str, *flags):
"""Looks up a callsign on [QRZ.com](https://www.qrz.com/). Add `--link` to only link the QRZ page."""
flags = [f.lower() for f in flags]
if not callsign.isalnum():
embed = cmn.embed_factory(ctx)
embed.title = "QRZ Data for Callsign"
embed.colour = cmn.colours.bad
embed.description = "Not a valid callsign!"
await ctx.send(embed=embed)
return
if keys.qrz_user == "" or keys.qrz_pass == "" or "--link" in flags:
if self.qrz is None or "--link" in flags:
if ctx.invoked_with == "qrz":
await ctx.send("⚠️ **Deprecated Command Alias**\n"
f"This command has been renamed to `{ctx.prefix}call`!\n"
"This alias will be removed in the next version.")
await ctx.send(f"http://qrz.com/db/{callsign}")
return
embed = cmn.embed_factory(ctx)
embed.title = f"QRZ Data for {callsign.upper()}"
if ctx.invoked_with == "qrz":
embed.description = ("⚠️ **Deprecated Command Alias**\n"
f"This command has been renamed to `{ctx.prefix}call`!\n"
"This alias will be removed in the next version.")
async with ctx.typing():
try:
await qrz_test_session(self.key, self.session)
except ConnectionError:
await self.get_session()
data = await self.qrz.get_callsign(callsign)
except QrzError as e:
embed.colour = cmn.colours.bad
embed.description = str(e)
await ctx.send(embed=embed)
return
url = f"http://xmldata.qrz.com/xml/current/?s={self.key};callsign={callsign}"
async with self.session.get(url) as resp:
if resp.status != 200:
raise ConnectionError(f"Unable to connect to QRZ (HTTP Error {resp.status})")
with BytesIO(await resp.read()) as resp_file:
resp_xml = etree.parse(resp_file).getroot()
resp_xml_session = resp_xml.xpath("/x:QRZDatabase/x:Session", namespaces={"x": "http://xmldata.qrz.com"})
resp_session = {el.tag.split("}")[1]: el.text for el in resp_xml_session[0].getiterator()}
if "Error" in resp_session:
if "Session Timeout" in resp_session["Error"]:
await self.get_session()
await self._qrz_lookup(ctx, callsign)
return
if "Not found" in resp_session["Error"]:
embed = cmn.embed_factory(ctx)
embed.title = f"QRZ Data for {callsign.upper()}"
embed.colour = cmn.colours.bad
embed.description = "No data found!"
await ctx.send(embed=embed)
return
raise ValueError(resp_session["Error"])
resp_xml_data = resp_xml.xpath("/x:QRZDatabase/x:Callsign", namespaces={"x": "http://xmldata.qrz.com"})
resp_data = {el.tag.split("}")[1]: el.text for el in resp_xml_data[0].getiterator()}
embed = cmn.embed_factory(ctx)
embed.title = f"QRZ Data for {resp_data['call']}"
embed.title = f"QRZ Data for {data.call}"
embed.colour = cmn.colours.good
embed.url = f"http://www.qrz.com/db/{resp_data['call']}"
if "image" in resp_data:
embed.set_thumbnail(url=resp_data["image"])
embed.url = data.url
if data.image != qrztools.QrzImage():
embed.set_thumbnail(url=data.image.url)
data = qrz_process_info(resp_data)
for title, val in data.items():
for title, val in qrz_process_info(data).items():
if val is not None:
embed.add_field(name=title, value=val, inline=True)
await ctx.send(embed=embed)
async def get_session(self):
"""Session creation and caching."""
self.key = await qrz_login(keys.qrz_user, keys.qrz_pass, self.session)
with open("data/qrz_session", "w") as qrz_file:
qrz_file.write(self.key)
@tasks.loop(count=1)
async def _qrz_session_init(self):
"""Helper task to allow obtaining a session at cog instantiation."""
try:
with open("data/qrz_session") as qrz_file:
self.key = qrz_file.readline().strip()
await qrz_test_session(self.key, self.session)
except (FileNotFoundError, ConnectionError):
await self.get_session()
async def qrz_login(user: str, passwd: str, session: aiohttp.ClientSession):
url = f"http://xmldata.qrz.com/xml/current/?username={user};password={passwd};agent=discord-qrm2"
async with session.get(url) as resp:
if resp.status != 200:
raise ConnectionError(f"Unable to connect to QRZ (HTTP Error {resp.status})")
with BytesIO(await resp.read()) as resp_file:
resp_xml = etree.parse(resp_file).getroot()
resp_xml_session = resp_xml.xpath("/x:QRZDatabase/x:Session", namespaces={"x": "http://xmldata.qrz.com"})
resp_session = {el.tag.split("}")[1]: el.text for el in resp_xml_session[0].getiterator()}
if "Error" in resp_session:
raise ConnectionError(resp_session["Error"])
if resp_session["SubExp"] == "non-subscriber":
raise ConnectionError("Invalid QRZ Subscription")
return resp_session["Key"]
async def qrz_test_session(key: str, session: aiohttp.ClientSession):
url = f"http://xmldata.qrz.com/xml/current/?s={key}"
async with session.get(url) as resp:
if resp.status != 200:
raise ConnectionError(f"Unable to connect to QRZ (HTTP Error {resp.status})")
with BytesIO(await resp.read()) as resp_file:
resp_xml = etree.parse(resp_file).getroot()
resp_xml_session = resp_xml.xpath("/x:QRZDatabase/x:Session", namespaces={"x": "http://xmldata.qrz.com"})
resp_session = {el.tag.split("}")[1]: el.text for el in resp_xml_session[0].getiterator()}
if "Error" in resp_session:
raise ConnectionError(resp_session["Error"])
def qrz_process_info(data: dict):
if "name" in data:
if "fname" in data:
name = data["fname"] + " " + data["name"]
def qrz_process_info(data: qrztools.QrzCallsignData) -> Dict:
if data.name != qrztools.Name():
if opt.qrz_only_nickname:
if data.name.nickname:
name = data.name.nickname + " " + data.name.name
elif data.name.first:
name = data.name.first + " " + data.name.name
else:
name = data.name.name
else:
name = data["name"]
name = data.name.formatted_name
else:
name = None
if "state" in data:
state = f", {data['state']}"
else:
state = ""
address = data.get("addr1", "") + "\n" + data.get("addr2", "") + state + " " + data.get("zip", "")
address = address.strip()
if address == "":
address = None
if "eqsl" in data:
eqsl = "Yes" if data["eqsl"] == "1" else "No"
else:
eqsl = "Unknown"
if "mqsl" in data:
mqsl = "Yes" if data["mqsl"] == "1" else "No"
else:
mqsl = "Unknown"
if "lotw" in data:
lotw = "Yes" if data["lotw"] == "1" else "No"
else:
lotw = "Unknown"
return {"Name": name,
"Country": data.get("country", None),
"Address": address,
"Grid Square": data.get("grid", None),
"County": data.get("county", None),
"CQ Zone": data.get("cqzone", None),
"ITU Zone": data.get("ituzone", None),
"IOTA Designator": data.get("iota", None),
"Expires": data.get("expdate", None),
"Aliases": data.get("aliases", None),
"Previous Callsign": data.get("p_call", None),
"License Class": data.get("class", None),
"Trustee": data.get("trustee", None),
"eQSL?": eqsl,
"Paper QSL?": mqsl,
"LotW?": lotw,
"QSL Info": data.get("qslmgr", None),
"Born": data.get("born", None)}
if data.address != qrztools.Address():
state = ", " + data.address.state + " " if data.address.state else ""
address = "\n".join([data.address.attn, data.address.line1, data.address.line2 + state, data.address.zip])
else:
address = None
return {
"Name": name,
"Country": data.address.country,
"Address": address,
"Grid Square": data.grid if data.grid != Grid(LatLong(0, 0)) else None,
"County": data.county if data.county else None,
"CQ Zone": data.cq_zone if data.cq_zone else None,
"ITU Zone": data.itu_zone if data.itu_zone else None,
"IOTA Designator": data.iota if data.iota else None,
"Expires": f"{data.expire_date:%Y-%m-%d}" if data.expire_date != datetime.min else None,
"Aliases": ", ".join(data.aliases) if data.aliases else None,
"Previous Callsign": data.prev_call if data.prev_call else None,
"License Class": data.lic_class if data.lic_class else None,
"Trustee": data.trustee if data.trustee else None,
"eQSL?": "Yes" if data.eqsl else "No",
"Paper QSL?": "Yes" if data.mail_qsl else "No",
"LotW?": "Yes" if data.lotw_qsl else "No",
"QSL Info": data.qsl_manager if data.qsl_manager else None,
"Born": f"{data.born:%Y-%m-%d}" if data.born != datetime.min else None
}
def setup(bot):

View File

@ -1,7 +1,7 @@
discord.py~=1.5.0
ctyparser~=2.0
gridtools~=1.0
qrztools[async]~=1.0
beautifulsoup4
lxml
pytz
cairosvg

View File

@ -46,6 +46,10 @@ exts = [
"propagation",
]
# If True (default): when doing QRZ callsign lookups, show the nickname in place of the first name, if it exists
# if False: use QRZ's default name format
qrz_only_nickname = True
# enable a command that provides a link to add the bot to a server
enable_invite_cmd = True