mirror of
https://github.com/miaowware/qrm2.git
synced 2026-06-02 22:14:53 -04:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fed97a03b3 | |||
| 5f796d479e | |||
| 3ba55d4c35 | |||
| f4ed93dc76 | |||
| 2cb4b03532 | |||
| bc93462c29 | |||
| 6867c45c8c | |||
| dcbb7acab8 | |||
| 3803ce6045 | |||
| 8ca4911072 | |||
| 6e2468f04f | |||
| b17a8a1749 | |||
| 8dfa7001ef | |||
| f6ed8430b9 | |||
| d650cbd6c1 | |||
| 4d9f9d1b19 | |||
| 1c649aacc2 | |||
| 2049ca9fca |
@@ -0,0 +1,58 @@
|
||||
name: Docker Build and Push
|
||||
|
||||
on:
|
||||
push:
|
||||
# Publish `master` as Docker `dev` image.
|
||||
branches:
|
||||
- master
|
||||
# Publish `v*` tags as releases and as `latest`.
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
IMAGE_NAME: qrm2
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Build image
|
||||
run: |
|
||||
echo ${{ github.sha }} > git_commit
|
||||
docker build . --file Dockerfile -t $IMAGE_NAME
|
||||
|
||||
- name: Log into Github Package Registry
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin
|
||||
|
||||
- name: Log into Docker Hub
|
||||
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||
|
||||
- name: Push image to registries
|
||||
run: |
|
||||
GITHUB_IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME
|
||||
DOCKER_IMAGE_ID=${{ secrets.DOCKER_USERNAME }}/$IMAGE_NAME
|
||||
|
||||
# Strip git ref prefix from version
|
||||
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
|
||||
|
||||
# Strip "v" prefix from tag name
|
||||
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
|
||||
|
||||
[[ "$VERSION" == "master" ]] && VERSION=dev
|
||||
|
||||
echo GITHUB_IMAGE_ID=$GITHUB_IMAGE_ID
|
||||
echo DOCKER_IMAGE_ID=$DOCKER_IMAGE_ID
|
||||
echo VERSION=$VERSION
|
||||
|
||||
# tag for Github Registry
|
||||
docker tag $IMAGE_NAME $GITHUB_IMAGE_ID:$VERSION
|
||||
[[ "$VERSION" != "dev" ]] && docker tag $IMAGE_NAME $GITHUB_IMAGE_ID:latest
|
||||
docker push $GITHUB_IMAGE_ID
|
||||
|
||||
# tag for Docker Hub
|
||||
docker tag $IMAGE_NAME $DOCKER_IMAGE_ID:$VERSION
|
||||
[[ "$VERSION" != "dev" ]] && docker tag $IMAGE_NAME $DOCKER_IMAGE_ID:latest
|
||||
docker push $DOCKER_IMAGE_ID
|
||||
+30
-1
@@ -7,6 +7,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
## [2.3.1] - 2020-04-02
|
||||
### Fixed
|
||||
- Wordlist containing innappropriate words.
|
||||
|
||||
|
||||
## [2.3.0] - 2020-03-30
|
||||
### Added
|
||||
- `?phoneticweight` command, which calculates a message's length in syllables.
|
||||
- `?standards` command to display [xkcd 927](https://xkcd.com/927/).
|
||||
### Changed
|
||||
- Python>=3.7 now required.
|
||||
|
||||
|
||||
## [2.2.3] - 2020-03-29
|
||||
### Fixed
|
||||
- Commands are no longer case-sensitive.
|
||||
|
||||
|
||||
## [2.2.2] - 2020-02-25
|
||||
### Fixed
|
||||
- Fixed issue where HamStudy questions with images would cause an error.
|
||||
- Added/fixed/removed typing indicators in numerous commands.
|
||||
|
||||
|
||||
## [2.2.1] - 2020-02-20
|
||||
### Fixed
|
||||
- Fixed issue where some HamStudy pools will become unselectable.
|
||||
@@ -81,7 +105,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
## 1.0.0 - 2019-07-31 [YANKED]
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/miaowware/qrm2/compare/v2.2.0...HEAD
|
||||
[Unreleased]: https://github.com/miaowware/qrm2/compare/v2.3.1...HEAD
|
||||
[2.3.1]: https://github.com/miaowware/qrm2/releases/tag/v2.3.1
|
||||
[2.3.0]: https://github.com/miaowware/qrm2/releases/tag/v2.3.0
|
||||
[2.2.3]: https://github.com/miaowware/qrm2/releases/tag/v2.2.3
|
||||
[2.2.2]: https://github.com/miaowware/qrm2/releases/tag/v2.2.2
|
||||
[2.2.1]: https://github.com/miaowware/qrm2/releases/tag/v2.2.1
|
||||
[2.2.0]: https://github.com/miaowware/qrm2/releases/tag/v2.2.0
|
||||
[2.1.0]: https://github.com/miaowware/qrm2/releases/tag/v2.1.0
|
||||
[2.0.0]: https://github.com/miaowware/qrm2/releases/tag/v2.0.0
|
||||
|
||||
@@ -14,6 +14,8 @@ See [README-DOCKER.md](./README-DOCKER.md)
|
||||
|
||||
### Without Docker
|
||||
|
||||
Requires Python 3.7 or newer.
|
||||
|
||||
Prep the environment. For more information on extra options, see the [quick-bot-no-pain Makefile documentation](https://github.com/0x5c/quick-bot-no-pain/blob/master/docs/makefile.md).
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"_id": "56956f51f65e5c590272e372",
|
||||
"appears": "2016-04-01T06:00:00.000Z",
|
||||
"class": "Amateur Extra",
|
||||
"subtext": "Expires Jul 1, 2020",
|
||||
"valid_from": "2016-07-01T06:00:00.000Z",
|
||||
"expires": "2020-07-01T06:00:00.000Z",
|
||||
"official_name": "Element 4",
|
||||
"id": "E4_2016",
|
||||
"slug": "extra2016",
|
||||
"passing": 37,
|
||||
"year": 2016,
|
||||
"pool": [{
|
||||
"_id": "5cd63f15910d9b003d545bd7",
|
||||
"qcount": 4,
|
||||
"id": "E5",
|
||||
"name": "ELECTRICAL PRINCIPLES",
|
||||
"sections": [{
|
||||
"_id": "5cd63f15910d9b003d545beb",
|
||||
"id": "E5C",
|
||||
"questions": [{
|
||||
"_id": "5cd63f15910d9b003d545bef",
|
||||
"keywords": ["4"],
|
||||
"answer": "B",
|
||||
"answers": {
|
||||
"A": "Point 2",
|
||||
"B": "Point 4",
|
||||
"C": "Point 5",
|
||||
"D": "Point 6"
|
||||
},
|
||||
"fccpart": "",
|
||||
"id": "E5C14",
|
||||
"image": "E5-2.png",
|
||||
"text": "Which point on Figure E5-2 best represents the impedance of a series circuit consisting of a 400 ohm resistor and a 38 picofarad capacitor at 14 MHz?"
|
||||
}],
|
||||
"summary": "Coordinate systems and phasors in electronics: Rectangular Coordinates; Polar Coordinates; Phasors"
|
||||
}]
|
||||
}],
|
||||
"updated": "2019-05-11T03:18:46.121Z",
|
||||
"category": "default",
|
||||
"testIdEnd": 19999,
|
||||
"testIdStart": 10000,
|
||||
"__v": 12,
|
||||
"mat_icon": "flash_on",
|
||||
"tagline": "Serious General operators only! This is the most advanced US license class!",
|
||||
"keywords": ["ham radio extra test prep", "amateur extra class radio study", "amateur extra class ham exam", "ham radio amateur extra test 2016", "2016 amateur extra class", "ham radio license exam", "extra class flash card"],
|
||||
"replaces": "E4_2012"
|
||||
}
|
||||
+357
-296
@@ -16,8 +16,10 @@ the GNU General Public License, version 2.
|
||||
# KC4USA: reserved, no call history, *but* has application history
|
||||
|
||||
|
||||
import re
|
||||
|
||||
import aiohttp
|
||||
from bs4 import BeautifulSoup
|
||||
import ae7qparser
|
||||
|
||||
import discord.ext.commands as commands
|
||||
|
||||
@@ -29,7 +31,7 @@ class AE7QCog(commands.Cog):
|
||||
self.bot = bot
|
||||
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
|
||||
|
||||
@commands.group(name="ae7q", aliases=["ae"], category=cmn.cat.lookup)
|
||||
@commands.group(name="ae7q", aliases=["ae"], case_insensitive=True, category=cmn.cat.lookup)
|
||||
async def _ae7q_lookup(self, ctx: commands.Context):
|
||||
"""Looks up a callsign, FRN, or Licensee ID on [ae7q.com](http://ae7q.com/)."""
|
||||
if ctx.invoked_subcommand is None:
|
||||
@@ -40,64 +42,86 @@ class AE7QCog(commands.Cog):
|
||||
"""Looks up the history of 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:
|
||||
raise cmn.BotHTTPError(resp)
|
||||
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}`")
|
||||
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 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:])
|
||||
call_data = ae7qparser.get_call(callsign)
|
||||
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"AE7Q History for {callsign}"
|
||||
embed.title = f"AE7Q Callsign History for {callsign}"
|
||||
embed.url = call_data.query_url
|
||||
embed.colour = cmn.colours.good
|
||||
embed.url = base_url + callsign
|
||||
embed.description = ""
|
||||
|
||||
# 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 isinstance(call_data, ae7qparser.Ae7qCallData):
|
||||
if call_data.conditions:
|
||||
embed.description = " ".join([
|
||||
" ".join([y.strip() for y in x]) for x in call_data.conditions
|
||||
]).replace(callsign, f"`{callsign}`")
|
||||
|
||||
if len(table) > 3:
|
||||
desc += f"\nRecords 1 to 3 of {len(table)}. See ae7q.com for more..."
|
||||
if not call_data.call_history:
|
||||
if not call_data.event_callsign_history:
|
||||
embed.colour = cmn.colours.bad
|
||||
embed.description += f"\nNo records found for `{callsign}`"
|
||||
else:
|
||||
# add the first five rows of the event callsign history to the embed
|
||||
for row in call_data.event_callsign_history[0:5]:
|
||||
header = f"**{row.start_date:%Y-%m-%d}-{row.end_date:%Y-%m-%d}**"
|
||||
body = (f"Requestor: *{row.entity_name} ({row.callsign})*\n"
|
||||
f"Event: *{row.event_name}*")
|
||||
embed.add_field(name=header, value=body, inline=False)
|
||||
|
||||
embed.description = desc
|
||||
if len(call_data.event_callsign_history) > 5:
|
||||
embed.description += (f"\nRecords 1 to 5 of {len(call_data.event_callsign_history)}. "
|
||||
f"See [ae7q.com]({call_data.query_url}) for more...")
|
||||
|
||||
else:
|
||||
# add the first three rows of the callsign history to the embed
|
||||
for row in call_data.call_history[0:3]:
|
||||
header = f"**{row.entity_name}** ({row.applicant_type})"
|
||||
body = (f"Class: *{row.operator_class}*\n"
|
||||
f"Region: *{row.region_state}*\n"
|
||||
f"Status: *{row.license_status}*\n")
|
||||
if row.grant_date:
|
||||
body += f"Granted: *{row.grant_date:%Y-%m-%d}*\n"
|
||||
if row.effective_date:
|
||||
body += f"Effective: *{row.effective_date:%Y-%m-%d}*\n"
|
||||
if row.cancel_date:
|
||||
body += f"Cancelled: *{row.cancel_date:%Y-%m-%d}*\n"
|
||||
if row.expire_date:
|
||||
body += f"Expires: *{row.expire_date:%Y-%m-%d}*\n"
|
||||
|
||||
embed.add_field(name=header, value=body, inline=False)
|
||||
|
||||
if len(call_data.call_history) > 3:
|
||||
embed.description += (f"\nRecords 1 to 3 of {len(call_data.call_history)}. "
|
||||
f"See [ae7q.com]({call_data.query_url}) for more...")
|
||||
|
||||
elif isinstance(call_data, ae7qparser.Ae7qCanadianCallData):
|
||||
if not call_data.callsign_data:
|
||||
embed.colour = cmn.colours.bad
|
||||
embed.description += f"\nNo records found for `{callsign}`"
|
||||
else:
|
||||
if call_data.given_names != "" and call_data.surname != "":
|
||||
embed.add_field(name="Name", value=f"{call_data.given_names} {call_data.surname}", inline=True)
|
||||
if call_data.address != "":
|
||||
embed.add_field(name="Address", value=call_data.address, inline=True)
|
||||
if call_data.locality != "":
|
||||
embed.add_field(name="Locality", value=call_data.locality, inline=True)
|
||||
if call_data.province != "":
|
||||
embed.add_field(name="Province", value=call_data.province, inline=True)
|
||||
if call_data.postal_code != "":
|
||||
embed.add_field(name="Postal Code", value=call_data.postal_code, inline=True)
|
||||
if call_data.country != "":
|
||||
embed.add_field(name="Country", value=call_data.country, inline=True)
|
||||
if call_data.region != "":
|
||||
embed.add_field(name="Region", value=call_data.region, inline=True)
|
||||
if call_data.grid_square != "":
|
||||
embed.add_field(name="Grid Square", value=call_data.grid_square, inline=True)
|
||||
if call_data.qualifications != "":
|
||||
embed.add_field(name="License Qualifications", value=call_data.qualifications, inline=True)
|
||||
|
||||
else:
|
||||
embed.colour = cmn.colours.bad
|
||||
embed.description += f"\nNo records found for `{callsign}`"
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@@ -106,202 +130,283 @@ class AE7QCog(commands.Cog):
|
||||
"""Looks up the licenses for which a licensee is trustee 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:
|
||||
raise cmn.BotHTTPError(resp)
|
||||
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")]
|
||||
|
||||
try:
|
||||
table = tables[2] if len(tables[0][0]) == 1 else tables[1]
|
||||
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
|
||||
|
||||
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"):
|
||||
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
|
||||
|
||||
table = await process_table(table[2:])
|
||||
call_data = ae7qparser.get_call(callsign)
|
||||
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"AE7Q Trustee History for {callsign}"
|
||||
embed.url = call_data.query_url
|
||||
embed.colour = cmn.colours.good
|
||||
embed.url = base_url + callsign
|
||||
embed.description = ""
|
||||
|
||||
# add the first three rows of the table to the embed
|
||||
for row in table[0:3]:
|
||||
header = f"**{row[0]}** ({row[3]})" # **Name** (Applicant Type)
|
||||
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 isinstance(call_data, ae7qparser.Ae7qCallData):
|
||||
if not call_data.trustee_history:
|
||||
embed.colour = cmn.colours.bad
|
||||
embed.description += f"\nNo records found for `{callsign}`"
|
||||
else:
|
||||
# add the first three rows of the trustee history to the embed
|
||||
for row in call_data.trustee_history[0:3]:
|
||||
header = f"**{row.callsign}** ({row.applicant_type})"
|
||||
body = (f"Name: *{row.entity_name}*\n"
|
||||
f"Region: *{row.region_state}*\n"
|
||||
f"Status: *{row.license_status}*\n")
|
||||
if row.grant_date:
|
||||
body += f"Granted: *{row.grant_date:%Y-%m-%d}*\n"
|
||||
if row.effective_date:
|
||||
body += f"Effective: *{row.effective_date:%Y-%m-%d}*\n"
|
||||
if row.cancel_date:
|
||||
body += f"Cancelled: *{row.cancel_date:%Y-%m-%d}*\n"
|
||||
if row.expire_date:
|
||||
body += f"Expires: *{row.expire_date:%Y-%m-%d}*\n"
|
||||
|
||||
if len(table) > 3:
|
||||
desc += f"\nRecords 1 to 3 of {len(table)}. See ae7q.com for more..."
|
||||
embed.add_field(name=header, value=body, inline=False)
|
||||
|
||||
embed.description = desc
|
||||
if len(call_data.trustee_history) > 3:
|
||||
embed.description += (f"\nRecords 1 to 3 of {len(call_data.trustee_history)}. "
|
||||
f"See [ae7q.com]({call_data.query_url}) for more...")
|
||||
|
||||
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):
|
||||
"""Looks 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:
|
||||
raise cmn.BotHTTPError(resp)
|
||||
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}"
|
||||
else:
|
||||
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)
|
||||
"""
|
||||
raise NotImplementedError("Application history lookup not yet supported. "
|
||||
"Check back in a later version of the bot.")
|
||||
|
||||
@_ae7q_lookup.command(name="applications", aliases=["a", "apps"], category=cmn.cat.lookup)
|
||||
async def _ae7q_applications(self, ctx: commands.Context, query: str):
|
||||
"""Looks up the application history for a callsign, FRN, or Licensee ID on [ae7q.com](http://ae7q.com/)."""
|
||||
with ctx.typing():
|
||||
query = query.upper()
|
||||
|
||||
# LID
|
||||
if re.match(r"L\d+", query):
|
||||
data = ae7qparser.get_licensee_id(query)
|
||||
# FRN
|
||||
elif re.match(r"\d{10}", query):
|
||||
data = ae7qparser.get_frn(query)
|
||||
# callsign
|
||||
else:
|
||||
data = ae7qparser.get_call(query)
|
||||
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"AE7Q Application History for {query}"
|
||||
embed.url = data.query_url
|
||||
embed.colour = cmn.colours.good
|
||||
embed.description = ""
|
||||
|
||||
if not data.application_history:
|
||||
embed.colour = cmn.colours.bad
|
||||
embed.description += f"\nNo records found for `{query}`"
|
||||
else:
|
||||
# add the first three rows of the app history to the embed
|
||||
if isinstance(data.application_history, ae7qparser.ApplicationsHistoryTable):
|
||||
for row in data.application_history[0:3]:
|
||||
header = f"**{row.uls_file_number[0]}** ({row.receipt_date:%Y-%m-%d})"
|
||||
body = (f"Name: *{row.entity_name}*\n"
|
||||
f"Callsign: *{row.application_callsign}*\n"
|
||||
f"Region: *{row.region_state}*\n"
|
||||
f"Purpose: *{row.application_purpose}*\n")
|
||||
if row.payment_date:
|
||||
body += f"Payment: *{row.payment_date:%Y-%m-%d}*\n"
|
||||
if row.last_action_date:
|
||||
body += f"Last Action: *{row.last_action_date:%Y-%m-%d}*\n"
|
||||
body += f"Status: *{row.application_status}*"
|
||||
embed.add_field(name=header, value=body, inline=False)
|
||||
|
||||
elif isinstance(data.application_history, ae7qparser.VanityApplicationsHistoryTable):
|
||||
for row in data.application_history[0:3]:
|
||||
header = f"**{row.uls_file_number[0]}** ({row.receipt_date:%Y-%m-%d})"
|
||||
body = (f"Callsign: *{row.application_callsign}*\n"
|
||||
f"Region: *{row.region_state}*\n"
|
||||
f"Operator Class: *{row.operator_class}*\n"
|
||||
f"Purpose: *{row.application_purpose}*\n")
|
||||
if row.payment_date:
|
||||
body += f"Payment: *{row.payment_date:%Y-%m-%d}*\n"
|
||||
if row.last_action_date:
|
||||
body += f"Last Action: *{row.last_action_date:%Y-%m-%d}*\n"
|
||||
body += f"Status: *{row.application_status}*\n"
|
||||
if row.applied_callsigns:
|
||||
body += (f"Callsign{'s' if len(row.applied_callsigns) > 1 else ''} "
|
||||
f"Applied For: *{', '.join(row.applied_callsigns)}*")
|
||||
embed.add_field(name=header, value=body, inline=False)
|
||||
|
||||
if len(data.application_history) > 3:
|
||||
embed.description += (f"\nRecords 1 to 3 of {len(data.application_history)}. "
|
||||
f"See [ae7q.com]({data.query_url}) for more...")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@_ae7q_lookup.command(name="pending_apps", aliases=["pa"], category=cmn.cat.lookup)
|
||||
async def _ae7q_pending_applications(self, ctx: commands.Context, query: str):
|
||||
"""Looks up the pending applications for a callsign, FRN, or Licensee ID on [ae7q.com](http://ae7q.com/)."""
|
||||
with ctx.typing():
|
||||
query = query.upper()
|
||||
|
||||
# LID
|
||||
if re.match(r"L\d+", query):
|
||||
data = ae7qparser.get_licensee_id(query)
|
||||
# FRN
|
||||
elif re.match(r"\d{10}", query):
|
||||
data = ae7qparser.get_frn(query)
|
||||
# callsign
|
||||
else:
|
||||
data = ae7qparser.get_call(query)
|
||||
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"AE7Q Pending Applications for {query}"
|
||||
embed.url = data.query_url
|
||||
embed.colour = cmn.colours.good
|
||||
embed.description = ""
|
||||
|
||||
if not data.pending_applications:
|
||||
embed.colour = cmn.colours.bad
|
||||
embed.description += f"\nNo records found for `{query}`"
|
||||
else:
|
||||
# add the first three rows of the pending apps to the embed
|
||||
if isinstance(data.pending_applications, ae7qparser.PendingApplicationsPredictionsTable):
|
||||
for row in data.pending_applications[0:3]:
|
||||
header = f"**{row.uls_file_number}** ({row.receipt_date:%Y-%m-%d})"
|
||||
body = (f"Callsign: *{row.applicant_callsign}*\n"
|
||||
f"Region: *{row.region_state}*\n"
|
||||
f"Operator Class: *{row.operator_class}*\n"
|
||||
f"Vanity Type: *{row.vanity_type}*\n")
|
||||
if row.process_date:
|
||||
body += f"Processes On: *{row.process_date:%Y-%m-%d}*\n"
|
||||
body += (f"Sequential #: *{row.sequential_number}*\n"
|
||||
f"Vanity Callsign: *{row.vanity_callsign}*\n"
|
||||
f"Prediction: *{row.prediction}*")
|
||||
embed.add_field(name=header, value=body, inline=False)
|
||||
|
||||
elif isinstance(data.pending_applications, ae7qparser.CallsignPendingApplicationsPredictionsTable):
|
||||
for row in data.pending_applications[0:3]:
|
||||
header = f"**{row.uls_file_number}** ({row.receipt_date:%Y-%m-%d})"
|
||||
body = (f"Callsign: *{row.applicant_callsign}*\n"
|
||||
f"Region: *{row.region_state}*\n"
|
||||
f"Operator Class: *{row.operator_class}*\n"
|
||||
f"Vanity Type: *{row.vanity_type}*\n")
|
||||
if row.process_date:
|
||||
body += f"Processes On: *{row.process_date:%Y-%m-%d}*\n"
|
||||
body += (f"Sequential #: *{row.sequential_number}*\n"
|
||||
f"Prediction: *{row.prediction}*")
|
||||
embed.add_field(name=header, value=body, inline=False)
|
||||
|
||||
if len(data.pending_applications) > 3:
|
||||
embed.description += (f"\nRecords 1 to 3 of {len(data.pending_applications)}. "
|
||||
f"See [ae7q.com]({data.query_url}) for more...")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@_ae7q_lookup.command(name="app_detail", aliases=["ad"], category=cmn.cat.lookup)
|
||||
async def _ae7q_app_detail(self, ctx: commands.Context, ufn: str):
|
||||
"""Looks up the application data for a ULS file number on [ae7q.com](http://ae7q.com/)."""
|
||||
with ctx.typing():
|
||||
app_data = ae7qparser.get_application(ufn)
|
||||
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"AE7Q Application Data for {ufn}"
|
||||
embed.url = app_data.query_url
|
||||
embed.colour = cmn.colours.good
|
||||
|
||||
if not app_data.application_data:
|
||||
embed.colour = cmn.colours.bad
|
||||
embed.description += f"\nNo records found for `{ufn}`"
|
||||
else:
|
||||
if app_data.frn:
|
||||
embed.add_field(name="FRN", value=app_data.frn, inline=True)
|
||||
if app_data.licensee_id:
|
||||
embed.add_field(name="Licensee ID", value=app_data.licensee_id, inline=True)
|
||||
if app_data.applicant_type:
|
||||
embed.add_field(name="Applicant Type", value=app_data.applicant_type, inline=True)
|
||||
if app_data.entity_type:
|
||||
embed.add_field(name="Entity Type", value=app_data.entity_type, inline=True)
|
||||
if app_data.entity_name:
|
||||
embed.add_field(name="Name", value=app_data.entity_name, inline=True)
|
||||
if app_data.callsign:
|
||||
embed.add_field(name="Callsign", value=app_data.callsign, inline=True)
|
||||
|
||||
address = f"ATTN: {app_data.attention}\n" if app_data.attention else ""
|
||||
address += f"{app_data.street_address}\n" if app_data.street_address else ""
|
||||
address += f"{app_data.po_box}\n" if app_data.po_box else ""
|
||||
address += (f"{', '.join([app_data.locality, app_data.state.split('-')[0].strip()])}"
|
||||
f" {app_data.postal_code}")
|
||||
if address:
|
||||
embed.add_field(name="Address", value=address, inline=True)
|
||||
if app_data.county:
|
||||
embed.add_field(name="County", value=app_data.county, inline=True)
|
||||
if app_data.maidenhead:
|
||||
embed.add_field(name="Grid Square", value=app_data.maidenhead, inline=True)
|
||||
if app_data.uls_geo_region:
|
||||
embed.add_field(name="Region", value=app_data.uls_geo_region, inline=True)
|
||||
if app_data.last_action_date:
|
||||
embed.add_field(name="Last Action", value=f"{app_data.last_action_date:%Y-%m-%d}", inline=True)
|
||||
if app_data.receipt_date:
|
||||
embed.add_field(name="Receipt Date", value=f"{app_data.receipt_date:%Y-%m-%d}", inline=True)
|
||||
if app_data.entered_timestamp:
|
||||
embed.add_field(name="Entered Time", value=f"{app_data.entered_timestamp:%Y-%m-%d}", inline=True)
|
||||
if app_data.application_source:
|
||||
embed.add_field(name="Application Source", value=app_data.application_source, inline=True)
|
||||
if app_data.application_purpose:
|
||||
embed.add_field(name="Purpose", value=app_data.application_purpose, inline=True)
|
||||
if app_data.result:
|
||||
embed.add_field(name="Result", value=app_data.result, inline=True)
|
||||
if app_data.fee_control_number:
|
||||
embed.add_field(name="Fee Control Number", value=app_data.fee_control_number, inline=True)
|
||||
if app_data.payment_date:
|
||||
embed.add_field(name="Payment Date", value=f"{app_data.payment_date:%Y-%m-%d}", inline=True)
|
||||
if app_data.operator_class:
|
||||
embed.add_field(name="Operator Class", value=app_data.operator_class, inline=True)
|
||||
if app_data.operator_group:
|
||||
embed.add_field(name="Operator Group", value=app_data.operator_group, inline=True)
|
||||
if app_data.uls_group:
|
||||
embed.add_field(name="ULS Group", value=app_data.uls_group, inline=True)
|
||||
if app_data.vanity_type:
|
||||
embed.add_field(name="Vanity Type", value=app_data.vanity_type, inline=True)
|
||||
if app_data.vanity_relationship:
|
||||
embed.add_field(name="Vanity Relationship", value=app_data.vanity_relationship, inline=True)
|
||||
if app_data.trustee_name and app_data.trustee_callsign:
|
||||
embed.add_field(name="Trustee",
|
||||
value=f"{app_data.trustee_name} ({app_data.trustee_callsign})",
|
||||
inline=True)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@_ae7q_lookup.command(name="frn", aliases=["f"], category=cmn.cat.lookup)
|
||||
async def _ae7q_frn(self, ctx: commands.Context, frn: str):
|
||||
"""Looks 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)
|
||||
frn = frn.upper()
|
||||
|
||||
async with self.session.get(base_url + frn) as resp:
|
||||
if resp.status != 200:
|
||||
raise cmn.BotHTTPError(resp)
|
||||
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:])
|
||||
frn_data = ae7qparser.get_frn(frn)
|
||||
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"AE7Q History for FRN {frn}"
|
||||
embed.title = f"AE7Q FRN History for {frn}"
|
||||
embed.url = frn_data.query_url
|
||||
embed.colour = cmn.colours.good
|
||||
embed.url = base_url + frn
|
||||
embed.description = ""
|
||||
|
||||
# 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 not frn_data.frn_history:
|
||||
embed.colour = cmn.colours.bad
|
||||
embed.description += f"\nNo records found for `{frn}`"
|
||||
else:
|
||||
# add the first three rows of the FRN history to the embed
|
||||
for row in frn_data.frn_history[0:3]:
|
||||
header = f"**{row.callsign}** ({row.applicant_type})"
|
||||
body = (f"Name: *{row.entity_name}*\n"
|
||||
f"Region: *{row.region_state}*\n"
|
||||
f"Operator Class: *{row.operator_class}*\n"
|
||||
f"Status: *{row.license_status}*\n")
|
||||
if row.grant_date:
|
||||
body += f"Granted: *{row.grant_date:%Y-%m-%d}*\n"
|
||||
if row.effective_date:
|
||||
body += f"Effective: *{row.effective_date:%Y-%m-%d}*\n"
|
||||
if row.cancel_date:
|
||||
body += f"Cancelled: *{row.cancel_date:%Y-%m-%d}*\n"
|
||||
if row.expire_date:
|
||||
body += f"Expires: *{row.expire_date:%Y-%m-%d}*\n"
|
||||
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..."
|
||||
if len(frn_data.frn_history) > 3:
|
||||
embed.description += (f"\nRecords 1 to 3 of {len(frn_data.frn_history)}. "
|
||||
f"See [ae7q.com]({frn_data.query_url}) for more...")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@@ -310,87 +415,43 @@ class AE7QCog(commands.Cog):
|
||||
"""Looks 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:
|
||||
raise cmn.BotHTTPError(resp)
|
||||
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:])
|
||||
lid_data = ae7qparser.get_licensee_id(licensee_id)
|
||||
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"AE7Q History for Licensee {licensee_id}"
|
||||
embed.url = lid_data.query_url
|
||||
embed.colour = cmn.colours.good
|
||||
embed.url = base_url + licensee_id
|
||||
embed.description = ""
|
||||
|
||||
# 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 not lid_data.licensee_id_history:
|
||||
embed.colour = cmn.colours.bad
|
||||
embed.description += f"\nNo records found for `{licensee_id}`"
|
||||
else:
|
||||
# add the first three rows of the FRN history to the embed
|
||||
for row in lid_data.licensee_id_history[0:3]:
|
||||
header = f"**{row.callsign}** ({row.applicant_type})"
|
||||
body = (f"Name: *{row.entity_name}*\n"
|
||||
f"Region: *{row.region_state}*\n"
|
||||
f"Operator Class: *{row.operator_class}*\n"
|
||||
f"Status: *{row.license_status}*\n")
|
||||
if row.grant_date:
|
||||
body += f"Granted: *{row.grant_date:%Y-%m-%d}*\n"
|
||||
if row.effective_date:
|
||||
body += f"Effective: *{row.effective_date:%Y-%m-%d}*\n"
|
||||
if row.cancel_date:
|
||||
body += f"Cancelled: *{row.cancel_date:%Y-%m-%d}*\n"
|
||||
if row.expire_date:
|
||||
body += f"Expires: *{row.expire_date:%Y-%m-%d}*\n"
|
||||
|
||||
if len(table) > 3:
|
||||
embed.description = f"Records 1 to 3 of {len(table)}. See ae7q.com for more..."
|
||||
embed.add_field(name=header, value=body, inline=False)
|
||||
|
||||
if len(lid_data.licensee_id_history) > 3:
|
||||
embed.description += (f"\nRecords 1 to 3 of {len(lid_data.licensee_id_history)}. "
|
||||
f"See [ae7q.com]({lid_data.query_url}) 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):
|
||||
bot.add_cog(AE7QCog(bot))
|
||||
|
||||
+1
-2
@@ -10,7 +10,6 @@ the GNU General Public License, version 2.
|
||||
|
||||
import random
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from typing import Union
|
||||
|
||||
import discord
|
||||
@@ -189,7 +188,7 @@ class BaseCog(commands.Cog):
|
||||
|
||||
|
||||
def parse_changelog():
|
||||
changelog = OrderedDict()
|
||||
changelog = {}
|
||||
ver = ""
|
||||
heading = ""
|
||||
|
||||
|
||||
+16
-12
@@ -31,6 +31,11 @@ class FunCog(commands.Cog):
|
||||
"""Returns xkcd: tar."""
|
||||
await ctx.send("http://xkcd.com/1168")
|
||||
|
||||
@commands.command(name="standards", category=cmn.cat.fun)
|
||||
async def _standards(self, ctx: commands.Context):
|
||||
"""Returns xkcd: Standards."""
|
||||
await ctx.send("http://xkcd.com/927")
|
||||
|
||||
@commands.command(name="xd", hidden=True, category=cmn.cat.fun)
|
||||
async def _xd(self, ctx: commands.Context):
|
||||
"""ecks dee"""
|
||||
@@ -39,18 +44,17 @@ class FunCog(commands.Cog):
|
||||
@commands.command(name="funetics", aliases=["fun"], category=cmn.cat.fun)
|
||||
async def _funetics_lookup(self, ctx: commands.Context, *, msg: str):
|
||||
"""Generates fun/wacky phonetics for a word or phrase."""
|
||||
with ctx.typing():
|
||||
result = ""
|
||||
for char in msg.lower():
|
||||
if char.isalpha():
|
||||
result += random.choice([word for word in self.words if word[0] == char])
|
||||
else:
|
||||
result += char
|
||||
result += " "
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Funetics for {msg}"
|
||||
embed.description = result.title()
|
||||
embed.colour = cmn.colours.good
|
||||
result = ""
|
||||
for char in msg.lower():
|
||||
if char.isalpha():
|
||||
result += random.choice([word for word in self.words if word[0] == char])
|
||||
else:
|
||||
result += char
|
||||
result += " "
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Funetics for {msg}"
|
||||
embed.description = result.title()
|
||||
embed.colour = cmn.colours.good
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
|
||||
+75
-77
@@ -23,93 +23,91 @@ class GridCog(commands.Cog):
|
||||
async def _grid_sq_lookup(self, ctx: commands.Context, lat: str, lon: str):
|
||||
("""Calculates the grid square for latitude and longitude coordinates, """
|
||||
"""with negative being latitude South and longitude West.""")
|
||||
with ctx.typing():
|
||||
grid = "**"
|
||||
latf = float(lat) + 90
|
||||
lonf = float(lon) + 180
|
||||
if 0 <= latf <= 180 and 0 <= lonf <= 360:
|
||||
grid += chr(ord("A") + int(lonf / 20))
|
||||
grid += chr(ord("A") + int(latf / 10))
|
||||
grid += chr(ord("0") + int((lonf % 20)/2))
|
||||
grid += chr(ord("0") + int((latf % 10)/1))
|
||||
grid += chr(ord("a") + int((lonf - (int(lonf/2)*2)) / (5/60)))
|
||||
grid += chr(ord("a") + int((latf - (int(latf/1)*1)) / (2.5/60)))
|
||||
grid += "**"
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Maidenhead Grid Locator for {float(lat):.6f}, {float(lon):.6f}"
|
||||
embed.description = grid
|
||||
embed.colour = cmn.colours.good
|
||||
else:
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Error generating grid square for {lat}, {lon}."
|
||||
embed.description = """Coordinates out of range.
|
||||
The valid ranges are:
|
||||
- Latitude: `-90` to `+90`
|
||||
- Longitude: `-180` to `+180`"""
|
||||
embed.colour = cmn.colours.bad
|
||||
grid = "**"
|
||||
latf = float(lat) + 90
|
||||
lonf = float(lon) + 180
|
||||
if 0 <= latf <= 180 and 0 <= lonf <= 360:
|
||||
grid += chr(ord("A") + int(lonf / 20))
|
||||
grid += chr(ord("A") + int(latf / 10))
|
||||
grid += chr(ord("0") + int((lonf % 20)/2))
|
||||
grid += chr(ord("0") + int((latf % 10)/1))
|
||||
grid += chr(ord("a") + int((lonf - (int(lonf/2)*2)) / (5/60)))
|
||||
grid += chr(ord("a") + int((latf - (int(latf/1)*1)) / (2.5/60)))
|
||||
grid += "**"
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Maidenhead Grid Locator for {float(lat):.6f}, {float(lon):.6f}"
|
||||
embed.description = grid
|
||||
embed.colour = cmn.colours.good
|
||||
else:
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Error generating grid square for {lat}, {lon}."
|
||||
embed.description = """Coordinates out of range.
|
||||
The valid ranges are:
|
||||
- Latitude: `-90` to `+90`
|
||||
- Longitude: `-180` to `+180`"""
|
||||
embed.colour = cmn.colours.bad
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(name="ungrid", aliases=["loc"], category=cmn.cat.maps)
|
||||
async def _location_lookup(self, ctx: commands.Context, grid: str, grid2: str = None):
|
||||
"""Calculates the latitude and longitude for the center of a grid square.
|
||||
If two grid squares are given, the distance and azimuth between them is calculated."""
|
||||
with ctx.typing():
|
||||
if grid2 is None or grid2 == "":
|
||||
try:
|
||||
grid = grid.upper()
|
||||
loc = get_coords(grid)
|
||||
if grid2 is None or grid2 == "":
|
||||
try:
|
||||
grid = grid.upper()
|
||||
loc = get_coords(grid)
|
||||
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Latitude and Longitude for {grid}"
|
||||
embed.colour = cmn.colours.good
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Latitude and Longitude for {grid}"
|
||||
embed.colour = cmn.colours.good
|
||||
|
||||
if len(grid) >= 6:
|
||||
embed.description = f"**{loc[0]:.5f}, {loc[1]:.5f}**"
|
||||
embed.url = f"https://www.openstreetmap.org/#map=13/{loc[0]:.5f}/{loc[1]:.5f}"
|
||||
else:
|
||||
embed.description = f"**{loc[0]:.1f}, {loc[1]:.1f}**"
|
||||
embed.url = f"https://www.openstreetmap.org/#map=10/{loc[0]:.1f}/{loc[1]:.1f}"
|
||||
except Exception as e:
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Error generating latitude and longitude for grid {grid}."
|
||||
embed.description = str(e)
|
||||
embed.colour = cmn.colours.bad
|
||||
else:
|
||||
radius = 6371
|
||||
try:
|
||||
grid = grid.upper()
|
||||
grid2 = grid2.upper()
|
||||
loc = get_coords(grid)
|
||||
loc2 = get_coords(grid2)
|
||||
# Haversine formula
|
||||
d_lat = math.radians(loc2[0] - loc[0])
|
||||
d_lon = math.radians(loc2[1] - loc[1])
|
||||
a = (math.sin(d_lat/2) ** 2
|
||||
+ math.cos(math.radians(loc[0]))
|
||||
* math.cos(math.radians(loc2[0]))
|
||||
* math.sin(d_lon/2) ** 2)
|
||||
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
|
||||
d = radius * c
|
||||
d_mi = 0.6213712 * d
|
||||
if len(grid) >= 6:
|
||||
embed.description = f"**{loc[0]:.5f}, {loc[1]:.5f}**"
|
||||
embed.url = f"https://www.openstreetmap.org/#map=13/{loc[0]:.5f}/{loc[1]:.5f}"
|
||||
else:
|
||||
embed.description = f"**{loc[0]:.1f}, {loc[1]:.1f}**"
|
||||
embed.url = f"https://www.openstreetmap.org/#map=10/{loc[0]:.1f}/{loc[1]:.1f}"
|
||||
except Exception as e:
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Error generating latitude and longitude for grid {grid}."
|
||||
embed.description = str(e)
|
||||
embed.colour = cmn.colours.bad
|
||||
else:
|
||||
radius = 6371
|
||||
try:
|
||||
grid = grid.upper()
|
||||
grid2 = grid2.upper()
|
||||
loc = get_coords(grid)
|
||||
loc2 = get_coords(grid2)
|
||||
# Haversine formula
|
||||
d_lat = math.radians(loc2[0] - loc[0])
|
||||
d_lon = math.radians(loc2[1] - loc[1])
|
||||
a = (math.sin(d_lat/2) ** 2
|
||||
+ math.cos(math.radians(loc[0]))
|
||||
* math.cos(math.radians(loc2[0]))
|
||||
* math.sin(d_lon/2) ** 2)
|
||||
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
|
||||
d = radius * c
|
||||
d_mi = 0.6213712 * d
|
||||
|
||||
# Bearing
|
||||
y_dist = math.sin(math.radians(loc2[1]-loc[1])) * math.cos(math.radians(loc2[0]))
|
||||
x_dist = (math.cos(math.radians(loc[0]))
|
||||
* math.sin(math.radians(loc2[0]))
|
||||
- math.sin(math.radians(loc[0]))
|
||||
* math.cos(math.radians(loc2[0]))
|
||||
* math.cos(math.radians(loc2[1] - loc[1])))
|
||||
bearing = (math.degrees(math.atan2(y_dist, x_dist)) + 360) % 360
|
||||
# Bearing
|
||||
y_dist = math.sin(math.radians(loc2[1]-loc[1])) * math.cos(math.radians(loc2[0]))
|
||||
x_dist = (math.cos(math.radians(loc[0]))
|
||||
* math.sin(math.radians(loc2[0]))
|
||||
- math.sin(math.radians(loc[0]))
|
||||
* math.cos(math.radians(loc2[0]))
|
||||
* math.cos(math.radians(loc2[1] - loc[1])))
|
||||
bearing = (math.degrees(math.atan2(y_dist, x_dist)) + 360) % 360
|
||||
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Great Circle Distance and Bearing from {grid} to {grid2}"
|
||||
embed.description = f"**Distance:** {d:.1f} km ({d_mi:.1f} mi)\n**Bearing:** {bearing:.1f}°"
|
||||
embed.colour = cmn.colours.good
|
||||
except Exception as e:
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Error generating great circle distance and bearing from {grid} and {grid2}."
|
||||
embed.description = str(e)
|
||||
embed.colour = cmn.colours.bad
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Great Circle Distance and Bearing from {grid} to {grid2}"
|
||||
embed.description = f"**Distance:** {d:.1f} km ({d_mi:.1f} mi)\n**Bearing:** {bearing:.1f}°"
|
||||
embed.colour = cmn.colours.good
|
||||
except Exception as e:
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Error generating great circle distance and bearing from {grid} and {grid2}."
|
||||
embed.description = str(e)
|
||||
embed.colour = cmn.colours.bad
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
|
||||
+46
-29
@@ -25,45 +25,42 @@ class HamCog(commands.Cog):
|
||||
@commands.command(name="qcode", aliases=["q"], category=cmn.cat.ref)
|
||||
async def _qcode_lookup(self, ctx: commands.Context, qcode: str):
|
||||
"""Looks up the meaning of a Q Code."""
|
||||
with ctx.typing():
|
||||
qcode = qcode.upper()
|
||||
embed = cmn.embed_factory(ctx)
|
||||
if qcode in qcodes.qcodes:
|
||||
embed.title = qcode
|
||||
embed.description = qcodes.qcodes[qcode]
|
||||
embed.colour = cmn.colours.good
|
||||
else:
|
||||
embed.title = f"Q Code {qcode} not found"
|
||||
embed.colour = cmn.colours.bad
|
||||
qcode = qcode.upper()
|
||||
embed = cmn.embed_factory(ctx)
|
||||
if qcode in qcodes.qcodes:
|
||||
embed.title = qcode
|
||||
embed.description = qcodes.qcodes[qcode]
|
||||
embed.colour = cmn.colours.good
|
||||
else:
|
||||
embed.title = f"Q Code {qcode} not found"
|
||||
embed.colour = cmn.colours.bad
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(name="phonetics", aliases=["ph", "phoneticize", "phoneticise", "phone"], category=cmn.cat.ref)
|
||||
async def _phonetics_lookup(self, ctx: commands.Context, *, msg: str):
|
||||
"""Returns NATO phonetics for a word or phrase."""
|
||||
with ctx.typing():
|
||||
result = ""
|
||||
for char in msg.lower():
|
||||
if char.isalpha():
|
||||
result += phonetics.phonetics[char]
|
||||
else:
|
||||
result += char
|
||||
result += " "
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Phonetics for {msg}"
|
||||
embed.description = result.title()
|
||||
embed.colour = cmn.colours.good
|
||||
result = ""
|
||||
for char in msg.lower():
|
||||
if char.isalpha():
|
||||
result += phonetics.phonetics[char]
|
||||
else:
|
||||
result += char
|
||||
result += " "
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Phonetics for {msg}"
|
||||
embed.description = result.title()
|
||||
embed.colour = cmn.colours.good
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(name="utc", aliases=["z"], category=cmn.cat.ref)
|
||||
async def _utc_lookup(self, ctx: commands.Context):
|
||||
"""Returns the current time in UTC."""
|
||||
with ctx.typing():
|
||||
now = datetime.utcnow()
|
||||
result = "**" + now.strftime("%Y-%m-%d %H:%M") + "Z**"
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = "The current time is:"
|
||||
embed.description = result
|
||||
embed.colour = cmn.colours.good
|
||||
now = datetime.utcnow()
|
||||
result = "**" + now.strftime("%Y-%m-%d %H:%M") + "Z**"
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = "The current time is:"
|
||||
embed.description = result
|
||||
embed.colour = cmn.colours.good
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(name="prefixes", aliases=["vanity", "pfx", "vanities", "prefix"], category=cmn.cat.ref)
|
||||
@@ -95,6 +92,26 @@ class HamCog(commands.Cog):
|
||||
embed.colour = cmn.colours.good
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(name="phoneticweight", aliases=["pw"], category=cmn.cat.ref)
|
||||
async def _weight(self, ctx: commands.Context, *, msg: str):
|
||||
"""Calculates the phonetic weight of a callsign or message."""
|
||||
embed = cmn.embed_factory(ctx)
|
||||
msg = msg.upper()
|
||||
weight = 0
|
||||
for char in msg:
|
||||
try:
|
||||
weight += phonetics.pweights[char]
|
||||
except KeyError:
|
||||
embed.title = "Error in calculation of phonetic weight"
|
||||
embed.description = f"Unknown character `{char}` in message"
|
||||
embed.colour = cmn.colours.bad
|
||||
await ctx.send(embed=embed)
|
||||
return
|
||||
embed.title = f"Phonetic Weight of {msg}"
|
||||
embed.description = f"The phonetic weight is **{weight}**"
|
||||
embed.colour = cmn.colours.good
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot):
|
||||
bot.add_cog(HamCog(bot))
|
||||
|
||||
+22
-23
@@ -40,30 +40,29 @@ class LookupCog(commands.Cog):
|
||||
@commands.command(name="dxcc", aliases=["dx"], category=cmn.cat.lookup)
|
||||
async def _dxcc_lookup(self, ctx: commands.Context, query: str):
|
||||
"""Gets DXCC info about a callsign prefix."""
|
||||
with ctx.typing():
|
||||
query = query.upper()
|
||||
full_query = query
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = "DXCC Info for "
|
||||
embed.description = f"*Last Updated: {self.cty.formatted_version}*"
|
||||
embed.colour = cmn.colours.bad
|
||||
while query:
|
||||
if query in self.cty.keys():
|
||||
data = self.cty[query]
|
||||
embed.add_field(name="Entity", value=data["entity"])
|
||||
embed.add_field(name="CQ Zone", value=data["cq"])
|
||||
embed.add_field(name="ITU Zone", value=data["itu"])
|
||||
embed.add_field(name="Continent", value=data["continent"])
|
||||
embed.add_field(name="Time Zone",
|
||||
value=f"+{data['tz']}" if data["tz"] > 0 else str(data["tz"]))
|
||||
embed.title += query
|
||||
embed.colour = cmn.colours.good
|
||||
break
|
||||
else:
|
||||
query = query[:-1]
|
||||
query = query.upper()
|
||||
full_query = query
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = "DXCC Info for "
|
||||
embed.description = f"*Last Updated: {self.cty.formatted_version}*"
|
||||
embed.colour = cmn.colours.bad
|
||||
while query:
|
||||
if query in self.cty.keys():
|
||||
data = self.cty[query]
|
||||
embed.add_field(name="Entity", value=data["entity"])
|
||||
embed.add_field(name="CQ Zone", value=data["cq"])
|
||||
embed.add_field(name="ITU Zone", value=data["itu"])
|
||||
embed.add_field(name="Continent", value=data["continent"])
|
||||
embed.add_field(name="Time Zone",
|
||||
value=f"+{data['tz']}" if data["tz"] > 0 else str(data["tz"]))
|
||||
embed.title += query
|
||||
embed.colour = cmn.colours.good
|
||||
break
|
||||
else:
|
||||
embed.title += full_query + " not found"
|
||||
embed.colour = cmn.colours.bad
|
||||
query = query[:-1]
|
||||
else:
|
||||
embed.title += full_query + " not found"
|
||||
embed.colour = cmn.colours.bad
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@tasks.loop(hours=24)
|
||||
|
||||
+41
-44
@@ -21,61 +21,58 @@ class MorseCog(commands.Cog):
|
||||
@commands.command(name="morse", aliases=["cw"], category=cmn.cat.ref)
|
||||
async def _morse(self, ctx: commands.Context, *, msg: str):
|
||||
"""Converts ASCII to international morse code."""
|
||||
with ctx.typing():
|
||||
result = ""
|
||||
for char in msg.upper():
|
||||
try:
|
||||
result += morse.morse[char]
|
||||
except KeyError:
|
||||
result += "<?>"
|
||||
result += " "
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Morse Code for {msg}"
|
||||
embed.description = "**" + result + "**"
|
||||
embed.colour = cmn.colours.good
|
||||
result = ""
|
||||
for char in msg.upper():
|
||||
try:
|
||||
result += morse.morse[char]
|
||||
except KeyError:
|
||||
result += "<?>"
|
||||
result += " "
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Morse Code for {msg}"
|
||||
embed.description = "**" + result + "**"
|
||||
embed.colour = cmn.colours.good
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(name="unmorse", aliases=["demorse", "uncw", "decw"], category=cmn.cat.ref)
|
||||
async def _unmorse(self, ctx: commands.Context, *, msg: str):
|
||||
"""Converts international morse code to ASCII."""
|
||||
with ctx.typing():
|
||||
result = ""
|
||||
msg0 = msg
|
||||
msg = msg.split("/")
|
||||
msg = [m.split() for m in msg]
|
||||
for word in msg:
|
||||
for char in word:
|
||||
try:
|
||||
result += morse.ascii[char]
|
||||
except KeyError:
|
||||
result += "<?>"
|
||||
result += " "
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"ASCII for {msg0}"
|
||||
embed.description = result
|
||||
embed.colour = cmn.colours.good
|
||||
result = ""
|
||||
msg0 = msg
|
||||
msg = msg.split("/")
|
||||
msg = [m.split() for m in msg]
|
||||
for word in msg:
|
||||
for char in word:
|
||||
try:
|
||||
result += morse.ascii[char]
|
||||
except KeyError:
|
||||
result += "<?>"
|
||||
result += " "
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"ASCII for {msg0}"
|
||||
embed.description = result
|
||||
embed.colour = cmn.colours.good
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(name="cwweight", aliases=["weight", "cww"], category=cmn.cat.ref)
|
||||
async def _weight(self, ctx: commands.Context, *, msg: str):
|
||||
"""Calculates the CW weight of a callsign or message."""
|
||||
embed = cmn.embed_factory(ctx)
|
||||
with ctx.typing():
|
||||
msg = msg.upper()
|
||||
weight = 0
|
||||
for char in msg:
|
||||
try:
|
||||
cw_char = morse.morse[char].replace("-", "==")
|
||||
weight += len(cw_char) * 2 + 2
|
||||
except KeyError:
|
||||
embed.title = "Error in calculation of CW weight"
|
||||
embed.description = f"Unknown character `{char}` in message"
|
||||
embed.colour = cmn.colours.bad
|
||||
await ctx.send(embed=embed)
|
||||
return
|
||||
embed.title = f"CW Weight of {msg}"
|
||||
embed.description = f"The CW weight is **{weight}**"
|
||||
embed.colour = cmn.colours.good
|
||||
msg = msg.upper()
|
||||
weight = 0
|
||||
for char in msg:
|
||||
try:
|
||||
cw_char = morse.morse[char].replace("-", "==")
|
||||
weight += len(cw_char) * 2 + 2
|
||||
except KeyError:
|
||||
embed.title = "Error in calculation of CW weight"
|
||||
embed.description = f"Unknown character `{char}` in message"
|
||||
embed.colour = cmn.colours.bad
|
||||
await ctx.send(embed=embed)
|
||||
return
|
||||
embed.title = f"CW Weight of {msg}"
|
||||
embed.description = f"The CW weight is **{weight}**"
|
||||
embed.colour = cmn.colours.good
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
|
||||
+58
-61
@@ -8,7 +8,6 @@ the GNU General Public License, version 2.
|
||||
"""
|
||||
|
||||
|
||||
from collections import OrderedDict
|
||||
from io import BytesIO
|
||||
|
||||
import aiohttp
|
||||
@@ -36,50 +35,51 @@ class QRZCog(commands.Cog):
|
||||
await ctx.send(f"http://qrz.com/db/{callsign}")
|
||||
return
|
||||
|
||||
try:
|
||||
await qrz_test_session(self.key, self.session)
|
||||
except ConnectionError:
|
||||
await self.get_session()
|
||||
|
||||
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"]:
|
||||
async with ctx.typing():
|
||||
try:
|
||||
await qrz_test_session(self.key, self.session)
|
||||
except ConnectionError:
|
||||
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()}
|
||||
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()
|
||||
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"QRZ Data for {resp_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"])
|
||||
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"])
|
||||
|
||||
data = qrz_process_info(resp_data)
|
||||
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()}
|
||||
|
||||
for title, val in data.items():
|
||||
if val is not None:
|
||||
embed.add_field(name=title, value=val, inline=True)
|
||||
await ctx.send(embed=embed)
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"QRZ Data for {resp_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"])
|
||||
|
||||
data = qrz_process_info(resp_data)
|
||||
|
||||
for title, val in 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."""
|
||||
@@ -158,27 +158,24 @@ def qrz_process_info(data: dict):
|
||||
else:
|
||||
lotw = "Unknown"
|
||||
|
||||
return OrderedDict([("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)),
|
||||
("CQ Zone", data.get("cqzone", None)),
|
||||
("ITU Zone", data.get("ituzone", None)),
|
||||
("IOTA Designator", data.get("iota", None)),
|
||||
("Born", data.get("born", None))])
|
||||
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)}
|
||||
|
||||
|
||||
def setup(bot):
|
||||
|
||||
+1
-2
@@ -14,7 +14,6 @@ from datetime import datetime
|
||||
import asyncio
|
||||
|
||||
import aiohttp
|
||||
import pytz
|
||||
|
||||
import discord.ext.commands as commands
|
||||
|
||||
@@ -134,7 +133,7 @@ class StudyCog(commands.Cog):
|
||||
" the answer will be revealed."),
|
||||
inline=False)
|
||||
if "image" in question:
|
||||
image_url = f"https://hamstudy.org/_1330011/images/{pool.split('_',1)[1]}/{question['image']}"
|
||||
image_url = f"https://hamstudy.org/images/{pool_meta['year']}/{question['image']}"
|
||||
embed.set_image(url=image_url)
|
||||
|
||||
q_msg = await ctx.send(embed=embed)
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ class WeatherCog(commands.Cog):
|
||||
embed.set_image(url="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"], case_insensitive=True, category=cmn.cat.weather)
|
||||
async def _weather_conditions(self, ctx: commands.Context):
|
||||
"""Gets local weather conditions from [wttr.in](http://wttr.in/).
|
||||
|
||||
|
||||
@@ -12,5 +12,5 @@ authors = ("@ClassAbbyAmplifier#2229", "@0x5c#0639")
|
||||
description = """A bot with various useful ham radio-related functions, written in Python."""
|
||||
license = "Released under the GNU General Public License v2"
|
||||
contributing = "Check out the source on GitHub, contributions welcome: https://github.com/miaowware/qrm2"
|
||||
release = "2.2.1"
|
||||
release = "2.3.1"
|
||||
bot_server = "https://discord.gg/Ntbg3J4"
|
||||
|
||||
@@ -45,8 +45,8 @@ loop = asyncio.get_event_loop()
|
||||
connector = loop.run_until_complete(conn.new_connector())
|
||||
|
||||
bot = commands.Bot(command_prefix=opt.prefix,
|
||||
description=info.description,
|
||||
help_command=commands.MinimalHelpCommand(),
|
||||
case_insensitive=True,
|
||||
description=info.description, help_command=commands.MinimalHelpCommand(),
|
||||
loop=loop,
|
||||
connector=connector)
|
||||
|
||||
@@ -82,7 +82,7 @@ async def _shutdown_bot(ctx: commands.Context):
|
||||
await bot.logout()
|
||||
|
||||
|
||||
@bot.group(name="extctl", aliases=["ex"], category=cmn.cat.admin)
|
||||
@bot.group(name="extctl", aliases=["ex"], case_insensitive=True, category=cmn.cat.admin)
|
||||
@commands.check(cmn.check_if_owner)
|
||||
async def _extctl(ctx: commands.Context):
|
||||
"""Extension control commands.
|
||||
|
||||
@@ -3,3 +3,4 @@ ctyparser
|
||||
beautifulsoup4
|
||||
lxml
|
||||
pytz
|
||||
ae7qparser
|
||||
|
||||
+39
-40
@@ -8,49 +8,48 @@ the GNU General Public License, version 2.
|
||||
"""
|
||||
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
us_calls_title = "Valid US Vanity Callsigns"
|
||||
us_calls_desc = ("#x# is the number of letters in the prefix and suffix of a callsign. "
|
||||
"E.g., WY4RC would be a 2x2 callsign, with prefix WY and suffix RC.")
|
||||
us_calls = OrderedDict([("**Group A** (Extra Only)", ("**Any:** K, N, W (1x2)\n"
|
||||
" AA-AL, KA-KZ, NA-NZ, WA-WZ (2x1)\n"
|
||||
" AA-AL (2x2)\n"
|
||||
"*Except*\n"
|
||||
"**Alaska:** AL, KL, NL, WL (2x1)\n"
|
||||
"**Caribbean:** KP, NP, WP (2x1)\n"
|
||||
"**Pacific:** AH, KH, NH, WH (2x1)")),
|
||||
("**Group B** (Advanced and Extra Only)", ("**Any:** KA-KZ, NA-NZ, WA-WZ (2x2)\n"
|
||||
"*Except*\n"
|
||||
"**Alaska:** AL (2x2)\n"
|
||||
"**Caribbean:** KP (2x2)\n"
|
||||
"**Pacific:** AH (2x2)")),
|
||||
("**Group C** (Technician, General, Advanced, Extra Only)", ("**Any Region:** K, N, W (1x3)\n"
|
||||
"*Except*\n"
|
||||
"**Alaska:** KL, NL, WL (2x2)\n"
|
||||
"**Caribbean:** NP, WP (2x2)\n"
|
||||
"**Pacific:** KH, NH, WH (2x2)")),
|
||||
("**Group D** (Any License Class)", ("**Any Region:** KA-KZ, WA-WZ (2x3)\n"
|
||||
"*Except*\n"
|
||||
"**Alaska:** KL, WL (2x3)\n"
|
||||
"**Caribbean:** KP, WP (2x3)\n"
|
||||
"**Pacific:** KH, WH (2x3)")),
|
||||
("**Unavailable**", ("- KA2AA-KA9ZZ: US Army in Japan\n"
|
||||
"- KC4AAA-KC4AAF: NSF in Antartica\n"
|
||||
"- KC4USA-KC4USZ: US Navy in Antartica\n"
|
||||
"- KG4AA-KG4ZZ: US Navy in Guantanamo Bay\n"
|
||||
"- KL9KAA-KL9KHZ: US military in Korea\n"
|
||||
"- KC6AA-KC6ZZ: Former US (Eastern and Western Caroline Islands), "
|
||||
"now Federated States of Micronesia (V6) and Republic of Palau (T8)\n"
|
||||
"- KX6AA-KX6ZZ: Former US (Marshall Islands), "
|
||||
"now Republic of the Marshall Islands (V73)\n"
|
||||
"- Any suffix SOS or QRA-QUZ\n"
|
||||
"- Any 2x3 with X as the first suffix letter\n"
|
||||
"- Any 2x3 with AF, KF, NF, or WF prefix and suffix EMA: FEMA\n"
|
||||
"- Any 2x3 with AA-AL, NA-NZ, WC, WK, WM, WR, or WT prefix: \"Group X\"\n"
|
||||
"- Any 2x1, 2x2, or 2x3 with KP, NP, WP prefix and 0, 6, 7, 8, 9 number\n"
|
||||
"- Any 1x1 callsign: Special Event"))])
|
||||
us_calls = {
|
||||
"**Group A** (Extra Only)": ("**Any:** K, N, W (1x2)\n"
|
||||
" AA-AL, KA-KZ, NA-NZ, WA-WZ (2x1)\n"
|
||||
" AA-AL (2x2)\n"
|
||||
"*Except*\n"
|
||||
"**Alaska:** AL, KL, NL, WL (2x1)\n"
|
||||
"**Caribbean:** KP, NP, WP (2x1)\n"
|
||||
"**Pacific:** AH, KH, NH, WH (2x1)"),
|
||||
"**Group B** (Advanced and Extra Only)": ("**Any:** KA-KZ, NA-NZ, WA-WZ (2x2)\n"
|
||||
"*Except*\n"
|
||||
"**Alaska:** AL (2x2)\n"
|
||||
"**Caribbean:** KP (2x2)\n"
|
||||
"**Pacific:** AH (2x2)"),
|
||||
"**Group C** (Technician, General, Advanced, Extra Only)": ("**Any Region:** K, N, W (1x3)\n"
|
||||
"*Except*\n"
|
||||
"**Alaska:** KL, NL, WL (2x2)\n"
|
||||
"**Caribbean:** NP, WP (2x2)\n"
|
||||
"**Pacific:** KH, NH, WH (2x2)"),
|
||||
"**Group D** (Any License Class)": ("**Any Region:** KA-KZ, WA-WZ (2x3)\n"
|
||||
"*Except*\n"
|
||||
"**Alaska:** KL, WL (2x3)\n"
|
||||
"**Caribbean:** KP, WP (2x3)\n"
|
||||
"**Pacific:** KH, WH (2x3)"),
|
||||
"**Unavailable**": ("- KA2AA-KA9ZZ: US Army in Japan\n"
|
||||
"- KC4AAA-KC4AAF: NSF in Antartica\n"
|
||||
"- KC4USA-KC4USZ: US Navy in Antartica\n"
|
||||
"- KG4AA-KG4ZZ: US Navy in Guantanamo Bay\n"
|
||||
"- KL9KAA-KL9KHZ: US military in Korea\n"
|
||||
"- KC6AA-KC6ZZ: Former US (Eastern and Western Caroline Islands), "
|
||||
"now Federated States of Micronesia (V6) and Republic of Palau (T8)\n"
|
||||
"- KX6AA-KX6ZZ: Former US (Marshall Islands), "
|
||||
"now Republic of the Marshall Islands (V73)\n"
|
||||
"- Any suffix SOS or QRA-QUZ\n"
|
||||
"- Any 2x3 with X as the first suffix letter\n"
|
||||
"- Any 2x3 with AF, KF, NF, or WF prefix and suffix EMA: FEMA\n"
|
||||
"- Any 2x3 with AA-AL, NA-NZ, WC, WK, WM, WR, or WT prefix: \"Group X\"\n"
|
||||
"- Any 2x1, 2x2, or 2x3 with KP, NP, WP prefix and 0, 6, 7, 8, 9 number\n"
|
||||
"- Any 1x1 callsign: Special Event")
|
||||
}
|
||||
|
||||
# format: country: (title, description, text)
|
||||
options = {"us": (us_calls_title, us_calls_desc, us_calls)}
|
||||
|
||||
@@ -36,3 +36,43 @@ phonetics = {
|
||||
"y": "yankee",
|
||||
"z": "zulu"
|
||||
}
|
||||
|
||||
pweights = {
|
||||
"A": 2,
|
||||
"B": 2,
|
||||
"C": 2,
|
||||
"D": 2,
|
||||
"E": 2,
|
||||
"F": 2,
|
||||
"G": 1,
|
||||
"H": 2,
|
||||
"I": 3,
|
||||
"J": 3,
|
||||
"K": 2,
|
||||
"L": 2,
|
||||
"M": 1,
|
||||
"N": 3,
|
||||
"O": 2,
|
||||
"P": 2,
|
||||
"Q": 2,
|
||||
"R": 3,
|
||||
"S": 3,
|
||||
"T": 2,
|
||||
"U": 3,
|
||||
"V": 2,
|
||||
"W": 2,
|
||||
"X": 2,
|
||||
"Y": 2,
|
||||
"Z": 2,
|
||||
"0": 2,
|
||||
"1": 1,
|
||||
"2": 1,
|
||||
"3": 1,
|
||||
"4": 1,
|
||||
"5": 1,
|
||||
"6": 1,
|
||||
"7": 2,
|
||||
"8": 1,
|
||||
"9": 2,
|
||||
"/": 1,
|
||||
}
|
||||
|
||||
+119
-44225
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user