move target into state

This commit is contained in:
Dylan Knutson
2025-09-05 22:17:30 +00:00
parent af5b8883af
commit a40ac5f345
14 changed files with 134 additions and 205 deletions

View File

@@ -12,7 +12,6 @@ use crate::{
dptree_utils::MapTwo, dptree_utils::MapTwo,
handle_error::with_error_handler, handle_error::with_error_handler,
handler_utils::find_listing_by_id, handler_utils::find_listing_by_id,
message_utils::MessageTarget,
start_command_data::StartCommandData, start_command_data::StartCommandData,
App, BotError, BotHandler, BotResult, DialogueRootState, RootDialogue, App, BotError, BotHandler, BotResult, DialogueRootState, RootDialogue,
}; };
@@ -75,7 +74,6 @@ pub fn bidding_handler() -> BotHandler {
async fn handle_place_bid_on_listing( async fn handle_place_bid_on_listing(
app: App, app: App,
user_dao: UserDAO, user_dao: UserDAO,
target: MessageTarget,
user: PersistedUser, user: PersistedUser,
listing: PersistedListing, listing: PersistedListing,
dialogue: RootDialogue, dialogue: RootDialogue,
@@ -116,7 +114,7 @@ async fn handle_place_bid_on_listing(
.append_row([InlineKeyboardButton::callback("Bid $1", "cancel")]); .append_row([InlineKeyboardButton::callback("Bid $1", "cancel")]);
app.bot app.bot
.send_html_message(target, response_lines.join("\n"), Some(keyboard)) .send_html_message(response_lines.join("\n"), Some(keyboard))
.await?; .await?;
Ok(()) Ok(())
@@ -125,7 +123,6 @@ async fn handle_place_bid_on_listing(
async fn handle_awaiting_bid_amount_input( async fn handle_awaiting_bid_amount_input(
app: App, app: App,
listing: PersistedListing, listing: PersistedListing,
target: MessageTarget,
dialogue: RootDialogue, dialogue: RootDialogue,
msg: Message, msg: Message,
) -> BotResult { ) -> BotResult {
@@ -146,7 +143,6 @@ async fn handle_awaiting_bid_amount_input(
let bid_amount_str = format!("{}{}", listing.base.currency_type.symbol(), bid_amount); let bid_amount_str = format!("{}{}", listing.base.currency_type.symbol(), bid_amount);
app.bot app.bot
.send_html_message( .send_html_message(
target,
format!("Confirm bid amount: {bid_amount_str} - this cannot be undone!"), format!("Confirm bid amount: {bid_amount_str} - this cannot be undone!"),
Some(InlineKeyboardMarkup::default().append_row([ Some(InlineKeyboardMarkup::default().append_row([
InlineKeyboardButton::callback( InlineKeyboardButton::callback(
@@ -174,7 +170,6 @@ async fn handle_awaiting_confirm_bid_amount_callback(
listing: PersistedListing, listing: PersistedListing,
user: PersistedUser, user: PersistedUser,
bid_amount: MoneyAmount, bid_amount: MoneyAmount,
target: MessageTarget,
dialogue: RootDialogue, dialogue: RootDialogue,
callback_query: CallbackQuery, callback_query: CallbackQuery,
) -> BotResult { ) -> BotResult {
@@ -188,7 +183,7 @@ async fn handle_awaiting_confirm_bid_amount_callback(
"cancel_bid" => { "cancel_bid" => {
dialogue.exit().await.context("failed to exit dialogue")?; dialogue.exit().await.context("failed to exit dialogue")?;
app.bot app.bot
.send_html_message(target, "Bid cancelled".to_string(), None) .send_html_message("Bid cancelled".to_string(), None)
.await?; .await?;
return Ok(()); 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); let bid_amount_str = format!("{}{}", listing.base.currency_type.symbol(), bid_amount);
app.bot app.bot
.with_target(callback_query.from.into())
.send_html_message( .send_html_message(
target.only_chat_id(),
format!("Bid placed for {bid_amount_str} on {}", listing.base.title), format!("Bid placed for {bid_amount_str} on {}", listing.base.title),
None, None,
) )

View File

@@ -1,7 +1,7 @@
use crate::{message_utils::MessageTarget, App, BotResult, Command}; use crate::{App, BotResult, Command};
use teloxide::utils::command::BotCommands; 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!( let help_message = format!(
"📋 Available Commands:\n\n{}\n\n\ "📋 Available Commands:\n\n{}\n\n\
📧 Support: Contact @admin for help\n\ 📧 Support: Contact @admin for help\n\
@@ -9,8 +9,6 @@ pub async fn handle_help(app: App, target: MessageTarget) -> BotResult {
Command::descriptions() Command::descriptions()
); );
app.bot app.bot.send_html_message(help_message, None).await?;
.send_html_message(target, help_message, None)
.await?;
Ok(()) Ok(())
} }

View File

@@ -1,8 +1,8 @@
use crate::{message_utils::MessageTarget, App, BotResult}; use crate::{App, BotResult};
use log::info; use log::info;
use teloxide::types::Message; 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\ let response = "🎯 My Bids (Coming Soon)\n\n\
Here you'll be able to view:\n\ Here you'll be able to view:\n\
• Your active bids\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 msg.chat.id
); );
app.bot.send_html_message(target, response, None).await?; app.bot.send_html_message(response, None).await?;
Ok(()) Ok(())
} }

View File

@@ -19,7 +19,7 @@ use crate::{
}, },
handle_error::with_error_handler, handle_error::with_error_handler,
handler_utils::{find_listing_by_id, find_or_create_db_user_from_update}, 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, start_command_data::StartCommandData,
App, BotError, BotResult, Command, DialogueRootState, RootDialogue, 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( async fn handle_view_listing_details(app: App, listing: PersistedListing) -> BotResult {
app: App, send_listing_details_message(app, listing, None).await?;
listing: PersistedListing,
target: MessageTarget,
) -> BotResult {
send_listing_details_message(app, target, listing, None).await?;
Ok(()) Ok(())
} }
@@ -230,9 +226,8 @@ async fn handle_my_listings_command_input(
app: App, app: App,
dialogue: RootDialogue, dialogue: RootDialogue,
user: PersistedUser, user: PersistedUser,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
enter_my_listings(app, dialogue, user, target, None).await?; enter_my_listings(app, dialogue, user, None).await?;
Ok(()) Ok(())
} }
@@ -240,7 +235,6 @@ pub async fn enter_my_listings(
app: App, app: App,
dialogue: RootDialogue, dialogue: RootDialogue,
user: PersistedUser, user: PersistedUser,
target: MessageTarget,
flash: Option<String>, flash: Option<String>,
) -> BotResult { ) -> BotResult {
// Transition to ViewingListings state // Transition to ViewingListings state
@@ -263,7 +257,6 @@ pub async fn enter_my_listings(
if listings.is_empty() { if listings.is_empty() {
app.bot app.bot
.send_html_message( .send_html_message(
target,
"📋 <b>My Listings</b>\n\n\ "📋 <b>My Listings</b>\n\n\
You don't have any listings yet." You don't have any listings yet."
.to_string(), .to_string(),
@@ -284,9 +277,7 @@ pub async fn enter_my_listings(
response = format!("{flash}\n\n{response}"); response = format!("{flash}\n\n{response}");
} }
app.bot app.bot.send_html_message(response, Some(keyboard)).await?;
.send_html_message(target, response, Some(keyboard))
.await?;
Ok(()) Ok(())
} }
@@ -295,12 +286,11 @@ async fn handle_viewing_listings_callback(
dialogue: RootDialogue, dialogue: RootDialogue,
callback_query: CallbackQuery, callback_query: CallbackQuery,
user: PersistedUser, user: PersistedUser,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let data = extract_callback_data(app.bot.deref(), callback_query).await?; let data = extract_callback_data(app.bot.deref(), callback_query).await?;
if let Ok(NavKeyboardButtons::Back) = NavKeyboardButtons::try_from(data.as_str()) { 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 // Check if it's the back to menu button
@@ -308,10 +298,10 @@ async fn handle_viewing_listings_callback(
match button { match button {
MyListingsButtons::SelectListing(listing_id) => { MyListingsButtons::SelectListing(listing_id) => {
let listing = get_listing_for_user(&app.daos, user, listing_id).await?; 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 => { 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, app: App,
dialogue: RootDialogue, dialogue: RootDialogue,
listing: PersistedListing, listing: PersistedListing,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let listing_id = listing.persisted.id; let listing_id = listing.persisted.id;
dialogue dialogue
@@ -342,13 +331,12 @@ async fn enter_show_listing_details(
ManageListingButtons::Delete.to_button(), ManageListingButtons::Delete.to_button(),
]) ])
.append_row([ManageListingButtons::Back.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(()) Ok(())
} }
async fn send_listing_details_message( async fn send_listing_details_message(
app: App, app: App,
target: MessageTarget,
listing: PersistedListing, listing: PersistedListing,
keyboard: Option<InlineKeyboardMarkup>, keyboard: Option<InlineKeyboardMarkup>,
) -> BotResult { ) -> BotResult {
@@ -365,7 +353,7 @@ async fn send_listing_details_message(
response_lines.push(format!("<b>{}:</b> {}", step.field_name, field_value)); response_lines.push(format!("<b>{}:</b> {}", step.field_name, field_value));
} }
app.bot app.bot
.send_html_message(target, response_lines.join("\n"), keyboard) .send_html_message(response_lines.join("\n"), keyboard)
.await?; .await?;
Ok(()) Ok(())
} }
@@ -376,7 +364,6 @@ async fn handle_managing_listing_callback(
callback_query: CallbackQuery, callback_query: CallbackQuery,
user: PersistedUser, user: PersistedUser,
listing_id: ListingDbId, listing_id: ListingDbId,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let from = callback_query.from.clone(); let from = callback_query.from.clone();
let data = extract_callback_data(&app.bot, callback_query).await?; let data = extract_callback_data(&app.bot, callback_query).await?;
@@ -397,21 +384,14 @@ async fn handle_managing_listing_callback(
ManageListingButtons::Edit => { ManageListingButtons::Edit => {
let listing = get_listing_for_user(&app.daos, user, listing_id).await?; let listing = get_listing_for_user(&app.daos, user, listing_id).await?;
let draft = ListingDraft::from_persisted(listing); 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 => { ManageListingButtons::Delete => {
app.daos.listing.delete_listing(listing_id).await?; app.daos.listing.delete_listing(listing_id).await?;
enter_my_listings( enter_my_listings(app, dialogue, user, Some("Listing deleted.".to_string())).await?;
app,
dialogue,
user,
target,
Some("Listing deleted.".to_string()),
)
.await?;
} }
ManageListingButtons::Back => { 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 { if let Some(description) = &listing.base.description {
response_lines.push(description.to_owned()); response_lines.push(description.to_owned());
} }
app.bot app.bot
.with_target(from.into())
.send_html_message( .send_html_message(
from.into(),
response_lines.join("\n\n"), response_lines.join("\n\n"),
Some(keyboard_for_listing(&listing)), Some(keyboard_for_listing(&listing)),
) )

View File

@@ -28,13 +28,12 @@ pub async fn handle_selecting_listing_type_callback(
dialogue: RootDialogue, dialogue: RootDialogue,
user: PersistedUser, user: PersistedUser,
callback_query: CallbackQuery, callback_query: CallbackQuery,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let data = extract_callback_data(&app.bot, callback_query).await?; 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()) { 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 // Parse the listing type from callback data
@@ -62,11 +61,7 @@ pub async fn handle_selecting_listing_type_callback(
); );
app.bot app.bot
.send_html_message( .send_html_message(response, get_keyboard_for_field(ListingField::Title))
target,
response,
get_keyboard_for_field(ListingField::Title),
)
.await?; .await?;
Ok(()) Ok(())
@@ -79,21 +74,20 @@ pub async fn handle_awaiting_draft_field_callback(
field: ListingField, field: ListingField,
draft: ListingDraft, draft: ListingDraft,
callback_query: CallbackQuery, callback_query: CallbackQuery,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let data = extract_callback_data(&app.bot, callback_query).await?; 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()) { if let Ok(button) = NavKeyboardButtons::try_from(data.as_str()) {
match button { match button {
NavKeyboardButtons::Back => { NavKeyboardButtons::Back => {
return enter_select_new_listing_type(app, dialogue, target).await; return enter_select_new_listing_type(app, dialogue).await;
} }
NavKeyboardButtons::Skip => { NavKeyboardButtons::Skip => {
return handle_skip_field(app, dialogue, field, draft, target).await; return handle_skip_field(app, dialogue, field, draft).await;
} }
NavKeyboardButtons::Cancel => { 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 { match field {
ListingField::Slots => { ListingField::Slots => {
let button = SlotsKeyboardButtons::try_from(data.as_str())?; 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 => { ListingField::StartTime => {
let button = StartTimeKeyboardButtons::try_from(data.as_str())?; 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 => { ListingField::EndTime => {
let button = DurationKeyboardButtons::try_from(data.as_str())?; 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 => { ListingField::MinBidIncrement => {
let button = EditMinimumBidIncrementKeyboardButtons::try_from(data.as_str())?; 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 => { ListingField::CurrencyType => {
let button = CurrencyTypeKeyboardButtons::try_from(data.as_str())?; 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}"); error!("Unknown callback data for field {field:?}: {data}");
@@ -132,7 +126,6 @@ async fn handle_skip_field(
dialogue: RootDialogue, dialogue: RootDialogue,
current_field: ListingField, current_field: ListingField,
draft: ListingDraft, draft: ListingDraft,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let field_name = get_field_name(current_field, draft.listing_type()); let field_name = get_field_name(current_field, draft.listing_type());
let next_field = get_next_field(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?; transition_to_field(dialogue, next_field, draft).await?;
app.bot app.bot
.send_html_message(target, response, get_keyboard_for_field(next_field)) .send_html_message(response, get_keyboard_for_field(next_field))
.await?; .await?;
} else { } else {
enter_confirm_save_listing(app, dialogue, target, draft, Some(flash)).await?; enter_confirm_save_listing(app, dialogue, draft, Some(flash)).await?;
} }
Ok(()) Ok(())
} }
@@ -159,7 +152,6 @@ async fn handle_slots_callback(
dialogue: RootDialogue, dialogue: RootDialogue,
mut draft: ListingDraft, mut draft: ListingDraft,
button: SlotsKeyboardButtons, button: SlotsKeyboardButtons,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let num_slots = match button { let num_slots = match button {
SlotsKeyboardButtons::OneSlot => 1, SlotsKeyboardButtons::OneSlot => 1,
@@ -181,11 +173,7 @@ async fn handle_slots_callback(
); );
transition_to_field(dialogue, ListingField::StartTime, draft).await?; transition_to_field(dialogue, ListingField::StartTime, draft).await?;
app.bot app.bot
.send_html_message( .send_html_message(response, get_keyboard_for_field(ListingField::StartTime))
target,
response,
get_keyboard_for_field(ListingField::StartTime),
)
.await?; .await?;
Ok(()) Ok(())
} }
@@ -196,7 +184,6 @@ async fn handle_start_time_callback(
dialogue: RootDialogue, dialogue: RootDialogue,
mut draft: ListingDraft, mut draft: ListingDraft,
button: StartTimeKeyboardButtons, button: StartTimeKeyboardButtons,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let start_time = match button { let start_time = match button {
StartTimeKeyboardButtons::Now => ListingDuration::zero(), StartTimeKeyboardButtons::Now => ListingDuration::zero(),
@@ -216,11 +203,7 @@ async fn handle_start_time_callback(
); );
transition_to_field(dialogue, ListingField::EndTime, draft).await?; transition_to_field(dialogue, ListingField::EndTime, draft).await?;
app.bot app.bot
.send_html_message( .send_html_message(response, get_keyboard_for_field(ListingField::EndTime))
target,
response,
get_keyboard_for_field(ListingField::EndTime),
)
.await?; .await?;
Ok(()) Ok(())
} }
@@ -231,7 +214,6 @@ async fn handle_duration_callback(
dialogue: RootDialogue, dialogue: RootDialogue,
mut draft: ListingDraft, mut draft: ListingDraft,
button: DurationKeyboardButtons, button: DurationKeyboardButtons,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let duration = ListingDuration::days(match button { let duration = ListingDuration::days(match button {
DurationKeyboardButtons::OneDay => 1, DurationKeyboardButtons::OneDay => 1,
@@ -248,7 +230,7 @@ async fn handle_duration_callback(
.map_err(|e| anyhow::anyhow!("Error updating duration: {e:?}"))?; .map_err(|e| anyhow::anyhow!("Error updating duration: {e:?}"))?;
let flash = get_success_message(ListingField::EndTime, draft.listing_type()); 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( async fn handle_starting_bid_amount_callback(
@@ -256,7 +238,6 @@ async fn handle_starting_bid_amount_callback(
dialogue: RootDialogue, dialogue: RootDialogue,
mut draft: ListingDraft, mut draft: ListingDraft,
button: EditMinimumBidIncrementKeyboardButtons, button: EditMinimumBidIncrementKeyboardButtons,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let starting_bid_amount = MoneyAmount::from_str(match button { let starting_bid_amount = MoneyAmount::from_str(match button {
EditMinimumBidIncrementKeyboardButtons::OneDollar => "1.00", 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:?}"))?; .map_err(|e| anyhow::anyhow!("Error updating starting bid amount: {e:?}"))?;
let flash = get_success_message(ListingField::StartingBidAmount, draft.listing_type()); 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( async fn handle_currency_type_callback(
@@ -281,7 +262,6 @@ async fn handle_currency_type_callback(
dialogue: RootDialogue, dialogue: RootDialogue,
mut draft: ListingDraft, mut draft: ListingDraft,
button: CurrencyTypeKeyboardButtons, button: CurrencyTypeKeyboardButtons,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let currency_type = match button { let currency_type = match button {
CurrencyTypeKeyboardButtons::Usd => CurrencyType::Usd, CurrencyTypeKeyboardButtons::Usd => CurrencyType::Usd,
@@ -305,14 +285,14 @@ async fn handle_currency_type_callback(
); );
transition_to_field(dialogue, next_field, draft).await?; transition_to_field(dialogue, next_field, draft).await?;
app.bot app.bot
.send_html_message(target, response, get_keyboard_for_field(next_field)) .send_html_message(response, get_keyboard_for_field(next_field))
.await?; .await?;
Ok(()) Ok(())
} }
/// Cancel the wizard and exit /// Cancel the wizard and exit
pub async fn cancel_wizard(app: App, dialogue: RootDialogue, target: MessageTarget) -> BotResult { pub async fn cancel_wizard(app: App, dialogue: RootDialogue) -> BotResult {
info!("{target:?} cancelled new listing wizard"); info!("User cancelled new listing wizard");
enter_select_new_listing_type(app, dialogue, target).await?; enter_select_new_listing_type(app, dialogue).await?;
Ok(()) Ok(())
} }

View File

@@ -35,20 +35,12 @@ use log::info;
use teloxide::{prelude::*, types::*}; use teloxide::{prelude::*, types::*};
/// Handle the /newlisting command - starts the dialogue /// Handle the /newlisting command - starts the dialogue
pub(super) async fn handle_new_listing_command( pub(super) async fn handle_new_listing_command(app: App, dialogue: RootDialogue) -> BotResult {
app: App, enter_select_new_listing_type(app, dialogue).await?;
dialogue: RootDialogue,
target: MessageTarget,
) -> BotResult {
enter_select_new_listing_type(app, dialogue, target).await?;
Ok(()) Ok(())
} }
pub async fn enter_select_new_listing_type( pub async fn enter_select_new_listing_type(app: App, dialogue: RootDialogue) -> BotResult {
app: App,
dialogue: RootDialogue,
target: MessageTarget,
) -> BotResult {
// Initialize the dialogue to listing type selection state // Initialize the dialogue to listing type selection state
dialogue dialogue
.update(NewListingState::SelectingListingType) .update(NewListingState::SelectingListingType)
@@ -57,7 +49,6 @@ pub async fn enter_select_new_listing_type(
app.bot app.bot
.send_html_message( .send_html_message(
target,
get_listing_type_selection_message().to_string(), get_listing_type_selection_message().to_string(),
Some(get_listing_type_keyboard()), Some(get_listing_type_keyboard()),
) )
@@ -71,10 +62,9 @@ pub async fn handle_awaiting_draft_field_input(
dialogue: RootDialogue, dialogue: RootDialogue,
field: ListingField, field: ListingField,
mut draft: ListingDraft, mut draft: ListingDraft,
target: MessageTarget,
msg: Message, msg: Message,
) -> BotResult { ) -> BotResult {
info!("User {target:?} entered input step: {field:?}"); info!("User entered input step: {field:?}");
// Process the field update // Process the field update
match update_field_on_draft(field, &mut draft, msg.text()) { 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?; transition_to_field(dialogue, next_field, draft).await?;
app.bot app.bot
.send_html_message(target, response, get_keyboard_for_field(next_field)) .send_html_message(response, get_keyboard_for_field(next_field))
.await?; .await?;
} else { } else {
// Final step - go to confirmation // 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(()) Ok(())
} }
@@ -114,10 +104,9 @@ pub async fn handle_editing_field_input(
dialogue: RootDialogue, dialogue: RootDialogue,
field: ListingField, field: ListingField,
mut draft: ListingDraft, mut draft: ListingDraft,
target: MessageTarget,
msg: Message, msg: Message,
) -> BotResult { ) -> BotResult {
info!("User {target:?} editing field {field:?}"); info!("User editing field {field:?}");
// Process the field update // Process the field update
match update_field_on_draft(field, &mut draft, msg.text()) { 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()); 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(()) Ok(())
} }
@@ -145,37 +134,36 @@ pub async fn handle_viewing_draft_callback(
draft: ListingDraft, draft: ListingDraft,
user: PersistedUser, user: PersistedUser,
callback_query: CallbackQuery, callback_query: CallbackQuery,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let data = extract_callback_data(&app.bot, callback_query).await?; let data = extract_callback_data(&app.bot, callback_query).await?;
match ConfirmationKeyboardButtons::try_from(data.as_str())? { match ConfirmationKeyboardButtons::try_from(data.as_str())? {
ConfirmationKeyboardButtons::Create | ConfirmationKeyboardButtons::Save => { 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?; 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 => { ConfirmationKeyboardButtons::Cancel => {
info!("User {target:?} cancelled listing update"); info!("User cancelled listing update");
let response = "🗑️ <b>Changes Discarded</b>\n\n\ let response = "🗑️ <b>Changes Discarded</b>\n\n\
Your changes have been discarded and not saved." Your changes have been discarded and not saved."
.to_string(); .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")?; dialogue.exit().await.context("failed to exit dialogue")?;
} }
ConfirmationKeyboardButtons::Discard => { ConfirmationKeyboardButtons::Discard => {
info!("User {target:?} discarded listing creation"); info!("User discarded listing creation");
let response = "🗑️ <b>Listing Discarded</b>\n\n\ let response = "🗑️ <b>Listing Discarded</b>\n\n\
Your listing has been discarded and not created.\n\ Your listing has been discarded and not created.\n\
You can start a new listing anytime with /newlisting." You can start a new listing anytime with /newlisting."
.to_string(); .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")?; dialogue.exit().await.context("failed to exit dialogue")?;
} }
ConfirmationKeyboardButtons::Edit => { ConfirmationKeyboardButtons::Edit => {
info!("User {target:?} chose to edit listing"); info!("User chose to edit listing");
enter_edit_listing_draft(app, target, draft, dialogue, None).await?; enter_edit_listing_draft(app, draft, dialogue, None).await?;
} }
} }
@@ -188,14 +176,13 @@ pub async fn handle_editing_draft_callback(
draft: ListingDraft, draft: ListingDraft,
dialogue: RootDialogue, dialogue: RootDialogue,
callback_query: CallbackQuery, callback_query: CallbackQuery,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let data = extract_callback_data(&app.bot, callback_query).await?; 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())?; let button = FieldSelectionKeyboardButtons::try_from(data.as_str())?;
if button == FieldSelectionKeyboardButtons::Done { 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 { let field = match button {
@@ -221,9 +208,7 @@ pub async fn handle_editing_draft_callback(
.context("failed to update dialogue")?; .context("failed to update dialogue")?;
let response = format!("Editing {field:?}\n\nPrevious value: {value}"); let response = format!("Editing {field:?}\n\nPrevious value: {value}");
app.bot app.bot.send_html_message(response, Some(keyboard)).await?;
.send_html_message(target, response, Some(keyboard))
.await?;
Ok(()) Ok(())
} }
@@ -235,19 +220,18 @@ pub async fn handle_editing_draft_field_callback(
field: ListingField, field: ListingField,
draft: ListingDraft, draft: ListingDraft,
callback_query: CallbackQuery, callback_query: CallbackQuery,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let data = extract_callback_data(&app.bot, callback_query).await?; 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" { 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(()); return Ok(());
} }
// This callback handler typically receives button presses, not text input // 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 // 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(()) Ok(())
} }
@@ -255,14 +239,12 @@ pub async fn handle_editing_draft_field_callback(
/// Enter the edit listing draft screen /// Enter the edit listing draft screen
pub async fn enter_edit_listing_draft( pub async fn enter_edit_listing_draft(
app: App, app: App,
target: MessageTarget,
draft: ListingDraft, draft: ListingDraft,
dialogue: RootDialogue, dialogue: RootDialogue,
flash_message: Option<String>, flash_message: Option<String>,
) -> BotResult { ) -> BotResult {
display_listing_summary( display_listing_summary(
app, app,
target,
&draft, &draft,
Some(FieldSelectionKeyboardButtons::to_keyboard()), Some(FieldSelectionKeyboardButtons::to_keyboard()),
flash_message, flash_message,

View File

@@ -9,14 +9,13 @@ use crate::commands::new_listing::NewListingState;
use crate::db::ListingType; use crate::db::ListingType;
use crate::App; use crate::App;
use crate::RootDialogue; 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 anyhow::Context;
use teloxide::types::InlineKeyboardMarkup; use teloxide::types::InlineKeyboardMarkup;
/// Display the listing summary with optional flash message and keyboard /// Display the listing summary with optional flash message and keyboard
pub async fn display_listing_summary( pub async fn display_listing_summary(
app: App, app: App,
target: MessageTarget,
draft: &ListingDraft, draft: &ListingDraft,
keyboard: Option<InlineKeyboardMarkup>, keyboard: Option<InlineKeyboardMarkup>,
flash_message: Option<String>, flash_message: Option<String>,
@@ -50,7 +49,7 @@ pub async fn display_listing_summary(
response_lines.push("Edit your listing:".to_string()); response_lines.push("Edit your listing:".to_string());
app.bot app.bot
.send_html_message(target, response_lines.join("\n"), keyboard) .send_html_message(response_lines.join("\n"), keyboard)
.await?; .await?;
Ok(()) Ok(())
@@ -60,7 +59,6 @@ pub async fn display_listing_summary(
pub async fn enter_confirm_save_listing( pub async fn enter_confirm_save_listing(
app: App, app: App,
dialogue: RootDialogue, dialogue: RootDialogue,
target: MessageTarget,
draft: ListingDraft, draft: ListingDraft,
flash: Option<String>, flash: Option<String>,
) -> BotResult { ) -> 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 dialogue
.update(NewListingState::ViewingDraft(draft)) .update(NewListingState::ViewingDraft(draft))
.await .await

View File

@@ -1,8 +1,8 @@
use crate::{message_utils::MessageTarget, App, BotResult}; use crate::{App, BotResult};
use log::info; use log::info;
use teloxide::types::Message; 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\ let response = "⚙️ Settings (Coming Soon)\n\n\
Here you'll be able to configure:\n\ Here you'll be able to configure:\n\
• Notification preferences\n\ • Notification preferences\n\
@@ -18,6 +18,6 @@ pub async fn handle_settings(app: App, msg: Message, target: MessageTarget) -> B
msg.chat.id msg.chat.id
); );
app.bot.send_html_message(target, response, None).await?; app.bot.send_html_message(response, None).await?;
Ok(()) Ok(())
} }

View File

@@ -6,11 +6,8 @@ use teloxide::{
}; };
use crate::{ use crate::{
commands::my_listings::enter_my_listings, commands::my_listings::enter_my_listings, db::user::PersistedUser, keyboard_buttons,
db::user::PersistedUser, message_utils::extract_callback_data, App, BotResult, Command, DialogueRootState, RootDialogue,
keyboard_buttons,
message_utils::{extract_callback_data, MessageTarget},
App, BotResult, Command, DialogueRootState, RootDialogue,
}; };
keyboard_buttons! { keyboard_buttons! {
@@ -37,19 +34,14 @@ fn get_main_menu_message() -> &'static str {
Choose an option below to get started! 🚀" Choose an option below to get started! 🚀"
} }
pub async fn handle_start( pub async fn handle_start(app: App, dialogue: RootDialogue, update: Update) -> BotResult {
app: App,
dialogue: RootDialogue,
target: MessageTarget,
update: Update,
) -> BotResult {
info!("got start message: {update:?}"); info!("got start message: {update:?}");
enter_main_menu(app, dialogue, target).await?; enter_main_menu(app, dialogue).await?;
Ok(()) Ok(())
} }
/// Show the main menu with buttons /// 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 dialogue
.update(DialogueRootState::MainMenu) .update(DialogueRootState::MainMenu)
.await .await
@@ -57,7 +49,6 @@ pub async fn enter_main_menu(app: App, dialogue: RootDialogue, target: MessageTa
app.bot app.bot
.send_html_message( .send_html_message(
target,
get_main_menu_message().to_string(), get_main_menu_message().to_string(),
Some(MainMenuButtons::to_keyboard()), Some(MainMenuButtons::to_keyboard()),
) )
@@ -71,21 +62,19 @@ pub async fn handle_main_menu_callback(
dialogue: RootDialogue, dialogue: RootDialogue,
user: PersistedUser, user: PersistedUser,
callback_query: CallbackQuery, callback_query: CallbackQuery,
target: MessageTarget,
) -> BotResult { ) -> BotResult {
let data = extract_callback_data(&app.bot, callback_query).await?; 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())?; let button = MainMenuButtons::try_from(data.as_str())?;
match button { match button {
MainMenuButtons::MyListings => { MainMenuButtons::MyListings => {
// Call show_listings_for_user directly // 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 => { MainMenuButtons::MyBids => {
app.bot app.bot
.send_html_message( .send_html_message(
target,
"💰 <b>My Bids (Coming Soon)</b>\n\n\ "💰 <b>My Bids (Coming Soon)</b>\n\n\
Here you'll be able to view:\n\ Here you'll be able to view:\n\
• Your active bids\n\ • Your active bids\n\
@@ -101,7 +90,6 @@ pub async fn handle_main_menu_callback(
MainMenuButtons::Settings => { MainMenuButtons::Settings => {
app.bot app.bot
.send_html_message( .send_html_message(
target,
"⚙️ <b>Settings (Coming Soon)</b>\n\n\ "⚙️ <b>Settings (Coming Soon)</b>\n\n\
Here you'll be able to configure:\n\ Here you'll be able to configure:\n\
• Notification preferences\n\ • Notification preferences\n\
@@ -122,7 +110,7 @@ pub async fn handle_main_menu_callback(
Command::descriptions() Command::descriptions()
); );
app.bot app.bot
.send_html_message(target, help_message, Some(MainMenuButtons::to_keyboard())) .send_html_message(help_message, Some(MainMenuButtons::to_keyboard()))
.await?; .await?;
} }
} }

View File

@@ -1,18 +1,13 @@
use crate::{ use crate::{wrap_endpoint, App, BotError, BotResult, WrappedAsyncFn};
message_utils::MessageTarget, wrap_endpoint, App, BotError, BotResult, WrappedAsyncFn,
};
use futures::future::BoxFuture; 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:?}"); log::error!("Error in handler: {error:?}");
match error { match error {
BotError::UserVisibleError(message) => { BotError::UserVisibleError(message) => app.bot.send_html_message(message, None).await?,
app.bot.send_html_message(target, message, None).await?
}
BotError::InternalError(_) => { BotError::InternalError(_) => {
app.bot app.bot
.send_html_message( .send_html_message(
target,
"An internal error occurred. Please try again later.".to_string(), "An internal error occurred. Please try again later.".to_string(),
None, None,
) )
@@ -22,20 +17,16 @@ pub async fn handle_error(app: App, target: MessageTarget, error: BotError) -> B
Ok(()) Ok(())
} }
fn boxed_handle_error( fn boxed_handle_error(app: App, error: BotError) -> BoxFuture<'static, BotResult> {
app: App, Box::pin(handle_error(app, error))
target: MessageTarget,
error: BotError,
) -> BoxFuture<'static, BotResult> {
Box::pin(handle_error(app, target, error))
} }
pub type ErrorHandlerWrapped<FnBase, FnBaseArgs> = WrappedAsyncFn< pub type ErrorHandlerWrapped<FnBase, FnBaseArgs> = WrappedAsyncFn<
FnBase, FnBase,
fn(App, MessageTarget, BotError) -> BoxFuture<'static, BotResult>, fn(App, BotError) -> BoxFuture<'static, BotResult>,
BotError, BotError,
FnBaseArgs, FnBaseArgs,
(App, MessageTarget), (App,),
>; >;
pub fn with_error_handler<FnBase, FnBaseArgs>( pub fn with_error_handler<FnBase, FnBaseArgs>(

View File

@@ -25,7 +25,7 @@ use crate::commands::{
use crate::db::DAOs; use crate::db::DAOs;
use crate::handle_error::with_error_handler; use crate::handle_error::with_error_handler;
use crate::handler_utils::{find_or_create_db_user_from_update, update_into_message_target}; 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 crate::sqlite_storage::SqliteStorage;
use anyhow::Result; use anyhow::Result;
pub use bot_result::*; pub use bot_result::*;
@@ -39,12 +39,12 @@ pub use wrap_endpoint::*;
#[derive(Clone)] #[derive(Clone)]
pub struct App { pub struct App {
pub bot: Arc<BoxMessageSender>, pub bot: Arc<BoxedMessageSender>,
pub daos: DAOs, pub daos: DAOs,
} }
impl App { impl App {
pub fn new(bot: BoxMessageSender, daos: DAOs) -> Self { pub fn new(bot: BoxedMessageSender, daos: DAOs) -> Self {
Self { Self {
bot: Arc::new(bot), bot: Arc::new(bot),
daos, daos,
@@ -100,11 +100,9 @@ type RootDialogue = Dialogue<DialogueRootState, SqliteStorage<Json>>;
pub fn main_handler() -> BotHandler { pub fn main_handler() -> BotHandler {
dptree::entry() dptree::entry()
.map(|app: App| app.daos.clone())
.map(|daos: DAOs| daos.user.clone()) .map(|daos: DAOs| daos.user.clone())
.map(|daos: DAOs| daos.listing.clone()) .map(|daos: DAOs| daos.listing.clone())
.map(|daos: DAOs| daos.bid.clone()) .map(|daos: DAOs| daos.bid.clone())
.filter_map(update_into_message_target)
.filter_map_async(find_or_create_db_user_from_update) .filter_map_async(find_or_create_db_user_from_update)
.branch(my_listings_inline_handler()) .branch(my_listings_inline_handler())
.branch( .branch(
@@ -156,20 +154,21 @@ async fn main() -> Result<()> {
// Set up the bot's command menu // Set up the bot's command menu
setup_bot_commands(&bot).await?; setup_bot_commands(&bot).await?;
let handler_with_deps = dptree::entry()
.filter_map(|bot: Box<Bot>, 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 dialog_storage = SqliteStorage::new(db_pool.clone(), Json).await?;
let daos = DAOs::new(db_pool.clone()); let daos = DAOs::new(db_pool.clone());
let app = App::new(bot.clone(), daos.clone());
// Create dispatcher with dialogue system Dispatcher::builder(bot, handler_with_deps)
Dispatcher::builder(bot, main_handler()) .dependencies(dptree::deps![dialog_storage, daos])
.dependencies(dptree::deps![
dialog_storage,
daos,
app.daos.user.clone(),
app.daos.listing.clone(),
app.daos.bid.clone(),
app
])
.enable_ctrlc_handler() .enable_ctrlc_handler()
.worker_queue_size(1) .worker_queue_size(1)
.build() .build()
@@ -199,7 +198,7 @@ mod tests {
let mut bot = MockMessageSender::new(); let mut bot = MockMessageSender::new();
bot.expect_send_html_message() bot.expect_send_html_message()
.times(1) .times(1)
.returning(|_, _, _| Ok(())); .returning(|_, _| Ok(()));
let deps = create_deps(bot).await; let deps = create_deps(bot).await;
let handler = main_handler(); let handler = main_handler();
dptree::type_check(handler.sig(), &deps, &[]); dptree::type_check(handler.sig(), &deps, &[]);

View File

@@ -14,10 +14,10 @@ use teloxide::{
pub trait MessageSender { pub trait MessageSender {
async fn send_html_message( async fn send_html_message(
&self, &self,
target: MessageTarget,
text: String, text: String,
keyboard: Option<InlineKeyboardMarkup>, keyboard: Option<InlineKeyboardMarkup>,
) -> BotResult; ) -> BotResult;
fn with_target(&self, target: MessageTarget) -> BoxedMessageSender;
async fn answer_inline_query( async fn answer_inline_query(
&self, &self,
inline_query_id: InlineQueryId, inline_query_id: InlineQueryId,
@@ -27,7 +27,7 @@ pub trait MessageSender {
async fn get_me(&self) -> BotResult<Me>; async fn get_me(&self) -> BotResult<Me>;
} }
pub type BoxMessageSender = Box<dyn MessageSender + Send + Sync>; pub type BoxedMessageSender = Box<dyn MessageSender + Send + Sync>;
#[cfg(test)] #[cfg(test)]
mockall::mock! { mockall::mock! {
@@ -37,9 +37,9 @@ mockall::mock! {
} }
#[async_trait] #[async_trait]
impl MessageSender for MessageSender { impl MessageSender for MessageSender {
fn with_target(&self, target: MessageTarget) -> BoxedMessageSender;
async fn send_html_message( async fn send_html_message(
&self, &self,
target: MessageTarget,
text: String, text: String,
keyboard: Option<InlineKeyboardMarkup>, keyboard: Option<InlineKeyboardMarkup>,
) -> BotResult; ) -> 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] #[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( async fn send_html_message(
&self, &self,
target: MessageTarget,
text: String, text: String,
keyboard: Option<InlineKeyboardMarkup>, keyboard: Option<InlineKeyboardMarkup>,
) -> BotResult { ) -> BotResult {
let target = self.1.clone();
if let Some(message_id) = target.message_id { if let Some(message_id) = target.message_id {
log::info!("Editing message in chat: {target:?}"); log::info!("Editing message in chat: {target:?}");
let mut message = self let mut message = self
.0
.edit_message_text(target.chat_id, message_id, &text) .edit_message_text(target.chat_id, message_id, &text)
.parse_mode(ParseMode::Html); .parse_mode(ParseMode::Html);
if let Some(kb) = keyboard { if let Some(kb) = keyboard {
@@ -73,6 +85,7 @@ impl MessageSender for Bot {
} else { } else {
log::info!("Sending message to chat: {target:?}"); log::info!("Sending message to chat: {target:?}");
let mut message = self let mut message = self
.0
.send_message(target.chat_id, &text) .send_message(target.chat_id, &text)
.parse_mode(ParseMode::Html); .parse_mode(ParseMode::Html);
if let Some(kb) = keyboard { if let Some(kb) = keyboard {
@@ -88,21 +101,24 @@ impl MessageSender for Bot {
inline_query_id: InlineQueryId, inline_query_id: InlineQueryId,
results: Vec<InlineQueryResult>, results: Vec<InlineQueryResult>,
) -> BotResult { ) -> BotResult {
teloxide::prelude::Requester::answer_inline_query(self, inline_query_id, results) self.0
.answer_inline_query(inline_query_id, results)
.await .await
.map(|_| ()) .map(|_| ())
.map_err(|err| BotError::InternalError(err.into())) .map_err(|err| BotError::InternalError(err.into()))
} }
async fn answer_callback_query(&self, query_id: CallbackQueryId) -> BotResult { 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 .await
.map(|_| ()) .map(|_| ())
.map_err(|err| BotError::InternalError(err.into())) .map_err(|err| BotError::InternalError(err.into()))
} }
async fn get_me(&self) -> BotResult<Me> { async fn get_me(&self) -> BotResult<Me> {
teloxide::prelude::Requester::get_me(self) self.0
.get_me()
.await .await
.map_err(|err| BotError::InternalError(err.into())) .map_err(|err| BotError::InternalError(err.into()))
} }

View File

@@ -1,4 +1,4 @@
use crate::{message_sender::BoxMessageSender, BotResult}; use crate::{message_sender::BoxedMessageSender, BotResult};
use anyhow::anyhow; use anyhow::anyhow;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use num::One; 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 // Extract callback data and answer callback query
pub async fn extract_callback_data( pub async fn extract_callback_data(
bot: &BoxMessageSender, bot: &BoxedMessageSender,
callback_query: CallbackQuery, callback_query: CallbackQuery,
) -> BotResult<String> { ) -> BotResult<String> {
let data = match callback_query.data { let data = match callback_query.data {

View File

@@ -178,7 +178,8 @@ pub async fn create_deps(mock_bot: MockMessageSender) -> DependencyMap {
let update = create_tele_update(); let update = create_tele_update();
let pool = create_test_pool().await; let pool = create_test_pool().await;
let dialog_storage = SqliteStorage::new(pool.clone(), Json).await.unwrap(); 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_user = create_tele_user("me");
let me = Me { let me = Me {
user: me_user, user: me_user,
@@ -188,7 +189,7 @@ pub async fn create_deps(mock_bot: MockMessageSender) -> DependencyMap {
can_connect_to_business: true, can_connect_to_business: true,
has_main_web_app: true, has_main_web_app: true,
}; };
dptree::deps![update, dialog_storage, app, me] dptree::deps![update, dialog_storage, app, me, daos]
} }
#[cfg(test)] #[cfg(test)]