34 Commits

Author SHA1 Message Date
0x5c cf93773a3c Merge pull request #471 from miaowware/rel-2.9.1
rel 2.9.1
2023-01-29 00:52:48 -05:00
classabbyamp 642b49041a bump version 2.9.1 2023-01-29 00:44:29 -05:00
classabbyamp e95f991300 bump copyright 2023-01-29 00:43:56 -05:00
0x5c 56ae14a5c3 Merge pull request #469 from miaowware/new-clt
exts/callsign: unworkaround some things solved in CLT 1.1.0
2023-01-29 00:34:04 -05:00
classabbyamp 30c6e96883 exts/callsign: unworkaround some things solved in CLT 1.1.0
fixes #466
2023-01-29 00:23:20 -05:00
0x5c 44a6905f7b Merge pull request #468 from miaowware/embed-avatar
Embed factory/pycord fixes
2023-01-28 23:41:01 -05:00
classabbyamp d7de78e582 common.py: use tz-aware datetime for proper timestamp display
https://docs.pycord.dev/en/stable/api/data_classes.html#discord.Embed.timestamp
2023-01-28 20:11:23 -05:00
classabbyamp b000c9173e common.py: don't error when creating embeds for users without avatars
behaviour changed in pycord 2.0:
https://docs.pycord.dev/en/stable/api/models.html#discord.User.display_avatar

fixes #467
2023-01-28 20:11:00 -05:00
0x5c 5460dd811b Merge pull request #465 from miaowware/fix-rel-workflow
.github/workflows/release.yml: move to ncipollo/create-release action
2023-01-13 18:57:54 -05:00
classabbyamp a00d613430 .github/workflows/release.yml: move to ncipollo/create-release action
fixes #464
2023-01-13 12:42:26 -05:00
0x5c 6b0cdb6249 Merge pull request #463 from miaowware/update-changelog
Bump version to 2.9.0
2023-01-13 03:51:01 -05:00
0x5c 4eed94b55b Bump version to 2.9.0 2023-01-13 03:48:45 -05:00
0x5c 3110961a3a exts/propagation: Fix ?solarweather no image bug
Back to the ugly hack of downloading the image and uploading it to discord.

