Skip to content

Architecture

This document describes the internal architecture of the Project 1999 Discord bot for developers and contributors.

index.ts <- Entry point: init DB, load commands & events, login
|-- app_data.ts <- TypeORM DataSource (PostgreSQL)
|-- client.ts <- Singleton TSClient instance
|-- types.ts <- Command interface, TSClient class
|
|-- commands/
| |-- census/ <- Character registration & management
| | |-- main.ts, alt.ts, bot.ts (declare characters)
| | |-- ding.ts, drop.ts, change.ts, claim.ts
| | |-- toons.ts, whois.ts (lookup)
| | |-- assign.ts, reassign.ts (officer tools)
| | |-- promote.ts (officer promotion)
| | \-- census_functions.ts (shared helpers)
| |
| |-- dkp/ <- Dragon Kill Points
| | |-- attendance.ts (modal-based raid attendance)
| | \-- dkp.ts (balance lookup)
| |
| |-- bank/ <- Guild bank management
| | |-- find.ts (item search + request flow)
| | |-- browse.ts (-> actually in utility/)
| | |-- expense.ts, income.ts, plat.ts
| | \-- item_functions.ts (MediaWiki integration)
| |
| \-- utility/ <- Roles, accounts, shared toons, misc
| |-- login.ts, account.ts, browse.ts, listaccounts.ts
| |-- add.ts, remove.ts, roles.ts, create_role.ts
| |-- note.ts, delete_shared_toon.ts
| |-- help.ts, ping.ts, permissions.ts
| \-- utility_functions.ts
|
|-- events/
| |-- ready.ts <- ClientReady: log in + start promotion timer
| |-- interaction_create.ts <- Central router for commands/autocomplete/modals
| \-- check_for_promotions.ts <- Scheduled daily promotion check
|
|-- entities/ <- TypeORM entity classes (1:1 with DB tables/views)
| |-- Census.ts, ActiveToons.ts, Dkp.ts, Bank.ts, ...
| \-- SharedModels.ts (SharedAccounts + SharedToons in one file)
|
|-- services/
| \-- role_manager.ts <- DiscordRoleManager helper class
|
\-- register_guild_commands.ts <- Standalone script to push commands to Discord API

Every command module in commands/ must export an object satisfying the Command interface from types.ts:

interface Command {
data: SlashCommandBuilder; // Required -- slash command definition
execute?(interaction): Promise<void>; // Required -- handler
autocomplete?(interaction): Promise<void>; // Optional
handleModal?(interaction): Promise<void>; // Optional (only attendance uses this)
}

The entry point (index.ts) dynamically imports every .js file under commands/<category>/ and registers it on client.commands if it has both data and execute.

Events are loaded from events/*.js. Each module exports:

  • name — the Discord.js event name (e.g., Events.InteractionCreate)
  • once — (optional) if true, listener fires only once
  • execute() — the handler function

The interaction_create event is the central router that dispatches to command.execute(), command.autocomplete(), or command.handleModal() based on interaction type.

The bot uses PostgreSQL via TypeORM with these main tables:

TableEntityPurpose
censusCensusCharacter records (name, class, level, status, owner)
dkpDkpPer-member DKP balances
attendanceAttendanceRaid attendance records
raidsRaidsRaid definitions with DKP modifiers
bankBankGuild bank inventory
itemsItemsLoot award history
platPlatPlatinum transaction ledger
shared_accountsSharedAccountsShared guild EQ accounts
shared_toonsSharedToonsCharacters on shared accounts
class_definitionsClassDefinitionsEQ class/title/level mappings
class_loreClassLoreClass flavour text
class_rolesClassRolesEQ class to Discord role mapping
self_rolesSelfRolesSelf-assignable Discord roles
racesRacesValid EQ races
trashTrashItems filtered from bank imports
inventoryInventoryCharacter inventory snapshots

Views:

  • ActiveToonsSELECT * FROM census WHERE status != 'Dropped'
  • StatusSELECT DISTINCT status FROM census

Schema synchronisation is disabled (synchronize: false). Migrations are managed externally.

  1. dotenv/config loads environment variables
  2. initializeDataSource() connects to PostgreSQL
  3. Command modules are dynamically imported and registered
  4. Event handlers are dynamically imported and attached
  5. client.login(token) connects to Discord
  6. On ClientReady, the promotion check runs immediately and then every 24 hours
IDPurpose
884164383498965042Census announcements channel (new members, promotion reminders)
851549677815070751General channel (promotion congratulations)
1213309735886000159Bank request channel (item requests from /find)