diff options
| author | Mishio595 <[email protected]> | 2018-07-09 21:57:59 -0600 |
|---|---|---|
| committer | Mishio595 <[email protected]> | 2018-07-09 21:57:59 -0600 |
| commit | b457eb5415e692f2edc62c39a7d936d6976654de (patch) | |
| tree | 0155ba89ea4d16a3ec05a5ee88e5844c58f40c27 /src | |
| parent | Merge https://github.com/serenity-rs/serenity (diff) | |
| parent | Fix ffmpeg_optioned doctest (diff) | |
| download | serenity-b457eb5415e692f2edc62c39a7d936d6976654de.tar.xz serenity-b457eb5415e692f2edc62c39a7d936d6976654de.zip | |
Merge https://github.com/serenity-rs/serenity
Diffstat (limited to 'src')
| -rw-r--r-- | src/cache/mod.rs | 77 | ||||
| -rw-r--r-- | src/cache/settings.rs | 50 | ||||
| -rw-r--r-- | src/client/dispatch.rs | 8 | ||||
| -rw-r--r-- | src/framework/standard/args.rs | 19 | ||||
| -rw-r--r-- | src/framework/standard/command.rs | 4 | ||||
| -rw-r--r-- | src/framework/standard/mod.rs | 2 | ||||
| -rw-r--r-- | src/lib.rs | 9 | ||||
| -rw-r--r-- | src/model/channel/message.rs | 25 | ||||
| -rw-r--r-- | src/model/event.rs | 81 | ||||
| -rw-r--r-- | src/model/id.rs | 6 | ||||
| -rw-r--r-- | src/utils/colour.rs | 78 | ||||
| -rw-r--r-- | src/voice/mod.rs | 1 | ||||
| -rw-r--r-- | src/voice/streamer.rs | 43 |
13 files changed, 343 insertions, 60 deletions
diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 3bf5a5a..6b6e1c4 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -47,7 +47,8 @@ use parking_lot::RwLock; use std::collections::{ hash_map::Entry, HashMap, - HashSet + HashSet, + VecDeque, }; use std::{ default::Default, @@ -55,8 +56,12 @@ use std::{ }; mod cache_update; +mod settings; pub use self::cache_update::CacheUpdate; +pub use self::settings::Settings; + +type MessageCache = HashMap<ChannelId, HashMap<MessageId, Message>>; /// A cache of all events received over a [`Shard`], where storing at least /// some data from the event is possible. @@ -97,6 +102,12 @@ pub struct Cache { /// [`Emoji`]: ../model/guild/struct.Emoji.html /// [`Role`]: ../model/guild/struct.Role.html pub guilds: HashMap<GuildId, Arc<RwLock<Guild>>>, + /// A map of channels to messages. + /// + /// This is a map of channel IDs to another map of message IDs to messages. + /// + /// This keeps only the ten most recent messages. + pub messages: MessageCache, /// A map of notes that a user has made for individual users. /// /// An empty note is equivalent to having no note, and creating an empty @@ -160,6 +171,15 @@ pub struct Cache { /// [`PresenceUpdateEvent`]: ../model/event/struct.PresenceUpdateEvent.html /// [`ReadyEvent`]: ../model/event/struct.ReadyEvent.html pub users: HashMap<UserId, Arc<RwLock<User>>>, + /// Queue of message IDs for each channel. + /// + /// This is simply a vecdeque so we can keep track of the order of messages + /// inserted into the cache. When a maximum number of messages are in a + /// channel's cache, we can pop the front and remove that ID from the cache. + pub(crate) message_queue: HashMap<ChannelId, VecDeque<MessageId>>, + /// The settings for the cache. + settings: Settings, + __nonexhaustive: (), } impl Cache { @@ -169,6 +189,25 @@ impl Cache { Self::default() } + /// Creates a new cache instance with settings applied. + /// + /// # Examples + /// + /// ```rust + /// use serenity::cache::{Cache, Settings}; + /// + /// let mut settings = Settings::new(); + /// settings.max_messages(10); + /// + /// let cache = Cache::new_with_settings(settings); + /// ``` + pub fn new_with_settings(settings: Settings) -> Self { + Self { + settings, + ..Default::default() + } + } + /// Fetches the number of [`Member`]s that have not had data received. /// /// The important detail to note here is that this is the number of @@ -622,6 +661,38 @@ impl Cache { .and_then(|g| g.read().roles.get(&role_id).cloned()) } + /// Returns an immutable reference to the settings. + /// + /// # Examples + /// + /// Printing the maximum number of messages in a channel to be cached: + /// + /// ```rust + /// use serenity::cache::Cache; + /// + /// let mut cache = Cache::new(); + /// println!("Max settings: {}", cache.settings().max_messages); + /// ``` + pub fn settings(&self) -> &Settings { + &self.settings + } + + /// Returns a mutable reference to the settings. + /// + /// # Examples + /// + /// Create a new cache and modify the settings afterwards: + /// + /// ```rust + /// use serenity::cache::Cache; + /// + /// let mut cache = Cache::new(); + /// cache.settings_mut().max_messages(10); + /// ``` + pub fn settings_mut(&mut self) -> &mut Settings { + &mut self.settings + } + /// Retrieves a `User` from the cache's [`users`] map, if it exists. /// /// The only advantage of this method is that you can pass in anything that @@ -704,13 +775,17 @@ impl Default for Cache { categories: HashMap::default(), groups: HashMap::with_capacity(128), guilds: HashMap::default(), + messages: HashMap::default(), notes: HashMap::default(), presences: HashMap::default(), private_channels: HashMap::with_capacity(128), + settings: Settings::default(), shard_count: 1, unavailable_guilds: HashSet::default(), user: CurrentUser::default(), users: HashMap::default(), + message_queue: HashMap::default(), + __nonexhaustive: (), } } } diff --git a/src/cache/settings.rs b/src/cache/settings.rs new file mode 100644 index 0000000..e8e456b --- /dev/null +++ b/src/cache/settings.rs @@ -0,0 +1,50 @@ +/// Settings for the cache.
+///
+/// # Examples
+///
+/// Create new settings, specifying the maximum number of messages:
+///
+/// ```rust
+/// use serenity::cache::Settings as CacheSettings;
+///
+/// let mut settings = CacheSettings::new();
+/// settings.max_messages(10);
+/// ```
+#[derive(Clone, Debug, Default)]
+pub struct Settings {
+ /// The maximum number of messages to store in a channel's message cache.
+ ///
+ /// Defaults to 0.
+ pub max_messages: usize,
+ __nonexhaustive: (),
+}
+
+impl Settings {
+ /// Creates new settings to be used with a cache.
+ #[inline]
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Sets the maximum number of messages to cache in a channel.
+ ///
+ /// Refer to [`max_messages`] for more information.
+ ///
+ /// # Examples
+ ///
+ /// Set the maximum number of messages to cache:
+ ///
+ /// ```rust
+ /// use serenity::cache::Settings;
+ ///
+ /// let mut settings = Settings::new();
+ /// settings.max_messages(10);
+ /// ```
+ ///
+ /// [`max_messages`]: #structfield.max_messages
+ pub fn max_messages(&mut self, max: usize) -> &mut Self {
+ self.max_messages = max;
+
+ self
+ }
+}
diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs index b91b8d7..04bd8c9 100644 --- a/src/client/dispatch.rs +++ b/src/client/dispatch.rs @@ -68,7 +68,9 @@ pub(crate) fn dispatch<H: EventHandler + Send + Sync + 'static>( shard_id: u64, ) { match event { - DispatchEvent::Model(Event::MessageCreate(event)) => { + DispatchEvent::Model(Event::MessageCreate(mut event)) => { + update!(event); + let context = context(data, runner_tx, shard_id); dispatch_message( context.clone(), @@ -103,6 +105,8 @@ pub(crate) fn dispatch<H: EventHandler + Send + Sync + 'static>( ) { match event { DispatchEvent::Model(Event::MessageCreate(event)) => { + update!(event); + let context = context(data, runner_tx, shard_id); dispatch_message(context, event.message, event_handler, threadpool); }, @@ -506,6 +510,8 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::MessageUpdate(mut event)) => { + update!(event); + let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); diff --git a/src/framework/standard/args.rs b/src/framework/standard/args.rs index 2648a30..8af8124 100644 --- a/src/framework/standard/args.rs +++ b/src/framework/standard/args.rs @@ -53,6 +53,20 @@ impl<E: StdError> fmt::Display for Error<E> { type Result<T, E> = ::std::result::Result<T, Error<E>>; +fn find_start(s: &str, i: usize) -> Option<usize> { + if i > s.len() { + return None; + } + + let mut start = i - 1; + + while !s.is_char_boundary(start) { + start -= 1; + } + + Some(start) +} + fn find_end(s: &str, i: usize) -> Option<usize> { if i > s.len() { return None; @@ -176,10 +190,11 @@ impl<'a> Lexer<'a> { } self.next(); + let end = self.offset; - return if self.at_end() && &self.msg[end-1..end] != "\"" { - // invalid, missing an end quote; view it as a normal argument instead. + return if self.at_end() && &self.msg[find_start(self.msg, end).unwrap()..end] != "\"" { + // We're missing an end quote. View this as a normal argument. Token::new(TokenKind::Argument, &self.msg[start..], start) } else { Token::new(TokenKind::QuotedArgument, &self.msg[start..end], start) diff --git a/src/framework/standard/command.rs b/src/framework/standard/command.rs index f88d2de..0fa51f9 100644 --- a/src/framework/standard/command.rs +++ b/src/framework/standard/command.rs @@ -247,8 +247,8 @@ impl Default for HelpOptions { lacking_role: HelpBehaviour::Strike, lacking_permissions: HelpBehaviour::Strike, wrong_channel: HelpBehaviour::Strike, - embed_error_colour: Colour::dark_red(), - embed_success_colour: Colour::rosewater(), + embed_error_colour: Colour::DARK_RED, + embed_success_colour: Colour::ROSEWATER, } } } diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs index 95fa074..435069e 100644 --- a/src/framework/standard/mod.rs +++ b/src/framework/standard/mod.rs @@ -529,7 +529,7 @@ impl StandardFramework { // If not, assert that it does always. let apply = bucket.check.as_ref().map_or(true, |check| { let apply = feature_cache! {{ - let guild_id = message.guild_id(); + let guild_id = message.guild_id; (check)(context, guild_id, message.channel_id, message.author.id) } else { (check)(context, message.channel_id, message.author.id) @@ -202,6 +202,15 @@ lazy_static! { /// println!("{}", CACHE.read().user.id); /// ``` /// + /// Update the cache's settings to enable caching of channels' messages: + /// + /// ```rust + /// use serenity::CACHE; + /// + /// // Cache up to the 10 most recent messages per channel. + /// CACHE.write().settings_mut().max_messages(10); + /// ``` + /// /// [`CurrentUser`]: model/struct.CurrentUser.html /// [`Cache`]: cache/struct.Cache.html /// [cache module documentation]: cache/index.html diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index be17772..3134725 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -30,17 +30,17 @@ pub struct Message { /// /// [`Channel`]: enum.Channel.html pub channel_id: ChannelId, - /// The Id of the [`Guild`] that the message was sent in. This value will - /// only be present if this message was received over the gateway. - /// - /// [`Guild`]: ../guild/struct.Guild.html - pub guild_id: Option<GuildId>, /// The content of the message. pub content: String, /// The timestamp of the last time the message was updated, if it was. pub edited_timestamp: Option<DateTime<FixedOffset>>, /// Array of embeds sent with the message. pub embeds: Vec<Embed>, + /// The Id of the [`Guild`] that the message was sent in. This value will + /// only be present if this message was received over the gateway. + /// + /// [`Guild`]: ../guild/struct.Guild.html + pub guild_id: Option<GuildId>, /// Indicator of the type of message this is, i.e. whether it is a regular /// message or a system message. #[serde(rename = "type")] @@ -337,21 +337,18 @@ impl Message { /// [`guild_id`]: #method.guild_id #[cfg(feature = "cache")] pub fn guild(&self) -> Option<Arc<RwLock<Guild>>> { - self.guild_id() - .and_then(|guild_id| CACHE.read().guild(guild_id)) + CACHE.read().guild(self.guild_id?) } /// Retrieves the Id of the guild that the message was sent in, if sent in /// one. /// - /// Returns `None` if the channel data or guild data does not exist in the - /// cache. - #[cfg(feature = "cache")] + /// Refer to [`guild_id`] for more information. + /// + /// [`guild_id`]: #structfield.guild_id + #[deprecated(note = "Use `guild_id` structfield instead", since = "0.5.5")] pub fn guild_id(&self) -> Option<GuildId> { - match CACHE.read().channel(self.channel_id) { - Some(Channel::Guild(ch)) => Some(ch.read().guild_id), - _ => None, - } + self.guild_id } /// True if message was sent using direct messages. diff --git a/src/model/event.rs b/src/model/event.rs index d78e7df..7a7e3de 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -153,6 +153,9 @@ impl CacheUpdate for ChannelDeleteEvent { Channel::Private(_) | Channel::Group(_) => unreachable!(), }; + // Remove the cached messages for the channel. + cache.messages.remove(&self.channel.id()); + None } } @@ -400,7 +403,11 @@ impl CacheUpdate for GuildDeleteEvent { // Remove channel entries for the guild if the guild is found. cache.guilds.remove(&self.guild.id).map(|guild| { for channel_id in guild.write().channels.keys() { + // Remove the channel from the cache. cache.channels.remove(channel_id); + + // Remove the channel's cached messages. + cache.messages.remove(channel_id); } guild @@ -759,6 +766,40 @@ pub struct MessageCreateEvent { pub message: Message, } +#[cfg(feature = "cache")] +impl CacheUpdate for MessageCreateEvent { + /// The oldest message, if the channel's message cache was already full. + type Output = Message; + + fn update(&mut self, cache: &mut Cache) -> Option<Self::Output> { + let max = cache.settings().max_messages; + + if max == 0 { + return None; + } + + let messages = cache.messages + .entry(self.message.channel_id) + .or_insert_with(Default::default); + let queue = cache.message_queue + .entry(self.message.channel_id) + .or_insert_with(Default::default); + + let mut removed_msg = None; + + if messages.len() == max { + if let Some(id) = queue.pop_front() { + removed_msg = messages.remove(&id); + } + } + + queue.push_back(self.message.id); + messages.insert(self.message.id, self.message.clone()); + + removed_msg + } +} + impl<'de> Deserialize<'de> for MessageCreateEvent { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { Ok(Self { @@ -805,6 +846,46 @@ pub struct MessageUpdateEvent { pub embeds: Option<Vec<Value>>, } +#[cfg(feature = "cache")] +impl CacheUpdate for MessageUpdateEvent { + type Output = (); + + fn update(&mut self, cache: &mut Cache) -> Option<Self::Output> { + let messages = cache.messages.get_mut(&self.channel_id)?; + let message = messages.get_mut(&self.id)?; + + if let Some(attachments) = self.attachments.clone() { + message.attachments = attachments; + } + + if let Some(content) = self.content.clone() { + message.content = content; + } + + if let Some(edited_timestamp) = self.edited_timestamp.clone() { + message.edited_timestamp = Some(edited_timestamp); + } + + if let Some(mentions) = self.mentions.clone() { + message.mentions = mentions; + } + + if let Some(mention_everyone) = self.mention_everyone { + message.mention_everyone = mention_everyone; + } + + if let Some(mention_roles) = self.mention_roles.clone() { + message.mention_roles = mention_roles; + } + + if let Some(pinned) = self.pinned { + message.pinned = pinned; + } + + None + } +} + #[derive(Clone, Debug, Serialize)] pub struct PresenceUpdateEvent { pub guild_id: Option<GuildId>, diff --git a/src/model/id.rs b/src/model/id.rs index 4e2fbc3..046de11 100644 --- a/src/model/id.rs +++ b/src/model/id.rs @@ -27,6 +27,12 @@ macro_rules! id_u64 { } } + impl<'a> From<&'a $name> for $name { + fn from(id: &'a $name) -> $name { + id.clone() + } + } + impl From<u64> for $name { fn from(id_as_u64: u64) -> $name { $name(id_as_u64) diff --git a/src/utils/colour.rs b/src/utils/colour.rs index 81b6ee9..cac3d46 100644 --- a/src/utils/colour.rs +++ b/src/utils/colour.rs @@ -2,10 +2,16 @@ #![allow(unreadable_literal)] macro_rules! colour { - ($(#[$attr:meta] $name:ident, $val:expr;)*) => { + ($(#[$attr:meta] $constname:ident, $name:ident, $val:expr;)*) => { impl Colour { $( #[$attr] + pub const $constname: Colour = Colour($val); + )* + + $( + #[$attr] + #[deprecated(note = "Use the constant instead", since = "0.5.5")] pub fn $name() -> Colour { Colour::new($val) } @@ -33,7 +39,7 @@ macro_rules! colour { /// # use serenity::model::permissions; /// # /// # let role = Role { -/// # colour: Colour::blurple(), +/// # colour: Colour::BLURPLE, /// # hoist: false, /// # id: RoleId(1), /// # managed: false, @@ -52,12 +58,12 @@ macro_rules! colour { /// println!("The green component is: {}", green); /// ``` /// -/// Creating an instance with the [`dark_teal`] presets: +/// Creating an instance with the [`DARK_TEAL`] preset: /// /// ```rust /// use serenity::utils::Colour; /// -/// let colour = Colour::dark_teal(); +/// let colour = Colour::DARK_TEAL; /// /// assert_eq!(colour.tuple(), (17, 128, 106)); /// ``` @@ -67,16 +73,16 @@ macro_rules! colour { /// ```rust /// use serenity::utils::Colour; /// -/// let blitz_blue = Colour::blitz_blue(); -/// let fooyoo = Colour::fooyoo(); -/// let fooyoo2 = Colour::fooyoo(); +/// let blitz_blue = Colour::BLITZ_BLUE; +/// let fooyoo = Colour::FOOYOO; +/// let fooyoo2 = Colour::FOOYOO; /// assert!(blitz_blue != fooyoo); /// assert_eq!(fooyoo, fooyoo2); /// assert!(blitz_blue > fooyoo); /// ``` /// /// [`Role`]: ../model/guild/struct.Role.html -/// [`dark_teal`]: #method.dark_teal +/// [`DARK_TEAL`]: #associatedconstant.DARK_TEAL /// [`g`]: #method.g #[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] pub struct Colour(pub u32); @@ -254,61 +260,61 @@ impl From<(u8, u8, u8)> for Colour { colour! { /// Creates a new `Colour`, setting its RGB value to `(111, 198, 226)`. - blitz_blue, 0x6FC6E2; + BLITZ_BLUE, blitz_blue, 0x6FC6E2; /// Creates a new `Colour`, setting its RGB value to `(52, 152, 219)`. - blue, 0x3498DB; + BLUE, blue, 0x3498DB; /// Creates a new `Colour`, setting its RGB value to `(114, 137, 218)`. - blurple, 0x7289DA; + BLURPLE, blurple, 0x7289DA; /// Creates a new `Colour`, setting its RGB value to `(32, 102, 148)`. - dark_blue, 0x206694; + DARK_BLUE, dark_blue, 0x206694; /// Creates a new `Colour`, setting its RGB value to `(194, 124, 14)`. - dark_gold, 0xC27C0E; + DARK_GOLD, dark_gold, 0xC27C0E; /// Creates a new `Colour`, setting its RGB value to `(31, 139, 76)`. - dark_green, 0x1F8B4C; + DARK_GREEN, dark_green, 0x1F8B4C; /// Creates a new `Colour`, setting its RGB value to `(96, 125, 139)`. - dark_grey, 0x607D8B; + DARK_GREY, dark_grey, 0x607D8B; /// Creates a new `Colour`, setting its RGB value to `(173, 20, 87)`. - dark_magenta, 0xAD1457; + DARK_MAGENTA, dark_magenta, 0xAD1457; /// Creates a new `Colour`, setting its RGB value to `(168, 67, 0)`. - dark_orange, 0xA84300; + DARK_ORANGE, dark_orange, 0xA84300; /// Creates a new `Colour`, setting its RGB value to `(113, 54, 138)`. - dark_purple, 0x71368A; + DARK_PURPLE, dark_purple, 0x71368A; /// Creates a new `Colour`, setting its RGB value to `(153, 45, 34)`. - dark_red, 0x992D22; + DARK_RED, dark_red, 0x992D22; /// Creates a new `Colour`, setting its RGB value to `(17, 128, 106)`. - dark_teal, 0x11806A; + DARK_TEAL, dark_teal, 0x11806A; /// Creates a new `Colour`, setting its RGB value to `(84, 110, 122)`. - darker_grey, 0x546E7A; + DARKER_GREY, darker_grey, 0x546E7A; /// Creates a new `Colour`, setting its RGB value to `(250, 177, 237)`. - fabled_pink, 0xFAB1ED; + FABLED_PINK, fabled_pink, 0xFAB1ED; /// Creates a new `Colour`, setting its RGB value to `(136, 130, 196)`.` - faded_purple, 0x8882C4; + FADED_PURPLE, faded_purple, 0x8882C4; /// Creates a new `Colour`, setting its RGB value to `(17, 202, 128)`. - fooyoo, 0x11CA80; + FOOYOO, fooyoo, 0x11CA80; /// Creates a new `Colour`, setting its RGB value to `(241, 196, 15)`. - gold, 0xF1C40F; + GOLD, gold, 0xF1C40F; /// Creates a new `Colour`, setting its RGB value to `(186, 218, 85)`. - kerbal, 0xBADA55; + KERBAL, kerbal, 0xBADA55; /// Creates a new `Colour`, setting its RGB value to `(151, 156, 159)`. - light_grey, 0x979C9F; + LIGHT_GREY, light_grey, 0x979C9F; /// Creates a new `Colour`, setting its RGB value to `(149, 165, 166)`. - lighter_grey, 0x95A5A6; + LIGHTER_GREY, lighter_grey, 0x95A5A6; /// Creates a new `Colour`, setting its RGB value to `(233, 30, 99)`. - magenta, 0xE91E63; + MAGENTA, magenta, 0xE91E63; /// Creates a new `Colour`, setting its RGB value to `(230, 131, 151)`. - meibe_pink, 0xE68397; + MEIBE_PINK, meibe_pink, 0xE68397; /// Creates a new `Colour`, setting its RGB value to `(230, 126, 34)`. - orange, 0xE67E22; + ORANGE, orange, 0xE67E22; /// Creates a new `Colour`, setting its RGB value to `(155, 89, 182)`. - purple, 0x9B59B6; + PURPLE, purple, 0x9B59B6; /// Creates a new `Colour`, setting its RGB value to `(231, 76, 60)`. - red, 0xE74C3C; + RED, red, 0xE74C3C; /// Creates a new `Colour`, setting its RGB value to `(117, 150, 255)`. - rohrkatze_blue, 0x7596FF; + ROHRKATZE_BLUE, rohrkatze_blue, 0x7596FF; /// Creates a new `Colour`, setting its RGB value to `(246, 219, 216)`. - rosewater, 0xF6DBD8; + ROSEWATER, rosewater, 0xF6DBD8; /// Creates a new `Colour`, setting its RGB value to `(26, 188, 156)`. - teal, 0x1ABC9C; + TEAL, teal, 0x1ABC9C; } impl Default for Colour { diff --git a/src/voice/mod.rs b/src/voice/mod.rs index 51fb0e2..7b5aaa0 100644 --- a/src/voice/mod.rs +++ b/src/voice/mod.rs @@ -26,6 +26,7 @@ pub use self::{ streamer::{ dca, ffmpeg, + ffmpeg_optioned, opus, pcm, ytdl diff --git a/src/voice/streamer.rs b/src/voice/streamer.rs index cd7cae8..893f92a 100644 --- a/src/voice/streamer.rs +++ b/src/voice/streamer.rs @@ -108,7 +108,7 @@ fn _ffmpeg(path: &OsStr) -> Result<Box<AudioSource>> { let is_stereo = is_stereo(path).unwrap_or(false); let stereo_val = if is_stereo { "2" } else { "1" }; - let args = [ + ffmpeg_optioned(path, &[ "-f", "s16le", "-ac", @@ -118,12 +118,49 @@ fn _ffmpeg(path: &OsStr) -> Result<Box<AudioSource>> { "-acodec", "pcm_s16le", "-", - ]; + ]) +} + +/// Opens an audio file through `ffmpeg` and creates an audio source, with +/// user-specified arguments to pass to ffmpeg. +/// +/// Note that this does _not_ build on the arguments passed by the [`ffmpeg`] +/// function. +/// +/// # Examples +/// +/// Pass options to create a custom ffmpeg streamer: +/// +/// ```rust,no_run +/// use serenity::voice; +/// +/// let stereo_val = "2"; +/// +/// let streamer = voice::ffmpeg_optioned("./some_file.mp3", &[ +/// "-f", +/// "s16le", +/// "-ac", +/// stereo_val, +/// "-ar", +/// "48000", +/// "-acodec", +/// "pcm_s16le", +/// "-", +/// ]); +pub fn ffmpeg_optioned<P: AsRef<OsStr>>( + path: P, + args: &[&str], +) -> Result<Box<AudioSource>> { + _ffmpeg_optioned(path.as_ref(), args) +} + +fn _ffmpeg_optioned(path: &OsStr, args: &[&str]) -> Result<Box<AudioSource>> { + let is_stereo = is_stereo(path).unwrap_or(false); let command = Command::new("ffmpeg") .arg("-i") .arg(path) - .args(&args) + .args(args) .stderr(Stdio::null()) .stdin(Stdio::null()) .stdout(Stdio::piped()) |