From a40ac5f3454fa62c20fef13f4f92d50572c81ece Mon Sep 17 00:00:00 2001 From: Dylan Knutson Date: Fri, 5 Sep 2025 22:17:30 +0000 Subject: [PATCH] move target into state --- src/bidding/mod.rs | 11 ++--- src/commands/help.rs | 8 ++-- src/commands/my_bids.rs | 6 +-- src/commands/my_listings/mod.rs | 49 +++++++------------- src/commands/new_listing/callbacks.rs | 64 +++++++++------------------ src/commands/new_listing/handlers.rs | 62 +++++++++----------------- src/commands/new_listing/ui.rs | 8 ++-- src/commands/settings.rs | 6 +-- src/commands/start.rs | 28 ++++-------- src/handle_error.rs | 23 +++------- src/main.rs | 33 +++++++------- src/message_sender.rs | 32 ++++++++++---- src/message_utils.rs | 4 +- src/test_utils.rs | 5 ++- 14 files changed, 134 insertions(+), 205 deletions(-) diff --git a/src/bidding/mod.rs b/src/bidding/mod.rs index 29b8564..8f9d18d 100644 --- a/src/bidding/mod.rs +++ b/src/bidding/mod.rs @@ -12,7 +12,6 @@ use crate::{ dptree_utils::MapTwo, handle_error::with_error_handler, handler_utils::find_listing_by_id, - message_utils::MessageTarget, start_command_data::StartCommandData, App, BotError, BotHandler, BotResult, DialogueRootState, RootDialogue, }; @@ -75,7 +74,6 @@ pub fn bidding_handler() -> BotHandler { async fn handle_place_bid_on_listing( app: App, user_dao: UserDAO, - target: MessageTarget, user: PersistedUser, listing: PersistedListing, dialogue: RootDialogue, @@ -116,7 +114,7 @@ async fn handle_place_bid_on_listing( .append_row([InlineKeyboardButton::callback("Bid $1", "cancel")]); app.bot - .send_html_message(target, response_lines.join("\n"), Some(keyboard)) + .send_html_message(response_lines.join("\n"), Some(keyboard)) .await?; Ok(()) @@ -125,7 +123,6 @@ async fn handle_place_bid_on_listing( async fn handle_awaiting_bid_amount_input( app: App, listing: PersistedListing, - target: MessageTarget, dialogue: RootDialogue, msg: Message, ) -> BotResult { @@ -146,7 +143,6 @@ async fn handle_awaiting_bid_amount_input( let bid_amount_str = format!("{}{}", listing.base.currency_type.symbol(), bid_amount); app.bot .send_html_message( - target, format!("Confirm bid amount: {bid_amount_str} - this cannot be undone!"), Some(InlineKeyboardMarkup::default().append_row([ InlineKeyboardButton::callback( @@ -174,7 +170,6 @@ async fn handle_awaiting_confirm_bid_amount_callback( listing: PersistedListing, user: PersistedUser, bid_amount: MoneyAmount, - target: MessageTarget, dialogue: RootDialogue, callback_query: CallbackQuery, ) -> BotResult { @@ -188,7 +183,7 @@ async fn handle_awaiting_confirm_bid_amount_callback( "cancel_bid" => { dialogue.exit().await.context("failed to exit dialogue")?; app.bot - .send_html_message(target, "Bid cancelled".to_string(), None) + .send_html_message("Bid cancelled".to_string(), None) .await?; return Ok(()); } @@ -206,8 +201,8 @@ async fn handle_awaiting_confirm_bid_amount_callback( let bid_amount_str = format!("{}{}", listing.base.currency_type.symbol(), bid_amount); app.bot + .with_target(callback_query.from.into()) .send_html_message( - target.only_chat_id(), format!("Bid placed for {bid_amount_str} on {}", listing.base.title), None, ) diff --git a/src/commands/help.rs b/src/commands/help.rs index d8de6b1..17e6167 100644 --- a/src/commands/help.rs +++ b/src/commands/help.rs @@ -1,7 +1,7 @@ -use crate::{message_utils::MessageTarget, App, BotResult, Command}; +use crate::{App, BotResult, Command}; use teloxide::utils::command::BotCommands; -pub async fn handle_help(app: App, target: MessageTarget) -> BotResult { +pub async fn handle_help(app: App) -> BotResult { let help_message = format!( "📋 Available Commands:\n\n{}\n\n\ 📧 Support: Contact @admin for help\n\ @@ -9,8 +9,6 @@ pub async fn handle_help(app: App, target: MessageTarget) -> BotResult { Command::descriptions() ); - app.bot - .send_html_message(target, help_message, None) - .await?; + app.bot.send_html_message(help_message, None).await?; Ok(()) } diff --git a/src/commands/my_bids.rs b/src/commands/my_bids.rs index f69f9d5..8d49364 100644 --- a/src/commands/my_bids.rs +++ b/src/commands/my_bids.rs @@ -1,8 +1,8 @@ -use crate::{message_utils::MessageTarget, App, BotResult}; +use crate::{App, BotResult}; use log::info; use teloxide::types::Message; -pub async fn handle_my_bids(app: App, msg: Message, target: MessageTarget) -> BotResult { +pub async fn handle_my_bids(app: App, msg: Message) -> BotResult { let response = "🎯 My Bids (Coming Soon)\n\n\ Here you'll be able to view:\n\ • Your active bids\n\ @@ -18,6 +18,6 @@ pub async fn handle_my_bids(app: App, msg: Message, target: MessageTarget) -> Bo msg.chat.id ); - app.bot.send_html_message(target, response, None).await?; + app.bot.send_html_message(response, None).await?; Ok(()) } diff --git a/src/commands/my_listings/mod.rs b/src/commands/my_listings/mod.rs index 7674147..5f67218 100644 --- a/src/commands/my_listings/mod.rs +++ b/src/commands/my_listings/mod.rs @@ -19,7 +19,7 @@ use crate::{ }, handle_error::with_error_handler, handler_utils::{find_listing_by_id, find_or_create_db_user_from_update}, - message_utils::{extract_callback_data, pluralize_with_count, MessageTarget}, + message_utils::{extract_callback_data, pluralize_with_count}, start_command_data::StartCommandData, App, BotError, BotResult, Command, DialogueRootState, RootDialogue, }; @@ -87,12 +87,8 @@ pub fn my_listings_handler() -> Handler<'static, BotResult, DpHandlerDescription ) } -async fn handle_view_listing_details( - app: App, - listing: PersistedListing, - target: MessageTarget, -) -> BotResult { - send_listing_details_message(app, target, listing, None).await?; +async fn handle_view_listing_details(app: App, listing: PersistedListing) -> BotResult { + send_listing_details_message(app, listing, None).await?; Ok(()) } @@ -230,9 +226,8 @@ async fn handle_my_listings_command_input( app: App, dialogue: RootDialogue, user: PersistedUser, - target: MessageTarget, ) -> BotResult { - enter_my_listings(app, dialogue, user, target, None).await?; + enter_my_listings(app, dialogue, user, None).await?; Ok(()) } @@ -240,7 +235,6 @@ pub async fn enter_my_listings( app: App, dialogue: RootDialogue, user: PersistedUser, - target: MessageTarget, flash: Option, ) -> BotResult { // Transition to ViewingListings state @@ -263,7 +257,6 @@ pub async fn enter_my_listings( if listings.is_empty() { app.bot .send_html_message( - target, "📋 My Listings\n\n\ You don't have any listings yet." .to_string(), @@ -284,9 +277,7 @@ pub async fn enter_my_listings( response = format!("{flash}\n\n{response}"); } - app.bot - .send_html_message(target, response, Some(keyboard)) - .await?; + app.bot.send_html_message(response, Some(keyboard)).await?; Ok(()) } @@ -295,12 +286,11 @@ async fn handle_viewing_listings_callback( dialogue: RootDialogue, callback_query: CallbackQuery, user: PersistedUser, - target: MessageTarget, ) -> BotResult { let data = extract_callback_data(app.bot.deref(), callback_query).await?; if let Ok(NavKeyboardButtons::Back) = NavKeyboardButtons::try_from(data.as_str()) { - return enter_main_menu(app, dialogue, target).await; + return enter_main_menu(app, dialogue).await; } // Check if it's the back to menu button @@ -308,10 +298,10 @@ async fn handle_viewing_listings_callback( match button { MyListingsButtons::SelectListing(listing_id) => { let listing = get_listing_for_user(&app.daos, user, listing_id).await?; - enter_show_listing_details(app, dialogue, listing, target).await?; + enter_show_listing_details(app, dialogue, listing).await?; } MyListingsButtons::NewListing => { - enter_select_new_listing_type(app, dialogue, target).await?; + enter_select_new_listing_type(app, dialogue).await?; } } @@ -322,7 +312,6 @@ async fn enter_show_listing_details( app: App, dialogue: RootDialogue, listing: PersistedListing, - target: MessageTarget, ) -> BotResult { let listing_id = listing.persisted.id; dialogue @@ -342,13 +331,12 @@ async fn enter_show_listing_details( ManageListingButtons::Delete.to_button(), ]) .append_row([ManageListingButtons::Back.to_button()]); - send_listing_details_message(app, target, listing, Some(keyboard)).await?; + send_listing_details_message(app, listing, Some(keyboard)).await?; Ok(()) } async fn send_listing_details_message( app: App, - target: MessageTarget, listing: PersistedListing, keyboard: Option, ) -> BotResult { @@ -365,7 +353,7 @@ async fn send_listing_details_message( response_lines.push(format!("{}: {}", step.field_name, field_value)); } app.bot - .send_html_message(target, response_lines.join("\n"), keyboard) + .send_html_message(response_lines.join("\n"), keyboard) .await?; Ok(()) } @@ -376,7 +364,6 @@ async fn handle_managing_listing_callback( callback_query: CallbackQuery, user: PersistedUser, listing_id: ListingDbId, - target: MessageTarget, ) -> BotResult { let from = callback_query.from.clone(); let data = extract_callback_data(&app.bot, callback_query).await?; @@ -397,21 +384,14 @@ async fn handle_managing_listing_callback( ManageListingButtons::Edit => { let listing = get_listing_for_user(&app.daos, user, listing_id).await?; let draft = ListingDraft::from_persisted(listing); - enter_edit_listing_draft(app, target, draft, dialogue, None).await?; + enter_edit_listing_draft(app, draft, dialogue, None).await?; } ManageListingButtons::Delete => { app.daos.listing.delete_listing(listing_id).await?; - enter_my_listings( - app, - dialogue, - user, - target, - Some("Listing deleted.".to_string()), - ) - .await?; + enter_my_listings(app, dialogue, user, Some("Listing deleted.".to_string())).await?; } ManageListingButtons::Back => { - enter_my_listings(app, dialogue, user, target, None).await?; + enter_my_listings(app, dialogue, user, None).await?; } } @@ -459,9 +439,10 @@ async fn send_preview_listing_message( if let Some(description) = &listing.base.description { response_lines.push(description.to_owned()); } + app.bot + .with_target(from.into()) .send_html_message( - from.into(), response_lines.join("\n\n"), Some(keyboard_for_listing(&listing)), ) diff --git a/src/commands/new_listing/callbacks.rs b/src/commands/new_listing/callbacks.rs index 59f723c..84e37b7 100644 --- a/src/commands/new_listing/callbacks.rs +++ b/src/commands/new_listing/callbacks.rs @@ -28,13 +28,12 @@ pub async fn handle_selecting_listing_type_callback( dialogue: RootDialogue, user: PersistedUser, callback_query: CallbackQuery, - target: MessageTarget, ) -> BotResult { let data = extract_callback_data(&app.bot, callback_query).await?; - info!("User {target:?} selected listing type: {data:?}"); + info!("User selected listing type: {data:?}"); if let Ok(NavKeyboardButtons::Back) = NavKeyboardButtons::try_from(data.as_str()) { - return enter_my_listings(app, dialogue, user, target, None).await; + return enter_my_listings(app, dialogue, user, None).await; } // Parse the listing type from callback data @@ -62,11 +61,7 @@ pub async fn handle_selecting_listing_type_callback( ); app.bot - .send_html_message( - target, - response, - get_keyboard_for_field(ListingField::Title), - ) + .send_html_message(response, get_keyboard_for_field(ListingField::Title)) .await?; Ok(()) @@ -79,21 +74,20 @@ pub async fn handle_awaiting_draft_field_callback( field: ListingField, draft: ListingDraft, callback_query: CallbackQuery, - target: MessageTarget, ) -> BotResult { let data = extract_callback_data(&app.bot, callback_query).await?; - info!("User {target:?} selected callback: {data:?}"); + info!("User selected callback: {data:?}"); if let Ok(button) = NavKeyboardButtons::try_from(data.as_str()) { match button { NavKeyboardButtons::Back => { - return enter_select_new_listing_type(app, dialogue, target).await; + return enter_select_new_listing_type(app, dialogue).await; } NavKeyboardButtons::Skip => { - return handle_skip_field(app, dialogue, field, draft, target).await; + return handle_skip_field(app, dialogue, field, draft).await; } NavKeyboardButtons::Cancel => { - return cancel_wizard(app, dialogue, target).await; + return cancel_wizard(app, dialogue).await; } } } @@ -102,23 +96,23 @@ pub async fn handle_awaiting_draft_field_callback( match field { ListingField::Slots => { let button = SlotsKeyboardButtons::try_from(data.as_str())?; - handle_slots_callback(app, dialogue, draft, button, target).await + handle_slots_callback(app, dialogue, draft, button).await } ListingField::StartTime => { let button = StartTimeKeyboardButtons::try_from(data.as_str())?; - handle_start_time_callback(app, dialogue, draft, button, target).await + handle_start_time_callback(app, dialogue, draft, button).await } ListingField::EndTime => { let button = DurationKeyboardButtons::try_from(data.as_str())?; - handle_duration_callback(app, dialogue, draft, button, target).await + handle_duration_callback(app, dialogue, draft, button).await } ListingField::MinBidIncrement => { let button = EditMinimumBidIncrementKeyboardButtons::try_from(data.as_str())?; - handle_starting_bid_amount_callback(app, dialogue, draft, button, target).await + handle_starting_bid_amount_callback(app, dialogue, draft, button).await } ListingField::CurrencyType => { let button = CurrencyTypeKeyboardButtons::try_from(data.as_str())?; - handle_currency_type_callback(app, dialogue, draft, button, target).await + handle_currency_type_callback(app, dialogue, draft, button).await } _ => { error!("Unknown callback data for field {field:?}: {data}"); @@ -132,7 +126,6 @@ async fn handle_skip_field( dialogue: RootDialogue, current_field: ListingField, draft: ListingDraft, - target: MessageTarget, ) -> BotResult { let field_name = get_field_name(current_field, draft.listing_type()); let next_field = get_next_field(current_field, draft.listing_type()); @@ -145,10 +138,10 @@ async fn handle_skip_field( ); transition_to_field(dialogue, next_field, draft).await?; app.bot - .send_html_message(target, response, get_keyboard_for_field(next_field)) + .send_html_message(response, get_keyboard_for_field(next_field)) .await?; } else { - enter_confirm_save_listing(app, dialogue, target, draft, Some(flash)).await?; + enter_confirm_save_listing(app, dialogue, draft, Some(flash)).await?; } Ok(()) } @@ -159,7 +152,6 @@ async fn handle_slots_callback( dialogue: RootDialogue, mut draft: ListingDraft, button: SlotsKeyboardButtons, - target: MessageTarget, ) -> BotResult { let num_slots = match button { SlotsKeyboardButtons::OneSlot => 1, @@ -181,11 +173,7 @@ async fn handle_slots_callback( ); transition_to_field(dialogue, ListingField::StartTime, draft).await?; app.bot - .send_html_message( - target, - response, - get_keyboard_for_field(ListingField::StartTime), - ) + .send_html_message(response, get_keyboard_for_field(ListingField::StartTime)) .await?; Ok(()) } @@ -196,7 +184,6 @@ async fn handle_start_time_callback( dialogue: RootDialogue, mut draft: ListingDraft, button: StartTimeKeyboardButtons, - target: MessageTarget, ) -> BotResult { let start_time = match button { StartTimeKeyboardButtons::Now => ListingDuration::zero(), @@ -216,11 +203,7 @@ async fn handle_start_time_callback( ); transition_to_field(dialogue, ListingField::EndTime, draft).await?; app.bot - .send_html_message( - target, - response, - get_keyboard_for_field(ListingField::EndTime), - ) + .send_html_message(response, get_keyboard_for_field(ListingField::EndTime)) .await?; Ok(()) } @@ -231,7 +214,6 @@ async fn handle_duration_callback( dialogue: RootDialogue, mut draft: ListingDraft, button: DurationKeyboardButtons, - target: MessageTarget, ) -> BotResult { let duration = ListingDuration::days(match button { DurationKeyboardButtons::OneDay => 1, @@ -248,7 +230,7 @@ async fn handle_duration_callback( .map_err(|e| anyhow::anyhow!("Error updating duration: {e:?}"))?; let flash = get_success_message(ListingField::EndTime, draft.listing_type()); - enter_confirm_save_listing(app, dialogue, target, draft, Some(flash)).await + enter_confirm_save_listing(app, dialogue, draft, Some(flash)).await } async fn handle_starting_bid_amount_callback( @@ -256,7 +238,6 @@ async fn handle_starting_bid_amount_callback( dialogue: RootDialogue, mut draft: ListingDraft, button: EditMinimumBidIncrementKeyboardButtons, - target: MessageTarget, ) -> BotResult { let starting_bid_amount = MoneyAmount::from_str(match button { EditMinimumBidIncrementKeyboardButtons::OneDollar => "1.00", @@ -273,7 +254,7 @@ async fn handle_starting_bid_amount_callback( .map_err(|e| anyhow::anyhow!("Error updating starting bid amount: {e:?}"))?; let flash = get_success_message(ListingField::StartingBidAmount, draft.listing_type()); - enter_confirm_save_listing(app, dialogue, target, draft, Some(flash)).await + enter_confirm_save_listing(app, dialogue, draft, Some(flash)).await } async fn handle_currency_type_callback( @@ -281,7 +262,6 @@ async fn handle_currency_type_callback( dialogue: RootDialogue, mut draft: ListingDraft, button: CurrencyTypeKeyboardButtons, - target: MessageTarget, ) -> BotResult { let currency_type = match button { CurrencyTypeKeyboardButtons::Usd => CurrencyType::Usd, @@ -305,14 +285,14 @@ async fn handle_currency_type_callback( ); transition_to_field(dialogue, next_field, draft).await?; app.bot - .send_html_message(target, response, get_keyboard_for_field(next_field)) + .send_html_message(response, get_keyboard_for_field(next_field)) .await?; Ok(()) } /// Cancel the wizard and exit -pub async fn cancel_wizard(app: App, dialogue: RootDialogue, target: MessageTarget) -> BotResult { - info!("{target:?} cancelled new listing wizard"); - enter_select_new_listing_type(app, dialogue, target).await?; +pub async fn cancel_wizard(app: App, dialogue: RootDialogue) -> BotResult { + info!("User cancelled new listing wizard"); + enter_select_new_listing_type(app, dialogue).await?; Ok(()) } diff --git a/src/commands/new_listing/handlers.rs b/src/commands/new_listing/handlers.rs index e46efb8..fcaf48b 100644 --- a/src/commands/new_listing/handlers.rs +++ b/src/commands/new_listing/handlers.rs @@ -35,20 +35,12 @@ use log::info; use teloxide::{prelude::*, types::*}; /// Handle the /newlisting command - starts the dialogue -pub(super) async fn handle_new_listing_command( - app: App, - dialogue: RootDialogue, - target: MessageTarget, -) -> BotResult { - enter_select_new_listing_type(app, dialogue, target).await?; +pub(super) async fn handle_new_listing_command(app: App, dialogue: RootDialogue) -> BotResult { + enter_select_new_listing_type(app, dialogue).await?; Ok(()) } -pub async fn enter_select_new_listing_type( - app: App, - dialogue: RootDialogue, - target: MessageTarget, -) -> BotResult { +pub async fn enter_select_new_listing_type(app: App, dialogue: RootDialogue) -> BotResult { // Initialize the dialogue to listing type selection state dialogue .update(NewListingState::SelectingListingType) @@ -57,7 +49,6 @@ pub async fn enter_select_new_listing_type( app.bot .send_html_message( - target, get_listing_type_selection_message().to_string(), Some(get_listing_type_keyboard()), ) @@ -71,10 +62,9 @@ pub async fn handle_awaiting_draft_field_input( dialogue: RootDialogue, field: ListingField, mut draft: ListingDraft, - target: MessageTarget, msg: Message, ) -> BotResult { - info!("User {target:?} entered input step: {field:?}"); + info!("User entered input step: {field:?}"); // Process the field update match update_field_on_draft(field, &mut draft, msg.text()) { @@ -99,11 +89,11 @@ pub async fn handle_awaiting_draft_field_input( ); transition_to_field(dialogue, next_field, draft).await?; app.bot - .send_html_message(target, response, get_keyboard_for_field(next_field)) + .send_html_message(response, get_keyboard_for_field(next_field)) .await?; } else { // Final step - go to confirmation - enter_confirm_save_listing(app, dialogue, target, draft, None).await?; + enter_confirm_save_listing(app, dialogue, draft, None).await?; } Ok(()) } @@ -114,10 +104,9 @@ pub async fn handle_editing_field_input( dialogue: RootDialogue, field: ListingField, mut draft: ListingDraft, - target: MessageTarget, msg: Message, ) -> BotResult { - info!("User {target:?} editing field {field:?}"); + info!("User editing field {field:?}"); // Process the field update match update_field_on_draft(field, &mut draft, msg.text()) { @@ -134,7 +123,7 @@ pub async fn handle_editing_field_input( }; let flash = get_edit_success_message(field, draft.listing_type()); - enter_edit_listing_draft(app, target, draft, dialogue, Some(flash)).await?; + enter_edit_listing_draft(app, draft, dialogue, Some(flash)).await?; Ok(()) } @@ -145,37 +134,36 @@ pub async fn handle_viewing_draft_callback( draft: ListingDraft, user: PersistedUser, callback_query: CallbackQuery, - target: MessageTarget, ) -> BotResult { let data = extract_callback_data(&app.bot, callback_query).await?; match ConfirmationKeyboardButtons::try_from(data.as_str())? { ConfirmationKeyboardButtons::Create | ConfirmationKeyboardButtons::Save => { - info!("User {target:?} confirmed listing creation"); + info!("User confirmed listing creation"); let success_message = save_listing(&app.daos.listing, draft).await?; - enter_my_listings(app, dialogue, user, target, Some(success_message)).await?; + enter_my_listings(app, dialogue, user, Some(success_message)).await?; } ConfirmationKeyboardButtons::Cancel => { - info!("User {target:?} cancelled listing update"); + info!("User cancelled listing update"); let response = "🗑️ Changes Discarded\n\n\ Your changes have been discarded and not saved." .to_string(); - app.bot.send_html_message(target, response, None).await?; + app.bot.send_html_message(response, None).await?; dialogue.exit().await.context("failed to exit dialogue")?; } ConfirmationKeyboardButtons::Discard => { - info!("User {target:?} discarded listing creation"); + info!("User discarded listing creation"); let response = "🗑️ Listing Discarded\n\n\ Your listing has been discarded and not created.\n\ You can start a new listing anytime with /newlisting." .to_string(); - app.bot.send_html_message(target, response, None).await?; + app.bot.send_html_message(response, None).await?; dialogue.exit().await.context("failed to exit dialogue")?; } ConfirmationKeyboardButtons::Edit => { - info!("User {target:?} chose to edit listing"); - enter_edit_listing_draft(app, target, draft, dialogue, None).await?; + info!("User chose to edit listing"); + enter_edit_listing_draft(app, draft, dialogue, None).await?; } } @@ -188,14 +176,13 @@ pub async fn handle_editing_draft_callback( draft: ListingDraft, dialogue: RootDialogue, callback_query: CallbackQuery, - target: MessageTarget, ) -> BotResult { let data = extract_callback_data(&app.bot, callback_query).await?; - info!("User {target:?} in editing screen, showing field selection"); + info!("User in editing screen, showing field selection"); let button = FieldSelectionKeyboardButtons::try_from(data.as_str())?; if button == FieldSelectionKeyboardButtons::Done { - return enter_confirm_save_listing(app, dialogue, target, draft, None).await; + return enter_confirm_save_listing(app, dialogue, draft, None).await; } let field = match button { @@ -221,9 +208,7 @@ pub async fn handle_editing_draft_callback( .context("failed to update dialogue")?; let response = format!("Editing {field:?}\n\nPrevious value: {value}"); - app.bot - .send_html_message(target, response, Some(keyboard)) - .await?; + app.bot.send_html_message(response, Some(keyboard)).await?; Ok(()) } @@ -235,19 +220,18 @@ pub async fn handle_editing_draft_field_callback( field: ListingField, draft: ListingDraft, callback_query: CallbackQuery, - target: MessageTarget, ) -> BotResult { let data = extract_callback_data(&app.bot, callback_query).await?; - info!("User {target:?} editing field: {field:?} -> {data:?}"); + info!("User editing field: {field:?} -> {data:?}"); if data == "edit_back" { - enter_edit_listing_draft(app, target, draft, dialogue, None).await?; + enter_edit_listing_draft(app, draft, dialogue, None).await?; return Ok(()); } // This callback handler typically receives button presses, not text input // For now, just redirect back to edit screen since callback data isn't suitable for validation - enter_edit_listing_draft(app, target, draft, dialogue, None).await?; + enter_edit_listing_draft(app, draft, dialogue, None).await?; Ok(()) } @@ -255,14 +239,12 @@ pub async fn handle_editing_draft_field_callback( /// Enter the edit listing draft screen pub async fn enter_edit_listing_draft( app: App, - target: MessageTarget, draft: ListingDraft, dialogue: RootDialogue, flash_message: Option, ) -> BotResult { display_listing_summary( app, - target, &draft, Some(FieldSelectionKeyboardButtons::to_keyboard()), flash_message, diff --git a/src/commands/new_listing/ui.rs b/src/commands/new_listing/ui.rs index a8bd65d..6d12dab 100644 --- a/src/commands/new_listing/ui.rs +++ b/src/commands/new_listing/ui.rs @@ -9,14 +9,13 @@ use crate::commands::new_listing::NewListingState; use crate::db::ListingType; use crate::App; use crate::RootDialogue; -use crate::{commands::new_listing::types::ListingDraft, message_utils::*, BotResult}; +use crate::{commands::new_listing::types::ListingDraft, BotResult}; use anyhow::Context; use teloxide::types::InlineKeyboardMarkup; /// Display the listing summary with optional flash message and keyboard pub async fn display_listing_summary( app: App, - target: MessageTarget, draft: &ListingDraft, keyboard: Option, flash_message: Option, @@ -50,7 +49,7 @@ pub async fn display_listing_summary( response_lines.push("Edit your listing:".to_string()); app.bot - .send_html_message(target, response_lines.join("\n"), keyboard) + .send_html_message(response_lines.join("\n"), keyboard) .await?; Ok(()) @@ -60,7 +59,6 @@ pub async fn display_listing_summary( pub async fn enter_confirm_save_listing( app: App, dialogue: RootDialogue, - target: MessageTarget, draft: ListingDraft, flash: Option, ) -> BotResult { @@ -78,7 +76,7 @@ pub async fn enter_confirm_save_listing( ]) }; - display_listing_summary(app, target, &draft, Some(keyboard), flash).await?; + display_listing_summary(app, &draft, Some(keyboard), flash).await?; dialogue .update(NewListingState::ViewingDraft(draft)) .await diff --git a/src/commands/settings.rs b/src/commands/settings.rs index 51523d3..4d6256b 100644 --- a/src/commands/settings.rs +++ b/src/commands/settings.rs @@ -1,8 +1,8 @@ -use crate::{message_utils::MessageTarget, App, BotResult}; +use crate::{App, BotResult}; use log::info; use teloxide::types::Message; -pub async fn handle_settings(app: App, msg: Message, target: MessageTarget) -> BotResult { +pub async fn handle_settings(app: App, msg: Message) -> BotResult { let response = "⚙️ Settings (Coming Soon)\n\n\ Here you'll be able to configure:\n\ • Notification preferences\n\ @@ -18,6 +18,6 @@ pub async fn handle_settings(app: App, msg: Message, target: MessageTarget) -> B msg.chat.id ); - app.bot.send_html_message(target, response, None).await?; + app.bot.send_html_message(response, None).await?; Ok(()) } diff --git a/src/commands/start.rs b/src/commands/start.rs index ef8b87c..c800514 100644 --- a/src/commands/start.rs +++ b/src/commands/start.rs @@ -6,11 +6,8 @@ use teloxide::{ }; use crate::{ - commands::my_listings::enter_my_listings, - db::user::PersistedUser, - keyboard_buttons, - message_utils::{extract_callback_data, MessageTarget}, - App, BotResult, Command, DialogueRootState, RootDialogue, + commands::my_listings::enter_my_listings, db::user::PersistedUser, keyboard_buttons, + message_utils::extract_callback_data, App, BotResult, Command, DialogueRootState, RootDialogue, }; keyboard_buttons! { @@ -37,19 +34,14 @@ fn get_main_menu_message() -> &'static str { Choose an option below to get started! 🚀" } -pub async fn handle_start( - app: App, - dialogue: RootDialogue, - target: MessageTarget, - update: Update, -) -> BotResult { +pub async fn handle_start(app: App, dialogue: RootDialogue, update: Update) -> BotResult { info!("got start message: {update:?}"); - enter_main_menu(app, dialogue, target).await?; + enter_main_menu(app, dialogue).await?; Ok(()) } /// Show the main menu with buttons -pub async fn enter_main_menu(app: App, dialogue: RootDialogue, target: MessageTarget) -> BotResult { +pub async fn enter_main_menu(app: App, dialogue: RootDialogue) -> BotResult { dialogue .update(DialogueRootState::MainMenu) .await @@ -57,7 +49,6 @@ pub async fn enter_main_menu(app: App, dialogue: RootDialogue, target: MessageTa app.bot .send_html_message( - target, get_main_menu_message().to_string(), Some(MainMenuButtons::to_keyboard()), ) @@ -71,21 +62,19 @@ pub async fn handle_main_menu_callback( dialogue: RootDialogue, user: PersistedUser, callback_query: CallbackQuery, - target: MessageTarget, ) -> BotResult { let data = extract_callback_data(&app.bot, callback_query).await?; - info!("User {target:?} selected main menu option: {data:?}"); + info!("User selected main menu option: {data:?}"); let button = MainMenuButtons::try_from(data.as_str())?; match button { MainMenuButtons::MyListings => { // Call show_listings_for_user directly - enter_my_listings(app, dialogue, user, target, None).await?; + enter_my_listings(app, dialogue, user, None).await?; } MainMenuButtons::MyBids => { app.bot .send_html_message( - target, "💰 My Bids (Coming Soon)\n\n\ Here you'll be able to view:\n\ • Your active bids\n\ @@ -101,7 +90,6 @@ pub async fn handle_main_menu_callback( MainMenuButtons::Settings => { app.bot .send_html_message( - target, "⚙️ Settings (Coming Soon)\n\n\ Here you'll be able to configure:\n\ • Notification preferences\n\ @@ -122,7 +110,7 @@ pub async fn handle_main_menu_callback( Command::descriptions() ); app.bot - .send_html_message(target, help_message, Some(MainMenuButtons::to_keyboard())) + .send_html_message(help_message, Some(MainMenuButtons::to_keyboard())) .await?; } } diff --git a/src/handle_error.rs b/src/handle_error.rs index c9b5b94..77608bd 100644 --- a/src/handle_error.rs +++ b/src/handle_error.rs @@ -1,18 +1,13 @@ -use crate::{ - message_utils::MessageTarget, wrap_endpoint, App, BotError, BotResult, WrappedAsyncFn, -}; +use crate::{wrap_endpoint, App, BotError, BotResult, WrappedAsyncFn}; use futures::future::BoxFuture; -pub async fn handle_error(app: App, target: MessageTarget, error: BotError) -> BotResult { +pub async fn handle_error(app: App, error: BotError) -> BotResult { log::error!("Error in handler: {error:?}"); match error { - BotError::UserVisibleError(message) => { - app.bot.send_html_message(target, message, None).await? - } + BotError::UserVisibleError(message) => app.bot.send_html_message(message, None).await?, BotError::InternalError(_) => { app.bot .send_html_message( - target, "An internal error occurred. Please try again later.".to_string(), None, ) @@ -22,20 +17,16 @@ pub async fn handle_error(app: App, target: MessageTarget, error: BotError) -> B Ok(()) } -fn boxed_handle_error( - app: App, - target: MessageTarget, - error: BotError, -) -> BoxFuture<'static, BotResult> { - Box::pin(handle_error(app, target, error)) +fn boxed_handle_error(app: App, error: BotError) -> BoxFuture<'static, BotResult> { + Box::pin(handle_error(app, error)) } pub type ErrorHandlerWrapped = WrappedAsyncFn< FnBase, - fn(App, MessageTarget, BotError) -> BoxFuture<'static, BotResult>, + fn(App, BotError) -> BoxFuture<'static, BotResult>, BotError, FnBaseArgs, - (App, MessageTarget), + (App,), >; pub fn with_error_handler( diff --git a/src/main.rs b/src/main.rs index 9bc1909..1419058 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,7 @@ use crate::commands::{ use crate::db::DAOs; use crate::handle_error::with_error_handler; use crate::handler_utils::{find_or_create_db_user_from_update, update_into_message_target}; -use crate::message_sender::BoxMessageSender; +use crate::message_sender::{BotMessageSender, BoxedMessageSender}; use crate::sqlite_storage::SqliteStorage; use anyhow::Result; pub use bot_result::*; @@ -39,12 +39,12 @@ pub use wrap_endpoint::*; #[derive(Clone)] pub struct App { - pub bot: Arc, + pub bot: Arc, pub daos: DAOs, } impl App { - pub fn new(bot: BoxMessageSender, daos: DAOs) -> Self { + pub fn new(bot: BoxedMessageSender, daos: DAOs) -> Self { Self { bot: Arc::new(bot), daos, @@ -100,11 +100,9 @@ type RootDialogue = Dialogue>; pub fn main_handler() -> BotHandler { dptree::entry() - .map(|app: App| app.daos.clone()) .map(|daos: DAOs| daos.user.clone()) .map(|daos: DAOs| daos.listing.clone()) .map(|daos: DAOs| daos.bid.clone()) - .filter_map(update_into_message_target) .filter_map_async(find_or_create_db_user_from_update) .branch(my_listings_inline_handler()) .branch( @@ -156,20 +154,21 @@ async fn main() -> Result<()> { // Set up the bot's command menu setup_bot_commands(&bot).await?; + let handler_with_deps = dptree::entry() + .filter_map(|bot: Box, update: Update, daos: DAOs| { + let target = update_into_message_target(update)?; + Some(App::new( + Box::new(BotMessageSender::new(*bot, target)), + daos.clone(), + )) + }) + .chain(main_handler()); + let dialog_storage = SqliteStorage::new(db_pool.clone(), Json).await?; let daos = DAOs::new(db_pool.clone()); - let app = App::new(bot.clone(), daos.clone()); - // Create dispatcher with dialogue system - Dispatcher::builder(bot, main_handler()) - .dependencies(dptree::deps![ - dialog_storage, - daos, - app.daos.user.clone(), - app.daos.listing.clone(), - app.daos.bid.clone(), - app - ]) + Dispatcher::builder(bot, handler_with_deps) + .dependencies(dptree::deps![dialog_storage, daos]) .enable_ctrlc_handler() .worker_queue_size(1) .build() @@ -199,7 +198,7 @@ mod tests { let mut bot = MockMessageSender::new(); bot.expect_send_html_message() .times(1) - .returning(|_, _, _| Ok(())); + .returning(|_, _| Ok(())); let deps = create_deps(bot).await; let handler = main_handler(); dptree::type_check(handler.sig(), &deps, &[]); diff --git a/src/message_sender.rs b/src/message_sender.rs index 8fb1e65..14bf524 100644 --- a/src/message_sender.rs +++ b/src/message_sender.rs @@ -14,10 +14,10 @@ use teloxide::{ pub trait MessageSender { async fn send_html_message( &self, - target: MessageTarget, text: String, keyboard: Option, ) -> BotResult; + fn with_target(&self, target: MessageTarget) -> BoxedMessageSender; async fn answer_inline_query( &self, inline_query_id: InlineQueryId, @@ -27,7 +27,7 @@ pub trait MessageSender { async fn get_me(&self) -> BotResult; } -pub type BoxMessageSender = Box; +pub type BoxedMessageSender = Box; #[cfg(test)] mockall::mock! { @@ -37,9 +37,9 @@ mockall::mock! { } #[async_trait] impl MessageSender for MessageSender { + fn with_target(&self, target: MessageTarget) -> BoxedMessageSender; async fn send_html_message( &self, - target: MessageTarget, text: String, keyboard: Option, ) -> BotResult; @@ -53,17 +53,29 @@ mockall::mock! { } } +pub struct BotMessageSender(Bot, MessageTarget); +impl BotMessageSender { + pub fn new(bot: Bot, message_target: MessageTarget) -> Self { + Self(bot, message_target) + } +} + #[async_trait] -impl MessageSender for Bot { +impl MessageSender for BotMessageSender { + fn with_target(&self, target: MessageTarget) -> BoxedMessageSender { + let clone = Self(self.0.clone(), target); + Box::new(clone) + } async fn send_html_message( &self, - target: MessageTarget, text: String, keyboard: Option, ) -> BotResult { + let target = self.1.clone(); if let Some(message_id) = target.message_id { log::info!("Editing message in chat: {target:?}"); let mut message = self + .0 .edit_message_text(target.chat_id, message_id, &text) .parse_mode(ParseMode::Html); if let Some(kb) = keyboard { @@ -73,6 +85,7 @@ impl MessageSender for Bot { } else { log::info!("Sending message to chat: {target:?}"); let mut message = self + .0 .send_message(target.chat_id, &text) .parse_mode(ParseMode::Html); if let Some(kb) = keyboard { @@ -88,21 +101,24 @@ impl MessageSender for Bot { inline_query_id: InlineQueryId, results: Vec, ) -> BotResult { - teloxide::prelude::Requester::answer_inline_query(self, inline_query_id, results) + self.0 + .answer_inline_query(inline_query_id, results) .await .map(|_| ()) .map_err(|err| BotError::InternalError(err.into())) } async fn answer_callback_query(&self, query_id: CallbackQueryId) -> BotResult { - teloxide::prelude::Requester::answer_callback_query(self, query_id) + self.0 + .answer_callback_query(query_id) .await .map(|_| ()) .map_err(|err| BotError::InternalError(err.into())) } async fn get_me(&self) -> BotResult { - teloxide::prelude::Requester::get_me(self) + self.0 + .get_me() .await .map_err(|err| BotError::InternalError(err.into())) } diff --git a/src/message_utils.rs b/src/message_utils.rs index 371ad50..0caf2a2 100644 --- a/src/message_utils.rs +++ b/src/message_utils.rs @@ -1,4 +1,4 @@ -use crate::{message_sender::BoxMessageSender, BotResult}; +use crate::{message_sender::BoxedMessageSender, BotResult}; use anyhow::anyhow; use chrono::{DateTime, Utc}; use num::One; @@ -127,7 +127,7 @@ pub fn create_single_button_keyboard(text: &str, callback_data: &str) -> InlineK // Extract callback data and answer callback query pub async fn extract_callback_data( - bot: &BoxMessageSender, + bot: &BoxedMessageSender, callback_query: CallbackQuery, ) -> BotResult { let data = match callback_query.data { diff --git a/src/test_utils.rs b/src/test_utils.rs index 0f952c1..3acffbe 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -178,7 +178,8 @@ pub async fn create_deps(mock_bot: MockMessageSender) -> DependencyMap { let update = create_tele_update(); let pool = create_test_pool().await; let dialog_storage = SqliteStorage::new(pool.clone(), Json).await.unwrap(); - let app = App::new(Box::new(mock_bot), DAOs::new(pool)); + let daos = DAOs::new(pool); + let app = App::new(Box::new(mock_bot), daos.clone()); let me_user = create_tele_user("me"); let me = Me { user: me_user, @@ -188,7 +189,7 @@ pub async fn create_deps(mock_bot: MockMessageSender) -> DependencyMap { can_connect_to_business: true, has_main_web_app: true, }; - dptree::deps![update, dialog_storage, app, me] + dptree::deps![update, dialog_storage, app, me, daos] } #[cfg(test)]