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 | ||||
|  | ||||