1
0
Fork 0
mirror of synced 2024-05-04 12:42:30 +12:00

Database rewrite/User queue creation

This commit is contained in:
phxntxm 2017-06-07 03:30:19 -05:00
parent 1cc88ce6ab
commit efc1a3cf6d
31 changed files with 1197 additions and 740 deletions

15
bot.py
View file

@ -24,8 +24,6 @@ logging.basicConfig(level=logging.INFO, filename='bonfire.log')
@bot.event
async def on_ready():
if not hasattr(bot, 'uptime'):
bot.uptime = pendulum.utcnow()
if not hasattr(bot, 'owner'):
appinfo = await bot.application_info()
bot.owner = appinfo.owner
@ -33,7 +31,7 @@ async def on_ready():
@bot.event
async def on_message(message):
if message.author.bot or utils.should_ignore(message):
if message.author.bot or utils.should_ignore(bot, message):
return
await bot.process_commands(message)
@ -49,9 +47,8 @@ async def process_command(ctx):
server = ctx.message.guild
command = ctx.command
command_usage = await utils.get_content('command_usage', key=command.qualified_name)
if command_usage is None:
command_usage = {'command': command.qualified_name}
command_usage = await bot.db.actual_load('command_usage', key=command.qualified_name) or \
{'command': command.qualified_name}
# Add one to the total usage for this command, basing it off 0 to start with (obviously)
total_usage = command_usage.get('total_usage', 0) + 1
@ -71,8 +68,7 @@ async def process_command(ctx):
command_usage['server_usage'] = total_server_usage
# Save all the changes
if not await utils.update_content('command_usage', command_usage, command.qualified_name):
await utils.add_content('command_usage', command_usage)
bot.db.save('command_usage', command_usage)
@bot.event
@ -129,4 +125,7 @@ if __name__ == '__main__':
for e in utils.extensions:
bot.load_extension(e)
bot.db = utils.DB()
bot.uptime = pendulum.utcnow()
bot.run(utils.bot_token)

View file

@ -5,7 +5,6 @@ from . import utils
import discord
import re
import rethinkdb as r
valid_perms = [p for p in dir(discord.Permissions) if isinstance(getattr(discord.Permissions, p), property)]
@ -51,7 +50,7 @@ class Administration:
EXAMPLE: !ignore #general
RESULT: Bonfire will ignore commands sent in the general channel"""
key = str(ctx.message.guild.id)
key = ctx.message.guild.id
converter = commands.converter.MemberConverter()
member = None
@ -66,9 +65,7 @@ class Administration:
await ctx.send("{} does not appear to be a member or channel!".format(member_or_channel))
return
settings = await utils.get_content('server_settings', key)
if settings is None:
settings = {}
settings = self.bot.db.load('server_settings', key=key, pluck='ignored') or {}
ignored = settings.get('ignored', {'members': [], 'channels': []})
if member:
if str(member.id) in ignored['members']:
@ -80,7 +77,7 @@ class Administration:
else:
ignored['members'].append(str(member.id))
fmt = "Ignoring {}".format(member.display_name)
elif channel:
else:
if str(channel.id) in ignored['channels']:
await ctx.send("I am already ignoring {}!".format(channel.mention))
return
@ -88,8 +85,12 @@ class Administration:
ignored['channels'].append(str(channel.id))
fmt = "Ignoring {}".format(channel.mention)
update = {'ignored': ignored}
await utils.update_content('server_settings', update, key)
entry = {
'ignored': ignored,
'server_id': str(key)
}
self.bot.db.save('server_settings', entry)
await ctx.send(fmt)
@commands.command()
@ -115,9 +116,7 @@ class Administration:
await ctx.send("{} does not appear to be a member or channel!".format(member_or_channel))
return
settings = await utils.get_content('server_settings', key)
if settings is None:
settings = {}
settings = self.bot.db.load('server_settings', key=key) or {}
ignored = settings.get('ignored', {'members': [], 'channels': []})
if member:
if str(member.id) not in ignored['members']:
@ -126,7 +125,7 @@ class Administration:
ignored['members'].remove(str(member.id))
fmt = "I am no longer ignoring {}".format(member.display_name)
elif channel:
else:
if str(channel.id) not in ignored['channels']:
await ctx.send("I'm not even ignoring {}!".format(channel.mention))
return
@ -134,8 +133,12 @@ class Administration:
ignored['channels'].remove(str(channel.id))
fmt = "I am no longer ignoring {}".format(channel.mention)
update = {'ignored': ignored}
await utils.update_content('server_settings', update, key)
entry = {
'ignored': ignored,
'server_id': str(key)
}
self.bot.db.save('server_settings', entry)
await ctx.send(fmt)
@commands.command(aliases=['alerts'])
@ -147,11 +150,12 @@ class Administration:
EXAMPLE: !alerts #alerts
RESULT: No more alerts spammed in #general!"""
key = str(ctx.message.guild.id)
entry = {'server_id': key,
'notification_channel': str(channel.id)}
if not await utils.update_content('server_settings', entry, key):
await utils.add_content('server_settings', entry)
entry = {
'server_id': str(ctx.message.guild.id),
'notifications_channel': str(channel.id)
}
self.bot.db.save('server_settings', entry)
await ctx.send("I have just changed this server's 'notifications' channel"
"\nAll notifications will now go to `{}`".format(channel))
@ -168,12 +172,13 @@ class Administration:
# So we base this channel on it's own and not from alerts
# When mod logging becomes available, that will be kept to it's own channel if wanted as well
on_off = True if re.search("(on|yes|true)", on_off.lower()) else False
key = str(ctx.message.guild.id)
entry = {'server_id': key,
'join_leave': on_off}
if not await utils.update_content('server_settings', entry, key):
await utils.add_content('server_settings', entry)
entry = {
'server_id': str(ctx.message.guild.id),
'join_leave': on_off
}
self.bot.db.save('server_settings',entry)
fmt = "notify" if on_off else "not notify"
await ctx.send("This server will now {} if someone has joined or left".format(fmt))
@ -196,17 +201,15 @@ class Administration:
else:
key = str(ctx.message.guild.id)
entry = {'server_id': key,
'nsfw_channels': [str(ctx.message.channel.id)]}
update = {'nsfw_channels': r.row['nsfw_channels'].append(str(ctx.message.channel.id))}
channels = self.bot.db.load('server_settings', key=key, pluck='nsfw_channels') or []
channels.append(str(ctx.message.channel.id))
server_settings = await utils.get_content('server_settings', key)
if server_settings and 'nsfw_channels' in server_settings.keys():
await utils.update_content('server_settings', update, key)
elif server_settings:
await utils.update_content('server_settings', entry, key)
else:
await utils.add_content('server_settings', entry)
entry = {
'server_id': key,
'nsfw_channels': channels
}
self.bot.db.save('server_settings', entry)
await ctx.send("This channel has just been registered as 'nsfw'! Have fun you naughties ;)")
@ -217,27 +220,24 @@ class Administration:
EXAMPLE: !nsfw remove
RESULT: ;("""
channel = str(ctx.message.channel.id)
if type(ctx.message.channel) is discord.DMChannel:
key = 'DMs'
else:
key = str(ctx.message.guild.id)
server_settings = await utils.get_content('server_settings', key)
channel = str(ctx.message.channel.id)
try:
channels = server_settings.get('nsfw_channels', None)
if channel in channels:
channels.remove(channel)
channels = self.bot.db.load('server_settings', key=key, pluck='nsfw_channels') or []
if channel in channels:
channels.remove(channel)
entry = {'nsfw_channels': channels}
await utils.update_content('server_settings', entry, key)
await ctx.send("This channel has just been unregistered as a nsfw channel")
return
except (TypeError, IndexError):
pass
await ctx.send("This channel is not registered as a 'nsfw' channel!")
entry = {
'server_id': key,
'nsfw_channels': channels
}
self.bot.db.save('server_settings', entry)
await ctx.send("This channel has just been unregistered as a nsfw channel")
else:
await ctx.send("This channel is not registerred as a nsfw channel!")
@commands.group(invoke_without_command=True)
@commands.guild_only()
@ -259,11 +259,7 @@ class Administration:
await ctx.send("That is not a valid command!")
return
server_settings = await utils.get_content('server_settings', str(ctx.message.guild.id))
try:
server_perms = server_settings['permissions']
except (TypeError, IndexError, KeyError):
server_perms = {}
server_perms = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='permissions') or {}
perms_value = server_perms.get(cmd.qualified_name)
if perms_value is None:
@ -351,12 +347,12 @@ class Administration:
await ctx.send("This command cannot have custom permissions setup!")
return
key = str(ctx.message.guild.id)
entry = {'server_id': key,
'permissions': {cmd.qualified_name: perm_value}}
entry = {
'server_id': str(ctx.message.guild.id),
'permissions': {cmd.qualified_name: perm_value}
}
if not await utils.update_content('server_settings', entry, key):
await utils.add_content('server_settings', entry)
self.bot.db.save('server_settings', entry)
await ctx.send("I have just added your custom permissions; "
"you now need to have `{}` permissions to use the command `{}`".format(permissions, command))
@ -377,8 +373,12 @@ class Administration:
"That command does not exist! You can't have custom permissions on a non-existant command....")
return
update = {'permissions': {cmd.qualified_name: None}}
await utils.update_content('server_settings', update, str(ctx.message.guild.id))
entry = {
'server_id': str(ctx.message.guild.id),
'permissions': {cmd.qualified_name: None}
}
self.bot.db.save('server_settings', entry)
await ctx.send("I have just removed the custom permissions for {}!".format(cmd))
@commands.command()
@ -396,11 +396,12 @@ class Administration:
if prefix.lower().strip() == "none":
prefix = None
entry = {'server_id': key,
'prefix': prefix}
entry = {
'server_id': key,
'prefix': prefix
}
if not await utils.update_content('server_settings', entry, key):
await utils.add_content('server_settings', entry)
self.bot.db.save('server_settings', entry)
if prefix is None:
fmt = "I have just cleared your custom prefix, the default prefix will have to be used now"
@ -417,14 +418,9 @@ class Administration:
EXAMPLE: !rules 5
RESULT: Rule 5 is printed"""
server_settings = await utils.get_content('server_settings', str(ctx.message.guild.id))
if server_settings is None:
await ctx.send("This server currently has no rules on it! I see you like to live dangerously...")
return
rules = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='rules')
rules = server_settings.get('rules')
if not rules or len(rules) == 0:
if rules is None:
await ctx.send("This server currently has no rules on it! I see you like to live dangerously...")
return
@ -452,17 +448,15 @@ class Administration:
EXAMPLE: !rules add No fun allowed in this server >:c
RESULT: No more fun...unless they break the rules!"""
key = str(ctx.message.guild.id)
entry = {'server_id': key,
'rules': [rule]}
update = {'rules': r.row['rules'].append(rule)}
rules = self.bot.db.load('server_settings', key=key, pluck='rules') or []
rules.append(rule)
server_settings = await utils.get_content('server_settings', key)
if server_settings and 'rules' in server_settings.keys():
await utils.update_content('server_settings', update, key)
elif server_settings:
await utils.update_content('server_settings', entry, key)
else:
await utils.add_content('server_settings', entry)
entry = {
'server_id': key,
'rules': rules
}
self.bot.db.save('server_settings', entry)
await ctx.send("I have just saved your new rule, use the rules command to view this server's current rules")
@ -475,11 +469,55 @@ class Administration:
EXAMPLE: !rules delete 5
RESULT: Freedom from opression!"""
update = {'rules': r.row['rules'].delete_at(rule - 1)}
if not await utils.update_content('server_settings', update, str(ctx.message.guild.id)):
await ctx.send("That is not a valid rule number, try running the command again.")
else:
key = str(ctx.message.guild.id)
rules = self.bot.db.load('server_settings', key=key, pluck='rules') or []
try:
rules.pop(rule - 1)
entry = {
'server_id': key,
'rules': rules
}
self.bot.db.save('server_settings', entry)
await ctx.send("I have just removed that rule from your list of rules!")
except IndexError:
await ctx.send("That is not a valid rule number, try running the command again.")
@commands.command()
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
async def queuetype(self, ctx, new_type=None):
"""Switches the song queue type for music
Choices are `user` or `song` queue
The `user` queue rotates off of a wait list, where people join the waitlist and the next song in their
playlist is the one that is played.
The `song` queue rotates based on songs themselves, where people add a song to the server's playlist,
and these are rotated through.
EXAMPLE: !queuetype user
RESULT: !queuetype """
key = str(ctx.message.guild.id)
if new_type is None:
cur_type = self.bot.db.load('server_settings', key=key, pluck='queue_type') or 'song'
await ctx.send("Current queue type is {}".format(cur_type))
return
new_type = new_type.lower().strip()
if new_type not in ['user', 'song']:
await ctx.send("Queue choices are either `user` or `song`. "
"Run `{}help queuetype` if you need more information".format(ctx.prefix))
else:
entry = {
'server_id': key,
'queue_type': new_type
}
self.bot.db.save('server_settings', entry)
state = self.bot.get_cog('Music').voice_states.get(ctx.message.guild.id)
if state:
if new_type == "user" and not state.user_queue or new_type == "song" and state.user_queue:
state.switch_queue_type()
await ctx.send("Current queue type is now `{}`".format(new_type))
def setup(bot):

65
cogs/dj.py Normal file
View file

