aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar mbaruh <[email protected]>2022-02-27 00:06:56 +0200
committerGravatar mbaruh <[email protected]>2022-07-16 02:11:29 +0300
commit08d06555d28a9588d9d8c814655df9c55c0c5563 (patch)
tree870689da7b4ef281a1a4729a9f78a3ea3a8c92e7
parentAdd 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__.py4
-rw-r--r--bot/exts/filtering/_filter_lists/filter_list.py26
-rw-r--r--bot/exts/filtering/_filter_lists/invite.py2
-rw-r--r--bot/exts/filtering/_ui.py40
-rw-r--r--bot/exts/filtering/filtering.py14
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]