Update reg handler to use client state

The handler now, instead of returning Registration,
sets the Client's internal ClientState.
This means useful state info is more readily-available to handlers.

Updated examples to match.
This commit is contained in:
TheDaemoness
2024-04-28 09:41:23 -07:00
parent 1ebc86cc5e
commit b2f40896bf
8 changed files with 70 additions and 37 deletions

View File

@@ -47,14 +47,17 @@ fn main() -> std::io::Result<()> {
// so we can ignore it. // so we can ignore it.
client.run()?; client.run()?;
// If we're here, the handler finished and gave us a value back. // If we're here, the handler finished and gave us a value back.
// Let's fetch it and see what it is! // The registration handler only tells us what the error was, if it failed.
let reg = reg_result.0.recv_now().unwrap()?; // So, let's check to see if we made it.
reg_result.0.recv_now().unwrap()?;
// Connection registration is done! // Connection registration is done!
// But how does the network we connected to choose to name itself? // Now, what is our nick? `Client` can store information about the current session
// ISUPPORT is vital for understanding the capabilities of the target network, // for use by other handlers. We can access that information through the `state` method.
// and vinezombie eagerly parses it during registration. Let's print the network name. // While we're at it, let's get the network name from ISUPPORT.
let network_name = reg.isupport.get_parsed(vinezombie::names::isupport::NETWORK).transpose()?; let nick = &client.state().get::<vinezombie::client::state::ClientSource>().unwrap().nick;
tracing::info!("{} connected to {}!", reg.nick, network_name.unwrap_or(Word::from_str("IRC"))); let isupport = client.state().get::<vinezombie::client::state::ISupport>().unwrap();
let network_name = isupport.get_parsed(vinezombie::names::isupport::NETWORK).transpose()?;
tracing::info!("{} connected to {}!", nick, network_name.unwrap_or(Word::from_str("IRC")));
// From here, we can keep reading messages (including 004 and 005) // From here, we can keep reading messages (including 004 and 005)
// but we don't care about any of that, so let's just quit. // but we don't care about any of that, so let's just quit.
// We create the message, push it onto the internal message queue, // We create the message, push it onto the internal message queue,

View File

@@ -27,10 +27,12 @@ async fn main() -> std::io::Result<()> {
// but instead we run it using a run_tokio function. // but instead we run it using a run_tokio function.
let (_id, reg_result) = client.add(&register_as_bot(), &options).unwrap(); let (_id, reg_result) = client.add(&register_as_bot(), &options).unwrap();
client.run_tokio().await?; client.run_tokio().await?;
let reg = reg_result.await.unwrap()?; reg_result.await.unwrap()?;
let network_name = reg.isupport.get_parsed(vinezombie::names::isupport::NETWORK).transpose()?; // Almost everything past this point is the same as the sync example.
tracing::info!("{} connected to {}!", reg.nick, network_name.unwrap_or(Word::from_str("IRC"))); let nick = &client.state().get::<vinezombie::client::state::ClientSource>().unwrap().nick;
// As with the earlier example, let's just quit here. let isupport = client.state().get::<vinezombie::client::state::ISupport>().unwrap();
let network_name = isupport.get_parsed(vinezombie::names::isupport::NETWORK).transpose()?;
tracing::info!("{} connected to {}!", nick, network_name.unwrap_or(Word::from_str("IRC")));
let msg = ClientMsg::new(vinezombie::names::cmd::QUIT); let msg = ClientMsg::new(vinezombie::names::cmd::QUIT);
client.queue_mut().edit().push(msg); client.queue_mut().edit().push(msg);
client.run_tokio().await?; client.run_tokio().await?;

View File

@@ -6,6 +6,7 @@ use vinezombie::{
conn::ServerAddr, conn::ServerAddr,
handlers::{AutoPong, YieldParsed}, handlers::{AutoPong, YieldParsed},
register::{register_as_bot, Options}, register::{register_as_bot, Options},
state::ClientSource,
Client, Client,
}, },
ircmsg::ClientMsg, ircmsg::ClientMsg,
@@ -30,8 +31,9 @@ async fn main() -> std::io::Result<()> {
let mut client = Client::new(sock, TokioChannels); let mut client = Client::new(sock, TokioChannels);
let (_id, reg_result) = client.add(&register_as_bot(), &options).unwrap(); let (_id, reg_result) = client.add(&register_as_bot(), &options).unwrap();
client.run_tokio().await?; client.run_tokio().await?;
reg_result.await.unwrap()?;
// The only piece of reg info we care about for this example is our nick. // The only piece of reg info we care about for this example is our nick.
let nick = reg_result.await.unwrap()?.nick; let nick = client.state().get::<ClientSource>().unwrap().nick.clone();
tracing::info!("nick: {}", nick); tracing::info!("nick: {}", nick);
// Let's add a handler to auto-reply to PING messages for us. Most IRC networks need this. // Let's add a handler to auto-reply to PING messages for us. Most IRC networks need this.
// Do this before anything else. // Do this before anything else.

View File

@@ -7,6 +7,7 @@ use vinezombie::{
conn::{ServerAddr, Stream}, conn::{ServerAddr, Stream},
handlers::{AutoPong, YieldParsed}, handlers::{AutoPong, YieldParsed},
register::{register_as_bot, Options}, register::{register_as_bot, Options},
state::ClientSource,
tls::TlsConfig, tls::TlsConfig,
Client, Client,
}, },
@@ -49,7 +50,7 @@ fn main() -> std::io::Result<()> {
loop { loop {
let (_, reg_result) = client.add(&register_as_bot(), &options).unwrap(); let (_, reg_result) = client.add(&register_as_bot(), &options).unwrap();
client.run()?; client.run()?;
let nick = reg_result.0.recv_now().unwrap()?.nick; reg_result.0.recv_now().unwrap()?;
let _ = client.add((), AutoPong); let _ = client.add((), AutoPong);
// As we can interact with this bot, let's add a handler to auto-reply to // As we can interact with this bot, let's add a handler to auto-reply to
// CTCP VERSION and CTCP SOURCE. // CTCP VERSION and CTCP SOURCE.
@@ -61,7 +62,7 @@ fn main() -> std::io::Result<()> {
// This time we're actually going to use that information, // This time we're actually going to use that information,
// starting by saving the id of the message handler. // starting by saving the id of the message handler.
let (id, msgs) = client.add((), YieldParsed::just(PRIVMSG)).unwrap(); let (id, msgs) = client.add((), YieldParsed::just(PRIVMSG)).unwrap();
tracing::info!("bot {nick} ready for 'q'~"); tracing::info!("bot {} ready for 'q'~", client.state().get::<ClientSource>().unwrap().nick);
loop { loop {
let Ok(result) = client.run() else { let Ok(result) = client.run() else {
tracing::info!("connection broke, making new connection"); tracing::info!("connection broke, making new connection");

View File

@@ -5,6 +5,7 @@ use vinezombie::{
auth::{sasl::Password, Clear, Secret}, auth::{sasl::Password, Clear, Secret},
channel::TokioChannels, channel::TokioChannels,
register::{register_as_bot, Options}, register::{register_as_bot, Options},
state::Account,
Client, Client,
}, },
string::{tf::TrimAscii, Line, NoNul}, string::{tf::TrimAscii, Line, NoNul},
@@ -46,9 +47,9 @@ async fn main() -> std::io::Result<()> {
let mut client = Client::new(sock, TokioChannels); let mut client = Client::new(sock, TokioChannels);
let (_id, reg_result) = client.add(&register_as_bot(), &options).unwrap(); let (_id, reg_result) = client.add(&register_as_bot(), &options).unwrap();
client.run_tokio().await?; client.run_tokio().await?;
let reg = reg_result.await.unwrap()?; reg_result.await.unwrap()?;
// Who'd we log in as? // Who'd we log in as?
if let Some(account) = reg.account { if let Some(account) = client.state().get::<Account>().unwrap() {
tracing::info!("Logged in as {account}"); tracing::info!("Logged in as {account}");
} else { } else {
// We should never get here unless `options.allow_sasl_fail` is set to `true`. // We should never get here unless `options.allow_sasl_fail` is set to `true`.

View File

@@ -21,6 +21,11 @@ use crate::{
/// ///
/// Consider using the [`register_as_bot()`], [`register_as_client()`], /// Consider using the [`register_as_bot()`], [`register_as_client()`],
/// or [`register_as_custom()`] functions to instantiate one of these. /// or [`register_as_custom()`] functions to instantiate one of these.
///
/// The handler returned by using this type signals completion over its channel.
/// Most of the useful data about client registration is added to the client as shared state.
/// In particular, connection statistics and the MOTD are NOT stored,
/// and should be read using a different handler.
#[derive(Clone)] #[derive(Clone)]
pub struct Register<O> { pub struct Register<O> {
/// Returns the server password, if any. /// Returns the server password, if any.
@@ -110,7 +115,7 @@ impl<O> Register<O> {
} }
impl<'a, O> MakeHandler<&'a O> for &'a Register<O> { impl<'a, O> MakeHandler<&'a O> for &'a Register<O> {
type Value = Result<Registration, HandlerError>; type Value = Result<(), HandlerError>;
type Error = std::convert::Infallible; type Error = std::convert::Infallible;

View File

@@ -1,5 +1,6 @@
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use super::CapFn;
use crate::{ use crate::{
client::{ client::{
auth::{self, SaslQueue}, auth::{self, SaslQueue},
@@ -14,9 +15,7 @@ use crate::{
string::{Arg, Key, Line, Nick, Splitter, Word}, string::{Arg, Key, Line, Nick, Splitter, Word},
}; };
use super::CapFn; /// A useful subset of information yielded by client registration.
/// The result of successful registration.
#[derive(Clone, PartialEq, Eq, Hash, Debug)] #[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Registration { pub struct Registration {
/// The nickname used for this connection. /// The nickname used for this connection.
@@ -55,6 +54,21 @@ impl Registration {
isupport: NameMap::new(), isupport: NameMap::new(),
} }
} }
/// Saves registration to a [`ClientState`][crate::client::ClientState].
pub fn save(self, state: &mut crate::client::ClientState) {
use crate::client::state::*;
let source = Source { nick: self.nick, userhost: self.userhost };
state.insert::<ClientSource>(source);
state.insert::<Account>(self.account);
state.insert::<Caps>(self.caps);
state.insert::<ISupport>(self.isupport);
if let Some(server_source) = self.source {
state.insert::<ServerSource>(server_source);
}
if let Some(version) = self.version {
state.insert::<ServerVersion>(version);
}
}
} }
impl Registration { impl Registration {
@@ -224,11 +238,7 @@ impl Handler {
reg: Registration::new(nick), reg: Registration::new(nick),
} }
} }
/// Handles a server message sent during connection registration. fn handle(
///
/// It is a logic error to call `handle` after
/// it errors or returns `Ok(Done)`.
pub fn handle(
&mut self, &mut self,
msg: &ServerMsg<'_>, msg: &ServerMsg<'_>,
mut sink: impl ClientMsgSink<'static>, mut sink: impl ClientMsgSink<'static>,
@@ -515,18 +525,19 @@ impl Handler {
} }
impl crate::client::Handler for Handler { impl crate::client::Handler for Handler {
type Value = Result<Registration, HandlerError>; type Value = Result<(), HandlerError>;
fn handle( fn handle(
&mut self, &mut self,
msg: &ServerMsg<'_>, msg: &ServerMsg<'_>,
_: &mut crate::client::ClientState, state: &mut crate::client::ClientState,
mut queue: crate::client::queue::QueueEditGuard<'_>, mut queue: crate::client::queue::QueueEditGuard<'_>,
mut channel: crate::client::channel::SenderRef<'_, Self::Value>, mut channel: crate::client::channel::SenderRef<'_, Self::Value>,
) -> bool { ) -> bool {
match self.handle(msg, &mut queue) { match self.handle(msg, &mut queue) {
Ok(Some(v)) => { Ok(Some(v)) => {
channel.send(Ok(v)); v.save(state);
channel.send(Ok(()));
true true
} }
Ok(None) => false, Ok(None) => false,

View File

@@ -1,14 +1,19 @@
use std::{io::Cursor, time::Duration}; use std::{io::Cursor, time::Duration};
use super::{register_as_bot, HandlerError, Options};
use crate::{ use crate::{
client::{auth::Clear, channel::SyncChannels, conn::Bidir, Client}, client::{
auth::Clear,
channel::SyncChannels,
conn::Bidir,
state::{Caps, ISupport},
Client, ClientState,
},
string::{Key, Nick}, string::{Key, Nick},
}; };
use super::{register_as_bot, HandlerError, Options, Registration};
/// Test registration while ignoring the messages the handler sends. /// Test registration while ignoring the messages the handler sends.
fn static_register(msg: &[u8]) -> Result<Registration, HandlerError> { fn static_register(msg: &[u8]) -> Result<ClientState, HandlerError> {
let mut options: Options<Clear> = Options::new(); let mut options: Options<Clear> = Options::new();
options.nicks = vec![Nick::from_str("Me")]; options.nicks = vec![Nick::from_str("Me")];
let reg = register_as_bot(); // Somewhat more deterministic. let reg = register_as_bot(); // Somewhat more deterministic.
@@ -17,7 +22,8 @@ fn static_register(msg: &[u8]) -> Result<Registration, HandlerError> {
client.queue_mut().set_rate_limit(Duration::ZERO, 1); client.queue_mut().set_rate_limit(Duration::ZERO, 1);
let (_, reg) = client.add(&reg, &options).unwrap(); let (_, reg) = client.add(&reg, &options).unwrap();
client.run().unwrap(); client.run().unwrap();
reg.0.recv_now().unwrap() reg.0.recv_now().expect("Handler should send on channel after success")?;
Ok(std::mem::take(client.state_mut()))
} }
#[test] #[test]
@@ -45,7 +51,7 @@ fn ircv3_reg_simple() {
// TODO: Test more thoroughly. // TODO: Test more thoroughly.
// We should be able to handle any values for messages 001 through 003, // We should be able to handle any values for messages 001 through 003,
// so we're just going to put silliness here. // so we're just going to put silliness here.
let reg = static_register( let state = static_register(
concat!( concat!(
":example.com CAP * LS :quickbrownfox/lazydogjumping labeled-response\r\n", ":example.com CAP * LS :quickbrownfox/lazydogjumping labeled-response\r\n",
":example.com CAP * ACK :labeled-response\r\n", ":example.com CAP * ACK :labeled-response\r\n",
@@ -61,12 +67,14 @@ fn ircv3_reg_simple() {
.as_bytes(), .as_bytes(),
) )
.expect("ircv3 reg failed"); .expect("ircv3 reg failed");
assert_eq!(reg.caps.get_extra(LABELED_RESPONSE).copied(), Some(true)); let caps = state.get::<Caps>().expect("Handler should set Caps on success");
let isupport = state.get::<ISupport>().expect("Handler should set ISupport on success");
assert_eq!(caps.get_extra(LABELED_RESPONSE).copied(), Some(true));
assert_eq!( assert_eq!(
reg.caps.get_extra_raw(&Key::from_str("quickbrownfox/lazydogjumping")).copied(), caps.get_extra_raw(&Key::from_str("quickbrownfox/lazydogjumping")).copied(),
Some(false) Some(false)
); );
let netname = reg.isupport.get_parsed(NETWORK).expect("NETWORK should have a value").unwrap(); let netname = isupport.get_parsed(NETWORK).expect("NETWORK should have a value").unwrap();
assert_eq!(netname, b"example.com"); assert_eq!(netname, b"example.com");
} }