Переписал бота с 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>
This commit is contained in:
146
src/bot.rs
Normal file
146
src/bot.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user