-
Notifications
You must be signed in to change notification settings - Fork 0
Building a Bot On This
Generated from the thetowersdk repository. Edits made here are overwritten on the next push — change the source file instead.
A Tower bot answers the same few hundred questions from data that does not change between releases, so most of it is already written:
import { calculatorCommands, createTowerBot } from 'thetowersdk/bot'
const bot = createTowerBot({ commands: calculatorCommands() })
const reply = await bot.run('lab-research', { args: { labName: 'Damage', targetLevel: 10 } })calculatorCommands() turns every builder into a command — options, coercion, formatting and
notes — because each builder already describes its own inputs. A builder added to the SDK later
appears in your bot with no edit. The registry handles dispatch, argument coercion, memoising and
errors; bot.run never throws, so a gateway handler cannot leave someone watching a spinner until
the interaction expires.
Nothing in it knows what a gateway is, so the same commands run under discord.js, on a CLI, and at full speed in a test.
npm run bot:demo # list the commands
npm run bot:demo -- lab-research labName=Damage targetLevel=10
npm run bot:testtemplates/bot-starter.ts is a complete bot with a hand-written command
and a CLI. docs/BUILDING_A_BOT.md covers performance and the Discord
adapter.
Three Discord bots are built on this package and run in production. The rules below came out of getting that wrong first, and almost none of them are Discord-specific — they apply to Slack, Telegram, Matrix, or anything else built around callbacks with opaque ids.
Treat the bot as a first-class client, not a bolt-on. Domain logic — parsing, normalization, cost math, run shapes — belongs in a shared package that both the bot and your UI import. Only platform glue (embeds, components, modals, collectors) lives in the bot repo. The failure this prevents is subtle and expensive: a bot that reimplements a calculation drifts from the website, and now the same question has two answers depending on where a player asks it. That is the whole reason this SDK is framework-agnostic.
One router. Every interaction dispatches through a single entry point, with persistent handlers registered against it. Scattered per-feature listeners are how you get two handlers responding to one click, and the second one erroring because the first already replied.
Component ids get exactly one owner. Build and parse them in one module, and let handlers
consume parsed values only. A custom id is a wire format — it is serialized, handed to a remote
client, and handed back later, possibly after your process restarted. Once a handler does
split(':') inline, the format is defined in as many places as it is read, and adding a field
breaks callers nobody remembered. Registry lookup should use exact or longest-prefix matching for
the same reason, never slice(prefix.length) at each call site.
Interactions are owned. Wait on a modal by filtering on the component id and the initiating user. Without both, one user's click resolves another user's pending wait — a bug that never appears in single-user testing and appears constantly in a busy channel.
Guard tokens before touching session state. Anything carrying a session token must check it is present and unexpired first. Callbacks arrive late, arrive twice, and arrive after a redeploy; treating an expired token as a valid one corrupts state rather than erroring.
Support every component kind from the start. Buttons, all select-menu variants, and modals — even when the current feature only uses one. Retrofitting a router that assumed buttons is worse than writing it general, and it is a small amount of extra work up front.
Stay quiet when the interaction is not yours. An unregistered submission that belongs to a command-local ownership flow should return silently, not log an error or reply. Otherwise normal operation fills your logs with noise and hides the real failures.
Keep the probe scripts. Diagnostic tooling — "what does this record actually look like in the
database" — belongs in a checked-in scripts/ directory, not in a throwaway file. You will need it
again, and next time it will be during an incident.
Generated from TheTowerSDK — do not edit here. Docs and live demos: https://tmrxjd.github.io/TheTowerSDK/
- Quick Start
- Entry Points
- How It Fits Together
- Getting a Save File
- Reading a Save
- Reading The Community Wiki
- Formulas
- Builders
- Charts
- Effective Paths
- Examples
- Templates
- Building a Bot On This
- Google Sheets
- Desktop and Mobile
- Optional Add-ons
- Using It With An AI Agent
- Using Your Own Artwork
- Names And Acronyms
- Patch Notes
- Accuracy
- Versioning
- Where the Docs Live
- Contributing
- Credits
- License
Guides
Examples
- 01-browse-game-data.ts
- 02-read-a-save-file.ts
- 03-plan-upgrades-from-a-save.ts
- 04-generate-a-chart.ts
- 05-generate-a-cost-table.ts
- 06-read-the-community-wiki.ts
- 07-format-like-the-game.ts
- 08-build-a-calculator.ts
- 09-build-a-bot.ts
- 10-read-a-sheet.ts
- 11-build-a-knowledge-base.ts
- 12-show-module-and-card-art.ts
Templates