1
Fork 0
mirror of https://github.com/wlinator/luminara.git synced 2024-10-02 18:23:12 +00:00

Add case commands

This commit is contained in:
wlinator 2024-08-29 07:03:25 -04:00
parent d68368a30e
commit 84759ab294
10 changed files with 2769 additions and 1 deletions

168
modules/moderation/cases.py Normal file
View file

@ -0,0 +1,168 @@
import asyncio
import discord
from discord.ext import commands
from reactionmenu import ViewButton, ViewMenu
from lib.case_handler import edit_case_modlog
from lib.const import CONST
from lib.exceptions import LumiException
from lib.format import format_case_number
from services.case_service import CaseService
from ui.cases import (
create_case_embed,
create_case_list_embed,
)
from ui.embeds import Builder
case_service = CaseService()
class Cases(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.hybrid_command(name="case", aliases=["c", "ca"], description="View a specific case by number")
@commands.has_permissions(manage_messages=True)
async def view_case_by_number(self, ctx: commands.Context[commands.Bot], case_number: int) -> None:
guild_id = ctx.guild.id if ctx.guild else 0
case = case_service.fetch_case_by_guild_and_number(guild_id, case_number)
if not case:
embed = Builder.create_embed(
user_name=ctx.author.name,
author_text=CONST.STRINGS["error_no_case_found_author"],
description=CONST.STRINGS["error_no_case_found_description"],
)
await ctx.send(embed=embed)
return
target = await commands.UserConverter().convert(ctx, str(case["target_id"]))
embed: discord.Embed = create_case_embed(
ctx=ctx,
target=target,
case_number=case["case_number"],
action_type=case["action_type"],
reason=case["reason"],
timestamp=case["created_at"],
duration=case["duration"] or None,
)
await ctx.send(embed=embed)
@commands.hybrid_command(name="cases", description="View all cases in the guild")
@commands.has_permissions(manage_messages=True)
async def view_all_cases_in_guild(self, ctx: commands.Context[commands.Bot]) -> None:
if not ctx.guild:
raise LumiException(CONST.STRINGS["error_not_in_guild"])
guild_id = ctx.guild.id
cases = case_service.fetch_cases_by_guild(guild_id)
if not cases:
embed = Builder.create_embed(
user_name=ctx.author.name,
author_text=CONST.STRINGS["case_guild_no_cases_author"],
description=CONST.STRINGS["case_guild_no_cases"],
)
await ctx.send(embed=embed)
return
menu = ViewMenu(ctx, menu_type=ViewMenu.TypeEmbed, all_can_click=True, delete_on_timeout=True)
for i in range(0, len(cases), 10):
chunk = cases[i : i + 10]
embed = create_case_list_embed(
ctx,
chunk,
CONST.STRINGS["case_guild_cases_author"],
)
menu.add_page(embed)
menu.add_button(
ViewButton(style=discord.ButtonStyle.secondary, custom_id=ViewButton.ID_GO_TO_FIRST_PAGE, emoji="⏮️"),
)
menu.add_button(
ViewButton(style=discord.ButtonStyle.secondary, custom_id=ViewButton.ID_PREVIOUS_PAGE, emoji=""),
)
menu.add_button(ViewButton(style=discord.ButtonStyle.secondary, custom_id=ViewButton.ID_NEXT_PAGE, emoji=""))
menu.add_button(
ViewButton(style=discord.ButtonStyle.secondary, custom_id=ViewButton.ID_GO_TO_LAST_PAGE, emoji="⏭️"),
)
await menu.start()
@commands.hybrid_command(name="modcases", aliases=["mc", "modc"], description="View all cases in the guild")
@commands.has_permissions(manage_messages=True)
async def view_all_cases_by_mod(self, ctx: commands.Context[commands.Bot], moderator: discord.Member) -> None:
if not ctx.guild:
raise LumiException(CONST.STRINGS["error_not_in_guild"])
guild_id = ctx.guild.id
cases = case_service.fetch_cases_by_moderator(guild_id, moderator.id)
menu = ViewMenu(ctx, menu_type=ViewMenu.TypeEmbed, all_can_click=True, delete_on_timeout=True)
if not cases:
embed = Builder.create_embed(
user_name=ctx.author.name,
author_text=CONST.STRINGS["case_mod_no_cases_author"],
description=CONST.STRINGS["case_mod_no_cases"],
)
await ctx.send(embed=embed)
return
for i in range(0, len(cases), 10):
chunk = cases[i : i + 10]
embed = create_case_list_embed(
ctx,
chunk,
CONST.STRINGS["case_mod_cases_author"].format(moderator.name),
)
menu.add_page(embed)
menu.add_button(
ViewButton(style=discord.ButtonStyle.secondary, custom_id=ViewButton.ID_GO_TO_FIRST_PAGE, emoji="⏮️"),
)
menu.add_button(
ViewButton(style=discord.ButtonStyle.secondary, custom_id=ViewButton.ID_PREVIOUS_PAGE, emoji=""),
)
menu.add_button(ViewButton(style=discord.ButtonStyle.secondary, custom_id=ViewButton.ID_NEXT_PAGE, emoji=""))
menu.add_button(
ViewButton(style=discord.ButtonStyle.secondary, custom_id=ViewButton.ID_GO_TO_LAST_PAGE, emoji="⏭️"),
)
await menu.start()
@commands.hybrid_command(name="editcase", aliases=["ec"], description="Edit the reason for a case")
@commands.has_permissions(manage_messages=True)
async def edit_case_reason(self, ctx: commands.Context[commands.Bot], case_number: int, *, new_reason: str):
if not ctx.guild:
raise LumiException(CONST.STRINGS["error_not_in_guild"])
guild_id = ctx.guild.id
case_service.edit_case_reason(
guild_id,
case_number,
new_reason,
)
embed = Builder.create_embed(
user_name=ctx.author.name,
author_text=CONST.STRINGS["case_reason_update_author"],
description=CONST.STRINGS["case_reason_update_description"].format(
format_case_number(case_number),
),
)
async def update_tasks():
await asyncio.gather(
ctx.send(embed=embed),
edit_case_modlog(ctx, guild_id, case_number, new_reason),
)
await update_tasks()
async def setup(bot: commands.Bot) -> None:
await bot.add_cog(Cases(bot))

16
poetry.lock generated
View file

@ -1056,6 +1056,20 @@ files = [
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
] ]
[[package]]
name = "reactionmenu"
version = "3.1.7"
description = "A library to create a discord.py 2.0+ paginator. Supports pagination with buttons, reactions, and category selection using selects."
optional = false
python-versions = ">=3.8"
files = [
{file = "reactionmenu-3.1.7-py3-none-any.whl", hash = "sha256:51a217c920382dfecbb2f05d60bd20b79ed9895e9f5663f6c0edb75e806f863a"},
{file = "reactionmenu-3.1.7.tar.gz", hash = "sha256:10da3c1966de2b6264fcdf72537348923c5e151501644375c25f430bfd870463"},
]
[package.dependencies]
"discord.py" = ">=2.0.0"
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.32.3" version = "2.32.3"
@ -1310,4 +1324,4 @@ multidict = ">=4.0"
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.12" python-versions = "^3.12"
content-hash = "4a7a75036f4de7e0126a8f6b058eb3deb52f710becf44e4da6bac4a0ea0a1a2f" content-hash = "122c3bd137956c87143ead4ae71a7800669a9f379a62c93da21a7ef61231c0f5"

View file

@ -26,6 +26,7 @@ ruff = "^0.6.2"
typing-extensions = "^4.12.2" typing-extensions = "^4.12.2"
pydantic = "^2.8.2" pydantic = "^2.8.2"
pytimeparse = "^1.1.8" pytimeparse = "^1.1.8"
reactionmenu = "^3.1.7"
[build-system] [build-system]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"

View file

@ -0,0 +1,21 @@
"""
This type stub file was generated by pyright.
"""
from .buttons import ReactionButton, ViewButton
from .core import ReactionMenu
from .views_menu import ViewMenu, ViewSelect
from .abc import Page
"""
reactionmenu discord pagination
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A library to create a discord.py 2.0+ paginator. Supports pagination with buttons, reactions, and category selection using selects.
:copyright: (c) 2021-present @defxult
:license: MIT
"""
__source__ = ...
__all__ = ('ReactionMenu', 'ReactionButton', 'ViewMenu', 'ViewButton', 'ViewSelect', 'Page')

816
stubs/reactionmenu/abc.pyi Normal file
View file

