diff --git a/crates/buzz-cli/src/commands/dms.rs b/crates/buzz-cli/src/commands/dms.rs index 589e411827..8d7a700bab 100644 --- a/crates/buzz-cli/src/commands/dms.rs +++ b/crates/buzz-cli/src/commands/dms.rs @@ -4,19 +4,60 @@ use crate::client::{extract_d_tag, normalize_write_response, BuzzClient}; use crate::error::CliError; use crate::validate::{parse_uuid, sdk_err, validate_hex64}; -/// List DM conversations by querying kind:41001 (relay-confirmed DMs) filtered by our pubkey. +/// List DM conversations by querying relay-emitted NIP-29 discovery events: +/// kind:39002 (our channel-memberships) gives the channel IDs, then kind:39000 +/// filtered by `#t: dm` gives the metadata for those DMs. The relay emits both +/// kinds from `emit_group_discovery_events` (called when a DM is opened, so +/// `dms list` sees the DMs we are already a member of). A previous version of +/// this command queried kind:41001, but no upstream event of that kind is ever +/// published — `KIND_DM_CREATED` is an enum constant without an emitter — so the +/// query always returned an empty list (issue #5137). pub async fn cmd_list_dms(client: &BuzzClient, limit: Option) -> Result<(), CliError> { let my_pk = client.keys().public_key().to_hex(); let limit = limit.unwrap_or(50).min(200); - let filter = serde_json::json!({ - "kinds": [41001], + + // Step 1: find channel IDs where we are a member (kind:39002). + let member_filter = serde_json::json!({ + "kinds": [39002], "#p": [my_pk], - "limit": limit }); - let resp = client.query(&filter).await?; - let events: Vec = serde_json::from_str(&resp).unwrap_or_default(); - let dms: Vec = events + let member_events = client.query_paginated(member_filter, limit).await?; + let channel_ids: Vec = member_events .iter() + .map(extract_d_tag) + .filter(|id| !id.is_empty()) + .collect(); + if channel_ids.is_empty() { + println!("[]"); + return Ok(()); + } + + // Step 2: fetch kind:39000 metadata, keeping only DMs (`t == "dm"`). + let metadata_filter = serde_json::json!({ + "kinds": [39000], + "#d": channel_ids, + }); + let metadata_events = client.query_paginated(metadata_filter, limit).await?; + let dms: Vec = metadata_events + .iter() + .filter(|e| { + e.get("tags") + .and_then(|t| t.as_array()) + .map(|tags| { + tags.iter().any(|tag| { + let arr = tag.as_array(); + let is_t_tag = arr.and_then(|a| { + if a.first().and_then(|v| v.as_str()) == Some("t") { + a.get(1).and_then(|v| v.as_str()) + } else { + None + } + }); + is_t_tag == Some("dm") + }) + }) + .unwrap_or(false) + }) .map(|e| { let dm_id = extract_d_tag(e); let participants: Vec = e