Metadata update!
- Added flag emojis to commands with countries. - Added image attribution and description to "image" commands. - Added file metadata to image resources. - Added new key in options.py: pika. - Added classes to deal with file metadata. - Added common paths to common.py. - Changed directory/file names of resources. - Documented the new metadata format. Fixes #83 - Metadata storage Fixes #86 - Country flags
@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- Flag emojis to commands with countries.
|
||||
- Image attribution and description to "image" commands.
|
||||
- New key in options.py: pika.
|
||||
|
||||
|
||||
## [v2.0.0] - 2019-12-16
|
||||
### Added
|
||||
@ -31,7 +36,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- Cleaned up code to comply with the PEP8 Standard
|
||||
- Issue in morse and unmorse commands where spaces were not interpreted correctly
|
||||
|
||||
|
||||
## v1.0.0 - 2019-07-31 [YANKED]
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/classabbyamp/discord-qrm2/compare/v2.0.0...HEAD
|
||||
[v2.0.0]: https://github.com/classabbyamp/discord-qrm2/releases/tag/v2.0.0
|
||||
|
49
common.py
@ -13,17 +13,18 @@ General Public License, version 2.
|
||||
"""
|
||||
|
||||
|
||||
import collections
|
||||
import json
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from types import SimpleNamespace
|
||||
|
||||
import discord
|
||||
import discord.ext.commands as commands
|
||||
|
||||
|
||||
import data.options as opt
|
||||
|
||||
|
||||
__all__ = ["colours", "cat", "emojis", "error_embed_factory", "add_react", "check_if_owner"]
|
||||
|
||||
|
||||
@ -43,6 +44,50 @@ cat = SimpleNamespace(lookup='Information Lookup',
|
||||
emojis = SimpleNamespace(good='✅',
|
||||
bad='❌')
|
||||
|
||||
paths = SimpleNamespace(data=Path("./data/"),
|
||||
resources=Path("./resources/"),
|
||||
bandcharts=Path("./resources/img/bandcharts/"),
|
||||
maps=Path("./resources/img/maps/"))
|
||||
|
||||
|
||||
# --- Classes ---
|
||||
|
||||
class ImageMetadata:
|
||||
"""Represents the metadata of a single image."""
|
||||
def __init__(self, metadata: list):
|
||||
self.filename: str = metadata[0]
|
||||
self.name: str = metadata[1]
|
||||
self.long_name: str = metadata[2]
|
||||
self.description: str = metadata[3]
|
||||
self.source: str = metadata[4]
|
||||
self.emoji: str = metadata[5]
|
||||
|
||||
|
||||
class ImagesGroup(collections.abc.Mapping):
|
||||
"""Represents a group of images, loaded from a meta.json file."""
|
||||
def __init__(self, file_path):
|
||||
self._images = {}
|
||||
self.path = file_path
|
||||
|
||||
with open(file_path, "r") as file:
|
||||
images: dict = json.load(file)
|
||||
for key, imgdata in images.items():
|
||||
self._images[key] = ImageMetadata(imgdata)
|
||||
|
||||
# Wrappers to implement dict-like functionality
|
||||
def __len__(self):
|
||||
return len(self._images)
|
||||
|
||||
def __getitem__(self, key: str):
|
||||
return self._images[key]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._images)
|
||||
|
||||
# str(): Simply return what it would be for the underlaying dict
|
||||
def __str__(self):
|
||||
return str(self._images)
|
||||
|
||||
|
||||
# --- Helper functions ---
|
||||
|
||||
|
24
dev-notes/metadata-format.md
Normal file
@ -0,0 +1,24 @@
|
||||
# File metadata format
|
||||
|
||||
Used for grouping info such as name, description, source, and such.
|
||||
|
||||
|
||||
## Format
|
||||
|
||||
*`id` is the dictionary key, and the list items are the attributes for each files.*
|
||||
|
||||
`meta.json`
|
||||
```json
|
||||
{
|
||||
"id": ["filename", "name", "long_name", "description", "source", "emoji"]
|
||||
}
|
||||
```
|
||||
|
||||
| Attribute | Description | Examples |
|
||||
| ------------- | ----------------------------------------------- | ------------------------------------------------ |
|
||||
| `filename` | The file name of the file, without the path. | `ca.png`, `itu.png` |
|
||||
| `name` | The name of the file. | `Canada`, `ITU Zones` |
|
||||
| `long_name` | The long name (title) of the file. | `Worldwide map of ITU Zones` |
|
||||
| `description` | The description accompanying the file. | `Full radio allocations chart for all services.` |
|
||||
| `source` | The source of the file. | `Instituto Federal de Telecomunicaciones (IFT)` |
|
||||
| `emoji` | A Unicode emoji associated with the file. | `📻`, `🇨🇦` |
|
@ -9,46 +9,78 @@ General Public License, version 2.
|
||||
|
||||
import io
|
||||
|
||||
import aiohttp
|
||||
|
||||
import discord
|
||||
import discord.ext.commands as commands
|
||||
|
||||
import aiohttp
|
||||
|
||||
import common as cmn
|
||||
|
||||
|
||||
class ImageCog(commands.Cog):
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
self.bandcharts = cmn.ImagesGroup(cmn.paths.bandcharts / "meta.json")
|
||||
self.maps = cmn.ImagesGroup(cmn.paths.maps / "meta.json")
|
||||
|
||||
@commands.command(name="bandplan", aliases=['plan', 'bands'], category=cmn.cat.ref)
|
||||
async def _bandplan(self, ctx: commands.Context, region: str = ''):
|
||||
'''Posts an image of Frequency Allocations.'''
|
||||
name = {'cn': 'Chinese',
|
||||
'ca': 'Canadian',
|
||||
'nl': 'Dutch',
|
||||
'us': 'US',
|
||||
'mx': 'Mexican'}
|
||||
arg = region.lower()
|
||||
|
||||
with ctx.typing():
|
||||
embed = cmn.embed_factory(ctx)
|
||||
if arg not in name:
|
||||
if arg not in self.bandcharts:
|
||||
desc = 'Possible arguments are:\n'
|
||||
for abbrev, title in name.items():
|
||||
desc += f'`{abbrev}`: {title}\n'
|
||||
for key, img in self.bandcharts.items():
|
||||
desc += f'`{key}`: {img.name}{(" " + img.emoji if img.emoji else "")}\n'
|
||||
embed.title = f'Bandplan Not Found!'
|
||||
embed.description = desc
|
||||
embed.colour = cmn.colours.bad
|
||||
await ctx.send(embed=embed)
|
||||
else:
|
||||
img = discord.File(f"resources/images/bandchart/{arg}bandchart.png",
|
||||
filename=f'{arg}bandchart.png')
|
||||
embed.title = f'{name[arg]} Amateur Radio Bands'
|
||||
metadata: cmn.ImageMetadata = self.bandcharts[arg]
|
||||
img = discord.File(cmn.paths.bandcharts / metadata.filename,
|
||||
filename=metadata.filename)
|
||||
if metadata.description:
|
||||
embed.description = metadata.description
|
||||
if metadata.source:
|
||||
embed.add_field(name="Source", value=metadata.source)
|
||||
embed.title = metadata.long_name + (" " + metadata.emoji if metadata.emoji else "")
|
||||
embed.colour = cmn.colours.good
|
||||
embed.set_image(url=f'attachment://{arg}bandchart.png')
|
||||
embed.set_image(url='attachment://' + metadata.filename)
|
||||
await ctx.send(embed=embed, file=img)
|
||||
|
||||
@commands.command(name="map", category=cmn.cat.maps)
|
||||
async def _map(self, ctx: commands.Context, map_id: str = ''):
|
||||
'''Posts an image of a ham-relevant map.'''
|
||||
arg = map_id.lower()
|
||||
|
||||
with ctx.typing():
|
||||
embed = cmn.embed_factory(ctx)
|
||||
if arg not in self.maps:
|
||||
desc = 'Possible arguments are:\n'
|
||||
for key, img in self.maps.items():
|
||||
desc += f'`{key}`: {img.name}{(" " + img.emoji if img.emoji else "")}\n'
|
||||
embed.title = 'Map Not Found!'
|
||||
embed.description = desc
|
||||
embed.colour = cmn.colours.bad
|
||||
await ctx.send(embed=embed)
|
||||
else:
|
||||
metadata: cmn.ImageMetadata = self.maps[arg]
|
||||
img = discord.File(cmn.paths.maps / metadata.filename,
|
||||
filename=metadata.filename)
|
||||
if metadata.description:
|
||||
embed.description = metadata.description
|
||||
if metadata.source:
|
||||
embed.add_field(name="Source", value=metadata.source)
|
||||
embed.title = metadata.long_name + (" " + metadata.emoji if metadata.emoji else "")
|
||||
embed.colour = cmn.colours.good
|
||||
embed.set_image(url='attachment://' + metadata.filename)
|
||||
await ctx.send(embed=embed, file=img)
|
||||
|
||||
|
||||
|
||||
@commands.command(name="grayline", aliases=['greyline', 'grey', 'gray', 'gl'], category=cmn.cat.maps)
|
||||
async def _grayline(self, ctx: commands.Context):
|
||||
'''Posts a map of the current greyline, where HF propagation is the best.'''
|
||||
@ -68,35 +100,6 @@ class ImageCog(commands.Cog):
|
||||
embed.set_image(url=f'attachment://greyline.jpg')
|
||||
await ctx.send(embed=embed, file=discord.File(data, 'greyline.jpg'))
|
||||
|
||||
@commands.command(name="map", category=cmn.cat.maps)
|
||||
async def _map(self, ctx: commands.Context, map_id: str = ''):
|
||||
'''Posts an image of a ham-relevant map.'''
|
||||
map_titles = {"cq": 'Worldwide CQ Zones Map',
|
||||
"itu": 'Worldwide ITU Zones Map',
|
||||
"arrl": 'ARRL/RAC Section Map',
|
||||
"rac": 'ARRL/RAC Section Map',
|
||||
"cn": 'Chinese Callsign Areas',
|
||||
"us": 'US Callsign Areas'}
|
||||
|
||||
arg = map_id.lower()
|
||||
with ctx.typing():
|
||||
embed = cmn.embed_factory(ctx)
|
||||
if arg not in map_titles:
|
||||
desc = 'Possible arguments are:\n'
|
||||
for abbrev, title in map_titles.items():
|
||||
desc += f'`{abbrev}`: {title}\n'
|
||||
embed.title = 'Map Not Found!'
|
||||
embed.description = desc
|
||||
embed.colour = cmn.colours.bad
|
||||
await ctx.send(embed=embed)
|
||||
else:
|
||||
img = discord.File(f"resources/images/map/{arg}map.png",
|
||||
filename=f'{arg}map.png')
|
||||
embed.title = f'{map_titles[arg]}'
|
||||
embed.colour = cmn.colours.good
|
||||
embed.set_image(url=f'attachment://{arg}map.png')
|
||||
await ctx.send(embed=embed, file=img)
|
||||
|
||||
|
||||
def setup(bot: commands.Bot):
|
||||
bot.add_cog(ImageCog(bot))
|
||||
|
6
main.py
@ -87,8 +87,12 @@ async def _extctl_load(ctx: commands.Context, extension: str):
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
@_extctl.command(name="reload")
|
||||
@_extctl.command(name="reload", aliases=["relaod"])
|
||||
async def _extctl_reload(ctx: commands.Context, extension: str):
|
||||
if ctx.invoked_with == "relaod":
|
||||
pika = bot.get_emoji(opt.pika)
|
||||
if pika:
|
||||
await cmn.add_react(ctx.message, pika)
|
||||
try:
|
||||
bot.reload_extension(ext_dir + "." + extension)
|
||||
await cmn.add_react(ctx.message, cmn.emojis.good)
|
||||
|
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 153 KiB |
7
resources/img/bandcharts/meta.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"ca": ["ca.png", "Canada", "Amateur radio bands in Canada", "**This bandplan is incomplete**; some bands, like 630m, are simply not present. It also does not cover any band above 30MHz.", "[RAC 0-30MHz Band Plan](https://www.rac.ca/wp-content/uploads/files/pdf/RAC%20Bandplan%20December%201%202015.pdf)", "🇨🇦"],
|
||||
"cn": ["cn.png", "China", "Amateur radio bands in China", "", "Created by KN8U and NY7H", "🇨🇳"],
|
||||
"mx": ["mx.png", "Mexico", "Radio allocations in Mexico", "Full radio allocations chart for all services. No information specific to amateur radio is shown.", "Secretaría de Comunicaciones y Transportes (SCT) / Instituto Federal de Telecomunicaciones (IFT)", "🇲🇽"],
|
||||
"nl": ["nl.png", "Netherlands", "Amateur radio bands in the Netherlands", "", "", "🇳🇱"],
|
||||
"us": ["us.png", "USA", "Amateur radio bands in the USA", "", "*[ARRL Frequency Chart](https://www.arrl.org/shop/ARRL-Frequency-Chart-11-17/)* [[PDF]](http://www.arrl.org/files/file/Regulatory/Band%20Chart/Band%20Chart%20-%2011X17%20Color.pdf)", "🇺🇸"]
|
||||
}
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 504 KiB After Width: | Height: | Size: 504 KiB |
6
resources/img/maps/meta.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"arrl": ["arrl-rac.png", "ARRL Sections", "ARRL Sections", "", "", "🇺🇸"],
|
||||
"rac": ["arrl-rac.png", "RAC Sections", "RAC Sections", "", "", "🇨🇦"],
|
||||
"cn": ["cn.png", "China's Prefixes", "Map of prefix regions in China", "", "CRAC", "🇨🇳"],
|
||||
"us": ["us.png", "USA's Prefixes", "Map of prefix regions in the USA", "", "*[ARRL WAS Map](https://www.arrl.org/was-forms)* [[PDF]](http://www.arrl.org/files/file/Awards%20Application%20Forms/WASmap_Color.pdf)", "🇺🇸"]
|
||||
}
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 136 KiB |
@ -31,3 +31,6 @@ exts = ['ae7q', 'base', 'fun', 'grid', 'ham', 'image', 'lookup', 'morse', 'qrz',
|
||||
|
||||
# The text to put in the "playing" status.
|
||||
game = 'with lids on 7.200'
|
||||
|
||||
# A :pika: emote's ID, None for no emote :c
|
||||
pika = 658733876176355338
|
||||
|