23 Commits

Author SHA1 Message Date
Abigail Gold 0af82ac241 pin dependency versions (#239) 2020-07-23 00:05:32 -04:00
0x5c 2ba6249b90 Merge pull request #223 from miaowware/action-update
Update linting action
2020-04-19 21:25:08 -04:00
0x5c 2c11dad358 Update linting action
Should now run on PR
2020-04-19 21:10:34 -04:00
Abigail 5f796d479e bump version to 2.3.1 2020-04-02 23:04:29 -04:00
Abigail Gold 3ba55d4c35 update funetics words list (#218)
Fixes #217
2020-04-02 23:02:19 -04:00
Abigail f4ed93dc76 bump version to 2.3.0 2020-03-30 19:00:38 -04:00
Abigail Gold 2cb4b03532 add phonetic weight command (#215)
Fixes #170

Co-authored-by: 0x5c <dev@0x5c.io>
2020-03-30 18:56:29 -04:00
Abigail Gold bc93462c29 convert all OrderedDicts to dictionaries (#214)
Fixes #184

Co-authored-by: 0x5c <dev@0x5c.io>
2020-03-30 18:54:33 -04:00
Abigail Gold 6867c45c8c add ?standards command for xkcd 927 (#213)
Fixes #187
2020-03-30 18:24:50 -04:00
Abigail dcbb7acab8 bump release to 2.2.3 2020-03-29 12:34:58 -04:00
Abigail Gold 3803ce6045 add git commit hash to file "git_commit" for automatically-built docker images (#211) 2020-03-29 12:34:12 -04:00
Abigail Gold 8ca4911072 make commands case-insensitive (#210)
Fixes #209
2020-03-28 19:07:23 -04:00
Abigail Gold 6e2468f04f create github action for docker image creation and publication (#206) 2020-03-28 18:18:29 -04:00
0x5c b17a8a1749 Merge pull request #207 from miaowware/bumpversion
Bump version + changelog (2.2.2)
2020-02-25 21:44:35 -05:00
0x5c 8dfa7001ef Bump version + changelog (2.2.2)
- CL: fixed missing 2.2.1 link
2020-02-25 21:41:36 -05:00
0x5c f6ed8430b9 Merge pull request #205 from miaowware/fix-typing
Added/fixed/removed typing managers
2020-02-25 20:57:07 -05:00
0x5c d650cbd6c1 Removed lint 2020-02-25 20:44:53 -05:00
0x5c 4d9f9d1b19 Added/fixed/removed typing managers
Fixes #167
2020-02-25 20:36:21 -05:00
0x5c 1c649aacc2 Merge pull request #204 from miaowware/fix-studyimg
Fix HamStudy image error
2020-02-25 19:51:03 -05:00
0x5c 2049ca9fca Fix HamStudy image error
- Fixed the image link
- Removed unused import (pytz)
- Added test pool file to dev-notes

Fixes #203
2020-02-25 19:45:32 -05:00
Abigail 38416d9050 whoopsie 2020-02-20 20:02:45 -05:00
Abigail G 8dcdc22fe4 Merge pull request #202 from miaowware/hamstudy-extra-fix
fix offset-naive and offset-aware datetime conflict
2020-02-20 20:02:07 -05:00
Abigail c57056e586 fix offset-naive and offset-aware datetime conflict
Fixes #201
2020-02-20 01:32:17 -05:00
22 changed files with 622 additions and 44529 deletions
+58
View File
@@ -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
+1 -1
View File
@@ -1,6 +1,6 @@
name: Linting name: Linting
on: [push] on: [push,pull_request]
jobs: jobs:
flake8_py3: flake8_py3:
+2
View File
@@ -9,6 +9,8 @@ cty.zip
/docker-compose.yml /docker-compose.yml
.vscode/
######################################################### #########################################################
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
+40 -1
View File
@@ -7,6 +7,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
## [2.3.2] - 2020-07-22
### Fixed
- Dependency issues
## [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 ## [2.2.0] - 2020-02-15
### Added ### Added
- Added Trustee field to qrz command for club callsigns. - Added Trustee field to qrz command for club callsigns.
@@ -76,7 +109,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## 1.0.0 - 2019-07-31 [YANKED] ## 1.0.0 - 2019-07-31 [YANKED]
[Unreleased]: https://github.com/miaowware/qrm2/compare/v2.2.0...HEAD [Unreleased]: https://github.com/miaowware/qrm2/compare/v2.3.2...HEAD
[2.3.2]: https://github.com/miaowware/qrm2/releases/tag/v2.3.2
[2.3.1]: https://github.com/miaowware/qrm2/releases/tag/v2.3.1
[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.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.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 [2.0.0]: https://github.com/miaowware/qrm2/releases/tag/v2.0.0
+2
View File
@@ -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).
``` ```
+48
View File
@@ -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"
}
+1 -1
View File
@@ -29,7 +29,7 @@ class AE7QCog(commands.Cog):
self.bot = bot self.bot = bot
self.session = aiohttp.ClientSession(connector=bot.qrm.connector) 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):
"""Looks 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:
+1 -2
View File
@@ -10,7 +10,6 @@ the GNU General Public License, version 2.
import random import random
import re import re
from collections import OrderedDict
from typing import Union from typing import Union
import discord import discord
@@ -189,7 +188,7 @@ class BaseCog(commands.Cog):
def parse_changelog(): def parse_changelog():
changelog = OrderedDict() changelog = {}
ver = "" ver = ""
heading = "" heading = ""
+16 -12
View File
@@ -31,6 +31,11 @@ class FunCog(commands.Cog):
"""Returns xkcd: 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"""
@@ -39,18 +44,17 @@ class FunCog(commands.Cog):
@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):
"""Generates fun/wacky 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)
+75 -77
View File
@@ -23,93 +23,91 @@ class GridCog(commands.Cog):
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 = "**" 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: 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 = """Coordinates out of range.
embed.description = """Coordinates out of range. The valid ranges are:
The valid ranges are: - Latitude: `-90` to `+90`
- Latitude: `-90` to `+90` - Longitude: `-180` to `+180`"""
- 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:
radius = 6371 radius = 6371
try: try:
grid = grid.upper() grid = grid.upper()
grid2 = grid2.upper() grid2 = grid2.upper()
loc = get_coords(grid) loc = get_coords(grid)
loc2 = get_coords(grid2) loc2 = get_coords(grid2)
# Haversine formula # Haversine formula
d_lat = math.radians(loc2[0] - loc[0]) d_lat = math.radians(loc2[0] - loc[0])
d_lon = math.radians(loc2[1] - loc[1]) d_lon = math.radians(loc2[1] - loc[1])
a = (math.sin(d_lat/2) ** 2 a = (math.sin(d_lat/2) ** 2
+ math.cos(math.radians(loc[0])) + math.cos(math.radians(loc[0]))
* math.cos(math.radians(loc2[0])) * math.cos(math.radians(loc2[0]))
* math.sin(d_lon/2) ** 2) * math.sin(d_lon/2) ** 2)
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
d = radius * c d = radius * c
d_mi = 0.6213712 * d d_mi = 0.6213712 * d
# Bearing # Bearing
y_dist = math.sin(math.radians(loc2[1]-loc[1])) * math.cos(math.radians(loc2[0])) y_dist = math.sin(math.radians(loc2[1]-loc[1])) * math.cos(math.radians(loc2[0]))
x_dist = (math.cos(math.radians(loc[0])) x_dist = (math.cos(math.radians(loc[0]))
* math.sin(math.radians(loc2[0])) * math.sin(math.radians(loc2[0]))
- math.sin(math.radians(loc[0])) - math.sin(math.radians(loc[0]))
* math.cos(math.radians(loc2[0])) * math.cos(math.radians(loc2[0]))
* math.cos(math.radians(loc2[1] - loc[1]))) * math.cos(math.radians(loc2[1] - loc[1])))
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)
+46 -29
View File
@@ -25,45 +25,42 @@ class HamCog(commands.Cog):
@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):
"""Looks up the meaning of 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: embed.title = qcode
embed.title = qcode 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):
"""Returns NATO 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):
"""Returns 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"], category=cmn.cat.ref) @commands.command(name="prefixes", aliases=["vanity", "pfx", "vanities", "prefix"], category=cmn.cat.ref)
@@ -95,6 +92,26 @@ class HamCog(commands.Cog):
embed.colour = cmn.colours.good 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))
+22 -23
View File
@@ -40,30 +40,29 @@ class LookupCog(commands.Cog):
@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 DXCC info about a callsign 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 = "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:
query = query[:-1]
else: else:
embed.title += full_query + " not found" query = query[:-1]
embed.colour = cmn.colours.bad else:
embed.title += full_query + " not found"
embed.colour = cmn.colours.bad
await ctx.send(embed=embed) await ctx.send(embed=embed)
@tasks.loop(hours=24) @tasks.loop(hours=24)
+41 -44
View File
@@ -21,61 +21,58 @@ class MorseCog(commands.Cog):
@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 message"
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)
+58 -61
View File
@@ -8,7 +8,6 @@ the GNU General Public License, version 2.
""" """
from collections import OrderedDict
from io import BytesIO from io import BytesIO
import aiohttp import aiohttp
@@ -36,50 +35,51 @@ class QRZCog(commands.Cog):
await ctx.send(f"http://qrz.com/db/{callsign}") await ctx.send(f"http://qrz.com/db/{callsign}")
return return
try: async with ctx.typing():
await qrz_test_session(self.key, self.session) try:
except ConnectionError: await qrz_test_session(self.key, self.session)
await self.get_session() except ConnectionError:
url = f"http://xmldata.qrz.com/xml/current/?s={self.key};callsign={callsign}"
async with self.session.get(url) as resp:
if resp.status != 200:
raise ConnectionError(f"Unable to connect to QRZ (HTTP Error {resp.status})")
with BytesIO(await resp.read()) as resp_file:
resp_xml = etree.parse(resp_file).getroot()
resp_xml_session = resp_xml.xpath("/x:QRZDatabase/x:Session", namespaces={"x": "http://xmldata.qrz.com"})
resp_session = {el.tag.split("}")[1]: el.text for el in resp_xml_session[0].getiterator()}
if "Error" in resp_session:
if "Session Timeout" in resp_session["Error"]:
await self.get_session() await self.get_session()
await self._qrz_lookup(ctx, callsign)
return
if "Not found" in resp_session["Error"]:
embed = cmn.embed_factory(ctx)
embed.title = f"QRZ Data for {callsign.upper()}"
embed.colour = cmn.colours.bad
embed.description = "No data found!"
await ctx.send(embed=embed)
return
raise ValueError(resp_session["Error"])
resp_xml_data = resp_xml.xpath("/x:QRZDatabase/x:Callsign", namespaces={"x": "http://xmldata.qrz.com"}) url = f"http://xmldata.qrz.com/xml/current/?s={self.key};callsign={callsign}"
resp_data = {el.tag.split("}")[1]: el.text for el in resp_xml_data[0].getiterator()} async with self.session.get(url) as resp:
if resp.status != 200:
raise ConnectionError(f"Unable to connect to QRZ (HTTP Error {resp.status})")
with BytesIO(await resp.read()) as resp_file:
resp_xml = etree.parse(resp_file).getroot()
embed = cmn.embed_factory(ctx) resp_xml_session = resp_xml.xpath("/x:QRZDatabase/x:Session", namespaces={"x": "http://xmldata.qrz.com"})
embed.title = f"QRZ Data for {resp_data['call']}" resp_session = {el.tag.split("}")[1]: el.text for el in resp_xml_session[0].getiterator()}
embed.colour = cmn.colours.good if "Error" in resp_session:
embed.url = f"http://www.qrz.com/db/{resp_data['call']}" if "Session Timeout" in resp_session["Error"]:
if "image" in resp_data: await self.get_session()
embed.set_thumbnail(url=resp_data["image"]) await self._qrz_lookup(ctx, callsign)
return
if "Not found" in resp_session["Error"]:
embed = cmn.embed_factory(ctx)
embed.title = f"QRZ Data for {callsign.upper()}"
embed.colour = cmn.colours.bad
embed.description = "No data found!"
await ctx.send(embed=embed)
return
raise ValueError(resp_session["Error"])
data = qrz_process_info(resp_data) resp_xml_data = resp_xml.xpath("/x:QRZDatabase/x:Callsign", namespaces={"x": "http://xmldata.qrz.com"})
resp_data = {el.tag.split("}")[1]: el.text for el in resp_xml_data[0].getiterator()}
for title, val in data.items(): embed = cmn.embed_factory(ctx)
if val is not None: embed.title = f"QRZ Data for {resp_data['call']}"
embed.add_field(name=title, value=val, inline=True) embed.colour = cmn.colours.good
await ctx.send(embed=embed) embed.url = f"http://www.qrz.com/db/{resp_data['call']}"
if "image" in resp_data:
embed.set_thumbnail(url=resp_data["image"])
data = qrz_process_info(resp_data)
for title, val in data.items():
if val is not None:
embed.add_field(name=title, value=val, inline=True)
await ctx.send(embed=embed)
async def get_session(self): async def get_session(self):
"""Session creation and caching.""" """Session creation and caching."""
@@ -158,27 +158,24 @@ def qrz_process_info(data: dict):
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),
("Trustee", data.get("trustee", None)), "Trustee": data.get("trustee", None),
("eQSL?", eqsl), "eQSL?": eqsl,
("Paper QSL?", mqsl), "Paper QSL?": mqsl,
("LotW?", lotw), "LotW?": lotw,
("QSL Info", data.get("qslmgr", None)), "QSL Info": data.get("qslmgr", None),
("CQ Zone", data.get("cqzone", None)), "Born": data.get("born", None)}
("ITU Zone", data.get("ituzone", None)),
("IOTA Designator", data.get("iota", None)),
("Born", data.get("born", None))])
def setup(bot): def setup(bot):
+3 -3
View File
@@ -88,8 +88,8 @@ class StudyCog(commands.Cog):
else: else:
# look at valid_from and expires dates to find the correct one # look at valid_from and expires dates to find the correct one
for p in pool_matches: for p in pool_matches:
valid_from = datetime.fromisoformat(pools[p]["valid_from"][:-1] + "+00:00") valid_from = datetime.fromisoformat(pools[p]["valid_from"][:-1])
expires = datetime.fromisoformat(pools[p]["expires"][:-1] + "+00:00") expires = datetime.fromisoformat(pools[p]["expires"][:-1])
if valid_from < datetime.utcnow() < expires: if valid_from < datetime.utcnow() < expires:
pool = p pool = p
@@ -133,7 +133,7 @@ class StudyCog(commands.Cog):
" the answer will be revealed."), " the answer will be revealed."),
inline=False) inline=False)
if "image" in question: if "image" in question:
image_url = f"https://hamstudy.org/_1330011/images/{pool.split('_',1)[1]}/{question['image']}" image_url = f"https://hamstudy.org/images/{pool_meta['year']}/{question['image']}"
embed.set_image(url=image_url) embed.set_image(url=image_url)
q_msg = await ctx.send(embed=embed) q_msg = await ctx.send(embed=embed)
+1 -1
View File
@@ -40,7 +40,7 @@ class WeatherCog(commands.Cog):
embed.set_image(url="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):
"""Gets local weather conditions from [wttr.in](http://wttr.in/). """Gets local weather conditions from [wttr.in](http://wttr.in/).
+1 -1
View File
@@ -12,5 +12,5 @@ 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/miaowware/qrm2" contributing = "Check out the source on GitHub, contributions welcome: https://github.com/miaowware/qrm2"
release = "2.2.0" release = "2.3.2"
bot_server = "https://discord.gg/Ntbg3J4" bot_server = "https://discord.gg/Ntbg3J4"
+3 -3
View File
@@ -45,8 +45,8 @@ loop = asyncio.get_event_loop()
connector = loop.run_until_complete(conn.new_connector()) 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, loop=loop,
connector=connector) connector=connector)
@@ -82,7 +82,7 @@ async def _shutdown_bot(ctx: commands.Context):
await bot.logout() await bot.logout()
@bot.group(name="extctl", aliases=["ex"], category=cmn.cat.admin) @bot.group(name="extctl", aliases=["ex"], case_insensitive=True, category=cmn.cat.admin)
@commands.check(cmn.check_if_owner) @commands.check(cmn.check_if_owner)
async def _extctl(ctx: commands.Context): async def _extctl(ctx: commands.Context):
"""Extension control commands. """Extension control commands.
+5 -5
View File
@@ -1,5 +1,5 @@
discord.py discord.py==1.3.4
ctyparser ctyparser==2.0.0.post1
beautifulsoup4 beautifulsoup4==4.9.1
lxml lxml==4.5.2
pytz pytz==2020.1
+39 -40
View File
@@ -8,49 +8,48 @@ 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)}
+40
View File
@@ -36,3 +36,43 @@ phonetics = {
"y": "yankee", "y": "yankee",
"z": "zulu" "z": "zulu"
} }
pweights = {
"A": 2,
"B": 2,
"C": 2,
"D": 2,
"E": 2,
"F": 2,
"G": 1,
"H": 2,
"I": 3,
"J": 3,
"K": 2,
"L": 2,
"M": 1,
"N": 3,
"O": 2,
"P": 2,
"Q": 2,
"R": 3,
"S": 3,
"T": 2,
"U": 3,
"V": 2,
"W": 2,
"X": 2,
"Y": 2,
"Z": 2,
"0": 2,
"1": 1,
"2": 1,
"3": 1,
"4": 1,
"5": 1,
"6": 1,
"7": 2,
"8": 1,
"9": 2,
"/": 1,
}
+119 -44225
View File
File diff suppressed because it is too large Load Diff