Merge branch 'master' into 5c-metadata

Fix conflicts
This commit is contained in:
0x5c 2019-12-23 21:13:01 -05:00
commit 1b21583d41
No known key found for this signature in database
GPG Key ID: 82039FC95E3FE970
11 changed files with 159 additions and 81 deletions

View File

@ -25,7 +25,8 @@ import discord.ext.commands as commands
import data.options as opt
__all__ = ["colours", "cat", "emojis", "error_embed_factory", "add_react", "check_if_owner"]
__all__ = ["colours", "cat", "emojis", "embed_factory", "error_embed_factory", "add_react", "check_if_owner"]
# --- Common values ---

View File

@ -16,7 +16,6 @@ NA2AAA: unassigned, no records
import discord.ext.commands as commands
from bs4 import BeautifulSoup
import aiohttp
import common as cmn
@ -24,6 +23,7 @@ import common as cmn
class AE7QCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.session = bot.qrm.session
@commands.group(name="ae7q", aliases=["ae"], category=cmn.cat.lookup)
async def _ae7q_lookup(self, ctx: commands.Context):
@ -37,12 +37,16 @@ class AE7QCog(commands.Cog):
callsign = callsign.upper()
desc = ''
base_url = "http://ae7q.com/query/data/CallHistory.php?CALL="
embed = cmn.embed_factory(ctx)
async with aiohttp.ClientSession() as session:
async with session.get(base_url + callsign) as resp:
if resp.status != 200:
return await ctx.send('Could not load AE7Q')
page = await resp.text()
async with self.session.get(base_url + callsign) as resp:
if resp.status != 200:
embed.title = "Error in AE7Q call command"
embed.description = 'Could not load AE7Q'
embed.colour = cmn.colours.bad
await ctx.send(embed=embed)
return
page = await resp.text()
soup = BeautifulSoup(page, features="html.parser")
tables = soup.select("table.Database")
@ -59,7 +63,6 @@ class AE7QCog(commands.Cog):
rows = None
if rows is None:
embed = cmn.embed_factory(ctx)
embed.title = f"AE7Q History for {callsign}"
embed.colour = cmn.colours.bad
embed.url = f"{base_url}{callsign}"

View File

@ -99,10 +99,10 @@ class BaseCog(commands.Cog):
embed.title = "About qrm"
embed.description = info.description
embed = embed.add_field(name="Authors", value=", ".join(info.authors))
embed = embed.add_field(name="License", value=info.license)
embed = embed.add_field(name="Version", value=f'v{info.release}')
embed = embed.add_field(name="Contributing", value=info.contributing, inline=False)
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}')
embed.add_field(name="Contributing", value=info.contributing, inline=False)
embed.set_thumbnail(url=str(self.bot.user.avatar_url))
await ctx.send(embed=embed)

View File

@ -22,6 +22,7 @@ class ImageCog(commands.Cog):
self.bot = bot
self.bandcharts = cmn.ImagesGroup(cmn.paths.bandcharts / "meta.json")
self.maps = cmn.ImagesGroup(cmn.paths.maps / "meta.json")
self.session = bot.qrm.session
@commands.command(name="bandplan", aliases=['plan', 'bands'], category=cmn.cat.ref)
async def _bandplan(self, ctx: commands.Context, region: str = ''):
@ -88,14 +89,13 @@ class ImageCog(commands.Cog):
embed = cmn.embed_factory(ctx)
embed.title = 'Current Greyline Conditions'
embed.colour = cmn.colours.good
async with aiohttp.ClientSession() as session:
async with session.get(gl_url) 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://greyline.jpg')
async with self.session.get(gl_url) 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://greyline.jpg')
await ctx.send(embed=embed, file=discord.File(data, 'greyline.jpg'))

View File