@ -0,0 +1,65 @@
from .voice_utilities import *
import discord
class DJEvents:
"""A simple class to save our DJ objects, once someone is detected to have joined a channel,
their DJ information will automatically update"""
def __init__(self, bot):
self.bot = bot
self.djs = {}
async def on_ready(self):
for channel in [c for c in self.bot.get_all_channels() if isinstance(c, discord.VoiceChannel)]:
for member in [m for m in channel.members if not m.bot]:
if member.id not in self.djs:
dj = DJ(member, self.bot)
self.bot.loop.create_task(dj.resolve_playlist())
self.djs[member.id] = dj
async def on_voice_state_update(self, member, _, after):
if member and not member.bot and member.id not in self.djs:
dj = DJ(member, self.bot)
self.bot.loop.create_task(dj.resolve_playlist())
self.djs[member.id] = dj
# Alternatively, if the bot has joined the channel and we never detected the members that are in the channel
# This most likely means the bot has just started up, lets get these user's ready too
if member and member.id == member.guild.me.id and after and after.channel:
for m in after.channel.members:
if not m.bot and m.id not in self.djs:
dj = DJ(m, self.bot)
self.bot.loop.create_task(dj.resolve_playlist())
self.djs[m.id] = dj
class DJ(Playlist):
def __init__(self, member, bot):
super().__init__(bot)
self.member = member
self.playlists = []
async def get_next_entry(self, predownload_next=True):
if not self.entries:
return None
else:
entry = self.entries[0]
self.entries.rotate(-1)
return await entry.get_ready_future()
async def resolve_playlist(self):
self.playlists = self.bot.db.load('user_playlists', key=self.member.id, pluck='playlists') or []
self.clear()
for pl in self.playlists:
if pl['active']:
for song in pl['songs']:
try:
await self.add_entry(song['url'])
except ExtractionError:
# For now, just silently ignore this
pass
def setup(bot):
bot.add_cog(DJEvents(bot))

View file

@ -44,10 +44,10 @@ class StatsUpdate:
async with self.session.post(url, data=payload, headers=headers) as resp:
log.info('bots.discord.pw statistics returned {} for {}'.format(resp.status, payload))
async def on_server_join(self, server):
async def on_guild_join(self, _):
self.bot.loop.create_task(self.update())
async def on_server_leave(self, server):
async def on_guild_leave(self, _):
self.bot.loop.create_task(self.update())
async def on_ready(self):
@ -55,12 +55,12 @@ class StatsUpdate:
async def on_member_join(self, member):
guild = member.guild
server_settings = await config.get_content('server_settings', str(guild.id))
server_settings = self.bot.db.load('server_settings', key=str(guild.id))
try:
join_leave_on = server_settings['join_leave']
if join_leave_on:
channel_id = server_settings.get('notification_channel') or member.guild.id
channel_id = server_settings.get('notifications_channel') or member.guild.id
else:
return
except (IndexError, TypeError, KeyError):
@ -74,12 +74,12 @@ class StatsUpdate:
async def on_member_remove(self, member):
guild = member.guild
server_settings = await config.get_content('server_settings', str(guild.id))
server_settings = self.bot.db.load('server_settings', key=str(guild.id))
try:
join_leave_on = server_settings['join_leave']
if join_leave_on:
channel_id = server_settings.get('notification_channel') or member.guild.id
channel_id = server_settings.get('notifications_channel') or member.guild.id
else:
return
except (IndexError, TypeError, KeyError):

View file

@ -8,6 +8,7 @@ import re
import random
import asyncio
class Game:
def __init__(self, word):
self.word = word
@ -142,12 +143,17 @@ class Hangman:
return
try:
msg = await ctx.message.author.send("Please respond with a phrase you would like to use for your hangman game in **{}**\n\nPlease keep phrases less than 20 characters".format(ctx.message.guild.name))
msg = await ctx.message.author.send(
"Please respond with a phrase you would like to use for your hangman game in **{}**\n\nPlease keep phrases less than 20 characters".format(
ctx.message.guild.name))
except discord.Forbidden:
await ctx.send("I can't message you {}! Please allow DM's so I can message you and ask for the hangman phrase you want to use!".format(ctx.message.author.display_name))
await ctx.send(
"I can't message you {}! Please allow DM's so I can message you and ask for the hangman phrase you want to use!".format(
ctx.message.author.display_name))
return
await ctx.send("I have DM'd you {}, please respond there with the phrase you would like to setup".format(ctx.message.author.display_name))
await ctx.send("I have DM'd you {}, please respond there with the phrase you would like to setup".format(
ctx.message.author.display_name))
def check(m):
return m.channel == msg.channel and len(m.content) < 20
@ -155,12 +161,14 @@ class Hangman:
try:
msg = await self.bot.wait_for('message', check=check, timeout=60)
except asyncio.TimeoutError:
await ctx.send("You took too long! Please look at your DM's next to as that's where I'm asking for the phrase you want to use")
await ctx.send(
"You took too long! Please look at your DM's next to as that's where I'm asking for the phrase you want to use")
return
forbidden_phrases = ['stop', 'delete', 'remove', 'end', 'create', 'start']
if msg.content in forbidden_phrases:
await ctx.send("Detected forbidden hangman phrase; current forbidden phrases are: \n{}".format("\n".join(forbidden_phrases)))
await ctx.send("Detected forbidden hangman phrase; current forbidden phrases are: \n{}".format(
"\n".join(forbidden_phrases)))
return
game = self.create(msg.content, ctx)

View file

@ -112,7 +112,7 @@ class Images:
query = ' '.join(value for value in search if not re.search('&?filter_id=[0-9]+', value))
params = {'q': query}
nsfw = await utils.channel_is_nsfw(ctx.message.channel)
nsfw = await utils.channel_is_nsfw(ctx.message.channel, self.bot.db)
# If this is a nsfw channel, we just need to tack on 'explicit' to the terms
# Also use the custom filter that I have setup, that blocks some certain tags
# If the channel is not nsfw, we don't need to do anything, as the default filter blocks explicit
@ -185,7 +185,7 @@ class Images:
params = {'limit': 320,
'tags': tags}
nsfw = await utils.channel_is_nsfw(ctx.message.channel)
nsfw = await utils.channel_is_nsfw(ctx.message.channel, self.bot.db)
# e621 by default does not filter explicit content, so tack on
# safe/explicit based on if this channel is nsfw or not

View file

