From b736bad1812959761fc49fd492f945028d8b5870 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Sat, 7 Jan 2023 21:13:51 +0000 Subject: [PATCH] Implement KICK command --- src/channel.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++++--- src/client.rs | 31 ++++++++++++++++++++++++++++--- src/messages.rs | 18 ++++++++++++++++++ 3 files changed, 95 insertions(+), 6 deletions(-) diff --git a/src/channel.rs b/src/channel.rs index 9a12dc3..83fd925 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -4,7 +4,7 @@ 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 @@ use crate::{ 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, }, }; @@ -179,6 +180,51 @@ impl Handler for Channel { } } +/// Sent from an oper client to remove a user from the channel. +impl Handler 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); + } +} + /// Returns the current channel topic to the user. impl Handler for Channel { type Result = MessageResult; diff --git a/src/client.rs b/src/client.rs index bf015fb..6768949 100644 --- a/src/client.rs +++ b/src/client.rs @@ -13,8 +13,9 @@ use crate::{ 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 for Client { } } +/// Sent by channels when the current user is removed from it. +impl Handler 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); + } +} + /// Receives messages from the user's incoming TCP stream and processes them, passing them onto /// other actors or self-notifying and calling a [`Handler`]. impl StreamHandler> for Client { @@ -384,7 +395,21 @@ impl StreamHandler> for Client { 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() { // private message to another user diff --git a/src/messages.rs b/src/messages.rs index 83998a9..1dd8bec 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -73,6 +73,24 @@ pub struct ChannelFetchTopic { pub span: Span, } +/// Attempts to kick a user from a channel. +#[derive(Message)] +#[rtype(result = "()")] +pub struct ChannelKickUser { + pub span: Span, + pub client: Addr, + pub user: String, + pub reason: Option, +} + +/// Sent from channels to users when a user is removed from the channel. +#[derive(Message)] +#[rtype(result = "()")] +pub struct UserKickedFromChannel { + pub channel: String, + pub span: Span, +} + /// Sent from a particular user to a channel when the user attempts to update the /// topic. #[derive(Message)] -- libgit2 1.7.2