Implement KICK command
Diff
src/channel.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++-
src/client.rs | 31 +++++++++++++++++++++++++++++++
src/messages.rs | 18 ++++++++++++++++++
3 files changed, 95 insertions(+), 6 deletions(-)
@@ -1,10 +1,10 @@
pub mod response;
use std::collections::HashMap;
use actix::{Actor, Addr, AsyncContext, Context, Handler, MessageResult};
use chrono::{DateTime, Utc};
use irc_proto::Command;
use irc_proto::{Command, Message};
use tracing::{debug, error, info, instrument, Span};
use crate::{
@@ -12,8 +12,9 @@
client::Client,
connection::InitiatedConnection,
messages::{
Broadcast, ChannelFetchTopic, ChannelJoin, ChannelMemberList, ChannelMessage, ChannelPart,
ChannelUpdateTopic, ServerDisconnect, UserNickChange,
Broadcast, ChannelFetchTopic, ChannelJoin, ChannelKickUser, ChannelMemberList,
ChannelMessage, ChannelPart, ChannelUpdateTopic, ServerDisconnect, UserKickedFromChannel,
UserNickChange,
},
};
@@ -176,6 +177,51 @@
});
}
}
}
}
impl Handler<ChannelKickUser> for Channel {
type Result = ();
fn handle(&mut self, msg: ChannelKickUser, _ctx: &mut Self::Context) -> Self::Result {
let Some(kicker) = self.clients.get(&msg.client) else {
error!("Kicker is unknown");
return;
};
let kicker = kicker.to_nick();
let kicked_user = self
.clients
.iter()
.find(|(_handle, client)| client.nick == msg.user)
.map(|(k, v)| (k.clone(), v));
let Some((kicked_user_handle, kicked_user_info)) = kicked_user else {
error!(msg.user, "Attempted to kick unknown user");
return;
};
for client in self.clients.keys() {
client.do_send(Broadcast {
message: Message {
tags: None,
prefix: Some(kicker.clone()),
command: Command::KICK(
self.name.to_string(),
kicked_user_info.nick.to_string(),
msg.reason.clone(),
),
},
span: Span::current(),
});
}
kicked_user_handle.do_send(UserKickedFromChannel {
channel: self.name.to_string(),
span: Span::current(),
});
self.clients.remove(&kicked_user_handle);
}
}
@@ -13,8 +13,9 @@
channel::Channel,
connection::{InitiatedConnection, MessageSink},
messages::{
Broadcast, ChannelFetchTopic, ChannelJoin, ChannelList, ChannelMemberList, ChannelMessage,
ChannelPart, ChannelUpdateTopic, FetchClientDetails, ServerDisconnect, UserNickChange,
Broadcast, ChannelFetchTopic, ChannelJoin, ChannelKickUser, ChannelList, ChannelMemberList,
ChannelMessage, ChannelPart, ChannelUpdateTopic, FetchClientDetails, ServerDisconnect,
UserKickedFromChannel, UserNickChange,
},
server::Server,
SERVER_NAME,
@@ -234,6 +235,16 @@
}
}
impl Handler<UserKickedFromChannel> for Client {
type Result = ();
#[instrument(parent = &msg.span, skip_all)]
fn handle(&mut self, msg: UserKickedFromChannel, _ctx: &mut Self::Context) -> Self::Result {
self.channels.remove(&msg.channel);
}
}
impl StreamHandler<Result<irc_proto::Message, ProtocolError>> for Client {
@@ -384,7 +395,21 @@
ctx.spawn(fut);
}
Command::INVITE(_, _) => {}
Command::KICK(_, _, _) => {}
Command::KICK(channel, users, reason) => {
let Some(channel) = self.channels.get(&channel) else {
error!(%channel, "User not connected to channel");
return;
};
for user in parse_channel_name_list(&users) {
channel.do_send(ChannelKickUser {
span: Span::current(),
client: ctx.address(),
user,
reason: reason.clone(),
});
}
}
Command::PRIVMSG(target, message) => {
if !target.is_channel_name() {
@@ -73,6 +73,24 @@
pub span: Span,
}
#[derive(Message)]
#[rtype(result = "()")]
pub struct ChannelKickUser {
pub span: Span,
pub client: Addr<Client>,
pub user: String,
pub reason: Option<String>,
}
#[derive(Message)]
#[rtype(result = "()")]
pub struct UserKickedFromChannel {
pub channel: String,
pub span: Span,
}
#[derive(Message)]