pub mod bridge;
mod context;
#[cfg(feature = "gateway")]
mod dispatch;
mod error;
#[cfg(feature = "gateway")]
mod event_handler;
#[cfg(feature = "gateway")]
mod extras;
pub use self::{
context::Context,
error::Error as ClientError,
};
#[cfg(feature = "gateway")]
pub use self::{
event_handler::{EventHandler, RawEventHandler},
extras::Extras,
};
pub use crate::CacheAndHttp;
#[cfg(feature = "cache")]
pub use crate::cache::Cache;
use crate::internal::prelude::*;
use tokio::sync::{Mutex, RwLock};
#[cfg(feature = "gateway")]
use super::gateway::GatewayError;
#[cfg(feature = "gateway")]
use self::bridge::gateway::{GatewayIntents, ShardManager, ShardManagerMonitor, ShardManagerOptions, ShardManagerError};
use std::{
boxed::Box,
sync::Arc,
future::Future,
pin::Pin,
task::{Context as FutContext, Poll},
};
#[cfg(all(feature = "cache", feature = "gateway"))]
use std::time::Duration;
use tracing::{error, debug, info, instrument};
#[cfg(feature = "framework")]
use crate::framework::Framework;
#[cfg(feature = "voice")]
use crate::model::id::UserId;
#[cfg(feature = "voice")]
use self::bridge::voice::ClientVoiceManager;
use crate::http::Http;
use typemap_rev::{TypeMap, TypeMapKey};
use futures::future::BoxFuture;
#[cfg(feature = "gateway")]
pub struct ClientBuilder<'a> {
data: Option<TypeMap>,
http: Option<Http>,
fut: Option<BoxFuture<'a, Result<Client>>>,
guild_subscriptions: bool,
intents: Option<GatewayIntents>,
#[cfg(feature = "cache")]
timeout: Option<Duration>,
#[cfg(feature = "framework")]
framework: Option<Arc<Box<dyn Framework + Send + Sync + 'static>>>,
event_handler: Option<Arc<dyn EventHandler>>,
raw_event_handler: Option<Arc<dyn RawEventHandler>>,
}
#[cfg(feature = "gateway")]
impl<'a> ClientBuilder<'a> {
pub fn new(token: impl AsRef<str>) -> Self {
Self {
data: Some(TypeMap::new()),
http: None,
fut: None,
guild_subscriptions: true,
intents: None,
#[cfg(feature = "cache")]
timeout: None,
#[cfg(feature = "framework")]
framework: None,
event_handler: None,
raw_event_handler: None,
}.token(token)
}
pub fn token(mut self, token: impl AsRef<str>) -> Self {
let token = token.as_ref().trim();
let token = if token.starts_with("Bot ") {
token.to_string()
} else {
format!("Bot {}", token)
};
self.http = Some(Http::new_with_token(&token));
self
}
pub fn type_map(mut self, type_map: TypeMap) -> Self {
self.data = Some(type_map);
self
}
pub fn type_map_insert<T: TypeMapKey>(mut self, value: T::Value) -> Self {
if let Some(ref mut data) = self.data {
data.insert::<T>(value);
} else {
let mut type_map = TypeMap::new();
type_map.insert::<T>(value);
self.data = Some(type_map);
}
self
}
#[cfg(feature = "cache")]
pub fn cache_update_timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn guild_subscriptions(mut self, is_enabled: bool) -> Self {
self.guild_subscriptions = is_enabled;
self
}
#[cfg(feature = "framework")]
pub fn framework<F>(mut self, framework: F) -> Self
where F: Framework + Send + Sync + 'static,
{
self.framework = Some(Arc::new(Box::new(framework)));
self
}
#[cfg(feature = "framework")]
pub fn framework_arc(mut self, framework: Arc<Box<dyn Framework + Send + Sync + 'static>>) -> Self {
self.framework = Some(framework);
self
}
pub fn intents(mut self, intents: GatewayIntents) -> Self {
self.intents = Some(intents);
self
}
pub fn add_intent(mut self, intent: GatewayIntents) -> Self {
if let Some(ref mut intents) = self.intents {
intents.insert(intent);
} else {
self.intents = Some(intent);
}
self
}
pub fn event_handler<H: EventHandler + 'static>(mut self, event_handler: H) -> Self {
self.event_handler = Some(Arc::new(event_handler));
self
}
pub fn raw_event_handler<H: RawEventHandler + 'static>(mut self, raw_event_handler: H) -> Self {
self.raw_event_handler = Some(Arc::new(raw_event_handler));
self
}
}
#[cfg(feature = "gateway")]
impl<'a> Future for ClientBuilder<'a> {
type Output = Result<Client>;
#[instrument(skip(self))]
fn poll(mut self: Pin<&mut Self>, ctx: &mut FutContext<'_>) -> Poll<Self::Output> {
if self.fut.is_none() {
let data = Arc::new(RwLock::new(self.data.take().unwrap()));
#[cfg(feature = "framework")]
let framework = self.framework.take()
.expect("The `framework`-feature is enabled (it's on by default), but no framework was provided.\n\
If you don't want to use the command framework, disable default features and specify all features you want to use.");
let event_handler = self.event_handler.take();
let raw_event_handler = self.raw_event_handler.take();
let guild_subscriptions = self.guild_subscriptions;
let intents = self.intents;
let http = Arc::new(self.http.take().unwrap());
#[cfg(feature = "voice")]
let voice_manager = Arc::new(Mutex::new(ClientVoiceManager::new(
0,
UserId(0),
)));
let cache_and_http = Arc::new(CacheAndHttp {
#[cfg(feature = "cache")]
cache: Arc::new(Cache::default()),
#[cfg(feature = "cache")]
update_cache_timeout: self.timeout.take(),
http: Arc::clone(&http),
});
self.fut = Some(Box::pin(async move {
let url = Arc::new(Mutex::new(http.get_gateway().await?.url));
let (shard_manager, shard_manager_worker) = {
ShardManager::new(ShardManagerOptions {
data: &data,
event_handler: &event_handler,
raw_event_handler: &raw_event_handler,
#[cfg(feature = "framework")]
framework: &framework,
shard_index: 0,
shard_init: 0,
shard_total: 0,
#[cfg(feature = "voice")]
voice_manager: &voice_manager,
ws_url: &url,
cache_and_http: &cache_and_http,
guild_subscriptions,
intents,
}).await
};
Ok(Client {
ws_uri: url,
data,
shard_manager,
shard_manager_worker,
#[cfg(feature = "voice")]
voice_manager,
cache_and_http,
})
}))
}
self.fut.as_mut().unwrap().as_mut().poll(ctx)
}
}
#[cfg(feature = "gateway")]
pub struct Client {
pub data: Arc<RwLock<TypeMap>>,
pub shard_manager: Arc<Mutex<ShardManager>>,
shard_manager_worker: ShardManagerMonitor,
#[cfg(feature = "voice")]
pub voice_manager: Arc<Mutex<ClientVoiceManager>>,
pub ws_uri: Arc<Mutex<String>>,
pub cache_and_http: Arc<CacheAndHttp>,
}
impl Client {
#[deprecated(since="0.9.0", note="please use `builder` instead")]
#[allow(clippy::new_ret_no_self)]
pub fn new<'a>(token: impl AsRef<str>) -> ClientBuilder<'a> {
Self::builder(token)
}
pub fn builder<'a>(token: impl AsRef<str>) -> ClientBuilder<'a> {
ClientBuilder::new(token)
}
#[instrument(skip(self))]
pub async fn start(&mut self) -> Result<()> {
self.start_connection([0, 0, 1]).await
}
#[instrument(skip(self))]
pub async fn start_autosharded(&mut self) -> Result<()> {
let (x, y) = {
let res = self.cache_and_http.http.get_bot_gateway().await?;
(res.shards as u64 - 1, res.shards as u64)
};
self.start_connection([0, x, y]).await
}
#[instrument(skip(self))]
pub async fn start_shard(&mut self, shard: u64, shards: u64) -> Result<()> {
self.start_connection([shard, shard, shards]).await
}
#[instrument(skip(self))]
pub async fn start_shards(&mut self, total_shards: u64) -> Result<()> {
self.start_connection([0, total_shards - 1, total_shards]).await
}
#[instrument(skip(self))]
pub async fn start_shard_range(&mut self, range: [u64; 2], total_shards: u64) -> Result<()> {
self.start_connection([range[0], range[1], total_shards]).await
}
#[instrument(skip(self))]
async fn start_connection(&mut self, shard_data: [u64; 3]) -> Result<()> {
#[cfg(feature = "voice")]
self.voice_manager.lock().await.set_shard_count(shard_data[2]);
#[cfg(feature = "voice")]
{
let user = self.cache_and_http.http.get_current_user().await?;
self.voice_manager.lock().await.set_user_id(user.id);
}
{
let mut manager = self.shard_manager.lock().await;
let init = shard_data[1] - shard_data[0] + 1;
manager.set_shards(shard_data[0], init, shard_data[2]).await;
debug!(
"Initializing shard info: {} - {}/{}",
shard_data[0],
init,
shard_data[2],
);
if let Err(why) = manager.initialize() {
error!("Failed to boot a shard: {:?}", why);
info!("Shutting down all shards");
manager.shutdown_all().await;
return Err(Error::Client(ClientError::ShardBootFailure));
}
}
if let Err(why) = self.shard_manager_worker.run().await {
let err = match why {
ShardManagerError::DisallowedGatewayIntents => GatewayError::DisallowedGatewayIntents,
ShardManagerError::InvalidGatewayIntents => GatewayError::InvalidGatewayIntents,
ShardManagerError::InvalidToken => GatewayError::InvalidAuthentication,
};
return Err(Error::Gateway(err));
}
Ok(())
}
}
pub fn validate_token(token: impl AsRef<str>) -> Result<()> {
let token = token.as_ref();
if token.is_empty() {
return Err(Error::Client(ClientError::InvalidToken));
}
let parts: Vec<&str> = token.split('.').collect();
if parts.len() != 3 {
return Err(Error::Client(ClientError::InvalidToken));
}
if parts[1].len() < 6 {
return Err(Error::Client(ClientError::InvalidToken));
}
if token.trim() != token {
return Err(Error::Client(ClientError::InvalidToken));
}
Ok(())
}