UCS's specialized Discord bot for managing Capture The Flag (CTF) competitions, now with blockchain-backed task tracking, reputation, and document anchoring powered by Luce.
- π Event Scheduling β Schedule CTF events with timezone support
- ποΈ Channel Management β Automatically create dedicated CTF channels
- π₯ User Registration β Track team member participation
- ποΈ Database Storage β Persistent storage of CTF data and registrations
- π CTFd Integration β Ready for CTFd platform integration (coming soon)
- βοΈ Blockchain-Backed Tasks β Create and track tasks with immutable audit trail
- π Reputation System β Give +1/-1 rep via reactions, replies, or
/repcommand - π Document Anchoring β Permanently store documents on the blockchain
- β° Automatic Reminders β Task deadline reminders sent to a dedicated channel
/scheduleβ Schedule custom events with timezone support/createctfβ Create a CTF text channel and schedule its event/registerctfβ Register your participation for the CTF in the current channel/addchalctfβ Add challenges to a CTF/solvectfβ Mark a challenge as solved/archivectfβ Archive a completed CTF/chalptsβ View challenge points/summarizectfβ View CTF summary/syncchallengesβ Sync challenges from CTFd/pactβ Manage pacts
/task addβ Create a new task (title, description, assignee, deadline)/task list [period]β View remaining tasks for this week/month/quarter/year/task doneβ Mark a task as completed
/rep [downvote]β Give +1 or -1 rep (must reply to someone's message; response is ephemeral)/repleaderboard [limit]β View the reputation leaderboard
You can also give rep by:
- Reacting with π or π to someone's message
- Replying to someone with
+1,-1, π, or π
/document addβ Anchor a document to the blockchain/document getβ Retrieve an anchored document
/pingβ Check bot responsiveness/helpβ List all available commands
- Node.js v18.0.0 or higher
- pnpm package manager
- A Discord Bot Token (Create one here)
- Luce blockchain server running locally on port 5500
-
Clone the repository
git clone https://github.com/ucsits/ctfbot.git cd ctfbot -
Install dependencies
pnpm install
-
Configure environment variables
cp .env.example .env
Edit
.envand fill in the required values:DISCORD_TOKENβ Your Discord bot tokenGUILD_IDβ Your Discord server ID (for development)CTF_CATEGORY_IDβ Category ID where CTF channels will be createdCTFD_API_TOKENβ Your CTFd API token (optional)LUCE_PORTβ Luce blockchain RPC port (default: 5500)
-
Run the bot
Development mode (with auto-reload):
pnpm dev
Production mode:
pnpm start
-
Create a CTF Category
- Right-click in your server's channel list
- Select "Create Category"
- Name it "CTF" or similar
- Right-click the category β Copy ID
- Paste the ID in
.envasCTF_CATEGORY_ID
-
Invite the Bot
- Go to Discord Developer Portal
- Select your application
- Go to OAuth2 β URL Generator
- Select scopes:
bot,applications.commands - Select permissions:
Manage Channels,Manage Events,Send Messages,Embed Links,Add Reactions,Read Message History - Copy and visit the generated URL
src/
βββ commands/ # Slash command implementations
β βββ createctf.js # Create CTF channels and events
β βββ registerctf.js # User registration for CTFs
β βββ schedule.js # Generic event scheduling
β βββ task.js # Blockchain-backed task management
β βββ rep.js # Reputation command (ephemeral)
β βββ repleaderboard.js # Reputation leaderboard
β βββ document.js # Document anchoring
β βββ help.js # List all commands
β βββ ping.js # Health check
βββ listeners/ # Event listeners
β βββ ready.js # Bot ready event (starts reminder service)
β βββ messageCreate.js # Detects reply-based +1/-1/π/π
β βββ messageReactionAdd.js # Detects π/π reaction rep
β βββ applicationCommandRegistriesRegistered.js
βββ services/
β βββ reminder.js # Background reminder poller (every 30s)
βββ lib/
β βββ luce/
β β βββ index.js # Luce blockchain RPC client
β βββ constants/
β βββ embeds/
β βββ errors/
β βββ helpers/
β βββ middleware/
β βββ utils/
β βββ validators/
βββ database/
β βββ repositories/
β β βββ task.repository.js # Tasks + reminders queries
β β βββ reputation.repository.js # Reputation ledger queries
β β βββ document.repository.js # Document queries
β βββ connection.js
β βββ index.js
β βββ migrations.js
βββ migrations/ # SQL migration files
βββ index.js # Main entry point
Core tables (existing):
ctfsβ Stores CTF competition detailsctf_registrationsβ Stores user registrationsctf_challengesβ Stores CTF challengesctf_challenge_solvesβ Tracks challenge solvespactsβ Pact agreementsadminsβ Admin user IDs
Blockchain-backed tables (new):
tasksβ Tasks with reference to blockchain block heighttask_remindersβ Scheduled reminders for task deadlinesreputationsβ Reputation ledger (one entry per giver per day)documentsβ Anchored documents with blockchain reference
Every block stored on the Luce blockchain has a data field containing a JSON
string. Below are the supported schemas with their type discriminator.
Created when a user runs /task add.
{
"type": "task",
"v": 1,
"taskId": "a1b2c3d4-...",
"title": "Implement login page",
"description": "Build the user authentication UI",
"assignedTo": "123456789012345678",
"createdBy": "987654321098765432",
"deadline": 1740000000
}| Field | Type | Description |
|---|---|---|
type |
string |
Always "task" |
v |
number |
Schema version (currently 1) |
taskId |
string |
UUID v4 |
title |
string |
Task title |
description |
string |
Task description (may be empty) |
assignedTo |
string |
Discord user ID of assignee |
createdBy |
string |
Discord user ID of creator |
deadline |
number |
Unix timestamp (seconds) |
Created when a user runs /task done.
{
"type": "task_done",
"v": 1,
"taskId": "a1b2c3d4-...",
"completedBy": "123456789012345678"
}| Field | Type | Description |
|---|---|---|
type |
string |
Always "task_done" |
v |
number |
Schema version (currently 1) |
taskId |
string |
UUID of the completed task |
completedBy |
string |
Discord user ID of completer |
Created via reaction (π/π), reply (+1/-1/π/π), or /rep command.
{
"type": "rep",
"v": 1,
"toUser": "123456789012345678",
"fromUser": "987654321098765432",
"amount": 1,
"reason": "reaction",
"date": "2026-07-10"
}| Field | Type | Description |
|---|---|---|
type |
string |
Always "rep" |
v |
number |
Schema version (currently 1) |
toUser |
string |
Discord user ID of recipient |
fromUser |
string |
Discord user ID of giver |
amount |
number |
1 (upvote) or -1 (downvote) |
reason |
string |
Trigger source: "reaction", "reply", or "" (slash command) |
date |
string |
UTC date in YYYY-MM-DD format (enforces daily limit) |
Daily limit: A user may only give rep once per UTC calendar day, regardless of target or sign. This is enforced at both the application and database level.
Created when a user runs /document add.
{
"type": "document",
"v": 1,
"docId": "a1b2c3d4-...",
"title": "Meeting Notes 2026-07-10",
"content": "Discussed sprint goals for Q3...",
"author": "123456789012345678",
"mimeType": "text/plain"
}| Field | Type | Description |
|---|---|---|
type |
string |
Always "document" |
v |
number |
Schema version (currently 1) |
docId |
string |
UUID v4 |
title |
string |
Document title |
content |
string |
Document content (plain text; base64 for binary in future versions) |
author |
string |
Discord user ID of author |
mimeType |
string |
MIME type (default: "text/plain") |
The v field allows future schema evolution without breaking existing blocks.
When introducing a breaking change, increment v and handle both versions in
your parser.
A background service (src/services/reminder.js) polls every 30 seconds for
unsent task reminders. When a reminder is due (set to 1 hour before the task
deadline), it sends a message to the channel ID 1524933314119467200.
Flow:
/task addβ creates blockchain block + DB row + reminder schedule- Reminder service polls
task_reminderstable every 30s - Due reminders are sent to channel
1524933314119467200 - Reminder is marked
sent = 1to prevent duplicates
The bot is built using the @sapphire/framework which provides:
- Structured command handling
- Event listener system
- Plugin support
- TypeScript-ready architecture
pnpm testThis project uses tabs for indentation. Please ensure your editor is configured accordingly.
The bot includes a unified logging system built on top of the Sapphire framework's logger.
Set the LOG_LEVEL environment variable to control verbosity:
| Level | Usage |
|---|---|
error |
Fatal errors and exceptions only |
warn |
Warnings + errors |
info |
Normal operational messages (default) |
debug |
Detailed debugging information |
trace |
Everything, including individual HTTP request logs |
LOG_LEVEL=infoAll logs are output to stdout (info/debug/trace) and stderr (error/warn). There is no file-based logging β use your process manager (e.g., systemd, Docker, PM2) to capture and rotate stdout/stderr.
The logger (src/lib/logger.js) works in two modes:
- Pre-init mode: Before the bot has logged into Discord, the logger
falls back to raw
consolecalls with ISO timestamps. - Post-init mode: Once the SapphireClient is ready, the logger delegates to Sapphire's structured logger for consistent formatting across all commands and listeners.
Child loggers with a context prefix are used throughout the codebase:
| Prefix | Module |
|---|---|
[ErrorHandler] |
src/lib/errorHandler.js |
[CTFd] |
src/lib/ctfd/index.js |
[DB] |
src/database/index.js |
[Migration] |
src/database/migrations.js |
[Luce] |
src/lib/luce/index.js |
const { logger } = require('./lib/logger');
const myLog = logger.child('MyModule');
myLog.info('Operation completed successfully');
myLog.error('Something went wrong', error);
myLog.debug('Detailed info: %s', detail);Logger methods follow the same signature as console.log:
logger.info(message, ...optionalArgs).
Contributions are welcome! Please read our comprehensive guides:
- π Contributing Guide β Detailed contribution guidelines
- ποΈ Project Structure β Understanding the codebase
- π Quick Reference β Fast reference for common tasks
- ποΈ Architecture β System design and patterns
- π Changelog β Version history
- Fork the repository
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/ctfbot.git - Install dependencies:
pnpm install - Create a branch:
git checkout -b feature/your-feature - Make your changes
- Run linting:
pnpm run lint:fix - Commit:
git commit -m "feat: add your feature" - Push:
git push origin feature/your-feature - Open a Pull Request
See CONTRIBUTING.md for detailed guidelines.
- Blockchain-backed task management
- Reputation system (+1/-1 with daily limit)
- Document anchoring
- Additional rep triggers (message content, reactions on other platforms)
- Web dashboard for blockchain explorer
- Multi-chain support
This project is licensed under the MIT License β see the LICENSE file for details.
- Issues: GitHub Issues
- Discord: Join our server for support and discussions
- Built with discord.js
- Powered by @sapphire/framework
- Database: better-sqlite3
- Blockchain: Luce
Made with β€οΈ by UCS ITS