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::dispute::Dispute;
9use mostro_core::message::Message;
10use mostro_core::order::Kind as MostroKind;
11use mostro_core::order::{SmallOrder, Status};
12use mostro_core::NOSTR_REPLACEABLE_EVENT_KIND;
13use nip44::v2::{decrypt_to_bytes, encrypt_to_bytes, ConversationKey};
14use nostr_sdk::prelude::*;
15use std::thread::sleep;
16use std::time::Duration;
17use std::{fs, path::Path};
18
19pub async fn send_dm(
20 client: &Client,
21 identity_keys: Option<&Keys>,
22 trade_keys: &Keys,
23 receiver_pubkey: &PublicKey,
24 payload: String,
25 expiration: Option<Timestamp>,
26 to_user: bool,
27) -> Result<()> {
28 let pow: u8 = var("POW").unwrap_or('0'.to_string()).parse().unwrap();
29 let event = if to_user {
30 let ck = ConversationKey::derive(trade_keys.secret_key(), receiver_pubkey);
32 let encrypted_content = encrypt_to_bytes(&ck, payload.as_bytes())?;
34 let b64decoded_content = general_purpose::STANDARD.encode(encrypted_content);
36 EventBuilder::new(Kind::PrivateDirectMessage, b64decoded_content)
38 .pow(pow)
39 .tag(Tag::public_key(*receiver_pubkey))
40 .sign_with_keys(trade_keys)?
41 } else {
42 let identity_keys = identity_keys
43 .ok_or_else(|| Error::msg("identity_keys required when to_user is false"))?;
44
45 let message = Message::from_json(&payload).unwrap();
46 let sig = message.get_inner_message_kind().sign(trade_keys);
48 let content = (message, sig);
50 let content = serde_json::to_string(&content).unwrap();
51 let rumor = EventBuilder::text_note(content)
53 .pow(pow)
54 .build(trade_keys.public_key());
55 let mut tags: Vec<Tag> = Vec::with_capacity(1 + usize::from(expiration.is_some()));
56
57 if let Some(timestamp) = expiration {
58 tags.push(Tag::expiration(timestamp));
59 }
60 let tags = Tags::new(tags);
61
62 EventBuilder::gift_wrap(identity_keys, receiver_pubkey, rumor, tags).await?
63 };
64
65 info!("Sending event: {event:#?}");
66 client.send_event(event).await?;
67
68 Ok(())
69}
70
71pub async fn connect_nostr() -> Result<Client> {
72 let my_keys = Keys::generate();
73
74 let relays = var("RELAYS").expect("RELAYS is not set");
75 let relays = relays.split(',').collect::<Vec<&str>>();
76 let client = Client::new(my_keys);
78 for r in relays.into_iter() {
80 client.add_relay(r).await?;
81 }
82 client.connect().await;
84
85 Ok(client)
86}
87
88pub async fn send_message_sync(
89 client: &Client,
90 identity_keys: Option<&Keys>,
91 trade_keys: &Keys,
92 receiver_pubkey: PublicKey,
93 message: Message,
94 wait_for_dm: bool,
95 to_user: bool,
96) -> Result<Vec<(Message, u64)>> {
97 let message_json = message.as_json()?;
98 println!(
100 "SENDING DM with trade keys: {:?}",
101 trade_keys.public_key().to_hex()
102 );
103 send_dm(
104 client,
105 identity_keys,
106 trade_keys,
107 &receiver_pubkey,
108 message_json,
109 None,
110 to_user,
111 )
112 .await?;
113 sleep(Duration::from_secs(2));
115
116 let dm: Vec<(Message, u64)> = if wait_for_dm {
117 get_direct_messages(client, trade_keys, 15, to_user).await
118 } else {
119 Vec::new()
120 };
121
122 Ok(dm)
123}
124
125pub async fn get_direct_messages(
126 client: &Client,
127 my_key: &Keys,
128 since: i64,
129 from_user: bool,
130) -> Vec<(Message, u64)> {
131 let fake_since = 2880;
133 let fake_since_time = chrono::Utc::now()
134 .checked_sub_signed(chrono::Duration::minutes(fake_since))
135 .unwrap()
136 .timestamp() as u64;
137
138 let fake_timestamp = Timestamp::from(fake_since_time);
139 let filters = if from_user {
140 let since_time = chrono::Utc::now()
141 .checked_sub_signed(chrono::Duration::minutes(since))
142 .unwrap()
143 .timestamp() as u64;
144 let timestamp = Timestamp::from(since_time);
145 Filter::new()
146 .kind(Kind::PrivateDirectMessage)
147 .pubkey(my_key.public_key())
148 .since(timestamp)
149 } else {
150 Filter::new()
151 .kind(Kind::GiftWrap)
152 .pubkey(my_key.public_key())
153 .since(fake_timestamp)
154 };
155
156 info!("Request events with event kind : {:?} ", filters.kinds);
157
158 let mut direct_messages: Vec<(Message, u64)> = Vec::new();
159
160 if let Ok(mostro_req) = client
161 .fetch_events(vec![filters], Duration::from_secs(15))
162 .await
163 {
164 let mut id_list = Vec::<EventId>::new();
167
168 for dm in mostro_req.iter() {
169 if !id_list.contains(&dm.id) {
170 id_list.push(dm.id);
171 let created_at: Timestamp;
172 let message: Message;
173 if from_user {
174 let ck = ConversationKey::derive(my_key.secret_key(), &dm.pubkey);
175 let b64decoded_content =
176 match general_purpose::STANDARD.decode(dm.content.as_bytes()) {
177 Ok(b64decoded_content) => b64decoded_content,
178 Err(_) => {
179 continue;
180 }
181 };
182 let unencrypted_content = decrypt_to_bytes(&ck, &b64decoded_content).unwrap();
184 let message_str =
185 String::from_utf8(unencrypted_content).expect("Found invalid UTF-8");
186 message = Message::from_json(&message_str).unwrap();
187 created_at = dm.created_at;
188 } else {
189 let unwrapped_gift = match nip59::extract_rumor(my_key, dm).await {
190 Ok(u) => u,
191 Err(_) => {
192 println!("Error unwrapping gift");
193 continue;
194 }
195 };
196 let (rumor_message, sig): (Message, nostr_sdk::secp256k1::schnorr::Signature) =
197 serde_json::from_str(&unwrapped_gift.rumor.content).unwrap();
198 if !rumor_message
199 .get_inner_message_kind()
200 .verify_signature(unwrapped_gift.rumor.pubkey, sig)
201 {
202 println!("Signature verification failed");
203 continue;
204 }
205 message = rumor_message;
206 created_at = unwrapped_gift.rumor.created_at;
207 }
208 let since_time = chrono::Utc::now()
210 .checked_sub_signed(chrono::Duration::minutes(30))
211 .unwrap()
212 .timestamp() as u64;
213 if created_at.as_u64() < since_time {
214 continue;
215 }
216 direct_messages.push((message, created_at.as_u64()));
217 }
218 }
219 direct_messages.sort_by(|a, b| a.1.cmp(&b.1));
221 }
222
223 direct_messages
224}
225
226pub async fn get_orders_list(
227 pubkey: PublicKey,
228 status: Status,
229 currency: Option<String>,
230 kind: Option<MostroKind>,
231 client: &Client,
232) -> Result<Vec<SmallOrder>> {
233 let since_time = chrono::Utc::now()
234 .checked_sub_signed(chrono::Duration::days(7))
235 .unwrap()
236 .timestamp() as u64;
237
238 let timestamp = Timestamp::from(since_time);
239
240 let filters = Filter::new()
241 .author(pubkey)
242 .limit(50)
243 .since(timestamp)
244 .custom_tag(SingleLetterTag::lowercase(Alphabet::Z), vec!["order"])
245 .kind(Kind::Custom(NOSTR_REPLACEABLE_EVENT_KIND));
246
247 info!(
248 "Request to mostro id : {:?} with event kind : {:?} ",
249 filters.authors, filters.kinds
250 );
251
252 let mut complete_events_list = Vec::<SmallOrder>::new();
254 let mut requested_orders_list = Vec::<SmallOrder>::new();
255
256 if let Ok(mostro_req) = client
258 .fetch_events(vec![filters], Duration::from_secs(15))
259 .await
260 {
261 for el in mostro_req.iter() {
263 let order = order_from_tags(el.tags.clone());
264
265 if order.is_err() {
266 error!("{order:?}");
267 continue;
268 }
269 let mut order = order?;
270
271 info!("Found Order id : {:?}", order.id.unwrap());
272
273 if order.id.is_none() {
274 info!("Order ID is none");
275 continue;
276 }
277
278 if order.kind.is_none() {
279 info!("Order kind is none");
280 continue;
281 }
282
283 if order.status.is_none() {
284 info!("Order status is none");
285 continue;
286 }
287
288 order.created_at = Some(el.created_at.as_u64() as i64);
290
291 complete_events_list.push(order.clone());
292
293 if order.status.ne(&Some(status)) {
294 continue;
295 }
296
297 if currency.is_some() && order.fiat_code.ne(¤cy.clone().unwrap()) {
298 continue;
299 }
300
301 if kind.is_some() && order.kind.ne(&kind) {
302 continue;
303 }
304 requested_orders_list.push(order);
306 }
307 }
308
309 requested_orders_list.retain(|keep| {
312 !complete_events_list
313 .iter()
314 .any(|x| x.id == keep.id && x.created_at > keep.created_at)
315 });
316 requested_orders_list.sort_by(|a, b| b.id.cmp(&a.id));
318 requested_orders_list.dedup_by(|a, b| a.id == b.id);
319
320 requested_orders_list.sort_by(|a, b| b.created_at.cmp(&a.created_at));
322
323 Ok(requested_orders_list)
324}
325
326pub async fn get_disputes_list(pubkey: PublicKey, client: &Client) -> Result<Vec<Dispute>> {
327 let since_time = chrono::Utc::now()
328 .checked_sub_signed(chrono::Duration::days(7))
329 .unwrap()
330 .timestamp() as u64;
331
332 let timestamp = Timestamp::from(since_time);
333
334 let filter = Filter::new()
335 .author(pubkey)
336 .limit(50)
337 .since(timestamp)
338 .custom_tag(SingleLetterTag::lowercase(Alphabet::Z), vec!["dispute"])
339 .kind(Kind::Custom(NOSTR_REPLACEABLE_EVENT_KIND));
340
341 let mut disputes_list = Vec::<Dispute>::new();
343
344 if let Ok(mostro_req) = client
346 .fetch_events(vec![filter], Duration::from_secs(15))
347 .await
348 {
349 for d in mostro_req.iter() {
351 let dispute = dispute_from_tags(d.tags.clone());
352
353 if dispute.is_err() {
354 error!("{dispute:?}");
355 continue;
356 }
357 let mut dispute = dispute?;
358
359 info!("Found Dispute id : {:?}", dispute.id);
360
361 dispute.created_at = d.created_at.as_u64() as i64;
363 disputes_list.push(dispute);
364 }
365 }
366
367 let buffer_dispute_list = disputes_list.clone();
368 disputes_list.retain(|keep| {
371 !buffer_dispute_list
372 .iter()
373 .any(|x| x.id == keep.id && x.created_at > keep.created_at)
374 });
375
376 disputes_list.sort_by(|a, b| b.id.cmp(&a.id));
378 disputes_list.dedup_by(|a, b| a.id == b.id);
379
380 disputes_list.sort_by(|a, b| b.created_at.cmp(&a.created_at));
382
383 Ok(disputes_list)
384}
385
386pub fn uppercase_first(s: &str) -> String {
388 let mut c = s.chars();
389 match c.next() {
390 None => String::new(),
391 Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
392 }
393}
394
395pub fn get_mcli_path() -> String {
396 let home_dir = dirs::home_dir().expect("Couldn't get home directory");
397 let mcli_path = format!("{}/.mcli", home_dir.display());
398 if !Path::new(&mcli_path).exists() {
399 fs::create_dir(&mcli_path).expect("Couldn't create mostro-cli directory in HOME");
400 println!("Directory {} created.", mcli_path);
401 }
402
403 mcli_path
404}