diff --git a/flask_discord_interactions/client.py b/flask_discord_interactions/client.py index 896c7c5..c035204 100644 --- a/flask_discord_interactions/client.py +++ b/flask_discord_interactions/client.py @@ -79,7 +79,7 @@ def run(self, *names, **params): command.run(self.current_context, self.current_context.target) ) - def run_handler(self, custom_id, *args): + def run_handler(self, custom_id: str, *args): """ Run a specified custom ID handler. diff --git a/flask_discord_interactions/command.py b/flask_discord_interactions/command.py index 2646379..5b71d16 100644 --- a/flask_discord_interactions/command.py +++ b/flask_discord_interactions/command.py @@ -2,7 +2,10 @@ import enum import inspect import itertools -import warnings + +from typing import Callable, List, Dict, TYPE_CHECKING + +from flask import Flask from flask_discord_interactions.context import Context, AsyncContext from flask_discord_interactions.models import ( @@ -19,6 +22,9 @@ Attachment, ) +if TYPE_CHECKING: + from flask_discord_interactions.discord import DiscordInteractions + _type = type @@ -28,57 +34,57 @@ class Command: Attributes ---------- - command + command: Callable Function to call when the command is invoked. - name + name: str Name for this command (appears in the Discord client). If omitted, infers the name based on the name of the function. - name_localizations + name_localizations: Dict[str, str] Localization dictionary for name field. - description + description: str Description for this command (appears in the Discord client). If omitted, infers the description based on the docstring of the function, or sets the description to "No description", if ``ApplicationCommandType`` is ``CHAT_INPUT``, else set description to ``None``. - description_localizations + description_localizations: Dict[str, str] Localization dictionary for description field. - options + options: List[Option] Array of options that can be passed to this command. If omitted, infers the options based on the function parameters and type annotations. - annotations + annotations: Dict[str, str] Dictionary of descriptions for each option provided. Use this only if you want the options to be inferred from the parameters and type annotations. Do not use with ``options``. If omitted, and if ``options`` is not provided, option descriptions default to "No description". - type + type: int Type for this command (depend on the action in the Discord client). The value is in ``ApplicationCommandType``. If omitted, set the default value to ``ApplicationCommandType.CHAT_INPUT``. - default_member_permissions + default_member_permissions: int A permission integer defining the required permissions a user must have to run the command - dm_permission + dm_permission: bool Indicates whether the command can be used in DMs - discord + discord: DiscordInteractions DiscordInteractionsBlueprint instance which this Command is associated with. """ def __init__( self, - command, - name, - description, + command: Callable, + name: str, + description: str, *, - options, - annotations, - type=ApplicationCommandType.CHAT_INPUT, - default_member_permissions=None, - dm_permission=None, - name_localizations=None, - description_localizations=None, - discord=None, + options: List[Option], + annotations: Dict[str, str], + type: int = ApplicationCommandType.CHAT_INPUT, + default_member_permissions: int = None, + dm_permission: bool = None, + name_localizations: Dict[str, str] = None, + description_localizations: Dict[str, str] = None, + discord: "DiscordInteractions" = None, ): self.command = command self.name = name @@ -201,19 +207,21 @@ def __init__( self.options.append(option) - def make_context_and_run(self, *, discord, app, data): + def make_context_and_run( + self, *, discord: "DiscordInteractions", app: Flask, data: dict + ): """ Creates the :class:`Context` object for an invocation of this command, then invokes itself. Parameters ---------- - discord + discord: DiscordInteractions The :class:`DiscordInteractions` object used to receive this interaction. - app + app: Flask The Flask app used to receive this interaction. - data + data: dict The incoming interaction data. Returns @@ -235,13 +243,13 @@ def make_context_and_run(self, *, discord, app, data): else: return Message.from_return_value(result) - def run(self, context, *args, **kwargs): + def run(self, context: Context, *args, **kwargs): """ Invokes the function defining this command. Parameters ---------- - context + context: Context The :class:`Context` object representing the current state. *args Any subcommands of the current command being called. @@ -300,27 +308,27 @@ class SlashCommandSubgroup(Command): Attributes ---------- - name + name: str The name of this subgroup, shown in the Discord client. - name_localizations + name_localizations: Dict[str, str] A dict of localized names for this subgroup. - description + description: str The description of this subgroup, shown in the Discord client. - description_localizations + description_localizations: Dict[str, str] A dict of localized descriptions for this subgroup. - is_async + is_async: bool Whether the subgroup should be considered async (if subcommands get an :class:`AsyncContext` instead of a :class:`Context`.) """ def __init__( self, - name, - description, + name: str, + description: str, *, - name_localizations=None, - description_localizations=None, - is_async=False, + name_localizations: Dict[str, str] = None, + description_localizations: Dict[str, str] = None, + is_async: bool = False, ): self.name = name self.description = description @@ -336,31 +344,31 @@ def __init__( def command( self, - name=None, - description=None, + name: str = None, + description: str = None, *, - name_localizations=None, - description_localizations=None, - options=None, - annotations=None, + name_localizations: Dict[str, str] = None, + description_localizations: Dict[str, str] = None, + options: List[Option] = None, + annotations: Dict[str, str] = None, ): """ Decorator to create a new Subcommand of this Subgroup. Parameters ---------- - name + name: str The name of the command, as displayed in the Discord client. - name_localizations + name_localizations: Dict[str, str] A dict of localized names for the command. - description + description: str The description of the command. - description_localizations + description_localizations: Dict[str, str] A dict of localized descriptions for the command. - options + options: List[Option] A list of options for the command, overriding the function's keyword arguments. - annotations + annotations: Dict[str, str] If ``options`` is not provided, descriptions for each of the options defined in the function's keyword arguments. """ @@ -385,6 +393,11 @@ def options(self): """ Returns an array of options that can be passed to this command. Computed based on the options of each subcommand or subcommand group. + + Returns + ------- + List[Option] + The options for this command. """ options = [] for command in self.subcommands.values(): @@ -403,7 +416,7 @@ def run(self, context, *subcommands, **kwargs): Parameters ---------- - context + context: Context The :class:`Context` object representing the current state. *args List of subcommands of the current command group being invoked. @@ -419,20 +432,20 @@ class SlashCommandGroup(SlashCommandSubgroup): Attributes ---------- - name + name: str The name of this subgroup, shown in the Discord client. - name_localizations + name_localizations: Dict[str, str] A dict of localized names for this subgroup. - description + description: str The description of this subgroup, shown in the Discord client. - description_localizations + description_localizations: Dict[str, str] A dict of localized descriptions for this subgroup. - is_async + is_async: bool Whether the subgroup should be considered async (if subcommands get an :class:`AsyncContext` instead of a :class:`Context`.) - default_member_permissions: + default_member_permissions: int Permission integer setting permission defaults for a command - dm_permission + dm_permission: int Indicates whether the command can be used in DMs """ @@ -461,12 +474,12 @@ def __init__( def subgroup( self, - name, - description="No description", + name: str, + description: str = "No description", *, - name_localizations=None, - description_localizations=None, - is_async=False, + name_localizations: Dict[str, str] = None, + description_localizations: Dict[str, str] = None, + is_async: bool = False, ): """ Create a new :class:`SlashCommandSubroup` @@ -474,15 +487,15 @@ def subgroup( Parameters ---------- - name + name: str The name of the subgroup, as displayed in the Discord client. - name_localizations + name_localizations: Dict[str, str] A dict of localized names for the subgroup. - description + description: str The description of the subgroup. Defaults to "No description". - description_localizations + description_localizations: Dict[str, str] A dict of localized descriptions for the subgroup. - is_async + is_async: bool Whether the subgroup should be considered async (if subcommands get an :class:`AsyncContext` instead of a :class:`Context`.) """ diff --git a/flask_discord_interactions/context.py b/flask_discord_interactions/context.py index f7116b4..829549a 100644 --- a/flask_discord_interactions/context.py +++ b/flask_discord_interactions/context.py @@ -1,11 +1,11 @@ from dataclasses import dataclass -from typing import Any, List, Optional, Union +from typing import Callable, List, Optional, Union, TYPE_CHECKING import inspect import itertools -import warnings import types import requests +from flask import Flask from flask_discord_interactions.models import ( LoadableDataclass, @@ -21,6 +21,9 @@ Option, ) +if TYPE_CHECKING: + from flask_discord_interactions.discord import DiscordInteractions + @dataclass class Context(LoadableDataclass): @@ -99,8 +102,8 @@ class Context(LoadableDataclass): locale: Optional[str] = None guild_locale: Optional[str] = None app_permissions: Optional[str] = None - app: Any = None - discord: Any = None + app: Flask = None + discord: "DiscordInteractions" = None custom_id: str = None primary_id: str = None @@ -110,7 +113,9 @@ class Context(LoadableDataclass): target: Union[User, Message] = None @classmethod - def from_data(cls, discord=None, app=None, data={}): + def from_data( + cls, discord: "DiscordInteractions" = None, app: Flask = None, data={} + ): if data is None: data = {} @@ -159,7 +164,7 @@ def auth_headers(self): else: return self.frozen_auth_headers - def parse_author(self, data): + def parse_author(self, data: dict): """ Parse the author (invoking user) of this interaction. @@ -179,7 +184,7 @@ def parse_author(self, data): else: self.author = None - def parse_message(self, data): + def parse_message(self, data: dict): """ Parse the message out of in interaction. @@ -334,14 +339,14 @@ def create_args_recursive(data, resolved): return create_args_recursive({"options": self.options}, self.resolved) - def create_handler_args(self, handler): + def create_handler_args(self, handler: Callable): """ Create the arguments which will be passed to the function when a custom ID handler is invoked. Parameters ---------- - handler + handler: Callable The custom ID handler to create arguments for. """ @@ -376,14 +381,14 @@ def create_handler_args(self, handler): def create_autocomplete_args(self): return [Option.from_data(option) for option in self.options] - def followup_url(self, message=None): + def followup_url(self, message: str = None): """ Return the followup URL for this interaction. This URL can be used to send a new message, or to edit or delete an existing message. Parameters ---------- - message + message: str The ID of the message to edit or delete. If None, sends a new message. If "@original", refers to the original message. @@ -398,15 +403,15 @@ def followup_url(self, message=None): return url - def edit(self, updated, message="@original"): + def edit(self, updated: Union[Message, str], message: str = "@original"): """ Edit an existing message. Parameters ---------- - updated + updated: Union[Message, str] The updated Message to edit the message to. - message + message: str The ID of the message to edit. If omitted, edits the original message. """ @@ -424,13 +429,13 @@ def edit(self, updated, message="@original"): ) updated.raise_for_status() - def delete(self, message="@original"): + def delete(self, message: str = "@original"): """ Delete an existing message. Parameters ---------- - message + message: str The ID of the message to delete. If omitted, deletes the original message. """ @@ -441,13 +446,13 @@ def delete(self, message="@original"): response = requests.delete(self.followup_url(message)) response.raise_for_status() - def send(self, message): + def send(self, message: Union[Message, str]): """ Send a new followup message. Parameters ---------- - message + message: Union[Message, str] The :class:`Message` to send as a followup message. """ @@ -463,8 +468,15 @@ def send(self, message): message.raise_for_status() return message.json()["id"] - def get_command(self, command_name=None): - "Get the ID of a command by name." + def get_command(self, command_name: str = None): + """ + Get the ID of a command by name. + + Parameters + ---------- + command_name: str + The name of the command to get the ID of. + """ if command_name is None: return self.command_id else: @@ -492,8 +504,15 @@ def freeze(self): return new_context def get_component(self, component_id: str): - """Get a Component, only available for Modal Contexts. - If the component was not found, raises a LookupError.""" + """ + Get a Component, only available for Modal Contexts. + If the component was not found, raises a LookupError. + + Parameters + ---------- + component_id: str + The ID of the component to look up. + """ if not self.components: raise ValueError("This Context does not have any components.") for action_row in self.components: @@ -518,15 +537,15 @@ def __post_init__(self): self.session = self.app.discord_client_session - async def edit(self, updated, message="@original"): + async def edit(self, updated: Union[str, Message], message: str = "@original"): """ Edit an existing message. Parameters ---------- - updated + updated: Union[str, Message] The updated Message to edit the message to. - message + message: str The ID of the message to edit. If omitted, edits the original message. """ @@ -543,13 +562,13 @@ async def edit(self, updated, message="@original"): headers={"Content-Type": mimetype}, ) - async def delete(self, message="@original"): + async def delete(self, message: str = "@original"): """ Delete an existing message. Parameters ---------- - message + message: str The ID of the message to delete. If omitted, deletes the original message. """ @@ -559,13 +578,13 @@ async def delete(self, message="@original"): await self.session.delete(self.followup_url(message)) - async def send(self, message): + async def send(self, message: Union[Message, str]): """ Send a new followup message. Parameters ---------- - message + message: Union[Message, str] The Message object to send as a followup message. """ @@ -581,32 +600,3 @@ async def send(self, message): headers={"Content-Type": mimetype}, ) as message: return (await message.json())["id"] - - async def overwrite_permissions(self, permissions, command=None): - """ - Overwrite the permission overwrites for this command. - - Parameters - ---------- - permissions - The new list of permission overwrites. - command - The name of the command to overwrite permissions for. If omitted, - overwrites for the invoking command. - """ - - url = ( - f"{self.app.config['DISCORD_BASE_URL']}/" - f"applications/{self.app.config['DISCORD_CLIENT_ID']}/" - f"guilds/{self.guild_id}/" - f"commands/{self.get_command(command)}/permissions" - ) - - data = [permission.dump() for permission in permissions] - - if not self.app or self.app.config["DONT_REGISTER_WITH_DISCORD"]: - return - - await self.session.put( - url, headers=self.auth_headers, json={"permissions": data} - ) diff --git a/flask_discord_interactions/discord.py b/flask_discord_interactions/discord.py index 7c99e38..bee92df 100644 --- a/flask_discord_interactions/discord.py +++ b/flask_discord_interactions/discord.py @@ -1,5 +1,6 @@ import time import inspect +from typing import Callable, Dict, List import uuid import atexit import asyncio @@ -7,12 +8,13 @@ import requests -from flask import Response, current_app, request, jsonify, abort +from flask import Flask, Response, current_app, request, jsonify, abort from nacl.exceptions import BadSignatureError from nacl.signing import VerifyKey from flask_discord_interactions.models.autocomplete import AutocompleteResult +from flask_discord_interactions.models.option import Option try: import aiohttp @@ -47,45 +49,45 @@ def __init__(self): def add_command( self, - command, - name=None, - description=None, + command: Callable, + name: str = None, + description: str = None, *, - options=None, - annotations=None, - type=ApplicationCommandType.CHAT_INPUT, - default_member_permissions=None, - dm_permission=None, - name_localizations=None, - description_localizations=None, + options: List[Option] = None, + annotations: Dict[str, str] = None, + type: int = ApplicationCommandType.CHAT_INPUT, + default_member_permissions: int = None, + dm_permission: bool = None, + name_localizations: Dict[str, str] = None, + description_localizations: Dict[str, str] = None, ): """ Create and add a new :class:`ApplicationCommand`. Parameters ---------- - command + command: Callable Function to execute when the command is run. - name + name: str The name of the command, as displayed in the Discord client. - name_localizations + name_localizations: Dict[str, str] A dictionary of localizations for the name of the command. - description + description: str The description of the command. - description_localizations + description_localizations: Dict[str, str] A dictionary of localizations for the description of the command. - options + options: List[Option] A list of options for the command, overriding the function's keyword arguments. - annotations + annotations: Dict[str, str] If ``options`` is not provided, descriptions for each of the options defined in the function's keyword arguments. - type - The ``ApplicationCommandType`` of the command. - default_member_permissions - A permission integer defining the required permissions a user must have to run the command - dm_permission - Indicates whether the command can be used in DMs + type: int + The class:`.ApplicationCommandType` of the command. + default_member_permissions: int + A permission integer defining the required permissions a user must have to run the command. + dm_permission: bool + Indicates whether the command can be used in DMs. """ command = Command( command=command, @@ -105,42 +107,46 @@ def add_command( def command( self, - name=None, - description=None, + name: str = None, + description: str = None, *, - options=None, - annotations=None, - type=ApplicationCommandType.CHAT_INPUT, - default_member_permissions=None, - dm_permission=None, - name_localizations=None, - description_localizations=None, + options: List[Option] = None, + annotations: Dict[str, str] = None, + type: int = ApplicationCommandType.CHAT_INPUT, + default_member_permissions: int = None, + dm_permission: bool = None, + name_localizations: Dict[str, str] = None, + description_localizations: Dict[str, str] = None, ): """ Decorator to create a new :class:`Command`. Parameters ---------- - name + name: str The name of the command, as displayed in the Discord client. - name_localizations + name_localizations: Dict[str, str] A dictionary of localizations for the name of the command. - description + description: str The description of the command. - description_localizations + description_localizations: Dict[str, str] A dictionary of localizations for the description of the command. - options + options: List[Option] A list of options for the command, overriding the function's keyword arguments. - annotations + annotations: Dict[str, str] If ``options`` is not provided, descriptions for each of the options defined in the function's keyword arguments. - type + type: int The ``ApplicationCommandType`` of the command. - default_member_permissions + default_member_permissions: int A permission integer defining the required permissions a user must have to run the command - dm_permission + dm_permission: bool Indicates whether the command can be used in DMs + + Returns + ------- + Callable[Callable, Command] """ def decorator(func): @@ -163,14 +169,14 @@ def decorator(func): def command_group( self, - name, - description="No description", + name: str, + description: str = "No description", *, - is_async=False, - default_member_permissions=None, - dm_permission=None, - name_localizations=None, - description_localizations=None, + is_async: bool = False, + default_member_permissions: int = None, + dm_permission: bool = None, + name_localizations: Dict[str, str] = None, + description_localizations: Dict[str, str] = None, ): """ Create a new :class:`SlashCommandGroup` @@ -178,21 +184,26 @@ def command_group( Parameters ---------- - name + name: str The name of the command group, as displayed in the Discord client. - name_localizations + name_localizations: Dict[str, str] A dictionary of localizations for the name of the command group. - description + description: str The description of the command group. - description_localizations + description_localizations: Dict[str, str] A dictionary of localizations for the description of the command group. - is_async + is_async: bool Whether the subgroup should be considered async (if subcommands get an :class:`.AsyncContext` instead of a :class:`Context`.) - default_member_permissions + default_member_permissions: int A permission integer defining the required permissions a user must have to run the command - dm_permission + dm_permission: bool Indicates whether the command canbe used in DMs + + Returns + ------- + SlashCommandGroup + The newly created command group. """ group = SlashCommandGroup( @@ -207,15 +218,15 @@ def command_group( self.discord_commands[name] = group return group - def add_custom_handler(self, handler, custom_id=None): + def add_custom_handler(self, handler: Callable, custom_id: str = None): """ Add a handler for an incoming interaction with the specified custom ID. Parameters ---------- - handler + handler: Callable The function to call to handle the incoming interaction. - custom_id + custom_id: str The custom ID to respond to. If not specified, the ID will be generated randomly. @@ -230,7 +241,7 @@ def add_custom_handler(self, handler, custom_id=None): self.custom_id_handlers[custom_id] = handler return custom_id - def custom_handler(self, custom_id=None): + def custom_handler(self, custom_id: str = None): """ Returns a decorator to register a handler for a custom ID. @@ -239,6 +250,10 @@ def custom_handler(self, custom_id=None): custom_id The custom ID to respond to. If not specified, the ID will be generated randomly. + + Returns + ------- + Callable[Callable, str] """ def decorator(func): @@ -248,15 +263,15 @@ def decorator(func): return decorator - def add_autocomplete_handler(self, handler, command_name): + def add_autocomplete_handler(self, handler: Callable, command_name: str): """ Add a handler for an incoming autocomplete request. Parameters ---------- - handler + handler: Callable The function to call to handle the incoming autocomplete request. - command_name + command_name: str The name of the command to autocomplete. """ self.autocomplete_handlers[command_name] = handler @@ -267,23 +282,28 @@ class DiscordInteractions(DiscordInteractionsBlueprint): Handles registering a collection of :class:`Command` s, receiving incoming interaction data, and sending/editing/deleting messages via webhook. + + Parameters + ---------- + app: Flask + The Flask application to bind to. """ - def __init__(self, app=None): + def __init__(self, app: Flask = None): super().__init__() self.app = app if app is not None: self.init_app(app) - def init_app(self, app): + def init_app(self, app: Flask): """ Initialize a Flask app with Discord-specific configuration and attributes. Parameters ---------- - app + app: Flask The Flask app to initialize. """ @@ -300,7 +320,7 @@ def init_app(self, app): app.discord_token = None @static_or_instance - def fetch_token(self, app=None): + def fetch_token(self, app: Flask = None): """ Fetch an OAuth2 token from Discord using the ``CLIENT_ID`` and ``CLIENT_SECRET`` with the ``applications.commands.update`` scope. This @@ -326,6 +346,7 @@ def fetch_token(self, app=None): time.time() + app.discord_token["expires_in"] / 2 ) return + response = requests.post( app.config["DISCORD_BASE_URL"] + "/oauth2/token", data={ @@ -343,22 +364,27 @@ def fetch_token(self, app=None): ) @staticmethod - def auth_headers(app): + def auth_headers(app: Flask): """ Get the Authorization header required for HTTP requests to the Discord API. Parameters ---------- - app + app: Flask The Flask app with the relevant access token. + + Returns + ------- + Dict[str, str] + The Authorization header. """ if app.discord_token is None or time.time() > app.discord_token["expires_on"]: DiscordInteractions.fetch_token(app) return {"Authorization": f"Bearer {app.discord_token['access_token']}"} - def update_commands(self, app=None, guild_id=None): + def update_commands(self, app: Flask = None, guild_id: str = None): """ Update the list of commands registered with Discord. This method will overwrite all existing commands. @@ -370,9 +396,9 @@ def update_commands(self, app=None, guild_id=None): Parameters ---------- - app + app: Flask The Flask app with the relevant Discord access token. - guild_id + guild_id: str The ID of the Discord guild to register commands to. If omitted, the commands are registered globally. """ @@ -416,31 +442,17 @@ def update_commands(self, app=None, guild_id=None): for command in app.discord_commands.values(): command.id = command.name - def update_slash_commands(self, *args, **kwargs): - """ - Deprecated! As of v1.1.0, ``update_slash_commands`` has been renamed to - ``update_commands``, as it updates User and Message commands as well. - """ - warnings.warn( - "Deprecated! As of v1.1.0, update_slash_commands has been renamed " - "to update_commands, as it updates User and Message commands too.", - DeprecationWarning, - stacklevel=2, - ) - - return self.update_commands(*args, **kwargs) - @staticmethod def build_permission_overwrite_url( self, - command=None, + command: Command = None, *, - guild_id, - command_id=None, - token=None, - app=None, - application_id=None, - base_url=None, + guild_id: str, + command_id: str = None, + token: str = None, + app: Flask = None, + application_id: str = None, + base_url: str = None, ): """ Build the URL for getting or setting permission overwrites for a @@ -480,14 +492,14 @@ def build_permission_overwrite_url( @static_or_instance def get_permission_overwrites( self, - command=None, + command: Command = None, *, - guild_id, - command_id=None, - token=None, - app=None, - application_id=None, - base_url=None, + guild_id: str, + command_id: str = None, + token: str = None, + app: Flask = None, + application_id: str = None, + base_url: str = None, ): """ Get the list of permission overwrites in a specific guild for a @@ -529,21 +541,21 @@ def get_permission_overwrites( Parameters ---------- - command + command: Command The :class:`.Command` to retrieve permissions for. - guild_id + guild_id: str The ID of the guild to retrieve permissions from. - command_id + command_id: str The ID of the command to retrieve permissions for. - token + token: str A bearer token from an admin of the guild (not including the leading ``Bearer`` word). If omitted, the bot's token will be used instead. - app + app: Flask The Flask app with the relevant Discord application ID. - application_id + application_id: str The ID of the Discord application to retrieve permissions from. - base_url + base_url: str The base URL of the Discord API. Returns @@ -574,15 +586,15 @@ def get_permission_overwrites( @static_or_instance def set_permission_overwrites( self, - permissions, - command=None, + permissions: List[Permission], + command: Command = None, *, - guild_id, - command_id=None, - token=None, - app=None, - application_id=None, - base_url=None, + guild_id: str, + command_id: str = None, + token: str = None, + app: Flask = None, + application_id: str = None, + base_url: str = None, ): """ Overwrite the list of permission overwrites in a specific guild for a @@ -595,21 +607,21 @@ def set_permission_overwrites( Parameters ---------- - command + command: Command The :class:`.Command` to retrieve permissions for. - guild_id + guild_id: str The ID of the guild to retrieve permissions from. - command_id + command_id: str The ID of the command to retrieve permissions for. - token + token: str A bearer token from an admin of the guild (not including the leading ``Bearer`` word). If omitted, the bot's token will be used instead. - app + app: Flask The Flask app with the relevant Discord application ID. - application_id + application_id: str The ID of the Discord application to retrieve permissions from. - base_url + base_url: str The base URL of the Discord API. """ @@ -631,7 +643,7 @@ def set_permission_overwrites( ) response.raise_for_status() - def throttle(self, response): + def throttle(self, response: requests.Response): """ Throttle the number of HTTP requests made to Discord using the ``X-RateLimit`` headers @@ -639,7 +651,7 @@ def throttle(self, response): Parameters ---------- - response + response: requests.Response Response object from a previous HTTP request """ @@ -655,7 +667,9 @@ def throttle(self, response): ) time.sleep(max(wait_time, 0)) - def register_blueprint(self, blueprint, app=None): + def register_blueprint( + self, blueprint: DiscordInteractionsBlueprint, app: Flask = None + ): """ Register a :class:`DiscordInteractionsBlueprint` to this DiscordInteractions class. Updates this instance's list of @@ -664,10 +678,10 @@ def register_blueprint(self, blueprint, app=None): Parameters ---------- - blueprint + blueprint: DiscordInteractionsBlueprint The :class:`DiscordInteractionsBlueprint` to add :class:`Command` s from. - app + app: Flask The Flask app with the relevant Discord commands. """ @@ -678,7 +692,7 @@ def register_blueprint(self, blueprint, app=None): app.custom_id_handlers.update(blueprint.custom_id_handlers) app.autocomplete_handlers.update(blueprint.autocomplete_handlers) - def run_command(self, data): + def run_command(self, data: dict): """ Run the corresponding :class:`Command` given incoming interaction data. @@ -687,6 +701,11 @@ def run_command(self, data): ---------- data Incoming interaction data. + + Returns + ------- + Message + The resulting message from the command. """ command_name = data["data"]["name"] @@ -698,7 +717,7 @@ def run_command(self, data): return command.make_context_and_run(discord=self, app=current_app, data=data) - def run_handler(self, data, *, allow_modal=True): + def run_handler(self, data: dict, *, allow_modal: bool = True): """ Run the corresponding custom ID handler given incoming interaction data. @@ -707,6 +726,11 @@ def run_handler(self, data, *, allow_modal=True): ---------- data Incoming interaction data. + + Returns + ------- + Message + The resulting message. """ context = Context.from_data(self, current_app, data) @@ -722,7 +746,7 @@ def run_handler(self, data, *, allow_modal=True): return Message.from_return_value(result) - def run_autocomplete(self, data): + def run_autocomplete(self, data: dict): """ Run the corresponding autocomplete handler given incoming interaction data. @@ -731,6 +755,11 @@ def run_autocomplete(self, data): ---------- data Incoming interaction data. + + Returns + ------- + AutocompleteResult + The result of the autocomplete handler. """ context = Context.from_data(self, current_app, data) @@ -773,6 +802,11 @@ def handle_request(self): """ Verify the signature in the incoming request and return the Message result from the given command. + + Returns + ------- + Message + The resulting message from the command. """ self.verify_signature(request) @@ -792,7 +826,7 @@ def handle_request(self): f"Interaction type {interaction_type} is not yet supported" ) - def set_route(self, route, app=None): + def set_route(self, route: str, app: Flask = None): """ Add a route handler to the Flask app that handles incoming interaction data. @@ -802,9 +836,9 @@ def set_route(self, route, app=None): Parameters ---------- - route + route: str The URL path to receive interactions on. - app + app: Flask The Flask app to add the route to. """ @@ -817,7 +851,7 @@ def interactions(): response, mimetype = result.encode() return Response(response, mimetype=mimetype) - def set_route_async(self, route, app=None): + def set_route_async(self, route: str, app: Flask = None): """ Add a route handler to a Quart app that handles incoming interaction data using asyncio. @@ -827,10 +861,10 @@ def set_route_async(self, route, app=None): Parameters ---------- - route + route: str The URL path to receive interactions on. - app - The Flask app to add the route to. + app: Flask + The Quart app to add the route to. """ if app is None: @@ -838,7 +872,7 @@ def set_route_async(self, route, app=None): if aiohttp is None: raise ImportError( - "The aiohttp module is required for async usage of this " "library" + "The aiohttp module is required for async usage of this library" ) @app.route(route, methods=["POST"]) diff --git a/flask_discord_interactions/models/autocomplete.py b/flask_discord_interactions/models/autocomplete.py index b404fae..164347e 100644 --- a/flask_discord_interactions/models/autocomplete.py +++ b/flask_discord_interactions/models/autocomplete.py @@ -33,7 +33,16 @@ def __init__(self, choices=[]): self.choices = choices def encode(self): - "Return this result as a complete interaction response." + """ + Return this result as a complete interaction response. + + Returns + ------- + str + The encoded JSON object. + str + The mimetype of the response (``application/json``). + """ data = { "type": ResponseType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT, "data": {"choices": self.choices}, diff --git a/flask_discord_interactions/models/embed.py b/flask_discord_interactions/models/embed.py index 6538935..dd83dbc 100644 --- a/flask_discord_interactions/models/embed.py +++ b/flask_discord_interactions/models/embed.py @@ -93,7 +93,14 @@ class Embed(LoadableDataclass): fields: List[Field] = None def dump(self): - "Returns this Embed as a dictionary, removing fields which are None." + """ + Returns this Embed as a dictionary, removing fields which are None. + + Returns + ------- + dict + A dictionary representation of this Embed. + """ def filter_none(d): if isinstance(d, dict): diff --git a/flask_discord_interactions/models/message.py b/flask_discord_interactions/models/message.py index 164325d..5f515db 100644 --- a/flask_discord_interactions/models/message.py +++ b/flask_discord_interactions/models/message.py @@ -125,17 +125,36 @@ def flags(self): """ The flags sent with this Message, determined by whether it is ephemeral. + + Returns + ------- + int + Integer representation of the flags. """ return 64 if self.ephemeral else 0 def dump_embeds(self): - "Returns the embeds of this Message as a list of dicts." + """ + Returns the embeds of this Message as a list of dicts. + + Returns + ------- + List[dict] + A list of dicts representing the embeds. + """ return ( [embed.dump() for embed in self.embeds] if self.embeds is not None else None ) def dump_components(self): - "Returns the message components as a list of dicts." + """ + Returns the message components as a list of dicts. + + Returns + ------- + List[dict] + A list of dicts representing the components. + """ return ( [c.dump() for c in self.components] if self.components is not None else None ) @@ -151,6 +170,11 @@ def from_return_value(cls, result): ---------- result The function return value to convert into a ``Message`` object. + + Returns + ------- + Message + A ``Message`` object representing the return value. """ async def construct_async(result): diff --git a/flask_discord_interactions/models/modal.py b/flask_discord_interactions/models/modal.py index 4aca335..96e13d6 100644 --- a/flask_discord_interactions/models/modal.py +++ b/flask_discord_interactions/models/modal.py @@ -58,13 +58,32 @@ def __post_init__(self): raise ValueError("Only Action Row components are supported for Modals") def dump_components(self): - "Returns the message components as a list of dicts." + """ + Returns the message components as a list of dicts. + + Returns + ------- + List[dict] + The message components as a list of dicts. + """ return [c.dump() for c in self.components] - def encode(self, followup=False): + def encode(self, followup: bool = False): """ Return this ``Modal`` as a dict to be sent in response to an incoming webhook. + + Parameters + ---------- + followup: bool + Whether this is a followup to a previous modal. + + Returns + ------- + str + The JSON-encoded modal. + str + The mimetype of the response (``application/json``). """ payload = { "type": ResponseType.MODAL, diff --git a/flask_discord_interactions/models/option.py b/flask_discord_interactions/models/option.py index 5e5c153..d74c477 100644 --- a/flask_discord_interactions/models/option.py +++ b/flask_discord_interactions/models/option.py @@ -105,8 +105,10 @@ def __post_init__(self): raise ValueError(f"Unknown type {self.type}") @classmethod - def from_data(cls, data): - "Load this option from incoming Interaction data." + def from_data(cls, data: dict): + """ + Load this option from incoming Interaction data. + """ return cls( name=data["name"], type=data["type"], @@ -115,7 +117,14 @@ def from_data(cls, data): ) def dump(self): - "Return this option as as a dict for registration with Discord." + """ + Return this option as as a dict for registration with Discord. + + Returns + ------- + dict + A dict representing this option. + """ data = { "name": self.name, "name_localizations": self.name_localizations, @@ -155,7 +164,14 @@ class Choice: name_localizations: Optional[dict] = None def dump(self): - "Return this choice as a dict for registration with Discord." + """ + Return this choice as a dict for registration with Discord. + + Returns + ------- + dict + A dict representing this choice. + """ data = { "name": self.name, "value": self.value, diff --git a/flask_discord_interactions/models/permission.py b/flask_discord_interactions/models/permission.py index 2eeb355..6be21df 100644 --- a/flask_discord_interactions/models/permission.py +++ b/flask_discord_interactions/models/permission.py @@ -17,6 +17,17 @@ class Permission: ``Permission(role='3456', allow=False)`` denies users with role ID 3456 from using the command + + Parameters + ---------- + role: str + The role ID to apply the permission to. + user: str + The user ID to apply the permission to. + channel: str + The channel ID to apply the permission to. + allow: bool + Whether use of the command is allowed or denied for the specified criteria. """ def __init__(self, role=None, user=None, channel=None, allow=True): @@ -36,11 +47,26 @@ def __init__(self, role=None, user=None, channel=None, allow=True): self.permission = allow def dump(self): - "Returns a dict representation of the permission" + """ + Returns a dict representation of the permission. + + Returns + ------- + dict + A dict representation of the permission. + """ return {"type": self.type, "id": self.id, "permission": self.permission} @classmethod - def from_dict(cls, data): + def from_dict(cls, data: dict): + """ + Returns a Permission object loaded from a dict. + + Parameters + ---------- + data: dict + A dict representation of the permission. + """ if data["type"] == PermissionType.ROLE: return cls(role=data["id"], allow=data["permission"]) elif data["type"] == PermissionType.USER: diff --git a/flask_discord_interactions/models/user.py b/flask_discord_interactions/models/user.py index b2bc445..0673fd2 100644 --- a/flask_discord_interactions/models/user.py +++ b/flask_discord_interactions/models/user.py @@ -55,12 +55,26 @@ def from_dict(cls, data): @property def display_name(self): - "The displayed name of the user (the username)." + """ + The displayed name of the user (the username). + + Returns + ------- + str + The displayed name of the user. + """ return self.username @property def avatar_url(self): - "The URL of the user's profile picture." + """ + The URL of the user's profile picture. + + Returns + ------- + str + The URL of the user's profile picture. + """ if self.avatar_hash is None: return f"https://cdn.discordapp.com/embed/avatars/{int(self.discriminator) % 5}.png" elif str(self.avatar_hash).startswith("a_"): @@ -117,5 +131,10 @@ def display_name(self): """ The displayed name of the user (their nickname, or if none exists, their username). + + Returns + ------- + str + The displayed name of the user. """ return self.nick or self.username diff --git a/flask_discord_interactions/models/utils.py b/flask_discord_interactions/models/utils.py index df92f70..de6e81e 100644 --- a/flask_discord_interactions/models/utils.py +++ b/flask_discord_interactions/models/utils.py @@ -10,7 +10,7 @@ def from_dict(cls, data): Parameters ---------- - data + data: dict A dictionary of fields to set on the dataclass. """ return cls(