Files
random_coffee_bot/CLAUDE.md
Artem Tsyrulnikov f833aeaf41 first commit
2026-02-05 15:41:56 +03:00

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 instance
    • routes.py: Message/poll sending utilities
  • database/ - SQLAlchemy async database operations
    • connection.py: Database helper initialization
    • participant.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 DatabaseHelper class 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.db is 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)

  1. Friday 17:00: Quiz sent to all configured groups asking about participation
  2. Poll ID → group ID mapping stored in poll_mapping table
  3. Users respond to poll (anonymous=False to track users)
  4. Responses handled by handle_quiz_answer use case → poll_id looked up to find group_id → participant stored with group_id
  5. Sunday 19:00: create_pairs use case runs for each group independently
  6. Available participants matched using logic in adapter/database/pair.py (filtered by group_id)
  7. Pairs posted to respective group with usernames
  8. 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.log with admin notifications on errors
  • Bot uses skip_updates=True to ignore messages received while offline
  • Router registered in main.py, actual handlers in controller/telegram_callback.py
  • The main entry point expects router to be imported from src.controller.tg_handlers, but the actual file is telegram_callback.py (verify import path)

Multi-Group Implementation

  • All database queries filter by group_id to 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