Skip to main content

Discord Self Bot Tester (DSBT) Library

Package for testing commands using discord selfbot. Assertions (messages, and autocomplete) and requests (interactions, events, reactions) can be made using the bot.

Install

pip install discord-self-bot-tester

Needs python 3.11 or newer.

Setup

  • Create a Bot with a user token
  • Run it with bot.run(). This has to be called inside a running event loop, and every test has to use that same loop
  • Wait for it with await bot.wait_ready()
  • Index the commands with await bot.index_application_commands(GUILD_ID) (has to happen before sending any command)
  • Make tests and have fun or pain, idk ur pick
  • Call await bot.stop() in a finally so a failed test still closes the socket
from discord_self_bot_tester import Bot
import asyncio

async def main():
    bot = Bot(USER_TOKEN)
    bot.run()
    try:
        await bot.wait_ready()
        await bot.index_application_commands(GUILD_ID)
        # tests go here
    finally:
        await bot.stop()

asyncio.run(main())

Commands

Build the command, then assert on the reply. assert_request sends it for you.

from discord_self_bot_tester.requests import ApplicationCommandBuilder, InteractionType
from discord_self_bot_tester.assertions import MessageAssertionBuilder

cmd = ApplicationCommandBuilder(InteractionType.APP_COMMAND, "ping")\
    .set_arg("count", 3)\
    .compile(APPLICATION_ID, GUILD_ID, CHANNEL_ID)

msg = await MessageAssertionBuilder()\
    .assert_by_message_content("pong")\
    .compile()\
    .assert_request(bot, cmd)

set_main_command("sub") for subcommands. await cmd.send(bot) if u dont care about the reply.

Filters

Which message to pick. Leave them all out and it picks the reply to the command.

  • filter_by_author(user_id)
  • filter_by_channel(channel_id)
  • filter_by_message_update() for deferred or edited replies
  • filter_by_message_flags(64) for ephemeral replies
  • filter_by_followup_message() ties a filtered assertion back to the command

Checks

  • assert_by_message_content(regex)
  • assert_by_mentions([ids])
  • assert_by_mention_roles([ids])

Default deadline is 5 seconds, change it with deadline= on assert_request. Raises TimeoutError if nothing shows up.

Views

Assert what buttons and dropdowns are on the message.

from discord_self_bot_tester.gateway import ButtonStyle

msg = await MessageAssertionBuilder()\
    .assert_by_button("Confirm", style=ButtonStyle.SUCCESS)\
    .assert_by_button_labels(["Confirm", "Cancel"])\
    .assert_by_dropdown(placeholder="Pick one", option_labels=["A", "B"])\
    .compile()\
    .assert_request(bot, cmd)

Then click stuff. Ids get pulled off the message so u only need the label.

from discord_self_bot_tester.requests import press_button, select_dropdown, select_dropdown_values

await MessageAssertionBuilder()\
    .filter_by_message_update()\
    .compile()\
    .assert_request(bot, press_button(msg, "Confirm"))

await select_dropdown(msg, "A", "B", dropdown="Pick one").send(bot)
await select_dropdown_values(msg, USER_ID, dropdown="pick_user").send(bot)

dropdown can be left out if the message only has one. User, role and channel selects take snowflakes, so use select_dropdown_values for those.

Modals

Open it, fill it in by label, submit it.

from discord_self_bot_tester.requests import ModalResponseBuilder
from discord_self_bot_tester.assertions import ModalAssertionBuilder

modal = await ModalAssertionBuilder()\
    .assert_by_modal_title("Sign up")\
    .compile()\
    .assert_request(bot, press_button(msg, "Register"))

submit = ModalResponseBuilder(modal)\
    .select("Name").set_text("oliver")\
    .select("Colour").choose("Green")\
    .select("Agree").set_checked()\
    .compile(GUILD_ID, CHANNEL_ID, APPLICATION_ID)

await MessageAssertionBuilder().compile().assert_request(bot, submit)
  • set_text(str) for text inputs
  • choose(*labels) for selects, radios and checkbox groups
  • set_values(*values) and set_option(value) if u want raw values instead of labels
  • set_checked(bool) for checkboxes
  • select_by_custom_id(id) if the label is annoying

Nested labels work like select("Address", "Postcode"). Required fields left empty fail at compile, not on discord.

Autocomplete

from discord_self_bot_tester.assertions import AutoCompleteAssertionBuilder

res = await AutoCompleteAssertionBuilder().compile().assert_request(
    bot,
    ApplicationCommandBuilder(InteractionType.APPLICATION_COMMAND_AUTOCOMPLETE, "search")
        .set_arg("query", "ab", focused=True)
        .compile(APPLICATION_ID, GUILD_ID, CHANNEL_ID)
)

Other requests

Reactions, events and attachments all have builders. .send(bot) sends them.

from discord_self_bot_tester.requests import build_reaction, build_attachment

await build_reaction("🖥️", MESSAGE_ID, CHANNEL_ID).send(bot)

file = build_attachment(data, "image.png", CHANNEL_ID)
await file.send(bot)
file.get_attachment_id()  # pass this to set_values on a modal file upload

Metadata

Release files for discord-self-bot-tester 0.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for discord-self-bot-tester 0.8.0
File Size Uploaded
discord_self_bot_tester-0.8.0.tar.gz 42.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for discord-self-bot-tester 0.8.0
File Interpreter ABI Platform
discord_self_bot_tester-0.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 90.4 kB

Release files / discord_self_bot_tester-0.8.0.tar.gz

Download URL discord_self_bot_tester-0.8.0.tar.gz
Size 42.0 kB
Tags Source
SHA-256 checksum
How to use checksums
5fc5e1c82ebcad84ab8bdfaaf677f9f12c97032467bb16b2d70211130ddf9370
BLAKE2b-256 checksum
How to use checksums
5f9c42eb2bceba7ca207fc23a11cf3b981954ef7ab22ea599f72dfb34631ceb1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / discord_self_bot_tester-0.8.0-py3-none-any.whl

Download URL discord_self_bot_tester-0.8.0-py3-none-any.whl
Size 48.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
105d0142d0b47cd1c976cc56c41137f432881d19dcf97af2d33d53a7604fe0f4
BLAKE2b-256 checksum
How to use checksums
7dd941de986c6e8c64551525e9a2e0bdc43d7bda9c3c5296d2cacd047c4e9fb8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release history Release notifications | RSS feed

0.9.0

2 release files

This release

0.8.0 This release

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page