use std::thread; use std::time::Duration; use rustbot::bot::Bot; use rustbot::config::{self, Config}; use rustbot::irc::Connection; use rustbot::log; fn main() { let configs = config::load(); if configs.is_empty() { log("no networks configured; nothing to do"); std::process::exit(1); } let names: Vec<&str> = configs.iter().map(|c| c.name.as_str()).collect(); log(&format!( "starting rustbot across {} network(s): {}", configs.len(), names.join(", ") )); let labelled = configs.len() > 1; let mut handles = Vec::new(); for config in configs { let name = config.name.clone(); let handle = thread::Builder::new() .name(name.clone()) .spawn(move || supervise(config, labelled)) .unwrap_or_else(|e| panic!("failed to spawn thread for {name}: {e}")); handles.push(handle); } for handle in handles { let _ = handle.join(); } } fn supervise(config: Config, labelled: bool) { let tag = if labelled { format!("[{}] ", config.name) } else { String::new() }; log(&format!( "{tag}connecting as {} -> {}:{} (tls={})", config.nick, config.server, config.port, config.tls )); let mut backoff = 1u64; loop { match run_once(&config, labelled) { Ok(()) => { log(&format!( "{tag}server closed the connection; reconnecting shortly" )); backoff = 1; } Err(e) => log(&format!("{tag}session error: {e}; retrying in {backoff}s")), } thread::sleep(Duration::from_secs(backoff)); backoff = (backoff * 2).min(60); } } fn run_once(config: &Config, labelled: bool) -> std::io::Result<()> { let mut conn = Connection::connect(&config.server, config.port, config.tls, config.verbose)?; if labelled { conn.set_label(&config.name); } let mut bot = Bot::new(config.clone(), conn); bot.run() }