mostro_client/util/
messaging.rs

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