2023-09-15 16:33:25 +00:00
|
|
|
use nonempty::NonEmpty;
|
2023-02-10 21:27:29 +00:00
|
|
|
use tokio::io::AsyncWrite;
|
|
|
|
use tokio::io::AsyncWriteExt;
|
|
|
|
|
2023-02-09 19:26:05 +00:00
|
|
|
use super::*;
|
2023-09-22 13:20:47 +00:00
|
|
|
use crate::user::PrefixedNick;
|
2023-02-09 19:26:05 +00:00
|
|
|
|
|
|
|
/// Server-to-client message.
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
|
|
pub struct ServerMessage {
|
|
|
|
/// Optional tags section, prefixed with `@`
|
|
|
|
pub tags: Vec<Tag>,
|
|
|
|
/// Optional server name, prefixed with `:`.
|
2023-04-13 22:38:26 +00:00
|
|
|
pub sender: Option<Str>,
|
2023-02-09 19:26:05 +00:00
|
|
|
pub body: ServerMessageBody,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ServerMessage {
|
2023-02-10 21:27:29 +00:00
|
|
|
pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
|
2023-02-10 18:47:58 +00:00
|
|
|
match &self.sender {
|
|
|
|
Some(ref sender) => {
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b":").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(sender.as_bytes()).await?;
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b" ").await?;
|
2023-02-10 18:47:58 +00:00
|
|
|
}
|
|
|
|
None => {}
|
|
|
|
}
|
2023-02-10 21:27:29 +00:00
|
|
|
self.body.write_async(writer).await?;
|
2023-07-22 19:11:01 +00:00
|
|
|
writer.write_all(b"\r\n").await?;
|
2023-02-09 19:26:05 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
pub fn server_message(input: &str) -> IResult<&str, ServerMessage> {
|
2023-02-09 19:26:05 +00:00
|
|
|
let (input, command) = server_message_body(input)?;
|
2023-07-23 13:21:26 +00:00
|
|
|
let (input, _) = tag("\r\n")(input)?;
|
2023-02-09 19:26:05 +00:00
|
|
|
|
|
|
|
let message = ServerMessage {
|
|
|
|
tags: vec![],
|
|
|
|
sender: None,
|
|
|
|
body: command,
|
|
|
|
};
|
|
|
|
Ok((input, message))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
|
|
pub enum ServerMessageBody {
|
|
|
|
Notice {
|
2023-04-13 22:38:26 +00:00
|
|
|
first_target: Str,
|
|
|
|
rest_targets: Vec<Str>,
|
|
|
|
text: Str,
|
2023-02-09 19:26:05 +00:00
|
|
|
},
|
|
|
|
Ping {
|
2023-04-13 22:38:26 +00:00
|
|
|
token: Str,
|
2023-02-09 19:26:05 +00:00
|
|
|
},
|
|
|
|
Pong {
|
2023-04-13 22:38:26 +00:00
|
|
|
from: Str,
|
|
|
|
token: Str,
|
2023-02-09 19:26:05 +00:00
|
|
|
},
|
2023-02-13 17:08:37 +00:00
|
|
|
PrivateMessage {
|
|
|
|
target: Recipient,
|
2023-04-13 22:38:26 +00:00
|
|
|
body: Str,
|
2023-02-13 17:08:37 +00:00
|
|
|
},
|
2023-02-13 18:32:52 +00:00
|
|
|
Join(Chan),
|
2023-02-15 17:54:48 +00:00
|
|
|
Part(Chan),
|
2023-02-16 23:38:34 +00:00
|
|
|
Error {
|
2023-04-13 22:38:26 +00:00
|
|
|
reason: Str,
|
2023-02-16 23:38:34 +00:00
|
|
|
},
|
2023-12-08 02:47:09 +00:00
|
|
|
Cap {
|
|
|
|
target: Str,
|
|
|
|
subcmd: CapSubBody,
|
|
|
|
},
|
|
|
|
Authenticate(Str),
|
2023-02-10 18:47:58 +00:00
|
|
|
N001Welcome {
|
2023-04-13 22:38:26 +00:00
|
|
|
client: Str,
|
|
|
|
text: Str,
|
2023-02-10 18:47:58 +00:00
|
|
|
},
|
|
|
|
N002YourHost {
|
2023-04-13 22:38:26 +00:00
|
|
|
client: Str,
|
|
|
|
text: Str,
|
2023-02-10 18:47:58 +00:00
|
|
|
},
|
|
|
|
N003Created {
|
2023-04-13 22:38:26 +00:00
|
|
|
client: Str,
|
|
|
|
text: Str,
|
2023-02-10 18:47:58 +00:00
|
|
|
},
|
|
|
|
N004MyInfo {
|
2023-04-13 22:38:26 +00:00
|
|
|
client: Str,
|
|
|
|
hostname: Str,
|
|
|
|
softname: Str,
|
2023-02-10 18:47:58 +00:00
|
|
|
// TODO user modes, channel modes, channel modes with a parameter
|
|
|
|
},
|
|
|
|
N005ISupport {
|
2023-04-13 22:38:26 +00:00
|
|
|
client: Str,
|
|
|
|
params: Str, // TODO make this a datatype
|
2023-02-10 18:47:58 +00:00
|
|
|
},
|
2023-02-16 19:37:17 +00:00
|
|
|
/// Reply to a client's [Mode](crate::protos::irc::client::ClientMessage::Mode) request.
|
|
|
|
N221UserModeIs {
|
2023-04-13 22:38:26 +00:00
|
|
|
client: Str,
|
|
|
|
modes: Str,
|
2023-02-16 19:37:17 +00:00
|
|
|
},
|
2023-02-16 17:39:54 +00:00
|
|
|
/// Final reply to a client's [Who](crate::protos::irc::client::ClientMessage::Who) request.
|
|
|
|
N315EndOfWho {
|
2023-04-13 22:38:26 +00:00
|
|
|
client: Str,
|
2023-02-16 17:39:54 +00:00
|
|
|
mask: Recipient,
|
|
|
|
/// Usually `b"End of WHO list"`
|
2023-04-13 22:38:26 +00:00
|
|
|
msg: Str,
|
2023-02-16 17:39:54 +00:00
|
|
|
},
|
2023-02-13 18:32:52 +00:00
|
|
|
N332Topic {
|
2023-04-13 22:38:26 +00:00
|
|
|
client: Str,
|
2023-02-13 18:32:52 +00:00
|
|
|
chat: Chan,
|
2023-04-13 22:38:26 +00:00
|
|
|
topic: Str,
|
2023-02-13 18:32:52 +00:00
|
|
|
},
|
2023-02-16 17:39:54 +00:00
|
|
|
/// A reply to a client's [Who](crate::protos::irc::client::ClientMessage::Who) request.
|
|
|
|
N352WhoReply {
|
2023-04-13 22:38:26 +00:00
|
|
|
client: Str,
|
2023-02-16 17:39:54 +00:00
|
|
|
// chan = *
|
2023-04-13 22:38:26 +00:00
|
|
|
username: Str,
|
2023-02-16 17:39:54 +00:00
|
|
|
/// User's hostname
|
2023-04-13 22:38:26 +00:00
|
|
|
host: Str,
|
2023-02-16 17:39:54 +00:00
|
|
|
/// Hostname of the server the user is connected to
|
2023-04-13 22:38:26 +00:00
|
|
|
server: Str,
|
|
|
|
nickname: Str,
|
2023-02-16 17:39:54 +00:00
|
|
|
/// Flags
|
|
|
|
flags: AwayStatus,
|
|
|
|
hops: u8,
|
2023-04-13 22:38:26 +00:00
|
|
|
realname: Str,
|
2023-02-16 17:39:54 +00:00
|
|
|
},
|
2023-02-13 18:32:52 +00:00
|
|
|
N353NamesReply {
|
2023-04-13 22:38:26 +00:00
|
|
|
client: Str,
|
2023-02-13 18:32:52 +00:00
|
|
|
chan: Chan,
|
2023-09-15 16:33:25 +00:00
|
|
|
members: NonEmpty<PrefixedNick>,
|
2023-02-13 18:32:52 +00:00
|
|
|
},
|
|
|
|
N366NamesReplyEnd {
|
2023-04-13 22:38:26 +00:00
|
|
|
client: Str,
|
2023-02-13 18:32:52 +00:00
|
|
|
chan: Chan,
|
|
|
|
},
|
2023-02-16 21:49:17 +00:00
|
|
|
N474BannedFromChan {
|
2023-04-13 22:38:26 +00:00
|
|
|
client: Str,
|
2023-02-16 21:49:17 +00:00
|
|
|
chan: Chan,
|
2023-04-13 22:38:26 +00:00
|
|
|
message: Str,
|
2023-02-16 21:49:17 +00:00
|
|
|
},
|
2023-09-06 18:43:07 +00:00
|
|
|
N502UsersDontMatch {
|
|
|
|
client: Str,
|
|
|
|
message: Str,
|
|
|
|
},
|
2023-12-08 02:47:09 +00:00
|
|
|
N900LoggedIn {
|
|
|
|
nick: Str,
|
|
|
|
address: Str,
|
|
|
|
account: Str,
|
|
|
|
message: Str,
|
|
|
|
},
|
|
|
|
N903SaslSuccess {
|
|
|
|
nick: Str,
|
|
|
|
message: Str,
|
|
|
|
}
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ServerMessageBody {
|
2023-02-10 21:27:29 +00:00
|
|
|
pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
|
2023-02-09 19:26:05 +00:00
|
|
|
match self {
|
|
|
|
ServerMessageBody::Notice {
|
|
|
|
first_target,
|
|
|
|
rest_targets,
|
|
|
|
text,
|
|
|
|
} => {
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b"NOTICE ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(first_target.as_bytes()).await?;
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b" :").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(text.as_bytes()).await?;
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
ServerMessageBody::Ping { token } => {
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b"PING ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(token.as_bytes()).await?;
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
ServerMessageBody::Pong { from, token } => {
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b"PONG ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(from.as_bytes()).await?;
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b" :").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(token.as_bytes()).await?;
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
2023-02-13 17:08:37 +00:00
|
|
|
ServerMessageBody::PrivateMessage { target, body } => {
|
|
|
|
writer.write_all(b"PRIVMSG ").await?;
|
|
|
|
target.write_async(writer).await?;
|
|
|
|
writer.write_all(b" :").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(body.as_bytes()).await?;
|
2023-02-13 17:08:37 +00:00
|
|
|
}
|
2023-02-13 18:32:52 +00:00
|
|
|
ServerMessageBody::Join(chan) => {
|
|
|
|
writer.write_all(b"JOIN ").await?;
|
|
|
|
chan.write_async(writer).await?;
|
|
|
|
}
|
2023-02-15 17:54:48 +00:00
|
|
|
ServerMessageBody::Part(chan) => {
|
|
|
|
writer.write_all(b"PART ").await?;
|
|
|
|
chan.write_async(writer).await?;
|
|
|
|
}
|
2023-02-16 23:38:34 +00:00
|
|
|
ServerMessageBody::Error { reason } => {
|
|
|
|
writer.write_all(b"ERROR :").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(reason.as_bytes()).await?;
|
2023-02-16 23:38:34 +00:00
|
|
|
}
|
2023-12-08 02:47:09 +00:00
|
|
|
ServerMessageBody::Cap { target, subcmd } => {
|
|
|
|
writer.write_all(b"CAP ").await?;
|
|
|
|
writer.write_all(target.as_bytes()).await?;
|
|
|
|
match subcmd {
|
|
|
|
CapSubBody::Ls(caps) => {
|
|
|
|
writer.write_all(b" LS :").await?;
|
|
|
|
writer.write_all(caps.as_bytes()).await?;
|
|
|
|
}
|
|
|
|
CapSubBody::Ack(caps) => {
|
|
|
|
writer.write_all(b" ACK :").await?;
|
|
|
|
writer.write_all(caps.as_bytes()).await?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ServerMessageBody::Authenticate(body) => {
|
|
|
|
writer.write_all(b"AUTHENTICATE ").await?;
|
|
|
|
writer.write_all(body.as_bytes()).await?;
|
|
|
|
}
|
2023-02-10 18:47:58 +00:00
|
|
|
ServerMessageBody::N001Welcome { client, text } => {
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b"001 ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(client.as_bytes()).await?;
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b" :").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(text.as_bytes()).await?;
|
2023-02-10 18:47:58 +00:00
|
|
|
}
|
|
|
|
ServerMessageBody::N002YourHost { client, text } => {
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b"002 ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(client.as_bytes()).await?;
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b" :").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(text.as_bytes()).await?;
|
2023-02-10 18:47:58 +00:00
|
|
|
}
|
|
|
|
ServerMessageBody::N003Created { client, text } => {
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b"003 ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(client.as_bytes()).await?;
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b" :").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(text.as_bytes()).await?;
|
2023-02-10 18:47:58 +00:00
|
|
|
}
|
|
|
|
ServerMessageBody::N004MyInfo {
|
|
|
|
client,
|
|
|
|
hostname,
|
|
|
|
softname,
|
|
|
|
} => {
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b"004 ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(client.as_bytes()).await?;
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b" ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(hostname.as_bytes()).await?;
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b" ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(softname.as_bytes()).await?;
|
2023-02-16 21:49:17 +00:00
|
|
|
writer.write_all(b" r CFILPQbcefgijklmnopqrstvz").await?;
|
2023-02-10 18:47:58 +00:00
|
|
|
// TODO remove hardcoded modes
|
|
|
|
}
|
|
|
|
ServerMessageBody::N005ISupport { client, params } => {
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b"005 ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(client.as_bytes()).await?;
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b" ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(params.as_bytes()).await?;
|
2023-02-10 21:27:29 +00:00
|
|
|
writer.write_all(b" :are supported by this server").await?;
|
2023-02-10 18:47:58 +00:00
|
|
|
}
|
2023-02-16 19:37:17 +00:00
|
|
|
ServerMessageBody::N221UserModeIs { client, modes } => {
|
|
|
|
writer.write_all(b"221 ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(client.as_bytes()).await?;
|
2023-02-16 19:37:17 +00:00
|
|
|
writer.write_all(b" ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(modes.as_bytes()).await?;
|
2023-02-16 21:49:17 +00:00
|
|
|
}
|
2023-02-16 17:39:54 +00:00
|
|
|
ServerMessageBody::N315EndOfWho { client, mask, msg } => {
|
|
|
|
writer.write_all(b"315 ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(client.as_bytes()).await?;
|
2023-02-16 17:39:54 +00:00
|
|
|
writer.write_all(b" ").await?;
|
|
|
|
mask.write_async(writer).await?;
|
|
|
|
writer.write_all(b" :").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(msg.as_bytes()).await?;
|
2023-02-16 17:39:54 +00:00
|
|
|
}
|
2023-02-13 18:32:52 +00:00
|
|
|
ServerMessageBody::N332Topic {
|
|
|
|
client,
|
|
|
|
chat,
|
|
|
|
topic,
|
|
|
|
} => {
|
|
|
|
writer.write_all(b"332 ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(client.as_bytes()).await?;
|
2023-02-13 18:32:52 +00:00
|
|
|
writer.write_all(b" ").await?;
|
|
|
|
chat.write_async(writer).await?;
|
|
|
|
writer.write_all(b" :").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(topic.as_bytes()).await?;
|
2023-02-13 18:32:52 +00:00
|
|
|
}
|
2023-02-16 17:39:54 +00:00
|
|
|
ServerMessageBody::N352WhoReply {
|
|
|
|
client,
|
|
|
|
username,
|
|
|
|
host,
|
|
|
|
server,
|
|
|
|
flags,
|
2023-02-16 18:33:36 +00:00
|
|
|
nickname,
|
2023-02-16 17:39:54 +00:00
|
|
|
hops,
|
|
|
|
realname,
|
|
|
|
} => {
|
|
|
|
writer.write_all(b"352 ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(client.as_bytes()).await?;
|
2023-02-16 17:39:54 +00:00
|
|
|
writer.write_all(b" * ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(username.as_bytes()).await?;
|
2023-02-16 17:39:54 +00:00
|
|
|
writer.write_all(b" ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(host.as_bytes()).await?;
|
2023-02-16 17:39:54 +00:00
|
|
|
writer.write_all(b" ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(server.as_bytes()).await?;
|
2023-02-16 17:39:54 +00:00
|
|
|
writer.write_all(b" ").await?;
|
|
|
|
match flags {
|
|
|
|
AwayStatus::Here => writer.write_all(b"H").await?,
|
|
|
|
AwayStatus::Gone => writer.write_all(b"G").await?,
|
|
|
|
}
|
2023-02-16 18:33:36 +00:00
|
|
|
writer.write_all(b" ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(nickname.as_bytes()).await?;
|
2023-02-16 17:39:54 +00:00
|
|
|
writer.write_all(b" :").await?;
|
|
|
|
writer.write_all(hops.to_string().as_bytes()).await?;
|
|
|
|
writer.write_all(b" ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(realname.as_bytes()).await?;
|
2023-02-16 17:39:54 +00:00
|
|
|
}
|
2023-02-13 18:32:52 +00:00
|
|
|
ServerMessageBody::N353NamesReply {
|
|
|
|
client,
|
|
|
|
chan,
|
|
|
|
members,
|
|
|
|
} => {
|
|
|
|
writer.write_all(b"353 ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(client.as_bytes()).await?;
|
2023-02-13 18:32:52 +00:00
|
|
|
writer.write_all(b" = ").await?;
|
|
|
|
chan.write_async(writer).await?;
|
|
|
|
writer.write_all(b" :").await?;
|
2023-09-15 16:33:25 +00:00
|
|
|
for member in members {
|
2023-09-22 13:20:47 +00:00
|
|
|
writer
|
|
|
|
.write_all(member.prefix.to_string().as_bytes())
|
|
|
|
.await?;
|
2023-09-15 16:33:25 +00:00
|
|
|
writer.write_all(member.nick.as_bytes()).await?;
|
|
|
|
writer.write_all(b" ").await?;
|
|
|
|
}
|
2023-02-13 18:32:52 +00:00
|
|
|
}
|
|
|
|
ServerMessageBody::N366NamesReplyEnd { client, chan } => {
|
|
|
|
writer.write_all(b"366 ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(client.as_bytes()).await?;
|
2023-02-13 18:32:52 +00:00
|
|
|
writer.write_all(b" ").await?;
|
|
|
|
chan.write_async(writer).await?;
|
|
|
|
writer.write_all(b" :End of /NAMES list").await?;
|
|
|
|
}
|
2023-02-16 21:49:17 +00:00
|
|
|
ServerMessageBody::N474BannedFromChan {
|
|
|
|
client,
|
|
|
|
chan,
|
|
|
|
message,
|
|
|
|
} => {
|
|
|
|
writer.write_all(b"474 ").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(client.as_bytes()).await?;
|
2023-02-16 21:49:17 +00:00
|
|
|
writer.write_all(b" ").await?;
|
|
|
|
chan.write_async(writer).await?;
|
|
|
|
writer.write_all(b" :").await?;
|
2023-09-06 18:43:07 +00:00
|
|
|
writer.write_all(message.as_bytes()).await?;
|
|
|
|
}
|
|
|
|
ServerMessageBody::N502UsersDontMatch { client, message } => {
|
|
|
|
writer.write_all(b"502 ").await?;
|
|
|
|
writer.write_all(client.as_bytes()).await?;
|
|
|
|
writer.write_all(b" ").await?;
|
|
|
|
writer.write_all(b" :").await?;
|
2023-04-13 19:15:48 +00:00
|
|
|
writer.write_all(message.as_bytes()).await?;
|
2023-02-16 21:49:17 +00:00
|
|
|
}
|
2023-12-08 02:47:09 +00:00
|
|
|
ServerMessageBody::N900LoggedIn { nick, address, account, message } => {
|
|
|
|
writer.write_all(b"900 ").await?;
|
|
|
|
writer.write_all(nick.as_bytes()).await?;
|
|
|
|
writer.write_all(b" ").await?;
|
|
|
|
writer.write_all(address.as_bytes()).await?;
|
|
|
|
writer.write_all(b" ").await?;
|
|
|
|
writer.write_all(account.as_bytes()).await?;
|
|
|
|
writer.write_all(b" :").await?;
|
|
|
|
writer.write_all(message.as_bytes()).await?;
|
|
|
|
}
|
|
|
|
ServerMessageBody::N903SaslSuccess { nick, message } => {
|
|
|
|
writer.write_all(b"903 ").await?;
|
|
|
|
writer.write_all(nick.as_bytes()).await?;
|
|
|
|
writer.write_all(b" :").await?;
|
|
|
|
writer.write_all(message.as_bytes()).await?;
|
|
|
|
}
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-08 02:47:09 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
|
|
pub enum CapSubBody {
|
|
|
|
Ls(Str),
|
|
|
|
Ack(Str),
|
|
|
|
}
|
|
|
|
|
2023-02-16 17:39:54 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
|
|
pub enum AwayStatus {
|
|
|
|
Here,
|
|
|
|
Gone,
|
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn server_message_body(input: &str) -> IResult<&str, ServerMessageBody> {
|
2023-02-09 19:26:05 +00:00
|
|
|
alt((
|
|
|
|
server_message_body_notice,
|
|
|
|
server_message_body_ping,
|
|
|
|
server_message_body_pong,
|
2023-12-08 02:47:09 +00:00
|
|
|
server_message_body_cap,
|
2023-02-09 19:26:05 +00:00
|
|
|
))(input)
|
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn server_message_body_notice(input: &str) -> IResult<&str, ServerMessageBody> {
|
2023-02-09 19:26:05 +00:00
|
|
|
let (input, _) = tag("NOTICE ")(input)?;
|
|
|
|
let (input, first_target) = receiver(input)?;
|
|
|
|
let (input, _) = tag(" :")(input)?;
|
|
|
|
let (input, text) = token(input)?;
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
let first_target = first_target.into();
|
|
|
|
let text = text.into();
|
2023-02-09 19:26:05 +00:00
|
|
|
Ok((
|
|
|
|
input,
|
|
|
|
ServerMessageBody::Notice {
|
|
|
|
first_target,
|
|
|
|
rest_targets: vec![],
|
|
|
|
text,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn server_message_body_ping(input: &str) -> IResult<&str, ServerMessageBody> {
|
2023-02-09 19:26:05 +00:00
|
|
|
let (input, _) = tag("PING ")(input)?;
|
|
|
|
let (input, token) = token(input)?;
|
|
|
|
|
2023-12-08 02:47:09 +00:00
|
|
|
Ok((input, ServerMessageBody::Ping { token: token.into() }))
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
fn server_message_body_pong(input: &str) -> IResult<&str, ServerMessageBody> {
|
2023-02-09 19:26:05 +00:00
|
|
|
let (input, _) = tag("PONG ")(input)?;
|
|
|
|
let (input, from) = receiver(input)?;
|
|
|
|
let (input, _) = tag(" :")(input)?;
|
|
|
|
let (input, token) = token(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
input,
|
|
|
|
ServerMessageBody::Pong {
|
2023-04-13 19:15:48 +00:00
|
|
|
from: from.into(),
|
|
|
|
token: token.into(),
|
2023-02-09 19:26:05 +00:00
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2023-12-08 02:47:09 +00:00
|
|
|
fn server_message_body_cap(input: &str) -> IResult<&str, ServerMessageBody> {
|
|
|
|
let (input, _) = tag("CAP ")(input)?;
|
|
|
|
let (input, from) = receiver(input)?;
|
|
|
|
let (input, _) = tag(" LS :")(input)?;
|
|
|
|
let (input, token) = token(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
input,
|
|
|
|
ServerMessageBody::Cap {
|
|
|
|
target: from.into(),
|
|
|
|
subcmd: CapSubBody::Ls(token.into()),
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2023-02-09 19:26:05 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use assert_matches::*;
|
|
|
|
|
|
|
|
use super::*;
|
2023-09-22 13:20:47 +00:00
|
|
|
use crate::testkit::*;
|
2023-02-09 19:26:05 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_server_message_notice() {
|
2023-07-23 13:21:26 +00:00
|
|
|
let input = "NOTICE * :*** Looking up your hostname...\r\n";
|
2023-02-09 19:26:05 +00:00
|
|
|
let expected = ServerMessage {
|
|
|
|
tags: vec![],
|
|
|
|
sender: None,
|
|
|
|
body: ServerMessageBody::Notice {
|
2023-04-13 19:15:48 +00:00
|
|
|
first_target: "*".into(),
|
2023-02-09 19:26:05 +00:00
|
|
|
rest_targets: vec![],
|
2023-04-13 19:15:48 +00:00
|
|
|
text: "*** Looking up your hostname...".into(),
|
2023-02-09 19:26:05 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = server_message(input);
|
|
|
|
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
|
|
|
|
|
|
|
|
let mut bytes = vec![];
|
2023-12-08 02:47:09 +00:00
|
|
|
sync_future(expected.write_async(&mut bytes)).unwrap().unwrap();
|
2023-04-13 19:15:48 +00:00
|
|
|
assert_eq!(bytes, input.as_bytes());
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_server_message_pong() {
|
2023-07-23 13:21:26 +00:00
|
|
|
let input = "PONG server.example :LAG004911\r\n";
|
2023-02-09 19:26:05 +00:00
|
|
|
let expected = ServerMessage {
|
|
|
|
tags: vec![],
|
|
|
|
sender: None,
|
|
|
|
body: ServerMessageBody::Pong {
|
2023-04-13 19:15:48 +00:00
|
|
|
from: "server.example".into(),
|
|
|
|
token: "LAG004911".into(),
|
2023-02-09 19:26:05 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = server_message(input);
|
|
|
|
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
|
|
|
|
|
|
|
|
let mut bytes = vec![];
|
2023-12-08 02:47:09 +00:00
|
|
|
sync_future(expected.write_async(&mut bytes)).unwrap().unwrap();
|
|
|
|
assert_eq!(bytes, input.as_bytes());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_server_message_cap_ls() {
|
|
|
|
let input = "CAP * LS :sasl\r\n";
|
|
|
|
let expected = ServerMessage {
|
|
|
|
tags: vec![],
|
|
|
|
sender: None,
|
|
|
|
body: ServerMessageBody::Cap {
|
|
|
|
target: "*".into(),
|
|
|
|
subcmd: CapSubBody::Ls("sasl".into()),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = server_message(input);
|
|
|
|
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
|
|
|
|
|
|
|
|
let mut bytes = vec![];
|
|
|
|
sync_future(expected.write_async(&mut bytes)).unwrap().unwrap();
|
2023-04-13 19:15:48 +00:00
|
|
|
assert_eq!(bytes, input.as_bytes());
|
2023-02-09 19:26:05 +00:00
|
|
|
}
|
|
|
|
}
|