@ -62,6 +62,7 @@ class MorseCog(commands.Cog):
@commands.command(name="cwweight", aliases=["weight", 'cww'], category=cmn.cat.ref)
async def _weight(self, ctx: commands.Context, *, msg: str):
'''Calculates the CW Weight of a callsign or message.'''
embed = cmn.embed_factory(ctx)
with ctx.typing():
msg = msg.upper()
weight = 0
@ -70,13 +71,13 @@ class MorseCog(commands.Cog):
cw_char = self.ascii2morse[char].replace('-', '==')
weight += len(cw_char) * 2 + 2
except KeyError:
res = f'Unknown character {char} in callsign'
await ctx.send(res)
embed.title = 'Error in calculation of CW weight'
embed.description = f'Unknown character {char} in callsign'
embed.colour = cmn.colours.bad
await ctx.send(embed=embed)
return
res = f'The CW weight is **{weight}**'
embed = cmn.embed_factory(ctx)
embed.title = f'CW Weight of {msg}'
embed.description = res
embed.description = f'The CW weight is **{weight}**'
embed.colour = cmn.colours.good
await ctx.send(embed=embed)

View File

@ -21,7 +21,7 @@ import data.keys as keys
class QRZCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.session = aiohttp.ClientSession()
self.session = bot.qrm.session
self._qrz_session_init.start()
@commands.command(name="call", aliases=["qrz"], category=cmn.cat.lookup)
@ -139,6 +139,9 @@ def qrz_process_info(data: dict):
state = ''
address = data.get('addr1', '') + '\n' + data.get('addr2', '') + \
state + ' ' + data.get('zip', '')
address = address.strip()
if address == '':
address = None
if 'eqsl' in data:
eqsl = 'Yes' if data['eqsl'] == 1 else 'No'
else:

View File

@ -12,8 +12,6 @@ import json
import discord.ext.commands as commands
import aiohttp
import common as cmn
@ -22,6 +20,7 @@ class StudyCog(commands.Cog):
self.bot = bot
self.lastq = dict()
self.source = 'Data courtesy of [HamStudy.org](https://hamstudy.org/)'
self.session = bot.qrm.session
@commands.command(name="hamstudy", aliases=['rq', 'randomquestion', 'randomq'], category=cmn.cat.study)
async def _random_question(self, ctx: commands.Context, level: str = None):
@ -30,6 +29,7 @@ class StudyCog(commands.Cog):
gen_pool = 'E3_2019'
extra_pool = 'E4_2016'
embed = cmn.embed_factory(ctx)
with ctx.typing():
selected_pool = None
try:
@ -49,36 +49,41 @@ class StudyCog(commands.Cog):
if (level is None) or (level == 'all'): # no pool given or user wants all, so pick a random pool
selected_pool = random.choice([tech_pool, gen_pool, extra_pool])
if (level is not None) and (selected_pool is None): # unrecognized pool given by user
await ctx.send('The question pool you gave was unrecognized. ' +
'There are many ways to call up certain question pools - try ?rq t, g, or e. ' +
'(Note that only the US question pools are available).')
embed.title = 'Error in HamStudy command'
embed.description = ('The question pool you gave was unrecognized. '
'There are many ways to call up certain question pools - try ?rq t, g, or e. '
'\n\nNote that currently only the US question pools are available.')
embed.colour = cmn.colours.bad
await ctx.send(embed=embed)
return
async with aiohttp.ClientSession() as session:
async with session.get(f'https://hamstudy.org/pools/{selected_pool}') as resp:
if resp.status != 200:
return await ctx.send('Could not load questions...')
pool = json.loads(await resp.read())['pool']
async with self.session.get(f'https://hamstudy.org/pools/{selected_pool}') as resp:
if resp.status != 200:
embed.title = 'Error in HamStudy command'
embed.description = 'Could not load questions'
embed.colour = cmn.colours.bad
await ctx.send(embed=embed)
return
pool = json.loads(await resp.read())['pool']
# Select a question
pool_section = random.choice(pool)['sections']
pool_questions = random.choice(pool_section)['questions']
question = random.choice(pool_questions)
embed = cmn.embed_factory(ctx)
embed.title = question['id']
embed.description = self.source
embed.colour = cmn.colours.good
embed = embed.add_field(name='Question:', value=question['text'], inline=False)
embed = embed.add_field(name='Answers:', value='**A:** ' + question['answers']['A'] +
'\n**B:** ' + question['answers']['B'] +
'\n**C:** ' + question['answers']['C'] +
'\n**D:** ' + question['answers']['D'],
inline=False)
embed = embed.add_field(name='Answer:', value='Type _?rqa_ for answer', inline=False)
embed.add_field(name='Question:', value=question['text'], inline=False)
embed.add_field(name='Answers:', value='**A:** ' + question['answers']['A'] +
'\n**B:** ' + question['answers']['B'] +
'\n**C:** ' + question['answers']['C'] +
'\n**D:** ' + question['answers']['D'],
inline=False)
embed.add_field(name='Answer:', value='Type _?rqa_ for answer', inline=False)
if 'image' in question:
image_url = f'https://hamstudy.org/_1330011/images/{selected_pool.split("_",1)[1]}/{question["image"]}'
embed = embed.set_image(url=image_url)
embed.set_image(url=image_url)
self.lastq[ctx.message.channel.id] = (question['id'], question['answer'])
await ctx.send(embed=embed)

