Merge branch 'rewrite' of https://github.com/Phxntxm/Bonfire into rewrite
This commit is contained in:
commit
eedcd9572a
15
bot.py
15
bot.py
|
@ -24,8 +24,6 @@ logging.basicConfig(level=logging.INFO, filename='bonfire.log')
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_ready():
|
async def on_ready():
|
||||||
if not hasattr(bot, 'uptime'):
|
|
||||||
bot.uptime = pendulum.utcnow()
|
|
||||||
if not hasattr(bot, 'owner'):
|
if not hasattr(bot, 'owner'):
|
||||||
appinfo = await bot.application_info()
|
appinfo = await bot.application_info()
|
||||||
bot.owner = appinfo.owner
|
bot.owner = appinfo.owner
|
||||||
|
@ -33,7 +31,7 @@ async def on_ready():
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_message(message):
|
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
|
return
|
||||||
await bot.process_commands(message)
|
await bot.process_commands(message)
|
||||||
|
|
||||||
|
@ -49,9 +47,8 @@ async def process_command(ctx):
|
||||||
server = ctx.message.guild
|
server = ctx.message.guild
|
||||||
command = ctx.command
|
command = ctx.command
|
||||||
|
|
||||||
command_usage = await utils.get_content('command_usage', key=command.qualified_name)
|
command_usage = await bot.db.actual_load('command_usage', key=command.qualified_name) or \
|
||||||
if command_usage is None:
|
{'command': command.qualified_name}
|
||||||
command_usage = {'command': command.qualified_name}
|
|
||||||
|
|
||||||
# Add one to the total usage for this command, basing it off 0 to start with (obviously)
|
# 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
|
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
|
command_usage['server_usage'] = total_server_usage
|
||||||
|
|
||||||
# Save all the changes
|
# Save all the changes
|
||||||
if not await utils.update_content('command_usage', command_usage, command.qualified_name):
|
bot.db.save('command_usage', command_usage)
|
||||||
await utils.add_content('command_usage', command_usage)
|
|
||||||
|
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
|
@ -129,4 +125,7 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
for e in utils.extensions:
|
for e in utils.extensions:
|
||||||
bot.load_extension(e)
|
bot.load_extension(e)
|
||||||
|
|
||||||
|
bot.db = utils.DB()
|
||||||
|
bot.uptime = pendulum.utcnow()
|
||||||
bot.run(utils.bot_token)
|
bot.run(utils.bot_token)
|
||||||
|
|
210
cogs/admin.py
210
cogs/admin.py
|
@ -5,7 +5,6 @@ from . import utils
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import rethinkdb as r
|
|
||||||
|
|
||||||
valid_perms = [p for p in dir(discord.Permissions) if isinstance(getattr(discord.Permissions, p), property)]
|
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
|
EXAMPLE: !ignore #general
|
||||||
RESULT: Bonfire will ignore commands sent in the general channel"""
|
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()
|
converter = commands.converter.MemberConverter()
|
||||||
member = None
|
member = None
|
||||||
|
@ -66,9 +65,7 @@ class Administration:
|
||||||
await ctx.send("{} does not appear to be a member or channel!".format(member_or_channel))
|
await ctx.send("{} does not appear to be a member or channel!".format(member_or_channel))
|
||||||
return
|
return
|
||||||
|
|
||||||
settings = await utils.get_content('server_settings', key)
|
settings = self.bot.db.load('server_settings', key=key, pluck='ignored') or {}
|
||||||
if settings is None:
|
|
||||||
settings = {}
|
|
||||||
ignored = settings.get('ignored', {'members': [], 'channels': []})
|
ignored = settings.get('ignored', {'members': [], 'channels': []})
|
||||||
if member:
|
if member:
|
||||||
if str(member.id) in ignored['members']:
|
if str(member.id) in ignored['members']:
|
||||||
|
@ -80,7 +77,7 @@ class Administration:
|
||||||
else:
|
else:
|
||||||
ignored['members'].append(str(member.id))
|
ignored['members'].append(str(member.id))
|
||||||
fmt = "Ignoring {}".format(member.display_name)
|
fmt = "Ignoring {}".format(member.display_name)
|
||||||
elif channel:
|
else:
|
||||||
if str(channel.id) in ignored['channels']:
|
if str(channel.id) in ignored['channels']:
|
||||||
await ctx.send("I am already ignoring {}!".format(channel.mention))
|
await ctx.send("I am already ignoring {}!".format(channel.mention))
|
||||||
return
|
return
|
||||||
|
@ -88,8 +85,12 @@ class Administration:
|
||||||
ignored['channels'].append(str(channel.id))
|
ignored['channels'].append(str(channel.id))
|
||||||
fmt = "Ignoring {}".format(channel.mention)
|
fmt = "Ignoring {}".format(channel.mention)
|
||||||
|
|
||||||
update = {'ignored': ignored}
|
entry = {
|
||||||
await utils.update_content('server_settings', update, key)
|
'ignored': ignored,
|
||||||
|
'server_id': str(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bot.db.save('server_settings', entry)
|
||||||
await ctx.send(fmt)
|
await ctx.send(fmt)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
|
@ -115,9 +116,7 @@ class Administration:
|
||||||
await ctx.send("{} does not appear to be a member or channel!".format(member_or_channel))
|
await ctx.send("{} does not appear to be a member or channel!".format(member_or_channel))
|
||||||
return
|
return
|
||||||
|
|
||||||
settings = await utils.get_content('server_settings', key)
|
settings = self.bot.db.load('server_settings', key=key) or {}
|
||||||
if settings is None:
|
|
||||||
settings = {}
|
|
||||||
ignored = settings.get('ignored', {'members': [], 'channels': []})
|
ignored = settings.get('ignored', {'members': [], 'channels': []})
|
||||||
if member:
|
if member:
|
||||||
if str(member.id) not in ignored['members']:
|
if str(member.id) not in ignored['members']:
|
||||||
|
@ -126,7 +125,7 @@ class Administration:
|
||||||
|
|
||||||
ignored['members'].remove(str(member.id))
|
ignored['members'].remove(str(member.id))
|
||||||
fmt = "I am no longer ignoring {}".format(member.display_name)
|
fmt = "I am no longer ignoring {}".format(member.display_name)
|
||||||
elif channel:
|
else:
|
||||||
if str(channel.id) not in ignored['channels']:
|
if str(channel.id) not in ignored['channels']:
|
||||||
await ctx.send("I'm not even ignoring {}!".format(channel.mention))
|
await ctx.send("I'm not even ignoring {}!".format(channel.mention))
|
||||||
return
|
return
|
||||||
|
@ -134,8 +133,12 @@ class Administration:
|
||||||
ignored['channels'].remove(str(channel.id))
|
ignored['channels'].remove(str(channel.id))
|
||||||
fmt = "I am no longer ignoring {}".format(channel.mention)
|
fmt = "I am no longer ignoring {}".format(channel.mention)
|
||||||
|
|
||||||
update = {'ignored': ignored}
|
entry = {
|
||||||
await utils.update_content('server_settings', update, key)
|
'ignored': ignored,
|
||||||
|
'server_id': str(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bot.db.save('server_settings', entry)
|
||||||
await ctx.send(fmt)
|
await ctx.send(fmt)
|
||||||
|
|
||||||
@commands.command(aliases=['alerts'])
|
@commands.command(aliases=['alerts'])
|
||||||
|
@ -147,11 +150,12 @@ class Administration:
|
||||||
|
|
||||||
EXAMPLE: !alerts #alerts
|
EXAMPLE: !alerts #alerts
|
||||||
RESULT: No more alerts spammed in #general!"""
|
RESULT: No more alerts spammed in #general!"""
|
||||||
key = str(ctx.message.guild.id)
|
entry = {
|
||||||
entry = {'server_id': key,
|
'server_id': str(ctx.message.guild.id),
|
||||||
'notification_channel': str(channel.id)}
|
'notifications_channel': str(channel.id)
|
||||||
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 changed this server's 'notifications' channel"
|
await ctx.send("I have just changed this server's 'notifications' channel"
|
||||||
"\nAll notifications will now go to `{}`".format(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
|
# 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
|
# 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
|
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"
|
fmt = "notify" if on_off else "not notify"
|
||||||
await ctx.send("This server will now {} if someone has joined or left".format(fmt))
|
await ctx.send("This server will now {} if someone has joined or left".format(fmt))
|
||||||
|
|
||||||
|
@ -196,17 +201,15 @@ class Administration:
|
||||||
else:
|
else:
|
||||||
key = str(ctx.message.guild.id)
|
key = str(ctx.message.guild.id)
|
||||||
|
|
||||||
entry = {'server_id': key,
|
channels = self.bot.db.load('server_settings', key=key, pluck='nsfw_channels') or []
|
||||||
'nsfw_channels': [str(ctx.message.channel.id)]}
|
channels.append(str(ctx.message.channel.id))
|
||||||
update = {'nsfw_channels': r.row['nsfw_channels'].append(str(ctx.message.channel.id))}
|
|
||||||
|
|
||||||
server_settings = await utils.get_content('server_settings', key)
|
entry = {
|
||||||
if server_settings and 'nsfw_channels' in server_settings.keys():
|
'server_id': key,
|
||||||
await utils.update_content('server_settings', update, key)
|
'nsfw_channels': channels
|
||||||
elif server_settings:
|
}
|
||||||
await utils.update_content('server_settings', entry, key)
|
|
||||||
else:
|
self.bot.db.save('server_settings', entry)
|
||||||
await utils.add_content('server_settings', entry)
|
|
||||||
|
|
||||||
await ctx.send("This channel has just been registered as 'nsfw'! Have fun you naughties ;)")
|
await ctx.send("This channel has just been registered as 'nsfw'! Have fun you naughties ;)")
|
||||||
|
|
||||||
|
@ -217,27 +220,24 @@ class Administration:
|
||||||
|
|
||||||
EXAMPLE: !nsfw remove
|
EXAMPLE: !nsfw remove
|
||||||
RESULT: ;("""
|
RESULT: ;("""
|
||||||
|
channel = str(ctx.message.channel.id)
|
||||||
if type(ctx.message.channel) is discord.DMChannel:
|
if type(ctx.message.channel) is discord.DMChannel:
|
||||||
key = 'DMs'
|
key = 'DMs'
|
||||||
else:
|
else:
|
||||||
key = str(ctx.message.guild.id)
|
key = str(ctx.message.guild.id)
|
||||||
|
|
||||||
server_settings = await utils.get_content('server_settings', key)
|
channels = self.bot.db.load('server_settings', key=key, pluck='nsfw_channels') or []
|
||||||
channel = str(ctx.message.channel.id)
|
if channel in channels:
|
||||||
try:
|
channels.remove(channel)
|
||||||
channels = server_settings.get('nsfw_channels', None)
|
|
||||||
if channel in channels:
|
|
||||||
channels.remove(channel)
|
|
||||||
|
|
||||||
entry = {'nsfw_channels': channels}
|
entry = {
|
||||||
await utils.update_content('server_settings', entry, key)
|
'server_id': key,
|
||||||
await ctx.send("This channel has just been unregistered as a nsfw channel")
|
'nsfw_channels': channels
|
||||||
return
|
}
|
||||||
except (TypeError, IndexError):
|
self.bot.db.save('server_settings', entry)
|
||||||
pass
|
await ctx.send("This channel has just been unregistered as a nsfw channel")
|
||||||
|
else:
|
||||||
await ctx.send("This channel is not registered as a 'nsfw' channel!")
|
await ctx.send("This channel is not registerred as a nsfw channel!")
|
||||||
|
|
||||||
@commands.group(invoke_without_command=True)
|
@commands.group(invoke_without_command=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@ -259,11 +259,7 @@ class Administration:
|
||||||
await ctx.send("That is not a valid command!")
|
await ctx.send("That is not a valid command!")
|
||||||
return
|
return
|
||||||
|
|
||||||
server_settings = await utils.get_content('server_settings', str(ctx.message.guild.id))
|
server_perms = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='permissions') or {}
|
||||||
try:
|
|
||||||
server_perms = server_settings['permissions']
|
|
||||||
except (TypeError, IndexError, KeyError):
|
|
||||||
server_perms = {}
|
|
||||||
|
|
||||||
perms_value = server_perms.get(cmd.qualified_name)
|
perms_value = server_perms.get(cmd.qualified_name)
|
||||||
if perms_value is None:
|
if perms_value is None:
|
||||||
|
@ -351,12 +347,12 @@ class Administration:
|
||||||
await ctx.send("This command cannot have custom permissions setup!")
|
await ctx.send("This command cannot have custom permissions setup!")
|
||||||
return
|
return
|
||||||
|
|
||||||
key = str(ctx.message.guild.id)
|
entry = {
|
||||||
entry = {'server_id': key,
|
'server_id': str(ctx.message.guild.id),
|
||||||
'permissions': {cmd.qualified_name: perm_value}}
|
'permissions': {cmd.qualified_name: perm_value}
|
||||||
|
}
|
||||||
|
|
||||||
if not await utils.update_content('server_settings', entry, key):
|
self.bot.db.save('server_settings', entry)
|
||||||
await utils.add_content('server_settings', entry)
|
|
||||||
|
|
||||||
await ctx.send("I have just added your custom permissions; "
|
await ctx.send("I have just added your custom permissions; "
|
||||||
"you now need to have `{}` permissions to use the command `{}`".format(permissions, command))
|
"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....")
|
"That command does not exist! You can't have custom permissions on a non-existant command....")
|
||||||
return
|
return
|
||||||
|
|
||||||
update = {'permissions': {cmd.qualified_name: None}}
|
entry = {
|
||||||
await utils.update_content('server_settings', update, str(ctx.message.guild.id))
|
'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))
|
await ctx.send("I have just removed the custom permissions for {}!".format(cmd))
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
|
@ -396,11 +396,12 @@ class Administration:
|
||||||
if prefix.lower().strip() == "none":
|
if prefix.lower().strip() == "none":
|
||||||
prefix = None
|
prefix = None
|
||||||
|
|
||||||
entry = {'server_id': key,
|
entry = {
|
||||||
'prefix': prefix}
|
'server_id': key,
|
||||||
|
'prefix': prefix
|
||||||
|
}
|
||||||
|
|
||||||
if not await utils.update_content('server_settings', entry, key):
|
self.bot.db.save('server_settings', entry)
|
||||||
await utils.add_content('server_settings', entry)
|
|
||||||
|
|
||||||
if prefix is None:
|
if prefix is None:
|
||||||
fmt = "I have just cleared your custom prefix, the default prefix will have to be used now"
|
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
|
EXAMPLE: !rules 5
|
||||||
RESULT: Rule 5 is printed"""
|
RESULT: Rule 5 is printed"""
|
||||||
server_settings = await utils.get_content('server_settings', str(ctx.message.guild.id))
|
rules = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='rules')
|
||||||
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 = server_settings.get('rules')
|
if rules is None:
|
||||||
|
|
||||||
if not rules or len(rules) == 0:
|
|
||||||
await ctx.send("This server currently has no rules on it! I see you like to live dangerously...")
|
await ctx.send("This server currently has no rules on it! I see you like to live dangerously...")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -452,17 +448,15 @@ class Administration:
|
||||||
EXAMPLE: !rules add No fun allowed in this server >:c
|
EXAMPLE: !rules add No fun allowed in this server >:c
|
||||||
RESULT: No more fun...unless they break the rules!"""
|
RESULT: No more fun...unless they break the rules!"""
|
||||||
key = str(ctx.message.guild.id)
|
key = str(ctx.message.guild.id)
|
||||||
entry = {'server_id': key,
|
rules = self.bot.db.load('server_settings', key=key, pluck='rules') or []
|
||||||
'rules': [rule]}
|
rules.append(rule)
|
||||||
update = {'rules': r.row['rules'].append(rule)}
|
|
||||||
|
|
||||||
server_settings = await utils.get_content('server_settings', key)
|
entry = {
|
||||||
if server_settings and 'rules' in server_settings.keys():
|
'server_id': key,
|
||||||
await utils.update_content('server_settings', update, key)
|
'rules': rules
|
||||||
elif server_settings:
|
}
|
||||||
await utils.update_content('server_settings', entry, key)
|
|
||||||
else:
|
self.bot.db.save('server_settings', entry)
|
||||||
await utils.add_content('server_settings', entry)
|
|
||||||
|
|
||||||
await ctx.send("I have just saved your new rule, use the rules command to view this server's current rules")
|
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
|
EXAMPLE: !rules delete 5
|
||||||
RESULT: Freedom from opression!"""
|
RESULT: Freedom from opression!"""
|
||||||
update = {'rules': r.row['rules'].delete_at(rule - 1)}
|
key = str(ctx.message.guild.id)
|
||||||
if not await utils.update_content('server_settings', update, str(ctx.message.guild.id)):
|
rules = self.bot.db.load('server_settings', key=key, pluck='rules') or []
|
||||||
await ctx.send("That is not a valid rule number, try running the command again.")
|
try:
|
||||||
else:
|
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!")
|
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):
|
def setup(bot):
|
||||||
|
|
65
cogs/dj.py
Normal file
65
cogs/dj.py
Normal 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))
|
|
@ -44,10 +44,10 @@ class StatsUpdate:
|
||||||
async with self.session.post(url, data=payload, headers=headers) as resp:
|
async with self.session.post(url, data=payload, headers=headers) as resp:
|
||||||
log.info('bots.discord.pw statistics returned {} for {}'.format(resp.status, payload))
|
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())
|
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())
|
self.bot.loop.create_task(self.update())
|
||||||
|
|
||||||
async def on_ready(self):
|
async def on_ready(self):
|
||||||
|
@ -55,12 +55,12 @@ class StatsUpdate:
|
||||||
|
|
||||||
async def on_member_join(self, member):
|
async def on_member_join(self, member):
|
||||||
guild = member.guild
|
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:
|
try:
|
||||||
join_leave_on = server_settings['join_leave']
|
join_leave_on = server_settings['join_leave']
|
||||||
if join_leave_on:
|
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:
|
else:
|
||||||
return
|
return
|
||||||
except (IndexError, TypeError, KeyError):
|
except (IndexError, TypeError, KeyError):
|
||||||
|
@ -74,12 +74,12 @@ class StatsUpdate:
|
||||||
|
|
||||||
async def on_member_remove(self, member):
|
async def on_member_remove(self, member):
|
||||||
guild = member.guild
|
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:
|
try:
|
||||||
join_leave_on = server_settings['join_leave']
|
join_leave_on = server_settings['join_leave']
|
||||||
if join_leave_on:
|
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:
|
else:
|
||||||
return
|
return
|
||||||
except (IndexError, TypeError, KeyError):
|
except (IndexError, TypeError, KeyError):
|
||||||
|
|
|
@ -8,6 +8,7 @@ import re
|
||||||
import random
|
import random
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
class Game:
|
class Game:
|
||||||
def __init__(self, word):
|
def __init__(self, word):
|
||||||
self.word = word
|
self.word = word
|
||||||
|
@ -142,12 +143,17 @@ class Hangman:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
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:
|
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
|
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):
|
def check(m):
|
||||||
return m.channel == msg.channel and len(m.content) < 20
|
return m.channel == msg.channel and len(m.content) < 20
|
||||||
|
@ -155,12 +161,14 @@ class Hangman:
|
||||||
try:
|
try:
|
||||||
msg = await self.bot.wait_for('message', check=check, timeout=60)
|
msg = await self.bot.wait_for('message', check=check, timeout=60)
|
||||||
except asyncio.TimeoutError:
|
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
|
return
|
||||||
|
|
||||||
forbidden_phrases = ['stop', 'delete', 'remove', 'end', 'create', 'start']
|
forbidden_phrases = ['stop', 'delete', 'remove', 'end', 'create', 'start']
|
||||||
if msg.content in forbidden_phrases:
|
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
|
return
|
||||||
|
|
||||||
game = self.create(msg.content, ctx)
|
game = self.create(msg.content, ctx)
|
||||||
|
|
|
@ -112,7 +112,7 @@ class Images:
|
||||||
query = ' '.join(value for value in search if not re.search('&?filter_id=[0-9]+', value))
|
query = ' '.join(value for value in search if not re.search('&?filter_id=[0-9]+', value))
|
||||||
params = {'q': query}
|
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
|
# 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
|
# 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
|
# 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,
|
params = {'limit': 320,
|
||||||
'tags': tags}
|
'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
|
# e621 by default does not filter explicit content, so tack on
|
||||||
# safe/explicit based on if this channel is nsfw or not
|
# safe/explicit based on if this channel is nsfw or not
|
||||||
|
|
|
@ -117,7 +117,6 @@ class Interaction:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def start_battle(self, player1, player2):
|
def start_battle(self, player1, player2):
|
||||||
battles = self.battles.get(player1.guild.id, [])
|
battles = self.battles.get(player1.guild.id, [])
|
||||||
entry = {
|
entry = {
|
||||||
|
@ -161,7 +160,7 @@ class Interaction:
|
||||||
|
|
||||||
@commands.group(invoke_without_command=True)
|
@commands.group(invoke_without_command=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.cooldown(1, 180, BucketType.user)
|
@commands.cooldown(1, 20, BucketType.user)
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
async def battle(self, ctx, player2: discord.Member):
|
async def battle(self, ctx, player2: discord.Member):
|
||||||
"""Challenges the mentioned user to a battle
|
"""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
|
# All we need to do is change what order the challengers are printed/added as a paramater
|
||||||
if random.SystemRandom().randint(0, 1):
|
if random.SystemRandom().randint(0, 1):
|
||||||
await ctx.send(fmt.format(battleP1.mention, battleP2.mention))
|
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:
|
else:
|
||||||
await ctx.send(fmt.format(battleP2.mention, battleP1.mention))
|
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.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@ -259,7 +258,7 @@ class Interaction:
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.cooldown(1, 180, BucketType.user)
|
@commands.cooldown(1, 10, BucketType.user)
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
async def boop(self, ctx, boopee: discord.Member = None, *, message=""):
|
async def boop(self, ctx, boopee: discord.Member = None, *, message=""):
|
||||||
"""Boops the mentioned person
|
"""Boops the mentioned person
|
||||||
|
@ -284,20 +283,15 @@ class Interaction:
|
||||||
return
|
return
|
||||||
|
|
||||||
key = str(booper.id)
|
key = str(booper.id)
|
||||||
boops = await utils.get_content('boops', key)
|
boops = self.bot.db.load('boops', key=key, pluck='boops') or {}
|
||||||
if boops is not None:
|
amount = boops.get(str(boopee.id), 0) + 1
|
||||||
boops = boops['boops']
|
entry = {
|
||||||
# If the booper has never booped the member provided, assure it's 0
|
'member_id': str(booper.id),
|
||||||
amount = boops.get(str(boopee.id), 0) + 1
|
'boops': {
|
||||||
boops[str(boopee.id)] = amount
|
str(boopee.id): amount
|
||||||
|
}
|
||||||
await utils.update_content('boops', {'boops': boops}, key)
|
}
|
||||||
else:
|
self.bot.db.save('boops', entry)
|
||||||
entry = {'member_id': str(booper.id),
|
|
||||||
'boops': {str(boopee.id): 1}}
|
|
||||||
|
|
||||||
await utils.add_content('boops', entry)
|
|
||||||
amount = 1
|
|
||||||
|
|
||||||
fmt = "{0.mention} has just booped {1.mention}{3}! That's {2} times now!"
|
fmt = "{0.mention} has just booped {1.mention}{3}! That's {2} times now!"
|
||||||
await ctx.send(fmt.format(booper, boopee, amount, message))
|
await ctx.send(fmt.format(booper, boopee, amount, message))
|
||||||
|
|
|
@ -5,9 +5,7 @@ from . import utils
|
||||||
from bs4 import BeautifulSoup as bs
|
from bs4 import BeautifulSoup as bs
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import random
|
|
||||||
import re
|
import re
|
||||||
import math
|
|
||||||
|
|
||||||
|
|
||||||
class Links:
|
class Links:
|
||||||
|
@ -29,7 +27,7 @@ class Links:
|
||||||
url = "https://www.google.com/search"
|
url = "https://www.google.com/search"
|
||||||
|
|
||||||
# Turn safe filter on or off, based on whether or not this is a nsfw channel
|
# 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'
|
safe = 'off' if nsfw else 'on'
|
||||||
|
|
||||||
params = {'q': query,
|
params = {'q': query,
|
||||||
|
|
62
cogs/misc.py
62
cogs/misc.py
|
@ -3,9 +3,6 @@ from discord.ext import commands
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
from bs4 import BeautifulSoup as bs
|
|
||||||
import subprocess
|
|
||||||
import glob
|
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import calendar
|
import calendar
|
||||||
|
@ -42,7 +39,7 @@ class Miscallaneous:
|
||||||
|
|
||||||
if command is None:
|
if command is None:
|
||||||
for cmd in utils.get_all_commands(self.bot):
|
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
|
continue
|
||||||
|
|
||||||
cog = cmd.cog_name
|
cog = cmd.cog_name
|
||||||
|
@ -82,7 +79,7 @@ class Miscallaneous:
|
||||||
except utils.CannotPaginate as e:
|
except utils.CannotPaginate as e:
|
||||||
await ctx.send(str(e))
|
await ctx.send(str(e))
|
||||||
else:
|
else:
|
||||||
# Get the description for a command
|
# Get the description for a command
|
||||||
description = command.help
|
description = command.help
|
||||||
if description is not None:
|
if description is not None:
|
||||||
# Split into examples, results, and the description itself based on the string
|
# Split into examples, results, and the description itself based on the string
|
||||||
|
@ -108,7 +105,6 @@ class Miscallaneous:
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
async def say(self, ctx, *, msg: str):
|
async def say(self, ctx, *, msg: str):
|
||||||
|
@ -123,49 +119,6 @@ class Miscallaneous:
|
||||||
except:
|
except:
|
||||||
pass
|
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()
|
@commands.command()
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
async def calendar(self, ctx, month: str = None, year: int = None):
|
async def calendar(self, ctx, month: str = None, year: int = None):
|
||||||
|
@ -255,7 +208,7 @@ class Miscallaneous:
|
||||||
if hasattr(self.bot, 'uptime'):
|
if hasattr(self.bot, 'uptime'):
|
||||||
embed.add_field(name='Uptime', value=(pendulum.utcnow() - self.bot.uptime).in_words())
|
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()
|
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='Memory Usage', value='{:.2f} MiB'.format(memory_usage))
|
||||||
embed.add_field(name='CPU Usage', value='{}%'.format(cpu_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{}"
|
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)))
|
.format(discord.utils.oauth_url(app_info.id, perms)))
|
||||||
|
|
||||||
@commands.command()
|
@commands.command(enabled=False)
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
async def joke(self, ctx):
|
async def joke(self, ctx):
|
||||||
"""Prints a random riddle
|
"""Prints a random riddle
|
||||||
|
|
||||||
EXAMPLE: !joke
|
EXAMPLE: !joke
|
||||||
RESULT: An absolutely terrible joke."""
|
RESULT: An absolutely terrible joke."""
|
||||||
joke = await utils.request('http://tambal.azurewebsites.net/joke/random')
|
# Currently disabled until I can find a free API
|
||||||
if joke is not None and 'joke' in joke:
|
pass
|
||||||
await ctx.send(joke.get('joke'))
|
|
||||||
else:
|
|
||||||
await ctx.send("Sorry, I'm not feeling funny right now...try later")
|
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
|
|
266
cogs/music.py
266
cogs/music.py
|
@ -11,7 +11,7 @@ import asyncio
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
from collections import deque
|
||||||
|
|
||||||
log = logging.getLogger()
|
log = logging.getLogger()
|
||||||
|
|
||||||
|
@ -20,13 +20,16 @@ if not discord.opus.is_loaded():
|
||||||
|
|
||||||
|
|
||||||
class VoiceState:
|
class VoiceState:
|
||||||
def __init__(self, guild, bot):
|
def __init__(self, guild, bot, user_queue=False):
|
||||||
self.guild = guild
|
self.guild = guild
|
||||||
self.songs = Playlist(bot)
|
self.songs = Playlist(bot)
|
||||||
|
self.djs = deque()
|
||||||
|
self.dj = None
|
||||||
self.current = None
|
self.current = None
|
||||||
self.required_skips = 0
|
self.required_skips = 0
|
||||||
self.skip_votes = set()
|
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
|
self._volume = 50
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -50,40 +53,44 @@ class VoiceState:
|
||||||
else:
|
else:
|
||||||
return self.voice.is_playing() or self.voice.is_paused()
|
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):
|
def skip(self):
|
||||||
self.skip_votes.clear()
|
self.skip_votes.clear()
|
||||||
if self.playing:
|
if self.playing:
|
||||||
self.voice.stop()
|
self.voice.stop()
|
||||||
|
|
||||||
def after(self):
|
def after(self, _=None):
|
||||||
self.current = 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 audio_player_task(self):
|
async def play_next_song(self):
|
||||||
while True:
|
if self.playing or not self.voice:
|
||||||
if self.playing:
|
return
|
||||||
await asyncio.sleep(1)
|
|
||||||
continue
|
|
||||||
song = self.songs.peek()
|
|
||||||
if song is None:
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
self.skip_votes.clear()
|
||||||
self.current = await self.songs.get_next_entry()
|
try:
|
||||||
embed = self.current.to_embed()
|
await self.next_song()
|
||||||
embed.title = "Now playing!"
|
except ExtractionError:
|
||||||
await song.channel.send(embed=embed)
|
# For now lets just silently continue in the queue
|
||||||
except ExtractionError as e:
|
# Implementation to the music notifications channel will change what we do here
|
||||||
error = str(e).partition(" ")[2]
|
return await self.play_next_song()
|
||||||
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
|
|
||||||
|
|
||||||
|
if self.playing or not self.voice:
|
||||||
|
return
|
||||||
|
if self.current:
|
||||||
source = FFmpegPCMAudio(
|
source = FFmpegPCMAudio(
|
||||||
self.current.filename,
|
self.current.filename,
|
||||||
before_options='-nostdin',
|
before_options='-nostdin',
|
||||||
|
@ -92,6 +99,42 @@ class VoiceState:
|
||||||
source = PCMVolumeTransformer(source, volume=self.volume)
|
source = PCMVolumeTransformer(source, volume=self.volume)
|
||||||
self.voice.play(source, after=self.after)
|
self.voice.play(source, after=self.after)
|
||||||
self.current.start_time = time.time()
|
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
|
||||||
|
# Or....this is a song queue, and the last song in the queue was retrieved, and failed to download
|
||||||
|
# So what we'll do here is just call this again a few seconds later if it's a user queue, otherwise return
|
||||||
|
if self.user_queue:
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
return await self.play_next_song()
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
async def next_song(self):
|
||||||
|
if not self.user_queue:
|
||||||
|
song = await self.songs.get_next_entry()
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
dj = self.djs.popleft()
|
||||||
|
except IndexError:
|
||||||
|
song = None
|
||||||
|
else:
|
||||||
|
song = await self.dj.get_next_entry()
|
||||||
|
if song is None:
|
||||||
|
self.djs.remove(dj)
|
||||||
|
await self.next_song()
|
||||||
|
else:
|
||||||
|
song.requester = dj.member
|
||||||
|
|
||||||
|
# Add an extra check here in case in the very short period of time possible, someone has queued a
|
||||||
|
# song while we are downloading the next
|
||||||
|
if not self.playing:
|
||||||
|
self.dj = dj
|
||||||
|
|
||||||
|
if not self.playing:
|
||||||
|
self.current = song
|
||||||
|
|
||||||
|
|
||||||
class Music:
|
class Music:
|
||||||
|
@ -106,16 +149,6 @@ class Music:
|
||||||
self.downloader = down
|
self.downloader = down
|
||||||
self.bot.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):
|
async def queue_embed_task(self, state, channel, author):
|
||||||
index = 0
|
index = 0
|
||||||
message = None
|
message = None
|
||||||
|
@ -136,7 +169,10 @@ class Music:
|
||||||
while True:
|
while True:
|
||||||
# Get the current queue (It might change while we're doing this)
|
# Get the current queue (It might change while we're doing this)
|
||||||
# So do this in the while loop
|
# 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)
|
count = len(queue)
|
||||||
# This means the last song was removed
|
# This means the last song was removed
|
||||||
if count == 0:
|
if count == 0:
|
||||||
|
@ -144,8 +180,13 @@ class Music:
|
||||||
break
|
break
|
||||||
# Get the current entry
|
# Get the current entry
|
||||||
entry = queue[index]
|
entry = queue[index]
|
||||||
|
dj = None
|
||||||
|
if state.user_queue:
|
||||||
|
dj = entry
|
||||||
|
entry = entry.peek()
|
||||||
# Get the entry's embed
|
# Get the entry's embed
|
||||||
embed = entry.to_embed()
|
embed = entry.to_embed()
|
||||||
|
|
||||||
# Set the embed's title to indicate the amount of things in the queue
|
# Set the embed's title to indicate the amount of things in the queue
|
||||||
count = len(queue)
|
count = len(queue)
|
||||||
embed.title = "Current Queue [{}/{}]".format(index + 1, count)
|
embed.title = "Current Queue [{}/{}]".format(index + 1, count)
|
||||||
|
@ -201,33 +242,45 @@ class Music:
|
||||||
elif '\u2b06' in reaction.emoji:
|
elif '\u2b06' in reaction.emoji:
|
||||||
# A second check just to make sure, as well as ensuring index is higher than 0
|
# 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 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`"
|
fmt = "`Error: Position of this entry has changed, cannot complete your action`"
|
||||||
else:
|
else:
|
||||||
# Remove the current entry
|
# Remove the current entry
|
||||||
del queue[index]
|
del queue[index]
|
||||||
# Add it one position higher
|
# 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
|
# Lets move the index to look at the new place of the entry
|
||||||
index -= 1
|
index -= 1
|
||||||
# If down is clicked
|
# If down is clicked
|
||||||
elif '\u2b07' in reaction.emoji:
|
elif '\u2b07' in reaction.emoji:
|
||||||
# A second check just to make sure, as well as ensuring index is lower than last
|
# 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 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`"
|
fmt = "`Error: Position of this entry has changed, cannot complete your action`"
|
||||||
else:
|
else:
|
||||||
# Remove the current entry
|
# Remove the current entry
|
||||||
del queue[index]
|
del queue[index]
|
||||||
# Add it one position lower
|
# 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
|
# Lets move the index to look at the new place of the entry
|
||||||
index += 1
|
index += 1
|
||||||
# If x is clicked
|
# If x is clicked
|
||||||
elif '\u274c' in reaction.emoji:
|
elif '\u274c' in reaction.emoji:
|
||||||
# A second check just to make sure
|
# A second check just to make sure
|
||||||
if author.guild_permissions.kick_members or author == entry.requester:
|
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`"
|
fmt = "`Error: Position of this entry has changed, cannot complete your action`"
|
||||||
else:
|
else:
|
||||||
# Simply remove the entry in place
|
# Simply remove the entry in place
|
||||||
|
@ -261,16 +314,19 @@ class Music:
|
||||||
|
|
||||||
async def add_entry(self, song, ctx):
|
async def add_entry(self, song, ctx):
|
||||||
state = self.voice_states[ctx.message.guild.id]
|
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
|
return entry
|
||||||
|
|
||||||
async def join_channel(self, channel):
|
async def join_channel(self, channel, text_channel):
|
||||||
state = self.voice_states.get(channel.guild.id)
|
state = self.voice_states.get(channel.guild.id)
|
||||||
log.info("Joining channel {} in guild {}".format(channel.id, 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
|
# Send a message letting the channel know we are attempting to join
|
||||||
try:
|
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:
|
except discord.Forbidden:
|
||||||
msg = None
|
msg = None
|
||||||
|
|
||||||
|
@ -283,7 +339,9 @@ class Music:
|
||||||
await channel.connect()
|
await channel.connect()
|
||||||
|
|
||||||
# If we have connnected, create our voice state
|
# 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 we can send messages, edit it to let the channel know we have succesfully joined
|
||||||
if msg:
|
if msg:
|
||||||
|
@ -306,10 +364,10 @@ class Music:
|
||||||
"Force cleared voice connection on guild {} after being stuck "
|
"Force cleared voice connection on guild {} after being stuck "
|
||||||
"between connected/not connected".format(channel.guild.id))
|
"between connected/not connected".format(channel.guild.id))
|
||||||
# Let them know what happened
|
# Let them know what happened
|
||||||
await channel.send("Sorry but I couldn't connect...try again?")
|
await text_channel.send("Sorry but I couldn't connect...try again?")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@commands.command(pass_context=True)
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
async def progress(self, ctx):
|
async def progress(self, ctx):
|
||||||
|
@ -356,7 +414,7 @@ class Music:
|
||||||
"voice activation`".format(channel.name))
|
"voice activation`".format(channel.name))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return await self.join_channel(channel)
|
return await self.join_channel(channel, ctx.channel)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@ -372,6 +430,10 @@ class Music:
|
||||||
if ctx.message.guild.id not in self.voice_states:
|
if ctx.message.guild.id not in self.voice_states:
|
||||||
if not await ctx.invoke(self.join):
|
if not await ctx.invoke(self.join):
|
||||||
return
|
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)
|
song = re.sub('[<>\[\]]', '', song)
|
||||||
if len(song) == 11:
|
if len(song) == 11:
|
||||||
|
@ -382,8 +444,6 @@ class Music:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
entry = await self.add_entry(song, ctx)
|
entry = await self.add_entry(song, ctx)
|
||||||
except asyncio.TimeoutError:
|
|
||||||
await ctx.send("You took too long!")
|
|
||||||
except LiveStreamError as e:
|
except LiveStreamError as e:
|
||||||
await ctx.send(str(e))
|
await ctx.send(str(e))
|
||||||
except WrongEntryTypeError:
|
except WrongEntryTypeError:
|
||||||
|
@ -396,7 +456,7 @@ class Music:
|
||||||
# We want youtube_dl's error message, but just the first part, the actual "error"
|
# We want youtube_dl's error message, but just the first part, the actual "error"
|
||||||
error = error[2]
|
error = error[2]
|
||||||
# This is colour formatting for the console...it's just going to show up as text on discord
|
# This is colour formatting for the console...it's just going to show up as text on discord
|
||||||
error = error.strip("[0;31mERROR:[0m ")
|
error = error.replace("[0;31mERROR:[0m ", "")
|
||||||
else:
|
else:
|
||||||
# This happens when the download just returns `None`
|
# This happens when the download just returns `None`
|
||||||
error = error[0]
|
error = error[0]
|
||||||
|
@ -409,10 +469,10 @@ class Music:
|
||||||
embed = entry.to_embed()
|
embed = entry.to_embed()
|
||||||
embed.title = "Enqueued song!"
|
embed.title = "Enqueued song!"
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
except (discord.Forbidden, discord.HTTPException):
|
except discord.Forbidden:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@commands.command(pass_context=True)
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(kick_members=True)
|
@utils.custom_perms(kick_members=True)
|
||||||
async def volume(self, ctx, value: int = None):
|
async def volume(self, ctx, value: int = None):
|
||||||
|
@ -431,7 +491,7 @@ class Music:
|
||||||
state.volume = value
|
state.volume = value
|
||||||
await ctx.send('Set the volume to {:.0%}'.format(state.volume))
|
await ctx.send('Set the volume to {:.0%}'.format(state.volume))
|
||||||
|
|
||||||
@commands.command(pass_context=True)
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(kick_members=True)
|
@utils.custom_perms(kick_members=True)
|
||||||
async def pause(self, ctx):
|
async def pause(self, ctx):
|
||||||
|
@ -440,7 +500,7 @@ class Music:
|
||||||
if state and state.voice and state.voice.is_connected():
|
if state and state.voice and state.voice.is_connected():
|
||||||
state.voice.pause()
|
state.voice.pause()
|
||||||
|
|
||||||
@commands.command(pass_context=True)
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(kick_members=True)
|
@utils.custom_perms(kick_members=True)
|
||||||
async def resume(self, ctx):
|
async def resume(self, ctx):
|
||||||
|
@ -449,7 +509,7 @@ class Music:
|
||||||
if state and state.voice and state.voice.is_connected():
|
if state and state.voice and state.voice.is_connected():
|
||||||
state.voice.resume()
|
state.voice.resume()
|
||||||
|
|
||||||
@commands.command(pass_context=True)
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(kick_members=True)
|
@utils.custom_perms(kick_members=True)
|
||||||
async def stop(self, ctx):
|
async def stop(self, ctx):
|
||||||
|
@ -457,23 +517,22 @@ class Music:
|
||||||
This also clears the queue.
|
This also clears the queue.
|
||||||
"""
|
"""
|
||||||
state = self.voice_states.get(ctx.message.guild.id)
|
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()
|
state.songs.clear()
|
||||||
|
|
||||||
# This will cancel the audio event we're using to loop through the queue
|
# 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
|
# Then erase the voice_state entirely, and disconnect from the channel
|
||||||
state.audio_player.cancel()
|
await state.voice.disconnect()
|
||||||
try:
|
try:
|
||||||
del self.voice_states[ctx.message.guild.id]
|
del self.voice_states[ctx.message.guild.id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@commands.command(pass_context=True)
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
async def eta(self, ctx):
|
async def eta(self, ctx):
|
||||||
|
@ -485,7 +544,10 @@ class Music:
|
||||||
await ctx.send('Not playing any music right now...')
|
await ctx.send('Not playing any music right now...')
|
||||||
return
|
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:
|
if len(queue) == 0:
|
||||||
await ctx.send("Nothing currently in the queue")
|
await ctx.send("Nothing currently in the queue")
|
||||||
return
|
return
|
||||||
|
@ -506,7 +568,7 @@ class Music:
|
||||||
return
|
return
|
||||||
await ctx.send("ETA till your next play is: {0[0]}m {0[1]}s".format(divmod(round(count, 0), 60)))
|
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()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
async def queue(self, ctx):
|
async def queue(self, ctx):
|
||||||
|
@ -515,25 +577,35 @@ class Music:
|
||||||
if state is None:
|
if state is None:
|
||||||
await ctx.send("Nothing currently in the queue")
|
await ctx.send("Nothing currently in the queue")
|
||||||
return
|
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:
|
if len(_queue) == 0:
|
||||||
await ctx.send("Nothing currently in the queue")
|
await ctx.send("Nothing currently in the queue")
|
||||||
else:
|
else:
|
||||||
self.bot.loop.create_task(self.queue_embed_task(state, ctx.message.channel, ctx.message.author))
|
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()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
async def queuelength(self, ctx):
|
async def queuelength(self, ctx):
|
||||||
"""Prints the length of the queue"""
|
"""Prints the length of the queue"""
|
||||||
state = self.voice_states.get(ctx.message.guild.id)
|
state = self.voice_states.get(ctx.message.guild.id)
|
||||||
if state:
|
if state is None:
|
||||||
await ctx.send("There are a total of {} songs in the queue".format(len(state.songs.entries)))
|
await ctx.send("Nothing currently in the queue")
|
||||||
else:
|
return
|
||||||
await ctx.send("There are no songs in the queue")
|
|
||||||
|
|
||||||
@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()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
async def skip(self, ctx):
|
async def skip(self, ctx):
|
||||||
|
@ -543,13 +615,13 @@ class Music:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
state = self.voice_states.get(ctx.message.guild.id)
|
state = self.voice_states.get(ctx.message.guild.id)
|
||||||
if state is None or not state.playing or state.current is None:
|
if state is None or not state.playing:
|
||||||
await ctx.send('Not playing any music right now...')
|
await ctx.send('Not playing any music right now...')
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if the person requesting a skip is the requester of the song, if so automatically skip
|
# Check if the person requesting a skip is the requester of the song, if so automatically skip
|
||||||
voter = ctx.message.author
|
voter = ctx.message.author
|
||||||
if voter == state.current.requester:
|
if hasattr(state.current, 'requester') and voter == state.current.requester:
|
||||||
await ctx.send('Requester requested skipping song...')
|
await ctx.send('Requester requested skipping song...')
|
||||||
state.skip()
|
state.skip()
|
||||||
# Otherwise check if the voter has already voted
|
# Otherwise check if the voter has already voted
|
||||||
|
@ -566,7 +638,7 @@ class Music:
|
||||||
else:
|
else:
|
||||||
await ctx.send('You have already voted to skip this song.')
|
await ctx.send('You have already voted to skip this song.')
|
||||||
|
|
||||||
@commands.command(pass_context=True)
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(kick_members=True)
|
@utils.custom_perms(kick_members=True)
|
||||||
async def modskip(self, ctx):
|
async def modskip(self, ctx):
|
||||||
|
@ -579,7 +651,7 @@ class Music:
|
||||||
state.skip()
|
state.skip()
|
||||||
await ctx.send('Song has just been skipped.')
|
await ctx.send('Song has just been skipped.')
|
||||||
|
|
||||||
@commands.command(pass_context=True)
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
async def playing(self, ctx):
|
async def playing(self, ctx):
|
||||||
|
@ -608,6 +680,38 @@ class Music:
|
||||||
# And send the embed
|
# And send the embed
|
||||||
await ctx.send(embed=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):
|
def setup(bot):
|
||||||
bot.add_cog(Music(bot))
|
bot.add_cog(Music(bot))
|
||||||
|
|
16
cogs/osu.py
16
cogs/osu.py
|
@ -44,7 +44,7 @@ class Osu:
|
||||||
|
|
||||||
async def get_users(self):
|
async def get_users(self):
|
||||||
"""A task used to 'cache' all member's and their Osu profile's"""
|
"""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:
|
if data is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -56,11 +56,12 @@ class Osu:
|
||||||
|
|
||||||
@commands.group(invoke_without_command=True)
|
@commands.group(invoke_without_command=True)
|
||||||
@utils.custom_perms(send_messages=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
|
"""Provides basic information about a specific user
|
||||||
|
|
||||||
EXAMPLE: !osu @Person
|
EXAMPLE: !osu @Person
|
||||||
RESULT: Informationa bout that person's osu account"""
|
RESULT: Informationa bout that person's osu account"""
|
||||||
|
await ctx.message.channel.trigger_typing()
|
||||||
if member is None:
|
if member is None:
|
||||||
member = ctx.message.author
|
member = ctx.message.author
|
||||||
|
|
||||||
|
@ -92,6 +93,7 @@ class Osu:
|
||||||
|
|
||||||
EXAMPLE: !osu add username
|
EXAMPLE: !osu add username
|
||||||
RESULT: Links your username to your account, and allows stats to be pulled from it"""
|
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
|
author = ctx.message.author
|
||||||
user = await self.get_user(author, username)
|
user = await self.get_user(author, username)
|
||||||
if user is None:
|
if user is None:
|
||||||
|
@ -103,8 +105,7 @@ class Osu:
|
||||||
'osu_username': user.username
|
'osu_username': user.username
|
||||||
}
|
}
|
||||||
|
|
||||||
if not await utils.add_content('osu', entry):
|
self.bot.db.save('osu', entry)
|
||||||
await utils.update_content('osu', entry, str(author.id))
|
|
||||||
|
|
||||||
await ctx.send("I have just saved your Osu user {}".format(author.display_name))
|
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
|
EXAMPLE: !osu scores @Person 5
|
||||||
RESULT: The top 5 maps for the user @Person"""
|
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
|
# Set the defaults before we go through our passed data to figure out what we want
|
||||||
limit = 5
|
limit = 5
|
||||||
member = ctx.message.author
|
member = ctx.message.author
|
||||||
|
@ -166,7 +167,9 @@ class Osu:
|
||||||
{'name': 'Length', 'value': m.total_length},
|
{'name': 'Length', 'value': m.total_length},
|
||||||
{'name': 'Score', 'value': i.score},
|
{'name': 'Score', 'value': i.score},
|
||||||
{'name': 'Max Combo', 'value': i.maxcombo},
|
{'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': 'Perfect', 'value': "Yes" if i.perfect else "No"},
|
||||||
{'name': 'Rank', 'value': i.rank},
|
{'name': 'Rank', 'value': i.rank},
|
||||||
{'name': 'PP', 'value': i.pp},
|
{'name': 'PP', 'value': i.pp},
|
||||||
|
@ -182,5 +185,6 @@ class Osu:
|
||||||
except utils.CannotPaginate as e:
|
except utils.CannotPaginate as e:
|
||||||
await ctx.send(str(e))
|
await ctx.send(str(e))
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
bot.add_cog(Osu(bot))
|
bot.add_cog(Osu(bot))
|
||||||
|
|
|
@ -38,14 +38,11 @@ class Overwatch:
|
||||||
await ctx.message.channel.trigger_typing()
|
await ctx.message.channel.trigger_typing()
|
||||||
|
|
||||||
user = user or ctx.message.author
|
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!")
|
await ctx.send("I do not have this user's battletag saved!")
|
||||||
return
|
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 hero == "":
|
||||||
# If no hero was provided, we just want the base stats for a player
|
# If no hero was provided, we just want the base stats for a player
|
||||||
|
@ -114,11 +111,12 @@ class Overwatch:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Now just save the battletag
|
# Now just save the battletag
|
||||||
entry = {'member_id': key, 'battletag': bt}
|
entry = {
|
||||||
update = {'battletag': bt}
|
'member_id': key,
|
||||||
# Try adding this first, if that fails, update the saved entry
|
'battletag': bt
|
||||||
if not await utils.add_content('overwatch', entry):
|
}
|
||||||
await utils.update_content('overwatch', update, key)
|
|
||||||
|
self.bot.db.save('overwatch', entry)
|
||||||
await ctx.send("I have just saved your battletag {}".format(ctx.message.author.mention))
|
await ctx.send("I have just saved your battletag {}".format(ctx.message.author.mention))
|
||||||
|
|
||||||
@ow.command(pass_context=True, name="delete", aliases=['remove'])
|
@ow.command(pass_context=True, name="delete", aliases=['remove'])
|
||||||
|
@ -128,10 +126,12 @@ class Overwatch:
|
||||||
|
|
||||||
EXAMPLE: !ow delete
|
EXAMPLE: !ow delete
|
||||||
RESULT: Your battletag is no longer saved"""
|
RESULT: Your battletag is no longer saved"""
|
||||||
if await utils.remove_content('overwatch', str(ctx.message.author.id)):
|
entry = {
|
||||||
await ctx.send("I no longer have your battletag saved {}".format(ctx.message.author.mention))
|
'member_id': str(ctx.message.author.id),
|
||||||
else:
|
'battletag': None
|
||||||
await ctx.send("I don't even have your battletag saved {}".format(ctx.message.author.mention))
|
}
|
||||||
|
self.bot.db.save('overwatch', entry)
|
||||||
|
await ctx.send("I no longer have your battletag saved {}".format(ctx.message.author.mention))
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
|
|
|
@ -180,19 +180,6 @@ class Owner:
|
||||||
except discord.HTTPException as e:
|
except discord.HTTPException as e:
|
||||||
await ctx.send('Unexpected error: `{}`'.format(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.command()
|
||||||
@commands.check(utils.is_owner)
|
@commands.check(utils.is_owner)
|
||||||
async def sendtochannel(self, ctx, cid: int, *, message):
|
async def sendtochannel(self, ctx, cid: int, *, message):
|
||||||
|
|
119
cogs/picarto.py
119
cogs/picarto.py
|
@ -17,10 +17,6 @@ BASE_URL = 'https://ptvappapi.picarto.tv'
|
||||||
api_key = '03e26294-b793-11e5-9a41-005056984bd4'
|
api_key = '03e26294-b793-11e5-9a41-005056984bd4'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Picarto:
|
class Picarto:
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
@ -47,7 +43,7 @@ class Picarto:
|
||||||
try:
|
try:
|
||||||
while not self.bot.is_closed():
|
while not self.bot.is_closed():
|
||||||
await self.get_online_users()
|
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:
|
for data in picarto:
|
||||||
m_id = int(data['member_id'])
|
m_id = int(data['member_id'])
|
||||||
url = data['picarto_url']
|
url = data['picarto_url']
|
||||||
|
@ -62,17 +58,16 @@ class Picarto:
|
||||||
member = server.get_member(m_id)
|
member = server.get_member(m_id)
|
||||||
if member is None:
|
if member is None:
|
||||||
continue
|
continue
|
||||||
server_settings = await utils.get_content('server_settings', s_id)
|
channel_id = self.bot.db.load('server_settings', key=s_id,
|
||||||
if server_settings is not None:
|
pluck='notifications_channel') or int(s_id)
|
||||||
channel_id = int(server_settings.get('notification_channel', s_id))
|
|
||||||
else:
|
|
||||||
channel_id = int(s_id)
|
|
||||||
channel = server.get_channel(channel_id)
|
channel = server.get_channel(channel_id)
|
||||||
try:
|
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:
|
except discord.Forbidden:
|
||||||
pass
|
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:
|
elif not online and data['live'] == 1:
|
||||||
for s_id in data['servers']:
|
for s_id in data['servers']:
|
||||||
server = self.bot.get_guild(int(s_id))
|
server = self.bot.get_guild(int(s_id))
|
||||||
|
@ -81,17 +76,16 @@ class Picarto:
|
||||||
member = server.get_member(m_id)
|
member = server.get_member(m_id)
|
||||||
if member is None:
|
if member is None:
|
||||||
continue
|
continue
|
||||||
server_settings = await utils.get_content('server_settings', s_id)
|
channel_id = self.bot.db.load('server_settings', key=s_id,
|
||||||
if server_settings is not None:
|
pluck='notifications_channel') or int(s_id)
|
||||||
channel_id = int(server_settings.get('notification_channel', s_id))
|
|
||||||
else:
|
|
||||||
channel_id = int(s_id)
|
|
||||||
channel = server.get_channel(channel_id)
|
channel = server.get_channel(channel_id)
|
||||||
try:
|
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:
|
except discord.Forbidden:
|
||||||
pass
|
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)
|
await asyncio.sleep(30)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
|
@ -109,13 +103,11 @@ class Picarto:
|
||||||
|
|
||||||
# If member is not given, base information on the author
|
# If member is not given, base information on the author
|
||||||
member = member or ctx.message.author
|
member = member or ctx.message.author
|
||||||
picarto_entry = await utils.get_content('picarto', str(member.id))
|
member_url = self.bot.db.load('picarto', key=member.id, pluck='picarto_url')
|
||||||
if picarto_entry is None:
|
if member_url is None:
|
||||||
await ctx.send("That user does not have a picarto url setup!")
|
await ctx.send("That user does not have a picarto url setup!")
|
||||||
return
|
return
|
||||||
|
|
||||||
member_url = picarto_entry['picarto_url']
|
|
||||||
|
|
||||||
# Use regex to get the actual username so that we can make a request to the API
|
# 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)
|
stream = re.search("(?<=picarto.tv/)(.*)", member_url).group(1)
|
||||||
url = BASE_URL + '/channel/{}'.format(stream)
|
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
|
# 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
|
# 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)
|
embed.add_field(name=i.title(), value=result)
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
@ -183,29 +175,38 @@ class Picarto:
|
||||||
return
|
return
|
||||||
|
|
||||||
key = str(ctx.message.author.id)
|
key = str(ctx.message.author.id)
|
||||||
entry = {'picarto_url': url,
|
|
||||||
'servers': [str(ctx.message.guild.id)],
|
# Check if it exists first, if it does we don't want to override some of the settings
|
||||||
'notifications_on': 1,
|
result = self.bot.db.load('picarto', key=key)
|
||||||
'live': 0,
|
if result:
|
||||||
'member_id': key}
|
entry = {
|
||||||
if await utils.add_content('picarto', entry):
|
'picarto_url': url,
|
||||||
await ctx.send(
|
'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(
|
"I have just saved your Picarto URL {}, this guild will now be notified when you go live".format(
|
||||||
ctx.message.author.mention))
|
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'])
|
@picarto.command(name='remove', aliases=['delete'])
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
async def remove_picarto_url(self, ctx):
|
async def remove_picarto_url(self, ctx):
|
||||||
"""Removes your picarto URL"""
|
"""Removes your picarto URL"""
|
||||||
if await utils.remove_content('picarto', str(ctx.message.author.id)):
|
entry = {
|
||||||
await ctx.send("I am no longer saving your picarto URL {}".format(ctx.message.author.mention))
|
'picarto_url': None,
|
||||||
else:
|
'member_id': str(ctx.message.author.id)
|
||||||
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))
|
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)
|
@picarto.group(invoke_without_command=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@ -217,39 +218,61 @@ class Picarto:
|
||||||
EXAMPLE: !picarto notify
|
EXAMPLE: !picarto notify
|
||||||
RESULT: This guild will now be notified of you going live"""
|
RESULT: This guild will now be notified of you going live"""
|
||||||
key = str(ctx.message.author.id)
|
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
|
# Check if this user is saved at all
|
||||||
if result is None:
|
if servers is None:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"I do not have your Picarto URL added {}. You can save your Picarto url with !picarto add".format(
|
"I do not have your Picarto URL added {}. You can save your Picarto url with !picarto add".format(
|
||||||
ctx.message.author.mention))
|
ctx.message.author.mention))
|
||||||
# Then check if this guild is already added as one to notify in
|
# 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...")
|
await ctx.send("I am already set to notify in this guild...")
|
||||||
else:
|
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'])
|
@notify.command(name='on', aliases=['start,yes'])
|
||||||
|
@commands.guild_only()
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
async def notify_on(self, ctx):
|
async def notify_on(self, ctx):
|
||||||
"""Turns picarto notifications on
|
"""Turns picarto notifications on
|
||||||
|
|
||||||
EXAMPLE: !picarto notify on
|
EXAMPLE: !picarto notify on
|
||||||
RESULT: Notifications are sent when you go live"""
|
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(
|
await ctx.send("I will notify if you go live {}, you'll get a bajillion followers I promise c:".format(
|
||||||
ctx.message.author.mention))
|
ctx.message.author.mention))
|
||||||
else:
|
else:
|
||||||
await ctx.send("I can't notify if you go live if I don't know your picarto URL yet!")
|
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)
|
@utils.custom_perms(send_messages=True)
|
||||||
async def notify_off(self, ctx):
|
async def notify_off(self, ctx):
|
||||||
"""Turns picarto notifications off
|
"""Turns picarto notifications off
|
||||||
|
|
||||||
EXAMPLE: !picarto notify off
|
EXAMPLE: !picarto notify off
|
||||||
RESULT: No more notifications sent when you go live"""
|
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(
|
await ctx.send(
|
||||||
"I will not notify if you go live anymore {}, "
|
"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(
|
"are you going to stream some lewd stuff you don't want people to see?~".format(
|
||||||
|
|
346
cogs/playlist.py
Normal file
346
cogs/playlist.py
Normal file
|
@ -0,0 +1,346 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
await self.update_dj_for_member(author)
|
||||||
|
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)
|
||||||
|
await self.update_dj_for_member(author)
|
||||||
|
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)
|
||||||
|
await self.update_dj_for_member(author)
|
||||||
|
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
|
||||||
|
author = ctx.message.author
|
||||||
|
key = str(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)
|
||||||
|
playlist = playlist.lower().strip()
|
||||||
|
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)
|
||||||
|
response = response.lower().strip()
|
||||||
|
|
||||||
|
if not response:
|
||||||
|
break
|
||||||
|
|
||||||
|
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()
|
||||||
|
if await self.add_to_playlist(author, playlist, response):
|
||||||
|
delete_msgs.append(await ctx.send("Successfully added song {} to playlist {}".format(response,
|
||||||
|
playlist)))
|
||||||
|
else:
|
||||||
|
delete_msgs.append(await ctx.send("Failed to lookup {}".format(response)))
|
||||||
|
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.lower().strip()) - 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)
|
||||||
|
new_name = new_name.lower().strip()
|
||||||
|
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!"))
|
||||||
|
|
||||||
|
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))
|
|
@ -1,9 +1,11 @@
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
def to_keycap(c):
|
def to_keycap(c):
|
||||||
return '\N{KEYCAP TEN}' if c == 10 else str(c) + '\u20e3'
|
return '\N{KEYCAP TEN}' if c == 10 else str(c) + '\u20e3'
|
||||||
|
|
||||||
|
|
||||||
class Poll:
|
class Poll:
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
self.message = message
|
self.message = message
|
||||||
|
@ -68,7 +70,6 @@ class Polls:
|
||||||
if poll:
|
if poll:
|
||||||
await poll.remove_other_reaction(reaction, user)
|
await poll.remove_other_reaction(reaction, user)
|
||||||
|
|
||||||
|
|
||||||
@commands.command(pass_context=True)
|
@commands.command(pass_context=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import pendulum
|
||||||
import re
|
import re
|
||||||
import asyncio
|
import asyncio
|
||||||
import traceback
|
import traceback
|
||||||
|
import rethinkdb as r
|
||||||
|
|
||||||
|
|
||||||
class Raffle:
|
class Raffle:
|
||||||
|
@ -28,16 +29,20 @@ class Raffle:
|
||||||
async def check_raffles(self):
|
async def check_raffles(self):
|
||||||
# This is used to periodically check the current raffles, and see if they have ended yet
|
# 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
|
# 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:
|
if raffles is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
for raffle in raffles:
|
for raffle in raffles:
|
||||||
server = self.bot.get_guild(int(raffle['server_id']))
|
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
|
# Check to see if this cog can find the server in question
|
||||||
if server is None:
|
if server is None:
|
||||||
|
await self.bot.db.query(r.table('raffles').get(raffle_id).delete())
|
||||||
continue
|
continue
|
||||||
|
|
||||||
now = pendulum.utcnow()
|
now = pendulum.utcnow()
|
||||||
|
@ -47,10 +52,6 @@ class Raffle:
|
||||||
if expires > now:
|
if expires > now:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
title = raffle['title']
|
|
||||||
entrants = raffle['entrants']
|
|
||||||
raffle_id = raffle['id']
|
|
||||||
|
|
||||||
# Make sure there are actually entrants
|
# Make sure there are actually entrants
|
||||||
if len(entrants) == 0:
|
if len(entrants) == 0:
|
||||||
fmt = 'Sorry, but there were no entrants for the raffle `{}`!'.format(title)
|
fmt = 'Sorry, but there were no entrants for the raffle `{}`!'.format(title)
|
||||||
|
@ -72,21 +73,17 @@ class Raffle:
|
||||||
else:
|
else:
|
||||||
fmt = 'The raffle `{}` has just ended! The winner is {}!'.format(title, winner.display_name)
|
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
|
channel_id = self.bot.db.load('server_settings', key=server.id,
|
||||||
# We don't have to wait for it however, so create a task for it
|
pluck='notifications_channel') or server.id
|
||||||
self.bot.loop.create_task(utils.remove_content('raffles', raffle_id))
|
channel = self.bot.get_channel(channel_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)
|
|
||||||
try:
|
try:
|
||||||
await channel.send(fmt)
|
await channel.send(fmt)
|
||||||
except (discord.Forbidden, AttributeError):
|
except (discord.Forbidden, AttributeError):
|
||||||
pass
|
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.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
|
@ -96,11 +93,16 @@ class Raffle:
|
||||||
EXAMPLE: !raffles
|
EXAMPLE: !raffles
|
||||||
RESULT: A list of the raffles setup on this server"""
|
RESULT: A list of the raffles setup on this server"""
|
||||||
r_filter = {'server_id': str(ctx.message.guild.id)}
|
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:
|
if raffles is None:
|
||||||
await ctx.send("There are currently no raffles setup on this server!")
|
await ctx.send("There are currently no raffles setup on this server!")
|
||||||
return
|
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(
|
fmt = "\n\n".join("**Raffle:** {}\n**Title:** {}\n**Total Entrants:** {}\n**Ends:** {} UTC".format(
|
||||||
num + 1,
|
num + 1,
|
||||||
raffle['title'],
|
raffle['title'],
|
||||||
|
@ -122,12 +124,16 @@ class Raffle:
|
||||||
r_filter = {'server_id': str(ctx.message.guild.id)}
|
r_filter = {'server_id': str(ctx.message.guild.id)}
|
||||||
author = ctx.message.author
|
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:
|
if raffles is None:
|
||||||
await ctx.send("There are currently no raffles setup on this server!")
|
await ctx.send("There are currently no raffles setup on this server!")
|
||||||
return
|
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
|
# There is only one raffle, so use the first's info
|
||||||
if raffle_count == 1:
|
if raffle_count == 1:
|
||||||
|
@ -138,8 +144,11 @@ class Raffle:
|
||||||
return
|
return
|
||||||
entrants.append(str(author.id))
|
entrants.append(str(author.id))
|
||||||
|
|
||||||
update = {'entrants': entrants}
|
update = {
|
||||||
await utils.update_content('raffles', update, raffles[0]['id'])
|
'entrants': entrants,
|
||||||
|
'id': raffles[0]['id']
|
||||||
|
}
|
||||||
|
self.bot.db.save('raffles', update)
|
||||||
await ctx.send("{} you have just entered the raffle!".format(author.mention))
|
await ctx.send("{} you have just entered the raffle!".format(author.mention))
|
||||||
# Otherwise, make sure the author gave a valid raffle_id
|
# Otherwise, make sure the author gave a valid raffle_id
|
||||||
elif raffle_id in range(raffle_count - 1):
|
elif raffle_id in range(raffle_count - 1):
|
||||||
|
@ -152,13 +161,17 @@ class Raffle:
|
||||||
entrants.append(str(author.id))
|
entrants.append(str(author.id))
|
||||||
|
|
||||||
# Since we have no good thing to filter things off of, lets use the internal rethinkdb 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))
|
await ctx.send("{} you have just entered the raffle!".format(author.mention))
|
||||||
else:
|
else:
|
||||||
fmt = "Please provide a valid raffle ID, as there are more than one setup on the server! " \
|
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(
|
"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)
|
await ctx.send(fmt)
|
||||||
|
|
||||||
@raffle.command(pass_context=True, name='create', aliases=['start', 'begin', 'add'])
|
@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
|
return re.search("\d+ (minutes?|hours?|days?|weeks?|months?)", m.content.lower()) is not None
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
msg = await self.bot.wait_for('message', timeout=120, check=check)
|
msg = await self.bot.wait_for('message', timeout=120, check=check)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
|
@ -238,14 +252,16 @@ class Raffle:
|
||||||
expires = now.add(**payload)
|
expires = now.add(**payload)
|
||||||
|
|
||||||
# Now we're ready to add this as a new raffle
|
# Now we're ready to add this as a new raffle
|
||||||
entry = {'title': title,
|
entry = {
|
||||||
'expires': expires.to_datetime_string(),
|
'title': title,
|
||||||
'entrants': [],
|
'expires': expires.to_datetime_string(),
|
||||||
'author': str(author.id),
|
'entrants': [],
|
||||||
'server_id': str(server.id)}
|
'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
|
# 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!")
|
await ctx.send("I have just saved your new raffle!")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -324,12 +324,8 @@ class Roles:
|
||||||
|
|
||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
key = str(ctx.message.guild.id)
|
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:
|
if len(self_assignable_roles) == 0:
|
||||||
await ctx.send("There are no self-assignable roles on this server")
|
await ctx.send("There are no self-assignable roles on this server")
|
||||||
return
|
return
|
||||||
|
@ -361,12 +357,8 @@ class Roles:
|
||||||
|
|
||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
key = str(ctx.message.guild.id)
|
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:
|
if len(self_assignable_roles) == 0:
|
||||||
await ctx.send("There are no self-assignable roles on this server")
|
await ctx.send("There are no self-assignable roles on this server")
|
||||||
return
|
return
|
||||||
|
@ -394,17 +386,16 @@ class Roles:
|
||||||
RESULT: Allows users to self-assign the roles Member, and NSFW"""
|
RESULT: Allows users to self-assign the roles Member, and NSFW"""
|
||||||
roles = [str(r.id) for r in role]
|
roles = [str(r.id) for r in role]
|
||||||
key = str(ctx.message.guild.id)
|
key = str(ctx.message.guild.id)
|
||||||
server_settings = await utils.get_content('server_settings', key)
|
|
||||||
|
|
||||||
if server_settings is None:
|
self_assignable_roles = self.bot.db.load('server_settings', key=key, pluck='self_assignable_roles') or []
|
||||||
entry = {'server_id': key, 'self_assignable_roles': roles}
|
self_assignable_roles.extend(roles)
|
||||||
await utils.add_content('server_settings', entry)
|
self_assignable_roles = list(set(self_assignable_roles))
|
||||||
else:
|
entry = {
|
||||||
self_assignable_roles = server_settings.get('self_assignable_roles', [])
|
'server_id': key,
|
||||||
self_assignable_roles.extend(roles)
|
'self_assignable_roles': self_assignable_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.bot.db.save('server_settings', entry)
|
||||||
|
|
||||||
if len(roles) == 1:
|
if len(roles) == 1:
|
||||||
fmt = "Successfully added {} as a self-assignable role".format(role[0].name)
|
fmt = "Successfully added {} as a self-assignable role".format(role[0].name)
|
||||||
|
@ -423,12 +414,7 @@ class Roles:
|
||||||
EXAMPLE: !assigns list
|
EXAMPLE: !assigns list
|
||||||
RESUL: A list of all the self-assignable roles"""
|
RESUL: A list of all the self-assignable roles"""
|
||||||
key = str(ctx.message.guild.id)
|
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:
|
if len(self_assignable_roles) == 0:
|
||||||
await ctx.send("There are no self-assignable roles on this server")
|
await ctx.send("There are no self-assignable roles on this server")
|
||||||
return
|
return
|
||||||
|
@ -458,12 +444,7 @@ class Roles:
|
||||||
EXAMPLE: !assigns remove Member NSFW
|
EXAMPLE: !assigns remove Member NSFW
|
||||||
RESULT: Removes the ability for users to self-assign the roles Member, and NSFW"""
|
RESULT: Removes the ability for users to self-assign the roles Member, and NSFW"""
|
||||||
key = str(ctx.message.guild.id)
|
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:
|
if len(self_assignable_roles) == 0:
|
||||||
await ctx.send("There are no self-assignable roles on this server")
|
await ctx.send("There are no self-assignable roles on this server")
|
||||||
return
|
return
|
||||||
|
@ -478,8 +459,11 @@ class Roles:
|
||||||
else:
|
else:
|
||||||
fmt += "\n{} is no longer a self-assignable role".format(r.name)
|
fmt += "\n{} is no longer a self-assignable role".format(r.name)
|
||||||
|
|
||||||
update = {'self_assignable_roles': self_assignable_roles}
|
update = {
|
||||||
await utils.update_content('server_settings', update, key)
|
'self_assignable_roles': self_assignable_roles,
|
||||||
|
'server_id': key
|
||||||
|
}
|
||||||
|
self.bot.db.save('server_settings', update)
|
||||||
await ctx.send(fmt)
|
await ctx.send(fmt)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ class Stats:
|
||||||
await ctx.send("`{}` is not a valid command".format(command))
|
await ctx.send("`{}` is not a valid command".format(command))
|
||||||
return
|
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:
|
if command_stats is None:
|
||||||
await ctx.send("That command has never been used! You know I worked hard on that! :c")
|
await ctx.send("That command has never been used! You know I worked hard on that! :c")
|
||||||
return
|
return
|
||||||
|
@ -103,7 +103,7 @@ class Stats:
|
||||||
if re.search('(author|me)', option):
|
if re.search('(author|me)', option):
|
||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
# First lets get all the command usage
|
# 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
|
# Now use a dictionary comprehension to get just the command name, and usage
|
||||||
# Based on the author's usage of the command
|
# Based on the author's usage of the command
|
||||||
stats = {data['command']: data['member_usage'].get(str(author.id)) for data in command_stats
|
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):
|
elif re.search('server', option):
|
||||||
# This is exactly the same as above, except server usage instead of member usage
|
# This is exactly the same as above, except server usage instead of member usage
|
||||||
server = ctx.message.guild
|
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
|
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}
|
if data['server_usage'].get(str(server.id), 0) > 0}
|
||||||
sorted_stats = sorted(stats.items(), key=lambda x: x[1], reverse=True)
|
sorted_stats = sorted(stats.items(), key=lambda x: x[1], reverse=True)
|
||||||
|
@ -148,7 +148,7 @@ class Stats:
|
||||||
|
|
||||||
EXAMPLE: !mostboops
|
EXAMPLE: !mostboops
|
||||||
RESULT: You've booped @OtherPerson 351253897120935712093572193057310298 times!"""
|
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:
|
if boops is None:
|
||||||
await ctx.send("You have not booped anyone {} Why the heck not...?".format(ctx.message.author.mention))
|
await ctx.send("You have not booped anyone {} Why the heck not...?".format(ctx.message.author.mention))
|
||||||
return
|
return
|
||||||
|
@ -182,7 +182,7 @@ class Stats:
|
||||||
RESULT: The list of your booped members!"""
|
RESULT: The list of your booped members!"""
|
||||||
await ctx.message.channel.trigger_typing()
|
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:
|
if boops is None:
|
||||||
await ctx.send("You have not booped anyone {} Why the heck not...?".format(ctx.message.author.mention))
|
await ctx.send("You have not booped anyone {} Why the heck not...?".format(ctx.message.author.mention))
|
||||||
return
|
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
|
# 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]
|
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:
|
if battles is None or len(battles) == 0:
|
||||||
await ctx.send("No one has battled on this server!")
|
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
|
# 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
|
# 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:
|
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
|
# 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:
|
if len([entry for entry in all_members if entry['member_id'] == str(member.id)]) == 0:
|
||||||
|
|
88
cogs/tags.py
88
cogs/tags.py
|
@ -4,7 +4,7 @@ import discord
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import rethinkdb as r
|
|
||||||
|
|
||||||
class Tags:
|
class Tags:
|
||||||
"""This class contains all the commands for custom tags"""
|
"""This class contains all the commands for custom tags"""
|
||||||
|
@ -20,9 +20,9 @@ class Tags:
|
||||||
|
|
||||||
EXAMPLE: !tags
|
EXAMPLE: !tags
|
||||||
RESULT: All tags setup on this server"""
|
RESULT: All tags setup on this server"""
|
||||||
tags = await utils.get_content('tags', str(ctx.message.guild.id))
|
tags = self.bot.db.load('tags', key=ctx.message.guild.id, pluck='tags')
|
||||||
if tags and len(tags['tags']) > 0:
|
if tags:
|
||||||
entries = [t['trigger'] for t in tags['tags']]
|
entries = [t['trigger'] for t in tags]
|
||||||
pages = utils.Pages(self.bot, message=ctx.message, entries=entries)
|
pages = utils.Pages(self.bot, message=ctx.message, entries=entries)
|
||||||
await pages.paginate()
|
await pages.paginate()
|
||||||
else:
|
else:
|
||||||
|
@ -36,16 +36,16 @@ class Tags:
|
||||||
|
|
||||||
EXAMPLE: !mytags
|
EXAMPLE: !mytags
|
||||||
RESULT: All your tags setup on this server"""
|
RESULT: All your tags setup on this server"""
|
||||||
tags = await utils.get_content('tags', str(ctx.message.guild.id))
|
tags = self.bot.db.load('tags', key=ctx.message.guild.id, pluck='tags')
|
||||||
if not tags:
|
if tags:
|
||||||
await ctx.send("There are no tags setup on this server!")
|
entries = [t['trigger'] for t in tags if t['author'] == str(ctx.message.author.id)]
|
||||||
else:
|
|
||||||
entries = [t['trigger'] for t in tags['tags'] if t['author'] == str(ctx.message.author.id)]
|
|
||||||
if len(entries) == 0:
|
if len(entries) == 0:
|
||||||
await ctx.send("You have no tags setup on this server!")
|
await ctx.send("You have no tags setup on this server!")
|
||||||
else:
|
else:
|
||||||
pages = utils.Pages(self.bot, message=ctx.message, entries=entries)
|
pages = utils.Pages(self.bot, message=ctx.message, entries=entries)
|
||||||
await pages.paginate()
|
await pages.paginate()
|
||||||
|
else:
|
||||||
|
await ctx.send("There are no tags setup on this server!")
|
||||||
|
|
||||||
@commands.group(invoke_without_command=True)
|
@commands.group(invoke_without_command=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@ -57,9 +57,9 @@ class Tags:
|
||||||
EXAMPLE: !tag butts
|
EXAMPLE: !tag butts
|
||||||
RESULT: Whatever you setup for the butts tag!!"""
|
RESULT: Whatever you setup for the butts tag!!"""
|
||||||
tag = tag.lower().strip()
|
tag = tag.lower().strip()
|
||||||
tags = await utils.get_content('tags', str(ctx.message.guild.id))
|
tags = self.bot.db.load('tags', key=ctx.message.guild.id, pluck='tags')
|
||||||
if tags and len(tags['tags']) > 0:
|
if tags:
|
||||||
for t in tags['tags']:
|
for t in tags:
|
||||||
if t['trigger'].lower().strip() == tag:
|
if t['trigger'].lower().strip() == tag:
|
||||||
await ctx.send("\u200B{}".format(t['result']))
|
await ctx.send("\u200B{}".format(t['result']))
|
||||||
return
|
return
|
||||||
|
@ -67,7 +67,6 @@ class Tags:
|
||||||
else:
|
else:
|
||||||
await ctx.send("There are no tags setup on this server!")
|
await ctx.send("There are no tags setup on this server!")
|
||||||
|
|
||||||
|
|
||||||
@tag.command(name='add', aliases=['create', 'setup'])
|
@tag.command(name='add', aliases=['create', 'setup'])
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
|
@ -76,8 +75,10 @@ class Tags:
|
||||||
|
|
||||||
EXAMPLE: !tag add
|
EXAMPLE: !tag add
|
||||||
RESULT: A follow-along in order to create a new tag"""
|
RESULT: A follow-along in order to create a new tag"""
|
||||||
|
|
||||||
def check(m):
|
def check(m):
|
||||||
return m.channel == ctx.message.channel and m.author == ctx.message.author and len(m.content) > 0
|
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?")
|
my_msg = await ctx.send("Ready to setup a new tag! What do you want the trigger for the tag to be?")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -92,12 +93,14 @@ class Tags:
|
||||||
await ctx.send("Please keep tag triggers under 100 characters")
|
await ctx.send("Please keep tag triggers under 100 characters")
|
||||||
return
|
return
|
||||||
elif trigger in forbidden_tags:
|
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
|
return
|
||||||
|
|
||||||
tags = await utils.get_content('tags', str(ctx.message.guild.id))
|
tags = self.bot.db.load('tags', key=ctx.message.guild.id, pluck='tags') or []
|
||||||
if tags and len(tags['tags']) > 0:
|
if tags:
|
||||||
for t in tags['tags']:
|
for t in tags:
|
||||||
if t['trigger'].lower().strip() == trigger:
|
if t['trigger'].lower().strip() == trigger:
|
||||||
await ctx.send("There is already a tag setup called {}!".format(trigger))
|
await ctx.send("There is already a tag setup called {}!".format(trigger))
|
||||||
return
|
return
|
||||||
|
@ -112,7 +115,9 @@ class Tags:
|
||||||
await ctx.send("You can't create a tag with {}!".format(trigger))
|
await ctx.send("You can't create a tag with {}!".format(trigger))
|
||||||
return
|
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:
|
try:
|
||||||
msg = await self.bot.wait_for("message", check=check, timeout=60)
|
msg = await self.bot.wait_for("message", check=check, timeout=60)
|
||||||
|
@ -133,13 +138,12 @@ class Tags:
|
||||||
'trigger': trigger,
|
'trigger': trigger,
|
||||||
'result': result
|
'result': result
|
||||||
}
|
}
|
||||||
|
tags.append(tag)
|
||||||
entry = {
|
entry = {
|
||||||
'server_id': str(ctx.message.guild.id),
|
'server_id': str(ctx.message.guild.id),
|
||||||
'tags': [tag]
|
'tags': tags
|
||||||
}
|
}
|
||||||
key = str(ctx.message.guild.id)
|
self.bot.db.save('tags', entry)
|
||||||
if not await utils.add_content('tags', entry):
|
|
||||||
await utils.update_content('tags', {'tags': r.row['tags'].append(tag)}, key)
|
|
||||||
await ctx.send("I have just setup a new tag for this server! You can call your tag with {}".format(trigger))
|
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')
|
@tag.command(name='edit')
|
||||||
|
@ -149,16 +153,17 @@ class Tags:
|
||||||
"""This will allow you to edit a tag that you have created
|
"""This will allow you to edit a tag that you have created
|
||||||
EXAMPLE: !tag edit this tag
|
EXAMPLE: !tag edit this tag
|
||||||
RESULT: I'll ask what you want the new result to be"""
|
RESULT: I'll ask what you want the new result to be"""
|
||||||
key = str(ctx.message.guild.id)
|
tags = self.bot.db.load('tags', key=ctx.message.guild.id, pluck='tags')
|
||||||
tags = await utils.get_content('tags', key)
|
|
||||||
def check(m):
|
def check(m):
|
||||||
return m.channel == ctx.message.channel and m.author == ctx.message.author and len(m.content) > 0
|
return m.channel == ctx.message.channel and m.author == ctx.message.author and len(m.content) > 0
|
||||||
|
|
||||||
if tags and len(tags['tags']) > 0:
|
if tags:
|
||||||
for i, t in enumerate(tags['tags']):
|
for i, t in enumerate(tags):
|
||||||
if t['trigger'] == tag:
|
if t['trigger'] == tag:
|
||||||
if t['author'] == str(ctx.message.author.id):
|
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:
|
try:
|
||||||
msg = await self.bot.wait_for("message", check=check, timeout=60)
|
msg = await self.bot.wait_for("message", check=check, timeout=60)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
|
@ -166,13 +171,17 @@ class Tags:
|
||||||
return
|
return
|
||||||
new_tag = t.copy()
|
new_tag = t.copy()
|
||||||
new_tag['result'] = msg.content
|
new_tag['result'] = msg.content
|
||||||
tags['tags'][i] = new_tag
|
tags[i] = new_tag
|
||||||
try:
|
try:
|
||||||
await my_msg.delete()
|
await my_msg.delete()
|
||||||
await msg.delete()
|
await msg.delete()
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
pass
|
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))
|
await ctx.send("Alright, the tag {} has been updated".format(tag))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
@ -182,7 +191,6 @@ class Tags:
|
||||||
else:
|
else:
|
||||||
await ctx.send("There are no tags setup on this server!")
|
await ctx.send("There are no tags setup on this server!")
|
||||||
|
|
||||||
|
|
||||||
@tag.command(name='delete', aliases=['remove', 'stop'])
|
@tag.command(name='delete', aliases=['remove', 'stop'])
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@utils.custom_perms(send_messages=True)
|
@utils.custom_perms(send_messages=True)
|
||||||
|
@ -192,14 +200,18 @@ class Tags:
|
||||||
|
|
||||||
EXAMPLE: !tag delete stupid_tag
|
EXAMPLE: !tag delete stupid_tag
|
||||||
RESULT: Deletes that stupid tag"""
|
RESULT: Deletes that stupid tag"""
|
||||||
key = str(ctx.message.guild.id)
|
tags = self.bot.db.load('tags', key=ctx.message.guild.id, pluck='tags')
|
||||||
tags = await utils.get_content('tags', key)
|
if tags:
|
||||||
if tags and len(tags['tags']) > 0:
|
for t in tags:
|
||||||
for t in tags['tags']:
|
if t['trigger'].lower().strip() == tag:
|
||||||
if t['trigger'] == tag:
|
if ctx.message.author.permissions_in(ctx.message.channel).manage_guild or str(
|
||||||
if ctx.message.author.permissions_in(ctx.message.channel).manage_guild or str(ctx.message.author.id) == t['author']:
|
ctx.message.author.id) == t['author']:
|
||||||
tags['tags'].remove(t)
|
tags.remove(t)
|
||||||
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("I have just removed the tag {}".format(tag))
|
await ctx.send("I have just removed the tag {}".format(tag))
|
||||||
else:
|
else:
|
||||||
await ctx.send("You don't own that tag! You can't remove it!")
|
await ctx.send("You don't own that tag! You can't remove it!")
|
||||||
|
|
|
@ -191,7 +191,7 @@ class TicTacToe:
|
||||||
await ctx.send("{} has won this game of TicTacToe, better luck next time {}".format(winner.display_name,
|
await ctx.send("{} has won this game of TicTacToe, better luck next time {}".format(winner.display_name,
|
||||||
loser.display_name))
|
loser.display_name))
|
||||||
# Handle updating ratings based on the winner and loser
|
# 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
|
# This game has ended, delete it so another one can be made
|
||||||
del self.boards[ctx.message.guild.id]
|
del self.boards[ctx.message.guild.id]
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -44,7 +44,7 @@ class Twitch:
|
||||||
# Loop through as long as the bot is connected
|
# Loop through as long as the bot is connected
|
||||||
try:
|
try:
|
||||||
while not self.bot.is_closed():
|
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:
|
for data in twitch:
|
||||||
m_id = int(data['member_id'])
|
m_id = int(data['member_id'])
|
||||||
url = data['twitch_url']
|
url = data['twitch_url']
|
||||||
|
@ -59,11 +59,8 @@ class Twitch:
|
||||||
member = server.get_member(m_id)
|
member = server.get_member(m_id)
|
||||||
if member is None:
|
if member is None:
|
||||||
continue
|
continue
|
||||||
server_settings = await utils.get_content('server_settings', s_id)
|
channel_id = self.bot.db.load('server_settings', key=s_id,
|
||||||
if server_settings is not None:
|
pluck='notifications_channel') or int(s_id)
|
||||||
channel_id = int(server_settings.get('notification_channel', s_id))
|
|
||||||
else:
|
|
||||||
channel_id = int(s_id)
|
|
||||||
channel = server.get_channel(channel_id)
|
channel = server.get_channel(channel_id)
|
||||||
if channel is None:
|
if channel is None:
|
||||||
continue
|
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']))
|
await channel.send("{} has just gone live! View their stream at <{}>".format(member.display_name, data['twitch_url']))
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
pass
|
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:
|
elif not online and data['live'] == 1:
|
||||||
for s_id in data['servers']:
|
for s_id in data['servers']:
|
||||||
server = self.bot.get_guild(int(s_id))
|
server = self.bot.get_guild(int(s_id))
|
||||||
|
@ -80,17 +77,14 @@ class Twitch:
|
||||||
member = server.get_member(m_id)
|
member = server.get_member(m_id)
|
||||||
if member is None:
|
if member is None:
|
||||||
continue
|
continue
|
||||||
server_settings = await utils.get_content('server_settings', s_id)
|
channel_id = self.bot.db.load('server_settings', key=s_id,
|
||||||
if server_settings is not None:
|
pluck='notifications_channel') or int(s_id)
|
||||||
channel_id = int(server_settings.get('notification_channel', s_id))
|
|
||||||
else:
|
|
||||||
channel_id = int(s_id)
|
|
||||||
channel = server.get_channel(channel_id)
|
channel = server.get_channel(channel_id)
|
||||||
try:
|
try:
|
||||||
await channel.send("{} has just gone offline! View their stream next time at <{}>".format(member.display_name, data['twitch_url']))
|
await channel.send("{} has just gone offline! View their stream next time at <{}>".format(member.display_name, data['twitch_url']))
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
pass
|
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)
|
await asyncio.sleep(30)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
|
@ -110,12 +104,11 @@ class Twitch:
|
||||||
if member is None:
|
if member is None:
|
||||||
member = ctx.message.author
|
member = ctx.message.author
|
||||||
|
|
||||||
result = await utils.get_content('twitch', str(member.id))
|
url = self.bot.db.load('twitch', key=member.id, pluck='twitch_url')
|
||||||
if result is None:
|
if url is None:
|
||||||
await ctx.send("{} has not saved their twitch URL yet!".format(member.name))
|
await ctx.send("{} has not saved their twitch URL yet!".format(member.name))
|
||||||
return
|
return
|
||||||
|
|
||||||
url = result['twitch_url']
|
|
||||||
user = re.search("(?<=twitch.tv/)(.*)", url).group(1)
|
user = re.search("(?<=twitch.tv/)(.*)", url).group(1)
|
||||||
twitch_url = "https://api.twitch.tv/kraken/channels/{}".format(user)
|
twitch_url = "https://api.twitch.tv/kraken/channels/{}".format(user)
|
||||||
payload = {'client_id': self.key}
|
payload = {'client_id': self.key}
|
||||||
|
@ -170,18 +163,23 @@ class Twitch:
|
||||||
return
|
return
|
||||||
|
|
||||||
key = str(ctx.message.author.id)
|
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
|
# Check if it exists first, if it does we don't want to override some of the settings
|
||||||
# If they have, update the URL, otherwise create a new entry
|
result = self.bot.db.load('twitch', key=key)
|
||||||
# Assuming they're not live, and notifications should be on
|
if result:
|
||||||
if not await utils.add_content('twitch', entry):
|
entry = {
|
||||||
await utils.update_content('twitch', update, key)
|
'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))
|
await ctx.send("I have just saved your twitch url {}".format(ctx.message.author.mention))
|
||||||
|
|
||||||
@twitch.command(name='remove', aliases=['delete'])
|
@twitch.command(name='remove', aliases=['delete'])
|
||||||
|
@ -192,8 +190,12 @@ class Twitch:
|
||||||
|
|
||||||
EXAMPLE: !twitch remove
|
EXAMPLE: !twitch remove
|
||||||
RESULT: I stop saving your twitch URL"""
|
RESULT: I stop saving your twitch URL"""
|
||||||
# Just try to remove it, if it doesn't exist, nothing is going to happen
|
entry = {
|
||||||
await utils.remove_content('twitch', str(ctx.message.author.id))
|
'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))
|
await ctx.send("I am no longer saving your twitch URL {}".format(ctx.message.author.mention))
|
||||||
|
|
||||||
@twitch.group(invoke_without_command=True)
|
@twitch.group(invoke_without_command=True)
|
||||||
|
@ -206,17 +208,22 @@ class Twitch:
|
||||||
EXAMPLE: !twitch notify
|
EXAMPLE: !twitch notify
|
||||||
RESULT: This server will now be notified when you go live"""
|
RESULT: This server will now be notified when you go live"""
|
||||||
key = str(ctx.message.author.id)
|
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
|
# Check if this user is saved at all
|
||||||
if result is None:
|
if servers is None:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"I do not have your twitch URL added {}. You can save your twitch url with !twitch add".format(
|
"I do not have your twitch URL added {}. You can save your twitch url with !twitch add".format(
|
||||||
ctx.message.author.mention))
|
ctx.message.author.mention))
|
||||||
# Then check if this server is already added as one to notify in
|
# 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...")
|
await ctx.send("I am already set to notify in this server...")
|
||||||
else:
|
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")
|
await ctx.send("This server will now be notified if you go live")
|
||||||
|
|
||||||
@notify.command(name='on', aliases=['start,yes'])
|
@notify.command(name='on', aliases=['start,yes'])
|
||||||
|
@ -227,7 +234,14 @@ class Twitch:
|
||||||
|
|
||||||
EXAMPLE: !twitch notify on
|
EXAMPLE: !twitch notify on
|
||||||
RESULT: Notifications will be sent when you go live"""
|
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(
|
await ctx.send("I will notify if you go live {}, you'll get a bajillion followers I promise c:".format(
|
||||||
ctx.message.author.mention))
|
ctx.message.author.mention))
|
||||||
else:
|
else:
|
||||||
|
@ -241,7 +255,14 @@ class Twitch:
|
||||||
|
|
||||||
EXAMPLE: !twitch notify off
|
EXAMPLE: !twitch notify off
|
||||||
RESULT: Notifications will not be sent when you go live"""
|
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(
|
await ctx.send(
|
||||||
"I will not notify if you go live anymore {}, "
|
"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(
|
"are you going to stream some lewd stuff you don't want people to see?~".format(
|
||||||
|
|
|
@ -4,3 +4,4 @@ from .config import *
|
||||||
from .utilities import *
|
from .utilities import *
|
||||||
from .images import create_banner
|
from .images import create_banner
|
||||||
from .paginator import Pages, CannotPaginate, DetailedPages
|
from .paginator import Pages, CannotPaginate, DetailedPages
|
||||||
|
from .database import DB
|
||||||
|
|
|
@ -12,7 +12,6 @@ required_tables = {
|
||||||
'battle_records': 'member_id',
|
'battle_records': 'member_id',
|
||||||
'boops': 'member_id',
|
'boops': 'member_id',
|
||||||
'command_usage': 'command',
|
'command_usage': 'command',
|
||||||
'motd': 'date',
|
|
||||||
'overwatch': 'member_id',
|
'overwatch': 'member_id',
|
||||||
'picarto': 'member_id',
|
'picarto': 'member_id',
|
||||||
'server_settings': 'server_id',
|
'server_settings': 'server_id',
|
||||||
|
@ -21,7 +20,8 @@ required_tables = {
|
||||||
'osu': 'member_id',
|
'osu': 'member_id',
|
||||||
'tags': 'server_id',
|
'tags': 'server_id',
|
||||||
'tictactoe': 'member_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
|
return ctx.bot.owner.id == ctx.message.author.id
|
||||||
|
|
||||||
|
|
||||||
def should_ignore(message):
|
def should_ignore(bot, message):
|
||||||
if message.guild is None:
|
if message.guild is None:
|
||||||
return False
|
return False
|
||||||
try:
|
ignored = bot.db.load('server_settings', key=message.guild.id, pluck='ignored')
|
||||||
server_settings = config.cache.get('server_settings').values
|
if not ignored:
|
||||||
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):
|
|
||||||
return False
|
return False
|
||||||
|
return str(message.author.id) in ignored['members'] or str(message.channel.id) in ignored['channels']
|
||||||
|
|
||||||
|
|
||||||
def custom_perms(**perms):
|
def custom_perms(**perms):
|
||||||
|
@ -94,13 +91,10 @@ def custom_perms(**perms):
|
||||||
for perm, setting in perms.items():
|
for perm, setting in perms.items():
|
||||||
setattr(required_perm, perm, setting)
|
setattr(required_perm, perm, setting)
|
||||||
|
|
||||||
try:
|
required_perm_value = ctx.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='permissions') or {}
|
||||||
server_settings = config.cache.get('server_settings').values
|
required_perm_value = required_perm_value.get(ctx.command.qualified_name)
|
||||||
required_perm_value = [x for x in server_settings if x['server_id'] == str(
|
if required_perm_value:
|
||||||
ctx.message.guild.id)][0]['permissions'][ctx.command.qualified_name]
|
|
||||||
required_perm = discord.Permissions(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
|
# Now just check if the person running the command has these permissions
|
||||||
return member_perms >= required_perm
|
return member_perms >= required_perm
|
||||||
|
|
|
@ -23,20 +23,6 @@ except KeyError:
|
||||||
quit()
|
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
|
# Default bot's description
|
||||||
bot_description = global_config.get("description")
|
bot_description = global_config.get("description")
|
||||||
# Bot's default prefix for commands
|
# Bot's default prefix for commands
|
||||||
|
@ -80,7 +66,9 @@ extensions = [
|
||||||
'cogs.tags',
|
'cogs.tags',
|
||||||
'cogs.roulette',
|
'cogs.roulette',
|
||||||
'cogs.music',
|
'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}
|
# {'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}
|
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):
|
def command_prefix(bot, message):
|
||||||
# We do not want to make a query for every message that is sent
|
if not message.guild:
|
||||||
# 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):
|
|
||||||
return default_prefix
|
return default_prefix
|
||||||
|
return bot.db.load('server_settings', key=message.guild.id, pluck='prefix') or 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
|
|
||||||
|
|
139
cogs/utils/database.py
Normal file
139
cogs/utils/database.py
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
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))
|
||||||
|
if result.get('replaced', 0) == 0 and result.get('unchanged', 0) == 0:
|
||||||
|
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
|
|
@ -33,7 +33,7 @@ def get_all_subcommands(command):
|
||||||
yield from get_all_subcommands(subcmd)
|
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:
|
if type(channel) is discord.DMChannel:
|
||||||
server = 'DMs'
|
server = 'DMs'
|
||||||
elif channel.is_nsfw():
|
elif channel.is_nsfw():
|
||||||
|
@ -43,12 +43,8 @@ async def channel_is_nsfw(channel):
|
||||||
|
|
||||||
channel = str(channel.id)
|
channel = str(channel.id)
|
||||||
|
|
||||||
server_settings = await config.get_content('server_settings', server)
|
channels = db.load('server_settings', key=server, pluck='nsfw_channels') or []
|
||||||
|
return channel in channels
|
||||||
try:
|
|
||||||
return channel in server_settings['nsfw_channels']
|
|
||||||
except (TypeError, IndexError, KeyError):
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
async def download_image(url):
|
async def download_image(url):
|
||||||
|
@ -101,11 +97,11 @@ async def request(url, *, headers=None, payload=None, method='GET', attr='json')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
async def update_records(key, winner, loser):
|
async def update_records(key, db, winner, loser):
|
||||||
# We're using the Harkness scale to rate
|
# We're using the Harkness scale to rate
|
||||||
# http://opnetchessclub.wikidot.com/harkness-rating-system
|
# http://opnetchessclub.wikidot.com/harkness-rating-system
|
||||||
r_filter = lambda row: (row['member_id'] == str(winner.id)) | (row['member_id'] == str(loser.id))
|
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 = await db.actual_load(key, table_filter=r_filter)
|
||||||
|
|
||||||
winner_stats = {}
|
winner_stats = {}
|
||||||
loser_stats = {}
|
loser_stats = {}
|
||||||
|
@ -150,12 +146,8 @@ async def update_records(key, winner, loser):
|
||||||
loser_losses += 1
|
loser_losses += 1
|
||||||
|
|
||||||
# Now save the new wins, losses, and ratings
|
# Now save the new wins, losses, and ratings
|
||||||
winner_stats = {'wins': winner_wins, 'losses': winner_losses, 'rating': winner_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}
|
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)):
|
db.save(key, winner_stats)
|
||||||
winner_stats['member_id'] = str(winner.id)
|
db.save(key, loser_stats)
|
||||||
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)
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
from .downloader import Downloader
|
from .downloader import Downloader
|
||||||
from .playlist import Playlist
|
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
|
from .playlist import Playlist
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ class BasePlaylistEntry:
|
||||||
|
|
||||||
|
|
||||||
class URLPlaylistEntry(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__()
|
super().__init__()
|
||||||
|
|
||||||
self.playlist = playlist
|
self.playlist = playlist
|
||||||
|
@ -102,8 +102,6 @@ class URLPlaylistEntry(BasePlaylistEntry):
|
||||||
self.thumbnail = thumbnail
|
self.thumbnail = thumbnail
|
||||||
self.expected_filename = expected_filename
|
self.expected_filename = expected_filename
|
||||||
self.meta = meta
|
self.meta = meta
|
||||||
self.requester = ctx.message.author
|
|
||||||
self.channel = ctx.message.channel
|
|
||||||
self.download_folder = self.playlist.downloader.download_folder
|
self.download_folder = self.playlist.downloader.download_folder
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -132,7 +130,6 @@ class URLPlaylistEntry(BasePlaylistEntry):
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, playlist, jsonstring):
|
def from_json(cls, playlist, jsonstring):
|
||||||
data = json.loads(jsonstring)
|
data = json.loads(jsonstring)
|
||||||
print(data)
|
|
||||||
# TODO: version check
|
# TODO: version check
|
||||||
url = data['url']
|
url = data['url']
|
||||||
title = data['title']
|
title = data['title']
|
||||||
|
@ -187,7 +184,6 @@ class URLPlaylistEntry(BasePlaylistEntry):
|
||||||
|
|
||||||
# the generic extractor requires special handling
|
# the generic extractor requires special handling
|
||||||
if extractor == 'generic':
|
if extractor == 'generic':
|
||||||
# print("Handling generic")
|
|
||||||
flistdir = [f.rsplit('-', 1)[0] for f in os.listdir(self.download_folder)]
|
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)
|
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)]
|
os.listdir(self.download_folder)[flistdir.index(expected_fname_noex)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# print("Resolved %s to %s" % (self.expected_filename, lfile))
|
|
||||||
lsize = os.path.getsize(lfile)
|
lsize = os.path.getsize(lfile)
|
||||||
# print("Remote size: %s Local size: %s" % (rsize, lsize))
|
|
||||||
|
|
||||||
if lsize != rsize:
|
if lsize != rsize:
|
||||||
await self._really_download(hash=True)
|
await self._really_download(hash=True)
|
||||||
else:
|
else:
|
||||||
# print("[Download] Cached:", self.url)
|
|
||||||
self.filename = lfile
|
self.filename = lfile
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# print("File not found in cache (%s)" % expected_fname_noex)
|
|
||||||
await self._really_download(hash=True)
|
await self._really_download(hash=True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -227,15 +219,9 @@ class URLPlaylistEntry(BasePlaylistEntry):
|
||||||
|
|
||||||
if expected_fname_base in ldir:
|
if expected_fname_base in ldir:
|
||||||
self.filename = os.path.join(self.download_folder, expected_fname_base)
|
self.filename = os.path.join(self.download_folder, expected_fname_base)
|
||||||
print("[Download] Cached:", self.url)
|
|
||||||
|
|
||||||
elif expected_fname_noex in flistdir:
|
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)])
|
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:
|
else:
|
||||||
await self._really_download()
|
await self._really_download()
|
||||||
|
@ -252,15 +238,12 @@ class URLPlaylistEntry(BasePlaylistEntry):
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
# noinspection PyShadowingBuiltins
|
||||||
async def _really_download(self, *, hash=False):
|
async def _really_download(self, *, hash=False):
|
||||||
print("[Download] Started:", self.url)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = await self.playlist.downloader.extract_info(self.playlist.loop, self.url, download=True)
|
result = await self.playlist.downloader.extract_info(self.playlist.loop, self.url, download=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ExtractionError(e)
|
raise ExtractionError(e)
|
||||||
|
|
||||||
print("[Download] Complete:", self.url)
|
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
raise ExtractionError("ytdl broke and hell if I know why")
|
raise ExtractionError("ytdl broke and hell if I know why")
|
||||||
# What the fuck do I do now?
|
# What the fuck do I do now?
|
||||||
|
@ -293,4 +276,4 @@ class URLPlaylistEntry(BasePlaylistEntry):
|
||||||
fmt = "{0[0]}m {0[1]}s".format(length)
|
fmt = "{0[0]}m {0[1]}s".format(length)
|
||||||
embed.add_field(name='Duration', value=fmt, inline=False)
|
embed.add_field(name='Duration', value=fmt, inline=False)
|
||||||
# And return the embed we created
|
# And return the embed we created
|
||||||
return embed
|
return embed
|
|
@ -37,7 +37,7 @@ class Playlist(EventEmitter):
|
||||||
else:
|
else:
|
||||||
return 0
|
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.
|
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/758
|
||||||
# https://github.com/KeepSafe/aiohttp/issues/852
|
# https://github.com/KeepSafe/aiohttp/issues/852
|
||||||
content_type = await get_header(self.bot.aiosession, info['url'], 'CONTENT-TYPE')
|
content_type = await get_header(self.bot.aiosession, info['url'], 'CONTENT-TYPE')
|
||||||
print("Got content type", content_type)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("[Warning] Failed to get content type for url %s (%s)" % (song_url, e))
|
|
||||||
content_type = None
|
content_type = None
|
||||||
|
|
||||||
if content_type:
|
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
|
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))
|
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):
|
if info.get('is_live', False):
|
||||||
raise LiveStreamError("Cannot download from a livestream")
|
raise LiveStreamError("Cannot download from a livestream")
|
||||||
|
|
||||||
|
@ -97,7 +92,6 @@ class Playlist(EventEmitter):
|
||||||
self,
|
self,
|
||||||
song_url,
|
song_url,
|
||||||
info.get('title', 'Untitled'),
|
info.get('title', 'Untitled'),
|
||||||
ctx,
|
|
||||||
info.get('thumbnail', None),
|
info.get('thumbnail', None),
|
||||||
info.get('duration', 0) or 0,
|
info.get('duration', 0) or 0,
|
||||||
self.downloader.ytdl.prepare_filename(info),
|
self.downloader.ytdl.prepare_filename(info),
|
||||||
|
@ -151,14 +145,9 @@ class Playlist(EventEmitter):
|
||||||
baditems += 1
|
baditems += 1
|
||||||
# Once I know more about what's happening here I can add a proper message
|
# Once I know more about what's happening here I can add a proper message
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
print(items)
|
|
||||||
print("Could not add item")
|
|
||||||
else:
|
else:
|
||||||
baditems += 1
|
baditems += 1
|
||||||
|
|
||||||
if baditems:
|
|
||||||
print("Skipped %s bad entries" % baditems)
|
|
||||||
|
|
||||||
return entry_list, position
|
return entry_list, position
|
||||||
|
|
||||||
async def async_process_youtube_playlist(self, playlist_url, **meta):
|
async def async_process_youtube_playlist(self, playlist_url, **meta):
|
||||||
|
@ -191,14 +180,9 @@ class Playlist(EventEmitter):
|
||||||
baditems += 1
|
baditems += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
baditems += 1
|
baditems += 1
|
||||||
print("There was an error adding the song {}: {}: {}\n".format(
|
|
||||||
entry_data['id'], e.__class__.__name__, e))
|
|
||||||
else:
|
else:
|
||||||
baditems += 1
|
baditems += 1
|
||||||
|
|
||||||
if baditems:
|
|
||||||
print("Skipped %s bad entries" % baditems)
|
|
||||||
|
|
||||||
return gooditems
|
return gooditems
|
||||||
|
|
||||||
async def async_process_sc_bc_playlist(self, playlist_url, **meta):
|
async def async_process_sc_bc_playlist(self, playlist_url, **meta):
|
||||||
|
@ -230,14 +214,9 @@ class Playlist(EventEmitter):
|
||||||
baditems += 1
|
baditems += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
baditems += 1
|
baditems += 1
|
||||||
print("There was an error adding the song {}: {}: {}\n".format(
|
|
||||||
entry_data['id'], e.__class__.__name__, e))
|
|
||||||
else:
|
else:
|
||||||
baditems += 1
|
baditems += 1
|
||||||
|
|
||||||
if baditems:
|
|
||||||
print("Skipped %s bad entries" % baditems)
|
|
||||||
|
|
||||||
return gooditems
|
return gooditems
|
||||||
|
|
||||||
def _add_entry(self, entry):
|
def _add_entry(self, entry):
|
||||||
|
@ -286,4 +265,4 @@ class Playlist(EventEmitter):
|
||||||
return datetime.timedelta(seconds=estimated_time)
|
return datetime.timedelta(seconds=estimated_time)
|
||||||
|
|
||||||
def count_for_user(self, user):
|
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)
|
|
@ -1,9 +1,9 @@
|
||||||
beautifulsoup4
|
beautifulsoup4==4.6.0
|
||||||
Pillow==3.4.1
|
Pillow==3.4.1
|
||||||
rethinkdb
|
rethinkdb==2.3.0.post6
|
||||||
ruamel.yaml
|
ruamel.yaml==0.14.12
|
||||||
youtube-dl
|
youtube-dl
|
||||||
psutil
|
psutil==5.2.2
|
||||||
pendulum
|
pendulum==1.2.0
|
||||||
-e git+https://github.com/Rapptz/discord.py@rewrite#egg=discord.py[voice]
|
-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
|
Loading…
Reference in a new issue