Docs

TC-Bot looks like “a Discord bot” from the outside; underneath it is a straight-line boot sequence, recursive command discovery, optional Google Sheets caching, and a mopup scheduler that keeps two channels honest about timers. This page sketches that shape, the sheet dependency for troop-heavy commands, and the sharp edges that show up when credentials or channel IDs drift.

Repository map

At the repository root, package.json drives a single app: compile with tsc, run dist/tc-bot.js. Source layout is intentionally flat:

tc-bot/
  src/
    commands/
      aow/          # Ark of War: gearcheck, healtroop, its, mopup
      utility/      # help, ping, metrics, reboot
    events/         # interaction handlers, ready, messageCreate
    helper/         # constants, sheets cache, mopup math, metrics DB, idempotency
    env/            # dotenv loading
  dist/             # compiled output (after build)

If CI only runs pnpm install and pnpm run validate at the root, you are aligned with how the project expects to ship. If someone tries to run TypeScript files by hand without building, send them to pnpm run build with kindness.

Boot flow (high level)

Slash commands vs. deploy

Commands are implemented as Discord slash command builders plus an executehandler. pnpm run deploy walks src/commands in TypeScript and pushes definitions to the application (guild commands when GUILD_ID is set - check src/deployCommands.ts for the exact REST routes). The running bot loads compiled .js from dist/commands after build. Change a command name or option? Rebuild and redeploy; Discord does not guess.

Google Sheets: why the credentials file matters

Healing (/healtroop) and iTS (/its) pull troop rows through getSheetRowsCached, backed by the Sheets API and GOOGLE_SPREADSHEET_ID / GOOGLE_SHEET_ID. Boot validates those IDs and prefetches rows to warm the cache. If client_secret.json is missing or the service account cannot read the sheet, initialization fails fast - there is no silent fallback to fictional troop stats.

Gear check (/gearcheck) uses fixed level breakpoints and multipliers from helper/constants.ts, so it remains usable even when sheet access is misconfigured - once the bot starts at all.

Mopup: channels, embeds, and time windows

Mopup timing is computed from a deterministic schedule (helper/mopup.ts): alternating daily windows expressed relative to epoch-aligned “days,” surfaced as embeds with status and countdown-style timestamps. The bot also schedules periodic updates to the configured status and timer channels (CHANNEL_ID1, CHANNEL_ID2) so members see live countdowns without typing commands.

Optional legacy !tcmu message commands require ENABLE_LEGACY_MESSAGE_COMMANDS and extra intents (GuildMessages, MessageContent). Idempotency guards and duplicate-reply cleanup exist because Discord can retry events; the bot tries not to spam the same answer twice.

Metrics and PM2

Usage and operational metrics land in SQLite (SQLITE_DB_PATH) with configurable retention and flush batching. PM2 IO counters in helper/metrics.ts expose command counts, error counts, Discord latency, and sheet cache stats for operators who want dashboards without reading logs.

Testing and CI

Vitest covers logic that must not drift silently - validators, formatters, and command math. Treat failing tests as regressions in public-facing numbers: alliances remember the old answer. pnpm run validate is the “would CI merge this?” button; run it before tagging a release.

Building and validating like you mean it

  • pnpm run build - TypeScript to dist/, the artifact nodeactually runs.
  • pnpm run validate - format, lint, typecheck, tests per project script.

Using both before deploy saves everyone the emotional cost of a Sunday rollback because /healtroop suddenly throws on a mistyped tier.