diff options
| author | Zeyla Hellyer <[email protected]> | 2018-03-25 19:48:53 -0700 |
|---|---|---|
| committer | Zeyla Hellyer <[email protected]> | 2018-03-25 19:48:53 -0700 |
| commit | e2f3ece27934af8b7b756a70d3b6090385d37c1e (patch) | |
| tree | e8085382bb92f88bfbaf95042360eb66f0a1921a /src | |
| parent | Remove `http::FutureResult`, use `error`'s (diff) | |
| parent | Remove useless clones (#292) (diff) | |
| download | serenity-e2f3ece27934af8b7b756a70d3b6090385d37c1e.tar.xz serenity-e2f3ece27934af8b7b756a70d3b6090385d37c1e.zip | |
Merge branch 'master' into futures
Diffstat (limited to 'src')
| -rw-r--r-- | src/builder/create_embed.rs | 16 | ||||
| -rw-r--r-- | src/client/context.rs | 430 | ||||
| -rw-r--r-- | src/client/dispatch.rs | 638 | ||||
| -rw-r--r-- | src/client/event_handler.rs | 85 | ||||
| -rw-r--r-- | src/framework/standard/args.rs | 38 | ||||
| -rw-r--r-- | src/framework/standard/command.rs | 1 | ||||
| -rw-r--r-- | src/framework/standard/create_command.rs | 11 | ||||
| -rw-r--r-- | src/framework/standard/create_group.rs | 1 | ||||
| -rw-r--r-- | src/framework/standard/help_commands.rs | 18 | ||||
| -rw-r--r-- | src/framework/standard/mod.rs | 67 | ||||
| -rw-r--r-- | src/http/mod.rs | 18 | ||||
| -rw-r--r-- | src/http/routing.rs | 19 | ||||
| -rw-r--r-- | src/model/event.rs | 6 | ||||
| -rw-r--r-- | src/model/gateway.rs | 2 | ||||
| -rw-r--r-- | src/voice/audio.rs | 84 | ||||
| -rw-r--r-- | src/voice/connection.rs | 25 | ||||
| -rw-r--r-- | src/voice/handler.rs | 3 | ||||
| -rw-r--r-- | src/voice/streamer.rs | 15 |
18 files changed, 270 insertions, 1207 deletions
diff --git a/src/builder/create_embed.rs b/src/builder/create_embed.rs index 7610dd4..bf312c6 100644 --- a/src/builder/create_embed.rs +++ b/src/builder/create_embed.rs @@ -176,14 +176,14 @@ impl CreateEmbed { /// Set the image associated with the embed. This only supports HTTP(S). #[inline] - pub fn image(self, url: &str) -> Self { - self.url_object("image", url) + pub fn image<S: AsRef<str>>(self, url: S) -> Self { + self.url_object("image", url.as_ref()) } /// Set the thumbnail of the embed. This only supports HTTP(S). #[inline] - pub fn thumbnail(self, url: &str) -> Self { - self.url_object("thumbnail", url) + pub fn thumbnail<S: AsRef<str>>(self, url: S) -> Self { + self.url_object("thumbnail", url.as_ref()) } /// Set the timestamp. @@ -288,9 +288,9 @@ impl CreateEmbed { } /// Set the URL to direct to when clicking on the title. - pub fn url(mut self, url: &str) -> Self { + pub fn url<S: AsRef<str>>(mut self, url: S) -> Self { self.0 - .insert("url", Value::String(url.to_string())); + .insert("url", Value::String(url.as_ref().to_string())); CreateEmbed(self.0) } @@ -301,8 +301,8 @@ impl CreateEmbed { /// with the provided filename. Or else this won't work. /// /// [`ChannelId::send_files`]: ../model/id/struct.ChannelId.html#send_files - pub fn attachment(self, filename: &str) -> Self { - self.image(&format!("attachment://{}", filename)) + pub fn attachment<S: AsRef<str>>(self, filename: S) -> Self { + self.image(&format!("attachment://{}", filename.as_ref())) } } diff --git a/src/client/context.rs b/src/client/context.rs deleted file mode 100644 index 7737172..0000000 --- a/src/client/context.rs +++ /dev/null @@ -1,430 +0,0 @@ -use client::bridge::gateway::ShardMessenger; -use gateway::InterMessage; -use model::prelude::*; -use parking_lot::Mutex; -use std::sync::mpsc::Sender; -use std::sync::Arc; -use typemap::ShareMap; - -#[cfg(feature = "builder")] -use builder::EditProfile; -#[cfg(feature = "builder")] -use internal::prelude::*; -#[cfg(all(feature = "builder", feature = "cache"))] -use super::CACHE; -#[cfg(feature = "builder")] -use {Result, http}; -#[cfg(feature = "builder")] -use utils::{self, VecMap}; - -/// The context is a general utility struct provided on event dispatches, which -/// helps with dealing with the current "context" of the event dispatch. -/// The context also acts as a general high-level interface over the associated -/// [`Shard`] which received the event, or the low-level [`http`] module. -/// -/// The context contains "shortcuts", like for interacting with the shard. -/// Methods like [`set_game`] will unlock the shard and perform an update for -/// you to save a bit of work. -/// -/// A context will only live for the event it was dispatched for. After the -/// event handler finished, it is destroyed and will not be re-used. -/// -/// [`Shard`]: ../gateway/struct.Shard.html -/// [`http`]: ../http/index.html -/// [`set_game`]: #method.set_game -#[derive(Clone)] -pub struct Context { - /// A clone of [`Client::data`]. Refer to its documentation for more - /// information. - /// - /// [`Client::data`]: struct.Client.html#structfield.data - pub data: Arc<Mutex<ShareMap>>, - /// The messenger to communicate with the shard runner. - pub shard: ShardMessenger, - /// The ID of the shard this context is related to. - pub shard_id: u64, -} - -impl Context { - /// Create a new Context to be passed to an event handler. - pub(crate) fn new( - data: Arc<Mutex<ShareMap>>, - runner_tx: Sender<InterMessage>, - shard_id: u64, - ) -> Context { - Context { - shard: ShardMessenger::new(runner_tx), - shard_id, - data, - } - } - - /// Edits the current user's profile settings. - /// - /// Refer to `EditProfile`'s documentation for its methods. - /// - /// # Examples - /// - /// Change the current user's username: - /// - /// ```rust,no_run - /// # use serenity::prelude::*; - /// # use serenity::model::channel::Message; - /// # - /// struct Handler; - /// - /// impl EventHandler for Handler { - /// fn message(&self, ctx: Context, msg: Message) { - /// if msg.content == "!changename" { - /// ctx.edit_profile(|e| e.username("Edward Elric")); - /// } - /// } - /// } - /// let mut client = Client::new("token", Handler).unwrap(); - /// - /// client.start().unwrap(); - /// ``` - #[cfg(feature = "builder")] - pub fn edit_profile<F: FnOnce(EditProfile) -> EditProfile>(&self, f: F) -> Result<CurrentUser> { - let mut map = VecMap::with_capacity(2); - - feature_cache! { - { - let cache = CACHE.read(); - - map.insert("username", Value::String(cache.user.name.clone())); - - if let Some(email) = cache.user.email.as_ref() { - map.insert("email", Value::String(email.clone())); - } - } else { - let user = http::get_current_user()?; - - map.insert("username", Value::String(user.name.clone())); - - if let Some(email) = user.email { - map.insert("email", Value::String(email)); - } - } - } - - let edited = utils::vecmap_to_json_map(f(EditProfile(map)).0); - - http::edit_profile(&edited) - } - - - /// Sets the current user as being [`Online`]. This maintains the current - /// game. - /// - /// # Examples - /// - /// Set the current user to being online on the shard: - /// - /// ```rust,no_run - /// # use serenity::prelude::*; - /// # use serenity::model::channel::Message; - /// # - /// struct Handler; - /// - /// impl EventHandler for Handler { - /// fn message(&self, ctx: Context, msg: Message) { - /// if msg.content == "!online" { - /// ctx.online(); - /// } - /// } - /// } - /// - /// let mut client = Client::new("token", Handler).unwrap(); - /// - /// client.start().unwrap(); - /// ``` - /// - /// [`Online`]: ../model/user/enum.OnlineStatus.html#variant.Online - #[inline] - pub fn online(&self) { - self.shard.set_status(OnlineStatus::Online); - } - - /// Sets the current user as being [`Idle`]. This maintains the current - /// game. - /// - /// # Examples - /// - /// Set the current user to being idle on the shard: - /// - /// ```rust,no_run - /// # use serenity::prelude::*; - /// # use serenity::model::channel::Message; - /// # - /// struct Handler; - /// - /// impl EventHandler for Handler { - /// fn message(&self, ctx: Context, msg: Message) { - /// if msg.content == "!idle" { - /// ctx.idle(); - /// } - /// } - /// } - /// let mut client = Client::new("token", Handler).unwrap(); - /// - /// client.start().unwrap(); - /// ``` - /// - /// [`Idle`]: ../model/user/enum.OnlineStatus.html#variant.Idle - #[inline] - pub fn idle(&self) { - self.shard.set_status(OnlineStatus::Idle); - } - - /// Sets the current user as being [`DoNotDisturb`]. This maintains the - /// current game. - /// - /// # Examples - /// - /// Set the current user to being Do Not Disturb on the shard: - /// - /// ```rust,no_run - /// # use serenity::prelude::*; - /// # use serenity::model::channel::Message; - /// # - /// struct Handler; - /// - /// impl EventHandler for Handler { - /// fn message(&self, ctx: Context, msg: Message) { - /// if msg.content == "!dnd" { - /// ctx.dnd(); - /// } - /// } - /// } - /// let mut client = Client::new("token", Handler).unwrap(); - /// - /// client.start().unwrap(); - /// ``` - /// - /// [`DoNotDisturb`]: ../model/user/enum.OnlineStatus.html#variant.DoNotDisturb - #[inline] - pub fn dnd(&self) { - self.shard.set_status(OnlineStatus::DoNotDisturb); - } - - /// Sets the current user as being [`Invisible`]. This maintains the current - /// game. - /// - /// # Examples - /// - /// Set the current user to being invisible on the shard when an - /// [`Event::Ready`] is received: - /// - /// ```rust,no_run - /// # use serenity::prelude::*; - /// # use serenity::model::gateway::Ready; - /// # - /// struct Handler; - /// - /// impl EventHandler for Handler { - /// fn ready(&self, ctx: Context, _: Ready) { - /// ctx.invisible(); - /// } - /// } - /// - /// let mut client = Client::new("token", Handler).unwrap(); - /// - /// client.start().unwrap(); - /// ``` - /// - /// [`Event::Ready`]: ../model/event/enum.Event.html#variant.Ready - /// [`Invisible`]: ../model/user/enum.OnlineStatus.html#variant.Invisible - #[inline] - pub fn invisible(&self) { - self.shard.set_status(OnlineStatus::Invisible); - } - - /// "Resets" the current user's presence, by setting the game to `None` and - /// the online status to [`Online`]. - /// - /// Use [`set_presence`] for fine-grained control over individual details. - /// - /// # Examples - /// - /// Reset the presence when an [`Event::Resumed`] is received: - /// - /// ```rust,no_run - /// # use serenity::prelude::*; - /// # use serenity::model::event::ResumedEvent; - /// # - /// struct Handler; - /// - /// impl EventHandler for Handler { - /// fn resume(&self, ctx: Context, _: ResumedEvent) { - /// ctx.reset_presence(); - /// } - /// } - /// - /// let mut client = Client::new("token", Handler).unwrap(); - /// - /// client.start().unwrap(); - /// ``` - /// - /// [`Event::Resumed`]: ../model/event/enum.Event.html#variant.Resumed - /// [`Online`]: ../model/user/enum.OnlineStatus.html#variant.Online - /// [`set_presence`]: #method.set_presence - #[inline] - pub fn reset_presence(&self) { - self.shard.set_presence(None, OnlineStatus::Online); - } - - /// Sets the current game, defaulting to an online status of [`Online`]. - /// - /// # Examples - /// - /// Create a command named `~setgame` that accepts a name of a game to be - /// playing: - /// - /// ```rust,no_run - /// # fn main() { - /// # use serenity::prelude::*; - /// # use serenity::model::channel::Message; - /// # - /// use serenity::model::gateway::Game; - /// - /// struct Handler; - /// - /// impl EventHandler for Handler { - /// fn message(&self, ctx: Context, msg: Message) { - /// let args = msg.content.splitn(2, ' ').collect::<Vec<&str>>(); - /// - /// if args.len() < 2 || *unsafe { args.get_unchecked(0) } != "~setgame" { - /// return; - /// } - /// - /// ctx.set_game(Game::playing(*unsafe { args.get_unchecked(1) })); - /// } - /// } - /// - /// let mut client = Client::new("token", Handler).unwrap(); - /// - /// client.start().unwrap(); - /// # } - /// ``` - /// - /// [`Online`]: ../model/user/enum.OnlineStatus.html#variant.Online - #[inline] - pub fn set_game(&self, game: Game) { - self.shard.set_presence(Some(game), OnlineStatus::Online); - } - - /// Sets the current game, passing in only its name. This will automatically - /// set the current user's [`OnlineStatus`] to [`Online`], and its - /// [`GameType`] as [`Playing`]. - /// - /// Use [`reset_presence`] to clear the current game, or [`set_presence`] - /// for more fine-grained control. - /// - /// **Note**: Maximum length is 128. - /// - /// # Examples - /// - /// When an [`Event::Ready`] is received, set the game name to `"test"`: - /// - /// ```rust,no_run - /// # use serenity::prelude::*; - /// # use serenity::model::gateway::Ready; - /// # - /// struct Handler; - /// - /// impl EventHandler for Handler { - /// fn ready(&self, ctx: Context, _: Ready) { - /// ctx.set_game_name("test"); - /// } - /// } - /// - /// let mut client = Client::new("token", Handler).unwrap(); - /// client.start().unwrap(); - /// ``` - /// - /// [`Event::Ready`]: ../model/event/enum.Event.html#variant.Ready - /// [`GameType`]: ../model/gateway/enum.GameType.html - /// [`Online`]: ../model/user/enum.OnlineStatus.html#variant.Online - /// [`OnlineStatus`]: ../model/user/enum.OnlineStatus.html - /// [`Playing`]: ../model/gateway/enum.GameType.html#variant.Playing - /// [`reset_presence`]: #method.reset_presence - /// [`set_presence`]: #method.set_presence - pub fn set_game_name(&self, game_name: &str) { - let game = Game { - kind: GameType::Playing, - name: game_name.to_string(), - url: None, - }; - - self.shard.set_presence(Some(game), OnlineStatus::Online); - } - - /// Sets the current user's presence, providing all fields to be passed. - /// - /// # Examples - /// - /// Setting the current user as having no game and being [`Idle`]: - /// - /// ```rust,no_run - /// # use serenity::prelude::*; - /// # use serenity::model::gateway::Ready; - /// # - /// struct Handler; - /// - /// impl EventHandler for Handler { - /// fn ready(&self, ctx: Context, _: Ready) { - /// use serenity::model::user::OnlineStatus; - /// - /// ctx.set_presence(None, OnlineStatus::Idle); - /// } - /// } - /// let mut client = Client::new("token", Handler).unwrap(); - /// - /// client.start().unwrap(); - /// ``` - /// - /// Setting the current user as playing `"Heroes of the Storm"`, while being - /// [`DoNotDisturb`]: - /// - /// ```rust,ignore - /// # use serenity::prelude::*; - /// # use serenity::model::gateway::Ready; - /// # - /// struct Handler; - /// - /// impl EventHandler for Handler { - /// fn ready(&self, context: Context, _: Ready) { - /// use serenity::model::gateway::Game; - /// use serenity::model::user::OnlineStatus; - /// - /// let game = Game::playing("Heroes of the Storm"); - /// let status = OnlineStatus::DoNotDisturb; - /// - /// context.set_presence(Some(game), status); - /// } - /// } - /// - /// let mut client = Client::new("token", Handler).unwrap(); - /// - /// client.start().unwrap(); - /// ``` - /// - /// [`DoNotDisturb`]: ../model/user/enum.OnlineStatus.html#variant.DoNotDisturb - /// [`Idle`]: ../model/user/enum.OnlineStatus.html#variant.Idle - #[inline] - pub fn set_presence(&self, game: Option<Game>, status: OnlineStatus) { - self.shard.set_presence(game, status); - } - - /// Disconnects the shard from the websocket, essentially "quiting" it. - /// Note however that this will only exit the one which the `Context` was given. - /// If it's just one shard that's on, then serenity will stop any further actions - /// until [`Client::start`] and vice versa are called again. - /// - /// [`Client::start`]: ./struct.Client.html#method.start - #[inline] - pub fn quit(&self) { - self.shard.shutdown_clean(); - } -} diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs deleted file mode 100644 index e076f93..0000000 --- a/src/client/dispatch.rs +++ /dev/null @@ -1,638 +0,0 @@ -use gateway::InterMessage; -use model::event::Event; -use model::channel::{Channel, Message}; -use std::sync::Arc; -use parking_lot::Mutex; -use super::bridge::gateway::event::ClientEvent; -use super::event_handler::EventHandler; -use super::Context; -use std::sync::mpsc::Sender; -use threadpool::ThreadPool; -use typemap::ShareMap; - -#[cfg(feature = "cache")] -use chrono::{Timelike, Utc}; -#[cfg(feature = "framework")] -use framework::Framework; -#[cfg(feature = "cache")] -use model::id::GuildId; -#[cfg(feature = "cache")] -use std::{thread, time}; - -#[cfg(feature = "cache")] -use super::CACHE; - -macro_rules! update { - ($event:expr) => { - { - #[cfg(feature="cache")] - { - CACHE.write().update(&mut $event) - } - } - }; -} - -#[cfg(feature = "cache")] -macro_rules! now { - () => (Utc::now().time().second() * 1000) -} - -fn context( - data: &Arc<Mutex<ShareMap>>, - runner_tx: &Sender<InterMessage>, - shard_id: u64, -) -> Context { - Context::new(Arc::clone(data), runner_tx.clone(), shard_id) -} - -pub(crate) enum DispatchEvent { - Client(ClientEvent), - Model(Event), -} - -#[cfg(feature = "framework")] -#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] -pub(crate) fn dispatch<H: EventHandler + Send + Sync + 'static>( - event: DispatchEvent, - framework: &Arc<Mutex<Option<Box<Framework + Send>>>>, - data: &Arc<Mutex<ShareMap>>, - event_handler: &Arc<H>, - runner_tx: &Sender<InterMessage>, - threadpool: &ThreadPool, - shard_id: u64, -) { - match event { - DispatchEvent::Model(Event::MessageCreate(event)) => { - let context = context(data, runner_tx, shard_id); - dispatch_message( - context.clone(), - event.message.clone(), - event_handler, - threadpool, - ); - - if let Some(ref mut framework) = *framework.lock() { - framework.dispatch(context, event.message, threadpool); - } - }, - other => handle_event( - other, - data, - event_handler, - runner_tx, - threadpool, - shard_id, - ), - } -} - -#[cfg(not(feature = "framework"))] -pub(crate) fn dispatch<H: EventHandler + Send + Sync + 'static>( - event: DispatchEvent, - data: &Arc<Mutex<ShareMap>>, - event_handler: &Arc<H>, - runner_tx: &Sender<InterMessage>, - threadpool: &ThreadPool, - shard_id: u64, -) { - match event { - DispatchEvent::Model(Event::MessageCreate(event)) => { - let context = context(data, runner_tx, shard_id); - dispatch_message(context, event.message, event_handler, threadpool); - }, - other => handle_event( - other, - data, - event_handler, - runner_tx, - threadpool, - shard_id, - ), - } -} - -#[allow(unused_mut)] -fn dispatch_message<H>( - context: Context, - mut message: Message, - event_handler: &Arc<H>, - threadpool: &ThreadPool, -) where H: EventHandler + Send + Sync + 'static { - message.transform_content(); - - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.message(context, message); - }); -} - -#[allow(cyclomatic_complexity, unused_assignments, unused_mut)] -fn handle_event<H: EventHandler + Send + Sync + 'static>( - event: DispatchEvent, - data: &Arc<Mutex<ShareMap>>, - event_handler: &Arc<H>, - runner_tx: &Sender<InterMessage>, - threadpool: &ThreadPool, - shard_id: u64, -) { - #[cfg(feature = "cache")] - let mut last_guild_create_time = now!(); - - #[cfg(feature = "cache")] - let wait_for_guilds = move || -> ::Result<()> { - let unavailable_guilds = CACHE.read().unavailable_guilds.len(); - - while unavailable_guilds != 0 && (now!() < last_guild_create_time + 2000) { - thread::sleep(time::Duration::from_millis(500)); - } - - Ok(()) - }; - - match event { - DispatchEvent::Client(ClientEvent::ShardStageUpdate(event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.shard_stage_update(context, event); - }); - } - DispatchEvent::Model(Event::ChannelCreate(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - - // This different channel_create dispatching is only due to the fact that - // each time the bot receives a dm, this event is also fired. - // So in short, only exists to reduce unnecessary clutter. - match event.channel { - Channel::Private(channel) => { - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.private_channel_create(context, channel); - }); - }, - Channel::Group(_) => {}, - Channel::Guild(channel) => { - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.channel_create(context, channel); - }); - }, - Channel::Category(channel) => { - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.category_create(context, channel); - }); - }, - } - }, - DispatchEvent::Model(Event::ChannelDelete(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - - match event.channel { - Channel::Private(_) | Channel::Group(_) => {}, - Channel::Guild(channel) => { - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.channel_delete(context, channel); - }); - }, - Channel::Category(channel) => { - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.category_delete(context, channel); - }); - }, - } - }, - DispatchEvent::Model(Event::ChannelPinsUpdate(mut event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.channel_pins_update(context, event); - }); - }, - DispatchEvent::Model(Event::ChannelRecipientAdd(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.channel_recipient_addition( - context, - event.channel_id, - event.user, - ); - }); - }, - DispatchEvent::Model(Event::ChannelRecipientRemove(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.channel_recipient_removal( - context, - event.channel_id, - event.user, - ); - }); - }, - DispatchEvent::Model(Event::ChannelUpdate(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - feature_cache! {{ - let before = CACHE.read().channel(event.channel.id()); - - event_handler.channel_update(context, before, event.channel); - } else { - event_handler.channel_update(context, event.channel); - }} - }); - }, - DispatchEvent::Model(Event::GuildBanAdd(mut event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.guild_ban_addition(context, event.guild_id, event.user); - }); - }, - DispatchEvent::Model(Event::GuildBanRemove(mut event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.guild_ban_removal(context, event.guild_id, event.user); - }); - }, - DispatchEvent::Model(Event::GuildCreate(mut event)) => { - #[cfg(feature = "cache")] - let _is_new = { - let cache = CACHE.read(); - - !cache.unavailable_guilds.contains(&event.guild.id) - }; - - update!(event); - - #[cfg(feature = "cache")] - { - last_guild_create_time = now!(); - - let cache = CACHE.read(); - - if cache.unavailable_guilds.is_empty() { - let context = context(data, runner_tx, shard_id); - - let guild_amount = cache - .guilds - .iter() - .map(|(&id, _)| id) - .collect::<Vec<GuildId>>(); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.cached(context, guild_amount); - }); - } - } - - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - feature_cache! {{ - event_handler.guild_create(context, event.guild, _is_new); - } else { - event_handler.guild_create(context, event.guild); - }} - }); - }, - DispatchEvent::Model(Event::GuildDelete(mut event)) => { - let _full = update!(event); - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - feature_cache! {{ - event_handler.guild_delete(context, event.guild, _full); - } else { - event_handler.guild_delete(context, event.guild); - }} - }); - }, - DispatchEvent::Model(Event::GuildEmojisUpdate(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.guild_emojis_update(context, event.guild_id, event.emojis); - }); - }, - DispatchEvent::Model(Event::GuildIntegrationsUpdate(mut event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.guild_integrations_update(context, event.guild_id); - }); - }, - DispatchEvent::Model(Event::GuildMemberAdd(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.guild_member_addition(context, event.guild_id, event.member); - }); - }, - DispatchEvent::Model(Event::GuildMemberRemove(mut event)) => { - let _member = update!(event); - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - feature_cache! {{ - event_handler.guild_member_removal(context, event.guild_id, event.user, _member); - } else { - event_handler.guild_member_removal(context, event.guild_id, event.user); - }} - }); - }, - DispatchEvent::Model(Event::GuildMemberUpdate(mut event)) => { - let _before = update!(event); - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - feature_cache! {{ - // This is safe to unwrap, as the update would have created - // the member if it did not exist. So, there is be _no_ way - // that this could fail under any circumstance. - let after = CACHE.read() - .member(event.guild_id, event.user.id) - .unwrap() - .clone(); - - event_handler.guild_member_update(context, _before, after); - } else { - event_handler.guild_member_update(context, event); - }} - }); - }, - DispatchEvent::Model(Event::GuildMembersChunk(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.guild_members_chunk(context, event.guild_id, event.members); - }); - }, - DispatchEvent::Model(Event::GuildRoleCreate(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.guild_role_create(context, event.guild_id, event.role); - }); - }, - DispatchEvent::Model(Event::GuildRoleDelete(mut event)) => { - let _role = update!(event); - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - feature_cache! {{ - event_handler.guild_role_delete(context, event.guild_id, event.role_id, _role); - } else { - event_handler.guild_role_delete(context, event.guild_id, event.role_id); - }} - }); - }, - DispatchEvent::Model(Event::GuildRoleUpdate(mut event)) => { - let _before = update!(event); - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - feature_cache! {{ - event_handler.guild_role_update(context, event.guild_id, _before, event.role); - } else { - event_handler.guild_role_update(context, event.guild_id, event.role); - }} - }); - }, - DispatchEvent::Model(Event::GuildUnavailable(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.guild_unavailable(context, event.guild_id); - }); - }, - DispatchEvent::Model(Event::GuildUpdate(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - feature_cache! {{ - let before = CACHE.read() - .guilds - .get(&event.guild.id) - .cloned(); - - event_handler.guild_update(context, before, event.guild); - } else { - event_handler.guild_update(context, event.guild); - }} - }); - }, - // Already handled by the framework check macro - DispatchEvent::Model(Event::MessageCreate(_)) => {}, - DispatchEvent::Model(Event::MessageDeleteBulk(mut event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.message_delete_bulk(context, event.channel_id, event.ids); - }); - }, - DispatchEvent::Model(Event::MessageDelete(mut event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.message_delete(context, event.channel_id, event.message_id); - }); - }, - DispatchEvent::Model(Event::MessageUpdate(mut event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.message_update(context, event); - }); - }, - DispatchEvent::Model(Event::PresencesReplace(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.presence_replace(context, event.presences); - }); - }, - DispatchEvent::Model(Event::PresenceUpdate(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.presence_update(context, event); - }); - }, - DispatchEvent::Model(Event::ReactionAdd(mut event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.reaction_add(context, event.reaction); - }); - }, - DispatchEvent::Model(Event::ReactionRemove(mut event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.reaction_remove(context, event.reaction); - }); - }, - DispatchEvent::Model(Event::ReactionRemoveAll(mut event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.reaction_remove_all(context, event.channel_id, event.message_id); - }); - }, - DispatchEvent::Model(Event::Ready(mut event)) => { - update!(event); - - let event_handler = Arc::clone(event_handler); - - feature_cache! {{ - last_guild_create_time = now!(); - - let _ = wait_for_guilds() - .map(move |_| { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(&event_handler); - - threadpool.execute(move || { - event_handler.ready(context, event.ready); - }); - }); - } else { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(&event_handler); - - threadpool.execute(move || { - event_handler.ready(context, event.ready); - }); - }} - }, - DispatchEvent::Model(Event::Resumed(mut event)) => { - let context = context(data, runner_tx, shard_id); - - event_handler.resume(context, event); - }, - DispatchEvent::Model(Event::TypingStart(mut event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.typing_start(context, event); - }); - }, - DispatchEvent::Model(Event::Unknown(mut event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.unknown(context, event.kind, event.value); - }); - }, - DispatchEvent::Model(Event::UserUpdate(mut event)) => { - let _before = update!(event); - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - feature_cache! {{ - event_handler.user_update(context, _before.unwrap(), event.current_user); - } else { - event_handler.user_update(context, event.current_user); - }} - }); - }, - DispatchEvent::Model(Event::VoiceServerUpdate(mut event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.voice_server_update(context, event); - }); - }, - DispatchEvent::Model(Event::VoiceStateUpdate(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.voice_state_update(context, event.guild_id, event.voice_state); - }); - }, - DispatchEvent::Model(Event::WebhookUpdate(mut event)) => { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(event_handler); - - threadpool.execute(move || { - event_handler.webhook_update(context, event.guild_id, event.channel_id); - }); - }, - } -} diff --git a/src/client/event_handler.rs b/src/client/event_handler.rs deleted file mode 100644 index 552c3a9..0000000 --- a/src/client/event_handler.rs +++ /dev/null @@ -1,85 +0,0 @@ -use model::prelude::*; -use parking_lot::RwLock; -use serde_json::Value; -use std::collections::HashMap; -use std::sync::Arc; -use super::context::Context; -use ::client::bridge::gateway::event::*; - -pub trait EventHandler { - #[cfg(feature = "cache")] - fn cached(&self, _: Context, _: Vec<GuildId>) {} - fn channel_create(&self, _: Context, _: Arc<RwLock<GuildChannel>>) {} - fn category_create(&self, _: Context, _: Arc<RwLock<ChannelCategory>>) {} - fn category_delete(&self, _: Context, _: Arc<RwLock<ChannelCategory>>) {} - fn private_channel_create(&self, _: Context, _: Arc<RwLock<PrivateChannel>>) {} - fn channel_delete(&self, _: Context, _: Arc<RwLock<GuildChannel>>) {} - fn channel_pins_update(&self, _: Context, _: ChannelPinsUpdateEvent) {} - fn channel_recipient_addition(&self, _: Context, _: ChannelId, _: User) {} - fn channel_recipient_removal(&self, _: Context, _: ChannelId, _: User) {} - #[cfg(feature = "cache")] - fn channel_update(&self, _: Context, _: Option<Channel>, _: Channel) {} - #[cfg(not(feature = "cache"))] - fn channel_update(&self, _: Context, _: Channel) {} - fn guild_ban_addition(&self, _: Context, _: GuildId, _: User) {} - fn guild_ban_removal(&self, _: Context, _: GuildId, _: User) {} - #[cfg(feature = "cache")] - fn guild_create(&self, _: Context, _: Guild, _: bool) {} - #[cfg(not(feature = "cache"))] - fn guild_create(&self, _: Context, _: Guild) {} - #[cfg(feature = "cache")] - fn guild_delete(&self, _: Context, _: PartialGuild, _: Option<Arc<RwLock<Guild>>>) {} - #[cfg(not(feature = "cache"))] - fn guild_delete(&self, _: Context, _: PartialGuild) {} - fn guild_emojis_update(&self, _: Context, _: GuildId, _: HashMap<EmojiId, Emoji>) {} - fn guild_integrations_update(&self, _: Context, _: GuildId) {} - fn guild_member_addition(&self, _: Context, _: GuildId, _: Member) {} - #[cfg(feature = "cache")] - fn guild_member_removal(&self, _: Context, _: GuildId, _: User, _: Option<Member>) {} - #[cfg(not(feature = "cache"))] - fn guild_member_removal(&self, _: Context, _: GuildId, _: User) {} - #[cfg(feature = "cache")] - fn guild_member_update(&self, _: Context, _: Option<Member>, _: Member) {} - #[cfg(not(feature = "cache"))] - fn guild_member_update(&self, _: Context, _: GuildMemberUpdateEvent) {} - fn guild_members_chunk(&self, _: Context, _: GuildId, _: HashMap<UserId, Member>) {} - fn guild_role_create(&self, _: Context, _: GuildId, _: Role) {} - #[cfg(feature = "cache")] - fn guild_role_delete(&self, _: Context, _: GuildId, _: RoleId, _: Option<Role>) {} - #[cfg(not(feature = "cache"))] - fn guild_role_delete(&self, _: Context, _: GuildId, _: RoleId) {} - #[cfg(feature = "cache")] - fn guild_role_update(&self, _: Context, _: GuildId, _: Option<Role>, _: Role) {} - #[cfg(not(feature = "cache"))] - fn guild_role_update(&self, _: Context, _: GuildId, _: Role) {} - fn guild_unavailable(&self, _: Context, _: GuildId) {} - #[cfg(feature = "cache")] - fn guild_update(&self, _: Context, _: Option<Arc<RwLock<Guild>>>, _: PartialGuild) {} - #[cfg(not(feature = "cache"))] - fn guild_update(&self, _: Context, _: PartialGuild) {} - fn message(&self, _: Context, _: Message) {} - fn message_delete(&self, _: Context, _: ChannelId, _: MessageId) {} - fn message_delete_bulk(&self, _: Context, _: ChannelId, _: Vec<MessageId>) {} - fn reaction_add(&self, _: Context, _: Reaction) {} - fn reaction_remove(&self, _: Context, _: Reaction) {} - fn reaction_remove_all(&self, _: Context, _: ChannelId, _: MessageId) {} - fn message_update(&self, _: Context, _: MessageUpdateEvent) {} - fn presence_replace(&self, _: Context, _: Vec<Presence>) {} - fn presence_update(&self, _: Context, _: PresenceUpdateEvent) {} - fn ready(&self, _: Context, _: Ready) {} - fn resume(&self, _: Context, _: ResumedEvent) {} - - /// Called when a shard's connection stage is updated, providing the context - /// of the shard and the event information about the update. - fn shard_stage_update(&self, _: Context, _: ShardStageUpdateEvent) {} - - fn typing_start(&self, _: Context, _: TypingStartEvent) {} - fn unknown(&self, _: Context, _: String, _: Value) {} - #[cfg(feature = "cache")] - fn user_update(&self, _: Context, _: CurrentUser, _: CurrentUser) {} - #[cfg(not(feature = "cache"))] - fn user_update(&self, _: Context, _: CurrentUser) {} - fn voice_server_update(&self, _: Context, _: VoiceServerUpdateEvent) {} - fn voice_state_update(&self, _: Context, _: Option<GuildId>, _: VoiceState) {} - fn webhook_update(&self, _: Context, _: GuildId, _: ChannelId) {} -} diff --git a/src/framework/standard/args.rs b/src/framework/standard/args.rs index 87cc0a5..1a819b6 100644 --- a/src/framework/standard/args.rs +++ b/src/framework/standard/args.rs @@ -159,7 +159,7 @@ impl Args { /// ``` pub fn single<T: FromStr>(&mut self) -> Result<T, T::Err> where T::Err: StdError { - if self.message.is_empty() { + if self.is_empty() { return Err(Error::Eos); } @@ -186,7 +186,7 @@ impl Args { /// [`single`]: #method.single pub fn single_n<T: FromStr>(&self) -> Result<T, T::Err> where T::Err: StdError { - if self.message.is_empty() { + if self.is_empty() { return Err(Error::Eos); } @@ -221,7 +221,7 @@ impl Args { if let Some(len) = self.len { len - } else if self.message.is_empty() { + } else if self.is_empty() { 0 } else { let mut words: Box<Iterator<Item = &str>> = Box::new(Some(&self.message[..]).into_iter()); @@ -263,7 +263,7 @@ impl Args { /// assert_eq!(args.len_quoted(), 2); // `2` because `["42", "69"]` /// ``` pub fn len_quoted(&mut self) -> usize { - if self.message.is_empty() { + if self.is_empty() { 0 } else if let Some(len_quoted) = self.len_quoted { len_quoted @@ -291,6 +291,10 @@ impl Args { /// assert_eq!(args.full(), "69"); /// ``` pub fn skip(&mut self) -> Option<String> { + if self.is_empty() { + return None; + } + if let Some(ref mut val) = self.len { if 1 <= *val { @@ -315,6 +319,10 @@ impl Args { /// /// [`skip`]: #method.skip pub fn skip_for(&mut self, i: u32) -> Option<Vec<String>> { + if self.is_empty() { + return None; + } + let mut vec = Vec::with_capacity(i as usize); for _ in 0..i { @@ -349,6 +357,10 @@ impl Args { /// [`single`]: #method.single pub fn single_quoted<T: FromStr>(&mut self) -> Result<T, T::Err> where T::Err: StdError { + if self.is_empty() { + return Err(Error::Eos); + } + if let Some(ref mut val) = self.len_quoted { *val -= 1 }; @@ -372,6 +384,10 @@ impl Args { /// [`single_quoted`]: #method.single_quoted pub fn single_quoted_n<T: FromStr>(&self) -> Result<T, T::Err> where T::Err: StdError { + if self.is_empty() { + return Err(Error::Eos); + } + parse_quotes::<T>(&mut self.message.clone(), &self.delimiters) } @@ -390,6 +406,10 @@ impl Args { /// [`multiple`]: #method.multiple pub fn multiple_quoted<T: FromStr>(mut self) -> Result<Vec<T>, T::Err> where T::Err: StdError { + if self.is_empty() { + return Err(Error::Eos); + } + IterQuoted::<T>::new(&mut self).collect() } @@ -425,6 +445,10 @@ impl Args { /// ``` pub fn multiple<T: FromStr>(mut self) -> Result<Vec<T>, T::Err> where T::Err: StdError { + if self.is_empty() { + return Err(Error::Eos); + } + Iter::<T>::new(&mut self).collect() } @@ -441,7 +465,7 @@ impl Args { /// assert!(args.is_empty()); /// ``` pub fn iter<T: FromStr>(&mut self) -> Iter<T> - where T::Err: StdError { + where T::Err: StdError { Iter::new(self) } @@ -462,7 +486,7 @@ impl Args { /// ``` pub fn find<T: FromStr>(&mut self) -> Result<T, T::Err> where T::Err: StdError { - if self.message.is_empty() { + if self.is_empty() { return Err(Error::Eos); } @@ -521,7 +545,7 @@ impl Args { /// ``` pub fn find_n<T: FromStr>(&mut self) -> Result<T, T::Err> where T::Err: StdError { - if self.message.is_empty() { + if self.is_empty() { return Err(Error::Eos); } diff --git a/src/framework/standard/command.rs b/src/framework/standard/command.rs index 541d1b9..a971c1f 100644 --- a/src/framework/standard/command.rs +++ b/src/framework/standard/command.rs @@ -32,6 +32,7 @@ impl HelpCommand for Help { pub type BeforeHook = Fn(&mut Context, &Message, &str) -> bool + Send + Sync + 'static; pub type AfterHook = Fn(&mut Context, &Message, &str, Result<(), Error>) + Send + Sync + 'static; +pub type UnrecognisedCommandHook = Fn(&mut Context, &Message, &str) + Send + Sync + 'static; pub(crate) type InternalCommand = Arc<Command>; pub type PrefixCheck = Fn(&mut Context, &Message) -> Option<String> + Send + Sync + 'static; diff --git a/src/framework/standard/create_command.rs b/src/framework/standard/create_command.rs index 672a955..8fbb975 100644 --- a/src/framework/standard/create_command.rs +++ b/src/framework/standard/create_command.rs @@ -218,6 +218,7 @@ impl CreateCommand { } /// Sets roles that are allowed to use the command. + #[cfg(feature="cache")] pub fn allowed_roles<T: ToString, It: IntoIterator<Item=T>>(mut self, allowed_roles: It) -> Self { self.0.allowed_roles = allowed_roles.into_iter().map(|x| x.to_string()).collect(); @@ -225,7 +226,7 @@ impl CreateCommand { } /// Sets an initialise middleware to be called upon the command's actual registration. - /// + /// /// This is similiar to implementing the `init` function on `Command`. pub fn init<F: Fn() + Send + Sync + 'static>(mut self, f: F) -> Self { self.2.init = Some(Arc::new(f)); @@ -234,9 +235,9 @@ impl CreateCommand { } /// Sets a before middleware to be called before the command's execution. - /// + /// /// This is similiar to implementing the `before` function on `Command`. - pub fn before<F: Send + Sync + 'static>(mut self, f: F) -> Self + pub fn before<F: Send + Sync + 'static>(mut self, f: F) -> Self where F: Fn(&mut Context, &Message) -> bool { self.2.before = Some(Arc::new(f)); @@ -244,9 +245,9 @@ impl CreateCommand { } /// Sets an after middleware to be called after the command's execution. - /// + /// /// This is similiar to implementing the `after` function on `Command`. - pub fn after<F: Send + Sync + 'static>(mut self, f: F) -> Self + pub fn after<F: Send + Sync + 'static>(mut self, f: F) -> Self where F: Fn(&mut Context, &Message, &Result<(), CommandError>) { self.2.after = Some(Arc::new(f)); diff --git a/src/framework/standard/create_group.rs b/src/framework/standard/create_group.rs index df3d97f..c33f67a 100644 --- a/src/framework/standard/create_group.rs +++ b/src/framework/standard/create_group.rs @@ -148,6 +148,7 @@ impl CreateGroup { } /// Sets roles that are allowed to use the command. + #[cfg(feature = "cache")] pub fn allowed_roles<T: ToString, It: IntoIterator<Item=T>>(mut self, allowed_roles: It) -> Self { self.0.allowed_roles = allowed_roles.into_iter().map(|x| x.to_string()).collect(); diff --git a/src/framework/standard/help_commands.rs b/src/framework/standard/help_commands.rs index d740086..9979423 100644 --- a/src/framework/standard/help_commands.rs +++ b/src/framework/standard/help_commands.rs @@ -24,6 +24,7 @@ //! [`with_embeds`]: fn.with_embeds.html use client::Context; +#[cfg(feature = "cache")] use framework::standard::{has_correct_roles, has_correct_permissions}; use model::channel::Message; use model::id::ChannelId; @@ -55,6 +56,7 @@ fn remove_aliases(cmds: &HashMap<String, CommandOrAlias>) -> HashMap<&String, &I /// Checks whether a user is member of required roles /// and given the required permissions. +#[cfg(feature = "cache")] pub fn has_all_requirements(cmd: &Arc<CommandOptions>, msg: &Message) -> bool { if let Some(guild) = msg.guild() { let guild = guild.read(); @@ -92,6 +94,7 @@ pub fn has_all_requirements(cmd: &Arc<CommandOptions>, msg: &Message) -> bool { /// client.with_framework(StandardFramework::new() /// .help(help_commands::with_embeds)); /// ``` +#[cfg(feature = "cache")] pub fn with_embeds<H: BuildHasher>( _: &mut Context, msg: &Message, @@ -153,20 +156,20 @@ pub fn with_embeds<H: BuildHasher>( let _ = msg.channel_id.send_message(|m| { m.embed(|e| { - let mut embed = e.colour(help_options.embed_success_colour).title(command_name.clone()); + let mut embed = e.colour(help_options.embed_success_colour).title(command_name); if let Some(ref desc) = command.desc { embed = embed.description(desc); } if let Some(ref usage) = command.usage { - let value = format!("`{} {}`", command_name.clone(), usage); + let value = format!("`{} {}`", command_name, usage); embed = embed.field(&help_options.usage_label, value, true); } if let Some(ref example) = command.example { - let value = format!("`{} {}`", command_name.clone(), example); + let value = format!("`{} {}`", command_name, example); embed = embed.field(&help_options.usage_sample_label, value, true); } @@ -208,9 +211,9 @@ pub fn with_embeds<H: BuildHasher>( let _ = msg.channel_id.send_message(|m| { m.embed(|mut e| { - if let Some(striked_command_text) = help_options.striked_commands_tip.clone() { + if let Some(ref striked_command_text) = help_options.striked_commands_tip { e = e.colour(help_options.embed_success_colour).description( - format!("{}\n{}", &help_options.individual_command_tip, &striked_command_text), + format!("{}\n{}", &help_options.individual_command_tip, striked_command_text), ); } else { e = e.colour(help_options.embed_success_colour).description( @@ -335,6 +338,7 @@ pub fn with_embeds<H: BuildHasher>( /// client.with_framework(StandardFramework::new() /// .help(help_commands::plain)); /// ``` +#[cfg(feature = "cache")] pub fn plain<H: BuildHasher>( _: &mut Context, msg: &Message, @@ -445,8 +449,8 @@ pub fn plain<H: BuildHasher>( let mut result = "**Commands**\n".to_string(); - if let Some(striked_command_text) = help_options.striked_commands_tip.clone() { - let _ = write!(result, "{}\n{}\n\n", &help_options.individual_command_tip, &striked_command_text); + if let Some(ref striked_command_text) = help_options.striked_commands_tip { + let _ = write!(result, "{}\n{}\n\n", &help_options.individual_command_tip, striked_command_text); } else { let _ = write!(result, "{}\n\n", &help_options.individual_command_tip); } diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs index 16e7757..a4fbad0 100644 --- a/src/framework/standard/mod.rs +++ b/src/framework/standard/mod.rs @@ -25,7 +25,7 @@ use model::channel::Message; use model::guild::{Guild, Member}; use model::id::{ChannelId, GuildId, UserId}; use model::Permissions; -use self::command::{AfterHook, BeforeHook}; +use self::command::{AfterHook, BeforeHook, UnrecognisedCommandHook}; use std::collections::HashMap; use std::default::Default; use std::sync::Arc; @@ -205,6 +205,7 @@ pub struct StandardFramework { dispatch_error_handler: Option<Arc<DispatchErrorHook>>, buckets: HashMap<String, Bucket>, after: Option<Arc<AfterHook>>, + unrecognised_command: Option<Arc<UnrecognisedCommandHook>>, /// Whether the framework has been "initialized". /// /// The framework is initialized once one of the following occurs: @@ -566,15 +567,19 @@ impl StandardFramework { } else if self.configuration.disabled_commands.contains(built) { Some(DispatchError::CommandDisabled(built.to_string())) } else { - if !command.allowed_roles.is_empty() { - if let Some(guild) = message.guild() { - let guild = guild.read(); - - if let Some(member) = guild.members.get(&message.author.id) { - if let Ok(permissions) = member.permissions() { - if !permissions.administrator() - && !has_correct_roles(command, &guild, member) { - return Some(DispatchError::LackingRole); + + #[cfg(feature = "cache")] { + if !command.allowed_roles.is_empty() { + if let Some(guild) = message.guild() { + let guild = guild.read(); + + if let Some(member) = guild.members.get(&message.author.id) { + if let Ok(permissions) = member.permissions() { + + if !permissions.administrator() + && !has_correct_roles(command, &guild, member) { + return Some(DispatchError::LackingRole); + } } } } @@ -879,6 +884,31 @@ impl StandardFramework { self } + /// Specify the function to be called if no command could be dispatched. + /// + /// # Examples + /// + /// Using `unrecognised_command`: + /// + /// ```rust + /// # use serenity::prelude::*; + /// # struct Handler; + /// # + /// # impl EventHandler for Handler {} + /// # let mut client = Client::new("token", Handler).unwrap(); + /// # + /// use serenity::framework::StandardFramework; + /// + /// client.with_framework(StandardFramework::new() + /// .unrecognised_command(|ctx, msg, unrecognised_command_name| { })); + /// ``` + pub fn unrecognised_command<F>(mut self, f: F) -> Self + where F: Fn(&mut Context, &Message, &str) + Send + Sync + 'static { + self.unrecognised_command = Some(Arc::new(f)); + + self + } + /// Sets what code should be executed when a user sends `(prefix)help`. pub fn help(mut self, f: HelpFunction) -> Self { let a = CreateHelpCommand(HelpOptions::default(), f).finish(); @@ -909,6 +939,7 @@ impl Framework for StandardFramework { threadpool: &ThreadPool, ) { let res = command::positions(&mut context, &message, &self.configuration); + let mut unrecognised_command_name = String::from(""); let positions = match res { Some(mut positions) => { @@ -953,13 +984,17 @@ impl Framework for StandardFramework { built }; +<<<<<<< HEAD +======= + unrecognised_command_name = built.clone(); +>>>>>>> master let cmd = group.commands.get(&built); if let Some(&CommandOrAlias::Alias(ref points_to)) = cmd { built = points_to.to_string(); } - let mut to_check = if let Some(ref prefix) = group.prefix { + let to_check = if let Some(ref prefix) = group.prefix { if built.starts_with(prefix) && command_length > prefix.len() + 1 { built[(prefix.len() + 1)..].to_string() } else { @@ -1049,13 +1084,20 @@ impl Framework for StandardFramework { } } } + + let unrecognised_command = self.unrecognised_command.clone(); + + threadpool.execute(move || { + if let Some(unrecognised_command) = unrecognised_command { + (unrecognised_command)(&mut context, &message, &unrecognised_command_name); + } + }); } fn update_current_user(&mut self, user_id: UserId) { self.user_id = user_id.0; } } - #[cfg(feature = "cache")] pub fn has_correct_permissions(command: &Arc<CommandOptions>, message: &Message) -> bool { if !command.required_permissions.is_empty() { @@ -1070,7 +1112,6 @@ pub fn has_correct_permissions(command: &Arc<CommandOptions>, message: &Message) true } -#[cfg(feature = "cache")] pub fn has_correct_roles(cmd: &Arc<CommandOptions>, guild: &Guild, member: &Member) -> bool { if cmd.allowed_roles.is_empty() { true diff --git a/src/http/mod.rs b/src/http/mod.rs index 52845af..b874406 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -1054,6 +1054,23 @@ impl Client { self.get(Route::GetGuildRoles { guild_id }) } + /// Gets a guild's vanity URL if it has one. + pub fn get_guild_vanity_url(&self, guild_id: u64) + -> FutureResult<String> { + #[derive(Deserialize)] + struct GuildVanityUrl { + code: String, + } + + let done = self.get::<GuildVanityUrl>( + Route::GetGuildVanityUrl { guild_id }, + ).map(|resp| { + resp.code + }); + + Box::new(done) + } + /// Retrieves the webhooks for the given [guild][`Guild`]'s Id. /// /// This method requires authentication. @@ -1366,6 +1383,7 @@ impl Client { // Value::Bool(true) => request.write_text(&k, "true")?, // Value::Number(inner) => request.write_text(&k, inner.to_string())?, // Value::String(inner) => request.write_text(&k, inner)?, + // Value::Object(inner) => request.write_text(&k, serde_json::to_string(&inner)?)?, // _ => continue, // }; // } diff --git a/src/http/routing.rs b/src/http/routing.rs index 780d21c..53b7875 100644 --- a/src/http/routing.rs +++ b/src/http/routing.rs @@ -227,6 +227,12 @@ pub enum Path { /// /// [`GuildId`]: struct.GuildId.html GuildsIdRolesId(u64), + /// Route for the `/guilds/:guild_id/vanity-url` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdVanityUrl(u64), /// Route for the `/guilds/:guild_id/webhooks` path. /// /// The data is the relevant [`GuildId`]. @@ -501,6 +507,10 @@ impl Path { format!(api!("/guilds/{}/roles"), guild_id) } + pub fn guild_vanity_url(guild_id: u64) -> String { + format!(api!("/guilds/{}/vanity-url"), guild_id) + } + pub fn guild_webhooks(guild_id: u64) -> String { format!(api!("/guilds/{}/webhooks"), guild_id) } @@ -632,6 +642,7 @@ impl Path { GuildsIdRegions(_) => "/guilds/{}/regions", GuildsIdRoles(_) => "/guilds/{}/roles", GuildsIdRolesId(_) => "/guilds/{}/roles/{}", + GuildsIdVanityUrl(_) => "/guilds/{}/vanity-url", GuildsIdWebhooks(_) => "/guilds/{}/webhooks", InvitesCode => "/invites/{}", StatusMaintenancesActive => "/scheduled-maintenances/active.json", @@ -852,6 +863,9 @@ pub enum Route<'a> { GetGuildRoles { guild_id: u64, }, + GetGuildVanityUrl { + guild_id: u64, + }, GetGuildWebhooks { guild_id: u64, }, @@ -1276,6 +1290,11 @@ impl<'a> Route<'a> { Path::GuildsIdRoles(guild_id), Cow::from(Path::guild_roles(guild_id)), ), + Route::GetGuildVanityUrl { guild_id } => ( + LightMethod::Get, + Path::GuildsIdVanityUrl(guild_id), + Cow::from(Path::guild_vanity_url(guild_id)), + ), Route::GetGuildWebhooks { guild_id } => ( LightMethod::Get, Path::GuildsIdWebhooks(guild_id), diff --git a/src/model/event.rs b/src/model/event.rs index fe710da..44135dc 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -176,7 +176,7 @@ impl<'de> Deserialize<'de> for GuildMemberAddEvent { let guild_id = map.get("guild_id") .ok_or_else(|| DeError::custom("missing member add guild id")) - .and_then(|v| GuildId::deserialize(v.clone())) + .and_then(|v| GuildId::deserialize(v)) .map_err(DeError::custom)?; Ok(GuildMemberAddEvent { @@ -213,7 +213,7 @@ impl<'de> Deserialize<'de> for GuildMembersChunkEvent { let guild_id = map.get("guild_id") .ok_or_else(|| DeError::custom("missing member chunk guild id")) - .and_then(|v| GuildId::deserialize(v.clone())) + .and_then(|v| GuildId::deserialize(v)) .map_err(DeError::custom)?; let mut members = map.remove("members") @@ -516,7 +516,7 @@ impl<'de> Deserialize<'de> for VoiceStateUpdateEvent { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { let map = JsonMap::deserialize(deserializer)?; let guild_id = match map.get("guild_id") { - Some(v) => Some(GuildId::deserialize(v.clone()).map_err(DeError::custom)?), + Some(v) => Some(GuildId::deserialize(v).map_err(DeError::custom)?), None => None, }; diff --git a/src/model/gateway.rs b/src/model/gateway.rs index b5fa84b..28968f7 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -228,7 +228,7 @@ impl<'de> Deserialize<'de> for Presence { let user_id = user_map .remove("id") .ok_or_else(|| DeError::custom("Missing presence user id")) - .and_then(|x| UserId::deserialize(x.clone())) + .and_then(|x| UserId::deserialize(x)) .map_err(DeError::custom)?; (user_id, None) diff --git a/src/voice/audio.rs b/src/voice/audio.rs index 8a001d1..367c8da 100644 --- a/src/voice/audio.rs +++ b/src/voice/audio.rs @@ -1,5 +1,6 @@ use parking_lot::Mutex; use std::sync::Arc; +use std::time::Duration; pub const HEADER_LEN: usize = 12; pub const SAMPLE_RATE: u32 = 48_000; @@ -36,12 +37,63 @@ pub enum AudioType { /// Control object for audio playback. /// /// Accessed by both commands and the playback code -- as such, access is -/// always guarded. +/// always guarded. In particular, you should expect to receive +/// a [`LockedAudio`] when calling [`Handler::play_returning`] or +/// [`Handler::play_only`]. +/// +/// # Example +/// +/// ```rust,ignore +/// use serenity::voice::{Handler, LockedAudio, ffmpeg}; +/// +/// let handler: Handler = /* ... */; +/// let source = ffmpeg("../audio/my-favourite-song.mp3")?; +/// let safe_audio: LockedAudio = handler.play_only(); +/// { +/// let audio_lock = safe_audio_control.clone(); +/// let mut audio = audio_lock.lock(); +/// +/// audio.volume(0.5); +/// } +/// ``` +/// +/// [`LockedAudio`]: type.LockedAudio.html +/// [`Handler::play_only`]: struct.Handler.html#method.play_only +/// [`Handler::play_returning`]: struct.Handler.html#method.play_returning pub struct Audio { + + /// Whether or not this sound is currently playing. + /// + /// Can be controlled with [`play`] or [`pause`] + /// if chaining is desired. + /// + /// [`play`]: #method.play + /// [`pause`]: #method.pause pub playing: bool, + + /// The desired volume for playback. + /// + /// Sensible values fall between `0.0` and `1.0`. + /// Can be controlled with [`volume`] if chaining is desired. + /// + /// [`volume`]: #method.volume pub volume: f32, + + /// Whether or not the sound has finished, or reached the end of its stream. + /// + /// ***Read-only*** for now. pub finished: bool, + + /// Underlying data access object. + /// + /// *Calling code is not expected to use this.* pub source: Box<AudioSource>, + + /// The current position for playback. + /// + /// Consider the position fields **read-only** for now. + pub position: Duration, + pub position_modified: bool, } impl Audio { @@ -51,26 +103,56 @@ impl Audio { volume: 1.0, finished: false, source, + position: Duration::new(0, 0), + position_modified: false, } } + /// Sets [`playing`] to `true` in a manner that allows method chaining. + /// + /// [`playing`]: #structfield.playing pub fn play(&mut self) -> &mut Self { self.playing = true; self } + /// Sets [`playing`] to `false` in a manner that allows method chaining. + /// + /// [`playing`]: #structfield.playing pub fn pause(&mut self) -> &mut Self { self.playing = false; self } + /// Sets [`volume`] in a manner that allows method chaining. + /// + /// [`volume`]: #structfield.volume pub fn volume(&mut self, volume: f32) -> &mut Self { self.volume = volume; self } + + /// Change the position in the stream for subsequent playback. + /// + /// Currently a No-op. + pub fn position(&mut self, position: Duration) -> &mut Self { + self.position = position; + self.position_modified = true; + + self + } + + /// Steps playback location forward by one frame. + /// + /// *Used internally*, although in future this might affect seek position. + pub(crate) fn step_frame(&mut self) { + self.position += Duration::from_millis(20); + self.position_modified = false; + } + } /// Threadsafe form of an instance of the [`Audio`] struct, locked behind a diff --git a/src/voice/connection.rs b/src/voice/connection.rs index 3588818..a98dbc7 100644 --- a/src/voice/connection.rs +++ b/src/voice/connection.rs @@ -11,6 +11,7 @@ use opus::{ Channels, Decoder as OpusDecoder, Encoder as OpusEncoder, + SoftClip, }; use parking_lot::Mutex; use serde::Deserialize; @@ -58,6 +59,7 @@ pub struct Connection { key: Key, sequence: u16, silence_frames: u8, + soft_clip: SoftClip, speaking: bool, ssrc: u32, thread_items: ThreadItems, @@ -151,6 +153,8 @@ impl Connection { let encoder = OpusEncoder::new(SAMPLE_RATE, Channels::Mono, CodingMode::Audio)?; + let soft_clip = SoftClip::new(Channels::Stereo); + // Per discord dev team's current recommendations: // (https://discordapp.com/developers/docs/topics/voice-connections#heartbeating) let temp_heartbeat = (hello.heartbeat_interval as f64 * 0.75) as u64; @@ -159,17 +163,18 @@ impl Connection { audio_timer: Timer::new(1000 * 60 * 4), client: mutexed_client, decoder_map: HashMap::new(), - destination: destination, - encoder: encoder, + destination, + encoder, encoder_stereo: false, - key: key, + key, keepalive_timer: Timer::new(temp_heartbeat), - udp: udp, + udp, sequence: 0, silence_frames: 0, + soft_clip, speaking: false, ssrc: hello.ssrc, - thread_items: thread_items, + thread_items, timestamp: 0, user_id: info.user_id, }) @@ -344,14 +349,20 @@ impl Connection { } aud.finished = finished; + + if !finished { + aud.step_frame(); + } }; + self.soft_clip.apply(&mut mix_buffer); + if len == 0 { if self.silence_frames > 0 { self.silence_frames -= 1; // Explicit "Silence" frame. - opus_frame.extend_from_slice(&[0xf, 0x8, 0xf, 0xf, 0xf, 0xe]); + opus_frame.extend_from_slice(&[0xf8, 0xff, 0xfe]); } else { // Per official guidelines, send 5x silence BEFORE we stop speaking. self.set_speaking(false)?; @@ -453,7 +464,7 @@ fn combine_audio( let sample_index = if true_stereo { i } else { i/2 }; let sample = (raw_buffer[sample_index] as f32) / 32768.0; - float_buffer[i] = (float_buffer[i] + sample*volume).max(-1.0).min(1.0); + float_buffer[i] = float_buffer[i] + sample * volume; } } diff --git a/src/voice/handler.rs b/src/voice/handler.rs index 82e117c..4124d66 100644 --- a/src/voice/handler.rs +++ b/src/voice/handler.rs @@ -257,10 +257,11 @@ impl Handler { /// Plays audio from a source. /// - /// Unlike `play`, this stops all other sources attached + /// Unlike [`play`] or [`play_returning`], this stops all other sources attached /// to the channel. /// /// [`play`]: #method.play + /// [`play_returning`]: #method.play_returning pub fn play_only(&mut self, source: Box<AudioSource>) -> LockedAudio { let player = Arc::new(Mutex::new(Audio::new(source))); self.send(VoiceStatus::SetSender(Some(player.clone()))); diff --git a/src/voice/streamer.rs b/src/voice/streamer.rs index a0334f3..c5954c8 100644 --- a/src/voice/streamer.rs +++ b/src/voice/streamer.rs @@ -16,6 +16,14 @@ impl Read for ChildContainer { } } +impl Drop for ChildContainer { + fn drop (&mut self) { + if let Err(e) = self.0.wait() { + debug!("[Voice] Error awaiting child process: {:?}", e); + } + } +} + struct InputSource<R: Read + Send + 'static> { stereo: bool, reader: R, @@ -146,7 +154,12 @@ pub fn dca<P: AsRef<OsStr>>(path: P) -> StdResult<Box<AudioSource>, DcaError> { Ok(opus(metadata.is_stereo(), reader)) } -/// Creates an Opus audio source. +/// Creates an Opus audio source. This makes certain assumptions: namely, that the input stream +/// is composed ONLY of opus frames of the variety that Discord expects. +/// +/// If you want to decode a `.opus` file, use [`ffmpeg`] +/// +/// [`ffmpeg`]: fn.ffmpeg.html pub fn opus<R: Read + Send + 'static>(is_stereo: bool, reader: R) -> Box<AudioSource> { Box::new(InputSource { stereo: is_stereo, |