Merge branch 'master' into lookupcog

This commit is contained in:
Abigail Gold 2019-12-10 21:34:57 -05:00
commit 25ea169bf1
No known key found for this signature in database
GPG Key ID: CF88335E873C3FB4
22 changed files with 207 additions and 75 deletions

29
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,29 @@
---
name: Bug report
about: Report a bug to help us improve qrm
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Run command '...' with input '...'
2. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**System (include if related to running the bot):**
- OS: [e.g. Linux, Docker]
- Version: [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for qrm
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Rich lookup for QRZ, if a QRZ subscription is present - Rich lookup for QRZ, if a QRZ subscription is present
- Timestamp and requester username and avatar are now shown on embeds - Timestamp and requester username and avatar are now shown on embeds
- Current and 3-Day Forecast terrestrial weather conditions lookup commands - Current and 3-Day Forecast terrestrial weather conditions lookup commands
- Extension control commands.
- Changelog command - Changelog command
### Changed ### Changed
- Rewrote code to take advantage of discord.py's cogs - Rewrote code to take advantage of discord.py's cogs

View File

@ -4,8 +4,8 @@ A sample `docker-compose.yml` file:
version: '3' version: '3'
services: services:
bot: bot:
image: "classabbyamp/discord-qrm-bot:latest" image: "classabbyamp/discord-qrm2:latest"
container_name: "qrmbot" container_name: "discord-qrm2"
volumes: volumes:
- "./data:/app/data:rw" - "./data:/app/data:rw"
``` ```

View File

@ -1,4 +1,4 @@
# Discord QRM Bot # qrm, a Bot for Discord
A discord bot with ham radio functionalities. A discord bot with ham radio functionalities.

View File

@ -3,7 +3,7 @@ Common tools for the bot.
--- ---
Copyright (C) 2019 Abigail Gold, 0x5c Copyright (C) 2019 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of the GNU
General Public License, version 2. General Public License, version 2.
--- ---
@ -12,8 +12,22 @@ General Public License, version 2.
`cat`: Category names for the HelpCommand. `cat`: Category names for the HelpCommand.
""" """
import traceback
from datetime import datetime
from types import SimpleNamespace 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"]
# --- Common values ---
colours = SimpleNamespace(good=0x43B581, colours = SimpleNamespace(good=0x43B581,
neutral=0x7289DA, neutral=0x7289DA,
@ -25,3 +39,39 @@ cat = SimpleNamespace(lookup='Information Lookup',
ref='Reference', ref='Reference',
study='Exam Study', study='Exam Study',
weather='Land and Space Weather') weather='Land and Space Weather')
emojis = SimpleNamespace(good='',
bad='')
# --- Helper functions ---
def error_embed_factory(ctx: commands.Context, exception: Exception, debug_mode: bool) -> discord.Embed:
"""Creates an Error embed."""
if debug_mode:
fmtd_ex = traceback.format_exception(exception.__class__, exception, exception.__traceback__)
else:
fmtd_ex = traceback.format_exception_only(exception.__class__, exception)
embed = discord.Embed(title="Error",
timestamp=datetime.utcnow(),
colour=colours.bad)
embed.set_footer(text=ctx.author,
icon_url=str(ctx.author.avatar_url))
embed.description = "```\n" + '\n'.join(fmtd_ex) + "```"
return embed
async def add_react(msg: discord.Message, react: str):
try:
await msg.add_reaction(react)
except discord.Forbidden:
print(f"[!!] Missing permissions to add reaction in '{msg.guild.id}/{msg.channel.id}'!")
# --- Checks ---
async def check_if_owner(ctx: commands.Context):
if ctx.author.id in opt.owners_uids:
return True
await add_react(ctx.message, emojis.bad)
return False

View File

@ -2,6 +2,6 @@ version: '3'
services: services:
bot: bot:
build: . build: .
container_name: "qrmbot" container_name: "discord-qrm2"
volumes: volumes:
- "./data:/app/data:rw" - "./data:/app/data:rw"

View File

@ -1,9 +1,9 @@
""" """
ae7q cog for qrm ae7q extension for qrm
--- ---
Copyright (C) 2019 Abigail Gold, 0x5c Copyright (C) 2019 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of the GNU
General Public License, version 2. General Public License, version 2.
--- ---
Test callsigns: Test callsigns:

View File

@ -1,9 +1,9 @@
""" """
Base cog for qrm Base extension for qrm
--- ---
Copyright (C) 2019 Abigail Gold, 0x5c Copyright (C) 2019 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of the GNU
General Public License, version 2. General Public License, version 2.
""" """
@ -17,7 +17,7 @@ import discord.ext.commands as commands
import info import info
from data import options as opt import data.options as opt
import common as cmn import common as cmn
@ -142,7 +142,7 @@ class BaseCog(commands.Cog):
"""Show what has changed in recent bot versions.""" """Show what has changed in recent bot versions."""
embed = discord.Embed(title="qrm Changelog", embed = discord.Embed(title="qrm Changelog",
description=("For a full listing, visit [Github](https://" description=("For a full listing, visit [Github](https://"
"github.com/classabbyamp/discord-qrm-bot/blob/master/CHANGELOG.md)."), "github.com/classabbyamp/discord-qrm2/blob/master/CHANGELOG.md)."),
colour=cmn.colours.neutral, colour=cmn.colours.neutral,
timestamp=datetime.utcnow()) timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name, embed.set_footer(text=ctx.author.name,

View File

@ -1,9 +1,9 @@
""" """
Fun cog for qrm Fun extension for qrm
--- ---
Copyright (C) 2019 Abigail Gold, 0x5c Copyright (C) 2019 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of the GNU
General Public License, version 2. General Public License, version 2.
""" """

View File

@ -1,9 +1,9 @@
""" """
Grid cog for qrm Grid extension for qrm
--- ---
Copyright (C) 2019 Abigail Gold, 0x5c Copyright (C) 2019 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of the GNU
General Public License, version 2. General Public License, version 2.
""" """

View File

@ -1,9 +1,9 @@
""" """
Ham cog for qrm Ham extension for qrm
--- ---
Copyright (C) 2019 Abigail Gold, 0x5c Copyright (C) 2019 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of the GNU
General Public License, version 2. General Public License, version 2.
""" """
import json import json
@ -84,7 +84,7 @@ class HamCog(commands.Cog):
if country.lower() not in callsign_info.options: if country.lower() not in callsign_info.options:
embed = discord.Embed(title=f'{country} not found!', embed = discord.Embed(title=f'{country} not found!',
description=f'Valid countries: {", ".join(callsign_info.options.keys())}', description=f'Valid countries: {", ".join(callsign_info.options.keys())}',
colour=self.gs.colours.bad, colour=cmn.colours.bad,
timestamp=datetime.utcnow()) timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name, embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url)) icon_url=str(ctx.author.avatar_url))
@ -92,7 +92,7 @@ class HamCog(commands.Cog):
return return
embed = discord.Embed(title=callsign_info.options[country.lower()][0], embed = discord.Embed(title=callsign_info.options[country.lower()][0],
description=callsign_info.options[country.lower()][1], description=callsign_info.options[country.lower()][1],
colour=self.gs.colours.good, colour=cmn.colours.good,
timestamp=datetime.utcnow()) timestamp=datetime.utcnow())
embed.set_footer(text=ctx.author.name, embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url)) icon_url=str(ctx.author.avatar_url))

