diff options
| author | Austin Hellyer <[email protected]> | 2016-09-19 09:00:03 -0700 |
|---|---|---|
| committer | Austin Hellyer <[email protected]> | 2016-10-18 11:14:27 -0700 |
| commit | 8fc8c81403c3daa187ba96a7d488a64db21463bf (patch) | |
| tree | 81bc4890c28b08ce806f69084617066bce863c2d /src/client/mod.rs | |
| download | serenity-8fc8c81403c3daa187ba96a7d488a64db21463bf.tar.xz serenity-8fc8c81403c3daa187ba96a7d488a64db21463bf.zip | |
Initial commit
Diffstat (limited to 'src/client/mod.rs')
| -rw-r--r-- | src/client/mod.rs | 973 |
1 files changed, 973 insertions, 0 deletions
diff --git a/src/client/mod.rs b/src/client/mod.rs new file mode 100644 index 0000000..8e291fb --- /dev/null +++ b/src/client/mod.rs @@ -0,0 +1,973 @@ +//! The Client contains information about a single bot or user's "session" with +//! Discord. Event handers and starting the connection are handled directly via +//! the client. In addition, the [http module] and [`State`] are also +//! automatically handled by the Client module for you. +//! +//! A [`Context`] is provided for every handler. The +//! context is an ergonomic way of accessing the lower-level Http struct's +//! methods. +//! +//! The Http struct is the lower-level method of accessing the Discord REST API. +//! Realistically there should be little reason to use this yourself, as the +//! Context will do this for you. A possible use case of using the Http struct +//! is if you do not have a state for purposes such as low memory requirements. +//! +//! Creating a Client instance and adding a handler on every message +//! receive, acting as a "ping-pong" bot is simple: +//! +//! ```rust,ignore +//! use serenity::Client; +//! +//! let client = Client::login_bot("my token here"); +//! +//! client.on_message(|context, message| { +//! if message.content == "!ping" { +//! context.say("Pong!"); +//! } +//! }); +//! +//! client.start(); +//! ``` +//! +//! [`Context`]: struct.Context.html +//! [`State`]: ext/state/index.html +//! [http module]: client/http/index.html + +pub mod http; + +mod connection; +mod context; +mod dispatch; +mod event_store; +mod login_type; +mod ratelimiting; + +pub use self::connection::{Connection, ConnectionError}; +pub use self::context::Context; + +use hyper::status::StatusCode; +use self::dispatch::dispatch; +use self::event_store::EventStore; +use self::login_type::LoginType; +use serde_json::builder::ObjectBuilder; +use std::collections::{BTreeMap, HashMap}; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; +use ::model::*; +use ::prelude::*; +use ::ext::framework::Framework; +use ::ext::state::State; + +lazy_static! { + /// The STATE is a mutable lazily-initialized static binding. It can be + /// accessed across any function and in any context. + /// + /// This [`State`] instance is updated for every event received, so you do + /// not need to maintain your own state. + /// + /// See the [state module documentation] for more details. + /// + /// # Examples + /// + /// Retrieve the [current user][`CurrentUser`]'s Id: + /// + /// ```rust,ignore + /// use serenity::client::STATE; + /// + /// println!("{}", STATE.lock().unwrap().user.id); + /// ``` + /// + /// [`CurrentUser`]: ../model/struct.CurrentUser.html + /// [`State`]: ../ext/state/struct.State.html + /// [state module documentation]: ../ext/state/index.html + pub static ref STATE: Arc<Mutex<State>> = Arc::new(Mutex::new(State::default())); +} + +/// An error returned from the [`Client`] or the [`Context`], or model instance. +/// +/// This is always wrapped within the library's generic [`Error::Client`] +/// variant. +/// +/// # Examples +/// +/// Matching an [`Error`] with this variant may look something like the +/// following for the [`Context::ban_user`] method: +/// +/// ```rust,ignore +/// use serenity::client::ClientError; +/// use serenity::Error; +/// +/// match context.ban_user(context.guild_id, context.message.author, 8) { +/// Ok(()) => { +/// // Ban successful. +/// }, +/// Err(Error::Client(ClientError::DeleteMessageDaysAmount(amount)) => { +/// println!("Tried deleting {} days' worth of messages", amount); +/// }, +/// Err(why) => { +/// println!("Unexpected error: {:?}", why); +/// }, +/// } +/// ``` +/// +/// [`Client`]: struct.Client.html +/// [`Context`]: struct.Context.html +/// [`Context::ban_user`]: struct.Context.html#method.ban_user +/// [`Error::Client`]: ../enum.Error.html#Client.v +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub enum ClientError { + /// When attempting to delete below or above the minimum and maximum allowed + /// number of messages. + BulkDeleteAmount, + /// When the connection being retrieved from within the Client could not be + /// found after being inserted into the Client's internal vector of + /// [`Connection`]s. + /// + /// This can be returned from one of the options for starting one or + /// multiple connections. + /// + /// **This should never be received.** + /// + /// [`Connection`]: struct.Connection.html + ConnectionUnknown, + /// When attempting to delete a number of days' worth of messages that is + /// not allowed. + DeleteMessageDaysAmount(u8), + /// When there was an error retrieving the gateway URI from the REST API. + Gateway, + /// An indication that a [guild][`LiveGuild`] could not be found by + /// [Id][`GuildId`] in the [`State`]. + /// + /// [`GuildId`]: ../model/struct.GuildId.html + /// [`LiveGuild`]: ../model/struct.LiveGuild.html + /// [`State`]: ../ext/state/struct.State.html + GuildNotFound, + /// When attempting to perform an action which is only available to user + /// accounts. + InvalidOperationAsBot, + /// When attempting to perform an action which is only available to bot + /// accounts. + InvalidOperationAsUser, + /// Indicates that you do not have the required permissions to perform an + /// operation. + /// + /// The provided [`Permission`]s is the set of required permissions + /// required. + /// + /// [`Permission`]: ../model/permissions/struct.Permissions.html + InvalidPermissions(Permissions), + /// An indicator that the shard data received from the gateway is invalid. + InvalidShards, + /// When the token provided is invalid. This is returned when validating a + /// token through the [`validate_token`] function. + /// + /// [`validate_token`]: fn.validate_token.html + InvalidToken, + /// An indicator that the [current user] can not perform an action. + /// + /// [current user]: ../model/struct.CurrentUser.html + InvalidUser, + /// An indicator that an item is missing from the [`State`], and the action + /// can not be continued. + /// + /// [`State`]: ../ext/state/struct.State.html + ItemMissing, + /// When attempting to use a [`Context`] helper method which requires a + /// contextual [`ChannelId`], but the current context is not appropriate for + /// the action. + /// + /// [`ChannelId`]: ../model/struct.ChannelId.html + /// [`Context`]: struct.Context.html + NoChannelId, + /// When the decoding of a ratelimit header could not be properly decoded + /// into an `i64`. + RateLimitI64, + /// When the decoding of a ratelimit header could not be properly decoded + /// from UTF-8. + RateLimitUtf8, + /// When attempting to find a required record from the State could not be + /// found. This is required in methods such as + /// [Context::edit_role](struct.Context.html#method.edit_role). + RecordNotFound, + /// When a function such as [`Context::edit_channel`] did not expect the + /// received [`ChannelType`]. + /// + /// [`ChannelType`]: ../model/enum.ChannelType.html + /// [`Context::edit_channel`]: struct.Context.html#method.edit_channel + UnexpectedChannelType(ChannelType), + /// When a status code was unexpectedly received for a request's status. + UnexpectedStatusCode(StatusCode), + /// When a status is received, but the verification to ensure the response + /// is valid does not recognize the status. + UnknownStatus(u16), +} + +pub struct Client { + pub connections: Vec<Arc<Mutex<Connection>>>, + event_store: Arc<Mutex<EventStore>>, + framework: Arc<Mutex<Framework>>, + login_type: LoginType, + token: String, +} + +#[allow(type_complexity)] +impl Client { + /// Creates a Client for a bot. + pub fn login_bot(bot_token: &str) -> Client { + let token = format!("Bot {}", bot_token); + + login(&token, LoginType::Bot) + } + /// Create an instance from "raw values" + #[doc(hidden)] + pub fn login_raw(token: &str, login_type: LoginType) -> Client { + login(&token.to_owned(), login_type) + } + + /// Creates a Client for a user. + pub fn login_user(user_token: &str) -> Client { + login(&user_token.to_owned(), LoginType::User) + } + + /// Logout from the Discord API. This theoretically is supposed to + /// invalidate the current token, but currently does not do anything. This + /// is an issue on Discord's side. + /// + /// **Note**: This can only be used by users. + pub fn logout(self) -> Result<()> { + if self.login_type == LoginType::Bot { + return Err(Error::Client(ClientError::InvalidOperationAsBot)); + } + + let map = ObjectBuilder::new() + .insert("provider", Value::Null) + .insert("token", Value::Null) + .build(); + + http::logout(map) + } + + /// Sets a framework to be used with the client. All message events will be + /// passed through the framework _after_ being passed to the [`on_message`] + /// event handler. + /// + /// See the [framework module-level documentation][framework docs] for more + /// information on usage. + /// + /// [`on_message`]: #method.on_message + /// [framework docs]: ../ext/framework/index.html + pub fn with_framework<F>(&mut self, f: F) + where F: FnOnce(Framework) -> Framework + Send + Sync + 'static { + self.framework = Arc::new(Mutex::new(f(Framework::default()))); + } + + /// Establish the connection and start listening for events. + /// + /// This will start receiving events in a loop and start dispatching the + /// events to your registered handlers. + /// + /// Note that this should be used only for users and for bots which are in + /// less than 2500 guilds. If you have a reason for sharding and/or are in + /// more than 2500 guilds, use one of these depending on your use case: + /// + /// Refer to the [module-level documentation][connection docs] for more + /// information on effectively using sharding. + /// + /// [connection docs]: struct.Connection.html#sharding + pub fn start(&mut self) -> Result<()> { + self.start_connection(None) + } + + /// Establish the connection(s) and start listening for events. + /// + /// This will start receiving events in a loop and start dispatching the + /// events to your registered handlers. + /// + /// This will retrieve an automatically determined number of shards to use + /// from the API - determined by Discord - and then open a number of shards + /// equivilant to that amount. + /// + /// Refer to the [module-level documentation][connection docs] for more + /// information on effectively using sharding. + /// + /// [connection docs]: struct.Connection.html#sharding + pub fn start_autosharded(&mut self) -> Result<()> { + let res = try!(http::get_bot_gateway()); + + self.start_connection(Some([0, res.shards as u8 - 1, res.shards as u8])) + } + + /// Establish a sharded connection and start listening for events. + /// + /// This will start receiving events and dispatch them to your registered + /// handlers. + /// + /// This will create a single shard by ID. If using one shard per process, + /// you will need to start other processes with the other shard IDs in some + /// way. + /// + /// Refer to the [module-level documentation][connection docs] for more + /// information on effectively using sharding. + /// + /// [connection docs]: struct.Connection.html#sharding + pub fn start_shard(&mut self, shard: u8, shards: u8) -> Result<()> { + self.start_connection(Some([shard, shard, shards])) + } + + /// Establish sharded connections and start listening for events. + /// + /// This will start receiving events and dispatch them to your registered + /// handlers. + /// + /// This will create and handle all shards within this single process. If + /// you only need to start a single shard within the process, or a range of + /// shards, use [`start_shard`] or [`start_shard_range`], respectively. + /// + /// Refer to the [module-level documentation][connection docs] for more + /// information on effectively using sharding. + /// + /// [`start_shard`]: #method.start_shard + /// [`start_shard_range`]: #method.start_shards + /// [connection docs]: struct.Connection.html#sharding + pub fn start_shards(&mut self, total_shards: u8) -> Result<()> { + self.start_connection(Some([0, total_shards - 1, total_shards])) + } + + /// Establish a range of sharded connections and start listening for events. + /// + /// This will start receiving events and dispatch them to your registered + /// handlers. + /// + /// This will create and handle all shards within a given range within this + /// single process. If you only need to start a single shard within the + /// process, or all shards within the process, use [`start_shard`] or + /// [`start_shards`], respectively. + /// + /// Refer to the [module-level documentation][connection docs] for more + /// information on effectively using sharding. + /// + /// # Examples + /// + /// For a bot using a total of 10 shards, initialize shards 4 through 7: + /// + /// ```rust,ignore + /// // assumes a `client` has already been initialized + /// let _ = client.start_shard_range([4, 7], 10); + /// ``` + /// + /// [`start_shard`]: #method.start_shard + /// [`start_shards`]: #method.start_shards + /// [connection docs]: struct.Connection.html#sharding + pub fn start_shard_range(&mut self, range: [u8; 2], total_shards: u8) + -> Result<()> { + self.start_connection(Some([range[0], range[1], total_shards])) + } + + /// Attaches a handler for when a [`CallCreate`] is received. + /// + /// [`CallCreate`]: ../model/enum.Event.html#CallCreate.v + pub fn on_call_create<F>(&mut self, handler: F) + where F: Fn(Context, Call) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_call_create = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`CallDelete`] is received. + /// + /// [`CallDelete`]: ../model/enum.Event.html#CallDelete.v + pub fn on_call_delete<F>(&mut self, handler: F) + where F: Fn(Context, Option<Call>) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_call_delete = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`CallUpdate`] is received. + /// + /// [`CallUpdate`]: ../model/enum.Event.html#CallUpdate.v + pub fn on_call_update<F>(&mut self, handler: F) + where F: Fn(Context, Option<Call>, Option<Call>) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_call_update = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`ChannelCreate`] is received. + /// + /// [`ChannelCreate`]: ../model/enum.Event.html#ChannelCreate.v + pub fn on_channel_create<F>(&mut self, handler: F) + where F: Fn(Context, Channel) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_channel_create = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`ChannelDelete`] is received. + /// + /// [`ChannelDelete`]: ../model/enum.Event.html#ChannelDelete.v + pub fn on_channel_delete<F>(&mut self, handler: F) + where F: Fn(Context, Channel) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_channel_delete = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`ChannelPinsAck`] is received. + /// + /// [`ChannelPinsAck`]: ../model/enum.Event.html#ChannelPinsAck.v + pub fn on_channel_pins_ack<F>(&mut self, handler: F) + where F: Fn(Context, ChannelPinsAckEvent) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_channel_pins_ack = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`ChannelPinsUpdate`] is received. + /// + /// [`ChannelPinsUpdate`]: ../model/enum.Event.html#ChannelPinsUpdate.v + pub fn on_channel_pins_update<F>(&mut self, handler: F) + where F: Fn(Context, ChannelPinsUpdateEvent) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_channel_pins_update = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`ChannelUpdate`] is received. + /// + /// [`ChannelUpdate`]: ../model/enum.Event.html#ChannelUpdate.v + pub fn on_channel_update<F>(&mut self, handler: F) + where F: Fn(Context, Option<Channel>, Channel) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_channel_update = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildCreate`] is received. + /// + /// [`GuildCreate`]: ../model/enum.Event.html#GuildCreate.v + pub fn on_guild_create<F>(&mut self, handler: F) + where F: Fn(Context, LiveGuild) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_create = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuilDelete`] is received. + /// + /// [`GuilDelete`]: ../model/enum.Event.html#GuilDelete.v + pub fn on_guild_delete<F>(&mut self, handler: F) + where F: Fn(Context, Guild) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_delete = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildEmojisUpdate`] is received. + /// + /// [`GuildEmojisUpdate`]: ../model/enum.Event.html#GuildEmojisUpdate.v + pub fn on_guild_emojis_update<F>(&mut self, handler: F) + where F: Fn(Context, GuildEmojisUpdateEvent) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_emojis_update = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildIntegrationsUpdate`] is received. + /// + /// [`GuildIntegrationsUpdate`]: ../model/enum.Event.html#GuildIntegrationsUpdate.v + pub fn on_guild_integrations_update<F>(&mut self, handler: F) + where F: Fn(Context, GuildIntegrationsUpdateEvent) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_integrations_update = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildMemberAdd`] is received. + /// + /// [`GuildMemberAdd`]: ../model/enum.Event.html#GuildMemberAdd.v + pub fn on_guild_member_add<F>(&mut self, handler: F) + where F: Fn(Context, GuildId, Member) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_member_addition = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildMemberRemove`] is received. + /// + /// [`GuildMemberRemove`]: ../model/enum.Event.html#GuildMemberRemove.v + pub fn on_guild_member_remove<F>(&mut self, handler: F) + where F: Fn(Context, GuildId, User) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_member_removal = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildMemberUpdate`] is received. + /// + /// [`GuildMemberUpdate`]: ../model/enum.Event.html#GuildMemberUpdate.v + pub fn on_guild_member_update<F>(&mut self, handler: F) + where F: Fn(Context, Option<Member>, Member) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_member_update = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildMembersChunk`] is received. + /// + /// [`GuildMembersChunk`]: ../model/enum.Event.html#GuildMembersChunk.v + pub fn on_guild_members_chunk<F>(&mut self, handler: F) + where F: Fn(Context, GuildId, HashMap<UserId, Member>) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_members_chunk = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildRoleCreate`] is received. + /// + /// [`GuildRoleCreate`]: ../model/enum.Event.html#GuildRoleCreate.v + pub fn on_guild_role_create<F>(&mut self, handler: F) + where F: Fn(Context, GuildId, Role) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_role_create = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildRoleDelete`] is received. + /// + /// [`GuildRoleDelete`]: ../model/enum.Event.html#GuildRoleDelete.v + pub fn on_guild_role_delete<F>(&mut self, handler: F) + where F: Fn(Context, GuildId, RoleId) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_role_delete = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildRoleUpdate`] is received. + /// + /// [`GuildRoleUpdate`]: ../model/enum.Event.html#GuildRoleUpdate.v + pub fn on_guild_role_update<F>(&mut self, handler: F) + where F: Fn(Context, GuildId, Role) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_role_update = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildRoleSync`] is received. + /// + /// [`GuildRoleSync`]: ../model/enum.Event.html#GuildRoleSync.v + pub fn on_guild_sync<F>(&mut self, handler: F) + where F: Fn(Context, GuildSyncEvent) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_sync = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildUnavailable`] is received. + /// + /// [`GuildUnavailable`]: ../model/enum.Event.html#GuildUnavailable.v + pub fn on_guild_unavailable<F>(&mut self, handler: F) + where F: Fn(Context, GuildId) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_unavailable = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildUpdate`] is received. + /// + /// [`GuildUpdate`]: ../model/enum.Event.html#GuildUpdate.v + pub fn on_guild_update<F>(&mut self, handler: F) + where F: Fn(Context, Option<LiveGuild>, Guild) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_update = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildBan`] is received. + /// + /// [`GuildBan`]: ../model/enum.Event.html#GuildBan.v + pub fn on_member_ban<F>(&mut self, handler: F) + where F: Fn(Context, GuildId, User) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_ban_addition = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`GuildUnban`] is received. + /// + /// [`GuildUnban`]: ../model/enum.Event.html#GuildUnban.v + pub fn on_member_unban<F>(&mut self, handler: F) + where F: Fn(Context, GuildId, User) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_guild_ban_removal = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`MessageCreate`] is received. + /// + /// [`MessageCreate`]: ../model/enum.Event.html#MessageCreate.v + pub fn on_message<F>(&mut self, handler: F) + where F: Fn(Context, Message) + Send + Sync + 'static { + + self.event_store.lock() + .unwrap() + .on_message = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`MessageAck`] is received. + /// + /// [`MessageAck`]: ../model/enum.Event.html#MessageAck.v + pub fn on_message_ack<F>(&mut self, handler: F) + where F: Fn(Context, ChannelId, Option<MessageId>) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_message_ack = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`MessageDelete`] is received. + /// + /// [`MessageDelete`]: ../model/enum.Event.html#MessageDelete.v + pub fn on_message_delete<F>(&mut self, handler: F) + where F: Fn(Context, ChannelId, MessageId) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_message_delete = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`MessageDeleteBulk`] is received. + /// + /// [`MessageDeleteBulk`]: ../model/enum.Event.html#MessageDeleteBulk.v + pub fn on_message_delete_bulk<F>(&mut self, handler: F) + where F: Fn(Context, ChannelId, Vec<MessageId>) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_message_delete_bulk = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`MessageUpdate`] is received. + /// + /// [`MessageUpdate`]: ../model/enum.Event.html#MessageUpdate.v + pub fn on_message_update<F>(&mut self, handler: F) + where F: Fn(Context, MessageUpdateEvent) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_message_update = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`UserNoteUpdate`] is received. + /// + /// [`UserNoteUpdate`]: ../model/enum.Event.html#UserNoteUpdate.v + pub fn on_note_update<F>(&mut self, handler: F) + where F: Fn(Context, UserId, String) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_note_update = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`PresencesReplace`] is received. + /// + /// [`PresencesReplace`]: ../model/enum.Event.html#PresencesReplace.v + pub fn on_presence_replace<F>(&mut self, handler: F) + where F: Fn(Context, Vec<Presence>) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_presence_replace = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`PresenceUpdate`] is received. + /// + /// [`PresenceUpdate`]: ../model/enum.Event.html#PresenceUpdate.v + pub fn on_presence_update<F>(&mut self, handler: F) + where F: Fn(Context, PresenceUpdateEvent) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_presence_update = Some(Arc::new(handler)); + } + + /// Register an event to be called whenever a Ready event is received. + /// + /// Registering a handler for the ready event is good for noting when your + /// bot has established a connection to the gateway. + /// + /// **Note**: The Ready event is not guarenteed to be the first event you + /// will receive by Discord. Do not actively rely on it. + /// + /// # Examples + /// + /// Print the [current user][`CurrentUser`]'s name on ready: + /// + /// ```rust,ignore + /// // assuming a `client` has been bound + /// client.on_ready(|_context, ready| { + /// println!("{} is connected", ready.user.name); + /// }); + /// ``` + /// + /// [`CurrentUser`]: ../model/struct.CurrentUser.html + pub fn on_ready<F>(&mut self, handler: F) + where F: Fn(Context, Ready) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_ready = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`ChannelRecipientAdd`] is received. + /// + /// [`ChannelRecipientAdd`]: ../model/enum.Event.html#ChannelRecipientAdd.v + pub fn on_recipient_add<F>(&mut self, handler: F) + where F: Fn(Context, ChannelId, User) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_channel_recipient_addition = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`ChannelRecipientRemove`] is received. + /// + /// [`ChannelRecipientRemove`]: ../model/enum.Event.html#ChannelRecipientRemove.v + pub fn on_recipient_remove<F>(&mut self, handler: F) + where F: Fn(Context, ChannelId, User) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_channel_recipient_removal = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`RelationshipAdd`] is received. + /// + /// [`RelationshipAdd`]: ../model/enum.Event.html#RelationshipAdd.v + pub fn on_relationship_add<F>(&mut self, handler: F) + where F: Fn(Context, Relationship) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_relationship_addition = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`RelationshipRemove`] is received. + /// + /// [`RelationshipRemove`]: ../model/enum.Event.html#RelationshipRemove.v + pub fn on_relationship_remove<F>(&mut self, handler: F) + where F: Fn(Context, UserId, RelationshipType) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_relationship_removal = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`Resumed`] is received. + /// + /// [`Resumed`]: ../model/enum.Event.html#Resumed.v + pub fn on_resume<F>(&mut self, handler: F) + where F: Fn(Context, ResumedEvent) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_resume = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`TypingStart`] is received. + /// + /// [`TypingStart`]: ../model/enum.Event.html#TypingStart.v + pub fn on_typing_start<F>(&mut self, handler: F) + where F: Fn(Context, TypingStartEvent) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_typing_start = Some(Arc::new(handler)); + } + + /// Attaches a handler for when an [`Unknown`] is received. + /// + /// [`Unknown`]: ../model/enum.Event.html#Unknown.v + pub fn on_unknown<F>(&mut self, handler: F) + where F: Fn(Context, String, BTreeMap<String, Value>) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_unknown = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`UserGuildSettingsUpdate`] is received. + /// + /// [`UserGuildSettingsUpdate`]: ../model/enum.Event.html#UserGuildSettingsUpdate.v + pub fn on_user_guild_settings_update<F>(&mut self, handler: F) + where F: Fn(Context, Option<UserGuildSettings>, UserGuildSettings) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_user_guild_settings_update = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`UserUpdate`] is received. + /// + /// [`UserUpdate`]: ../model/enum.Event.html#UserUpdate.v + pub fn on_user_update<F>(&mut self, handler: F) + where F: Fn(Context, CurrentUser, CurrentUser) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_user_update = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`UserSettingsUpdate`] is received. + /// + /// [`UserSettingsUpdate`]: ../model/enum.Event.html#UserSettingsUpdate.v + pub fn on_user_settings_update<F>(&mut self, handler: F) + where F: Fn(Context, UserSettings, UserSettings) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_user_settings_update = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`VoiceStateUpdate`] is received. + /// + /// [`VoiceStateUpdate`]: ../model/enum.Event.html#VoiceStateUpdate.v + pub fn on_voice_state_update<F>(&mut self, handler: F) + where F: Fn(Context, VoiceStateUpdateEvent) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_voice_state_update = Some(Arc::new(handler)); + } + + /// Attaches a handler for when a [`VoiceServerUpdate`] is received. + /// + /// [`VoiceServerUpdate`]: ../model/enum.Event.html#VoiceServerUpdate.v + pub fn on_voice_server_update<F>(&mut self, handler: F) + where F: Fn(Context, VoiceServerUpdateEvent) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_voice_server_update = Some(Arc::new(handler)); + } + + // Shard data layout is: + // 0: first shard number to initialize + // 1: shard number to initialize up to and including + // 2: total number of shards the bot is sharding for + // + // Not all shards need to be initialized in this process. + fn start_connection(&mut self, shard_data: Option<[u8; 3]>) -> Result<()> { + let gateway_url = try!(http::get_gateway()).url; + + for i in 0..shard_data.map_or(1, |x| x[1] + 1) { + let connection = Connection::new(&gateway_url, + &self.token, + shard_data.map(|s| [i, s[2]]), + self.login_type); + match connection { + Ok((connection, ready)) => { + self.connections.push(Arc::new(Mutex::new(connection))); + + STATE.lock() + .unwrap() + .update_with_ready(&ready); + + match self.connections.last() { + Some(connection) => { + dispatch(Ok(Event::Ready(ready)), + connection.clone(), + self.framework.clone(), + self.login_type, + self.event_store.clone()); + + let connection_clone = connection.clone(); + let event_store = self.event_store.clone(); + let framework = self.framework.clone(); + let login_type = self.login_type; + thread::spawn(move || { + handle_connection(connection_clone, + framework, + login_type, + event_store); + }); + }, + None => return Err(Error::Client(ClientError::ConnectionUnknown)), + } + }, + Err(why) => return Err(why), + } + } + + // How to avoid the problem while still working on other parts of the + // library 101 + loop { + thread::sleep(Duration::from_secs(1)); + } + } + + // Boot up a new connection. This is used primarily in the scenario of + // re-instantiating a connection in the reconnect logic in another + // Connection. + #[doc(hidden)] + pub fn boot_connection(&mut self, + shard_info: Option<[u8; 2]>) + -> Result<(Connection, ReadyEvent)> { + let gateway_url = try!(http::get_gateway()).url; + + Connection::new(&gateway_url, &self.token, shard_info, self.login_type) + } +} + +fn handle_connection(connection: Arc<Mutex<Connection>>, + framework: Arc<Mutex<Framework>>, + login_type: LoginType, + event_store: Arc<Mutex<EventStore>>) { + loop { + let event = { + let mut connection = connection.lock().unwrap(); + + connection.receive() + }; + + dispatch(event, + connection.clone(), + framework.clone(), + login_type, + event_store.clone()); + } +} + +fn login(token: &str, login_type: LoginType) -> Client { + let token = token.to_owned(); + + http::set_token(&token); + + Client { + connections: Vec::default(), + event_store: Arc::new(Mutex::new(EventStore::default())), + framework: Arc::new(Mutex::new(Framework::default())), + login_type: login_type, + token: token.to_owned(), + } +} + +/// Validates that a token is likely in a valid format. +/// +/// This performs the following checks on a given token: +/// +/// - At least one character long; +/// - Contains 3 parts (split by the period char `'.'`); +/// - The second part of the token is at least 6 characters long; +/// - The token does not contain any whitespace prior to or after the token. +/// +/// # Errors +/// +/// Returns a +/// [ClientError::InvalidToken](enum.ClientError.html#InvalidToken.v) when one +/// of the above checks fail. The type of failure is not specified. +pub fn validate_token(token: &str) -> Result<()> { + if token.is_empty() { + return Err(Error::Client(ClientError::InvalidToken)); + } + + let parts: Vec<&str> = token.split('.').collect(); + + // Check that the token has a total of 3 parts. + if parts.len() != 3 { + return Err(Error::Client(ClientError::InvalidToken)); + } + + // Check that the second part is at least 6 characters long. + if parts.get(1).unwrap().len() < 6 { + return Err(Error::Client(ClientError::InvalidToken)); + } + + // Check that there is no whitespace before/after the token. + if token.trim() != token { + return Err(Error::Client(ClientError::InvalidToken)); + } + + Ok(()) +} |