1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::protocol::{Message, Reply};
use crate::state::ServerState;
pub async fn handle_who(
server_state: Arc<RwLock<ServerState>>,
connection_id: u64,
params: Vec<String>,
) -> Result<Vec<Message>, Box<dyn std::error::Error>> {
let state = server_state.read().await;
let mut messages = Vec::new();
// Get the requesting connection
let requesting_connection = state.connections.get(&connection_id);
if requesting_connection.is_none() {
return Ok(messages);
}
let req_conn = requesting_connection.unwrap();
let requester_nick = req_conn.nickname.as_deref().unwrap_or("*");
// Default to "*" (all visible users) if no parameter given
let target = params.get(0).cloned().unwrap_or_else(|| "*".to_string());
if target.starts_with('#') || target.starts_with('&') || target.starts_with('!') {
// WHO for a specific channel
if let Some(channel) = state.channels.get(&target) {
// Check if requesting user is in channel or channel is not secret/private
let can_see_channel = channel.is_member(connection_id) ||
!channel.modes.contains(&'s') && !channel.modes.contains(&'p');
if can_see_channel {
// Return all visible members of this channel
for member_entry in channel.members.iter() {
let member_id = *member_entry.key();
if let Some(member_conn) = state.connections.get(&member_id) {
if let Some(member_nick) = &member_conn.nickname {
// Determine status flags (H = here, G = gone/away, @ = op, + = voice)
// TODO: Implement AWAY support in Connection struct
let mut flags = "H".to_string(); // Always "here" for now
// Add channel status (highest status first)
if member_entry.value().modes.contains(&'o') {
flags.push('@');
} else if member_entry.value().modes.contains(&'v') {
flags.push('+');
}
// Create a basic WHO reply using available reply types
// Since WHO reply (352) isn't available, we'll document this limitation
// TODO: Implement proper RPL_WHOREPLY (352) when available in legion-protocol
// For now, we'll skip individual WHO replies and just send end of WHO
}
}
}
}
}
} else if target == "*" {
// WHO for all visible users - very limited implementation
// In a real implementation, this would show all visible users not in channels
// For now, we'll just acknowledge the command
} else {
// WHO for a specific user
if let Some(target_id_ref) = state.nicknames.get(&target.to_lowercase()) {
let target_id = *target_id_ref;
if let Some(target_conn) = state.connections.get(&target_id) {
// TODO: Implement proper RPL_WHOREPLY (352) when available in legion-protocol
// For now, we'll just acknowledge the user exists by not sending NoSuchNick
} else {
messages.push(Message::from(Reply::NoSuchNick {
nick: requester_nick.to_string(),
target: target.clone(),
}));
}
} else {
messages.push(Message::from(Reply::NoSuchNick {
nick: requester_nick.to_string(),
target: target.clone(),
}));
}
}
// Always send end of WHO
// TODO: Replace with proper RPL_ENDOFWHO (315) when available in legion-protocol
// For now, we'll create a basic message indicating the command completed
let end_who_msg = Message::new("315")
.with_params(vec![
requester_nick.to_string(),
target.clone(),
"End of WHO list".to_string(),
]);
messages.push(end_who_msg);
Ok(messages)
}
#[cfg(test)]
#[path = "who_test.rs"]
mod tests;