@ -117,7 +117,6 @@ class Interaction:
return False
return True
def start_battle(self, player1, player2):
battles = self.battles.get(player1.guild.id, [])
entry = {
@ -159,9 +158,9 @@ class Interaction:
fmt = random.SystemRandom().choice(hugs)
await ctx.send(fmt.format(user.display_name))
@commands.group(invoke_without_command=True)
@commands.group(invoke_without_command=True, enabled=False)
@commands.guild_only()
@commands.cooldown(1, 180, BucketType.user)
@commands.cooldown(1, 20, BucketType.user)
@utils.custom_perms(send_messages=True)
async def battle(self, ctx, player2: discord.Member):
"""Challenges the mentioned user to a battle
@ -226,10 +225,10 @@ class Interaction:
# All we need to do is change what order the challengers are printed/added as a paramater
if random.SystemRandom().randint(0, 1):
await ctx.send(fmt.format(battleP1.mention, battleP2.mention))
await utils.update_records('battle_records', battleP1, battleP2)
await utils.update_records('battle_records', self.bot.db, battleP1, battleP2)
else:
await ctx.send(fmt.format(battleP2.mention, battleP1.mention))
await utils.update_records('battle_records', battleP2, battleP1)
await utils.update_records('battle_records', self.bot.db, battleP2, battleP1)
@commands.command()
@commands.guild_only()
@ -257,9 +256,9 @@ class Interaction:
self.battling_off(player2=battleP2)
await ctx.send("{} has chickened out! What a loser~".format(battleP2.mention))
@commands.command()
@commands.command(enabled=False)
@commands.guild_only()
@commands.cooldown(1, 180, BucketType.user)
@commands.cooldown(1, 10, BucketType.user)
@utils.custom_perms(send_messages=True)
async def boop(self, ctx, boopee: discord.Member = None, *, message=""):
"""Boops the mentioned person
@ -284,20 +283,15 @@ class Interaction:
return
key = str(booper.id)
boops = await utils.get_content('boops', key)
if boops is not None:
boops = boops['boops']
# If the booper has never booped the member provided, assure it's 0
amount = boops.get(str(boopee.id), 0) + 1
boops[str(boopee.id)] = amount
await utils.update_content('boops', {'boops': boops}, key)
else:
entry = {'member_id': str(booper.id),
'boops': {str(boopee.id): 1}}
await utils.add_content('boops', entry)
amount = 1
boops = self.bot.db.load('boops', key=key, pluck='boops') or {}
amount = boops.get(str(boopee.id), 0) + 1
entry = {
'member_id': str(booper.id),
'boops': {
str(boopee.id): amount
}
}
self.bot.db.save('boops', entry)
fmt = "{0.mention} has just booped {1.mention}{3}! That's {2} times now!"
await ctx.send(fmt.format(booper, boopee, amount, message))

View file

@ -5,9 +5,7 @@ from . import utils
from bs4 import BeautifulSoup as bs
import discord
import random
import re
import math
class Links:
@ -29,7 +27,7 @@ class Links:
url = "https://www.google.com/search"
# Turn safe filter on or off, based on whether or not this is a nsfw channel
nsfw = await utils.channel_is_nsfw(ctx.message.channel)
nsfw = await utils.channel_is_nsfw(ctx.message.channel, self.bot.db)
safe = 'off' if nsfw else 'on'
params = {'q': query,

View file

@ -3,9 +3,6 @@ from discord.ext import commands
from . import utils
from bs4 import BeautifulSoup as bs
import subprocess
import glob
import random
import re
import calendar
@ -42,7 +39,7 @@ class Miscallaneous:
if command is None:
for cmd in utils.get_all_commands(self.bot):
if not await cmd.can_run(ctx):
if not await cmd.can_run(ctx) or not cmd.enabled:
continue
cog = cmd.cog_name
@ -82,7 +79,7 @@ class Miscallaneous:
except utils.CannotPaginate as e:
await ctx.send(str(e))
else:
# Get the description for a command
# Get the description for a command
description = command.help
if description is not None:
# Split into examples, results, and the description itself based on the string
@ -108,7 +105,6 @@ class Miscallaneous:
await ctx.send(embed=embed)
@commands.command()
@utils.custom_perms(send_messages=True)
async def say(self, ctx, *, msg: str):
@ -123,49 +119,6 @@ class Miscallaneous:
except:
pass
@commands.command()
@utils.custom_perms(send_messages=True)
async def motd(self, ctx, *, date=None):
"""This command can be used to print the current MOTD (Message of the day)
This will most likely not be updated every day, however messages will still be pushed to this every now and then
EXAMPLE: !motd
RESULT: 'This is an example message of the day!'"""
if date is None:
motd = await utils.get_content('motd')
try:
# Lets set this to the first one in the list first
latest_motd = motd[0]
for entry in motd:
d = pendulum.parse(entry['date'])
# Check if the date for this entry is newer than our currently saved latest entry
if d > pendulum.parse(latest_motd['date']):
latest_motd = entry
date = latest_motd['date']
motd = latest_motd['motd']
# This will be hit if we do not have any entries for motd
except TypeError:
await ctx.send("No message of the day!")
else:
fmt = "Last updated: {}\n\n{}".format(date, motd)
await ctx.send(fmt)
else:
try:
motd = await utils.get_content('motd', str(pendulum.parse(date).date()))
date = motd['date']
motd = motd['motd']
fmt = "Message of the day for {}:\n\n{}".format(date, motd)
await ctx.send(fmt)
# This one will be hit if we return None for that day
except TypeError:
await ctx.send("No message of the day for {}!".format(date))
# This will be hit if pendulum fails to parse the date passed
except ValueError:
now = pendulum.utcnow().to_date_string()
await ctx.send("Invalid date format! Try like {}".format(now))
@commands.command()
@utils.custom_perms(send_messages=True)
async def calendar(self, ctx, month: str = None, year: int = None):
@ -255,7 +208,7 @@ class Miscallaneous:
if hasattr(self.bot, 'uptime'):
embed.add_field(name='Uptime', value=(pendulum.utcnow() - self.bot.uptime).in_words())
memory_usage = self.process.memory_full_info().uss / 1024**2
memory_usage = self.process.memory_full_info().uss / 1024 ** 2
cpu_usage = self.process.cpu_percent() / psutil.cpu_count()
embed.add_field(name='Memory Usage', value='{:.2f} MiB'.format(memory_usage))
embed.add_field(name='CPU Usage', value='{}%'.format(cpu_usage))
@ -300,18 +253,15 @@ class Miscallaneous:
await ctx.send("Use this URL to add me to a server that you'd like!\n{}"
.format(discord.utils.oauth_url(app_info.id, perms)))
@commands.command()
@commands.command(enabled=False)
@utils.custom_perms(send_messages=True)
async def joke(self, ctx):
"""Prints a random riddle
EXAMPLE: !joke
RESULT: An absolutely terrible joke."""
joke = await utils.request('http://tambal.azurewebsites.net/joke/random')
if joke is not None and 'joke' in joke:
await ctx.send(joke.get('joke'))
else:
await ctx.send("Sorry, I'm not feeling funny right now...try later")
# Currently disabled until I can find a free API
pass
@commands.command()
@utils.custom_perms(send_messages=True)

View file

@ -11,7 +11,7 @@ import asyncio
import time
import re
import logging
import traceback
from collections import deque
log = logging.getLogger()
@ -20,13 +20,16 @@ if not discord.opus.is_loaded():
class VoiceState:
def __init__(self, guild, bot):
def __init__(self, guild, bot, user_queue=False):
self.guild = guild
self.songs = Playlist(bot)
self.djs = deque()
self.dj = None
self.current = None
self.required_skips = 0
self.skip_votes = set()
self.audio_player = bot.loop.create_task(self.audio_player_task())
self.user_queue = user_queue
self.loop = bot.loop
self._volume = 50
@property
@ -50,40 +53,38 @@ class VoiceState:
else:
return self.voice.is_playing() or self.voice.is_paused()
def switch_queue_type(self):
self.songs.clear()
self.djs.clear()
self.dj = None
self.user_queue = not self.user_queue
self.skip()
def get_dj(self, member):
for x in self.djs:
if x.member.id == member.id:
return x
def skip(self):
self.skip_votes.clear()
if self.playing:
self.voice.stop()
def after(self):
self.current = None
async def audio_player_task(self):
while True:
if self.playing:
await asyncio.sleep(1)
continue
song = self.songs.peek()
if song is None:
await asyncio.sleep(1)
continue
try:
self.current = await self.songs.get_next_entry()
embed = self.current.to_embed()
embed.title = "Now playing!"
await song.channel.send(embed=embed)
except ExtractionError as e:
error = str(e).partition(" ")[2]
await song.channel.send("Failed to download {}!\nError: {}".format(song.title, error))
continue
except discord.Forbidden:
pass
except:
await song.channel.send("Failed to download {}!".format(song.title))
log.error(traceback.format_exc())
continue
def after(self, _=None):
if self.user_queue:
self.djs.append(self.dj)
fut = asyncio.run_coroutine_threadsafe(self.play_next_song(), self.loop)
fut.result()
async def play_next_song(self):
self.skip_votes.clear()
try:
await self.next_song()
except ExtractionError:
# For now lets just silently continue in the queue
# Implementation to the music notifications channel will change what we do here
return await self.play_next_song()
if self.current:
source = FFmpegPCMAudio(
self.current.filename,
before_options='-nostdin',
@ -92,6 +93,31 @@ class VoiceState:
source = PCMVolumeTransformer(source, volume=self.volume)
self.voice.play(source, after=self.after)
self.current.start_time = time.time()
else:
# If we're here what we can assume is the following took place:
# 1) The queue type is `user`
# 2) Someone joined for the first time, starting off the queue
# 3) They don't have a song in their playlist ready yet
# So what we'll do here is just call this again a few seconds later
await asyncio.sleep(2)
return await self.play_next_song()
async def next_song(self):
if not self.user_queue:
self.current = await self.songs.get_next_entry()
else:
try:
self.dj = self.djs.popleft()
except IndexError:
self.current = None
else:
self.current = await self.dj.get_next_entry()
self.djs.rotate(-1)
if self.current is None:
self.djs.remove(self.dj)
await self.next_song()
else:
self.current.requester = self.dj.member
class Music:
@ -106,16 +132,6 @@ class Music:
self.downloader = down
self.bot.downloader = down
def __unload(self):
# If this is unloaded, cancel all players and disconnect from all channels
for state in self.voice_states.values():
try:
state.audio_player.cancel()
if state.voice:
self.bot.loop.create_task(state.voice.disconnect())
except:
pass
async def queue_embed_task(self, state, channel, author):
index = 0
message = None
@ -136,7 +152,10 @@ class Music:
while True:
# Get the current queue (It might change while we're doing this)
# So do this in the while loop
queue = state.songs.entries
if state.user_queue:
queue = state.djs
else:
queue = state.songs.entries
count = len(queue)
# This means the last song was removed
if count == 0:
@ -144,8 +163,13 @@ class Music:
break
# Get the current entry
entry = queue[index]
dj = None
if state.user_queue:
dj = entry
entry = entry.peek()
# Get the entry's embed
embed = entry.to_embed()
# Set the embed's title to indicate the amount of things in the queue
count = len(queue)
embed.title = "Current Queue [{}/{}]".format(index + 1, count)
@ -201,33 +225,45 @@ class Music:
elif '\u2b06' in reaction.emoji:
# A second check just to make sure, as well as ensuring index is higher than 0
if author.guild_permissions.kick_members and index > 0:
if entry != queue[index]:
if dj and dj != queue[index]:
fmt = "`Error: Position of this entry has changed, cannot complete your action`"
elif not dj and entry != queue[index]:
fmt = "`Error: Position of this entry has changed, cannot complete your action`"
else:
# Remove the current entry
del queue[index]
# Add it one position higher
queue.insert(index - 1, entry)
if state.user_queue:
queue.insert(index - 1, dj)
else:
queue.insert(index - 1, entry)
# Lets move the index to look at the new place of the entry
index -= 1
# If down is clicked
elif '\u2b07' in reaction.emoji:
# A second check just to make sure, as well as ensuring index is lower than last
if author.guild_permissions.kick_members and index < (count - 1):
if entry != queue[index]:
if dj and dj != queue[index]:
fmt = "`Error: Position of this entry has changed, cannot complete your action`"
elif not dj and entry != queue[index]:
fmt = "`Error: Position of this entry has changed, cannot complete your action`"
else:
# Remove the current entry
del queue[index]
# Add it one position lower
queue.insert(index + 1, entry)
if state.user_queue:
queue.insert(index + 1, dj)
else:
queue.insert(index + 1, entry)
# Lets move the index to look at the new place of the entry
index += 1
# If x is clicked
elif '\u274c' in reaction.emoji:
# A second check just to make sure
if author.guild_permissions.kick_members or author == entry.requester:
if entry != queue[index]:
if dj and dj != queue[index]:
fmt = "`Error: Position of this entry has changed, cannot complete your action`"
elif not dj and entry != queue[index]:
fmt = "`Error: Position of this entry has changed, cannot complete your action`"
else:
# Simply remove the entry in place
@ -261,16 +297,19 @@ class Music:
async def add_entry(self, song, ctx):
state = self.voice_states[ctx.message.guild.id]
entry, _ = await state.songs.add_entry(song, ctx)
entry, _ = await state.songs.add_entry(song)
if not state.playing:
await state.play_next_song()
entry.requester = ctx.message.author
return entry
async def join_channel(self, channel):
async def join_channel(self, channel, text_channel):
state = self.voice_states.get(channel.guild.id)
log.info("Joining channel {} in guild {}".format(channel.id, channel.guild.id))
# Send a message letting the channel know we are attempting to join
try:
msg = await channel.send("Trying to join channel {}...".format(channel.name))
msg = await text_channel.send("Trying to join channel {}...".format(channel.name))
except discord.Forbidden:
msg = None
@ -283,7 +322,9 @@ class Music:
await channel.connect()
# If we have connnected, create our voice state
self.voice_states[channel.guild.id] = VoiceState(channel.guild, self.bot)
queue_type = self.bot.db.load('server_settings', key=channel.guild.id, pluck='queue_type')
user_queue = queue_type == "user"
self.voice_states[channel.guild.id] = VoiceState(channel.guild, self.bot, user_queue=user_queue)
# If we can send messages, edit it to let the channel know we have succesfully joined
if msg:
@ -309,7 +350,7 @@ class Music:
await channel.send("Sorry but I couldn't connect...try again?")
return False
@commands.command(pass_context=True)
@commands.command()
@commands.guild_only()
@utils.custom_perms(send_messages=True)
async def progress(self, ctx):
@ -356,7 +397,7 @@ class Music:
"voice activation`".format(channel.name))
return False
return await self.join_channel(channel)
return await self.join_channel(channel, ctx.channel)
@commands.command()
@commands.guild_only()
@ -372,6 +413,10 @@ class Music:
if ctx.message.guild.id not in self.voice_states:
if not await ctx.invoke(self.join):
return
if self.voice_states.get(ctx.message.guild.id).user_queue:
await ctx.send("The current queue type is the DJ queue. "
"Use the command {}dj to join this queue".format(ctx.prefix))
return
song = re.sub('[<>\[\]]', '', song)
if len(song) == 11:
@ -382,8 +427,6 @@ class Music:
try:
entry = await self.add_entry(song, ctx)
except asyncio.TimeoutError:
await ctx.send("You took too long!")
except LiveStreamError as e:
await ctx.send(str(e))
except WrongEntryTypeError:
@ -396,7 +439,7 @@ class Music:
# We want youtube_dl's error message, but just the first part, the actual "error"
error = error[2]
# This is colour formatting for the console...it's just going to show up as text on discord
error = error.strip("ERROR: ")
error = error.replace("ERROR: ", "")
else:
# This happens when the download just returns `None`
error = error[0]
@ -409,10 +452,10 @@ class Music:
embed = entry.to_embed()
embed.title = "Enqueued song!"
await ctx.send(embed=embed)
except (discord.Forbidden, discord.HTTPException):
except discord.Forbidden:
pass
@commands.command(pass_context=True)
@commands.command()
@commands.guild_only()
@utils.custom_perms(kick_members=True)
async def volume(self, ctx, value: int = None):
@ -431,7 +474,7 @@ class Music:
state.volume = value
await ctx.send('Set the volume to {:.0%}'.format(state.volume))
@commands.command(pass_context=True)
@commands.command()
@commands.guild_only()
@utils.custom_perms(kick_members=True)
async def pause(self, ctx):
@ -440,7 +483,7 @@ class Music:
if state and state.voice and state.voice.is_connected():
state.voice.pause()
@commands.command(pass_context=True)
@commands.command()
@commands.guild_only()
@utils.custom_perms(kick_members=True)
async def resume(self, ctx):
@ -449,7 +492,7 @@ class Music:
if state and state.voice and state.voice.is_connected():
state.voice.resume()
@commands.command(pass_context=True)
@commands.command()
@commands.guild_only()
@utils.custom_perms(kick_members=True)
async def stop(self, ctx):
@ -457,23 +500,22 @@ class Music:
This also clears the queue.
"""
state = self.voice_states.get(ctx.message.guild.id)
voice = ctx.message.guild.voice_client
if voice:
voice.stop()
await voice.disconnect(force=True)
if state:
# Stop playing whatever song is playing.
if state and state.voice:
state.voice.stop()
state.songs.clear()
# This will cancel the audio event we're using to loop through the queue
# Then erase the voice_state entirely, and disconnect from the channel
state.audio_player.cancel()
await state.voice.disconnect()
try:
del self.voice_states[ctx.message.guild.id]
except KeyError:
pass
@commands.command(pass_context=True)
@commands.command()
@commands.guild_only()
@utils.custom_perms(send_messages=True)
async def eta(self, ctx):
@ -485,7 +527,10 @@ class Music:
await ctx.send('Not playing any music right now...')
return
queue = state.songs.entries
if state.user_queue:
queue = [x.peek() for x in state.djs if x.peek()]
else:
queue = state.songs.entries
if len(queue) == 0:
await ctx.send("Nothing currently in the queue")
return
@ -506,7 +551,7 @@ class Music:
return
await ctx.send("ETA till your next play is: {0[0]}m {0[1]}s".format(divmod(round(count, 0), 60)))
@commands.command(pass_context=True)
@commands.command()
@commands.guild_only()
@utils.custom_perms(send_messages=True)
async def queue(self, ctx):
@ -515,25 +560,35 @@ class Music:
if state is None:
await ctx.send("Nothing currently in the queue")
return
# Asyncio provides no non-private way to access the queue, so we have to use _queue
_queue = state.songs.entries
if state.user_queue:
_queue = [x.peek() for x in state.djs if x.peek()]
else:
_queue = state.songs.entries
if len(_queue) == 0:
await ctx.send("Nothing currently in the queue")
else:
self.bot.loop.create_task(self.queue_embed_task(state, ctx.message.channel, ctx.message.author))
@commands.command(pass_context=True)
@commands.command()
@commands.guild_only()
@utils.custom_perms(send_messages=True)
async def queuelength(self, ctx):
"""Prints the length of the queue"""
state = self.voice_states.get(ctx.message.guild.id)
if state:
await ctx.send("There are a total of {} songs in the queue".format(len(state.songs.entries)))
else:
await ctx.send("There are no songs in the queue")
if state is None:
await ctx.send("Nothing currently in the queue")
return
@commands.command(pass_context=True)
if state.user_queue:
_queue = [x.peek() for x in state.djs if x.peek()]
else:
_queue = state.songs.entries
if len(_queue) == 0:
await ctx.send("Nothing currently in the queue")
await ctx.send("There are a total of {} songs in the queue".format(len(_queue)))
@commands.command()
@commands.guild_only()
@utils.custom_perms(send_messages=True)
async def skip(self, ctx):
@ -566,7 +621,7 @@ class Music:
else:
await ctx.send('You have already voted to skip this song.')
@commands.command(pass_context=True)
@commands.command()
@commands.guild_only()
@utils.custom_perms(kick_members=True)
async def modskip(self, ctx):
@ -579,7 +634,7 @@ class Music:
state.skip()
await ctx.send('Song has just been skipped.')
@commands.command(pass_context=True)
@commands.command()
@commands.guild_only()
@utils.custom_perms(send_messages=True)
async def playing(self, ctx):
@ -608,6 +663,38 @@ class Music:
# And send the embed
await ctx.send(embed=embed)
@commands.command()
@commands.guild_only()
@utils.custom_perms(send_messages=True)
async def dj(self, ctx):
"""Attempts to join the current DJ queue
EXAMPLE: !dj
RESULT: You are 7th on the waitlist for the queue"""
if ctx.message.guild.id not in self.voice_states:
if not await ctx.invoke(self.join):
return
state = self.voice_states.get(ctx.message.guild.id)
if not state.user_queue:
await ctx.send("The current queue type is the song queue. "
"Use the command {}play to add a song to the queue".format(ctx.prefix))
return
if state.get_dj(ctx.message.author):
await ctx.send("You are already in the DJ queue!")
else:
new_dj = self.bot.get_cog('DJEvents').djs[ctx.message.author.id]
state.djs.append(new_dj)
try:
await ctx.send("You have joined the DJ queue; there are currently {} people ahead of you".format(
state.djs.index(new_dj)))
except discord.Forbidden:
pass
if not state.playing:
await state.play_next_song()
def setup(bot):
bot.add_cog(Music(bot))

View file

@ -44,7 +44,7 @@ class Osu:
async def get_users(self):
"""A task used to 'cache' all member's and their Osu profile's"""
data = await utils.get_content('osu')
data = await self.bot.db.actual_load('osu')
if data is None:
return
@ -56,11 +56,12 @@ class Osu:
@commands.group(invoke_without_command=True)
@utils.custom_perms(send_messages=True)
async def osu(self, ctx, member: discord.Member=None):
async def osu(self, ctx, member: discord.Member = None):
"""Provides basic information about a specific user
EXAMPLE: !osu @Person
RESULT: Informationa bout that person's osu account"""
await ctx.message.channel.trigger_typing()
if member is None:
member = ctx.message.author
@ -92,6 +93,7 @@ class Osu:
EXAMPLE: !osu add username
RESULT: Links your username to your account, and allows stats to be pulled from it"""
await ctx.message.channel.trigger_typing()
author = ctx.message.author
user = await self.get_user(author, username)
if user is None:
@ -103,8 +105,7 @@ class Osu:
'osu_username': user.username
}
if not await utils.add_content('osu', entry):
await utils.update_content('osu', entry, str(author.id))
self.bot.db.save('osu', entry)
await ctx.send("I have just saved your Osu user {}".format(author.display_name))
@ -116,7 +117,7 @@ class Osu:
EXAMPLE: !osu scores @Person 5
RESULT: The top 5 maps for the user @Person"""
await ctx.message.channel.trigger_typing()
# Set the defaults before we go through our passed data to figure out what we want
limit = 5
member = ctx.message.author
@ -166,7 +167,9 @@ class Osu:
{'name': 'Length', 'value': m.total_length},
{'name': 'Score', 'value': i.score},
{'name': 'Max Combo', 'value': i.maxcombo},
{'name': 'Hits', 'value': "{}/{}/{}/{} (300/100/50/misses)".format(i.count300, i.count100, i.count50, i.countmiss), "inline": False},
{'name': 'Hits',
'value': "{}/{}/{}/{} (300/100/50/misses)".format(i.count300, i.count100, i.count50, i.countmiss),
"inline": False},
{'name': 'Perfect', 'value': "Yes" if i.perfect else "No"},
{'name': 'Rank', 'value': i.rank},
{'name': 'PP', 'value': i.pp},
@ -182,5 +185,6 @@ class Osu:
except utils.CannotPaginate as e:
await ctx.send(str(e))
def setup(bot):
bot.add_cog(Osu(bot))

View file

@ -38,14 +38,11 @@ class Overwatch:
await ctx.message.channel.trigger_typing()
user = user or ctx.message.author
ow_stats = await utils.get_content('overwatch', str(user.id))
bt = self.bot.db.load('overwatch', key=str(user.id), pluck='battletag')
if ow_stats is None:
if bt is None:
await ctx.send("I do not have this user's battletag saved!")
return
# This API sometimes takes a while to look up information, so send a message saying we're processing
bt = ow_stats['battletag']
if hero == "":
# If no hero was provided, we just want the base stats for a player
@ -114,11 +111,12 @@ class Overwatch:
return
# Now just save the battletag
entry = {'member_id': key, 'battletag': bt}
update = {'battletag': bt}
# Try adding this first, if that fails, update the saved entry
if not await utils.add_content('overwatch', entry):
await utils.update_content('overwatch', update, key)
entry = {
'member_id': key,
'battletag': bt
}
self.bot.db.save('overwatch', entry)
await ctx.send("I have just saved your battletag {}".format(ctx.message.author.mention))
@ow.command(pass_context=True, name="delete", aliases=['remove'])
@ -128,10 +126,12 @@ class Overwatch:
EXAMPLE: !ow delete
RESULT: Your battletag is no longer saved"""
if await utils.remove_content('overwatch', str(ctx.message.author.id)):
await ctx.send("I no longer have your battletag saved {}".format(ctx.message.author.mention))
else:
await ctx.send("I don't even have your battletag saved {}".format(ctx.message.author.mention))
entry = {
'member_id': str(ctx.message.author.id),
'battletag': None
}
self.bot.db.save('overwatch', entry)
await ctx.send("I no longer have your battletag saved {}".format(ctx.message.author.mention))
def setup(bot):

View file

@ -180,19 +180,6 @@ class Owner:
except discord.HTTPException as e:
await ctx.send('Unexpected error: `{}`'.format(e))
@commands.command()
@commands.check(utils.is_owner)
async def motd_push(self, ctx, *, message):
"""Used to push a new message to the message of the day"""
date = pendulum.utcnow().to_date_string()
key = date
entry = {'motd': message, 'date': date}
# Try to add this, if there's an entry for that date, lets update it to make sure only one motd is sent a day
# I should be managing this myself, more than one should not be sent in a day
if await utils.add_content('motd', entry):
await utils.update_content('motd', entry, key)
await ctx.send("New motd update for {}!".format(date))
@commands.command()
@commands.check(utils.is_owner)
async def sendtochannel(self, ctx, cid: int, *, message):

View file

@ -17,10 +17,6 @@ BASE_URL = 'https://ptvappapi.picarto.tv'
api_key = '03e26294-b793-11e5-9a41-005056984bd4'
class Picarto:
def __init__(self, bot):
self.bot = bot
@ -47,7 +43,7 @@ class Picarto:
try:
while not self.bot.is_closed():
await self.get_online_users()
picarto = await utils.filter_content('picarto', {'notifications_on': 1})
picarto = self.bot.db.load('picarto', table_filter={'notifications_on': 1})
for data in picarto:
m_id = int(data['member_id'])
url = data['picarto_url']
@ -62,17 +58,16 @@ class Picarto:
member = server.get_member(m_id)
if member is None:
continue
server_settings = await utils.get_content('server_settings', s_id)
if server_settings is not None:
channel_id = int(server_settings.get('notification_channel', s_id))
else:
channel_id = int(s_id)
channel_id = self.bot.db.load('server_settings', key=s_id,
pluck='notifications_channel') or int(s_id)
channel = server.get_channel(channel_id)
try:
await channel.send("{} has just gone live! View their stream at <{}>".format(member.display_name, data['picarto_url']))
await channel.send(
"{} has just gone live! View their stream at <{}>".format(member.display_name,
data['picarto_url']))
except discord.Forbidden:
pass
self.bot.loop.create_task(utils.update_content('picarto', {'live': 1}, str(m_id)))
self.bot.db.save('picarto', {'live': 1, 'member_id': str(m_id)})
elif not online and data['live'] == 1:
for s_id in data['servers']:
server = self.bot.get_guild(int(s_id))
@ -81,17 +76,16 @@ class Picarto:
member = server.get_member(m_id)
if member is None:
continue
server_settings = await utils.get_content('server_settings', s_id)
if server_settings is not None:
channel_id = int(server_settings.get('notification_channel', s_id))
else:
channel_id = int(s_id)
channel_id = self.bot.db.load('server_settings', key=s_id,
pluck='notifications_channel') or int(s_id)
channel = server.get_channel(channel_id)
try:
await channel.send("{} has just gone offline! View their stream next time at <{}>".format(member.display_name, data['picarto_url']))
await channel.send(
"{} has just gone offline! View their stream next time at <{}>".format(
member.display_name, data['picarto_url']))
except discord.Forbidden:
pass
self.bot.loop.create_task(utils.update_content('picarto', {'live': 0}, str(m_id)))
self.bot.db.save('picarto', {'live': 0, 'member_id': str(m_id)})
await asyncio.sleep(30)
except Exception as e:
tb = traceback.format_exc()
@ -109,13 +103,11 @@ class Picarto:
# If member is not given, base information on the author
member = member or ctx.message.author
picarto_entry = await utils.get_content('picarto', str(member.id))
if picarto_entry is None:
member_url = self.bot.db.load('picarto', key=member.id, pluck='picarto_url')
if member_url is None:
await ctx.send("That user does not have a picarto url setup!")
return
member_url = picarto_entry['picarto_url']
# Use regex to get the actual username so that we can make a request to the API
stream = re.search("(?<=picarto.tv/)(.*)", member_url).group(1)
url = BASE_URL + '/channel/{}'.format(stream)
@ -141,9 +133,9 @@ class Picarto:
# Social URL's can be given if a user wants them to show
# Print them if they exist, otherwise don't try to include them
social_links = data.get('social_urls')
social_links = data.get('social_urls', {})
for i, result in data['social_urls'].items():
for i, result in social_links.items():
embed.add_field(name=i.title(), value=result)
await ctx.send(embed=embed)
@ -183,29 +175,38 @@ class Picarto:
return
key = str(ctx.message.author.id)
entry = {'picarto_url': url,
'servers': [str(ctx.message.guild.id)],
'notifications_on': 1,
'live': 0,
'member_id': key}
if await utils.add_content('picarto', entry):
await ctx.send(
# Check if it exists first, if it does we don't want to override some of the settings
result = self.bot.db.load('picarto', key=key)
if result:
entry = {
'picarto_url': url,
'member_id': key
}
else:
entry = {
'picarto_url': url,
'servers': [str(ctx.message.guild.id)],
'notifications_on': 1,
'live': 0,
'member_id': key
}
self.bot.db.save('picarto', entry)
await ctx.send(
"I have just saved your Picarto URL {}, this guild will now be notified when you go live".format(
ctx.message.author.mention))
else:
await utils.update_content('picarto', {'picarto_url': url}, key)
await ctx.send("I have just updated your Picarto URL")
@picarto.command(name='remove', aliases=['delete'])
@utils.custom_perms(send_messages=True)
async def remove_picarto_url(self, ctx):
"""Removes your picarto URL"""
if await utils.remove_content('picarto', str(ctx.message.author.id)):
await ctx.send("I am no longer saving your picarto URL {}".format(ctx.message.author.mention))
else:
await ctx.send(
"I do not have your picarto URL added {}. You can save your picarto url with {}picarto add".format(
ctx.message.author.mention, ctx.prefix))
entry = {
'picarto_url': None,
'member_id': str(ctx.message.author.id)
}
self.bot.db.save('picarto', entry)
await ctx.send("I am no longer saving your picarto URL {}".format(ctx.message.author.mention))
@picarto.group(invoke_without_command=True)
@commands.guild_only()
@ -217,39 +218,61 @@ class Picarto:
EXAMPLE: !picarto notify
RESULT: This guild will now be notified of you going live"""
key = str(ctx.message.author.id)
result = await utils.get_content('picarto', key)
servers = self.bot.db.load('picarto', key=key, pluck='servers')
# Check if this user is saved at all
if result is None:
if servers is None:
await ctx.send(
"I do not have your Picarto URL added {}. You can save your Picarto url with !picarto add".format(
ctx.message.author.mention))
# Then check if this guild is already added as one to notify in
elif ctx.message.guild.id in result['servers']:
elif str(ctx.message.guild.id) in servers:
await ctx.send("I am already set to notify in this guild...")
else:
await utils.update_content('picarto', {'servers': r.row['servers'].append(str(ctx.message.guild.id))}, key)
servers.append(str(ctx.message.guild.id))
entry = {
'member_id': key,
'servers': servers
}
self.bot.db.save('picarto', entry)
await ctx.send("This server will now be notified if you go live")
@notify.command(name='on', aliases=['start,yes'])
@commands.guild_only()
@utils.custom_perms(send_messages=True)
async def notify_on(self, ctx):
"""Turns picarto notifications on
EXAMPLE: !picarto notify on
RESULT: Notifications are sent when you go live"""
if await utils.update_content('picarto', {'notifications_on': 1}, str(ctx.message.author.id)):
key = str(ctx.message.author.id)
result = self.bot.db.load('picarto', key=key)
if result:
entry = {
'member_id': key,
'notifications_on': 1
}
self.bot.db.save('picarto', entry)
await ctx.send("I will notify if you go live {}, you'll get a bajillion followers I promise c:".format(
ctx.message.author.mention))
else:
await ctx.send("I can't notify if you go live if I don't know your picarto URL yet!")
@notify.command(name='off', aliases=['stop,no'], pass_context=True)
@notify.command(name='off', aliases=['stop,no'])
@commands.guild_only()
@utils.custom_perms(send_messages=True)
async def notify_off(self, ctx):
"""Turns picarto notifications off
EXAMPLE: !picarto notify off
RESULT: No more notifications sent when you go live"""
if await utils.update_content('picarto', {'notifications_on': 0}, str(ctx.message.author.id)):
key = str(ctx.message.author.id)
result = self.bot.db.load('picarto', key=key)
if result:
entry = {
'member_id': key,
'notifications_on': 0
}
self.bot.db.save('picarto', entry)
await ctx.send(
"I will not notify if you go live anymore {}, "
"are you going to stream some lewd stuff you don't want people to see?~".format(

337
cogs/playlist.py Normal file
View file

@ -0,0 +1,337 @@
import discord
import asyncio
from discord.ext import commands
from . import utils
class Playlist:
"""Used to manage user playlists"""
def __init__(self, bot):
self.bot = bot
async def get_response(self, ctx, question):
# Save our simple variables
channel = ctx.message.channel
author = ctx.message.author
# Create our check function used to ensure the author and channel are the only possible message we get
check = lambda m: m.author == author and m.channel == channel
try:
# Ask our question, wait 60 seconds for a response
my_msg = await ctx.send(question)
response = await self.bot.wait_for('message', check=check, timeout=60)
except asyncio.TimeoutError:
# If we timeout, let them know and return None
await ctx.send("You took too long. I'm impatient, don't make me wait")
return None
else:
# If succesful try to delete the message we sent, and the response
try:
await my_msg.delete()
await response.delete()
except discord.Forbidden:
pass
# For our case here, everything needs to be lowered and stripped, so just do this now
return response.content.lower().strip()
async def get_info(self, song_url):
try:
# Just download the information
info = await self.bot.downloader.extract_info(self.bot.loop, song_url, download=False)
except Exception as e:
# If we fail, it's possibly due to an incorrect detection as a URL instead of a search
if "gaierror" in str(e) or "unknown url type" in str(e):
# So just force a search
song_url = "ytsearch:" + song_url
info = await self.bot.downloader.extract_info(self.bot.loop, song_url, download=False)
else:
# Otherwise if we fail, we just want to return None
return None
# If we detected a search, get the first entry in the results
if info.get('_type', None) == 'playlist':
if info.get('extractor') == 'youtube:search':
if len(info['entries']) == 0:
return None
else:
info = info['entries'][0]
song_url = info['webpage_url']
# If we are successful, create the entry we'll need to add to the playlist database, and return it
if info:
return {
'title': info.get('title', 'Untitled'),
'url': song_url
}
else:
return None
async def add_to_playlist(self, author, playlist, url):
# Simply get the database entry for this user's playlist
key = str(author.id)
playlist = playlist.lower().strip()
playlists = self.bot.db.load('user_playlists', key=key, pluck='playlists') or []
entry = await self.get_info(url)
# Search through, find the name that matches the playlist
if entry:
for pl in playlists:
if pl['name'] == playlist:
# If we find it, add the song entry to the songs
pl['songs'].append(entry)
# Create the json needed to save to the database, and save
update = {
'member_id': key,
'playlists': playlists
}
self.bot.db.save('user_playlists', update)
return True
async def rename_playlist(self, author, old_name, new_name):
# Simply get the database entry for this user's playlist
key = str(author.id)
old_name = old_name.lower().strip()
new_name = new_name.lower().strip()
playlists = self.bot.db.load('user_playlists', key=key, pluck='playlists') or []
# Find the playlist that matches the old name
for pl in playlists:
if pl['name'] == old_name:
# Once found, change the name, update the json, save
pl['name'] = new_name
update = {
'member_id': key,
'playlists': playlists
}
self.bot.db.save('user_playlists', update)
return True
async def remove_from_playlist(self, author, playlist, index):
# Simply get the database entry for this user's playlist
key = str(author.id)
playlist = playlist.lower().strip()
playlists = self.bot.db.load('user_playlists', key=key, pluck='playlists') or []
# Loop through till we find the playlist that matches
for pl in playlists:
if pl['name'] == playlist:
song = pl['songs'][index]
# Once found, remove the matching song, update json, save
pl['songs'].remove(song)
update = {
'member_id': key,
'playlists': playlists
}
self.bot.db.save('user_playlists', update)
return song
async def update_dj_for_member(self, member):
music = self.bot.get_cog('Music')
if music:
for state in music.voice_states.values():
dj = state.get_dj(member)
if dj:
# We want to add a slight delay to this, because our database method launches a task to update
# Before we update what is live, we need the information saved in (at least the cache) the database
await asyncio.sleep(2)
self.bot.loop.create_task(dj.resolve_playlist())
@commands.command()
@utils.custom_perms(send_messages=True)
async def playlists(self, ctx):
"""Displays the playlists you have
EXAMPLE: !playlists
RESULT: All your playlists"""
# Get all the author's playlists
playlists = self.bot.db.load('user_playlists', key=ctx.message.author.id, pluck='playlists')
if playlists:
# Create the entries for our paginator detailing the name of the playlist, and the number of songs in it
entries = [
"{} ({} songs)".format(x['name'], len(x['songs'])) if not x.get('active')
else "{} ({} songs) - Active playlist".format(x['name'], len(x['songs']))
for x in playlists
]
try:
# And paginate
pages = utils.Pages(self.bot, message=ctx.message, entries=entries)
await pages.paginate()
except utils.CannotPaginate as e:
await ctx.send(str(e))
else:
await ctx.send("You do not have any playlists")
@commands.group(invoke_without_command=True)
@utils.custom_perms(send_messages=True)
async def playlist(self, ctx, *, playlist_name):
"""Used to view your playlists
EXAMPLE: !playlist Playlist 2
RESULT: Displays the songs in your playlist called "Playlist 2" """
playlist_name = playlist_name.lower().strip()
playlists = self.bot.db.load('user_playlists', key=ctx.message.author.id, pluck='playlists')
try:
# Get the playlist if the name matches
playlist = [x for x in playlists if playlist_name == x['name']][0]
# Create the entries for our paginator just based on the title of the songs in the playlist
entries = ["{}".format(x['title']) for x in playlist['songs']]
# Paginate
pages = utils.Pages(self.bot, message=ctx.message, entries=entries)
await pages.paginate()
except (IndexError, TypeError, KeyError):
await ctx.send("You do not have a playlist named {}!".format(playlist_name))
except utils.CannotPaginate as e:
await ctx.send(str(e))
@playlist.command(name='create')
@utils.custom_perms(send_messages=True)
async def _pl_create(self, ctx, *, name):
"""Used to create a new playlist
EXAMPLE: !playlist create Playlist
RESULT: A new playlist called Playlist"""
key = str(ctx.message.author.id)
playlists = self.bot.db.load('user_playlists', key=key, pluck='playlists') or []
# Create the new playlist entry
entry = {
'name': name.lower().strip(),
'songs': []
}
# Check to make sure that there isn't a playlist with the same name
names = [x['name'] for x in playlists]
if name in names:
await ctx.send('You already have a playlist called {}'.format(name))
# Otherwise add this new playlist, and save
else:
# This is here to set the first playlist we create as the active one.
# If someone has a playlist already, we don't want to change which is the active one
# If they don't have any, then we want to set our first one as the active one
entry['active'] = len(playlists) == 0
playlists.append(entry)
update = {
'member_id': key,
'playlists': playlists
}
self.bot.db.save('user_playlists', update)
await ctx.send("You have just created a new playlist called {}".format(name))
@playlist.command(name='edit')
@utils.custom_perms(send_messages=True)
async def _pl_edit(self, ctx):
"""A command used to edit a current playlist
The available ways to edit a playlist are to rename, add a song, remove a song, or delete the playlist
EXAMPLE: !playlist edit
RESULT: A followalong asking for what you need"""
# Load the playlists for the author
key = str(ctx.message.author.id)
playlists = self.bot.db.load('user_playlists', key=key, pluck='playlists') or []
# Also create a list of the names for easy comparision
names = [x['name'] for x in playlists]
if not playlists:
await ctx.send("You have no playlists to edit!")
return
# Show the playlists we have, and ask which to choose from
await ctx.invoke(self.playlists)
question = "Please provide what playlist you would like to edit, the playlists you have available are above."
playlist = await self.get_response(ctx, question)
if not playlist:
return
if playlist not in names:
await ctx.send("You do not have a playlist named {}!".format(playlist))
return
q1 = "How would you like to edit {}? Choices are `add`, `remove`, `rename`, `delete`, or `activate`.\n" \
"**add** - Adds a song to this playlist\n" \
"**remove** - Removes a song from this playlist\n" \
"**rename** - Changes the name of this playlist\n" \
"**delete** - Deletes this playlist\n" \
"**activate** - Sets this as the active playlist\n\n" \
"Type **quit** to stop editing this playlist".format(playlist)
# Lets create a list of the messages we'll delete after
delete_msgs = []
# We want to loop this in order to continue editing, till the user is done
while True:
response = await self.get_response(ctx, q1)
if 'add' in response:
# Ask the user what song to add, get the response, add it
question = "What is the song you would like to add to {}?".format(playlist)
response = await self.get_response(ctx, question)
# If we didn't get a response, just continue with the loop, we have no need to say anything
# The "error" message is sent with our `get_response` helper method
if response:
await ctx.message.channel.trigger_typing()
await self.add_to_playlist(ctx.message.author, playlist, response)
delete_msgs.append(await ctx.send("Successfully added song {} to playlist {}".format(response,
playlist)))
elif 'remove' in response:
await ctx.invoke(self.playlist, playlist_name=playlist)
question = "Please provide just the number of the song you want to delete"
try:
response = await self.get_response(ctx, question)
if response:
num = int(response) - 1
song = await self.remove_from_playlist(ctx.author, playlist, num)
await ctx.send("Successfully removed {} from {}".format(song['title'], playlist))
except (ValueError, IndexError):
delete_msgs.append(await ctx.send("Please provide just the number of the song you want to delete "
"next time!"))
elif 'delete' in response:
playlists = [x for x in playlists if x['name'] != playlist]
entry = {
'member_id': str(key),
'playlists': playlists
}
self.bot.db.save('user_playlists', entry)
delete_msgs.append(await ctx.send("Successfully deleted playlist {}".format(playlist)))
await ctx.send("Finished editing {}".format(playlist))
break
elif 'rename' in response:
question = "What would you like to rename the playlist {} to?".format(playlist)
new_name = await self.get_response(ctx, question)
if new_name:
await self.rename_playlist(ctx.message.author, playlist, new_name)
playlist = new_name
delete_msgs.append(await ctx.send("Successfully renamed {} to {}!".format(playlist, new_name)))
elif 'activate' in response:
for x in playlists:
x['active'] = x['name'] == playlist
entry = {
'member_id': str(key),
'playlists': playlists
}
self.bot.db.save('user_playlists', entry)
# Now we have edited the user's actual playlist...but we need to
delete_msgs.append(await ctx.send("{} is now your active playlist".format(playlist)))
elif 'quit' in response:
await ctx.send("Finished editing {}".format(playlist))
break
else:
delete_msgs.append(await ctx.send("That is not a valid option!"))
# After whatever has been edited, we need to update the live DJ's
await self.update_dj_for_member(ctx.message.author)
if len(delete_msgs) == 1:
await delete_msgs[0].delete()
elif len(delete_msgs) > 1:
await ctx.message.channel.delete_messages(delete_msgs)
def setup(bot):
bot.add_cog(Playlist(bot))

View file

@ -1,9 +1,11 @@
from discord.ext import commands
from . import utils
def to_keycap(c):
return '\N{KEYCAP TEN}' if c == 10 else str(c) + '\u20e3'
class Poll:
def __init__(self, message):
self.message = message
@ -68,7 +70,6 @@ class Polls:
if poll:
await poll.remove_other_reaction(reaction, user)
@commands.command(pass_context=True)
@commands.guild_only()
@utils.custom_perms(send_messages=True)

View file

@ -8,6 +8,7 @@ import pendulum
import re
import asyncio
import traceback
import rethinkdb as r
class Raffle:
@ -28,16 +29,20 @@ class Raffle:
async def check_raffles(self):
# This is used to periodically check the current raffles, and see if they have ended yet
# If the raffle has ended, we'll pick a winner from the entrants
raffles = await utils.get_content('raffles')
raffles = self.bot.db.load('raffles')
if raffles is None:
return
for raffle in raffles:
server = self.bot.get_guild(int(raffle['server_id']))
title = raffle['title']
entrants = raffle['entrants']
raffle_id = raffle['id']
# Check to see if this cog can find the server in question
if server is None:
await self.bot.db.query(r.table('raffles').get(raffle_id).delete())
continue
now = pendulum.utcnow()
@ -47,10 +52,6 @@ class Raffle:
if expires > now:
continue
title = raffle['title']
entrants = raffle['entrants']
raffle_id = raffle['id']
# Make sure there are actually entrants
if len(entrants) == 0:
fmt = 'Sorry, but there were no entrants for the raffle `{}`!'.format(title)
@ -72,21 +73,17 @@ class Raffle:
else:
fmt = 'The raffle `{}` has just ended! The winner is {}!'.format(title, winner.display_name)
# No matter which one of these matches were met, the raffle has ended and we want to remove it
# We don't have to wait for it however, so create a task for it
self.bot.loop.create_task(utils.remove_content('raffles', raffle_id))
server_settings = await utils.get_content('server_settings', str(server.id))
if server_settings is None:
channel = self.bot.get_channel(server.id)
else:
channel_id = server_settings.get('notification_channel', server.id)
channel = self.bot.get_channel(channel_id)
channel_id = self.bot.db.load('server_settings', key=server.id,
pluck='notifications_channel') or server.id
channel = self.bot.get_channel(channel_id)
try:
await channel.send(fmt)
except (discord.Forbidden, AttributeError):
pass
# No matter which one of these matches were met, the raffle has ended and we want to remove it
await self.bot.db.query(r.table('raffles').get(raffle_id).delete())
@commands.command()
@commands.guild_only()
@utils.custom_perms(send_messages=True)
@ -96,11 +93,16 @@ class Raffle:
EXAMPLE: !raffles
RESULT: A list of the raffles setup on this server"""
r_filter = {'server_id': str(ctx.message.guild.id)}
raffles = await utils.filter_content('raffles', r_filter)
raffles = self.bot.db.load('raffles', table_filter=r_filter)
if raffles is None:
await ctx.send("There are currently no raffles setup on this server!")
return
# For EVERY OTHER COG, when we get one result, it is nice to have it return that exact object
# This is the only cog where that is different, so just to make this easier lets throw it
# back in a one-indexed list, for easier parsing
if isinstance(raffles, dict):
raffles = [raffles]
fmt = "\n\n".join("**Raffle:** {}\n**Title:** {}\n**Total Entrants:** {}\n**Ends:** {} UTC".format(
num + 1,
raffle['title'],
@ -122,12 +124,16 @@ class Raffle:
r_filter = {'server_id': str(ctx.message.guild.id)}
author = ctx.message.author
raffles = await utils.filter_content('raffles', r_filter)
raffles = self.bot.db.load('raffles', table_filter=r_filter)
if raffles is None:
await ctx.send("There are currently no raffles setup on this server!")
return
raffle_count = len(raffles)
if isinstance(raffles, list):
raffle_count = len(raffles)
elif isinstance(raffles, dict):
raffles = [raffles]
raffle_count = 1
# There is only one raffle, so use the first's info
if raffle_count == 1:
@ -138,8 +144,11 @@ class Raffle:
return
entrants.append(str(author.id))
update = {'entrants': entrants}
await utils.update_content('raffles', update, raffles[0]['id'])
update = {
'entrants': entrants,
'id': raffles[0]['id']
}
self.bot.db.save('raffles', update)
await ctx.send("{} you have just entered the raffle!".format(author.mention))
# Otherwise, make sure the author gave a valid raffle_id
elif raffle_id in range(raffle_count - 1):
@ -152,13 +161,17 @@ class Raffle:
entrants.append(str(author.id))
# Since we have no good thing to filter things off of, lets use the internal rethinkdb id
update = {'entrants': entrants}
await utils.update_content('raffles', update, raffles[raffle_id]['id'])
update = {
'entrants': entrants,
'id': raffles[0]['id']
}
self.bot.db.save('raffles', update)
await ctx.send("{} you have just entered the raffle!".format(author.mention))
else:
fmt = "Please provide a valid raffle ID, as there are more than one setup on the server! " \
"There are currently `{}` raffles running, use {}raffles to view the current running raffles".format(
raffle_count, ctx.prefix)
raffle_count, ctx.prefix)
await ctx.send(fmt)
@raffle.command(pass_context=True, name='create', aliases=['start', 'begin', 'add'])
@ -198,6 +211,7 @@ class Raffle:
return re.search("\d+ (minutes?|hours?|days?|weeks?|months?)", m.content.lower()) is not None
else:
return False
try:
msg = await self.bot.wait_for('message', timeout=120, check=check)
except asyncio.TimeoutError:
@ -238,14 +252,16 @@ class Raffle:
expires = now.add(**payload)
# Now we're ready to add this as a new raffle
entry = {'title': title,
'expires': expires.to_datetime_string(),
'entrants': [],
'author': str(author.id),
'server_id': str(server.id)}
entry = {
'title': title,
'expires': expires.to_datetime_string(),
'entrants': [],
'author': str(author.id),
'server_id': str(server.id)
}
# We don't want to pass a filter to this, because we can have multiple raffles per server
await utils.add_content('raffles', entry)
self.bot.db.save('raffles', entry)
await ctx.send("I have just saved your new raffle!")

View file

@ -324,12 +324,8 @@ class Roles:
author = ctx.message.author
key = str(ctx.message.guild.id)
server_settings = await utils.get_content('server_settings', key)
self_assignable_roles = self.bot.db.load('server_settings', key=key, pluck='self_assignable_roles') or []
if server_settings is None:
await ctx.send("There are no self-assignable roles on this server")
return
self_assignable_roles = server_settings.get('self_assignable_roles', [])
if len(self_assignable_roles) == 0:
await ctx.send("There are no self-assignable roles on this server")
return
@ -361,12 +357,8 @@ class Roles:
author = ctx.message.author
key = str(ctx.message.guild.id)
server_settings = await utils.get_content('server_settings', key)
self_assignable_roles = self.bot.db.load('server_settings', key=key, pluck='self_assignable_roles') or []
if server_settings is None:
await ctx.send("There are no self-assignable roles on this server")
return
self_assignable_roles = server_settings.get('self_assignable_roles', [])
if len(self_assignable_roles) == 0:
await ctx.send("There are no self-assignable roles on this server")
return
@ -394,17 +386,16 @@ class Roles:
RESULT: Allows users to self-assign the roles Member, and NSFW"""
roles = [str(r.id) for r in role]
key = str(ctx.message.guild.id)
server_settings = await utils.get_content('server_settings', key)
if server_settings is None:
entry = {'server_id': key, 'self_assignable_roles': roles}
await utils.add_content('server_settings', entry)
else:
self_assignable_roles = server_settings.get('self_assignable_roles', [])
self_assignable_roles.extend(roles)
self_assignable_roles = list(set(self_assignable_roles))
update = {'self_assignable_roles': self_assignable_roles}
await utils.update_content('server_settings', update, key)
self_assignable_roles = self.bot.db.load('server_settings', key=key, pluck='self_assignable_roles') or []
self_assignable_roles.extend(roles)
self_assignable_roles = list(set(self_assignable_roles))
entry = {
'server_id': key,
'self_assignable_roles': self_assignable_roles
}
self.bot.db.save('server_settings', entry)
if len(roles) == 1:
fmt = "Successfully added {} as a self-assignable role".format(role[0].name)
@ -423,12 +414,7 @@ class Roles:
EXAMPLE: !assigns list
RESUL: A list of all the self-assignable roles"""
key = str(ctx.message.guild.id)
server_settings = await utils.get_content('server_settings', key)
if server_settings is None:
await ctx.send("There are no self-assignable roles on this server")
return
self_assignable_roles = server_settings.get('self_assignable_roles', [])
self_assignable_roles = self.bot.db.load('server_settings', key=key, pluck='self_assignable_roles') or []
if len(self_assignable_roles) == 0:
await ctx.send("There are no self-assignable roles on this server")
return
@ -458,12 +444,7 @@ class Roles:
EXAMPLE: !assigns remove Member NSFW
RESULT: Removes the ability for users to self-assign the roles Member, and NSFW"""
key = str(ctx.message.guild.id)
server_settings = await utils.get_content('server_settings', key)
if server_settings is None:
await ctx.send("There are no self-assignable roles on this server")
return
self_assignable_roles = server_settings.get('self_assignable_roles', [])
self_assignable_roles = self.bot.db.load('server_settings', key=key, pluck='self_assignable_roles') or []
if len(self_assignable_roles) == 0:
await ctx.send("There are no self-assignable roles on this server")
return
@ -478,8 +459,11 @@ class Roles:
else:
fmt += "\n{} is no longer a self-assignable role".format(r.name)
update = {'self_assignable_roles': self_assignable_roles}
await utils.update_content('server_settings', update, key)
update = {
'self_assignable_roles': self_assignable_roles,
'server_id': key
}
self.bot.db.save('server_settings', update)
await ctx.send(fmt)

View file

@ -65,7 +65,7 @@ class Stats:
await ctx.send("`{}` is not a valid command".format(command))
return
command_stats = await utils.get_content('command_usage', cmd.qualified_name)
command_stats = self.bot.db.load('command_usage', key=cmd.qualified_name)
if command_stats is None:
await ctx.send("That command has never been used! You know I worked hard on that! :c")
return
@ -103,7 +103,7 @@ class Stats:
if re.search('(author|me)', option):
author = ctx.message.author
# First lets get all the command usage
command_stats = await utils.get_content('command_usage')
command_stats = self.bot.db.load('command_usage')
# Now use a dictionary comprehension to get just the command name, and usage
# Based on the author's usage of the command
stats = {data['command']: data['member_usage'].get(str(author.id)) for data in command_stats
@ -125,7 +125,7 @@ class Stats:
elif re.search('server', option):
# This is exactly the same as above, except server usage instead of member usage
server = ctx.message.guild
command_stats = await utils.get_content('command_usage')
command_stats = self.bot.db.load('command_usage')
stats = {data['command']: data['server_usage'].get(str(server.id)) for data in command_stats
if data['server_usage'].get(str(server.id), 0) > 0}
sorted_stats = sorted(stats.items(), key=lambda x: x[1], reverse=True)
@ -148,7 +148,7 @@ class Stats:
EXAMPLE: !mostboops
RESULT: You've booped @OtherPerson 351253897120935712093572193057310298 times!"""
boops = await utils.get_content('boops', str(ctx.message.author.id))
boops = self.bot.db.load('boops', key=ctx.message.author.id)
if boops is None:
await ctx.send("You have not booped anyone {} Why the heck not...?".format(ctx.message.author.mention))
return
@ -182,7 +182,7 @@ class Stats:
RESULT: The list of your booped members!"""
await ctx.message.channel.trigger_typing()
boops = await utils.get_content('boops', str(ctx.message.author.id))
boops = self.bot.db.load('boops', key=ctx.message.author.id)
if boops is None:
await ctx.send("You have not booped anyone {} Why the heck not...?".format(ctx.message.author.mention))
return
@ -220,7 +220,7 @@ class Stats:
# Create a list of the ID's of all members in this server, for comparison to the records saved
server_member_ids = [member.id for member in ctx.message.guild.members]
battles = await utils.get_content('battle_records')
battles = self.bot.db.load('battle_records')
if battles is None or len(battles) == 0:
await ctx.send("No one has battled on this server!")
@ -256,9 +256,10 @@ class Stats:
# For this one, we don't want to pass a filter, as we do need all battle records
# We need this because we want to make a comparison for overall rank
all_members = await utils.get_content('battle_records')
all_members = self.bot.db.load('battle_records')
if all_members is None or len(all_members) == 0:
await ctx.send("You have not battled anyone!")
await ctx.send("That user has not battled yet!")
return
# Make a list comprehension to just check if the user has battled
if len([entry for entry in all_members if entry['member_id'] == str(member.id)]) == 0:

View file

@ -4,7 +4,7 @@ import discord
from . import utils
import asyncio
import rethinkdb as r
class Tags:
"""This class contains all the commands for custom tags"""
@ -20,9 +20,9 @@ class Tags:
EXAMPLE: !tags
RESULT: All tags setup on this server"""
tags = await utils.get_content('tags', str(ctx.message.guild.id))
if tags and len(tags['tags']) > 0:
entries = [t['trigger'] for t in tags['tags']]
tags = self.bot.db.load('tags', key=ctx.message.guild.id, pluck='tags')
if tags:
entries = [t['trigger'] for t in tags]
pages = utils.Pages(self.bot, message=ctx.message, entries=entries)
await pages.paginate()
else:
@ -36,16 +36,16 @@ class Tags:
EXAMPLE: !mytags
RESULT: All your tags setup on this server"""
tags = await utils.get_content('tags', str(ctx.message.guild.id))
if not tags:
await ctx.send("There are no tags setup on this server!")
else:
entries = [t['trigger'] for t in tags['tags'] if t['author'] == str(ctx.message.author.id)]
tags = self.bot.db.load('tags', key=ctx.message.guild.id, pluck='tags')
if tags:
entries = [t['trigger'] for t in tags if t['author'] == str(ctx.message.author.id)]
if len(entries) == 0:
await ctx.send("You have no tags setup on this server!")
else:
pages = utils.Pages(self.bot, message=ctx.message, entries=entries)
await pages.paginate()
else:
await ctx.send("There are no tags setup on this server!")
@commands.group(invoke_without_command=True)
@commands.guild_only()
@ -57,9 +57,9 @@ class Tags:
EXAMPLE: !tag butts
RESULT: Whatever you setup for the butts tag!!"""
tag = tag.lower().strip()
tags = await utils.get_content('tags', str(ctx.message.guild.id))
if tags and len(tags['tags']) > 0:
for t in tags['tags']:
tags = self.bot.db.load('tags', key=ctx.message.guild.id, pluck='tags')
if tags:
for t in tags:
if t['trigger'].lower().strip() == tag:
await ctx.send("\u200B{}".format(t['result']))
return
@ -67,7 +67,6 @@ class Tags:
else:
await ctx.send("There are no tags setup on this server!")
@tag.command(name='add', aliases=['create', 'setup'])
@commands.guild_only()
@utils.custom_perms(send_messages=True)
@ -76,8 +75,10 @@ class Tags:
EXAMPLE: !tag add
RESULT: A follow-along in order to create a new tag"""
def check(m):
return m.channel == ctx.message.channel and m.author == ctx.message.author and len(m.content) > 0
my_msg = await ctx.send("Ready to setup a new tag! What do you want the trigger for the tag to be?")
try:
@ -92,12 +93,14 @@ class Tags:
await ctx.send("Please keep tag triggers under 100 characters")
return
elif trigger in forbidden_tags:
await ctx.send("Sorry, but your tag trigger was detected to be forbidden. Current forbidden tag triggers are: \n{}".format("\n".join(forbidden_tags)))
await ctx.send(
"Sorry, but your tag trigger was detected to be forbidden. "
"Current forbidden tag triggers are: \n{}".format("\n".join(forbidden_tags)))
return
tags = await utils.get_content('tags', str(ctx.message.guild.id))
if tags and len(tags['tags']) > 0:
for t in tags['tags']:
tags = self.bot.db.load('tags', key=ctx.message.guild.id, pluck='tags') or []
if tags:
for t in tags:
if t['trigger'].lower().strip() == trigger:
await ctx.send("There is already a tag setup called {}!".format(trigger))
return
@ -112,7 +115,9 @@ class Tags:
await ctx.send("You can't create a tag with {}!".format(trigger))
return
my_msg = await ctx.send("Alright, your new tag can be called with {}!\n\nWhat do you want to be displayed with this tag?".format(trigger))
my_msg = await ctx.send(
"Alright, your new tag can be called with {}!\n\nWhat do you want to be displayed with this tag?".format(
trigger))
try:
msg = await self.bot.wait_for("message", check=check, timeout=60)
@ -133,13 +138,12 @@ class Tags:
'trigger': trigger,
'result': result
}
tags.append(tag)
entry = {
'server_id': str(ctx.message.guild.id),
'tags': [tag]
'tags': tags
}
key = str(ctx.message.guild.id)
if not await utils.add_content('tags', entry):
await utils.update_content('tags', {'tags': r.row['tags'].append(tag)}, key)
self.bot.db.save('tags', entry)
await ctx.send("I have just setup a new tag for this server! You can call your tag with {}".format(trigger))
@tag.command(name='edit')
@ -149,16 +153,17 @@ class Tags:
"""This will allow you to edit a tag that you have created
EXAMPLE: !tag edit this tag
RESULT: I'll ask what you want the new result to be"""
key = str(ctx.message.guild.id)
tags = await utils.get_content('tags', key)
tags = self.bot.db.load('tags', key=ctx.message.guild.id, pluck='tags')
def check(m):
return m.channel == ctx.message.channel and m.author == ctx.message.author and len(m.content) > 0
if tags and len(tags['tags']) > 0:
for i, t in enumerate(tags['tags']):
if tags:
for i, t in enumerate(tags):
if t['trigger'] == tag:
if t['author'] == str(ctx.message.author.id):
my_msg = await ctx.send("Alright, what do you want the new result for the tag {} to be".format(tag))
my_msg = await ctx.send(
"Alright, what do you want the new result for the tag {} to be".format(tag))
try:
msg = await self.bot.wait_for("message", check=check, timeout=60)
except asyncio.TimeoutError:
@ -166,13 +171,17 @@ class Tags:
return
new_tag = t.copy()
new_tag['result'] = msg.content
tags['tags'][i] = new_tag
tags[i] = new_tag
try:
await my_msg.delete()
await msg.delete()
except discord.Forbidden:
pass
await utils.update_content('tags', tags, key)
entry = {
'server_id': str(ctx.message.guild.id),
'tags': tags
}
self.bot.db.save('tags', entry)
await ctx.send("Alright, the tag {} has been updated".format(tag))
return
else:
@ -182,7 +191,6 @@ class Tags:
else:
await ctx.send("There are no tags setup on this server!")
@tag.command(name='delete', aliases=['remove', 'stop'])
@commands.guild_only()
@utils.custom_perms(send_messages=True)
@ -192,14 +200,18 @@ class Tags:
EXAMPLE: !tag delete stupid_tag
RESULT: Deletes that stupid tag"""
key = str(ctx.message.guild.id)
tags = await utils.get_content('tags', key)
if tags and len(tags['tags']) > 0:
for t in tags['tags']:
if t['trigger'] == tag:
if ctx.message.author.permissions_in(ctx.message.channel).manage_guild or str(ctx.message.author.id) == t['author']:
tags['tags'].remove(t)
await utils.update_content('tags', tags, key)
tags = self.bot.db.load('tags', key=ctx.message.guild.id, pluck='tags')
if tags:
for t in tags:
if t['trigger'].lower().strip() == tag:
if ctx.message.author.permissions_in(ctx.message.channel).manage_guild or str(
ctx.message.author.id) == t['author']:
tags.remove(t)
entry = {
'server_id': str(ctx.message.guild.id),
'tags': tags
}
self.bot.db.save('tags', entry)
await ctx.send("I have just removed the tag {}".format(tag))
else:
await ctx.send("You don't own that tag! You can't remove it!")

View file

@ -191,7 +191,7 @@ class TicTacToe:
await ctx.send("{} has won this game of TicTacToe, better luck next time {}".format(winner.display_name,
loser.display_name))
# Handle updating ratings based on the winner and loser
await utils.update_records('tictactoe', winner, loser)
await utils.update_records('tictactoe', self.bot.db, winner, loser)
# This game has ended, delete it so another one can be made
del self.boards[ctx.message.guild.id]
else:

View file

@ -44,7 +44,7 @@ class Twitch:
# Loop through as long as the bot is connected
try:
while not self.bot.is_closed():
twitch = await utils.filter_content('twitch', {'notifications_on': 1})
twitch = self.bot.db.load('twitch', table_filter={'notifications_on': 1})
for data in twitch:
m_id = int(data['member_id'])
url = data['twitch_url']
@ -59,11 +59,8 @@ class Twitch:
member = server.get_member(m_id)
if member is None:
continue
server_settings = await utils.get_content('server_settings', s_id)
if server_settings is not None:
channel_id = int(server_settings.get('notification_channel', s_id))
else:
channel_id = int(s_id)
channel_id = self.bot.db.load('server_settings', key=s_id,
pluck='notifications_channel') or int(s_id)
channel = server.get_channel(channel_id)
if channel is None:
continue
@ -71,7 +68,7 @@ class Twitch:
await channel.send("{} has just gone live! View their stream at <{}>".format(member.display_name, data['twitch_url']))
except discord.Forbidden:
pass
self.bot.loop.create_task(utils.update_content('twitch', {'live': 1}, str(m_id)))
self.bot.db.save('twitch', {'live': 1, 'member_id': str(m_id)})
elif not online and data['live'] == 1:
for s_id in data['servers']:
server = self.bot.get_guild(int(s_id))
@ -80,17 +77,14 @@ class Twitch:
member = server.get_member(m_id)
if member is None:
continue
server_settings = await utils.get_content('server_settings', s_id)
if server_settings is not None:
channel_id = int(server_settings.get('notification_channel', s_id))
else:
channel_id = int(s_id)
channel_id = self.bot.db.load('server_settings', key=s_id,
pluck='notifications_channel') or int(s_id)
channel = server.get_channel(channel_id)
try:
await channel.send("{} has just gone offline! View their stream next time at <{}>".format(member.display_name, data['twitch_url']))
except discord.Forbidden:
pass
self.bot.loop.create_task(utils.update_content('twitch', {'live': 0}, str(m_id)))
self.bot.db.save('twitch', {'live': 0, 'member_id': str(m_id)})
await asyncio.sleep(30)
except Exception as e:
tb = traceback.format_exc()
@ -110,12 +104,11 @@ class Twitch:
if member is None:
member = ctx.message.author
result = await utils.get_content('twitch', str(member.id))
if result is None:
url = self.bot.db.load('twitch', key=member.id, pluck='twitch_url')
if url is None:
await ctx.send("{} has not saved their twitch URL yet!".format(member.name))
return
url = result['twitch_url']
user = re.search("(?<=twitch.tv/)(.*)", url).group(1)
twitch_url = "https://api.twitch.tv/kraken/channels/{}".format(user)
payload = {'client_id': self.key}
@ -170,18 +163,23 @@ class Twitch:
return
key = str(ctx.message.author.id)
entry = {'twitch_url': url,
'servers': [str(ctx.message.guild.id)],
'notifications_on': 1,
'live': 0,
'member_id': key}
update = {'twitch_url': url}
# Check to see if this user has already saved a twitch URL
# If they have, update the URL, otherwise create a new entry
# Assuming they're not live, and notifications should be on
if not await utils.add_content('twitch', entry):
await utils.update_content('twitch', update, key)
# Check if it exists first, if it does we don't want to override some of the settings
result = self.bot.db.load('twitch', key=key)
if result:
entry = {
'twitch_url': url,
'member_id': key
}
else:
entry = {
'twitch_url': url,
'servers': [str(ctx.message.guild.id)],
'notifications_on': 1,
'live': 0,
'member_id': key
}
self.bot.db.save('twitch', entry)
await ctx.send("I have just saved your twitch url {}".format(ctx.message.author.mention))
@twitch.command(name='remove', aliases=['delete'])
@ -192,8 +190,12 @@ class Twitch:
EXAMPLE: !twitch remove
RESULT: I stop saving your twitch URL"""
# Just try to remove it, if it doesn't exist, nothing is going to happen
await utils.remove_content('twitch', str(ctx.message.author.id))
entry = {
'twitch_url': None,
'member_id': str(ctx.message.author.id)
}
self.bot.db.save('twitch', entry)
await ctx.send("I am no longer saving your twitch URL {}".format(ctx.message.author.mention))
@twitch.group(invoke_without_command=True)
@ -206,17 +208,22 @@ class Twitch:
EXAMPLE: !twitch notify
RESULT: This server will now be notified when you go live"""
key = str(ctx.message.author.id)
result = await utils.get_content('twitch', key)
servers = self.bot.db.load('twitch', key=key, pluck='servers')
# Check if this user is saved at all
if result is None:
if servers is None:
await ctx.send(
"I do not have your twitch URL added {}. You can save your twitch url with !twitch add".format(
ctx.message.author.mention))
# Then check if this server is already added as one to notify in
elif str(ctx.message.guild.id) in result['servers']:
elif str(ctx.message.guild.id) in servers:
await ctx.send("I am already set to notify in this server...")
else:
await utils.update_content('twitch', {'servers': r.row['servers'].append(str(ctx.message.guild.id))}, key)
servers.append(str(ctx.message.guild.id))
entry = {
'member_id': key,
'servers': servers
}
self.bot.db.save('twitch', entry)
await ctx.send("This server will now be notified if you go live")
@notify.command(name='on', aliases=['start,yes'])
@ -227,7 +234,14 @@ class Twitch:
EXAMPLE: !twitch notify on
RESULT: Notifications will be sent when you go live"""
if await utils.update_content('twitch', {"notifications_on": 1}, str(ctx.message.author.id)):
key = str(ctx.message.author.id)
result = self.bot.db.load('twitch', key=key)
if result:
entry = {
'member_id': key,
'notifications_on': 1
}
self.bot.db.save('twitch', entry)
await ctx.send("I will notify if you go live {}, you'll get a bajillion followers I promise c:".format(
ctx.message.author.mention))
else:
@ -241,7 +255,14 @@ class Twitch:
EXAMPLE: !twitch notify off
RESULT: Notifications will not be sent when you go live"""
if await utils.update_content('twitch', {"notifications_on": 0}, str(ctx.message.author.id)):
key = str(ctx.message.author.id)
result = self.bot.db.load('twitch', key=key)
if result:
entry = {
'member_id': key,
'notifications_on': 0
}
self.bot.db.save('twitch', entry)
await ctx.send(
"I will not notify if you go live anymore {}, "
"are you going to stream some lewd stuff you don't want people to see?~".format(

View file

@ -4,3 +4,4 @@ from .config import *
from .utilities import *
from .images import create_banner
from .paginator import Pages, CannotPaginate, DetailedPages
from .database import DB

View file

@ -12,7 +12,6 @@ required_tables = {
'battle_records': 'member_id',
'boops': 'member_id',
'command_usage': 'command',
'motd': 'date',
'overwatch': 'member_id',
'picarto': 'member_id',
'server_settings': 'server_id',
@ -21,7 +20,8 @@ required_tables = {
'osu': 'member_id',
'tags': 'server_id',
'tictactoe': 'member_id',
'twitch': 'member_id'
'twitch': 'member_id',
'user_playlists': 'member_id'
}
@ -68,16 +68,13 @@ def is_owner(ctx):
return ctx.bot.owner.id == ctx.message.author.id
def should_ignore(message):
def should_ignore(bot, message):
if message.guild is None:
return False
try:
server_settings = config.cache.get('server_settings').values
ignored = [x for x in server_settings if x['server_id'] == str(
message.guild.id)][0]['ignored']
return str(message.author.id) in ignored['members'] or str(message.channel.id) in ignored['channels']
except (TypeError, IndexError, KeyError):
ignored = bot.db.load('server_settings', key=message.guild.id, pluck='ignored')
if not ignored:
return False
return str(message.author.id) in ignored['members'] or str(message.channel.id) in ignored['channels']
def custom_perms(**perms):
@ -94,13 +91,10 @@ def custom_perms(**perms):
for perm, setting in perms.items():
setattr(required_perm, perm, setting)
try:
server_settings = config.cache.get('server_settings').values
required_perm_value = [x for x in server_settings if x['server_id'] == str(
ctx.message.guild.id)][0]['permissions'][ctx.command.qualified_name]
required_perm_value = ctx.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='permissions') or {}
required_perm_value = required_perm_value.get(ctx.command.qualified_name)
if required_perm_value:
required_perm = discord.Permissions(required_perm_value)
except (TypeError, IndexError, KeyError):
pass
# Now just check if the person running the command has these permissions
return member_perms >= required_perm

View file

@ -23,20 +23,6 @@ except KeyError:
quit()
# This is a simple class for the cache concept, all it holds is it's own key and the values
# With a method that gets content based on it's key
class Cache:
def __init__(self, key):
self.key = key
self.values = {}
self.refreshed = pendulum.utcnow()
loop.create_task(self.update())
async def update(self):
self.values = await get_content(self.key)
self.refreshed = pendulum.utcnow()
# Default bot's description
bot_description = global_config.get("description")
# Bot's default prefix for commands
@ -80,7 +66,9 @@ extensions = [
'cogs.tags',
'cogs.roulette',
'cogs.music',
'cogs.polls'
'cogs.polls',
'cogs.playlist',
'cogs.dj'
]
@ -103,155 +91,8 @@ db_pass = global_config.get('db_pass', '')
# {'ca_certs': db_cert}, 'user': db_user, 'password': db_pass}
db_opts = {'host': db_host, 'db': db_name, 'port': db_port, 'user': db_user, 'password': db_pass}
possible_keys = ['prefixes', 'battle_records', 'boops', 'server_alerts', 'user_notifications', 'nsfw_channels',
'custom_permissions', 'rules', 'overwatch', 'picarto', 'twitch', 'strawpolls', 'tags',
'tictactoe', 'bot_data', 'command_manage']
# This will be a dictionary that holds the cache object, based on the key that is saved
cache = {}
# Populate cache with each object
# With the new saving method, we're not going to be able to cache the way that I was before
# This is on standby until I rethink how to do this, because I do still want to cache data
"""for k in possible_keys:
ca che[k] = Cache(k)"""
# We still need 'cache' for prefixes and custom permissions however, so for now, just include that
cache['prefixes'] = Cache('prefixes')
cache['server_settings'] = Cache('server_settings')
async def update_cache():
for value in cache.values():
await value.update()
def command_prefix(bot, message):
# We do not want to make a query for every message that is sent
# So assume it's in cache, or it doesn't exist
# If the prefix does exist in the database and isn't in our cache; too bad, something has messed up
# But it is not worth a query for every single message the bot detects, to fix
try:
prefixes = cache['server_settings'].values
prefix = [x for x in prefixes if x['server_id'] == str(message.guild.id)][0]['prefix']
return prefix or default_prefix
except (KeyError, TypeError, IndexError, AttributeError, KeyError):
if not message.guild:
return default_prefix
async def add_content(table, content):
r.set_loop_type("asyncio")
conn = await r.connect(**db_opts)
# First we need to make sure that this entry doesn't exist
# For all rethinkDB cares, multiple entries can exist with the same content
# For our purposes however, we do not want this
try:
result = await r.table(table).insert(content).run(conn)
except r.ReqlOpFailedError:
# This means the table does not exist
await r.table_create(table).run(conn)
await r.table(table).insert(content).run(conn)
result = {}
await conn.close()
if table == 'prefixes' or table == 'server_settings':
loop.create_task(cache[table].update())
return result.get('inserted', 0) > 0
async def remove_content(table, key):
r.set_loop_type("asyncio")
conn = await r.connect(**db_opts)
try:
result = await r.table(table).get(key).delete().run(conn)
except r.ReqlOpFailedError:
result = {}
pass
await conn.close()
if table == 'prefixes' or table == 'server_settings':
loop.create_task(cache[table].update())
return result.get('deleted', 0) > 0
async def update_content(table, content, key):
r.set_loop_type("asyncio")
conn = await r.connect(**db_opts)
# This method is only for updating content, so if we find that it doesn't exist, just return false
try:
# Update based on the content and filter passed to us
# rethinkdb allows you to do many many things inside of update
# This is why we're accepting a variable and using it, whatever it may be, as the query
result = await r.table(table).get(key).update(content).run(conn)
except r.ReqlOpFailedError:
result = {}
await conn.close()
if table == 'prefixes' or table == 'server_settings':
loop.create_task(cache[table].update())
return result.get('replaced', 0) > 0 or result.get('unchanged', 0) > 0
async def replace_content(table, content, key):
# This method is here because .replace and .update can have some different functionalities
r.set_loop_type("asyncio")
conn = await r.connect(**db_opts)
try:
result = await r.table(table).get(key).replace(content).run(conn)
except r.ReqlOpFailedError:
result = {}
await conn.close()
if table == 'prefixes' or table == 'server_settings':
loop.create_task(cache[table].update())
return result.get('replaced', 0) > 0 or result.get('unchanged', 0) > 0
async def get_content(table, key=None):
r.set_loop_type("asyncio")
conn = await r.connect(**db_opts)
try:
if key:
cursor = await r.table(table).get(key).run(conn)
else:
cursor = await r.table(table).run(conn)
if cursor is None:
content = None
elif type(cursor) is not dict:
content = await _convert_to_list(cursor)
if len(content) == 0:
content = None
else:
content = cursor
except (IndexError, r.ReqlOpFailedError):
content = None
await conn.close()
return content
async def filter_content(table: str, r_filter):
r.set_loop_type("asyncio")
conn = await r.connect(**db_opts)
try:
cursor = await r.table(table).filter(r_filter).run(conn)
content = await _convert_to_list(cursor)
if len(content) == 0:
content = None
except (IndexError, r.ReqlOpFailedError):
content = None
await conn.close()
return content
async def _convert_to_list(cursor):
# This method is here because atm, AsyncioCursor is not iterable
# For our purposes, we want a list, so we need to do this manually
cursor_list = []
while True:
try:
val = await cursor.next()
cursor_list.append(val)
except r.ReqlCursorEmpty:
break
return cursor_list
return bot.db.load('server_settings', key=message.guild.id, pluck='prefix') or default_prefix

141
cogs/utils/database.py Normal file
View file

@ -0,0 +1,141 @@
import asyncio
import rethinkdb as r
from datetime import datetime
from .checks import required_tables
from . import config
async def _convert_to_list(cursor):
# This method is here because atm, AsyncioCursor is not iterable
# For our purposes, we want a list, so we need to do this manually
cursor_list = []
while True:
try:
val = await cursor.next()
cursor_list.append(val)
except r.ReqlCursorEmpty:
break
return cursor_list
class Cache:
"""A class to hold the cached database entries"""
def __init__(self, table, key, db, loop):
self.table = table # The name of the database table
self.key = key # The name of primary key
self.db = db # The database class connections are made through
self.loop = loop
self.values = [] # The values returned from the database
self.refreshed_time = None
self.loop.create_task(self.check_refresh())
async def refresh(self):
self.values = await self.db.actual_load(self.table)
self.refreshed_time = datetime.now()
async def check_refresh(self):
if self.refreshed_time is None:
await self.refresh()
else:
difference = datetime.now() - self.refreshed_time
if difference.total_seconds() > 300:
await self.refresh()
self.loop.call_later(60, self.check_refresh())
def get(self, key=None, table_filter=None, pluck=None):
"""This simulates the database call, to make it easier to get the data"""
if key is None and table_filter is None:
return self.values
elif key:
for value in self.values:
if value[self.key] == key:
if pluck:
return value.get(pluck)
else:
return value
elif table_filter:
req_key = list(table_filter.keys())[0]
req_val = list(table_filter.values())[0]
for value in self.values:
if value[req_key] == req_val:
if pluck:
return value.get(pluck)
else:
return value
class DB:
def __init__(self):
self.loop = asyncio.get_event_loop()
self.opts = config.db_opts
self.cache = {}
for table, key in required_tables.items():
self.cache[table] = Cache(table, key, self, self.loop)
async def query(self, query):
"""Lets you run a manual query"""
r.set_loop_type("asyncio")
conn = await r.connect(**self.opts)
try:
cursor = await query.run(conn)
except (r.ReqlOpFailedError, r.ReqlNonExistenceError):
cursor = None
if isinstance(cursor, r.Cursor):
cursor = await _convert_to_list(cursor)
await conn.close()
return cursor
def save(self, table, content):
"""A synchronous task to throw saving content into a task"""
self.loop.create_task(self._save(table, content))
async def _save(self, table, content):
"""Saves data in the table"""
index = await self.query(r.table(table).info())
index = index.get("primary_key")
key = content.get(index)
if key:
cur_content = await self.query(r.table(table).get(key))
if cur_content:
# We have content...we either need to update it, or replace
# Update will typically be more common so lets try that first
result = await self.query(r.table(table).get(key).update(content))
print(result)
if result.get('replaced', 0) == 0 and result.get('unchanged', 0) == 0:
print("Replacing...")
await self.query(r.table(table).get(key).replace(content))
else:
await self.query(r.table(table).insert(content))
else:
await self.query(r.table(table).insert(content))
await self.cache.get(table).refresh()
def load(self, table, **kwargs):
if kwargs.get('key'):
kwargs['key'] = str(kwargs.get('key'))
return self.cache.get(table).get(**kwargs)
async def actual_load(self, table, key=None, table_filter=None, pluck=None):
"""Loads the specified content from the specific table"""
query = r.table(table)
# If a key has been provided, get content with that key
if key:
query = query.get(str(key))
# A key and a filter shouldn't be combined for any case we'll ever use, so seperate these
elif table_filter:
query = query.filter(table_filter)
# If we want to pluck something specific, do that
if pluck:
query = query.pluck(pluck).values()[0]
cursor = await self.query(query)
return cursor

View file

@ -33,7 +33,7 @@ def get_all_subcommands(command):
yield from get_all_subcommands(subcmd)
async def channel_is_nsfw(channel):
async def channel_is_nsfw(channel, db):
if type(channel) is discord.DMChannel:
server = 'DMs'
elif channel.is_nsfw():
@ -43,12 +43,8 @@ async def channel_is_nsfw(channel):
channel = str(channel.id)
server_settings = await config.get_content('server_settings', server)
try:
return channel in server_settings['nsfw_channels']
except (TypeError, IndexError, KeyError):
return False
channels = db.load('server_settings', key=server, pluck='nsfw_channels') or []
return channel in channels
async def download_image(url):
@ -101,11 +97,11 @@ async def request(url, *, headers=None, payload=None, method='GET', attr='json')
continue
async def update_records(key, winner, loser):
async def update_records(key, db, winner, loser):
# We're using the Harkness scale to rate
# http://opnetchessclub.wikidot.com/harkness-rating-system
r_filter = lambda row: (row['member_id'] == str(winner.id)) | (row['member_id'] == str(loser.id))
matches = await config.filter_content(key, r_filter)
matches = db.load(key, table_filter=r_filter)
winner_stats = {}
loser_stats = {}
@ -150,12 +146,8 @@ async def update_records(key, winner, loser):
loser_losses += 1
# Now save the new wins, losses, and ratings
winner_stats = {'wins': winner_wins, 'losses': winner_losses, 'rating': winner_rating}
loser_stats = {'wins': loser_wins, 'losses': loser_losses, 'rating': loser_rating}
winner_stats = {'wins': winner_wins, 'losses': winner_losses, 'rating': winner_rating, 'member_id': str(winner.id)}
loser_stats = {'wins': loser_wins, 'losses': loser_losses, 'rating': loser_rating, 'member_id': str(loser.id)}
if not await config.update_content(key, winner_stats, str(winner.id)):
winner_stats['member_id'] = str(winner.id)
await config.add_content(key, winner_stats)
if not await config.update_content(key, loser_stats, str(loser.id)):
loser_stats['member_id'] = str(loser.id)
await config.add_content(key, loser_stats)
db.save(key, winner_stats)
db.save(key, loser_stats)

View file

@ -1,3 +1,4 @@
from .downloader import Downloader
from .playlist import Playlist
from .exceptions import *
from .playlist import Playlist

View file

@ -92,7 +92,7 @@ class BasePlaylistEntry:
class URLPlaylistEntry(BasePlaylistEntry):
def __init__(self, playlist, url, title, ctx, thumbnail, duration=0, expected_filename=None, **meta):
def __init__(self, playlist, url, title, thumbnail, duration=0, expected_filename=None, **meta):
super().__init__()
self.playlist = playlist
@ -102,8 +102,6 @@ class URLPlaylistEntry(BasePlaylistEntry):
self.thumbnail = thumbnail
self.expected_filename = expected_filename
self.meta = meta
self.requester = ctx.message.author
self.channel = ctx.message.channel
self.download_folder = self.playlist.downloader.download_folder
def __str__(self):
@ -119,7 +117,7 @@ class URLPlaylistEntry(BasePlaylistEntry):
@property
def progress(self):
if self.start_time:
if hasattr(self, 'start_time') and self.start_time:
return round(time.time() - self.start_time)
@property
@ -132,7 +130,6 @@ class URLPlaylistEntry(BasePlaylistEntry):
@classmethod
def from_json(cls, playlist, jsonstring):
data = json.loads(jsonstring)
print(data)
# TODO: version check
url = data['url']
title = data['title']
@ -187,7 +184,6 @@ class URLPlaylistEntry(BasePlaylistEntry):
# the generic extractor requires special handling
if extractor == 'generic':
# print("Handling generic")
flistdir = [f.rsplit('-', 1)[0] for f in os.listdir(self.download_folder)]
expected_fname_noex, fname_ex = os.path.basename(self.expected_filename).rsplit('.', 1)
@ -202,18 +198,14 @@ class URLPlaylistEntry(BasePlaylistEntry):
os.listdir(self.download_folder)[flistdir.index(expected_fname_noex)]
)
# print("Resolved %s to %s" % (self.expected_filename, lfile))
lsize = os.path.getsize(lfile)
# print("Remote size: %s Local size: %s" % (rsize, lsize))
if lsize != rsize:
await self._really_download(hash=True)
else:
# print("[Download] Cached:", self.url)
self.filename = lfile
else:
# print("File not found in cache (%s)" % expected_fname_noex)
await self._really_download(hash=True)
else:
@ -227,15 +219,9 @@ class URLPlaylistEntry(BasePlaylistEntry):
if expected_fname_base in ldir:
self.filename = os.path.join(self.download_folder, expected_fname_base)
print("[Download] Cached:", self.url)
elif expected_fname_noex in flistdir:
print("[Download] Cached (different extension):", self.url)
self.filename = os.path.join(self.download_folder, ldir[flistdir.index(expected_fname_noex)])
print("Expected %s, got %s" % (
self.expected_filename.rsplit('.', 1)[-1],
self.filename.rsplit('.', 1)[-1]
))
else:
await self._really_download()
@ -252,15 +238,12 @@ class URLPlaylistEntry(BasePlaylistEntry):
# noinspection PyShadowingBuiltins
async def _really_download(self, *, hash=False):
print("[Download] Started:", self.url)
try:
result = await self.playlist.downloader.extract_info(self.playlist.loop, self.url, download=True)
except Exception as e:
raise ExtractionError(e)
print("[Download] Complete:", self.url)
if result is None:
raise ExtractionError("ytdl broke and hell if I know why")
# What the fuck do I do now?
@ -293,4 +276,4 @@ class URLPlaylistEntry(BasePlaylistEntry):
fmt = "{0[0]}m {0[1]}s".format(length)
embed.add_field(name='Duration', value=fmt, inline=False)
# And return the embed we created
return embed
return embed

View file

@ -37,7 +37,7 @@ class Playlist(EventEmitter):
else:
return 0
async def add_entry(self, song_url, ctx, **meta):
async def add_entry(self, song_url, **meta):
"""
Validates and adds a song_url to be played. This does not start the download of the song.
@ -76,10 +76,8 @@ class Playlist(EventEmitter):
# https://github.com/KeepSafe/aiohttp/issues/758
# https://github.com/KeepSafe/aiohttp/issues/852
content_type = await get_header(self.bot.aiosession, info['url'], 'CONTENT-TYPE')
print("Got content type", content_type)
except Exception as e:
print("[Warning] Failed to get content type for url %s (%s)" % (song_url, e))
content_type = None
if content_type:
@ -87,9 +85,6 @@ class Playlist(EventEmitter):
if '/ogg' not in content_type: # How does a server say `application/ogg` what the actual fuck
raise ExtractionError("Invalid content type \"%s\" for url %s" % (content_type, song_url))
elif not content_type.startswith(('audio/', 'video/')):
print("[Warning] Questionable content type \"%s\" for url %s" % (content_type, song_url))
if info.get('is_live', False):
raise LiveStreamError("Cannot download from a livestream")
@ -97,7 +92,6 @@ class Playlist(EventEmitter):
self,
song_url,
info.get('title', 'Untitled'),
ctx,
info.get('thumbnail', None),
info.get('duration', 0) or 0,
self.downloader.ytdl.prepare_filename(info),
@ -151,14 +145,9 @@ class Playlist(EventEmitter):
baditems += 1
# Once I know more about what's happening here I can add a proper message
traceback.print_exc()
print(items)
print("Could not add item")
else:
baditems += 1
if baditems:
print("Skipped %s bad entries" % baditems)
return entry_list, position
async def async_process_youtube_playlist(self, playlist_url, **meta):
@ -191,14 +180,9 @@ class Playlist(EventEmitter):
baditems += 1
except Exception as e:
baditems += 1
print("There was an error adding the song {}: {}: {}\n".format(
entry_data['id'], e.__class__.__name__, e))
else:
baditems += 1
if baditems:
print("Skipped %s bad entries" % baditems)
return gooditems
async def async_process_sc_bc_playlist(self, playlist_url, **meta):
@ -230,14 +214,9 @@ class Playlist(EventEmitter):
baditems += 1
except Exception as e:
baditems += 1
print("There was an error adding the song {}: {}: {}\n".format(
entry_data['id'], e.__class__.__name__, e))
else:
baditems += 1
if baditems:
print("Skipped %s bad entries" % baditems)
return gooditems
def _add_entry(self, entry):
@ -286,4 +265,4 @@ class Playlist(EventEmitter):
return datetime.timedelta(seconds=estimated_time)
def count_for_user(self, user):
return sum(1 for e in self.entries if e.meta.get('author', None) == user)
return sum(1 for e in self.entries if e.meta.get('author', None) == user)

View file

@ -1,9 +1,9 @@
beautifulsoup4
beautifulsoup4==4.6.0
Pillow==3.4.1
rethinkdb
ruamel.yaml
rethinkdb==2.3.0.post6
ruamel.yaml==0.14.12
youtube-dl
psutil
pendulum
psutil==5.2.2
pendulum==1.2.0
-e git+https://github.com/Rapptz/discord.py@rewrite#egg=discord.py[voice]
-e git+https://github.com/khazhyk/osuapi.git#egg=osuapi
-e git+https://github.com/khazhyk/osuapi.git#egg=osuapi