mirror of
https://github.com/block/buzz.git
synced 2026-10-29 05:21:31 +00:00
Signed-off-by: Tyler Longwell <tlongwell@block.xyz> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Mari <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Quinn <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
137 lines
5.4 KiB
Rust
137 lines
5.4 KiB
Rust
use uuid::Uuid;
|
|
|
|
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.
|
|
pub async fn cmd_list_dms(client: &BuzzClient, limit: Option<u32>) -> 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],
|
|
"#p": [my_pk],
|
|
"limit": limit
|
|
});
|
|
let resp = client.query(&filter).await?;
|
|
let events: Vec<serde_json::Value> = serde_json::from_str(&resp).unwrap_or_default();
|
|
let dms: Vec<serde_json::Value> = events
|
|
.iter()
|
|
.map(|e| {
|
|
let dm_id = extract_d_tag(e);
|
|
let participants: Vec<String> = e
|
|
.get("tags")
|
|
.and_then(|t| t.as_array())
|
|
.map(|tags| {
|
|
tags.iter()
|
|
.filter_map(|tag| {
|
|
let arr = tag.as_array()?;
|
|
if arr.first()?.as_str()? == "p" {
|
|
arr.get(1)?.as_str().map(|s| s.to_string())
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect()
|
|
})
|
|
.unwrap_or_default();
|
|
serde_json::json!({
|
|
"dm_id": dm_id,
|
|
"participants": participants,
|
|
"created_at": e.get("created_at").and_then(|v| v.as_u64()).unwrap_or(0),
|
|
})
|
|
})
|
|
.collect();
|
|
let output = serde_json::to_string(&dms).unwrap_or_default();
|
|
println!("{output}");
|
|
Ok(())
|
|
}
|
|
|
|
/// Open a DM with one or more users — sign and submit a kind:41010 event with a d-tag.
|
|
pub async fn cmd_open_dm(client: &BuzzClient, pubkeys: &[String]) -> Result<(), CliError> {
|
|
if pubkeys.is_empty() || pubkeys.len() > 8 {
|
|
return Err(CliError::Usage("--pubkey: must provide 1-8 pubkeys".into()));
|
|
}
|
|
for pk in pubkeys {
|
|
validate_hex64(pk)?;
|
|
}
|
|
let dm_id = Uuid::new_v4().to_string();
|
|
let refs: Vec<&str> = pubkeys.iter().map(String::as_str).collect();
|
|
|
|
// build_dm_open doesn't accept a d-tag, so we build the event manually
|
|
// using the SDK builder and add the d-tag ourselves.
|
|
use nostr::{EventBuilder, Kind, Tag};
|
|
let mut tags: Vec<Tag> = refs
|
|
.iter()
|
|
.map(|pk| Tag::parse(["p", *pk]).map_err(|e| CliError::Other(format!("tag error: {e}"))))
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
tags.push(Tag::parse(["d", &dm_id]).map_err(|e| CliError::Other(format!("tag error: {e}")))?);
|
|
let builder = EventBuilder::new(Kind::Custom(41010), "").tags(tags);
|
|
let event = client.sign_event(builder)?;
|
|
|
|
let resp = client.submit_event(event).await?;
|
|
// Try to extract relay-assigned channel_id from response message.
|
|
// Relay returns: {"event_id":"...","accepted":true,"message":"response:{\"channel_id\":\"...\",\"created\":true}"}
|
|
let relay_dm_id = serde_json::from_str::<serde_json::Value>(&resp)
|
|
.ok()
|
|
.and_then(|v| v.get("message")?.as_str().map(|s| s.to_string()))
|
|
.and_then(|msg| {
|
|
let json_part = msg.strip_prefix("response:")?;
|
|
serde_json::from_str::<serde_json::Value>(json_part).ok()
|
|
})
|
|
.and_then(|v| v.get("channel_id")?.as_str().map(|s| s.to_string()));
|
|
let final_dm_id = relay_dm_id.unwrap_or(dm_id);
|
|
|
|
let mut normalized: serde_json::Value =
|
|
serde_json::from_str(&resp).unwrap_or(serde_json::json!({}));
|
|
normalized["dm_id"] = serde_json::json!(final_dm_id);
|
|
if normalized.get("accepted").is_none() {
|
|
normalized["accepted"] = serde_json::json!(true);
|
|
}
|
|
println!("{normalized}");
|
|
Ok(())
|
|
}
|
|
|
|
/// Hide a DM channel — sign and submit a kind:41012 event with h-tag.
|
|
pub async fn cmd_hide_dm(client: &BuzzClient, channel_id: &str) -> Result<(), CliError> {
|
|
let channel_uuid = parse_uuid(channel_id)?;
|
|
|
|
use nostr::{EventBuilder, Kind, Tag};
|
|
let tags = vec![Tag::parse(["h", &channel_uuid.to_string()])
|
|
.map_err(|e| CliError::Other(format!("tag error: {e}")))?];
|
|
let builder =
|
|
EventBuilder::new(Kind::Custom(buzz_sdk::kind::KIND_DM_HIDE as u16), "").tags(tags);
|
|
let event = client.sign_event(builder)?;
|
|
|
|
let resp = client.submit_event(event).await?;
|
|
println!("{}", normalize_write_response(&resp));
|
|
Ok(())
|
|
}
|
|
|
|
/// Add a member to a DM group — sign and submit a kind:41011 event.
|
|
pub async fn cmd_add_dm_member(
|
|
client: &BuzzClient,
|
|
channel_id: &str,
|
|
pubkey: &str,
|
|
) -> Result<(), CliError> {
|
|
let channel_uuid = parse_uuid(channel_id)?;
|
|
validate_hex64(pubkey)?;
|
|
|
|
let builder = buzz_sdk::build_dm_add_member(channel_uuid, pubkey).map_err(sdk_err)?;
|
|
let event = client.sign_event(builder)?;
|
|
|
|
let resp = client.submit_event(event).await?;
|
|
println!("{}", normalize_write_response(&resp));
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn dispatch(cmd: crate::DmsCmd, client: &BuzzClient) -> Result<(), CliError> {
|
|
use crate::DmsCmd;
|
|
match cmd {
|
|
DmsCmd::List { limit } => cmd_list_dms(client, limit).await,
|
|
DmsCmd::Open { pubkeys } => cmd_open_dm(client, &pubkeys).await,
|
|
DmsCmd::AddMember { channel, pubkey } => cmd_add_dm_member(client, &channel, &pubkey).await,
|
|
DmsCmd::Hide { channel } => cmd_hide_dm(client, &channel).await,
|
|
}
|
|
}
|