Skip to content

Botstrap

The primary point of integration between a Discord bot and the Botstrap library.

This class features a modular, step-by-step flow for securely handling Discord bot tokens and parsing/customizing command-line options. Each method in this class corresponds to a step in the flow, and all of them are highly customizable in order to adapt to the needs of individual bots.

For an overview of this class's usage, see the flowchart below. It establishes a foundation for the subsequent example, which includes code snippets and demonstrates the CLI (command-line interface) created by a lightly-customized Botstrap integration.

Diagram - The Botstrap Flowchart

The following diagram provides a high-level roadmap for this class and illustrates which methods are (and aren't) necessary to invoke as you proceed through the flow. Start at the top, and click on each method name that you encounter on the way down for more detailed information and usage instructions.

flowchart TB
    A{{" __init__()   "}}
    A --> B("Do you want to use a custom bot token?")
    B -- Yes --> C{{" register_token()   "}}
    C --> D("Do you have more tokens?")
    D -- Yes --> C
    D -- No --> E("Do you want to customize your bot's CLI?")
    B -- No --> E
    E -- Yes --> F{{" parse_args()  "}}
    F --> G("Is your bot simple to create and set up?")
    E -- No --> G
    G -- Yes --> H{{" run_bot()  "}}
    G -- No --> I{{" retrieve_active_token()    "}}
    I --> J("Plug the result into your bot.")
    H --> K(("Done!"))
    J --> K

    click A "#botstrap.flow.Botstrap.__init__"
    click C "#botstrap.flow.Botstrap.register_token"
    click F "#botstrap.flow.Botstrap.parse_args"
    click H "#botstrap.flow.Botstrap.run_bot"
    click I "#botstrap.flow.Botstrap.retrieve_active_token"

    class A,C,F,H,I method;
    classDef method font-family: Roboto Mono, font-size: 14px, font-weight: bold
    classDef method fill: #7c4dff66, stroke: #7c4dffff, stroke-width: 2px

    class B,D,E,G,J textBox;
    classDef textBox fill: #00b0ff11, stroke: #00b0ff33, stroke-width: 2px

    class K lastNode;
    classDef lastNode fill: #00c85366, stroke: #00c853cc
    classDef lastNode font-weight: bold, stroke-width: 2px

    linkStyle 1,3,6,9 opacity: 0.9, stroke-dasharray: 8 4
    linkStyle 4,5,8,10 opacity: 0.9, stroke-dasharray: 5 6
    linkStyle 0,2,7,11,12,13 opacity: 0.9, stroke-width: 3px

Example - The Botstrap Interface
📁 workspace/
└── 📁 examplebot/
    ├── 📄 __main__.py
    └── 📄 extras.py

This is an extensive, multi-part example that uses the file structure outlined above, along with the Pycord library.

If any of the code or console output in the following tabs is unclear, try using the search bar at the top of the page to look up the key terms and read through the relevant documentation, then come back to this example.

(And if the docs aren't helpful, please do start a discussion about what could be clarified or improved!)


The next two tabs (after this "Overview" tab) provide the contents of the Python files used in this example:

  • __main__.py - This file contains the entire Botstrap integration and exercises all of the recommended methods in this class, as well as a few other classes that are also part of Botstrap's public API.
  • extras.py - This file represents a very small subset of the extra pieces that a Discord bot might have. Its purpose is simply to provide more context for the integration, so feel free to skip it if you don't find it useful.

The subsequent tabs demonstrate the CLI output for various program flows and/or command-line arguments:

  • CLI #1: Basics
    1. python -m examplebot -h Viewing the bot's help menu.
    2. python -m examplebot Running the bot for the first time using the command from the help menu.
    3. python -m examplebot Demonstrating automatic bot token decryption on subsequent runs.
  • CLI #2: Tokens
    1. python -m examplebot prod Setting up a new password-protected token for production.
    2. python -m examplebot -t Viewing existing tokens and deleting one of them.
  • CLI #3: Options
    1. python -m examplebot prod --alpha -a watching -s "you." Playing with bot options in prod!
    2. python -m examplebot --alpha -m Running the alpha bot in dev mode with mentions enabled.
    3. python -m examplebot --alpha -m -l 1 Same as above, but with the minimum log level set to 1.

Note: All of these commands are run from the outermost (workspace) directory.

from botstrap import Botstrap, CliColors, Color, Option
from discord import Activity, ActivityType, AllowedMentions
from examplebot.extras import AlphaBot, initialize_system_logging


botstrap = (
    Botstrap(
        name="examplebot",
        desc="A really cool Discord bot that uses Botstrap!",
        colors=CliColors(primary=Color.pink),
    )
    .register_token("dev", display_name=Color.yellow("development"))
    .register_token(
        uid="prod",
        requires_password=True,
        display_name=Color.green("production"),
    )
)

args = botstrap.parse_args(
    loglevel=Option(
        default=2,
        choices=range(1, 5),
        help="A value from 1 to 4 specifying the minimum log level.",
    ),
    status=Option(default="", help="Text to show in the bot's Discord profile status."),
    activity=Option(
        default="playing",
        choices=("streaming", "listening", "watching"),
        help="The text preceding '--status'. Defaults to '%(default)s'.",
    ),
    mentions=Option(flag=True, help="Allow the bot to @mention members and/or roles."),
    alpha=Option(flag=True, help=Option.HIDE_HELP),
)

initialize_system_logging(log_level=args.loglevel)

activity = (
    Activity(type=getattr(ActivityType, args.activity.lower()), name=args.status)
    if args.status
    else None
)
allowed_mentions = AllowedMentions.all() if args.mentions else AllowedMentions.none()

bot_class: str | type = AlphaBot if args.alpha else "discord.Bot"
botstrap.run_bot(bot_class, activity=activity, allowed_mentions=allowed_mentions)
import logging
import sys

from discord import Bot
from discord.ext import tasks


def initialize_system_logging(log_level: int) -> None:
    logging.basicConfig(
        level=log_level * 10,
        style="{",
        format="{asctime} | {levelname[0]} | {message}",
        datefmt="%Y-%m-%d %H:%M:%S",
        stream=sys.stdout,
    )
    logging.getLogger("discord").setLevel(logging.ERROR)


class AlphaBot(Bot):
    def __init__(self, **options) -> None:
        super().__init__(**options)
        self.log = logging.getLogger()

    async def on_ready(self) -> None:
        if self.activity:
            activity = f"{self.activity.type.name} {self.activity.name}"
            self.log.info(f"{self.user.name} is currently {activity}")

        if self.allowed_mentions.everyone:
            self.log.warning(f"{self.user.name} will ping @everyone in 10 seconds!!!")
            self.count_down.start()

    @tasks.loop(seconds=1, count=10)
    async def count_down(self) -> None:
        seconds_remaining = 10 - self.count_down.current_loop
        log = self.log.debug if (seconds_remaining > 3) else self.log.warning
        log(f"{seconds_remaining}...".rjust(5))

    @count_down.after_loop
    async def after_count_down(self) -> None:
        self.log.info("Just kidding! ^_^")

Note: For more information about the contents of this file, check out these helpful guides about setting up logging, subclassing bots, and the tasks extension. (Those links point to the Pycord website, but the concepts they explain are likely useful regardless of which Discord API wrapper you're using.)

1A) Viewing the bot's help menu.
$ python -m examplebot -h
usage: examplebot [-l <int>] [-s <str>] [-a <str>] [-m] [-t] [--help] [<token id>]

  A really cool Discord bot that uses Botstrap!
  Run "python -m examplebot" with no parameters to start the bot in development mode.

positional arguments:
  <token id>            The ID of the token to use to run the bot.
                        Valid options are "dev" and "prod".

options:
  -l <>, --loglevel <>  A value from 1 to 4 specifying the minimum log level.
  -s <>, --status <>    Text to show in the bot's Discord profile status.
  -a <>, --activity <>  The text preceding '--status'. Defaults to 'playing'.
  -m, --mentions        Allow the bot to @mention members and/or roles.
  -t, --tokens          View/manage your saved Discord bot tokens.
  -h, --help            Display this help message.
1B) Running the bot using the command from the help menu.
$ python -m examplebot

examplebot: You currently don't have a saved development bot token.
Would you like to add one now? If so, type "yes" or "y": y

Please enter your bot token now. It'll be hidden for security reasons.
BOT TOKEN: ************************.******.***************************

Your token has been successfully encrypted and saved.

Do you want to run your bot with this token now? If so, type "yes" or "y": y

examplebot: development: Attempting to log in to Discord...
examplebot: development: Successfully logged in as "BotstrapBot#1234".
1C) No need to re-enter the bot token after initial setup!
$ python -m examplebot