@ -0,0 +1,816 @@
"""
This type stub file was generated by pyright.
"""
import abc
import discord
from typing import Any, Callable, ClassVar, Final, Generic, List, Literal, NamedTuple, Optional, Set, TYPE_CHECKING, Tuple, TypeVar, Union, overload
from datetime import datetime
from typing_extensions import Self
from collections.abc import Sequence
from enum import Enum
from discord.ext.commands import Context
from discord.utils import MISSING
from .decorators import ensure_not_primed
from .errors import *
"""
MIT License
Copyright (c) 2021-present @defxult
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.
"""
if TYPE_CHECKING:
...
_DYNAMIC_EMBED_LIMIT: Final[int] = ...
_DEFAULT_STYLE: Final[str] = ...
DEFAULT_BUTTONS = ...
DEFAULT = MISSING
GB = TypeVar('GB', bound='_BaseButton')
M = TypeVar('M', bound='_BaseMenu')
class Page:
"""Represents a single "page" in the pagination process
.. added:: v3.1.0
"""
__slots__ = ...
def __init__(self, *, content: Optional[str] = ..., embed: Optional[discord.Embed] = ..., files: Optional[List[discord.File]] = ...) -> None:
...
def __repr__(self) -> str:
...
@staticmethod
def from_embeds(embeds: Sequence[discord.Embed]) -> List[Page]:
"""|static method|
Converts a sequence of embeds into a list of :class:`Page`
"""
...
class PaginationEmojis:
"""A set of basic emojis for your convenience to use for your buttons emoji
- as `BACK_BUTTON`
- as `NEXT_BUTTON`
- as `FIRST_PAGE`
- as `LAST_PAGE`
- 🔢 as `GO_TO_PAGE`
- as `END_SESSION`
"""
BACK_BUTTON: ClassVar[str] = ...
NEXT_BUTTON: ClassVar[str] = ...
FIRST_PAGE: ClassVar[str] = ...
LAST_PAGE: ClassVar[str] = ...
GO_TO_PAGE: ClassVar[str] = ...
END_SESSION: ClassVar[str] = ...
class _PageController:
def __init__(self, pages: List[Page]) -> None:
...
@property
def current_page(self) -> Page:
...
@property
def total_pages(self) -> int:
"""Return the total amount of pages registered to the menu"""
...
def validate_index(self) -> Page:
"""If the index is out of bounds, assign the appropriate values so the pagination process can continue and return the associated page"""
...
def skip_loop(self, action: str, amount: int) -> None:
"""Using `self.index += amount` does not work because this library is used to operating on a +-1 basis. This loop
provides a simple way to still operate on the +-1 standard.
"""
...
def skip(self, skip: _BaseButton.Skip) -> Page:
"""Return the page that the skip value was set to"""
...
def next(self) -> Page:
"""Return the next page in the pagination process"""
...
def prev(self) -> Page:
"""Return the previous page in the pagination process"""
...
def first_page(self) -> Page:
"""Return the first page in the pagination process"""
...
def last_page(self) -> Page:
"""Return the last page in the pagination process"""
...
class _MenuType(Enum):
TypeEmbed = ...
TypeEmbedDynamic = ...
TypeText = ...
MenuType = _MenuType
class _LimitDetails(NamedTuple):
limit: int
per: str
message: str
set_by_user: bool = ...
@classmethod
def default(cls) -> Self:
...
class _BaseButton(Generic[GB], metaclass=abc.ABCMeta):
Emojis: ClassVar[PaginationEmojis] = ...
def __init__(self, name: str, event: Optional[_BaseButton.Event], skip: _BaseButton.Skip) -> None:
...
@property
@abc.abstractmethod
def menu(self):
...
@property
def clicked_by(self) -> Set[discord.Member]:
"""
Returns
-------
Set[:class:`discord.Member`]: The members who clicked the button
"""
...
@property
def total_clicks(self) -> int:
"""
Returns
-------
:class:`int`: The amount of clicks on the button
"""
...
@property
def last_clicked(self) -> Optional[datetime]:
"""
Returns
-------
Optional[:class:`datetime.datetime`]: The time in UTC for when the button was last clicked. Can be :class:`None` if the button has not been clicked
"""
...
class Skip:
"""Initialize a skip button with the appropriate values
Parameters
----------
action: :class:`str`
Whether to go forward in the pagination process ("+") or backwards ("-")
amount: :class:`int`
The amount of pages to skip. Must be >= 1. If value is <= 0, it is implicitly set to 2
"""
def __init__(self, action: Literal['+', '-'], amount: int) -> None:
...
class Event:
"""Set a button to be disabled or removed when it has been pressed a certain amount of times. If the button is a :class:`ReactionButton`, only the "remove" event is available
Parameters
----------
event: :class:`str`
The action to take. Can either be "disable" or "remove"
value: :class:`int`
The amount set for the specified event. Must be >= 1. If value is <= 0, it is implicitly set to 1"""
_DISABLE = ...
_REMOVE = ...
def __init__(self, event_type: Literal['disable', 'remove'], value: int) -> None:
...
class _BaseMenu(metaclass=abc.ABCMeta):
TypeEmbed: Final[_MenuType] = ...
TypeEmbedDynamic: Final[_MenuType] = ...
TypeText: Final[_MenuType] = ...
_sessions_limit_details = ...
_active_sessions: List[Self]
def __init__(self, method: Union[Context, discord.Interaction], /, menu_type: _MenuType, **kwargs) -> None:
...
@abc.abstractmethod
def remove_all_buttons(self):
...
@abc.abstractmethod
def get_button(self):
...
@abc.abstractmethod
def remove_button(self):
...
@abc.abstractmethod
def add_button(self):
...
@abc.abstractmethod
def add_buttons(self):
...
@abc.abstractmethod
def stop(self):
...
@abc.abstractmethod
async def start(self):
...
@abc.abstractmethod
async def quick_start(cls):
...
@staticmethod
def separate(values: Sequence[Any]) -> Tuple[List[discord.Embed], List[str]]:
"""|static method|
Sorts all embeds and strings into a single tuple
Parameters
----------
values: Sequence[`Any`]
The values to separate
Returns
-------
Tuple[List[:class:`discord.Embed`], List[:class:`str`]]
Example
-------
>>> embeds, strings = .separate([...])
"""
...
@staticmethod
def all_embeds(values: Sequence[Any]) -> bool:
"""|static method|
Tests to see if all items in the sequence are of type :class:`discord.Embed`
Parameters
----------
values: Sequence[`Any`]
The values to test
Returns
-------
:class:`bool`: Can return `False` if the sequence is empty
"""
...
@staticmethod
def all_strings(values: Sequence[Any]) -> bool:
"""|static method|
Tests to see if all items in the sequence are of type :class:`str`
Parameters
----------
values: Sequence[`Any`]
The values to test
Returns
-------
:class:`bool`: Can return `False` if the sequence is empty
"""
...
@classmethod
def remove_limit(cls) -> None:
"""|class method|
Remove the limits currently set for menu's
"""
...
@classmethod
def get_all_dm_sessions(cls) -> List[Self]:
"""|class method|
Retrieve all active DM menu sessions
Returns
-------
A :class:`list` of active DM menu sessions that are currently running. Can be an empty list if there are no active DM sessions
"""
...
@classmethod
def get_all_sessions(cls) -> List[Self]:
"""|class method|
Retrieve all active menu sessions
Returns
-------
A :class:`list` of menu sessions that are currently running. Can be an empty list if there are no active sessions
"""
...
@classmethod
def get_session(cls, name: str) -> List[Self]:
"""|class method|
Get a menu instance by it's name
Parameters
----------
name: :class:`str`
The name of the menu to return
Returns
-------
A :class:`list` of menu sessions that are currently running that match the supplied :param:`name`. Can be an empty list if there are no active sessions that matched the :param:`name`
"""
...
@classmethod
def get_sessions_count(cls) -> int:
"""|class method|
Returns the number of active sessions
Returns
-------
:class:`int`: The amount of menu sessions that are active
"""
...
@classmethod
def set_sessions_limit(cls, limit: int, per: Literal['channel', 'guild', 'member'] = ..., message: str = ...) -> None:
"""|class method|
Sets the amount of menu sessions that can be active at the same time per guild, channel, or member. This applies to both :class:`ReactionMenu` & :class:`ViewMenu`
Parameters
----------
limit: :class:`int`
The amount of menu sessions allowed
per: :class:`str`
How menu sessions should be limited. Options: "channel", "guild", or "member"
message: :class:`str`
Message that will be sent informing users about the menu limit when the limit is reached. Can be :class:`None` for no message
Raises
------
- `IncorrectType`: The :param:`limit` parameter was not of type :class:`int`
- `MenuException`: The value of :param:`per` was not valid or the limit was not greater than or equal to one
"""
...
@classmethod
async def stop_session(cls, name: str, include_all: bool = ...) -> None:
"""|coro class method|
Stop a specific menu with the supplied name
Parameters
----------
name: :class:`str`
The menus name
include_all: :class:`bool`
If set to `True`, it stops all menu sessions with the supplied :param:`name`. If `False`, stops only the most recently started menu with the supplied :param:`name`
Raises
------
- `MenuException`: The session with the supplied name was not found
"""
...
@classmethod
async def stop_all_sessions(cls) -> None:
"""|coro class method|
Stops all menu sessions that are currently running
"""
...
@classmethod
def get_menu_from_message(cls, message_id: int, /) -> Optional[Self]:
"""|class method|
Return the menu object associated with the message with the given ID
Parameters
----------
message_id: :class:`int`
The `discord.Message.id` from the menu message
Returns
-------
The menu object. Can be :class:`None` if the menu was not found in the list of active menu sessions
"""
...
@property
def rows(self) -> Optional[List[str]]:
"""
Returns
-------
Optional[List[:class:`str`]]: All rows that's been added to the menu. Can return `None` if the menu has not started or the `menu_type` is not `TypeEmbedDynamic`
.. added: v3.1.0
"""
...
@property
def menu_type(self) -> str:
"""
Returns
-------
:class:`str`: The `menu_type` you set via the constructor. This will either be `TypeEmbed`, `TypeEmbedDynamic`, or `TypeText`
.. added:: v3.1.0
"""
...
@property
def last_viewed(self) -> Optional[Page]:
"""
Returns
-------
Optional[:class:`Page`]: The last page that was viewed in the pagination process. Can be :class:`None` if the menu has not been started
"""
...
@property
def owner(self) -> Union[discord.Member, discord.User]:
"""
Returns
-------
Union[:class:`discord.Member`, :class:`discord.User`]: The owner of the menu (the person that started the menu). If the menu was started in a DM, this will return :class:`discord.User`
"""
...
@property
def total_pages(self) -> int:
"""
Returns
-------
:class:`int`: The amount of pages that have been added to the menu. If the `menu_type` is :attr:`TypeEmbedDynamic`, the amount of pages is not known until AFTER the menu has started.
If attempted to retrieve the value before a dynamic menu has started, this will return a value of -1
"""
...
@property
def pages(self) -> Optional[List[Page]]:
"""
Returns
-------
Optional[List[:class:`Page`]]: The pages currently applied to the menu. Can return :class:`None` if there are no pages
Note: If the `menu_type` is :attr:`TypeEmbedDynamic`, the pages aren't known until after the menu has started
"""
...
@property
def message(self) -> Optional[Union[discord.Message, discord.InteractionMessage]]:
"""
Returns
-------
Optional[Union[:class:`discord.Message`, :class:`discord.InteractionMessage`]]: The menu's message object. Can be :class:`None` if the menu has not been started
"""
...
@property
def is_running(self) -> bool:
"""
Returns
-------
:class:`bool`: `True` if the menu is currently running, `False` otherwise
"""
...
@property
def in_dms(self) -> bool:
"""
Returns
-------
:class:`bool`: If the menu was started in a DM
"""
...
def randomize_embed_colors(self) -> None:
"""Randomize the color of all the embeds that have been added to the menu
Raises
------
- `MenuException`: The `menu_type` was not of `TypeEmbed`
.. added:: v3.1.0
"""
...
def set_page_director_style(self, style_id: int, separator: str = ...) -> None:
"""Set how the page numbers dictating what page you are on (in the footer of an embed/regular message) are displayed
Parameters
----------
style_id: :class:`int`
Varying formats of how the page director can be presented. The following ID's are available:
- `1` = Page 1/10
- `2` = Page 1 out of 10
- `3` = 1 out of 10
- `4` = 1 10
- `5` = 1 » 10
- `6` = 1 | 10
- `7` = 1 : 10
- `8` = 1 - 10
- `9` = 1 / 10
- `10` = 1 🔹 10
- `11` = 1 🔸 10
separator: :class:`str`
The separator between the page director and any text you may have in the embed footer. The default separator is ":". It should be noted that whichever separator you assign,
if you wish to have spacing between the page director and the separator, you must place the space inside the string yourself as such: " :"
Raises
------
- `MenuException`: The :param:`style_id` value was not valid
"""
...
async def wait_until_closed(self) -> None:
"""|coro|
Waits until the menu session ends using `.stop()` or when the menu times out. This should not be used inside relays
.. added:: v3.0.1
"""
...
@ensure_not_primed
def add_from_messages(self, messages: Sequence[discord.Message]) -> None:
"""Add pages to the menu using the message object itself
Parameters
----------
messages: Sequence[:class:`discord.Message`]
A sequence of discord message objects
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `MenuSettingsMismatch`: The messages provided did not have the correct values. For example, the `menu_type` was set to `TypeEmbed`, but the messages you've provided only contains text. If the `menu_type` is `TypeEmbed`, only messages with embeds should be provided
- `IncorrectType`: All messages were not of type :class:`discord.Message`
"""
...
@ensure_not_primed
async def add_from_ids(self, messageable: discord.abc.Messageable, message_ids: Sequence[int]) -> None:
"""|coro|
Add pages to the menu using the IDs of messages. This only grabs embeds (if the `menu_type` is :attr:`TypeEmbed`) or the content (if the `menu_type` is :attr:`TypeText`) from the message
Parameters
----------
messageable: :class:`discord.abc.Messageable`
A discord :class:`Messageable` object (:class:`discord.TextChannel`, :class:`commands.Context`, etc.)
message_ids: Sequence[:class:`int`]
The messages to fetch
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `MenuSettingsMismatch`: The message IDs provided did not have the correct values when fetched. For example, the `menu_type` was set to `TypeEmbed`, but the messages you've provided for the library to fetch only contains text. If the `menu_type` is `TypeEmbed`, only messages with embeds should be provided
- `MenuException`: An error occurred when attempting to fetch a message or not all :param:`message_ids` were of type int
"""
...
@ensure_not_primed
def clear_all_row_data(self) -> None:
"""Delete all the data thats been added using :meth:`add_row()`
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `MenuSettingsMismatch`: This method was called but the menus `menu_type` was not :attr:`TypeEmbedDynamic`
"""
...
@ensure_not_primed
def add_row(self, data: str) -> None:
"""Add text to the embed page by rows of data
Parameters
----------
data: :class:`str`
The data to add
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `MenuSettingsMismatch`: This method was called but the menus `menu_type` was not :attr:`TypeEmbedDynamic`
- `MissingSetting`: The kwarg "rows_requested" (int) has not been set for the menu
"""
...
@ensure_not_primed
def set_main_pages(self, *embeds: discord.Embed) -> None:
"""On a menu with a `menu_type` of :attr:`TypeEmbedDynamic`, set the pages you would like to show first. These embeds will be shown before the embeds that contain your data
Parameters
----------
*embeds: :class:`discord.Embed`
An argument list of :class:`discord.Embed` objects
Raises
------
- `MenuSettingsMismatch`: Tried to use method on a menu that was not of `menu_type` :attr:`TypeEmbedDynamic`
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `MenuException`: The "embeds" parameter was empty. At least one value is needed
- `IncorrectType`: All values in the argument list were not of type :class:`discord.Embed`
"""
...
@ensure_not_primed
def set_last_pages(self, *embeds: discord.Embed) -> None:
"""On a menu with a `menu_type` of :attr:`TypeEmbedDynamic`, set the pages you would like to show last. These embeds will be shown after the embeds that contain your data
Parameters
----------
*embeds: :class:`discord.Embed`
An argument list of :class:`discord.Embed` objects
Raises
------
- `MenuSettingsMismatch`: Tried to use method on a menu that was not of `menu_type` :attr:`TypeEmbedDynamic`
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `MenuException`: The "embeds" parameter was empty. At least one value is needed
- `IncorrectType`: All values in the argument list were not of type :class:`discord.Embed`
"""
...
@ensure_not_primed
def add_page(self, embed: Optional[discord.Embed] = ..., content: Optional[str] = ..., files: Optional[List[discord.File]] = ...) -> None:
"""Add a page to the menu
Parameters
----------
embed: Optional[:class:`discord.Embed`]
The embed of the page
content: Optional[:class:`str`]
The text that appears above an embed in a message
files: Optional[Sequence[:class:`discord.File`]]
Files you'd like to attach to the page
Raises
------
- `MenuException`: Attempted to add a page with no parameters
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `MenuSettingsMismatch`: The page being added does not match the menus `menu_type`
.. changes::
v3.1.0
Added parameter content
Added parameter embed
Added parameter files
Removed parameter "page"
"""
...
@overload
def add_pages(self, pages: Sequence[discord.Embed]) -> None:
...
@overload
def add_pages(self, pages: Sequence[str]) -> None:
...
@ensure_not_primed
def add_pages(self, pages: Sequence[Union[discord.Embed, str]]) -> None:
"""Add multiple pages to the menu at once
Parameters
----------
pages: Sequence[Union[:class:`discord.Embed`, :class:`str`]]
The pages to add. Can only be used when the menus `menu_type` is :attr:`TypeEmbed` (adding a :class:`discord.Embed`)
or :attr:`TypeText` (adding a :class:`str`)
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `MenuSettingsMismatch`: The page being added does not match the menus `menu_type`
"""
...
@ensure_not_primed
def remove_all_pages(self) -> None:
"""Remove all pages from the menu
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
"""
...
@ensure_not_primed
def remove_page(self, page_number: int) -> None:
"""Remove a page from the menu
Parameters
----------
page_number: :class:`int`
The page to remove
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `InvalidPage`: The page associated with the given page number was not valid
"""
...
def set_on_timeout(self, func: Callable[[M], None]) -> None:
"""Set the function to be called when the menu times out
Parameters
----------
func: Callable[[:type:`M`]], :class:`None`]
The function object that will be called when the menu times out. The function should contain a single positional argument
and should not return anything. The argument passed to that function is an instance of the menu.
Raises
------
- `IncorrectType`: Parameter "func" was not a callable object
"""
...
def remove_on_timeout(self) -> None:
"""Remove the timeout call to the function you have set when the menu times out"""
...
def set_relay(self, func: Callable[[NamedTuple], None], *, only: Optional[List[GB]] = ...) -> None:
"""Set a function to be called with a given set of information when a button is pressed on the menu. The information passed is `RelayPayload`, a named tuple.
The named tuple contains the following attributes:
- `member`: The :class:`discord.Member` object of the person who pressed the button. Could be :class:`discord.User` if the menu was started in a DM
- `button`: Depending on the menu instance, the :class:`ReactionButton` or :class:`ViewButton` object of the button that was pressed
Parameters
----------
func: Callable[[:class:`NamedTuple`], :class:`None`]
The function should only contain a single positional argument. Command functions (`@bot.command()`) not supported
only: Optional[List[:generic:`GB`]]
A list of buttons (`GB`) associated with the current menu instance. If the menu instance is :class:`ReactionMenu`, this should be a list of :class:`ReactionButton`
and vice-versa for :class:`ViewMenu` instances. If this is :class:`None`, all buttons on the menu will be relayed. If set, only button presses from those specified buttons will be relayed
Raises
------
- `IncorrectType`: The :param:`func` argument provided was not callable
"""
...
def remove_relay(self) -> None:
"""Remove the relay that's been set"""
...

