Source code for miru.button
from __future__ import annotations
import inspect
import typing as t
import hikari
from .abc.item import DecoratedItem, ViewItem
if t.TYPE_CHECKING:
from .context import ViewContext
from .view import View
ViewT = t.TypeVar("ViewT", bound="View")
ViewContextT = t.TypeVar("ViewContextT", bound="ViewContext")
__all__ = ("Button", "button")
[docs]
class Button(ViewItem):
"""A view component representing a button.
Parameters
----------
style : hikari.ButtonStyle, optional
The button's style, by default hikari.ButtonStyle.PRIMARY
label : Optional[str], optional
The button's label, by default None
disabled : bool, optional
A boolean determining if the button should be disabled or not, by default False
custom_id : Optional[str], optional
The custom identifier of the button, by default None
url : Optional[str], optional
The URL of the button, by default None
emoji : Union[hikari.Emoji, str, None], optional
The emoji present on the button, by default None
row : Optional[int], optional
The row the button should be in, leave as None for auto-placement.
position : Optional[int], optional
The position the button should be in within a row, leave as None for auto-placement.
Raises
------
TypeError
If both label and emoji are left empty.
TypeError
if both custom_id and url are provided.
"""
def __init__(
self,
*,
style: hikari.ButtonStyle = hikari.ButtonStyle.PRIMARY,
label: t.Optional[str] = None,
disabled: bool = False,
custom_id: t.Optional[str] = None,
url: t.Optional[str] = None,
emoji: t.Union[hikari.Emoji, str, None] = None,
row: t.Optional[int] = None,
position: t.Optional[int] = None,
) -> None:
super().__init__(custom_id=custom_id, row=row, position=position, disabled=disabled)
self._emoji: t.Optional[hikari.Emoji] = hikari.Emoji.parse(emoji) if isinstance(emoji, str) else emoji
self.label = label
self.url = self._url = url
self.style = self._style = style if self._url is None else hikari.ButtonStyle.LINK
if self._is_persistent and self.url:
raise TypeError("Cannot provide both 'url' and 'custom_id'.")
@property
def type(self) -> hikari.ComponentType:
return hikari.ComponentType.BUTTON
@property
def style(self) -> hikari.ButtonStyle:
"""The button's style."""
return self._style
@style.setter
def style(self, value: hikari.ButtonStyle) -> None:
if not isinstance(value, hikari.ButtonStyle):
raise TypeError("Expected type 'hikari.ButtonStyle' or 'int' for property 'style'.")
if self._url is not None and value != hikari.ButtonStyle.LINK:
raise ValueError("A link button cannot have it's style changed. Set 'url' to 'None' to change the style.")
self._style = value
@property
def label(self) -> t.Optional[str]:
"""The button's label. This is the text visible on the button."""
return self._label
@label.setter
def label(self, value: t.Optional[str]) -> None:
if value is not None and len(value) > 80:
raise ValueError(f"Parameter 'label' must be 80 or fewer in length. (Found {len(value)})")
self._label = str(value) if value else None
@property
def emoji(self) -> t.Optional[hikari.Emoji]:
"""The emoji that should be visible on the button."""
return self._emoji
@emoji.setter
def emoji(self, value: t.Union[str, hikari.Emoji, None]) -> None:
if value and isinstance(value, str):
value = hikari.Emoji.parse(value)
self._emoji = value # type: ignore [assignment]
@property
def url(self) -> t.Optional[str]:
"""The button's URL. If specified, the button will turn into a link button,
and the style parameter will be ignored.
"""
return self._url
@url.setter
def url(self, value: t.Optional[str]) -> None:
if value and not isinstance(value, str):
raise TypeError("Expected type 'str' for property 'url'.")
if value:
self._style = hikari.ButtonStyle.LINK
self._url = value
@classmethod
def _from_component(cls, component: hikari.PartialComponent, row: t.Optional[int] = None) -> Button:
assert isinstance(component, hikari.ButtonComponent)
return cls(
style=hikari.ButtonStyle(component.style),
label=component.label,
disabled=component.is_disabled,
custom_id=component.custom_id,
url=component.url,
emoji=component.emoji,
row=row,
)
def _build(self, action_row: hikari.api.MessageActionRowBuilder) -> None:
if self.emoji is None and self.label is None:
raise TypeError("Must provide at least one of 'emoji' or 'label' when building Button.")
if self.url is not None:
action_row.add_link_button(
self.url, emoji=self.emoji or hikari.UNDEFINED, label=self.label or hikari.UNDEFINED
)
else:
action_row.add_interactive_button(
self.style if self.style is not hikari.ButtonStyle.LINK else hikari.ButtonStyle.PRIMARY,
self.custom_id,
emoji=self.emoji or hikari.UNDEFINED,
label=self.label or hikari.UNDEFINED,
is_disabled=self.disabled,
)
[docs]
def button(
*,
label: t.Optional[str] = None,
custom_id: t.Optional[str] = None,
style: hikari.ButtonStyle = hikari.ButtonStyle.PRIMARY,
emoji: t.Optional[t.Union[str, hikari.Emoji]] = None,
row: t.Optional[int] = None,
disabled: bool = False,
) -> t.Callable[
[t.Callable[[ViewT, Button, ViewContextT], t.Awaitable[None]]], DecoratedItem[ViewT, Button, ViewContextT]
]:
"""A decorator to transform a coroutine function into a Discord UI Button's callback.
This must be inside a subclass of View.
Parameters
----------
label : Optional[str], optional
The button's label, by default None
custom_id : Optional[str], optional
The button's custom identifier, by default None
style : hikari.ButtonStyle, optional
The style of the button, by default hikari.ButtonStyle.PRIMARY
emoji : Optional[Union[str, hikari.Emoji]], optional
The emoji shown on the button, by default None
row : Optional[int], optional
The row the button should be in, leave as None for auto-placement.
disabled : bool, optional
A boolean determining if the button should be disabled or not, by default False
Returns
-------
Callable[[Callable[[ViewT, Button, ViewContextT], Awaitable[None]]], DecoratedItem[ViewT, Button, ViewContextT]]
The decorated callback function.
"""
def decorator(
func: t.Callable[[ViewT, Button, ViewContextT], t.Awaitable[None]],
) -> DecoratedItem[ViewT, Button, ViewContextT]:
if not inspect.iscoroutinefunction(func):
raise TypeError("button must decorate coroutine function.")
item = Button(label=label, custom_id=custom_id, style=style, emoji=emoji, row=row, disabled=disabled, url=None)
return DecoratedItem(item, func)
return decorator
# MIT License
#
# Copyright (c) 2022-present hypergonial
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.