mirror of
https://github.com/miaowware/qrm2.git
synced 2025-04-13 23:18:33 -04:00
Merge pull request #359 from miaowware/qrztools-integration
convert qrzcog to use qrztools
This commit is contained in:
commit
bc2515c9fc
@ -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
|
||||
|
220
exts/qrz.py
220
exts/qrz.py
@ -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):
|
||||
|
@ -1,7 +1,7 @@
|
||||
discord.py~=1.5.0
|
||||
ctyparser~=2.0
|
||||
gridtools~=1.0
|
||||
qrztools[async]~=1.0
|
||||
beautifulsoup4
|
||||
lxml
|
||||
pytz
|
||||
cairosvg
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user