examplebot: development: Attempting to log in to Discord...
examplebot: development: Successfully in as "BotstrapBot#1234".
2A) Setting up a new password-protected token.
$ python -m examplebot prod

examplebot: You currently don't have a saved production bot token.
Would you like to add one now? If so, type "yes" or "y": y

Please enter your bot token now. It'll be hidden for security reasons.
BOT TOKEN: ************************.******.***************************

To keep your bot token extra safe, it must be encrypted with a password.
This password won't be stored anywhere. It will only be used as a key to
decrypt your token every time you run your bot in production mode.

Please enter a password for your production bot token.
PASSWORD: ****

Your password must be at least 8 characters long.
Would you like to try a different one? If so, type "yes" or "y": y
PASSWORD: ********

Please re-enter the same password again to confirm.
PASSWORD: ********

Your token has been successfully encrypted and saved.

Do you want to run your bot with this token now? If so, type "yes" or "y": n

Received a non-affirmative response. Exiting process.
2B) Viewing existing tokens and deleting one of them.
$ python -m examplebot -t

examplebot: You currently have the following bot tokens saved:
  1. development ->  ~/botstrap/examples/examplebot/.botstrap_keys/.dev.*
  2. production  ->  ~/botstrap/examples/examplebot/.botstrap_keys/.prod.*