View file

@ -0,0 +1,549 @@
"""
This type stub file was generated by pyright.
"""
import discord
from typing import Any, Callable, Dict, Final, Iterable, List, Literal, NamedTuple, Optional, TYPE_CHECKING, Union
from . import ReactionButton, ReactionMenu, ViewMenu
from enum import Enum
from .abc import _BaseButton
"""
MIT License
Copyright (c) 2021-present @defxult
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.
"""
if TYPE_CHECKING:
...
class _Details(NamedTuple):
"""Used for buttons with a `custom_id` of `ID_CALLER`"""
func: Callable[..., None]
args: Iterable[Any]
kwargs: Dict[str, Any]
...
Details = _Details
class ViewButton(discord.ui.Button, _BaseButton):
"""A helper class for :class:`ViewMenu`. Represents a UI button.
Parameters
----------
style: :class:`discord.ButtonStyle`
The style of the button
label: Optional[:class:`str`]
The button label, if any
disabled: :class:`bool`
Whether the button is disabled or not
custom_id: Optional[:class:`str`]
The ID of the button that gets received during an interaction. If this button is for a URL, it does not have a custom ID
url: Optional[:class:`str`]
The URL this button sends you to
emoji: Optional[Union[:class:`str`, :class:`discord.PartialEmoji`]]
The emoji of the button, if available
followup: Optional[:class:`ViewButton.Follow`]
Used with buttons with custom_id :attr:`ViewButton.ID_CALLER`, :attr:`ViewButton.ID_SEND_MESSAGE`, :attr:`ViewButton.ID_CUSTOM_EMBED`
event: Optional[:class:`ViewButton.Event`]
Set the button to be disabled or removed when it has been pressed a certain amount of times
Kwargs
------
name: :class:`str`
An optional name for the button. Can be set to retrieve it later via :meth:`ViewMenu.get_button()`
skip: :class:`ViewButton.Skip`
Set the action and the amount of pages to skip when using a `custom_id` of `ViewButton.ID_SKIP`
persist: :class:`bool`
Available only when using link buttons. This prevents link buttons from being disabled/removed when the menu times out or is stopped so they can remain clickable
.. added v3.1.0
:param:`persist`
"""
ID_NEXT_PAGE: Final[str] = ...
ID_PREVIOUS_PAGE: Final[str] = ...
ID_GO_TO_FIRST_PAGE: Final[str] = ...
ID_GO_TO_LAST_PAGE: Final[str] = ...
ID_GO_TO_PAGE: Final[str] = ...
ID_END_SESSION: Final[str] = ...
ID_CALLER: Final[str] = ...
ID_SEND_MESSAGE: Final[str] = ...
ID_CUSTOM_EMBED: Final[str] = ...
ID_SKIP: Final[str] = ...
_RE_IDs = ...
_RE_UNIQUE_ID_SET = ...
def __init__(self, *, style: discord.ButtonStyle = ..., label: Optional[str] = ..., disabled: bool = ..., custom_id: Optional[str] = ..., url: Optional[str] = ..., emoji: Optional[Union[str, discord.PartialEmoji]] = ..., followup: Optional[ViewButton.Followup] = ..., event: Optional[ViewButton.Event] = ..., **kwargs) -> None:
...
def __repr__(self): # -> str:
...
async def callback(self, interaction: discord.Interaction) -> None:
"""*INTERNAL USE ONLY* - The callback function from the button interaction. This should not be manually called"""
...
class Followup:
"""A class that represents the message sent using a :class:`ViewButton`. Contains parameters similar to method `discord.abc.Messageable.send`. Only to be used with :class:`ViewButton` kwarg "followup".
It is to be noted that this should not be used with :class:`ViewButton` with a "style" of `discord.ButtonStyle.link` because link buttons do not send interaction events.
Parameters
----------
content: Optional[:class:`str`]
Message to send
embed: Optional[:class:`discord.Embed`]
Embed to send. Can also bet set for buttons with a custom_id of :attr:`ViewButton.ID_CUSTOM_EMBED`
file: Optional[:class:`discord.File`]
File to send. If the :class:`ViewButton` custom_id is :attr:`ViewButton.ID_SEND_MESSAGE`, the file will be ignored because of discord API limitations
tts: :class:`bool`
If discord should read the message aloud. Not valid for `ephemeral` messages
allowed_mentions: Optional[:class:`discord.AllowedMentions`]
Controls the mentions being processed in the menu message. Not valid for `ephemeral` messages
delete_after: Optional[Union[:class:`int`, :class:`float`]]
Amount of time to wait before the message is deleted. Not valid for `ephemeral` messages
ephemeral: :class:`bool`
If the message will be hidden from everyone except the person that pressed the button. This is only valid for a :class:`ViewButton` with custom_id :attr:`ViewButton.ID_SEND_MESSAGE`
Kwargs
------
details: :meth:`ViewButton.Followup.set_caller_details()`
The information that will be used when a `ViewButton.ID_CALLER` button is pressed (defaults to :class:`None`)
"""
__slots__ = ...
def __repr__(self): # -> LiteralString:
...
def __init__(self, content: Optional[str] = ..., *, embed: Optional[discord.Embed] = ..., file: Optional[discord.File] = ..., tts: bool = ..., allowed_mentions: Optional[discord.AllowedMentions] = ..., delete_after: Optional[Union[int, float]] = ..., ephemeral: bool = ..., **kwargs) -> None:
...
@staticmethod
def set_caller_details(func: Callable[..., None], *args, **kwargs) -> Details:
"""|static method|
Set the parameters for the function you set for a :class:`ViewButton` with the custom_id :attr:`ViewButton.ID_CALLER`
Parameters
----------
func: Callable[..., :class:`None`]
The function object that will be called when the associated button is pressed
*args: `Any`
An argument list that represents the parameters of that function
**kwargs: `Any`
An argument list that represents the kwarg parameters of that function
Returns
-------
:class:`Details`: The :class:`NamedTuple` containing the values needed to internally call the function you have set
Raises
------
- `IncorrectType`: Parameter "func" was not a callable object
"""
...
@property
def menu(self) -> Optional[ViewMenu]:
"""
Returns
-------
Optional[:class:`ViewMenu`]: The menu instance this button is attached to. Could be :class:`None` if the button is not attached to a menu
"""
...
@classmethod
def generate_skip(cls, label: str, action: Literal['+', '-'], amount: int) -> ViewButton:
"""|class method|
A factory method that returns a :class:`ViewButton` with the following parameters set:
- style: `discord.ButtonStyle.gray`
- label: `<label>`
- custom_id: :attr:`ViewButton.ID_SKIP`
- skip: `ViewButton.Skip(<action>, <amount>)`
"""
...
@classmethod
def link(cls, label: str, url: str) -> ViewButton:
"""|class method|
A factory method that returns a :class:`ViewButton` with the following parameters set:
- style: `discord.ButtonStyle.link`
- label: `<label>`
- url: `<url>`
"""
...
@classmethod
def back(cls) -> ViewButton:
"""|class method|
A factory method that returns a :class:`ViewButton` with the following parameters set:
- style: `discord.ButtonStyle.gray`
- label: "Back"
- custom_id: :attr:`ViewButton.ID_PREVIOUS_PAGE`
"""
...
@classmethod
def next(cls) -> ViewButton:
"""|class method|
A factory method that returns a :class:`ViewButton` with the following parameters set:
- style: `discord.ButtonStyle.gray`
- label: "Next"
- custom_id: :attr:`ViewButton.ID_NEXT_PAGE`
"""
...
@classmethod
def go_to_first_page(cls) -> ViewButton:
"""|class method|
A factory method that returns a :class:`ViewButton` with the following parameters set:
- style: `discord.ButtonStyle.gray`
- label: "First Page"
- custom_id: :attr:`ViewButton.ID_GO_TO_FIRST_PAGE`
"""
...
@classmethod
def go_to_last_page(cls) -> ViewButton:
"""|class method|
A factory method that returns a :class:`ViewButton` with the following parameters set:
- style: `discord.ButtonStyle.gray`
- label: "Last Page"
- custom_id: :attr:`ViewButton.ID_GO_TO_LAST_PAGE`
"""
...
@classmethod
def go_to_page(cls) -> ViewButton:
"""|class method|
A factory method that returns a :class:`ViewButton` with the following parameters set:
- style: `discord.ButtonStyle.gray`
- label: "Page Selection"
- custom_id: :attr:`ViewButton.ID_GO_TO_PAGE`
"""
...
@classmethod
def end_session(cls) -> ViewButton:
"""|class method|
A factory method that returns a :class:`ViewButton` with the following parameters set:
- style: `discord.ButtonStyle.gray`
- label: "Close"
- custom_id: :attr:`ViewButton.ID_END_SESSION`
"""
...
@classmethod
def all(cls) -> List[ViewButton]:
"""|class method|
A factory method that returns a list of all base navigation buttons. The following buttons are returned with pre-set values:
- Button 1
- style: `discord.ButtonStyle.gray`
- label: "First Page"
- custom_id: :attr:`ViewButton.ID_GO_TO_FIRST_PAGE`
- Button 2
- style: `discord.ButtonStyle.gray`
- label: "Back"
- custom_id: :attr:`ViewButton.ID_PREVIOUS_PAGE`
- Button 3
- style: `discord.ButtonStyle.gray`
- label: "Next"
- custom_id: :attr:`ViewButton.ID_NEXT_PAGE`
- Button 4
- style: `discord.ButtonStyle.gray`
- label: "Last Page"
- custom_id: :attr:`ViewButton.ID_GO_TO_LAST_PAGE`
- Button 5
- style: `discord.ButtonStyle.gray`
- label: "Page Selection"
- custom_id: :attr:`ViewButton.ID_GO_TO_PAGE`
- Button 6
- style: `discord.ButtonStyle.gray`
- label: "Close"
- custom_id: :attr:`ViewButton.ID_END_SESSION`
They are returned in that order
Returns
-------
List[:class:`ViewButton`]
"""
...
@classmethod
def all_with_emojis(cls) -> List[ViewButton]:
"""|class method|
A factory method that returns a list of all base navigation buttons with emojis assigned instead of labels. The following buttons are returned with pre-set values:
- Button 1
- style: `discord.ButtonStyle.gray`
- emoji:
- custom_id: :attr:`ViewButton.ID_GO_TO_FIRST_PAGE`
- Button 2
- style: `discord.ButtonStyle.gray`
- emoji:
- custom_id: :attr:`ViewButton.ID_PREVIOUS_PAGE`
- Button 3
- style: `discord.ButtonStyle.gray`
- emoji:
- custom_id: :attr:`ViewButton.ID_NEXT_PAGE`
- Button 4
- style: `discord.ButtonStyle.gray`
- emoji:
- custom_id: :attr:`ViewButton.ID_GO_TO_LAST_PAGE`
- Button 5
- style: `discord.ButtonStyle.gray`
- emoji: 🔢
- custom_id: :attr:`ViewButton.ID_GO_TO_PAGE`
- Button 6
- style: `discord.ButtonStyle.gray`
- emoji:
- custom_id: :attr:`ViewButton.ID_END_SESSION`
They are returned in that order
Returns
-------
List[:class:`ViewButton`]
.. added:: v3.1.0
"""
...
class ButtonType(Enum):
"""A helper class for :class:`ReactionMenu`. Determines the generic action a button can perform."""
NEXT_PAGE = ...
PREVIOUS_PAGE = ...
GO_TO_FIRST_PAGE = ...
GO_TO_LAST_PAGE = ...
GO_TO_PAGE = ...
END_SESSION = ...
CUSTOM_EMBED = ...
CALLER = ...
SKIP = ...
class ReactionButton(_BaseButton):
"""A helper class for :class:`ReactionMenu`. Represents a reaction.
Parameters
----------
emoji: :class:`str`
The discord reaction that will be used
linked_to: :class:`ReactionButton.Type`
A generic action a button can perform
Kwargs
------
embed: :class:`discord.Embed`
Only used when :param:`linked_to` is set as :attr:`ReactionButton.Type.CUSTOM_EMBED`. This is the embed that can be selected separately from the menu (`TypeEmbed` menu's only)
name: :class:`str`
An optional name for the button. Can be set to retrieve it later via :meth:`ReactionMenu.get_button()`
details: :meth:`ReactionButton.set_caller_details()`
The class method used to set the function and it's arguments to be called when the button is pressed
event: :class:`ReactionButton.Event`
Determine when a button should be removed depending on how many times it has been pressed
skip: :class:`ReactionButton.Skip`
Set the action and the amount of pages to skip when using a `linked_to` of `ReactionButton.Type.SKIP`
"""
Type = ButtonType
def __init__(self, *, emoji: str, linked_to: ReactionButton.Type, **kwargs) -> None:
...
def __str__(self) -> str:
...
def __repr__(self): # -> str:
...
@property
def menu(self) -> Optional[ReactionMenu]:
"""
Returns
-------
Optional[:class:`ReactionMenu`]: The menu the button is currently operating under. Can be :class:`None` if the button is not registered to a menu
"""
...
@staticmethod
def set_caller_details(func: Callable[..., None], *args, **kwargs) -> Details:
"""|static method|
Set the parameters for the function you set for a :class:`ReactionButton` with a `linked_to` of :attr:`ReactionButton.Type.CALLER`
Parameters
----------
func: Callable[..., :class:`None`]
The function object that will be called when the associated button is pressed
*args: `Any`
An argument list that represents the parameters of that function
**kwargs: `Any`
An argument list that represents the kwarg parameters of that function
Returns
-------
:class:`Details`: The :class:`NamedTuple` containing the values needed to internally call the function you have set
Raises
------
- `IncorrectType`: Parameter "func" was not a callable object
"""
...
@classmethod
def generate_skip(cls, emoji: str, action: Literal['+', '-'], amount: int) -> ReactionButton:
"""|class method|
A factory method that returns a :class:`ReactionButton` with the following parameters set:
- emoji: `<emoji>`
- linked_to: :attr:`ReactionButton.Type.SKIP`
- skip: `ReactionButton.Skip(<action>, <amount>)`
"""
...
@classmethod
def back(cls) -> ReactionButton:
"""|class method|
A factory method that returns a :class:`ReactionButton` with the following parameters set:
- emoji:
- linked_to: :attr:`ReactionButton.Type.PREVIOUS_PAGE`
"""
...
@classmethod
def next(cls) -> ReactionButton:
"""|class method|
A factory method that returns a :class:`ReactionButton` with the following parameters set:
- emoji:
- linked_to: :attr:`ReactionButton.Type.NEXT_PAGE`
"""
...
@classmethod
def go_to_first_page(cls) -> ReactionButton:
"""|class method|
A factory method that returns a :class:`ReactionButton` with the following parameters set:
- emoji:
- linked_to: :attr:`ReactionButton.Type.GO_TO_FIRST_PAGE`
"""
...
@classmethod
def go_to_last_page(cls) -> ReactionButton:
"""|class method|
A factory method that returns a :class:`ReactionButton` with the following parameters set:
- emoji:
- linked_to: :attr:`ReactionButton.Type.GO_TO_LAST_PAGE`
"""
...
@classmethod
def go_to_page(cls) -> ReactionButton:
"""|class method|
A factory method that returns a :class:`ReactionButton` with the following parameters set:
- emoji: 🔢
- linked_to: :attr:`ReactionButton.Type.GO_TO_PAGE`
"""
...
@classmethod
def end_session(cls) -> ReactionButton:
"""|class method|
A factory method that returns a :class:`ReactionButton` with the following parameters set:
- emoji:
- linked_to: :attr:`ReactionButton.Type.END_SESSION`
"""
...
@classmethod
def all(cls) -> List[ReactionButton]:
"""|class method|
A factory method that returns a `list` of all base navigation buttons. Base navigation buttons are :class:`ReactionButton` with a `linked_to` of:
- :attr:`ReactionButton.Type.GO_TO_FIRST_PAGE`
- :attr:`ReactionButton.Type.PREVIOUS_PAGE`
- :attr:`ReactionButton.Type.NEXT_PAGE`
- :attr:`ReactionButton.Type.GO_TO_LAST_PAGE`
- :attr:`ReactionButton.Type.GO_TO_PAGE`
- :attr:`ReactionButton.Type.END_SESSION`
They are returned in that order
"""
...