View File

@ -13,8 +13,6 @@ import re
import discord
import discord.ext.commands as commands
import aiohttp
import common as cmn
@ -23,6 +21,7 @@ class WeatherCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.session = bot.qrm.session
@commands.command(name="bandconditions", aliases=['cond', 'condx', 'conditions'], category=cmn.cat.weather)
async def _band_conditions(self, ctx: commands.Context):
@ -31,14 +30,13 @@ class WeatherCog(commands.Cog):
embed = cmn.embed_factory(ctx)
embed.title = 'Current Solar Conditions'
embed.colour = cmn.colours.good
async with aiohttp.ClientSession() as session:
async with session.get('http://www.hamqsl.com/solarsun.php') 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://condx.png')
async with self.session.get('http://www.hamqsl.com/solarsun.php') 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://condx.png')
await ctx.send(embed=embed, file=discord.File(data, 'condx.png'))
@commands.group(name="weather", aliases=['wttr'], category=cmn.cat.weather)
@ -81,14 +79,13 @@ See help for weather command for possible location types. Add a `-c` or `-f` to
embed.colour = cmn.colours.good
loc = loc.replace(' ', '+')
async with aiohttp.ClientSession() as session:
async with 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')
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, f'wttr_forecast.png'))
@_weather_conditions.command(name='now', aliases=['n'], category=cmn.cat.weather)
@ -115,14 +112,13 @@ See help for weather command for possible location types. Add a `-c` or `-f` to
embed.colour = cmn.colours.good
loc = loc.replace(' ', '+')
async with aiohttp.ClientSession() as session:
async with session.get(f'http://wttr.in/{loc}_0{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_now.png')
async with self.session.get(f'http://wttr.in/{loc}_0{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_now.png')
await ctx.send(embed=embed, file=discord.File(data, 'wttr_now.png'))

66
main.py
View File

