mostro_client/
util.rs

1use crate::nip33::{dispute_from_tags, order_from_tags};
2
3use anyhow::{Error, Result};
4use base64::engine::general_purpose;
5use base64::Engine;
6use dotenvy::var;
7use log::{error, info};
8use mostro_core::prelude::*;
9use nip44::v2::{decrypt_to_bytes, encrypt_to_bytes, ConversationKey};
10use nostr_sdk::prelude::*;
11use std::thread::sleep;
12use std::time::Duration;
13use std::{fs, path::Path};
14
15async fn send_gift_wrap_dm_internal(
16    client: &Client,
17    sender_keys: &Keys,
18    receiver_pubkey: &PublicKey,
19    message: &str,
20    is_admin: bool,
21) -> Result<()> {
22    let pow: u8 = var("POW")
23        .unwrap_or_else(|_| "0".to_string())
24        .parse()
25        .unwrap_or(0);
26    
27    // Create Message struct for consistency with Mostro protocol
28    let dm_message = Message::new_dm(
29        None,
30        None,
31        Action::SendDm,
32        Some(Payload::TextMessage(message.to_string())),
33    );
34    
35    // Serialize as JSON with the expected format (Message, Option<Signature>)
36    let content = serde_json::to_string(&(dm_message, None::<String>))?;
37    
38    // Create the rumor with JSON content
39    let rumor = EventBuilder::text_note(content)
40        .pow(pow)
41        .build(sender_keys.public_key());
42    
43    // Create gift wrap using sender_keys as the signing key
44    let event = EventBuilder::gift_wrap(sender_keys, receiver_pubkey, rumor, Tags::new()).await?;
45    
46    let sender_type = if is_admin { "admin" } else { "user" };
47    info!("Sending {} gift wrap event to {}", sender_type, receiver_pubkey);
48    client.send_event(&event).await?;
49    
50    Ok(())
51}
52
53pub async fn send_admin_gift_wrap_dm(
54    client: &Client,
55    admin_keys: &Keys,
56    receiver_pubkey: &PublicKey,
57    message: &str,
58) -> Result<()> {
59    send_gift_wrap_dm_internal(client, admin_keys, receiver_pubkey, message, true).await
60}
61
62pub async fn send_gift_wrap_dm(
63    client: &Client,
64    trade_keys: &Keys,
65    receiver_pubkey: &PublicKey,
66    message: &str,
67) -> Result<()> {
68    send_gift_wrap_dm_internal(client, trade_keys, receiver_pubkey, message, false).await
69}
70
71pub async fn send_dm(
72    client: &Client,
73    identity_keys: Option<&Keys>,
74    trade_keys: &Keys,
75    receiver_pubkey: &PublicKey,
76    payload: String,
77    expiration: Option<Timestamp>,
78    to_user: bool,
79) -> Result<()> {
80    let pow: u8 = var("POW").unwrap_or('0'.to_string()).parse().unwrap();
81    let private = var("SECRET")
82        .unwrap_or("false".to_string())
83        .parse::<bool>()
84        .unwrap();
85    let event = if to_user {
86        // Derive conversation key
87        let ck = ConversationKey::derive(trade_keys.secret_key(), receiver_pubkey)?;
88        // Encrypt payload
89        let encrypted_content = encrypt_to_bytes(&ck, payload.as_bytes())?;
90        // Encode with base64
91        let b64decoded_content = general_purpose::STANDARD.encode(encrypted_content);
92        // Compose builder
93        EventBuilder::new(nostr_sdk::Kind::PrivateDirectMessage, b64decoded_content)
94            .pow(pow)
95            .tag(Tag::public_key(*receiver_pubkey))
96            .sign_with_keys(trade_keys)?
97    } else if private {
98        let message = Message::from_json(&payload).unwrap();
99        // We compose the content, when private we don't sign the payload
100        let content: (Message, Option<Signature>) = (message, None);
101        let content = serde_json::to_string(&content).unwrap();
102        // We create the rumor
103        let rumor = EventBuilder::text_note(content)
104            .pow(pow)
105            .build(trade_keys.public_key());
106        let mut tags: Vec<Tag> = Vec::with_capacity(1 + usize::from(expiration.is_some()));
107
108        if let Some(timestamp) = expiration {
109            tags.push(Tag::expiration(timestamp));
110        }
111        let tags = Tags::from_list(tags);
112
113        EventBuilder::gift_wrap(trade_keys, receiver_pubkey, rumor, tags).await?
114    } else {
115        let identity_keys = identity_keys
116            .ok_or_else(|| Error::msg("identity_keys required when to_user is false"))?;
117        // We sign the message
118        let message = Message::from_json(&payload).unwrap();
119        let sig = Message::sign(payload.clone(), trade_keys);
120        // We compose the content
121        let content = serde_json::to_string(&(message, sig)).unwrap();
122        // We create the rumor
123        let rumor = EventBuilder::text_note(content)
124            .pow(pow)
125            .build(trade_keys.public_key());
126        let mut tags: Vec<Tag> = Vec::with_capacity(1 + usize::from(expiration.is_some()));
127
128        if let Some(timestamp) = expiration {
129            tags.push(Tag::expiration(timestamp));
130        }
131        let tags = Tags::from_list(tags);
132
133        EventBuilder::gift_wrap(identity_keys, receiver_pubkey, rumor, tags).await?
134    };
135
136    info!("Sending event: {event:#?}");
137    client.send_event(&event).await?;
138
139    Ok(())
140}
141
142pub async fn connect_nostr() -> Result<Client> {
143    let my_keys = Keys::generate();
144
145    let relays = var("RELAYS").expect("RELAYS is not set");
146    let relays = relays.split(',').collect::<Vec<&str>>();
147    // Create new client
148    let client = Client::new(my_keys);
149    // Add relays
150    for r in relays.into_iter() {
151        client.add_relay(r).await?;
152    }
153    // Connect to relays and keep connection alive
154    client.connect().await;
155
156    Ok(client)
157}
158
159pub async fn send_message_sync(
160    client: &Client,
161    identity_keys: Option<&Keys>,
162    trade_keys: &Keys,
163    receiver_pubkey: PublicKey,
164    message: Message,
165    wait_for_dm: bool,
166    to_user: bool,
167) -> Result<Vec<(Message, u64)>> {
168    let message_json = message
169        .as_json()
170        .map_err(|_| Error::msg("Failed to serialize message"))?;
171    // Send dm to receiver pubkey
172    println!(
173        "SENDING DM with trade keys: {:?}",
174        trade_keys.public_key().to_hex()
175    );
176    send_dm(
177        client,
178        identity_keys,
179        trade_keys,
180        &receiver_pubkey,
181        message_json,
182        None,
183        to_user,
184    )
185    .await?;
186    // FIXME: This is a hack to wait for the DM to be sent
187    sleep(Duration::from_secs(2));
188
189    let dm: Vec<(Message, u64)> = if wait_for_dm {
190        get_direct_messages(client, trade_keys, 15, to_user, None).await
191    } else {
192        Vec::new()
193    };
194
195    Ok(dm)
196}
197
198pub async fn get_direct_messages_from_trade_keys(
199    client: &Client,
200    trade_keys_hex: Vec<String>,
201    since: i64,
202    mostro_pubkey: &PublicKey,
203) -> Vec<(Message, u64, PublicKey)> {
204    if trade_keys_hex.is_empty() {
205        return Vec::new();
206    }
207
208    let fake_since = 2880;
209    let fake_since_time = chrono::Utc::now()
210        .checked_sub_signed(chrono::Duration::minutes(fake_since))
211        .unwrap()
212        .timestamp() as u64;
213    let fake_timestamp = Timestamp::from(fake_since_time);
214    
215    let since_time = chrono::Utc::now()
216        .checked_sub_signed(chrono::Duration::minutes(since))
217        .unwrap()
218        .timestamp() as u64;
219
220    let mut all_direct_messages: Vec<(Message, u64, PublicKey)> = Vec::new();
221    let mut id_set = std::collections::HashSet::<EventId>::new();
222
223    for trade_key_hex in trade_keys_hex {
224        if let Ok(trade_keys) = Keys::parse(&trade_key_hex) {
225            let filters = Filter::new()
226                .kind(nostr_sdk::Kind::GiftWrap)
227                .pubkey(trade_keys.public_key())
228                .since(fake_timestamp);
229
230            info!("Request events with event kind : {:?} for trade key: {}", 
231                  filters.kinds, trade_keys.public_key());
232
233            if let Ok(events) = client.fetch_events(filters, Duration::from_secs(15)).await {
234                for dm in events.iter() {
235                    if !id_set.insert(dm.id) {
236                        continue; // Already processed
237                    }
238                        
239                    let unwrapped_gift = match nip59::extract_rumor(&trade_keys, dm).await {
240                        Ok(u) => u,
241                        Err(_) => {
242                            error!("Error unwrapping gift for trade key: {}", trade_keys.public_key());
243                            continue;
244                        }
245                    };
246                    
247                    // Filter: only process messages NOT from Mostro (user-to-user messages)
248                    if unwrapped_gift.rumor.pubkey == *mostro_pubkey {
249                        continue; // Skip Mostro messages
250                    }
251
252                    if unwrapped_gift.rumor.created_at.as_u64() < since_time {
253                        continue;
254                    }
255
256                    // Parse JSON content (all messages should be JSON now)
257                    let (message, _): (Message, Option<String>) = match serde_json::from_str(&unwrapped_gift.rumor.content) {
258                        Ok(parsed) => parsed,
259                        Err(_) => {
260                            error!("Error parsing JSON content from: {}", unwrapped_gift.rumor.pubkey);
261                            continue;
262                        }
263                    };
264                    
265                    all_direct_messages.push((
266                        message, 
267                        unwrapped_gift.rumor.created_at.as_u64(),
268                        unwrapped_gift.rumor.pubkey
269                    ));
270                }
271            }
272        } else {
273            error!("Failed to parse trade key: {}", trade_key_hex);
274        }
275    }
276
277    all_direct_messages.sort_by(|a, b| a.1.cmp(&b.1));
278    all_direct_messages
279}
280
281pub async fn get_direct_messages(
282    client: &Client,
283    my_key: &Keys,
284    since: i64,
285    from_user: bool,
286    mostro_pubkey: Option<&PublicKey>,
287) -> Vec<(Message, u64)> {
288    // We use a fake timestamp to thwart time-analysis attacks
289    let fake_since = 2880;
290    let fake_since_time = chrono::Utc::now()
291        .checked_sub_signed(chrono::Duration::minutes(fake_since))
292        .unwrap()
293        .timestamp() as u64;
294
295    let fake_timestamp = Timestamp::from(fake_since_time);
296    let filters = if from_user {
297        let since_time = chrono::Utc::now()
298            .checked_sub_signed(chrono::Duration::minutes(since))
299            .unwrap()
300            .timestamp() as u64;
301        let timestamp = Timestamp::from(since_time);
302        Filter::new()
303            .kind(nostr_sdk::Kind::PrivateDirectMessage)
304            .pubkey(my_key.public_key())
305            .since(timestamp)
306    } else {
307        Filter::new()
308            .kind(nostr_sdk::Kind::GiftWrap)
309            .pubkey(my_key.public_key())
310            .since(fake_timestamp)
311    };
312
313    info!("Request events with event kind : {:?} ", filters.kinds);
314
315    let mut direct_messages: Vec<(Message, u64)> = Vec::new();
316
317    if let Ok(mostro_req) = client.fetch_events(filters, Duration::from_secs(15)).await {
318        // Buffer vector for direct messages
319        // Vector for single order id check - maybe multiple relay could send the same order id? Check unique one...
320        let mut id_list = Vec::<EventId>::new();
321
322        for dm in mostro_req.iter() {
323            if !id_list.contains(&dm.id) {
324                id_list.push(dm.id);
325                let (created_at, message) = if from_user {
326                    let ck =
327                        if let Ok(ck) = ConversationKey::derive(my_key.secret_key(), &dm.pubkey) {
328                            ck
329                        } else {
330                            continue;
331                        };
332                    let b64decoded_content =
333                        match general_purpose::STANDARD.decode(dm.content.as_bytes()) {
334                            Ok(b64decoded_content) => b64decoded_content,
335                            Err(_) => {
336                                continue;
337                            }
338                        };
339
340                    let unencrypted_content = decrypt_to_bytes(&ck, &b64decoded_content)
341                        .expect("Failed to decrypt message");
342
343                    let message =
344                        String::from_utf8(unencrypted_content).expect("Found invalid UTF-8");
345                    let message = Message::from_json(&message).expect("Failed on deserializing");
346
347                    (dm.created_at, message)
348                } else {
349                    let unwrapped_gift = match nip59::extract_rumor(my_key, dm).await {
350                        Ok(u) => u,
351                        Err(_) => {
352                            println!("Error unwrapping gift");
353                            continue;
354                        }
355                    };
356                    
357                    // Filter: only process messages from Mostro
358                    if let Some(mostro_pk) = mostro_pubkey {
359                        if unwrapped_gift.rumor.pubkey != *mostro_pk {
360                            continue; // Skip non-Mostro messages
361                        }
362                    }
363                    
364                    let (message, _): (Message, Option<String>) =
365                        serde_json::from_str(&unwrapped_gift.rumor.content).unwrap();
366
367                    (unwrapped_gift.rumor.created_at, message)
368                };
369
370                // Here we discard messages older than the real since parameter
371                let since_time = chrono::Utc::now()
372                    .checked_sub_signed(chrono::Duration::minutes(30))
373                    .unwrap()
374                    .timestamp() as u64;
375                if created_at.as_u64() < since_time {
376                    continue;
377                }
378                direct_messages.push((message, created_at.as_u64()));
379            }
380        }
381        // Return element sorted by second tuple element ( Timestamp )
382        direct_messages.sort_by(|a, b| a.1.cmp(&b.1));
383    }
384
385    direct_messages
386}
387
388pub async fn get_orders_list(
389    pubkey: PublicKey,
390    status: Status,
391    currency: Option<String>,
392    kind: Option<mostro_core::order::Kind>,
393    client: &Client,
394) -> Result<Vec<SmallOrder>> {
395    let since_time = chrono::Utc::now()
396        .checked_sub_signed(chrono::Duration::days(7))
397        .unwrap()
398        .timestamp() as u64;
399
400    let timestamp = Timestamp::from(since_time);
401
402    let filters = Filter::new()
403        .author(pubkey)
404        .limit(50)
405        .since(timestamp)
406        .custom_tag(SingleLetterTag::lowercase(Alphabet::Z), "order".to_string())
407        .kind(nostr_sdk::Kind::Custom(NOSTR_REPLACEABLE_EVENT_KIND));
408
409    info!(
410        "Request to mostro id : {:?} with event kind : {:?} ",
411        filters.authors, filters.kinds
412    );
413
414    // Extracted Orders List
415    let mut complete_events_list = Vec::<SmallOrder>::new();
416    let mut requested_orders_list = Vec::<SmallOrder>::new();
417
418    // Send all requests to relays
419    if let Ok(mostro_req) = client.fetch_events(filters, Duration::from_secs(15)).await {
420        // Scan events to extract all orders
421        for el in mostro_req.iter() {
422            let order = order_from_tags(el.tags.clone());
423
424            if order.is_err() {
425                error!("{order:?}");
426                continue;
427            }
428            let mut order = order?;
429
430            info!("Found Order id : {:?}", order.id.unwrap());
431
432            if order.id.is_none() {
433                info!("Order ID is none");
434                continue;
435            }
436
437            if order.kind.is_none() {
438                info!("Order kind is none");
439                continue;
440            }
441
442            if order.status.is_none() {
443                info!("Order status is none");
444                continue;
445            }
446
447            // Get created at field from Nostr event
448            order.created_at = Some(el.created_at.as_u64() as i64);
449
450            complete_events_list.push(order.clone());
451
452            if order.status.ne(&Some(status)) {
453                continue;
454            }
455
456            if currency.is_some() && order.fiat_code.ne(&currency.clone().unwrap()) {
457                continue;
458            }
459
460            if kind.is_some() && order.kind.ne(&kind) {
461                continue;
462            }
463            // Add just requested orders requested by filtering
464            requested_orders_list.push(order);
465        }
466    }
467
468    // Order all element ( orders ) received to filter - discard disaligned messages
469    // if an order has an older message with the state we received is discarded for the latest one
470    requested_orders_list.retain(|keep| {
471        !complete_events_list
472            .iter()
473            .any(|x| x.id == keep.id && x.created_at > keep.created_at)
474    });
475    // Sort by id to remove duplicates
476    requested_orders_list.sort_by(|a, b| b.id.cmp(&a.id));
477    requested_orders_list.dedup_by(|a, b| a.id == b.id);
478
479    // Finally sort list by creation time
480    requested_orders_list.sort_by(|a, b| b.created_at.cmp(&a.created_at));
481
482    Ok(requested_orders_list)
483}
484
485pub async fn get_disputes_list(pubkey: PublicKey, client: &Client) -> Result<Vec<Dispute>> {
486    let since_time = chrono::Utc::now()
487        .checked_sub_signed(chrono::Duration::days(7))
488        .unwrap()
489        .timestamp() as u64;
490
491    let timestamp = Timestamp::from(since_time);
492
493    let filter = Filter::new()
494        .author(pubkey)
495        .limit(50)
496        .since(timestamp)
497        .custom_tag(
498            SingleLetterTag::lowercase(Alphabet::Z),
499            "dispute".to_string(),
500        )
501        .kind(nostr_sdk::Kind::Custom(NOSTR_REPLACEABLE_EVENT_KIND));
502
503    // Extracted Orders List
504    let mut disputes_list = Vec::<Dispute>::new();
505
506    // Send all requests to relays
507    if let Ok(mostro_req) = client.fetch_events(filter, Duration::from_secs(15)).await {
508        // Scan events to extract all disputes
509        for d in mostro_req.iter() {
510            let dispute = dispute_from_tags(d.tags.clone());
511
512            if dispute.is_err() {
513                error!("{dispute:?}");
514                continue;
515            }
516            let mut dispute = dispute?;
517
518            info!("Found Dispute id : {:?}", dispute.id);
519
520            // Get created at field from Nostr event
521            dispute.created_at = d.created_at.as_u64() as i64;
522            disputes_list.push(dispute);
523        }
524    }
525
526    let buffer_dispute_list = disputes_list.clone();
527    // Order all element ( orders ) received to filter - discard disaligned messages
528    // if an order has an older message with the state we received is discarded for the latest one
529    disputes_list.retain(|keep| {
530        !buffer_dispute_list
531            .iter()
532            .any(|x| x.id == keep.id && x.created_at > keep.created_at)
533    });
534
535    // Sort by id to remove duplicates
536    disputes_list.sort_by(|a, b| b.id.cmp(&a.id));
537    disputes_list.dedup_by(|a, b| a.id == b.id);
538
539    // Finally sort list by creation time
540    disputes_list.sort_by(|a, b| b.created_at.cmp(&a.created_at));
541
542    Ok(disputes_list)
543}
544
545/// Uppercase first letter of a string.
546pub fn uppercase_first(s: &str) -> String {
547    let mut c = s.chars();
548    match c.next() {
549        None => String::new(),
550        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
551    }
552}
553
554pub fn get_mcli_path() -> String {
555    let home_dir = dirs::home_dir().expect("Couldn't get home directory");
556    let mcli_path = format!("{}/.mcli", home_dir.display());
557    if !Path::new(&mcli_path).exists() {
558        fs::create_dir(&mcli_path).expect("Couldn't create mostro-cli directory in HOME");
559        println!("Directory {} created.", mcli_path);
560    }
561
562    mcli_path
563}