aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar mbaruh <[email protected]>2022-10-09 00:45:15 +0300
committerGravatar mbaruh <[email protected]>2022-10-09 00:45:15 +0300
commit67bb6b8e36b7202c83094be529f71aab0c0d9f36 (patch)
tree6de133dd43e6fa7151794584bd16442c39ea69fb
parentIgnore overrides that are equal to their defaults (diff)
Add filter template option
An option is added to filter add and edit to copy the overrides over from another filter.
-rw-r--r--bot/exts/filtering/_ui.py88
-rw-r--r--bot/exts/filtering/filtering.py38
2 files changed, 100 insertions, 26 deletions
diff --git a/bot/exts/filtering/_ui.py b/bot/exts/filtering/_ui.py
index 8bfcded77..a6bc1addd 100644
--- a/bot/exts/filtering/_ui.py
+++ b/bot/exts/filtering/_ui.py
@@ -383,6 +383,21 @@ class EnumSelectView(discord.ui.View):
self.add_item(self.EnumSelect(setting_name, enum_cls, update_callback))
+class TemplateModal(discord.ui.Modal, title="Template"):
+ """A modal to enter a filter ID to copy its overrides over."""
+
+ template = discord.ui.TextInput(label="Template Filter ID")
+
+ def __init__(self, embed_view: SettingsEditView, message: discord.Message):
+ super().__init__(timeout=COMPONENT_TIMEOUT)
+ self.embed_view = embed_view
+ self.message = message
+
+ async def on_submit(self, interaction: Interaction) -> None:
+ """Update the embed with the new description."""
+ await self.embed_view.apply_template(self.template.value, self.message, interaction)
+
+
class SettingsEditView(discord.ui.View):
"""A view used to edit a filter's settings before updating the database."""
@@ -470,6 +485,12 @@ class SettingsEditView(discord.ui.View):
"""A button to empty the filter's description."""
await self.update_embed(interaction, description=self._REMOVE)
+ @discord.ui.button(label="Template", row=3)
+ async def enter_template(self, interaction: Interaction, button: discord.ui.Button) -> None:
+ """A button to enter a filter template ID and copy its overrides over."""
+ modal = TemplateModal(self, interaction.message)
+ await interaction.response.send_modal(modal)
+
@discord.ui.button(label="✅ Confirm", style=discord.ButtonStyle.green, row=4)
async def confirm(self, interaction: Interaction, button: discord.ui.Button) -> None:
"""Confirm the content, description, and settings, and update the filters database."""
@@ -599,7 +620,7 @@ class SettingsEditView(discord.ui.View):
await interaction_or_msg.response.edit_message(embed=self.embed, view=new_view)
else:
await interaction_or_msg.edit(embed=self.embed, view=new_view)
- except discord.errors.HTTPException: # Various error such as embed description being too long.
+ except discord.errors.HTTPException: # Various errors such as embed description being too long.
pass
else:
self.stop()
@@ -612,6 +633,21 @@ class SettingsEditView(discord.ui.View):
"""
await self.update_embed(interaction, setting_name=setting_name, setting_value=override_value)
+ async def apply_template(self, template_id: str, embed_message: discord.Message, interaction: Interaction) -> None:
+ """Replace any non-overridden settings with overrides from the given filter."""
+ try:
+ settings, filter_settings = template_settings(template_id, self.filter_list, self.list_type)
+ except ValueError as e: # The interaction is necessary to send an ephemeral message.
+ await interaction.response.send_message(f":x: {e}", ephemeral=True)
+ return
+ else:
+ await interaction.response.defer()
+
+ self.settings_overrides = settings | self.settings_overrides
+ self.filter_settings_overrides = filter_settings | self.filter_settings_overrides
+ self.embed.clear_fields()
+ await embed_message.edit(embed=self.embed, view=self.copy())
+
async def _remove_override(self, interaction: Interaction, select: discord.ui.Select) -> None:
"""
Remove the override for the setting the user selected, and edit the embed.
@@ -690,6 +726,9 @@ def description_and_settings_converter(
description, *parsed = parsed
settings = {setting: value for setting, value in [part.split("=", maxsplit=1) for part in parsed]}
+ template = None
+ if "--template" in settings:
+ template = settings.pop("--template")
filter_settings = {}
for setting, _ in list(settings.items()):
@@ -723,9 +762,56 @@ def description_and_settings_converter(
except (TypeError, ValueError) as e:
raise BadArgument(e)
+ # Pull templates settings and apply them.
+ if template is not None:
+ try:
+ t_settings, t_filter_settings = template_settings(template, filter_list, list_type)
+ except ValueError as e:
+ raise BadArgument(str(e))
+ else:
+ # The specified settings go on top of the template
+ settings = t_settings | settings
+ filter_settings = t_filter_settings | filter_settings
+
return description, settings, filter_settings
+def filter_overrides(filter_: Filter, filter_list: FilterList, list_type: ListType) -> tuple[dict, dict]:
+ """Get the filter's overrides to the filter list settings and the extra fields settings."""
+ overrides_values = {}
+ for settings in (filter_.actions, filter_.validations):
+ if settings:
+ for _, setting in settings.items():
+ for setting_name, value in to_serializable(setting.dict()).items():
+ if not repr_equals(value, filter_list.default(list_type, setting_name)):
+ overrides_values[setting_name] = value
+
+ if filter_.extra_fields_type:
+ # The values here can be safely used since overrides equal to the defaults won't be saved.
+ extra_fields_overrides = filter_.extra_fields.dict(exclude_unset=True)
+ else:
+ extra_fields_overrides = {}
+
+ return overrides_values, extra_fields_overrides
+
+
+def template_settings(filter_id: str, filter_list: FilterList, list_type: ListType) -> tuple[dict, dict]:
+ """Find the filter with specified ID, and return its settings."""
+ try:
+ filter_id = int(filter_id)
+ if filter_id < 0:
+ raise ValueError()
+ except ValueError:
+ raise ValueError("Template value must be a non-negative integer.")
+
+ if filter_id not in filter_list.filter_lists[list_type]:
+ raise ValueError(
+ f"Could not find filter with ID `{filter_id}` in the {list_type.name} {filter_list.name} list."
+ )
+ filter_ = filter_list.filter_lists[list_type][filter_id]
+ return filter_overrides(filter_, filter_list, list_type)
+
+
def format_response_error(e: ResponseCodeError) -> Embed:
"""Format the response error into an embed."""
description = ""
diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py
index aa90d1600..427735add 100644
--- a/bot/exts/filtering/filtering.py
+++ b/bot/exts/filtering/filtering.py
@@ -19,9 +19,10 @@ from bot.exts.filtering._filter_lists import FilterList, ListType, filter_list_t
from bot.exts.filtering._filters.filter import Filter
from bot.exts.filtering._settings import ActionSettings
from bot.exts.filtering._ui import (
- ArgumentCompletionView, build_filter_repr_dict, description_and_settings_converter, populate_embed_from_dict
+ ArgumentCompletionView, build_filter_repr_dict, description_and_settings_converter, filter_overrides,
+ populate_embed_from_dict
)
-from bot.exts.filtering._utils import past_tense, repr_equals, to_serializable
+from bot.exts.filtering._utils import past_tense, to_serializable
from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils.messages import format_channel, format_user
@@ -269,7 +270,7 @@ class Filtering(Cog):
return
filter_, filter_list, list_type = result
- overrides_values, extra_fields_overrides = self._filter_overrides(filter_, filter_list, list_type)
+ overrides_values, extra_fields_overrides = filter_overrides(filter_, filter_list, list_type)
all_settings_repr_dict = build_filter_repr_dict(
filter_list, list_type, type(filter_), overrides_values, extra_fields_overrides
@@ -337,7 +338,10 @@ class Filtering(Cog):
The settings can be provided in the command itself, in the format of `setting_name=value` (no spaces around the
equal sign). The value doesn't need to (shouldn't) be surrounded in quotes even if it contains spaces.
- Example: `!filter add denied token "Scaleios is great" delete_messages=True send_alert=False`
+ A template filter can be specified in the settings area to copy overrides from. The setting name is "--template"
+ and the value is the filter ID. The template will be used before applying any other override.
+
+ Example: `!filter add denied token "Scaleios is great" delete_messages=True send_alert=False --template=100`
"""
result = await self._resolve_list_type_and_name(ctx, list_type, list_name)
if result is None:
@@ -363,6 +367,9 @@ class Filtering(Cog):
The settings can be provided in the command itself, in the format of `setting_name=value` (no spaces around the
equal sign). The value doesn't need to (shouldn't) be surrounded in quotes even if it contains spaces.
+ A template filter can be specified in the settings area to copy overrides from. The setting name is "--template"
+ and the value is the filter ID. The template will be used before applying any other override.
+
To edit the filter's content, use the UI.
"""
result = self._get_filter_by_id(filter_id)
@@ -371,7 +378,7 @@ class Filtering(Cog):
return
filter_, filter_list, list_type = result
filter_type = type(filter_)
- settings, filter_settings = self._filter_overrides(filter_, filter_list, list_type)
+ settings, filter_settings = filter_overrides(filter_, filter_list, list_type)
description, new_settings, new_filter_settings = description_and_settings_converter(
filter_list,
list_type, filter_type,
@@ -623,25 +630,6 @@ class Filtering(Cog):
if id_ in sublist:
return sublist[id_], filter_list, list_type
- @staticmethod
- def _filter_overrides(filter_: Filter, filter_list: FilterList, list_type: ListType) -> tuple[dict, dict]:
- """Get the filter's overrides to the filter list settings and the extra fields settings."""
- overrides_values = {}
- for settings in (filter_.actions, filter_.validations):
- if settings:
- for _, setting in settings.items():
- for setting_name, value in to_serializable(setting.dict()).items():
- if not repr_equals(value, filter_list.default(list_type, setting_name)):
- overrides_values[setting_name] = value
-
- if filter_.extra_fields_type:
- # The values here can be safely used since overrides equal to the defaults won't be saved.
- extra_fields_overrides = filter_.extra_fields.dict(exclude_unset=True)
- else:
- extra_fields_overrides = {}
-
- return overrides_values, extra_fields_overrides
-
async def _add_filter(
self,
ctx: Context,
@@ -736,7 +724,7 @@ class Filtering(Cog):
"filter_list": list_id, "content": content, "description": description,
"additional_field": json.dumps(filter_settings), **settings
}
- response = await bot.instance.api_client.post('bot/filter/filters', json=payload)
+ response = await bot.instance.api_client.post('bot/filter/filters', json=to_serializable(payload))
new_filter = filter_list.add_filter(response, list_type)
extra_msg = Filtering._identical_filters_message(content, filter_list, list_type, new_filter)
await msg.reply(f"✅ Added filter: {new_filter}" + extra_msg)