View File

@ -1,9 +1,9 @@
""" """
Image cog for qrm Image extension for qrm
--- ---
Copyright (C) 2019 Abigail Gold, 0x5c Copyright (C) 2019 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of the GNU
General Public License, version 2. General Public License, version 2.
""" """

View File

@ -1,9 +1,9 @@
""" """
Morse Code cog for qrm Morse Code extension for qrm
--- ---
Copyright (C) 2019 Abigail Gold, 0x5c Copyright (C) 2019 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of the GNU
General Public License, version 2. General Public License, version 2.
""" """

View File

@ -1,9 +1,9 @@
""" """
QRZ cog for qrm QRZ extension for qrm
--- ---
Copyright (C) 2019 Abigail Gold, 0x5c Copyright (C) 2019 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of the GNU
General Public License, version 2. General Public License, version 2.
""" """
from collections import OrderedDict from collections import OrderedDict
@ -17,7 +17,7 @@ import aiohttp
from lxml import etree from lxml import etree
import common as cmn import common as cmn
import keys import data.keys as keys
class QRZCog(commands.Cog): class QRZCog(commands.Cog):
@ -101,7 +101,7 @@ class QRZCog(commands.Cog):
async def qrz_login(user: str, passwd: str, session: aiohttp.ClientSession): async def qrz_login(user: str, passwd: str, session: aiohttp.ClientSession):
url = f'http://xmldata.qrz.com/xml/current/?username={user};password={passwd};agent=qrmbot' url = f'http://xmldata.qrz.com/xml/current/?username={user};password={passwd};agent=discord-qrm2'
async with session.get(url) as resp: async with session.get(url) as resp:
if resp.status != 200: if resp.status != 200:
raise ConnectionError(f'Unable to connect to QRZ (HTTP Error {resp.status})') raise ConnectionError(f'Unable to connect to QRZ (HTTP Error {resp.status})')

