Source code for byte_bot.byte.views.abstract_views
"""Inheritable views that include extra functionality for base Views classes."""
from __future__ import annotations
from copy import deepcopy
from typing import TYPE_CHECKING, Any, Literal, ParamSpec, TypedDict
from discord import ButtonStyle, Colour, Embed, Interaction
from discord.ui import Button, View, button
if TYPE_CHECKING:
from datetime import datetime
from typing import NotRequired, Self
from discord.ext.commands import Bot
__all__ = ("ButtonEmbedView", "ExtendedEmbed", "Field")
P = ParamSpec("P")
[docs]
class ButtonEmbedView(View):
"""Base view including common buttons."""
[docs]
def __init__(
self,
author: int,
bot: Bot,
original_embed: Embed,
minified_embed: Embed,
*args,
**kwargs, # type: ignore[misc]
) -> None:
"""Initialize the view.
Args:
author: Author ID.
bot: Bot object.
original_embed: The original embed to display.
minified_embed: The minified embed to display.
*args: Variable length argument list.
**kwargs: Arbitrary keyword arguments.
"""
super().__init__(*args, **kwargs)
self.author_id = author
self.bot = bot
self.original_embed = original_embed
self.minified_embed = minified_embed
[docs]
async def delete_interaction_check(self, interaction: Interaction) -> bool:
"""Check if the user is the author or a guild admin.
.. note:: Only checks for the ``delete`` button, as we want to expose
the ``learn more`` button to anyone.
Args:
interaction: Interaction object.
Returns:
True if the user is the author or a guild admin, False otherwise.
"""
if interaction.user.id == self.author_id or (
getattr(interaction.user, "guild_permissions", None) and interaction.user.guild_permissions.administrator # type: ignore[attr-defined]
):
return True
await interaction.response.send_message(
"You do not have permission to interact with this message.", ephemeral=True
)
return False
[docs]
async def delete_button_callback(self, interaction: Interaction) -> None:
"""Delete the message this view is attached to.
Args:
interaction: Interaction object.
"""
if await self.delete_interaction_check(interaction):
await interaction.message.delete()
[docs]
async def learn_more_button_callback(self, interaction: Interaction) -> None:
"""Send the original embed to the user privately.
Args:
interaction: Interaction object.
"""
await interaction.response.send_message(embed=self.original_embed, ephemeral=True)
[docs]
@button(label="Delete", style=ButtonStyle.red, custom_id="delete_button")
async def delete_button(self, interaction: Interaction, _: Button[Self]) -> None:
"""Button to delete the message this view is attached to.
Args:
interaction: Interaction object.
_: Button object.
"""
await self.delete_button_callback(interaction)
[docs]
@button(label="Learn More", style=ButtonStyle.green, custom_id="learn_more_button")
async def learn_more_button(self, interaction: Interaction, _: Button[Self]) -> None:
"""Button to privately message the requesting user the full embed.
Args:
interaction: Interaction object.
_: Button object.
"""
await self.learn_more_button_callback(interaction)
[docs]
class Field(TypedDict):
"""Field type for ``ExtendedEmbed``.
.. note:: types are matching the ones in ``Embed.add_fields``.
"""
name: Any
value: Any
inline: NotRequired[bool]
[docs]
class ExtendedEmbed(Embed):
"""Extended Embed class for discord.py."""
[docs]
def add_field_dict(self, field: Field) -> Self:
"""Add a field to the embed.
Args:
field (Field): The field to add to the embed.
Returns:
Self: The embed with the field added.
"""
self.add_field(**field)
return self
[docs]
def add_field_dicts(self, fields: list[Field]) -> Self:
"""Add multiple fields to the embed.
Args:
fields (list[Field]): A list of fields to add to the embed.
Returns:
Self: The embed with the fields added.
"""
for field in fields:
self.add_field_dict(field)
return self
[docs]
@classmethod
def from_field_dicts(
cls,
colour: int | Colour | None = None,
color: int | Colour | None = None,
title: Any | None = None,
type: Literal["rich", "image", "video", "gifv", "article", "link"] = "rich", # noqa: A002
url: Any | None = None,
description: Any | None = None,
timestamp: datetime | None = None,
fields: list[Field] | None = None,
) -> Self:
"""Create an embed from a list of fields.
Args:
colour (int | Colour | None): The colour of the embed.
color (int | Colour | None): The colour of the embed.
title (Any | None): The title of the embed.
type (Literal["rich", "image", "video", "gifv", "article", "link"]): The type of the embed.
url (Any | None): The URL of the embed.
description (Any | None): The description of the embed.
timestamp (datetime | None): The timestamp of the embed.
fields (list[Field] | None): A list of fields to add to the embed.
Returns:
Self: The embed with the fields added.
"""
embed = cls(
colour=colour,
color=color,
title=title,
type=type,
url=url,
description=description,
timestamp=timestamp,
)
embed.add_field_dicts(fields or [])
return embed
[docs]
def deepcopy(self) -> Self:
"""Create a deep copy of the embed.
Returns:
Self: A deep copy of the embed.
"""
return deepcopy(self)