[Core] add [p]set api list and [p]set api remove (#4370)

* add get_shared_api_keys

* add set listapi command

* fix typo

* refactor `[p]set listapi` into `[p]set api list`

* remove unnecessary check

* fix decorators

* corrected wording

* add remove_shared_api_services

* add `set api remove`

* further typo sniping

* bugsniping

* minor typo

* aaaaaaaaa

* Apply suggestions from code review

Co-authored-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* update api docstring to reflect new subcommands

* Apply suggestions from code review

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>

* update framework_apikeys.rst with new methods

* Update redbot/core/bot.py

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>

* rewrite get_shared_api_services into a special case of
get_shared_api_tokens

* rewrite api_list to include keys and tokens in response

* don't show secrets

* better api_remove response

* black

* remove nonexistent method

* remove unnecessary import

* fix wording

* Improve docstrings and type hints

- added overloads to help out developers a bit more
  * this wasn't necessary, but development tools work better
    with this information
- fixed type hint to use Union as now
  its return type depends on whether `service_name` is passed
- updated docstrings to contain information about the added behavior

* Use `humanize_list()` rather than `str.join()`

This is done for consistency within Red
and it makes the list have the last element joined
with `and` (or its equivalent in chosen locale)

* Use `.format()` after translation is applied

If `.format()` is used before `_()`, the search for translation is done
with the string that already has the dynamic text added,
which results in no matches.

* Add plural support

* Improve error message

Updated message to be more specific
(used phrases: "None of the services" and "had any keys set")
and a bit more nice to the user (judging word "anyway" removed)

* Improve readability of `[p]set api list`

It's a lot clearer when the sublists are indented,
especially on mobile where code blocks aren't colored at all.

New look:
https://user-images.githubusercontent.com/6032823/94614944-3bbd7c80-02a7-11eb-89e4-64bdf06c650b.png

Co-authored-by: Draper <27962761+Drapersniper@users.noreply.github.com>
Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
This commit is contained in:
Vuks 2020-10-05 22:10:14 +02:00 committed by GitHub
parent 8b477db0a6
commit b45e62f354
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 8 deletions

View File

@ -73,3 +73,5 @@ Additional References
.. automethod:: Red.set_shared_api_tokens .. automethod:: Red.set_shared_api_tokens
.. automethod:: Red.remove_shared_api_tokens .. automethod:: Red.remove_shared_api_tokens
.. automethod:: Red.remove_shared_api_services

View File

@ -26,6 +26,7 @@ from typing import (
Any, Any,
Literal, Literal,
MutableMapping, MutableMapping,
overload,
) )
from types import MappingProxyType from types import MappingProxyType
@ -986,21 +987,36 @@ class RedBase(
""" """
return await self._config.guild(discord.Object(id=guild_id)).mod_role() return await self._config.guild(discord.Object(id=guild_id)).mod_role()
async def get_shared_api_tokens(self, service_name: str) -> Dict[str, str]: @overload
async def get_shared_api_tokens(self, service_name: str = ...) -> Dict[str, str]:
...
@overload
async def get_shared_api_tokens(self, service_name: None = ...) -> Dict[str, Dict[str, str]]:
...
async def get_shared_api_tokens(
self, service_name: Optional[str] = None
) -> Union[Dict[str, Dict[str, str]], Dict[str, str]]:
""" """
Gets the shared API tokens for a service Gets the shared API tokens for a service, or all of them if no argument specified.
Parameters Parameters
---------- ----------
service_name: str service_name: str, optional
The service to get tokens for. The service to get tokens for. Leave empty to get tokens for all services.
Returns Returns
------- -------
Dict[str, str] Dict[str, Dict[str, str]] or Dict[str, str]
A Mapping of token names to tokens. A Mapping of token names to tokens.
This mapping exists because some services have multiple tokens. This mapping exists because some services have multiple tokens.
If ``service_name`` is `None`, this method will return
a mapping with mappings for all services.
""" """
if service_name is None:
return await self._config.custom(SHARED_API_TOKENS).all()
else:
return await self._config.custom(SHARED_API_TOKENS, service_name).all() return await self._config.custom(SHARED_API_TOKENS, service_name).all()
async def set_shared_api_tokens(self, service_name: str, **tokens: str): async def set_shared_api_tokens(self, service_name: str, **tokens: str):
@ -1051,6 +1067,25 @@ class RedBase(
for name in token_names: for name in token_names:
group.pop(name, None) group.pop(name, None)
async def remove_shared_api_services(self, *service_names: str):
"""
Removes shared API services, as well as keys and tokens associated with them.
Parameters
----------
*service_names: str
The services to remove.
Examples
----------
Removing the youtube service
>>> await ctx.bot.remove_shared_api_services("youtube")
"""
async with self._config.custom(SHARED_API_TOKENS).all() as group:
for service in service_names:
group.pop(service, None)
async def get_context(self, message, *, cls=commands.Context): async def get_context(self, message, *, cls=commands.Context):
return await super().get_context(message, cls=cls) return await super().get_context(message, cls=cls)

View File

@ -23,6 +23,7 @@ import aiohttp
import discord import discord
from babel import Locale as BabelLocale, UnknownLocaleError from babel import Locale as BabelLocale, UnknownLocaleError
from redbot.core.data_manager import storage_type from redbot.core.data_manager import storage_type
from redbot.core.utils.chat_formatting import box, pagify
from . import ( from . import (
__version__, __version__,
@ -2071,10 +2072,10 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
else: else:
await ctx.send(_("Text must be fewer than 1024 characters long.")) await ctx.send(_("Text must be fewer than 1024 characters long."))
@_set.command() @_set.group(invoke_without_command=True)
@checks.is_owner() @checks.is_owner()
async def api(self, ctx: commands.Context, service: str, *, tokens: TokenConverter): async def api(self, ctx: commands.Context, service: str, *, tokens: TokenConverter):
"""Set various external API tokens. """Set, list or remove various external API tokens.
This setting will be asked for by some 3rd party cogs and some core cogs. This setting will be asked for by some 3rd party cogs and some core cogs.
@ -2089,6 +2090,47 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
await ctx.bot.set_shared_api_tokens(service, **tokens) await ctx.bot.set_shared_api_tokens(service, **tokens)
await ctx.send(_("`{service}` API tokens have been set.").format(service=service)) await ctx.send(_("`{service}` API tokens have been set.").format(service=service))
@api.command(name="list")
async def api_list(self, ctx: commands.Context):
"""Show all external API services along with their keys that have been set.
Secrets are not shown."""
services: dict = await ctx.bot.get_shared_api_tokens()
if not services:
await ctx.send(_("No API services have been set yet."))
return
sorted_services = sorted(services.keys(), key=str.lower)
joined = _("Set API services:\n") if len(services) > 1 else _("Set API service:\n")
for service_name in sorted_services:
joined += "+ {}\n".format(service_name)
for key_name in services[service_name].keys():
joined += " - {}\n".format(key_name)
for page in pagify(joined, ["\n"], shorten_by=16):
await ctx.send(box(page.lstrip(" "), lang="diff"))
@api.command(name="remove")
async def api_remove(self, ctx: commands.Context, *services: str):
"""Remove the given services with all their keys and tokens."""
bot_services = (await ctx.bot.get_shared_api_tokens()).keys()
services = [s for s in services if s in bot_services]
if services:
await self.bot.remove_shared_api_services(*services)
if len(services) > 1:
msg = _("Services deleted successfully:\n{services_list}").format(
services_list=humanize_list(services)
)
else:
msg = _("Service deleted successfully: {service_name}").format(
service_name=services[0]
)
await ctx.send(msg)
else:
await ctx.send(_("None of the services you provided had any keys set."))
@commands.group() @commands.group()
@checks.is_owner() @checks.is_owner()
async def helpset(self, ctx: commands.Context): async def helpset(self, ctx: commands.Context):