Would you like to delete any of these tokens? If so, type "yes" or "y": y
Please enter the number next to the token you want to delete: 0

That number doesn't match any of the above tokens. (Expected "1" or "2".)

Would you like to try again? If so, type "yes" or "y": y
Please enter the number next to the token you want to delete: 1

Token successfully deleted.

examplebot: You currently have the following bot tokens saved:
  1. production  ->  ~/botstrap/examples/examplebot/.botstrap_keys/.prod.*

Would you like to delete any of these tokens? If so, type "yes" or "y": n

Received a non-affirmative response. Exiting process.
3A) Running the alpha bot in production with a custom status.
$ python -m examplebot prod --alpha -a watching -s "you."

examplebot: Please enter the password to decrypt your production bot token.
PASSWORD: ********

examplebot: production: Attempting to log in to Discord...
examplebot: production: Successfully logged in as "BotstrapBot#1234".

2022-09-04 21:47:57 | I | BotstrapBot is currently watching you.
3B) Running the alpha bot in dev mode with mentions enabled.
$ python -m examplebot --alpha -m

examplebot: development: Attempting to log in to Discord...
examplebot: development: Successfully logged in as "BotstrapBot#1234".

2022-09-04 21:48:32 | W | BotstrapBot will ping @everyone in 10 seconds!!!
2022-09-04 21:48:39 | W |  3...
2022-09-04 21:48:40 | W |  2...
2022-09-04 21:48:41 | W |  1...
2022-09-04 21:48:42 | I | Just kidding! ^_^
3C) Same as above, but with the minimum log level set to 1 (debug).
$ python -m examplebot --alpha -m -l 1

examplebot: development: Attempting to log in to Discord...
examplebot: development: Successfully logged in as "BotstrapBot#1234".

