v0.0.9: Group management — leave, kick, members
Server: - POST /groups/:name/leave — remove self from group - POST /groups/:name/kick — creator can kick members - GET /groups/:name/members — list with aliases + creator badge CLI TUI: - /gleave — leave current group - /gkick <fp_or_alias> — kick (creator only) - /gmembers — show member list with aliases and ★ for creator Web client: - Same commands: /gleave, /gkick, /gmembers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -98,7 +98,7 @@ impl App {
|
||||
|
||||
messages.lock().unwrap().push(ChatLine {
|
||||
sender: "system".into(),
|
||||
text: "Commands: /alias <name>, /peer <fp|@alias>, /g <group>, /file <path>, /info, /quit".into(),
|
||||
text: "/alias /peer /g /gleave /gkick /gmembers /file /info /quit".into(),
|
||||
is_system: true,
|
||||
is_self: false,
|
||||
message_id: None,
|
||||
@@ -325,6 +325,41 @@ impl App {
|
||||
self.group_list(client).await;
|
||||
return;
|
||||
}
|
||||
if text == "/gleave" {
|
||||
if let Some(ref peer) = self.peer_fp {
|
||||
if peer.starts_with('#') {
|
||||
let name = peer[1..].to_string();
|
||||
self.group_leave(&name, client).await;
|
||||
self.peer_fp = None;
|
||||
} else {
|
||||
self.add_message(ChatLine { sender: "system".into(), text: "Not in a group. Use /g <name> first".into(), is_system: true, is_self: false, message_id: None });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if text.starts_with("/gkick ") {
|
||||
if let Some(ref peer) = self.peer_fp {
|
||||
if peer.starts_with('#') {
|
||||
let name = peer[1..].to_string();
|
||||
let target = text[7..].trim().to_string();
|
||||
self.group_kick(&name, &target, client).await;
|
||||
} else {
|
||||
self.add_message(ChatLine { sender: "system".into(), text: "Not in a group".into(), is_system: true, is_self: false, message_id: None });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if text == "/gmembers" {
|
||||
if let Some(ref peer) = self.peer_fp {
|
||||
if peer.starts_with('#') {
|
||||
let name = peer[1..].to_string();
|
||||
self.group_members(&name, client).await;
|
||||
} else {
|
||||
self.add_message(ChatLine { sender: "system".into(), text: "Not in a group".into(), is_system: true, is_self: false, message_id: None });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if text.starts_with("/file ") {
|
||||
let path_str = text[6..].trim();
|
||||
self.handle_file_send(path_str, identity, db, client).await;
|
||||
@@ -762,6 +797,69 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
async fn group_leave(&self, name: &str, client: &ServerClient) {
|
||||
let url = format!("{}/v1/groups/{}/leave", client.base_url, name);
|
||||
match client.client.post(&url)
|
||||
.json(&serde_json::json!({"fingerprint": normfp(&self.our_fp)}))
|
||||
.send().await
|
||||
{
|
||||
Ok(resp) => {
|
||||
if let Ok(data) = resp.json::<serde_json::Value>().await {
|
||||
if let Some(err) = data.get("error") {
|
||||
self.add_message(ChatLine { sender: "system".into(), text: format!("Error: {}", err), is_system: true, is_self: false, message_id: None });
|
||||
} else {
|
||||
self.add_message(ChatLine { sender: "system".into(), text: format!("Left group '{}'", name), is_system: true, is_self: false, message_id: None });
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => self.add_message(ChatLine { sender: "system".into(), text: format!("Error: {}", e), is_system: true, is_self: false, message_id: None }),
|
||||
}
|
||||
}
|
||||
|
||||
async fn group_kick(&self, name: &str, target: &str, client: &ServerClient) {
|
||||
let url = format!("{}/v1/groups/{}/kick", client.base_url, name);
|
||||
match client.client.post(&url)
|
||||
.json(&serde_json::json!({"fingerprint": normfp(&self.our_fp), "target": target}))
|
||||
.send().await
|
||||
{
|
||||
Ok(resp) => {
|
||||
if let Ok(data) = resp.json::<serde_json::Value>().await {
|
||||
if let Some(err) = data.get("error") {
|
||||
self.add_message(ChatLine { sender: "system".into(), text: format!("Error: {}", err), is_system: true, is_self: false, message_id: None });
|
||||
} else {
|
||||
let kicked = data.get("kicked").and_then(|v| v.as_str()).unwrap_or("?");
|
||||
self.add_message(ChatLine { sender: "system".into(), text: format!("Kicked {} from '{}'", kicked, name), is_system: true, is_self: false, message_id: None });
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => self.add_message(ChatLine { sender: "system".into(), text: format!("Error: {}", e), is_system: true, is_self: false, message_id: None }),
|
||||
}
|
||||
}
|
||||
|
||||
async fn group_members(&self, name: &str, client: &ServerClient) {
|
||||
let url = format!("{}/v1/groups/{}/members", client.base_url, name);
|
||||
match client.client.get(&url).send().await {
|
||||
Ok(resp) => {
|
||||
if let Ok(data) = resp.json::<serde_json::Value>().await {
|
||||
if let Some(members) = data.get("members").and_then(|v| v.as_array()) {
|
||||
self.add_message(ChatLine { sender: "system".into(), text: format!("Members of #{}:", name), is_system: true, is_self: false, message_id: None });
|
||||
for m in members {
|
||||
let fp = m.get("fingerprint").and_then(|v| v.as_str()).unwrap_or("?");
|
||||
let alias = m.get("alias").and_then(|v| v.as_str());
|
||||
let creator = m.get("is_creator").and_then(|v| v.as_bool()).unwrap_or(false);
|
||||
let label = match alias {
|
||||
Some(a) => format!(" @{} ({}{})", a, &fp[..fp.len().min(12)], if creator { " ★" } else { "" }),
|
||||
None => format!(" {}...{}", &fp[..fp.len().min(12)], if creator { " ★" } else { "" }),
|
||||
};
|
||||
self.add_message(ChatLine { sender: "system".into(), text: label, is_system: true, is_self: false, message_id: None });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => self.add_message(ChatLine { sender: "system".into(), text: format!("Error: {}", e), is_system: true, is_self: false, message_id: None }),
|
||||
}
|
||||
}
|
||||
|
||||
async fn group_send(
|
||||
&self,
|
||||
group_name: &str,
|
||||
|
||||
Reference in New Issue
Block a user