Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions crates/buzz-cli/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,31 @@ buzz channels unarchive --channel "$CHANNEL_ID" | jq .
# Expected: {"event_id":"...","accepted":true,"message":"..."}
```

#### 6.1.1 User Groups

```bash
MEMBER_PK="0000000000000000000000000000000000000000000000000000000000000001"

GROUP_ID=$(buzz groups create --handle cli-team --name "CLI Team" | jq -r '.group_id')
buzz groups list | jq .
buzz --format compact groups get cli-team | jq .

buzz groups edit cli-team --description "CLI group test" \
--default-channel "$CHANNEL_ID" | jq .
buzz groups add-members cli-team --member "$MEMBER_PK" | jq .
buzz groups remove-members "$GROUP_ID" --member "$MEMBER_PK" | jq .

# Bulk channel addition resolves the current 39100 snapshot.
buzz groups add-members cli-team --member "$MEMBER_PK" | jq .
buzz channels add-group --channel "$CHANNEL_ID" --group cli-team | jq .

# The message carries a group marker and p-tags only for group members
# who are currently in the channel.
buzz messages send --channel "$CHANNEL_ID" --content "hello @cli-team" | jq .

buzz groups delete "$GROUP_ID" | jq .
```

### 6.2 Canvas

```bash
Expand Down
121 changes: 119 additions & 2 deletions crates/buzz-cli/src/commands/channels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,19 @@ pub async fn cmd_add_channel_member(
pubkey: &str,
role: Option<&str>,
) -> Result<(), CliError> {
println!(
"{}",
add_channel_member(client, channel_id, pubkey, role).await?
);
Ok(())
}

async fn add_channel_member(
client: &BuzzClient,
channel_id: &str,
pubkey: &str,
role: Option<&str>,
) -> Result<String, CliError> {
validate_hex64(pubkey)?;
let channel_uuid = parse_uuid(channel_id)?;

Expand All @@ -980,8 +993,109 @@ pub async fn cmd_add_channel_member(

let event = client.sign_event(builder)?;
let resp = client.submit_event(event).await?;
println!("{}", normalize_write_response(&resp));
Ok(())
Ok(normalize_write_response(&resp))
}

pub async fn cmd_add_group_to_channel(
client: &BuzzClient,
channel_id: &str,
group_reference: &str,
) -> Result<(), CliError> {
validate_uuid(channel_id)?;
let group = crate::commands::groups::resolve_group(client, group_reference).await?;
let filter = serde_json::json!({
"kinds": [39002],
"#d": [channel_id],
"limit": 1,
});
let raw = client.query(&filter).await?;
let events: Vec<serde_json::Value> = serde_json::from_str(&raw)
.map_err(|error| CliError::Other(format!("failed to parse channel members: {error}")))?;
let member_snapshot = events.first().ok_or_else(|| {
CliError::NotFound(format!("channel membership for '{channel_id}' not found"))
})?;
let existing_members: HashSet<String> = extract_p_tags(member_snapshot)
.into_iter()
.filter_map(|member| {
member
.get("pubkey")
.and_then(serde_json::Value::as_str)
.map(str::to_ascii_lowercase)
})
.collect();

let mut responses = Vec::with_capacity(group.members.len());
let mut failure_count = 0;
for pubkey in &group.members {
if existing_members.contains(&pubkey.to_ascii_lowercase()) {
responses.push(serde_json::json!({
"pubkey": pubkey,
"accepted": true,
"skipped": true,
"message": "already a channel member",
}));
continue;
}

match add_channel_member(client, channel_id, pubkey, None).await {
Ok(response) => match serde_json::from_str::<serde_json::Value>(&response) {
Ok(serde_json::Value::Object(mut result)) => {
let accepted = result
.get("accepted")
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
result.insert(
"pubkey".to_string(),
serde_json::Value::String(pubkey.clone()),
);
if !accepted {
failure_count += 1;
let message = result
.get("message")
.and_then(serde_json::Value::as_str)
.unwrap_or("relay rejected member add")
.to_string();
result.insert("error".to_string(), serde_json::Value::String(message));
}
responses.push(serde_json::Value::Object(result));
}
Ok(result) => {
failure_count += 1;
responses.push(serde_json::json!({
"pubkey": pubkey,
"accepted": false,
"error": "relay returned an invalid member-add response",
"response": result,
}));
}
Err(error) => {
failure_count += 1;
responses.push(serde_json::json!({
"pubkey": pubkey,
"accepted": false,
"error": format!("failed to parse member-add response: {error}"),
"response": response,
}));
}
},
Err(error) => {
failure_count += 1;
responses.push(serde_json::json!({
"pubkey": pubkey,
"accepted": false,
"error": error.to_string(),
}));
}
}
}
println!("{}", serde_json::to_string(&responses).unwrap_or_default());
if failure_count == 0 {
Ok(())
} else {
Err(CliError::Other(format!(
"failed to add {failure_count} group member(s) to the channel"
)))
}
}

pub async fn cmd_remove_channel_member(
Expand Down Expand Up @@ -1158,6 +1272,9 @@ pub async fn dispatch(
pubkey,
role,
} => cmd_add_channel_member(client, &channel, &pubkey, role.as_deref()).await,
ChannelsCmd::AddGroup { channel, group } => {
cmd_add_group_to_channel(client, &channel, &group).await
}
ChannelsCmd::RemoveMember { channel, pubkey } => {
cmd_remove_channel_member(client, &channel, &pubkey).await
}
Expand Down
Loading