Files
pawctioneer-bot/src/main.rs
Dylan Knutson cf02bfd6d7 Major refactor: restructure new listing command and update data models
- Refactor new_listing from single file to modular structure
- Add handler factory pattern for state management
- Improve keyboard utilities and validations
- Update database models for bid, listing, and user systems
- Add new types: listing_duration, user_row_id
- Remove deprecated user_id type
- Update Docker configuration
- Enhance test utilities and message handling
2025-08-29 06:31:19 +00:00

130 lines
3.9 KiB
Rust

mod commands;
mod config;
mod db;
mod dptree_utils;
mod keyboard_utils;
mod message_utils;
mod sqlite_storage;
#[cfg(test)]
mod test_utils;
use crate::commands::{
my_listings::{my_listings_handler, MyListingsState},
new_listing::{new_listing_handler, NewListingState},
};
use crate::sqlite_storage::SqliteStorage;
use anyhow::Result;
use commands::*;
use config::Config;
use log::info;
use serde::{Deserialize, Serialize};
use teloxide::dispatching::{dialogue::serializer::Json, DpHandlerDescription};
use teloxide::{prelude::*, types::BotCommand, utils::command::BotCommands};
pub type HandlerResult<T = ()> = anyhow::Result<T>;
pub type Handler = dptree::Handler<'static, HandlerResult, DpHandlerDescription>;
/// Set up the bot's command menu that appears when users tap the menu button
async fn setup_bot_commands(bot: &Bot) -> Result<()> {
info!("Setting up bot command menu...");
// Convert our Command enum to Telegram BotCommand structs
let commands: Vec<BotCommand> = Command::bot_commands()
.into_iter()
.map(|cmd| BotCommand::new(cmd.command, cmd.description))
.collect();
// Set the commands for the bot's menu
bot.set_my_commands(commands).await?;
info!("Bot command menu configured successfully");
Ok(())
}
#[derive(BotCommands, Clone)]
#[command(rename_rule = "lowercase", description = "Auction Bot Commands")]
pub enum Command {
#[command(description = "Show welcome message")]
Start,
#[command(description = "Show help message")]
Help,
#[command(description = "Create a new listing or auction")]
NewListing,
#[command(description = "View your listings and auctions")]
MyListings,
#[command(description = "View your active bids")]
MyBids,
#[command(description = "Configure notifications")]
Settings,
}
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
enum DialogueRootState {
#[default]
Start,
NewListing(NewListingState),
MyListings(MyListingsState),
}
type RootDialogue = Dialogue<DialogueRootState, SqliteStorage<Json>>;
#[tokio::main]
async fn main() -> Result<()> {
// Load and validate configuration from environment/.env file
let config = Config::from_env()?;
// Create database connection pool
let db_pool = config.create_database_pool().await?;
info!("Starting Pawctioneer Bot...");
let bot = Bot::new(&config.telegram_token);
// Set up the bot's command menu
setup_bot_commands(&bot).await?;
let dialog_storage = SqliteStorage::new(db_pool.clone(), Json).await?;
// Create dispatcher with dialogue system
Dispatcher::builder(
bot,
dptree::entry()
.enter_dialogue::<Update, SqliteStorage<Json>, DialogueRootState>()
.branch(new_listing_handler())
.branch(my_listings_handler())
.branch(
Update::filter_message().branch(
dptree::entry()
.filter_command::<Command>()
.branch(dptree::case![Command::Start].endpoint(handle_start))
.branch(dptree::case![Command::Help].endpoint(handle_help))
.branch(dptree::case![Command::MyBids].endpoint(handle_my_bids))
.branch(dptree::case![Command::Settings].endpoint(handle_settings)),
),
)
.branch(Update::filter_message().endpoint(unknown_message_handler)),
)
.dependencies(dptree::deps![db_pool, dialog_storage])
.enable_ctrlc_handler()
.worker_queue_size(1)
.build()
.dispatch()
.await;
Ok(())
}
async fn unknown_message_handler(bot: Bot, msg: Message) -> HandlerResult {
bot.send_message(
msg.chat.id,
format!(
"
Unknown command: `{}`\n\n\
Try /help to see the list of commands.\
",
msg.text().unwrap_or("")
),
)
.await?;
Ok(())
}