Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 105 additions & 52 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,14 @@ async fn main() -> anyhow::Result<()> {
None
};

// Spawn shutdown signal listener that notifies all adapters via shutdown_tx.
let shutdown_tx_signal = shutdown_tx.clone();
tokio::spawn(async move {
shutdown_signal().await;
info!("shutdown signal received");
let _ = shutdown_tx_signal.send(true);
});

// Run Discord adapter (foreground, blocking) or wait for ctrl_c
if let Some(discord_cfg) = cfg.discord {
let allow_all_channels = config::resolve_allow_all(
Expand Down Expand Up @@ -403,71 +411,116 @@ async fn main() -> anyhow::Result<()> {
));
dispatchers.lock().unwrap().push(discord_dispatcher.clone());

let handler = discord::Handler {
router,
allow_all_channels,
allow_all_users,
allowed_channels,
allowed_users,
stt_config: cfg.stt.clone(),
adapter: std::sync::OnceLock::new(),
allow_bot_messages: discord_cfg.allow_bot_messages,
trusted_bot_ids,
allow_user_messages: discord_cfg.allow_user_messages,
allowed_role_ids,
participated_threads: tokio::sync::Mutex::new(std::collections::HashMap::new()),
multibot_threads: tokio::sync::Mutex::new(std::collections::HashMap::new()),
session_ttl: std::time::Duration::from_secs(ttl_secs),
max_bot_turns: discord_cfg.max_bot_turns,
bot_turns: tokio::sync::Mutex::new(bot_turns::BotTurnTracker::new(
discord_cfg.max_bot_turns,
)),
allow_dm: discord_cfg.allow_dm,
dispatcher: discord_dispatcher,
};

let intents = GatewayIntents::GUILD_MESSAGES
| GatewayIntents::MESSAGE_CONTENT
| GatewayIntents::GUILDS
| GatewayIntents::DIRECT_MESSAGES;

let mut client = Client::builder(&discord_cfg.bot_token, intents)
.event_handler(handler)
.await?;
let mut reconnect_delay = std::time::Duration::from_secs(1);
const MAX_RECONNECT_DELAY: std::time::Duration = std::time::Duration::from_secs(60);
let mut shutdown_rx_discord = shutdown_rx.clone();

// Graceful Discord shutdown on ctrl_c
let shard_manager = client.shard_manager.clone();
tokio::spawn(async move {
shutdown_signal().await;
info!("shutdown signal received");
shard_manager.shutdown_all().await;
});
loop {
let handler = discord::Handler {
router: router.clone(),
allow_all_channels,
allow_all_users,
allowed_channels: allowed_channels.clone(),
allowed_users: allowed_users.clone(),
stt_config: cfg.stt.clone(),
adapter: std::sync::OnceLock::new(),
allow_bot_messages: discord_cfg.allow_bot_messages,
trusted_bot_ids: trusted_bot_ids.clone(),
allow_user_messages: discord_cfg.allow_user_messages,
allowed_role_ids: allowed_role_ids.clone(),
participated_threads: tokio::sync::Mutex::new(std::collections::HashMap::new()),
multibot_threads: tokio::sync::Mutex::new(std::collections::HashMap::new()),
session_ttl: std::time::Duration::from_secs(ttl_secs),
max_bot_turns: discord_cfg.max_bot_turns,
bot_turns: tokio::sync::Mutex::new(bot_turns::BotTurnTracker::new(
discord_cfg.max_bot_turns,
)),
allow_dm: discord_cfg.allow_dm,
dispatcher: discord_dispatcher.clone(),
};

// F1 fix: handle builder errors within the loop instead of propagating with ?
let mut client = match Client::builder(&discord_cfg.bot_token, intents)
.event_handler(handler)
.await
{
Ok(c) => c,
Err(e) => {
warn!(error = %e, delay_secs = reconnect_delay.as_secs(), "failed to build discord client, retrying");
tokio::select! {
_ = tokio::time::sleep(reconnect_delay) => {}
_ = shutdown_rx_discord.changed() => { break; }
}
reconnect_delay = (reconnect_delay * 2).min(MAX_RECONNECT_DELAY);
continue;
}
};

// F2 fix: use an abort handle so the shutdown listener is cleaned up each iteration
let shard_manager = client.shard_manager.clone();
let mut shutdown_rx_inner = shutdown_rx.clone();
let shutdown_task = tokio::spawn(async move {
let _ = shutdown_rx_inner.changed().await;
shard_manager.shutdown_all().await;
});

info!("discord bot running");
match client.start().await {
Err(serenity::Error::Gateway(GatewayError::DisallowedGatewayIntents)) => {
error!(
"Discord rejected privileged intents. \
Enable MESSAGE CONTENT INTENT at: \
https://discord.com/developers/applications → Bot → Privileged Gateway Intents"
);
std::process::exit(1);
info!("discord bot running");
let result = client.start().await;

// Abort the shutdown listener for this iteration to avoid accumulation.
shutdown_task.abort();

// Check if we're shutting down — if so, don't reconnect.
if *shutdown_rx_discord.borrow() {
break;
}
Err(serenity::Error::Gateway(GatewayError::InvalidAuthentication)) => {
error!(
"Discord rejected bot token. \
Verify your bot_token in config.toml is correct and has not been reset."
);
std::process::exit(1);

match result {
Err(serenity::Error::Gateway(GatewayError::DisallowedGatewayIntents)) => {
error!(
"Discord rejected privileged intents. \
Enable MESSAGE CONTENT INTENT at: \
https://discord.com/developers/applications → Bot → Privileged Gateway Intents"
);
std::process::exit(1);
}
Err(serenity::Error::Gateway(GatewayError::InvalidAuthentication)) => {
error!(
"Discord rejected bot token. \
Verify your bot_token in config.toml is correct and has not been reset."
);
std::process::exit(1);
}
Err(e) => {
warn!(error = %e, delay_secs = reconnect_delay.as_secs(), "discord gateway error, reconnecting");
// F3 fix: escalate backoff only on errors
tokio::select! {
_ = tokio::time::sleep(reconnect_delay) => {}
_ = shutdown_rx_discord.changed() => { break; }
}
reconnect_delay = (reconnect_delay * 2).min(MAX_RECONNECT_DELAY);
}
Ok(_) => {
// Gateway ran successfully then disconnected — reset backoff and retry quickly.
reconnect_delay = std::time::Duration::from_secs(1);
warn!("discord gateway exited, reconnecting in 1s");
tokio::select! {
_ = tokio::time::sleep(reconnect_delay) => {}
_ = shutdown_rx_discord.changed() => { break; }
}
}
}
Err(e) => return Err(e.into()),
Ok(_) => {}
}
} else {
// No Discord — wait for SIGINT or SIGTERM
info!("running without discord, press ctrl+c to stop");
shutdown_signal().await;
info!("shutdown signal received");
let mut shutdown_rx_wait = shutdown_rx.clone();
let _ = shutdown_rx_wait.changed().await;
}

// Cleanup
Expand Down
Loading