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
25fn 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
50fn 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
61fn 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
81fn format_payload_details(payload: &Payload, action: &Action) -> String {
83 match payload {
84 Payload::TextMessage(t) => format!("āļø {}", t),
85 Payload::PaymentRequest(_, inv, _) => {
86 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 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 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 match serde_json::to_string_pretty(payload) {
175 Ok(json) => format!("š Payload:\n{}", json),
176 Err(_) => format!("š Payload: {:?}", payload),
177 }
178 }
179 }
180}
181
182fn 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
237fn 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 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 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 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 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 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 rows.push(Row::from(vec![
357 Cell::new("š Previous Status"),
358 Cell::new(dispute_info.order_previous_status.clone()),
359 ]));
360
361 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
379pub async fn print_commands_results(message: &MessageKind, ctx: &Context) -> Result<()> {
381 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 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 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 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 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 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 Action::Canceled => {
534 if let Some(order_id) = &message.id {
535 println!("š« Order Canceled");
536 println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
537 println!("š Order ID: {}", order_id);
538
539 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 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 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 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 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 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 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 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 if let Some(since_time) = since {
825 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 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 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 println!("š Message {}:", i + 1);
892 println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
893 println!("ā° Time: {}", date);
894 println!("šØ From: {}", from_label);
895 println!("šÆ Action: {} {}", action_icon, action_str);
896
897 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 {}