332
stubs/reactionmenu/core.pyi Normal file
View file

@ -0,0 +1,332 @@
"""
This type stub file was generated by pyright.
"""
import discord
from typing import ClassVar, List, Literal, Optional, Sequence, TYPE_CHECKING, Union, overload
from discord.ext.commands import Context
from .abc import MenuType, _BaseMenu
from .buttons import ReactionButton
from .decorators import ensure_not_primed
from .errors import *
"""
MIT License
Copyright (c) 2021-present @defxult
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.
"""
if TYPE_CHECKING:
...
class ReactionMenu(_BaseMenu):
"""A class to create a discord pagination menu using reactions
Parameters
----------
method: Union[:class:`discord.ext.commands.Context`, :class:`discord.Interaction`]
The Context object. You can get this using a command or if you're in a `discord.on_message` event. Also accepts interactions, typically received when using slash commands
menu_type: :class:`MenuType`
The configuration of the menu. Class variables :attr:`ReactionMenu.TypeEmbed`, :attr:`ReactionMenu.TypeEmbedDynamic`, or :attr:`ReactionMenu.TypeText`
Kwargs
------
all_can_click: :class:`bool`
Sets if everyone is allowed to control when pages are 'turned' when buttons are pressed (defaults to `False`)
allowed_mentions: :class:`discord.AllowedMentions`
Controls the mentions being processed in the menu message (defaults to :class:`discord.AllowedMentions(everyone=False, users=True, roles=False, replied_user=True)`)
clear_reactions_after: :class:`bool`
If the menu times out, remove all reactions (defaults to `True`)
custom_embed: :class:`discord.Embed`
Embed object to use when adding data with :meth:`ReactionMenu.add_row()`. Used for styling purposes (:attr:`ReactionMenu.TypeEmbedDynamic` only/defaults to :class:`None`)
delete_interactions: :class:`bool`
Delete the prompt message by the bot and response message by the user when asked what page they would like to go to when using :attr:`ReactionButton.Type.GO_TO_PAGE` (defaults to `True`)
delete_on_timeout: :class:`bool`
When the menu times out, delete the menu message. This overrides :attr:`clear_reactions_after` (defaults to `False`)
name: :class:`str`
A name you can set for the menu (defaults to :class:`None`)
navigation_speed: :class:`str`
Sets if the user needs to wait for the reaction to be removed by the bot before "turning" the page. Setting the speed to :attr:`ReactionMenu.FAST` makes it so that there is no need to wait (reactions are not removed on each press) and can
navigate lengthy menu's more quickly (defaults to :attr:`ReactionMenu.NORMAL`)
only_roles: List[:class:`discord.Role`]
Members with any of the provided roles are the only ones allowed to control the menu. The member who started the menu will always be able to control it. This overrides :attr:`all_can_click` (defaults to :class:`None`)
remove_extra_reactions: :class:`bool`
If `True`, all emojis (reactions) added to the menu message that were not originally added to the menu will be removed (defaults to `False`)
rows_requested: :class:`int`
The amount of information per :meth:`ReactionMenu.add_row()` you would like applied to each embed page (:attr:`ReactionMenu.TypeEmbedDynamic` only/defaults to :class:`None`)
show_page_director: :class:`bool`
Shown at the bottom of each embed page. "Page 1/20" (defaults to `True`)
style: :class:`str`
A custom page director style you can select. "$" represents the current page, "&" represents the total amount of pages (defaults to "Page $/&") Example: `ReactionMenu(ctx, ..., style='On $ out of &')`
timeout: Union[:class:`float`, :class:`int`, :class:`None`]
Timer for when the menu should end. Can be :class:`None` for no timeout (defaults to 60.0)
wrap_in_codeblock: :class:`str`
The discord codeblock language identifier (:attr:`ReactionMenu.TypeEmbedDynamic` only/defaults to :class:`None`). Example: `ReactionMenu(ctx, ..., wrap_in_codeblock='py')`
"""
NORMAL: ClassVar[str] = ...
FAST: ClassVar[str] = ...
_active_sessions: List[ReactionMenu] = ...
def __init__(self, method: Union[Context, discord.Interaction], /, *, menu_type: MenuType, **kwargs) -> None:
...
def __repr__(self): # -> str:
...
@property
def navigation_speed(self) -> str:
"""
Returns
-------
:class:`str`: The current :attr:`navigation_speed` that is set for the menu
"""
...
@property
def buttons(self) -> List[ReactionButton]:
"""
Returns
-------
List[:class:`ReactionButton`]: A list of all the buttons that have been added to the menu
"""
...
@property
def buttons_most_clicked(self) -> List[ReactionButton]:
"""
Returns
-------
List[:class:`ReactionButton`]: The list of buttons on the menu ordered from highest (button with the most clicks) to lowest (button with the least clicks). Can be an empty list if there are no buttons registered to the menu
"""
...
@classmethod
async def quick_start(cls, method: Union[Context, discord.Interaction], /, pages: Sequence[Union[discord.Embed, str]], buttons: Optional[Sequence[ReactionButton]] = ...) -> ReactionMenu:
"""|coro class method|
Start a menu with default settings either with a `menu_type` of `ReactionMenu.TypeEmbed` (all values in `pages` are of type `discord.Embed`) or `ReactionMenu.TypeText` (all values in `pages` are of type `str`)
Parameters
----------
method: Union[:class:`discord.ext.commands.Context`, :class:`discord.Interaction`]
The Context object. You can get this using a command or if you're in a `discord.on_message` event. Also accepts interactions, typically received when using slash commands
pages: Sequence[Union[:class:`discord.Embed`, :class:`str`]]
The pages to add
buttons: Optional[Sequence[:class:`ReactionButton`]]
The buttons to add. If left as `DEFAULT_BUTTONS`, that is equivalent to `ReactionButton.all()`
Returns
-------
:class:`ReactionMenu`: The menu that was started
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `NoPages`: The menu was started when no pages have been added
- `NoButtons`: Attempted to start the menu when no Buttons have been registered
- `IncorrectType`: All items in :param:`pages` were not of type :class:`discord.Embed` or :class:`str`
.. added v3.1.0
"""
...
@overload
def get_button(self, identity: str, *, search_by: Literal['name', 'emoji', 'type'] = ...) -> List[ReactionButton]:
...
@overload
def get_button(self, identity: int, *, search_by: Literal['name', 'emoji', 'type'] = ...) -> List[ReactionButton]:
...
def get_button(self, identity: Union[str, int], *, search_by: Literal['name', 'emoji', 'type'] = ...) -> List[ReactionButton]:
"""Get a button that has been registered to the menu by name, emoji, or type
Parameters
----------
identity: :class:`str`
The button name, emoji, or type
search_by: :class:`str`
How to search for the button. If "name", it's searched by button names. If "emoji", it's searched by it's emojis.
If "type", it's searched by :attr:`ReactionMenu.Type`, aka the `linked_to` of the button
Returns
-------
List[:class:`ReactionButton`]: The button(s) matching the given identity
Raises
------
- `ReactionMenuException`: Parameter :param:`search_by` was not "name", "emoji", or "type"
"""
...
@ensure_not_primed
def add_button(self, button: ReactionButton) -> None:
"""Add a button to the menu
Parameters
----------
button: :class:`ReactionButton`
The button to add
Raises
------
- `MenuAlreadyRunning`: Attempted to call this method after the menu has started
- `MissingSetting`: Set the buttons `linked_to` as :attr:`ReactionButton.Type.CALLER` but did not assign the :class:`ReactionButton` kwarg "details" a value
- `DuplicateButton`: The emoji used is already registered as a button
- `TooManyButtons`: More than 20 buttons were added. Discord has a reaction limit of 20
- `IncorrectType`: Parameter :param:`button` was not of type :class:`ReactionButton`
- `ReactionMenuException`: When attempting to add a button with the type `ReactionButton.Type.SKIP`, the "skip" kwarg was not set
- `MenuSettingsMismatch`: A :class:`ReactionButton` with a `linked_to` of :attr:`ReactionButton.Type.CUSTOM_EMBED` cannot be used when the `menu_type` is `TypeText`
"""
...
@ensure_not_primed
def add_buttons(self, buttons: Sequence[ReactionButton]) -> None:
"""Add multiple buttons to the menu at once
Parameters
----------
buttons: Sequence[:class:`ReactionButton`]
The buttons to add
Raises
------
- `MenuAlreadyRunning`: Attempted to call this method after the menu has started
- `MissingSetting`: Set the buttons `linked_to` as :attr:`ReactionButton.Type.CALLER` but did not assign the :class:`ReactionButton` kwarg "details" a value
- `DuplicateButton`: The emoji used is already registered as a button
- `TooManyButtons`: More than 20 buttons were added. Discord has a reaction limit of 20
- `IncorrectType`: Parameter :param:`button` was not of type :class:`ReactionButton`
- `ReactionMenuException`: When attempting to add a button with the type `ReactionButton.Type.SKIP`, the "skip" kwarg was not set
- `MenuSettingsMismatch`: A :class:`ReactionButton` with a `linked_to` of :attr:`ReactionButton.Type.CUSTOM_EMBED` cannot be used when the `menu_type` is `TypeText`
"""
...
@ensure_not_primed
def remove_button(self, button: ReactionButton) -> None:
"""Remove a button from the menu
Parameters
----------
button: :class:`ReactionButton`
The button to remove
Raises
------
- `MenuAlreadyRunning`: Attempted to call this method after the menu has started
- `ButtonNotFound`: The provided button was not found in the list of buttons on the menu
"""
...
@ensure_not_primed
def remove_all_buttons(self) -> None:
"""Remove all buttons from the menu
Raises
------
- `MenuAlreadyRunning`: Attempted to call this method after the menu has started
"""
...
async def stop(self, *, delete_menu_message: bool = ..., clear_reactions: bool = ...) -> None:
"""|coro|
Stops the process of the menu with the option of deleting the menu's message or clearing reactions upon stop
Parameters
----------
delete_menu_message: :class:`bool`
Delete the menu message
clear_reactions: :class:`bool`
Remove all reactions
Raises
------
- `discord.DiscordException`: Any exception that can be raised when deleting a message or removing a reaction from a message
"""
...
@overload
async def start(self, *, send_to: Optional[str] = ..., reply: bool = ...) -> None:
...
@overload
async def start(self, *, send_to: Optional[int] = ..., reply: bool = ...) -> None:
...
@overload
async def start(self, *, send_to: Optional[discord.TextChannel] = ..., reply: bool = ...) -> None:
...
@overload
async def start(self, *, send_to: Optional[discord.VoiceChannel] = ..., reply: bool = ...) -> None:
...
@overload
async def start(self, *, send_to: Optional[discord.Thread] = ..., reply: bool = ...) -> None:
...
@ensure_not_primed
async def start(self, *, send_to: Optional[Union[str, int, discord.TextChannel, discord.VoiceChannel, discord.Thread]] = ..., reply: bool = ...) -> None:
"""|coro|
Start the menu
Parameters
----------
send_to: Optional[Union[:class:`str`, :class:`int`, :class:`discord.TextChannel`, :class:`discord.VoiceChannel`, :class:`discord.Thread`]]
The channel/thread you'd like the menu to start in. Use the channel/threads name, ID, or it's object. Please note that if you intend to use a channel/thread object, using
method :meth:`discord.Client.get_channel()` (or any other related methods), that channel should be in the same list as if you were to use `ctx.guild.text_channels`
or `ctx.guild.threads`. This only works on a context guild channel basis. That means a menu instance cannot be created in one guild and the menu itself (:param:`send_to`)
be sent to another. Whichever guild context the menu was instantiated in, the channels/threads of that guild are the only options for :param:`send_to`
reply: :class:`bool`
Enables the menu message to reply to the message that triggered it. Parameter :param:`send_to` must be :class:`None` if this is `True`. This only pertains to a non-interaction based menu.
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `NoPages`: The menu was started when no pages have been added
- `NoButtons`: Attempted to start the menu when no Buttons have been registered
- `ReactionMenuException`: The :class:`ReactionMenu`'s `menu_type` was not recognized
- `DescriptionOversized`: When using a `menu_type` of :attr:`ReactionMenu.TypeEmbedDynamic`, the embed description was over discords size limit
- `IncorrectType`: Parameter :param:`send_to` was not of the expected type
- `MenuException`: The channel set in :param:`send_to` was not found
"""
...

