mostro_client/cli/
send_msg.rs

1use crate::cli::{Commands, Context};
2use crate::db::{Order, User};
3use crate::parser::common::{
4    create_emoji_field_row, create_field_value_header, create_standard_table,
5};
6use crate::parser::{dms::print_commands_results, parse_dm_events};
7use crate::util::{
8    create_filter, print_dm_events, send_dm, wait_for_dm, ListKind, FETCH_EVENTS_TIMEOUT,
9};
10
11use anyhow::Result;
12use mostro_core::prelude::*;
13use nostr_sdk::prelude::*;
14use uuid::Uuid;
15
16pub async fn execute_send_msg(
17    command: Commands,
18    order_id: Option<Uuid>,
19    ctx: &Context,
20    text: Option<&str>,
21) -> Result<()> {
22    // Map CLI command to action
23    let requested_action = match command {
24        Commands::FiatSent { .. } => Action::FiatSent,
25        Commands::Release { .. } => Action::Release,
26        Commands::Cancel { .. } => Action::Cancel,
27        Commands::Dispute { .. } => Action::Dispute,
28        Commands::AdmCancel { .. } => Action::AdminCancel,
29        Commands::AdmSettle { .. } => Action::AdminSettle,
30        Commands::AdmAddSolver { .. } => Action::AdminAddSolver,
31        _ => {
32            return Err(anyhow::anyhow!("Invalid command for send msg"));
33        }
34    };
35
36    // Printout command information
37    println!("📤 Send Message Command");
38    println!("═══════════════════════════════════════");
39    let mut table = create_standard_table();
40    table.set_header(create_field_value_header());
41    table.add_row(create_emoji_field_row(
42        "🎯 ",
43        "Action",
44        &requested_action.to_string(),
45    ));
46    table.add_row(create_emoji_field_row(
47        "📋 ",
48        "Order ID",
49        &order_id.map_or_else(|| "N/A".to_string(), |id| id.to_string()),
50    ));
51    table.add_row(create_emoji_field_row(
52        "🎯 ",
53        "Target",
54        &ctx.mostro_pubkey.to_string(),
55    ));
56    println!("{table}");
57    println!("💡 Sending command to Mostro...\n");
58
59    // Determine payload
60    let payload = match requested_action {
61        Action::FiatSent | Action::Release => create_next_trade_payload(ctx, &order_id).await?,
62        _ => text.map(|t| Payload::TextMessage(t.to_string())),
63    };
64    // Update last trade index if next trade payload
65    if let Some(Payload::NextTrade(_, trade_index)) = &payload {
66        // Update last trade index
67        match User::get(&ctx.pool).await {
68            Ok(mut user) => {
69                user.set_last_trade_index(*trade_index as i64);
70                if let Err(e) = user.save(&ctx.pool).await {
71                    println!("Failed to update user: {}", e);
72                }
73            }
74            Err(e) => println!("Failed to get user: {}", e),
75        }
76    }
77
78    // Create request id
79    let request_id = Uuid::new_v4().as_u128() as u64;
80
81    // Clone values before they're moved into the message
82    let requested_action_clone = requested_action.clone();
83    let payload_clone = payload.clone();
84
85    // Create and send the message
86    let message = Message::new_order(order_id, Some(request_id), None, requested_action, payload);
87
88    if let Some(order_id) = order_id {
89        let order = Order::get_by_id(&ctx.pool, &order_id.to_string()).await?;
90
91        if let Some(trade_keys_str) = order.trade_keys.clone() {
92            let trade_keys = Keys::parse(&trade_keys_str)?;
93
94            // Send DM
95            let message_json = message
96                .as_json()
97                .map_err(|e| anyhow::anyhow!("Failed to serialize message: {e}"))?;
98
99            // Send DM
100            let sent_message = send_dm(
101                &ctx.client,
102                Some(&ctx.identity_keys),
103                &trade_keys,
104                &ctx.mostro_pubkey,
105                message_json,
106                None,
107                false,
108            );
109
110            // Wait for incoming DM
111            let recv_event = wait_for_dm(ctx, Some(&trade_keys), sent_message).await?;
112
113            // Parse the incoming DM
114            print_dm_events(recv_event, request_id, ctx, Some(&trade_keys)).await?;
115
116            // For release actions, check if we need to wait for additional messages (new order creation)
117            if requested_action_clone == Action::Release {
118                // Check if this was a range order that might generate a new order
119                if let Some(Payload::NextTrade(_, index)) = &payload_clone {
120                    // Get the correct keys for decoding the child order message
121                    let next_trade_key = User::get_trade_keys(&ctx.pool, *index as i64).await?;
122                    // Fake timestamp for giftwraps
123                    let subscription = create_filter(
124                        ListKind::DirectMessagesUser,
125                        next_trade_key.public_key,
126                        None,
127                    )?;
128
129                    // Wait for potential new order message from Mostro
130                    let events = ctx
131                        .client
132                        .fetch_events(subscription, FETCH_EVENTS_TIMEOUT)
133                        .await?;
134                    let messages = parse_dm_events(events, &next_trade_key, Some(&2)).await;
135                    if !messages.is_empty() {
136                        for (message, _, _) in messages {
137                            let message_kind = message.get_inner_message_kind();
138                            if message_kind.action == Action::NewOrder {
139                                print_commands_results(message_kind, ctx).await?;
140                                return Ok(());
141                            }
142                        }
143                    }
144                }
145            }
146        }
147    }
148    Ok(())
149}
150
151async fn create_next_trade_payload(
152    ctx: &Context,
153    order_id: &Option<Uuid>,
154) -> Result<Option<Payload>> {
155    if let Some(order_id) = order_id {
156        let order = Order::get_by_id(&ctx.pool, &order_id.to_string()).await?;
157
158        if let (Some(_), Some(min_amount), Some(max_amount)) =
159            (order.is_mine, order.min_amount, order.max_amount)
160        {
161            if max_amount - order.fiat_amount >= min_amount {
162                let (trade_keys, trade_index) = User::get_next_trade_keys(&ctx.pool).await?;
163                return Ok(Some(Payload::NextTrade(
164                    trade_keys.public_key().to_string(),
165                    trade_index.try_into()?,
166                )));
167            }
168        }
169    }
170    Ok(None)
171}