Implement NAMES command
Diff
src/channel.rs | 6 +++---
src/client.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
src/connection.rs | 1 +
src/messages.rs | 2 +-
src/channel/response.rs | 18 +++++++++++++++++-
5 files changed, 96 insertions(+), 19 deletions(-)
@@ -1,4 +1,4 @@
mod response;
pub mod response;
use std::collections::HashMap;
@@ -12,9 +12,9 @@
connection::InitiatedConnection,
messages::{
Broadcast, ChannelJoin, ChannelList, ChannelMessage, ChannelPart, ServerDisconnect,
UserNickChange,
},
};
use crate::messages::UserNickChange;
@@ -45,7 +45,7 @@
#[instrument(parent = &msg.span, skip_all)]
fn handle(&mut self, msg: ChannelList, _ctx: &mut Self::Context) -> Self::Result {
MessageResult(self.clients.values().cloned().collect())
MessageResult(ChannelNamesList::new(self))
}
}
@@ -7,14 +7,14 @@
use futures::FutureExt;
use irc_proto::{error::ProtocolError, ChannelExt, Command, Message};
use tokio::time::Instant;
use tracing::{error, info_span, instrument, warn, Instrument, Span, debug};
use tracing::{debug, error, info_span, instrument, warn, Instrument, Span};
use crate::{
channel::Channel,
connection::{InitiatedConnection, MessageSink},
messages::{
Broadcast, ChannelJoin, ChannelMessage, ChannelPart, FetchClientDetails, ServerDisconnect,
UserNickChange,
Broadcast, ChannelJoin, ChannelList, ChannelMessage, ChannelPart, FetchClientDetails,
ServerDisconnect, UserNickChange,
},
server::Server,
SERVER_NAME,
@@ -133,7 +133,7 @@
}
@@ -180,6 +180,45 @@
}
}
impl Handler<ListChannelMemberRequest> for Client {
type Result = ResponseActFuture<Self, ()>;
#[instrument(parent = &msg.span, skip_all)]
fn handle(&mut self, msg: ListChannelMemberRequest, _ctx: &mut Self::Context) -> Self::Result {
let mut futures = Vec::with_capacity(msg.channels.len());
for (channel_name, handle) in &self.channels {
if !msg.channels.contains(channel_name) {
continue;
}
futures.push(handle.send(ChannelList {
span: Span::current(),
}));
}
let fut = wrap_future::<_, Self>(
futures::future::join_all(futures.into_iter()).instrument(Span::current()),
)
.map(|result, this, _ctx| {
for list in result {
let list = list.unwrap();
for message in list.into_messages(this.connection.nick.clone()) {
this.writer.write(message);
}
}
});
Box::pin(fut)
}
}
impl Handler<UserNickChange> for Client {
@@ -263,11 +302,7 @@
Command::SQUIT(_, _) => {}
Command::JOIN(channel_names, _passwords, _real_name) => {
let channels = channel_names
.split(',')
.filter(|v| !v.is_empty())
.map(ToString::to_string)
.collect();
let channels = parse_channel_name_list(&channel_names);
ctx.notify(JoinChannelRequest {
@@ -290,7 +325,21 @@
}
Command::ChannelMODE(_, _) => {}
Command::TOPIC(_, _) => {}
Command::NAMES(_, _) => {}
Command::NAMES(channel_names, _) => {
let channels = parse_channel_name_list(channel_names.as_deref().unwrap_or(""));
if channels.is_empty() {
warn!("Client didn't request names for a particular channel");
return;
}
ctx.notify(ListChannelMemberRequest {
channels,
span: Span::current(),
});
}
Command::LIST(_, _) => {}
Command::INVITE(_, _) => {}
Command::KICK(_, _, _) => {}
@@ -361,6 +410,13 @@
Command::Raw(_, _) => {}
}
}
}
pub fn parse_channel_name_list(s: &str) -> Vec<String> {
s.split(',')
.filter(|v| !v.is_empty())
.map(ToString::to_string)
.collect()
}
@@ -370,12 +426,20 @@
error!(%error, "Failed to write message to client");
Running::Continue
}
}
#[derive(actix::Message, Debug)]
#[rtype(result = "()")]
struct ListChannelMemberRequest {
channels: Vec<String>,
span: Span,
}
#[derive(actix::Message, Debug)]
#[rtype(result = "()")]
pub struct JoinChannelRequest {
struct JoinChannelRequest {
channels: Vec<String>,
span: Span,
}
@@ -78,6 +78,7 @@
request.mode = Some(mode);
request.real_name = Some(real_name);
}
Command::AUTHENTICATE(_) => {}
Command::CAP(_, _, _, _) => {}
_ => {
warn!(?msg, "Client sent unknown command during negotiation");
@@ -54,7 +54,7 @@
#[derive(Message)]
#[rtype(result = "Vec<crate::connection::InitiatedConnection>")]
#[rtype(result = "super::channel::response::ChannelNamesList")]
pub struct ChannelList {
pub span: Span,
}
@@ -8,6 +8,7 @@
}
impl ChannelTopic {
#[must_use]
pub fn new(channel: &Channel) -> Self {
Self {
channel_name: channel.name.to_string(),
@@ -15,8 +16,9 @@
}
}
#[must_use]
pub fn into_message(self, for_user: String) -> Message {
irc_proto::Message {
Message {
tags: None,
prefix: Some(Prefix::ServerName(SERVER_NAME.to_string())),
command: Command::Response(
@@ -29,10 +31,11 @@
pub struct ChannelNamesList {
channel_name: String,
nick_list: Vec<String>,
pub nick_list: Vec<String>,
}
impl ChannelNamesList {
#[must_use]
pub fn new(channel: &Channel) -> Self {
Self {
channel_name: channel.name.to_string(),
@@ -44,9 +47,18 @@
}
}
#[must_use]
pub const fn empty(channel_name: String) -> Self {
Self {
channel_name,
nick_list: vec![],
}
}
#[must_use]
pub fn into_messages(self, for_user: String) -> Vec<Message> {
vec![
irc_proto::Message {
Message {
tags: None,
prefix: Some(Prefix::ServerName(SERVER_NAME.to_string())),
command: Command::Response(