async def _grid_sq_lookup(self, ctx: commands.Context, lat: str, lon: str): '''Calculates the grid square for latitude and longitude coordinates, with negative being latitude South and longitude West.''' with ctx.typing(): grid = "**" try: latf = float(lat) + 90 lonf = float(lon) + 180 if 0 <= latf <= 180 and 0 <= lonf <= 360: grid += chr(ord('A') + int(lonf / 20)) grid += chr(ord('A') + int(latf / 10)) grid += chr(ord('0') + int((lonf % 20) / 2)) 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((latf - (int(latf / 1) * 1)) / (2.5 / 60))) grid += "**" embed = cmn.embed_factory(ctx) embed.title = f'Maidenhead Grid Locator for {float(lat):.6f}, {float(lon):.6f}' embed.description = grid embed.colour = cmn.colours.good else: raise ValueError('Out of range.') except ValueError as err: embed = cmn.embed_factory(ctx) embed.title = f'Error generating grid square for {lat}, {lon}.' embed.description = str(err) embed.colour = cmn.colours.bad await ctx.send(embed=embed)
async def _grid_sq_lookup(self, ctx: commands.Context, lat: str, lon: str): ("""Calculates the grid square for latitude and longitude coordinates, """ """with negative being latitude South and longitude West.""") grid = "**" latf = float(lat) + 90 lonf = float(lon) + 180 if 0 <= latf <= 180 and 0 <= lonf <= 360: grid += chr(ord("A") + int(lonf / 20)) grid += chr(ord("A") + int(latf / 10)) grid += chr(ord("0") + int((lonf % 20) / 2)) 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((latf - (int(latf / 1) * 1)) / (2.5 / 60))) grid += "**" embed = cmn.embed_factory(ctx) embed.title = f"Maidenhead Grid Locator for {float(lat):.6f}, {float(lon):.6f}" embed.description = grid embed.colour = cmn.colours.good else: embed = cmn.embed_factory(ctx) embed.title = f"Error generating grid square for {lat}, {lon}." embed.description = """Coordinates out of range. The valid ranges are: - Latitude: `-90` to `+90` - Longitude: `-180` to `+180`""" embed.colour = cmn.colours.bad await ctx.send(embed=embed)
async def _qrz_lookup(self, ctx: commands.Context, callsign: str, *flags): """Looks up a callsign on [QRZ.com](https://www.qrz.com/). Add `--link` to only link the QRZ page.""" flags = [f.lower() for f in flags] if keys.qrz_user == "" or keys.qrz_pass == "" or "--link" in flags: await ctx.send(f"http://qrz.com/db/{callsign}") return async with ctx.typing(): try: await qrz_test_session(self.key, self.session) except ConnectionError: await self.get_session() 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._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"}) resp_data = {el.tag.split("}")[1]: el.text for el in resp_xml_data[0].getiterator()} embed = cmn.embed_factory(ctx) embed.title = f"QRZ Data for {resp_data['call']}" embed.colour = cmn.colours.good 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 _info(self, ctx: commands.Context): """Shows info about qrm.""" embed = cmn.embed_factory(ctx) embed.title = "About qrm" embed.description = info.description embed.add_field(name="Authors", value=", ".join(info.authors)) embed.add_field(name="License", value=info.license) embed.add_field( name="Version", value= f"v{info.release} {'(`' + self.commit + '`)' if self.commit else ''}" ) 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="Donate", value="\n".join( f"{k}: {v}" for k, v in self.donation_links.items()), inline=False) if opt.enable_invite_cmd and (await self.bot.application_info()).bot_public: embed.add_field(name="Invite qrm to Your Server", value=self.bot_invite, inline=False) embed.set_thumbnail(url=str(self.bot.user.avatar_url)) await ctx.send(embed=embed)
async def _bandplan(self, ctx: commands.Context, region: str = ""): """Gets the frequency allocations chart for a given country.""" async with ctx.typing(): arg = region.lower() embed = cmn.embed_factory(ctx) if arg not in self.bandcharts: desc = "Possible arguments are:\n" for key, img in self.bandcharts.items(): desc += f"`{key}`: {img.name}{(' ' + img.emoji if img.emoji else '')}\n" embed.title = "Bandplan Not Found!" embed.description = desc embed.colour = cmn.colours.bad await ctx.send(embed=embed) return 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="attachment://" + metadata.filename) await ctx.send(embed=embed, file=img)
async def _weather_conditions_now(self, ctx: commands.Context, *, location: str): """Gets current local weather conditions from [wttr.in](http://wttr.in/). See help of the `weather` command for possible location types and options.""" try: units_arg = re.search(self.wttr_units_regex, location).group(1) except AttributeError: units_arg = "" if units_arg.lower() == "f": units = "u" elif units_arg.lower() == "c": units = "m" else: units = "" loc = self.wttr_units_regex.sub("", location).strip() embed = cmn.embed_factory(ctx) embed.title = f"Current Weather for {loc}" embed.description = "Data from [wttr.in](http://wttr.in/)." embed.colour = cmn.colours.good loc = loc.replace(" ", "+") embed.set_image(url=f"http://wttr.in/{loc}_0{units}pnFQ.png") await ctx.send(embed=embed)
async def _issue(self, ctx: commands.Context): """Shows how to create a bug report or feature request about the bot.""" embed = cmn.embed_factory(ctx) embed.title = "Found a bug? Have a feature request?" embed.description = ("Submit an issue on the [issue tracker]" "(https://github.com/miaowware/qrm2/issues)!") await ctx.send(embed=embed)
async def _changelog(self, ctx: commands.Context, version: str = 'latest'): """Show what has changed in a bot version.""" embed = cmn.embed_factory(ctx) embed.title = "qrm Changelog" embed.description = ( "For a full listing, visit [Github](https://" "github.com/classabbyamp/discord-qrm2/blob/master/CHANGELOG.md).") changelog = self.changelog vers = list(changelog.keys()) vers.remove("Unreleased") version = version.lower() if version == 'latest': version = info.release 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: embed.description += f'\n\n**v{version}**' embed = await format_changelog(log, embed) await ctx.send(embed=embed)
async def _band_conditions(self, ctx: commands.Context): """Gets a solar conditions report.""" embed = cmn.embed_factory(ctx) embed.title = "Current Solar Conditions" embed.colour = cmn.colours.good embed.set_image(url="http://www.hamqsl.com/solarsun.php") await ctx.send(embed=embed)
async def send_bot_help(self, mapping): embed = cmn.embed_factory(self.context) embed.title = "qrm Help" embed.description = ( f"For command-specific help and usage, use `{self.context.prefix}help [command name]`." " Many commands have shorter aliases.") if isinstance(self.context.bot.command_prefix, list): embed.description += ( " All of the following prefixes work with the bot: `" + "`, `".join(self.context.bot.command_prefix) + "`.") mapping = await mapping for cat, cmds in mapping.items(): if cmds == []: continue names = sorted([cmd.name for cmd in cmds]) if cat is not None: embed.add_field(name=cat.title(), value=", ".join(names), inline=False) else: embed.add_field(name="Other", value=", ".join(names), inline=False) await self.context.send(embed=embed)
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/). See help for weather command for possible location types. Add a `-c` or `-f` to use Celcius or Fahrenheit.''' with ctx.typing(): try: units_arg = re.search(self.wttr_units_regex, location).group(1) except AttributeError: units_arg = '' if units_arg.lower() == 'f': units = 'u' elif units_arg.lower() == 'c': units = 'm' else: units = '' loc = self.wttr_units_regex.sub('', location).strip() embed = cmn.embed_factory(ctx) embed.title = f'Weather Forecast for {loc}' embed.description = 'Data from [wttr.in](http://wttr.in/).' embed.colour = cmn.colours.good loc = loc.replace(' ', '+') async with self.session.get( f'http://wttr.in/{loc}_{units}pnFQ.png') as resp: if resp.status != 200: embed.description = 'Could not download file...' embed.colour = cmn.colours.bad else: data = io.BytesIO(await resp.read()) embed.set_image(url=f'attachment://wttr_forecast.png') await ctx.send(embed=embed, file=discord.File(data, 'wttr_forecast.png'))
async def _grayline(self, ctx: commands.Context): """Gets a map of the current greyline, where HF propagation is the best.""" embed = cmn.embed_factory(ctx) embed.title = "Current Greyline Conditions" embed.colour = cmn.colours.good embed.set_image(url=self.gl_url) await ctx.send(embed=embed)
async def _map(self, ctx: commands.Context, map_id: str = ""): """Posts a ham-relevant map.""" async with ctx.typing(): arg = map_id.lower() 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) return 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)
async def _extctl_list(ctx: commands.Context): """Lists loaded extensions.""" embed = cmn.embed_factory(ctx) embed.title = "Loaded Extensions" embed.description = "\n".join( ["‣ " + x.split(".")[1] for x in bot.extensions.keys()]) await ctx.send(embed=embed)
async def _dxcc_lookup(self, ctx: commands.Context, query: str): """Gets DXCC info about a callsign prefix.""" query = query.upper() full_query = query embed = cmn.embed_factory(ctx) embed.title = "DXCC Info for " embed.description = f"*Last Updated: {self.cty.formatted_version}*" embed.colour = cmn.colours.bad while query: if query in self.cty.keys(): data = self.cty[query] embed.add_field(name="Entity", value=data["entity"]) embed.add_field(name="CQ Zone", value=data["cq"]) embed.add_field(name="ITU Zone", value=data["itu"]) embed.add_field(name="Continent", value=data["continent"]) embed.add_field(name="Time Zone", value=f"+{data['tz']}" if data["tz"] > 0 else str(data["tz"])) embed.title += query embed.colour = cmn.colours.good break else: query = query[:-1] else: embed.title += full_query + " not found" embed.colour = cmn.colours.bad await ctx.send(embed=embed)
async def _dxcc_lookup(self, ctx: commands.Context, query: str): '''Gets info about a DXCC prefix.''' with ctx.typing(): query = query.upper() full_query = query embed = cmn.embed_factory(ctx) embed.title = f'DXCC Info for ' embed.description = f'*Last Updated: {self.cty.formatted_version}*' embed.colour = cmn.colours.bad while query: if query in self.cty.keys(): data = self.cty[query] embed.add_field(name="Entity", value=data['entity']) embed.add_field(name="CQ Zone", value=data['cq']) embed.add_field(name="ITU Zone", value=data['itu']) embed.add_field(name="Continent", value=data['continent']) embed.add_field(name="Time Zone", value=f'+{data["tz"]}' if data['tz'] > 0 else str(data['tz'])) embed.title += query embed.colour = cmn.colours.good break else: query = query[:-1] else: embed.title += full_query + ' not found' embed.colour = cmn.colours.bad await ctx.send(embed=embed)
async def _bandplan(self, ctx: commands.Context, region: str = ''): '''Posts an image of Frequency Allocations.''' arg = region.lower() with ctx.typing(): embed = cmn.embed_factory(ctx) if arg not in self.bandcharts: desc = 'Possible arguments are:\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: 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='attachment://' + metadata.filename) await ctx.send(embed=embed, file=img)
async def _contests(self, ctx: commands.Context): embed = cmn.embed_factory(ctx) embed.title = "Contest Calendar" embed.description = ("*We are currently rewriting the old, Chrome-based `contests` command. In the meantime, " "use [the website](https://www.contestcalendar.com/weeklycont.php).*") embed.colour = cmn.colours.good await ctx.send(embed=embed)
async def _weather_conditions_forecast(self, ctx: commands.Context, *, location: str): """Gets local weather forecast for the next three days from [wttr.in](http://wttr.in/). See help of the `weather` command for possible location types and options.""" async with ctx.typing(): try: units_arg = re.search(self.wttr_units_regex, location).group(1) except AttributeError: units_arg = "" if units_arg.lower() == "f": units = "u" elif units_arg.lower() == "c": units = "m" else: units = "" loc = self.wttr_units_regex.sub("", location).strip() embed = cmn.embed_factory(ctx) embed.title = f"Weather Forecast for {loc}" embed.description = "Data from [wttr.in](http://wttr.in/)." embed.colour = cmn.colours.good loc = loc.replace(" ", "+") async with self.session.get( f"http://wttr.in/{loc}_{units}pnFQ.png") as resp: if resp.status != 200: raise cmn.BotHTTPError(resp) data = io.BytesIO(await resp.read()) embed.set_image(url="attachment://wttr_forecast.png") await ctx.send(embed=embed, file=discord.File(data, "wttr_forecast.png"))
async def _changelog(self, ctx: commands.Context, version: str = "latest"): """Shows what has changed in a bot version. Defaults to the latest version.""" embed = cmn.embed_factory(ctx) embed.title = "qrm Changelog" embed.description = ( "For a full listing, visit [Github](https://" "github.com/miaowware/qrm2/blob/master/CHANGELOG.md).") changelog = self.changelog vers = list(changelog.keys()) vers.remove("Unreleased") version = version.lower() if version == "latest": version = info.release 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: embed.description += f"\n\n**v{version}**" embed = await format_changelog(log, embed) await ctx.send(embed=embed)
async def _worksplit(self, ctx: commands.Context): """Posts "Work split you lids".""" fn = "worksplit.jpg" embed = cmn.embed_factory(ctx) embed.title = "Work Split, You Lids!" embed.set_image(url="attachment://" + fn) img = discord.File(cmn.paths.img / fn, filename=fn) await ctx.send(embed=embed, file=img)
async def _issue(self, ctx: commands.Context): """Shows how to create an issue for the bot.""" embed = cmn.embed_factory(ctx) embed.title = "Found a bug? Have a feature request?" embed.description = ( "Submit an issue on the [issue tracker]" "(https://github.com/classabbyamp/discord-qrm2/issues)!") await ctx.send(embed=embed)
async def _invite(self, ctx: commands.Context): """Generates a link to invite the bot to a server.""" if not (await self.bot.application_info()).bot_public: raise commands.DisabledCommand embed = cmn.embed_factory(ctx) embed.title = "Invite qrm to Your Server!" embed.description = self.bot_invite await ctx.send(embed=embed)
async def _grayline(self, ctx: commands.Context): """Gets a map of the current greyline, where HF propagation is the best.""" embed = cmn.embed_factory(ctx) embed.title = "Current Greyline Conditions" embed.colour = cmn.colours.good date_params = f"&date=1&utc={datetime.utcnow():%Y-%m-%d+%H:%M:%S}" embed.set_image(url=self.gl_baseurl + date_params) await ctx.send(embed=embed)
async def tex(self, ctx: commands.Context, *, expr: str): """Renders a LaTeX expression. In paragraph mode by default. To render math, add `$` around math expressions. """ payload = { "format": "png", "code": self.template.replace("#CONTENT#", expr), "quality": 50 } with ctx.typing(): # ask rTeX to render our expression async with self.session.post(urljoin(opt.rtex_instance, "api/v2"), json=payload) as r: if r.status != 200: raise cmn.BotHTTPError(r) render_result = await r.json() if render_result["status"] != "success": embed = cmn.embed_factory(ctx) embed.title = "LaTeX Rendering Failed!" embed.description = ( "Here are some common reasons:\n" "• Did you forget to use math mode? Surround math expressions with `$`," " like `$x^3$`.\n" "• Are you using a command from a package? It might not be available.\n" "• Are you including the document headers? We already did that for you." ) embed.colour = cmn.colours.bad await ctx.send(embed=embed) return # if rendering went well, download the file given in the response async with self.session.get( urljoin(opt.rtex_instance, "api/v2/" + render_result["filename"])) as r: png_buffer = BytesIO(await r.read()) embed = cmn.embed_factory(ctx) embed.title = "LaTeX Expression" embed.description = "Rendered by [rTeX](https://rtex.probablyaweb.site/)." embed.set_image(url="attachment://tex.png") await ctx.send(file=discord.File(png_buffer, "tex.png"), embed=embed)
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.title = await self.get_command_signature(group) embed.description = group.help for cmd in await self.filter_commands(group.commands, sort=True): embed.add_field(name=await self.get_command_signature(cmd), value=cmd.help, inline=False) await self.context.send(embed=embed)
async def _utc_lookup(self, ctx: commands.Context): """Returns the current time in UTC.""" now = datetime.utcnow() result = "**" + now.strftime("%Y-%m-%d %H:%M") + "Z**" embed = cmn.embed_factory(ctx) embed.title = "The current time is:" embed.description = result embed.colour = cmn.colours.good await ctx.send(embed=embed)
async def send_group_help(self, group): embed = cmn.embed_factory(self.context) embed.title = self.get_command_signature(group) embed.description = group.help for cmd in group.commands: embed.add_field(name=self.get_command_signature(cmd), value=cmd.help, inline=False) await self.context.send(embed=embed)
async def _grayline(self, ctx: commands.Context): """Gets a map of the current greyline, where HF propagation is the best.""" embed = cmn.embed_factory(ctx) embed.title = "Current Greyline Conditions" embed.colour = cmn.colours.good # Generate a nonce to force discord to recache this cachenonce = (ctx.message.id >> 22) // 1000 // 600 # nonce will stay the same for ~10min embed.set_image(url=self.gl_url + f"&cachenonce={cachenonce}") await ctx.send(embed=embed)
async def _issue(self, ctx: commands.Context): """Shows how to create a bug report or feature request about the bot.""" embed = cmn.embed_factory(ctx) embed.title = "Found a bug? Have a feature request?" embed.description = """Submit an issue on the [issue tracker](https://github.com/miaowware/qrm2/issues)! All issues and requests related to resources (including maps, band charts, data) \ should be added in \ [miaowware/qrm-resources](https://github.com/miaowware/qrm-resources/issues).""" await ctx.send(embed=embed)