View file

@ -0,0 +1,31 @@
"""
This type stub file was generated by pyright.
"""
"""
MIT License
Copyright (c) 2021-present @defxult
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.
"""
def ensure_not_primed(func): # -> _Wrapped[Callable[..., Any], Any, Callable[..., Any], Coroutine[Any, Any, Any]] | _Wrapped[Callable[..., Any], Any, Callable[..., Any], Any]:
"""Check to make sure certain methods cannot be ran once the menu has been fully started"""
...

View file

@ -0,0 +1,122 @@
"""
This type stub file was generated by pyright.
"""
"""
MIT License
Copyright (c) 2021-present @defxult
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.
"""
class MenuException(Exception):
"""Base exception for all menu's"""
...
class ViewMenuException(MenuException):
"""Base :class:`ViewMenu` exception"""
...
class ReactionMenuException(MenuException):
"""Base :class:`ReactionMenu` exception"""
...
class IncorrectType(MenuException):
"""Raised when the expected type was not given"""
def __init__(self, message: str) -> None:
...
class NoButtons(MenuException):
"""Raised when the menu was started but no buttons were registered or the action initiated requires buttons to be registered"""
def __init__(self, message: str = ...) -> None:
...
class InvalidPage(MenuException):
"""Raised when the page selected to remove does not exist"""
...
class DescriptionOversized(MenuException):
"""Used for `TypeEmbedDynamic` menus. The embed description of the menu has a character count > 4096"""
...
class MenuSettingsMismatch(MenuException):
"""Used settings for a specific `menu_type` but different/unrecognized values for those settings were given"""
...
class DuplicateButton(MenuException):
"""The emoji selected as a button has already been registered"""
...
class ImproperStyleFormat(MenuException):
"""The custom style selected by the user did not meet formatting requirements"""
def __init__(self) -> None:
...
class TooManyButtons(MenuException):
"""The amount of buttons registered is > 20 (over discords reaction limit)"""
def __init__(self, message: str = ...) -> None:
...
class MissingSetting(MenuException):
"""Raised when an action requires specific input from the user"""
...
class NoPages(MenuException):
"""Tried to start the menu when they haven't added any pages"""
def __init__(self, message: str = ...) -> None:
...
class MenuAlreadyRunning(MenuException):
"""Called a method that is not allowed to be called after the menu has started"""
def __init__(self, message: str) -> None:
...
class ButtonNotFound(MenuException):
"""Raised when :meth:`.remove_button()` did not find any matching buttons"""
...
class SelectNotFound(MenuException):
"""Raised when :meth:`.remove_select()` did not find any matching selects
.. added:: v3.1.0
"""
...