View File

@ -1,9 +1,9 @@
""" """
Study cog for qrm Study extension for qrm
--- ---
Copyright (C) 2019 Abigail Gold, 0x5c Copyright (C) 2019 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of the GNU
General Public License, version 2. General Public License, version 2.
""" """

View File

@ -1,9 +1,9 @@
""" """
Weather cog for qrm Weather extension for qrm
--- ---
Copyright (C) 2019 Abigail Gold, 0x5c Copyright (C) 2019 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of the GNU
General Public License, version 2. General Public License, version 2.
""" """
@ -90,13 +90,10 @@ See help for weather command for possible location types. Add a `-c` or `-f` to
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
else: else:
data = io.BytesIO(await resp.read()) data = io.BytesIO(await resp.read())
loc = loc.replace('+', '') embed.set_image(url=f'attachment://wttr_forecast.png')
loc = loc.replace('@', '')
loc = loc.replace('~', '')
embed.set_image(url=f'attachment://{loc}_{units}pnFQ.png')
embed.set_footer(text=ctx.author.name, embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url)) icon_url=str(ctx.author.avatar_url))
await ctx.send(embed=embed, file=discord.File(data, f'{loc}_{units}pnFQ.png')) await ctx.send(embed=embed, file=discord.File(data, f'wttr_forecast.png'))
@_weather_conditions.command(name='now', aliases=['n'], category=cmn.cat.weather) @_weather_conditions.command(name='now', aliases=['n'], category=cmn.cat.weather)
async def _weather_conditions_now(self, ctx: commands.Context, *, location: str): async def _weather_conditions_now(self, ctx: commands.Context, *, location: str):
@ -128,13 +125,10 @@ See help for weather command for possible location types. Add a `-c` or `-f` to
embed.colour = cmn.colours.bad embed.colour = cmn.colours.bad
else: else:
data = io.BytesIO(await resp.read()) data = io.BytesIO(await resp.read())
loc = loc.replace('+', '') embed.set_image(url=f'attachment://wttr_now.png')
loc = loc.replace('@', '')
loc = loc.replace('~', '')
embed.set_image(url=f'attachment://{loc}_0{units}pnFQ.png')
embed.set_footer(text=ctx.author.name, embed.set_footer(text=ctx.author.name,
icon_url=str(ctx.author.avatar_url)) icon_url=str(ctx.author.avatar_url))
await ctx.send(embed=embed, file=discord.File(data, f'{loc}_0{units}pnFQ.png')) await ctx.send(embed=embed, file=discord.File(data, 'wttr_now.png'))
def setup(bot: commands.Bot): def setup(bot: commands.Bot):

View File

