<tech_stack> Python 3.14+ (async/await throughout) python-telegram-bot >= 22.0 (async API via telegram.ext) PostgreSQL via psycopg 3 with AsyncConnectionPool; InMemoryRepository fallback python-dotenv for environment variables Hatchling build backend; uv for dependency management Docker multi-stage build; deployed on Fly.io (polling mode) ruff (linter and formatter) <type_checking>mypy</type_checking> pytest </tech_stack>
Layered architecture: Handlers → Services → Repository → Database. Dependency injection via context.bot_data dictionary populated at startup. Receive Telegram updates and delegate to services. Each module exposes a create_*_handlers() factory returning a list of telegram.ext handler objects. Private async handler functions follow the _handle_* naming convention. Modules: welcome.py, moderation.py, spam.py, settings.py, id.py, announce.py, ping.py. Business logic layer. Classes: CaptchaService, ModerationService, SpamDetector. Depend on AsyncRepository for persistence. Stateless except for repository reference. Abstract AsyncRepository base class (db/base.py) with two implementations: - InMemoryRepository (db/in_memory.py) — for development and testing - PostgresRepository (db/postgres.py) — production with psycopg AsyncConnectionPool Factory function create_repository() in db/__init__.py selects implementation based on DATABASE_URL. Domain models (Ban, Mute, Report) are dataclasses in db/models.py. Settings class loads environment variables. Required vars raise ValueError if missing. See .env.example for the full list of configuration options. All user-facing bot messages centralized here, in Italian. Uses named placeholders for .format() substitution. Creates ApplicationBuilder, registers handlers via _post_init callback, initializes repository and services, runs polling loop.<coding_conventions> Use modern Python type syntax: int | None, list[int], dict[str, Any]. Do not use Optional or Union from typing. All handlers, service methods, and repository methods must be async def. Every file has a module-level docstring. Functions and classes have one-line docstrings. Use relative imports within the package (from ..services.captcha import CaptchaService). Define private async handle* functions. Expose a public create_*_handlers() factory returning list. Wrap Telegram API calls in try/except, log warnings with logger, degrade gracefully. Use logging.getLogger(name) per module. All user-facing text lives in strings.py in Italian. Use named placeholders. No global mutable state. Pass dependencies through services and bot_data. </coding_conventions>
uv sync --dev uv run python-italy-bot uv run ruff check src/ uv run ruff format src/ uv run mypy src/ uv run pytest pytest with async support tests/ - Test services and repository implementations independently. - Use InMemoryRepository for unit tests; do not mock the database interface. - Mirror the src/ directory structure in tests/. - Keep tests focused and avoid testing Telegram API internals. Bot token from @BotFather PostgreSQL connection string; omit for in-memory Secret command for captcha verification Path to rules file Main group chat ID Comma-separated local group IDs External rules page URL Owner user ID for /announce Always guard against None for update.effective_chat, update.effective_user, and update.message. Verification and bans are global across all tracked chats. Use register_chat() for new chats. PostgresRepository requires async with self._pool.connection() for all DB access. Bot must have admin permissions to restrict or ban members. Bot messages are in Italian — keep strings.py consistent.