|
| 1 | +# CLAUDE.md — Project Specification for python-italy-telegram-bot |
| 2 | + |
| 3 | +<project> |
| 4 | + <name>python-italy-telegram-bot (Electus)</name> |
| 5 | + <description> |
| 6 | + Official Telegram bot for the Italian Python community groups. |
| 7 | + Handles welcome captcha verification, moderation (ban/mute/report), |
| 8 | + spam detection, and multi-group management. |
| 9 | + </description> |
| 10 | + <language>Python 3.14+</language> |
| 11 | + <license>MIT</license> |
| 12 | +</project> |
| 13 | + |
| 14 | +<tech_stack> |
| 15 | + <runtime>Python 3.14+ (async/await throughout)</runtime> |
| 16 | + <framework>python-telegram-bot >= 22.0 (async API via telegram.ext)</framework> |
| 17 | + <database>PostgreSQL via psycopg 3 with AsyncConnectionPool; InMemoryRepository fallback</database> |
| 18 | + <config>python-dotenv for environment variables</config> |
| 19 | + <build>Hatchling build backend; uv for dependency management</build> |
| 20 | + <deployment>Docker multi-stage build; deployed on Fly.io (polling mode)</deployment> |
| 21 | + <linting>ruff (linter and formatter)</linting> |
| 22 | + <type_checking>mypy</type_checking> |
| 23 | + <testing>pytest</testing> |
| 24 | +</tech_stack> |
| 25 | + |
| 26 | +<architecture> |
| 27 | + <overview> |
| 28 | + Layered architecture: Handlers → Services → Repository → Database. |
| 29 | + Dependency injection via context.bot_data dictionary populated at startup. |
| 30 | + </overview> |
| 31 | + |
| 32 | + <layer name="handlers" path="src/python_italy_bot/handlers/"> |
| 33 | + Receive Telegram updates and delegate to services. |
| 34 | + Each module exposes a create_*_handlers() factory returning a list of telegram.ext handler objects. |
| 35 | + Private async handler functions follow the _handle_* naming convention. |
| 36 | + Modules: welcome.py, moderation.py, spam.py, settings.py, id.py, announce.py, ping.py. |
| 37 | + </layer> |
| 38 | + |
| 39 | + <layer name="services" path="src/python_italy_bot/services/"> |
| 40 | + Business logic layer. Classes: CaptchaService, ModerationService, SpamDetector. |
| 41 | + Depend on AsyncRepository for persistence. Stateless except for repository reference. |
| 42 | + </layer> |
| 43 | + |
| 44 | + <layer name="repository" path="src/python_italy_bot/db/"> |
| 45 | + Abstract AsyncRepository base class (db/base.py) with two implementations: |
| 46 | + - InMemoryRepository (db/in_memory.py) — for development and testing |
| 47 | + - PostgresRepository (db/postgres.py) — production with psycopg AsyncConnectionPool |
| 48 | + Factory function create_repository() in db/__init__.py selects implementation based on DATABASE_URL. |
| 49 | + Domain models (Ban, Mute, Report) are dataclasses in db/models.py. |
| 50 | + </layer> |
| 51 | + |
| 52 | + <layer name="config" path="src/python_italy_bot/config.py"> |
| 53 | + Settings class loads environment variables. Required vars raise ValueError if missing. |
| 54 | + See .env.example for the full list of configuration options. |
| 55 | + </layer> |
| 56 | + |
| 57 | + <layer name="strings" path="src/python_italy_bot/strings.py"> |
| 58 | + All user-facing bot messages centralized here, in Italian. |
| 59 | + Uses named placeholders for .format() substitution. |
| 60 | + </layer> |
| 61 | + |
| 62 | + <layer name="entry_point" path="src/python_italy_bot/main.py"> |
| 63 | + Creates ApplicationBuilder, registers handlers via _post_init callback, |
| 64 | + initializes repository and services, runs polling loop. |
| 65 | + </layer> |
| 66 | +</architecture> |
| 67 | + |
| 68 | +<coding_conventions> |
| 69 | + <rule name="type_hints"> |
| 70 | + Use modern Python type syntax: int | None, list[int], dict[str, Any]. |
| 71 | + Do not use Optional or Union from typing. |
| 72 | + </rule> |
| 73 | + <rule name="async"> |
| 74 | + All handlers, service methods, and repository methods must be async def. |
| 75 | + </rule> |
| 76 | + <rule name="docstrings"> |
| 77 | + Every file has a module-level docstring. Functions and classes have one-line docstrings. |
| 78 | + </rule> |
| 79 | + <rule name="imports"> |
| 80 | + Use relative imports within the package (from ..services.captcha import CaptchaService). |
| 81 | + </rule> |
| 82 | + <rule name="handler_pattern"> |
| 83 | + Define private async _handle_* functions. Expose a public create_*_handlers() factory returning list. |
| 84 | + </rule> |
| 85 | + <rule name="error_handling"> |
| 86 | + Wrap Telegram API calls in try/except, log warnings with logger, degrade gracefully. |
| 87 | + </rule> |
| 88 | + <rule name="logging"> |
| 89 | + Use logging.getLogger(__name__) per module. |
| 90 | + </rule> |
| 91 | + <rule name="strings"> |
| 92 | + All user-facing text lives in strings.py in Italian. Use named placeholders. |
| 93 | + </rule> |
| 94 | + <rule name="no_global_state"> |
| 95 | + No global mutable state. Pass dependencies through services and bot_data. |
| 96 | + </rule> |
| 97 | +</coding_conventions> |
| 98 | + |
| 99 | +<commands> |
| 100 | + <command name="install">uv sync --dev</command> |
| 101 | + <command name="run">uv run python-italy-bot</command> |
| 102 | + <command name="lint">uv run ruff check src/</command> |
| 103 | + <command name="format">uv run ruff format src/</command> |
| 104 | + <command name="typecheck">uv run mypy src/</command> |
| 105 | + <command name="test">uv run pytest</command> |
| 106 | +</commands> |
| 107 | + |
| 108 | +<testing> |
| 109 | + <framework>pytest with async support</framework> |
| 110 | + <directory>tests/</directory> |
| 111 | + <guidelines> |
| 112 | + - Test services and repository implementations independently. |
| 113 | + - Use InMemoryRepository for unit tests; do not mock the database interface. |
| 114 | + - Mirror the src/ directory structure in tests/. |
| 115 | + - Keep tests focused and avoid testing Telegram API internals. |
| 116 | + </guidelines> |
| 117 | +</testing> |
| 118 | + |
| 119 | +<environment> |
| 120 | + <variable name="TELEGRAM_BOT_TOKEN" required="true">Bot token from @BotFather</variable> |
| 121 | + <variable name="DATABASE_URL" required="false">PostgreSQL connection string; omit for in-memory</variable> |
| 122 | + <variable name="CAPTCHA_SECRET_COMMAND" required="false" default="python-italy">Secret command for captcha verification</variable> |
| 123 | + <variable name="CAPTCHA_FILE_PATH" required="false" default="assets/regolamento.md">Path to rules file</variable> |
| 124 | + <variable name="MAIN_GROUP_ID" required="false">Main group chat ID</variable> |
| 125 | + <variable name="LOCAL_GROUP_IDS" required="false">Comma-separated local group IDs</variable> |
| 126 | + <variable name="RULES_URL" required="false">External rules page URL</variable> |
| 127 | + <variable name="BOT_OWNER_ID" required="false">Owner user ID for /announce</variable> |
| 128 | +</environment> |
| 129 | + |
| 130 | +<pitfalls> |
| 131 | + <pitfall>Always guard against None for update.effective_chat, update.effective_user, and update.message.</pitfall> |
| 132 | + <pitfall>Verification and bans are global across all tracked chats. Use register_chat() for new chats.</pitfall> |
| 133 | + <pitfall>PostgresRepository requires async with self._pool.connection() for all DB access.</pitfall> |
| 134 | + <pitfall>Bot must have admin permissions to restrict or ban members.</pitfall> |
| 135 | + <pitfall>Bot messages are in Italian — keep strings.py consistent.</pitfall> |
| 136 | +</pitfalls> |
0 commit comments