From 68590dfdb89050125ea29456a65c638b168fff56 Mon Sep 17 00:00:00 2001 From: Michael H Date: Sat, 25 May 2019 17:58:14 -0400 Subject: [PATCH] [Core] Improve API token converter (#2692) * improve api converter * make usage more clear --- redbot/core/commands/converter.py | 59 ++++++++++++++++++++++++++++--- redbot/core/core_commands.py | 7 ++-- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/redbot/core/commands/converter.py b/redbot/core/commands/converter.py index 308c442c9..e62a6e2c1 100644 --- a/redbot/core/commands/converter.py +++ b/redbot/core/commands/converter.py @@ -1,7 +1,9 @@ import re -from typing import TYPE_CHECKING +import functools +from typing import TYPE_CHECKING, Optional, List, Dict import discord +from discord.ext import commands as dpy_commands from . import BadArgument from ..i18n import Translator @@ -9,7 +11,7 @@ from ..i18n import Translator if TYPE_CHECKING: from .context import Context -__all__ = ["GuildConverter"] +__all__ = ["GuildConverter", "APIToken", "DictConverter", "get_dict_converter"] _ = Translator("commands.converter", __file__) @@ -47,16 +49,20 @@ class APIToken(discord.ext.commands.Converter): This will parse the input argument separating the key value pairs into a format to be used for the core bots API token storage. - This will split the argument by either `;` or `,` and return a dict + This will split the argument by either `;` ` `, or `,` and return a dict to be stored. Since all API's are different and have different naming convention, this leaves the onus on the cog creator to clearly define how to setup the correct credential names for their cogs. + + Note: Core usage of this has been replaced with DictConverter use instead. + + This may be removed at a later date (with warning) """ async def convert(self, ctx, argument) -> dict: bot = ctx.bot result = {} - match = re.split(r";|,", argument) + match = re.split(r";|,| ", argument) # provide two options to split incase for whatever reason one is part of the api key we're using if len(match) > 1: result[match[0]] = "".join(r for r in match[1:]) @@ -65,3 +71,48 @@ class APIToken(discord.ext.commands.Converter): if not result: raise BadArgument(_("The provided tokens are not in a valid format.")) return result + + +class DictConverter(dpy_commands.Converter): + """ + Converts pairs of space seperated values to a dict + """ + + def __init__(self, *expected_keys: str, delims: Optional[List[str]] = None): + self.expected_keys = expected_keys + self.delims = delims or [" "] + self.pattern = re.compile(r"|".join(re.escape(d) for d in self.delims)) + + async def convert(self, ctx: "Context", argument: str) -> Dict[str, str]: + + ret: Dict[str, str] = {} + args = self.pattern.split(argument) + + if len(args) % 2 != 0: + raise BadArgument() + + iterator = iter(args) + + for key in iterator: + if self.expected_keys and key not in self.expected_keys: + raise BadArgument(_("Unexpected key {key}").format(key)) + + ret[key] = next(iterator) + + return ret + + +def get_dict_converter(*expected_keys: str, delims: Optional[List[str]] = None) -> type: + """ + Returns a typechecking safe `DictConverter` suitable for use with discord.py + """ + + class PartialMeta(type(DictConverter)): + __call__ = functools.partialmethod( + type(DictConverter).__call__, *expected_keys, delims=delims + ) + + class ValidatedConverter(DictConverter, metaclass=PartialMeta): + pass + + return ValidatedConverter diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index 6c4869625..e30ac1aa4 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -45,6 +45,8 @@ log = logging.getLogger("red") _ = i18n.Translator("Core", __file__) +TokenConverter = commands.get_dict_converter(delims=[" ", ",", ";"]) + class CoreLogic: def __init__(self, bot: "Red"): @@ -1063,7 +1065,7 @@ class Core(commands.Cog, CoreLogic): @_set.command() @checks.is_owner() - async def api(self, ctx: commands.Context, service: str, *tokens: commands.converter.APIToken): + async def api(self, ctx: commands.Context, service: str, *, tokens: TokenConverter): """Set various external API tokens. This setting will be asked for by some 3rd party cogs and some core cogs. @@ -1076,8 +1078,7 @@ class Core(commands.Cog, CoreLogic): """ if ctx.channel.permissions_for(ctx.me).manage_messages: await ctx.message.delete() - entry = {k: v for t in tokens for k, v in t.items()} - await ctx.bot.db.api_tokens.set_raw(service, value=entry) + await ctx.bot.db.api_tokens.set_raw(service, value=tokens) await ctx.send(_("`{service}` API tokens have been set.").format(service=service)) @commands.group()