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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
use crate::protocol::Message;
use crate::state::ServerState;
use crate::utils::generate_message_id;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
// Helper function to add server tags to a message
fn add_server_tags(mut message: Message, connection_capabilities: &Vec<String>) -> Message {
// Add msgid tag if client supports message-tags
if connection_capabilities.contains(&"message-tags".to_string()) {
let msg_id = generate_message_id();
message = message.with_tag("msgid".to_string(), Some(msg_id));
}
// Add server-time tag if client supports server-time
if connection_capabilities.contains(&"server-time".to_string()) {
let timestamp = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
message = message.with_tag("time".to_string(), Some(timestamp));
}
message
}
pub async fn handle_tagmsg(
server_state: Arc<RwLock<ServerState>>,
sender_id: u64,
target: String,
tags: HashMap<String, String>,
) -> Result<Vec<Message>, Box<dyn std::error::Error>> {
let responses = Vec::new();
// Get sender info
let sender_info = {
let state = server_state.read().await;
state.connections.get(&sender_id).map(|conn| (
conn.nickname.clone().unwrap_or_else(|| "*".to_string()),
conn.username.clone().unwrap_or_else(|| "~unknown".to_string()),
conn.hostname.clone(),
))
};
let (sender_nick, sender_user, sender_host) = match sender_info {
Some(info) => info,
None => return Ok(responses),
};
let sender_prefix = format!("{}!{}@{}", sender_nick, sender_user, sender_host);
// Check if target is a channel or user
if target.starts_with('#') || target.starts_with('&') {
// Handle channel TAGMSG
let channel_member_ids = {
let state = server_state.read().await;
state.channels.get(&target).map(|channel| {
channel.members.iter().map(|member| *member.key()).collect::<Vec<u64>>()
})
};
if let Some(member_ids) = channel_member_ids {
// Check if sender has echo-message capability
let sender_has_echo = {
let state = server_state.read().await;
state.connections.get(&sender_id)
.map(|conn| conn.capabilities.contains(&"echo-message".to_string()))
.unwrap_or(false)
};
// Send to all channel members
let state = server_state.read().await;
for member_id in member_ids {
// Echo back to sender only if they have echo-message capability
if member_id == sender_id && !sender_has_echo {
continue;
}
if let Some(conn) = state.connections.get(&member_id) {
// Send TAGMSG to all clients that support message-tags
if conn.capabilities.contains(&"message-tags".to_string()) {
// Build TAGMSG message for this specific client
let mut tagmsg = Message::new("TAGMSG")
.with_prefix(sender_prefix.clone())
.with_params(vec![target.clone()]);
// Add server tags based on client capabilities
tagmsg = add_server_tags(tagmsg, &conn.capabilities);
// Add all client tags
for (key, value) in &tags {
tagmsg = tagmsg.with_tag(key.clone(), Some(value.clone()));
}
let _ = conn.tx.send(tagmsg).await;
}
}
}
}
} else {
// Handle private TAGMSG
let target_id = {
let state = server_state.read().await;
state.nicknames.get(&target.to_lowercase()).map(|entry| *entry.value())
};
if let Some(target_id) = target_id {
// Check if target has message-tags capability
let has_capability = {
let state = server_state.read().await;
state.connections.get(&target_id)
.map(|conn| conn.capabilities.contains(&"message-tags".to_string()))
.unwrap_or(false)
};
if has_capability {
// Get target connection for server tags
let target_capabilities = {
let state = server_state.read().await;
state.connections.get(&target_id).map(|conn| conn.capabilities.clone())
};
if let Some(capabilities) = target_capabilities {
let mut tagmsg = Message::new("TAGMSG")
.with_prefix(sender_prefix.clone())
.with_params(vec![target.clone()]);
// Add server tags based on target capabilities
tagmsg = add_server_tags(tagmsg, &capabilities);
// Add all client tags
for (key, value) in &tags {
tagmsg = tagmsg.with_tag(key.clone(), Some(value.clone()));
}
// Send the message
let tx = {
let state = server_state.read().await;
state.connections.get(&target_id).map(|conn| conn.tx.clone())
};
if let Some(tx) = tx {
let _ = tx.send(tagmsg.clone()).await;
}
// Echo back to sender if they have echo-message capability
let sender_echo_info = {
let state = server_state.read().await;
state.connections.get(&sender_id).map(|conn| (
conn.capabilities.contains(&"echo-message".to_string()),
conn.capabilities.clone(),
conn.tx.clone()
))
};
if let Some((has_echo, sender_caps, sender_tx)) = sender_echo_info {
if has_echo {
// Build echo message with sender's capabilities
let mut echo_tagmsg = Message::new("TAGMSG")
.with_prefix(sender_prefix)
.with_params(vec![target]);
// Add server tags for sender
echo_tagmsg = add_server_tags(echo_tagmsg, &sender_caps);
// Add all client tags
for (key, value) in &tags {
echo_tagmsg = echo_tagmsg.with_tag(key.clone(), Some(value.clone()));
}
let _ = sender_tx.send(echo_tagmsg).await;
}
}
}
}
}
}
Ok(responses)
}