Files
mental_health/src/bot.rs
Mikhail Kilin b885fd39b9 Переписал бота с NestJS/TypeScript на Rust
Стек: teloxide + sqlx + axum + tokio-cron-scheduler.
Вся логика перенесена: /start, /help, /settings, выбор частоты,
cron-рассылка цитат, admin API. Совместимость с существующей БД
сохранена (camelCase колонки). Старый TypeScript-код удалён.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:55:20 +03:00

147 lines
5.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::sync::Arc;
use teloxide::dispatching::UpdateHandler;
use teloxide::prelude::*;
use teloxide::types::{InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, KeyboardMarkup};
use teloxide::utils::command::BotCommands;
use crate::app_state::AppState;
use crate::db;
type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
#[derive(BotCommands, Clone)]
#[command(rename_rule = "lowercase")]
pub enum Command {
#[command(description = "Запуск бота")]
Start,
#[command(description = "Справка")]
Help,
#[command(description = "Настройки частоты цитат")]
Settings,
}
/// Постоянная клавиатура с кнопкой "⚙️ Настройки"
fn settings_keyboard() -> KeyboardMarkup {
KeyboardMarkup::new(vec![vec![KeyboardButton::new("⚙️ Настройки")]]).resize_keyboard()
}
/// Inline-клавиатура выбора частоты
fn frequency_keyboard() -> InlineKeyboardMarkup {
InlineKeyboardMarkup::new(vec![
vec![
InlineKeyboardButton::callback("1 час", "frequency_1"),
InlineKeyboardButton::callback("3 часа", "frequency_3"),
],
vec![
InlineKeyboardButton::callback("5 часов", "frequency_5"),
InlineKeyboardButton::callback("7 часов", "frequency_7"),
],
vec![
InlineKeyboardButton::callback("9 часов", "frequency_9"),
InlineKeyboardButton::callback("12 часов", "frequency_12"),
],
])
}
/// Обработка команд /start, /help, /settings
async fn command_handler(
bot: Bot,
msg: Message,
cmd: Command,
state: Arc<AppState>,
) -> HandlerResult {
match cmd {
Command::Start => {
if let Some(user) = msg.from {
let fio = if let Some(ref last) = user.last_name {
format!("{} {}", user.first_name, last)
} else {
user.first_name.clone()
};
db::upsert_user(
&state.pool,
user.id.0 as i64,
Some(&fio),
user.username.as_deref(),
)
.await;
}
bot.send_message(
msg.chat.id,
"Приветствую тебя, мой дорогой друг. Я бот, который будет писать тебе мотивирующие цитаты. Сейчас цитаты буду приходит один раз в час, в настройках можно изменить это время.",
)
.reply_markup(settings_keyboard())
.await?;
}
Command::Help => {
bot.send_message(
msg.chat.id,
"Я буду присылать тебе мотивирующие цитаты. Используй меню для настроек.",
)
.reply_markup(settings_keyboard())
.await?;
}
Command::Settings => {
bot.send_message(msg.chat.id, "Выберите частоту получения цитат:")
.reply_markup(frequency_keyboard())
.await?;
}
}
Ok(())
}
/// Обработка текстового сообщения "⚙️ Настройки" от reply-кнопки
async fn text_message_handler(bot: Bot, msg: Message) -> HandlerResult {
if let Some(text) = msg.text() {
if text == "⚙️ Настройки" {
bot.send_message(msg.chat.id, "Выберите частоту получения цитат:")
.reply_markup(frequency_keyboard())
.await?;
}
}
Ok(())
}
/// Обработка callback-запроса frequency_N
async fn callback_handler(bot: Bot, q: CallbackQuery, state: Arc<AppState>) -> HandlerResult {
if let Some(ref data) = q.data {
if let Some(hours_str) = data.strip_prefix("frequency_") {
if let Ok(hours) = hours_str.parse::<i32>() {
let telegram_id = q.from.id.0 as i64;
db::update_frequency(&state.pool, telegram_id, hours).await;
bot.answer_callback_query(&q.id).await?;
if let Some(msg) = q.regular_message() {
bot.edit_message_text(
msg.chat.id,
msg.id,
format!(
"Отлично! Теперь я буду присылать цитаты каждые {} ч.",
hours
),
)
.await?;
}
}
}
}
Ok(())
}
/// Собрать dptree-обработчик для Dispatcher
pub fn build_handler() -> UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> {
dptree::entry()
.branch(
Update::filter_message()
.branch(
dptree::entry()
.filter_command::<Command>()
.endpoint(command_handler),
)
.branch(dptree::endpoint(text_message_handler)),
)
.branch(Update::filter_callback_query().endpoint(callback_handler))
}