Compare commits

...

15 Commits

Author SHA1 Message Date
classabbyamp 4b55ab49b7 .github/workflows/docker.yml: fix labels, again
Docker Build and Deploy / Build and push docker images (push) Has been cancelled Details
Linting / flake8 (push) Has been cancelled Details
2023-12-15 14:47:33 -05:00
classabbyamp cf378a2ef4 CHANGELOG.md: relbump 2023-12-15 14:30:59 -05:00
classabbyamp 13a8a63300 info.py: use pomelo usernames, relbump 2023-12-15 14:30:59 -05:00
classabbyamp 23619949d7 .github/workflows/docker.yml: fix tagging latest
turns out, most of this is handled automatically.
see https://github.com/docker/metadata-action#latest-tag
2023-12-15 14:20:05 -05:00
classabbyamp 444687bd12 .github/workflows/docker.yml: use metadata action, run on PRs
Docker Build and Deploy / Build and push docker images (push) Has been cancelled Details
Linting / flake8 (push) Has been cancelled Details
2023-12-11 08:21:33 -05:00
classabbyamp 86da8d135a Dockerfile: use new void container, python3.11 2023-12-11 08:21:33 -05:00
classabbyamp 67add85a7a run.sh: shellcheck 2023-12-11 08:21:33 -05:00
0x5c abdc5ebacb
Merge pull request #476 from miaowware/fix/metar
Docker Build and Deploy / Build and push docker images (push) Waiting to run Details
Linting / flake8 (push) Waiting to run Details
Fix metar + version bumps
2023-12-10 14:17:35 -05:00
0x5c a5cbb5a09a
exts/land_weather: switch to the new aviationweather.gov API
Fixes #475
2023-12-10 07:47:59 -05:00
0x5c ce99cc194e
bump pydantic to version 2 2023-12-10 07:19:45 -05:00
0x5c f8d7316071
bump pycord to pre-release v5 2023-12-10 07:16:41 -05:00
0x5c 9feeb01e42
Merge pull request #474 from cschmittiey/hamqsl-https
hamqsl.com has moved to https
2023-04-22 02:55:08 -04:00
Caleb Smith fcb682ec4a hamqsl.com has moved to https 2023-04-22 00:53:03 -06:00
Judd West c8a1128927 Add NOAA D-RAP map to propagation plugin 2023-01-30 05:25:01 -05:00
classabbyamp df08cefe25 exts/callsign: fix mail qsl display 2023-01-29 15:40:43 -05:00
11 changed files with 119 additions and 120 deletions

View File

