This bot is a guild-only Discord order system for boosting services. It is built with TypeScript, discord.js v14, and SQLite, and it is designed to move an order from a public product panel to a private ticket, then through payment confirmation, booster assignment, proof-based completion, and final closure.
The codebase is currently structured around a single interaction router in src/interactionHandler.ts, a product/pricing source of truth in src/catalog.ts, a pricing engine in src/pricing.ts, and a synchronous SQLite repository in src/db/ordersRepository.ts.
- Node.js 25+
- TypeScript with
NodeNextmodule resolution discord.jsv14dotenvnode:sqliteviaDatabaseSynctsxfor development execution
src/index.ts: bot startup, client initialization, command registration on ready, and global interaction error handlingsrc/interactionHandler.ts: all slash command, button, select menu, modal, and context menu logicsrc/catalog.ts: product definitions, panel content, promo image mapping, rank/package/trophy configurationsrc/pricing.ts: quote engine and validation logicsrc/components.ts: Discord component builders and custom IDssrc/embeds.ts: embed and attachment builders for public panels, wizard previews, tickets, claims, and completed jobssrc/config.ts:.envloading and runtime config validationsrc/permissions.ts: role-based authorization checkssrc/db/ordersRepository.ts: SQLite schema and order/audit log persistencesrc/draftStore.ts: in-memory ephemeral wizard state per userpromo_images/: images used for public product panels and ticket summaries
This section is written for someone who has never created or run a Discord bot before.
- Install Node.js
25or newer. - Install Git if you want to clone the repository instead of downloading it as a ZIP.
- Make sure you have a Discord account with permission to manage the server where the bot will run.
- Open the Discord Developer Portal.
- Click New Application.
- Enter a name for the bot application and create it.
- Open the application after it is created.
- In General Information, copy the Application ID. You will use this later as
DISCORD_CLIENT_ID.
- Open the Bot tab in the Developer Portal.
- If the application does not already have a bot user, click Add Bot.
- In the bot settings, reset or copy the bot token.
- Store that token safely. You will place it in
.envasDISCORD_TOKEN.
- In the Bot tab, enable Message Content Intent.
- Leave the bot configured for guild/server usage. This project is designed for servers, not for direct messages.
- Save the changes before leaving the page.
Why this matters:
- The bot listens for proof images posted directly in ticket messages.
- Without Message Content Intent, Discord may not provide the message data the bot needs for attachment-based proof handling.
- Use the application's install flow in the Developer Portal to generate an invite link for the bot.
- Choose the server where you want the bot to live.
- Authorize the bot.
- Make sure the bot can:
- view channels
- send messages
- read message history
- attach files
- embed links
- create and manage ticket channels
For a first setup, giving the bot broad server permissions is the simplest option. You can tighten them later once the flow is working.
Create or choose the following Discord objects before starting the bot:
- One public text channel where staff can deploy product panels
- One category for private order tickets
- One text channel for booster claim posts
- One text channel for completed job logs
- Optionally one text channel for audit logs
- One support role
- One manager role
- One admin role
- One booster role
- In the Discord client, enable Developer Mode.
- Copy the server ID for the target server.
- Copy the ID of each required channel and category.
- Copy the ID of each required role.
You will need these values for the .env file.
- Clone the repository or download it as a ZIP.
- Open a terminal in the project folder.
- Run:
npm.cmd install- Copy
.env.exampleto.env. - Fill in every required value.
Required values:
DISCORD_TOKEN: bot token from the Developer PortalDISCORD_CLIENT_ID: application ID from the Developer PortalDISCORD_GUILD_ID: Discord server ID where the bot will runORDER_HERE_CHANNEL_ID: default public channel used when/deploy-order-panelis run without a channel argumentBOOST_TICKET_CATEGORY_ID: category where private tickets are createdCLAIM_ORDERS_CHANNEL_ID: channel where paid jobs are posted for boostersCOMPLETED_JOBS_CHANNEL_ID: channel where completed orders are loggedSUPPORT_ROLE_ID: support role IDMANAGER_ROLE_ID: manager role IDADMIN_ROLE_ID: admin role IDSERVER_BOOSTERS_ROLE_ID: booster role ID
Optional values:
AUDIT_LOG_CHANNEL_ID: channel for audit embedsDATABASE_PATH: SQLite file path, defaults to./data/orders.sqliteEUR_TO_USD_RATE: exchange rate used when the configured product currency is EUR
Run these commands from the project root:
npm.cmd run check
npm.cmd run buildIf either command fails, fix that problem before continuing.
Run:
npm.cmd run register:commandsThis registers the bot's guild-scoped slash commands for the server configured in .env.
Important:
- Re-run this command when slash command definitions change.
- Re-run this command when enabled products change and the available
/orderchoices should update.
For development:
npm.cmd run devFor a built/runtime launch:
npm.cmd run startWhen the bot starts successfully, you should see:
- a login message
- the number of recovered active orders from SQLite
For a basic deployment:
- Place the project on a machine that stays online.
- Install Node.js
25or newer on that machine. - Copy the project files and your
.envfile there. - Run
npm.cmd install. - Run
npm.cmd run build. - Run
npm.cmd run start.
For long-term uptime:
- Configure your host or operating system to restart the bot automatically after a reboot or crash.
- Keep the
.envfile anddata/directory persistent. - Re-run
npm.cmd run register:commandsafter command-definition changes.
- Run
/deploy-order-panelas staff. - Click the panel button or run
/order. - Complete the selection flow and confirm a ticket is created.
- Confirm payment as staff.
- Search for a booster.
- Claim the order as a booster.
- Upload a proof image in the ticket or use Submit Proof Link.
- Complete the order.
- Confirm the completed job log is posted.
- Close the ticket.
If those steps work, the bot is configured correctly enough for normal use.
All runtime configuration comes from .env. Start by copying .env.example to .env.
Required keys:
DISCORD_TOKEN: bot tokenDISCORD_CLIENT_ID: application/client IDDISCORD_GUILD_ID: target guild IDORDER_HERE_CHANNEL_ID: fallback text channel used if/deploy-order-panelis run without a channel argumentBOOST_TICKET_CATEGORY_ID: category where new private order tickets are createdCLAIM_ORDERS_CHANNEL_ID: channel where paid jobs are posted for boosters to claimCOMPLETED_JOBS_CHANNEL_ID: channel where completed jobs are loggedSUPPORT_ROLE_ID: staff role with ticket/payment permissionsMANAGER_ROLE_ID: additional staff roleADMIN_ROLE_ID: additional staff roleSERVER_BOOSTERS_ROLE_ID: role allowed to claim jobs
Optional keys:
AUDIT_LOG_CHANNEL_ID: if set, audit embeds are posted here for state transitionsDATABASE_PATH: defaults to./data/orders.sqliteEUR_TO_USD_RATE: defaults to1.08, must be a positive number
Behavioral notes:
DISCORD_CLIENT_IDalso acceptsCLIENT_IDas a fallback alias.DISCORD_GUILD_IDalso acceptsGUILD_IDas a fallback alias.embedFooteris hardcoded in code toElite Boosters.rootDiris the current working directory and is used to resolve promo image files.
The bot expects the guild to have:
- One text channel for public product panels
- One category to hold private boost tickets
- One text channel for booster claim posts
- One text channel for completed job logs
- Optionally one text channel for audit log messages
- Three staff roles: support, manager, admin
- One booster role: server boosters
The bot should have permission to:
- View and send messages in all operational channels
- Create channels in the ticket category
- Manage channels and permission overwrites for tickets
- Manage messages in tickets
- Attach files and embed links
Developer Portal requirement:
- Enable the Message Content Intent for the application. The proof-upload listener relies on
MESSAGE_CREATEattachment data, and Discord does not reliably include attachments for guild messages without that intent.
Windows commands used in this repository:
npm.cmd installnpm.cmd run devnpm.cmd run checknpm.cmd run buildnpm.cmd run startnpm.cmd run register:commands
Recommended setup sequence:
- Run
npm.cmd install - Copy
.env.exampleto.env - Fill every required ID and token
- Run
npm.cmd run check - Run
npm.cmd run build - Run
npm.cmd run register:commands - Run
npm.cmd run devornpm.cmd run start
Important:
- Re-run
npm.cmd run register:commandswhenever command definitions change. - The command set is guild-scoped in
src/index.tsand is registered on startup withapplication.commands.set(commands, config.guildId).
/deploy-order-panel
- Staff-only unless the member also has
ManageGuild - Arguments:
service(required): one enabled product fromsrc/catalog.tschannel(optional): target text channel
- Behavior:
- Posts a product-specific public embed with the product image and a single button
- Responds ephemerally to the command user with the deployment confirmation
/order
- Available to guild users
- Argument:
service(required): one enabled product fromsrc/catalog.ts
- Behavior:
- Starts the order wizard ephemerally with the product preselected
/cancel-order
- Staff-only at runtime
- Arguments:
order-id(required): the order ID shown in the ticket summaryreason(optional): staff note stored in audit logs
- Behavior:
- Cancels an active order from anywhere in the guild
- Archives the related ticket for staff
- Disables the claim post if one exists
Manage Order
- Staff-only
- Run on a target user
- Shows an ephemeral embed summarizing that user’s active orders
On startup, the bot:
- Loads and validates environment configuration
- Creates the SQLite repository and ensures tables exist
- Creates the in-memory draft store
- Logs in the Discord client
- Registers the current command set for the configured guild
- Reads active orders from SQLite
- Attempts to re-sync each active ticket summary message
Current caveat:
- Ticket resync depends on guild channel cache lookups. If a relevant channel is not in cache, sync can fail until the channel is available in cache.
- The bot also listens to
messageCreateso it can detect proof attachments posted directly in ticket channels.
Each product has its own public panel content defined in src/catalog.ts:
panelTitlepanelDescriptionpanelButtonLabelpromoImagePath
When a staff member runs /deploy-order-panel service:<product>:
- The bot looks up the enabled product in the catalog
- It builds a public embed using the product’s panel text
- It attaches the product promo image from
promo_images/ - It posts a single product button whose custom ID includes the product ID
The public panel is not interactive beyond opening the order wizard. Pricing is not computed from the public embed; pricing comes from the quote engine inside the order flow.
The order wizard is always ephemeral.
Entry points:
- Clicking a public product panel button
- Running
/order service:<product>
Wizard state:
- Stored per user in
DraftStore - Drafts expire after 15 minutes of inactivity
- Drafts are reset when a user starts a fresh flow from a panel or
/order
Wizard components are built from src/components.ts:
- Product select
- Service select:
BoostorCarry - Ranked profile select when applicable
- Current value select
- Desired value select
- Package select
- Numeric range modal trigger for trophy-based products
Wizard completion rules:
- Ranked products require:
- service mode
- profile when the product has more than one profile
- current value
- desired value
- Numeric range products require:
- service mode
- current value
- desired value
- Breakpoint products require:
- service mode
- current value
- desired value
- Package products require:
- service mode
- selected package
When the draft becomes complete:
- The bot computes a quote
- It creates a private ticket channel
- It posts the ticket summary and action buttons
- It writes the order to SQLite
- It clears the draft
- It edits the ephemeral interaction reply to point the user to the created ticket
Numeric range products use a modal instead of select menus.
Current implementation:
- This is used for
trophies - The user enters
currentanddesiredvalues in text inputs - On submit, the values are validated by the pricing engine
New ticket channels are created in BOOST_TICKET_CATEGORY_ID.
Ticket channel permissions:
@everyone: deniedViewChannel- Customer: allowed to view/send/read/attach/embed
- Support, manager, admin roles: allowed to view/send/read/attach/embed
- Bot user: allowed to view/send/read/attach/embed/manage channels/manage messages
Ticket metadata stored on creation:
- order ID
- customer snapshot
- product and service mode
- profile/package/current/desired selection data
- native subtotal and total
- USD total
- price breakdown
- promo image path
The first ticket message:
- Mentions the support role
- Includes the ticket summary embed
- Includes action buttons for staff and boosters
The ticket summary message always includes two rows of buttons.
Row 1:
Confirm PaymentSearch Booster
Row 2:
Complete OrderSubmit Proof LinkCancel OrderClose Ticket
Enablement rules:
Confirm Payment: enabled only inAWAITING_PAYMENTSearch Booster: enabled only inPAIDComplete Order: enabled only inIN_PROGRESSSubmit Proof Link: enabled only inIN_PROGRESSCancel Order: enabled only inAWAITING_PAYMENT,PAID,SEARCHING_BOOSTER, andIN_PROGRESSClose Ticket: disabled once the ticket is alreadyCLOSEDorCANCELLED
The implemented order status flow is:
AWAITING_PAYMENT -> PAID -> SEARCHING_BOOSTER -> IN_PROGRESS -> COMPLETED -> CLOSED
Cancellation is also available from any active state:
AWAITING_PAYMENT | PAID | SEARCHING_BOOSTER | IN_PROGRESS -> CANCELLED
Initial state after ticket creation.
Allowed actor:
- Staff only for payment confirmation
Reached when staff clicks Confirm Payment.
Effects:
paid_atis set- ticket summary is refreshed
- audit log entry is written
- optional audit channel embed is posted
Allowed next actor:
- Staff only for booster search
Reached when staff clicks Search Booster.
Effects:
- Claim message is posted in
CLAIM_ORDERS_CHANNEL_ID - Booster role is pinged
- Claim message ID is stored on the order
- ticket summary is refreshed
- audit log entry is written
Allowed next actor:
- Booster role members can claim
Reached when a booster claims the order.
Effects:
- assigned booster ID and name snapshot are stored
assigned_atis set- ticket permission overwrite is added for the booster
- claim message is updated to show who claimed it
- ticket summary is refreshed
- an informational embed is posted in the ticket instructing the booster to upload proof or use the backup proof-link modal
Reached when the assigned booster or staff clicks Complete Order and proof is found.
Proof requirement:
- Proof can come from either:
- the newest qualifying image attachment posted in the ticket by the assigned booster, with acting staff attachments also considered at completion time
- a direct image URL submitted through the
Submit Proof Linkmodal by the assigned booster or acting staff member
- Supported Discord image attachments posted by the assigned booster in an in-progress ticket are persisted immediately
- Proof links must be direct
httporhttpsimage URLs ending inpng,jpg,jpeg,webp, orgif - Completion compares the saved proof record against the latest qualifying attachment and uses whichever one is newer
- If neither a qualifying attachment nor a saved proof link exists, completion is rejected
Effects:
- proof attachment URL is stored
completed_atis set- completed-job embed is posted to
COMPLETED_JOBS_CHANNEL_ID - ticket summary is refreshed
- audit log entry is written
Reached when staff clicks Cancel Order in the ticket or runs /cancel-order.
Effects:
- status becomes
CANCELLED - optional reason is stored in the audit log details
- existing claim post, if present, is updated and disabled
- customer loses
ViewChannel,SendMessages, andAttachFiles - assigned booster, if present, also loses
ViewChannel,SendMessages, andAttachFiles - ticket is renamed with a
cancelled-prefix if not already prefixed - ticket summary is refreshed
- audit log entry is written
Cancelled tickets are treated as staff-only archives and are excluded from active-order recovery.
Reached when staff clicks Close Ticket.
Effects:
closed_atis set- customer loses
ViewChannel,SendMessages, andAttachFiles - assigned booster, if present, also loses
ViewChannel,SendMessages, andAttachFiles - ticket is renamed with a
closed-prefix if not already prefixed - ticket summary is refreshed
- audit log entry is written
Operationally, closed tickets are intended to become staff-only archives.
Booster claiming is driven from the claim channel, not from the ticket itself.
Claim post behavior:
- Sent to
CLAIM_ORDERS_CHANNEL_ID - Pings
SERVER_BOOSTERS_ROLE_ID - Shows customer, service, selection, USD total, and ticket link
- Includes a claim button
Claim rules:
- Only members with the booster role can claim
- Only orders in
SEARCHING_BOOSTERcan be claimed - The repository update is conditional, so the first successful claimant wins
Defined as any member with one of:
SUPPORT_ROLE_IDMANAGER_ROLE_IDADMIN_ROLE_ID
Staff can:
- deploy product panels
- use the
Manage Ordercontext menu - use
/cancel-order - confirm payment
- search for boosters
- cancel active orders
- complete any order
- close tickets
Defined as members with SERVER_BOOSTERS_ROLE_ID.
Boosters can:
- claim posted jobs
- complete an order only if they are the assigned booster
Customers can:
- click public product panels
- use
/order - progress through the ephemeral wizard
- access and chat in their private ticket until it is closed
All pricing and product behavior comes from src/catalog.ts and src/pricing.ts.
Boostuses the base subtotalCarrymultiplies the native subtotal by2- If the product currency is
USD, USD total equals native total - If the product currency is
EUR, USD total isnativeTotal * EUR_TO_USD_RATE - Missing or invalid price paths are rejected with an error; the bot does not estimate prices
Selection mode:
ranked
Profiles:
Diamond to MythicMythic to Pro
Diamond to Mythic path:
Diamond 1 -> Diamond 2: 3 EURDiamond 2 -> Diamond 3: 3 EURDiamond 3 -> Mythic 1: 3 EUR
Diamond to Mythic options:
Diamond 1Diamond 2Diamond 3Mythic 1
Mythic to Pro path:
Mythic 1 -> Mythic 2: 35 EURMythic 2 -> Mythic 3: 70 EURMythic 3 -> Pro: 135 EUR
Mythic to Pro options:
Mythic 1Mythic 2Mythic 3PRO
Notes:
- Ranked orders require both current and desired values
- When multiple profiles exist, a profile must be chosen before the draft is complete
- Pricing is computed by walking linked steps from current to desired
Selection mode:
numeric_range
Requirements:
- Current and desired values must be integers
- Desired must be greater than current
- Values must remain between
0and150000 - Values must be multiples of
1000
Pricing brackets:
0-10000: 6 USD per 100010000-20000: 8 USD per 100020000-30000: 12 USD per 100030000-40000: 14 USD per 100040000-50000: 15 USD per 100050000-60000: 16 USD per 100060000-70000: 20 USD per 100070000-80000: 22 USD per 100080000-90000: 24 USD per 100090000-100000: 30 USD per 1000100000-125000: 36 USD per 1000125000-150000: 40 USD per 1000
Selection mode:
breakpoints
Supported milestone options:
075090010001100120013001400150016001700180019002000
Configured step ladder:
0 -> 750: 11.25 USD750 -> 900: 5 USD900 -> 1000: 2.3 USD1000 -> 1100: 4 USD1100 -> 1200: 4.5 USD1200 -> 1300: 5 USD1300 -> 1400: 5.5 USD1400 -> 1500: 7.5 USD1500 -> 1600: 7.5 USD1600 -> 1700: 8.5 USD1700 -> 1800: 10.5 USD1800 -> 1900: 12.5 USD1900 -> 2000: 13.5 USD
Important:
- Only exact configured breakpoints are accepted
- If a path is not configured, the order is rejected
Selection mode:
breakpoints
Options:
IIIIII
Configured steps:
I -> II: 30 EURII -> III: 77 EUR
Selection mode:
package
Packages:
50 wins: 23 USD69 wins: 33 USD101 wins: 51 USD111 wins: 63 USD125 wins: 77 USD200 wins: 115 USD
Package orders require a selected package and do not use current/desired progression steps.
SQLite schema is initialized automatically in OrdersRepository.
Stores:
- customer snapshot
- product and service mode
- ranked profile if applicable
- current/desired/package values
- selection summary
- native subtotal and total
- native currency
- USD total
- status
- ticket channel/message IDs
- claim message ID
- assigned booster snapshot
- proof attachment URL
- proof source and proof update timestamp
- timestamps
- JSON metadata
Stores:
- order ID
- action
- actor ID
- details
- timestamp
Audit actions currently written by code include:
ORDER_CREATEDPAYMENT_CONFIRMEDBOOSTER_SEARCH_POSTEDBOOSTER_ASSIGNEDORDER_COMPLETEDTICKET_CLOSED
- Product-specific title and description
- Product promo image
- Single product button
- Product
- Service mode
- Ranked profile when applicable
- Current/desired/package values
- Native total
- USD total
- Product image when available
- Customer
- Service mode
- Order status
- Selection summary
- Native total
- USD total
- Ranked profile if applicable
- Assigned booster if applicable
- Price breakdown
- Stored proof image when available, otherwise the product promo image
- Customer
- Service mode
- Selection
- USD total
- Ticket link
- Customer
- Booster
- Service
- Starting and final values
- Total price
- Status
- Proof image if available
Global interaction error handling lives in src/index.ts.
Behavior:
- Exceptions are logged to the console
- Repliable interactions receive an ephemeral error message
- If the interaction was already replied to or deferred, the bot uses
followUp - Otherwise it uses
reply
- This is a guild-only bot; interactions outside a guild are rejected
- There is no automated test suite in the repo
- SQLite access is synchronous
- Draft selections live only in memory; they are not persisted across restarts
- Active orders are persisted and ticket summaries are re-synced on startup
- Promo images must exist in
promo_images/and stay aligned withsrc/catalog.ts - Product pricing and validation live in code, not in the public panel art
- The public panel is product-specific, but the actual price shown to staff/customers comes from the quote engine during order creation
After configuration or pricing changes, validate:
/deploy-order-panelfor each product- Public panel button -> ephemeral wizard flow
/orderflow- Ranked profile and step selection
- Trophy modal validation
- Breakpoint validation for Brawler and Prestige
- Package selection for Winstreak
- Ticket creation and support ping
- Payment confirmation
- Booster search post
- Booster claim
- Proof upload, proof-link fallback, and completion
- Completed jobs log
- Ticket closure and visibility removal
If behavior in this document and code ever diverge, the code in these files is authoritative:
src/interactionHandler.tssrc/catalog.tssrc/pricing.tssrc/components.tssrc/embeds.tssrc/db/ordersRepository.ts