Add per-command embed settings (embed_requested) (#4049)

* Add per-command embed settings

* Update `[p]embedset showsettings`

* Use command.qualified_name in `[p]embedset` commands

* Fix the call to subcommand and simplify it

* And I oversimplified this

* Update end user messages to use 'guild' rather than 'server'

* meh

* This should be a named field

* Add check for Embed Links perm to confuse users a bit less

* Wrap this string in _()

* Let's use a different exception then...

* Let's clear the setting when we can in whole `[p]embedset`

* Add the order of checking to help of `[p]embedset`

* Add note about full evaluation order to subcommands
This commit is contained in:
jack1142 2021-04-04 21:37:46 +02:00 committed by GitHub
parent 03d72dd5fa
commit 560e1dfc3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 240 additions and 43 deletions

View File

@ -53,6 +53,7 @@ from .utils import common_filters, AsyncIter
from .utils._internal_utils import send_to_owners_with_prefix_replaced
CUSTOM_GROUPS = "CUSTOM_GROUPS"
COMMAND_SCOPE = "COMMAND"
SHARED_API_TOKENS = "SHARED_API_TOKENS"
log = logging.getLogger("red")
@ -155,6 +156,12 @@ class RedBase(
self._config.init_custom(CUSTOM_GROUPS, 2)
self._config.register_custom(CUSTOM_GROUPS)
# {COMMAND_NAME: {GUILD_ID: {...}}}
# GUILD_ID=0 for global setting
self._config.init_custom(COMMAND_SCOPE, 2)
self._config.register_custom(COMMAND_SCOPE, embeds=None)
# TODO: add cache for embed settings
self._config.init_custom(SHARED_API_TOKENS, 2)
self._config.register_custom(SHARED_API_TOKENS)
self._prefix_cache = PrefixManager(self._config, cli_flags)
@ -1022,7 +1029,12 @@ class RedBase(
ctx, help_for, from_help_command=from_help_command
)
async def embed_requested(self, channel, user, command=None) -> bool:
async def embed_requested(
self,
channel: Union[discord.abc.GuildChannel, discord.abc.PrivateChannel],
user: discord.abc.User,
command: Optional[commands.Command] = None,
) -> bool:
"""
Determine if an embed is requested for a response.
@ -1032,26 +1044,38 @@ class RedBase(
The channel to check embed settings for.
user : `discord.abc.User`
The user to check embed settings for.
command
(Optional) the command ran.
command : `commands.Command`, optional
The command ran.
Returns
-------
bool
:code:`True` if an embed is requested
"""
async def get_command_setting(guild_id: int) -> Optional[bool]:
if command is None:
return None
scope = self._config.custom(COMMAND_SCOPE, command.qualified_name, guild_id)
return await scope.embeds()
if isinstance(channel, discord.abc.PrivateChannel):
user_setting = await self._config.user(user).embeds()
if user_setting is not None:
if (user_setting := await self._config.user(user).embeds()) is not None:
return user_setting
else:
channel_setting = await self._config.channel(channel).embeds()
if channel_setting is not None:
if (channel_setting := await self._config.channel(channel).embeds()) is not None:
return channel_setting
guild_setting = await self._config.guild(channel.guild).embeds()
if guild_setting is not None:
if (command_setting := await get_command_setting(channel.guild.id)) is not None:
return command_setting
if (guild_setting := await self._config.guild(channel.guild).embeds()) is not None:
return guild_setting
# XXX: maybe this should be checked before guild setting?
if (global_command_setting := await get_command_setting(0)) is not None:
return global_command_setting
global_setting = await self._config.embeds()
return global_setting

View File

@ -1049,22 +1049,62 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
use embeds as a response to a command (for
commands that support it). The default is to
use embeds.
The embed settings are checked until the first True/False in this order:
- In guild context:
1. Channel override ([p]embedset channel)
2. Server command override ([p]embedset command server)
3. Server override ([p]embedset server)
4. Global command override ([p]embedset command global)
5. Global setting ([p]embedset global)
- In DM context:
1. User override ([p]embedset user)
2. Global command override ([p]embedset command global)
3. Global setting ([p]embedset global)
"""
@embedset.command(name="showsettings")
async def embedset_showsettings(self, ctx: commands.Context):
async def embedset_showsettings(self, ctx: commands.Context, command_name: str = None) -> None:
"""Show the current embed settings."""
if command_name is not None:
command_obj: Optional[commands.Command] = ctx.bot.get_command(command_name)
if command_obj is None:
await ctx.send(
_("I couldn't find that command. Please note that it is case sensitive.")
)
return
# qualified name might be different if alias was passed to this command
command_name = command_obj.qualified_name
text = _("Embed settings:\n\n")
global_default = await self.bot._config.embeds()
text += _("Global default: {}\n").format(global_default)
text += _("Global default: {value}\n").format(value=global_default)
if command_name is not None:
scope = self.bot._config.custom("COMMAND", command_name, 0)
global_command_setting = await scope.embeds()
text += _("Global command setting for {command} command: {value}\n").format(
command=inline(command_name), value=global_command_setting
)
if ctx.guild:
guild_setting = await self.bot._config.guild(ctx.guild).embeds()
text += _("Guild setting: {}\n").format(guild_setting)
text += _("Guild setting: {value}\n").format(value=guild_setting)
if command_name is not None:
scope = self.bot._config.custom("COMMAND", command_name, ctx.guild.id)
command_setting = await scope.embeds()
text += _("Server command setting for {command} command: {value}\n").format(
command=inline(command_name), value=command_setting
)
if ctx.channel:
channel_setting = await self.bot._config.channel(ctx.channel).embeds()
text += _("Channel setting: {}\n").format(channel_setting)
text += _("Channel setting: {value}\n").format(value=channel_setting)
user_setting = await self.bot._config.user(ctx.author).embeds()
text += _("User setting: {}").format(user_setting)
text += _("User setting: {value}").format(value=user_setting)
await ctx.send(box(text))
@embedset.command(name="global")
@ -1076,12 +1116,16 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
This is used as a fallback if the user
or guild hasn't set a preference. The
default is to use embeds.
To see full evaluation order of embed settings, run `[p]help embedset`
"""
current = await self.bot._config.embeds()
await self.bot._config.embeds.set(not current)
await ctx.send(
_("Embeds are now {} by default.").format(_("disabled") if current else _("enabled"))
)
if current:
await self.bot._config.embeds.set(False)
await ctx.send(_("Embeds are now disabled by default."))
else:
await self.bot._config.embeds.clear()
await ctx.send(_("Embeds are now enabled by default."))
@embedset.command(name="server", aliases=["guild"])
@checks.guildowner_or_permissions(administrator=True)
@ -1095,16 +1139,139 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
If set, this is used instead of the global default
to determine whether or not to use embeds. This is
used for all commands done in a guild channel except
for help commands.
used for all commands done in a server channel.
To see full evaluation order of embed settings, run `[p]help embedset`
"""
await self.bot._config.guild(ctx.guild).embeds.set(enabled)
if enabled is None:
await self.bot._config.guild(ctx.guild).embeds.clear()
await ctx.send(_("Embeds will now fall back to the global setting."))
return
await self.bot._config.guild(ctx.guild).embeds.set(enabled)
await ctx.send(
_("Embeds are now enabled for this guild.")
if enabled
else _("Embeds are now disabled for this guild.")
)
@checks.guildowner_or_permissions(administrator=True)
@embedset.group(name="command", invoke_without_command=True)
async def embedset_command(
self, ctx: commands.Context, command_name: str, enabled: bool = None
) -> None:
"""
Toggle the command's embed setting.
If you're the bot owner, this will change command's embed setting
globally by default.
To see full evaluation order of embed settings, run `[p]help embedset`
"""
# Select the scope based on the author's privileges
if await ctx.bot.is_owner(ctx.author):
await self.embedset_command_global(ctx, command_name, enabled)
else:
await self.embedset_command_guild(ctx, command_name, enabled)
def _check_if_command_requires_embed_links(self, command_obj: commands.Command) -> None:
for command in itertools.chain((command_obj,), command_obj.parents):
if command_obj.requires.bot_perms.embed_links:
# a slight abuse of this exception to save myself two lines later...
raise commands.UserFeedbackCheckFailure(
_(
"The passed command requires Embed Links permission"
" and therefore cannot be set to not use embeds."
)
)
@commands.is_owner()
@embedset_command.command(name="global")
async def embedset_command_global(
self, ctx: commands.Context, command_name: str, enabled: bool = None
):
"""
Toggle the commmand's embed setting.
If enabled is None, the setting will be unset and
the global default will be used instead.
If set, this is used instead of the global default
to determine whether or not to use embeds.
To see full evaluation order of embed settings, run `[p]help embedset`
"""
command_obj: Optional[commands.Command] = ctx.bot.get_command(command_name)
if command_obj is None:
await ctx.send(
_("I couldn't find that command. Please note that it is case sensitive.")
)
return
self._check_if_command_requires_embed_links(command_obj)
# qualified name might be different if alias was passed to this command
command_name = command_obj.qualified_name
if enabled is None:
await self.bot._config.custom("COMMAND", command_name, 0).embeds.clear()
await ctx.send(_("Embeds will now fall back to the global setting."))
return
await self.bot._config.custom("COMMAND", command_name, 0).embeds.set(enabled)
if enabled:
await ctx.send(
_("Embeds are now enabled for {command_name} command.").format(
command_name=inline(command_name)
)
)
else:
await ctx.send(
_("Embeds are now {} for this guild.").format(
_("enabled") if enabled else _("disabled")
_("Embeds are now disabled for {command_name} command.").format(
command_name=inline(command_name)
)
)
@commands.guild_only()
@embedset_command.command(name="server", aliases=["guild"])
async def embedset_command_guild(
self, ctx: commands.GuildContext, command_name: str, enabled: bool = None
):
"""
Toggle the commmand's embed setting.
If enabled is None, the setting will be unset and
the server default will be used instead.
If set, this is used instead of the server default
to determine whether or not to use embeds.
To see full evaluation order of embed settings, run `[p]help embedset`
"""
command_obj: Optional[commands.Command] = ctx.bot.get_command(command_name)
if command_obj is None:
await ctx.send(
_("I couldn't find that command. Please note that it is case sensitive.")
)
return
self._check_if_command_requires_embed_links(command_obj)
# qualified name might be different if alias was passed to this command
command_name = command_obj.qualified_name
if enabled is None:
await self.bot._config.custom("COMMAND", command_name, ctx.guild.id).embeds.clear()
await ctx.send(_("Embeds will now fall back to the server setting."))
return
await self.bot._config.custom("COMMAND", command_name, ctx.guild.id).embeds.set(enabled)
if enabled:
await ctx.send(
_("Embeds are now enabled for {command_name} command.").format(
command_name=inline(command_name)
)
)
else:
await ctx.send(
_("Embeds are now disabled for {command_name} command.").format(
command_name=inline(command_name)
)
)
@ -1118,15 +1285,18 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
If enabled is None, the setting will be unset and
the guild default will be used instead.
If set, this is used instead of the guild default
If set, this is used instead of the guild and command default
to determine whether or not to use embeds. This is
used for all commands done in a channel except
for help commands.
used for all commands done in a channel.
To see full evaluation order of embed settings, run `[p]help embedset`
"""
await self.bot._config.channel(ctx.channel).embeds.set(enabled)
if enabled is None:
await self.bot._config.channel(ctx.channel).embeds.clear()
await ctx.send(_("Embeds will now fall back to the global setting."))
else:
return
await self.bot._config.channel(ctx.channel).embeds.set(enabled)
await ctx.send(
_("Embeds are now {} for this channel.").format(
_("enabled") if enabled else _("disabled")
@ -1144,11 +1314,14 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
If set, this is used instead of the global default
to determine whether or not to use embeds. This is
used for all commands executed in a DM with the bot.
To see full evaluation order of embed settings, run `[p]help embedset`
"""
await self.bot._config.user(ctx.author).embeds.set(enabled)
if enabled is None:
await self.bot._config.user(ctx.author).embeds.clear()
await ctx.send(_("Embeds will now fall back to the global setting."))
else:
await self.bot._config.user(ctx.author).embeds.set(enabled)
await ctx.send(
_("Embeds are now enabled for you in DMs.")
if enabled
@ -3268,7 +3441,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
@command_disable.command(name="global")
async def command_disable_global(self, ctx: commands.Context, *, command: str):
"""Disable a command globally."""
command_obj: commands.Command = ctx.bot.get_command(command)
command_obj: Optional[commands.Command] = ctx.bot.get_command(command)
if command_obj is None:
await ctx.send(
_("I couldn't find that command. Please note that it is case sensitive.")
@ -3302,7 +3475,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
@command_disable.command(name="server", aliases=["guild"])
async def command_disable_guild(self, ctx: commands.Context, *, command: str):
"""Disable a command in this server only."""
command_obj: commands.Command = ctx.bot.get_command(command)
command_obj: Optional[commands.Command] = ctx.bot.get_command(command)
if command_obj is None:
await ctx.send(
_("I couldn't find that command. Please note that it is case sensitive.")
@ -3352,7 +3525,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
@command_enable.command(name="global")
async def command_enable_global(self, ctx: commands.Context, *, command: str):
"""Enable a command globally."""
command_obj: commands.Command = ctx.bot.get_command(command)
command_obj: Optional[commands.Command] = ctx.bot.get_command(command)
if command_obj is None:
await ctx.send(
_("I couldn't find that command. Please note that it is case sensitive.")
@ -3374,7 +3547,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
@command_enable.command(name="server", aliases=["guild"])
async def command_enable_guild(self, ctx: commands.Context, *, command: str):
"""Enable a command in this server."""
command_obj: commands.Command = ctx.bot.get_command(command)
command_obj: Optional[commands.Command] = ctx.bot.get_command(command)
if command_obj is None:
await ctx.send(
_("I couldn't find that command. Please note that it is case sensitive.")