Skip to content

Commit

Permalink
config: allow emojis to be set (need more work)
Browse files Browse the repository at this point in the history
  • Loading branch information
onerandomusername committed Sep 29, 2021
1 parent 2111943 commit b5bd77a
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 6 deletions.
34 changes: 32 additions & 2 deletions modmail/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import os
import pathlib
import types
import typing
from collections import defaultdict

Expand Down Expand Up @@ -54,6 +55,30 @@
_CWD / (USER_CONFIG_FILE_NAME + ".toml"),
]


class BetterPartialEmojiConverter(discord.ext.commands.converter.EmojiConverter):
"""
Converts to a :class:`~discord.PartialEmoji`.
This is done by extracting the animated flag, name and ID from the emoji.
"""

async def convert(self, _: discord.ext.commands.context.Context, argument: str) -> discord.PartialEmoji:
# match = self._get_id_match(argument) or re.match(
# r"<a?:[a-zA-Z0-9\_]{1,32}:([0-9]{15,20})>$", argument
# )

match = discord.PartialEmoji._CUSTOM_EMOJI_RE.match(argument)
if match is not None:
groups = match.groupdict()
animated = bool(groups["animated"])
emoji_id = int(groups["id"])
name = groups["name"]
return discord.PartialEmoji(name=name, animated=animated, id=emoji_id)

return discord.PartialEmoji(name=argument)


# load env before we do *anything*
# TODO: Convert this to a function and check the parent directory too, if the CWD is within the bot.
# TODO: add the above feature to the other configuration locations too.
Expand Down Expand Up @@ -164,6 +189,9 @@ class ConfigMetadata:
# as a solution, I'm implementing a field which can provide a rich converter object,
# in the style that discord.py uses. This will be called like discord py calls.
discord_converter: discord.ext.commands.converter.Converter = attr.ib(default=None)
discord_converter_attribute: typing.Optional[
types.FunctionType
] = None # if we want an attribute off of the converted value

# hidden, eg log_level
# hidden values mean they do not show up in the bot configuration menu
Expand Down Expand Up @@ -320,7 +348,8 @@ class EmojiCfg:
metadata={
METADATA_TABLE: ConfigMetadata(
description="This is used in most cases when the bot does a successful action.",
discord_converter=discord.ext.commands.converter.EmojiConverter,
discord_converter=BetterPartialEmojiConverter,
discord_converter_attribute=lambda x: x.id or f"{x.name}",
)
},
)
Expand All @@ -330,7 +359,8 @@ class EmojiCfg:
metadata={
METADATA_TABLE: ConfigMetadata(
description="This is used in most cases when the bot fails an action.",
discord_converter=discord.ext.commands.converter.EmojiConverter,
discord_converter=BetterPartialEmojiConverter,
discord_converter_attribute=lambda x: x.id or f"{x.name}",
)
},
)
Expand Down
52 changes: 48 additions & 4 deletions modmail/extensions/configuration_manager.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import logging
import operator
import string
import types
import typing

import attr
import attr._make
import marshmallow
from discord.ext import commands
from discord.ext.commands import Context
from discord.ext.commands import converter as commands_converter

from modmail import config
from modmail.bot import ModmailBot
Expand All @@ -20,6 +22,8 @@

logger: ModmailLogger = logging.getLogger(__name__)

KeyT = str


@attr.mutable
class ConfOptions:
Expand Down Expand Up @@ -68,13 +72,13 @@ def from_field(cls, field: attr.Attribute, *, frozen: bool = False, nested: str
return cls(**kw)


def get_all_conf_options(klass: config.ClassT, *, prefix: str = None) -> typing.Dict[str, ConfOptions]:
def get_all_conf_options(klass: config.ClassT, *, prefix: str = "") -> typing.Dict[str, ConfOptions]:
"""Get a dict of ConfOptions for a designated configuration field recursively."""
options = dict()
for field in attr.fields(klass):
# make conf option list
if attr.has(field.type):
options.update(get_all_conf_options(field.type, prefix=field.name + "."))
options.update(get_all_conf_options(field.type, prefix=prefix + field.name + "."))
else:
is_frozen = klass.__setattr__ is attr._make._frozen_setattrs
try:
Expand All @@ -94,6 +98,38 @@ def get_all_conf_options(klass: config.ClassT, *, prefix: str = None) -> typing.
return options


class KeyConverter(commands.Converter):
"""Convert argument into a configuration key."""

async def convert(self, ctx: Context, arg: str) -> KeyT:
"""Ensure that a key is of the valid format, allowing a user to input other formats."""
# basically we're converting an argument to a key.
# config keys are delimited by `.`, and always lowercase, which means that we can make a few passes
# before actually trying to make any guesses.
# the problems are twofold. a: we want to suggest other keys
# and b: the interface needs to be easy to use.

# as a partial solution for this, `/`, `-`, `.` are all valid delimiters and are converted to `.`
# depending on common problems, it is *possible* to add `_` but would require fuzzy matching over
# all of the keys since that can also be a valid character name.

fields = get_all_conf_options(config.default().__class__)

new_arg = ""
for c in arg.lower():
if c in " /`":
new_arg += "."
else:
new_arg += c

if new_arg in fields:
return new_arg
else:
raise commands.BadArgument(
f"{ctx.current_parameter.name} {arg} is not a valid configuration key."
)


class ConfigurationManager(ModmailCog, name="Configuration Manager"):
"""Manage the bot configuration."""

Expand Down Expand Up @@ -136,18 +172,26 @@ async def list_config(self, ctx: Context) -> None:
await ButtonPaginator.paginate(options.values(), ctx.message)

@config_group.command(name="set", aliases=("edit",))
async def modify_config(self, ctx: Context, option: str, value: str) -> None:
async def modify_config(self, ctx: Context, option: KeyConverter, value: str) -> None:
"""Modify an existing configuration value."""
if option not in self.config_fields:
raise commands.BadArgument(f"Option must be in {', '.join(self.config_fields.keys())}")
meta = self.config_fields[option]

if meta.frozen:
# TODO: replace with responses module.
await ctx.send("Can't modify this value.")
return

if meta.modmail_metadata.discord_converter is not None:
value = await meta.modmail_metadata.discord_converter().convert(ctx, value)
if meta.modmail_metadata.discord_converter_attribute is not None:
if isinstance(meta.modmail_metadata.discord_converter_attribute, types.FunctionType):
value = meta.modmail_metadata.discord_converter_attribute(value)
if meta._type in commands_converter.CONVERTER_MAPPING:
value = await commands_converter.CONVERTER_MAPPING[meta._type]().convert(ctx, value)
if isinstance(meta.modmail_metadata.discord_converter_attribute, types.FunctionType):
value = meta.modmail_metadata.discord_converter_attribute(value)
elif meta._field.converter:
value = meta._field.converter(value)

Expand All @@ -162,7 +206,7 @@ async def modify_config(self, ctx: Context, option: str, value: str) -> None:
await ctx.message.reply("ok.")

@config_group.command(name="get", aliases=("show",))
async def get_config(self, ctx: Context, option: str) -> None:
async def get_config(self, ctx: Context, option: KeyConverter) -> None:
"""Modify an existing configuration value."""
if option not in self.config_fields:
raise commands.BadArgument(f"Option must be in {', '.join(self.config_fields.keys())}")
Expand Down

0 comments on commit b5bd77a

Please sign in to comment.