diff options
author | 2022-02-27 00:06:56 +0200 | |
---|---|---|
committer | 2022-07-16 02:11:29 +0300 | |
commit | 08d06555d28a9588d9d8c814655df9c55c0c5563 (patch) | |
tree | 870689da7b4ef281a1a4729a9f78a3ea3a8c92e7 | |
parent | Add guild invite filtering (diff) |
Fix argument completion for non-last args
Previously the completed arg would be appended to the end of the message, which didn't work if the missing argument wasn't the last one (for example, a value was given to a following argument because of a failed conversion for previous ones).
The select now employs a different strategy of storing the provided args, and the position where the new are should be inserted.
-rw-r--r-- | bot/exts/filtering/_filter_lists/__init__.py | 4 | ||||
-rw-r--r-- | bot/exts/filtering/_filter_lists/filter_list.py | 26 | ||||
-rw-r--r-- | bot/exts/filtering/_filter_lists/invite.py | 2 | ||||
-rw-r--r-- | bot/exts/filtering/_ui.py | 40 | ||||
-rw-r--r-- | bot/exts/filtering/filtering.py | 14 |
5 files changed, 54 insertions, 32 deletions
diff --git a/bot/exts/filtering/_filter_lists/__init__.py b/bot/exts/filtering/_filter_lists/__init__.py index 1273e5588..82e0452f9 100644 --- a/bot/exts/filtering/_filter_lists/__init__.py +++ b/bot/exts/filtering/_filter_lists/__init__.py @@ -1,9 +1,9 @@ from os.path import dirname -from bot.exts.filtering._filter_lists.filter_list import FilterList, ListType, ListTypeConverter +from bot.exts.filtering._filter_lists.filter_list import FilterList, ListType, list_type_converter from bot.exts.filtering._utils import subclasses_in_package filter_list_types = subclasses_in_package(dirname(__file__), f"{__name__}.", FilterList) filter_list_types = {filter_list.name: filter_list for filter_list in filter_list_types} -__all__ = [filter_list_types, FilterList, ListType, ListTypeConverter] +__all__ = [filter_list_types, FilterList, ListType, list_type_converter] diff --git a/bot/exts/filtering/_filter_lists/filter_list.py b/bot/exts/filtering/_filter_lists/filter_list.py index 672811444..5fc992597 100644 --- a/bot/exts/filtering/_filter_lists/filter_list.py +++ b/bot/exts/filtering/_filter_lists/filter_list.py @@ -2,7 +2,7 @@ from abc import abstractmethod from enum import Enum from typing import Dict, List, Optional, Type -from discord.ext.commands import BadArgument, Context, Converter +from discord.ext.commands import BadArgument from bot.exts.filtering._filter_context import FilterContext from bot.exts.filtering._filters.filter import Filter @@ -20,20 +20,20 @@ class ListType(Enum): ALLOW = 1 -class ListTypeConverter(Converter): - """A Converter to get the appropriate list type.""" +# Alternative names with which each list type can be specified in commands. +aliases = ( + (ListType.DENY, {"deny", "blocklist", "blacklist", "denylist", "bl", "dl"}), + (ListType.ALLOW, {"allow", "allowlist", "whitelist", "al", "wl"}) +) - aliases = ( - (ListType.DENY, {"deny", "blocklist", "blacklist", "denylist", "bl", "dl"}), - (ListType.ALLOW, {"allow", "allowlist", "whitelist", "al", "wl"}) - ) - async def convert(self, ctx: Context, argument: str) -> ListType: - """Get the appropriate list type.""" - for list_type, aliases in self.aliases: - if argument in aliases or argument in map(past_tense, aliases): - return list_type - raise BadArgument(f"No matching list type found for {argument!r}.") +def list_type_converter(argument: str) -> ListType: + """A converter to get the appropriate list type.""" + argument = argument.lower() + for list_type, list_aliases in aliases: + if argument in list_aliases or argument in map(past_tense, list_aliases): + return list_type + raise BadArgument(f"No matching list type found for {argument!r}.") class FilterList(FieldRequiring): diff --git a/bot/exts/filtering/_filter_lists/invite.py b/bot/exts/filtering/_filter_lists/invite.py index 04afff0f7..cadd82d0c 100644 --- a/bot/exts/filtering/_filter_lists/invite.py +++ b/bot/exts/filtering/_filter_lists/invite.py @@ -5,7 +5,7 @@ from functools import reduce from operator import or_ from typing import Optional -from botcore.regex import DISCORD_INVITE +from botcore.utils.regex import DISCORD_INVITE from discord import Embed, Invite from discord.errors import NotFound diff --git a/bot/exts/filtering/_ui.py b/bot/exts/filtering/_ui.py index 95a840be8..efedb2c0c 100644 --- a/bot/exts/filtering/_ui.py +++ b/bot/exts/filtering/_ui.py @@ -1,10 +1,9 @@ -from copy import copy +from typing import Callable, Optional import discord import discord.ui from discord.ext.commands import Context -import bot from bot.log import get_logger log = get_logger(__name__) @@ -13,30 +12,51 @@ log = get_logger(__name__) class ArgumentCompletionSelect(discord.ui.Select): """A select detailing the options that can be picked to assign to a missing argument.""" - def __init__(self, ctx: Context, arg_name: str, options: list[str]): + def __init__( + self, + ctx: Context, + args: list, + arg_name: str, + options: list[str], + position: int, + converter: Optional[Callable] = None + ): super().__init__( placeholder=f"Select a value for {arg_name!r}", options=[discord.SelectOption(label=option) for option in options] ) self.ctx = ctx + self.args = args + self.position = position + self.converter = converter async def callback(self, interaction: discord.Interaction) -> None: """re-invoke the context command with the completed argument value.""" await interaction.response.defer() - value = interaction.data['values'][0] - message = copy(self.ctx.message) - message.content = f'{message.content} "{value}"' - log.trace(f"Argument filled with the value {value}. Invoking {message.content!r}") - await bot.instance.process_commands(message) + value = interaction.data["values"][0] + if self.converter: + value = self.converter(value) + args = self.args.copy() # This makes the view reusable. + args.insert(self.position, value) + log.trace(f"Argument filled with the value {value}. Re-invoking command") + await self.ctx.invoke(self.ctx.command, *args) class ArgumentCompletionView(discord.ui.View): """A view used to complete a missing argument in an in invoked command.""" - def __init__(self, ctx: Context, arg_name: str, options: list[str]): + def __init__( + self, + ctx: Context, + args: list, + arg_name: str, + options: list[str], + position: int, + converter: Optional[Callable] = None + ): super().__init__() log.trace(f"The {arg_name} argument was designated missing in the invocation {ctx.view.buffer!r}") - self.add_item(ArgumentCompletionSelect(ctx, arg_name, options)) + self.add_item(ArgumentCompletionSelect(ctx, args, arg_name, options, position, converter)) self.ctx = ctx async def interaction_check(self, interaction: discord.Interaction) -> bool: diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index 5eefdf4e4..2e5cca5fa 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -11,7 +11,7 @@ from discord.utils import escape_markdown from bot.bot import Bot from bot.constants import Colours, MODERATION_ROLES, Webhooks from bot.exts.filtering._filter_context import Event, FilterContext -from bot.exts.filtering._filter_lists import FilterList, ListType, ListTypeConverter, filter_list_types +from bot.exts.filtering._filter_lists import FilterList, ListType, filter_list_types, list_type_converter from bot.exts.filtering._settings import ActionSettings from bot.exts.filtering._ui import ArgumentCompletionView from bot.exts.filtering._utils import past_tense @@ -114,7 +114,7 @@ class Filtering(Cog): if list_name is None: await ctx.send( "The **list_name** argument is unspecified. Please pick a value from the options below:", - view=ArgumentCompletionView(ctx, "list_name", list(self.filter_lists)) + view=ArgumentCompletionView(ctx, [], "list_name", list(self.filter_lists), 1, None) ) return await self._send_list(ctx, list_name, ListType.DENY) @@ -134,7 +134,7 @@ class Filtering(Cog): if list_name is None: await ctx.send( "The **list_name** argument is unspecified. Please pick a value from the options below:", - view=ArgumentCompletionView(ctx, "list_name", list(self.filter_lists)) + view=ArgumentCompletionView(ctx, [], "list_name", list(self.filter_lists), 1, None) ) return await self._send_list(ctx, list_name, ListType.ALLOW) @@ -150,13 +150,13 @@ class Filtering(Cog): @filter.command(name="list", aliases=("get",)) async def f_list( - self, ctx: Context, list_type: Optional[ListTypeConverter] = None, list_name: Optional[str] = None + self, ctx: Context, list_type: Optional[list_type_converter] = None, list_name: Optional[str] = None ) -> None: """List the contents of a specified list of filters.""" if list_name is None: await ctx.send( "The **list_name** argument is unspecified. Please pick a value from the options below:", - view=ArgumentCompletionView(ctx, "list_name", list(self.filter_lists)) + view=ArgumentCompletionView(ctx, [list_type], "list_name", list(self.filter_lists), 1, None) ) return @@ -165,7 +165,9 @@ class Filtering(Cog): if len(filter_list.filter_lists) > 1: await ctx.send( "The **list_type** argument is unspecified. Please pick a value from the options below:", - view=ArgumentCompletionView(ctx, "list_type", [option.name for option in ListType]) + view=ArgumentCompletionView( + ctx, [list_name], "list_type", [option.name for option in ListType], 0, list_type_converter + ) ) return list_type = list(filter_list.filter_lists)[0] |