Skip to content

Commit d836cd0

Browse files
committed
feat: add user-to-user and user-to-admin communication via gift wraps
- Add dmtouser command: send gift wrapped messages between users using trade keys from order IDs - Add admsenddm command: admin can send gift wrapped messages - Add getdmuser command: view gift wrapped messages received on trade keys and admin key - Filter messages by sender: getdm shows only Mostro messages (include admin messages), getdmuser shows only user messages New commands: - mostro-cli dmtouser -p <pubkey> -o <order_id> -m <message> - mostro-cli admsenddm -p <pubkey> -m <message> - mostro-cli getdmuser -s <minutes>
1 parent 7f174b2 commit d836cd0

7 files changed

Lines changed: 346 additions & 5 deletions

File tree

src/cli.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
pub mod add_invoice;
2+
pub mod adm_send_dm;
23
pub mod conversation_key;
4+
pub mod dm_to_user;
35
pub mod get_dm;
6+
pub mod get_dm_user;
47
pub mod list_disputes;
58
pub mod list_orders;
69
pub mod new_order;
@@ -13,8 +16,11 @@ pub mod take_dispute;
1316
pub mod take_sell;
1417

1518
use crate::cli::add_invoice::execute_add_invoice;
19+
use crate::cli::adm_send_dm::execute_adm_send_dm;
1620
use crate::cli::conversation_key::execute_conversation_key;
21+
use crate::cli::dm_to_user::execute_dm_to_user;
1722
use crate::cli::get_dm::execute_get_dm;
23+
use crate::cli::get_dm_user::execute_get_dm_user;
1824
use crate::cli::list_disputes::execute_list_disputes;
1925
use crate::cli::list_orders::execute_list_orders;
2026
use crate::cli::new_order::execute_new_order;
@@ -158,6 +164,13 @@ pub enum Commands {
158164
#[arg(short)]
159165
from_user: bool,
160166
},
167+
/// Get direct messages sent to any trade keys
168+
GetDmUser {
169+
/// Since time of the messages in minutes
170+
#[arg(short, long)]
171+
#[clap(default_value_t = 30)]
172+
since: i64,
173+
},
161174
/// Get the latest direct messages for admin
162175
GetAdminDm {
163176
/// Since time of the messages in minutes
@@ -180,6 +193,18 @@ pub enum Commands {
180193
#[arg(short, long)]
181194
message: String,
182195
},
196+
/// Send gift wrapped direct message to a user
197+
DmToUser {
198+
/// Pubkey of the recipient
199+
#[arg(short, long)]
200+
pubkey: String,
201+
/// Order id to get ephemeral keys
202+
#[arg(short, long)]
203+
order_id: Uuid,
204+
/// Message to send
205+
#[arg(short, long)]
206+
message: String,
207+
},
183208
/// Send fiat sent message to confirm payment to other user
184209
FiatSent {
185210
/// Order id
@@ -241,6 +266,15 @@ pub enum Commands {
241266
#[arg(short, long)]
242267
dispute_id: Uuid,
243268
},
269+
/// Send gift wrapped direct message to a user (only admin)
270+
AdmSendDm {
271+
/// Pubkey of the recipient
272+
#[arg(short, long)]
273+
pubkey: String,
274+
/// Message to send
275+
#[arg(short, long)]
276+
message: String,
277+
},
244278
/// Get the conversation key for direct messaging with a user
245279
ConversationKey {
246280
/// Pubkey of the counterpart
@@ -373,10 +407,13 @@ pub async fn run() -> Result<()> {
373407
execute_add_invoice(order_id, invoice, &identity_keys, mostro_key, &client).await?
374408
}
375409
Commands::GetDm { since, from_user } => {
376-
execute_get_dm(since, trade_index, &client, *from_user, false).await?
410+
execute_get_dm(since, trade_index, &client, *from_user, false, &mostro_key).await?
411+
}
412+
Commands::GetDmUser { since } => {
413+
execute_get_dm_user(since, &client, &mostro_key).await?
377414
}
378415
Commands::GetAdminDm { since, from_user } => {
379-
execute_get_dm(since, trade_index, &client, *from_user, true).await?
416+
execute_get_dm(since, trade_index, &client, *from_user, true, &mostro_key).await?
380417
}
381418
Commands::FiatSent { order_id }
382419
| Commands::Release { order_id }
@@ -477,6 +514,18 @@ pub async fn run() -> Result<()> {
477514
let pubkey = PublicKey::from_str(pubkey)?;
478515
execute_send_dm(pubkey, &client, order_id, message).await?
479516
}
517+
Commands::DmToUser {
518+
pubkey,
519+
order_id,
520+
message,
521+
} => {
522+
let pubkey = PublicKey::from_str(pubkey)?;
523+
execute_dm_to_user(pubkey, &client, order_id, message).await?
524+
}
525+
Commands::AdmSendDm { pubkey, message } => {
526+
let pubkey = PublicKey::from_str(pubkey)?;
527+
execute_adm_send_dm(pubkey, &client, message).await?
528+
}
480529
};
481530
}
482531

src/cli/adm_send_dm.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use crate::util::send_admin_gift_wrap_dm;
2+
use anyhow::Result;
3+
use nostr_sdk::prelude::*;
4+
5+
pub async fn execute_adm_send_dm(
6+
receiver: PublicKey,
7+
client: &Client,
8+
message: &str,
9+
) -> Result<()> {
10+
let admin_keys = match std::env::var("NSEC_PRIVKEY") {
11+
Ok(key) => Keys::parse(&key)?,
12+
Err(e) => {
13+
println!("Failed to get admin private key: {}", e);
14+
println!("Make sure NSEC_PRIVKEY environment variable is set");
15+
std::process::exit(1);
16+
}
17+
};
18+
19+
println!("SENDING DM with admin keys: {}", admin_keys.public_key().to_hex());
20+
21+
send_admin_gift_wrap_dm(client, &admin_keys, &receiver, message).await?;
22+
23+
println!("Admin gift wrap message sent to {}", receiver);
24+
25+
Ok(())
26+
}

src/cli/dm_to_user.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use crate::{db::Order, util::send_gift_wrap_dm};
2+
use anyhow::Result;
3+
use nostr_sdk::prelude::*;
4+
use uuid::Uuid;
5+
6+
pub async fn execute_dm_to_user(
7+
receiver: PublicKey,
8+
client: &Client,
9+
order_id: &Uuid,
10+
message: &str,
11+
) -> Result<()> {
12+
let pool = crate::db::connect().await?;
13+
14+
let trade_keys = if let Ok(order) = Order::get_by_id(&pool, &order_id.to_string()).await {
15+
match order.trade_keys.as_ref() {
16+
Some(trade_keys) => Keys::parse(trade_keys)?,
17+
None => {
18+
anyhow::bail!("No trade_keys found for this order");
19+
}
20+
}
21+
} else {
22+
println!("order {} not found", order_id);
23+
std::process::exit(0)
24+
};
25+
26+
println!("SENDING DM with trade keys: {}", trade_keys.public_key().to_hex());
27+
28+
send_gift_wrap_dm(client, &trade_keys, &receiver, message).await?;
29+
30+
Ok(())
31+
}

src/cli/get_dm.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ pub async fn execute_get_dm(
1414
client: &Client,
1515
from_user: bool,
1616
admin: bool,
17+
mostro_pubkey: &PublicKey,
1718
) -> Result<()> {
1819
let mut dm: Vec<(Message, u64)> = Vec::new();
1920
let pool = connect().await?;
2021
if !admin {
2122
for index in 1..=trade_index {
2223
let keys = User::get_trade_keys(&pool, index).await?;
23-
let dm_temp = get_direct_messages(client, &keys, *since, from_user).await;
24+
let dm_temp = get_direct_messages(client, &keys, *since, from_user, Some(&mostro_pubkey)).await;
2425
dm.extend(dm_temp);
2526
}
2627
} else {
@@ -31,7 +32,7 @@ pub async fn execute_get_dm(
3132
std::process::exit(1);
3233
}
3334
};
34-
let dm_temp = get_direct_messages(client, &id_key, *since, from_user).await;
35+
let dm_temp = get_direct_messages(client, &id_key, *since, from_user, Some(mostro_pubkey)).await;
3536
dm.extend(dm_temp);
3637
}
3738

src/cli/get_dm_user.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use crate::{db::Order, util::get_direct_messages_from_trade_keys};
2+
use anyhow::Result;
3+
use comfy_table::modifiers::UTF8_ROUND_CORNERS;
4+
use comfy_table::presets::UTF8_FULL;
5+
use comfy_table::Table;
6+
use mostro_core::prelude::*;
7+
use nostr_sdk::prelude::*;
8+
9+
pub async fn execute_get_dm_user(since: &i64, client: &Client, mostro_pubkey: &PublicKey) -> Result<()> {
10+
let pool = crate::db::connect().await?;
11+
12+
// Get all trade keys from orders
13+
let mut trade_keys_hex = Order::get_all_trade_keys(&pool).await?;
14+
15+
// Add admin private key to search for messages sent TO admin
16+
if let Ok(admin_privkey_hex) = std::env::var("NSEC_PRIVKEY") {
17+
trade_keys_hex.push(admin_privkey_hex);
18+
}
19+
20+
if trade_keys_hex.is_empty() {
21+
println!("No trade keys found in orders and NSEC_PRIVKEY not set");
22+
return Ok(());
23+
}
24+
25+
println!("Searching for DMs in {} trade keys...", trade_keys_hex.len());
26+
27+
let direct_messages = get_direct_messages_from_trade_keys(client, trade_keys_hex, *since, mostro_pubkey).await;
28+
29+
if direct_messages.is_empty() {
30+
println!("You don't have any direct messages in your trade keys");
31+
return Ok(());
32+
}
33+
34+
let mut table = Table::new();
35+
table
36+
.load_preset(UTF8_FULL)
37+
.apply_modifier(UTF8_ROUND_CORNERS)
38+
.set_content_arrangement(comfy_table::ContentArrangement::Dynamic)
39+
.set_header(vec!["Time", "From", "Message"]);
40+
41+
for (message, created_at, sender_pubkey) in direct_messages.iter() {
42+
let datetime = chrono::DateTime::from_timestamp(*created_at as i64, 0);
43+
let formatted_date = match datetime {
44+
Some(dt) => dt.format("%Y-%m-%d %H:%M:%S").to_string(),
45+
None => "Invalid timestamp".to_string(),
46+
};
47+
48+
let inner = message.get_inner_message_kind();
49+
let message_str = match &inner.payload {
50+
Some(Payload::TextMessage(text)) => text.clone(),
51+
_ => format!("{:?}", message),
52+
};
53+
54+
let sender_hex = sender_pubkey.to_hex();
55+
56+
table.add_row(vec![&formatted_date, &sender_hex, &message_str]);
57+
}
58+
59+
println!("{table}");
60+
println!();
61+
Ok(())
62+
}

src/db.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,26 @@ impl Order {
439439
Ok(orders)
440440
}
441441

442+
pub async fn get_all_trade_keys(pool: &SqlitePool) -> Result<Vec<String>> {
443+
#[derive(sqlx::FromRow)]
444+
struct TradeKeyRow {
445+
trade_keys: Option<String>,
446+
}
447+
448+
let rows = sqlx::query_as::<_, TradeKeyRow>(
449+
"SELECT DISTINCT trade_keys FROM orders WHERE trade_keys IS NOT NULL"
450+
)
451+
.fetch_all(pool)
452+
.await?;
453+
454+
let trade_keys: Vec<String> = rows
455+
.into_iter()
456+
.filter_map(|row| row.trade_keys)
457+
.collect();
458+
459+
Ok(trade_keys)
460+
}
461+
442462
pub async fn delete_by_id(pool: &SqlitePool, id: &str) -> Result<bool> {
443463
let rows_affected = sqlx::query(
444464
r#"

0 commit comments

Comments
 (0)