mostro_client/util/
messaging.rs

1use anyhow::{Error, Result};
2use base64::engine::general_purpose;
3use base64::Engine;
4use log::info;
5use mostro_core::prelude::*;
6use nip44::v2::{encrypt_to_bytes, ConversationKey};
7use nostr_sdk::prelude::*;
8use std::env::var;
9
10use crate::cli::Context;
11use crate::parser::dms::print_commands_results;
12use crate::parser::parse_dm_events;
13use crate::util::types::MessageType;
14
15/// Helper function to retrieve and validate admin keys from context
16pub fn get_admin_keys(ctx: &Context) -> Result<&Keys> {
17    let admin_keys = ctx.context_keys.as_ref().ok_or_else(|| {
18        anyhow::anyhow!("Admin keys not available. ADMIN_NSEC must be set for admin commands.")
19    })?;
20
21    // Only log admin public key in verbose mode
22    if std::env::var("RUST_LOG").is_ok() {
23        println!("🔑 Admin Keys: {}", admin_keys.public_key);
24    }
25
26    Ok(admin_keys)
27}
28
29pub async fn send_admin_gift_wrap_dm(
30    client: &Client,
31    admin_keys: &Keys,
32    receiver_pubkey: &PublicKey,
33    message: &str,
34) -> Result<()> {
35    send_gift_wrap_dm_internal(client, admin_keys, receiver_pubkey, message, true).await
36}
37
38pub async fn send_gift_wrap_dm(
39    client: &Client,
40    trade_keys: &Keys,
41    receiver_pubkey: &PublicKey,
42    message: &str,
43) -> Result<()> {
44    send_gift_wrap_dm_internal(client, trade_keys, receiver_pubkey, message, false).await
45}
46
47async fn send_gift_wrap_dm_internal(
48    client: &Client,
49    sender_keys: &Keys,
50    receiver_pubkey: &PublicKey,
51    message: &str,
52    is_admin: bool,
53) -> Result<()> {
54    let pow: u8 = var("POW")
55        .unwrap_or_else(|_| "0".to_string())
56        .parse()
57        .unwrap_or(0);
58
59    let dm_message = Message::new_dm(
60        None,
61        None,
62        Action::SendDm,
63        Some(Payload::TextMessage(message.to_string())),
64    );
65
66    let content = serde_json::to_string(&(dm_message, None::<String>))?;
67
68    let rumor = EventBuilder::text_note(content)
69        .pow(pow)
70        .build(sender_keys.public_key());
71
72    let event = EventBuilder::gift_wrap(sender_keys, receiver_pubkey, rumor, Tags::new()).await?;
73
74    let sender_type = if is_admin { "admin" } else { "user" };
75    info!(
76        "Sending {} gift wrap event to {}",
77        sender_type, receiver_pubkey
78    );
79    client.send_event(&event).await?;
80
81    Ok(())
82}
83
84pub async fn wait_for_dm<F>(
85    ctx: &crate::cli::Context,
86    order_trade_keys: Option<&Keys>,
87    sent_message: F,
88) -> anyhow::Result<Events>
89where
90    F: std::future::Future<Output = Result<()>> + Send,
91{
92    let trade_keys = order_trade_keys.unwrap_or(&ctx.trade_keys);
93    let mut notifications = ctx.client.notifications();
94    let opts =
95        SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEventsAfterEOSE(1));
96    let subscription = Filter::new()
97        .pubkey(trade_keys.public_key())
98        .kind(nostr_sdk::Kind::GiftWrap)
99        .limit(0);
100    ctx.client.subscribe(subscription, Some(opts)).await?;
101
102    // Send message here after opening notifications to avoid missing messages.
103    sent_message.await?;
104
105    // Wait for the DM or gift wrap event
106    let event = tokio::time::timeout(super::events::FETCH_EVENTS_TIMEOUT, async move {
107        loop {
108            match notifications.recv().await {
109                Ok(notification) => match notification {
110                    RelayPoolNotification::Event { event, .. } => {
111                        return Ok(*event);
112                    }
113                    _ => continue,
114                },
115                Err(e) => {
116                    return Err(anyhow::anyhow!("Error receiving notification: {:?}", e));
117                }
118            }
119        }
120    })
121    .await?
122    .map_err(|_| anyhow::anyhow!("Timeout waiting for DM or gift wrap event"))?;
123
124    let mut events = Events::default();
125    events.insert(event);
126    Ok(events)
127}
128
129fn determine_message_type(to_user: bool, private: bool) -> MessageType {
130    match (to_user, private) {
131        (true, _) => MessageType::PrivateDirectMessage,
132        (false, true) => MessageType::PrivateGiftWrap,
133        (false, false) => MessageType::SignedGiftWrap,
134    }
135}
136
137fn create_expiration_tags(expiration: Option<Timestamp>) -> Tags {
138    let mut tags: Vec<Tag> = Vec::with_capacity(1 + usize::from(expiration.is_some()));
139    if let Some(timestamp) = expiration {
140        tags.push(Tag::expiration(timestamp));
141    }
142    Tags::from_list(tags)
143}
144
145async fn create_private_dm_event(
146    trade_keys: &Keys,
147    receiver_pubkey: &PublicKey,
148    payload: String,
149    pow: u8,
150) -> Result<nostr_sdk::Event> {
151    let ck = ConversationKey::derive(trade_keys.secret_key(), receiver_pubkey)?;
152    let encrypted_content = encrypt_to_bytes(&ck, payload.as_bytes())?;
153    let b64decoded_content = general_purpose::STANDARD.encode(encrypted_content);
154    Ok(
155        EventBuilder::new(nostr_sdk::Kind::PrivateDirectMessage, b64decoded_content)
156            .pow(pow)
157            .tag(Tag::public_key(*receiver_pubkey))
158            .sign_with_keys(trade_keys)?,
159    )
160}
161
162async fn create_gift_wrap_event(
163    trade_keys: &Keys,
164    identity_keys: Option<&Keys>,
165    receiver_pubkey: &PublicKey,
166    payload: String,
167    pow: u8,
168    expiration: Option<Timestamp>,
169    signed: bool,
170) -> Result<nostr_sdk::Event> {
171    let message = Message::from_json(&payload)
172        .map_err(|e| anyhow::anyhow!("Failed to deserialize message: {e}"))?;
173
174    let content = if signed {
175        let _identity_keys = identity_keys
176            .ok_or_else(|| Error::msg("identity_keys required for signed messages"))?;
177        let sig = Message::sign(payload, trade_keys);
178        serde_json::to_string(&(message, sig))
179            .map_err(|e| anyhow::anyhow!("Failed to serialize message: {e}"))?
180    } else {
181        let content: (Message, Option<Signature>) = (message, None);
182        serde_json::to_string(&content)
183            .map_err(|e| anyhow::anyhow!("Failed to serialize message: {e}"))?
184    };
185
186    let rumor = EventBuilder::text_note(content)
187        .pow(pow)
188        .build(trade_keys.public_key());
189
190    let tags = create_expiration_tags(expiration);
191
192    let signer_keys = if signed {
193        identity_keys.ok_or_else(|| Error::msg("identity_keys required for signed messages"))?
194    } else {
195        trade_keys
196    };
197
198    Ok(EventBuilder::gift_wrap(signer_keys, receiver_pubkey, rumor, tags).await?)
199}
200
201pub async fn send_dm(
202    client: &Client,
203    identity_keys: Option<&Keys>,
204    trade_keys: &Keys,
205    receiver_pubkey: &PublicKey,
206    payload: String,
207    expiration: Option<Timestamp>,
208    to_user: bool,
209) -> Result<()> {
210    let pow: u8 = var("POW")
211        .unwrap_or('0'.to_string())
212        .parse()
213        .map_err(|e| anyhow::anyhow!("Failed to parse POW: {}", e))?;
214    let private = var("SECRET")
215        .unwrap_or("false".to_string())
216        .parse::<bool>()
217        .map_err(|e| anyhow::anyhow!("Failed to parse SECRET: {}", e))?;
218
219    let message_type = determine_message_type(to_user, private);
220
221    let event = match message_type {
222        MessageType::PrivateDirectMessage => {
223            create_private_dm_event(trade_keys, receiver_pubkey, payload, pow).await?
224        }
225        MessageType::PrivateGiftWrap => {
226            create_gift_wrap_event(
227                trade_keys,
228                identity_keys,
229                receiver_pubkey,
230                payload,
231                pow,
232                expiration,
233                false,
234            )
235            .await?
236        }
237        MessageType::SignedGiftWrap => {
238            create_gift_wrap_event(
239                trade_keys,
240                identity_keys,
241                receiver_pubkey,
242                payload,
243                pow,
244                expiration,
245                true,
246            )
247            .await?
248        }
249    };
250
251    client.send_event(&event).await?;
252    Ok(())
253}
254
255pub async fn print_dm_events(
256    recv_event: Events,
257    request_id: u64,
258    ctx: &crate::cli::Context,
259    order_trade_keys: Option<&Keys>,
260) -> Result<()> {
261    let trade_keys = order_trade_keys.unwrap_or(&ctx.trade_keys);
262    let messages = parse_dm_events(recv_event, trade_keys, None).await;
263    if let Some((message, _, _)) = messages.first() {
264        let message = message.get_inner_message_kind();
265        match message.request_id {
266            Some(id) => {
267                if request_id == id {
268                    print_commands_results(message, ctx).await?;
269                }
270            }
271            None if message.action == Action::RateReceived
272                || message.action == Action::NewOrder =>
273            {
274                print_commands_results(message, ctx).await?;
275            }
276            None => {
277                return Err(anyhow::anyhow!(
278                    "Received response with mismatched request_id. Expected: {}, Got: Null",
279                    request_id,
280                ));
281            }
282        }
283    } else {
284        return Err(anyhow::anyhow!("No response received from Mostro"));
285    }
286    Ok(())
287}