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}