1
0
Fork 0
mirror of synced 2024-05-17 19:12:33 +12:00
Bonfire/cogs/admin.py
2018-09-23 14:23:27 -05:00

1228 lines
52 KiB
Python

from discord.ext import commands
from . import utils
import discord
import asyncio
import re
valid_perms = [p for p in dir(discord.Permissions) if isinstance(getattr(discord.Permissions, p), property)]
class Administration:
def __init__(self, bot):
self.bot = bot
@commands.group(invoke_without_command=True)
@commands.guild_only()
@utils.custom_perms(send_messages=True)
@utils.check_restricted()
async def battles(self, ctx):
"""Used to list the server specific battles messages on this server
EXAMPLE: !battles
RESULT: A list of the battle messages that can be used on this server"""
msgs = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='battles')
if msgs:
try:
pages = utils.Pages(self.bot, message=ctx.message, entries=msgs)
await pages.paginate()
except utils.CannotPaginate as e:
await ctx.send(str(e))
else:
await ctx.send("There are no server specific battles on this server!")
@battles.command(name='add')
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def add_battles(self, ctx, *, message):
"""Used to add a battle message to the server specific battle messages
Use {winner} or {loser} in order to display the winner/loser's display name
EXAMPLE: !battles add {winner} has beaten {loser}
RESULT: Player1 has beaten Player2"""
# Try to simulate the message, to ensure they haven't provided an invalid phrase
try:
message.format(loser="player1", winner="player2")
except Exception:
await ctx.send("That is an invalid format! The winner needs to be "
"labeled with {winner} and the loser with {loser}")
return
# Now simply load the current messages
msgs = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='battles') or []
# Append this one
msgs.append("*{}*".format(message))
# And save it
update = {
'server_id': str(ctx.message.guild.id),
'battles': msgs
}
await self.bot.db.save('server_settings', update)
fmt = "I have just saved your new battle message, it will appear like this: \n\n*{}*".format(message)
await ctx.send(fmt.format(loser=ctx.message.author.display_name, winner=ctx.message.guild.me.display_name))
@battles.command(name='remove', aliases=['delete'])
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def remove_battles(self, ctx):
"""Used to remove one of the custom hugs from the server's list of hug messages
EXAMPLE: !hugs remove
RESULT: I'll ask which hug you want to remove"""
# First just send the hugs
await ctx.invoke(self.battles)
# Then let them know to respond with the number needed
await ctx.send("Please respond with the number matching the battle message you want to remove")
# The check to ensure it's in this channel...and what's provided is an int
def check(m):
if m.author == ctx.message.author and m.channel == ctx.message.channel:
try:
return bool(int(m.content))
except Exception:
return False
else:
return False
# Get the message
try:
msg = await self.bot.wait_for('message', check=check, timeout=60.0)
except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait")
return
# Get the number needed
num = int(msg.content) - 1
msgs = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='battles')
# Try to remove it, if it fails then it doesn't match
try:
msgs.pop(num)
except (IndexError, AttributeError):
await ctx.send("That is not a valid match!")
return
entry = {
'server_id': str(ctx.message.guild.id),
'battles': msgs
}
await self.bot.db.save('server_settings', entry)
await ctx.send("I have just removed that battle message")
@battles.command(name='default')
@commands.guild_only()
@utils.custom_perms(send_messages=True)
@utils.check_restricted()
async def default_battles(self, ctx):
"""Used to toggle if battles should include default messages as well as server-custom messages
EXAMPLE: !hugs default
RESULT: No longer uses both defaults!"""
# Get the setting
setting = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='default_battles')
if setting is None:
setting = True
# Now reverse it
setting = not setting
entry = {
'server_id': str(ctx.message.guild.id),
'default_battles': setting
}
await self.bot.db.save('server_settings', entry)
fmt = "" if setting else "not "
await ctx.send("Default messages will {}be used as well as custom messages".format(fmt))
@commands.group(invoke_without_command=True)
@commands.guild_only()
@utils.custom_perms(send_messages=True)
@utils.check_restricted()
async def hugs(self, ctx):
"""Used to list the server specific hug messages on this server
EXAMPLE: !hugs
RESULT: A list of the hug messages that can be used on this server"""
msgs = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='hugs')
if msgs:
try:
pages = utils.Pages(self.bot, message=ctx.message, entries=msgs)
await pages.paginate()
except utils.CannotPaginate as e:
await ctx.send(str(e))
else:
await ctx.send("There are no server specific hugs on this server!")
@hugs.command(name='add')
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def add_hugs(self, ctx, *, message):
"""Used to add a hug to the server specific hug messages
Use {user} in order to display the user's display name
EXAMPLE: !hugs add I hugged {user}
RESULT: *new hug message that says I hugged UserName*"""
# Try to simulate the message, to ensure they haven't provided an invalid phrase
try:
message.format(user="user")
except Exception:
await ctx.send("That is an invalid format! The user being hugged needs to be labeled with {user}")
return
msgs = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='hugs') or []
msgs.append("*{}*".format(message))
update = {
'server_id': str(ctx.message.guild.id),
'hugs': msgs
}
await self.bot.db.save('server_settings', update)
fmt = "I have just saved your new hug message, it will appear like this: \n\n*{}*".format(message)
await ctx.send(fmt.format(user=ctx.message.author.display_name))
@hugs.command(name='remove', aliases=['delete'])
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def remove_hugs(self, ctx):
"""Used to remove one of the custom hugs from the server's list of hug messages
EXAMPLE: !hugs remove
RESULT: I'll ask which hug you want to remove"""
# First just send the hugs
await ctx.invoke(self.hugs)
# Then let them know to respond with the number needed
await ctx.send("Please respond with the number matching the hug message you want to remove")
# The check to ensure it's in this channel...and what's provided is an int
def check(m):
if m.author == ctx.message.author and m.channel == ctx.message.channel:
try:
return bool(int(m.content))
except Exception:
return False
else:
return False
# Get the message
try:
msg = await self.bot.wait_for('message', check=check, timeout=60.0)
except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait")
return
# Get the number needed
num = int(msg.content) - 1
msgs = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='hugs')
# Try to remove it, if it fails then it doesn't match
try:
msgs.pop(num)
except (IndexError, AttributeError):
await ctx.send("That is not a valid match!")
return
entry = {
'server_id': str(ctx.message.guild.id),
'hugs': msgs
}
await self.bot.db.save('server_settings', entry)
await ctx.send("I have just removed that hug message")
@hugs.command(name='default')
@commands.guild_only()
@utils.custom_perms(send_messages=True)
@utils.check_restricted()
async def default_hugs(self, ctx):
"""Used to toggle if hugs should include default messages as well as server-custom messages
EXAMPLE: !hugs default
RESULT: No longer uses both defaults!"""
# Get the setting
setting = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='default_hugs')
if setting is None:
setting = True
# Now reverse it
setting = not setting
entry = {
'server_id': str(ctx.message.guild.id),
'default_hugs': setting
}
await self.bot.db.save('server_settings', entry)
fmt = "" if setting else "not "
await ctx.send("Default messages will {}be used as well as custom messages".format(fmt))
@commands.command()
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def allowbirthdays(self, ctx, setting):
"""Turns on/off the birthday announcements in this server
EXAMPLE: !allowbirthdays on
RESULT: Birthdays will now be announced"""
if setting.lower() in ['on', 'yes', 'true']:
allowed = True
else:
allowed = False
entry = {
'server_id': str(ctx.message.guild.id),
'birthdays_allowed': allowed
}
await self.bot.db.save('server_settings', entry)
fmt = "The birthday announcements have just been turned {}".format("on" if allowed else "off")
await ctx.send(fmt)
@commands.command()
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def allowcolours(self, ctx, setting):
"""Turns on/off the ability to use colour roles in this server
EXAMPLE: !allowcolours on
RESULT: Colour roles can now be used in this server"""
if setting.lower() in ['on', 'yes', 'true']:
allowed = True
else:
allowed = False
entry = {
'server_id': str(ctx.message.guild.id),
'colour_roles_allowed': allowed
}
await self.bot.db.save('server_settings', entry)
fmt = "The ability to use colour roles have just been turned {}".format("on" if allowed else "off")
await ctx.send(fmt)
@commands.command()
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def allowplaylists(self, ctx, setting):
"""Turns on/off the ability to playlists
EXAMPLE: !allowplaylists on
RESULT: Playlists can now be used"""
if setting.lower() in ['on', 'yes', 'true']:
allowed = True
else:
allowed = False
entry = {
'server_id': str(ctx.message.guild.id),
'playlists_allowed': allowed
}
await self.bot.db.save('server_settings', entry)
fmt = "The ability to use playlists has just been turned {}".format("on" if allowed else "off")
await ctx.send(fmt)
@commands.command()
@commands.guild_only()
@utils.custom_perms(kick_members=True)
@utils.check_restricted()
async def restrictions(self, ctx):
"""Used to list all the current restrictions set
EXAMPLE: !restrictions
RESULT: All the current restrictions"""
# Get the restrictions
restrictions = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='restrictions') or {}
entries = []
# Loop through all the from restrictions
for _from in restrictions.get('from', []):
source = _from.get('source')
# Resolve our destination based on the ID
dest = await utils.convert(ctx, _from.get('destination'))
# Don't add it if it doesn't exist
if dest:
entries.append("{} from {}".format(source, dest))
for _to in restrictions.get('to', []):
source = _to.get('source')
# Resolve our destination based on the ID
dest = await utils.convert(ctx, _to.get('destination'))
# Don't add it if it doesn't exist
if dest:
entries.append("{} to {}".format(source, dest))
if entries:
# Then paginate
try:
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("There are no restrictions!")
@commands.command()
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def restrict(self, ctx, *options):
"""
This is an intuitive command to restrict something to/from something
The format is `!restrict what from/to who/where`
For example, `!restrict command to role` will require a user to have `role`
to be able to run `command`
`!restrict command to channel` will only allow `command` to be ran in `channel`
EXAMPLE: !restrict boop from @user
RESULT: This user can no longer use the boop command
"""
# First make sure we're given three options
if len(options) != 3:
await ctx.send("You need to provide 3 options! Such as `command from @User`")
return
elif ctx.message.mention_everyone:
arg1, arg2, arg3 = options
await ctx.send("Please do not restrict something {} everyone".format(arg2))
return
else:
# Get the three arguments from this list, then make sure the 2nd is either from or to
arg1, arg2, arg3 = options
if arg2.lower() not in ['from', 'to']:
await ctx.send("The 2nd option needs to be either \"to\" or \"from\". Such as: `command from @user` "
"or `command to Role`")
return
else:
# Try to convert the other arguments
arg2 = arg2.lower()
option1 = await utils.convert(ctx, arg1)
option2 = await utils.convert(ctx, arg3)
if option1 is None or option2 is None:
await ctx.send("Sorry, but I don't know how to restrict {} {} {}".format(arg1, arg2, arg3))
return
from_entry = None
to_entry = None
overwrites = None
# The possible options:
# Member
# Role
# Command
# Text/Voice Channel
if isinstance(option1, (commands.core.Command, commands.core.Group)):
# From:
# Users - Command can't be run by this person
# Channels - Command can't be ran in this channel
# Roles - Command can't be ran by anyone in this role (least likely, but still possible uses)
if arg2 == "from":
if isinstance(option2, (discord.Member, discord.Role, discord.TextChannel)):
from_entry = {
'source': option1.qualified_name,
'destination': str(option2.id)
}
# To:
# Channels - Command can only be run in this channel
# Roles - This role is required in order to run this command
else:
if isinstance(option2, (discord.Role, discord.TextChannel)):
to_entry = {
'source': option1.qualified_name,
'destination': str(option2.id)
}
elif isinstance(option1, discord.Member):
# From:
# Channels - Setup an overwrite for this channel so that they cannot read it
# Command - Command cannot be used by this user
if arg2 == "from":
if isinstance(option2, (discord.TextChannel, discord.VoiceChannel)):
ov = discord.utils.find(lambda t: t[0] == option1, option2.overwrites)
if ov:
ov = ov[1]
ov.update(read_messages=False)
else:
ov = discord.PermissionOverwrite(read_messages=False)
overwrites = {
'channel': option2,
option1: ov
}
elif isinstance(option2, (commands.core.Command, commands.core.Group)):
from_entry = {
'source': option2.qualified_name,
'destination': str(option1.id)
}
elif isinstance(option1, (discord.TextChannel, discord.VoiceChannel)):
# From:
# Command - Command cannot be used in this channel
# Member - Setup an overwrite for this channel so that they cannot read it
# Role - Setup an overwrite for this channel so that this Role cannot read it
if arg2 == "from":
if isinstance(option2, (discord.Member, discord.Role)):
ov = discord.utils.find(lambda t: t[0] == option2, option1.overwrites)
if ov:
ov = ov[1]
ov.update(read_messages=False)
else:
ov = discord.PermissionOverwrite(read_messages=False)
overwrites = {
'channel': option1,
option2: ov
}
elif isinstance(option2, (commands.core.Command, commands.core.Group)) \
and isinstance(option1, discord.TextChannel):
from_entry = {
'source': option2.qualified_name,
'destination': str(option1.id)
}
# To:
# Command - Command can only be used in this channel
# Role - Setup an overwrite so only this role can read this channel
else:
if isinstance(option2, (commands.core.Command, commands.core.Group)) \
and isinstance(option1, discord.TextChannel):
to_entry = {
'source': option2.qualified_name,
'destination': str(option1.id)
}
elif isinstance(option2, (discord.Member, discord.Role)):
ov = discord.utils.find(lambda t: t[0] == option2, option1.overwrites)
if ov:
ov = ov[1]
ov.update(read_messages=True)
else:
ov = discord.PermissionOverwrite(read_messages=True)
ov2 = discord.utils.find(lambda t: t[0] == ctx.message.guild.default_role,
option1.overwrites)
if ov2:
ov2 = ov2[1]
ov2.update(read_messages=False)
else:
ov2 = discord.PermissionOverwrite(read_messages=False)
overwrites = {
'channel': option1,
option2: ov,
ctx.message.guild.default_role: ov2
}
elif isinstance(option1, discord.Role):
# From:
# Command - No one with this role can run this command
# Channel - Setup an overwrite for this channel so that this Role cannot read it
if arg2 == "from":
if isinstance(option2, (commands.core.Command, commands.core.Group)):
from_entry = {
'source': option2.qualified_name,
'destination': str(option1.id)
}
elif isinstance(option2, (discord.TextChannel, discord.VoiceChannel)):
ov = discord.utils.find(lambda t: t[0] == option1, option2.overwrites)
if ov:
ov = ov[1]
ov.update(read_messages=False)
else:
ov = discord.PermissionOverwrite(read_messages=False)
overwrites = {
'channel': option2,
option1: ov
}
# To:
# Command - You have to have this role to run this command
# Channel - Setup an overwrite so you have to have this role to read this channel
else:
if isinstance(option2, (discord.TextChannel, discord.VoiceChannel)):
ov = discord.utils.find(lambda t: t[0] == option1, option2.overwrites)
if ov:
ov = ov[1]
ov.update(read_messages=True)
else:
ov = discord.PermissionOverwrite(read_messages=True)
ov2 = discord.utils.find(lambda t: t[0] == ctx.message.guild.default_role,
option2.overwrites)
if ov2:
ov2 = ov2[1]
ov2.update(read_messages=False)
else:
ov2 = discord.PermissionOverwrite(read_messages=False)
overwrites = {
'channel': option2,
option1: ov,
ctx.message.guild.default_role: ov2
}
elif isinstance(option2, (commands.core.Command, commands.core.Group)):
to_entry = {
'source': option2.qualified_name,
'destination': str(option1.id)
}
if to_entry:
restrictions = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='restrictions') or {}
to = restrictions.get('to', [])
if to_entry not in to:
to.append(to_entry)
update = {
'server_id': str(ctx.message.guild.id),
'restrictions': {
'to': to
}
}
await self.bot.db.save('server_settings', update)
elif from_entry:
restrictions = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='restrictions') or {}
_from = restrictions.get('from', [])
if from_entry not in _from:
_from.append(from_entry)
update = {
'server_id': str(ctx.message.guild.id),
'restrictions': {
'from': _from
}
}
await self.bot.db.save('server_settings', update)
elif overwrites:
channel = overwrites.pop('channel')
for target, setting in overwrites.items():
await channel.set_permissions(target, overwrite=setting)
else:
await ctx.send("Sorry but I don't know how to restrict {} {} {}".format(arg1, arg2, arg3))
return
await ctx.send("I have just restricted {} {} {}".format(arg1, arg2, arg3))
@commands.command()
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def unrestrict(self, ctx, *options):
"""
This is an intuitive command to unrestrict something to/from something
The format is `!restrict what from/to who/where`
For example, `!unrestrict command to role` will remove the restriction on this command, requiring role
EXAMPLE: !unrestrict boop from @user
RESULT: The restriction on this user to use boop has been lifted
"""
# First make sure we're given three options
if len(options) != 3:
await ctx.send("You need to provide 3 options! Such as `command from @User`")
return
else:
# Get the three arguments from this list, then make sure the 2nd is either from or to
arg1, arg2, arg3 = options
if arg2.lower() not in ['from', 'to']:
await ctx.send("The 2nd option needs to be either \"to\" or \"from\". Such as: `command from @user` "
"or `command to Role`")
return
else:
# Try to convert the other arguments
arg2 = arg2.lower()
option1 = await utils.convert(ctx, arg1)
option2 = await utils.convert(ctx, arg3)
if option1 is None or option2 is None:
await ctx.send("Sorry, but I don't know how to unrestrict {} {} {}".format(arg1, arg2, arg3))
return
# First check if this is a blacklist/whitelist (by checking if we are unrestricting commands)
if any(isinstance(x, (commands.core.Command, commands.core.Group)) for x in [option1, option2]):
# The source should always be the command, so just set this based on which order is given (either is
# allowed)
if isinstance(option1, (commands.core.Command, commands.core.Group)):
restriction = {
'source': option1.qualified_name,
'destination': str(option2.id)
}
else:
restriction = {
'source': option2.qualified_name,
'destination': str(option1.id)
}
# Load restrictions
restrictions = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='restrictions') or {}
# Attempt to remove the restriction provided
try:
restrictions.get(arg2, []).remove(restriction)
# If it doesn't exist, nothing is needed to be done
except ValueError:
await ctx.send("The restriction {} {} {} does not exist!".format(arg1, arg2, arg3))
return
# If it was removed succesfully, save the change and let the author know this has been done
else:
entry = {
'server_id': str(ctx.message.guild.id),
'restrictions': restrictions
}
await self.bot.db.save('server_settings', entry)
# If this isn't a blacklist/whitelist, then we are attempting to remove an overwrite
else:
# Get the source and destination based on whatever order is provided
if isinstance(option1, (discord.TextChannel, discord.VoiceChannel)):
source = option2
destination = option1
else:
source = option1
destination = option2
# See if it's the blacklist that we're removing from
if arg2 == "from":
# Get overwrites if they exist
# If it doesn't, there's nothing to do here
ov = discord.utils.find(lambda t: t[0] == source, destination.overwrites)
if ov:
ov = ov[1]
ov.update(read_messages=True)
await destination.set_permissions(source, overwrite=ov)
else:
ov = discord.utils.find(lambda t: t[0] == source, destination.overwrites)
ov2 = discord.utils.find(lambda t: t[0] == ctx.message.guild.default_role, destination.overwrites)
if ov:
ov = ov[1]
ov.update(read_messages=None)
await destination.set_permissions(source, overwrite=ov)
if ov2:
ov2 = ov2[1]
ov2.update(read_messages=True)
await destination.set_permissions(source, overwrite=ov2)
await ctx.send("I have just unrestricted {} {} {}".format(arg1, arg2, arg3))
@commands.command(aliases=['nick'])
@commands.guild_only()
@utils.custom_perms(kick_members=True)
@utils.check_restricted()
async def nickname(self, ctx, *, name=None):
"""Used to set the nickname for Bonfire (provide no nickname and it will reset)
EXAMPLE: !nick Music Bot
RESULT: My nickname is now Music Bot"""
try:
await ctx.message.guild.me.edit(nick=name)
except discord.HTTPException:
await ctx.send("Sorry but I can't change my nickname to {}".format(name))
else:
await ctx.send("\N{OK HAND SIGN}")
@commands.command()
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def ignore(self, ctx, member_or_channel):
"""This command can be used to have Bonfire ignore certain members/channels
EXAMPLE: !ignore #general
RESULT: Bonfire will ignore commands sent in the general channel"""
key = ctx.message.guild.id
converter = commands.converter.MemberConverter()
member = None
channel = None
try:
member = await converter.convert(ctx, member_or_channel)
except commands.converter.BadArgument:
converter = commands.converter.TextChannelConverter()
try:
channel = await converter.convert(ctx, member_or_channel)
except commands.converter.BadArgument:
await ctx.send("{} does not appear to be a member or channel!".format(member_or_channel))
return
settings = self.bot.db.load('server_settings', key=key, pluck='ignored') or {}
ignored = settings.get('ignored', {'members': [], 'channels': []})
if member:
if str(member.id) in ignored['members']:
await ctx.send("I am already ignoring {}!".format(member.display_name))
return
elif member.guild_permissions >= ctx.message.author.guild_permissions:
await ctx.send("You cannot make me ignore someone at equal or higher rank than you!")
return
else:
ignored['members'].append(str(member.id))
fmt = "Ignoring {}".format(member.display_name)
else:
if str(channel.id) in ignored['channels']:
await ctx.send("I am already ignoring {}!".format(channel.mention))
return
else:
ignored['channels'].append(str(channel.id))
fmt = "Ignoring {}".format(channel.mention)
entry = {
'ignored': ignored,
'server_id': str(key)
}
await self.bot.db.save('server_settings', entry)
await ctx.send(fmt)
@commands.command()
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def unignore(self, ctx, member_or_channel):
"""This command can be used to have Bonfire stop ignoring certain members/channels
EXAMPLE: !unignore #general
RESULT: Bonfire will no longer ignore commands sent in the general channel"""
key = str(ctx.message.guild.id)
converter = commands.converter.MemberConverter()
member = None
channel = None
try:
member = await converter.convert(ctx, member_or_channel)
except commands.converter.BadArgument:
converter = commands.converter.TextChannelConverter()
try:
channel = await converter.convert(ctx, member_or_channel)
except commands.converter.BadArgument:
await ctx.send("{} does not appear to be a member or channel!".format(member_or_channel))
return
settings = self.bot.db.load('server_settings', key=key) or {}
ignored = settings.get('ignored', {'members': [], 'channels': []})
if member:
if str(member.id) not in ignored['members']:
await ctx.send("I'm not even ignoring {}!".format(member.display_name))
return
ignored['members'].remove(str(member.id))
fmt = "I am no longer ignoring {}".format(member.display_name)
else:
if str(channel.id) not in ignored['channels']:
await ctx.send("I'm not even ignoring {}!".format(channel.mention))
return
ignored['channels'].remove(str(channel.id))
fmt = "I am no longer ignoring {}".format(channel.mention)
entry = {
'ignored': ignored,
'server_id': str(key)
}
await self.bot.db.save('server_settings', entry)
await ctx.send(fmt)
@commands.command(aliases=['notifications'])
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def alerts(self, ctx, channel: discord.TextChannel):
"""This command is used to set a channel as the server's default 'notifications' channel
Any notifications (like someone going live on Twitch, or Picarto) will go to that channel by default
This can be overridden with specific alerts command, such as `!picarto alerts #channel`
This command is just the default; the one used if there is no other one set.
EXAMPLE: !alerts #alerts
RESULT: No more alerts spammed in #general!"""
entry = {
'server_id': str(ctx.message.guild.id),
'notifications': {
'default': str(channel.id)
}
}
await self.bot.db.save('server_settings', entry)
await ctx.send("I have just changed this server's default 'notifications' channel"
"\nAll notifications will now default to `{}`".format(channel))
@commands.group(invoke_without_command=True, aliases=['goodbye'])
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def welcome(self, ctx, on_off: str):
"""This command can be used to set whether or not you want user notificaitons to show
Provide on, yes, or true to set it on; otherwise it will be turned off
EXAMPLE: !welcome on
RESULT: Annoying join/leave notifications! Yay!"""
# Join/Leave notifications can be kept separate from normal 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
on_off = True if re.search("(on|yes|true)", on_off.lower()) else False
entry = {
'server_id': str(ctx.message.guild.id),
'join_leave': on_off
}
await self.bot.db.save('server_settings', entry)
fmt = "notify" if on_off else "not notify"
await ctx.send("This server will now {} if someone has joined or left".format(fmt))
@welcome.command(name='alerts', aliases=['notifications'])
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def _welcome_alerts(self, ctx, *, channel: discord.TextChannel):
"""A command used to set the override for notifications about users joining/leaving
EXAMPLE: !welcome alerts #notifications
RESULT: All user joins/leaves will be sent to the #notificatoins channel"""
entry = {
'server_id': str(ctx.message.guild.id),
'notifications': {
'welcome': str(channel.id)
}
}
await self.bot.db.save('server_settings', entry)
await ctx.send(
"I have just changed this server's welcome/goodbye notifications channel to {}".format(channel.name))
@welcome.command(name='message')
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def _welcome_message(self, ctx, *, msg):
"""A command to customize the welcome/goodbye message
There are a couple things that can be set to customize the message
{member} - Will mention the user joining
{server} - Will display the server's name
Give no message and it will be set to the default
EXAMPLE: !welcome message {member} to {server}
RESULT: Welcome Member#1234 to ServerName"""
parent = ctx.message.content.split()[0]
parent = parent[len(ctx.prefix):]
if re.search("{.*token.*}", msg):
await ctx.send("Illegal content in {} message".format(parent))
else:
try:
msg.format(member='test', server='test')
except KeyError:
await ctx.send("Illegal keyword in {0} message. Please use `{1.prefix}help {0} message` "
"for what keywords can be used".format(parent, ctx))
return
entry = {
'server_id': str(ctx.message.guild.id),
parent + '_message': msg
}
await self.bot.db.save('server_settings', entry)
await ctx.send("I have just updated your {} message".format(parent))
@commands.group()
@utils.check_restricted()
async def nsfw(self, ctx):
"""Handles adding or removing a channel as a nsfw channel"""
# This command isn't meant to do anything, so just send an error if an invalid subcommand is passed
pass
@nsfw.command(name="add")
@utils.custom_perms(kick_members=True)
@utils.check_restricted()
async def nsfw_add(self, ctx):
"""Registers this channel as a 'nsfw' channel
EXAMPLE: !nsfw add
RESULT: ;)"""
if type(ctx.message.channel) is discord.DMChannel:
key = 'DMs'
else:
key = str(ctx.message.guild.id)
channels = self.bot.db.load('server_settings', key=key, pluck='nsfw_channels') or []
channels.append(str(ctx.message.channel.id))
entry = {
'server_id': key,
'nsfw_channels': channels
}
await self.bot.db.save('server_settings', entry)
await ctx.send("This channel has just been registered as 'nsfw'! Have fun you naughties ;)")
@nsfw.command(name="remove", aliases=["delete"])
@utils.custom_perms(kick_members=True)
@utils.check_restricted()
async def nsfw_remove(self, ctx):
"""Removes this channel as a 'nsfw' channel
EXAMPLE: !nsfw remove
RESULT: ;("""
channel = str(ctx.message.channel.id)
if type(ctx.message.channel) is discord.DMChannel:
key = 'DMs'
else:
key = str(ctx.message.guild.id)
channels = self.bot.db.load('server_settings', key=key, pluck='nsfw_channels') or []
if channel in channels:
channels.remove(channel)
entry = {
'server_id': key,
'nsfw_channels': channels
}
await self.bot.db.save('server_settings', entry)
await ctx.send("This channel has just been unregistered as a nsfw channel")
else:
await ctx.send("This channel is not registerred as a nsfw channel!")
@commands.group(invoke_without_command=True)
@commands.guild_only()
@utils.custom_perms(send_messages=True)
@utils.check_restricted()
async def perms(self, ctx, *, command: str = None):
"""This command can be used to print the current allowed permissions on a specific command
This supports groups as well as subcommands; pass no argument to print a list of available permissions
EXAMPLE: !perms help
RESULT: Hopefully a result saying you just need send_messages permissions; otherwise lol
this server's admin doesn't like me """
if command is None:
await ctx.send(
"Valid permissions are: ```\n{}```".format("\n".join("{}".format(i) for i in valid_perms)))
return
cmd = self.bot.get_command(command)
if cmd is None:
# If a command wasn't provided, see if a user was
converter = commands.converter.MemberConverter()
try:
member = await converter.convert(ctx, command)
# If we failed to convert, just mention that an invalid command was provided
except commands.converter.BadArgument:
await ctx.send("That is not a valid command!")
return
else:
# Otherwise iterate through the permissions and their values, only including ones that are on
perms = [p for p, value in member.guild_permissions if value]
# Create an embed with their colour
embed = discord.Embed(colour=member.colour)
# Set the author to this user
embed.set_author(name=str(member), icon_url=member.avatar_url)
# Then add their permissions in one field
embed.add_field(name="Allowed permissions", value="\n".join(perms))
await ctx.send(embed=embed)
return
server_perms = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='permissions') or {}
perms_value = server_perms.get(cmd.qualified_name)
if perms_value is None:
# If we don't find custom permissions, get the required permission for a command
# based on what we set in utils.custom_perms, if custom_perms isn't found, we'll get an IndexError
try:
custom_perms = [func for func in cmd.checks if "custom_perms" in func.__qualname__][0]
except IndexError:
# Loop through and check if there is a check called is_owner
# If we loop through and don't find one, this means that the only other choice is to be
# Able to manage the server (for the utils on perm commands)
for func in cmd.checks:
if "is_owner" in func.__qualname__:
await ctx.send("You need to own the bot to run this command")
return
await ctx.send("You are required to have `manage_guild` permissions to run `{}`".format(
cmd.qualified_name
))
return
# Perms will be an attribute if custom_perms is found no matter what, so no need to check this
perms = "\n".join(attribute for attribute, setting in custom_perms.perms.items() if setting)
await ctx.send(
"You are required to have `{}` permissions to run `{}`".format(perms, cmd.qualified_name))
else:
# Permissions are saved as bit values, so create an object based on that value
# Then check which permission is true, that is our required permission
# There's no need to check for errors here, as we ensure a permission is valid when adding it
permissions = discord.Permissions(perms_value)
needed_perm = [perm[0] for perm in permissions if perm[1]][0]
await ctx.send("You need to have the permission `{}` "
"to use the command `{}` in this server".format(needed_perm, command))
@perms.command(name="add", aliases=["setup,create"])
@commands.guild_only()
@commands.has_permissions(manage_guild=True)
@utils.check_restricted()
async def add_perms(self, ctx, *msg: str):
"""Sets up custom permissions on the provided command
Format must be 'perms add <command> <permission>'
If you want to open the command to everyone, provide 'none' as the permission
EXAMPLE: !perms add skip ban_members
RESULT: No more random people voting to skip a song"""
# Since subcommands exist, base the last word in the list as the permission, and the rest of it as the command
command = " ".join(msg[0:len(msg) - 1])
if command == "":
await ctx.send("Please provide the permissions you want to setup, the format for this must be in:\n"
"`perms add <command> <permission>`")
return
try:
permissions = msg[len(msg) - 1]
except IndexError:
await ctx.send("Please provide the permissions you want to setup, the format for this must be in:\n"
"`perms add <command> <permission>`")
return
cmd = self.bot.get_command(command)
if cmd is None:
await ctx.send(
"That command does not exist! You can't have custom permissions on a non-existant command....")
return
# If a user can run a command, they have to have send_messages permissions; so use this as the base
if permissions.lower() == "none":
permissions = "send_messages"
# Convert the string to an int value of the permissions object, based on the required permission
# If we hit an attribute error, that means the permission given was not correct
perm_obj = discord.Permissions.none()
try:
setattr(perm_obj, permissions, True)
except AttributeError:
await ctx.send("{} does not appear to be a valid permission! Valid permissions are: ```\n{}```"
.format(permissions, "\n".join(valid_perms)))
return
perm_value = perm_obj.value
# Two cases I use should never have custom permissions setup on them, is_owner for obvious reasons
# The other case is if I'm using the default has_permissions case
# Which means I do not want to check custom permissions at all
# Currently the second case is only on adding and removing permissions, to avoid abuse on these
for check in cmd.checks:
if "is_owner" == check.__name__ or "has_permissions" in str(check):
await ctx.send("This command cannot have custom permissions setup!")
return
entry = {
'server_id': str(ctx.message.guild.id),
'permissions': {cmd.qualified_name: perm_value}
}
await self.bot.db.save('server_settings', entry)
await ctx.send("I have just added your custom permissions; "
"you now need to have `{}` permissions to use the command `{}`".format(permissions, command))
@perms.command(name="remove", aliases=["delete"])
@commands.guild_only()
@commands.has_permissions(manage_guild=True)
@utils.check_restricted()
async def remove_perms(self, ctx, *, command: str):
"""Removes the custom permissions setup on the command specified
EXAMPLE: !perms remove play
RESULT: Freedom!"""
cmd = self.bot.get_command(command)
if cmd is None:
await ctx.send(
"That command does not exist! You can't have custom permissions on a non-existant command....")
return
entry = {
'server_id': str(ctx.message.guild.id),
'permissions': {cmd.qualified_name: None}
}
await self.bot.db.save('server_settings', entry)
await ctx.send("I have just removed the custom permissions for {}!".format(cmd))
@commands.command()
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def prefix(self, ctx, *, prefix: str):
"""This command can be used to set a custom prefix per server
EXAMPLE: !prefix $
RESULT: You now need to call commands like: $help"""
key = str(ctx.message.guild.id)
if len(prefix.strip()) > 20:
await ctx.send("Please keep prefixes under 20 characters")
return
if prefix.lower().strip() == "none":
prefix = None
entry = {
'server_id': key,
'prefix': prefix
}
await self.bot.db.save('server_settings', entry)
if prefix is None:
fmt = "I have just cleared your custom prefix, the default prefix will have to be used now"
else:
fmt = "I have just updated the prefix for this server; you now need to call commands with `{0}`. " \
"For example, you can call this command again with {0}prefix".format(prefix)
await ctx.send(fmt)
@commands.group(aliases=['rule'], invoke_without_command=True)
@commands.guild_only()
@utils.custom_perms(send_messages=True)
@utils.check_restricted()
async def rules(self, ctx, rule: int = None):
"""This command can be used to view the current rules on the server
EXAMPLE: !rules 5
RESULT: Rule 5 is printed"""
rules = self.bot.db.load('server_settings', key=ctx.message.guild.id, pluck='rules')
if rules is None:
await ctx.send("This server currently has no rules on it! I see you like to live dangerously...")
return
if rule is None:
try:
pages = utils.Pages(self.bot, message=ctx.message, entries=rules, per_page=5)
pages.title = "Rules for {}".format(ctx.message.guild.name)
await pages.paginate()
except utils.CannotPaginate as e:
await ctx.send(str(e))
else:
try:
fmt = rules[rule - 1]
except IndexError:
await ctx.send("That rules does not exist.")
return
await ctx.send("Rule {}: \"{}\"".format(rule, fmt))
@rules.command(name='add', aliases=['create'])
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def rules_add(self, ctx, *, rule: str):
"""Adds a rule to this server's rules
EXAMPLE: !rules add No fun allowed in this server >:c
RESULT: No more fun...unless they break the rules!"""
key = str(ctx.message.guild.id)
rules = self.bot.db.load('server_settings', key=key, pluck='rules') or []
rules.append(rule)
entry = {
'server_id': key,
'rules': rules
}
await self.bot.db.save('server_settings', entry)
await ctx.send("I have just saved your new rule, use the rules command to view this server's current rules")
@rules.command(name='remove', aliases=['delete'])
@commands.guild_only()
@utils.custom_perms(manage_guild=True)
@utils.check_restricted()
async def rules_delete(self, ctx, rule: int):
"""Removes one of the rules from the list of this server's rules
Provide a number to delete that rule
EXAMPLE: !rules delete 5
RESULT: Freedom from opression!"""
key = str(ctx.message.guild.id)
rules = self.bot.db.load('server_settings', key=key, pluck='rules') or []
try:
rules.pop(rule - 1)
entry = {
'server_id': key,
'rules': rules
}
await self.bot.db.save('server_settings', entry)
await ctx.send("I have just removed that rule from your list of rules!")
except IndexError:
await ctx.send("That is not a valid rule number, try running the command again.")
def setup(bot):
bot.add_cog(Administration(bot))