mirror of
https://github.com/miaowware/qrm2.git
synced 2026-06-02 05:54:40 -04:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 940f45f4d4 | |||
| be7e29b387 | |||
| 518ead9ccd | |||
| 6e5acba6e9 | |||
| ad50a86f9d | |||
| 0bfa0c6e41 | |||
| ce64d882b8 | |||
| 0aac09f3bc | |||
| e3534d02d7 | |||
| cdcb0e17d2 | |||
| cd2503c953 | |||
| b4c165851c | |||
| 77a5af73bc | |||
| 4b7064cad9 | |||
| d8fe3cfa02 | |||
| ffc3be7e24 | |||
| 5dab93b7d3 | |||
| e660b1a8f5 | |||
| 3d96a43c50 | |||
| e8bb18ea8c | |||
| 19952396f2 | |||
| 1831c56f58 | |||
| 77e14a109c | |||
| 2ac13346d4 | |||
| 2eea7dce23 | |||
| f26a7af928 | |||
| 786440edcb | |||
| c47d211016 | |||
| 855935a26e | |||
| ff9d46f379 | |||
| 0f0c3bf723 | |||
| 7c818cfb34 | |||
| 27863ae6bf | |||
| de999bc39d | |||
| 80d80ab718 | |||
| 8cb1a8df15 | |||
| fa2dded81b | |||
| bda0540fa8 | |||
| c9510ad9b9 | |||
| 00f9929deb | |||
| 3597367046 |
@@ -1,29 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a bug to help us improve qrm
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Run command '...' with input '...'
|
||||
2. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**System (include if related to running the bot):**
|
||||
- OS: [e.g. Linux, Docker]
|
||||
- Version: [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for qrm
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -1,29 +0,0 @@
|
||||
### Description
|
||||
|
||||
*Describe the changes you made here.*
|
||||
|
||||
Fixes #{issue}
|
||||
|
||||
### Type of change
|
||||
|
||||
Please delete options that are not relevant.
|
||||
|
||||
- Bug fix (non-breaking change which fixes an issue)
|
||||
- New feature (non-breaking change which adds functionality)
|
||||
- Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- This change requires a documentation update
|
||||
|
||||
### How has this been tested?
|
||||
|
||||
*Describe the procedure used for verifying your changes here.*
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Issue exists for PR
|
||||
- [ ] Code reviewed by the author
|
||||
- [ ] Code documented (comments or other documentation)
|
||||
- [ ] Changes tested
|
||||
- [ ] `flake8` passes
|
||||
- [ ] `CHANGELOG.md` updated
|
||||
- [ ] Informative commit messages
|
||||
- [ ] Descriptive PR title
|
||||
@@ -1,11 +1,13 @@
|
||||
name: Docker Build and Push
|
||||
# vim: ts=2 sw=2:
|
||||
|
||||
name: Docker Build and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
# Publish `master` as Docker `dev` image.
|
||||
branches:
|
||||
- master
|
||||
# Publish `v*` tags as releases and as `latest`.
|
||||
# Publish `v*` tags as x.x.x images and as `latest`.
|
||||
tags:
|
||||
- v*
|
||||
|
||||
@@ -13,46 +15,53 @@ env:
|
||||
IMAGE_NAME: qrm2
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
docker:
|
||||
name: Build and push docker images
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Write ref to file
|
||||
run: git rev-list -n 1 $GITHUB_REF > ./git_commit
|
||||
|
||||
- name: Build image
|
||||
run: |
|
||||
echo ${{ github.sha }} > git_commit
|
||||
docker build . --file Dockerfile -t $IMAGE_NAME
|
||||
run: 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
|
||||
- name: Tag image
|
||||
id: tag_image
|
||||
run: |
|
||||
GITHUB_IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME
|
||||
DOCKER_IMAGE_ID=${{ secrets.DOCKER_USERNAME }}/$IMAGE_NAME
|
||||
IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME
|
||||
echo IMAGE_ID=$IMAGE_ID
|
||||
echo ::set-output name=image_id::$IMAGE_ID
|
||||
|
||||
# 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 GITHUB_IMAGE_ID=$GITHUB_IMAGE_ID
|
||||
echo DOCKER_IMAGE_ID=$DOCKER_IMAGE_ID
|
||||
echo VERSION=$VERSION
|
||||
echo ::set-output name=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 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
|
||||
|
||||
# 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
|
||||
- name: Push images to registry
|
||||
run: docker push ${{ steps.tag_image.outputs.image_id }}
|
||||
|
||||
- name: Deploy official images
|
||||
id: deploy_images
|
||||
uses: satak/webrequest-action@v1
|
||||
with:
|
||||
url: ${{ secrets.DEPLOY_URL }}
|
||||
method: POST
|
||||
headers: '{"Authentication": "Token ${{ secrets.DEPLOY_TOKEN }}"}'
|
||||
payload: '{"version": "${{ steps.tag_image.outputs.version }}"}'
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# vim: ts=2 sw=2:
|
||||
|
||||
on:
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
name: Create Release
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Get Version Info
|
||||
id: get_tag
|
||||
shell: bash
|
||||
run: |
|
||||
SUBJECT=$(/usr/bin/git tag -l ${GITHUB_REF#refs/tags/} --format='%(subject)')
|
||||
BODY=$(/usr/bin/git tag -l ${GITHUB_REF#refs/tags/} --format='%(body)' | sed '/-----BEGIN PGP SIGNATURE-----/,$d')
|
||||
|
||||
echo "SUBJECT=$SUBJECT"
|
||||
echo "BODY=$BODY"
|
||||
|
||||
echo 'tag_subject<<EOS' >> $GITHUB_ENV
|
||||
echo "$SUBJECT" >> $GITHUB_ENV
|
||||
echo 'EOS' >> $GITHUB_ENV
|
||||
echo 'tag_body<<EOB' >> $GITHUB_ENV
|
||||
echo "$BODY" >> $GITHUB_ENV
|
||||
echo 'EOB' >> $GITHUB_ENV
|
||||
echo "tag_version=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
echo "version_num=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
|
||||
- name: Get Changelog Content
|
||||
id: changelog_reader
|
||||
uses: mindsers/changelog-reader-action@v2
|
||||
with:
|
||||
version: ${{ env.version_num }}
|
||||
path: ./CHANGELOG.md
|
||||
|
||||
- name: Publish Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ env.tag_version }}
|
||||
release_name: ${{ env.tag_subject }}
|
||||
body: |
|
||||
${{ env.tag_body }}
|
||||
|
||||
## Changelog
|
||||
|
||||
${{ steps.changelog_reader.outputs.changes }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
+17
-1
@@ -7,6 +7,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
## [2.5.0] - 2020-10-31
|
||||
### Added
|
||||
- Italian (`it_hf`, `it_vhf`, `it_shf`), Japanese (`jp`) and Australian (`au`) band charts.
|
||||
### Fixed
|
||||
- Details to the Netherlands bandplan command to accurately represent VERNON (Netherlands ARRL equivalent organisation).
|
||||
- eQSL, paper QSL, and Logbook of the World status in `?qrz` sometimes being shown incorrectly.
|
||||
- Fixed network error in `?greyline`.
|
||||
|
||||
|
||||
## [2.4.1] - 2020-10-06
|
||||
### Changed
|
||||
- Bumped discord.py to 1.5.0
|
||||
|
||||
|
||||
## [2.4.0] - 2020-09-27
|
||||
### Added
|
||||
- Canadian prefix info to the `?prefixes` command.
|
||||
@@ -124,7 +138,9 @@ 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.4.0...HEAD
|
||||
[Unreleased]: https://github.com/miaowware/qrm2/compare/v2.5.0...HEAD
|
||||
[2.5.0]: https://github.com/miaowware/qrm2/releases/tag/v2.5.0
|
||||
[2.4.1]: https://github.com/miaowware/qrm2/releases/tag/v2.4.1
|
||||
[2.4.0]: https://github.com/miaowware/qrm2/releases/tag/v2.4.0
|
||||
[2.3.2]: https://github.com/miaowware/qrm2/releases/tag/v2.3.2
|
||||
[2.3.1]: https://github.com/miaowware/qrm2/releases/tag/v2.3.1
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
# Contributing to qrm
|
||||
|
||||
## Before You Start
|
||||
|
||||
- Make sure there's an issue for the feature, bugfix, or other improvement you want to make.
|
||||
- Make sure it's something that the project maintainers want.
|
||||
We can discuss it and assign the issue to you.
|
||||
- Make sure work isn't already being done on the issue.
|
||||
|
||||
### Environment Setup
|
||||
|
||||
Once all of the above is done, you can get started by setting up your development envronment.
|
||||
|
||||
1. [Fork this repo][1] into your own GitHub namespace.
|
||||
1. Make sure the `master` branch is up to date, then make yourself a new branch with a descriptive name.
|
||||
1. Once the forked repo is cloned and on the proper branch, you can set up the development environment.
|
||||
1. Install python 3.7 or higher.
|
||||
1. Run `make dev-install`.
|
||||
This should install everything you need to develop and run qrm.
|
||||
1. [Create a bot and token][2], and add it to `data/keys.py`.
|
||||
Also add your [QRZ credentials][3] if needed.
|
||||
1. In `data/options.py`, change values as needed.
|
||||
Some commands require adding your Discord user ID to `owner_uids`.
|
||||
1. To activate the virtual env that was created by `make`, run `source botenv/bin/activate` (or the equivelent for your shell or operating system).
|
||||
|
||||
## While You Develop
|
||||
|
||||
To run qrm, use the command `./run.sh`.
|
||||
We recommend you use the `--pass-errors` flags to avoid perpetual restart loops if you break the bot.
|
||||
It exists because repeatedly mashing [Ctrl+C] at high speed to break a fast loop is not fun.
|
||||
|
||||
Commit messages should be descriptive and mention issues that they fix ("fixes #123") or contain progress on ("progress on #123").
|
||||
Make commits as needed, but try to keep it reasonable.
|
||||
If there are too many, your contribution may be squashed when merged.
|
||||
You may want to squash your commits locally yourself:
|
||||
|
||||
```sh
|
||||
git reset --soft [commit before your changes]
|
||||
git commit
|
||||
```
|
||||
|
||||
Make sure to document your code as you go, in both comments and external documentation (in `/dev-notes/`) as needed.
|
||||
`dev-notes` is especially important if you introduce a new json file format or to document some development process (like the command to crush the various images in the repository).
|
||||
|
||||
**Test your changes.**
|
||||
If your code doesn't work, it's not ready for merging.
|
||||
Make sure you not only test intended behaviour, but also edge cases and error cases.
|
||||
Make sure to run `flake8` to ensure your code uses the proper style, and `mypy [files...]` to ensure proper typing.
|
||||
|
||||
If you're making a user-facing change, put a quick summary in `CHANGELOG.md` under the `[Unreleased]` heading.
|
||||
Follow the [Keep a Changelog][4] format.
|
||||
|
||||
### A Note on Style
|
||||
|
||||
qrm tries to keep to PEP 8 style whenever possible.
|
||||
Use the utility `flake8` to check that you follow this style.
|
||||
When you start a PR or push commits, GitHub will automatically run this for you;
|
||||
if that fails, you will be expected to fix those errors before merge.
|
||||
|
||||
Otherwise, try to follow the existing style:
|
||||
double-quotes except when required to be single,
|
||||
indentation of mult-line structures matching other examples in the code,
|
||||
add type hints,
|
||||
etc.
|
||||
|
||||
## When You're Ready to Merge
|
||||
|
||||
1. When you have finished working on your contribution, create a pull request from your fork's branch into the master branch of this repository.
|
||||
1. Read through and complete the pull request template.
|
||||
If the checklist is not complete, your contribution will not be merged.
|
||||
1. Your pull request will get reviewed by at least one maintainer.
|
||||
1. If approved, another maintainer may merge the pull request if everything looks good.
|
||||
|
||||
[1]: https://github.com/miaowware/qrm2/fork
|
||||
[2]: https://discordpy.readthedocs.io/en/latest/discord.html
|
||||
[3]: https://www.qrz.com/page/xml_data.html
|
||||
[4]: https://keepachangelog.com/en/1.0.0/
|
||||
@@ -0,0 +1,54 @@
|
||||
# Development Guide for qrm
|
||||
|
||||
**Make sure to also read [`CONTRIBUTING.md`][0], everything in there applies here.**
|
||||
|
||||
### Environment Setup
|
||||
|
||||
1. [Fork this repo][1] into your own GitHub namespace.
|
||||
1. Make sure the `master` branch is up to date, then make yourself a new branch with a descriptive name.
|
||||
1. Once the forked repo is cloned and on the proper branch, you can set up the development environment.
|
||||
1. Install python 3.7 or higher.
|
||||
1. Run `make dev-install`.
|
||||
This should install everything you need to develop and run qrm.
|
||||
1. [Create a bot and token][2], and add it to `data/keys.py`.
|
||||
Also add your [QRZ credentials][3] if needed.
|
||||
1. In `data/options.py`, change values as needed.
|
||||
Some commands require adding your Discord user ID to `owner_uids`.
|
||||
1. To activate the virtual env that was created by `make`, run `source botenv/bin/activate` (or the equivelent for your shell or operating system).
|
||||
|
||||
These instructions are fairly \*NIX-centric, so if you would like to develop on Windows, it is suggested that the Windows Subsystem for Linux be used.
|
||||
|
||||
## While You Develop
|
||||
|
||||
To run qrm, use the command `./run.sh`.
|
||||
We recommend you use the `--pass-errors` flag to avoid perpetual restart loops if you break the bot.
|
||||
It exists because repeatedly mashing [Ctrl+C] at high speed to break a fast loop is not fun.
|
||||
|
||||
Make sure to add [type hints][4] to your code.
|
||||
This is what `mypy` validates in the code.
|
||||
|
||||
Using `dev-notes` for documentation is especially important if you introduce a new json file format (like for maps and bandplans) or to document some development process (like the command to crush the various images in the repository).
|
||||
|
||||
### Test your changes
|
||||
|
||||
In addition to testing functionality, make sure to run `flake8` to ensure your code uses the proper style, and `mypy [files...]` to ensure proper typing.
|
||||
You can also enable them for this project in your IDE if supported.
|
||||
This will give you automatic and continuous linting and type checking.
|
||||
|
||||
### A Note on Style
|
||||
|
||||
qrm tries to keep to PEP 8 style whenever possible.
|
||||
Use the utility `flake8` to check that you follow this style.
|
||||
When you start a PR or push commits, GitHub will automatically run this for you,
|
||||
but we prefer that developers check this before committing and opening PRs.
|
||||
|
||||
Otherwise, try to follow the existing style:
|
||||
- double-quotes except when required to be single,
|
||||
- indentation of mult-line structures matching other examples in the code,
|
||||
- etc.
|
||||
|
||||
[0]: https://github.com/miaowware/.github/blob/master/CONTRIBUTING.md
|
||||
[1]: https://github.com/miaowware/qrm2/fork
|
||||
[2]: https://discordpy.readthedocs.io/en/latest/discord.html
|
||||
[3]: https://www.qrz.com/page/xml_data.html
|
||||
[4]: https://docs.python.org/3/library/typing.html
|
||||
+6
-8
@@ -23,16 +23,14 @@ This is the easiest method for running the bot without any modifications.
|
||||
version: '3'
|
||||
services:
|
||||
qrm2:
|
||||
image: "classabbyamp/qrm2:latest"
|
||||
# OR
|
||||
# image: "docker.pkg.github.com/miaowware/qrm2/qrm2:latest"
|
||||
image: "docker.pkg.github.com/miaowware/qrm2/qrm2:latest"
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- "./data:/app/data:rw"
|
||||
environment:
|
||||
- PYTHONUNBUFFERED=1
|
||||
```
|
||||
> Note that there are two possible sources for the image: docker's and github's registry. Github's registry requires [a few extra steps](https://docs.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-docker-for-use-with-github-packages) during the initial setup.
|
||||
*Note that Github's registry requires [a few extra steps](https://docs.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-docker-for-use-with-github-packages) during the initial setup.*
|
||||
|
||||
3. Create a subdirectory named `data`.
|
||||
|
||||
@@ -45,7 +43,7 @@ This is the easiest method for running the bot without any modifications.
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
> Run without "-d" to test the bot. (run in foreground)
|
||||
*Run without "-d" to test the bot (run in foreground).*
|
||||
|
||||
|
||||
|
||||
@@ -81,7 +79,7 @@ This is the easiest method to run the bot with modifications.
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
> Run without "-d" to test the bot. (run in foreground)
|
||||
*Run without "-d" to test the bot (run in foreground).*
|
||||
|
||||
|
||||
|
||||
@@ -113,5 +111,5 @@ This methods is not very nice to use.
|
||||
```
|
||||
|
||||
Where `[image]` is either of:
|
||||
- `discord-qrm2:local-latest` if you are building your own.
|
||||
- `classabbyamp/discord-qrm2:latest` if you want to use the prebuilt image.
|
||||
- `qrm2:local-latest` if you are building your own.
|
||||
- `docker.pkg.github.com/miaowware/qrm2/qrm2:latest` if you want to use the prebuilt image.
|
||||
|
||||
@@ -15,11 +15,13 @@ import traceback
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Union
|
||||
|
||||
import aiohttp
|
||||
|
||||
import discord
|
||||
import discord.ext.commands as commands
|
||||
from discord import Emoji, Reaction, PartialEmoji
|
||||
|
||||
import data.options as opt
|
||||
|
||||
@@ -72,15 +74,6 @@ paths = SimpleNamespace(
|
||||
# --- Classes ---
|
||||
|
||||
|
||||
class CallsignInfoData:
|
||||
"""Represents a country's callsign info"""
|
||||
def __init__(self, data: list):
|
||||
self.title: str = data[0]
|
||||
self.desc: str = data[1]
|
||||
self.calls: str = data[2]
|
||||
self.emoji: str = data[3]
|
||||
|
||||
|
||||
class ImageMetadata:
|
||||
"""Represents the metadata of a single image."""
|
||||
def __init__(self, metadata: list):
|
||||
@@ -158,7 +151,7 @@ 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=ctx.author, icon_url=str(ctx.author.avatar_url))
|
||||
embed.set_footer(text=str(ctx.author), icon_url=str(ctx.author.avatar_url))
|
||||
return embed
|
||||
|
||||
|
||||
@@ -175,11 +168,12 @@ def error_embed_factory(ctx: commands.Context, exception: Exception, debug_mode:
|
||||
return embed
|
||||
|
||||
|
||||
async def add_react(msg: discord.Message, react: str):
|
||||
async def add_react(msg: discord.Message, react: Union[Emoji, Reaction, PartialEmoji, str]):
|
||||
try:
|
||||
await msg.add_reaction(react)
|
||||
except discord.Forbidden:
|
||||
print(f"[!!] Missing permissions to add reaction in '{msg.guild.id}/{msg.channel.id}'!")
|
||||
idpath = (f"{msg.guild.id}/" if msg.guild else "") + str(msg.channel.id)
|
||||
print(f"[!!] Missing permissions to add reaction in '{idpath}'!")
|
||||
|
||||
|
||||
# --- Checks ---
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
-r requirements.txt
|
||||
flake8
|
||||
discord.py-stubs
|
||||
discord.py-stubs==1.5.0
|
||||
|
||||
@@ -108,7 +108,6 @@ class BaseCog(commands.Cog):
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = "About qrm"
|
||||
embed.description = info.description
|
||||
|
||||
embed.add_field(name="Authors", value=", ".join(info.authors))
|
||||
embed.add_field(name="License", value=info.license)
|
||||
embed.add_field(name="Version", value=f"v{info.release}")
|
||||
|
||||
+32
-33
@@ -11,6 +11,7 @@ the GNU General Public License, version 2.
|
||||
import math
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass
|
||||
|
||||
import discord.ext.commands as commands
|
||||
|
||||
@@ -21,49 +22,47 @@ from data import options as opt
|
||||
# not sure why but UnitConverter and Unit need to be defined before DbConvCog and convert()
|
||||
class UnitConverter(commands.Converter):
|
||||
async def convert(self, ctx: commands.Context, argument: str):
|
||||
is_db = None
|
||||
mult = None
|
||||
unit = None
|
||||
utype = None
|
||||
try:
|
||||
return Unit(argument)
|
||||
s = argument.lower()
|
||||
if len(s) > 2 and s[:2] == "db":
|
||||
is_db = True
|
||||
if s[2:] in units:
|
||||
u = units[s[2:]]
|
||||
mult = u["mult"]
|
||||
unit = u["log"]
|
||||
utype = u["type"]
|
||||
elif s in units:
|
||||
is_db = False
|
||||
u = units[s]
|
||||
mult = u["mult"]
|
||||
unit = u["scalar"]
|
||||
utype = u["type"]
|
||||
else:
|
||||
raise ValueError(f"Invalid unit: {argument}")
|
||||
return Unit(argument, unit, utype, is_db, mult)
|
||||
except ValueError as e:
|
||||
raise commands.BadArgument(message=str(e))
|
||||
|
||||
|
||||
class Unit:
|
||||
def __init__(self, raw: str):
|
||||
self.raw: str = raw
|
||||
self.unit: str
|
||||
self.type: UnitType
|
||||
self.is_db: bool
|
||||
self.mult: int
|
||||
self._parse()
|
||||
|
||||
def _parse(self):
|
||||
s = self.raw.lower()
|
||||
if len(s) > 2 and s[:2] == "db":
|
||||
self.is_db = True
|
||||
if s[2:] in units:
|
||||
u = units[s[2:]]
|
||||
self.mult = u["mult"]
|
||||
self.unit = u["log"]
|
||||
self.type = u["type"]
|
||||
elif s in units:
|
||||
self.is_db = False
|
||||
u = units[s]
|
||||
self.mult = u["mult"]
|
||||
self.unit = u["scalar"]
|
||||
self.type = u["type"]
|
||||
else:
|
||||
raise ValueError(f"Invalid unit: {self.raw}")
|
||||
|
||||
def __str__(self):
|
||||
return self.unit
|
||||
|
||||
|
||||
class UnitType(Enum):
|
||||
voltage = 1
|
||||
power = 2
|
||||
antenna = 3
|
||||
|
||||
|
||||
@dataclass
|
||||
class Unit:
|
||||
raw: str
|
||||
unit: str
|
||||
type: UnitType
|
||||
is_db: bool
|
||||
mult: int
|
||||
|
||||
|
||||
class DbConvCog(commands.Cog):
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
@@ -85,7 +84,7 @@ class DbConvCog(commands.Cog):
|
||||
if value is not None and unit_from is not None and unit_to is not None:
|
||||
converted = convert(value, unit_from, unit_to)
|
||||
|
||||
embed.title = f"{value:.3g} {unit_from} = {converted:.3g} {unit_to}"
|
||||
embed.title = f"{value:.3g} {unit_from.unit} = {converted:.3g} {unit_to.unit}"
|
||||
embed.colour = cmn.colours.good
|
||||
else:
|
||||
embed.title = "Decibel Quick Reference"
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ import common as cmn
|
||||
class FunCog(commands.Cog):
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
with open("resources/words") as words_file:
|
||||
with open(cmn.paths.resources / "words") as words_file:
|
||||
self.words = words_file.read().lower().splitlines()
|
||||
|
||||
@commands.command(name="xkcd", aliases=["x"], category=cmn.cat.fun)
|
||||
|
||||
+5
-12
@@ -8,8 +8,6 @@ the GNU General Public License, version 2.
|
||||
"""
|
||||
|
||||
|
||||
import io
|
||||
|
||||
import aiohttp
|
||||
|
||||
import discord
|
||||
@@ -85,16 +83,11 @@ class ImageCog(commands.Cog):
|
||||
@commands.command(name="grayline", aliases=["greyline", "grey", "gray", "gl"], category=cmn.cat.maps)
|
||||
async def _grayline(self, ctx: commands.Context):
|
||||
"""Gets a map of the current greyline, where HF propagation is the best."""
|
||||
async with ctx.typing():
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = "Current Greyline Conditions"
|
||||
embed.colour = cmn.colours.good
|
||||
async with self.session.get(self.gl_url) as resp:
|
||||
if resp.status != 200:
|
||||
raise cmn.BotHTTPError(resp)
|
||||
data = io.BytesIO(await resp.read())
|
||||
embed.set_image(url="attachment://greyline.jpg")
|
||||
await ctx.send(embed=embed, file=discord.File(data, "greyline.jpg"))
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = "Current Greyline Conditions"
|
||||
embed.colour = cmn.colours.good
|
||||
embed.set_image(url=self.gl_url)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot):
|
||||
|
||||
+6
-2
@@ -9,6 +9,7 @@ the GNU General Public License, version 2.
|
||||
|
||||
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
from ctyparser import BigCty
|
||||
|
||||
@@ -17,11 +18,14 @@ from discord.ext import commands, tasks
|
||||
import common as cmn
|
||||
|
||||
|
||||
cty_path = Path("./data/cty.json")
|
||||
|
||||
|
||||
class LookupCog(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
try:
|
||||
self.cty = BigCty("./data/cty.json")
|
||||
self.cty = BigCty(cty_path)
|
||||
except OSError:
|
||||
self.cty = BigCty()
|
||||
|
||||
@@ -67,7 +71,7 @@ class LookupCog(commands.Cog):
|
||||
|
||||
@tasks.loop(hours=24)
|
||||
async def _update_cty(self):
|
||||
update = threading.Thread(target=run_update, args=(self.cty, "./data/cty.json"))
|
||||
update = threading.Thread(target=run_update, args=(self.cty, cty_path))
|
||||
update.start()
|
||||
|
||||
|
||||
|
||||
+3
-3
@@ -146,15 +146,15 @@ def qrz_process_info(data: dict):
|
||||
if address == "":
|
||||
address = None
|
||||
if "eqsl" in data:
|
||||
eqsl = "Yes" if data["eqsl"] == 1 else "No"
|
||||
eqsl = "Yes" if data["eqsl"] == "1" else "No"
|
||||
else:
|
||||
eqsl = "Unknown"
|
||||
if "mqsl" in data:
|
||||
mqsl = "Yes" if data["mqsl"] == 1 else "No"
|
||||
mqsl = "Yes" if data["mqsl"] == "1" else "No"
|
||||
else:
|
||||
mqsl = "Unknown"
|
||||
if "lotw" in data:
|
||||
lotw = "Yes" if data["lotw"] == 1 else "No"
|
||||
lotw = "Yes" if data["lotw"] == "1" else "No"
|
||||
else:
|
||||
lotw = "Unknown"
|
||||
|
||||
|
||||
+41
-58
@@ -8,12 +8,10 @@ the GNU General Public License, version 2.
|
||||
"""
|
||||
|
||||
|
||||
import io
|
||||
import re
|
||||
|
||||
import aiohttp
|
||||
|
||||
import discord
|
||||
import discord.ext.commands as commands
|
||||
|
||||
import common as cmn
|
||||
@@ -29,16 +27,11 @@ class WeatherCog(commands.Cog):
|
||||
@commands.command(name="bandconditions", aliases=["cond", "condx", "conditions"], category=cmn.cat.weather)
|
||||
async def _band_conditions(self, ctx: commands.Context):
|
||||
"""Gets a solar conditions report."""
|
||||
async with ctx.typing():
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = "Current Solar Conditions"
|
||||
embed.colour = cmn.colours.good
|
||||
async with self.session.get("http://www.hamqsl.com/solarsun.php") as resp:
|
||||
if resp.status != 200:
|
||||
raise cmn.BotHTTPError(resp)
|
||||
data = io.BytesIO(await resp.read())
|
||||
embed.set_image(url="attachment://condx.png")
|
||||
await ctx.send(embed=embed, file=discord.File(data, "condx.png"))
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = "Current Solar Conditions"
|
||||
embed.colour = cmn.colours.good
|
||||
embed.set_image(url="http://www.hamqsl.com/solarsun.php")
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.group(name="weather", aliases=["wttr"], case_insensitive=True, category=cmn.cat.weather)
|
||||
async def _weather_conditions(self, ctx: commands.Context):
|
||||
@@ -61,63 +54,53 @@ class WeatherCog(commands.Cog):
|
||||
async def _weather_conditions_forecast(self, ctx: commands.Context, *, location: str):
|
||||
"""Gets local weather forecast for the next three days from [wttr.in](http://wttr.in/).
|
||||
See help of the `weather` command for possible location types and options."""
|
||||
async with ctx.typing():
|
||||
try:
|
||||
units_arg = re.search(self.wttr_units_regex, location).group(1)
|
||||
except AttributeError:
|
||||
units_arg = ""
|
||||
if units_arg.lower() == "f":
|
||||
units = "u"
|
||||
elif units_arg.lower() == "c":
|
||||
units = "m"
|
||||
else:
|
||||
units = ""
|
||||
try:
|
||||
units_arg = re.search(self.wttr_units_regex, location).group(1)
|
||||
except AttributeError:
|
||||
units_arg = ""
|
||||
if units_arg.lower() == "f":
|
||||
units = "u"
|
||||
elif units_arg.lower() == "c":
|
||||
units = "m"
|
||||
else:
|
||||
units = ""
|
||||
|
||||
loc = self.wttr_units_regex.sub("", location).strip()
|
||||
loc = self.wttr_units_regex.sub("", location).strip()
|
||||
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Weather Forecast for {loc}"
|
||||
embed.description = "Data from [wttr.in](http://wttr.in/)."
|
||||
embed.colour = cmn.colours.good
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Weather Forecast for {loc}"
|
||||
embed.description = "Data from [wttr.in](http://wttr.in/)."
|
||||
embed.colour = cmn.colours.good
|
||||
|
||||
loc = loc.replace(" ", "+")
|
||||
async with self.session.get(f"http://wttr.in/{loc}_{units}pnFQ.png") as resp:
|
||||
if resp.status != 200:
|
||||
raise cmn.BotHTTPError(resp)
|
||||
data = io.BytesIO(await resp.read())
|
||||
embed.set_image(url="attachment://wttr_forecast.png")
|
||||
await ctx.send(embed=embed, file=discord.File(data, "wttr_forecast.png"))
|
||||
loc = loc.replace(" ", "+")
|
||||
embed.set_image(url=f"http://wttr.in/{loc}_{units}pnFQ.png")
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@_weather_conditions.command(name="now", aliases=["n"], category=cmn.cat.weather)
|
||||
async def _weather_conditions_now(self, ctx: commands.Context, *, location: str):
|
||||
"""Gets current local weather conditions from [wttr.in](http://wttr.in/).
|
||||
See help of the `weather` command for possible location types and options."""
|
||||
async with ctx.typing():
|
||||
try:
|
||||
units_arg = re.search(self.wttr_units_regex, location).group(1)
|
||||
except AttributeError:
|
||||
units_arg = ""
|
||||
if units_arg.lower() == "f":
|
||||
units = "u"
|
||||
elif units_arg.lower() == "c":
|
||||
units = "m"
|
||||
else:
|
||||
units = ""
|
||||
try:
|
||||
units_arg = re.search(self.wttr_units_regex, location).group(1)
|
||||
except AttributeError:
|
||||
units_arg = ""
|
||||
if units_arg.lower() == "f":
|
||||
units = "u"
|
||||
elif units_arg.lower() == "c":
|
||||
units = "m"
|
||||
else:
|
||||
units = ""
|
||||
|
||||
loc = self.wttr_units_regex.sub("", location).strip()
|
||||
loc = self.wttr_units_regex.sub("", location).strip()
|
||||
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Current Weather for {loc}"
|
||||
embed.description = "Data from [wttr.in](http://wttr.in/)."
|
||||
embed.colour = cmn.colours.good
|
||||
embed = cmn.embed_factory(ctx)
|
||||
embed.title = f"Current Weather for {loc}"
|
||||
embed.description = "Data from [wttr.in](http://wttr.in/)."
|
||||
embed.colour = cmn.colours.good
|
||||
|
||||
loc = loc.replace(" ", "+")
|
||||
async with self.session.get(f"http://wttr.in/{loc}_0{units}pnFQ.png") as resp:
|
||||
if resp.status != 200:
|
||||
raise cmn.BotHTTPError(resp)
|
||||
data = io.BytesIO(await resp.read())
|
||||
embed.set_image(url="attachment://wttr_now.png")
|
||||
await ctx.send(embed=embed, file=discord.File(data, "wttr_now.png"))
|
||||
loc = loc.replace(" ", "+")
|
||||
embed.set_image(url=f"http://wttr.in/{loc}_0{units}pnFQ.png")
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot):
|
||||
|
||||
@@ -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.4.0"
|
||||
release = "2.5.0"
|
||||
bot_server = "https://discord.gg/Ntbg3J4"
|
||||
|
||||
@@ -44,9 +44,19 @@ debug_mode = opt.debug # Separate assignement in-case we define an override (te
|
||||
loop = asyncio.get_event_loop()
|
||||
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
|
||||
|
||||
member_cache = discord.MemberCacheFlags.from_intents(intents)
|
||||
|
||||
bot = commands.Bot(command_prefix=opt.prefix,
|
||||
case_insensitive=True,
|
||||
description=info.description, help_command=commands.MinimalHelpCommand(),
|
||||
intents=intents,
|
||||
member_cache=member_cache,
|
||||
loop=loop,
|
||||
connector=connector)
|
||||
|
||||
@@ -88,7 +98,7 @@ async def _extctl(ctx: commands.Context):
|
||||
"""Extension control commands.
|
||||
Defaults to `list` if no subcommand specified"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
cmd = bot.get_command("extctl list")
|
||||
cmd = _extctl_list
|
||||
await ctx.invoke(cmd)
|
||||
|
||||
|
||||
@@ -156,7 +166,7 @@ async def on_command_error(ctx: commands.Context, err: commands.CommandError):
|
||||
await cmn.add_react(ctx.message, cmn.emojis.warning)
|
||||
await ctx.send_help(ctx.command)
|
||||
elif isinstance(err, commands.CommandNotFound):
|
||||
if ctx.invoked_with.startswith(("?", "!")):
|
||||
if ctx.invoked_with and ctx.invoked_with.startswith(("?", "!")):
|
||||
return
|
||||
else:
|
||||
await cmn.add_react(ctx.message, cmn.emojis.question)
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
discord.py==1.3.4
|
||||
ctyparser==2.0.0.post1
|
||||
beautifulsoup4==4.9.1
|
||||
lxml==4.5.2
|
||||
pytz==2020.1
|
||||
discord.py~=1.5.0
|
||||
ctyparser~=2.0
|
||||
beautifulsoup4
|
||||
lxml
|
||||
pytz
|
||||
|
||||
@@ -7,12 +7,22 @@ This file is part of discord-qrmbot and is released under the terms of
|
||||
the GNU General Public License, version 2.
|
||||
"""
|
||||
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .callsigninfos import (us, ca)
|
||||
from common import CallsignInfoData
|
||||
|
||||
|
||||
# format: country: (title, description, text)
|
||||
@dataclass
|
||||
class CallsignInfoData:
|
||||
"""Represents a country's callsign info"""
|
||||
title: str = ""
|
||||
desc: str = ""
|
||||
calls: str = ""
|
||||
emoji: str = ""
|
||||
|
||||
|
||||
options = {
|
||||
"us": CallsignInfoData([us.title, us.desc, us.calls, us.emoji]),
|
||||
"ca": CallsignInfoData([ca.title, ca.desc, ca.calls, ca.emoji]),
|
||||
"us": CallsignInfoData(us.title, us.desc, us.calls, us.emoji),
|
||||
"ca": CallsignInfoData(ca.title, ca.desc, ca.calls, ca.emoji),
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 416 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 407 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 237 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 441 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
@@ -1,7 +1,12 @@
|
||||
{
|
||||
"au": ["au.png", "Australia", "Amateur radio bands in Australia", "", "[Radio Amateur Society of Australia](https://vkradioamateurs.org/its-my-frequency-and-ill-cry-if-i-want-to/) [[PDF]](https://vkradioamateurs.org/wp-content/uploads/2018/05/VK-Band-Plan-Quick-Reference-1.pdf)", "🇦🇺"],
|
||||
"ca": ["ca.png", "Canada", "Amateur radio bands in Canada", "**This bandplan is incomplete**; some bands, like 630m, are simply not present. It also does not cover any band above 30MHz.", "[RAC 0-30MHz Band Plan](https://www.rac.ca/wp-content/uploads/files/pdf/RAC%20Bandplan%20December%201%202015.pdf)", "🇨🇦"],
|
||||
"cn": ["cn.png", "China", "Amateur radio bands in China", "", "Created by KN8U and NY7H", "🇨🇳"],
|
||||
"it_hf": ["it_hf.png", "Italy (HF)", "HF amateur radio bands in Italy", "", "[Associazione Radioamatori Italiani](https://www.arimi.it/download/bandplan/) [[PDF]](http://www.arimi.it/wp-content/Plan/BP%20HF%202013%2032.pdf)", "🇮🇹"],
|
||||
"it_vhf": ["it_vhf.png", "Italy (VHF/UHF)", "VHF/UHF amateur radio bands in Italy", "", "[Associazione Radioamatori Italiani](https://www.arimi.it/download/bandplan/) [[PDF]](https://www.arimi.it/wp-content/Plan/BP%20V_UHF%202013%2032.pdf)", "🇮🇹"],
|
||||
"it_shf": ["it_shf.png", "Italy (UHF/SHF)", "UHF/SHF amateur radio bands in Italy", "", "[Associazione Radioamatori Italiani](https://www.arimi.it/download/bandplan/) [[PDF]](https://www.arimi.it/wp-content/Plan/BP%20U-SHF.pdf)", "🇮🇹"],
|
||||
"jp": ["jp.png", "Japan", "Amateur radio bands in Japan", "Pending checks against the latest update from the 総務省 (MIC) of Japan, March 2020", "[JARL Amateur-Use Bandplans](https://jarl.org/Japanese/A_Shiryo/A-3_Band_Plan/bandplan20150105.pdf)", "🇯🇵"],
|
||||
"mx": ["mx.png", "Mexico", "Radio allocations in Mexico", "Full radio allocations chart for all services. No information specific to amateur radio is shown.", "Secretaría de Comunicaciones y Transportes (SCT) / Instituto Federal de Telecomunicaciones (IFT)", "🇲🇽"],
|
||||
"nl": ["nl.png", "Netherlands", "Amateur radio bands in the Netherlands", "", "", "🇳🇱"],
|
||||
"nl": ["nl.png", "Netherlands", "Amateur radio bands in the Netherlands", "*This version of this bandplan is no longer hosted on VERNON's website and it's accuracy cannot be guaranteed.*", "[VERNON Global Band Plan van de Telecom Agency, Ministerie van Economische Zaken en Klimaat](https://www.veron.nl/downloads/brochures/)", "🇳🇱"],
|
||||
"us": ["us.png", "USA", "Amateur radio bands in the USA", "", "*[ARRL Frequency Chart](https://www.arrl.org/shop/ARRL-Frequency-Chart-11-17/)* [[PDF]](http://www.arrl.org/files/file/Regulatory/Band%20Chart/Band%20Chart%20-%2011X17%20Color.pdf)", "🇺🇸"]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"arrl": ["arrl-rac.png", "ARRL Sections", "ARRL Sections", "", "[EI8IC](https://www.mapability.com/ei8ic/maps/maps.php)", "🇺🇸"],
|
||||
"rac": ["arrl-rac.png", "RAC Sections", "RAC Sections", "", "[EI8IC](https://www.mapability.com/ei8ic/maps/maps.php)", "🇨🇦"],
|
||||
"ca": ["ca.png", "Canada's Prefixes", "Map of the prefix regions in Canada", "", "[Denelson83 (Wikimedia Commons)](https://commons.wikimedia.org/wiki/File:Amateur_radio_prefixes_in_Canada.svg)", "🇨🇦"],
|
||||
"cn": ["cn.png", "China's Prefixes", "Map of prefix regions in China", "", "CRAC", "🇨🇳"],
|
||||
"us": ["us.png", "USA's Prefixes", "Map of prefix regions in the USA", "", "*[ARRL WAS Map](https://www.arrl.org/was-forms)* [[PDF]](http://www.arrl.org/files/file/Awards%20Application%20Forms/WASmap_Color.pdf)", "🇺🇸"],
|
||||
"ca": ["ca.png", "Canada's Prefixes", "Map of the prefix regions in Canada", "", "[Denelson83 (Wikimedia Commons)](https://commons.wikimedia.org/wiki/File:Amateur_radio_prefixes_in_Canada.svg)", "🇨🇦"],
|
||||
"ituz": ["itu-zones.png", "ITU Zones", "ITU Zones", "", "[EI8IC](https://www.mapability.com/ei8ic/maps/maps.php)", "🇺🇳"],
|
||||
"itur": ["itu-regions.png", "ITU Regions", "ITU Regions", "These are also used by the IARU for their regions.", "[EI8IC](https://www.mapability.com/ei8ic/maps/maps.php)", "🇺🇳"],
|
||||
"cq": ["cq-zones.png", "CQ Zones", "CQ Zones", "These are used for the CQWW contest.", "[EI8IC](https://www.mapability.com/ei8ic/maps/maps.php)", "🌐"]
|
||||
"ituz": ["itu-zones.png", "ITU Zones", "ITU Zones", "", "[EI8IC](https://www.mapability.com/ei8ic/maps/maps.php)", "🇺🇳"],
|
||||
"arrl": ["arrl-rac.png", "ARRL Sections", "ARRL Sections", "", "[EI8IC](https://www.mapability.com/ei8ic/maps/maps.php)", "🇺🇸"],
|
||||
"cq": ["cq-zones.png", "CQ Zones", "CQ Zones", "These are used for the CQWW contest.", "[EI8IC](https://www.mapability.com/ei8ic/maps/maps.php)", "🌐"],
|
||||
"rac": ["arrl-rac.png", "RAC Sections", "RAC Sections", "", "[EI8IC](https://www.mapability.com/ei8ic/maps/maps.php)", "🇨🇦"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user