2022-09-04 21:50:48 | W | BotstrapBot will ping @everyone in 10 seconds!!!
2022-09-04 21:50:48 | D | 10...
2022-09-04 21:50:49 | D |  9...
2022-09-04 21:50:50 | D |  8...
2022-09-04 21:50:51 | D |  7...
2022-09-04 21:50:52 | D |  6...
2022-09-04 21:50:53 | D |  5...
2022-09-04 21:50:54 | D |  4...
2022-09-04 21:50:55 | W |  3...
2022-09-04 21:50:56 | W |  2...
2022-09-04 21:50:57 | W |  1...
2022-09-04 21:50:58 | I | Just kidding! ^_^

The primary point of integration between a Discord bot and the Botstrap library.

This class features a modular, step-by-step flow for securely handling Discord bot tokens and parsing/customizing command-line options. Each method in this class corresponds to a step in the flow, and all of them are highly customizable in order to adapt to the needs of individual bots.

__init__(name=None, *, desc=None, version=None, colors=CliColors.default(), strings=CliStrings.default())

Parameters:

Name Type Description Default
name str | None

The name of your bot. If omitted, Botstrap will try to determine an appropriate name from the package and/or file metadata. If unsuccessful, it will use the default name: "bot".

None
desc str | None

A short, human-readable description of your bot. Will be displayed when --help or -h is specified on the command line. If omitted, Botstrap will try to get a description from the package metadata. If unsuccessful, the -h menu will only display usage instructions.

None
version str | None

A string representing the current version of your bot. Will be displayed when --version is specified on the command line. If omitted, this option will not be available in your bot's CLI.

None
colors CliColors

The colors to be used by the CLI. Defaults to commonly-used colors (e.g. green for success, red for error). Set this to CliColors.off() to disable all colors.

CliColors.default()
strings CliStrings

The strings to be used by the CLI. Defaults to English text with ample vertical spacing for legibility. Set this to CliStrings.compact() to minimize spacing.

CliStrings.default()

register_token(uid, *, requires_password=False, display_name=None, storage_directory=None, allow_overwrites=False)

Defines a Discord bot token to be managed by this Botstrap integration.

After instantiating this class and before calling any of its other methods, this one must be called for each unique token that may be used by your bot. For instance, to define a "development" token as well as a password-protected "production" token, you would call this method twice.

This is what lets Botstrap know how to display the token's name, whether to ask for a password when it's accessed, and where to find (or create) its files. In other words, this method is simply a way to declare the token's behavior - not its value.

FAQ - Where is the token value defined?

So far, we haven't mentioned the value or "secret data" of the token, which may or may not exist at the time this method is called - it makes no difference at this point. The token value will be requested interactively and securely through the CLI only if/when it's actually needed, at which point it will be encrypted and saved for future use. 🔒

For more technical details about how the token values are encrypted and decrypted, feel free to check out the documentation for the internal Secret class.

Note - Automatically registering a default token

If all of the following statements are true, you can skip this method and move on to the next step in the flow:

  • Your bot only uses one token - it only needs to run in one environment, or only has a single configuration, or a similar reason along those lines.
  • It doesn't require password-protection - the Discord account it uses doesn't have access to any real users or servers that could potentially be damaged if a malicious actor were to gain control.
  • You do not disable allow_token_registration in any subsequent method calls - it's enabled by default, so unless you explicitly set it to False, you'll be fine.

If you decide to skip this method, a simple token named "default" will be created when you first run your bot. It will be saved in a directory named .botstrap_keys, which will be created in the same location as the file containing the "__main__" module of your bot's script.

Example - Registering multiple tokens

This example uses Color functions to make the tokens more easily identifiable when they're mentioned in the CLI.

bot.py
from botstrap import Botstrap, Color

Botstrap().register_token(
    uid="dev",
    display_name=Color.yellow("development"),
).register_token(
    uid="prod",
    requires_password=True,
    display_name=Color.green("production"),
).run_bot()
Console Session
$ python bot.py --tokens

