Discord bot to run a bracket via Discord polls
Find a file
2026-01-13 12:43:33 -05:00
src descriptions and pings, new yaml crate 2026-01-13 11:41:07 -05:00
.gitignore first commit, incomplete 2026-01-11 20:12:58 -05:00
agpl-3.0.txt add license and readme 2026-01-13 12:43:33 -05:00
Cargo.lock descriptions and pings, new yaml crate 2026-01-13 11:41:07 -05:00
Cargo.toml descriptions and pings, new yaml crate 2026-01-13 11:41:07 -05:00
readme.txt add license and readme 2026-01-13 12:43:33 -05:00

=== Bracket Bot

This is a Discord bot to run a bracket via Discord polls. The bot needs
permission to post and pin messages in any channel where it will run a
bracket, and permission to assign, unassign, and ping any roles that
will be used to ping users about brackets.

=== Commands

All of these commands must be run with an environment variable BOT_TOKEN set
to the token of the Discord bot. All except for gateway interact with a SQLite3
database named database.db, which will be created if it does not exist. To run
any of these from the source repository, use the command:
  BOT_TOKEN='<token>' cargo run --release --bin <command> [arguments]

== create-bracket <path to YAML file>

A message will be posted in the specified channel announcing the bracket, with
a button for users to toggle the specified role, and then that message will be
pinned to the channel. The bracket will be put into the database, and the ID
of the bracket will be printed to stdout.

The YAML file must contain the following keys:
  name: the name of the bracket (only used when posting the announcement)
  channel_id: the ID of the Discord channel to run the bracket in
  role_id: the ID of a role to ping when the polls are posted
  entries: a list of entries, in the format below

Each entry has these keys:
  name: the name of the entry
  description (optional):
    A message to post after any poll with this entry, or after the entry wins
    the bracket. This can just be a link to an image or a GIF to make Discord
    embed that image or GIF after the poll. The message posted will not include
    the name of the entry, so if the description is text it might be good to
    include the name in the description.
  emoji (optional):
    An emoji to include next to the option in the poll. If this consists
    entirely of decimal digits, then it is interpreted as an ID of a Discord
    emoji. Otherwise, this is interpreted as a literal Unicode emoji.

== gateway

This runs the "gateway" portion of the bot, which receives notifications
from Discord about the toggle role buttons being pressed, and toggles those
roles. This will continue running until there is some error communicating
with Discord. If this is not running, attempting to toggle roles will
display "interaction failed" under the button.

== post-polls <bracket id> <number of polls>

This posts the specified number of polls from the specified bracket to whatever
channel was assigned to the bracket, and then pings the role assigned to the
bracket. If there are not that many polls left in the current layer, this posts
as many as are left. If there are no polls left in the current layer, this goes
to the next layer first. If there is only one entry left in the bracket, this
posts a message announcing that that entry won. This should not be run on a
bracket after its winner has been announced. This should not be run on the same
bracket more than once without running process-polls in between. All polls are
posted with a length of 23 hours.

== process-polls

This reads all of the polls that have been posted since the last time this was
run, and updates each bracket with that information. If a poll has not expired
when this is run, or if Discord does not report a poll as "finalized", this
sleeps repeatedly, starting with sixty seconds and doubling each time after
that, until the poll is both expired and "finalized".

=== Recommended Setup

On my server, I have the gateway running in a tmux session. In a shell script
that cron runs once per day, I run process-polls and then I run post-polls for
each running bracket. To start a new bracket, I write out the YAML file for it,
run create-bracket, and then add it to the cron script. When the bot announces
a winner for a bracket, I remove it from the cron script.

=== License

This bot is Copyright 2026 Benji Dial and is released under the GNU Affero
General Public License, version 3 only. The full text of the license can
be found in agpl-3.0.txt, or at <https://www.gnu.org/licenses/agpl-3.0.html>.

The bot has a number of dependencies, including these direct dependencies:

  rand, version 0.9.2 <https://github.com/rust-random/rand>
    Used to shuffle new brackets, and to decide which entry gets to keep its
    place and which gets moved to the end of the layer when there are ties.

    Copyright 2014 The Rust Project Developers.
    Copright 2018 Developers of the Rand project.
    Licensed under the MIT license or the Apache license, version 2.0.

  rusqlite, version 0.38.0 <https://github.com/rusqlite/rusqlite>
    Used to interface with the database.

    Copright 2014 The rusqlite developers.
    Licensed under the MIT license.

  serenity, version 0.12.5 <https://github.com/serenity-rs/serenity>
    Used to interface with Discord.

    Copyright 2016, Serenity Contributors.
    License under the ISC license.

  tokio, version 1.49.0 <https://github.com/tokio-rs/tokio>
    Used as an asynchronous runtime.

    Copyright Tokio Contributors.
    Licensed under the MIT license.

  yaml-rust2, version 0.11.0 <https://github.com/Ethiraric/yaml-rust2>
    Used to parse the YAML file passed to create-bracket.

    Copyright 2015 Chen Yuheng.
    Copyright 2023 Ethiraric.
    Licensed under the MIT license or the Apache license, version 2.0.

See each dependency's source repository for the text of their licenses
(including warranty disclaimers), and for transitive dependencies.