Skip to content

Repository files navigation

πŸ† Quoto β€” Quote of the Day

English | Русский

A Telegram bot that tracks quote days and only publishes a quote of the day when the conversation was actually worth quoting.

GitHub Stars GitHub Repo Size GitHub License

✨ Features

  • πŸ† Quote of the Day Flow β€” scores messages between two daily cutoff points
  • πŸ€– AI Scoring β€” evaluates messages for humor, wit, depth, and memorability via OpenRouter API
  • 🧡 Optional Quote Context β€” adds up to 5 consecutive or reply-linked messages only when needed
  • 😴 Boring-Day Detection β€” if the day feels flat, the bot says so instead of forcing a weak quote
  • ❀️ Reaction Context β€” sends emoji reactions to AI as context for each message
  • 🧾 AI Audit Log β€” writes the exact OpenRouter request body and raw response to logs/ai_audit.jsonl for 7 days
  • 🌐 Localized Interface β€” supports Russian, Ukrainian, English, and German UI text
  • 🧭 One-Time Language Detection β€” if a group has no saved language, the next daily AI run chooses the best interface language and stores it
  • πŸ“ Text Context β€” stores message length signals for transparent details
  • 🧭 Single /start Control Panel β€” stats, group language, and close cleanup live behind inline buttons
  • πŸ“Œ Auto-Pin β€” pins the winning quote in the chat
  • πŸ“Š Statistics β€” chat stats, personal stats, top authors, and rating breakdown
  • ⏰ Scheduler β€” configurable daily time for quote selection
  • 🐳 Docker Support β€” easy deployment with Docker Compose

βš™οΈ How It Works

  1. Add the bot to your Telegram group and grant admin rights
  2. Members chat as usual β€” the bot silently collects messages and reactions
  3. The bot collects messages for the day from the previous cutoff to the next one
  4. At the scheduled time (default 21:00), the bot evaluates the closed day
  5. If there are fewer than 10 messages, the day is skipped silently
  6. If there are 10+ messages, AI both scores messages and decides whether the whole day is quote-worthy
  7. The best message is selected by the AI score; reactions and length are only context:
Component Weight Description
AI Score 100% LLM-based evaluation with reaction context
Reactions Context Emoji reactions sent to AI when present
Length Context Stored for transparent quote details
  1. If the quote needs setup, AI may attach a validated consecutive/reply-linked context block of up to 5 messages
  2. If the group has no saved interface language yet, the same AI run selects one of ru, uk, en, or de and the bot stores it for future UI messages
  3. If the day is boring, the bot posts a boring-day notice with a Details link instead of a weak quote

πŸ“Œ Commands

Command Description
/start Opens the context-aware control panel

πŸš€ Quick Start

Prerequisites

  • Python 3.10+
  • PostgreSQL 16+ (in production Quoto runs on a shared core database β€” see the Database section below)
  • Docker (optional)

Local Installation

# 1. Clone the repository
git clone https://github.com/FreshLabDev/quoto.git
cd quoto

# 2. Create virtual environment
python -m venv venv
source venv/bin/activate  # Linux/Mac
. venv\Scripts\activate  # Windows

# 3. Install dependencies
pip install -r requirements.txt

# 4. Apply database migrations
alembic upgrade head

Configuration

Create a .env file in the root directory (see .env.example):