bot.py: You currently have the following bot tokens saved:
  1. development ->  ~/path/to/your/bot/.botstrap_keys/.dev.*
  2. production  ->  ~/path/to/your/bot/.botstrap_keys/.prod.*

Would you like to delete any of these tokens? If so, type "yes" or "y":

Parameters:

Name Type Description Default
uid str

A unique string identifying this token. Will be used in the names of the files holding this token's data.

required
requires_password bool

Whether a password is required in order to create and subsequently retrieve this token.

False
display_name str | None

A human-readable string describing this token. Will be displayed in the CLI when referring to this token. If omitted, the uid will be shown instead.

None
storage_directory str | Path | None

Where to store the files containing this token's data. If omitted, the files will be saved in a folder named .botstrap_keys, which will be created in the same location as the file containing the main module of your bot's script.

None
allow_overwrites bool

Whether to allow this token to be registered even if uid already belongs to a registered token. If True, this token will clobber the previous token.

False

Returns:

Type Description
Botstrap

This Botstrap instance, for chaining method calls.

Raises:

Type Description
ValueError

If storage_directory does not point to a valid directory (i.e. it doesn't exist or points to a file), OR if allow_overwrites is False and uid belongs to a token that was already registered.

parse_args(**custom_options)

Parses any arguments and options passed in via the command line.

This should only be invoked after all of your bot's tokens are declared using register_token() in order to ensure that the active token can be correctly determined from the command that was used to run your bot's script.

If you decide that your bot doesn't need any custom command-line options, you may safely skip ahead to the next step in the flow. Behind the scenes, this method will be called with no params so that Botstrap can identify the active token and process any built-in options (such as -h).

Tip - Define your own command-line options!

By default, your bot's CLI will include options for --tokens and --help (and --version, if you specify a version). However, you aren't limited to just those three - you can define as many **custom_options as you want! 🎉

To add custom command-line options, simply create Option objects and pass them in as keyword arguments when you call this method. The names you choose for your keyword arguments will determine the names of the options. For example, an arg named my_custom_flag will create the CLI option --my-custom-flag.


For more information, check out:

  • The API reference for Option. It includes plenty of examples and goes into detail about the fields you need to specify when defining your own custom options.
  • The documentation for this method's return type, Option.Results. (It only occupies a short section at the very bottom, so it might be easy to miss.)
  • ... and if you're curious about how option abbreviations (such as -h and -v) are allotted, you can venture into the internal documentation to read this note, which explains this process and the reasoning behind it.
Example - Adding a custom option to the CLI
bot.py
from botstrap import Botstrap, Option

Botstrap(
    desc="An example bot with a single custom option that does nothing.",
).parse_args(
    custom_flag=Option(flag=True, help="Hello! I'm a command-line flag!"),
)
Console Session
$ python bot.py -h
usage: bot.py [-c] [-t] [--help]

  An example bot with a single custom option that does nothing.
  Run "python bot.py" with no parameters to start the bot.

options:
  -c, --custom-flag  Hello! I'm a command-line flag!
  -t, --tokens       View/manage your saved Discord bot tokens.
  -h, --help         Display this help message.

Parameters:

Name Type Description Default
**custom_options Option

Keyword arguments that define your bot's custom command-line options. If none are provided, then only the default options will be available in your bot's CLI.

{}

Returns:

Type Description
Option.Results

An object with attribute names & values corresponding to the parsed options.

Raises:

Type Description
SystemExit

If a specified command-line option calls for an alternate program flow that exits on completion, such as --help or --version.

retrieve_active_token(*, allow_token_creation=True, allow_token_registration=True)

Returns the value of the active token, if it exists and can be decrypted.

The active token is the one that should be used to run your bot, taking into account all tokens that have been registered and any arguments that were passed in from the command line. If no custom tokens have been defined, this will be the basic "default" token.

The value of the token is a string containing its decrypted data, which can be plugged into your bot to log it into Discord. This can (and for security reasons, should) be handled automatically by run_bot() - which means that ideally, you won't need to call this method at all.

Caution - Keep your tokens safe!

Token values should always be kept secret and can be very damaging if leaked... so make sure you don't print() (or log, or output in any way) the return value of this method! 🤐

If your bot is coded such that it can be both instantiated and started by run_bot(), consider using that method instead for brevity and safety. This method is provided for cases in which that isn't a viable option, but it should be avoided if possible to prevent potential security mishaps.

Example - Retrieving a new default token
bot.py
from botstrap import Botstrap

Botstrap().retrieve_active_token()
Console Session
$ python bot.py

bot.py: You currently don't have a saved default bot token.
Would you like to add one now? If so, type "yes" or "y":

Parameters:

Name Type Description Default
allow_token_creation bool

Whether to interactively prompt to create (i.e. add and encrypt) a new token if the active token has not already been created.

True
allow_token_registration bool

Whether to automatically register a basic "default" token if no tokens have been explicitly defined with register_token().

True

Returns:

Type Description
str | None

The active token value if it exists and can be decrypted, otherwise None.

Raises:

Type Description
RuntimeError

If no tokens have been registered and allow_token_registration is set to False.

run_bot(bot_class='', *, run_method_name='run', init_with_token=False, **options)

Instantiates the bot class as specified, and runs it using the active token.

In the simplest use case, this method will work out-of-the-box with no customization required. But in practice, you will most likely have to specify information such as the fully-qualified name (or the type) of your bot_class, and/or any **options expected by its constructor.

This method's parameters provide a straightforward solution for more complex use cases while preserving most, if not all, of the flexibility afforded by your chosen Discord API wrapper - as long as you're using one of the Python ones, of course. 🐍 See the parameter descriptions below for more detailed usage instructions.

Example - The simplest use case

This example makes the following assumptions:

  • You're using one of the Python Discord libraries for which Botstrap includes built-in support:
    discord.pydisnakehikariinteractions.pyNAFFNextcord, or  Pycord.
  • Your bot does not subclass the default bot_class (often named Bot or Client) from your chosen library.
    Note: Subclassing is often useful, especially when integrating with Botstrap. This guide explains the basics.
  • You've already completed the CLI flow to set up the "default" token for your bot.

If all of the above statements are true, then you can run your bot with this extremely pared-down code:

bot.py
from botstrap import Botstrap

Botstrap().run_bot()
Console Session
$ python bot.py

bot.py: default: Attempting to log in to Discord...
bot.py: default: Successfully logged in as "BotstrapBot#1234".

Of course, this simple example probably isn't very helpful unless you're trying to play golf with your bot's setup code. For a much more complex and interesting example, check out the one at the top of this page.

Parameters:

Name Type Description Default
bot_class str | type

The fully-qualified class name or the type of your bot. If omitted, Botstrap will look for installed Discord libraries and try to select a class from one of them. The class identified by this arg will be instantiated with the **options keyword args, as well as the value of the active token if init_with_token is set to True.

''
run_method_name str

The name of the bot_class method that logs the bot in using its token and connects to Discord, effectively "running" or "starting" the bot. This method will receive the token value unless init_with_token is set to True, in which case it will be called with no args.

'run'
init_with_token bool

Whether to pass the token value into the constructor of the bot_class, instead of the method specified by run_method_name. By default, it will go to the latter.

False
**options Any

Optional keyword arguments that will each be forwarded to one of two possible destinations:

  1. Any arguments with names matching the ones accepted by retrieve_active_token() will be passed to that method when it gets called by this one in order to obtain the token to run your bot.

  2. The remaining arguments will be passed to the constructor of bot_class upon instantiation. This allows you to, for example, define any special intents that might be required by your bot.

Any options that aren't specified will simply use the default values defined by their respective methods.

{}

Raises:

Type Description
RuntimeError

If bot_class is not provided and a class to use can't be determined from installed packages.

ImportError

If bot_class is a str that refers to a type that can't be imported in the current environment.

TypeError

If bot_class (after it's converted to a type, if it wasn't already) isn't an instantiable type.