mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-09 04:38:55 -05:00
[Core] Command disable feature (#2099)
* [Core] Command disable feature Signed-off-by: Toby Harradine <tobyharradine@gmail.com> * [Core] Allow user to set the "command disabled" message Signed-off-by: Toby Harradine <tobyharradine@gmail.com> * Reformat Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
This commit is contained in:
parent
1dbe9537e9
commit
9ee860c3f0
@ -58,6 +58,8 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
|||||||
help__page_char_limit=1000,
|
help__page_char_limit=1000,
|
||||||
help__max_pages_in_guild=2,
|
help__max_pages_in_guild=2,
|
||||||
help__tagline="",
|
help__tagline="",
|
||||||
|
disabled_commands=[],
|
||||||
|
disabled_command_msg="That command is disabled.",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.db.register_guild(
|
self.db.register_guild(
|
||||||
@ -69,6 +71,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
|||||||
embeds=None,
|
embeds=None,
|
||||||
use_bot_color=False,
|
use_bot_color=False,
|
||||||
fuzzy=False,
|
fuzzy=False,
|
||||||
|
disabled_commands=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.db.register_user(embeds=None)
|
self.db.register_user(embeds=None)
|
||||||
@ -340,6 +343,13 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
|||||||
)
|
)
|
||||||
super().add_cog(cog)
|
super().add_cog(cog)
|
||||||
|
|
||||||
|
def add_command(self, command: commands.Command):
|
||||||
|
if not isinstance(command, commands.Command):
|
||||||
|
raise TypeError("Command objects must derive from redbot.core.commands.Command")
|
||||||
|
|
||||||
|
super().add_command(command)
|
||||||
|
self.dispatch("command_add", command)
|
||||||
|
|
||||||
|
|
||||||
class Red(RedBase, discord.AutoShardedClient):
|
class Red(RedBase, discord.AutoShardedClient):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -4,8 +4,10 @@ This module contains extended classes and functions which are intended to
|
|||||||
replace those from the `discord.ext.commands` module.
|
replace those from the `discord.ext.commands` module.
|
||||||
"""
|
"""
|
||||||
import inspect
|
import inspect
|
||||||
from typing import TYPE_CHECKING
|
import weakref
|
||||||
|
from typing import Awaitable, Callable, TYPE_CHECKING
|
||||||
|
|
||||||
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from .errors import ConversionFailure
|
from .errors import ConversionFailure
|
||||||
@ -104,6 +106,49 @@ class Command(commands.Command):
|
|||||||
# We should expose anything which might be a bug in the converter
|
# We should expose anything which might be a bug in the converter
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
|
def disable_in(self, guild: discord.Guild) -> bool:
|
||||||
|
"""Disable this command in the given guild.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
guild : discord.Guild
|
||||||
|
The guild to disable the command in.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
``True`` if the command wasn't already disabled.
|
||||||
|
|
||||||
|
"""
|
||||||
|
disabler = get_command_disabler(guild)
|
||||||
|
if disabler in self.checks:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.checks.append(disabler)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def enable_in(self, guild: discord.Guild) -> bool:
|
||||||
|
"""Enable this command in the given guild.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
guild : discord.Guild
|
||||||
|
The guild to enable the command in.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
``True`` if the command wasn't already enabled.
|
||||||
|
|
||||||
|
"""
|
||||||
|
disabler = get_command_disabler(guild)
|
||||||
|
try:
|
||||||
|
self.checks.remove(disabler)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class GroupMixin(commands.GroupMixin):
|
class GroupMixin(commands.GroupMixin):
|
||||||
"""Mixin for `Group` and `Red` classes.
|
"""Mixin for `Group` and `Red` classes.
|
||||||
@ -162,6 +207,12 @@ class Group(GroupMixin, Command, commands.Group):
|
|||||||
if self.autohelp and not self.invoke_without_command:
|
if self.autohelp and not self.invoke_without_command:
|
||||||
await self._verify_checks(ctx)
|
await self._verify_checks(ctx)
|
||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
|
elif self.invoke_without_command:
|
||||||
|
# So invoke_without_command when a subcommand of this group is invoked
|
||||||
|
# will skip the the invokation of *this* command. However, because of
|
||||||
|
# how our permissions system works, we don't want it to skip the checks
|
||||||
|
# as well.
|
||||||
|
await self._verify_checks(ctx)
|
||||||
|
|
||||||
await super().invoke(ctx)
|
await super().invoke(ctx)
|
||||||
|
|
||||||
@ -184,3 +235,25 @@ def group(name=None, **attrs):
|
|||||||
Same interface as `discord.ext.commands.group`.
|
Same interface as `discord.ext.commands.group`.
|
||||||
"""
|
"""
|
||||||
return command(name, cls=Group, **attrs)
|
return command(name, cls=Group, **attrs)
|
||||||
|
|
||||||
|
|
||||||
|
__command_disablers = weakref.WeakValueDictionary()
|
||||||
|
|
||||||
|
|
||||||
|
def get_command_disabler(guild: discord.Guild) -> Callable[["Context"], Awaitable[bool]]:
|
||||||
|
"""Get the command disabler for a guild.
|
||||||
|
|
||||||
|
A command disabler is a simple check predicate which returns
|
||||||
|
``False`` if the context is within the given guild.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return __command_disablers[guild]
|
||||||
|
except KeyError:
|
||||||
|
|
||||||
|
async def disabler(ctx: "Context") -> bool:
|
||||||
|
if ctx.guild == guild:
|
||||||
|
raise commands.DisabledCommand()
|
||||||
|
return True
|
||||||
|
|
||||||
|
__command_disablers[guild] = disabler
|
||||||
|
return disabler
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import contextlib
|
||||||
import datetime
|
import datetime
|
||||||
import importlib
|
import importlib
|
||||||
import itertools
|
import itertools
|
||||||
@ -1561,6 +1562,138 @@ class Core(CoreLogic):
|
|||||||
await ctx.bot.db.guild(ctx.guild).blacklist.set([])
|
await ctx.bot.db.guild(ctx.guild).blacklist.set([])
|
||||||
await ctx.send(_("blacklist has been cleared."))
|
await ctx.send(_("blacklist has been cleared."))
|
||||||
|
|
||||||
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
|
@commands.group(name="command")
|
||||||
|
async def command_manager(self, ctx: commands.Context):
|
||||||
|
"""Manage the bot's commands."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@command_manager.group(name="disable", invoke_without_command=True)
|
||||||
|
async def command_disable(self, ctx: commands.Context, *, command: str):
|
||||||
|
"""Disable a command.
|
||||||
|
|
||||||
|
If you're the bot owner, this will disable commands
|
||||||
|
globally by default.
|
||||||
|
"""
|
||||||
|
# Select the scope based on the author's privileges
|
||||||
|
if await ctx.bot.is_owner(ctx.author):
|
||||||
|
await ctx.invoke(self.command_disable_global, command=command)
|
||||||
|
else:
|
||||||
|
await ctx.invoke(self.command_disable_guild, command=command)
|
||||||
|
|
||||||
|
@checks.is_owner()
|
||||||
|
@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)
|
||||||
|
if command_obj is None:
|
||||||
|
await ctx.send(
|
||||||
|
_("I couldn't find that command. Please note that it is case sensitive.")
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
async with ctx.bot.db.disabled_commands() as disabled_commands:
|
||||||
|
if command not in disabled_commands:
|
||||||
|
disabled_commands.append(command_obj.qualified_name)
|
||||||
|
|
||||||
|
if not command_obj.enabled:
|
||||||
|
await ctx.send(_("That command is already disabled globally."))
|
||||||
|
return
|
||||||
|
command_obj.enabled = False
|
||||||
|
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
|
@commands.guild_only()
|
||||||
|
@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)
|
||||||
|
if command_obj is None:
|
||||||
|
await ctx.send(
|
||||||
|
_("I couldn't find that command. Please note that it is case sensitive.")
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
async with ctx.bot.db.guild(ctx.guild).disabled_commands() as disabled_commands:
|
||||||
|
if command not in disabled_commands:
|
||||||
|
disabled_commands.append(command_obj.qualified_name)
|
||||||
|
|
||||||
|
done = command_obj.disable_in(ctx.guild)
|
||||||
|
|
||||||
|
if not done:
|
||||||
|
await ctx.send(_("That command is already disabled in this server."))
|
||||||
|
else:
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
|
@command_manager.group(name="enable", invoke_without_command=True)
|
||||||
|
async def command_enable(self, ctx: commands.Context, *, command: str):
|
||||||
|
"""Enable a command.
|
||||||
|
|
||||||
|
If you're a bot owner, this will try to enable a globally
|
||||||
|
disabled command by default.
|
||||||
|
"""
|
||||||
|
if await ctx.bot.is_owner(ctx.author):
|
||||||
|
await ctx.invoke(self.command_enable_global, command=command)
|
||||||
|
else:
|
||||||
|
await ctx.invoke(self.command_enable_guild, command=command)
|
||||||
|
|
||||||
|
@commands.is_owner()
|
||||||
|
@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)
|
||||||
|
if command_obj is None:
|
||||||
|
await ctx.send(
|
||||||
|
_("I couldn't find that command. Please note that it is case sensitive.")
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
async with ctx.bot.db.disabled_commands() as disabled_commands:
|
||||||
|
with contextlib.suppress(ValueError):
|
||||||
|
disabled_commands.remove(command_obj.qualified_name)
|
||||||
|
|
||||||
|
if command_obj.enabled:
|
||||||
|
await ctx.send(_("That command is already enabled globally."))
|
||||||
|
return
|
||||||
|
|
||||||
|
command_obj.enabled = True
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
|
@commands.guild_only()
|
||||||
|
@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)
|
||||||
|
if command_obj is None:
|
||||||
|
await ctx.send(
|
||||||
|
_("I couldn't find that command. Please note that it is case sensitive.")
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
async with ctx.bot.db.guild(ctx.guild).disabled_commands() as disabled_commands:
|
||||||
|
with contextlib.suppress(ValueError):
|
||||||
|
disabled_commands.remove(command_obj.qualified_name)
|
||||||
|
|
||||||
|
done = command_obj.enable_in(ctx.guild)
|
||||||
|
|
||||||
|
if not done:
|
||||||
|
await ctx.send(_("That command is already enabled in this server."))
|
||||||
|
else:
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
|
@checks.is_owner()
|
||||||
|
@command_manager.command(name="disabledmsg")
|
||||||
|
async def command_disabledmsg(self, ctx: commands.Context, *, message: str = ""):
|
||||||
|
"""Set the bot's response to disabled commands.
|
||||||
|
|
||||||
|
Leave blank to send nothing.
|
||||||
|
|
||||||
|
To include the command name in the message, include the
|
||||||
|
`{command}` placeholder.
|
||||||
|
"""
|
||||||
|
await ctx.bot.db.disabled_command_msg.set(message)
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
# RPC handlers
|
# RPC handlers
|
||||||
async def rpc_load(self, request):
|
async def rpc_load(self, request):
|
||||||
cog_name = request.params[0]
|
cog_name = request.params[0]
|
||||||
|
|||||||
@ -193,7 +193,9 @@ def init_events(bot, cli_flags):
|
|||||||
elif isinstance(error, commands.BadArgument):
|
elif isinstance(error, commands.BadArgument):
|
||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
elif isinstance(error, commands.DisabledCommand):
|
elif isinstance(error, commands.DisabledCommand):
|
||||||
await ctx.send("That command is disabled.")
|
disabled_message = await bot.db.disabled_command_msg()
|
||||||
|
if disabled_message:
|
||||||
|
await ctx.send(disabled_message.replace("{command}", ctx.invoked_with))
|
||||||
elif isinstance(error, commands.CommandInvokeError):
|
elif isinstance(error, commands.CommandInvokeError):
|
||||||
# Need to test if the following still works
|
# Need to test if the following still works
|
||||||
"""
|
"""
|
||||||
@ -241,7 +243,7 @@ def init_events(bot, cli_flags):
|
|||||||
await ctx.send("That command is not available in DMs.")
|
await ctx.send("That command is not available in DMs.")
|
||||||
elif isinstance(error, commands.CommandOnCooldown):
|
elif isinstance(error, commands.CommandOnCooldown):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"This command is on cooldown. " "Try again in {:.2f}s" "".format(error.retry_after)
|
"This command is on cooldown. Try again in {:.2f}s".format(error.retry_after)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
log.exception(type(error).__name__, exc_info=error)
|
log.exception(type(error).__name__, exc_info=error)
|
||||||
@ -280,6 +282,42 @@ def init_events(bot, cli_flags):
|
|||||||
async def on_command(command):
|
async def on_command(command):
|
||||||
bot.counter["processed_commands"] += 1
|
bot.counter["processed_commands"] += 1
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_command_add(command: commands.Command):
|
||||||
|
disabled_commands = await bot.db.disabled_commands()
|
||||||
|
if command.qualified_name in disabled_commands:
|
||||||
|
command.enabled = False
|
||||||
|
for guild in bot.guilds:
|
||||||
|
disabled_commands = await bot.db.guild(guild).disabled_commands()
|
||||||
|
if command.qualified_name in disabled_commands:
|
||||||
|
command.disable_in(guild)
|
||||||
|
|
||||||
|
async def _guild_added(guild: discord.Guild):
|
||||||
|
disabled_commands = await bot.db.guild(guild).disabled_commands()
|
||||||
|
for command_name in disabled_commands:
|
||||||
|
command_obj = bot.get_command(command_name)
|
||||||
|
if command_obj is not None:
|
||||||
|
command_obj.disable_in(guild)
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_guild_join(guild: discord.Guild):
|
||||||
|
await _guild_added(guild)
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_guild_available(guild: discord.Guild):
|
||||||
|
# We need to check guild-disabled commands here since some cogs
|
||||||
|
# are loaded prior to `on_ready`.
|
||||||
|
await _guild_added(guild)
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_guild_leave(guild: discord.Guild):
|
||||||
|
# Clean up any unneeded checks
|
||||||
|
disabled_commands = await bot.db.guild(guild).disabled_commands()
|
||||||
|
for command_name in disabled_commands:
|
||||||
|
command_obj = bot.get_command(command_name)
|
||||||
|
if command_obj is not None:
|
||||||
|
command_obj.enable_in(guild)
|
||||||
|
|
||||||
|
|
||||||
def _get_startup_screen_specs():
|
def _get_startup_screen_specs():
|
||||||
"""Get specs for displaying the startup screen on stdout.
|
"""Get specs for displaying the startup screen on stdout.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user