Kowlin acdc1df084
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>
2022-05-20 13:58:18 -06:00

177 lines
6.5 KiB
Python

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)
)