Mascot Kalorik
Mascot Kalorik

Введение

В данной статье мы рассмотрим архитектуру и реализацию Telegram-бота Kalorik, написанного на языке программирования Rust. Этот бот предоставляет пользователям возможность анализировать свой рацион питания, получая автоматический расчёт калорий, макроэлементов и индекса массы тела. Особенностью проекта является использование современного стека на основе tokio, sqlx, teloxide, а также продуманная архитектура с учётом масштабируемости.

Задачи, решаемые ботом

Kalorik реализует следующие функции:

  • Обработка текстовых сообщений с описанием приёма пищи

  • Распознавание изображений и голосовых сообщений

  • Подсчёт калорий, БЖУ, ИМТ

  • Хранение истории и профиля пользователя

  • Настройка целей и отслеживание прогресса

Архитектура проекта

Проект состоит из следующих ключевых модулей:

  • main.rs — точка входа, инициализация окружения и запуск бота

  • telegram/handlers.rs — обработка входящих сообщений Telegram

  • db/queries.rs — доступ к базе данных (PostgreSQL через sqlx)

  • db/models.rs — структура таблиц и моделей

  • services/nutrition.rs — логика анализа продуктов и подсчёта нутриентов

Проект построен с использованием OnceLock для глобального пула соединений с базой данных и асинхронного исполнения через tokio.

Пример: Регистрация пользователя

pub async fn register_user(chat_id: i64) -> Result<(), sqlx::Error> {
    let Some(pool) = DB_POOL.get() else {
        return Err(sqlx::Error::PoolTimedOut);
    };

    sqlx::query!(
        r#"
        INSERT INTO users (chat_id, created_at)
        VALUES ($1, $2)
        ON CONFLICT (chat_id) DO NOTHING
        "#,
        chat_id,
        Utc::now()
    )
    .execute(pool)
    .await?;

    Ok(())
}

Здесь реализуется вставка пользователя в таблицу, если он отсутствует. Используется UPSERT-подход, обеспечивающий идемпотентность.

Работа с Telegram

match &msg.kind {
    MessageKind::Common(msg) => match &msg.media_kind {
        MediaKind::Text { text, .. } => {
            if text == "/start" {
                register_user(msg.chat.id).await?;
                bot.send_message(msg.chat.id, "Введите описание приёма пищи").await?;
            } else {
                let result = analyze_food_description(text).await;
                bot.send_message(msg.chat.id, result).await?;
            }
        }
        MediaKind::Photo { photo, .. } => {
            // Обработка фото через модель
        }
        MediaKind::Voice { voice, .. } => {
            // Обработка голосовых сообщений
        }
        _ => {}
    }
    _ => {}
}

Здесь представлен разбор варианта обработки текста и мультимедиа. Используется teloxide, который предоставляет удобный API для работы с Telegram Bot API.

Хранение и миграции

let db_url = env::var("DATABASE_URL").expect("DATABASE_URL not set");
let pool = PgPoolOptions::new().connect(&db_url).await?;
sqlx::migrate!("./migrations").run(&pool).await?;

Проект использует sqlx с автоматическим применением миграций. Миграции хранятся в отдельной папке и обеспечивают прозрачность в изменениях схемы.

Развёртывание

Бот может быть запущен как systemd-сервис или Docker-контейнер. Пример systemd unit-файла:

[Unit]
Description=Kalorik Telegram Bot
After=network.target

[Service]
ExecStart=/usr/local/bin/kalorik
WorkingDirectory=/var/www/kalorik
Restart=always
Environment=DATABASE_URL=postgres://...

[Install]
WantedBy=multi-user.target

Также возможно подключение GitHub Actions для CI/CD и обновления контейнера при пуше в main ветку.

Заключение

Kalorik демонстрирует, как на Rust можно создать безопасного и надёжного Telegram-бота с использованием производительных и типобезопасных библиотек. Благодаря sqlx, tokio и teloxide, разработка получилась эффективной и лаконичной. Проект легко масштабируется и адаптируется под другие задачи, связанные с пользовательскими данными или обработкой сообщений.

Проект открыт для расширений: можно добавить мини-приложение, авторизацию через Telegram Web App, отчёты по питанию, интеграцию с OpenAI или Hugging Face для анализа описаний еды.

Попробовать бота @kalorikbot
Репозиторий проект, где находится весь код https://github.com/digkill/Kalorik

Комментарии (5)


  1. redfox0
    17.05.2025 16:06

    Единственная польза от статьи:

    SQLx supports compile-time checked queries. It does not, however, do this by providing a Rust API or DSL (domain-specific language) for building queries. Instead, it provides macros that take regular SQL as input and ensure that it is valid for your database. The way this works is that SQLx connects to your development DB at compile time to have the database itself verify (and return some info on) your SQL queries.

    Прям как в pl/sql - ранее связывание sql-кода и проверка валидности sql-запроса (и не много прав доступа) в compile-time.


    1. redfox0
      17.05.2025 16:06

      Ну так неинтересно: держать пустую БД с идентичной схемой, только ради compile-time проверок:

      The DATABASE_URL environment variable must be set at build time to a database which it can prepare queries against; the database does not have to contain any data but must be the same kind (MySQL, Postgres, etc.) and have the same schema as the database you will be connecting to at runtime.


  1. JBFW
    17.05.2025 16:06

    А где функция анализа сообщения пользователя?

    Ну например, "я сьел 24 штуки острых крыльев KFC и выпил же чаю" = 114342 калорий?

    На Rust.


  1. JRTJITEADER
    17.05.2025 16:06

    Очаровательный маскот и название)


  1. Cerberuser
    17.05.2025 16:06

    ...надо признать, настолько халтурная КДПВ действительно привлекает внимание. Смысла в статье, впрочем, не видно.