mirror of
https://github.com/miaowware/qrm2.git
synced 2026-06-04 06:54:45 -04:00
Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fed97a03b3 | |||
| 5f796d479e | |||
| 3ba55d4c35 | |||
| f4ed93dc76 | |||
| 2cb4b03532 | |||
| bc93462c29 | |||
| 6867c45c8c | |||
| dcbb7acab8 | |||
| 3803ce6045 | |||
| 8ca4911072 | |||
| 6e2468f04f | |||
| b17a8a1749 | |||
| 8dfa7001ef | |||
| f6ed8430b9 | |||
| d650cbd6c1 | |||
| 4d9f9d1b19 | |||
| 1c649aacc2 | |||
| 2049ca9fca | |||
| 38416d9050 | |||
| 8dcdc22fe4 | |||
| c57056e586 | |||
| b281122d41 | |||
| fa1cc16c6f | |||
| dfd9479e0d | |||
| 4b122518f5 | |||
| 0e28366265 | |||
| 9471eaa3cb | |||
| 27c290cff8 | |||
| 58878352aa | |||
| af136c3d3e | |||
| 9ef4864d15 | |||
| 6803b058d5 | |||
| e37fdf40fb | |||
| fdfc8cce06 | |||
| 56b74108eb | |||
| 29d0440d3d | |||
| 29e75c38e1 | |||
| 6c32dcbead | |||
| 74327378b9 | |||
| c1d3c63503 | |||
| 6768a1e01d | |||
| ea508d2a59 | |||
| f56efd3410 | |||
| 976b3e8bf3 | |||
| 0b4204dc13 | |||
| 8686b0ef96 | |||
| 9113638b67 | |||
| 831667ec10 | |||
| b64c7ee39a | |||
| 528307f22f | |||
| 09c58f9ba2 | |||
| 61f0c9423e | |||
| b6f6d0408c | |||
| 94f9865103 | |||
| 08ca455895 | |||
| 925a05aafb | |||
| 12886fad89 | |||
| ce107f5a82 | |||
| 0aa5e6ebd6 | |||
| 671b0e9ee5 | |||
| 776ff72581 | |||
| 58c69f5aeb | |||
| 2c3535d99e | |||
| 04cbc920ce | |||
| f5a9b0b780 | |||
| eb5e038624 | |||
| 1a1b4c9329 | |||
| 8db13755bc | |||
| ac740ee679 | |||
| 0608a74e6c | |||
| eaa47fc724 | |||
| 60764cd733 | |||
| 8bfaaf4af6 | |||
| 30455153ba | |||
| d9bd3d354c | |||
| 8f15feab94 | |||
| 4e73fa3734 | |||
| 5e33a6aca6 | |||
| 07fd9255e0 | |||
| 238cdf1c72 | |||
| 25eae1fc0f | |||
| f4f034abfe | |||
| ae673f7026 | |||
| 8a9c8cbd43 | |||
| b94a8d42dd | |||
| 60f8e80b82 | |||
| 6e516a12db | |||
| 9552a7f6d4 | |||
| e7baca453c | |||
| 673d154b94 | |||
| 118d5e0b63 |
@@ -0,0 +1,58 @@
|
|||||||
|
name: Docker Build and Push
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
# Publish `master` as Docker `dev` image.
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
# Publish `v*` tags as releases and as `latest`.
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE_NAME: qrm2
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
run: |
|
||||||
|
echo ${{ github.sha }} > git_commit
|
||||||
|
docker build . --file Dockerfile -t $IMAGE_NAME
|
||||||
|
|
||||||
|
- name: Log into Github Package Registry
|
||||||
|
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin
|
||||||
|
|
||||||
|
- name: Log into Docker Hub
|
||||||
|
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||||
|
|
||||||
|
- name: Push image to registries
|
||||||
|
run: |
|
||||||
|
GITHUB_IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME
|
||||||
|
DOCKER_IMAGE_ID=${{ secrets.DOCKER_USERNAME }}/$IMAGE_NAME
|
||||||
|
|
||||||
|
# Strip git ref prefix from version
|
||||||
|
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
|
||||||
|
|
||||||
|
# Strip "v" prefix from tag name
|
||||||
|
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
|
||||||
|
|
||||||
|
[[ "$VERSION" == "master" ]] && VERSION=dev
|
||||||
|
|
||||||
|
echo GITHUB_IMAGE_ID=$GITHUB_IMAGE_ID
|
||||||
|
echo DOCKER_IMAGE_ID=$DOCKER_IMAGE_ID
|
||||||
|
echo VERSION=$VERSION
|
||||||
|
|
||||||
|
# tag for Github Registry
|
||||||
|
docker tag $IMAGE_NAME $GITHUB_IMAGE_ID:$VERSION
|
||||||
|
[[ "$VERSION" != "dev" ]] && docker tag $IMAGE_NAME $GITHUB_IMAGE_ID:latest
|
||||||
|
docker push $GITHUB_IMAGE_ID
|
||||||
|
|
||||||
|
# tag for Docker Hub
|
||||||
|
docker tag $IMAGE_NAME $DOCKER_IMAGE_ID:$VERSION
|
||||||
|
[[ "$VERSION" != "dev" ]] && docker tag $IMAGE_NAME $DOCKER_IMAGE_ID:latest
|
||||||
|
docker push $DOCKER_IMAGE_ID
|
||||||
+63
-7
@@ -7,7 +7,57 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
|
||||||
## [v2.1.0] - 2020-01-04
|
## [2.3.1] - 2020-04-02
|
||||||
|
### Fixed
|
||||||
|
- Wordlist containing innappropriate words.
|
||||||
|
|
||||||
|
|
||||||
|
## [2.3.0] - 2020-03-30
|
||||||
|
### Added
|
||||||
|
- `?phoneticweight` command, which calculates a message's length in syllables.
|
||||||
|
- `?standards` command to display [xkcd 927](https://xkcd.com/927/).
|
||||||
|
### Changed
|
||||||
|
- Python>=3.7 now required.
|
||||||
|
|
||||||
|
|
||||||
|
## [2.2.3] - 2020-03-29
|
||||||
|
### Fixed
|
||||||
|
- Commands are no longer case-sensitive.
|
||||||
|
|
||||||
|
|
||||||
|
## [2.2.2] - 2020-02-25
|
||||||
|
### Fixed
|
||||||
|
- Fixed issue where HamStudy questions with images would cause an error.
|
||||||
|
- Added/fixed/removed typing indicators in numerous commands.
|
||||||
|
|
||||||
|
|
||||||
|
## [2.2.1] - 2020-02-20
|
||||||
|
### Fixed
|
||||||
|
- Fixed issue where some HamStudy pools will become unselectable.
|
||||||
|
|
||||||
|
|
||||||
|
## [2.2.0] - 2020-02-15
|
||||||
|
### Added
|
||||||
|
- Added Trustee field to qrz command for club callsigns.
|
||||||
|
- Added alias for `ae7q call` command (`ae7q c`).
|
||||||
|
- Added ae7q lookup by FRN and Licensee ID, and for trustee records (`ae7q frn, licensee, trustee`).
|
||||||
|
### Changed
|
||||||
|
- Changelog command to accept a version as argument.
|
||||||
|
- The qrz command can now link to a QRZ page instead of embedding the data with the `--link` flag.
|
||||||
|
- All currently-available pools can now be accessed by the `hamstudy` command.
|
||||||
|
- The `hamstudy` command now uses the syntax `?hamstudy <country> <pool>`.
|
||||||
|
- Replaced `hamstudyanswer` command with answering by reaction.
|
||||||
|
- Removed all generic error handling from commands.
|
||||||
|
- Cleaned up the description of multiple commands.
|
||||||
|
- Updated repository links from classabbyamp/discord-qrm2 to miaowware/qrm2.
|
||||||
|
### Fixed
|
||||||
|
- Fixed ditto marks (") appearing in the ae7q call command.
|
||||||
|
- Fixed issue where incorrect table was parsed in ae7q call command.
|
||||||
|
- Fixed warning emoji reaction on messages starting with "??".
|
||||||
|
- Fixed issue where `prefixes` would error when given an invalid argument.
|
||||||
|
|
||||||
|
|
||||||
|
## [2.1.0] - 2020-01-04
|
||||||
### Added
|
### Added
|
||||||
- New NATO "phonetics" command.
|
- New NATO "phonetics" command.
|
||||||
- Flag emojis to commands with countries.
|
- Flag emojis to commands with countries.
|
||||||
@@ -18,14 +68,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Command linking to the issue tracker.
|
- Command linking to the issue tracker.
|
||||||
- New key in options.py: pika.
|
- New key in options.py: pika.
|
||||||
### Changed
|
### Changed
|
||||||
- The "phonetics" command is not called "funetics".
|
- The "phonetics" command is now called "funetics".
|
||||||
- All commands now respond in embeds.
|
- All commands now respond in embeds.
|
||||||
- Playing status can now change on a schedule or randomly from a list.
|
- Playing status can now change on a schedule or randomly from a list.
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fixed incorrect information in the `prefixes` command.
|
- Fixed incorrect information in the `prefixes` command.
|
||||||
|
|
||||||
|
|
||||||
## [v2.0.0] - 2019-12-16
|
## [2.0.0] - 2019-12-16
|
||||||
### Added
|
### Added
|
||||||
- Rich lookup for AE7Q.com (callsigns only, more to come)
|
- Rich lookup for AE7Q.com (callsigns only, more to come)
|
||||||
- Rich lookup for QRZ.com, if a QRZ subscription is present
|
- Rich lookup for QRZ.com, if a QRZ subscription is present
|
||||||
@@ -52,9 +102,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Issue in morse and unmorse commands where spaces were not interpreted correctly
|
- Issue in morse and unmorse commands where spaces were not interpreted correctly
|
||||||
|
|
||||||
|
|
||||||
## v1.0.0 - 2019-07-31 [YANKED]
|
## 1.0.0 - 2019-07-31 [YANKED]
|
||||||
|
|
||||||
|
|
||||||
[Unreleased]: https://github.com/classabbyamp/discord-qrm2/compare/v2.1.0...HEAD
|
[Unreleased]: https://github.com/miaowware/qrm2/compare/v2.3.1...HEAD
|
||||||
[v2.1.0]: https://github.com/classabbyamp/discord-qrm2/releases/tag/v2.1.0
|
[2.3.1]: https://github.com/miaowware/qrm2/releases/tag/v2.3.1
|
||||||
[v2.0.0]: https://github.com/classabbyamp/discord-qrm2/releases/tag/v2.0.0
|
[2.3.0]: https://github.com/miaowware/qrm2/releases/tag/v2.3.0
|
||||||
|
[2.2.3]: https://github.com/miaowware/qrm2/releases/tag/v2.2.3
|
||||||
|
[2.2.2]: https://github.com/miaowware/qrm2/releases/tag/v2.2.2
|
||||||
|
[2.2.1]: https://github.com/miaowware/qrm2/releases/tag/v2.2.1
|
||||||
|
[2.2.0]: https://github.com/miaowware/qrm2/releases/tag/v2.2.0
|
||||||
|
[2.1.0]: https://github.com/miaowware/qrm2/releases/tag/v2.1.0
|
||||||
|
[2.0.0]: https://github.com/miaowware/qrm2/releases/tag/v2.0.0
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ See [README-DOCKER.md](./README-DOCKER.md)
|
|||||||
|
|
||||||
### Without Docker
|
### Without Docker
|
||||||
|
|
||||||
|
Requires Python 3.7 or newer.
|
||||||
|
|
||||||
Prep the environment. For more information on extra options, see the [quick-bot-no-pain Makefile documentation](https://github.com/0x5c/quick-bot-no-pain/blob/master/docs/makefile.md).
|
Prep the environment. For more information on extra options, see the [quick-bot-no-pain Makefile documentation](https://github.com/0x5c/quick-bot-no-pain/blob/master/docs/makefile.md).
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -28,7 +30,7 @@ $ run.sh
|
|||||||
|
|
||||||
## Copyright
|
## Copyright
|
||||||
|
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This program is released under the terms of the GNU General Public License,
|
This program is released under the terms of the GNU General Public License,
|
||||||
version 2. See `COPYING` for full license text.
|
version 2. See `COPYING` for full license text.
|
||||||
|
|||||||
@@ -1,58 +1,71 @@
|
|||||||
"""
|
"""
|
||||||
Common tools for the bot.
|
Common tools for the bot.
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrm2 and is released under the terms of the GNU
|
This file is part of qrm2 and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
---
|
|
||||||
|
|
||||||
`colours`: Colours used by embeds.
|
|
||||||
|
|
||||||
`cat`: Category names for the HelpCommand.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
from pathlib import Path
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
|
|
||||||
import data.options as opt
|
import data.options as opt
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["colours", "cat", "emojis", "embed_factory", "error_embed_factory", "add_react", "check_if_owner"]
|
__all__ = ["colours", "cat", "emojis", "paths", "ImageMetadata", "ImagesGroup",
|
||||||
|
"embed_factory", "error_embed_factory", "add_react", "check_if_owner"]
|
||||||
|
|
||||||
|
|
||||||
# --- Common values ---
|
# --- Common values ---
|
||||||
|
|
||||||
colours = SimpleNamespace(good=0x43B581,
|
colours = SimpleNamespace(
|
||||||
|
good=0x43B581,
|
||||||
neutral=0x7289DA,
|
neutral=0x7289DA,
|
||||||
bad=0xF04747)
|
bad=0xF04747,
|
||||||
|
)
|
||||||
|
|
||||||
# meow
|
# meow
|
||||||
cat = SimpleNamespace(lookup='Information Lookup',
|
cat = SimpleNamespace(
|
||||||
fun='Fun',
|
lookup="Information Lookup",
|
||||||
maps='Mapping',
|
fun="Fun",
|
||||||
ref='Reference',
|
maps="Mapping",
|
||||||
study='Exam Study',
|
ref="Reference",
|
||||||
weather='Land and Space Weather')
|
study="Exam Study",
|
||||||
|
weather="Land and Space Weather",
|
||||||
|
admin="Bot Control",
|
||||||
|
)
|
||||||
|
|
||||||
emojis = SimpleNamespace(check_mark='✅',
|
emojis = SimpleNamespace(
|
||||||
x='❌',
|
check_mark="✅",
|
||||||
warning='⚠️',
|
x="❌",
|
||||||
question='❓',
|
warning="⚠️",
|
||||||
no_entry='⛔',
|
question="❓",
|
||||||
bangbang='‼️')
|
no_entry="⛔",
|
||||||
|
bangbang="‼️",
|
||||||
|
a="🇦",
|
||||||
|
b="🇧",
|
||||||
|
c="🇨",
|
||||||
|
d="🇩",
|
||||||
|
)
|
||||||
|
|
||||||
paths = SimpleNamespace(data=Path("./data/"),
|
paths = SimpleNamespace(
|
||||||
|
data=Path("./data/"),
|
||||||
resources=Path("./resources/"),
|
resources=Path("./resources/"),
|
||||||
bandcharts=Path("./resources/img/bandcharts/"),
|
bandcharts=Path("./resources/img/bandcharts/"),
|
||||||
maps=Path("./resources/img/maps/"))
|
maps=Path("./resources/img/maps/"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# --- Classes ---
|
# --- Classes ---
|
||||||
@@ -94,6 +107,41 @@ class ImagesGroup(collections.abc.Mapping):
|
|||||||
return str(self._images)
|
return str(self._images)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Exceptions ---
|
||||||
|
|
||||||
|
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}"
|
||||||
|
super().__init__(msg)
|
||||||
|
self.response = response
|
||||||
|
self.status = response.status
|
||||||
|
self.reason = response.reason
|
||||||
|
|
||||||
|
|
||||||
|
# --- Converters ---
|
||||||
|
|
||||||
|
class GlobalChannelConverter(commands.IDConverter):
|
||||||
|
"""Converter to get any bot-acessible channel by ID/mention (global), or name (in current guild only)."""
|
||||||
|
async def convert(self, ctx: commands.Context, argument: str):
|
||||||
|
bot = ctx.bot
|
||||||
|
guild = ctx.guild
|
||||||
|
match = self._get_id_match(argument) or re.match(r"<#([0-9]+)>$", argument)
|
||||||
|
result = None
|
||||||
|
if match is None:
|
||||||
|
# not a mention/ID
|
||||||
|
if guild:
|
||||||
|
result = discord.utils.get(guild.text_channels, name=argument)
|
||||||
|
else:
|
||||||
|
raise commands.BadArgument(f"""Channel named "{argument}" not found in this guild.""")
|
||||||
|
else:
|
||||||
|
channel_id = int(match.group(1))
|
||||||
|
result = bot.get_channel(channel_id)
|
||||||
|
if not isinstance(result, (discord.TextChannel, discord.abc.PrivateChannel)):
|
||||||
|
raise commands.BadArgument(f"""Channel "{argument}" not found.""")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
# --- Helper functions ---
|
# --- Helper functions ---
|
||||||
|
|
||||||
def embed_factory(ctx: commands.Context) -> discord.Embed:
|
def embed_factory(ctx: commands.Context) -> discord.Embed:
|
||||||
@@ -111,7 +159,7 @@ def error_embed_factory(ctx: commands.Context, exception: Exception, debug_mode:
|
|||||||
fmtd_ex = traceback.format_exception_only(exception.__class__, exception)
|
fmtd_ex = traceback.format_exception_only(exception.__class__, exception)
|
||||||
embed = embed_factory(ctx)
|
embed = embed_factory(ctx)
|
||||||
embed.title = "⚠️ Error"
|
embed.title = "⚠️ Error"
|
||||||
embed.description = "```\n" + '\n'.join(fmtd_ex) + "```"
|
embed.description = "```\n" + "\n".join(fmtd_ex) + "```"
|
||||||
embed.colour = colours.bad
|
embed.colour = colours.bad
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"_id": "56956f51f65e5c590272e372",
|
||||||
|
"appears": "2016-04-01T06:00:00.000Z",
|
||||||
|
"class": "Amateur Extra",
|
||||||
|
"subtext": "Expires Jul 1, 2020",
|
||||||
|
"valid_from": "2016-07-01T06:00:00.000Z",
|
||||||
|
"expires": "2020-07-01T06:00:00.000Z",
|
||||||
|
"official_name": "Element 4",
|
||||||
|
"id": "E4_2016",
|
||||||
|
"slug": "extra2016",
|
||||||
|
"passing": 37,
|
||||||
|
"year": 2016,
|
||||||
|
"pool": [{
|
||||||
|
"_id": "5cd63f15910d9b003d545bd7",
|
||||||
|
"qcount": 4,
|
||||||
|
"id": "E5",
|
||||||
|
"name": "ELECTRICAL PRINCIPLES",
|
||||||
|
"sections": [{
|
||||||
|
"_id": "5cd63f15910d9b003d545beb",
|
||||||
|
"id": "E5C",
|
||||||
|
"questions": [{
|
||||||
|
"_id": "5cd63f15910d9b003d545bef",
|
||||||
|
"keywords": ["4"],
|
||||||
|
"answer": "B",
|
||||||
|
"answers": {
|
||||||
|
"A": "Point 2",
|
||||||
|
"B": "Point 4",
|
||||||
|
"C": "Point 5",
|
||||||
|
"D": "Point 6"
|
||||||
|
},
|
||||||
|
"fccpart": "",
|
||||||
|
"id": "E5C14",
|
||||||
|
"image": "E5-2.png",
|
||||||
|
"text": "Which point on Figure E5-2 best represents the impedance of a series circuit consisting of a 400 ohm resistor and a 38 picofarad capacitor at 14 MHz?"
|
||||||
|
}],
|
||||||
|
"summary": "Coordinate systems and phasors in electronics: Rectangular Coordinates; Polar Coordinates; Phasors"
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
"updated": "2019-05-11T03:18:46.121Z",
|
||||||
|
"category": "default",
|
||||||
|
"testIdEnd": 19999,
|
||||||
|
"testIdStart": 10000,
|
||||||
|
"__v": 12,
|
||||||
|
"mat_icon": "flash_on",
|
||||||
|
"tagline": "Serious General operators only! This is the most advanced US license class!",
|
||||||
|
"keywords": ["ham radio extra test prep", "amateur extra class radio study", "amateur extra class ham exam", "ham radio amateur extra test 2016", "2016 amateur extra class", "ham radio license exam", "extra class flash card"],
|
||||||
|
"replaces": "E4_2012"
|
||||||
|
}
|
||||||
+419
-98
@@ -1,21 +1,27 @@
|
|||||||
"""
|
"""
|
||||||
ae7q extension for qrm
|
ae7q extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrm2 and is released under the terms of the GNU
|
This file is part of qrm2 and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
---
|
|
||||||
Test callsigns:
|
|
||||||
KN8U: active, restricted
|
|
||||||
AB2EE: expired, restricted
|
|
||||||
KE8FGB: assigned once, no restrictions
|
|
||||||
NA2AAA: unassigned, no records
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import discord.ext.commands as commands
|
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
# 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 re
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import ae7qparser
|
||||||
|
|
||||||
|
import discord.ext.commands as commands
|
||||||
|
|
||||||
import common as cmn
|
import common as cmn
|
||||||
|
|
||||||
@@ -23,113 +29,428 @@ import common as cmn
|
|||||||
class AE7QCog(commands.Cog):
|
class AE7QCog(commands.Cog):
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.session = bot.qrm.session
|
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
|
||||||
|
|
||||||
@commands.group(name="ae7q", aliases=["ae"], category=cmn.cat.lookup)
|
@commands.group(name="ae7q", aliases=["ae"], case_insensitive=True, category=cmn.cat.lookup)
|
||||||
async def _ae7q_lookup(self, ctx: commands.Context):
|
async def _ae7q_lookup(self, ctx: commands.Context):
|
||||||
'''Look up a callsign, FRN, or Licensee ID on [ae7q.com](http://ae7q.com/).'''
|
"""Looks up a callsign, FRN, or Licensee ID on [ae7q.com](http://ae7q.com/)."""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
await ctx.send_help(ctx.command)
|
await ctx.send_help(ctx.command)
|
||||||
|
|
||||||
@_ae7q_lookup.command(name="call", category=cmn.cat.lookup)
|
@_ae7q_lookup.command(name="call", aliases=["c"], category=cmn.cat.lookup)
|
||||||
async def _ae7q_call(self, ctx: commands.Context, callsign: str):
|
async def _ae7q_call(self, ctx: commands.Context, callsign: str):
|
||||||
'''Look up the history for a callsign on [ae7q.com](http://ae7q.com/).'''
|
"""Looks up the history of a callsign on [ae7q.com](http://ae7q.com/)."""
|
||||||
|
with ctx.typing():
|
||||||
callsign = callsign.upper()
|
callsign = callsign.upper()
|
||||||
desc = ''
|
|
||||||
base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
|
|
||||||
embed = cmn.embed_factory(ctx)
|
|
||||||
|
|
||||||
async with self.session.get(base_url + callsign) as resp:
|
call_data = ae7qparser.get_call(callsign)
|
||||||
if resp.status != 200:
|
|
||||||
embed.title = "Error in AE7Q call command"
|
|
||||||
embed.description = 'Could not load AE7Q'
|
|
||||||
embed.colour = cmn.colours.bad
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
return
|
|
||||||
page = await resp.text()
|
|
||||||
|
|
||||||
soup = BeautifulSoup(page, features="html.parser")
|
|
||||||
tables = soup.select("table.Database")
|
|
||||||
|
|
||||||
for table in tables:
|
|
||||||
rows = table.find_all("tr")
|
|
||||||
if len(rows) > 1 and len(rows[0]) > 1:
|
|
||||||
break
|
|
||||||
if desc == '':
|
|
||||||
for row in rows:
|
|
||||||
desc += " ".join(row.getText().split())
|
|
||||||
desc += '\n'
|
|
||||||
desc = desc.replace(callsign, f'`{callsign}`')
|
|
||||||
rows = None
|
|
||||||
|
|
||||||
if rows is None:
|
|
||||||
embed.title = f"AE7Q History for {callsign}"
|
|
||||||
embed.colour = cmn.colours.bad
|
|
||||||
embed.url = f"{base_url}{callsign}"
|
|
||||||
embed.description = desc
|
|
||||||
embed.description += f'\nNo records found for `{callsign}`'
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
return
|
|
||||||
|
|
||||||
table_contents = [] # store your table here
|
|
||||||
for tr in rows:
|
|
||||||
if rows.index(tr) == 0:
|
|
||||||
continue
|
|
||||||
row_cells = []
|
|
||||||
for td in tr.find_all('td'):
|
|
||||||
if td.getText().strip() != '':
|
|
||||||
row_cells.append(td.getText().strip())
|
|
||||||
else:
|
|
||||||
row_cells.append('-')
|
|
||||||
if 'colspan' in td.attrs and int(td.attrs['colspan']) > 1:
|
|
||||||
for i in range(int(td.attrs['colspan']) - 1):
|
|
||||||
row_cells.append(row_cells[-1])
|
|
||||||
for i, cell in enumerate(row_cells):
|
|
||||||
if cell == '"':
|
|
||||||
cell = table_contents[-1][i]
|
|
||||||
if len(row_cells) > 1:
|
|
||||||
table_contents += [row_cells]
|
|
||||||
|
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f"AE7Q Records for {callsign}"
|
embed.title = f"AE7Q Callsign History for {callsign}"
|
||||||
|
embed.url = call_data.query_url
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
embed.url = f"{base_url}{callsign}"
|
embed.description = ""
|
||||||
|
|
||||||
for row in table_contents[0:3]:
|
if isinstance(call_data, ae7qparser.Ae7qCallData):
|
||||||
header = f'**{row[0]}** ({row[1]})'
|
if call_data.conditions:
|
||||||
body = (f'Class: *{row[2]}*\n'
|
embed.description = " ".join([
|
||||||
f'Region: *{row[3]}*\n'
|
" ".join([y.strip() for y in x]) for x in call_data.conditions
|
||||||
f'Status: *{row[4]}*\n'
|
]).replace(callsign, f"`{callsign}`")
|
||||||
f'Granted: *{row[5]}*\n'
|
|
||||||
f'Effective: *{row[6]}*\n'
|
if not call_data.call_history:
|
||||||
f'Cancelled: *{row[7]}*\n'
|
if not call_data.event_callsign_history:
|
||||||
f'Expires: *{row[8]}*')
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.description += f"\nNo records found for `{callsign}`"
|
||||||
|
else:
|
||||||
|
# add the first five rows of the event callsign history to the embed
|
||||||
|
for row in call_data.event_callsign_history[0:5]:
|
||||||
|
header = f"**{row.start_date:%Y-%m-%d}-{row.end_date:%Y-%m-%d}**"
|
||||||
|
body = (f"Requestor: *{row.entity_name} ({row.callsign})*\n"
|
||||||
|
f"Event: *{row.event_name}*")
|
||||||
embed.add_field(name=header, value=body, inline=False)
|
embed.add_field(name=header, value=body, inline=False)
|
||||||
|
|
||||||
embed.description = desc
|
if len(call_data.event_callsign_history) > 5:
|
||||||
if len(table_contents) > 3:
|
embed.description += (f"\nRecords 1 to 5 of {len(call_data.event_callsign_history)}. "
|
||||||
embed.description += f'\nRecords 1 to 3 of {len(table_contents)}. See ae7q.com for more...'
|
f"See [ae7q.com]({call_data.query_url}) for more...")
|
||||||
|
|
||||||
|
else:
|
||||||
|
# add the first three rows of the callsign history to the embed
|
||||||
|
for row in call_data.call_history[0:3]:
|
||||||
|
header = f"**{row.entity_name}** ({row.applicant_type})"
|
||||||
|
body = (f"Class: *{row.operator_class}*\n"
|
||||||
|
f"Region: *{row.region_state}*\n"
|
||||||
|
f"Status: *{row.license_status}*\n")
|
||||||
|
if row.grant_date:
|
||||||
|
body += f"Granted: *{row.grant_date:%Y-%m-%d}*\n"
|
||||||
|
if row.effective_date:
|
||||||
|
body += f"Effective: *{row.effective_date:%Y-%m-%d}*\n"
|
||||||
|
if row.cancel_date:
|
||||||
|
body += f"Cancelled: *{row.cancel_date:%Y-%m-%d}*\n"
|
||||||
|
if row.expire_date:
|
||||||
|
body += f"Expires: *{row.expire_date:%Y-%m-%d}*\n"
|
||||||
|
|
||||||
|
embed.add_field(name=header, value=body, inline=False)
|
||||||
|
|
||||||
|
if len(call_data.call_history) > 3:
|
||||||
|
embed.description += (f"\nRecords 1 to 3 of {len(call_data.call_history)}. "
|
||||||
|
f"See [ae7q.com]({call_data.query_url}) for more...")
|
||||||
|
|
||||||
|
elif isinstance(call_data, ae7qparser.Ae7qCanadianCallData):
|
||||||
|
if not call_data.callsign_data:
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.description += f"\nNo records found for `{callsign}`"
|
||||||
|
else:
|
||||||
|
if call_data.given_names != "" and call_data.surname != "":
|
||||||
|
embed.add_field(name="Name", value=f"{call_data.given_names} {call_data.surname}", inline=True)
|
||||||
|
if call_data.address != "":
|
||||||
|
embed.add_field(name="Address", value=call_data.address, inline=True)
|
||||||
|
if call_data.locality != "":
|
||||||
|
embed.add_field(name="Locality", value=call_data.locality, inline=True)
|
||||||
|
if call_data.province != "":
|
||||||
|
embed.add_field(name="Province", value=call_data.province, inline=True)
|
||||||
|
if call_data.postal_code != "":
|
||||||
|
embed.add_field(name="Postal Code", value=call_data.postal_code, inline=True)
|
||||||
|
if call_data.country != "":
|
||||||
|
embed.add_field(name="Country", value=call_data.country, inline=True)
|
||||||
|
if call_data.region != "":
|
||||||
|
embed.add_field(name="Region", value=call_data.region, inline=True)
|
||||||
|
if call_data.grid_square != "":
|
||||||
|
embed.add_field(name="Grid Square", value=call_data.grid_square, inline=True)
|
||||||
|
if call_data.qualifications != "":
|
||||||
|
embed.add_field(name="License Qualifications", value=call_data.qualifications, inline=True)
|
||||||
|
|
||||||
|
else:
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.description += f"\nNo records found for `{callsign}`"
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
# TODO: write commands for other AE7Q response types?
|
@_ae7q_lookup.command(name="trustee", aliases=["t"], category=cmn.cat.lookup)
|
||||||
# @_ae7q_lookup.command(name="trustee")
|
async def _ae7q_trustee(self, ctx: commands.Context, callsign: str):
|
||||||
# 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/)."""
|
||||||
# pass
|
with ctx.typing():
|
||||||
|
callsign = callsign.upper()
|
||||||
|
|
||||||
# @_ae7q_lookup.command(name="applications", aliases=['apps'])
|
call_data = ae7qparser.get_call(callsign)
|
||||||
# async def _ae7q_applications(self, ctx: commands.Context, callsign: str):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
# @_ae7q_lookup.command(name="frn")
|
embed = cmn.embed_factory(ctx)
|
||||||
# async def _ae7q_frn(self, ctx: commands.Context, frn: str):
|
embed.title = f"AE7Q Trustee History for {callsign}"
|
||||||
# base_url = "http://ae7q.com/query/data/FrnHistory.php?FRN="
|
embed.url = call_data.query_url
|
||||||
# pass
|
embed.colour = cmn.colours.good
|
||||||
|
embed.description = ""
|
||||||
|
|
||||||
# @_ae7q_lookup.command(name="licensee", aliases=["lic"])
|
if isinstance(call_data, ae7qparser.Ae7qCallData):
|
||||||
# async def _ae7q_licensee(self, ctx: commands.Context, frn: str):
|
if not call_data.trustee_history:
|
||||||
# base_url = "http://ae7q.com/query/data/LicenseeIdHistory.php?ID="
|
embed.colour = cmn.colours.bad
|
||||||
# pass
|
embed.description += f"\nNo records found for `{callsign}`"
|
||||||
|
else:
|
||||||
|
# add the first three rows of the trustee history to the embed
|
||||||
|
for row in call_data.trustee_history[0:3]:
|
||||||
|
header = f"**{row.callsign}** ({row.applicant_type})"
|
||||||
|
body = (f"Name: *{row.entity_name}*\n"
|
||||||
|
f"Region: *{row.region_state}*\n"
|
||||||
|
f"Status: *{row.license_status}*\n")
|
||||||
|
if row.grant_date:
|
||||||
|
body += f"Granted: *{row.grant_date:%Y-%m-%d}*\n"
|
||||||
|
if row.effective_date:
|
||||||
|
body += f"Effective: *{row.effective_date:%Y-%m-%d}*\n"
|
||||||
|
if row.cancel_date:
|
||||||
|
body += f"Cancelled: *{row.cancel_date:%Y-%m-%d}*\n"
|
||||||
|
if row.expire_date:
|
||||||
|
body += f"Expires: *{row.expire_date:%Y-%m-%d}*\n"
|
||||||
|
|
||||||
|
embed.add_field(name=header, value=body, inline=False)
|
||||||
|
|
||||||
|
if len(call_data.trustee_history) > 3:
|
||||||
|
embed.description += (f"\nRecords 1 to 3 of {len(call_data.trustee_history)}. "
|
||||||
|
f"See [ae7q.com]({call_data.query_url}) for more...")
|
||||||
|
|
||||||
|
else:
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.description += f"\nNo records found for `{callsign}`"
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@_ae7q_lookup.command(name="applications", aliases=["a", "apps"], category=cmn.cat.lookup)
|
||||||
|
async def _ae7q_applications(self, ctx: commands.Context, query: str):
|
||||||
|
"""Looks up the application history for a callsign, FRN, or Licensee ID on [ae7q.com](http://ae7q.com/)."""
|
||||||
|
with ctx.typing():
|
||||||
|
query = query.upper()
|
||||||
|
|
||||||
|
# LID
|
||||||
|
if re.match(r"L\d+", query):
|
||||||
|
data = ae7qparser.get_licensee_id(query)
|
||||||
|
# FRN
|
||||||
|
elif re.match(r"\d{10}", query):
|
||||||
|
data = ae7qparser.get_frn(query)
|
||||||
|
# callsign
|
||||||
|
else:
|
||||||
|
data = ae7qparser.get_call(query)
|
||||||
|
|
||||||
|
embed = cmn.embed_factory(ctx)
|
||||||
|
embed.title = f"AE7Q Application History for {query}"
|
||||||
|
embed.url = data.query_url
|
||||||
|
embed.colour = cmn.colours.good
|
||||||
|
embed.description = ""
|
||||||
|
|
||||||
|
if not data.application_history:
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.description += f"\nNo records found for `{query}`"
|
||||||
|
else:
|
||||||
|
# add the first three rows of the app history to the embed
|
||||||
|
if isinstance(data.application_history, ae7qparser.ApplicationsHistoryTable):
|
||||||
|
for row in data.application_history[0:3]:
|
||||||
|
header = f"**{row.uls_file_number[0]}** ({row.receipt_date:%Y-%m-%d})"
|
||||||
|
body = (f"Name: *{row.entity_name}*\n"
|
||||||
|
f"Callsign: *{row.application_callsign}*\n"
|
||||||
|
f"Region: *{row.region_state}*\n"
|
||||||
|
f"Purpose: *{row.application_purpose}*\n")
|
||||||
|
if row.payment_date:
|
||||||
|
body += f"Payment: *{row.payment_date:%Y-%m-%d}*\n"
|
||||||
|
if row.last_action_date:
|
||||||
|
body += f"Last Action: *{row.last_action_date:%Y-%m-%d}*\n"
|
||||||
|
body += f"Status: *{row.application_status}*"
|
||||||
|
embed.add_field(name=header, value=body, inline=False)
|
||||||
|
|
||||||
|
elif isinstance(data.application_history, ae7qparser.VanityApplicationsHistoryTable):
|
||||||
|
for row in data.application_history[0:3]:
|
||||||
|
header = f"**{row.uls_file_number[0]}** ({row.receipt_date:%Y-%m-%d})"
|
||||||
|
body = (f"Callsign: *{row.application_callsign}*\n"
|
||||||
|
f"Region: *{row.region_state}*\n"
|
||||||
|
f"Operator Class: *{row.operator_class}*\n"
|
||||||
|
f"Purpose: *{row.application_purpose}*\n")
|
||||||
|
if row.payment_date:
|
||||||
|
body += f"Payment: *{row.payment_date:%Y-%m-%d}*\n"
|
||||||
|
if row.last_action_date:
|
||||||
|
body += f"Last Action: *{row.last_action_date:%Y-%m-%d}*\n"
|
||||||
|
body += f"Status: *{row.application_status}*\n"
|
||||||
|
if row.applied_callsigns:
|
||||||
|
body += (f"Callsign{'s' if len(row.applied_callsigns) > 1 else ''} "
|
||||||
|
f"Applied For: *{', '.join(row.applied_callsigns)}*")
|
||||||
|
embed.add_field(name=header, value=body, inline=False)
|
||||||
|
|
||||||
|
if len(data.application_history) > 3:
|
||||||
|
embed.description += (f"\nRecords 1 to 3 of {len(data.application_history)}. "
|
||||||
|
f"See [ae7q.com]({data.query_url}) for more...")
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@_ae7q_lookup.command(name="pending_apps", aliases=["pa"], category=cmn.cat.lookup)
|
||||||
|
async def _ae7q_pending_applications(self, ctx: commands.Context, query: str):
|
||||||
|
"""Looks up the pending applications for a callsign, FRN, or Licensee ID on [ae7q.com](http://ae7q.com/)."""
|
||||||
|
with ctx.typing():
|
||||||
|
query = query.upper()
|
||||||
|
|
||||||
|
# LID
|
||||||
|
if re.match(r"L\d+", query):
|
||||||
|
data = ae7qparser.get_licensee_id(query)
|
||||||
|
# FRN
|
||||||
|
elif re.match(r"\d{10}", query):
|
||||||
|
data = ae7qparser.get_frn(query)
|
||||||
|
# callsign
|
||||||
|
else:
|
||||||
|
data = ae7qparser.get_call(query)
|
||||||
|
|
||||||
|
embed = cmn.embed_factory(ctx)
|
||||||
|
embed.title = f"AE7Q Pending Applications for {query}"
|
||||||
|
embed.url = data.query_url
|
||||||
|
embed.colour = cmn.colours.good
|
||||||
|
embed.description = ""
|
||||||
|
|
||||||
|
if not data.pending_applications:
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.description += f"\nNo records found for `{query}`"
|
||||||
|
else:
|
||||||
|
# add the first three rows of the pending apps to the embed
|
||||||
|
if isinstance(data.pending_applications, ae7qparser.PendingApplicationsPredictionsTable):
|
||||||
|
for row in data.pending_applications[0:3]:
|
||||||
|
header = f"**{row.uls_file_number}** ({row.receipt_date:%Y-%m-%d})"
|
||||||
|
body = (f"Callsign: *{row.applicant_callsign}*\n"
|
||||||
|
f"Region: *{row.region_state}*\n"
|
||||||
|
f"Operator Class: *{row.operator_class}*\n"
|
||||||
|
f"Vanity Type: *{row.vanity_type}*\n")
|
||||||
|
if row.process_date:
|
||||||
|
body += f"Processes On: *{row.process_date:%Y-%m-%d}*\n"
|
||||||
|
body += (f"Sequential #: *{row.sequential_number}*\n"
|
||||||
|
f"Vanity Callsign: *{row.vanity_callsign}*\n"
|
||||||
|
f"Prediction: *{row.prediction}*")
|
||||||
|
embed.add_field(name=header, value=body, inline=False)
|
||||||
|
|
||||||
|
elif isinstance(data.pending_applications, ae7qparser.CallsignPendingApplicationsPredictionsTable):
|
||||||
|
for row in data.pending_applications[0:3]:
|
||||||
|
header = f"**{row.uls_file_number}** ({row.receipt_date:%Y-%m-%d})"
|
||||||
|
body = (f"Callsign: *{row.applicant_callsign}*\n"
|
||||||
|
f"Region: *{row.region_state}*\n"
|
||||||
|
f"Operator Class: *{row.operator_class}*\n"
|
||||||
|
f"Vanity Type: *{row.vanity_type}*\n")
|
||||||
|
if row.process_date:
|
||||||
|
body += f"Processes On: *{row.process_date:%Y-%m-%d}*\n"
|
||||||
|
body += (f"Sequential #: *{row.sequential_number}*\n"
|
||||||
|
f"Prediction: *{row.prediction}*")
|
||||||
|
embed.add_field(name=header, value=body, inline=False)
|
||||||
|
|
||||||
|
if len(data.pending_applications) > 3:
|
||||||
|
embed.description += (f"\nRecords 1 to 3 of {len(data.pending_applications)}. "
|
||||||
|
f"See [ae7q.com]({data.query_url}) for more...")
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@_ae7q_lookup.command(name="app_detail", aliases=["ad"], category=cmn.cat.lookup)
|
||||||
|
async def _ae7q_app_detail(self, ctx: commands.Context, ufn: str):
|
||||||
|
"""Looks up the application data for a ULS file number on [ae7q.com](http://ae7q.com/)."""
|
||||||
|
with ctx.typing():
|
||||||
|
app_data = ae7qparser.get_application(ufn)
|
||||||
|
|
||||||
|
embed = cmn.embed_factory(ctx)
|
||||||
|
embed.title = f"AE7Q Application Data for {ufn}"
|
||||||
|
embed.url = app_data.query_url
|
||||||
|
embed.colour = cmn.colours.good
|
||||||
|
|
||||||
|
if not app_data.application_data:
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.description += f"\nNo records found for `{ufn}`"
|
||||||
|
else:
|
||||||
|
if app_data.frn:
|
||||||
|
embed.add_field(name="FRN", value=app_data.frn, inline=True)
|
||||||
|
if app_data.licensee_id:
|
||||||
|
embed.add_field(name="Licensee ID", value=app_data.licensee_id, inline=True)
|
||||||
|
if app_data.applicant_type:
|
||||||
|
embed.add_field(name="Applicant Type", value=app_data.applicant_type, inline=True)
|
||||||
|
if app_data.entity_type:
|
||||||
|
embed.add_field(name="Entity Type", value=app_data.entity_type, inline=True)
|
||||||
|
if app_data.entity_name:
|
||||||
|
embed.add_field(name="Name", value=app_data.entity_name, inline=True)
|
||||||
|
if app_data.callsign:
|
||||||
|
embed.add_field(name="Callsign", value=app_data.callsign, inline=True)
|
||||||
|
|
||||||
|
address = f"ATTN: {app_data.attention}\n" if app_data.attention else ""
|
||||||
|
address += f"{app_data.street_address}\n" if app_data.street_address else ""
|
||||||
|
address += f"{app_data.po_box}\n" if app_data.po_box else ""
|
||||||
|
address += (f"{', '.join([app_data.locality, app_data.state.split('-')[0].strip()])}"
|
||||||
|
f" {app_data.postal_code}")
|
||||||
|
if address:
|
||||||
|
embed.add_field(name="Address", value=address, inline=True)
|
||||||
|
if app_data.county:
|
||||||
|
embed.add_field(name="County", value=app_data.county, inline=True)
|
||||||
|
if app_data.maidenhead:
|
||||||
|
embed.add_field(name="Grid Square", value=app_data.maidenhead, inline=True)
|
||||||
|
if app_data.uls_geo_region:
|
||||||
|
embed.add_field(name="Region", value=app_data.uls_geo_region, inline=True)
|
||||||
|
if app_data.last_action_date:
|
||||||
|
embed.add_field(name="Last Action", value=f"{app_data.last_action_date:%Y-%m-%d}", inline=True)
|
||||||
|
if app_data.receipt_date:
|
||||||
|
embed.add_field(name="Receipt Date", value=f"{app_data.receipt_date:%Y-%m-%d}", inline=True)
|
||||||
|
if app_data.entered_timestamp:
|
||||||
|
embed.add_field(name="Entered Time", value=f"{app_data.entered_timestamp:%Y-%m-%d}", inline=True)
|
||||||
|
if app_data.application_source:
|
||||||
|
embed.add_field(name="Application Source", value=app_data.application_source, inline=True)
|
||||||
|
if app_data.application_purpose:
|
||||||
|
embed.add_field(name="Purpose", value=app_data.application_purpose, inline=True)
|
||||||
|
if app_data.result:
|
||||||
|
embed.add_field(name="Result", value=app_data.result, inline=True)
|
||||||
|
if app_data.fee_control_number:
|
||||||
|
embed.add_field(name="Fee Control Number", value=app_data.fee_control_number, inline=True)
|
||||||
|
if app_data.payment_date:
|
||||||
|
embed.add_field(name="Payment Date", value=f"{app_data.payment_date:%Y-%m-%d}", inline=True)
|
||||||
|
if app_data.operator_class:
|
||||||
|
embed.add_field(name="Operator Class", value=app_data.operator_class, inline=True)
|
||||||
|
if app_data.operator_group:
|
||||||
|
embed.add_field(name="Operator Group", value=app_data.operator_group, inline=True)
|
||||||
|
if app_data.uls_group:
|
||||||
|
embed.add_field(name="ULS Group", value=app_data.uls_group, inline=True)
|
||||||
|
if app_data.vanity_type:
|
||||||
|
embed.add_field(name="Vanity Type", value=app_data.vanity_type, inline=True)
|
||||||
|
if app_data.vanity_relationship:
|
||||||
|
embed.add_field(name="Vanity Relationship", value=app_data.vanity_relationship, inline=True)
|
||||||
|
if app_data.trustee_name and app_data.trustee_callsign:
|
||||||
|
embed.add_field(name="Trustee",
|
||||||
|
value=f"{app_data.trustee_name} ({app_data.trustee_callsign})",
|
||||||
|
inline=True)
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@_ae7q_lookup.command(name="frn", aliases=["f"], category=cmn.cat.lookup)
|
||||||
|
async def _ae7q_frn(self, ctx: commands.Context, frn: str):
|
||||||
|
"""Looks up the history of an FRN on [ae7q.com](http://ae7q.com/)."""
|
||||||
|
with ctx.typing():
|
||||||
|
frn = frn.upper()
|
||||||
|
|
||||||
|
frn_data = ae7qparser.get_frn(frn)
|
||||||
|
|
||||||
|
embed = cmn.embed_factory(ctx)
|
||||||
|
embed.title = f"AE7Q FRN History for {frn}"
|
||||||
|
embed.url = frn_data.query_url
|
||||||
|
embed.colour = cmn.colours.good
|
||||||
|
embed.description = ""
|
||||||
|
|
||||||
|
if not frn_data.frn_history:
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.description += f"\nNo records found for `{frn}`"
|
||||||
|
else:
|
||||||
|
# add the first three rows of the FRN history to the embed
|
||||||
|
for row in frn_data.frn_history[0:3]:
|
||||||
|
header = f"**{row.callsign}** ({row.applicant_type})"
|
||||||
|
body = (f"Name: *{row.entity_name}*\n"
|
||||||
|
f"Region: *{row.region_state}*\n"
|
||||||
|
f"Operator Class: *{row.operator_class}*\n"
|
||||||
|
f"Status: *{row.license_status}*\n")
|
||||||
|
if row.grant_date:
|
||||||
|
body += f"Granted: *{row.grant_date:%Y-%m-%d}*\n"
|
||||||
|
if row.effective_date:
|
||||||
|
body += f"Effective: *{row.effective_date:%Y-%m-%d}*\n"
|
||||||
|
if row.cancel_date:
|
||||||
|
body += f"Cancelled: *{row.cancel_date:%Y-%m-%d}*\n"
|
||||||
|
if row.expire_date:
|
||||||
|
body += f"Expires: *{row.expire_date:%Y-%m-%d}*\n"
|
||||||
|
embed.add_field(name=header, value=body, inline=False)
|
||||||
|
|
||||||
|
if len(frn_data.frn_history) > 3:
|
||||||
|
embed.description += (f"\nRecords 1 to 3 of {len(frn_data.frn_history)}. "
|
||||||
|
f"See [ae7q.com]({frn_data.query_url}) for more...")
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@_ae7q_lookup.command(name="licensee", aliases=["l"], category=cmn.cat.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()
|
||||||
|
|
||||||
|
lid_data = ae7qparser.get_licensee_id(licensee_id)
|
||||||
|
|
||||||
|
embed = cmn.embed_factory(ctx)
|
||||||
|
embed.title = f"AE7Q History for Licensee {licensee_id}"
|
||||||
|
embed.url = lid_data.query_url
|
||||||
|
embed.colour = cmn.colours.good
|
||||||
|
embed.description = ""
|
||||||
|
|
||||||
|
if not lid_data.licensee_id_history:
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
embed.description += f"\nNo records found for `{licensee_id}`"
|
||||||
|
else:
|
||||||
|
# add the first three rows of the FRN history to the embed
|
||||||
|
for row in lid_data.licensee_id_history[0:3]:
|
||||||
|
header = f"**{row.callsign}** ({row.applicant_type})"
|
||||||
|
body = (f"Name: *{row.entity_name}*\n"
|
||||||
|
f"Region: *{row.region_state}*\n"
|
||||||
|
f"Operator Class: *{row.operator_class}*\n"
|
||||||
|
f"Status: *{row.license_status}*\n")
|
||||||
|
if row.grant_date:
|
||||||
|
body += f"Granted: *{row.grant_date:%Y-%m-%d}*\n"
|
||||||
|
if row.effective_date:
|
||||||
|
body += f"Effective: *{row.effective_date:%Y-%m-%d}*\n"
|
||||||
|
if row.cancel_date:
|
||||||
|
body += f"Cancelled: *{row.cancel_date:%Y-%m-%d}*\n"
|
||||||
|
if row.expire_date:
|
||||||
|
body += f"Expires: *{row.expire_date:%Y-%m-%d}*\n"
|
||||||
|
|
||||||
|
embed.add_field(name=header, value=body, inline=False)
|
||||||
|
|
||||||
|
if len(lid_data.licensee_id_history) > 3:
|
||||||
|
embed.description += (f"\nRecords 1 to 3 of {len(lid_data.licensee_id_history)}. "
|
||||||
|
f"See [ae7q.com]({lid_data.query_url}) for more...")
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: commands.Bot):
|
def setup(bot: commands.Bot):
|
||||||
|
|||||||
+96
-72
@@ -1,88 +1,99 @@
|
|||||||
"""
|
"""
|
||||||
Base extension for qrm
|
Base extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrm2 and is released under the terms of the GNU
|
This file is part of qrm2 and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
|
||||||
from collections import OrderedDict
|
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
|
|
||||||
import info
|
import info
|
||||||
|
import common as cmn
|
||||||
|
|
||||||
import data.options as opt
|
import data.options as opt
|
||||||
import common as cmn
|
|
||||||
|
|
||||||
|
|
||||||
class QrmHelpCommand(commands.HelpCommand):
|
class QrmHelpCommand(commands.HelpCommand):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(command_attrs={'help': 'Shows help about qrm or a command', 'aliases': ['h']})
|
super().__init__(command_attrs={"help": "Shows help about qrm or a command", "aliases": ["h"]})
|
||||||
|
self.verify_checks = True
|
||||||
|
|
||||||
def get_bot_mapping(self):
|
async def get_bot_mapping(self):
|
||||||
bot = self.context.bot
|
bot = self.context.bot
|
||||||
mapping = {}
|
mapping = {}
|
||||||
for cmd in bot.commands:
|
|
||||||
cat = cmd.__original_kwargs__.get('category', None)
|
for cmd in await self.filter_commands(bot.commands, sort=True):
|
||||||
|
cat = cmd.__original_kwargs__.get("category", None)
|
||||||
if cat in mapping:
|
if cat in mapping:
|
||||||
mapping[cat].append(cmd)
|
mapping[cat].append(cmd)
|
||||||
else:
|
else:
|
||||||
mapping[cat] = [cmd]
|
mapping[cat] = [cmd]
|
||||||
return mapping
|
return mapping
|
||||||
|
|
||||||
def get_command_signature(self, command):
|
async def get_command_signature(self, command):
|
||||||
parent = command.full_parent_name
|
parent = command.full_parent_name
|
||||||
if command.aliases != []:
|
if command.aliases != []:
|
||||||
aliases = ', '.join(command.aliases)
|
aliases = ", ".join(command.aliases)
|
||||||
fmt = command.name
|
fmt = command.name
|
||||||
if parent:
|
if parent:
|
||||||
fmt = f'{parent} {fmt}'
|
fmt = f"{parent} {fmt}"
|
||||||
alias = fmt
|
alias = fmt
|
||||||
return f'{opt.prefix}{alias} {command.signature}\n *Aliases:* {aliases}'
|
return f"{opt.prefix}{alias} {command.signature}\n *Aliases:* {aliases}"
|
||||||
alias = command.name if not parent else f'{parent} {command.name}'
|
alias = command.name if not parent else f"{parent} {command.name}"
|
||||||
return f'{opt.prefix}{alias} {command.signature}'
|
return f"{opt.prefix}{alias} {command.signature}"
|
||||||
|
|
||||||
async def send_error_message(self, error):
|
async def send_error_message(self, error):
|
||||||
embed = cmn.embed_factory(self.context)
|
embed = cmn.embed_factory(self.context)
|
||||||
embed.title = 'qrm Help Error'
|
embed.title = "qrm Help Error"
|
||||||
embed.description = error
|
embed.description = error
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
await self.context.send(embed=embed)
|
await self.context.send(embed=embed)
|
||||||
|
|
||||||
async def send_bot_help(self, mapping):
|
async def send_bot_help(self, mapping):
|
||||||
embed = cmn.embed_factory(self.context)
|
embed = cmn.embed_factory(self.context)
|
||||||
embed.title = 'qrm Help'
|
embed.title = "qrm Help"
|
||||||
embed.description = (f'For command-specific help and usage, use `{opt.prefix}help [command name]`'
|
embed.description = (f"For command-specific help and usage, use `{opt.prefix}help [command name]`."
|
||||||
'. Many commands have shorter aliases.')
|
" Many commands have shorter aliases.")
|
||||||
|
mapping = await mapping
|
||||||
|
|
||||||
for cat, cmds in mapping.items():
|
for cat, cmds in mapping.items():
|
||||||
cmds = list(filter(lambda x: not x.hidden, cmds))
|
|
||||||
if cmds == []:
|
if cmds == []:
|
||||||
continue
|
continue
|
||||||
names = sorted([cmd.name for cmd in cmds])
|
names = sorted([cmd.name for cmd in cmds])
|
||||||
if cat is not None:
|
if cat is not None:
|
||||||
embed.add_field(name=cat.title(), value=', '.join(names), inline=False)
|
embed.add_field(name=cat.title(), value=", ".join(names), inline=False)
|
||||||
else:
|
else:
|
||||||
embed.add_field(name='Other', value=', '.join(names), inline=False)
|
embed.add_field(name="Other", value=", ".join(names), inline=False)
|
||||||
await self.context.send(embed=embed)
|
await self.context.send(embed=embed)
|
||||||
|
|
||||||
async def send_command_help(self, command):
|
async def send_command_help(self, command):
|
||||||
|
if self.verify_checks:
|
||||||
|
if not await command.can_run(self.context):
|
||||||
|
raise commands.CheckFailure
|
||||||
|
for p in command.parents:
|
||||||
|
if not await p.can_run(self.context):
|
||||||
|
raise commands.CheckFailure
|
||||||
embed = cmn.embed_factory(self.context)
|
embed = cmn.embed_factory(self.context)
|
||||||
embed.title = self.get_command_signature(command)
|
embed.title = await self.get_command_signature(command)
|
||||||
embed.description = command.help
|
embed.description = command.help
|
||||||
await self.context.send(embed=embed)
|
await self.context.send(embed=embed)
|
||||||
|
|
||||||
async def send_group_help(self, group):
|
async def send_group_help(self, group):
|
||||||
|
if self.verify_checks and not await group.can_run(self.context):
|
||||||
|
raise commands.CheckFailure
|
||||||
embed = cmn.embed_factory(self.context)
|
embed = cmn.embed_factory(self.context)
|
||||||
embed.title = self.get_command_signature(group)
|
embed.title = await self.get_command_signature(group)
|
||||||
embed.description = group.help
|
embed.description = group.help
|
||||||
for cmd in group.commands:
|
for cmd in await self.filter_commands(group.commands, sort=True):
|
||||||
embed.add_field(name=self.get_command_signature(cmd), value=cmd.help, inline=False)
|
embed.add_field(name=await self.get_command_signature(cmd), value=cmd.help, inline=False)
|
||||||
await self.context.send(embed=embed)
|
await self.context.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
@@ -100,101 +111,114 @@ class BaseCog(commands.Cog):
|
|||||||
|
|
||||||
embed.add_field(name="Authors", value=", ".join(info.authors))
|
embed.add_field(name="Authors", value=", ".join(info.authors))
|
||||||
embed.add_field(name="License", value=info.license)
|
embed.add_field(name="License", value=info.license)
|
||||||
embed.add_field(name="Version", value=f'v{info.release}')
|
embed.add_field(name="Version", value=f"v{info.release}")
|
||||||
embed.add_field(name="Contributing", value=info.contributing, inline=False)
|
embed.add_field(name="Contributing", value=info.contributing, inline=False)
|
||||||
embed.add_field(name="Official Server", value=info.bot_server, inline=False)
|
embed.add_field(name="Official Server", value=info.bot_server, inline=False)
|
||||||
embed.set_thumbnail(url=str(self.bot.user.avatar_url))
|
embed.set_thumbnail(url=str(self.bot.user.avatar_url))
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@commands.command(name="ping", aliases=['beep'])
|
@commands.command(name="ping", aliases=["beep"])
|
||||||
async def _ping(self, ctx: commands.Context):
|
async def _ping(self, ctx: commands.Context):
|
||||||
"""Show the current latency to the discord endpoint."""
|
"""Shows the current latency to the discord endpoint."""
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
content = ''
|
content = ""
|
||||||
if ctx.invoked_with == "beep":
|
if ctx.invoked_with == "beep":
|
||||||
embed.title = "**Boop!**"
|
embed.title = "**Boop!**"
|
||||||
else:
|
else:
|
||||||
content = ctx.message.author.mention if random.random() < 0.05 else ''
|
content = ctx.message.author.mention if random.random() < 0.05 else ""
|
||||||
embed.title = "🏓 **Pong!**"
|
embed.title = "🏓 **Pong!**"
|
||||||
embed.description = f'Current ping is {self.bot.latency*1000:.1f} ms'
|
embed.description = f"Current ping is {self.bot.latency*1000:.1f} ms"
|
||||||
await ctx.send(content, embed=embed)
|
await ctx.send(content, embed=embed)
|
||||||
|
|
||||||
@commands.command(name="changelog", aliases=["clog"])
|
@commands.command(name="changelog", aliases=["clog"])
|
||||||
async def _changelog(self, ctx: commands.Context):
|
async def _changelog(self, ctx: commands.Context, version: str = "latest"):
|
||||||
"""Show what has changed in the most recent bot version."""
|
"""Shows what has changed in a bot version. Defaults to the latest version."""
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = "qrm Changelog"
|
embed.title = "qrm Changelog"
|
||||||
embed.description = ("For a full listing, visit [Github](https://"
|
embed.description = ("For a full listing, visit [Github](https://"
|
||||||
"github.com/classabbyamp/discord-qrm2/blob/master/CHANGELOG.md).")
|
"github.com/miaowware/qrm2/blob/master/CHANGELOG.md).")
|
||||||
changelog = self.changelog
|
changelog = self.changelog
|
||||||
|
vers = list(changelog.keys())
|
||||||
|
vers.remove("Unreleased")
|
||||||
|
|
||||||
vers = 0
|
version = version.lower()
|
||||||
for ver, log in changelog.items():
|
|
||||||
if ver.lower() != 'unreleased':
|
if version == "latest":
|
||||||
if 'date' in log:
|
version = info.release
|
||||||
embed.description += f'\n\n**{ver}** ({log["date"]})'
|
if version == "unreleased":
|
||||||
|
version = "Unreleased"
|
||||||
|
|
||||||
|
try:
|
||||||
|
log = changelog[version]
|
||||||
|
except KeyError:
|
||||||
|
embed.title += ": Version Not Found"
|
||||||
|
embed.description += "\n\n**Valid versions:** latest, "
|
||||||
|
embed.description += ", ".join(vers)
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
if "date" in log:
|
||||||
|
embed.description += f"\n\n**v{version}** ({log['date']})"
|
||||||
else:
|
else:
|
||||||
embed.description += f'\n\n**{ver}**'
|
embed.description += f"\n\n**v{version}**"
|
||||||
embed = await format_changelog(log, embed)
|
embed = await format_changelog(log, embed)
|
||||||
vers += 1
|
|
||||||
if vers >= 1:
|
|
||||||
break
|
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@commands.command(name="issue")
|
@commands.command(name="issue")
|
||||||
async def _issue(self, ctx: commands.Context):
|
async def _issue(self, ctx: commands.Context):
|
||||||
"""Shows how to create an issue for the bot."""
|
"""Shows how to create a bug report or feature request about the bot."""
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = "Found a bug? Have a feature request?"
|
embed.title = "Found a bug? Have a feature request?"
|
||||||
embed.description = ("Submit an issue on the [issue tracker]"
|
embed.description = ("Submit an issue on the [issue tracker]"
|
||||||
"(https://github.com/classabbyamp/discord-qrm2/issues)!")
|
"(https://github.com/miaowware/qrm2/issues)!")
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@commands.command(name="bruce", hidden=True)
|
@commands.command(name="echo", aliases=["e"], category=cmn.cat.admin)
|
||||||
async def _b_issue(self, ctx: commands.Context):
|
|
||||||
"""Shows how to create an issue for the bot."""
|
|
||||||
await ctx.invoke(self._issue)
|
|
||||||
|
|
||||||
@commands.command(name="echo", aliases=["e"], hidden=True)
|
|
||||||
@commands.check(cmn.check_if_owner)
|
@commands.check(cmn.check_if_owner)
|
||||||
async def _echo(self, ctx: commands.Context, channel: commands.TextChannelConverter, *, msg: str):
|
async def _echo(self, ctx: commands.Context,
|
||||||
"""Send a message in a channel as qrm. Only works within a server or DM to server, not between servers."""
|
channel: Union[cmn.GlobalChannelConverter, commands.UserConverter], *, msg: str):
|
||||||
|
"""Sends a message in a channel as qrm. Accepts channel/user IDs/mentions.
|
||||||
|
Channel names are current-guild only.
|
||||||
|
Does not work with the ID of the bot user."""
|
||||||
|
if isinstance(channel, discord.ClientUser):
|
||||||
|
raise commands.BadArgument("Can't send to the bot user!")
|
||||||
await channel.send(msg)
|
await channel.send(msg)
|
||||||
|
|
||||||
|
|
||||||
def parse_changelog():
|
def parse_changelog():
|
||||||
changelog = OrderedDict()
|
changelog = {}
|
||||||
ver = ''
|
ver = ""
|
||||||
heading = ''
|
heading = ""
|
||||||
|
|
||||||
with open('CHANGELOG.md') as changelog_file:
|
with open("CHANGELOG.md") as changelog_file:
|
||||||
for line in changelog_file.readlines():
|
for line in changelog_file.readlines():
|
||||||
if line.strip() == '':
|
if line.strip() == "":
|
||||||
continue
|
continue
|
||||||
if re.match(r'##[^#]', line):
|
if re.match(r"##[^#]", line):
|
||||||
ver_match = re.match(r'\[(.+)\](?: - )?(\d{4}-\d{2}-\d{2})?', line.lstrip('#').strip())
|
ver_match = re.match(r"\[(.+)\](?: - )?(\d{4}-\d{2}-\d{2})?", line.lstrip("#").strip())
|
||||||
if ver_match is not None:
|
if ver_match is not None:
|
||||||
ver = ver_match.group(1)
|
ver = ver_match.group(1)
|
||||||
changelog[ver] = dict()
|
changelog[ver] = dict()
|
||||||
if ver_match.group(2):
|
if ver_match.group(2):
|
||||||
changelog[ver]['date'] = ver_match.group(2)
|
changelog[ver]["date"] = ver_match.group(2)
|
||||||
elif re.match(r'###[^#]', line):
|
elif re.match(r"###[^#]", line):
|
||||||
heading = line.lstrip('#').strip()
|
heading = line.lstrip("#").strip()
|
||||||
changelog[ver][heading] = []
|
changelog[ver][heading] = []
|
||||||
elif ver != '' and heading != '':
|
elif ver != "" and heading != "":
|
||||||
if line.startswith('-'):
|
if line.startswith("-"):
|
||||||
changelog[ver][heading].append(line.lstrip('-').strip())
|
changelog[ver][heading].append(line.lstrip("-").strip())
|
||||||
return changelog
|
return changelog
|
||||||
|
|
||||||
|
|
||||||
async def format_changelog(log: dict, embed: discord.Embed):
|
async def format_changelog(log: dict, embed: discord.Embed):
|
||||||
for header, lines in log.items():
|
for header, lines in log.items():
|
||||||
formatted = ''
|
formatted = ""
|
||||||
if header != 'date':
|
if header != "date":
|
||||||
for line in lines:
|
for line in lines:
|
||||||
formatted += f'- {line}\n'
|
formatted += f"- {line}\n"
|
||||||
embed.add_field(name=f'**{header}**', value=formatted, inline=False)
|
embed.add_field(name=f"**{header}**", value=formatted, inline=False)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+22
-17
@@ -1,12 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
Fun extension for qrm
|
Fun extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrm2 and is released under the terms of the GNU
|
This file is part of qrm2 and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
@@ -17,37 +18,41 @@ import common as cmn
|
|||||||
class FunCog(commands.Cog):
|
class FunCog(commands.Cog):
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
with open('resources/words') as words_file:
|
with open("resources/words") as words_file:
|
||||||
self.words = words_file.read().lower().splitlines()
|
self.words = words_file.read().lower().splitlines()
|
||||||
|
|
||||||
@commands.command(name="xkcd", aliases=['x'], category=cmn.cat.fun)
|
@commands.command(name="xkcd", aliases=["x"], category=cmn.cat.fun)
|
||||||
async def _xkcd(self, ctx: commands.Context, number: str):
|
async def _xkcd(self, ctx: commands.Context, number: str):
|
||||||
'''Look up an xkcd by number.'''
|
"""Looks up an xkcd comic by number."""
|
||||||
await ctx.send('http://xkcd.com/' + number)
|
await ctx.send("http://xkcd.com/" + number)
|
||||||
|
|
||||||
@commands.command(name="tar", category=cmn.cat.fun)
|
@commands.command(name="tar", category=cmn.cat.fun)
|
||||||
async def _tar(self, ctx: commands.Context):
|
async def _tar(self, ctx: commands.Context):
|
||||||
'''Returns an xkcd about tar.'''
|
"""Returns xkcd: tar."""
|
||||||
await ctx.send('http://xkcd.com/1168')
|
await ctx.send("http://xkcd.com/1168")
|
||||||
|
|
||||||
|
@commands.command(name="standards", category=cmn.cat.fun)
|
||||||
|
async def _standards(self, ctx: commands.Context):
|
||||||
|
"""Returns xkcd: Standards."""
|
||||||
|
await ctx.send("http://xkcd.com/927")
|
||||||
|
|
||||||
@commands.command(name="xd", hidden=True, category=cmn.cat.fun)
|
@commands.command(name="xd", hidden=True, category=cmn.cat.fun)
|
||||||
async def _xd(self, ctx: commands.Context):
|
async def _xd(self, ctx: commands.Context):
|
||||||
'''ecks dee'''
|
"""ecks dee"""
|
||||||
await ctx.send('ECKS DEE :smirk:')
|
await ctx.send("ECKS DEE :smirk:")
|
||||||
|
|
||||||
@commands.command(name="funetics", aliases=['fun'], category=cmn.cat.fun)
|
@commands.command(name="funetics", aliases=["fun"], category=cmn.cat.fun)
|
||||||
async def _funetics_lookup(self, ctx: commands.Context, *, msg: str):
|
async def _funetics_lookup(self, ctx: commands.Context, *, msg: str):
|
||||||
'''Get fun phonetics for a word or phrase.'''
|
"""Generates fun/wacky phonetics for a word or phrase."""
|
||||||
with ctx.typing():
|
result = ""
|
||||||
result = ''
|
|
||||||
for char in msg.lower():
|
for char in msg.lower():
|
||||||
if char.isalpha():
|
if char.isalpha():
|
||||||
result += random.choice([word for word in self.words if word[0] == char])
|
result += random.choice([word for word in self.words if word[0] == char])
|
||||||
else:
|
else:
|
||||||
result += char
|
result += char
|
||||||
result += ' '
|
result += " "
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f'Funetics for {msg}'
|
embed.title = f"Funetics for {msg}"
|
||||||
embed.description = result.title()
|
embed.description = result.title()
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|||||||
+40
-41
@@ -1,12 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
Grid extension for qrm
|
Grid extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrm2 and is released under the terms of the GNU
|
This file is part of qrm2 and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import math
|
import math
|
||||||
|
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
@@ -20,57 +21,55 @@ class GridCog(commands.Cog):
|
|||||||
|
|
||||||
@commands.command(name="grid", category=cmn.cat.maps)
|
@commands.command(name="grid", category=cmn.cat.maps)
|
||||||
async def _grid_sq_lookup(self, ctx: commands.Context, lat: str, lon: str):
|
async def _grid_sq_lookup(self, ctx: commands.Context, lat: str, lon: str):
|
||||||
'''Calculates the grid square for latitude and longitude coordinates,
|
("""Calculates the grid square for latitude and longitude coordinates, """
|
||||||
with negative being latitude South and longitude West.'''
|
"""with negative being latitude South and longitude West.""")
|
||||||
with ctx.typing():
|
|
||||||
grid = "**"
|
grid = "**"
|
||||||
try:
|
|
||||||
latf = float(lat) + 90
|
latf = float(lat) + 90
|
||||||
lonf = float(lon) + 180
|
lonf = float(lon) + 180
|
||||||
if 0 <= latf <= 180 and 0 <= lonf <= 360:
|
if 0 <= latf <= 180 and 0 <= lonf <= 360:
|
||||||
grid += chr(ord('A') + int(lonf / 20))
|
grid += chr(ord("A") + int(lonf / 20))
|
||||||
grid += chr(ord('A') + int(latf / 10))
|
grid += chr(ord("A") + int(latf / 10))
|
||||||
grid += chr(ord('0') + int((lonf % 20)/2))
|
grid += chr(ord("0") + int((lonf % 20)/2))
|
||||||
grid += chr(ord('0') + int((latf % 10)/1))
|
grid += chr(ord("0") + int((latf % 10)/1))
|
||||||
grid += chr(ord('a') + int((lonf - (int(lonf/2)*2)) / (5/60)))
|
grid += chr(ord("a") + int((lonf - (int(lonf/2)*2)) / (5/60)))
|
||||||
grid += chr(ord('a') + int((latf - (int(latf/1)*1)) / (2.5/60)))
|
grid += chr(ord("a") + int((latf - (int(latf/1)*1)) / (2.5/60)))
|
||||||
grid += "**"
|
grid += "**"
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f'Maidenhead Grid Locator for {float(lat):.6f}, {float(lon):.6f}'
|
embed.title = f"Maidenhead Grid Locator for {float(lat):.6f}, {float(lon):.6f}"
|
||||||
embed.description = grid
|
embed.description = grid
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
else:
|
else:
|
||||||
raise ValueError('Out of range.')
|
|
||||||
except ValueError as err:
|
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f'Error generating grid square for {lat}, {lon}.'
|
embed.title = f"Error generating grid square for {lat}, {lon}."
|
||||||
embed.description = str(err)
|
embed.description = """Coordinates out of range.
|
||||||
|
The valid ranges are:
|
||||||
|
- Latitude: `-90` to `+90`
|
||||||
|
- Longitude: `-180` to `+180`"""
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@commands.command(name="ungrid", aliases=['loc'], category=cmn.cat.maps)
|
@commands.command(name="ungrid", aliases=["loc"], category=cmn.cat.maps)
|
||||||
async def _location_lookup(self, ctx: commands.Context, grid: str, grid2: str = None):
|
async def _location_lookup(self, ctx: commands.Context, grid: str, grid2: str = None):
|
||||||
'''Calculates the latitude and longitude for the center of a grid square.
|
"""Calculates the latitude and longitude for the center of a grid square.
|
||||||
If two grid squares are given, the distance and azimuth between them is calculated.'''
|
If two grid squares are given, the distance and azimuth between them is calculated."""
|
||||||
with ctx.typing():
|
if grid2 is None or grid2 == "":
|
||||||
if grid2 is None or grid2 == '':
|
|
||||||
try:
|
try:
|
||||||
grid = grid.upper()
|
grid = grid.upper()
|
||||||
loc = get_coords(grid)
|
loc = get_coords(grid)
|
||||||
|
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f'Latitude and Longitude for {grid}'
|
embed.title = f"Latitude and Longitude for {grid}"
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
|
|
||||||
if len(grid) >= 6:
|
if len(grid) >= 6:
|
||||||
embed.description = f'**{loc[0]:.5f}, {loc[1]:.5f}**'
|
embed.description = f"**{loc[0]:.5f}, {loc[1]:.5f}**"
|
||||||
embed.url = f'https://www.openstreetmap.org/#map=13/{loc[0]:.5f}/{loc[1]:.5f}'
|
embed.url = f"https://www.openstreetmap.org/#map=13/{loc[0]:.5f}/{loc[1]:.5f}"
|
||||||
else:
|
else:
|
||||||
embed.description = f'**{loc[0]:.1f}, {loc[1]:.1f}**'
|
embed.description = f"**{loc[0]:.1f}, {loc[1]:.1f}**"
|
||||||
embed.url = f'https://www.openstreetmap.org/#map=10/{loc[0]:.1f}/{loc[1]:.1f}'
|
embed.url = f"https://www.openstreetmap.org/#map=10/{loc[0]:.1f}/{loc[1]:.1f}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f'Error generating latitude and longitude for grid {grid}.'
|
embed.title = f"Error generating latitude and longitude for grid {grid}."
|
||||||
embed.description = str(e)
|
embed.description = str(e)
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
else:
|
else:
|
||||||
@@ -101,12 +100,12 @@ If two grid squares are given, the distance and azimuth between them is calculat
|
|||||||
bearing = (math.degrees(math.atan2(y_dist, x_dist)) + 360) % 360
|
bearing = (math.degrees(math.atan2(y_dist, x_dist)) + 360) % 360
|
||||||
|
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f'Great Circle Distance and Bearing from {grid} to {grid2}'
|
embed.title = f"Great Circle Distance and Bearing from {grid} to {grid2}"
|
||||||
embed.description = f'**Distance:** {d:.1f} km ({d_mi:.1f} mi)\n**Bearing:** {bearing:.1f}°'
|
embed.description = f"**Distance:** {d:.1f} km ({d_mi:.1f} mi)\n**Bearing:** {bearing:.1f}°"
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f'Error generating great circle distance and bearing from {grid} and {grid2}.'
|
embed.title = f"Error generating great circle distance and bearing from {grid} and {grid2}."
|
||||||
embed.description = str(e)
|
embed.description = str(e)
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
@@ -114,23 +113,23 @@ If two grid squares are given, the distance and azimuth between them is calculat
|
|||||||
|
|
||||||
def get_coords(grid: str):
|
def get_coords(grid: str):
|
||||||
if len(grid) < 3:
|
if len(grid) < 3:
|
||||||
raise ValueError('The grid locator must be at least 4 characters long.')
|
raise ValueError("The grid locator must be at least 4 characters long.")
|
||||||
|
|
||||||
if not grid[0:2].isalpha() or not grid[2:4].isdigit():
|
if not grid[0:2].isalpha() or not grid[2:4].isdigit():
|
||||||
if len(grid) <= 4:
|
if len(grid) <= 4:
|
||||||
raise ValueError('The grid locator must be of the form AA##.')
|
raise ValueError("The grid locator must be of the form AA##.")
|
||||||
if len(grid) >= 6 and not grid[5:7].isalpha():
|
if len(grid) >= 6 and not grid[5:7].isalpha():
|
||||||
raise ValueError('The grid locator must be of the form AA##AA.')
|
raise ValueError("The grid locator must be of the form AA##AA.")
|
||||||
|
|
||||||
lon = ((ord(grid[0]) - ord('A')) * 20) - 180
|
lon = ((ord(grid[0]) - ord("A")) * 20) - 180
|
||||||
lat = ((ord(grid[1]) - ord('A')) * 10) - 90
|
lat = ((ord(grid[1]) - ord("A")) * 10) - 90
|
||||||
lon += ((ord(grid[2]) - ord('0')) * 2)
|
lon += ((ord(grid[2]) - ord("0")) * 2)
|
||||||
lat += ((ord(grid[3]) - ord('0')) * 1)
|
lat += ((ord(grid[3]) - ord("0")) * 1)
|
||||||
|
|
||||||
if len(grid) >= 6:
|
if len(grid) >= 6:
|
||||||
# have subsquares
|
# have subsquares
|
||||||
lon += ((ord(grid[4])) - ord('A')) * (5/60)
|
lon += ((ord(grid[4])) - ord("A")) * (5/60)
|
||||||
lat += ((ord(grid[5])) - ord('A')) * (2.5/60)
|
lat += ((ord(grid[5])) - ord("A")) * (2.5/60)
|
||||||
# move to center of subsquare
|
# move to center of subsquare
|
||||||
lon += (2.5/60)
|
lon += (2.5/60)
|
||||||
lat += (1.25/60)
|
lat += (1.25/60)
|
||||||
|
|||||||
+40
-22
@@ -1,12 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
Ham extension for qrm
|
Ham extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrm2 and is released under the terms of the GNU
|
This file is part of qrm2 and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
@@ -21,10 +22,9 @@ class HamCog(commands.Cog):
|
|||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
@commands.command(name="qcode", aliases=['q'], category=cmn.cat.ref)
|
@commands.command(name="qcode", aliases=["q"], category=cmn.cat.ref)
|
||||||
async def _qcode_lookup(self, ctx: commands.Context, qcode: str):
|
async def _qcode_lookup(self, ctx: commands.Context, qcode: str):
|
||||||
'''Look up a Q Code.'''
|
"""Looks up the meaning of a Q Code."""
|
||||||
with ctx.typing():
|
|
||||||
qcode = qcode.upper()
|
qcode = qcode.upper()
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
if qcode in qcodes.qcodes:
|
if qcode in qcodes.qcodes:
|
||||||
@@ -32,49 +32,47 @@ class HamCog(commands.Cog):
|
|||||||
embed.description = qcodes.qcodes[qcode]
|
embed.description = qcodes.qcodes[qcode]
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
else:
|
else:
|
||||||
embed.title = f'Q Code {qcode} not found'
|
embed.title = f"Q Code {qcode} not found"
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@commands.command(name="phonetics", aliases=['ph', 'phoneticize', 'phoneticise', 'phone'], category=cmn.cat.ref)
|
@commands.command(name="phonetics", aliases=["ph", "phoneticize", "phoneticise", "phone"], category=cmn.cat.ref)
|
||||||
async def _phonetics_lookup(self, ctx: commands.Context, *, msg: str):
|
async def _phonetics_lookup(self, ctx: commands.Context, *, msg: str):
|
||||||
'''Get phonetics for a word or phrase.'''
|
"""Returns NATO phonetics for a word or phrase."""
|
||||||
with ctx.typing():
|
result = ""
|
||||||
result = ''
|
|
||||||
for char in msg.lower():
|
for char in msg.lower():
|
||||||
if char.isalpha():
|
if char.isalpha():
|
||||||
result += phonetics.phonetics[char]
|
result += phonetics.phonetics[char]
|
||||||
else:
|
else:
|
||||||
result += char
|
result += char
|
||||||
result += ' '
|
result += " "
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f'Phonetics for {msg}'
|
embed.title = f"Phonetics for {msg}"
|
||||||
embed.description = result.title()
|
embed.description = result.title()
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@commands.command(name="utc", aliases=['z'], category=cmn.cat.ref)
|
@commands.command(name="utc", aliases=["z"], category=cmn.cat.ref)
|
||||||
async def _utc_lookup(self, ctx: commands.Context):
|
async def _utc_lookup(self, ctx: commands.Context):
|
||||||
'''Gets the current time in UTC.'''
|
"""Returns the current time in UTC."""
|
||||||
with ctx.typing():
|
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
result = '**' + now.strftime('%Y-%m-%d %H:%M') + 'Z**'
|
result = "**" + now.strftime("%Y-%m-%d %H:%M") + "Z**"
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = 'The current time is:'
|
embed.title = "The current time is:"
|
||||||
embed.description = result
|
embed.description = result
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@commands.command(name="prefixes", aliases=["vanity", "pfx", "vanities", "prefix"])
|
@commands.command(name="prefixes", aliases=["vanity", "pfx", "vanities", "prefix"], category=cmn.cat.ref)
|
||||||
async def _vanity_prefixes(self, ctx: commands.Context, country: str = None):
|
async def _vanity_prefixes(self, ctx: commands.Context, country: str = None):
|
||||||
'''Lists valid prefixes for countries.'''
|
"""Lists valid callsign prefixes for different countries."""
|
||||||
if country is None:
|
if country is None:
|
||||||
await ctx.send_help(ctx.command)
|
await ctx.send_help(ctx.command)
|
||||||
return
|
return
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
if country.lower() not in callsign_info.options:
|
if country.lower() not in callsign_info.options:
|
||||||
embed.title = f'{country} not found!',
|
embed.title = f"{country} not found!"
|
||||||
embed.description = f'Valid countries: {", ".join(callsign_info.options.keys())}',
|
embed.description = f"Valid countries: {', '.join(callsign_info.options.keys())}"
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
else:
|
else:
|
||||||
embed.title = callsign_info.options[country.lower()][0]
|
embed.title = callsign_info.options[country.lower()][0]
|
||||||
@@ -94,6 +92,26 @@ class HamCog(commands.Cog):
|
|||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.command(name="phoneticweight", aliases=["pw"], category=cmn.cat.ref)
|
||||||
|
async def _weight(self, ctx: commands.Context, *, msg: str):
|
||||||
|
"""Calculates the phonetic weight of a callsign or message."""
|
||||||
|
embed = cmn.embed_factory(ctx)
|
||||||
|
msg = msg.upper()
|
||||||
|
weight = 0
|
||||||
|
for char in msg:
|
||||||
|
try:
|
||||||
|
weight += phonetics.pweights[char]
|
||||||
|
except KeyError:
|
||||||
|
embed.title = "Error in calculation of phonetic weight"
|
||||||
|
embed.description = f"Unknown character `{char}` in message"
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
embed.title = f"Phonetic Weight of {msg}"
|
||||||
|
embed.description = f"The phonetic weight is **{weight}**"
|
||||||
|
embed.colour = cmn.colours.good
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: commands.Bot):
|
def setup(bot: commands.Bot):
|
||||||
bot.add_cog(HamCog(bot))
|
bot.add_cog(HamCog(bot))
|
||||||
|
|||||||
+35
-35
@@ -1,14 +1,17 @@
|
|||||||
"""
|
"""
|
||||||
Image extension for qrm
|
Image extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrm2 and is released under the terms of the GNU
|
This file is part of qrm2 and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
|
|
||||||
@@ -16,28 +19,30 @@ import common as cmn
|
|||||||
|
|
||||||
|
|
||||||
class ImageCog(commands.Cog):
|
class ImageCog(commands.Cog):
|
||||||
|
gl_url = ("http://www.fourmilab.ch/cgi-bin/uncgi/Earth?img=NOAAtopo.evif"
|
||||||
|
"&imgsize=320&dynimg=y&opt=-p&lat=&lon=&alt=&tle=&date=0&utc=&jd=")
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.bandcharts = cmn.ImagesGroup(cmn.paths.bandcharts / "meta.json")
|
self.bandcharts = cmn.ImagesGroup(cmn.paths.bandcharts / "meta.json")
|
||||||
self.maps = cmn.ImagesGroup(cmn.paths.maps / "meta.json")
|
self.maps = cmn.ImagesGroup(cmn.paths.maps / "meta.json")
|
||||||
self.session = bot.qrm.session
|
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
|
||||||
|
|
||||||
@commands.command(name="bandplan", aliases=['plan', 'bands'], category=cmn.cat.ref)
|
@commands.command(name="bandplan", aliases=["plan", "bands"], category=cmn.cat.ref)
|
||||||
async def _bandplan(self, ctx: commands.Context, region: str = ''):
|
async def _bandplan(self, ctx: commands.Context, region: str = ""):
|
||||||
'''Posts an image of Frequency Allocations.'''
|
"""Gets the frequency allocations chart for a given country."""
|
||||||
|
async with ctx.typing():
|
||||||
arg = region.lower()
|
arg = region.lower()
|
||||||
|
|
||||||
with ctx.typing():
|
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
if arg not in self.bandcharts:
|
if arg not in self.bandcharts:
|
||||||
desc = 'Possible arguments are:\n'
|
desc = "Possible arguments are:\n"
|
||||||
for key, img in self.bandcharts.items():
|
for key, img in self.bandcharts.items():
|
||||||
desc += f'`{key}`: {img.name}{(" " + img.emoji if img.emoji else "")}\n'
|
desc += f"`{key}`: {img.name}{(' ' + img.emoji if img.emoji else '')}\n"
|
||||||
embed.title = f'Bandplan Not Found!'
|
embed.title = "Bandplan Not Found!"
|
||||||
embed.description = desc
|
embed.description = desc
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
else:
|
return
|
||||||
metadata: cmn.ImageMetadata = self.bandcharts[arg]
|
metadata: cmn.ImageMetadata = self.bandcharts[arg]
|
||||||
img = discord.File(cmn.paths.bandcharts / metadata.filename,
|
img = discord.File(cmn.paths.bandcharts / metadata.filename,
|
||||||
filename=metadata.filename)
|
filename=metadata.filename)
|
||||||
@@ -47,25 +52,24 @@ class ImageCog(commands.Cog):
|
|||||||
embed.add_field(name="Source", value=metadata.source)
|
embed.add_field(name="Source", value=metadata.source)
|
||||||
embed.title = metadata.long_name + (" " + metadata.emoji if metadata.emoji else "")
|
embed.title = metadata.long_name + (" " + metadata.emoji if metadata.emoji else "")
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
embed.set_image(url='attachment://' + metadata.filename)
|
embed.set_image(url="attachment://" + metadata.filename)
|
||||||
await ctx.send(embed=embed, file=img)
|
await ctx.send(embed=embed, file=img)
|
||||||
|
|
||||||
@commands.command(name="map", category=cmn.cat.maps)
|
@commands.command(name="map", category=cmn.cat.maps)
|
||||||
async def _map(self, ctx: commands.Context, map_id: str = ''):
|
async def _map(self, ctx: commands.Context, map_id: str = ""):
|
||||||
'''Posts an image of a ham-relevant map.'''
|
"""Posts a ham-relevant map."""
|
||||||
|
async with ctx.typing():
|
||||||
arg = map_id.lower()
|
arg = map_id.lower()
|
||||||
|
|
||||||
with ctx.typing():
|
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
if arg not in self.maps:
|
if arg not in self.maps:
|
||||||
desc = 'Possible arguments are:\n'
|
desc = "Possible arguments are:\n"
|
||||||
for key, img in self.maps.items():
|
for key, img in self.maps.items():
|
||||||
desc += f'`{key}`: {img.name}{(" " + img.emoji if img.emoji else "")}\n'
|
desc += f"`{key}`: {img.name}{(' ' + img.emoji if img.emoji else '')}\n"
|
||||||
embed.title = 'Map Not Found!'
|
embed.title = "Map Not Found!"
|
||||||
embed.description = desc
|
embed.description = desc
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
else:
|
return
|
||||||
metadata: cmn.ImageMetadata = self.maps[arg]
|
metadata: cmn.ImageMetadata = self.maps[arg]
|
||||||
img = discord.File(cmn.paths.maps / metadata.filename,
|
img = discord.File(cmn.paths.maps / metadata.filename,
|
||||||
filename=metadata.filename)
|
filename=metadata.filename)
|
||||||
@@ -75,26 +79,22 @@ class ImageCog(commands.Cog):
|
|||||||
embed.add_field(name="Source", value=metadata.source)
|
embed.add_field(name="Source", value=metadata.source)
|
||||||
embed.title = metadata.long_name + (" " + metadata.emoji if metadata.emoji else "")
|
embed.title = metadata.long_name + (" " + metadata.emoji if metadata.emoji else "")
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
embed.set_image(url='attachment://' + metadata.filename)
|
embed.set_image(url="attachment://" + metadata.filename)
|
||||||
await ctx.send(embed=embed, file=img)
|
await ctx.send(embed=embed, file=img)
|
||||||
|
|
||||||
@commands.command(name="grayline", aliases=['greyline', 'grey', 'gray', 'gl'], category=cmn.cat.maps)
|
@commands.command(name="grayline", aliases=["greyline", "grey", "gray", "gl"], category=cmn.cat.maps)
|
||||||
async def _grayline(self, ctx: commands.Context):
|
async def _grayline(self, ctx: commands.Context):
|
||||||
'''Posts a map of the current greyline, where HF propagation is the best.'''
|
"""Gets a map of the current greyline, where HF propagation is the best."""
|
||||||
gl_url = ('http://www.fourmilab.ch/cgi-bin/uncgi/Earth?img=NOAAtopo.evif'
|
async with ctx.typing():
|
||||||
'&imgsize=320&dynimg=y&opt=-p&lat=&lon=&alt=&tle=&date=0&utc=&jd=')
|
|
||||||
with ctx.typing():
|
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = 'Current Greyline Conditions'
|
embed.title = "Current Greyline Conditions"
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
async with self.session.get(gl_url) as resp:
|
async with self.session.get(self.gl_url) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
embed.description = 'Could not download file...'
|
raise cmn.BotHTTPError(resp)
|
||||||
embed.colour = cmn.colours.bad
|
|
||||||
else:
|
|
||||||
data = io.BytesIO(await resp.read())
|
data = io.BytesIO(await resp.read())
|
||||||
embed.set_image(url=f'attachment://greyline.jpg')
|
embed.set_image(url="attachment://greyline.jpg")
|
||||||
await ctx.send(embed=embed, file=discord.File(data, 'greyline.jpg'))
|
await ctx.send(embed=embed, file=discord.File(data, "greyline.jpg"))
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: commands.Bot):
|
def setup(bot: commands.Bot):
|
||||||
|
|||||||
+24
-23
@@ -1,17 +1,19 @@
|
|||||||
"""
|
"""
|
||||||
Lookup extension for qrm
|
Lookup extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrm2 and is released under the terms of the GNU
|
This file is part of qrm2 and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from discord.ext import commands, tasks
|
|
||||||
from ctyparser import BigCty
|
from ctyparser import BigCty
|
||||||
|
|
||||||
|
from discord.ext import commands, tasks
|
||||||
|
|
||||||
import common as cmn
|
import common as cmn
|
||||||
|
|
||||||
|
|
||||||
@@ -19,48 +21,47 @@ class LookupCog(commands.Cog):
|
|||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
try:
|
try:
|
||||||
self.cty = BigCty('./data/cty.json')
|
self.cty = BigCty("./data/cty.json")
|
||||||
except OSError:
|
except OSError:
|
||||||
self.cty = BigCty()
|
self.cty = BigCty()
|
||||||
|
|
||||||
# TODO: See #107
|
# TODO: See #107
|
||||||
# @commands.command(name="sat", category=cmn.cat.lookup)
|
# @commands.command(name="sat", category=cmn.cat.lookup)
|
||||||
# async def _sat_lookup(self, ctx: commands.Context, sat_name: str, grid1: str, grid2: str = None):
|
# async def _sat_lookup(self, ctx: commands.Context, sat_name: str, grid1: str, grid2: str = None):
|
||||||
# '''Links to info about satellite passes on satmatch.com.'''
|
# """Links to info about satellite passes on satmatch.com."""
|
||||||
# now = datetime.utcnow().strftime('%Y-%m-%d%%20%H:%M')
|
# now = datetime.utcnow().strftime("%Y-%m-%d%%20%H:%M")
|
||||||
# if grid2 is None or grid2 == '':
|
# if grid2 is None or grid2 == "":
|
||||||
# await ctx.send(f'http://www.satmatch.com/satellite/{sat_name}/obs1/{grid1}'
|
# await ctx.send(f"http://www.satmatch.com/satellite/{sat_name}/obs1/{grid1}"
|
||||||
# f'?search_start_time={now}&duration_hrs=24')
|
# f"?search_start_time={now}&duration_hrs=24")
|
||||||
# else:
|
# else:
|
||||||
# await ctx.send(f'http://www.satmatch.com/satellite/{sat_name}/obs1/{grid1}'
|
# await ctx.send(f"http://www.satmatch.com/satellite/{sat_name}/obs1/{grid1}"
|
||||||
# f'/obs2/{grid2}?search_start_time={now}&duration_hrs=24')
|
# f"/obs2/{grid2}?search_start_time={now}&duration_hrs=24")
|
||||||
|
|
||||||
@commands.command(name="dxcc", aliases=['dx'], category=cmn.cat.lookup)
|
@commands.command(name="dxcc", aliases=["dx"], category=cmn.cat.lookup)
|
||||||
async def _dxcc_lookup(self, ctx: commands.Context, query: str):
|
async def _dxcc_lookup(self, ctx: commands.Context, query: str):
|
||||||
'''Gets info about a DXCC prefix.'''
|
"""Gets DXCC info about a callsign prefix."""
|
||||||
with ctx.typing():
|
|
||||||
query = query.upper()
|
query = query.upper()
|
||||||
full_query = query
|
full_query = query
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f'DXCC Info for '
|
embed.title = "DXCC Info for "
|
||||||
embed.description = f'*Last Updated: {self.cty.formatted_version}*'
|
embed.description = f"*Last Updated: {self.cty.formatted_version}*"
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
while query:
|
while query:
|
||||||
if query in self.cty.keys():
|
if query in self.cty.keys():
|
||||||
data = self.cty[query]
|
data = self.cty[query]
|
||||||
embed.add_field(name="Entity", value=data['entity'])
|
embed.add_field(name="Entity", value=data["entity"])
|
||||||
embed.add_field(name="CQ Zone", value=data['cq'])
|
embed.add_field(name="CQ Zone", value=data["cq"])
|
||||||
embed.add_field(name="ITU Zone", value=data['itu'])
|
embed.add_field(name="ITU Zone", value=data["itu"])
|
||||||
embed.add_field(name="Continent", value=data['continent'])
|
embed.add_field(name="Continent", value=data["continent"])
|
||||||
embed.add_field(name="Time Zone",
|
embed.add_field(name="Time Zone",
|
||||||
value=f'+{data["tz"]}' if data['tz'] > 0 else str(data['tz']))
|
value=f"+{data['tz']}" if data["tz"] > 0 else str(data["tz"]))
|
||||||
embed.title += query
|
embed.title += query
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
query = query[:-1]
|
query = query[:-1]
|
||||||
else:
|
else:
|
||||||
embed.title += full_query + ' not found'
|
embed.title += full_query + " not found"
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|||||||
+24
-26
@@ -1,12 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
Morse Code extension for qrm
|
Morse Code extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrm2 and is released under the terms of the GNU
|
This file is part of qrm2 and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
|
|
||||||
import common as cmn
|
import common as cmn
|
||||||
@@ -17,63 +18,60 @@ class MorseCog(commands.Cog):
|
|||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
@commands.command(name="morse", aliases=['cw'], category=cmn.cat.ref)
|
@commands.command(name="morse", aliases=["cw"], category=cmn.cat.ref)
|
||||||
async def _morse(self, ctx: commands.Context, *, msg: str):
|
async def _morse(self, ctx: commands.Context, *, msg: str):
|
||||||
"""Converts ASCII to international morse code."""
|
"""Converts ASCII to international morse code."""
|
||||||
with ctx.typing():
|
result = ""
|
||||||
result = ''
|
|
||||||
for char in msg.upper():
|
for char in msg.upper():
|
||||||
try:
|
try:
|
||||||
result += morse.morse[char]
|
result += morse.morse[char]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
result += '<?>'
|
result += "<?>"
|
||||||
result += ' '
|
result += " "
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f'Morse Code for {msg}'
|
embed.title = f"Morse Code for {msg}"
|
||||||
embed.description = '**' + result + '**'
|
embed.description = "**" + result + "**"
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@commands.command(name="unmorse", aliases=['demorse', 'uncw', 'decw'], category=cmn.cat.ref)
|
@commands.command(name="unmorse", aliases=["demorse", "uncw", "decw"], category=cmn.cat.ref)
|
||||||
async def _unmorse(self, ctx: commands.Context, *, msg: str):
|
async def _unmorse(self, ctx: commands.Context, *, msg: str):
|
||||||
'''Converts international morse code to ASCII.'''
|
"""Converts international morse code to ASCII."""
|
||||||
with ctx.typing():
|
result = ""
|
||||||
result = ''
|
|
||||||
msg0 = msg
|
msg0 = msg
|
||||||
msg = msg.split('/')
|
msg = msg.split("/")
|
||||||
msg = [m.split() for m in msg]
|
msg = [m.split() for m in msg]
|
||||||
for word in msg:
|
for word in msg:
|
||||||
for char in word:
|
for char in word:
|
||||||
try:
|
try:
|
||||||
result += morse.ascii[char]
|
result += morse.ascii[char]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
result += '<?>'
|
result += "<?>"
|
||||||
result += ' '
|
result += " "
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f'ASCII for {msg0}'
|
embed.title = f"ASCII for {msg0}"
|
||||||
embed.description = result
|
embed.description = result
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@commands.command(name="cwweight", aliases=["weight", 'cww'], category=cmn.cat.ref)
|
@commands.command(name="cwweight", aliases=["weight", "cww"], category=cmn.cat.ref)
|
||||||
async def _weight(self, ctx: commands.Context, *, msg: str):
|
async def _weight(self, ctx: commands.Context, *, msg: str):
|
||||||
'''Calculates the CW Weight of a callsign or message.'''
|
"""Calculates the CW weight of a callsign or message."""
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
with ctx.typing():
|
|
||||||
msg = msg.upper()
|
msg = msg.upper()
|
||||||
weight = 0
|
weight = 0
|
||||||
for char in msg:
|
for char in msg:
|
||||||
try:
|
try:
|
||||||
cw_char = morse.morse[char].replace('-', '==')
|
cw_char = morse.morse[char].replace("-", "==")
|
||||||
weight += len(cw_char) * 2 + 2
|
weight += len(cw_char) * 2 + 2
|
||||||
except KeyError:
|
except KeyError:
|
||||||
embed.title = 'Error in calculation of CW weight'
|
embed.title = "Error in calculation of CW weight"
|
||||||
embed.description = f'Unknown character {char} in callsign'
|
embed.description = f"Unknown character `{char}` in message"
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
return
|
return
|
||||||
embed.title = f'CW Weight of {msg}'
|
embed.title = f"CW Weight of {msg}"
|
||||||
embed.description = f'The CW weight is **{weight}**'
|
embed.description = f"The CW weight is **{weight}**"
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|||||||
+83
-80
@@ -1,73 +1,78 @@
|
|||||||
"""
|
"""
|
||||||
QRZ extension for qrm
|
QRZ extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrm2 and is released under the terms of the GNU
|
This file is part of qrm2 and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
from collections import OrderedDict
|
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
from discord.ext import commands, tasks
|
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
from discord.ext import commands, tasks
|
||||||
|
|
||||||
import common as cmn
|
import common as cmn
|
||||||
|
|
||||||
import data.keys as keys
|
import data.keys as keys
|
||||||
|
|
||||||
|
|
||||||
class QRZCog(commands.Cog):
|
class QRZCog(commands.Cog):
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.session = bot.qrm.session
|
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
|
||||||
self._qrz_session_init.start()
|
self._qrz_session_init.start()
|
||||||
|
|
||||||
@commands.command(name="call", aliases=["qrz"], category=cmn.cat.lookup)
|
@commands.command(name="call", aliases=["qrz"], category=cmn.cat.lookup)
|
||||||
async def _qrz_lookup(self, ctx: commands.Context, callsign: str):
|
async def _qrz_lookup(self, ctx: commands.Context, callsign: str, *flags):
|
||||||
'''Look up a callsign on [QRZ.com](https://www.qrz.com/).'''
|
"""Looks up a callsign on [QRZ.com](https://www.qrz.com/). Add `--link` to only link the QRZ page."""
|
||||||
if keys.qrz_user == '' or keys.qrz_pass == '':
|
flags = [f.lower() for f in flags]
|
||||||
await ctx.send(f'http://qrz.com/db/{callsign}')
|
|
||||||
|
if keys.qrz_user == "" or keys.qrz_pass == "" or "--link" in flags:
|
||||||
|
await ctx.send(f"http://qrz.com/db/{callsign}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
async with ctx.typing():
|
||||||
try:
|
try:
|
||||||
await qrz_test_session(self.key, self.session)
|
await qrz_test_session(self.key, self.session)
|
||||||
except ConnectionError:
|
except ConnectionError:
|
||||||
await self.get_session()
|
await self.get_session()
|
||||||
|
|
||||||
url = f'http://xmldata.qrz.com/xml/current/?s={self.key};callsign={callsign}'
|
url = f"http://xmldata.qrz.com/xml/current/?s={self.key};callsign={callsign}"
|
||||||
async with self.session.get(url) as resp:
|
async with self.session.get(url) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
raise ConnectionError(f'Unable to connect to QRZ (HTTP Error {resp.status})')
|
raise ConnectionError(f"Unable to connect to QRZ (HTTP Error {resp.status})")
|
||||||
with BytesIO(await resp.read()) as resp_file:
|
with BytesIO(await resp.read()) as resp_file:
|
||||||
resp_xml = etree.parse(resp_file).getroot()
|
resp_xml = etree.parse(resp_file).getroot()
|
||||||
|
|
||||||
resp_xml_session = resp_xml.xpath('/x:QRZDatabase/x:Session', namespaces={'x': 'http://xmldata.qrz.com'})
|
resp_xml_session = resp_xml.xpath("/x:QRZDatabase/x:Session", namespaces={"x": "http://xmldata.qrz.com"})
|
||||||
resp_session = {el.tag.split('}')[1]: el.text for el in resp_xml_session[0].getiterator()}
|
resp_session = {el.tag.split("}")[1]: el.text for el in resp_xml_session[0].getiterator()}
|
||||||
if 'Error' in resp_session:
|
if "Error" in resp_session:
|
||||||
if 'Session Timeout' in resp_session['Error']:
|
if "Session Timeout" in resp_session["Error"]:
|
||||||
await self.get_session()
|
await self.get_session()
|
||||||
await self._qrz_lookup(ctx, callsign)
|
await self._qrz_lookup(ctx, callsign)
|
||||||
return
|
return
|
||||||
if 'Not found' in resp_session['Error']:
|
if "Not found" in resp_session["Error"]:
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f"QRZ Data for {callsign.upper()}"
|
embed.title = f"QRZ Data for {callsign.upper()}"
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
embed.description = 'No data found!'
|
embed.description = "No data found!"
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
return
|
return
|
||||||
raise ValueError(resp_session['Error'])
|
raise ValueError(resp_session["Error"])
|
||||||
|
|
||||||
resp_xml_data = resp_xml.xpath('/x:QRZDatabase/x:Callsign', namespaces={'x': 'http://xmldata.qrz.com'})
|
resp_xml_data = resp_xml.xpath("/x:QRZDatabase/x:Callsign", namespaces={"x": "http://xmldata.qrz.com"})
|
||||||
resp_data = {el.tag.split('}')[1]: el.text for el in resp_xml_data[0].getiterator()}
|
resp_data = {el.tag.split("}")[1]: el.text for el in resp_xml_data[0].getiterator()}
|
||||||
|
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f"QRZ Data for {resp_data['call']}"
|
embed.title = f"QRZ Data for {resp_data['call']}"
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
embed.url = f'http://www.qrz.com/db/{resp_data["call"]}'
|
embed.url = f"http://www.qrz.com/db/{resp_data['call']}"
|
||||||
if 'image' in resp_data:
|
if "image" in resp_data:
|
||||||
embed.set_thumbnail(url=resp_data['image'])
|
embed.set_thumbnail(url=resp_data["image"])
|
||||||
|
|
||||||
data = qrz_process_info(resp_data)
|
data = qrz_process_info(resp_data)
|
||||||
|
|
||||||
@@ -79,14 +84,14 @@ class QRZCog(commands.Cog):
|
|||||||
async def get_session(self):
|
async def get_session(self):
|
||||||
"""Session creation and caching."""
|
"""Session creation and caching."""
|
||||||
self.key = await qrz_login(keys.qrz_user, keys.qrz_pass, self.session)
|
self.key = await qrz_login(keys.qrz_user, keys.qrz_pass, self.session)
|
||||||
with open('data/qrz_session', 'w') as qrz_file:
|
with open("data/qrz_session", "w") as qrz_file:
|
||||||
qrz_file.write(self.key)
|
qrz_file.write(self.key)
|
||||||
|
|
||||||
@tasks.loop(count=1)
|
@tasks.loop(count=1)
|
||||||
async def _qrz_session_init(self):
|
async def _qrz_session_init(self):
|
||||||
"""Helper task to allow obtaining a session at cog instantiation."""
|
"""Helper task to allow obtaining a session at cog instantiation."""
|
||||||
try:
|
try:
|
||||||
with open('data/qrz_session') as qrz_file:
|
with open("data/qrz_session") as qrz_file:
|
||||||
self.key = qrz_file.readline().strip()
|
self.key = qrz_file.readline().strip()
|
||||||
await qrz_test_session(self.key, self.session)
|
await qrz_test_session(self.key, self.session)
|
||||||
except (FileNotFoundError, ConnectionError):
|
except (FileNotFoundError, ConnectionError):
|
||||||
@@ -94,85 +99,83 @@ class QRZCog(commands.Cog):
|
|||||||
|
|
||||||
|
|
||||||
async def qrz_login(user: str, passwd: str, session: aiohttp.ClientSession):
|
async def qrz_login(user: str, passwd: str, session: aiohttp.ClientSession):
|
||||||
url = f'http://xmldata.qrz.com/xml/current/?username={user};password={passwd};agent=discord-qrm2'
|
url = f"http://xmldata.qrz.com/xml/current/?username={user};password={passwd};agent=discord-qrm2"
|
||||||
async with session.get(url) as resp:
|
async with session.get(url) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
raise ConnectionError(f'Unable to connect to QRZ (HTTP Error {resp.status})')
|
raise ConnectionError(f"Unable to connect to QRZ (HTTP Error {resp.status})")
|
||||||
with BytesIO(await resp.read()) as resp_file:
|
with BytesIO(await resp.read()) as resp_file:
|
||||||
resp_xml = etree.parse(resp_file).getroot()
|
resp_xml = etree.parse(resp_file).getroot()
|
||||||
|
|
||||||
resp_xml_session = resp_xml.xpath('/x:QRZDatabase/x:Session', namespaces={'x': 'http://xmldata.qrz.com'})
|
resp_xml_session = resp_xml.xpath("/x:QRZDatabase/x:Session", namespaces={"x": "http://xmldata.qrz.com"})
|
||||||
resp_session = {el.tag.split('}')[1]: el.text for el in resp_xml_session[0].getiterator()}
|
resp_session = {el.tag.split("}")[1]: el.text for el in resp_xml_session[0].getiterator()}
|
||||||
if 'Error' in resp_session:
|
if "Error" in resp_session:
|
||||||
raise ConnectionError(resp_session['Error'])
|
raise ConnectionError(resp_session["Error"])
|
||||||
if resp_session['SubExp'] == 'non-subscriber':
|
if resp_session["SubExp"] == "non-subscriber":
|
||||||
raise ConnectionError('Invalid QRZ Subscription')
|
raise ConnectionError("Invalid QRZ Subscription")
|
||||||
return resp_session['Key']
|
return resp_session["Key"]
|
||||||
|
|
||||||
|
|
||||||
async def qrz_test_session(key: str, session: aiohttp.ClientSession):
|
async def qrz_test_session(key: str, session: aiohttp.ClientSession):
|
||||||
url = f'http://xmldata.qrz.com/xml/current/?s={key}'
|
url = f"http://xmldata.qrz.com/xml/current/?s={key}"
|
||||||
async with session.get(url) as resp:
|
async with session.get(url) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
raise ConnectionError(f'Unable to connect to QRZ (HTTP Error {resp.status})')
|
raise ConnectionError(f"Unable to connect to QRZ (HTTP Error {resp.status})")
|
||||||
with BytesIO(await resp.read()) as resp_file:
|
with BytesIO(await resp.read()) as resp_file:
|
||||||
resp_xml = etree.parse(resp_file).getroot()
|
resp_xml = etree.parse(resp_file).getroot()
|
||||||
|
|
||||||
resp_xml_session = resp_xml.xpath('/x:QRZDatabase/x:Session', namespaces={'x': 'http://xmldata.qrz.com'})
|
resp_xml_session = resp_xml.xpath("/x:QRZDatabase/x:Session", namespaces={"x": "http://xmldata.qrz.com"})
|
||||||
resp_session = {el.tag.split('}')[1]: el.text for el in resp_xml_session[0].getiterator()}
|
resp_session = {el.tag.split("}")[1]: el.text for el in resp_xml_session[0].getiterator()}
|
||||||
if 'Error' in resp_session:
|
if "Error" in resp_session:
|
||||||
raise ConnectionError(resp_session['Error'])
|
raise ConnectionError(resp_session["Error"])
|
||||||
|
|
||||||
|
|
||||||
def qrz_process_info(data: dict):
|
def qrz_process_info(data: dict):
|
||||||
if 'name' in data:
|
if "name" in data:
|
||||||
if 'fname' in data:
|
if "fname" in data:
|
||||||
name = data['fname'] + ' ' + data['name']
|
name = data["fname"] + " " + data["name"]
|
||||||
else:
|
else:
|
||||||
name = data['name']
|
name = data["name"]
|
||||||
else:
|
else:
|
||||||
name = None
|
name = None
|
||||||
if 'state' in data:
|
if "state" in data:
|
||||||
state = f', {data["state"]}'
|
state = f", {data['state']}"
|
||||||
else:
|
else:
|
||||||
state = ''
|
state = ""
|
||||||
address = data.get('addr1', '') + '\n' + data.get('addr2', '') + state + ' ' + data.get('zip', '')
|
address = data.get("addr1", "") + "\n" + data.get("addr2", "") + state + " " + data.get("zip", "")
|
||||||
address = address.strip()
|
address = address.strip()
|
||||||
if address == '':
|
if address == "":
|
||||||
address = None
|
address = None
|
||||||
if 'eqsl' in data:
|
if "eqsl" in data:
|
||||||
eqsl = 'Yes' if data['eqsl'] == 1 else 'No'
|
eqsl = "Yes" if data["eqsl"] == 1 else "No"
|
||||||
else:
|
else:
|
||||||
eqsl = 'Unknown'
|
eqsl = "Unknown"
|
||||||
if 'mqsl' in data:
|
if "mqsl" in data:
|
||||||
mqsl = 'Yes' if data['mqsl'] == 1 else 'No'
|
mqsl = "Yes" if data["mqsl"] == 1 else "No"
|
||||||
else:
|
else:
|
||||||
mqsl = 'Unknown'
|
mqsl = "Unknown"
|
||||||
if 'lotw' in data:
|
if "lotw" in data:
|
||||||
lotw = 'Yes' if data['lotw'] == 1 else 'No'
|
lotw = "Yes" if data["lotw"] == 1 else "No"
|
||||||
else:
|
else:
|
||||||
lotw = 'Unknown'
|
lotw = "Unknown"
|
||||||
|
|
||||||
return OrderedDict([('Name', name),
|
return {"Name": name,
|
||||||
('Country', data.get('country', None)),
|
"Country": data.get("country", None),
|
||||||
('Address', address),
|
"Address": address,
|
||||||
('Grid Square', data.get('grid', None)),
|
"Grid Square": data.get("grid", None),
|
||||||
('County', data.get('county', None)),
|
"County": data.get("county", None),
|
||||||
('CQ Zone', data.get('cqzone', None)),
|
"CQ Zone": data.get("cqzone", None),
|
||||||
('ITU Zone', data.get('ituzone', None)),
|
"ITU Zone": data.get("ituzone", None),
|
||||||
('IOTA Designator', data.get('iota', None)),
|
"IOTA Designator": data.get("iota", None),
|
||||||
('Expires', data.get('expdate', None)),
|
"Expires": data.get("expdate", None),
|
||||||
('Aliases', data.get('aliases', None)),
|
"Aliases": data.get("aliases", None),
|
||||||
('Previous Callsign', data.get('p_call', None)),
|
"Previous Callsign": data.get("p_call", None),
|
||||||
('License Class', data.get('class', None)),
|
"License Class": data.get("class", None),
|
||||||
('eQSL?', eqsl),
|
"Trustee": data.get("trustee", None),
|
||||||
('Paper QSL?', mqsl),
|
"eQSL?": eqsl,
|
||||||
('LotW?', lotw),
|
"Paper QSL?": mqsl,
|
||||||
('QSL Info', data.get('qslmgr', None)),
|
"LotW?": lotw,
|
||||||
('CQ Zone', data.get('cqzone', None)),
|
"QSL Info": data.get("qslmgr", None),
|
||||||
('ITU Zone', data.get('ituzone', None)),
|
"Born": data.get("born", None)}
|
||||||
('IOTA Designator', data.get('iota', None)),
|
|
||||||
('Born', data.get('born', None))])
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
|
|||||||
+141
-74
@@ -1,117 +1,184 @@
|
|||||||
"""
|
"""
|
||||||
Study extension for qrm
|
Study extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrm2 and is released under the terms of the GNU
|
This file is part of qrm2 and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import random
|
import random
|
||||||
import json
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
|
|
||||||
import common as cmn
|
import common as cmn
|
||||||
|
from resources import study
|
||||||
|
|
||||||
|
|
||||||
class StudyCog(commands.Cog):
|
class StudyCog(commands.Cog):
|
||||||
|
choices = {cmn.emojis.a: "A", cmn.emojis.b: "B", cmn.emojis.c: "C", cmn.emojis.d: "D"}
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.lastq = dict()
|
self.lastq = dict()
|
||||||
self.source = 'Data courtesy of [HamStudy.org](https://hamstudy.org/)'
|
self.source = "Data courtesy of [HamStudy.org](https://hamstudy.org/)"
|
||||||
self.session = bot.qrm.session
|
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
|
||||||
|
|
||||||
@commands.command(name="hamstudy", aliases=['rq', 'randomquestion', 'randomq'], category=cmn.cat.study)
|
@commands.command(name="hamstudy", aliases=["rq", "randomquestion", "randomq"], category=cmn.cat.study)
|
||||||
async def _random_question(self, ctx: commands.Context, level: str = None):
|
async def _random_question(self, ctx: commands.Context, country: str = "", level: str = ""):
|
||||||
'''Gets a random question from the Technician, General, and/or Extra question pools.'''
|
"""Gets a random question from [HamStudy's](https://hamstudy.org) question pools."""
|
||||||
tech_pool = 'E2_2018'
|
|
||||||
gen_pool = 'E3_2019'
|
|
||||||
extra_pool = 'E4_2016'
|
|
||||||
|
|
||||||
embed = cmn.embed_factory(ctx)
|
|
||||||
with ctx.typing():
|
with ctx.typing():
|
||||||
selected_pool = None
|
embed = cmn.embed_factory(ctx)
|
||||||
try:
|
|
||||||
|
country = country.lower()
|
||||||
level = level.lower()
|
level = level.lower()
|
||||||
except AttributeError: # no level given (it's None)
|
|
||||||
pass
|
|
||||||
|
|
||||||
if level in ['t', 'technician', 'tech']:
|
if country in study.pool_names.keys():
|
||||||
selected_pool = tech_pool
|
if level in study.pool_names[country].keys():
|
||||||
|
pool_name = study.pool_names[country][level]
|
||||||
|
|
||||||
if level in ['g', 'gen', 'general']:
|
elif level in ("random", "r"):
|
||||||
selected_pool = gen_pool
|
# select a random level in that country
|
||||||
|
pool_name = random.choice(list(study.pool_names[country].values()))
|
||||||
|
|
||||||
if level in ['e', 'ae', 'extra']:
|
else:
|
||||||
selected_pool = extra_pool
|
# show list of possible pools
|
||||||
|
embed.title = "Pool Not Found!"
|
||||||
if (level is None) or (level == 'all'): # no pool given or user wants all, so pick a random pool
|
embed.description = "Possible arguments are:"
|
||||||
selected_pool = random.choice([tech_pool, gen_pool, extra_pool])
|
|
||||||
if (level is not None) and (selected_pool is None): # unrecognized pool given by user
|
|
||||||
embed.title = 'Error in HamStudy command'
|
|
||||||
embed.description = ('The question pool you gave was unrecognized. '
|
|
||||||
'There are many ways to call up certain question pools - try ?rq t, g, or e. '
|
|
||||||
'\n\nNote that currently only the US question pools are available.')
|
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
|
for cty in study.pool_names:
|
||||||
|
levels = "`, `".join(study.pool_names[cty].keys())
|
||||||
|
embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**",
|
||||||
|
value=f"Levels: `{levels}`", inline=False)
|
||||||
|
embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`")
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
return
|
return
|
||||||
|
|
||||||
async with self.session.get(f'https://hamstudy.org/pools/{selected_pool}') as resp:
|
elif country in ("random", "r"):
|
||||||
|
# select a random country and level
|
||||||
|
country = random.choice(list(study.pool_names.keys()))
|
||||||
|
pool_name = random.choice(list(study.pool_names[country].values()))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# show list of possible pools
|
||||||
|
embed.title = "Pool Not Found!"
|
||||||
|
embed.description = "Possible arguments are:"
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
for cty in study.pool_names:
|
||||||
|
levels = "`, `".join(study.pool_names[cty].keys())
|
||||||
|
embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**",
|
||||||
|
value=f"Levels: `{levels}`", inline=False)
|
||||||
|
embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`")
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
pools = await self.hamstudy_get_pools()
|
||||||
|
|
||||||
|
pool_matches = [p for p in pools.keys() if "_".join(p.split("_")[:-1]) == pool_name]
|
||||||
|
|
||||||
|
if len(pool_matches) > 0:
|
||||||
|
if len(pool_matches) == 1:
|
||||||
|
pool = pool_matches[0]
|
||||||
|
else:
|
||||||
|
# look at valid_from and expires dates to find the correct one
|
||||||
|
for p in pool_matches:
|
||||||
|
valid_from = datetime.fromisoformat(pools[p]["valid_from"][:-1])
|
||||||
|
expires = datetime.fromisoformat(pools[p]["expires"][:-1])
|
||||||
|
|
||||||
|
if valid_from < datetime.utcnow() < expires:
|
||||||
|
pool = p
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# show list of possible pools
|
||||||
|
embed.title = "Pool Not Found!"
|
||||||
|
embed.description = "Possible arguments are:"
|
||||||
|
embed.colour = cmn.colours.bad
|
||||||
|
for cty in study.pool_names:
|
||||||
|
levels = "`, `".join(study.pool_names[cty].keys())
|
||||||
|
embed.add_field(name=f"**Country: `{cty}` {study.pool_emojis[cty]}**",
|
||||||
|
value=f"Levels: `{levels}`", inline=False)
|
||||||
|
embed.add_field(name="**Random**", value="To select a random pool or country, use `random` or `r`")
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
pool_meta = pools[pool]
|
||||||
|
|
||||||
|
async with self.session.get(f"https://hamstudy.org/pools/{pool}") as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
embed.title = 'Error in HamStudy command'
|
raise cmn.BotHTTPError(resp)
|
||||||
embed.description = 'Could not load questions'
|
pool = json.loads(await resp.read())["pool"]
|
||||||
embed.colour = cmn.colours.bad
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
return
|
|
||||||
pool = json.loads(await resp.read())['pool']
|
|
||||||
|
|
||||||
# Select a question
|
# Select a question
|
||||||
pool_section = random.choice(pool)['sections']
|
pool_section = random.choice(pool)["sections"]
|
||||||
pool_questions = random.choice(pool_section)['questions']
|
pool_questions = random.choice(pool_section)["questions"]
|
||||||
question = random.choice(pool_questions)
|
question = random.choice(pool_questions)
|
||||||
|
|
||||||
embed.title = question['id']
|
embed.title = f"{study.pool_emojis[country]} {pool_meta['class']} {question['id']}"
|
||||||
embed.description = self.source
|
embed.description = self.source
|
||||||
embed.colour = cmn.colours.good
|
embed.add_field(name="Question:", value=question["text"], inline=False)
|
||||||
embed.add_field(name='Question:', value=question['text'], inline=False)
|
embed.add_field(name="Answers:",
|
||||||
embed.add_field(name='Answers:', value='**A:** ' + question['answers']['A']
|
value=(f"**{cmn.emojis.a}** {question['answers']['A']}"
|
||||||
+ '\n**B:** ' + question['answers']['B']
|
f"\n**{cmn.emojis.b}** {question['answers']['B']}"
|
||||||
+ '\n**C:** ' + question['answers']['C']
|
f"\n**{cmn.emojis.c}** {question['answers']['C']}"
|
||||||
+ '\n**D:** ' + question['answers']['D'], inline=False)
|
f"\n**{cmn.emojis.d}** {question['answers']['D']}"),
|
||||||
embed.add_field(name='Answer:', value='Type _?rqa_ for answer', inline=False)
|
inline=False)
|
||||||
if 'image' in question:
|
embed.add_field(name="To Answer:",
|
||||||
image_url = f'https://hamstudy.org/_1330011/images/{selected_pool.split("_",1)[1]}/{question["image"]}'
|
value=("Answer with reactions below. If not answered within 10 minutes,"
|
||||||
|
" the answer will be revealed."),
|
||||||
|
inline=False)
|
||||||
|
if "image" in question:
|
||||||
|
image_url = f"https://hamstudy.org/images/{pool_meta['year']}/{question['image']}"
|
||||||
embed.set_image(url=image_url)
|
embed.set_image(url=image_url)
|
||||||
self.lastq[ctx.message.channel.id] = (question['id'], question['answer'])
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
@commands.command(name="hamstudyanswer", aliases=['rqa', 'randomquestionanswer', 'randomqa', 'hamstudya'],
|
q_msg = await ctx.send(embed=embed)
|
||||||
category=cmn.cat.study)
|
|
||||||
async def _q_answer(self, ctx: commands.Context, answer: str = None):
|
await cmn.add_react(q_msg, cmn.emojis.a)
|
||||||
'''Returns the answer to question last asked (Optional argument: your answer).'''
|
await cmn.add_react(q_msg, cmn.emojis.b)
|
||||||
with ctx.typing():
|
await cmn.add_react(q_msg, cmn.emojis.c)
|
||||||
correct_ans = self.lastq[ctx.message.channel.id][1]
|
await cmn.add_react(q_msg, cmn.emojis.d)
|
||||||
q_num = self.lastq[ctx.message.channel.id][0]
|
|
||||||
embed = cmn.embed_factory(ctx)
|
def check(reaction, user):
|
||||||
if answer is not None:
|
return (user.id != self.bot.user.id
|
||||||
answer = answer.upper()
|
and reaction.message.id == q_msg.id
|
||||||
if answer == correct_ans:
|
and str(reaction.emoji) in self.choices.keys())
|
||||||
result = f'Correct! The answer to {q_num} was **{correct_ans}**.'
|
|
||||||
embed.title = f'{q_num} Answer'
|
try:
|
||||||
embed.description = f'{self.source}\n\n{result}'
|
reaction, user = await self.bot.wait_for("reaction_add", timeout=600.0, check=check)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
embed.remove_field(2)
|
||||||
|
embed.add_field(name="Answer:", value=f"Timed out! The correct answer was **{question['answer']}**.")
|
||||||
|
await q_msg.edit(embed=embed)
|
||||||
|
else:
|
||||||
|
if self.choices[str(reaction.emoji)] == question["answer"]:
|
||||||
|
embed.remove_field(2)
|
||||||
|
embed.add_field(name="Answer:", value=f"Correct! The answer was **{question['answer']}**.")
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
|
await q_msg.edit(embed=embed)
|
||||||
else:
|
else:
|
||||||
result = f'Incorrect. The answer to {q_num} was **{correct_ans}**, not **{answer}**.'
|
embed.remove_field(2)
|
||||||
embed.title = f'{q_num} Answer'
|
embed.add_field(name="Answer:", value=f"Incorrect! The correct answer was **{question['answer']}**.")
|
||||||
embed.description = f'{self.source}\n\n{result}'
|
|
||||||
embed.colour = cmn.colours.bad
|
embed.colour = cmn.colours.bad
|
||||||
|
await q_msg.edit(embed=embed)
|
||||||
|
|
||||||
|
async def hamstudy_get_pools(self):
|
||||||
|
async with self.session.get("https://hamstudy.org/pools/") as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
raise cmn.BotHTTPError(resp)
|
||||||
else:
|
else:
|
||||||
result = f'The correct answer to {q_num} was **{correct_ans}**.'
|
pools_dict = json.loads(await resp.read())
|
||||||
embed.title = f'{q_num} Answer'
|
|
||||||
embed.description = f'{self.source}\n\n{result}'
|
pools = dict()
|
||||||
embed.colour = cmn.colours.neutral
|
for ls in pools_dict.values():
|
||||||
await ctx.send(embed=embed)
|
for pool in ls:
|
||||||
|
pools[pool["id"]] = pool
|
||||||
|
|
||||||
|
return pools
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: commands.Bot):
|
def setup(bot: commands.Bot):
|
||||||
|
|||||||
+55
-57
@@ -1,15 +1,18 @@
|
|||||||
"""
|
"""
|
||||||
Weather extension for qrm
|
Weather extension for qrm
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrm2 and is released under the terms of the GNU
|
This file is part of qrm2 and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import discord.ext.commands as commands
|
import discord.ext.commands as commands
|
||||||
|
|
||||||
@@ -21,27 +24,25 @@ class WeatherCog(commands.Cog):
|
|||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.session = bot.qrm.session
|
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
|
||||||
|
|
||||||
@commands.command(name="bandconditions", aliases=['cond', 'condx', 'conditions'], category=cmn.cat.weather)
|
@commands.command(name="bandconditions", aliases=["cond", "condx", "conditions"], category=cmn.cat.weather)
|
||||||
async def _band_conditions(self, ctx: commands.Context):
|
async def _band_conditions(self, ctx: commands.Context):
|
||||||
'''Posts an image of HF Band Conditions.'''
|
"""Gets a solar conditions report."""
|
||||||
with ctx.typing():
|
async with ctx.typing():
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = 'Current Solar Conditions'
|
embed.title = "Current Solar Conditions"
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
async with self.session.get('http://www.hamqsl.com/solarsun.php') as resp:
|
async with self.session.get("http://www.hamqsl.com/solarsun.php") as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
embed.description = 'Could not download file...'
|
raise cmn.BotHTTPError(resp)
|
||||||
embed.colour = cmn.colours.bad
|
|
||||||
else:
|
|
||||||
data = io.BytesIO(await resp.read())
|
data = io.BytesIO(await resp.read())
|
||||||
embed.set_image(url=f'attachment://condx.png')
|
embed.set_image(url="attachment://condx.png")
|
||||||
await ctx.send(embed=embed, file=discord.File(data, 'condx.png'))
|
await ctx.send(embed=embed, file=discord.File(data, "condx.png"))
|
||||||
|
|
||||||
@commands.group(name="weather", aliases=['wttr'], category=cmn.cat.weather)
|
@commands.group(name="weather", aliases=["wttr"], case_insensitive=True, category=cmn.cat.weather)
|
||||||
async def _weather_conditions(self, ctx: commands.Context):
|
async def _weather_conditions(self, ctx: commands.Context):
|
||||||
'''Posts an image of Local Weather Conditions from [wttr.in](http://wttr.in/).
|
"""Gets local weather conditions from [wttr.in](http://wttr.in/).
|
||||||
|
|
||||||
*Supported location types:*
|
*Supported location types:*
|
||||||
city name: `paris`
|
city name: `paris`
|
||||||
@@ -51,75 +52,72 @@ class WeatherCog(commands.Cog):
|
|||||||
domain name `@stackoverflow.com`
|
domain name `@stackoverflow.com`
|
||||||
area codes: `12345`
|
area codes: `12345`
|
||||||
GPS coordinates: `-78.46,106.79`
|
GPS coordinates: `-78.46,106.79`
|
||||||
'''
|
|
||||||
|
Add a `-c` or `-f` to use Celcius or Fahrenheit: `-c YSC`"""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
await ctx.send_help(ctx.command)
|
await ctx.send_help(ctx.command)
|
||||||
|
|
||||||
@_weather_conditions.command(name='forecast', aliases=['fc', 'future'], category=cmn.cat.weather)
|
@_weather_conditions.command(name="forecast", aliases=["fc", "future"], category=cmn.cat.weather)
|
||||||
async def _weather_conditions_forecast(self, ctx: commands.Context, *, location: str):
|
async def _weather_conditions_forecast(self, ctx: commands.Context, *, location: str):
|
||||||
'''Posts an image of Local Weather Conditions for the next three days from [wttr.in](http://wttr.in/).
|
"""Gets local weather forecast for the next three days from [wttr.in](http://wttr.in/).
|
||||||
See help for weather command for possible location types. Add a `-c` or `-f` to use Celcius or Fahrenheit.'''
|
See help of the `weather` command for possible location types and options."""
|
||||||
with ctx.typing():
|
async with ctx.typing():
|
||||||
try:
|
try:
|
||||||
units_arg = re.search(self.wttr_units_regex, location).group(1)
|
units_arg = re.search(self.wttr_units_regex, location).group(1)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
units_arg = ''
|
units_arg = ""
|
||||||
if units_arg.lower() == 'f':
|
if units_arg.lower() == "f":
|
||||||
units = 'u'
|
units = "u"
|
||||||
elif units_arg.lower() == 'c':
|
elif units_arg.lower() == "c":
|
||||||
units = 'm'
|
units = "m"
|
||||||
else:
|
else:
|
||||||
units = ''
|
units = ""
|
||||||
|
|
||||||
loc = self.wttr_units_regex.sub('', location).strip()
|
loc = self.wttr_units_regex.sub("", location).strip()
|
||||||
|
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f'Weather Forecast for {loc}'
|
embed.title = f"Weather Forecast for {loc}"
|
||||||
embed.description = 'Data from [wttr.in](http://wttr.in/).'
|
embed.description = "Data from [wttr.in](http://wttr.in/)."
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
|
|
||||||
loc = loc.replace(' ', '+')
|
loc = loc.replace(" ", "+")
|
||||||
async with self.session.get(f'http://wttr.in/{loc}_{units}pnFQ.png') as resp:
|
async with self.session.get(f"http://wttr.in/{loc}_{units}pnFQ.png") as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
embed.description = 'Could not download file...'
|
raise cmn.BotHTTPError(resp)
|
||||||
embed.colour = cmn.colours.bad
|
|
||||||
else:
|
|
||||||
data = io.BytesIO(await resp.read())
|
data = io.BytesIO(await resp.read())
|
||||||
embed.set_image(url=f'attachment://wttr_forecast.png')
|
embed.set_image(url="attachment://wttr_forecast.png")
|
||||||
await ctx.send(embed=embed, file=discord.File(data, 'wttr_forecast.png'))
|
await ctx.send(embed=embed, file=discord.File(data, "wttr_forecast.png"))
|
||||||
|
|
||||||
@_weather_conditions.command(name='now', aliases=['n'], category=cmn.cat.weather)
|
@_weather_conditions.command(name="now", aliases=["n"], category=cmn.cat.weather)
|
||||||
async def _weather_conditions_now(self, ctx: commands.Context, *, location: str):
|
async def _weather_conditions_now(self, ctx: commands.Context, *, location: str):
|
||||||
'''Posts an image of current Local Weather Conditions from [wttr.in](http://wttr.in/).
|
"""Gets current local weather conditions from [wttr.in](http://wttr.in/).
|
||||||
See help for weather command for possible location types. Add a `-c` or `-f` to use Celcius or Fahrenheit.'''
|
See help of the `weather` command for possible location types and options."""
|
||||||
with ctx.typing():
|
async with ctx.typing():
|
||||||
try:
|
try:
|
||||||
units_arg = re.search(self.wttr_units_regex, location).group(1)
|
units_arg = re.search(self.wttr_units_regex, location).group(1)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
units_arg = ''
|
units_arg = ""
|
||||||
if units_arg.lower() == 'f':
|
if units_arg.lower() == "f":
|
||||||
units = 'u'
|
units = "u"
|
||||||
elif units_arg.lower() == 'c':
|
elif units_arg.lower() == "c":
|
||||||
units = 'm'
|
units = "m"
|
||||||
else:
|
else:
|
||||||
units = ''
|
units = ""
|
||||||
|
|
||||||
loc = self.wttr_units_regex.sub('', location).strip()
|
loc = self.wttr_units_regex.sub("", location).strip()
|
||||||
|
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = f'Current Weather for {loc}'
|
embed.title = f"Current Weather for {loc}"
|
||||||
embed.description = 'Data from [wttr.in](http://wttr.in/).'
|
embed.description = "Data from [wttr.in](http://wttr.in/)."
|
||||||
embed.colour = cmn.colours.good
|
embed.colour = cmn.colours.good
|
||||||
|
|
||||||
loc = loc.replace(' ', '+')
|
loc = loc.replace(" ", "+")
|
||||||
async with self.session.get(f'http://wttr.in/{loc}_0{units}pnFQ.png') as resp:
|
async with self.session.get(f"http://wttr.in/{loc}_0{units}pnFQ.png") as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
embed.description = 'Could not download file...'
|
raise cmn.BotHTTPError(resp)
|
||||||
embed.colour = cmn.colours.bad
|
|
||||||
else:
|
|
||||||
data = io.BytesIO(await resp.read())
|
data = io.BytesIO(await resp.read())
|
||||||
embed.set_image(url=f'attachment://wttr_now.png')
|
embed.set_image(url="attachment://wttr_now.png")
|
||||||
await ctx.send(embed=embed, file=discord.File(data, 'wttr_now.png'))
|
await ctx.send(embed=embed, file=discord.File(data, "wttr_now.png"))
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: commands.Bot):
|
def setup(bot: commands.Bot):
|
||||||
|
|||||||
@@ -1,28 +1,16 @@
|
|||||||
"""
|
"""
|
||||||
Static info about the bot.
|
Static info about the bot.
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrm2 and is released under the terms of the GNU
|
This file is part of qrm2 and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
---
|
|
||||||
|
|
||||||
`authors`: The authors of the bot.
|
|
||||||
|
|
||||||
`description`: A description of the bot.
|
|
||||||
|
|
||||||
`license`: The license the bot is released under.
|
|
||||||
|
|
||||||
`contrubuting`: Info on how to contribute to the bot.
|
|
||||||
|
|
||||||
`release`: Current bot version.
|
|
||||||
|
|
||||||
`release_timestamp`: When the bot was last released.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
authors = ("@ClassAbbyAmplifier#2229", "@0x5c#0639")
|
authors = ("@ClassAbbyAmplifier#2229", "@0x5c#0639")
|
||||||
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 = "Released under the GNU General Public License v2"
|
license = "Released under the GNU General Public License v2"
|
||||||
contributing = "Check out the source on GitHub, contributions welcome: https://github.com/classabbyamp/discord-qrm2"
|
contributing = "Check out the source on GitHub, contributions welcome: https://github.com/miaowware/qrm2"
|
||||||
release = '2.1.0'
|
release = "2.3.1"
|
||||||
bot_server = 'https://discord.gg/Ntbg3J4'
|
bot_server = "https://discord.gg/Ntbg3J4"
|
||||||
|
|||||||
@@ -2,29 +2,31 @@
|
|||||||
"""
|
"""
|
||||||
qrm, a bot for Discord
|
qrm, a bot for Discord
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrm2 and is released under the terms of the GNU
|
This file is part of qrm2 and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import random
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from datetime import time, datetime
|
from datetime import datetime, time
|
||||||
import random
|
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands, tasks
|
from discord.ext import commands, tasks
|
||||||
|
|
||||||
import common as cmn
|
|
||||||
import info
|
import info
|
||||||
import data.options as opt
|
import common as cmn
|
||||||
|
import utils.connector as conn
|
||||||
|
|
||||||
import data.keys as keys
|
import data.keys as keys
|
||||||
|
import data.options as opt
|
||||||
|
|
||||||
|
|
||||||
# --- Settings ---
|
# --- Settings ---
|
||||||
@@ -38,23 +40,30 @@ debug_mode = opt.debug # Separate assignement in-case we define an override (te
|
|||||||
|
|
||||||
# --- Bot setup ---
|
# --- Bot setup ---
|
||||||
|
|
||||||
|
# Loop/aiohttp stuff
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
connector = loop.run_until_complete(conn.new_connector())
|
||||||
|
|
||||||
bot = commands.Bot(command_prefix=opt.prefix,
|
bot = commands.Bot(command_prefix=opt.prefix,
|
||||||
description=info.description,
|
case_insensitive=True,
|
||||||
help_command=commands.MinimalHelpCommand())
|
description=info.description, help_command=commands.MinimalHelpCommand(),
|
||||||
|
loop=loop,
|
||||||
|
connector=connector)
|
||||||
|
|
||||||
|
# Simple way to access bot-wide stuff in extensions.
|
||||||
bot.qrm = SimpleNamespace()
|
bot.qrm = SimpleNamespace()
|
||||||
bot.qrm.session = aiohttp.ClientSession(headers={'User-Agent': f'discord-qrm2/{info.release}'})
|
|
||||||
|
|
||||||
|
# Let's store stuff here.
|
||||||
|
bot.qrm.connector = connector
|
||||||
bot.qrm.debug_mode = debug_mode
|
bot.qrm.debug_mode = debug_mode
|
||||||
|
|
||||||
|
|
||||||
# --- Commands ---
|
# --- Commands ---
|
||||||
|
|
||||||
@bot.command(name="restart", hidden=True)
|
@bot.command(name="restart", aliases=["rs"], category=cmn.cat.admin)
|
||||||
@commands.check(cmn.check_if_owner)
|
@commands.check(cmn.check_if_owner)
|
||||||
async def _restart_bot(ctx: commands.Context):
|
async def _restart_bot(ctx: commands.Context):
|
||||||
"""Restarts the bot."""
|
"""Restarts the bot."""
|
||||||
await bot.qrm.session.close()
|
|
||||||
global exit_code
|
global exit_code
|
||||||
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
|
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
|
||||||
print(f"[**] Restarting! Requested by {ctx.author}.")
|
print(f"[**] Restarting! Requested by {ctx.author}.")
|
||||||
@@ -62,11 +71,10 @@ async def _restart_bot(ctx: commands.Context):
|
|||||||
await bot.logout()
|
await bot.logout()
|
||||||
|
|
||||||
|
|
||||||
@bot.command(name="shutdown", hidden=True)
|
@bot.command(name="shutdown", aliases=["shut"], category=cmn.cat.admin)
|
||||||
@commands.check(cmn.check_if_owner)
|
@commands.check(cmn.check_if_owner)
|
||||||
async def _shutdown_bot(ctx: commands.Context):
|
async def _shutdown_bot(ctx: commands.Context):
|
||||||
"""Shuts down the bot."""
|
"""Shuts down the bot."""
|
||||||
await bot.qrm.session.close()
|
|
||||||
global exit_code
|
global exit_code
|
||||||
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
|
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
|
||||||
print(f"[**] Shutting down! Requested by {ctx.author}.")
|
print(f"[**] Shutting down! Requested by {ctx.author}.")
|
||||||
@@ -74,7 +82,7 @@ async def _shutdown_bot(ctx: commands.Context):
|
|||||||
await bot.logout()
|
await bot.logout()
|
||||||
|
|
||||||
|
|
||||||
@bot.group(name="extctl", hidden=True)
|
@bot.group(name="extctl", aliases=["ex"], case_insensitive=True, category=cmn.cat.admin)
|
||||||
@commands.check(cmn.check_if_owner)
|
@commands.check(cmn.check_if_owner)
|
||||||
async def _extctl(ctx: commands.Context):
|
async def _extctl(ctx: commands.Context):
|
||||||
"""Extension control commands.
|
"""Extension control commands.
|
||||||
@@ -84,47 +92,38 @@ async def _extctl(ctx: commands.Context):
|
|||||||
await ctx.invoke(cmd)
|
await ctx.invoke(cmd)
|
||||||
|
|
||||||
|
|
||||||
@_extctl.command(name="list")
|
@_extctl.command(name="list", aliases=["ls"])
|
||||||
async def _extctl_list(ctx: commands.Context):
|
async def _extctl_list(ctx: commands.Context):
|
||||||
"""Lists Extensions."""
|
"""Lists loaded extensions."""
|
||||||
embed = cmn.embed_factory(ctx)
|
embed = cmn.embed_factory(ctx)
|
||||||
embed.title = "Loaded Extensions"
|
embed.title = "Loaded Extensions"
|
||||||
embed.description = "\n".join(["‣ " + x.split(".")[1] for x in bot.extensions.keys()])
|
embed.description = "\n".join(["‣ " + x.split(".")[1] for x in bot.extensions.keys()])
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
@_extctl.command(name="load")
|
@_extctl.command(name="load", aliases=["ld"])
|
||||||
async def _extctl_load(ctx: commands.Context, extension: str):
|
async def _extctl_load(ctx: commands.Context, extension: str):
|
||||||
try:
|
"""Loads an extension."""
|
||||||
bot.load_extension(ext_dir + "." + extension)
|
bot.load_extension(ext_dir + "." + extension)
|
||||||
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
|
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
|
||||||
except commands.ExtensionError as ex:
|
|
||||||
embed = cmn.error_embed_factory(ctx, ex, bot.qrm.debug_mode)
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
|
|
||||||
@_extctl.command(name="reload", aliases=["relaod"])
|
@_extctl.command(name="reload", aliases=["rl", "r", "relaod"])
|
||||||
async def _extctl_reload(ctx: commands.Context, extension: str):
|
async def _extctl_reload(ctx: commands.Context, extension: str):
|
||||||
|
"""Reloads an extension."""
|
||||||
if ctx.invoked_with == "relaod":
|
if ctx.invoked_with == "relaod":
|
||||||
pika = bot.get_emoji(opt.pika)
|
pika = bot.get_emoji(opt.pika)
|
||||||
if pika:
|
if pika:
|
||||||
await cmn.add_react(ctx.message, pika)
|
await cmn.add_react(ctx.message, pika)
|
||||||
try:
|
|
||||||
bot.reload_extension(ext_dir + "." + extension)
|
bot.reload_extension(ext_dir + "." + extension)
|
||||||
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
|
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
|
||||||
except commands.ExtensionError as ex:
|
|
||||||
embed = cmn.error_embed_factory(ctx, ex, bot.qrm.debug_mode)
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
|
|
||||||
@_extctl.command(name="unload")
|
@_extctl.command(name="unload", aliases=["ul"])
|
||||||
async def _extctl_unload(ctx: commands.Context, extension: str):
|
async def _extctl_unload(ctx: commands.Context, extension: str):
|
||||||
try:
|
"""Unloads an extension."""
|
||||||
bot.unload_extension(ext_dir + "." + extension)
|
bot.unload_extension(ext_dir + "." + extension)
|
||||||
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
|
await cmn.add_react(ctx.message, cmn.emojis.check_mark)
|
||||||
except commands.ExtensionError as ex:
|
|
||||||
embed = cmn.error_embed_factory(ctx, ex, bot.qrm.debug_mode)
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
|
|
||||||
# --- Events ---
|
# --- Events ---
|
||||||
@@ -156,7 +155,10 @@ async def on_command_error(ctx: commands.Context, err: commands.CommandError):
|
|||||||
if isinstance(err, commands.UserInputError):
|
if isinstance(err, commands.UserInputError):
|
||||||
await cmn.add_react(ctx.message, cmn.emojis.warning)
|
await cmn.add_react(ctx.message, cmn.emojis.warning)
|
||||||
await ctx.send_help(ctx.command)
|
await ctx.send_help(ctx.command)
|
||||||
elif isinstance(err, commands.CommandNotFound) and not ctx.invoked_with.startswith("?"):
|
elif isinstance(err, commands.CommandNotFound):
|
||||||
|
if ctx.invoked_with.startswith(("?", "!")):
|
||||||
|
return
|
||||||
|
else:
|
||||||
await cmn.add_react(ctx.message, cmn.emojis.question)
|
await cmn.add_react(ctx.message, cmn.emojis.question)
|
||||||
elif isinstance(err, commands.CheckFailure):
|
elif isinstance(err, commands.CheckFailure):
|
||||||
# Add handling of other subclasses of CheckFailure as needed.
|
# Add handling of other subclasses of CheckFailure as needed.
|
||||||
@@ -168,7 +170,7 @@ async def on_command_error(ctx: commands.Context, err: commands.CommandError):
|
|||||||
await cmn.add_react(ctx.message, cmn.emojis.bangbang)
|
await cmn.add_react(ctx.message, cmn.emojis.bangbang)
|
||||||
elif isinstance(err, (commands.CommandInvokeError, commands.ConversionError)):
|
elif isinstance(err, (commands.CommandInvokeError, commands.ConversionError)):
|
||||||
# Emulating discord.py's default beaviour.
|
# Emulating discord.py's default beaviour.
|
||||||
print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr)
|
print("Ignoring exception in command {}:".format(ctx.command), file=sys.stderr)
|
||||||
traceback.print_exception(type(err), err, err.__traceback__, file=sys.stderr)
|
traceback.print_exception(type(err), err, err.__traceback__, file=sys.stderr)
|
||||||
|
|
||||||
embed = cmn.error_embed_factory(ctx, err.original, bot.qrm.debug_mode)
|
embed = cmn.error_embed_factory(ctx, err.original, bot.qrm.debug_mode)
|
||||||
@@ -177,7 +179,7 @@ async def on_command_error(ctx: commands.Context, err: commands.CommandError):
|
|||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
else:
|
else:
|
||||||
# Emulating discord.py's default beaviour. (safest bet)
|
# Emulating discord.py's default beaviour. (safest bet)
|
||||||
print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr)
|
print("Ignoring exception in command {}:".format(ctx.command), file=sys.stderr)
|
||||||
traceback.print_exception(type(err), err, err.__traceback__, file=sys.stderr)
|
traceback.print_exception(type(err), err, err.__traceback__, file=sys.stderr)
|
||||||
await cmn.add_react(ctx.message, cmn.emojis.warning)
|
await cmn.add_react(ctx.message, cmn.emojis.warning)
|
||||||
|
|
||||||
@@ -222,7 +224,7 @@ async def _ensure_activity_fixed():
|
|||||||
# --- Run ---
|
# --- Run ---
|
||||||
|
|
||||||
for ext in opt.exts:
|
for ext in opt.exts:
|
||||||
bot.load_extension(ext_dir + '.' + ext)
|
bot.load_extension(ext_dir + "." + ext)
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -246,6 +248,7 @@ except ConnectionResetError as ex:
|
|||||||
raise
|
raise
|
||||||
raise SystemExit("ConnectionResetError: {}".format(ex))
|
raise SystemExit("ConnectionResetError: {}".format(ex))
|
||||||
|
|
||||||
|
|
||||||
# --- Exit ---
|
# --- Exit ---
|
||||||
# Codes for the wrapper shell script:
|
# Codes for the wrapper shell script:
|
||||||
# 0 - Clean exit, don't restart
|
# 0 - Clean exit, don't restart
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ ctyparser
|
|||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
lxml
|
lxml
|
||||||
pytz
|
pytz
|
||||||
|
ae7qparser
|
||||||
|
|||||||
+45
-45
@@ -1,55 +1,55 @@
|
|||||||
"""
|
"""
|
||||||
Information about callsigns for the vanity prefixes command in hamcog.
|
Information about callsigns for the vanity prefixes command in hamcog.
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrmbot and is released under the terms of the GNU
|
This file is part of discord-qrmbot and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
|
|
||||||
us_calls_title = "Valid US Vanity Callsigns"
|
us_calls_title = "Valid US Vanity Callsigns"
|
||||||
us_calls_desc = ('#x# is the number of letters in the prefix and suffix of a callsign. '
|
us_calls_desc = ("#x# is the number of letters in the prefix and suffix of a callsign. "
|
||||||
'E.g., WY4RC would be a 2x2 callsign, with prefix WY and suffix RC.')
|
"E.g., WY4RC would be a 2x2 callsign, with prefix WY and suffix RC.")
|
||||||
us_calls = OrderedDict([('**Group A** (Extra Only)', ('**Any:** K, N, W (1x2)\n'
|
us_calls = {
|
||||||
' AA-AL, KA-KZ, NA-NZ, WA-WZ (2x1)\n'
|
"**Group A** (Extra Only)": ("**Any:** K, N, W (1x2)\n"
|
||||||
' AA-AL (2x2)\n'
|
" AA-AL, KA-KZ, NA-NZ, WA-WZ (2x1)\n"
|
||||||
'*Except*\n'
|
" AA-AL (2x2)\n"
|
||||||
'**Alaska:** AL, KL, NL, WL (2x1)\n'
|
"*Except*\n"
|
||||||
'**Caribbean:** KP, NP, WP (2x1)\n'
|
"**Alaska:** AL, KL, NL, WL (2x1)\n"
|
||||||
'**Pacific:** AH, KH, NH, WH (2x1)')),
|
"**Caribbean:** KP, NP, WP (2x1)\n"
|
||||||
('**Group B** (Advanced and Extra Only)', ('**Any:** KA-KZ, NA-NZ, WA-WZ (2x2)\n'
|
"**Pacific:** AH, KH, NH, WH (2x1)"),
|
||||||
'*Except*\n'
|
"**Group B** (Advanced and Extra Only)": ("**Any:** KA-KZ, NA-NZ, WA-WZ (2x2)\n"
|
||||||
'**Alaska:** AL (2x2)\n'
|
"*Except*\n"
|
||||||
'**Caribbean:** KP (2x2)\n'
|
"**Alaska:** AL (2x2)\n"
|
||||||
'**Pacific:** AH (2x2)')),
|
"**Caribbean:** KP (2x2)\n"
|
||||||
('**Group C** (Technician, General, Advanced, Extra Only)', ('**Any Region:** K, N, W (1x3)\n'
|
"**Pacific:** AH (2x2)"),
|
||||||
'*Except*\n'
|
"**Group C** (Technician, General, Advanced, Extra Only)": ("**Any Region:** K, N, W (1x3)\n"
|
||||||
'**Alaska:** KL, NL, WL (2x2)\n'
|
"*Except*\n"
|
||||||
'**Caribbean:** NP, WP (2x2)\n'
|
"**Alaska:** KL, NL, WL (2x2)\n"
|
||||||
'**Pacific:** KH, NH, WH (2x2)')),
|
"**Caribbean:** NP, WP (2x2)\n"
|
||||||
('**Group D** (Any License Class)', ('**Any Region:** KA-KZ, WA-WZ (2x3)\n'
|
"**Pacific:** KH, NH, WH (2x2)"),
|
||||||
'*Except*\n'
|
"**Group D** (Any License Class)": ("**Any Region:** KA-KZ, WA-WZ (2x3)\n"
|
||||||
'**Alaska:** KL, WL (2x3)\n'
|
"*Except*\n"
|
||||||
'**Caribbean:** KP, WP (2x3)\n'
|
"**Alaska:** KL, WL (2x3)\n"
|
||||||
'**Pacific:** KH, WH (2x3)')),
|
"**Caribbean:** KP, WP (2x3)\n"
|
||||||
('**Unavailable**', ('- KA2AA-KA9ZZ: US Army in Japan\n'
|
"**Pacific:** KH, WH (2x3)"),
|
||||||
'- KC4AAA-KC4AAF: NSF in Antartica\n'
|
"**Unavailable**": ("- KA2AA-KA9ZZ: US Army in Japan\n"
|
||||||
'- KC4USA-KC4USZ: US Navy in Antartica\n'
|
"- KC4AAA-KC4AAF: NSF in Antartica\n"
|
||||||
'- KG4AA-KG4ZZ: US Navy in Guantanamo Bay\n'
|
"- KC4USA-KC4USZ: US Navy in Antartica\n"
|
||||||
'- KL9KAA-KL9KHZ: US military in Korea\n'
|
"- KG4AA-KG4ZZ: US Navy in Guantanamo Bay\n"
|
||||||
'- KC6AA-KC6ZZ: Former US (Eastern and Western Caroline Islands), '
|
"- KL9KAA-KL9KHZ: US military in Korea\n"
|
||||||
'now Federated States of Micronesia (V6) and Republic of Palau (T8)\n'
|
"- KC6AA-KC6ZZ: Former US (Eastern and Western Caroline Islands), "
|
||||||
'- KX6AA-KX6ZZ: Former US (Marshall Islands), '
|
"now Federated States of Micronesia (V6) and Republic of Palau (T8)\n"
|
||||||
'now Republic of the Marshall Islands (V73)\n'
|
"- KX6AA-KX6ZZ: Former US (Marshall Islands), "
|
||||||
'- Any suffix SOS or QRA-QUZ\n'
|
"now Republic of the Marshall Islands (V73)\n"
|
||||||
'- Any 2x3 with X as the first suffix letter\n'
|
"- Any suffix SOS or QRA-QUZ\n"
|
||||||
'- Any 2x3 with AF, KF, NF, or WF prefix and suffix EMA: FEMA\n'
|
"- Any 2x3 with X as the first suffix letter\n"
|
||||||
'- Any 2x3 with AA-AL, NA-NZ, WC, WK, WM, WR, or WT prefix: "Group X"\n'
|
"- Any 2x3 with AF, KF, NF, or WF prefix and suffix EMA: FEMA\n"
|
||||||
'- Any 2x1, 2x2, or 2x3 with KP, NP, WP prefix and 0, 6, 7, 8, 9 number\n'
|
"- Any 2x3 with AA-AL, NA-NZ, WC, WK, WM, WR, or WT prefix: \"Group X\"\n"
|
||||||
'- Any 1x1 callsign: Special Event'))])
|
"- Any 2x1, 2x2, or 2x3 with KP, NP, WP prefix and 0, 6, 7, 8, 9 number\n"
|
||||||
|
"- Any 1x1 callsign: Special Event")
|
||||||
|
}
|
||||||
|
|
||||||
# format: country: (title, description, text)
|
# format: country: (title, description, text)
|
||||||
options = {'us': (us_calls_title, us_calls_desc, us_calls)}
|
options = {"us": (us_calls_title, us_calls_desc, us_calls)}
|
||||||
|
|||||||
+130
-23
@@ -1,29 +1,136 @@
|
|||||||
"""
|
"""
|
||||||
A listing of morse code symbols
|
A listing of morse code symbols
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrmbot and is released under the terms of the GNU
|
This file is part of discord-qrmbot and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
morse = {"A": ".-", "B": "-...", "C": "-.-.", "D": "-..", "E": ".", "F": "..-.", "G": "--.",
|
|
||||||
"H": "....", "I": "..", "J": ".---", "K": "-.-", "L": ".-..", "M": "--", "N": "-.",
|
|
||||||
"O": "---", "P": ".--.", "Q": "--.-", "R": ".-.", "S": "...", "T": "-", "U": "..-",
|
|
||||||
"V": "...-", "W": ".--", "X": "-..-", "Y": "-.--", "Z": "--..", "1": ".----",
|
|
||||||
"2": "..---", "3": "...--", "4": "....-", "5": ".....", "6": "-....", "7": "--...",
|
|
||||||
"8": "---..", "9": "----.", "0": "-----", ".": ".-.-.-", ",": "--..--", "?": "..--..",
|
|
||||||
"'": ".----.", "!": "-.-.--", "/": "-..-.", "(": "-.--.", ")": "-.--.-", "&": ".-...",
|
|
||||||
":": "---...", ";": "-.-.-.", "=": "-...-", "+": ".-.-.", "-": "-....-", "\"": ".-..-.",
|
|
||||||
"@": ".--.-.", "Ä": ".-.-", "Å": ".-.-", "Ą": ".-.-", "Æ": ".-.-", "É": "..-..",
|
|
||||||
"Ñ": "--.--", "Ö": "---.", "Ü": "..--", "Š": "----", " ": "/"}
|
|
||||||
|
|
||||||
ascii = {".-": "A", "-...": "B", "-.-.": "C", "-..": "D", ".": "E", "..-.": "F", "--.": "G",
|
|
||||||
"....": "H", "..": "I", ".---": "J", "-.-": "K", ".-..": "L", "--": "M", "-.": "N",
|
morse = {
|
||||||
"---": "O", ".--.": "P", "--.-": "Q", ".-.": "R", "...": "S", "-": "T", "..-": "U",
|
"A": ".-",
|
||||||
"...-": "V", ".--": "W", "-..-": "X", "-.--": "Y", "--..": "Z", ".----": "1",
|
"B": "-...",
|
||||||
"..---": "2", "...--": "3", "....-": "4", ".....": "5", "-....": "6", "--...": "7",
|
"C": "-.-.",
|
||||||
"---..": "8", "----.": "9", "-----": "0", ".-.-.-": ".", "--..--": ",", "..--..": "?",
|
"D": "-..",
|
||||||
".----.": "'", "-.-.--": "!", "-..-.": "/", "-.--.": "(", "-.--.-": ")", ".-...": "&",
|
"E": ".",
|
||||||
"---...": ":", "-.-.-.": ";", "-...-": "=", ".-.-.": "+", "-....-": "-", ".-..-.": "\"",
|
"F": "..-.",
|
||||||
".--.-.": "@", ".-.-": "Ä", "..-..": "É", "--.--": "Ñ", "---.": "Ö", "..--": "Ü",
|
"G": "--.",
|
||||||
"----": "Š", "/": " "}
|
"H": "....",
|
||||||
|
"I": "..",
|
||||||
|
"J": ".---",
|
||||||
|
"K": "-.-",
|
||||||
|
"L": ".-..",
|
||||||
|
"M": "--",
|
||||||
|
"N": "-.",
|
||||||
|
"O": "---",
|
||||||
|
"P": ".--.",
|
||||||
|
"Q": "--.-",
|
||||||
|
"R": ".-.",
|
||||||
|
"S": "...",
|
||||||
|
"T": "-",
|
||||||
|
"U": "..-",
|
||||||
|
"V": "...-",
|
||||||
|
"W": ".--",
|
||||||
|
"X": "-..-",
|
||||||
|
"Y": "-.--",
|
||||||
|
"Z": "--..",
|
||||||
|
"1": ".----",
|
||||||
|
"2": "..---",
|
||||||
|
"3": "...--",
|
||||||
|
"4": "....-",
|
||||||
|
"5": ".....",
|
||||||
|
"6": "-....",
|
||||||
|
"7": "--...",
|
||||||
|
"8": "---..",
|
||||||
|
"9": "----.",
|
||||||
|
"0": "-----",
|
||||||
|
".": ".-.-.-",
|
||||||
|
",": "--..--",
|
||||||
|
"?": "..--..",
|
||||||
|
"'": ".----.",
|
||||||
|
"!": "-.-.--",
|
||||||
|
"/": "-..-.",
|
||||||
|
"(": "-.--.",
|
||||||
|
")": "-.--.-",
|
||||||
|
"&": ".-...",
|
||||||
|
":": "---...",
|
||||||
|
";": "-.-.-.",
|
||||||
|
"=": "-...-",
|
||||||
|
"+": ".-.-.",
|
||||||
|
"-": "-....-",
|
||||||
|
"\"": ".-..-.",
|
||||||
|
"@": ".--.-.",
|
||||||
|
"Ä": ".-.-",
|
||||||
|
"Å": ".-.-",
|
||||||
|
"Ą": ".-.-",
|
||||||
|
"Æ": ".-.-",
|
||||||
|
"É": "..-..",
|
||||||
|
"Ñ": "--.--",
|
||||||
|
"Ö": "---.",
|
||||||
|
"Ü": "..--",
|
||||||
|
"Š": "----",
|
||||||
|
" ": "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
ascii = {
|
||||||
|
".-": "A",
|
||||||
|
"-...": "B",
|
||||||
|
"-.-.": "C",
|
||||||
|
"-..": "D",
|
||||||
|
".": "E",
|
||||||
|
"..-.": "F",
|
||||||
|
"--.": "G",
|
||||||
|
"....": "H",
|
||||||
|
"..": "I",
|
||||||
|
".---": "J",
|
||||||
|
"-.-": "K",
|
||||||
|
".-..": "L",
|
||||||
|
"--": "M",
|
||||||
|
"-.": "N",
|
||||||
|
"---": "O",
|
||||||
|
".--.": "P",
|
||||||
|
"--.-": "Q",
|
||||||
|
".-.": "R",
|
||||||
|
"...": "S",
|
||||||
|
"-": "T",
|
||||||
|
"..-": "U",
|
||||||
|
"...-": "V",
|
||||||
|
".--": "W",
|
||||||
|
"-..-": "X",
|
||||||
|
"-.--": "Y",
|
||||||
|
"--..": "Z",
|
||||||
|
".----": "1",
|
||||||
|
"..---": "2",
|
||||||
|
"...--": "3",
|
||||||
|
"....-": "4",
|
||||||
|
".....": "5",
|
||||||
|
"-....": "6",
|
||||||
|
"--...": "7",
|
||||||
|
"---..": "8",
|
||||||
|
"----.": "9",
|
||||||
|
"-----": "0",
|
||||||
|
".-.-.-": ".",
|
||||||
|
"--..--": ",",
|
||||||
|
"..--..": "?",
|
||||||
|
".----.": "'",
|
||||||
|
"-.-.--": "!",
|
||||||
|
"-..-.": "/",
|
||||||
|
"-.--.": "(",
|
||||||
|
"-.--.-": ")",
|
||||||
|
".-...": "&",
|
||||||
|
"---...": ":",
|
||||||
|
"-.-.-.": ";",
|
||||||
|
"-...-": "=",
|
||||||
|
".-.-.": "+",
|
||||||
|
"-....-": "-",
|
||||||
|
".-..-.": "\"",
|
||||||
|
".--.-.": "@",
|
||||||
|
".-.-": "Ä",
|
||||||
|
"..-..": "É",
|
||||||
|
"--.--": "Ñ",
|
||||||
|
"---.": "Ö",
|
||||||
|
"..--": "Ü",
|
||||||
|
"----": "Š",
|
||||||
|
"/": " "
|
||||||
|
}
|
||||||
|
|||||||
+72
-8
@@ -1,14 +1,78 @@
|
|||||||
"""
|
"""
|
||||||
A listing of NATO Phonetics
|
A listing of NATO Phonetics
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrmbot and is released under the terms of the GNU
|
This file is part of discord-qrmbot and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
phonetics = {'a': 'alfa', 'b': 'bravo', 'c': 'charlie', 'd': 'delta', 'e': 'echo', 'f': 'foxtrot',
|
|
||||||
'g': 'golf', 'h': 'hotel', 'i': 'india', 'j': 'juliett', 'k': 'kilo', 'l': 'lima',
|
phonetics = {
|
||||||
'm': 'mike', 'n': 'november', 'o': 'oscar', 'p': 'papa', 'q': 'quebec', 'r': 'romeo',
|
"a": "alfa",
|
||||||
's': 'sierra', 't': 'tango', 'u': 'uniform', 'v': 'victor', 'w': 'whiskey', 'x': 'x-ray',
|
"b": "bravo",
|
||||||
'y': 'yankee', 'z': 'zulu'}
|
"c": "charlie",
|
||||||
|
"d": "delta",
|
||||||
|
"e": "echo",
|
||||||
|
"f": "foxtrot",
|
||||||
|
"g": "golf",
|
||||||
|
"h": "hotel",
|
||||||
|
"i": "india",
|
||||||
|
"j": "juliett",
|
||||||
|
"k": "kilo",
|
||||||
|
"l": "lima",
|
||||||
|
"m": "mike",
|
||||||
|
"n": "november",
|
||||||
|
"o": "oscar",
|
||||||
|
"p": "papa",
|
||||||
|
"q": "quebec",
|
||||||
|
"r": "romeo",
|
||||||
|
"s": "sierra",
|
||||||
|
"t": "tango",
|
||||||
|
"u": "uniform",
|
||||||
|
"v": "victor",
|
||||||
|
"w": "whiskey",
|
||||||
|
"x": "x-ray",
|
||||||
|
"y": "yankee",
|
||||||
|
"z": "zulu"
|
||||||
|
}
|
||||||
|
|
||||||
|
pweights = {
|
||||||
|
"A": 2,
|
||||||
|
"B": 2,
|
||||||
|
"C": 2,
|
||||||
|
"D": 2,
|
||||||
|
"E": 2,
|
||||||
|
"F": 2,
|
||||||
|
"G": 1,
|
||||||
|
"H": 2,
|
||||||
|
"I": 3,
|
||||||
|
"J": 3,
|
||||||
|
"K": 2,
|
||||||
|
"L": 2,
|
||||||
|
"M": 1,
|
||||||
|
"N": 3,
|
||||||
|
"O": 2,
|
||||||
|
"P": 2,
|
||||||
|
"Q": 2,
|
||||||
|
"R": 3,
|
||||||
|
"S": 3,
|
||||||
|
"T": 2,
|
||||||
|
"U": 3,
|
||||||
|
"V": 2,
|
||||||
|
"W": 2,
|
||||||
|
"X": 2,
|
||||||
|
"Y": 2,
|
||||||
|
"Z": 2,
|
||||||
|
"0": 2,
|
||||||
|
"1": 1,
|
||||||
|
"2": 1,
|
||||||
|
"3": 1,
|
||||||
|
"4": 1,
|
||||||
|
"5": 1,
|
||||||
|
"6": 1,
|
||||||
|
"7": 2,
|
||||||
|
"8": 1,
|
||||||
|
"9": 2,
|
||||||
|
"/": 1,
|
||||||
|
}
|
||||||
|
|||||||
+11
-5
@@ -1,13 +1,18 @@
|
|||||||
"""
|
"""
|
||||||
A listing of Q Codes
|
A listing of Q Codes
|
||||||
---
|
---
|
||||||
Copyright (C) 2019 Abigail Gold, 0x5c
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
This file is part of discord-qrmbot and is released under the terms of the GNU
|
This file is part of discord-qrmbot and is released under the terms of
|
||||||
General Public License, version 2.
|
the GNU General Public License, version 2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
qcodes = {"QAB": "May I have clearance (for ...) from ... (place) to ... (place) at flight level/altitude ... ? / You are cleared (or ... is cleared) by ... from ... (place) to ... (place) at flight level/altitude ...",
|
|
||||||
|
|
||||||
|
qcodes = {
|
||||||
|
"QAB": "May I have clearance (for ...) from ... (place) to ... (place) at flight level/altitude ... ? / You are cleared (or ... is cleared) by ... from ... (place) to ... (place) at flight level/altitude ...",
|
||||||
"QAF": "Will you advise me when you are (were) at (over) ... (place)? / I am (was) at (over) ... (place) (at ... hours) at flight level/altitude ...",
|
"QAF": "Will you advise me when you are (were) at (over) ... (place)? / I am (was) at (over) ... (place) (at ... hours) at flight level/altitude ...",
|
||||||
"QAG": "Arrange your flight in order to arrive over ... (place) at ... hours.",
|
"QAG": "Arrange your flight in order to arrive over ... (place) at ... hours.",
|
||||||
"QAH": "What is your height above ... (datum)? / I am at .... flight level/altitude ... --or-- Arrange your flight so as to reach flight level/altitude ... at ... (hours or place).",
|
"QAH": "What is your height above ... (datum)? / I am at .... flight level/altitude ... --or-- Arrange your flight so as to reach flight level/altitude ... at ... (hours or place).",
|
||||||
@@ -277,4 +282,5 @@ qcodes = {"QAB": "May I have clearance (for ...) from ... (place) to ... (place)
|
|||||||
"QUX": "Do you have any navigational warnings or gale warnings in force? / I have the following navigational warning(s) or gale warning(s) in force: ...",
|
"QUX": "Do you have any navigational warnings or gale warnings in force? / I have the following navigational warning(s) or gale warning(s) in force: ...",
|
||||||
"QUY": "Is position of survival craft marked? / Position of survival craft was marked at ... hours by flame or smoke float/sea marker/sea marker dye/... (specify other marking).",
|
"QUY": "Is position of survival craft marked? / Position of survival craft was marked at ... hours by flame or smoke float/sea marker/sea marker dye/... (specify other marking).",
|
||||||
"QUZ": "May I resume restricted working? / Distress phase still in force; restricted working may be resumed.",
|
"QUZ": "May I resume restricted working? / Distress phase still in force; restricted working may be resumed.",
|
||||||
"QZZ": "Daily key change about to take place (German WWII usage)."}
|
"QZZ": "Daily key change about to take place (German WWII usage)."
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
"""
|
||||||
|
A listing of hamstudy command resources
|
||||||
|
---
|
||||||
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
|
This file is part of discord-qrmbot and is released under the terms of
|
||||||
|
the GNU General Public License, version 2.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
pool_names = {
|
||||||
|
"us": {
|
||||||
|
"technician": "E2",
|
||||||
|
"tech": "E2",
|
||||||
|
"t": "E2",
|
||||||
|
"general": "E3",
|
||||||
|
"gen": "E3",
|
||||||
|
"g": "E3",
|
||||||
|
"extra": "E4",
|
||||||
|
"e": "E4",
|
||||||
|
},
|
||||||
|
"ca": {
|
||||||
|
"basic": "CA_B",
|
||||||
|
"b": "CA_B",
|
||||||
|
"advanced": "CA_A",
|
||||||
|
"adv": "CA_A",
|
||||||
|
"a": "CA_A",
|
||||||
|
"basic_fr": "CA_FB",
|
||||||
|
"b_fr": "CA_FB",
|
||||||
|
"base": "CA_FB",
|
||||||
|
"advanced_fr": "CA_FS",
|
||||||
|
"adv_fr": "CA_FS",
|
||||||
|
"a_fr": "CA_FS",
|
||||||
|
"supérieure": "CA_FS",
|
||||||
|
"superieure": "CA_FS",
|
||||||
|
"s": "CA_FS",
|
||||||
|
},
|
||||||
|
"us_c": {
|
||||||
|
"c1": "C1",
|
||||||
|
"comm1": "C1",
|
||||||
|
"c3": "C3",
|
||||||
|
"comm3": "C3",
|
||||||
|
"c6": "C6",
|
||||||
|
"comm6": "C6",
|
||||||
|
"c7": "C7",
|
||||||
|
"comm7": "C7",
|
||||||
|
"c7r": "C7R",
|
||||||
|
"comm7r": "C7R",
|
||||||
|
"c8": "C8",
|
||||||
|
"comm8": "C8",
|
||||||
|
"c9": "C9",
|
||||||
|
"comm9": "C9",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pool_emojis = {
|
||||||
|
"us": "🇺🇸",
|
||||||
|
"ca": "🇨🇦",
|
||||||
|
"us_c": "🇺🇸 🏢",
|
||||||
|
}
|
||||||
+119
-44225
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@ debug = False
|
|||||||
owners_uids = (200102491231092736,)
|
owners_uids = (200102491231092736,)
|
||||||
|
|
||||||
# The extensions to load when running the bot.
|
# The extensions to load when running the bot.
|
||||||
exts = ['ae7q', 'base', 'fun', 'grid', 'ham', 'image', 'lookup', 'morse', 'qrz', 'study', 'weather']
|
exts = ["ae7q", "base", "fun", "grid", "ham", "image", "lookup", "morse", "qrz", "study", "weather"]
|
||||||
|
|
||||||
# Either "time", "random", or "fixed" (first item in statuses)
|
# Either "time", "random", or "fixed" (first item in statuses)
|
||||||
status_mode = "fixed"
|
status_mode = "fixed"
|
||||||
@@ -37,17 +37,17 @@ statuses = ["with lids on the air", "with fire"]
|
|||||||
|
|
||||||
# Timezone for the status (string)
|
# Timezone for the status (string)
|
||||||
# See https://pythonhosted.org/pytz/ for more info
|
# See https://pythonhosted.org/pytz/ for more info
|
||||||
status_tz = 'US/Eastern'
|
status_tz = "US/Eastern"
|
||||||
|
|
||||||
# The text to put in the "playing" status, with start and stop times
|
# The text to put in the "playing" status, with start and stop times
|
||||||
time_statuses = [('with lids on 3.840', (00, 00), (6, 00)),
|
time_statuses = [("with lids on 3.840", (00, 00), (6, 00)),
|
||||||
('with lids on 7.200', (6, 00), (10, 00)),
|
("with lids on 7.200", (6, 00), (10, 00)),
|
||||||
('with lids on 14.313', (10, 00), (18, 00)),
|
("with lids on 14.313", (10, 00), (18, 00)),
|
||||||
('with lids on 7.200', (18, 00), (20, 00)),
|
("with lids on 7.200", (18, 00), (20, 00)),
|
||||||
('with lids on 3.840', (20, 00), (23, 59))]
|
("with lids on 3.840", (20, 00), (23, 59))]
|
||||||
|
|
||||||
# Emoji IDs and keywords for emoji reactions
|
# Emoji IDs and keywords for emoji reactions
|
||||||
# Use the format {emoji_id (int): ('tuple', 'of', 'lowercase', 'keywords')}
|
# Use the format {emoji_id (int): ("tuple", "of", "lowercase", "keywords")}
|
||||||
msg_reacts = {}
|
msg_reacts = {}
|
||||||
|
|
||||||
# A :pika: emote's ID, None for no emote :c
|
# A :pika: emote's ID, None for no emote :c
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Various utilities for the bot.
|
||||||
|
"""
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
Wrapper to handle aiohttp connector creation.
|
||||||
|
---
|
||||||
|
Copyright (C) 2020 Abigail Gold, 0x5c
|
||||||
|
|
||||||
|
This file is part of qrm2 and is released under the terms of
|
||||||
|
the GNU General Public License, version 2.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
|
||||||
|
async def new_connector(*args, **kwargs) -> aiohttp.TCPConnector:
|
||||||
|
"""*Yes, it's just a coro to instantiate a class.*"""
|
||||||
|
return aiohttp.TCPConnector(*args, **kwargs)
|
||||||
Reference in New Issue
Block a user