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
18pub async fn print_commands_results(message: &MessageKind, ctx: &Context) -> Result<()> {
20 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 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 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 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 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 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 Action::Canceled => {
148 if let Some(order_id) = &message.id {
149 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 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 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 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 if let Some(since_time) = since {
303 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 {}