mostro_client/parser/
dms.rs

1use std::collections::HashSet;
2
3use anyhow::Result;
4use base64::engine::general_purpose;
5use base64::Engine;
6use chrono::DateTime;
7use mostro_core::prelude::*;
8use nip44::v2::{decrypt_to_bytes, ConversationKey};
9use nostr_sdk::prelude::*;
10
11use crate::{
12    cli::Context,
13    db::{Order, User},
14    util::save_order,
15};
16use sqlx::SqlitePool;
17
18/// Execute logic of command answer
19pub async fn print_commands_results(message: &MessageKind, ctx: &Context) -> Result<()> {
20    // Do the logic for the message response
21    match message.action {
22        Action::NewOrder => {
23            if let Some(Payload::Order(order)) = message.payload.as_ref() {
24                if let Some(req_id) = message.request_id {
25                    if let Err(e) = save_order(
26                        order.clone(),
27                        &ctx.trade_keys,
28                        req_id,
29                        ctx.trade_index,
30                        &ctx.pool,
31                    )
32                    .await
33                    {
34                        return Err(anyhow::anyhow!("Failed to save order: {}", e));
35                    }
36                    Ok(())
37                } else {
38                    Err(anyhow::anyhow!("No request id found in message"))
39                }
40            } else {
41                Err(anyhow::anyhow!("No order found in message"))
42            }
43        }
44        // this is the case where the buyer adds an invoice to a takesell order
45        Action::WaitingSellerToPay => {
46            println!("Now we should wait for the seller to pay the invoice");
47            if let Some(order_id) = &message.id {
48                let mut order = Order::get_by_id(&ctx.pool, &order_id.to_string()).await?;
49                match order
50                    .set_status(Status::WaitingPayment.to_string())
51                    .save(&ctx.pool)
52                    .await
53                {
54                    Ok(_) => println!("Order status updated"),
55                    Err(e) => println!("Failed to update order status: {}", e),
56                }
57                Ok(())
58            } else {
59                Err(anyhow::anyhow!("No order found in message"))
60            }
61        }
62        // this is the case where the buyer adds an invoice to a takesell order
63        Action::AddInvoice => {
64            if let Some(Payload::Order(order)) = &message.payload {
65                println!(
66                    "Please add a lightning invoice with amount of {}",
67                    order.amount
68                );
69                if let Some(req_id) = message.request_id {
70                    // Save the order
71                    if let Err(e) = save_order(
72                        order.clone(),
73                        &ctx.trade_keys,
74                        req_id,
75                        ctx.trade_index,
76                        &ctx.pool,
77                    )
78                    .await
79                    {
80                        return Err(anyhow::anyhow!("Failed to save order: {}", e));
81                    }
82                } else {
83                    return Err(anyhow::anyhow!("No request id found in message"));
84                }
85                Ok(())
86            } else {
87                Err(anyhow::anyhow!("No order found in message"))
88            }
89        }
90        // this is the case where the buyer pays the invoice coming from a takebuy
91        Action::PayInvoice => {
92            if let Some(Payload::PaymentRequest(order, invoice, _)) = &message.payload {
93                println!(
94                    "Mostro sent you this hold invoice for order id: {}",
95                    order
96                        .as_ref()
97                        .and_then(|o| o.id)
98                        .map_or("unknown".to_string(), |id| id.to_string())
99                );
100                println!();
101                println!("Pay this invoice to continue -->  {}", invoice);
102                println!();
103                if let Some(order) = order {
104                    if let Some(req_id) = message.request_id {
105                        let store_order = order.clone();
106                        // Save the order
107                        if let Err(e) = save_order(
108                            store_order,
109                            &ctx.trade_keys,
110                            req_id,
111                            ctx.trade_index,
112                            &ctx.pool,
113                        )
114                        .await
115                        {
116                            println!("Failed to save order: {}", e);
117                            return Err(anyhow::anyhow!("Failed to save order: {}", e));
118                        }
119                    } else {
120                        return Err(anyhow::anyhow!("No request id found in message"));
121                    }
122                } else {
123                    return Err(anyhow::anyhow!("No request id found in message"));
124                }
125            }
126            Ok(())
127        }
128        Action::CantDo => match message.payload {
129            Some(Payload::CantDo(Some(
130                CantDoReason::OutOfRangeFiatAmount | CantDoReason::OutOfRangeSatsAmount,
131            ))) => Err(anyhow::anyhow!(
132                "Amount is outside the allowed range. Please check the order's min/max limits."
133            )),
134            Some(Payload::CantDo(Some(CantDoReason::PendingOrderExists))) => Err(anyhow::anyhow!(
135                "A pending order already exists. Please wait for it to be filled or canceled."
136            )),
137            Some(Payload::CantDo(Some(CantDoReason::InvalidTradeIndex))) => Err(anyhow::anyhow!(
138                "Invalid trade index. Please synchronize the trade index with mostro"
139            )),
140            Some(Payload::CantDo(Some(CantDoReason::InvalidFiatCurrency))) => Err(anyhow::anyhow!(
141                "
142            Invalid currency"
143            )),
144            _ => Err(anyhow::anyhow!("Unknown reason: {:?}", message.payload)),
145        },
146        // this is the case where the user cancels the order
147        Action::Canceled => {
148            if let Some(order_id) = &message.id {
149                // Acquire database connection
150                // Verify order exists before deletion
151                if Order::get_by_id(&ctx.pool, &order_id.to_string())
152                    .await
153                    .is_ok()
154                {
155                    if let Err(e) = Order::delete_by_id(&ctx.pool, &order_id.to_string()).await {
156                        return Err(anyhow::anyhow!("Failed to delete order: {}", e));
157                    }
158                    // Release database connection
159                    println!("Order {} canceled!", order_id);
160                    Ok(())
161                } else {
162                    Err(anyhow::anyhow!("Order not found: {}", order_id))
163                }
164            } else {
165                Err(anyhow::anyhow!("No order id found in message"))
166            }
167        }
168        Action::RateReceived => {
169            println!("Rate received! Thank you!");
170            Ok(())
171        }
172        Action::FiatSentOk => {
173            if let Some(order_id) = &message.id {
174                println!("Fiat sent message for order {:?} received", order_id);
175                println!("Waiting for sats release from seller");
176                Ok(())
177            } else {
178                Err(anyhow::anyhow!("No order id found in message"))
179            }
180        }
181        Action::LastTradeIndex => {
182            if let Some(last_trade_index) = message.trade_index {
183                println!("Last trade index message received: {}", last_trade_index);
184                match User::get(&ctx.pool).await {
185                    Ok(mut user) => {
186                        user.set_last_trade_index(last_trade_index);
187                        if let Err(e) = user.save(&ctx.pool).await {
188                            println!("Failed to update user: {}", e);
189                        }
190                    }
191                    Err(_) => return Err(anyhow::anyhow!("Failed to get user")),
192                }
193                Ok(())
194            } else {
195                Err(anyhow::anyhow!("No trade index found in message"))
196            }
197        }
198        Action::DisputeInitiatedByYou => {
199            if let Some(Payload::Dispute(dispute_id, _)) = &message.payload {
200                println!("Dispute initiated successfully with ID: {}", dispute_id);
201                if let Some(order_id) = &message.id {
202                    let mut order = Order::get_by_id(&ctx.pool, &order_id.to_string()).await?;
203                    // Update order status to disputed if we have the order
204                    match order
205                        .set_status(Status::Dispute.to_string())
206                        .save(&ctx.pool)
207                        .await
208                    {
209                        Ok(_) => println!("Order status updated to Dispute"),
210                        Err(e) => println!("Failed to update order status: {}", e),
211                    }
212                }
213                Ok(())
214            } else {
215                println!("Warning: Dispute initiated but received unexpected payload structure");
216                Ok(())
217            }
218        }
219        Action::HoldInvoicePaymentSettled | Action::Released => {
220            println!("Hold invoice payment settled");
221            Ok(())
222        }
223        _ => Err(anyhow::anyhow!("Unknown action: {:?}", message.action)),
224    }
225}
226
227pub async fn parse_dm_events(
228    events: Events,
229    pubkey: &Keys,
230    since: Option<&i64>,
231) -> Vec<(Message, u64, PublicKey)> {
232    let mut id_set = HashSet::<EventId>::new();
233    let mut direct_messages: Vec<(Message, u64, PublicKey)> = Vec::new();
234
235    for dm in events.iter() {
236        // Skip if already processed
237        if !id_set.insert(dm.id) {
238            continue;
239        }
240
241        let (created_at, message) = match dm.kind {
242            nostr_sdk::Kind::GiftWrap => {
243                let unwrapped_gift = match nip59::extract_rumor(pubkey, dm).await {
244                    Ok(u) => u,
245                    Err(e) => {
246                        eprintln!(
247                            "Warning: Could not decrypt gift wrap (event {}): {}",
248                            dm.id, e
249                        );
250                        continue;
251                    }
252                };
253                let (message, _): (Message, Option<String>) =
254                    match serde_json::from_str(&unwrapped_gift.rumor.content) {
255                        Ok(msg) => msg,
256                        Err(e) => {
257                            eprintln!(
258                                "Warning: Could not parse message content (event {}): {}",
259                                dm.id, e
260                            );
261                            continue;
262                        }
263                    };
264                (unwrapped_gift.rumor.created_at, message)
265            }
266            nostr_sdk::Kind::PrivateDirectMessage => {
267                let ck = if let Ok(ck) = ConversationKey::derive(pubkey.secret_key(), &dm.pubkey) {
268                    ck
269                } else {
270                    continue;
271                };
272                let b64decoded_content =
273                    match general_purpose::STANDARD.decode(dm.content.as_bytes()) {
274                        Ok(b64decoded_content) => b64decoded_content,
275                        Err(_) => {
276                            continue;
277                        }
278                    };
279                let unencrypted_content = match decrypt_to_bytes(&ck, &b64decoded_content) {
280                    Ok(bytes) => bytes,
281                    Err(_) => {
282                        continue;
283                    }
284                };
285                let message_str = match String::from_utf8(unencrypted_content) {
286                    Ok(s) => s,
287                    Err(_) => {
288                        continue;
289                    }
290                };
291                let message = match Message::from_json(&message_str) {
292                    Ok(m) => m,
293                    Err(_) => {
294                        continue;
295                    }
296                };
297                (dm.created_at, message)
298            }
299            _ => continue,
300        };
301        // check if the message is older than the since time if it is, skip it
302        if let Some(since_time) = since {
303            // Calculate since time from now in minutes subtracting the since time
304            let since_time = chrono::Utc::now()
305                .checked_sub_signed(chrono::Duration::minutes(*since_time))
306                .unwrap()
307                .timestamp() as u64;
308
309            if created_at.as_u64() < since_time {
310                continue;
311            }
312        }
313        direct_messages.push((message, created_at.as_u64(), dm.pubkey));
314    }
315    direct_messages.sort_by(|a, b| a.1.cmp(&b.1));
316    direct_messages
317}
318
319pub async fn print_direct_messages(
320    dm: &[(Message, u64, PublicKey)],
321    pool: &SqlitePool,
322) -> Result<()> {
323    if dm.is_empty() {
324        println!();
325        println!("No new messages");
326        println!();
327    } else {
328        for m in dm.iter() {
329            let message = m.0.get_inner_message_kind();
330            let date = match DateTime::from_timestamp(m.1 as i64, 0) {
331                Some(dt) => dt,
332                None => {
333                    println!("Error: Invalid timestamp {}", m.1);
334                    continue;
335                }
336            };
337            if let Some(order_id) = message.id {
338                println!(
339                    "Mostro sent you this message for order id: {} at {}",
340                    order_id, date
341                );
342            }
343            if let Some(payload) = &message.payload {
344                match payload {
345                    Payload::PaymentRequest(_, inv, _) => {
346                        println!();
347                        println!("Pay this invoice to continue --> {}", inv);
348                        println!();
349                    }
350                    Payload::TextMessage(text) => {
351                        println!();
352                        println!("{text}");
353                        println!();
354                    }
355                    Payload::Dispute(id, info) => {
356                        println!("Action: {}", message.action);
357                        println!("Dispute id: {}", id);
358                        if let Some(info) = info {
359                            println!();
360                            println!("Dispute info: {:#?}", info);
361                            println!();
362                        }
363                    }
364                    Payload::CantDo(Some(cant_do_reason)) => {
365                        println!();
366                        println!("Error: {:?}", cant_do_reason);
367                        println!();
368                    }
369                    Payload::Order(new_order) if message.action == Action::NewOrder => {
370                        if let Some(order_id) = new_order.id {
371                            let db_order = Order::get_by_id(pool, &order_id.to_string()).await;
372                            if db_order.is_err() {
373                                if let Some(trade_index) = message.trade_index {
374                                    let trade_keys =
375                                        User::get_trade_keys(pool, trade_index).await?;
376                                    let _ = Order::new(pool, new_order.clone(), &trade_keys, None)
377                                        .await
378                                        .map_err(|e| {
379                                            anyhow::anyhow!("Failed to create DB order: {:?}", e)
380                                        })?;
381                                } else {
382                                    println!("Warning: No trade_index found for new order");
383                                }
384                            }
385                        }
386                        println!();
387                        println!("Order: {:#?}", new_order);
388                        println!();
389                    }
390                    _ => {
391                        println!();
392                        println!("Action: {}", message.action);
393                        println!("Payload: {:#?}", message.payload);
394                        println!();
395                    }
396                }
397            } else {
398                println!();
399                println!("Action: {}", message.action);
400                println!("Payload: {:#?}", message.payload);
401                println!();
402            }
403        }
404    }
405    Ok(())
406}
407
408#[cfg(test)]
409mod tests {}