Implement WHOIS
Diff
src/channel.rs | 11 ++++++-
src/channel/response.rs | 2 +-
src/client.rs | 53 +++++++++++++++++++++++++++++---
src/connection.rs | 7 ++--
src/messages.rs | 23 ++++++++++++++-
src/server.rs | 38 ++++++++++++++++++++---
src/server/response.rs | 84 +++++++++++++++++++++++++++++++++++++++++++++++++-
7 files changed, 205 insertions(+), 13 deletions(-)
@@ -25,7 +25,7 @@ use crate::{
messages::{
Broadcast, ChannelFetchTopic, ChannelFetchWhoList, ChannelInvite, ChannelJoin,
ChannelKickUser, ChannelMemberList, ChannelMessage, ChannelPart, ChannelSetMode,
ChannelUpdateTopic, FetchClientByNick, MessageKind, ServerDisconnect,
ChannelUpdateTopic, FetchClientByNick, FetchUserPermission, MessageKind, ServerDisconnect,
UserKickedFromChannel, UserNickChange,
},
persistence::{
@@ -115,6 +115,15 @@ impl Handler<Broadcast> for Channel {
}
}
impl Handler<FetchUserPermission> for Channel {
type Result = MessageResult<FetchUserPermission>;
fn handle(&mut self, msg: FetchUserPermission, _ctx: &mut Self::Context) -> Self::Result {
MessageResult(self.get_user_permissions(msg.user))
}
}
impl Handler<ChannelMemberList> for Channel {
type Result = MessageResult<ChannelMemberList>;
@@ -89,7 +89,7 @@ impl ChannelWhoList {
let mut out = Vec::with_capacity(self.nick_list.len());
for (perm, conn) in self.nick_list {
let presence = if conn.presence { "H" } else { "G" };
let presence = if conn.away.is_some() { "G" } else { "H" };
out.push(Message {
tags: None,
@@ -23,9 +23,10 @@ use crate::{
messages::{
Broadcast, ChannelFetchTopic, ChannelFetchWhoList, ChannelInvite, ChannelJoin,
ChannelKickUser, ChannelList, ChannelMemberList, ChannelMessage, ChannelPart,
ChannelSetMode, ChannelUpdateTopic, FetchClientDetails, FetchWhoList, MessageKind,
PrivateMessage, ServerAdminInfo, ServerDisconnect, ServerFetchMotd, ServerListUsers,
UserKickedFromChannel, UserNickChange, UserNickChangeInternal,
ChannelSetMode, ChannelUpdateTopic, ConnectedChannels, FetchClientDetails,
FetchUserPermission, FetchWhoList, FetchWhois, MessageKind, PrivateMessage,
ServerAdminInfo, ServerDisconnect, ServerFetchMotd, ServerListUsers, UserKickedFromChannel,
UserNickChange, UserNickChangeInternal,
},
persistence::{
events::{
@@ -215,10 +216,42 @@ impl Handler<Broadcast> for Client {
}
}
impl Handler<ConnectedChannels> for Client {
type Result = ResponseFuture<<ConnectedChannels as actix::Message>::Result>;
#[instrument(parent = &msg.span, skip_all)]
fn handle(&mut self, msg: ConnectedChannels, _ctx: &mut Self::Context) -> Self::Result {
let span = Span::current();
let user_id = self.connection.user_id;
let fut = self.channels.iter().map(move |(channel_name, handle)| {
let span = span.clone();
let channel_name = channel_name.to_string();
let handle = handle.clone();
async move {
let permission = handle
.send(FetchUserPermission {
span,
user: user_id,
})
.await
.unwrap();
(permission, channel_name)
}
});
Box::pin(future::join_all(fut))
}
}
impl Handler<FetchWhoList> for Client {
type Result = ResponseFuture<<FetchWhoList as actix::Message>::Result>;
#[instrument(parent = &msg.span, skip_all)]
fn handle(&mut self, msg: FetchWhoList, _ctx: &mut Self::Context) -> Self::Result {
let user_id = self.connection.user_id;
@@ -780,7 +813,19 @@ impl StreamHandler<Result<irc_proto::Message, ProtocolError>> for Client {
});
ctx.spawn(fut);
}
Command::WHOIS(_, _) => {}
Command::WHOIS(Some(query), _) => {
let span = Span::current();
let fut = self
.server
.send(FetchWhois { span, query })
.into_actor(self)
.map(|result, this, _ctx| {
for message in result.unwrap().into_messages(&this.connection.nick) {
this.writer.write(message);
}
});
ctx.spawn(fut);
}
Command::WHOWAS(_, _, _) => {}
Command::KILL(_, _) => {}
Command::PING(v, _) => {
@@ -11,6 +11,7 @@ use std::{
use actix::{io::FramedWrite, Actor, Addr};
use bitflags::bitflags;
use chrono::Utc;
use const_format::concatcp;
use futures::{SinkExt, TryStreamExt};
use irc_proto::{
@@ -58,7 +59,8 @@ pub struct InitiatedConnection {
pub real_name: String,
pub user_id: UserId,
pub capabilities: Capability,
pub presence: bool,
pub away: Option<String>,
pub at: chrono::DateTime<Utc>,
}
impl InitiatedConnection {
@@ -97,7 +99,8 @@ impl TryFrom<ConnectionRequest> for InitiatedConnection {
real_name,
user_id,
capabilities,
presence: true,
away: None,
at: Utc::now(),
})
}
}
@@ -47,6 +47,13 @@ pub struct UserNickChange {
pub span: Span,
}
#[derive(Message, Clone)]
#[rtype(result = "Vec<(crate::channel::permissions::Permission, String)>")]
pub struct ConnectedChannels {
pub span: Span,
}
#[derive(Message, Clone)]
#[rtype(result = "super::server::response::ChannelList")]
@@ -62,6 +69,14 @@ pub struct FetchWhoList {
pub query: String,
}
#[derive(Message, Clone)]
#[rtype(result = "super::server::response::Whois")]
pub struct FetchWhois {
pub span: Span,
pub query: String,
}
#[derive(Message)]
#[rtype(
@@ -90,6 +105,14 @@ pub struct ChannelMemberList {
pub span: Span,
}
#[derive(Message)]
#[rtype(result = "crate::channel::permissions::Permission")]
pub struct FetchUserPermission {
pub span: Span,
pub user: UserId,
}
#[derive(Message)]
#[rtype(result = "super::channel::response::ChannelTopic")]
@@ -9,6 +9,7 @@ use actix::{
use actix_rt::Arbiter;
use clap::crate_version;
use futures::{
future,
stream::{FuturesOrdered, FuturesUnordered},
TryFutureExt,
};
@@ -24,12 +25,12 @@ use crate::{
connection::InitiatedConnection,
messages::{
Broadcast, ChannelFetchTopic, ChannelFetchWhoList, ChannelJoin, ChannelList,
ChannelMemberList, FetchClientByNick, FetchWhoList, MessageKind, PrivateMessage,
ServerAdminInfo, ServerDisconnect, ServerFetchMotd, ServerListUsers, UserConnected,
UserNickChange, UserNickChangeInternal,
ChannelMemberList, ConnectedChannels, FetchClientByNick, FetchWhoList, FetchWhois,
MessageKind, PrivateMessage, ServerAdminInfo, ServerDisconnect, ServerFetchMotd,
ServerListUsers, UserConnected, UserNickChange, UserNickChangeInternal,
},
persistence::Persistence,
server::response::{AdminInfo, ListUsers, Motd, WhoList},
server::response::{AdminInfo, ListUsers, Motd, WhoList, Whois},
SERVER_NAME,
};
@@ -229,6 +230,35 @@ impl Handler<FetchClientByNick> for Server {
}
}
impl Handler<FetchWhois> for Server {
type Result = ResponseFuture<<FetchWhois as actix::Message>::Result>;
#[instrument(parent = &msg.span, skip_all)]
fn handle(&mut self, msg: FetchWhois, _ctx: &mut Self::Context) -> Self::Result {
let Some((handle, conn)) = self.clients.iter().find(|(_, conn)| conn.nick == msg.query)
else {
return Box::pin(future::ready(Whois {
query: msg.query,
conn: None,
channels: vec![],
}));
};
let conn = conn.clone();
let channels = handle.send(ConnectedChannels {
span: Span::current(),
});
Box::pin(async move {
Whois {
query: msg.query,
conn: Some(conn),
channels: channels.await.unwrap(),
}
})
}
}
impl Handler<FetchWhoList> for Server {
type Result = ResponseFuture<<FetchWhoList as actix::Message>::Result>;
@@ -1,6 +1,88 @@
use irc_proto::{Command, Message, Prefix, Response};
use itertools::Itertools;
use crate::{server::Server, SERVER_NAME};
use crate::{
channel::permissions::Permission, connection::InitiatedConnection, server::Server, SERVER_NAME,
};
pub struct Whois {
pub query: String,
pub conn: Option<InitiatedConnection>,
pub channels: Vec<(Permission, String)>,
}
impl Whois {
#[must_use]
pub fn into_messages(self, for_user: &str) -> Vec<Message> {
macro_rules! msg {
($response:ident, $($payload:expr),*) => {
Message {
tags: None,
prefix: Some(Prefix::ServerName(SERVER_NAME.to_string())),
command: Command::Response(
Response::$response,
vec![for_user.to_string(), $($payload),*],
),
}
};
}
let Some(conn) = self.conn else {
return vec![msg!(ERR_NOSUCHNICK, self.query, "No such nick".to_string())];
};
let channels = self
.channels
.into_iter()
.map(|(perm, channel)| format!("{}{channel}", perm.into_prefix()))
.join(" ");
let mut out = vec![
msg!(
RPL_WHOISUSER,
conn.nick.to_string(),
conn.user,
"*".to_string(),
conn.real_name
),
msg!(
RPL_WHOISSERVER,
conn.nick.to_string(),
SERVER_NAME.to_string(),
SERVER_NAME.to_string()
),
msg!(
RPL_WHOISIDLE,
conn.nick.to_string(),
"0".to_string(),
conn.at.timestamp().to_string(),
"seconds idle, signon time".to_string()
), msg!(RPL_WHOISCHANNELS, conn.nick.to_string(), channels),
];
if let Some(msg) = conn.away {
out.push(msg!(RPL_AWAY, conn.nick.to_string(), msg));
}
out.push(msg!(
RPL_ENDOFWHOIS,
conn.nick.to_string(),
"End of /WHOIS list".to_string()
));
out
}
}
#[derive(Default)]
pub struct WhoList {