aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorZeyla Hellyer <[email protected]>2018-03-25 19:48:53 -0700
committerZeyla Hellyer <[email protected]>2018-03-25 19:48:53 -0700
commite2f3ece27934af8b7b756a70d3b6090385d37c1e (patch)
treee8085382bb92f88bfbaf95042360eb66f0a1921a /src
parentRemove `http::FutureResult`, use `error`'s (diff)
parentRemove useless clones (#292) (diff)
downloadserenity-e2f3ece27934af8b7b756a70d3b6090385d37c1e.tar.xz
serenity-e2f3ece27934af8b7b756a70d3b6090385d37c1e.zip
Merge branch 'master' into futures
Diffstat (limited to 'src')
-rw-r--r--src/builder/create_embed.rs16
-rw-r--r--src/client/context.rs430
-rw-r--r--src/client/dispatch.rs638
-rw-r--r--src/client/event_handler.rs85
-rw-r--r--src/framework/standard/args.rs38
-rw-r--r--src/framework/standard/command.rs1
-rw-r--r--src/framework/standard/create_command.rs11
-rw-r--r--src/framework/standard/create_group.rs1
-rw-r--r--src/framework/standard/help_commands.rs18
-rw-r--r--src/framework/standard/mod.rs67
-rw-r--r--src/http/mod.rs18
-rw-r--r--src/http/routing.rs19
-rw-r--r--src/model/event.rs6
-rw-r--r--src/model/gateway.rs2
-rw-r--r--src/voice/audio.rs84
-rw-r--r--src/voice/connection.rs25
-rw-r--r--src/voice/handler.rs3
-rw-r--r--src/voice/streamer.rs15
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,