@ -3,6 +3,10 @@
name: Docker Build and Deploy name: Docker Build and Deploy
on: on:
workflow_dispatch:
pull_request:
branches:
- master
push: push:
# Publish `master` as Docker `dev` image. # Publish `master` as Docker `dev` image.
branches: branches:
@ -11,29 +15,42 @@ on:
tags: tags:
- v* - v*
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
docker: docker:
name: Build and push docker images name: Build and push docker images
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
permissions: permissions:
packages: write packages: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: classabbyamp/treeless-checkout-action@v1
with:
ref: ${{ github.ref }}
- name: Write ref to file - name: Write ref to file
if: ${{ github.event_name != 'pull_request' }}
run: git rev-list -n 1 $GITHUB_REF > ./git_commit run: git rev-list -n 1 $GITHUB_REF > ./git_commit
- name: Build image - name: Docker metadata
id: build_image id: meta
run: | uses: docker/metadata-action@v4
IMAGE_ID=${GITHUB_REPOSITORY,,} with:
IMAGE_NAME=${IMAGE_ID#*/} images: |
echo "image_id=$IMAGE_ID" >> $GITHUB_ENV ghcr.io/${{ github.repository }}
echo "image_name=$IMAGE_NAME" >> $GITHUB_ENV tags: |
docker build . --file Dockerfile -t $IMAGE_NAME type=sha,prefix=
type=raw,value=dev,enable={{is_default_branch}}
type=match,pattern=v(.*),group=1
labels: |
org.opencontainers.image.authors=classabbyamp and 0x5c
org.opencontainers.image.url=https://github.com/miaowware/qrm2
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.vendor=miaowware
org.opencontainers.image.title=qrm2
org.opencontainers.image.description=Discord bot with ham radio functions
org.opencontainers.image.licenses=LiLiQ-Rplus-1.1
- name: Login to Github Container Registry - name: Login to Github Container Registry
uses: docker/login-action@v1 uses: docker/login-action@v1
@ -42,41 +59,10 @@ jobs:
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Tag image - name: Build and push
id: tag_image uses: docker/build-push-action@v5
run: |
IMAGE_NAME=${{ env.image_name }}
IMAGE_ID=ghcr.io/${{ env.image_id }}
echo 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,')
# Strip "v" prefix from tag name
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
# if version is master, set version to dev
[[ "$VERSION" == "master" ]] && VERSION=dev
echo VERSION=$VERSION
echo "version=$VERSION" >> $GITHUB_ENV
# tag dev or x.x.x
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
# tag latest if not a dev release
[[ "$VERSION" != "dev" ]] && docker tag $IMAGE_NAME $IMAGE_ID:latest || true
- name: Push images to registry
run: |
VERSION=${{ env.version }}
IMAGE_ID=${{ env.image_id }}
[[ "$VERSION" != "dev" ]] && docker push $IMAGE_ID:latest || true
docker push $IMAGE_ID:$VERSION
- name: Deploy official images
id: deploy_images
uses: satak/webrequest-action@v1
with: with:
url: ${{ secrets.DEPLOY_URL }} context: .
method: POST push: ${{ github.event_name != 'pull_request' }}
headers: '{"Authentication": "Token ${{ secrets.DEPLOY_TOKEN }}"}' tags: ${{ steps.meta.outputs.tags }}
payload: '{"version": "${{ env.version }}"}' labels: ${{ steps.meta.outputs.labels }}

View File

@ -7,6 +7,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
## [2.9.2] - 2023-12-15
### Added
- `?drapmap` command to display NOAA D Region Absorption Predictions map.
- Support for the new username format.
### Fixed
- Issue where `?solarweather` would not show a picture (#474).
- Issue where `?metar` and `?taf` failed to fetch data (#475).
## [2.9.1] - 2023-01-29 ## [2.9.1] - 2023-01-29
### Fixed ### Fixed
- Issue where embeds would not work for users without avatars (#467). - Issue where embeds would not work for users without avatars (#467).
@ -246,7 +255,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## 1.0.0 - 2019-07-31 [YANKED] ## 1.0.0 - 2019-07-31 [YANKED]
[Unreleased]: https://github.com/miaowware/qrm2/compare/v2.9.1...HEAD [Unreleased]: https://github.com/miaowware/qrm2/compare/v2.9.2...HEAD
[2.9.2]: https://github.com/miaowware/qrm2/releases/tag/v2.9.2
[2.9.1]: https://github.com/miaowware/qrm2/releases/tag/v2.9.1 [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.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.8.0]: https://github.com/miaowware/qrm2/releases/tag/v2.8.0

View File

@ -1,10 +1,9 @@
FROM ghcr.io/void-linux/void-linux:latest-full-x86_64-musl FROM ghcr.io/void-linux/void-musl-full
LABEL org.opencontainers.image.source https://github.com/miaowware/qrm2
COPY . /app COPY . /app
WORKDIR /app WORKDIR /app
ARG REPOSITORY=https://repo-us.voidlinux.org/current ARG REPOSITORY=https://repo-fastly.voidlinux.org/current
ARG PKGS="cairo libjpeg-turbo" ARG PKGS="cairo libjpeg-turbo"
ARG UID 1000 ARG UID 1000
ARG GID 1000 ARG GID 1000
@ -14,19 +13,19 @@ RUN \
xbps-install -Suy xbps -R ${REPOSITORY} && \ xbps-install -Suy xbps -R ${REPOSITORY} && \
xbps-install -uy -R ${REPOSITORY} && \ xbps-install -uy -R ${REPOSITORY} && \
echo "**** install system packages ****" && \ echo "**** install system packages ****" && \
xbps-install -y -R ${REPOSITORY} ${PKGS} python3 python3-pip && \ xbps-install -y -R ${REPOSITORY} ${PKGS} python3.11 && \
echo "**** install pip packages ****" && \ echo "**** install pip packages ****" && \
pip3 install -U pip setuptools wheel && \ python3.11 -m venv botenv && \
pip3 install -r requirements.txt && \ botenv/bin/pip install -U pip setuptools wheel && \
botenv/bin/pip install -r requirements.txt && \
echo "**** clean up ****" && \ echo "**** clean up ****" && \
rm -rf \ rm -rf \
/root/.cache \ /root/.cache \
/tmp/* \ /tmp/* \
/var/cache/xbps/* /var/cache/xbps/*
ENV PYTHON_BIN python3
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
USER $UID:$GID USER $UID:$GID
CMD ["/bin/sh", "run.sh", "--pass-errors", "--no-botenv"] CMD ["/bin/sh", "run.sh", "--pass-errors"]

View File

@ -101,7 +101,7 @@ def qrz_process_info(data: CallsignData) -> Dict:
if data.qsl is not None: if data.qsl is not None:
qsl = { qsl = {
"eQSL?": data.qsl.eqsl, "eQSL?": data.qsl.eqsl,
"Paper QSL?": data.qsl, "Paper QSL?": data.qsl.mail,
"LotW?": data.qsl.lotw, "LotW?": data.qsl.lotw,
"QSL Info": data.qsl.info, "QSL Info": data.qsl.info,
} }

View File

@ -9,11 +9,9 @@ SPDX-License-Identifier: LiLiQ-Rplus-1.1
import re import re
from typing import List
import aiohttp import aiohttp
from discord import Embed
import discord.ext.commands as commands import discord.ext.commands as commands
import common as cmn import common as cmn
@ -102,7 +100,32 @@ class WeatherCog(commands.Cog):
Airports should be given as an \ Airports should be given as an \
[ICAO code](https://en.wikipedia.org/wiki/List_of_airports_by_IATA_and_ICAO_code).""" [ICAO code](https://en.wikipedia.org/wiki/List_of_airports_by_IATA_and_ICAO_code)."""
await ctx.send(embed=await self.gen_metar_taf_embed(ctx, airport, hours, False))
embed = cmn.embed_factory(ctx)
airport = airport.upper()
if not re.fullmatch(r"\w(\w|\d){2,3}", airport):
embed.title = "Invalid airport given!"
embed.colour = cmn.colours.bad
await ctx.send(embed=embed)
return
url = f"https://aviationweather.gov/api/data/metar?ids={airport}&format=raw&taf=false&hours={hours}"
async with self.session.get(url) as r:
if r.status != 200:
raise cmn.BotHTTPError(r)
metar = await r.text()
if hours > 0:
embed.title = f"METAR for {airport} for the last {hours} hour{'s' if hours > 1 else ''}"
else:
embed.title = f"Current METAR for {airport}"
embed.description = "Data from [aviationweather.gov](https://www.aviationweather.gov/)."
embed.colour = cmn.colours.good
embed.description += f"\n\n```\n{metar}\n```"
await ctx.send(embed=embed)
@commands.command(name="taf", category=cmn.Cats.WEATHER) @commands.command(name="taf", category=cmn.Cats.WEATHER)
async def taf(self, ctx: commands.Context, airport: str): async def taf(self, ctx: commands.Context, airport: str):
@ -110,57 +133,28 @@ class WeatherCog(commands.Cog):
Airports should be given as an \ Airports should be given as an \
[ICAO code](https://en.wikipedia.org/wiki/List_of_airports_by_IATA_and_ICAO_code).""" [ICAO code](https://en.wikipedia.org/wiki/List_of_airports_by_IATA_and_ICAO_code)."""
await ctx.send(embed=await self.gen_metar_taf_embed(ctx, airport, 0, True))
async def gen_metar_taf_embed(self, ctx: commands.Context, airport: str, hours: int, taf: bool) -> Embed:
embed = cmn.embed_factory(ctx) embed = cmn.embed_factory(ctx)
airport = airport.upper() airport = airport.upper()
if re.fullmatch(r"\w(\w|\d){2,3}", airport): if not re.fullmatch(r"\w(\w|\d){2,3}", airport):
metar = await self.get_metar_taf_data(airport, hours, taf)
if taf:
embed.title = f"Current TAF for {airport}"
elif hours > 0:
embed.title = f"METAR for {airport} for the last {hours} hour{'s' if hours > 1 else ''}"
else:
embed.title = f"Current METAR for {airport}"
embed.description = "Data from [aviationweather.gov](https://www.aviationweather.gov/metar/data)."
embed.colour = cmn.colours.good
data = "\n".join(metar)
embed.description += f"\n\n```\n{data}\n```"
else:
embed.title = "Invalid airport given!" embed.title = "Invalid airport given!"
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
return embed await ctx.send(embed=embed)
return
async def get_metar_taf_data(self, airport: str, hours: int, taf: bool) -> List[str]: url = f"https://aviationweather.gov/api/data/taf?ids={airport}&format=raw&metar=true"
url = (f"https://www.aviationweather.gov/metar/data?ids={airport}&format=raw&hours={hours}"
f"&taf={'on' if taf else 'off'}&layout=off")
async with self.session.get(url) as r: async with self.session.get(url) as r:
if r.status != 200: if r.status != 200:
raise cmn.BotHTTPError(r) raise cmn.BotHTTPError(r)
page = await r.text() taf = await r.text()
# pare down to just the data embed.title = f"Current TAF for {airport}"
page = page.split("<!-- Data starts here -->")[1].split("<!-- Data ends here -->")[0].strip() embed.description = "Data from [aviationweather.gov](https://www.aviationweather.gov/)."
# split at <hr>s embed.colour = cmn.colours.good
data = re.split(r"<hr.*>", page, maxsplit=len(airport)) embed.description += f"\n\n```\n{taf}\n```"
parsed = [] await ctx.send(embed=embed)
for sec in data:
if sec.strip():
for line in sec.split("\n"):
line = line.strip()
# remove HTML stuff
line = line.replace("<code>", "").replace("</code>", "")
line = line.replace("<strong>", "").replace("</strong>", "")
line = line.replace("<br/>", "\n").replace("&nbsp;", " ")
line = line.strip("\n")
parsed.append(line)
return parsed
def setup(bot: commands.Bot): def setup(bot: commands.Bot):

View File

@ -23,7 +23,8 @@ class PropagationCog(commands.Cog):
muf_url = "https://prop.kc2g.com/renders/current/mufd-normal-now.svg" muf_url = "https://prop.kc2g.com/renders/current/mufd-normal-now.svg"
fof2_url = "https://prop.kc2g.com/renders/current/fof2-normal-now.svg" fof2_url = "https://prop.kc2g.com/renders/current/fof2-normal-now.svg"
gl_baseurl = "https://www.fourmilab.ch/cgi-bin/uncgi/Earth?img=ETOPO1_day-m.evif&dynimg=y&opt=-p" gl_baseurl = "https://www.fourmilab.ch/cgi-bin/uncgi/Earth?img=ETOPO1_day-m.evif&dynimg=y&opt=-p"
n0nbh_sun_url = "http://www.hamqsl.com/solarsun.php" n0nbh_sun_url = "https://www.hamqsl.com/solarsun.php"
noaa_drap_url = "https://services.swpc.noaa.gov/images/animations/d-rap/global/d-rap/latest.png"
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
@ -86,6 +87,17 @@ class PropagationCog(commands.Cog):
embed.set_image(url="attachment://solarweather.png") embed.set_image(url="attachment://solarweather.png")
await ctx.send(file=file, embed=embed) await ctx.send(file=file, embed=embed)
@commands.command(name="drapmap", aliases=["drap"], category=cmn.Cats.WEATHER)
async def drapmap(self, ctx: commands.Context):
"""Gets the current D-RAP map for radio blackouts"""
embed = cmn.embed_factory(ctx)
embed.title = "D Region Absorption Predictions (D-RAP) Map"
embed.colour = cmn.colours.good
embed.description = \
"Image from [swpc.noaa.gov](https://www.swpc.noaa.gov/products/d-region-absorption-predictions-d-rap)"
embed.set_image(url=self.noaa_drap_url)
await ctx.send(embed=embed)
def setup(bot: commands.Bot): def setup(bot: commands.Bot):
bot.add_cog(PropagationCog(bot)) bot.add_cog(PropagationCog(bot))

View File

@ -7,12 +7,12 @@ SPDX-License-Identifier: LiLiQ-Rplus-1.1
""" """
authors = ("@ClassAbbyAmplifier#2229", "@0x5c#0639") authors = ("@classabbyamp", "@0x5c.io")
description = """A bot with various useful ham radio-related functions, written in Python.""" description = """A bot with various useful ham radio-related functions, written in Python."""
license = "Québec Free and Open-Source Licence Strong Reciprocity (LiLiQ-R+), version 1.1" 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! 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 \ 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).""" in [miaowware/qrm-resources](https://github.com/miaowware/qrm-resources)."""
release = "2.9.1" release = "2.9.2"
bot_server = "https://discord.gg/Ntbg3J4" bot_server = "https://discord.gg/Ntbg3J4"

View File

@ -1,5 +1,4 @@
py-cord~=2.3.2 py-cord-dev[speed]==2.5.0rc5
aiohttp[speedups]
ctyparser~=2.0 ctyparser~=2.0
gridtools~=1.0 gridtools~=1.0
callsignlookuptools[async]~=1.1 callsignlookuptools[async]~=1.1
@ -7,4 +6,4 @@ beautifulsoup4
pytz pytz
cairosvg cairosvg
httpx httpx
pydantic pydantic~=2.5

8
run.sh
View File

@ -17,7 +17,7 @@ fi
# Argument handling # Argument handling
_PASS_ERRORS=0 _PASS_ERRORS=0
_NO_BOTENV=0 _NO_BOTENV=0
while [ ! -z "$1" ]; do while [ -n "$1" ]; do
case $1 in case $1 in
--pass-errors) --pass-errors)
_PASS_ERRORS=1 _PASS_ERRORS=1
@ -35,7 +35,7 @@ done
# If $PYTHON_BIN is not defined, default to 'python3.11' # If $PYTHON_BIN is not defined, default to 'python3.11'
if [ $_NO_BOTENV -eq 1 -a -z "$PYTHON_BIN" ]; then if [ $_NO_BOTENV -eq 1 ] && [ -z "$PYTHON_BIN" ]; then
PYTHON_BIN='python3.11' PYTHON_BIN='python3.11'
fi fi
@ -69,9 +69,9 @@ echo "$0: Starting bot..."
# The loop # The loop
while true; do while true; do
if [ $_NO_BOTENV -eq 1 ]; then if [ $_NO_BOTENV -eq 1 ]; then
"$PYTHON_BIN" main.py $@ "$PYTHON_BIN" main.py "$@"
else else
./$BOTENV/bin/python3 main.py $@ "./$BOTENV/bin/python3" main.py "$@"
fi fi
err=$? err=$?
_message="$0: The bot exited with [$err]" _message="$0: The bot exited with [$err]"

View File

@ -23,7 +23,7 @@ class ResourcesManager:
def parse_index(self, index: str): def parse_index(self, index: str):
"""Parses the index.""" """Parses the index."""
return Index.parse_raw(index) return Index.model_validate_json(index)
def sync_fetch(self, filepath: str): def sync_fetch(self, filepath: str):
"""Fetches files in sync mode.""" """Fetches files in sync mode."""

View File

@ -10,7 +10,7 @@ SPDX-License-Identifier: LiLiQ-Rplus-1.1
from collections.abc import Mapping from collections.abc import Mapping
from datetime import datetime from datetime import datetime
from pydantic import BaseModel from pydantic import BaseModel, RootModel
class File(BaseModel): class File(BaseModel):
@ -22,18 +22,17 @@ class File(BaseModel):
return repr(self) return repr(self)
class Resource(BaseModel, Mapping): class Resource(RootModel, Mapping):
# 'A Beautiful Hack' https://github.com/samuelcolvin/pydantic/issues/1802 root: dict[str, list[File]]
__root__: dict[str, list[File]]
def __getitem__(self, key: str) -> list[File]: def __getitem__(self, key: str) -> list[File]:
return self.__root__[key] return self.root[key]
def __iter__(self): def __iter__(self):
return iter(self.__root__) return iter(self.root)
def __len__(self) -> int: def __len__(self) -> int:
return len(self.__root__) return len(self.root)
# For some reason those were not the same??? # For some reason those were not the same???
def __str__(self) -> str: def __str__(self) -> str:
@ -41,7 +40,7 @@ class Resource(BaseModel, Mapping):
# Make the repr more logical (despite the technical inaccuracy) # Make the repr more logical (despite the technical inaccuracy)
def __repr_args__(self): def __repr_args__(self):
return self.__root__.items() return self.root.items()
class Index(BaseModel, Mapping): class Index(BaseModel, Mapping):