Implement WHO command
Diff
src/channel.rs | 18 ++++++--
src/channel/response.rs | 49 ++++++++++++++++++++++-
src/client.rs | 107 ++++++++++++++++++++++++++++++++++++++-----------
src/connection.rs | 2 +-
src/messages.rs | 15 +++++++-
src/server.rs | 54 ++++++++++++++++++++++---
src/server/response.rs | 32 +++++++++++++++-
text/info.txt | 8 ++++-
8 files changed, 252 insertions(+), 33 deletions(-)
@@ -17,15 +17,16 @@ use crate::{
permissions::Permission,
response::{
ChannelInviteResult, ChannelJoinRejectionReason, ChannelNamesList, ChannelTopic,
MissingPrivileges,
ChannelWhoList, MissingPrivileges,
},
},
client::Client,
connection::{Capability, InitiatedConnection, UserId},
messages::{
Broadcast, ChannelFetchTopic, ChannelInvite, ChannelJoin, ChannelKickUser,
ChannelMemberList, ChannelMessage, ChannelPart, ChannelSetMode, ChannelUpdateTopic,
FetchClientByNick, MessageKind, ServerDisconnect, UserKickedFromChannel, UserNickChange,
Broadcast, ChannelFetchTopic, ChannelFetchWhoList, ChannelInvite, ChannelJoin,
ChannelKickUser, ChannelMemberList, ChannelMessage, ChannelPart, ChannelSetMode,
ChannelUpdateTopic, FetchClientByNick, MessageKind, ServerDisconnect,
UserKickedFromChannel, UserNickChange,
},
persistence::{
events::{FetchAllUserChannelPermissions, SetUserChannelPermissions},
@@ -196,6 +197,15 @@ impl Handler<ChannelMessage> for Channel {
}
}
impl Handler<ChannelFetchWhoList> for Channel {
type Result = MessageResult<ChannelFetchWhoList>;
#[instrument(parent = &msg.span, skip_all)]
fn handle(&mut self, msg: ChannelFetchWhoList, _ctx: &mut Self::Context) -> Self::Result {
MessageResult(ChannelWhoList::new(self))
}
}
impl Handler<ChannelSetMode> for Channel {
type Result = ();
@@ -66,6 +66,55 @@ impl ChannelTopic {
}
}
pub struct ChannelWhoList {
pub channel_name: String,
pub nick_list: Vec<(Permission, InitiatedConnection)>,
}
impl ChannelWhoList {
#[must_use]
pub fn new(channel: &Channel) -> Self {
Self {
channel_name: channel.name.to_string(),
nick_list: channel
.clients
.values()
.map(|v| (channel.get_user_permissions(v.user_id), v.clone()))
.collect(),
}
}
#[must_use]
pub fn into_messages(self, for_user: &str) -> Vec<Message> {
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" };
out.push(Message {
tags: None,
prefix: Some(Prefix::ServerName(SERVER_NAME.to_string())),
command: Command::Response(
Response::RPL_WHOREPLY,
vec![
for_user.to_string(),
self.channel_name.to_string(),
conn.user,
conn.host.to_string(),
SERVER_NAME.to_string(),
conn.nick,
format!("{presence}{}", perm.into_prefix()), "0".to_string(),
conn.real_name,
],
),
});
}
out
}
}
pub struct ChannelNamesList {
pub channel_name: String,
pub nick_list: Vec<(Permission, InitiatedConnection)>,
@@ -2,12 +2,12 @@ use std::{collections::HashMap, time::Duration};
use actix::{
fut::wrap_future, io::WriteHandler, Actor, ActorContext, ActorFuture, ActorFutureExt, Addr,
AsyncContext, Context, Handler, MessageResult, ResponseActFuture, Running, StreamHandler,
WrapFuture,
AsyncContext, Context, Handler, MessageResult, ResponseActFuture, ResponseFuture, Running,
StreamHandler, WrapFuture,
};
use chrono::{DateTime, SecondsFormat, Utc};
use clap::{crate_name, crate_version};
use futures::FutureExt;
use futures::{future, stream::FuturesUnordered, FutureExt, StreamExt};
use irc_proto::{
error::ProtocolError, message::Tag, ChannelExt, Command, Message, Prefix, Response,
};
@@ -21,11 +21,11 @@ use crate::{
NickNotOwnedByUser,
},
messages::{
Broadcast, ChannelFetchTopic, ChannelInvite, ChannelJoin, ChannelKickUser, ChannelList,
ChannelMemberList, ChannelMessage, ChannelPart, ChannelSetMode, ChannelUpdateTopic,
FetchClientDetails, MessageKind, PrivateMessage, ServerAdminInfo, ServerDisconnect,
ServerFetchMotd, ServerListUsers, UserKickedFromChannel, UserNickChange,
UserNickChangeInternal,
Broadcast, ChannelFetchTopic, ChannelFetchWhoList, ChannelInvite, ChannelJoin,
ChannelKickUser, ChannelList, ChannelMemberList, ChannelMessage, ChannelPart,
ChannelSetMode, ChannelUpdateTopic, FetchClientDetails, FetchWhoList, MessageKind,
PrivateMessage, ServerAdminInfo, ServerDisconnect, ServerFetchMotd, ServerListUsers,
UserKickedFromChannel, UserNickChange, UserNickChangeInternal,
},
persistence::{
events::{
@@ -34,7 +34,7 @@ use crate::{
},
Persistence,
},
server::Server,
server::{response::WhoList, Server},
SERVER_NAME,
};
@@ -215,6 +215,31 @@ impl Handler<Broadcast> for Client {
}
}
impl Handler<FetchWhoList> for Client {
type Result = ResponseFuture<<FetchWhoList as actix::Message>::Result>;
fn handle(&mut self, msg: FetchWhoList, _ctx: &mut Self::Context) -> Self::Result {
let user_id = self.connection.user_id;
let futures = self
.channels
.values()
.map(|v| {
v.send(ChannelFetchWhoList {
span: msg.span.clone(),
})
})
.collect::<FuturesUnordered<_>>();
Box::pin(futures.fold(WhoList::default(), move |mut acc, item| {
let mut item = item.unwrap();
item.nick_list.retain(|(_, conn)| conn.user_id == user_id);
acc.list.push(item);
future::ready(acc)
}))
}
}
impl Handler<FetchClientDetails> for Client {
type Result = MessageResult<FetchClientDetails>;
@@ -258,19 +283,17 @@ impl Handler<JoinChannelRequest> for Client {
span: Span::current(),
});
futures.push(
futures::future::join(channel_handle_fut, channel_messages_fut).map(
move |(handle, messages)| {
(channel_name, handle.unwrap().unwrap(), messages.unwrap())
},
),
);
futures.push(future::join(channel_handle_fut, channel_messages_fut).map(
move |(handle, messages)| {
(channel_name, handle.unwrap().unwrap(), messages.unwrap())
},
));
}
let fut = wrap_future::<_, Self>(
futures::future::join_all(futures.into_iter()).instrument(Span::current()),
future::join_all(futures.into_iter()).instrument(Span::current()),
)
.map(|result, this, _ctx| {
for (channel_name, handle, messages) in result {
@@ -327,7 +350,7 @@ impl Handler<ListChannelMemberRequest> for Client {
let fut = wrap_future::<_, Self>(
futures::future::join_all(futures.into_iter()).instrument(Span::current()),
future::join_all(futures.into_iter()).instrument(Span::current()),
)
.map(|result, this, _ctx| {
for list in result {
@@ -488,7 +511,9 @@ impl StreamHandler<Result<irc_proto::Message, ProtocolError>> for Client {
span: Span::current(),
});
}
Command::UserMODE(_, _) => {}
Command::UserMODE(_, _) => {
}
Command::QUIT(message) => {
@@ -717,10 +742,44 @@ impl StreamHandler<Result<irc_proto::Message, ProtocolError>> for Client {
});
ctx.spawn(fut);
}
Command::INFO(_) => {}
Command::SERVLIST(_, _) => {}
Command::SQUERY(_, _) => {}
Command::WHO(_, _) => {}
Command::INFO(_) => {
static INFO_STR: &str = include_str!("../text/info.txt");
for line in INFO_STR.trim().split('\n') {
self.writer.write(Message {
tags: None,
prefix: None,
command: Command::Response(
Response::RPL_INFO,
vec![self.connection.nick.to_string(), line.to_string()],
),
});
}
self.writer.write(Message {
tags: None,
prefix: None,
command: Command::Response(
Response::RPL_ENDOFINFO,
vec![
self.connection.nick.to_string(),
"End of INFO list".to_string(),
],
),
});
}
Command::WHO(Some(mask), _) => {
let span = Span::current();
let fut = self
.server
.send(FetchWhoList { span, query: mask })
.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::WHOIS(_, _) => {}
Command::WHOWAS(_, _, _) => {}
Command::KILL(_, _) => {}
@@ -770,7 +829,7 @@ impl StreamHandler<Result<irc_proto::Message, ProtocolError>> for Client {
Command::BATCH(_, _, _) => {}
Command::CHGHOST(_, _) => {}
Command::Response(_, _) => {}
v @ _ => self.writer.write(Message {
v => self.writer.write(Message {
tags: None,
prefix: Some(Prefix::new_from_str(&self.connection.nick)),
command: Command::Response(
@@ -58,6 +58,7 @@ pub struct InitiatedConnection {
pub real_name: String,
pub user_id: UserId,
pub capabilities: Capability,
pub presence: bool,
}
impl InitiatedConnection {
@@ -96,6 +97,7 @@ impl TryFrom<ConnectionRequest> for InitiatedConnection {
real_name,
user_id,
capabilities,
presence: true,
})
}
}
@@ -54,6 +54,14 @@ pub struct ChannelList {
pub span: Span,
}
#[derive(Message, Clone)]
#[rtype(result = "super::server::response::WhoList")]
pub struct FetchWhoList {
pub span: Span,
pub query: String,
}
#[derive(Message)]
#[rtype(
@@ -89,6 +97,13 @@ pub struct ChannelFetchTopic {
pub span: Span,
}
#[derive(Message)]
#[rtype(result = "super::channel::response::ChannelWhoList")]
pub struct ChannelFetchWhoList {
pub span: Span,
}
#[derive(Message)]
#[rtype(result = "()")]
@@ -8,7 +8,10 @@ use actix::{
};
use actix_rt::Arbiter;
use clap::crate_version;
use futures::{stream::FuturesOrdered, TryFutureExt};
use futures::{
stream::{FuturesOrdered, FuturesUnordered},
TryFutureExt,
};
use irc_proto::{Command, Message, Prefix, Response};
use rand::seq::SliceRandom;
use tokio_stream::StreamExt;
@@ -20,12 +23,13 @@ use crate::{
config::Config,
connection::InitiatedConnection,
messages::{
Broadcast, ChannelFetchTopic, ChannelJoin, ChannelList, ChannelMemberList,
FetchClientByNick, MessageKind, PrivateMessage, ServerAdminInfo, ServerDisconnect,
ServerFetchMotd, ServerListUsers, UserConnected, UserNickChange, UserNickChangeInternal,
Broadcast, ChannelFetchTopic, ChannelFetchWhoList, ChannelJoin, ChannelList,
ChannelMemberList, FetchClientByNick, FetchWhoList, MessageKind, PrivateMessage,
ServerAdminInfo, ServerDisconnect, ServerFetchMotd, ServerListUsers, UserConnected,
UserNickChange, UserNickChangeInternal,
},
persistence::Persistence,
server::response::{AdminInfo, ListUsers, Motd},
server::response::{AdminInfo, ListUsers, Motd, WhoList},
SERVER_NAME,
};
@@ -225,6 +229,46 @@ impl Handler<FetchClientByNick> for Server {
}
}
impl Handler<FetchWhoList> for Server {
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 {
if let Some(channel) = self.channels.get(&msg.query).cloned() {
Box::pin(async move {
WhoList {
list: vec![channel
.send(ChannelFetchWhoList { span: msg.span })
.await
.unwrap()],
query: msg.query,
}
})
} else {
let futures = self
.clients
.iter()
.filter(|(_, conn)| conn.nick == msg.query)
.map(|(client, _)| {
client.send(FetchWhoList {
span: msg.span.clone(),
query: String::new(),
})
})
.collect::<FuturesUnordered<_>>();
let init = WhoList {
query: msg.query,
list: Vec::new(),
};
Box::pin(futures.fold(init, |mut acc, item| {
acc.list.extend(item.unwrap().list);
acc
}))
}
}
}
impl Handler<ChannelList> for Server {
type Result = ResponseFuture<<ChannelList as actix::Message>::Result>;
@@ -2,6 +2,38 @@ use irc_proto::{Command, Message, Prefix, Response};
use crate::{server::Server, SERVER_NAME};
#[derive(Default)]
pub struct WhoList {
pub list: Vec<crate::channel::response::ChannelWhoList>,
pub query: String,
}
impl WhoList {
#[must_use]
pub fn into_messages(self, for_user: &str) -> Vec<Message> {
let mut out: Vec<_> = self
.list
.into_iter()
.flat_map(|v| v.into_messages(for_user))
.collect();
out.push(Message {
tags: None,
prefix: Some(Prefix::ServerName(SERVER_NAME.to_string())),
command: Command::Response(
Response::RPL_ENDOFWHO,
vec![
for_user.to_string(),
self.query,
"End of WHO list".to_string(),
],
),
});
out
}
}
pub struct AdminInfo {
pub line1: String,
pub line2: String,
@@ -0,0 +1,8 @@
_____ _____ _____ ___ _ _ ___________ ___________
|_ _|_ _|_ _/ _ \ | \ | |_ _| ___ \/ __ \ _ \
| | | | | |/ /_\ \| \| | | | | |_/ /| / \/ | | |
| | | | | || _ || . ` | | | | / | | | | | |
| | _| |_ | || | | || |\ |_| |_| |\ \ | \__/\ |/ /
\_/ \___/ \_/\_| |_/\_| \_/\___/\_| \_| \____/___/
https://github.com/mitborg/titanirc