mirror of
https://github.com/vinezombie/vinezombie.git
synced 2026-01-24 23:17:34 +00:00
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:
@@ -47,14 +47,17 @@ fn main() -> std::io::Result<()> {
|
||||
// so we can ignore it.
|
||||
client.run()?;
|
||||
// If we're here, the handler finished and gave us a value back.
|
||||
// Let's fetch it and see what it is!
|
||||
let reg = reg_result.0.recv_now().unwrap()?;
|
||||
// The registration handler only tells us what the error was, if it failed.
|
||||
// So, let's check to see if we made it.
|
||||
reg_result.0.recv_now().unwrap()?;
|
||||
// Connection registration is done!
|
||||
// But how does the network we connected to choose to name itself?
|
||||
// ISUPPORT is vital for understanding the capabilities of the target network,
|
||||
// and vinezombie eagerly parses it during registration. Let's print the network name.
|
||||
let network_name = reg.isupport.get_parsed(vinezombie::names::isupport::NETWORK).transpose()?;
|
||||
tracing::info!("{} connected to {}!", reg.nick, network_name.unwrap_or(Word::from_str("IRC")));
|
||||
// Now, what is our nick? `Client` can store information about the current session
|
||||
// for use by other handlers. We can access that information through the `state` method.
|
||||
// While we're at it, let's get the network name from ISUPPORT.
|
||||
let nick = &client.state().get::<vinezombie::client::state::ClientSource>().unwrap().nick;
|
||||
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)
|
||||
// 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,
|
||||
|
||||
@@ -27,10 +27,12 @@ async fn main() -> std::io::Result<()> {
|
||||
// but instead we run it using a run_tokio function.
|
||||
let (_id, reg_result) = client.add(®ister_as_bot(), &options).unwrap();
|
||||
client.run_tokio().await?;
|
||||
let reg = reg_result.await.unwrap()?;
|
||||
let network_name = reg.isupport.get_parsed(vinezombie::names::isupport::NETWORK).transpose()?;
|
||||
tracing::info!("{} connected to {}!", reg.nick, network_name.unwrap_or(Word::from_str("IRC")));
|
||||
// As with the earlier example, let's just quit here.
|
||||
reg_result.await.unwrap()?;
|
||||
// Almost everything past this point is the same as the sync example.
|
||||
let nick = &client.state().get::<vinezombie::client::state::ClientSource>().unwrap().nick;
|
||||
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);
|
||||
client.queue_mut().edit().push(msg);
|
||||
client.run_tokio().await?;
|
||||
|
||||
@@ -6,6 +6,7 @@ use vinezombie::{
|
||||
conn::ServerAddr,
|
||||
handlers::{AutoPong, YieldParsed},
|
||||
register::{register_as_bot, Options},
|
||||
state::ClientSource,
|
||||
Client,
|
||||
},
|
||||
ircmsg::ClientMsg,
|
||||
@@ -30,8 +31,9 @@ async fn main() -> std::io::Result<()> {
|
||||
let mut client = Client::new(sock, TokioChannels);
|
||||
let (_id, reg_result) = client.add(®ister_as_bot(), &options).unwrap();
|
||||
client.run_tokio().await?;
|
||||
reg_result.await.unwrap()?;
|
||||
// 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);
|
||||
// Let's add a handler to auto-reply to PING messages for us. Most IRC networks need this.
|
||||
// Do this before anything else.
|
||||
|
||||
@@ -7,6 +7,7 @@ use vinezombie::{
|
||||
conn::{ServerAddr, Stream},
|
||||
handlers::{AutoPong, YieldParsed},
|
||||
register::{register_as_bot, Options},
|
||||
state::ClientSource,
|
||||
tls::TlsConfig,
|
||||
Client,
|
||||
},
|
||||
@@ -49,7 +50,7 @@ fn main() -> std::io::Result<()> {
|
||||
loop {
|
||||
let (_, reg_result) = client.add(®ister_as_bot(), &options).unwrap();
|
||||
client.run()?;
|
||||
let nick = reg_result.0.recv_now().unwrap()?.nick;
|
||||
reg_result.0.recv_now().unwrap()?;
|
||||
let _ = client.add((), AutoPong);
|
||||
// As we can interact with this bot, let's add a handler to auto-reply to
|
||||
// CTCP VERSION and CTCP SOURCE.
|
||||
@@ -61,7 +62,7 @@ fn main() -> std::io::Result<()> {
|
||||
// This time we're actually going to use that information,
|
||||
// starting by saving the id of the message handler.
|
||||
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 {
|
||||
let Ok(result) = client.run() else {
|
||||
tracing::info!("connection broke, making new connection");
|
||||
|
||||
@@ -5,6 +5,7 @@ use vinezombie::{
|
||||
auth::{sasl::Password, Clear, Secret},
|
||||
channel::TokioChannels,
|
||||
register::{register_as_bot, Options},
|
||||
state::Account,
|
||||
Client,
|
||||
},
|
||||
string::{tf::TrimAscii, Line, NoNul},
|
||||
@@ -46,9 +47,9 @@ async fn main() -> std::io::Result<()> {
|
||||
let mut client = Client::new(sock, TokioChannels);
|
||||
let (_id, reg_result) = client.add(®ister_as_bot(), &options).unwrap();
|
||||
client.run_tokio().await?;
|
||||
let reg = reg_result.await.unwrap()?;
|
||||
reg_result.await.unwrap()?;
|
||||
// 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}");
|
||||
} else {
|
||||
// We should never get here unless `options.allow_sasl_fail` is set to `true`.
|
||||
|
||||
@@ -21,6 +21,11 @@ use crate::{
|
||||
///
|
||||
/// Consider using the [`register_as_bot()`], [`register_as_client()`],
|
||||
/// 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)]
|
||||
pub struct Register<O> {
|
||||
/// Returns the server password, if any.
|
||||
@@ -110,7 +115,7 @@ impl<O> 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;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use super::CapFn;
|
||||
use crate::{
|
||||
client::{
|
||||
auth::{self, SaslQueue},
|
||||
@@ -14,9 +15,7 @@ use crate::{
|
||||
string::{Arg, Key, Line, Nick, Splitter, Word},
|
||||
};
|
||||
|
||||
use super::CapFn;
|
||||
|
||||
/// The result of successful registration.
|
||||
/// A useful subset of information yielded by client registration.
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct Registration {
|
||||
/// The nickname used for this connection.
|
||||
@@ -55,6 +54,21 @@ impl Registration {
|
||||
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 {
|
||||
@@ -224,11 +238,7 @@ impl Handler {
|
||||
reg: Registration::new(nick),
|
||||
}
|
||||
}
|
||||
/// Handles a server message sent during connection registration.
|
||||
///
|
||||
/// It is a logic error to call `handle` after
|
||||
/// it errors or returns `Ok(Done)`.
|
||||
pub fn handle(
|
||||
fn handle(
|
||||
&mut self,
|
||||
msg: &ServerMsg<'_>,
|
||||
mut sink: impl ClientMsgSink<'static>,
|
||||
@@ -515,18 +525,19 @@ impl Handler {
|
||||
}
|
||||
|
||||
impl crate::client::Handler for Handler {
|
||||
type Value = Result<Registration, HandlerError>;
|
||||
type Value = Result<(), HandlerError>;
|
||||
|
||||
fn handle(
|
||||
&mut self,
|
||||
msg: &ServerMsg<'_>,
|
||||
_: &mut crate::client::ClientState,
|
||||
state: &mut crate::client::ClientState,
|
||||
mut queue: crate::client::queue::QueueEditGuard<'_>,
|
||||
mut channel: crate::client::channel::SenderRef<'_, Self::Value>,
|
||||
) -> bool {
|
||||
match self.handle(msg, &mut queue) {
|
||||
Ok(Some(v)) => {
|
||||
channel.send(Ok(v));
|
||||
v.save(state);
|
||||
channel.send(Ok(()));
|
||||
true
|
||||
}
|
||||
Ok(None) => false,
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
use std::{io::Cursor, time::Duration};
|
||||
|
||||
use super::{register_as_bot, HandlerError, Options};
|
||||
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},
|
||||
};
|
||||
|
||||
use super::{register_as_bot, HandlerError, Options, Registration};
|
||||
|
||||
/// 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();
|
||||
options.nicks = vec![Nick::from_str("Me")];
|
||||
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);
|
||||
let (_, reg) = client.add(®, &options).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]
|
||||
@@ -45,7 +51,7 @@ fn ircv3_reg_simple() {
|
||||
// TODO: Test more thoroughly.
|
||||
// We should be able to handle any values for messages 001 through 003,
|
||||
// so we're just going to put silliness here.
|
||||
let reg = static_register(
|
||||
let state = static_register(
|
||||
concat!(
|
||||
":example.com CAP * LS :quickbrownfox/lazydogjumping labeled-response\r\n",
|
||||
":example.com CAP * ACK :labeled-response\r\n",
|
||||
@@ -61,12 +67,14 @@ fn ircv3_reg_simple() {
|
||||
.as_bytes(),
|
||||
)
|
||||
.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!(
|
||||
reg.caps.get_extra_raw(&Key::from_str("quickbrownfox/lazydogjumping")).copied(),
|
||||
caps.get_extra_raw(&Key::from_str("quickbrownfox/lazydogjumping")).copied(),
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user