@@ -3,142 +3,102 @@ use crate::db::Order;
33use crate :: parser:: common:: {
44 print_info_line, print_key_value, print_no_data_message, print_section_header,
55} ;
6- use crate :: parser:: dms:: print_direct_messages;
76use crate :: util:: messaging:: { derive_shared_key_bytes, fetch_gift_wraps_for_shared_key} ;
8- use crate :: util:: { fetch_events_list, Event , ListKind } ;
97use anyhow:: Result ;
10- use mostro_core:: prelude:: * ;
118use nostr_sdk:: prelude:: * ;
9+ use uuid:: Uuid ;
1210
13- pub async fn execute_get_dm_user ( since : & i64 , ctx : & Context ) -> Result < ( ) > {
14- // Get all trade keys from orders
15- let mut trade_keys_hex = Order :: get_all_trade_keys ( & ctx. pool ) . await ?;
16-
17- // Include Mostro pubkey so we also fetch messages addressed to Mostro
18- let admin_pubkey_hex = ctx. mostro_pubkey . to_hex ( ) ;
19- if !trade_keys_hex. iter ( ) . any ( |k| k == & admin_pubkey_hex) {
20- trade_keys_hex. push ( admin_pubkey_hex) ;
21- }
22- // De-duplicate any repeated keys coming from DB/admin
23- trade_keys_hex. sort ( ) ;
24- trade_keys_hex. dedup ( ) ;
25-
26- // Check if the trade keys are empty
27- if trade_keys_hex. is_empty ( ) {
28- print_no_data_message ( "No trade keys found in orders" ) ;
29- return Ok ( ( ) ) ;
30- }
31-
11+ /// Fetch user-to-user chat messages over a shared conversation key.
12+ ///
13+ /// CLI parameters:
14+ /// - `pubkey`: counterparty pubkey
15+ /// - `order_id`: order used to look up the trade keys
16+ /// - `since`: minutes back in time to include
17+ pub async fn execute_get_dm_user (
18+ pubkey : PublicKey ,
19+ order_id : Uuid ,
20+ since : & i64 ,
21+ ctx : & Context ,
22+ ) -> Result < ( ) > {
3223 print_section_header ( "📨 Fetch User Direct Messages" ) ;
33- print_key_value (
34- "🔍" ,
35- "Searching for DMs in trade keys" ,
36- & format ! ( "{}" , trade_keys_hex. len( ) ) ,
37- ) ;
24+ print_key_value ( "👥" , "Counterparty" , & pubkey. to_string ( ) ) ;
25+ print_key_value ( "📋" , "Order ID" , & order_id. to_string ( ) ) ;
3826 print_key_value ( "⏰" , "Since" , & format ! ( "{} minutes ago" , since) ) ;
39- print_info_line ( "💡" , "Fetching direct messages..." ) ;
27+ print_info_line ( "💡" , "Fetching shared-key chat messages..." ) ;
4028 println ! ( ) ;
4129
42- let direct_messages = fetch_events_list (
43- ListKind :: DirectMessagesUser ,
44- None ,
45- None ,
46- None ,
47- ctx,
48- Some ( since) ,
49- )
50- . await ?;
30+ // 1. Get the order and its trade keys
31+ let order = Order :: get_by_id ( & ctx. pool , & order_id. to_string ( ) )
32+ . await
33+ . map_err ( |e| anyhow:: anyhow!( "Failed to load order {order_id}: {e}" ) ) ?;
5134
52- // Extract (Message, u64, PublicKey) tuples from Event::MessageTuple variants (classic DMs)
53- let mut dm_events: Vec < ( Message , u64 , PublicKey ) > = Vec :: new ( ) ;
54- for event in direct_messages {
55- if let Event :: MessageTuple ( tuple) = event {
56- dm_events. push ( * tuple) ;
57- }
35+ let trade_keys_str = order
36+ . trade_keys
37+ . clone ( )
38+ . ok_or_else ( || anyhow:: anyhow!( "Missing trade keys for order {order_id}" ) ) ?;
39+ let trade_keys =
40+ Keys :: parse ( & trade_keys_str) . map_err ( |e| anyhow:: anyhow!( "Invalid trade keys: {e}" ) ) ?;
41+
42+ // 2. Derive the shared conversation key (trade private key + counterparty pubkey)
43+ let shared_key_bytes = derive_shared_key_bytes ( & trade_keys, & pubkey) . map_err ( |e| {
44+ log:: warn!(
45+ "get_dm_user: could not derive shared key (trade + counterparty): {}" ,
46+ e
47+ ) ;
48+ anyhow:: anyhow!( "Could not derive shared key for chat with counterparty" )
49+ } ) ?;
50+
51+ let shared_keys = SecretKey :: from_slice ( & shared_key_bytes)
52+ . map ( Keys :: new)
53+ . map_err ( |e| anyhow:: anyhow!( "Could not build Keys from shared key: {e}" ) ) ?;
54+
55+ // 3. Fetch all gift wraps addressed to this shared key and decrypt them
56+ let mut messages = fetch_gift_wraps_for_shared_key ( & ctx. client , & shared_keys) . await ?;
57+
58+ // 4. Apply "since" filter (minutes back from now)
59+ if * since > 0 {
60+ let cutoff_ts = chrono:: Utc :: now ( )
61+ . checked_sub_signed ( chrono:: Duration :: minutes ( * since) )
62+ . unwrap ( )
63+ . timestamp ( ) ;
64+ messages. retain ( |( _, ts, _) | ( * ts as i64 ) >= cutoff_ts) ;
5865 }
5966
60- // Also fetch and decrypt shared-key custom wraps (shared key = trade_keys + identity_keys)
61- let trade_keys_hex_list = Order :: get_all_trade_keys ( & ctx. pool ) . await ?;
62- let identity_pubkey = ctx. identity_keys . public_key ( ) ;
63- for trade_hex in trade_keys_hex_list {
64- let trade_keys = match Keys :: parse ( & trade_hex) {
65- Ok ( k) => k,
66- Err ( e) => {
67- log:: warn!( "get_dm_user: could not parse trade_keys: {}" , e) ;
68- continue ;
69- }
70- } ;
71- let shared_key = match derive_shared_key_bytes ( & trade_keys, & identity_pubkey) {
72- Ok ( b) => b,
73- Err ( e) => {
74- log:: warn!(
75- "get_dm_user: could not derive shared key (trade + identity): {}" ,
76- e
77- ) ;
78- continue ;
79- }
80- } ;
81- let shared_keys = match SecretKey :: from_slice ( & shared_key) {
82- Ok ( sk) => Keys :: new ( sk) ,
83- Err ( e) => {
84- log:: warn!( "get_dm_user: could not build Keys from shared key: {}" , e) ;
85- continue ;
86- }
87- } ;
88- let shared_msgs = match fetch_gift_wraps_for_shared_key ( & ctx. client , & shared_keys) . await {
89- Ok ( m) => m,
90- Err ( e) => {
91- log:: warn!(
92- "get_dm_user: failed to fetch gift wraps for shared key: {}" ,
93- e
94- ) ;
95- continue ;
96- }
97- } ;
98- for ( content, ts, sender_pubkey) in shared_msgs {
99- let parsed: ( Message , Option < String > ) = match serde_json:: from_str ( & content) {
100- Ok ( m) => m,
101- Err ( e) => {
102- log:: warn!( "get_dm_user: could not parse shared-key DM content: {}" , e) ;
103- continue ;
104- }
105- } ;
106- dm_events. push ( ( parsed. 0 , ts as u64 , sender_pubkey) ) ;
107- }
67+ // 5. Keep only messages sent by the counterparty (not our own side)
68+ messages. retain ( |( _, _, sender_pk) | * sender_pk == pubkey) ;
69+
70+ if messages. is_empty ( ) {
71+ print_no_data_message ( "📭 No chat messages found for this shared conversation key." ) ;
72+ return Ok ( ( ) ) ;
73+ }
10874
109- // Also fetch shared-key wraps for (trade_keys + mostro_pubkey) so we see admin replies
110- // (send_admin_dm_attach uses that derivation when we send to admin; admin uses same key to reply)
111- let shared_key_admin = match derive_shared_key_bytes ( & trade_keys, & ctx. mostro_pubkey ) {
112- Ok ( b) => b,
113- Err ( e) => {
114- log:: warn!(
115- "get_dm_user: could not derive shared key (trade + mostro): {}" ,
116- e
117- ) ;
118- continue ;
119- }
75+ // 6. Pretty-print the messages
76+ println ! ( "" ) ;
77+ print_section_header ( "💬 Shared-Key Chat Messages" ) ;
78+
79+ for ( idx, ( content, ts, sender_pk) ) in messages. iter ( ) . enumerate ( ) {
80+ let date = match chrono:: DateTime :: from_timestamp ( * ts as i64 , 0 ) {
81+ Some ( dt) => dt. format ( "%Y-%m-%d %H:%M:%S" ) . to_string ( ) ,
82+ None => "Invalid timestamp" . to_string ( ) ,
12083 } ;
121- let shared_keys_admin = match SecretKey :: from_slice ( & shared_key_admin ) {
122- Ok ( sk ) => Keys :: new ( sk ) ,
123- Err ( e ) => {
124- log :: warn !( "get_dm_user: could not build Keys from shared key (admin): {}" , e ) ;
125- continue ;
126- }
84+
85+ // Mark messages from the counterparty vs our own future messages (if any)
86+ let from_label = if * sender_pk == pubkey {
87+ format ! ( "👤 Counterparty ({sender_pk})" )
88+ } else {
89+ format ! ( "🧑 You ({sender_pk})" )
12790 } ;
128- if let Ok ( admin_msgs) = fetch_gift_wraps_for_shared_key ( & ctx. client , & shared_keys_admin) . await {
129- for ( content, ts, sender_pubkey) in admin_msgs {
130- if let Ok ( ( parsed, _) ) = serde_json:: from_str :: < ( Message , Option < String > ) > ( & content) {
131- dm_events. push ( ( parsed, ts as u64 , sender_pubkey) ) ;
132- }
133- }
134- }
135- }
13691
137- if dm_events. is_empty ( ) {
138- print_no_data_message ( "You don't have any direct messages in your trade keys" ) ;
139- return Ok ( ( ) ) ;
92+ println ! ( "📄 Message {}:" , idx + 1 ) ;
93+ println ! ( "─────────────────────────────────────" ) ;
94+ println ! ( "⏰ Time: {}" , date) ;
95+ println ! ( "📨 From: {}" , from_label) ;
96+ println ! ( "📝 Content:" ) ;
97+ for line in content. lines ( ) {
98+ println ! ( " {}" , line) ;
99+ }
100+ println ! ( ) ;
140101 }
141102
142- print_direct_messages ( & dm_events, Some ( ctx. mostro_pubkey ) ) . await ?;
143103 Ok ( ( ) )
144104}
0 commit comments