View file

@ -0,0 +1,714 @@
"""
This type stub file was generated by pyright.
"""
import discord
from typing import Callable, Dict, List, Literal, NamedTuple, Optional, Sequence, TYPE_CHECKING, Union, overload
from .abc import MenuType, Page, _BaseMenu
from discord.ext.commands import Context
from . import ViewButton
from .decorators import ensure_not_primed
from .errors import *
"""
MIT License
Copyright (c) 2021-present @defxult
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.
"""
if TYPE_CHECKING:
...
_SelectOptionRelayPayload = ...
class ViewSelect(discord.ui.Select):
"""A class to assist in the process of categorizing information on a :class:`ViewMenu`
Parameters
----------
title: Union[:class:`str`, `None`]
The category name. If `None`, the category name defaults to "Select a category"
options: Dict[:class:`discord.SelectOption`, List[:class:`Page`]]
A key/value pair associating the category options with pages to navigate
disabled: :class:`bool`
If the select should start enabled or disabled
.. added:: v3.1.0
"""
def __init__(self, *, title: Union[str, None], options: Dict[discord.SelectOption, List[Page]], disabled: bool = ...) -> None:
...
def __repr__(self) -> str:
...
@property
def menu(self) -> Optional[ViewMenu]:
"""
Returns
-------
Optional[:class:`ViewMenu`]: The menu this select is attached to. Can be `None` if not attached to any menu
"""
...
async def callback(self, interaction: discord.Interaction) -> None:
"""*INTERNAL USE ONLY* - The callback function from the select interaction. This should not be manually called"""
...
class GoTo(discord.ui.Select):
"""Represents a UI based version of a :class:`ViewButton` with the ID `ViewButton.ID_GO_TO_PAGE`
Parameters
----------
title: Union[:class:`str`, `None`]
The selects name. If `None`, the name defaults to "Navigate to page..."
page_numbers: This parameter accepts 3 different types and are explained below
- List[:class:`int`]
- If set to a list of integers, those specified values are the only options that are available when the select is clicked
- Dict[:class:`int`, Union[:class:`str`, :class:`discord.Emoji`, :class:`discord.PartialEmoji`]]
- You can use this type if you'd like to utilize emojis in your select options
- `ellipsis`
- Set an ellipsis to have the library automatically assign all page numbers to the amount of pages that you've added to the menu.
NOTE: Setting the `page_numbers` parameter to an ellipsis (...) only works as intended if you've added the go to select AFTER you've added pages to the menu
.. added:: v3.1.0
"""
def __init__(self, *, title: Union[str, None], page_numbers: Union[List[int], Dict[int, Union[str, discord.Emoji, discord.PartialEmoji]], ellipsis]) -> None:
...
@property
def menu(self) -> Optional[ViewMenu]:
"""
Returns
-------
Optional[:class:`ViewMenu`]: The menu this select is attached to. Can be `None` if not attached to any menu
"""
...
class ViewMenu(_BaseMenu):
"""A class to create a discord pagination menu using :class:`discord.ui.View`
Parameters
----------
method: Union[:class:`discord.ext.commands.Context`, :class:`discord.Interaction`]
The Context object. You can get this using a command or if you're in a `discord.on_message` event. Also accepts interactions, typically received when using slash commands
menu_type: :class:`MenuType`
The configuration of the menu. Class variables :attr:`ViewMenu.TypeEmbed`, :attr:`ViewMenu.TypeEmbedDynamic`, or :attr:`ViewMenu.TypeText`
Kwargs
------
all_can_click: :class:`bool`
Sets if everyone is allowed to control when pages are 'turned' when buttons are pressed (defaults to `False`)
allowed_mentions: :class:`discord.AllowedMentions`
Controls the mentions being processed in the menu message (defaults to :class:`discord.AllowedMentions(everyone=False, users=True, roles=False, replied_user=True)`).
Not valid for `ViewMenu` with a `menu_type` of `TypeText`
custom_embed: :class:`discord.Embed`
Embed object to use when adding data with :meth:`ViewMenu.add_row()`. Used for styling purposes only (:attr:`ViewMenu.TypeEmbedDynamic` only/defaults to :class:`None`)
delete_interactions: :class:`bool`
Delete the prompt message by the bot and response message by the user when asked what page they would like to go to when using :attr:`ViewButton.ID_GO_TO_PAGE` (defaults to `True`)
delete_on_timeout: :class:`bool`
Delete the menu when it times out (defaults to `False`) If `True`, :attr:`disable_items_on_timeout` and :attr:`remove_items_on_timeout` will not execute regardless of if they are `True`. This takes priority over those actions
disable_items_on_timeout: :class:`bool`
Disable the buttons on the menu when the menu times out (defaults to `True`) If :attr:`delete_on_timeout` is `True`, this will be overridden
name: :class:`str`
A name you can set for the menu (defaults to :class:`None`)
only_roles: List[:class:`discord.Role`]
If set, only members with any of the given roles are allowed to control the menu. The menu owner can always control the menu (defaults to :class:`None`)
remove_items_on_timeout: :class:`bool`
Remove the buttons on the menu when the menu times out (defaults to `False`) If :attr:`disable_items_on_timeout` is `True`, this will be overridden
rows_requested: :class:`int`
The amount of information per :meth:`ViewMenu.add_row()` you would like applied to each embed page (:attr:`ViewMenu.TypeEmbedDynamic` only/defaults to :class:`None`)
show_page_director: :class:`bool`
Shown at the bottom of each embed page. "Page 1/20" (defaults to `True`)
style: :class:`str`
A custom page director style you can select. "$" represents the current page, "&" represents the total amount of pages (defaults to "Page $/&") Example: `ViewMenu(ctx, ..., style='On $ out of &')`
timeout: Union[:class:`int`, :class:`float`, :class:`None`]
The timer for when the menu times out. Can be :class:`None` for no timeout (defaults to `60.0`)
wrap_in_codeblock: :class:`str`
The discord codeblock language identifier to wrap your data in (:attr:`ViewMenu.TypeEmbedDynamic` only/defaults to :class:`None`). Example: `ViewMenu(ctx, ..., wrap_in_codeblock='py')`
"""
_active_sessions: List[ViewMenu] = ...
def __init__(self, method: Union[Context, discord.Interaction], /, *, menu_type: MenuType, **kwargs) -> None:
...
def __repr__(self): # -> str:
...
@property
def selects(self) -> List[ViewSelect]:
"""
Returns
-------
List[:class:`ViewSelect`]: All selects that have been added to the menu
.. added:: v3.1.0
"""
...
@property
def go_to_selects(self) -> List[ViewSelect.GoTo]:
"""
Returns
-------
List[:class:`ViewSelect.GoTo`]: All go to selects that have been added to the menu
.. added:: v3.1.0
"""
...
@property
def timeout(self): # -> int | float | None:
...
@timeout.setter
def timeout(self, value) -> Union[int, float, None]:
"""A property getter/setter for kwarg `timeout`"""
...
@property
def buttons(self) -> List[ViewButton]:
"""
Returns
-------
List[:class:`ViewButton`]: All buttons that have been added to the menu
"""
...
@property
def buttons_most_clicked(self) -> List[ViewButton]:
"""
Returns
-------
List[:class:`ViewButton`]: The list of buttons on the menu ordered from highest (button with the most clicks) to lowest (button with the least clicks). Can be an empty list if there are no buttons registered to the menu
"""
...
@classmethod
async def quick_start(cls, method: Union[Context, discord.Interaction], /, pages: Sequence[Union[discord.Embed, str]], buttons: Optional[Sequence[ViewButton]] = ...) -> ViewMenu:
"""|coro class method|
Start a menu with default settings either with a `menu_type` of `ViewMenu.TypeEmbed` (all values in `pages` are of type `discord.Embed`) or `ViewMenu.TypeText` (all values in `pages` are of type `str`)
Parameters
----------
method: Union[:class:`discord.ext.commands.Context`, :class:`discord.Interaction`]
The Context object. You can get this using a command or if you're in a `discord.on_message` event. Also accepts interactions, typically received when using slash commands
pages: Sequence[Union[:class:`discord.Embed`, :class:`str`]]
The pages to add
buttons: Optional[Sequence[:class:`ViewButton`]]
The buttons to add. If left as `DEFAULT_BUTTONS`, that is equivalent to `ViewButton.all()`
Returns
-------
:class:`ViewMenu`: The menu that was started
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `NoPages`: The menu was started when no pages have been added
- `NoButtons`: Attempted to start the menu when no Buttons have been registered
- `IncorrectType`: All items in :param:`pages` were not of type :class:`discord.Embed` or :class:`str`
.. added v3.1.0
"""
...
def set_select_option_relay(self, func: Callable[[NamedTuple], None], *, only: Optional[Sequence[str]] = ...) -> None:
"""Set a function to be called with a given set of information when a select option is pressed on the menu. The information passed is `SelectOptionRelayPayload`, a named tuple.
The named tuple contains the following attributes:
- `member`: The :class:`discord.Member` object of the person who pressed the option. Could be :class:`discord.User` if the menu was started in a DM
- `option`: The :class:`discord.SelectOption` that was pressed
- `menu`: An instance of :class:`ViewMenu` that the select option is operating under
Parameters
----------
func: Callable[[:class:`NamedTuple`], :class:`None`]
The function should only contain a single positional argument. Command functions (`@bot.command()`) not supported
only: Optional[Sequence[:class:`str`]]
A sequence of :class:`discord.SelectOption` labels associated with the current menu instance. If this is :class:`None`, all select options on the menu will be relayed.
If set, only presses from the options matching the given labels specified will be relayed
Raises
------
- `IncorrectType`: The :param:`func` argument provided was not callable
.. added:: v3.1.0
"""
...
def remove_select_option_relay(self) -> None:
"""Remove the select option relay that's been set
.. added:: v3.1.0
"""
...
@ensure_not_primed
def add_select(self, select: ViewSelect) -> None:
"""Add a select category to the menu
Parameters
----------
select: :class:`ViewSelect`
The category listing to add
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `ViewMenuException`: The `menu_type` was not of :attr:`TypeEmbed`. The "embed" parameter in a :class:`Page` was not set. Or both :class:`ViewSelect` and a :class:`ViewSelect.GoTo` were being used
.. added:: v3.1.0
"""
...
def remove_select(self, select: ViewSelect) -> None:
"""Remove a select from the menu
Parameters
----------
select: :class:`ViewSelect`
The select to remove
Raises
------
- `SelectNotFound`: The provided select was not found in the list of selects on the menu
.. added:: v3.1.0
"""
...
def remove_all_selects(self) -> None:
"""Remove all selects from the menu
.. added:: v3.1.0
"""
...
def disable_select(self, select: ViewSelect) -> None:
"""Disable a select on the menu
Parameters
----------
select: :class:`ViewSelect`
The select to disable
Raises
------
- `SelectNotFound`: The provided select was not found in the list of selects on the menu
.. added:: v3.1.0
"""
...
def disable_all_selects(self) -> None:
"""Disable all selects on the menu
.. added:: v3.1.0
"""
...
def enable_select(self, select: ViewSelect) -> None:
"""Enable the specified select
Parameters
----------
select: :class:`ViewSelect`
The select to enable
Raises
------
- `SelectNotFound`: The provided select was not found in the list of selects on the menu
.. added:: v3.1.0
"""
...
def enable_all_selects(self) -> None:
"""Enable all selects on the menu
.. added:: v3.1.0
"""
...
@overload
def get_select(self, title: str) -> List[ViewSelect]:
...
@overload
def get_select(self, title: None) -> List[ViewSelect]:
...
def get_select(self, title: Union[str, None]) -> List[ViewSelect]:
"""Get a select by it's title (category name) that has been registered to the menu
Parameters
----------
title: Union[:class:`str`, `None`]
Title of the category
Returns
-------
List[:class:`ViewSelect`]: All selects matching the given title
"""
...
@ensure_not_primed
def add_go_to_select(self, goto: ViewSelect.GoTo) -> None:
"""Add a select where the user can choose which page they'd like to navigate to.
Parameters
----------
goto: :class:`ViewSelect.GoTo`
The navigation listing
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `ViewMenuException`: A :class:`ViewSelect` was already added to the menu. A :class:`ViewSelect` and a :class:`ViewSelect.GoTo` cannot both be used on a single menu
.. added:: v3.1.0
"""
...
def enable_go_to_select(self, goto: ViewSelect.GoTo) -> None:
"""Enable the specified go to select
Parameters
----------
goto: :class:`ViewSelect.GoTo`
The go to select to enable
.. added:: v3.1.0
"""
...
def enable_all_go_to_selects(self) -> None:
"""Enable all go to selects
.. added:: v3.1.0
"""
...
def disable_go_to_select(self, goto: ViewSelect.GoTo) -> None:
"""Disable the specified go to select
Parameters
----------
goto: :class:`ViewSelect.GoTo`
The go to select to disable
.. added:: v3.1.0
"""
...
def disable_all_go_to_selects(self) -> None:
"""Disable all go to selects
.. added:: v3.1.0
"""
...
def remove_go_to_select(self, goto: ViewSelect.GoTo) -> None:
"""Remove a go to select from the menu
Parameters
----------
goto: :class:`ViewSelect.GoTo`
The go to select to remove
Raises
------
- `SelectNotFound`: The provided go to select was not found in the list of selects on the menu
.. added:: v3.1.0
"""
...
def remove_all_go_to_selects(self) -> None:
"""Remove all go to selects from the menu
.. added:: v3.1.0
"""
...
async def update(self, *, new_pages: Union[List[Union[discord.Embed, str]], None], new_buttons: Union[List[ViewButton], None]) -> None:
"""|coro|
When the menu is running, update the pages or buttons. It should be noted that `ViewSelect`s are not supported here, and they will automatically be removed
once the menu is updated. This method only supports pages and buttons.
Parameters
----------
new_pages: List[Union[:class:`discord.Embed`, :class:`str`]]
Pages to *replace* the current pages with. If the menus current `menu_type` is :attr:`ViewMenu.TypeEmbed`, only :class:`discord.Embed` can be used. If :attr:`ViewMenu.TypeText`, only :class:`str` can be used. If you
don't want to replace any pages, set this to :class:`None`
new_buttons: List[:class:`ViewButton`]
Buttons to *replace* the current buttons with. Can be an empty list if you want the updated menu to have no buttons. Can also be set to :class:`None` if you don't want to replace any buttons
Raises
------
- `ViewMenuException`: The :class:`ViewButton` custom_id was not recognized or a :class:`ViewButton` with that ID has already been added
- `TooManyButtons`: There are already 25 buttons on the menu
- `IncorrectType`: The values in :param:`new_pages` did not match the :class:`ViewMenu`'s `menu_type`. An attempt to use this method when the `menu_type` is :attr:`ViewMenu.TypeEmbedDynamic` which is not allowed. Or
all :param:`new_buttons` values were not of type :class:`ViewButton`
"""
...
def randomize_button_styles(self) -> None:
"""Set all buttons currently registered to the menu to a random :class:`discord.ButtonStyle` excluding link buttons"""
...
def set_button_styles(self, style: discord.ButtonStyle) -> None:
"""Set all buttons currently registered to the menu to the specified :class:`discord.ButtonStyle` excluding link buttons
Parameters
----------
style: :class:`discord.ButtonStyle`
The button style to set
"""
...
async def refresh_menu_items(self) -> None:
"""|coro|
When the menu is running, update the message to reflect the buttons/selects that were removed, enabled, or disabled
"""
...
def remove_button(self, button: ViewButton) -> None:
"""Remove a button from the menu
Parameters
----------
button: :class:`ViewButton`
The button to remove
Raises
------
- `ButtonNotFound`: The provided button was not found in the list of buttons on the menu
"""
...
def remove_all_buttons(self) -> None:
"""Remove all buttons from the menu"""
...
def disable_button(self, button: ViewButton) -> None:
"""Disable a button on the menu
Parameters
----------
button: :class:`ViewButton`
The button to disable
Raises
------
- `ButtonNotFound`: The provided button was not found in the list of buttons on the menu
"""
...
def disable_all_buttons(self) -> None:
"""Disable all buttons on the menu"""
...
def enable_button(self, button: ViewButton) -> None:
"""Enable the specified button
Parameters
----------
button: :class:`ViewButton`
The button to enable
Raises
------
- `ButtonNotFound`: The provided button was not found in the list of buttons on the menu
"""
...
def enable_all_buttons(self) -> None:
"""Enable all buttons on the menu"""
...
@ensure_not_primed
def add_button(self, button: ViewButton) -> None:
"""Add a button to the menu
Parameters
----------
button: :class:`ViewButton`
The button to add
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `MenuSettingsMismatch`: The buttons custom_id was set as :attr:`ViewButton.ID_CUSTOM_EMBED` but the `menu_type` is :attr:`ViewMenu.TypeText`
- `ViewMenuException`: The custom_id for the button was not recognized or a button with that custom_id has already been added
- `TooManyButtons`: There are already 25 buttons on the menu
- `IncorrectType`: Parameter :param:`button` was not of type :class:`ViewButton`
"""
...
@ensure_not_primed
def add_buttons(self, buttons: Sequence[ViewButton]) -> None:
"""Add multiple buttons to the menu at once
Parameters
----------
buttons: Sequence[:class:`ViewButton`]
The buttons to add
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `MenuSettingsMismatch`: One of the buttons `custom_id` was set as :attr:`ViewButton.ID_CUSTOM_EMBED` but the `menu_type` is :attr:`ViewMenu.TypeText`
- `ViewMenuException`: The `custom_id` for a button was not recognized or a button with that `custom_id` has already been added
- `TooManyButtons`: There are already 25 buttons on the menu
- `IncorrectType`: One or more values supplied in parameter :param:`buttons` was not of type :class:`ViewButton`
"""
...
def get_button(self, identity: str, *, search_by: Literal['label', 'id', 'name'] = ...) -> List[ViewButton]:
"""Get a button that has been registered to the menu by it's label, custom_id, or name
Parameters
----------
identity: :class:`str`
The button label, custom_id, or name
search_by: :class:`str`
How to search for the button. If "label", it's searched by button labels. If "id", it's searched by it's custom_id.
If "name", it's searched by button names
Returns
-------
List[:class:`ViewButton`]: The button(s) matching the given identity
Raises
------
- `ViewMenuException`: Parameter :param:`search_by` was not "label", "id", or "name"
"""
...
async def stop(self, *, delete_menu_message: bool = ..., disable_items: bool = ..., remove_items: bool = ...) -> None:
"""|coro|
Stops the process of the menu with the option of deleting the menu's message, removing the buttons, or disabling the buttons upon stop
Parameters
----------
delete_menu_message: :class:`bool`
Delete the message the menu is operating from
disable_items: :class:`bool`
Disable the buttons & selects on the menu
remove_items: :class:`bool`
Remove the buttons & selects from the menu
Parameter Hierarchy
-------------------
Only one option is available when stopping the menu. If you have multiple parameters as `True`, only one will execute
- `disable_items` > `remove_items`
Raises
------
- `discord.DiscordException`: Any exception that can be raised when deleting or editing a message
"""
...
@overload
async def start(self, *, send_to: Optional[str] = ..., reply: bool = ...) -> None:
...
@overload
async def start(self, *, send_to: Optional[int] = ..., reply: bool = ...) -> None:
...
@overload
async def start(self, *, send_to: Optional[discord.TextChannel] = ..., reply: bool = ...) -> None:
...
@overload
async def start(self, *, send_to: Optional[discord.VoiceChannel] = ..., reply: bool = ...) -> None:
...
@overload
async def start(self, *, send_to: Optional[discord.Thread] = ..., reply: bool = ...) -> None:
...
@ensure_not_primed
async def start(self, *, send_to: Optional[Union[str, int, discord.TextChannel, discord.VoiceChannel, discord.Thread]] = ..., reply: bool = ...) -> None:
"""|coro|
Start the menu
Parameters
----------
send_to: Optional[Union[:class:`str`, :class:`int`, :class:`discord.TextChannel`, :class:`discord.VoiceChannel`, :class:`discord.Thread`]]
The channel/thread you'd like the menu to start in. Use the channel/threads name, ID, or it's object. Please note that if you intend to use a channel/thread object, using
method :meth:`discord.Client.get_channel()` (or any other related methods), that channel should be in the same list as if you were to use `ctx.guild.text_channels`
or `ctx.guild.threads`. This only works on a context guild channel basis. That means a menu instance cannot be created in one guild and the menu itself (:param:`send_to`)
be sent to another. Whichever guild context the menu was instantiated in, the channels/threads of that guild are the only options for :param:`send_to`
Note: This parameter is not available if your `method` is a :class:`discord.Interaction`, aka a slash command
reply: :class:`bool`
Enables the menu message to reply to the message that triggered it. Parameter :param:`send_to` must be :class:`None` if this is `True`. This only pertains to a non-interaction based menu.
Raises
------
- `MenuAlreadyRunning`: Attempted to call method after the menu has already started
- `NoPages`: The menu was started when no pages have been added
- `NoButtons`: Attempted to start the menu when no Buttons have been registered
- `ViewMenuException`: The :class:`ViewMenu`'s `menu_type` was not recognized. There were more than one base navigation buttons. Or a :attr:`ViewButton.ID_CUSTOM_EMBED` button was not correctly formatted
- `DescriptionOversized`: When using a `menu_type` of :attr:`ViewMenu.TypeEmbedDynamic`, the embed description was over discords size limit
- `IncorrectType`: Parameter :param:`send_to` was not of the expected type
- `MenuException`: The channel set in :param:`send_to` was not found
"""
...