use std::time::Duration; use futures_util::StreamExt; use irc::client::prelude::*; use tokio::sync::mpsc; use tracing::{error, info}; use crate::config::IrcConfig; use crate::events::{BridgeEvent, Source}; pub async fn run_irc_task( config: IrcConfig, event_tx: mpsc::Sender, mut outbound_rx: mpsc::Receiver, mut shutdown: tokio::sync::watch::Receiver, ) { let mut backoff = Duration::from_secs(1); let max_backoff = Duration::from_secs(60); loop { info!(server = %config.server, channel = %config.channel, "Connecting to IRC"); match connect_and_run(&config, &event_tx, &mut outbound_rx, &mut shutdown).await { Ok(()) => { info!("IRC task exiting cleanly"); return; } Err(e) => { error!(error = %e, "IRC connection error"); info!(backoff_secs = backoff.as_secs(), "Reconnecting after backoff"); tokio::select! { _ = tokio::time::sleep(backoff) => {}, _ = shutdown.changed() => return, } backoff = (backoff * 2).min(max_backoff); } } } } async fn connect_and_run( config: &IrcConfig, event_tx: &mpsc::Sender, outbound_rx: &mut mpsc::Receiver, shutdown: &mut tokio::sync::watch::Receiver, ) -> anyhow::Result<()> { let irc_config = Config { nickname: Some(config.nick.clone()), server: Some(config.server.clone()), port: Some(config.port), use_tls: Some(config.tls), channels: vec![config.channel.clone()], ..Config::default() }; let mut client = Client::from_config(irc_config).await?; client.identify()?; let mut stream = client.stream()?; let sender = client.sender(); info!("IRC connected, waiting for messages"); loop { tokio::select! { msg = stream.next() => { match msg { Some(Ok(message)) => { if let Command::PRIVMSG(ref target, ref text) = message.command { if target == &config.channel { let nick = message.source_nickname().unwrap_or("unknown").to_string(); let event = BridgeEvent::ChatMessage { source: Source::Irc, username: nick, body: text.clone(), id: None, }; if event_tx.send(event).await.is_err() { return Ok(()); } } } } Some(Err(e)) => return Err(e.into()), None => return Err(anyhow::anyhow!("IRC stream ended")), } } Some(text) = outbound_rx.recv() => { sender.send_privmsg(&config.channel, &text)?; } _ = shutdown.changed() => { let _ = sender.send_quit("Bridge shutting down"); return Ok(()); } } } }