___ ___ ___ ___ ___
/\ \ /\ \ /\__\ /\__\ _____ /\ \
/::\ \ /::\ \ /:/ _/_ /:/ _/_ /::\ \ /::\ \ ___
/:/\:\ \ /:/\:\__\ /:/ /\__\ /:/ /\ \ /:/\:\ \ /:/\:\ \ /\__\
/:/ /::\ \ /:/ /:/ / /:/ /:/ _/_ /:/ /::\ \ /:/ /::\__\ /:/ \:\ \ /:/ /
/:/_/:/\:\__\ /:/_/:/__/___ /:/_/:/ /\__\ /:/_/:/\:\__\ /:/_/:/\:|__| /:/__/ \:\__\ /:/__/
\:\/:/ \/__/ \:\/:::::/ / \:\/:/ /:/ / \:\/:/ /:/ / \:\/:/ /:/ / \:\ \ /:/ / /::\ \
\::/__/ \::/~~/~~~~ \::/_/:/ / \::/ /:/ / \::/_/:/ / \:\ /:/ / /:/\:\ \
\:\ \ \:\~~\ \:\/:/ / \/_/:/ / \:\/:/ / \:\/:/ / \/__\:\ \
\:\__\ \:\__\ \::/ / /:/ / \::/ / \::/ / \:\__\
\/__/ \/__/ \/__/ \/__/ \/__/ \/__/ \/__/
FTC team statistics and match simulation as a Discord bot. Reads the season_<year> table
that the pipeline writes; the two agree on table names
through model::tables.
One team's season card: OPR by phase, overall rank, location and sponsors. A team favourited in this server is marked with a star.
Simulates a matchup and renders a scorecard. Each alliance is two team numbers separated by
a space — /match "12345 6789" "4321 9876". Omit the blue alliance to score the red one on
its own, which renders an alliance card instead.
Totals follow the real scoreboard: each alliance's auto, teleop and endgame, plus the fouls its opponent committed.
With a team number, toggles it — the first call adds, the next removes. With no argument, lists the server's favourites. Favourites are per-server, shared by everyone in it, and persist across restarts.
Three pages of command reference and a note on how to read OPR.
Inside the monorepo the crate is a workspace member:
git clone --recurse-submodules https://github.com/ares-hq/ares.git
cd aresStandalone also works — model then resolves from its published branch:
git clone https://github.com/ares-hq/bot.git && cd botOnce per Supabase project:
create table favorites (
guild_id text not null,
team_number integer not null,
primary key (guild_id, team_number)
);guild_id is text because Discord snowflakes do not round-trip through JSON integers.
Create .env (the monorepo keeps one at its root, shared with the pipeline):
DISCORD_TOKEN=your-bot-token
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your-anon-key
# Optional
SEASON=2025 # pin the season table; defaults to the current season
DEBUG_MODE=false # register to one guild and refuse other channels
DEV_SERVER_ID1=... # guild for debug-mode command registration
DEV_CHANNEL_ID1=... # channels debug mode will answer in
DEV_CHANNEL_ID2=...SEASON exists so the bot and the pipeline can be pinned to the same year. Left unset, both
derive it from the clock, rolling over in August.
Debug mode registers commands to DEV_SERVER_ID1 — which propagates instantly, unlike the
global registration used in production — and declines to answer outside the listed channels.
cargo run --release --bin bot- Team reads are cached for 60 seconds. The pipeline rewrites the season table every few
minutes, so anything fresher than that is reused. A full
/matchis a singleteamNumber=in.(…)query, not one request per team. - Cards are SVG. askama templates in
templates/are rendered by resvg into PNG, on a blocking thread so rasterisation never stalls the gateway. Fonts are embedded withinclude_bytes!and parsed once. - Commands are one list.
commands::Commanddrives both registration and dispatch, so a new command cannot be registered without also being handled.
Fonts, templates and TLS roots are compiled into the binary, so a deploy is one static
file. CI builds it; the server runs it under systemd with Restart=always and picks up
new releases on an hourly timer. Setup in deploy/README.md.
systemctl status ares-bot.service
journalctl -u ares-bot.service -f- Rewritten in Rust (was Python)
- Server favourites persist in Supabase instead of vanishing on restart
- Match and alliance cards rendered from SVG templates
- Reads the shared
season_<year>tables through themodelcrate
- Bot verified; migrated to slash commands
- Moved to the official FIRST API with a custom OPR solver
- Added
/favorite
- Match simulation from team OPR
- Initial release on the FTCScout API: team lookup and help