@ -3,7 +3,7 @@ Static info about the bot.
--- ---
Copyright (C) 2019 Abigail Gold, 0x5c Copyright (C) 2019 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of the GNU
General Public License, version 2. General Public License, version 2.
--- ---
@ -23,5 +23,5 @@ General Public License, version 2.
authors = ("@ClassAbbyAmplifier#2229", "@0x5c#0639") authors = ("@ClassAbbyAmplifier#2229", "@0x5c#0639")
description = """A bot with various useful ham radio-related functions, written in Python.""" description = """A bot with various useful ham radio-related functions, written in Python."""
license = "Released under the GNU General Public License v2" license = "Released under the GNU General Public License v2"
contributing = "Check out the source on GitHub, contributions welcome: https://github.com/classabbyamp/discord-qrm-bot" contributing = "Check out the source on GitHub, contributions welcome: https://github.com/classabbyamp/discord-qrm2"
release = '1.0.0' release = '1.0.0'

95
main.py
View File

@ -4,23 +4,29 @@ qrm, a bot for Discord
--- ---
Copyright (C) 2019 Abigail Gold, 0x5c Copyright (C) 2019 Abigail Gold, 0x5c
This file is part of discord-qrmbot and is released under the terms of the GNU This file is part of discord-qrm2 and is released under the terms of the GNU
General Public License, version 2. General Public License, version 2.
""" """
from datetime import datetime
import discord import discord
from discord.ext import commands, tasks from discord.ext import commands, tasks
import common as cmn
import info import info
from data import options as opt import data.options as opt
from data import keys import data.keys as keys
# --- Settings --- # --- Settings ---
exit_code = 1 # The default exit code. ?shutdown and ?restart will change it accordingly (fail-safe) exit_code = 1 # The default exit code. ?shutdown and ?restart will change it accordingly (fail-safe)
ext_dir = "exts" # The name of the directory where extensions are located.
debug_mode = opt.debug # Separate assignement in-case we define an override (ternary operator goes here) debug_mode = opt.debug # Separate assignement in-case we define an override (ternary operator goes here)
@ -31,46 +37,80 @@ bot = commands.Bot(command_prefix=opt.prefix,
help_command=commands.MinimalHelpCommand()) help_command=commands.MinimalHelpCommand())
# --- Helper functions ---
async def add_react(msg: discord.Message, react: str):
try:
await msg.add_reaction(react)
except discord.Forbidden:
print(f"!! Missing permissions to add reaction in '{msg.guild.id}/{msg.channel.id}'!")
# --- Checks ---
async def check_if_owner(ctx: commands.Context):
if ctx.author.id in opt.owners_uids:
return True
await add_react(ctx.message, "")
return False
# --- Commands --- # --- Commands ---
@bot.command(name="restart", hidden=True) @bot.command(name="restart", hidden=True)
@commands.check(check_if_owner) @commands.check(cmn.check_if_owner)
async def _restart_bot(ctx: commands.Context): async def _restart_bot(ctx: commands.Context):
"""Restarts the bot.""" """Restarts the bot."""
global exit_code global exit_code
await add_react(ctx.message, "") await cmn.add_react(ctx.message, cmn.emojis.good)
print(f"[**] Restarting! Requested by {ctx.author}.")
exit_code = 42 # Signals to the wrapper script that the bot needs to be restarted. exit_code = 42 # Signals to the wrapper script that the bot needs to be restarted.
await bot.logout() await bot.logout()
@bot.command(name="shutdown", hidden=True) @bot.command(name="shutdown", hidden=True)
@commands.check(check_if_owner) @commands.check(cmn.check_if_owner)
async def _shutdown_bot(ctx: commands.Context): async def _shutdown_bot(ctx: commands.Context):
"""Shuts down the bot.""" """Shuts down the bot."""
global exit_code global exit_code
await add_react(ctx.message, "") await cmn.add_react(ctx.message, cmn.emojis.good)
print(f"[**] Shutting down! Requested by {ctx.author}.")
exit_code = 0 # Signals to the wrapper script that the bot should not be restarted. exit_code = 0 # Signals to the wrapper script that the bot should not be restarted.
await bot.logout() await bot.logout()
@bot.group(name="extctl", hidden=True)
@commands.check(cmn.check_if_owner)
async def _extctl(ctx: commands.Context):
"""Extension control commands.
Defaults to `list` if no subcommand specified"""
if ctx.invoked_subcommand is None:
cmd = bot.get_command("extctl list")
await ctx.invoke(cmd)
@_extctl.command(name="list")
async def _extctl_list(ctx: commands.Context):
"""Lists Extensions."""
embed = discord.Embed(title="Loaded Extensions",
colour=cmn.colours.neutral,
timestamp=datetime.utcnow())
embed.description = "\n".join(["" + x.split(".")[1] for x in bot.extensions.keys()])
await ctx.send(embed=embed)
@_extctl.command(name="load")
async def _extctl_load(ctx: commands.Context, extension: str):
try:
bot.load_extension(ext_dir + "." + extension)
await cmn.add_react(ctx.message, cmn.emojis.good)
except commands.ExtensionError as ex:
embed = cmn.error_embed_factory(ctx, ex, debug_mode)
await ctx.send(embed=embed)
@_extctl.command(name="reload")
async def _extctl_reload(ctx: commands.Context, extension: str):
try:
bot.reload_extension(ext_dir + "." + extension)
await cmn.add_react(ctx.message, cmn.emojis.good)
except commands.ExtensionError as ex:
embed = cmn.error_embed_factory(ctx, ex, debug_mode)
await ctx.send(embed=embed)
@_extctl.command(name="unload")
async def _extctl_unload(ctx: commands.Context, extension: str):
try:
bot.unload_extension(ext_dir + "." + extension)
await cmn.add_react(ctx.message, cmn.emojis.good)
except commands.ExtensionError as ex:
embed = cmn.error_embed_factory(ctx, ex, debug_mode)
await ctx.send(embed=embed)
# --- Events --- # --- Events ---
@bot.event @bot.event
@ -93,9 +133,8 @@ async def _before_ensure_activity():
# --- Run --- # --- Run ---
# bot.add_cog(GlobalSettings(bot)) for ext in opt.exts:
for cog in opt.cogs: bot.load_extension(ext_dir + '.' + ext)
bot.load_extension(f"cogs.{cog}")
_ensure_activity.start() _ensure_activity.start()

