/discord-interactions.py

🧪 Discord Interactions via Python/Flask

Primary LanguagePythonMIT LicenseMIT

Discord Interactions

PyPI Documentation Status License

A wrapper for the Discord Interactions API that does not rely on websockets and can therefore be used in a stateless webhook environment.

Furthermore, it allows for strict separation between your commands' structure and and the data that is received when triggering it.

Installation

Requires Python 3.8+

Latest release from PyPI using pip:
pip install discord-interactions.py
Latest commit from GitHub using pip and git:
pip install git+https://github.com/LiBa001/discord-interactions.py

Note

Installing directly from GitHub requires you to have Git installed on your computer.

If this doesn't work, you might try:
python -m pip install ...
Or if you are on windows:
py -m pip install ...

Use with Flask

This library is specifically designed to work seamlessly with the Flask microframework.

Using API-like Data Classes

The most API-like example with the flask extension is this:

from discord_interactions.flask_ext import Interactions
from discord_interactions import (
    ApplicationCommand,
    ApplicationCommandOption,
    ApplicationCommandOptionType,
    Interaction,
    InteractionResponse,
    InteractionResponseType,
    InteractionApplicationCommandCallbackData,
)
from flask import Flask
import os

app = Flask(__name__)
interactions = Interactions(app, os.getenv("CLIENT_PUBLIC_KEY"))

echo_cmd = ApplicationCommand("echo", "what goes around comes around")
echo_cmd.add_option(
    ApplicationCommandOption(
        type=ApplicationCommandOptionType.STRING,
        name="message",
        description="This will be echoed.",
        required=True,
    )
)


@interactions.command(echo_cmd)
def _echo(interaction: Interaction):
    msg = interaction.data.options[0].value  # "message" option content

    return InteractionResponse(
        type=InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
        data=InteractionApplicationCommandCallbackData(content=msg),
    )

Here, we use the rudimentary ApplicationCommand, Interaction and InteractionResponse classes, which are in their structure basically exact counterparts of the original API models.

Let's make it a bit simpler:

@interactions.command(echo_cmd)
def _echo(interaction: Interaction):
    # different way of getting an option
    msg = interaction.data.get_option("message").value

    return msg

Now, we don't need to deal with InteractionResponse anymore, but instead just return the response content as a string. The response type then defaults to InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE. You could also just return None, if you don't want to send a response. You can also simply return a boolean as a second value, indicating whether or not the command call should be displayed in Discord (i.e. the _WITH_SOURCE part of the response type). Also we get the option via the get_option helper method.

The Object-Command Mapper

This library provides another abstraction layer, though. Inspired by the concept of database ORMs, it has an Object-Command Mapper (OCM) that lets you define a class for each command which will then serve as both a generic structural description of the command (like ApplicationCommand) and a container for the actual data that is received when the command is called (like Interaction).

So, the simplest possible example looks like this:

from discord_interactions.flask_ext import Interactions
from discord_interactions.ocm import Command, Option
from flask import Flask
import os

app = Flask(__name__)
interactions = Interactions(app, os.getenv("CLIENT_PUBLIC_KEY"))


class _Echo(Command):
    """ what goes around comes around """

    message: str = Option("This will be echoed.", required=True)


@interactions.command
def _echo(cmd: _Echo):
    return cmd.message

Followup Messages

If you want to send messages after the initial response, you need to create followup messages. For this purpose you can use the after_command decorator, that registers a function to be called after the actual command function has returned. The function needs to take exactly one parameter, the AfterCommandContext, which contains the several things, like the Interaction and initial InteractionResponse. To be able to create followup messages, you need to provide the ID of your Discord application to the Interactions constructor.

interactions = Interactions(app, PUBLIC_KEY, APP_ID)

@interactions.command("delay")
def delay(_: Interaction):
    return "starting countdown", True  # this message is ephemeral


@delay.after_command
def after_delay(ctx: AfterCommandContext):
    delay_time = ctx.interaction.data.options[0].value
    time.sleep(delay_time)
    ctx.send(f"{delay_time} seconds have passed")

More Examples

For more examples of the different features take a look at examples.

If you want to know how to make your Discord bot work with Slash Commands and how to set everything up, take a look at this example project. It hosts the program in a serverless environment via Google Cloud Run and also provides a demo bot, so you can try out Slash Commands in your Discord server. Check it out to learn more!