1use crate::cli::send_msg::execute_send_msg;
2use crate::cli::{Commands, Context};
3use crate::db::{Order, User};
4use crate::parser::{parse_dispute_events, parse_dm_events, parse_orders_events};
5use anyhow::{Error, Result};
6use base64::engine::general_purpose;
7use base64::Engine;
8use dotenvy::var;
9use log::info;
10use mostro_core::prelude::*;
11use nip44::v2::{encrypt_to_bytes, ConversationKey};
12use nostr_sdk::prelude::*;
13use sqlx::SqlitePool;
14use std::time::Duration;
15use std::{fs, path::Path};
16use uuid::Uuid;
17
18const FAKE_SINCE: i64 = 2880;
19const FETCH_EVENTS_TIMEOUT: Duration = Duration::from_secs(15);
20
21#[derive(Clone, Debug)]
22pub enum Event {
23 SmallOrder(SmallOrder),
24 Dispute(Dispute), MessageTuple(Box<(Message, u64)>),
26}
27
28#[derive(Clone, Debug)]
29pub enum ListKind {
30 Orders,
31 Disputes,
32 DirectMessagesUser,
33 DirectMessagesAdmin,
34 PrivateDirectMessagesUser,
35}
36
37async fn send_gift_wrap_dm_internal(
38 client: &Client,
39 sender_keys: &Keys,
40 receiver_pubkey: &PublicKey,
41 message: &str,
42 is_admin: bool,
43) -> Result<()> {
44 let pow: u8 = var("POW")
45 .unwrap_or_else(|_| "0".to_string())
46 .parse()
47 .unwrap_or(0);
48
49 let dm_message = Message::new_dm(
51 None,
52 None,
53 Action::SendDm,
54 Some(Payload::TextMessage(message.to_string())),
55 );
56
57 let content = serde_json::to_string(&(dm_message, None::<String>))?;
59
60 let rumor = EventBuilder::text_note(content)
62 .pow(pow)
63 .build(sender_keys.public_key());
64
65 let event = EventBuilder::gift_wrap(sender_keys, receiver_pubkey, rumor, Tags::new()).await?;
67
68 let sender_type = if is_admin { "admin" } else { "user" };
69 info!(
70 "Sending {} gift wrap event to {}",
71 sender_type, receiver_pubkey
72 );
73 client.send_event(&event).await?;
74
75 Ok(())
76}
77
78pub async fn send_admin_gift_wrap_dm(
79 client: &Client,
80 admin_keys: &Keys,
81 receiver_pubkey: &PublicKey,
82 message: &str,
83) -> Result<()> {
84 send_gift_wrap_dm_internal(client, admin_keys, receiver_pubkey, message, true).await
85}
86
87pub async fn send_gift_wrap_dm(
88 client: &Client,
89 trade_keys: &Keys,
90 receiver_pubkey: &PublicKey,
91 message: &str,
92) -> Result<()> {
93 send_gift_wrap_dm_internal(client, trade_keys, receiver_pubkey, message, false).await
94}
95
96pub async fn save_order(
97 order: SmallOrder,
98 trade_keys: &Keys,
99 request_id: u64,
100 trade_index: Option<i64>,
101 pool: &SqlitePool,
102) -> Result<()> {
103 if let Ok(order) = Order::new(pool, order, trade_keys, Some(request_id as i64)).await {
104 if let Some(order_id) = order.id {
105 println!("Order {} created", order_id);
106 } else {
107 println!("Warning: The newly created order has no ID.");
108 }
109 let trade_index = if let Some(trade_index) = trade_index {
111 trade_index
112 } else {
113 return Err(anyhow::anyhow!(
114 "No trade index found for new order, this should never happen"
115 ));
116 };
117
118 match User::get(pool).await {
120 Ok(mut user) => {
121 user.set_last_trade_index(trade_index);
122 if let Err(e) = user.save(pool).await {
123 println!("Failed to update user: {}", e);
124 }
125 }
126 Err(e) => println!("Failed to get user: {}", e),
127 }
128 }
129 Ok(())
130}
131
132pub async fn wait_for_dm(
134 client: &Client,
135 trade_keys: &Keys,
136 request_id: u64,
137 trade_index: Option<i64>,
138 mut order: Option<Order>,
139 pool: &SqlitePool,
140) -> anyhow::Result<()> {
141 let mut notifications = client.notifications();
142
143 match tokio::time::timeout(FETCH_EVENTS_TIMEOUT, async move {
144 while let Ok(notification) = notifications.recv().await {
145 if let RelayPoolNotification::Event { event, .. } = notification {
146 if event.kind == nostr_sdk::Kind::GiftWrap {
147 let gift = match nip59::extract_rumor(trade_keys, &event).await {
148 Ok(gift) => gift,
149 Err(e) => {
150 println!("Failed to extract rumor: {}", e);
151 continue;
152 }
153 };
154 let (message, _): (Message, Option<String>) = match serde_json::from_str(&gift.rumor.content) {
155 Ok(msg) => msg,
156 Err(e) => {
157 println!("Failed to deserialize message: {}", e);
158 continue;
159 }
160 };
161 let message = message.get_inner_message_kind();
162 if message.request_id == Some(request_id) {
163 match message.action {
164 Action::NewOrder => {
165 if let Some(Payload::Order(order)) = message.payload.as_ref() {
166 if let Err(e) = save_order(order.clone(), trade_keys, request_id, trade_index, pool).await {
167 println!("Failed to save order: {}", e);
168 return Err(());
169 }
170 return Ok(());
171 }
172 }
173 Action::WaitingSellerToPay => {
175 println!("Now we should wait for the seller to pay the invoice");
176 if let Some(mut order) = order.take() {
177 match order
178 .set_status(Status::WaitingPayment.to_string())
179 .save(pool)
180 .await
181 {
182 Ok(_) => println!("Order status updated"),
183 Err(e) => println!("Failed to update order status: {}", e),
184 }
185 return Ok(());
186 }
187 }
188 Action::AddInvoice => {
190 if let Some(Payload::Order(order)) = &message.payload {
191 println!(
192 "Please add a lightning invoice with amount of {}",
193 order.amount
194 );
195 if let Err(e) = save_order(order.clone(), trade_keys, request_id, trade_index, pool).await {
197 println!("Failed to save order: {}", e);
198 return Err(());
199 }
200 return Ok(());
201 }
202 }
203 Action::PayInvoice => {
205 if let Some(Payload::PaymentRequest(order, invoice, _)) = &message.payload {
206 println!(
207 "Mostro sent you this hold invoice for order id: {}",
208 order
209 .as_ref()
210 .and_then(|o| o.id)
211 .map_or("unknown".to_string(), |id| id.to_string())
212 );
213 println!();
214 println!("Pay this invoice to continue --> {}", invoice);
215 println!();
216 if let Some(order) = order {
217 let store_order = order.clone();
218 if let Err(e) = save_order(store_order, trade_keys, request_id, trade_index, pool).await {
220 println!("Failed to save order: {}", e);
221 return Err(());
222 }
223 }
224 return Ok(());
225 }
226 }
227 Action::CantDo => {
228 match message.payload {
229 Some(Payload::CantDo(Some(CantDoReason::OutOfRangeFiatAmount | CantDoReason::OutOfRangeSatsAmount))) => {
230 println!("Error: Amount is outside the allowed range. Please check the order's min/max limits.");
231 return Err(());
232 }
233 Some(Payload::CantDo(Some(CantDoReason::PendingOrderExists))) => {
234 println!("Error: A pending order already exists. Please wait for it to be filled or canceled.");
235 return Err(());
236 }
237 Some(Payload::CantDo(Some(CantDoReason::InvalidTradeIndex))) => {
238 println!("Error: Invalid trade index. Please synchronize the trade index with mostro");
239 return Err(());
240 }
241 _ => {
242 println!("Unknown reason: {:?}", message.payload);
243 return Err(());
244 }
245 }
246 }
247 Action::Canceled => {
249 if let Some(order_id) = &message.id {
250 if Order::get_by_id(pool, &order_id.to_string()).await.is_ok() {
253 if let Err(e) = Order::delete_by_id(pool, &order_id.to_string()).await {
254 println!("Failed to delete order: {}", e);
255 return Err(());
256 }
257 println!("Order {} canceled!", order_id);
259 return Ok(());
260 } else {
261 println!("Order not found: {}", order_id);
262 return Err(());
263 }
264 }
265 }
266 _ => {}
267 }
268 }
269 }
270 }
271 }
272 Ok(())
273 })
274 .await {
275 Ok(result) => match result {
276 Ok(()) => Ok(()),
277 Err(()) => Err(anyhow::anyhow!("Error in timeout closure")),
278 },
279 Err(_) => Err(anyhow::anyhow!("Timeout waiting for DM or gift wrap event"))
280 }
281}
282
283#[derive(Debug, Clone, Copy)]
284enum MessageType {
285 PrivateDirectMessage,
286 PrivateGiftWrap,
287 SignedGiftWrap,
288}
289
290fn determine_message_type(to_user: bool, private: bool) -> MessageType {
291 match (to_user, private) {
292 (true, _) => MessageType::PrivateDirectMessage,
293 (false, true) => MessageType::PrivateGiftWrap,
294 (false, false) => MessageType::SignedGiftWrap,
295 }
296}
297
298fn create_expiration_tags(expiration: Option<Timestamp>) -> Tags {
299 let mut tags: Vec<Tag> = Vec::with_capacity(1 + usize::from(expiration.is_some()));
300
301 if let Some(timestamp) = expiration {
302 tags.push(Tag::expiration(timestamp));
303 }
304
305 Tags::from_list(tags)
306}
307
308async fn create_private_dm_event(
309 trade_keys: &Keys,
310 receiver_pubkey: &PublicKey,
311 payload: String,
312 pow: u8,
313) -> Result<nostr_sdk::Event> {
314 let ck = ConversationKey::derive(trade_keys.secret_key(), receiver_pubkey)?;
316 let encrypted_content = encrypt_to_bytes(&ck, payload.as_bytes())?;
318 let b64decoded_content = general_purpose::STANDARD.encode(encrypted_content);
320 Ok(
322 EventBuilder::new(nostr_sdk::Kind::PrivateDirectMessage, b64decoded_content)
323 .pow(pow)
324 .tag(Tag::public_key(*receiver_pubkey))
325 .sign_with_keys(trade_keys)?,
326 )
327}
328
329async fn create_gift_wrap_event(
330 trade_keys: &Keys,
331 identity_keys: Option<&Keys>,
332 receiver_pubkey: &PublicKey,
333 payload: String,
334 pow: u8,
335 expiration: Option<Timestamp>,
336 signed: bool,
337) -> Result<nostr_sdk::Event> {
338 let message = Message::from_json(&payload)
339 .map_err(|e| anyhow::anyhow!("Failed to deserialize message: {e}"))?;
340
341 let content = if signed {
342 let _identity_keys = identity_keys
343 .ok_or_else(|| Error::msg("identity_keys required for signed messages"))?;
344 let sig = Message::sign(payload, trade_keys);
346 serde_json::to_string(&(message, sig))
347 .map_err(|e| anyhow::anyhow!("Failed to serialize message: {e}"))?
348 } else {
349 let content: (Message, Option<Signature>) = (message, None);
351 serde_json::to_string(&content)
352 .map_err(|e| anyhow::anyhow!("Failed to serialize message: {e}"))?
353 };
354
355 let rumor = EventBuilder::text_note(content)
357 .pow(pow)
358 .build(trade_keys.public_key());
359
360 let tags = create_expiration_tags(expiration);
361
362 let signer_keys = if signed {
363 identity_keys.ok_or_else(|| Error::msg("identity_keys required for signed messages"))?
364 } else {
365 trade_keys
366 };
367
368 Ok(EventBuilder::gift_wrap(signer_keys, receiver_pubkey, rumor, tags).await?)
369}
370
371pub async fn send_dm(
372 client: &Client,
373 identity_keys: Option<&Keys>,
374 trade_keys: &Keys,
375 receiver_pubkey: &PublicKey,
376 payload: String,
377 expiration: Option<Timestamp>,
378 to_user: bool,
379) -> Result<()> {
380 let pow: u8 = var("POW")
381 .unwrap_or('0'.to_string())
382 .parse()
383 .map_err(|e| anyhow::anyhow!("Failed to parse POW: {}", e))?;
384 let private = var("SECRET")
385 .unwrap_or("false".to_string())
386 .parse::<bool>()
387 .map_err(|e| anyhow::anyhow!("Failed to parse SECRET: {}", e))?;
388
389 let message_type = determine_message_type(to_user, private);
390
391 let event = match message_type {
392 MessageType::PrivateDirectMessage => {
393 create_private_dm_event(trade_keys, receiver_pubkey, payload, pow).await?
394 }
395 MessageType::PrivateGiftWrap => {
396 create_gift_wrap_event(
397 trade_keys,
398 identity_keys,
399 receiver_pubkey,
400 payload,
401 pow,
402 expiration,
403 false,
404 )
405 .await?
406 }
407 MessageType::SignedGiftWrap => {
408 create_gift_wrap_event(
409 trade_keys,
410 identity_keys,
411 receiver_pubkey,
412 payload,
413 pow,
414 expiration,
415 true,
416 )
417 .await?
418 }
419 };
420
421 client.send_event(&event).await?;
422 Ok(())
423}
424
425pub async fn connect_nostr() -> Result<Client> {
426 let my_keys = Keys::generate();
427
428 let relays = var("RELAYS").expect("RELAYS is not set");
429 let relays = relays.split(',').collect::<Vec<&str>>();
430 let client = Client::new(my_keys);
432 for r in relays.into_iter() {
434 client.add_relay(r).await?;
435 }
436
437 client.connect().await;
439
440 Ok(client)
441}
442
443pub async fn get_direct_messages_from_trade_keys(
444 client: &Client,
445 trade_keys_hex: Vec<String>,
446 since: i64,
447 _mostro_pubkey: &PublicKey,
448) -> Result<Vec<(Message, u64, PublicKey)>> {
449 if trade_keys_hex.is_empty() {
450 return Ok(Vec::new());
451 }
452
453 let since_time = chrono::Utc::now()
454 .checked_sub_signed(chrono::Duration::minutes(since))
455 .ok_or(anyhow::anyhow!("Failed to get since time"))?
456 .timestamp();
457
458 let mut all_messages: Vec<(Message, u64, PublicKey)> = Vec::new();
460
461 for trade_key_hex in trade_keys_hex {
464 if let Ok(public_key) = PublicKey::from_hex(&trade_key_hex) {
465 let filter =
467 create_filter(ListKind::DirectMessagesUser, public_key, Some(&since_time))?;
468 let events = client.fetch_events(filter, FETCH_EVENTS_TIMEOUT).await?;
469 for event in events {
472 if let Ok(message) = Message::from_json(&event.content) {
473 if event.created_at.as_u64() < since as u64 {
474 continue;
475 }
476 all_messages.push((message, event.created_at.as_u64(), event.pubkey));
477 }
478 }
479 }
480 }
481 Ok(all_messages)
482}
483
484fn create_fake_timestamp() -> Result<Timestamp> {
486 let fake_since_time = chrono::Utc::now()
487 .checked_sub_signed(chrono::Duration::minutes(FAKE_SINCE))
488 .ok_or(anyhow::anyhow!("Failed to get fake since time"))?
489 .timestamp() as u64;
490 Ok(Timestamp::from(fake_since_time))
491}
492
493fn create_seven_days_filter(letter: Alphabet, value: String, pubkey: PublicKey) -> Result<Filter> {
495 let since_time = chrono::Utc::now()
496 .checked_sub_signed(chrono::Duration::days(7))
497 .ok_or(anyhow::anyhow!("Failed to get since days ago"))?
498 .timestamp() as u64;
499
500 let timestamp = Timestamp::from(since_time);
501
502 Ok(Filter::new()
503 .author(pubkey)
504 .limit(50)
505 .since(timestamp)
506 .custom_tag(SingleLetterTag::lowercase(letter), value)
507 .kind(nostr_sdk::Kind::Custom(NOSTR_REPLACEABLE_EVENT_KIND)))
508}
509
510pub fn create_filter(
512 list_kind: ListKind,
513 pubkey: PublicKey,
514 since: Option<&i64>,
515) -> Result<Filter> {
516 match list_kind {
517 ListKind::Orders => create_seven_days_filter(Alphabet::Z, "order".to_string(), pubkey),
518 ListKind::Disputes => create_seven_days_filter(Alphabet::Z, "dispute".to_string(), pubkey),
519 ListKind::DirectMessagesAdmin | ListKind::DirectMessagesUser => {
520 let fake_timestamp = create_fake_timestamp()?;
522
523 Ok(Filter::new()
524 .kind(nostr_sdk::Kind::GiftWrap)
525 .pubkey(pubkey)
526 .since(fake_timestamp))
527 }
528 ListKind::PrivateDirectMessagesUser => {
529 let since = if let Some(mins) = since {
531 chrono::Utc::now()
532 .checked_sub_signed(chrono::Duration::minutes(*mins))
533 .unwrap()
534 .timestamp()
535 } else {
536 chrono::Utc::now()
537 .checked_sub_signed(chrono::Duration::minutes(30))
538 .unwrap()
539 .timestamp()
540 } as u64;
541 Ok(Filter::new()
543 .kind(nostr_sdk::Kind::PrivateDirectMessage)
544 .pubkey(pubkey)
545 .since(Timestamp::from(since)))
546 }
547 }
548}
549
550#[allow(clippy::too_many_arguments)]
551pub async fn fetch_events_list(
552 list_kind: ListKind,
553 status: Option<Status>,
554 currency: Option<String>,
555 kind: Option<mostro_core::order::Kind>,
556 ctx: &Context,
557 _since: Option<&i64>,
558) -> Result<Vec<Event>> {
559 match list_kind {
560 ListKind::Orders => {
561 let filters = create_filter(list_kind, ctx.mostro_pubkey, None)?;
562 let fetched_events = ctx
563 .client
564 .fetch_events(filters, FETCH_EVENTS_TIMEOUT)
565 .await?;
566 let orders = parse_orders_events(fetched_events, currency, status, kind);
567 Ok(orders.into_iter().map(Event::SmallOrder).collect())
568 }
569 ListKind::DirectMessagesAdmin => {
570 let filters = create_filter(list_kind, ctx.mostro_pubkey, None)?;
571 let fetched_events = ctx
572 .client
573 .fetch_events(filters, FETCH_EVENTS_TIMEOUT)
574 .await?;
575 let direct_messages_mostro = parse_dm_events(fetched_events, &ctx.context_keys).await;
576 Ok(direct_messages_mostro
577 .into_iter()
578 .map(|(message, timestamp, _)| Event::MessageTuple(Box::new((message, timestamp))))
579 .collect())
580 }
581 ListKind::PrivateDirectMessagesUser => {
582 let mut direct_messages: Vec<(Message, u64)> = Vec::new();
583 for index in 1..=ctx.trade_index {
584 let trade_key = User::get_trade_keys(&ctx.pool, index).await?;
585 let filter = create_filter(
586 ListKind::PrivateDirectMessagesUser,
587 trade_key.public_key(),
588 None,
589 )?;
590 let fetched_user_messages = ctx
591 .client
592 .fetch_events(filter, FETCH_EVENTS_TIMEOUT)
593 .await?;
594 let direct_messages_for_trade_key =
595 parse_dm_events(fetched_user_messages, &trade_key).await;
596 direct_messages.extend(
597 direct_messages_for_trade_key
598 .into_iter()
599 .map(|(message, timestamp, _)| (message, timestamp)),
600 );
601 }
602 Ok(direct_messages
603 .into_iter()
604 .map(|t| Event::MessageTuple(Box::new(t)))
605 .collect())
606 }
607 ListKind::DirectMessagesUser => {
608 let mut direct_messages: Vec<(Message, u64)> = Vec::new();
609 for index in 1..=ctx.trade_index {
610 let trade_key = User::get_trade_keys(&ctx.pool, index).await?;
611 let filter =
612 create_filter(ListKind::DirectMessagesUser, trade_key.public_key(), None)?;
613 let fetched_user_messages = ctx
614 .client
615 .fetch_events(filter, FETCH_EVENTS_TIMEOUT)
616 .await?;
617 let direct_messages_for_trade_key =
618 parse_dm_events(fetched_user_messages, &trade_key).await;
619 direct_messages.extend(
620 direct_messages_for_trade_key
621 .into_iter()
622 .map(|(message, timestamp, _)| (message, timestamp)),
623 );
624 }
625 Ok(direct_messages
626 .into_iter()
627 .map(|t| Event::MessageTuple(Box::new(t)))
628 .collect())
629 }
630 ListKind::Disputes => {
631 let filters = create_filter(list_kind, ctx.mostro_pubkey, None)?;
632 let fetched_events = ctx
633 .client
634 .fetch_events(filters, FETCH_EVENTS_TIMEOUT)
635 .await?;
636 let disputes = parse_dispute_events(fetched_events);
637 Ok(disputes.into_iter().map(Event::Dispute).collect())
638 }
639 }
640}
641
642pub fn uppercase_first(s: &str) -> String {
644 let mut c = s.chars();
645 match c.next() {
646 None => String::new(),
647 Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
648 }
649}
650
651pub fn get_mcli_path() -> String {
652 let home_dir = dirs::home_dir().expect("Couldn't get home directory");
653 let mcli_path = format!("{}/.mcli", home_dir.display());
654 if !Path::new(&mcli_path).exists() {
655 fs::create_dir(&mcli_path).expect("Couldn't create mostro-cli directory in HOME");
656 println!("Directory {} created.", mcli_path);
657 }
658 mcli_path
659}
660
661pub async fn run_simple_order_msg(command: Commands, order_id: &Uuid, ctx: &Context) -> Result<()> {
662 execute_send_msg(command, Some(*order_id), ctx, None).await
663}
664
665pub async fn admin_send_dm(ctx: &Context, msg: String) -> anyhow::Result<()> {
667 send_dm(
668 &ctx.client,
669 Some(&ctx.context_keys),
670 &ctx.trade_keys,
671 &ctx.mostro_pubkey,
672 msg,
673 None,
674 false,
675 )
676 .await?;
677 Ok(())
678}
679
680#[cfg(test)]
681mod tests {}