View File

@ -8,5 +8,5 @@
"?": "..--..", "'": ".----.", "!": "-.-.--", "/": "-..-.", "(": "-.--.", ")": "-.--.-", "?": "..--..", "'": ".----.", "!": "-.-.--", "/": "-..-.", "(": "-.--.", ")": "-.--.-",
"&": ".-...", ":": "---...", ";": "-.-.-.", "=": "-...-", "+": ".-.-.", "-": "-....-", "&": ".-...", ":": "---...", ";": "-.-.-.", "=": "-...-", "+": ".-.-.", "-": "-....-",
"\"": ".-..-.", "@": ".--.-.", "Ä": ".-.-", "Å": ".-.-", "Ą": ".-.-", "Æ": ".-.-", "\"": ".-..-.", "@": ".--.-.", "Ä": ".-.-", "Å": ".-.-", "Ą": ".-.-", "Æ": ".-.-",
"É": "..-..", "Ñ": "--.--", "Ö": "---.", "Ü": "..--", "Š": "----" "É": "..-..", "Ñ": "--.--", "Ö": "---.", "Ü": "..--", "Š": "----", " ": "/"
} }

View File

@ -26,9 +26,8 @@ debug = False
# ! This MUST be a tuple of integers. Single element tuple: `(123,)` # ! This MUST be a tuple of integers. Single element tuple: `(123,)`
owners_uids = (200102491231092736,) owners_uids = (200102491231092736,)
# The cogs to load when running the bot. # The extensions to load when running the bot.
cogs = ['basecog', 'morsecog', 'funcog', 'gridcog', 'hamcog', 'imagecog', exts = ['ae7q', 'base', 'fun', 'grid', 'ham', 'image', 'morse', 'qrz', 'study', 'weather']
'studycog', 'ae7qcog', 'qrzcog', 'weathercog']
# The text to put in the "playing" status. # The text to put in the "playing" status.
game = 'with lids on 7.200' game = 'with lids on 7.200'