#![doc = include_str!("../README.md")]
pub mod bot;
pub mod connection;
pub mod context;
pub mod handler;
pub mod hot_reload;
pub mod irc;
pub mod testing;
pub use bot::HandlerSet;
pub use connection::{
State, DEFAULT_FLOOD_BURST, DEFAULT_FLOOD_RATE, DEFAULT_KEEPALIVE_INTERVAL,
DEFAULT_KEEPALIVE_TIMEOUT,
};
pub use context::{make_messages, Context, User};
pub use handler::{BoxFuture, HandlerEntry, HandlerFn, Trigger};
pub use irc::CtcpMessage;
pub use ircbot_macros::bot;
#[doc = include_str!("../docs/command.md")]
pub use ircbot_macros::command;
#[doc = include_str!("../docs/on.md")]
pub use ircbot_macros::on;
pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
pub type Result = std::result::Result<(), BoxError>;
#[derive(Debug)]
pub enum Error {
MissingContext(&'static str),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::MissingContext(ctx) => write!(f, "missing context: {ctx}"),
}
}
}
impl std::error::Error for Error {}
pub struct ReloadHandle<T> {
handlers: HandlerSet<T>,
}
impl<T> ReloadHandle<T> {
pub fn new(handlers: HandlerSet<T>) -> Self {
ReloadHandle { handlers }
}
pub fn reload(&self, new_handlers: Vec<HandlerEntry<T>>) {
if let Ok(mut guard) = self.handlers.write() {
*guard = std::sync::Arc::new(new_handlers);
}
}
}
impl<T> Clone for ReloadHandle<T> {
fn clone(&self) -> Self {
ReloadHandle {
handlers: std::sync::Arc::clone(&self.handlers),
}
}
}
pub mod internal {
use std::sync::{Arc, RwLock};
use std::time::Duration;
use crate::{bot::HandlerSet, BoxError, HandlerEntry, State};
const RECONNECT_DELAY: Duration = Duration::from_secs(5);
#[must_use]
pub fn make_handler_set<T>(handlers: Vec<HandlerEntry<T>>) -> HandlerSet<T> {
Arc::new(RwLock::new(Arc::new(handlers)))
}
pub async fn run_bot<T: Send + Sync + 'static>(
bot: Arc<T>,
state: State,
handlers: Vec<HandlerEntry<T>>,
) -> std::result::Result<(), BoxError> {
let handlers = make_handler_set(handlers);
let server = state.server.clone();
let nick = state.nick.clone();
let channels = state.channels.clone();
let keepalive_interval = state.keepalive_interval;
let keepalive_timeout = state.keepalive_timeout;
let flood_burst = state.flood_burst;
let flood_rate = state.flood_rate;
let mut current_state = state;
loop {
if let Err(e) =
crate::bot::run_bot_internal(Arc::clone(&bot), current_state, Arc::clone(&handlers))
.await
{
eprintln!("[ircbot] connection error: {e}");
} else {
eprintln!("[ircbot] disconnected from {server}");
}
eprintln!(
"[ircbot] reconnecting to {server} in {:.0?}…",
RECONNECT_DELAY
);
tokio::time::sleep(RECONNECT_DELAY).await;
match State::connect(nick.clone(), &server, channels.clone()).await {
Ok(mut new_state) => {
new_state.keepalive_interval = keepalive_interval;
new_state.keepalive_timeout = keepalive_timeout;
new_state.flood_burst = flood_burst;
new_state.flood_rate = flood_rate;
current_state = new_state;
}
Err(e) => {
eprintln!("[ircbot] failed to reconnect to {server}: {e}");
return Err(e);
}
}
}
}
}