This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a Telegram bot aiming to help users make sure they don't miss adding compliance text to their messages. It's built with TypeScript, Bun runtime, and the grammY framework. The bot supports channel posting and user-specific channel configuration with persistent storage.
Language: This is a Russian-language bot. All user-facing messages, commands, and responses must be in Russian.
- Runtime: Bun (not Node.js) - use
buncommands for all operations - Package Manager: Bun (
bun install,bun add,bun remove) - Development: Hot reload is available via
bun --watch
# Development & Running
bun run dev # Development mode with hot reload
bun run start # Production mode
# Testing
bun test # Run all tests
bun test path/to/file.test.ts # Run specific test file
bun run test:coverage # Run tests with coverage report
# Code Quality
bun run lint # Check for linting issues
bun run lint:fix # Auto-fix linting issues
bun run format # Format code with Prettier
bun run format:check # Check formatting without modifyingThe bot uses a modular architecture with separate concerns:
- Entry point: src/index.ts - initializes Sentry and database, installs session middleware, registers all commands/handlers, and starts the bot with graceful shutdown handling
- Configuration: src/config/ - bot instance (
bot.ts), session configuration (session.ts), and Sentry initialization (sentry.ts) - Database: src/db/ - SQLite database for shared channel settings
database.ts- Database initialization and CRUD operations
- Commands: src/commands/ - command handlers registered via functions
definitions.ts- Centralized command definitions (single source of truth for all command metadata)start.ts- Welcome messagehelp.ts- Help command (dynamically generated from definitions)info.ts- Show bot configuration and channel settingschannel.ts- Channel management (/setchannel,/channelstatus,/removechannel)settings.ts- Channel settings management (/set_fa_blurb)notifications.ts- Notification management (/notify_add,/notify_remove,/notify_list)
- Handlers: src/handlers/ - generic message handling, error handling, and rejection notifications
- Utilities: src/utils.ts - channel resolution and formatting utilities
- Singleton bot instance: src/config/bot.ts exports a single
botinstance with session context type used throughout the app- IMPORTANT: The bot instance should NEVER be passed as a parameter to functions. Always import it directly from
src/config/bot.tswhere needed. - Access the bot API via
bot.apiwhen needed (e.g.,bot.api.getChat(),bot.api.sendMessage())
- IMPORTANT: The bot instance should NEVER be passed as a parameter to functions. Always import it directly from
- Session middleware: Uses grammY's session plugin with FileAdapter for per-user state in
data/sessions.json - SQLite database: Uses Bun's built-in SQLite for shared channel settings in
data/channels.db - Command definitions system: src/commands/definitions.ts contains all command metadata (name, description, help text, registration function) as a single source of truth
- Bot menu is automatically generated from definitions in
setBotCommands()(src/config/bot.ts) /helpmessage is automatically generated from definitions (src/commands/help.ts)- Commands are registered in src/index.ts by iterating through definitions (with deduplication for shared registration functions)
- Bot menu is automatically generated from definitions in
- Registration functions: All commands and handlers are registered via
register*()functions - Error handling: Global error handler in src/handlers/error.ts captures all bot errors and reports to Sentry with context
- Graceful shutdown: SIGINT/SIGTERM handlers ensure the bot stops cleanly, closes database connection, and flushes Sentry events
TELEGRAM_BOT_TOKEN(required) - Bot token from @BotFatherSENTRY_DSN(optional) - Sentry DSN for error trackingNODE_ENV(optional) - Environment name (development/production)- Use
.envfile for local development (copy from.env.example)
When deploying with Docker, ensure the /app/data directory is mounted as a volume to persist the SQLite database across container restarts:
# Using docker run
docker run -v ./data:/app/data -e TELEGRAM_BOT_TOKEN=your_token your_image
# Using docker-compose
services:
bot:
image: your_image
volumes:
- ./data:/app/data
environment:
- TELEGRAM_BOT_TOKEN=your_tokenThe database file channels.db will be stored in the mounted volume.
The bot uses a unified SQLite database for all storage:
1. Session Storage (Per-User State)
- Uses grammY's session plugin with custom
SqliteSessionStorageadapter (src/db/session-storage.ts) - Stores user-specific data in
sessionstable indata/channels.db - Session structure:
channelConfig?: { channelId: string; channelTitle?: string }- User's configured channelawaitingChannelSelection?: boolean- UI state flag for channel selectionawaitingNotificationUserSelection?: "add" | "remove"- UI state flag for notification user selection
- Accessed via
ctx.sessionin all handlers - Automatically persisted after each update via the custom storage adapter
2. SQLite Database (Shared Channel Settings)
- Uses Bun's built-in SQLite (src/db/database.ts)
- Single database at
data/channels.dbcontains two tables:channel_settingstable:channel_id(TEXT PRIMARY KEY) - Telegram channel IDsettings(TEXT) - JSON blob containing all settings (flexible schema)created_at(INTEGER) - Timestampupdated_at(INTEGER) - Timestamp
sessionstable:user_id(TEXT PRIMARY KEY) - Telegram user IDdata(TEXT) - JSON blob containing session dataupdated_at(INTEGER) - Timestamp
- Settings structure defined via TypeScript interface
ChannelSettingsData:foreignAgentBlurb?: string- Custom foreign agent disclaimer textnotificationUserIds?: number[]- List of admin user IDs to notify when messages are rejected- Additional settings can be easily added to the interface
- Uses JSON storage for maximum flexibility - new settings can be added without schema migrations
- Shared settings accessible to all users of the same channel
- Only channel administrators can modify settings via
/set_fa_blurbcommand - Database functions:
getChannelSettings(channelId)- Retrieve settings for a channelupdateChannelSettings(channelId, settings)- Update/merge settings (partial updates supported)deleteChannelSettings(channelId)- Remove all settings for a channeladdNotificationUser(channelId, userId)- Add a user to the notification listremoveNotificationUser(channelId, userId)- Remove a user from the notification listgetNotificationUsers(channelId)- Get list of notification users for a channel
- Uses Bun's built-in test runner (
bun:test) - Test files:
**/*.test.tsor**/*.spec.tspatterns - Coverage configured in bunfig.toml to output text and lcov formats
- Current tests:
- tests/bot.test.ts - Utility functions
- tests/db.test.ts - Database CRUD operations for channel settings
- tests/session-storage.test.ts - Session storage adapter operations
- TypeScript: Strict mode enabled, ESNext target
- Module system: ESM (module: "Preserve", verbatimModuleSyntax: true)
- Linting: ESLint with TypeScript plugin
- Unused vars with
_prefix are allowed anytypes trigger warnings- console.warn and console.error are allowed
- Unused vars with
- Formatting: Prettier for consistent code style
- Comments:
- DO NOT add self-explanatory comments that merely restate what the code does
- Only add comments for complex logic, non-obvious behavior, or important context
- Examples of comments to avoid:
// Initialize SentryaboveinitializeSentry();// Get user IDaboveconst userId = ctx.from?.id;// Return the resultabove a return statement
- Examples of useful comments:
- Implementation quirks or workarounds
- Business logic that's not obvious from code
- Complex algorithms or non-trivial operations
- Important security or performance considerations
To add a new command to the bot:
-
Create the command handler file in src/commands/ (e.g.,
mycommand.ts) with aregister*Command()function -
Add command definition to src/commands/definitions.ts:
{ command: "mycommand", description: "Short description for bot menu", helpText: "/mycommand <args> - Detailed help text with usage examples", register: registerMyCommand, }
-
Import the registration function in src/commands/definitions.ts
That's it! The command will automatically be:
- Added to the Telegram bot menu
- Included in the
/helpmessage - Registered when the bot starts
- Session data is accessed via
ctx.sessionin all command and message handlers (per-user state) - Channel settings are accessed via database functions from src/db/database.ts (shared state)
- Key utility functions in src/utils.ts:
resolveChannel(identifier)- Validates channel access and type (channel/supergroup only)resolveUserIdentifier(identifier, channelId)- Resolves numeric user ID to user info by looking them up in the channel (username lookups not supported by Telegram Bot API)checkUserChannelPermissions(channelId, userId)- Verifies user's admin status and permissionscheckChannelRequirements(channelId)- Checks if channel meets all bot requirements (exists, bot added, bot can post, settings configured)formatChannelInfo(),formatChannelRequirements(),allRequirementsPassed()- Display formatting utilities
- Rejection notifications are sent automatically when a user's message is rejected for missing foreign agent text
- Configured via
/notify_add,/notify_remove, and/notify_listcommands - Only channel administrators with
canManageChatpermission can manage the notification list - Commands support both manual user ID input and interactive keyboard-based user selection
- When called without arguments,
/notify_addand/notify_removeshow a custom keyboard to select users - User selection is handled via
message:users_sharedevent with session state tracking - Notifications are sent as private messages to subscribed administrators
- Includes details: channel, user info, message preview, timestamp, and rejection reason
- Configured via
- All bot errors are automatically captured by Sentry with Telegram context (update_id, user_id, chat_id, message_text)
- Database is initialized on startup and closed during graceful shutdown
- Both session and channel settings storage use the same SQLite database instance