Source code for scripts.base.modals

"""
PokeGambler - A Pokemon themed gambling bot for Discord.
Copyright (C) 2021 Harshith Thota

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
----------------------------------------------------------------------------

Compilation of Discord UI Modals
"""

from __future__ import annotations

import inspect
from typing import (
    TYPE_CHECKING, Any, Callable,
    Dict, List, Optional, Union
)

from discord.ui import TextInput, Modal
from discord.enums import TextStyle

from .handlers import CustomInteraction

if TYPE_CHECKING:
    from discord import Interaction


[docs]class Lookup(dict): """ A dictionary which iterates over values instead of keys. """ def __iter__(self) -> List: """ Iterate over values instead of keys. """ return iter(self.values()) def __getitem__(self, key: Union[int, str]) -> Any: """ Gets the value of the key. If key is an index, it will return the value at that index. :param key: The key to get the value of. :type key: str """ if isinstance(key, int): return list(self.values())[key] return super().__getitem__(key) def __setitem__(self, key: str, value: Any) -> None: """ Sets the value of the key. Forbid integer typed keys. :param key: The key to set the value of. :type key: str :param value: The value to set. """ if isinstance(key, int): raise ValueError("Cannot use integer as key.") super().__setitem__(key, value)
[docs]class BaseModal(Modal): """ A generic modal that can be used for getting user inputs. """ def __init__(self, title, check=None, **kwargs): super().__init__(title=title, **kwargs) self.title = title self._check = check self.latest_interaction = None self._results = None
[docs] def add_short(self, text: str, **kwargs): """Adds a short text input to the modal. :param text: The text to display. :type text: str """ return self.__get_textbox( text=text, style=TextStyle.short, **kwargs )
[docs] def add_long(self, text: str, **kwargs): """Adds a long text input to the modal. :param text: The text to display. :type text: str """ return self.__get_textbox( text=text, style=TextStyle.long, **kwargs )
@property def results(self) -> Lookup[str, str]: """Returns the results of the modal. :return: The results of the modal. :rtype: :class:`~scripts.base.modals.Lookup` """ return self._results or Lookup( { item.label: item.value for item in self.children } ) @results.setter def results(self, results: Any) -> None: """ Sets the results of the modal. :param results: The results to set. :type results: Any """ self._results = results # pylint: disable=unused-argument, no-self-use
[docs] async def on_submit(self, interaction: Interaction): """ Called when the modal is submitted. """ self.latest_interaction = CustomInteraction(interaction) await interaction.response.send_message( content="\u200B", ephemeral=True )
def __get_textbox(self, text, style, **kwargs): placeholder = kwargs.pop("placeholder", "Enter a value...") self.add_item( TextInput( label=text, style=style, placeholder=placeholder, **kwargs ) ) return next( ( item for item in self.children if isinstance(item, TextInput) and item.label == text ), None )
[docs]class ContentReplyModal(BaseModal): """ A Modal which returns a content on submit. """ def __init__(self, content, **kwargs): super().__init__(**kwargs) self.content = content
[docs] async def on_submit(self, interaction: Interaction): """ Called when the modal is submitted. """ self.latest_interaction = CustomInteraction(interaction) await interaction.response.send_message( content=self.content, ephemeral=True )
[docs]class EmbedReplyModal(BaseModal): """ A Modal which returns an embed on submit. """ def __init__(self, embed, **kwargs): super().__init__(**kwargs) self.embed = embed
[docs] async def on_submit(self, interaction: Interaction): """ Called when the modal is submitted. """ self.latest_interaction = CustomInteraction(interaction) await interaction.response.send_message( embed=self.embed, ephemeral=True )
[docs]class FullReplyModal(BaseModal): """ A Modal which replies the kwargs on submit. """ def __init__(self, reply: Dict[str, Any], **kwargs): super().__init__(**kwargs) self.reply = reply
[docs] async def on_submit(self, interaction: Interaction): """ Called when the modal is submitted. """ self.latest_interaction = CustomInteraction(interaction) await interaction.response.send_message( **self.reply, ephemeral=True )
[docs]class CallbackReplyModal(BaseModal): """ A Modal which generates response from callback on submit. """ def __init__( self, callback: Optional[Callable] = None, **kwargs ): super().__init__(**kwargs) self.callback = callback
[docs] def add_callback(self, callback: Callable): """ Adds a callback to the modal. :param callback: The callback to add. :type callback: Callable """ self.callback = callback
[docs] async def on_submit(self, interaction: Interaction): """ Called when the modal is submitted. """ self.latest_interaction = CustomInteraction(interaction) if self.callback is None: return if inspect.iscoroutinefunction(self.callback): callback_res = await self.callback( self, CustomInteraction(interaction) ) else: callback_res = self.callback( self, CustomInteraction(interaction) ) if callback_res is not None: await interaction.response.send_message( **callback_res, ephemeral=True )