Fixes #461
2023-01-13 03:44:27 -05:00
0x5c a4c8a056ac First steps for move from aiohttp to httpx 2023-01-13 03:44:27 -05:00
classabbyamp 9368ccd9e2 exts/study: fix in DMs
fixes #442
2023-01-13 01:14:54 -05:00
0x5c 8efd958314 Merge pull request #460 from miaowware/delete-solar-aliases
exts/propagation: Remove deprecated ?solarweather aliases
2023-01-13 01:09:57 -05:00
0x5c 4803bf89b2 exts/propagation: Remove deprecated ?solarweather aliases
Fixes #332
2023-01-13 01:03:37 -05:00
classabbyamp c82216cae6 Revert "update changelog, bump release"
This reverts commit 1b0b244f99.
2023-01-13 00:48:06 -05:00
classabbyamp 1650cd50dc exts/callsign: simplify stringification, fix data validation 2023-01-12 22:24:06 -05:00
classabbyamp 1b0b244f99 update changelog, bump release 2023-01-01 16:22:42 -05:00
classabbyamp 5db77f78d9 exts/callsign: convert to callsignlookuptools (qrz only for now) 2023-01-01 16:22:42 -05:00
classabbyamp c7ea5e0998 migrate to pycord 2023-01-01 16:22:42 -05:00
classabbyamp adffd82127 utils/resources_manager.py: use httpx instead of requests 2023-01-01 16:22:42 -05:00
classabbyamp 970159e81b Makefile: update default python version to 3.11 2023-01-01 16:22:42 -05:00
0x5c f5aeefc934 Merge pull request #454 from miaowware/token-perms
.github/workflows/docker.yml: add package write perms
2022-10-12 01:08:29 -04:00
classabbyamp aac9262469 .github/workflows/docker.yml: add package write perms 2022-10-12 00:56:56 -04:00
0x5c b472cdfa25 Merge pull request #453 from miaowware/xbps-update
Dockerfile: ensure system update works
2022-10-11 19:57:13 -04:00
classabbyamp 585cae8b97 Dockerfile: ensure system update works 2022-10-11 18:17:53 -04:00
0x5c c3fbd3e719 Merge pull request #452 from miaowware/set-output
.github/workflows/docker.yml: remove deprecated set-output
2022-10-11 18:14:12 -04:00
classabbyamp 7eadb50b96 exts/dbconv: fix lint 2022-10-11 18:12:55 -04:00
classabbyamp 98642c099d .github/workflows/docker.yml: remove deprecated set-output
https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
2022-10-11 14:24:03 -04:00
0x5c ef6f01d1a3 Merge pull request #450 from miaowware/bump
Bump version to 2.8.0
2022-06-24 17:44:24 -04:00
0x5c 91c5217d24 Bump version to 2.8.0 2022-06-24 17:42:41 -04:00
classabbyamp 4659cf2a48 exts/ae7q: remove extension
fixes #448
2022-06-24 17:17:26 -04:00
37 changed files with 220 additions and 577 deletions
+11 -9
View File
@@ -15,6 +15,8 @@ jobs:
docker:
name: Build and push docker images
runs-on: ubuntu-20.04
permissions:
packages: write
steps:
- name: Checkout
uses: actions/checkout@v2
@@ -29,8 +31,8 @@ jobs:
run: |
IMAGE_ID=${GITHUB_REPOSITORY,,}
IMAGE_NAME=${IMAGE_ID#*/}
echo ::set-output name=image_id::$IMAGE_ID
echo ::set-output name=image_name::$IMAGE_NAME
echo "image_id=$IMAGE_ID" >> $GITHUB_ENV
echo "image_name=$IMAGE_NAME" >> $GITHUB_ENV
docker build . --file Dockerfile -t $IMAGE_NAME
- name: Login to Github Container Registry
@@ -43,10 +45,10 @@ jobs:
- name: Tag image
id: tag_image
run: |
IMAGE_NAME=${{ steps.build_image.outputs.image_name }}
IMAGE_ID=ghcr.io/${{ steps.build_image.outputs.image_id }}
IMAGE_NAME=${{ env.image_name }}
IMAGE_ID=ghcr.io/${{ env.image_id }}
echo IMAGE_ID=$IMAGE_ID
echo ::set-output name=image_id::$IMAGE_ID
echo "image_id=$IMAGE_ID" >> $GITHUB_ENV
# Strip git ref prefix from version
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
@@ -55,7 +57,7 @@ jobs:
# if version is master, set version to dev
[[ "$VERSION" == "master" ]] && VERSION=dev
echo VERSION=$VERSION
echo ::set-output name=version::$VERSION
echo "version=$VERSION" >> $GITHUB_ENV
# tag dev or x.x.x
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
@@ -64,8 +66,8 @@ jobs:
- name: Push images to registry
run: |
VERSION=${{ steps.tag_image.outputs.version }}
IMAGE_ID=${{ steps.tag_image.outputs.image_id }}
VERSION=${{ env.version }}
IMAGE_ID=${{ env.image_id }}
[[ "$VERSION" != "dev" ]] && docker push $IMAGE_ID:latest || true
docker push $IMAGE_ID:$VERSION
@@ -77,4 +79,4 @@ jobs:
url: ${{ secrets.DEPLOY_URL }}
method: POST
headers: '{"Authentication": "Token ${{ secrets.DEPLOY_TOKEN }}"}'
payload: '{"version": "${{ steps.tag_image.outputs.version }}"}'
payload: '{"version": "${{ env.version }}"}'
+5 -5
View File
@@ -12,6 +12,8 @@ jobs:
release:
name: Create Release
runs-on: ubuntu-20.04
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v2
@@ -46,12 +48,10 @@ jobs:
- name: Publish Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: ncipollo/release-action@v1
with:
tag_name: ${{ env.tag_version }}
release_name: ${{ env.tag_subject }}
tag: ${{ env.tag_version }}
name: ${{ env.tag_subject }}
body: |
${{ env.tag_body }}
+26 -1
View File
@@ -7,6 +7,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
## [2.9.1] - 2023-01-29
### Fixed
- Issue where embeds would not work for users without avatars (#467).
- Issue where embeds would show the wrong timezone.
- Several issues with `?call` caused by issues in a library (#466).
## [2.9.0] - 2023-01-13
### Changed
- Migrated to Pycord.
### Removed
- Long-deprecated aliases for `?solarweather`.
### Fixed
- Issue where ?hamstudy would not work in direct messages (#442).
- Issue where `?solarweather` would not show a picture (#461).
## [2.8.0] - 2022-06-24
### Removed
- `?ae7q` command (#448).
## [2.7.6] - 2022-06-13
### Fixed
- Issue where `?muf` and `?fof2` would fail with an aiohttp error.
@@ -224,7 +246,10 @@ 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.7.6...HEAD
[Unreleased]: https://github.com/miaowware/qrm2/compare/v2.9.1...HEAD
[2.9.1]: https://github.com/miaowware/qrm2/releases/tag/v2.9.1
[2.9.0]: https://github.com/miaowware/qrm2/releases/tag/v2.9.0
[2.8.0]: https://github.com/miaowware/qrm2/releases/tag/v2.8.0
[2.7.6]: https://github.com/miaowware/qrm2/releases/tag/v2.7.6
[2.7.5]: https://github.com/miaowware/qrm2/releases/tag/v2.7.5
[2.7.4]: https://github.com/miaowware/qrm2/releases/tag/v2.7.4
+4 -3
View File
@@ -1,4 +1,4 @@
FROM ghcr.io/void-linux/void-linux:latest-mini-x86_64
FROM ghcr.io/void-linux/void-linux:latest-full-x86_64-musl
LABEL org.opencontainers.image.source https://github.com/miaowware/qrm2
COPY . /app
@@ -11,9 +11,10 @@ ARG GID 1000
RUN \
echo "**** update system ****" && \
xbps-install -SuyM -R ${REPOSITORY} && \
xbps-install -Suy xbps -R ${REPOSITORY} && \
xbps-install -uy -R ${REPOSITORY} && \
echo "**** install system packages ****" && \
xbps-install -yM -R ${REPOSITORY} ${PKGS} python3 python3-pip && \
xbps-install -y -R ${REPOSITORY} ${PKGS} python3 python3-pip && \
echo "**** install pip packages ****" && \
pip3 install -U pip setuptools wheel && \
pip3 install -r requirements.txt && \
+1 -1
View File
@@ -12,7 +12,7 @@
# Those are the defaults; they can be over-ridden if specified
# at en environment level or as 'make' arguments.
BOTENV ?= botenv
PYTHON_BIN ?= python3.9
PYTHON_BIN ?= python3.11
PIP_OUTPUT ?= -q
+1 -1
View File
@@ -38,7 +38,7 @@ All issues and requests related to resources (including maps, band charts, data)
## Copyright
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
This program is released under the terms of the *Québec Free and Open-Source Licence Strong Reciprocity (LiLiQ-R+)*, version 1.1.
See [`LICENCE`](LICENCE) for full license text (Français / English).
+16 -10
View File
@@ -1,7 +1,7 @@
"""
Common tools for the bot.
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
@@ -12,16 +12,17 @@ import enum
import json
import re
import traceback
from datetime import datetime
from datetime import datetime, timezone
from pathlib import Path
from types import SimpleNamespace
from typing import Union
import aiohttp
import httpx
import discord
import discord.ext.commands as commands
from discord import Emoji, Reaction, PartialEmoji
from discord import Emoji, PartialEmoji
import data.options as opt
@@ -125,12 +126,16 @@ class ImagesGroup(collections.abc.Mapping):
class BotHTTPError(Exception):
"""Raised whan a requests fails (status != 200) in a command."""
def __init__(self, response: aiohttp.ClientResponse):
msg = f"Request failed: {response.status} {response.reason}"
def __init__(self, response: aiohttp.ClientResponse | httpx.Response):
if isinstance(response, aiohttp.ClientResponse):
self.status = response.status
self.reason = response.reason
else:
self.status = response.status_code
self.reason = response.reason_phrase
msg = f"Request failed: {self.status} {self.reason}"
super().__init__(msg)
self.response = response
self.status = response.status
self.reason = response.reason
# --- Converters ---
@@ -160,8 +165,9 @@ class GlobalChannelConverter(commands.IDConverter):
def embed_factory(ctx: commands.Context) -> discord.Embed:
"""Creates an embed with neutral colour and standard footer."""
embed = discord.Embed(timestamp=datetime.utcnow(), colour=colours.neutral)
embed.set_footer(text=str(ctx.author), icon_url=str(ctx.author.avatar_url))
embed = discord.Embed(timestamp=datetime.now(timezone.utc), colour=colours.neutral)
if ctx.author:
embed.set_footer(text=str(ctx.author), icon_url=str(ctx.author.display_avatar))
return embed
@@ -178,7 +184,7 @@ def error_embed_factory(ctx: commands.Context, exception: Exception, debug_mode:
return embed
async def add_react(msg: discord.Message, react: Union[Emoji, Reaction, PartialEmoji, str]):
async def add_react(msg: discord.Message, react: Union[Emoji, PartialEmoji, str]):
try:
await msg.add_reaction(react)
except discord.Forbidden:
+1 -1
View File
@@ -1,3 +1,3 @@
-r requirements.txt
flake8
discord.py-stubs==1.7.3
mypy
+12 -419
View File
@@ -1,435 +1,28 @@
"""
ae7q extension for qrm
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
# Test callsigns:
# KN8U: active, restricted
# AB2EE: expired, restricted
# KE8FGB: assigned once, no restrictions
# KV4AAA: unassigned, no records
# KC4USA: reserved, no call history, *but* has application history
import aiohttp
from bs4 import BeautifulSoup
import discord.ext.commands as commands
import common as cmn
from common import embed_factory, colours
class AE7QCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
@commands.group(name="ae7q", aliases=["ae"], case_insensitive=True, category=cmn.Cats.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:
await ctx.send_help(ctx.command)
@_ae7q_lookup.command(name="call", aliases=["c"], category=cmn.Cats.LOOKUP)
async def _ae7q_call(self, ctx: commands.Context, callsign: str):
"""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)
if not callsign.isalnum():
embed = cmn.embed_factory(ctx)
embed.title = "AE7Q History for Callsign"
embed.colour = cmn.colours.bad
embed.description = "Not a valid callsign!"
await ctx.send(embed=embed)
return
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:])
embed = cmn.embed_factory(ctx)
embed.title = f"AE7Q 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[0]}** ({row[1]})" # **Name** (Applicant Type)
body = (f"Class: *{row[2]}*\n"
f"Region: *{row[3]}*\n"
f"Status: *{row[4]}*\n"
f"Granted: *{row[5]}*\n"
f"Effective: *{row[6]}*\n"
f"Cancelled: *{row[7]}*\n"
f"Expires: *{row[8]}*")
embed.add_field(name=header, value=body, inline=False)
if len(table) > 3:
desc += f"\nRecords 1 to 3 of {len(table)}. See ae7q.com for more..."
embed.description = desc
await ctx.send(embed=embed)
@_ae7q_lookup.command(name="trustee", aliases=["t"], category=cmn.Cats.LOOKUP)
async def _ae7q_trustee(self, ctx: commands.Context, callsign: str):
"""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)
if not callsign.isalnum():
embed = cmn.embed_factory(ctx)
embed.title = "AE7Q Trustee History for Callsign"
embed.colour = cmn.colours.bad
embed.description = "Not a valid callsign!"
await ctx.send(embed=embed)
return
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:])
embed = cmn.embed_factory(ctx)
embed.title = f"AE7Q Trustee 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[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 len(table) > 3:
desc += f"\nRecords 1 to 3 of {len(table)}. See ae7q.com for more..."
embed.description = desc
await ctx.send(embed=embed)
@_ae7q_lookup.command(name="applications", aliases=["a"], category=cmn.Cats.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)
if not callsign.isalnum():
embed = cmn.embed_factory(ctx)
embed.title = "AE7Q Application History for Callsign"
embed.colour = cmn.colours.bad
embed.description = "Not a valid callsign!"
await ctx.send(embed=embed)
return
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}"
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="frn", aliases=["f"], category=cmn.Cats.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)
if not frn.isdecimal():
embed = cmn.embed_factory(ctx)
embed.title = "AE7Q History for FRN"
embed.colour = cmn.colours.bad
embed.description = "Not a valid FRN!"
await ctx.send(embed=embed)
return
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:])
embed = cmn.embed_factory(ctx)
embed.title = f"AE7Q History for FRN {frn}"
embed.colour = cmn.colours.good
embed.url = base_url + frn
# add the first three rows of the table to the embed
for row in table[0:3]:
header = f"**{row[0]}** ({row[3]})" # **Callsign** (Applicant Type)
body = (f"Name: *{row[2]}*\n"
f"Class: *{row[4]}*\n"
f"Region: *{row[1]}*\n"
f"Status: *{row[5]}*\n"
f"Granted: *{row[6]}*\n"
f"Effective: *{row[7]}*\n"
f"Cancelled: *{row[8]}*\n"
f"Expires: *{row[9]}*")
embed.add_field(name=header, value=body, inline=False)
if len(table) > 3:
embed.description = f"Records 1 to 3 of {len(table)}. See ae7q.com for more..."
await ctx.send(embed=embed)
@_ae7q_lookup.command(name="licensee", aliases=["l"], category=cmn.Cats.LOOKUP)
async def _ae7q_licensee(self, ctx: commands.Context, licensee_id: str):
"""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)
if not licensee_id.isalnum():
embed = cmn.embed_factory(ctx)
embed.title = "AE7Q History for Licensee"
embed.colour = cmn.colours.bad
embed.description = "Not a valid licensee ID!"
await ctx.send(embed=embed)
return
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:])
embed = cmn.embed_factory(ctx)
embed.title = f"AE7Q History for Licensee {licensee_id}"
embed.colour = cmn.colours.good
embed.url = base_url + licensee_id
# add the first three rows of the table to the embed
for row in table[0:3]:
header = f"**{row[0]}** ({row[3]})" # **Callsign** (Applicant Type)
body = (f"Name: *{row[2]}*\n"
f"Class: *{row[4]}*\n"
f"Region: *{row[1]}*\n"
f"Status: *{row[5]}*\n"
f"Granted: *{row[6]}*\n"
f"Effective: *{row[7]}*\n"
f"Cancelled: *{row[8]}*\n"
f"Expires: *{row[9]}*")
embed.add_field(name=header, value=body, inline=False)
if len(table) > 3:
embed.description = f"Records 1 to 3 of {len(table)}. See ae7q.com for more..."
await ctx.send(embed=embed)
async def process_table(table: list):
"""Processes tables (*not* including headers) and returns the processed table"""
table_contents = []
for tr in table:
row = []
for td in tr.find_all("td"):
cell_val = td.getText().strip()
row.append(cell_val if cell_val else "-")
# take care of columns that span multiple rows by copying the contents rightward
if "colspan" in td.attrs and int(td.attrs["colspan"]) > 1:
for i in range(int(td.attrs["colspan"]) - 1):
row.append(row[-1])
# get rid of ditto marks by copying the contents from the previous row
for i, cell in enumerate(row):
if cell == "\"":
row[i] = table_contents[-1][i]
# add row to table
table_contents += [row]
return table_contents
@commands.command(name="ae7q", aliases=["ae"], case_insensitive=True)
async def _ae7q_lookup(self, ctx: commands.Context, *, _):
"""Removed in v2.8.0"""
embed = embed_factory(ctx)
embed.colour = colours.bad
embed.title = "Command removed"
embed.description = ("This command was removed in v2.8.0.\n"
"For context, see [this Github issue](https://github.com/miaowware/qrm2/issues/448)")
await ctx.send(embed=embed)
def setup(bot: commands.Bot):
bot.add_cog(AE7QCog(bot))
bot.add_cog(AE7QCog())
+6 -4
View File
@@ -1,7 +1,7 @@
"""
Base extension for qrm
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
@@ -141,7 +141,8 @@ class QrmHelpCommand(commands.HelpCommand):
embed.title = await self.get_command_signature(group)
embed.description = group.help
for cmd in await self.filter_commands(group.commands, sort=True):
embed.add_field(name=await self.get_command_signature(cmd), value=cmd.help, inline=False)
embed.add_field(name=await self.get_command_signature(cmd), value=cmd.help if cmd.help else "",
inline=False)
await self.context.send(embed=embed)
@@ -177,7 +178,7 @@ class BaseCog(commands.Cog):
@commands.Cog.listener()
async def on_ready(self):
if not self.bot_invite:
if not self.bot_invite and self.bot.user:
self.bot_invite = (f"https://discordapp.com/oauth2/authorize?client_id={self.bot.user.id}"
f"&scope=bot&permissions={opt.invite_perms}")
@@ -196,7 +197,8 @@ class BaseCog(commands.Cog):
inline=False)
if opt.enable_invite_cmd and (await self.bot.application_info()).bot_public:
embed.add_field(name="Invite qrm to Your Server", value=self.bot_invite, inline=False)
embed.set_thumbnail(url=str(self.bot.user.avatar_url))
if self.bot.user and self.bot.user.avatar:
embed.set_thumbnail(url=str(self.bot.user.avatar.url))
await ctx.send(embed=embed)
@commands.command(name="ping", aliases=["beep"], category=cmn.BoltCats.INFO)
+41 -45
View File
@@ -2,18 +2,16 @@
Callsign Lookup extension for qrm
---
Copyright (C) 2019-2020 classabbyamp, 0x5c (as qrz.py)
Copyright (C) 2021 classabbyamp, 0x5c
Copyright (C) 2021-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
from typing import Dict
from datetime import datetime
import aiohttp
from qrztools import qrztools, QrzAsync, QrzError
from gridtools import Grid, LatLong
from callsignlookuptools import QrzAsyncClient, CallsignLookupError, CallsignData
from discord.ext import commands
@@ -29,14 +27,16 @@ class QRZCog(commands.Cog):
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
session_key = ""
try:
with open("data/qrz_session") as qrz_file:
self.qrz.session_key = qrz_file.readline().strip()
session_key = qrz_file.readline().strip()
except FileNotFoundError:
pass
self.qrz = QrzAsyncClient(username=keys.qrz_user, password=keys.qrz_pass, useragent="discord-qrm2",
session_key=session_key,
session=aiohttp.ClientSession(connector=bot.qrm.connector))
except AttributeError:
pass
@@ -63,69 +63,65 @@ class QRZCog(commands.Cog):
async with ctx.typing():
try:
data = await self.qrz.get_callsign(callsign)
except QrzError as e:
data = await self.qrz.search(callsign)
except CallsignLookupError as e:
embed.colour = cmn.colours.bad
embed.description = str(e)
await ctx.send(embed=embed)
return
embed.title = f"QRZ Data for {data.call}"
embed.title = f"QRZ Data for {data.callsign}"
embed.colour = cmn.colours.good
embed.url = data.url
if data.image != qrztools.QrzImage():
if data.image is not None:
embed.set_thumbnail(url=data.image.url)
for title, val in qrz_process_info(data).items():
if val:
if val is not None and (val := str(val)):
embed.add_field(name=title, value=val, inline=True)
await ctx.send(embed=embed)
def qrz_process_info(data: qrztools.QrzCallsignData) -> Dict:
if data.name != qrztools.Name():
def qrz_process_info(data: CallsignData) -> Dict:
if data.name is not None:
if opt.qrz_only_nickname:
if data.name.nickname:
name = data.name.nickname + " " + data.name.name
nm = data.name.name if data.name.name is not None else ""
if data.name.nickname is not None:
name = data.name.nickname + " " + nm
elif data.name.first:
name = data.name.first + " " + data.name.name
name = data.name.first + " " + nm
else:
name = data.name.name
name = nm
else:
name = data.name.formatted_name
name = data.name
else:
name = None
if data.address != qrztools.Address():
state = ", " + data.address.state + " " if data.address.state else ""
address = "\n".join(
[x for x
in [data.address.attn, data.address.line1, data.address.line2 + state, data.address.zip]
if x]
)
else:
address = None
qsl = dict()
if data.qsl is not None:
qsl = {
"eQSL?": data.qsl.eqsl,
"Paper QSL?": data.qsl,
"LotW?": data.qsl.lotw,
"QSL Info": data.qsl.info,
}
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,
"Country": data.address.country if data.address is not None else None,
"Address": data.address,
"Grid Square": data.grid,
"County": data.county,
"CQ Zone": data.cq_zone,
"ITU Zone": data.itu_zone,
"IOTA Designator": data.iota,
"Expires": f"{data.expire_date:%Y-%m-%d}" if data.expire_date is not None 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
}
"Previous Callsign": data.prev_call,
"License Class": data.lic_class,
"Trustee": data.trustee,
"Born": data.born,
} | qsl
def setup(bot):
+1 -1
View File
@@ -2,7 +2,7 @@
Codes extension for qrm
---
Copyright (C) 2019-2021 classabbyamp, 0x5c (as ham.py)
Copyright (C) 2021 classabbyamp, 0x5c
Copyright (C) 2021-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Contest Calendar extension for qrm
---
Copyright (C) 2021 classabbyamp, 0x5c
Copyright (C) 2021-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+2 -2
View File
@@ -1,7 +1,7 @@
"""
Conversion extension for qrm
---
Copyright (C) 2020-2021 classabbyamp, 0x5c
Copyright (C) 2020-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
@@ -187,7 +187,7 @@ def _calc_volt(db: float, ref: float):
# testing code
if __name__ == "__main__":
while(True):
while True:
try:
ip = input("> ").split()
initial = float(ip[0])
+1 -1
View File
@@ -2,7 +2,7 @@
DXCC Prefix Lookup extension for qrm
---
Copyright (C) 2019-2020 classabbyamp, 0x5c (as lookup.py)
Copyright (C) 2021 classabbyamp, 0x5c
Copyright (C) 2021-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Fun extension for qrm
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Grid extension for qrm
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Image extension for qrm
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+1 -1
View File
@@ -2,7 +2,7 @@
Land Weather extension for qrm
---
Copyright (C) 2019-2020 classabbyamp, 0x5c (as weather.py)
Copyright (C) 2021 classabbyamp, 0x5c
Copyright (C) 2021-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Morse Code extension for qrm
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Prefixes Lookup extension for qrm
---
Copyright (C) 2021 classabbyamp, 0x5c
Copyright (C) 2021-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+23 -18
View File
@@ -1,17 +1,17 @@
"""
Propagation extension for qrm
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
from datetime import datetime
from io import BytesIO
import aiohttp
import cairosvg
from datetime import datetime
import httpx
import discord
import discord.ext.commands as commands
@@ -27,15 +27,17 @@ class PropagationCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
self.httpx_client: httpx.AsyncClient = bot.qrm.httpx_client
@commands.command(name="mufmap", aliases=["muf"], category=cmn.Cats.WEATHER)
async def mufmap(self, ctx: commands.Context):
"""Shows a world map of the Maximum Usable Frequency (MUF)."""
async with ctx.typing():
async with self.session.get(self.muf_url, headers={"Connection": "Upgrade", "Upgrade": "http/1.1"}) as r:
svg = await r.read()
out = BytesIO(cairosvg.svg2png(bytestring=svg))
resp = await self.httpx_client.get(self.muf_url)
await resp.aclose()
if resp.status_code != 200:
raise cmn.BotHTTPError(resp)
out = BytesIO(cairosvg.svg2png(bytestring=await resp.aread()))
file = discord.File(out, "muf_map.png")
embed = cmn.embed_factory(ctx)
embed.title = "Maximum Usable Frequency Map"
@@ -47,9 +49,11 @@ class PropagationCog(commands.Cog):
async def fof2map(self, ctx: commands.Context):
"""Shows a world map of the Critical Frequency (foF2)."""
async with ctx.typing():
async with self.session.get(self.fof2_url, headers={"Connection": "Upgrade", "Upgrade": "http/1.1"}) as r:
svg = await r.read()
out = BytesIO(cairosvg.svg2png(bytestring=svg))
resp = await self.httpx_client.get(self.fof2_url)
await resp.aclose()
if resp.status_code != 200:
raise cmn.BotHTTPError(resp)
out = BytesIO(cairosvg.svg2png(bytestring=await resp.aread()))
file = discord.File(out, "fof2_map.png")
embed = cmn.embed_factory(ctx)
embed.title = "Critical Frequency (foF2) Map"
@@ -67,19 +71,20 @@ class PropagationCog(commands.Cog):
embed.set_image(url=self.gl_baseurl + date_params)
await ctx.send(embed=embed)
@commands.command(name="solarweather", aliases=["solar", "bandconditions", "cond", "condx", "conditions"],
category=cmn.Cats.WEATHER)
@commands.command(name="solarweather", aliases=["solar"], category=cmn.Cats.WEATHER)
async def solarweather(self, ctx: commands.Context):
"""Gets a solar weather report."""
resp = await self.httpx_client.get(self.n0nbh_sun_url)
await resp.aclose()
if resp.status_code != 200:
raise cmn.BotHTTPError(resp)
img = BytesIO(await resp.aread())
file = discord.File(img, "solarweather.png")
embed = cmn.embed_factory(ctx)
embed.title = "☀️ Current Solar Weather"
if ctx.invoked_with in ["bandconditions", "cond", "condx", "conditions"]:
embed.add_field(name="⚠️ Deprecated Command Alias",
value=(f"This command has been renamed to `{ctx.prefix}solar`!\n"
"The alias you used will be removed in the next version."))
embed.colour = cmn.colours.good
embed.set_image(url=self.n0nbh_sun_url)
await ctx.send(embed=embed)
embed.set_image(url="attachment://solarweather.png")
await ctx.send(file=file, embed=embed)
def setup(bot: commands.Bot):
+20 -14
View File
@@ -1,7 +1,7 @@
"""
Study extension for qrm
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
@@ -159,13 +159,13 @@ class StudyCog(commands.Cog):
await cmn.add_react(q_msg, list(self.choices.values())[i])
await cmn.add_react(q_msg, cmn.emojis.question)
def check(reaction, user):
return (user.id != self.bot.user.id
and reaction.message.id == q_msg.id
and (str(reaction.emoji) in self.choices.values() or str(reaction.emoji) == cmn.emojis.question))
def check(ev):
return (ev.user_id != self.bot.user.id
and ev.message_id == q_msg.id
and (str(ev.emoji) in self.choices.values() or str(ev.emoji) == cmn.emojis.question))
try:
reaction, user = await self.bot.wait_for("reaction_add", timeout=300.0, check=check)
ev = await self.bot.wait_for("raw_reaction_add", timeout=300.0, check=check)
except asyncio.TimeoutError:
embed.set_field_at(1, name="Answers", value=answers_str_bolded, inline=False)
embed.set_field_at(2, name="Answer",
@@ -174,16 +174,18 @@ class StudyCog(commands.Cog):
embed.colour = cmn.colours.timeout
await q_msg.edit(embed=embed)
else:
if str(reaction.emoji) == cmn.emojis.question:
if str(ev.emoji) == cmn.emojis.question:
embed.set_field_at(1, name="Answers", value=answers_str_bolded, inline=False)
embed.set_field_at(2, name="Answer",
value=f"The correct answer was {self.choices[question['answer']]}", inline=False)
embed.add_field(name="Answer Requested By", value=str(user), inline=False)
# only available in guilds, but it only makes sense there
if ev.member:
embed.add_field(name="Answer Requested By", value=str(ev.member), inline=False)
embed.colour = cmn.colours.timeout
await q_msg.edit(embed=embed)
else:
answers_str_checked = ""
chosen_ans = self.choices_inv[str(reaction.emoji)]
chosen_ans = self.choices_inv[str(ev.emoji)]
for letter, ans in answers.items():
answers_str_checked += f"{self.choices[letter]}"
if letter == question["answer"] == chosen_ans:
@@ -195,19 +197,23 @@ class StudyCog(commands.Cog):
else:
answers_str_checked += f" {ans}\n"
if self.choices[question["answer"]] == str(reaction.emoji):
if self.choices[question["answer"]] == str(ev.emoji):
embed.set_field_at(1, name="Answers", value=answers_str_checked, inline=False)
embed.set_field_at(2, name="Answer", value=(f"{cmn.emojis.check_mark} "
f"**Correct!** The answer was {reaction.emoji}"))
embed.add_field(name="Answered By", value=str(user), inline=False)
f"**Correct!** The answer was {ev.emoji}"))
# only available in guilds, but it only makes sense there
if ev.member:
embed.add_field(name="Answered By", value=str(ev.member), inline=False)
embed.colour = cmn.colours.good
await q_msg.edit(embed=embed)
else:
embed.set_field_at(1, name="Answers", value=answers_str_checked, inline=False)
embed.set_field_at(2, name="Answer",
value=(f"{cmn.emojis.x} **Incorrect!** The correct answer was "
f"{self.choices[question['answer']]}, not {reaction.emoji}"))
embed.add_field(name="Answered By", value=str(user), inline=False)
f"{self.choices[question['answer']]}, not {ev.emoji}"))
# only available in guilds, but it only makes sense there
if ev.member:
embed.add_field(name="Answered By", value=str(ev.member), inline=False)
embed.colour = cmn.colours.bad
await q_msg.edit(embed=embed)
+1 -1
View File
@@ -1,7 +1,7 @@
"""
TeX extension for qrm
---
Copyright (C) 2021 classabbyamp, 0x5c
Copyright (C) 2021-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Time extension for qrm
---
Copyright (C) 2021 classabbyamp, 0x5c
Copyright (C) 2021-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+4 -4
View File
@@ -1,7 +1,7 @@
"""
Static info about the bot.
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
@@ -12,7 +12,7 @@ description = """A bot with various useful ham radio-related functions, written
license = "Québec Free and Open-Source Licence Strong Reciprocity (LiLiQ-R+), version 1.1"
contributing = """Check out the [source on GitHub](https://github.com/miaowware/qrm2). Contributions are welcome!
All issues and requests related to resources (including maps, band charts, data) should be added \
in [miaowware/qrm-resources](https://github.com/miaowware/qrm-resources)."""
release = "2.7.6"
All issues and requests related to resources (including maps, band charts, data) should be added \
in [miaowware/qrm-resources](https://github.com/miaowware/qrm-resources)."""
release = "2.9.1"
bot_server = "https://discord.gg/Ntbg3J4"
+14 -11
View File
@@ -2,7 +2,7 @@
"""
qrm, a bot for Discord
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
@@ -16,6 +16,7 @@ from datetime import datetime, time
from types import SimpleNamespace
from pathlib import Path
import httpx
import pytz
import discord
@@ -49,9 +50,9 @@ connector = loop.run_until_complete(conn.new_connector())
# Defining the intents
intents = discord.Intents.none()
intents.guilds = True
intents.guild_messages = True
intents.dm_messages = True
intents.messages = True
intents.reactions = True
intents.message_content = True
member_cache = discord.MemberCacheFlags.from_intents(intents)
@@ -69,6 +70,8 @@ bot.qrm = SimpleNamespace()
# Let's store stuff here.
bot.qrm.connector = connector
bot.qrm.debug_mode = debug_mode
# TODO: Add code to close the client
bot.qrm.httpx_client = httpx.AsyncClient()
# --- Commands ---
@@ -81,7 +84,7 @@ async def _restart_bot(ctx: commands.Context):
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
print(f"[**] Restarting! Requested by {ctx.author}.")
exit_code = 42 # Signals to the wrapper script that the bot needs to be restarted.
await bot.logout()
await bot.close()
@bot.command(name="shutdown", aliases=["shut"], category=cmn.BoltCats.ADMIN)
@@ -92,7 +95,7 @@ async def _shutdown_bot(ctx: commands.Context):
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
print(f"[**] Shutting down! Requested by {ctx.author}.")
exit_code = 0 # Signals to the wrapper script that the bot should not be restarted.
await bot.logout()
await bot.close()
@bot.group(name="extctl", aliases=["ex"], case_insensitive=True, category=cmn.BoltCats.ADMIN)
@@ -123,10 +126,10 @@ async def _extctl_load(ctx: commands.Context, extension: str):
"""Loads an extension."""
try:
bot.load_extension(ext_dir + "." + extension)
except commands.ExtensionNotFound as e:
except discord.errors.ExtensionNotFound as e:
try:
bot.load_extension(plugin_dir + "." + extension)
except commands.ExtensionNotFound:
except discord.errors.ExtensionNotFound:
raise e
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
@@ -140,10 +143,10 @@ async def _extctl_reload(ctx: commands.Context, extension: str):
await cmn.add_react(ctx.message, pika)
try:
bot.reload_extension(ext_dir + "." + extension)
except commands.ExtensionNotLoaded as e:
except discord.errors.ExtensionNotLoaded as e:
try:
bot.reload_extension(plugin_dir + "." + extension)
except commands.ExtensionNotLoaded:
except discord.errors.ExtensionNotLoaded:
raise e
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
@@ -153,10 +156,10 @@ async def _extctl_unload(ctx: commands.Context, extension: str):
"""Unloads an extension."""
try:
bot.unload_extension(ext_dir + "." + extension)
except commands.ExtensionNotLoaded as e:
except discord.errors.ExtensionNotLoaded as e:
try:
bot.unload_extension(plugin_dir + "." + extension)
except commands.ExtensionNotLoaded:
except discord.errors.ExtensionNotLoaded:
raise e
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
+4 -3
View File
@@ -1,9 +1,10 @@
discord.py~=1.7.3
py-cord~=2.3.2
aiohttp[speedups]
ctyparser~=2.0
gridtools~=1.0
qrztools[async]~=1.0
callsignlookuptools[async]~=1.1
beautifulsoup4
pytz
cairosvg
requests
httpx
pydantic
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Resource schemas generator for qrm2.
---
Copyright (C) 2021 classabbyamp, 0x5c
Copyright (C) 2021-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Information about callsigns for the prefixes command in hamcog.
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Information about callsigns for the CA prefixes command in hamcog.
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Information about callsigns for the US prefixes command in hamcog.
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+1 -1
View File
@@ -1,7 +1,7 @@
"""
A listing of hamstudy command resources
---
Copyright (C) 2019-2021 classabbyamp, 0x5c
Copyright (C) 2019-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+2 -2
View File
@@ -34,9 +34,9 @@ while [ ! -z "$1" ]; do
done
# If $PYTHON_BIN is not defined, default to 'python3.9'
# If $PYTHON_BIN is not defined, default to 'python3.11'
if [ $_NO_BOTENV -eq 1 -a -z "$PYTHON_BIN" ]; then
PYTHON_BIN='python3.9'
PYTHON_BIN='python3.11'
fi
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Wrapper to handle aiohttp connector creation.
---
Copyright (C) 2020-2021 classabbyamp, 0x5c
Copyright (C) 2020-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
+9 -6
View File
@@ -1,7 +1,7 @@
"""
Resources manager for qrm2.
---
Copyright (C) 2021 classabbyamp, 0x5c
Copyright (C) 2021-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""
@@ -9,7 +9,7 @@ SPDX-License-Identifier: LiLiQ-Rplus-1.1
from pathlib import Path
import requests
import httpx
from utils.resources_models import Index
@@ -28,8 +28,11 @@ class ResourcesManager:
def sync_fetch(self, filepath: str):
"""Fetches files in sync mode."""
self.print_msg(f"Fetching {filepath}", "sync")
with requests.get(self.url + filepath) as resp:
return resp.content
resp = httpx.get(self.url + filepath)
resp.raise_for_status()
r = resp.content
resp.close()
return r
def sync_start(self, basedir: Path) -> Index:
"""Takes cares of constructing the local resources repository and initialising the RM."""
@@ -40,7 +43,7 @@ class ResourcesManager:
new_index: Index = self.parse_index(raw)
with (basedir / "index.json").open("wb") as file:
file.write(raw)
except (requests.RequestException, OSError) as ex:
except (httpx.RequestError, OSError) as ex:
self.print_msg(f"There was an issue fetching the index: {ex.__class__.__name__}: {ex}", "sync")
if (basedir / "index.json").exists():
self.print_msg("Old file exist, using old resources", "fallback")
@@ -58,7 +61,7 @@ class ResourcesManager:
try:
with (basedir / file.filename).open("wb") as f:
f.write(self.sync_fetch(file.filename))
except (requests.RequestException, OSError) as ex:
except (httpx.RequestError, OSError) as ex:
ex_cls = ex.__class__.__name__
self.print_msg(f"There was an issue fetching {file.filename}: {ex_cls}: {ex}", "sync")
if not (basedir / file.filename).exists():
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Resource index models for qrm2.
---
Copyright (C) 2021 classabbyamp, 0x5c
Copyright (C) 2021-2023 classabbyamp, 0x5c
SPDX-License-Identifier: LiLiQ-Rplus-1.1
"""