diff --git a/crates/buzz-cli/src/commands/messages.rs b/crates/buzz-cli/src/commands/messages.rs index ea273336e3..bef0c7e5f1 100644 --- a/crates/buzz-cli/src/commands/messages.rs +++ b/crates/buzz-cli/src/commands/messages.rs @@ -830,7 +830,7 @@ pub async fn cmd_delete_message( let channel_uuid = resolve_channel_id(client, event_id).await?; let target_eid = parse_event_id(event_id)?; - let builder = buzz_sdk::build_delete_message_with_options( + let builder = build_cli_delete_message( channel_uuid, target_eid, DeleteMessageOptions { @@ -848,6 +848,24 @@ pub async fn cmd_delete_message( Ok(()) } +fn build_cli_delete_message( + channel_id: Uuid, + target_event_id: nostr::EventId, + options: DeleteMessageOptions<'_>, +) -> Result { + // Keep ordinary agent/member deletes aligned with Desktop's NIP-09 path. + // Any moderation metadata is an explicit opt-in to the audited kind:9005 + // command and its visible room-facing tombstone. + if options.action_id.is_none() + && options.reason_code.is_none() + && options.public_reason.is_none() + { + buzz_sdk::build_delete_compat(channel_id, target_event_id) + } else { + buzz_sdk::build_delete_message_with_options(channel_id, target_event_id, options) + } +} + /// Edit a message you previously sent. pub async fn cmd_edit_message( client: &BuzzClient, @@ -1056,8 +1074,8 @@ pub async fn dispatch( #[cfg(test)] mod tests { use super::{ - channel_id_from_event, cmd_get_thread, event_mention_pubkeys, find_root_from_tags, - match_profiles_by_name, merge_message_mentions, missing_members, + build_cli_delete_message, channel_id_from_event, cmd_get_thread, event_mention_pubkeys, + find_root_from_tags, match_profiles_by_name, merge_message_mentions, missing_members, normalize_explicit_mentions, parse_member_pubkeys, resolve_names_to_pubkeys, resolve_thread_target, thread_ref_from_event, thread_ref_from_parent_tags, BuzzClient, CliError, Uuid, @@ -1078,6 +1096,42 @@ mod tests { const PK_VALID_B: &str = "c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05"; const PK_VALID_C: &str = "f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68"; + #[test] + fn plain_cli_delete_uses_nip09_self_delete() { + let builder = build_cli_delete_message( + Uuid::nil(), + nostr::EventId::all_zeros(), + buzz_sdk::DeleteMessageOptions::default(), + ) + .unwrap(); + let event = builder.sign_with_keys(&Keys::generate()).unwrap(); + + assert_eq!(event.kind.as_u16(), 5); + } + + #[test] + fn delete_with_moderation_metadata_keeps_audited_kind() { + let builder = build_cli_delete_message( + Uuid::nil(), + nostr::EventId::all_zeros(), + buzz_sdk::DeleteMessageOptions { + action_id: Some(Uuid::nil()), + reason_code: Some("spam"), + public_reason: Some("Removed as spam"), + }, + ) + .unwrap(); + let event = builder.sign_with_keys(&Keys::generate()).unwrap(); + + assert_eq!(event.kind.as_u16(), 9005); + for tag_name in ["action_id", "reason_code", "public_reason"] { + assert!(event + .tags + .iter() + .any(|tag| tag.kind().to_string() == tag_name)); + } + } + #[tokio::test] async fn malformed_channel_is_rejected_before_thread_fetch() { let client = diff --git a/crates/buzz-cli/src/lib.rs b/crates/buzz-cli/src/lib.rs index 5cac8c941e..af1e5a8f0c 100644 --- a/crates/buzz-cli/src/lib.rs +++ b/crates/buzz-cli/src/lib.rs @@ -444,7 +444,10 @@ pub enum MessagesCmd { #[arg(long)] content: String, }, - /// Delete a message by event ID + /// Delete a message by event ID. + /// + /// Plain deletes are NIP-09 self-deletes. Supplying moderation metadata + /// uses Buzz's audited moderation delete path instead. Delete { /// Event ID to delete (64-char hex) #[arg(long)]