use std::sync::Arc;
use crate::client::Client;
use crate::config::Config;
use crate::dispatcher::Dispatcher;
use crate::error::Error;
use crate::events::Event;
use crate::gateway::{GatewayClient, GatewayCloseAction, GatewayEvent, GatewayHandle, Intents};
use crate::router::{HandlerResult, Router};
type ErrorHook = Arc<dyn Fn(&Error) + Send + Sync>;
pub struct BotBuilder {
token: Option<String>,
config: Option<Config>,
intents: Intents,
routers: Vec<Router>,
on_error: ErrorHook,
}
impl BotBuilder {
fn new() -> Self {
Self {
token: None,
config: None,
intents: Intents::none(),
routers: Vec::new(),
on_error: Arc::new(|error| eprintln!("vaidcord handler error: {error}")),
}
}
pub fn token(mut self, token: impl Into<String>) -> Self {
self.token = Some(token.into());
self
}
pub fn config(mut self, config: Config) -> Self {
self.config = Some(config);
self
}
pub fn intents(mut self, intents: Intents) -> Self {
self.intents = intents;
self
}
pub fn router(mut self, router: Router) -> Self {
self.routers.push(router);
self
}
pub fn on_error<F>(mut self, hook: F) -> Self
where
F: Fn(&Error) + Send + Sync + 'static,
{
self.on_error = Arc::new(hook);
self
}
pub fn build(self) -> Result<Bot, Error> {
let config = match (self.config, self.token) {
(Some(config), _) => config,
(None, Some(token)) => Config::new(token),
(None, None) => {
return Err(Error::Other(
"Bot::builder() requires .token(..) or .config(..)".to_string(),
));
}
};
let mut dispatcher = Dispatcher::new();
for router in &self.routers {
dispatcher.include(router);
}
Ok(Bot {
client: Client::new(config),
dispatcher,
intents: self.intents,
on_error: self.on_error,
})
}
pub async fn run(self) -> Result<(), Error> {
self.build()?.run().await
}
}
pub struct Bot {
client: Client,
dispatcher: Dispatcher,
intents: Intents,
on_error: ErrorHook,
}
impl Bot {
pub fn builder() -> BotBuilder {
BotBuilder::new()
}
pub fn client(&self) -> &Client {
&self.client
}
pub fn dispatcher(&self) -> &Dispatcher {
&self.dispatcher
}
pub fn feed_event(&self, event: &Event) -> HandlerResult {
self.dispatcher.dispatch(event)
}
pub async fn run(self) -> Result<(), Error> {
let gateway = GatewayClient::new(self.client.clone());
let mut connection = gateway.connect(self.intents).await?;
while let Some(event) = connection.next_event().await {
match event {
GatewayEvent::Dispatch(dispatch) => {
let Some(name) = dispatch.t.as_deref() else {
continue;
};
let parsed = Event::parse(name, dispatch.d);
if let Err(error) = self.dispatcher.dispatch(&parsed) {
(self.on_error)(&error);
}
}
GatewayEvent::Disconnected {
close_code,
action: GatewayCloseAction::Fatal,
} => {
return Err(Error::Other(format!(
"gateway closed fatally (close code {close_code:?})"
)));
}
GatewayEvent::Error(error) => (self.on_error)(&error),
_ => {}
}
}
Ok(())
}
pub async fn spawn(self) -> Result<(GatewayHandle, tokio::task::JoinHandle<Result<(), Error>>), Error>
{
let gateway = GatewayClient::new(self.client.clone());
let connection = gateway.connect(self.intents).await?;
let handle = connection.handle();
let mut connection = connection;
let dispatcher = self.dispatcher;
let on_error = self.on_error;
let task = tokio::spawn(async move {
while let Some(event) = connection.next_event().await {
match event {
GatewayEvent::Dispatch(dispatch) => {
let Some(name) = dispatch.t.as_deref() else {
continue;
};
let parsed = Event::parse(name, dispatch.d);
if let Err(error) = dispatcher.dispatch(&parsed) {
(on_error)(&error);
}
}
GatewayEvent::Disconnected {
close_code,
action: GatewayCloseAction::Fatal,
} => {
return Err(Error::Other(format!(
"gateway closed fatally (close code {close_code:?})"
)));
}
GatewayEvent::Error(error) => (on_error)(&error),
_ => {}
}
}
Ok(())
});
Ok((handle, task))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
#[test]
fn builder_requires_a_token() {
assert!(Bot::builder().build().is_err());
assert!(Bot::builder().token("t").build().is_ok());
assert!(Bot::builder().config(Config::new("t")).build().is_ok());
}
#[test]
fn builder_composes_routers_into_the_dispatcher() {
let mut router_a = Router::new();
router_a.on_message(|_| Ok(()));
let mut router_b = Router::new();
router_b.on_message(|_| Ok(()));
router_b.on_ready(|_| Ok(()));
let bot = Bot::builder()
.token("t")
.router(router_a)
.router(router_b)
.build()
.unwrap();
assert_eq!(bot.dispatcher().route_count(), 3);
}
#[test]
fn feed_event_dispatches_parsed_gateway_payloads() {
let seen: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let mut router = Router::new();
let sink = Arc::clone(&seen);
router.on_message(move |message| {
sink.lock().unwrap().push(message.content.clone());
Ok(())
});
let bot = Bot::builder().token("t").router(router).build().unwrap();
let event = Event::parse(
"MESSAGE_CREATE",
serde_json::json!({
"id": "1", "channel_id": "2", "content": "hello",
"author": {"id": "3", "username": "u"}
}),
);
bot.feed_event(&event).unwrap();
assert_eq!(*seen.lock().unwrap(), vec!["hello"]);
}
}