1
0
Fork 0
mirror of synced 2024-05-15 10:02:28 +12:00
Bonfire/cogs/config.py
2019-01-27 20:58:39 -06:00

557 lines
19 KiB
Python

from discord.ext import commands
from asyncpg import UniqueViolationError
import utils
import discord
valid_perms = [p for p in dir(discord.Permissions) if isinstance(getattr(discord.Permissions, p), property)]
class ConfigException(Exception):
pass
class WrongSettingType(ConfigException):
def __init__(self, message):
self.message = message
class MessageFormatError(ConfigException):
def __init__(self, original, keys):
self.original = original
self.keys = keys
class GuildConfiguration:
"""Handles configuring the different settings that can be used on the bot"""
def _str_to_bool(self, opt, setting):
setting = setting.title()
if setting.title() not in ["True", "False"]:
raise WrongSettingType(
f"The {opt} setting requires either 'True' or 'False', not {setting}"
)
return setting.title() == "True"
async def _get_channel(self, ctx, setting):
converter = commands.converter.TextChannelConverter()
return await converter.convert(ctx, setting)
async def _set_db_guild_opt(self, opt, setting, ctx):
try:
return await ctx.bot.db.execute(f"INSERT INTO guilds (id, {opt}) VALUES ($1, $2)", ctx.guild.id, setting)
except UniqueViolationError:
return await ctx.bot.db.execute(f"UPDATE guilds SET {opt} = $1 WHERE id = $2", setting, ctx.guild.id)
# These are handles for each setting type
async def _handle_set_birthday_notifications(self, ctx, setting):
opt = "birthday_notifications"
setting = self._str_to_bool(opt, setting)
return await self._set_db_guild_opt(opt, setting, ctx)
async def _handle_set_welcome_notifications(self, ctx, setting):
opt = "welcome_notifications"
setting = self._str_to_bool(opt, setting)
return await self._set_db_guild_opt(opt, setting, ctx)
async def _handle_set_goodbye_notifications(self, ctx, setting):
opt = "goodbye_notifications"
setting = self._str_to_bool(opt, setting)
return await self._set_db_guild_opt(opt, setting, ctx)
async def _handle_set_colour_roles(self, ctx, setting):
opt = "colour_roles"
setting = self._str_to_bool(opt, setting)
return await self._set_db_guild_opt(opt, setting, ctx)
async def _handle_set_include_default_battles(self, ctx, setting):
opt = "include_default_battles"
setting = self._str_to_bool(opt, setting)
return await self._set_db_guild_opt(opt, setting, ctx)
async def _handle_set_include_default_hugs(self, ctx, setting):
opt = "include_default_hugs"
setting = self._str_to_bool(opt, setting)
return await self._set_db_guild_opt(opt, setting, ctx)
async def _handle_set_welcome_msg(self, ctx, setting):
try:
setting.format(member='test', server='test')
except KeyError as e:
raise MessageFormatError(e, ["member", "server"])
else:
return await self._set_db_guild_opt("welcome_msg", setting, ctx)
async def _handle_set_goodbye_msg(self, ctx, setting):
try:
setting.format(member='test', server='test')
except KeyError as e:
raise MessageFormatError(e, ["member", "server"])
else:
return await self._set_db_guild_opt("goodbye_msg", setting, ctx)
async def _handle_set_prefix(self, ctx, setting):
if len(setting) > 20:
raise WrongSettingType("Please keep the prefix under 20 characters")
if setting.lower().strip() == "none":
setting = None
result = await self._set_db_guild_opt("prefix", setting, ctx)
# We want to update our cache for prefixes
ctx.bot.cache.update_prefix(ctx.guild, setting)
return result
async def _handle_set_default_alerts(self, ctx, setting):
channel = await self._get_channel(ctx, setting)
return await self._set_db_guild_opt("default_alerts", channel.id, ctx)
async def _handle_set_welcome_alerts(self, ctx, setting):
channel = await self._get_channel(ctx, setting)
return await self._set_db_guild_opt("welcome_alerts", channel.id, ctx)
async def _handle_set_goodbye_alerts(self, ctx, setting):
channel = await self._get_channel(ctx, setting)
return await self._set_db_guild_opt("goodbye_alerts", channel.id, ctx)
async def _handle_set_picarto_alerts(self, ctx, setting):
channel = await self._get_channel(ctx, setting)
return await self._set_db_guild_opt("picarto_alerts", channel.id, ctx)
async def _handle_set_birthday_alerts(self, ctx, setting):
channel = await self._get_channel(ctx, setting)
return await self._set_db_guild_opt("birthday_alerts", channel.id, ctx)
async def _handle_set_raffle_alerts(self, ctx, setting):
channel = await self._get_channel(ctx, setting)
return await self._set_db_guild_opt("raffle_alerts", channel.id, ctx)
async def _handle_set_followed_picarto_channels(self, ctx, setting):
user = await utils.request(f"http://api.picarto.tv/v1/channel/name/{setting}")
if user is None:
raise WrongSettingType(f"Could not find a picarto user with the username {setting}")
query = """
UPDATE
guilds
SET
followed_picarto_channels = array_append(followed_picarto_channels, $1)
WHERE
id=$2 AND
NOT $1 = ANY(followed_picarto_channels);
"""
return await ctx.bot.db.execute(query, setting, ctx.guild.id)
async def _handle_set_ignored_channels(self, ctx, setting):
channel = await self._get_channel(ctx, setting)
query = """
UPDATE
guilds
SET
ignored_channels = array_append(ignored_channels, $1)
WHERE
id=$2 AND
NOT $1 = ANY(ignored_channels);
"""
return await ctx.bot.db.execute(query, channel.id, ctx.guild.id)
async def _handle_set_ignored_members(self, ctx, setting):
# We want to make it possible to have members that aren't in the server ignored
# So first check if it's a digit (the id)
if not setting.isdigit():
converter = commands.converter.MemberConverter()
member = await converter.convert(ctx, setting)
setting = member.id
query = """
UPDATE
guilds
SET
ignored_members = array_append(ignored_members, $1)
WHERE
id=$2 AND
NOT $1 = ANY(ignored_members);
"""
return await ctx.bot.db.execute(query, setting, ctx.guild.id)
async def _handle_set_rules(self, ctx, setting):
query = """
UPDATE
guilds
SET
rules = array_append(rules, $1)
WHERE
id=$2 AND
NOT $1 = ANY(rules);
"""
return await ctx.bot.db.execute(query, setting, ctx.guild.id)
async def _handle_set_assignable_roles(self, ctx, setting):
converter = commands.converter.RoleConverter()
role = await converter.convert(ctx, setting)
query = """
UPDATE
guilds
SET
assignable_roles = array_append(assignable_roles, $1)
WHERE
id=$2 AND
NOT $1 = ANY(assignable_roles);
"""
return await ctx.bot.db.execute(query, role.id, ctx.guild.id)
async def _handle_set_custom_battles(self, ctx, setting):
try:
setting.format(loser="player1", winner="player2")
except KeyError as e:
raise MessageFormatError(e, ["loser", "winner"])
else:
query = """
UPDATE
guilds
SET
custom_battles = array_append(custom_battles, $1)
WHERE
id=$2 AND
NOT $1 = ANY(custom_battles);
"""
return await ctx.bot.db.execute(query, setting, ctx.guild.id)
async def _handle_set_custom_hugs(self, ctx, setting):
try:
setting.format(user="user")
except KeyError as e:
raise MessageFormatError(e, ["user"])
else:
query = """
UPDATE
guilds
SET
custom_hugs = array_append(custom_hugs, $1)
WHERE
id=$2 AND
NOT $1 = ANY(custom_hugs);
"""
return await ctx.bot.db.execute(query, setting, ctx.guild.id)
async def _handle_remove_birthday_notifications(self, ctx, setting=None):
return await self._set_db_guild_opt("birthday_notifications", False, ctx)
async def _handle_remove_welcome_notifications(self, ctx, setting=None):
return await self._set_db_guild_opt("welcome_notifications", False, ctx)
async def _handle_remove_goodbye_notifications(self, ctx, setting=None):
return await self._set_db_guild_opt("goodbye_notifications", False, ctx)
async def _handle_remove_colour_roles(self, ctx, setting=None):
return await self._set_db_guild_opt("colour_roles", False, ctx)
async def _handle_remove_include_default_battles(self, ctx, setting=None):
return await self._set_db_guild_opt("include_default_battles", False, ctx)
async def _handle_remove_include_default_hugs(self, ctx, setting=None):
return await self._set_db_guild_opt("include_default_hugs", False, ctx)
async def _handle_remove_welcome_msg(self, ctx, setting=None):
return await self._set_db_guild_opt("welcome_msg", None, ctx)
async def _handle_remove_goodbye_msg(self, ctx, setting=None):
return await self._set_db_guild_opt("goodbye_msg", None, ctx)
async def _handle_remove_prefix(self, ctx, setting=None):
return await self._set_db_guild_opt("prefix", None, ctx)
async def _handle_remove_default_alerts(self, ctx, setting=None):
return await self._set_db_guild_opt("default_alerts", None, ctx)
async def _handle_remove_welcome_alerts(self, ctx, setting=None):
return await self._set_db_guild_opt("welcome_alerts", None, ctx)
async def _handle_remove_goodbye_alerts(self, ctx, setting=None):
return await self._set_db_guild_opt("goodbye_alerts", None, ctx)
async def _handle_remove_picarto_alerts(self, ctx, setting=None):
return await self._set_db_guild_opt("picarto_alerts", None, ctx)
async def _handle_remove_birthday_alerts(self, ctx, setting=None):
return await self._set_db_guild_opt("birthday_alerts", None, ctx)
async def _handle_remove_raffle_alerts(self, ctx, setting=None):
return await self._set_db_guild_opt("raffle_alerts", None, ctx)
async def _handle_remove_followed_picarto_channels(self, ctx, setting=None):
if setting is None:
raise WrongSettingType("Specifying which channel you want to remove is required")
query = """
UPDATE
guilds
SET
followed_picarto_channels = array_remove(followed_picarto_channels, $1)
WHERE
id=$2
"""
return await ctx.bot.db.execute(query, setting, ctx.guild.id)
async def _handle_remove_ignored_channels(self, ctx, setting=None):
if setting is None:
raise WrongSettingType("Specifying which channel you want to remove is required")
channel = await self._get_channel(ctx, setting)
query = """
UPDATE
guilds
SET
ignored_channels = array_remove(ignored_channels, $1)
WHERE
id=$2
"""
return await ctx.bot.db.execute(query, channel.id, ctx.guild.id)
async def _handle_remove_ignored_members(self, ctx, setting=None):
if setting is None:
raise WrongSettingType("Specifying which channel you want to remove is required")
# We want to make it possible to have members that aren't in the server ignored
# So first check if it's a digit (the id)
if not setting.isdigit():
converter = commands.converter.MemberConverter()
member = await converter.convert(ctx, setting)
setting = member.id
query = """
UPDATE
guilds
SET
ignored_members = array_remove(ignored_members, $1)
WHERE
id=$2
"""
return await ctx.bot.db.execute(query, setting, ctx.guild.id)
async def _handle_remove_rules(self, ctx, setting=None):
if setting is None or not setting.isdigit():
raise WrongSettingType("Please provide the number of the rule you want to remove")
query = """
UPDATE
guilds
SET
rules = array_remove(rules, rules[$1])
WHERE
id=$2
"""
return await ctx.bot.db.execute(query, setting, ctx.guild.id)
async def _handle_remove_assignable_roles(self, ctx, setting=None):
if setting is None:
raise WrongSettingType("Specifying which channel you want to remove is required")
if not setting.isdigit():
converter = commands.converter.RoleConverter()
role = await converter.convert(ctx, setting)
setting = role.id
query = """
UPDATE
guilds
SET
assignable_roles = array_remove(assignable_roles, $1)
WHERE
id=$2
"""
return await ctx.bot.db.execute(query, setting, ctx.guild.id)
async def _handle_remove_custom_battles(self, ctx, setting=None):
if setting is None or not setting.isdigit():
raise WrongSettingType("Please provide the number of the custom battle you want to remove")
query = """
UPDATE
guilds
SET
custom_battles = array_remove(custom_battles, rules[$1])
WHERE
id=$2
"""
return await ctx.bot.db.execute(query, setting, ctx.guild.id)
async def _handle_remove_custom_hugs(self, ctx, setting=None):
if setting is None or not setting.isdigit():
raise WrongSettingType("Please provide the number of the custom hug you want to remove")
query = """
UPDATE
guilds
SET
custom_hugs = array_remove(custom_hugs, rules[$1])
WHERE
id=$2
"""
return await ctx.bot.db.execute(query, setting, ctx.guild.id)
async def __after_invoke(self, ctx):
"""Here we will facilitate cleaning up settings, will remove channels/roles that no longer exist, etc."""
pass
@commands.group(invoke_without_command=True)
@commands.guild_only()
@utils.can_run(manage_guild=True)
async def config(self, ctx, *, opt=None):
"""Handles the configuration of the bot for this server"""
if opt:
setting = await ctx.bot.db.fetchrow("SELECT * FROM guilds WHERE id=$1", ctx.guild.id)
if setting and opt in setting:
setting = await utils.convert(ctx, str(setting[opt])) or setting[opt]
await ctx.send(f"{opt} is set to:\n{setting}")
return
settings = await ctx.bot.db.fetchrow("SELECT * FROM guilds WHERE id=$1", ctx.guild.id)
# For convenience, if it's None, just create it and return the default values
if settings is None:
await ctx.bot.db.execute("INSERT INTO guilds (id) VALUES ($1)", ctx.guild.id)
settings = await ctx.bot.db.fetchrow("SELECT * FROM guilds WHERE id=$1", ctx.guild.id)
alerts = {}
# This is dirty I know, but oh well...
for alert_type in ["default", "welcome", "goodbye", "picarto", "birthday", "raffle"]:
channel = ctx.guild.get_channel(settings.get(f"{alert_type}_alerts"))
name = channel.name if channel else None
alerts[alert_type] = name
fmt = f"""
**Notification Settings**
birthday_notifications
*Notify on the birthday that users in this guild have saved*
**{settings.get("birthday_notifications")}**
welcome_notifications
*Notify when someone has joined this guild*
**{settings.get("welcome_notifications")}**
goodbye_notifications
*Notify when someone has left this guild
**{settings.get("goodbye_notifications")}**
welcome_msg
*A message that can be customized and used when someone joins the server*
**{"Set" if settings.get("welcome_msg") is not None else "Not set"}**
goodbye_msg
*A message that can be customized and used when someone leaves the server*
**{"Set" if settings.get("goodbye_msg") is not None else "Not set"}**
**Alert Channels**
default_alerts
*The channel to default alert messages to*
**{alerts.get("default_alerts")}**
welcome_alerts
*The channel to send welcome alerts to (when someone joins the server)*
**{alerts.get("welcome_alerts")}**
goodbye_alerts
*The channel to send goodbye alerts to (when someone leaves the server)*
**{alerts.get("goodbye_alerts")}**
picarto_alerts
*The channel to send Picarto alerts to (when a channel the server follows goes on/offline)*
**{alerts.get("picarto_alerts")}**
birthday_alerts
*The channel to send birthday alerts to (on the day of someone's birthday)*
**{alerts.get("birthday_alerts")}**
raffle_alerts
*The channel to send alerts for server raffles to*
**{alerts.get("raffle_alerts")}**
**Misc Settings**
followed_picarto_channels
*Channels for the bot to "follow" and notify this server when they go live*
**{len(settings.get("followed_picarto_channels"))}**
ignored_channels
*Channels that the bot ignores*
**{len(settings.get("ignored_channels"))}**
ignored_members
*Members that the bot ignores*
**{len(settings.get("ignored_members"))}**
rules
*Rules for this server*
**{len(settings.get("rules"))}**
assignable_roles
*Roles that can be self-assigned by users*
**{len(settings.get("assignable_roles"))}**
custom_battles
*Possible outcomes to battles that can be received on this server*
**{len(settings.get("custom_battles"))}**
custom_hugs
*Possible outcomes to hugs that can be received on this server*
**{len(settings.get("custom_hugs"))}**
""".strip()
embed = discord.Embed(title=f"Configuration for {ctx.guild.name}", description=fmt)
embed.set_image(url=ctx.guild.icon_url)
await ctx.send(embed=embed)
@config.command(name="set", aliases=["add"])
@commands.guild_only()
@utils.can_run(manage_guild=True)
async def _set_setting(self, ctx, option, *, setting):
"""Sets one of the configuration settings for this server"""
try:
coro = getattr(self, f"_handle_set_{option}")
except AttributeError:
await ctx.send(f"{option} is not a valid config option. Use {ctx.prefix}config to list all config options")
else:
try:
await coro(ctx, setting=setting)
except WrongSettingType as exc:
await ctx.send(exc.message)
except MessageFormatError as exc:
fmt = f"""
Failed to parse the format string provided, possible keys are: {', '.join(k for k in exc.keys)}
Extraneous args provided: {', '.join(k for k in exc.original.args)}
"""
await ctx.send(fmt)
except commands.BadArgument:
pass
else:
await ctx.send(f"{option} has succesfully been set to {setting}")
@config.command(name="unset", aliases=["remove"])
@commands.guild_only()
@utils.can_run(manage_guild=True)
async def _remove_setting(self, ctx, option, *, setting=None):
"""Unsets/removes an option from one of the settings."""
try:
coro = getattr(self, f"_handle_remove_{option}")
except AttributeError:
await ctx.send(f"{option} is not a valid config option. Use {ctx.prefix}config to list all config options")
else:
try:
await coro(ctx, setting=setting)
except WrongSettingType as exc:
await ctx.send(exc.message)
except commands.BadArgument:
pass
else:
await ctx.send(f"{option} has succesfully been unset")
def setup(bot):
bot.add_cog(GuildConfiguration())