BOT_TOKEN=your_telegram_bot_token
BOT_USERNAME=your_bot_username
# Production points DB_URL at the shared core-postgres as the quoto_core role
# (postgresql+asyncpg://quoto_core:***@core-postgres:5432/core). For local dev
# docker-compose builds it from the POSTGRES_* vars β€” see Database below.
DB_URL=postgresql+asyncpg://user:password@localhost:5432/dbname
OPENROUTER_API_KEY=sk-or-v1-your-key-here
OPENROUTER_EVAL_MODEL=poolside/laguna-s-2.1:free
# Used only if the primary eval model errors out after retries.
OPENROUTER_EVAL_FALLBACK_MODEL=poolside/laguna-s-2.1
OPENROUTER_EVAL_REASONING_EFFORT=medium
OPENROUTER_EVAL_MAX_TOKENS=32000
OPENROUTER_MEDIA_MODEL=nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free
OPENROUTER_MEDIA_FALLBACK_MODEL=google/gemini-2.5-flash-lite
OPENROUTER_MEDIA_REASONING_EFFORT=medium
OPENROUTER_HTTP_REFERER=https://t.me/quototbot
OPENROUTER_APP_TITLE=Quoto
MEDIA_ANALYSIS_ENABLED=True
MEDIA_PHASH_DISTANCE=5
MEDIA_CACHE_PROMPT_VERSION=v2
MEDIA_IMAGE_MAX_SIDE=1280
MEDIA_IMAGE_QUALITY=82
MEDIA_VIDEO_MAX_SECONDS=3600
MEDIA_VIDEO_LOW_RES_MAX_SECONDS=10800
MEDIA_VIDEO_MAX_HEIGHT=720
MEDIA_VIDEO_CRF=30
MEDIA_VIDEO_FPS=12
MEDIA_AUDIO_BITRATE=64k
MEDIA_AUDIO_SAMPLE_RATE=24000
MEDIA_COMMAND_TIMEOUT_SECONDS=300
MEDIA_PENDING_RETRY_INTERVAL_SECONDS=300
MEDIA_PENDING_RETRY_BATCH_SIZE=10
MEDIA_RETRY_MAX_ATTEMPTS=5
MEDIA_RETRY_BASE_DELAY_SECONDS=300
MEDIA_RETRY_MAX_DELAY_SECONDS=3600
MEDIA_PROVIDER_COOLDOWN_SECONDS=900
MEDIA_PENDING_ITEM_TIMEOUT_SECONDS=600
DEVELOPER_IDS=[1234567890]
QUOTE_HOUR=21
QUOTE_MINUTE=0
TIMEZONE=Europe/Kyiv
MIN_MESSAGES_FOR_AUTO_REVIEW=10
WEIGHT_REACTIONS=0.0
WEIGHT_AI=1.0
WEIGHT_LENGTH=0.0

Running

Use /start in a private chat or group. In groups, regular users see stats buttons, while admins also get group language controls.

python main.py

🐳 Docker Support

You can easily run the bot using Docker Compose:

docker-compose up -d --build

πŸ—„οΈ Database

Quoto stores its data in PostgreSQL and shares one database with the other FreshLabDev bots (vido, branchy, searcher):

  • In production it connects to the shared core database as the least-privilege role quoto_core. Quoto's own tables live in the quoto schema; shared identity, chats and language live in the core schema (core.person, core.chat, and the core.set_language / core.effective_language functions). Quoto's tables reference core.person and core.chat by the Telegram natural keys (user id, chat id) β€” there is no separate per-bot users/groups identity table.
  • For local development docker-compose up starts a bundled Postgres seeded with a copy of the core schema (deploy/core-init.sql) so the foreign keys into core.person / core.chat resolve; the app's DB_URL is built from the POSTGRES_* variables in .env.

Apply migrations with alembic upgrade head (the Alembic version table lives in the quoto schema).

πŸ› οΈ Tech Stack

Layer Technology
Framework Aiogram 3
Database Shared PostgreSQL core (schema quoto) + SQLAlchemy (Async)
Validation Pydantic
AI OpenRouter API (any LLM)
Scheduler APScheduler
HTTP Client HTTPX

πŸ“‚ Project Structure

quoto/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ ai.py           # OpenRouter AI integration & message evaluation
β”‚   β”œβ”€β”€ config.py       # Settings, logging, environment variables
β”‚   β”œβ”€β”€ core.py         # Core business logic (people, groups, messages)
β”‚   β”œβ”€β”€ core_client.py  # Helpers over the shared core.* functions (touch, language)
β”‚   β”œβ”€β”€ db.py           # Database session & initialization
β”‚   β”œβ”€β”€ handlers.py     # Telegram bot handlers & commands
β”‚   β”œβ”€β”€ menu.py         # /start control panel rendering
β”‚   β”œβ”€β”€ models.py       # SQLAlchemy models (GroupSettings, Message, Quote, …) β€” FK into core.person/core.chat
β”‚   β”œβ”€β”€ scheduler.py    # APScheduler jobs & quote of the day pipeline
β”‚   β”œβ”€β”€ scoring.py      # Scoring engine & best quote selection
β”‚   └── utils.py        # Utility functions
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ main.py             # Entry point
β”œβ”€β”€ requirements.in
β”œβ”€β”€ requirements.txt
└── .env.example

🀝 Contributing

Contributions are welcome! Feel free to:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

©️ License

GNUv3 License β€” see LICENSE file for details

πŸ‘€ Author

Created with ❀️ by FreshLabDev

⭐ Add a star to my project!
star

About

Telegram Bot, which uses AI to determine the best message of the day in the form of a quote.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages