6.3 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Random Coffee Bot is a Telegram bot that organizes random coffee meetings between participants. It automatically creates pairs, sends polls to collect participation, and notifies users about their matches. The bot runs on a scheduled basis using APScheduler.
Multi-Group Support: The bot supports multiple Telegram groups simultaneously, with isolated participant pools and pair tracking per group.
Development Commands
Docker Development (Primary Method)
# Build the Docker image
docker-compose build
# Start the bot in detached mode
docker-compose up -d
# View logs
docker-compose logs randomcoffee_bot
docker-compose logs -f randomcoffee_bot # Follow logs
# Stop the bot
docker-compose down
# Rebuild and restart
docker-compose up --build
# Clean up all resources
docker-compose down --rmi all --volumes --remove-orphans
Direct Python Execution
The bot uses uv for dependency management (see uv.lock). To run directly:
# Install dependencies
uv sync
# Run the bot
python -m src.main
Testing Bot Commands
The bot responds to these Telegram commands:
/create_pairs- Manually trigger pair creation/send_quiz- Manually send participation quiz
Architecture
Layered Architecture
The project follows a clean architecture pattern with clear separation of concerns:
src/
├── adapter/ # External interfaces (Telegram, Database)
├── controller/ # Request handlers and scheduling
├── usecase/ # Business logic
├── model/ # Domain models (SQLAlchemy)
└── config.py # Configuration using Pydantic Settings
Adapter Layer
- telegram/ - Telegram Bot API integration using aiogram 3.x
connection.py: Bot instanceroutes.py: Message/poll sending utilities
- database/ - SQLAlchemy async database operations
connection.py: Database helper initializationparticipant.py: Participant CRUD operations (filtered by group_id)pair.py: Pair CRUD and matching logic (filtered by group_id)poll_mapping.py: Maps poll IDs to group IDs for poll answer handling
Controller Layer
- telegram_callback.py: Aiogram router with command handlers and poll answer handlers
- Extracts group_id from message.chat.id for commands
- Uses poll_mapping to find group_id for poll answers
- scheduler.py: APScheduler configuration
- Sends quiz to all groups every Friday at 17:00 Moscow time
- Creates pairs for all groups every Sunday at 19:00 Moscow time
Use Case Layer
- send_quiz.py: Sends participation poll to specific group and stores poll_id -> group_id mapping
- create_pairs.py: Generates unique pairs from available participants for specific group
- handle_quiz_answer.py: Processes poll responses for specific group
Model Layer
- Based on SQLAlchemy 2.0 with async support
- base.py: Declarative base with auto table naming
- participant.py: Participant model with group_id
- pair.py: Pair model with group_id and week tracking, unique constraint on (group_id, week_start, user1_id, user2_id)
- poll_mapping.py: Poll ID to group ID mapping for tracking poll sources
Shared Utilities
Located in shared/ directory:
- db_helper.py: Reusable
DatabaseHelperclass for SQLAlchemy async session management - logger.py: Logging configuration with admin notifications
Configuration
Configuration uses Pydantic Settings with environment variables:
# Required environment variables (see .env.example)
bot__token=<telegram_bot_token>
bot__group_chat_ids='[group_id1, group_id2, ...]' # JSON list of group IDs (negative numbers)
bot__admin_chat_id=<admin_user_id>
db__url=<database_url> # PostgreSQL or SQLite URL
Multi-Group Configuration: The bot now supports multiple groups via bot__group_chat_ids as a JSON list. Each group operates independently with its own participant pool and pair history.
Database
- Supports both SQLite (for local dev) and PostgreSQL
- Uses SQLAlchemy 2.0 async API
- Database file
random_coffee.dbis mounted as volume in Docker - Tables auto-created on startup via
database.create_tables() - Session management through
database.session_getter()context manager
Scheduling
APScheduler (AsyncIOScheduler) runs timezone-aware jobs:
- Timezone: Europe/Moscow
- Jobs defined in
src/controller/scheduler.py - All jobs removed and re-added on startup to avoid duplicates
Key Workflows
Poll → Pair Creation Flow (Per Group)
- Friday 17:00: Quiz sent to all configured groups asking about participation
- Poll ID → group ID mapping stored in poll_mapping table
- Users respond to poll (anonymous=False to track users)
- Responses handled by
handle_quiz_answeruse case → poll_id looked up to find group_id → participant stored with group_id - Sunday 19:00:
create_pairsuse case runs for each group independently - Available participants matched using logic in
adapter/database/pair.py(filtered by group_id) - Pairs posted to respective group with usernames
- Participants table cleared for that group for next week
Pair Matching Logic
Located in src/adapter/database/pair.py:
- Ensures unique pairings (checks historical pairs for that group_id)
- Filters participants by group_id
- Uses SQL queries with aliases to find valid combinations
- Randomizes pair selection
Important Notes
- All Telegram operations use aiogram 3.x async API
- Logging configured to file
shared/logs/bot.logwith admin notifications on errors - Bot uses
skip_updates=Trueto ignore messages received while offline - Router registered in main.py, actual handlers in
controller/telegram_callback.py - The main entry point expects
routerto be imported fromsrc.controller.tg_handlers, but the actual file istelegram_callback.py(verify import path)
Multi-Group Implementation
- All database queries filter by
group_idto isolate data per group - Poll answers don't contain chat_id, so poll_id → group_id mapping is stored when quiz is sent
- Scheduler iterates over all configured groups for scheduled tasks
- Commands (like
/create_pairs,/send_quiz) extract group_id from message.chat.id - Each group maintains independent participant pools and pair history