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 comfy_table::presets::UTF8_FULL;
8use comfy_table::*;
9use mostro_core::prelude::*;
10use nip44::v2::{decrypt_to_bytes, ConversationKey};
11use nostr_sdk::prelude::*;
12
13use crate::{
14    cli::Context,
15    db::{Order, User},
16    parser::common::{
17        format_timestamp, print_amount_info, print_fiat_code, print_order_count,
18        print_payment_method, print_premium, print_required_amount, print_section_header,
19        print_success_message, print_trade_index,
20    },
21    util::save_order,
22};
23use serde_json;
24
25/// Handle new order creation display
26fn handle_new_order_display(order: &mostro_core::order::SmallOrder) {
27    print_section_header("šŸ†• New Order Created");
28    if let Some(order_id) = order.id {
29        println!("šŸ“‹ Order ID: {}", order_id);
30    }
31    print_amount_info(order.amount);
32    print_fiat_code(&order.fiat_code);
33    println!("šŸ’µ Fiat Amount: {}", order.fiat_amount);
34    print_premium(order.premium);
35    print_payment_method(&order.payment_method);
36    println!(
37        "šŸ“ˆ Kind: {:?}",
38        order
39            .kind
40            .as_ref()
41            .unwrap_or(&mostro_core::order::Kind::Sell)
42    );
43    println!(
44        "šŸ“Š Status: {:?}",
45        order.status.as_ref().unwrap_or(&Status::Pending)
46    );
47    print_success_message("Order saved successfully!");
48}
49
50/// Handle add invoice display
51fn handle_add_invoice_display(order: &mostro_core::order::SmallOrder) {
52    print_section_header("⚔ Add Lightning Invoice");
53    if let Some(order_id) = order.id {
54        println!("šŸ“‹ Order ID: {}", order_id);
55    }
56    print_required_amount(order.amount);
57    println!("šŸ’” Please add a lightning invoice with the exact amount above");
58    println!();
59}
60
61/// Handle pay invoice display
62fn handle_pay_invoice_display(order: &Option<mostro_core::order::SmallOrder>, invoice: &str) {
63    print_section_header("šŸ’³ Payment Invoice Received");
64    if let Some(order) = order {
65        if let Some(order_id) = order.id {
66            println!("šŸ“‹ Order ID: {}", order_id);
67        }
68        print_amount_info(order.amount);
69        print_fiat_code(&order.fiat_code);
70        println!("šŸ’µ Fiat Amount: {}", order.fiat_amount);
71    }
72    println!();
73    println!("⚔ LIGHTNING INVOICE TO PAY:");
74    println!("─────────────────────────────────────");
75    println!("{}", invoice);
76    println!("─────────────────────────────────────");
77    println!("šŸ’” Pay this invoice to continue the trade");
78    println!();
79}
80
81/// Format payload details for DM table display
82fn format_payload_details(payload: &Payload, action: &Action) -> String {
83    match payload {
84        Payload::TextMessage(t) => format!("āœ‰ļø {}", t),
85        Payload::PaymentRequest(_, inv, _) => {
86            // For invoices, show the full invoice without truncation
87            format!("⚔ Lightning Invoice:\n{}", inv)
88        }
89        Payload::Dispute(id, _) => format!("āš–ļø Dispute ID: {}", id),
90        Payload::Order(o) if *action == Action::NewOrder => format!(
91            "šŸ†• New Order: {} {} sats ({})",
92            o.id.as_ref()
93                .map(|x| x.to_string())
94                .unwrap_or_else(|| "N/A".to_string()),
95            o.amount,
96            o.fiat_code
97        ),
98        Payload::Order(o) => {
99            // Pretty format order details
100            let status_emoji = match o.status.as_ref().unwrap_or(&Status::Pending) {
101                Status::Pending => "ā³",
102                Status::Active => "āœ…",
103                Status::Dispute => "āš–ļø",
104                Status::Canceled => "🚫",
105                Status::CanceledByAdmin => "🚫",
106                Status::CooperativelyCanceled => "šŸ¤",
107                Status::Success => "šŸŽ‰",
108                Status::FiatSent => "šŸ’ø",
109                Status::WaitingPayment => "ā³",
110                Status::WaitingBuyerInvoice => "⚔",
111                Status::SettledByAdmin => "āœ…",
112                Status::CompletedByAdmin => "šŸŽ‰",
113                Status::Expired => "ā°",
114                Status::SettledHoldInvoice => "šŸ’°",
115                Status::InProgress => "šŸ”„",
116            };
117
118            let kind_emoji = match o.kind.as_ref().unwrap_or(&mostro_core::order::Kind::Sell) {
119                mostro_core::order::Kind::Buy => "šŸ“ˆ",
120                mostro_core::order::Kind::Sell => "šŸ“‰",
121            };
122
123            format!(
124                "šŸ“‹ Order: {} {} sats ({})\n{} Status: {:?}\n{} Kind: {:?}",
125                o.id.as_ref()
126                    .map(|x| x.to_string())
127                    .unwrap_or_else(|| "N/A".to_string()),
128                o.amount,
129                o.fiat_code,
130                status_emoji,
131                o.status.as_ref().unwrap_or(&Status::Pending),
132                kind_emoji,
133                o.kind.as_ref().unwrap_or(&mostro_core::order::Kind::Sell)
134            )
135        }
136        Payload::Peer(peer) => {
137            // Pretty format peer information
138            if let Some(reputation) = &peer.reputation {
139                let rating_emoji = if reputation.rating >= 4.0 {
140                    "⭐"
141                } else if reputation.rating >= 3.0 {
142                    "šŸ”¶"
143                } else if reputation.rating >= 2.0 {
144                    "šŸ”ø"
145                } else {
146                    "šŸ”»"
147                };
148
149                format!(
150                    "šŸ‘¤ Peer: {}\n{} Rating: {:.1}/5.0\nšŸ“Š Reviews: {}\nšŸ“… Operating Days: {}",
151                    if peer.pubkey.is_empty() {
152                        "Anonymous"
153                    } else {
154                        &peer.pubkey
155                    },
156                    rating_emoji,
157                    reputation.rating,
158                    reputation.reviews,
159                    reputation.operating_days
160                )
161            } else {
162                format!(
163                    "šŸ‘¤ Peer: {}",
164                    if peer.pubkey.is_empty() {
165                        "Anonymous"
166                    } else {
167                        &peer.pubkey
168                    }
169                )
170            }
171        }
172        _ => {
173            // For other payloads, try to pretty-print as JSON
174            match serde_json::to_string_pretty(payload) {
175                Ok(json) => format!("šŸ“„ Payload:\n{}", json),
176                Err(_) => format!("šŸ“„ Payload: {:?}", payload),
177            }
178        }
179    }
180}
181
182/// Handle orders list display
183fn handle_orders_list_display(orders: &[mostro_core::order::SmallOrder]) {
184    if orders.is_empty() {
185        print_section_header("šŸ“‹ Orders List");
186        println!("šŸ“­ No orders found or unauthorized access");
187    } else {
188        print_section_header("šŸ“‹ Orders List");
189        print_order_count(orders.len());
190        println!();
191        for (i, order) in orders.iter().enumerate() {
192            println!("šŸ“„ Order {}:", i + 1);
193            println!("─────────────────────────────────────");
194            println!(
195                "šŸ†” ID: {}",
196                order
197                    .id
198                    .as_ref()
199                    .map(|id| id.to_string())
200                    .unwrap_or_else(|| "N/A".to_string())
201            );
202            println!(
203                "šŸ“ˆ Kind: {:?}",
204                order
205                    .kind
206                    .as_ref()
207                    .unwrap_or(&mostro_core::order::Kind::Sell)
208            );
209            println!(
210                "šŸ“Š Status: {:?}",
211                order.status.as_ref().unwrap_or(&Status::Pending)
212            );
213            print_amount_info(order.amount);
214            print_fiat_code(&order.fiat_code);
215            if let Some(min) = order.min_amount {
216                if let Some(max) = order.max_amount {
217                    println!("šŸ’µ Fiat Range: {}-{}", min, max);
218                } else {
219                    println!("šŸ’µ Fiat Amount: {}", order.fiat_amount);
220                }
221            } else {
222                println!("šŸ’µ Fiat Amount: {}", order.fiat_amount);
223            }
224            print_payment_method(&order.payment_method);
225            print_premium(order.premium);
226            if let Some(created_at) = order.created_at {
227                if let Some(expires_at) = order.expires_at {
228                    println!("šŸ“… Created: {}", format_timestamp(created_at));
229                    println!("ā° Expires: {}", format_timestamp(expires_at));
230                }
231            }
232            println!();
233        }
234    }
235}
236
237/// Display SolverDisputeInfo in a beautiful table format
238fn display_solver_dispute_info(dispute_info: &mostro_core::dispute::SolverDisputeInfo) -> String {
239    let mut table = Table::new();
240    table
241        .load_preset(UTF8_FULL)
242        .set_content_arrangement(ContentArrangement::Dynamic)
243        .set_width(120)
244        .set_header(vec![
245            Cell::new("Field")
246                .add_attribute(Attribute::Bold)
247                .set_alignment(CellAlignment::Center),
248            Cell::new("Value")
249                .add_attribute(Attribute::Bold)
250                .set_alignment(CellAlignment::Center),
251        ]);
252
253    let mut rows: Vec<Row> = Vec::new();
254
255    // Basic dispute information
256    rows.push(Row::from(vec![
257        Cell::new("šŸ“‹ Order ID:"),
258        Cell::new(dispute_info.id.to_string()),
259    ]));
260    rows.push(Row::from(vec![
261        Cell::new("šŸ“Š Kind"),
262        Cell::new(dispute_info.kind.clone()),
263    ]));
264    rows.push(Row::from(vec![
265        Cell::new("šŸ“ˆ Status"),
266        Cell::new(dispute_info.status.clone()),
267    ]));
268
269    // Financial information
270    rows.push(Row::from(vec![
271        Cell::new("šŸ’° Amount"),
272        Cell::new(format!("{} sats", dispute_info.amount)),
273    ]));
274    rows.push(Row::from(vec![
275        Cell::new("šŸ’µ Fiat Amount"),
276        Cell::new(dispute_info.fiat_amount.to_string()),
277    ]));
278    rows.push(Row::from(vec![
279        Cell::new("šŸ“Š Premium"),
280        Cell::new(format!("{}%", dispute_info.premium)),
281    ]));
282    rows.push(Row::from(vec![
283        Cell::new("šŸ’³ Payment Method"),
284        Cell::new(dispute_info.payment_method.clone()),
285    ]));
286    rows.push(Row::from(vec![
287        Cell::new("šŸ’ø Fee"),
288        Cell::new(format!("{} sats", dispute_info.fee)),
289    ]));
290    rows.push(Row::from(vec![
291        Cell::new("šŸ›£ļø Routing Fee"),
292        Cell::new(format!("{} sats", dispute_info.routing_fee)),
293    ]));
294
295    // Participant information
296    rows.push(Row::from(vec![
297        Cell::new("šŸ‘¤ Initiator"),
298        Cell::new(dispute_info.initiator_pubkey.clone()),
299    ]));
300
301    if let Some(buyer) = &dispute_info.buyer_pubkey {
302        rows.push(Row::from(vec![
303            Cell::new("šŸ›’ Buyer"),
304            Cell::new(buyer.clone()),
305        ]));
306    }
307
308    if let Some(seller) = &dispute_info.seller_pubkey {
309        rows.push(Row::from(vec![
310            Cell::new("šŸŖ Seller"),
311            Cell::new(seller.clone()),
312        ]));
313    }
314
315    // Privacy settings
316    rows.push(Row::from(vec![
317        Cell::new("šŸ”’ Initiator Privacy"),
318        Cell::new(if dispute_info.initiator_full_privacy {
319            "Full Privacy"
320        } else {
321            "Standard"
322        }),
323    ]));
324    rows.push(Row::from(vec![
325        Cell::new("šŸ”’ Counterpart Privacy"),
326        Cell::new(if dispute_info.counterpart_full_privacy {
327            "Full Privacy"
328        } else {
329            "Standard"
330        }),
331    ]));
332
333    // Optional fields
334    if let Some(hash) = &dispute_info.hash {
335        rows.push(Row::from(vec![
336            Cell::new("šŸ” Hash"),
337            Cell::new(hash.clone()),
338        ]));
339    }
340
341    if let Some(preimage) = &dispute_info.preimage {
342        rows.push(Row::from(vec![
343            Cell::new("šŸ”‘ Preimage"),
344            Cell::new(preimage.clone()),
345        ]));
346    }
347
348    if let Some(buyer_invoice) = &dispute_info.buyer_invoice {
349        rows.push(Row::from(vec![
350            Cell::new("⚔ Buyer Invoice"),
351            Cell::new(buyer_invoice.clone()),
352        ]));
353    }
354
355    // Status information
356    rows.push(Row::from(vec![
357        Cell::new("šŸ“Š Previous Status"),
358        Cell::new(dispute_info.order_previous_status.clone()),
359    ]));
360
361    // Timestamps
362    rows.push(Row::from(vec![
363        Cell::new("šŸ“… Created"),
364        Cell::new(format_timestamp(dispute_info.created_at)),
365    ]));
366    rows.push(Row::from(vec![
367        Cell::new("ā° Taken At"),
368        Cell::new(format_timestamp(dispute_info.taken_at)),
369    ]));
370    rows.push(Row::from(vec![
371        Cell::new("⚔ Invoice Held At"),
372        Cell::new(format_timestamp(dispute_info.invoice_held_at)),
373    ]));
374
375    table.add_rows(rows);
376    table.to_string()
377}
378
379/// Execute logic of command answer
380pub async fn print_commands_results(message: &MessageKind, ctx: &Context) -> Result<()> {
381    // Do the logic for the message response
382    match message.action {
383        Action::NewOrder => {
384            if let Some(Payload::Order(order)) = message.payload.as_ref() {
385                if let Some(req_id) = message.request_id {
386                    if let Err(e) = save_order(
387                        order.clone(),
388                        &ctx.trade_keys,
389                        req_id,
390                        ctx.trade_index,
391                        &ctx.pool,
392                    )
393                    .await
394                    {
395                        return Err(anyhow::anyhow!("Failed to save order: {}", e));
396                    }
397
398                    handle_new_order_display(order);
399                    Ok(())
400                } else {
401                    Err(anyhow::anyhow!("No request id found in message"))
402                }
403            } else {
404                Err(anyhow::anyhow!("No order found in message"))
405            }
406        }
407        // this is the case where the buyer adds an invoice to a takesell order
408        Action::WaitingSellerToPay => {
409            println!("ā³ Waiting for Seller Payment");
410            println!("═══════════════════════════════════════");
411            if let Some(order_id) = &message.id {
412                println!("šŸ“‹ Order ID: {}", order_id);
413                let mut order = Order::get_by_id(&ctx.pool, &order_id.to_string()).await?;
414                match order
415                    .set_status(Status::WaitingPayment.to_string())
416                    .save(&ctx.pool)
417                    .await
418                {
419                    Ok(_) => {
420                        println!("šŸ“Š Status: Waiting for Payment");
421                        println!("šŸ’” The seller needs to pay the invoice to continue");
422                        println!("āœ… Order status updated successfully!");
423                    }
424                    Err(e) => println!("āŒ Failed to update order status: {}", e),
425                }
426                Ok(())
427            } else {
428                Err(anyhow::anyhow!("No order found in message"))
429            }
430        }
431        // this is the case where the buyer adds an invoice to a takesell order
432        Action::AddInvoice => {
433            if let Some(Payload::Order(order)) = &message.payload {
434                handle_add_invoice_display(order);
435
436                if let Some(req_id) = message.request_id {
437                    // Save the order
438                    if let Err(e) = save_order(
439                        order.clone(),
440                        &ctx.trade_keys,
441                        req_id,
442                        ctx.trade_index,
443                        &ctx.pool,
444                    )
445                    .await
446                    {
447                        return Err(anyhow::anyhow!("Failed to save order: {}", e));
448                    }
449                    print_success_message("Order saved successfully!");
450                } else {
451                    return Err(anyhow::anyhow!("No request id found in message"));
452                }
453                Ok(())
454            } else {
455                Err(anyhow::anyhow!("No order found in message"))
456            }
457        }
458        // this is the case where the buyer pays the invoice coming from a takebuy
459        Action::PayInvoice => {
460            if let Some(Payload::PaymentRequest(order, invoice, _)) = &message.payload {
461                handle_pay_invoice_display(order, invoice);
462
463                if let Some(order) = order {
464                    if let Some(req_id) = message.request_id {
465                        let store_order = order.clone();
466                        // Save the order
467                        if let Err(e) = save_order(
468                            store_order,
469                            &ctx.trade_keys,
470                            req_id,
471                            ctx.trade_index,
472                            &ctx.pool,
473                        )
474                        .await
475                        {
476                            println!("āŒ Failed to save order: {}", e);
477                            return Err(anyhow::anyhow!("Failed to save order: {}", e));
478                        }
479                        print_success_message("Order saved successfully!");
480                    } else {
481                        return Err(anyhow::anyhow!("No request id found in message"));
482                    }
483                } else {
484                    return Err(anyhow::anyhow!("No request id found in message"));
485                }
486            }
487            Ok(())
488        }
489        Action::CantDo => {
490            println!("āŒ Action Cannot Be Completed");
491            println!("═══════════════════════════════════════");
492            match message.payload {
493                Some(Payload::CantDo(Some(
494                    CantDoReason::OutOfRangeFiatAmount | CantDoReason::OutOfRangeSatsAmount,
495                ))) => {
496                    println!("šŸ’° Amount Error");
497                    println!("šŸ’” The amount is outside the allowed range");
498                    println!("šŸ“Š Please check the order's min/max limits");
499                    Err(anyhow::anyhow!(
500                        "Amount is outside the allowed range. Please check the order's min/max limits."
501                    ))
502                }
503                Some(Payload::CantDo(Some(CantDoReason::PendingOrderExists))) => {
504                    println!("ā³ Pending Order Exists");
505                    println!("šŸ’” A pending order already exists");
506                    println!("šŸ“Š Please wait for it to be filled or canceled");
507                    Err(anyhow::anyhow!(
508                        "A pending order already exists. Please wait for it to be filled or canceled."
509                    ))
510                }
511                Some(Payload::CantDo(Some(CantDoReason::InvalidTradeIndex))) => {
512                    println!("šŸ”¢ Invalid Trade Index");
513                    println!("šŸ’” The trade index is invalid");
514                    println!("šŸ“Š Please synchronize the trade index with mostro");
515                    Err(anyhow::anyhow!(
516                        "Invalid trade index. Please synchronize the trade index with mostro"
517                    ))
518                }
519                Some(Payload::CantDo(Some(CantDoReason::InvalidFiatCurrency))) => {
520                    println!("šŸ’± Invalid Currency");
521                    println!("šŸ’” The fiat currency is not supported");
522                    println!("šŸ“Š Please use a valid currency");
523                    Err(anyhow::anyhow!("Invalid currency"))
524                }
525                _ => {
526                    println!("ā“ Unknown Error");
527                    println!("šŸ’” An unknown error occurred");
528                    Err(anyhow::anyhow!("Unknown reason: {:?}", message.payload))
529                }
530            }
531        }
532        // this is the case where the user cancels the order
533        Action::Canceled => {
534            if let Some(order_id) = &message.id {
535                println!("🚫 Order Canceled");
536                println!("═══════════════════════════════════════");
537                println!("šŸ“‹ Order ID: {}", order_id);
538
539                // Acquire database connection
540                // Verify order exists before deletion
541                if Order::get_by_id(&ctx.pool, &order_id.to_string())
542                    .await
543                    .is_ok()
544                {
545                    if let Err(e) = Order::delete_by_id(&ctx.pool, &order_id.to_string()).await {
546                        println!("āŒ Failed to delete order: {}", e);
547                        return Err(anyhow::anyhow!("Failed to delete order: {}", e));
548                    }
549                    // Release database connection
550                    println!("āœ… Order {} canceled successfully!", order_id);
551                    Ok(())
552                } else {
553                    println!("āŒ Order not found: {}", order_id);
554                    Err(anyhow::anyhow!("Order not found: {}", order_id))
555                }
556            } else {
557                Err(anyhow::anyhow!("No order id found in message"))
558            }
559        }
560        Action::RateReceived => {
561            print_section_header("⭐ Rating Received");
562            println!("šŸ™ Thank you for your rating!");
563            println!("šŸ’” Your feedback helps improve the trading experience");
564            print_success_message("Rating processed successfully!");
565            Ok(())
566        }
567        Action::FiatSentOk => {
568            if let Some(order_id) = &message.id {
569                print_section_header("šŸ’ø Fiat Payment Confirmed");
570                println!("šŸ“‹ Order ID: {}", order_id);
571                println!("āœ… Fiat payment confirmation received");
572                println!("ā³ Waiting for sats release from seller");
573                println!("šŸ’” The seller will now release your Bitcoin");
574                Ok(())
575            } else {
576                Err(anyhow::anyhow!("No order id found in message"))
577            }
578        }
579        Action::LastTradeIndex => {
580            if let Some(last_trade_index) = message.trade_index {
581                print_section_header("šŸ”¢ Last Trade Index Updated");
582                print_trade_index(last_trade_index as u64);
583                match User::get(&ctx.pool).await {
584                    Ok(mut user) => {
585                        user.set_last_trade_index(last_trade_index);
586                        if let Err(e) = user.save(&ctx.pool).await {
587                            println!("āŒ Failed to update user: {}", e);
588                        } else {
589                            print_success_message("Trade index synchronized successfully!");
590                        }
591                    }
592                    Err(_) => {
593                        println!("āš ļø  Warning: Last trade index but received unexpected payload structure: {:#?}", message.payload);
594                    }
595                }
596            } else {
597                println!("āš ļø  Warning: Last trade index but received unexpected payload structure: {:#?}", message.payload);
598            }
599            Ok(())
600        }
601        Action::DisputeInitiatedByYou => {
602            if let Some(Payload::Dispute(dispute_id, _)) = &message.payload {
603                println!("āš–ļø  Dispute Initiated");
604                println!("═══════════════════════════════════════");
605                println!("šŸ†” Dispute ID: {}", dispute_id);
606                if let Some(order_id) = &message.id {
607                    println!("šŸ“‹ Order ID: {}", order_id);
608                    let mut order = Order::get_by_id(&ctx.pool, &order_id.to_string()).await?;
609                    // Update order status to disputed if we have the order
610                    match order
611                        .set_status(Status::Dispute.to_string())
612                        .save(&ctx.pool)
613                        .await
614                    {
615                        Ok(_) => {
616                            println!("šŸ“Š Status: Dispute");
617                            println!("āœ… Order status updated to Dispute");
618                        }
619                        Err(e) => println!("āŒ Failed to update order status: {}", e),
620                    }
621                }
622                println!("šŸ’” A dispute has been initiated for this order");
623                println!("āœ… Dispute created successfully!");
624                Ok(())
625            } else {
626                println!(
627                    "āš ļø  Warning: Dispute initiated but received unexpected payload structure"
628                );
629                Ok(())
630            }
631        }
632        Action::HoldInvoicePaymentAccepted => {
633            if let Some(order_id) = &message.id {
634                println!("šŸŽ‰ Hold Invoice Payment Accepted");
635                println!("═══════════════════════════════════════");
636                println!("šŸ“‹ Order ID: {}", order_id);
637                println!("āœ… Hold invoice payment accepted successfully!");
638                Ok(())
639            } else {
640                println!(
641                    "āš ļø  Warning: Hold invoice payment accepted but received unexpected payload structure"
642                );
643                Ok(())
644            }
645        }
646        Action::HoldInvoicePaymentSettled | Action::Released => {
647            println!("šŸŽ‰ Payment Settled & Released");
648            println!("═══════════════════════════════════════");
649            println!("āœ… Hold invoice payment settled successfully!");
650            println!("šŸ’° Bitcoin has been released to the buyer");
651            println!("šŸŽŠ Trade completed successfully!");
652            Ok(())
653        }
654        Action::Orders => {
655            if let Some(Payload::Orders(orders)) = &message.payload {
656                handle_orders_list_display(orders);
657            } else {
658                println!(
659                    "āš ļø  Warning: Orders list but received unexpected payload structure: {:#?}",
660                    message.payload
661                );
662            }
663            Ok(())
664        }
665        Action::AdminTookDispute => {
666            if let Some(Payload::Dispute(_, Some(dispute_info))) = &message.payload {
667                println!("šŸŽ‰ Dispute Successfully Taken!");
668                println!("═══════════════════════════════════════");
669                println!();
670
671                // Display the dispute info using our dedicated function
672                let dispute_table = display_solver_dispute_info(dispute_info);
673                println!("{dispute_table}");
674                println!();
675                println!("āœ… Dispute taken successfully! You are now the solver for this dispute.");
676                Ok(())
677            } else {
678                // Fallback for debugging - show what we actually received
679                println!("šŸŽ‰ Dispute Successfully Taken!");
680                println!("═══════════════════════════════════════");
681                println!();
682                println!(
683                    "āš ļø  Warning: Expected Dispute payload with SolverDisputeInfo but received:"
684                );
685                println!("šŸ“‹ Payload: {:#?}", message.payload);
686                println!();
687                println!("āœ… Dispute taken successfully! You are now the solver for this dispute.");
688                Ok(())
689            }
690        }
691        Action::RestoreSession => {
692            if let Some(Payload::RestoreData(restore_data)) = &message.payload {
693                println!("šŸ”„ Restore Session Response");
694                println!("═══════════════════════════════════════");
695                println!();
696
697                // Process orders
698                if !restore_data.restore_orders.is_empty() {
699                    println!(
700                        "šŸ“‹ Found {} pending order(s):",
701                        restore_data.restore_orders.len()
702                    );
703                    println!("─────────────────────────────────────");
704                    for (i, order_info) in restore_data.restore_orders.iter().enumerate() {
705                        println!("  {}. Order ID: {}", i + 1, order_info.order_id);
706                        println!("     Trade Index: {}", order_info.trade_index);
707                        println!("     Status: {:?}", order_info.status);
708                        println!();
709                    }
710                } else {
711                    println!("šŸ“‹ No pending orders found.");
712                    println!();
713                }
714
715                // Process disputes
716                if !restore_data.restore_disputes.is_empty() {
717                    println!(
718                        "āš–ļø  Found {} active dispute(s):",
719                        restore_data.restore_disputes.len()
720                    );
721                    println!("─────────────────────────────────────");
722                    for (i, dispute_info) in restore_data.restore_disputes.iter().enumerate() {
723                        println!("  {}. Dispute ID: {}", i + 1, dispute_info.dispute_id);
724                        println!("     Order ID: {}", dispute_info.order_id);
725                        println!("     Trade Index: {}", dispute_info.trade_index);
726                        println!("     Status: {:?}", dispute_info.status);
727                        println!();
728                    }
729                } else {
730                    println!("āš–ļø  No active disputes found.");
731                    println!();
732                }
733
734                println!("āœ… Session restore completed successfully!");
735                Ok(())
736            } else {
737                Err(anyhow::anyhow!("No restore data payload found in message"))
738            }
739        }
740        _ => Err(anyhow::anyhow!("Unknown action: {:?}", message.action)),
741    }
742}
743
744pub async fn parse_dm_events(
745    events: Events,
746    pubkey: &Keys,
747    since: Option<&i64>,
748) -> Vec<(Message, u64, PublicKey)> {
749    let mut id_set = HashSet::<EventId>::new();
750    let mut direct_messages: Vec<(Message, u64, PublicKey)> = Vec::new();
751
752    for dm in events.iter() {
753        // Skip if already processed
754        if !id_set.insert(dm.id) {
755            continue;
756        }
757
758        let (created_at, message, sender) = match dm.kind {
759            nostr_sdk::Kind::GiftWrap => {
760                let unwrapped_gift = match nip59::extract_rumor(pubkey, dm).await {
761                    Ok(u) => u,
762                    Err(e) => {
763                        eprintln!(
764                            "Warning: Could not decrypt gift wrap (event {}): {}",
765                            dm.id, e
766                        );
767                        continue;
768                    }
769                };
770                let (message, _): (Message, Option<String>) =
771                    match serde_json::from_str(&unwrapped_gift.rumor.content) {
772                        Ok(msg) => msg,
773                        Err(e) => {
774                            eprintln!(
775                                "Warning: Could not parse message content (event {}): {}",
776                                dm.id, e
777                            );
778                            continue;
779                        }
780                    };
781
782                (
783                    unwrapped_gift.rumor.created_at,
784                    message,
785                    unwrapped_gift.sender,
786                )
787            }
788            nostr_sdk::Kind::PrivateDirectMessage => {
789                let ck = if let Ok(ck) = ConversationKey::derive(pubkey.secret_key(), &dm.pubkey) {
790                    ck
791                } else {
792                    continue;
793                };
794                let b64decoded_content =
795                    match general_purpose::STANDARD.decode(dm.content.as_bytes()) {
796                        Ok(b64decoded_content) => b64decoded_content,
797                        Err(_) => {
798                            continue;
799                        }
800                    };
801                let unencrypted_content = match decrypt_to_bytes(&ck, &b64decoded_content) {
802                    Ok(bytes) => bytes,
803                    Err(_) => {
804                        continue;
805                    }
806                };
807                let message_str = match String::from_utf8(unencrypted_content) {
808                    Ok(s) => s,
809                    Err(_) => {
810                        continue;
811                    }
812                };
813                let message = match Message::from_json(&message_str) {
814                    Ok(m) => m,
815                    Err(_) => {
816                        continue;
817                    }
818                };
819                (dm.created_at, message, dm.pubkey)
820            }
821            _ => continue,
822        };
823        // check if the message is older than the since time if it is, skip it
824        if let Some(since_time) = since {
825            // Calculate since time from now in minutes subtracting the since time
826            let since_time = chrono::Utc::now()
827                .checked_sub_signed(chrono::Duration::minutes(*since_time))
828                .unwrap()
829                .timestamp() as u64;
830
831            if created_at.as_u64() < since_time {
832                continue;
833            }
834        }
835        direct_messages.push((message, created_at.as_u64(), sender));
836    }
837    direct_messages.sort_by(|a, b| a.1.cmp(&b.1));
838    direct_messages
839}
840
841pub async fn print_direct_messages(
842    dm: &[(Message, u64, PublicKey)],
843    mostro_pubkey: Option<PublicKey>,
844) -> Result<()> {
845    if dm.is_empty() {
846        println!();
847        println!("šŸ“­ No new messages");
848        println!();
849        return Ok(());
850    }
851
852    println!();
853    print_section_header("šŸ“Ø Direct Messages");
854
855    for (i, (message, created_at, sender_pubkey)) in dm.iter().enumerate() {
856        let date = match DateTime::from_timestamp(*created_at as i64, 0) {
857            Some(dt) => dt.format("%Y-%m-%d %H:%M:%S").to_string(),
858            None => "Invalid timestamp".to_string(),
859        };
860
861        let inner = message.get_inner_message_kind();
862        let action_str = inner.action.to_string();
863
864        // Select an icon for the action/payload
865        let action_icon = match inner.action {
866            Action::NewOrder => "šŸ†•",
867            Action::AddInvoice | Action::PayInvoice => "⚔",
868            Action::FiatSent | Action::FiatSentOk => "šŸ’ø",
869            Action::Release | Action::Released => "šŸ”“",
870            Action::Cancel | Action::Canceled => "🚫",
871            Action::Dispute | Action::DisputeInitiatedByYou => "āš–ļø",
872            Action::RateUser | Action::RateReceived => "⭐",
873            Action::Orders => "šŸ“‹",
874            Action::LastTradeIndex => "šŸ”¢",
875            Action::SendDm => "šŸ’¬",
876            _ => "šŸŽÆ",
877        };
878
879        // From label: show 🧌 Mostro if matches provided pubkey
880        let from_label = if let Some(pk) = mostro_pubkey {
881            if *sender_pubkey == pk {
882                format!("🧌 {}", sender_pubkey)
883            } else {
884                sender_pubkey.to_string()
885            }
886        } else {
887            sender_pubkey.to_string()
888        };
889
890        // Print message header
891        println!("šŸ“„ Message {}:", i + 1);
892        println!("─────────────────────────────────────");
893        println!("ā° Time: {}", date);
894        println!("šŸ“Ø From: {}", from_label);
895        println!("šŸŽÆ Action: {} {}", action_icon, action_str);
896
897        // Print details with proper formatting
898        if let Some(payload) = &inner.payload {
899            let details = format_payload_details(payload, &inner.action);
900            println!("šŸ“ Details:");
901            for line in details.lines() {
902                println!("   {}", line);
903            }
904        } else {
905            println!("šŸ“ Details: -");
906        }
907
908        println!();
909    }
910
911    Ok(())
912}
913
914#[cfg(test)]
915mod tests {}