mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -05:00
Add support for set api Modals (#5637)
* Add support for set api Modals Co-authored-by: TrustyJAID <TrustyJAID@gmail.com> * Blaacckkkk! * Swap locations of interaction and button. * Clarified template tokens * Update docs and some string * More docs * Rework the client Co-authored-by: TrustyJAID <TrustyJAID@gmail.com> * Goddamned black! * Missed a few arguments * Black... Again * Update redbot/core/utils/views.py Co-authored-by: TrustyJAID <TrustyJAID@gmail.com> * Update redbot/core/core_commands.py Co-authored-by: TrustyJAID <TrustyJAID@gmail.com> Co-authored-by: TrustyJAID <TrustyJAID@gmail.com>
This commit is contained in:
parent
ec55622418
commit
acdc1df084
@ -73,3 +73,9 @@ Common Filters
|
||||
|
||||
.. automodule:: redbot.core.utils.common_filters
|
||||
:members:
|
||||
|
||||
Utility UI
|
||||
==========
|
||||
|
||||
.. automodule:: redbot.core.utils.views
|
||||
:members:
|
||||
|
||||
@ -254,13 +254,13 @@ else:
|
||||
args = self.pattern.split(argument)
|
||||
|
||||
if len(args) % 2 != 0:
|
||||
raise BadArgument()
|
||||
raise BadArgument(_("Missing a key or value."))
|
||||
|
||||
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=key))
|
||||
raise BadArgument(_("Unexpected key `{key}`.").format(key=key))
|
||||
|
||||
ret[key] = next(iterator)
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ import traceback
|
||||
from pathlib import Path
|
||||
from redbot.core import data_manager
|
||||
from redbot.core.utils.menus import menu
|
||||
from redbot.core.utils.views import SetApiView
|
||||
from redbot.core.commands import GuildConverter, RawUserIdConverter
|
||||
from string import ascii_letters, digits
|
||||
from typing import TYPE_CHECKING, Union, Tuple, List, Optional, Iterable, Sequence, Dict, Set
|
||||
@ -3091,18 +3092,28 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
|
||||
|
||||
@_set.group(name="api", invoke_without_command=True)
|
||||
@checks.is_owner()
|
||||
async def _set_api(self, ctx: commands.Context, service: str, *, tokens: TokenConverter):
|
||||
async def _set_api(
|
||||
self,
|
||||
ctx: commands.Context,
|
||||
service: Optional[str] = None,
|
||||
*,
|
||||
tokens: Optional[TokenConverter] = None,
|
||||
):
|
||||
"""
|
||||
Commands to set, list or remove various external API tokens.
|
||||
|
||||
This setting will be asked for by some 3rd party cogs and some core cogs.
|
||||
|
||||
If passed without the `<service>` or `<tokens>` arguments it will allow you to open a modal to set your API keys securely.
|
||||
|
||||
To add the keys provide the service name and the tokens as a comma separated
|
||||
list of key,values as described by the cog requesting this command.
|
||||
|
||||
Note: API tokens are sensitive, so this command should only be used in a private channel or in DM with the bot.
|
||||
|
||||
**Examples:**
|
||||
- `[p]set api`
|
||||
- `[p]set api spotify
|
||||
- `[p]set api spotify redirect_uri localhost`
|
||||
- `[p]set api github client_id,whoops client_secret,whoops`
|
||||
|
||||
@ -3110,6 +3121,14 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
|
||||
- `<service>` - The service you're adding tokens to.
|
||||
- `<tokens>` - Pairs of token keys and values. The key and value should be separated by one of ` `, `,`, or `;`.
|
||||
"""
|
||||
if service is None: # Handled in order of missing operations
|
||||
await ctx.send(_("Click the button below to set your keys."), view=SetApiView())
|
||||
elif tokens is None:
|
||||
await ctx.send(
|
||||
_("Click the button below to set your keys."),
|
||||
view=SetApiView(default_service=service),
|
||||
)
|
||||
else:
|
||||
if ctx.channel.permissions_for(ctx.me).manage_messages:
|
||||
await ctx.message.delete()
|
||||
await ctx.bot.set_shared_api_tokens(service, **tokens)
|
||||
|
||||
176
redbot/core/utils/views.py
Normal file
176
redbot/core/utils/views.py
Normal file
@ -0,0 +1,176 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import discord
|
||||
|
||||
from discord.ext.commands import BadArgument
|
||||
from typing import List, Dict, Union, Optional
|
||||
from redbot.core.commands.converter import get_dict_converter
|
||||
from redbot.core.i18n import Translator
|
||||
|
||||
_ = Translator("UtilsViews", __file__)
|
||||
|
||||
|
||||
class SetApiModal(discord.ui.Modal):
|
||||
"""
|
||||
A secure ``discord.ui.Modal`` used to set API keys.
|
||||
|
||||
This Modal can either be used standalone with its own ``discord.ui.View``
|
||||
for custom implementations, or created via ``SetApiView``
|
||||
to have an easy to implemement secure way of setting API keys.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
default_service: Optional[str]
|
||||
The service to add the API keys to.
|
||||
If this is omitted the bot owner is allowed to set his own service.
|
||||
Defaults to ``None``.
|
||||
default_keys: Optional[Dict[str, str]]
|
||||
The API keys the service is expecting.
|
||||
This will only allow the bot owner to set keys the Modal is expecting.
|
||||
Defaults to ``None``.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
default_service: Optional[str] = None,
|
||||
default_keys: Optional[Dict[str, str]] = None,
|
||||
):
|
||||
self.default_service = default_service
|
||||
self.default_keys: List[str] = []
|
||||
if default_keys is not None:
|
||||
self.default_keys = list(default_keys.keys())
|
||||
self.default_keys_fmt = self._format_keys(default_keys)
|
||||
|
||||
_placeholder_service = "service"
|
||||
if self.default_service is not None:
|
||||
_placeholder_service = self.default_service
|
||||
_placeholder_token = "client_id YOUR_CLIENT_ID\nclient_secret YOUR_CLIENT_SECRET"
|
||||
if self.default_keys_fmt is not None:
|
||||
_placeholder_token = self.default_keys_fmt
|
||||
|
||||
self.title = _("Set API Keys")
|
||||
self.keys_label = _("Keys and tokens")
|
||||
if self.default_service is not None:
|
||||
self.title = _("Set API Keys for {service}").format(service=self.default_service)
|
||||
self.keys_label = _("Keys and tokens for {service}").format(
|
||||
service=self.default_service
|
||||
)
|
||||
self.default_service = self.default_service.lower()
|
||||
# Lower here to prevent someone from capitalizing a service name for the sake of UX.
|
||||
|
||||
super().__init__(title=self.title)
|
||||
|
||||
self.service_input = discord.ui.TextInput(
|
||||
label=_("Service"),
|
||||
required=True,
|
||||
placeholder=_placeholder_service,
|
||||
default=self.default_service,
|
||||
)
|
||||
|
||||
self.token_input = discord.ui.TextInput(
|
||||
label=self.keys_label,
|
||||
style=discord.TextStyle.long,
|
||||
required=True,
|
||||
placeholder=_placeholder_token,
|
||||
default=self.default_keys_fmt,
|
||||
)
|
||||
|
||||
if self.default_service is None:
|
||||
self.add_item(self.service_input)
|
||||
self.add_item(self.token_input)
|
||||
|
||||
@staticmethod
|
||||
def _format_keys(keys: Optional[Dict[str, str]]) -> Optional[str]:
|
||||
"""Format the keys to be used on a long discord.TextInput format"""
|
||||
if keys is not None:
|
||||
ret = ""
|
||||
for k, v in keys.items():
|
||||
if v:
|
||||
ret += f"{k} {v}\n"
|
||||
else:
|
||||
ret += f"{k} YOUR_{k.upper()}\n"
|
||||
return ret
|
||||
else:
|
||||
return None
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
if not await interaction.client.is_owner(
|
||||
interaction.user
|
||||
): # Prevent non-bot owners from somehow aquiring and saving the modal.
|
||||
return await interaction.response.send_message(
|
||||
_("This modal is for bot owners only. Whoops!"), ephemeral=True
|
||||
)
|
||||
|
||||
if self.default_keys is not None:
|
||||
converter = get_dict_converter(*self.default_keys, delims=[";", ",", " "])
|
||||
else:
|
||||
converter = get_dict_converter(delims=[";", ",", " "])
|
||||
tokens = " ".join(self.token_input.value.split("\n")).rstrip()
|
||||
|
||||
try:
|
||||
tokens = await converter().convert(None, tokens)
|
||||
except BadArgument as exc:
|
||||
return await interaction.response.send_message(
|
||||
_("{error_message}\nPlease try again.").format(error_message=str(exc)),
|
||||
ephemeral=True,
|
||||
)
|
||||
|
||||
if self.default_service is not None: # Check is there is a service set.
|
||||
await interaction.client.set_shared_api_tokens(self.default_service, **tokens)
|
||||
return await interaction.response.send_message(
|
||||
_("`{service}` API tokens have been set.").format(service=self.default_service),
|
||||
ephemeral=True,
|
||||
)
|
||||
else:
|
||||
service = self.service_input.value.lower()
|
||||
await interaction.client.set_shared_api_tokens(service, **tokens)
|
||||
return await interaction.response.send_message(
|
||||
_("`{service}` API tokens have been set.").format(service=service),
|
||||
ephemeral=True,
|
||||
)
|
||||
|
||||
|
||||
class SetApiView(discord.ui.View):
|
||||
"""
|
||||
A secure ``discord.ui.View`` used to set API keys.
|
||||
|
||||
This view is an standalone, easy to implement ``discord.ui.View``
|
||||
to allow an bot owner to securely set API keys in a public environment.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
default_service: Optional[str]
|
||||
The service to add the API keys to.
|
||||
If this is omitted the bot owner is allowed to set his own service.
|
||||
Defaults to ``None``.
|
||||
default_keys: Optional[Dict[str, str]]
|
||||
The API keys the service is expecting.
|
||||
This will only allow the bot owner to set keys the Modal is expecting.
|
||||
Defaults to ``None``.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
default_service: Optional[str] = None,
|
||||
default_keys: Optional[Dict[str, str]] = None,
|
||||
):
|
||||
self.default_service = default_service
|
||||
self.default_keys = default_keys
|
||||
super().__init__()
|
||||
|
||||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||
if not await interaction.client.is_owner(interaction.user):
|
||||
await interaction.response.send_message(
|
||||
_("This button is for bot owners only, oh well."), ephemeral=True
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
@discord.ui.button(
|
||||
label=_("Set API token"),
|
||||
style=discord.ButtonStyle.grey,
|
||||
)
|
||||
async def auth_button(self, interaction: discord.Interaction, button: discord.Button):
|
||||
return await interaction.response.send_modal(
|
||||
SetApiModal(self.default_service, self.default_keys)
|
||||
)
|
||||
Loading…
x
Reference in New Issue
Block a user