A feature-rich Discord bot for gacha game communities with automated coupon redemption, daily reset notifications, URL embed fixes, and community moderation tools.
# Install dependencies
bun install
# Set up environment variables (see .env.example)
cp .env.example .env
# Run in development mode
bun run dev
# Run tests
bun run test:run
# Type check
bunx tsc --noEmit
# Production build and run
bun run build
bun run start- Auto-redemption: Automatically redeem codes for BD2 (API-integrated)
- Multi-game support: BD2, NIKKE, Blue Archive
- Smart notifications: DM alerts for new codes, expiring codes, and weekly digests
- User preferences: Customizable notification settings
- Mod tools: Code management, subscriber analytics, manual triggers
Commands: /redeem subscribe, /redeem codes, /redeem status, /redeem preferences
- Automatically improves embeds in art channels using proxy services
- Deduplication system prevents reposting the same content
- Supports multiple URL formats and normalizes to best proxies
- Works in
#artand#nsfwchannels
- Configurable per-game notifications with checklists
- Random media attachments from CDN
- Timezone-aware scheduling
- Warning messages before resets
- Rate limiting for chat commands (3 per hour)
- Sensitive terms checking with timeout enforcement
- Spam statistics and violation tracking
- Permission-based command access
- 35+ fun response commands (media and text)
- Dynamic rate limiting
- Usage analytics and leaderboards
src/
βββ commands/ # Slash commands (/redeem, /spam, /gacha, etc.)
β βββ utils/ # Command utilities (commandBase, paginationBuilder)
β βββ tests/ # Command tests
βββ chatCommands/ # Message-based chat commands
β βββ mediaCommands.ts
β βββ types.ts
β βββ utils/
βββ handlers/ # Event handlers (messages, interactions)
βββ services/ # Business logic (gacha, embedFix, rules, etc.)
βββ bootstrap/ # Service initialization
βββ config/ # Centralized configuration (assets, etc.)
βββ utils/ # Shared utilities and helpers
βββ discord.ts # Bot orchestrator
βββ index.ts # Entry point with cron jobs
- Modular: Each file has a single, well-defined responsibility
- Service Layer: Business logic in services, commands are thin wrappers
- Utility Extraction: Common patterns in reusable helpers
- Type Safety: Proper TypeScript types, no
ascasts - Centralized Config: Asset URLs and constants in dedicated files
Standard Command:
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
import { replyEphemeral } from '../utils/interactionHelpers.js';
import { handleCommandError } from '../utils/commandErrorHandler.js';
export default {
data: new SlashCommandBuilder()
.setName('command-name')
.setDescription('Command description'),
async execute(interaction: ChatInputCommandInteraction) {
try {
await replyEphemeral(interaction, 'Response');
} catch (error) {
await handleCommandError(interaction, error, 'command-name');
}
},
};Utility Functions:
replyEphemeral(),replyWithEmbed()- Interaction helpershandleCommandError()- Centralized error handlingcheckModPermission()- Permission checksgetAssetUrls()- CDN asset managementcreateBasicEmbed()- Embed templates
Service Pattern:
export class MyService {
private static instance: MyService;
private constructor() { /* Initialize */ }
public static getInstance(): MyService {
if (!MyService.instance) {
MyService.instance = new MyService();
}
return MyService.instance;
}
}
export const getMyService = () => MyService.getInstance();- Framework: Vitest
- Coverage: 712 tests across all modules
- Run tests:
bun run test:run - Watch mode:
bun run test:watch
# Discord
WAIFUTOKEN=your_discord_bot_token
CLIENTID=your_discord_client_id
# AWS (for S3 data storage)
AWS_ACCESS_KEY_ID=your_aws_key
AWS_SECRET_ACCESS_KEY=your_aws_secret
AWS_REGION=us-east-1
S3_BUCKET=your_bucket_name
# CDN
CDN_DOMAIN_URL=https://your-cdn.com
# Optional
CATAPI=your_catapi_token
WAIFUPORT=8080
- Create
src/commands/command-name.tsusing standard command structure - Import in
src/discord.tsand add to command collection - Add tests in
src/commands/tests/ - Update
/helpcommand if user-facing
- Create service in
src/services/serviceName.ts - Use singleton pattern with
getInstance() - Export getter function:
export const getServiceName = () => ... - Add initialization in
src/bootstrap/serviceInitializer.tsif needed - Add tests in
src/services/tests/
- TypeScript: Strict mode enabled
- Imports: Use
.jsextensions for local imports - Logging: Use LogTape with appropriate levels (
warning,error,debug) - Error Handling: Always use
handleCommandError()in commands - Type Safety: Use
ChatInputCommandInteraction(notCommandInteraction)
- Keep PRs focused on a single feature or fix
- Include test coverage for new functionality
- Run
bunx tsc --noEmitandbun run test:runbefore submitting - Follow commit message format:
type: description- Types:
feat,fix,refactor,docs,chore,test
- Types:
- Include testing checklist in PR description
- End PR description with:
π€ Generated with [Claude Code](https://claude.com/claude-code)
- CLAUDE.md: Comprehensive codebase guide (LLM-optimized)
- Gacha System: See README section "Gacha Coupon Redemption System"
- URL Embed Fix: See README section "URL Embed Fix (Twitter/X + Pixiv)"
- Daily Resets: See README section "Managing Daily Reset Messages"
# Build and run
docker-compose up --build
# Stop
docker-compose downThis project is open source. See the repository for license information.
| Game | Auto-Redeem | Manual Redeem |
|---|---|---|
| Brown Dust 2 (BD2) | β | β |
| NIKKE | β | β |
| Blue Archive | β | β |
/redeem subscribe <game> <userid> <mode>- Subscribe (auto-redeem or notification-only)/redeem unsubscribe <game>- Unsubscribe/redeem status [game]- View subscription status/redeem codes <game>- View active codes/redeem preferences <game>- Configure notifications/redeem help- View help and FAQs
/redeem add- Add new coupon code/redeem remove- Deactivate code/redeem list- View all codes with status indicators/redeem stats- View analytics/redeem subscribers- View subscriber list/redeem trigger- Manual auto-redemption/redeem scrape- Fetch codes from BD2 Pulse
| Task | Production | Development |
|---|---|---|
| Auto-Redemption | Every 6h | Every 3min |
| Code Scraping | Every 30min | Every 2min |
| Expiration Warnings | Daily 09:00 | Every 5min |
| Weekly Digest | Sundays 12:00 | Every 10min |
| Expired Cleanup | Daily 00:00 | Every 15min |
- Update
GachaGameIdinsrc/utils/interfaces/GachaCoupon.interface.ts - Add config in
src/utils/data/gachaGamesConfig.ts - If auto-redeem: Implement handler in
src/services/gachaRedemptionService.ts - Restart bot
- Monitors
#artand#nsfwchannels for Twitter/X and Pixiv URLs - Extracts content IDs from any URL format
- Converts to proxy services (fixupx.com, phixiv.net)
- Suppresses original embeds
- Prevents duplicates by tracking content IDs
Twitter/X β https://fixupx.com/i/status/{id}
twitter.com/*/status/*,x.com/*/status/*mobile.twitter.com,vxtwitter.com,fxtwitter.com, etc.
Pixiv β https://phixiv.net/artworks/{id}
pixiv.net/artworks/*,pixiv.net/en/artworks/*pixiv.net/member_illust.php?illust_id=*
Edit src/utils/data/gamesResetConfig.ts to add/remove games.
Structure:
const gameResetConfig: DailyResetConfig = {
game: 'Game Name',
channelName: 'discord-channel',
roleName: 'RoleName', // Optional
resetTime: { hour: 19, minute: 0 }, // UTC
embedConfig: { /* ... */ },
checklist: [ /* ... */ ],
mediaConfig: { /* ... */ }
};Dev Mode: Set NODE_ENV=development for 5-minute intervals instead of scheduled times.
Uses LogTape for structured logging.
Levels:
- Production:
warninganderroronly - Development:
debugand above - Override:
LOG_LEVEL=debug bun run start
Categories:
bot- General / commandsbot.discord- Discord eventsbot.gacha- Coupon systembot.embed-fix- URL fixesbot.scheduler- Cron jobsbot.media- CDN mediabot.rules- Rules management
Moderator Commands:
- Require
modsrole OR Discord administrator permission - Check:
await checkModPermission(interaction)
Rate Limiting:
- 3 chat commands per hour per user
- Resets at top of every hour (UTC)
- View status:
/spam check - Mod stats:
/spam stats
For complete codebase patterns and LLM-optimized context, see CLAUDE.md.