@ -8,12 +8,18 @@ This file is part of discord-qrm2 and is released under the terms of the GNU
General Public License, version 2.
"""
from datetime import time, datetime
import random
from types import SimpleNamespace
import pytz
import aiohttp
import discord
from discord.ext import commands, tasks
import common as cmn
import info
import data.options as opt
import data.keys as keys
@ -33,6 +39,9 @@ bot = commands.Bot(command_prefix=opt.prefix,
description=info.description,
help_command=commands.MinimalHelpCommand())
bot.qrm = SimpleNamespace()
bot.qrm.session = aiohttp.ClientSession(headers={'User-Agent': f'discord-qrm2/{info.release}'})
# --- Commands ---
@ -40,6 +49,7 @@ bot = commands.Bot(command_prefix=opt.prefix,
@commands.check(cmn.check_if_owner)
async def _restart_bot(ctx: commands.Context):
"""Restarts the bot."""
await bot.qrm.session.close()
global exit_code
await cmn.add_react(ctx.message, cmn.emojis.good)
print(f"[**] Restarting! Requested by {ctx.author}.")
@ -51,6 +61,7 @@ async def _restart_bot(ctx: commands.Context):
@commands.check(cmn.check_if_owner)
async def _shutdown_bot(ctx: commands.Context):
"""Shuts down the bot."""
await bot.qrm.session.close()
global exit_code
await cmn.add_react(ctx.message, cmn.emojis.good)
print(f"[**] Shutting down! Requested by {ctx.author}.")
@ -117,18 +128,59 @@ async def _extctl_unload(ctx: commands.Context, extension: str):
async def on_ready():
print(f"Logged in as: {bot.user} - {bot.user.id}")
print("------")
if opt.status_mode == "time":
_ensure_activity_time.start()
elif opt.status_mode == "random":
_ensure_activity_random.start()
else:
_ensure_activity_fixed.start()
@bot.event
async def on_message(message):
msg = message.content.lower()
for emoji, keywords in opt.msg_reacts.items():
if any([keyword in msg for keyword in keywords]):
await message.add_reaction(discord.utils.find(lambda x: x.id == emoji, bot.emojis))
await bot.process_commands(message)
# --- Tasks ---
@tasks.loop(minutes=5)
async def _ensure_activity():
await bot.change_presence(activity=discord.Game(name=opt.game))
async def _ensure_activity_time():
status = opt.statuses[0]
try:
tz = pytz.timezone(opt.status_tz)
except pytz.exceptions.UnknownTimeZoneError:
await bot.change_presence(activity=discord.Game(name="with invalid timezones."))
return
now = datetime.now(tz=tz).time()
for sts in opt.time_statuses:
start_time = time(hour=sts[1][0], minute=sts[1][1], tzinfo=tz)
end_time = time(hour=sts[2][0], minute=sts[2][1], tzinfo=tz)
if start_time < now <= end_time:
status = sts[0]
await bot.change_presence(activity=discord.Game(name=status))
@_ensure_activity.before_loop
async def _before_ensure_activity():
await bot.wait_until_ready()
@tasks.loop(minutes=5)
async def _ensure_activity_random():
status = random.choice(opt.statuses)
await bot.change_presence(activity=discord.Game(name=status))
@tasks.loop(minutes=5)
async def _ensure_activity_fixed():
status = opt.statuses[0]
await bot.change_presence(activity=discord.Game(name=status))
# --- Run ---
@ -136,8 +188,6 @@ async def _before_ensure_activity():
for ext in opt.exts:
bot.load_extension(ext_dir + '.' + ext)
_ensure_activity.start()
try:
bot.run(keys.discord_token)

View File

@ -2,3 +2,4 @@ discord.py
ctyparser
beautifulsoup4
lxml
pytz

View File

@ -29,8 +29,26 @@ owners_uids = (200102491231092736,)
# The extensions to load when running the bot.
exts = ['ae7q', 'base', 'fun', 'grid', 'ham', 'image', 'lookup', 'morse', 'qrz', 'study', 'weather']
# The text to put in the "playing" status.
game = 'with lids on 7.200'
# Either "time", "random", or "fixed" (first item in statuses)
status_mode = "fixed"
# Random statuses pool
statuses = ["with lids on the air", "with fire"]
# Timezone for the status (string)
# See https://pythonhosted.org/pytz/ for more info
status_tz = 'US/Eastern'
# The text to put in the "playing" status, with start and stop times
time_statuses = [('with lids on 3.840', (00, 00), (6, 00)),
('with lids on 7.200', (6, 00), (10, 00)),
('with lids on 14.313', (10, 00), (18, 00)),
('with lids on 7.200', (18, 00), (20, 00)),
('with lids on 3.840', (20, 00), (23, 59))]
# Emoji IDs and keywords for emoji reactions
# Use the format {emoji_id (int): ('tuple', 'of', 'lowercase', 'keywords')}
msg_reacts = {}
# A :pika: emote's ID, None for no emote :c
pika = 658733876176355338