Skip to content

Commit

Permalink
Implement multiple attachments
Browse files Browse the repository at this point in the history
  • Loading branch information
Ken Swenson authored and Zeyla Hellyer committed May 28, 2017
1 parent 2afab7c commit 46b79dd
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 0 deletions.
86 changes: 86 additions & 0 deletions src/http/mod.rs
Expand Up @@ -45,7 +45,9 @@ use serde_json;
use std::collections::BTreeMap;
use std::default::Default;
use std::fmt::Write as FmtWrite;
use std::fs::File;
use std::io::{ErrorKind as IoErrorKind, Read};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use ::constants;
use ::internal::prelude::*;
Expand Down Expand Up @@ -1378,6 +1380,7 @@ pub fn remove_group_recipient(group_id: u64, user_id: u64) -> Result<()> {
/// if the file is too large to send.
///
/// [`HttpError::InvalidRequest`]: enum.HttpError.html#variant.InvalidRequest
#[deprecated(since="0.2.0", note="Please use `send_files` instead.")]
pub fn send_file<R: Read>(channel_id: u64, mut file: R, filename: &str, map: JsonMap)
-> Result<Message> {
let uri = format!(api!("/channels/{}/messages"), channel_id);
Expand Down Expand Up @@ -1415,6 +1418,63 @@ pub fn send_file<R: Read>(channel_id: u64, mut file: R, filename: &str, map: Jso
serde_json::from_reader::<HyperResponse, Message>(response).map_err(From::from)
}

/// Sends file(s) to a channel.
///
/// # Errors
///
/// Returns an
/// [`HttpError::InvalidRequest(PayloadTooLarge)`][`HttpError::InvalidRequest`]
/// if the file is too large to send.
///
/// [`HttpError::InvalidRequest`]: enum.HttpError.html#variant.InvalidRequest
pub fn send_files<T: Into<AttachmentType>>(channel_id: u64, files: Vec<T>, map: JsonMap)
-> Result<Message> {
let uri = format!(api!("/channels/{}/messages"), channel_id);
let url = match Url::parse(&uri) {
Ok(url) => url,
Err(_) => return Err(Error::Url(uri)),
};

let mut request = Request::new(Method::Post, url)?;
request.headers_mut()
.set(header::Authorization(TOKEN.lock().unwrap().clone()));
request.headers_mut()
.set(header::UserAgent(constants::USER_AGENT.to_owned()));

let mut request = Multipart::from_request(request)?;
let mut file_num = String::from("0".to_owned());

for file in files {
match file.into() {
AttachmentType::File((mut f, filename)) => {
request.write_stream(&file_num, &mut f, Some(&filename), None)?;
},
AttachmentType::Path(p) => {
request.write_file(&file_num, &p)?;
},
}

unsafe {
let vec = file_num.as_mut_vec();
vec[0] += 1;
}
}

for (k, v) in map {
match v {
Value::Bool(false) => request.write_text(&k, "false")?,
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)?,
_ => continue,
};
}

let response = request.send()?;

serde_json::from_reader::<HyperResponse, Message>(response).map_err(From::from)
}

/// Sends a message to a channel.
pub fn send_message(channel_id: u64, map: &Value) -> Result<Message> {
let body = map.to_string();
Expand Down Expand Up @@ -1544,6 +1604,32 @@ fn verify(expected_status_code: u16, mut response: HyperResponse) -> Result<()>
Err(Error::Http(HttpError::InvalidRequest(response.status)))
}

/// Enum that allows a user to pass a `Path` or a `File` type to `send_files`
pub enum AttachmentType {
/// Indicates that the `AttachmentType` is a `File`
File((File, String)),
/// Indicates that the `AttachmentType` is a `Path`
Path(PathBuf),
}

impl From<String> for AttachmentType {
fn from(s: String) -> AttachmentType {
AttachmentType::Path(PathBuf::from(&s))
}
}

impl<'a> From<&'a str> for AttachmentType {
fn from(s: &'a str) -> AttachmentType {
AttachmentType::Path(PathBuf::from(s))
}
}

impl<'a> From<(File, &'a str)> for AttachmentType {
fn from(f: (File, &str)) -> AttachmentType {
AttachmentType::File((f.0, f.1.to_owned()))
}
}

/// Representation of the method of a query to send for the [`get_guilds`]
/// function.
///
Expand Down
80 changes: 80 additions & 0 deletions src/model/channel/channel_id.rs
Expand Up @@ -8,6 +8,8 @@ use ::builder::{CreateMessage, EditChannel, GetMessages};
use ::CACHE;
#[cfg(feature="model")]
use ::http;
#[cfg(feature="model")]
use ::http::AttachmentType;

#[cfg(feature="model")]
impl ChannelId {
Expand Down Expand Up @@ -393,6 +395,8 @@ impl ChannelId {
/// [`GuildChannel`]: struct.GuildChannel.html
/// [Attach Files]: permissions/constant.ATTACH_FILES.html
/// [Send Messages]: permissions/constant.SEND_MESSAGES.html
#[deprecated(since="0.2.0", note="Please use `send_files` instead.")]
#[allow(deprecated)]
pub fn send_file<F, R>(&self, file: R, filename: &str, f: F) -> Result<Message>
where F: FnOnce(CreateMessage) -> CreateMessage, R: Read {
let mut map = f(CreateMessage::default()).0;
Expand All @@ -410,6 +414,82 @@ impl ChannelId {
http::send_file(self.0, file, filename, map)
}

/// Sends a file along with optional message contents. The filename _must_
/// be specified.
///
/// Message contents may be passed by using the [`CreateMessage::content`]
/// method.
///
/// An embed can _not_ be sent when sending a file. If you set one, it will
/// be automatically removed.
///
/// The [Attach Files] and [Send Messages] permissions are required.
///
/// **Note**: Message contents must be under 2000 unicode code points.
///
/// # Examples
///
/// Send files with the paths `/path/to/file.jpg` and `/path/to/file2.jpg`:
///
/// ```rust,no_run
/// use serenity::model::ChannelId;
///
/// let channel_id = ChannelId(7);
///
/// let paths = vec!["/path/to/file.jpg", "path/to/file2.jpg"];
///
/// let _ = channel_id.send_files(paths, |m| m.content("a file"));
/// ```
///
/// Send files using `File`:
///
/// ```rust,no_run
/// use serenity::model::ChannelId;
/// use std::fs::File;
///
/// let channel_id = ChannelId(7);
///
/// let f1 = File::open("my_file.jpg").unwrap();
/// let f2 = File::open("my_file2.jpg").unwrap();
///
/// let files = vec![(f1, "my_file.jpg"), (f2, "my_file2.jpg")];
///
/// let _ = channel_id.send_files(files, |m| m.content("a file"));
/// ```
///
/// # Errors
///
/// If the content of the message is over the above limit, then a
/// [`ClientError::MessageTooLong`] will be returned, containing the number
/// of unicode code points over the limit.
///
/// Returns an
/// [`HttpError::InvalidRequest(PayloadTooLarge)`][`HttpError::InvalidRequest`]
/// if the file is too large to send.
///
/// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong
/// [`HttpError::InvalidRequest`]: ../http/enum.HttpError.html#variant.InvalidRequest
/// [`CreateMessage::content`]: ../utils/builder/struct.CreateMessage.html#method.content
/// [`GuildChannel`]: struct.GuildChannel.html
/// [Attach Files]: permissions/constant.ATTACH_FILES.html
/// [Send Messages]: permissions/constant.SEND_MESSAGES.html
pub fn send_files<F, T: Into<AttachmentType>>(&self, files: Vec<T>, f: F) -> Result<Message>
where F: FnOnce(CreateMessage) -> CreateMessage {
let mut map = f(CreateMessage::default()).0;

if let Some(content) = map.get("content") {
if let Value::String(ref content) = *content {
if let Some(length_over) = Message::overflow_length(content) {
return Err(Error::Model(ModelError::MessageTooLong(length_over)));
}
}
}

let _ = map.remove("embed");

http::send_files(self.0, files, map)
}

/// Sends a message to the channel.
///
/// Refer to the documentation for [`CreateMessage`] for more information
Expand Down
28 changes: 28 additions & 0 deletions src/model/channel/group.rs
Expand Up @@ -7,6 +7,8 @@ use ::model::*;
use ::builder::{CreateMessage, GetMessages};
#[cfg(feature="model")]
use ::http;
#[cfg(feature="model")]
use ::http::AttachmentType;

/// A group channel - potentially including other [`User`]s - separate from a
/// [`Guild`].
Expand Down Expand Up @@ -298,11 +300,37 @@ impl Group {
/// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong
/// [Attach Files]: permissions/constant.ATTACH_FILES.html
/// [Send Messages]: permissions/constant.SEND_MESSAGES.html
#[deprecated(since="0.2.0", note="Please use `send_files` instead.")]
#[allow(deprecated)]
pub fn send_file<F, R>(&self, file: R, filename: &str, f: F) -> Result<Message>
where F: FnOnce(CreateMessage) -> CreateMessage, R: Read {
self.channel_id.send_file(file, filename, f)
}

/// Sends (a) file(s) along with optional message contents.
///
/// Refer to [`ChannelId::send_file`] for examples and more information.
///
/// The [Attach Files] and [Send Messages] permissions are required.
///
/// **Note**: Message contents must be under 2000 unicode code points.
///
/// # Errors
///
/// If the content of the message is over the above limit, then a
/// [`ClientError::MessageTooLong`] will be returned, containing the number
/// of unicode code points over the limit.
///
/// [`ChannelId::send_file`]: struct.ChannelId.html#method.send_file
/// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong
/// [Attach Files]: permissions/constant.ATTACH_FILES.html
/// [Send Messages]: permissions/constant.SEND_MESSAGES.html
#[inline]
pub fn send_files<F, T: Into<AttachmentType>>(&self, files: Vec<T>, f: F) -> Result<Message>
where F: FnOnce(CreateMessage) -> CreateMessage {
self.channel_id.send_files(files, f)
}

/// Sends a message to the group with the given content.
///
/// Refer to the documentation for [`CreateMessage`] for more information
Expand Down
28 changes: 28 additions & 0 deletions src/model/channel/guild_channel.rs
Expand Up @@ -10,6 +10,8 @@ use ::builder::{CreateInvite, CreateMessage, EditChannel, GetMessages};
use ::CACHE;
#[cfg(feature="model")]
use ::http;
#[cfg(feature="model")]
use ::http::AttachmentType;
#[cfg(all(feature="model", feature="utils"))]
use ::utils as serenity_utils;

Expand Down Expand Up @@ -557,11 +559,37 @@ impl GuildChannel {
/// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong
/// [Attach Files]: permissions/constant.ATTACH_FILES.html
/// [Send Messages]: permissions/constant.SEND_MESSAGES.html
#[deprecated(since="0.2.0", note="Please use `send_files` instead.")]
#[allow(deprecated)]
pub fn send_file<F, R>(&self, file: R, filename: &str, f: F) -> Result<Message>
where F: FnOnce(CreateMessage) -> CreateMessage, R: Read {
self.id.send_file(file, filename, f)
}

/// Sends (a) file(s) along with optional message contents.
///
/// Refer to [`ChannelId::send_file`] for examples and more information.
///
/// The [Attach Files] and [Send Messages] permissions are required.
///
/// **Note**: Message contents must be under 2000 unicode code points.
///
/// # Errors
///
/// If the content of the message is over the above limit, then a
/// [`ClientError::MessageTooLong`] will be returned, containing the number
/// of unicode code points over the limit.
///
/// [`ChannelId::send_file`]: struct.ChannelId.html#method.send_file
/// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong
/// [Attach Files]: permissions/constant.ATTACH_FILES.html
/// [Send Messages]: permissions/constant.SEND_MESSAGES.html
#[inline]
pub fn send_files<F, T: Into<AttachmentType>>(&self, files: Vec<T>, f: F) -> Result<Message>
where F: FnOnce(CreateMessage) -> CreateMessage {
self.id.send_files(files, f)
}

/// Sends a message to the channel with the given content.
///
/// **Note**: This will only work when a [`Message`] is received.
Expand Down
28 changes: 28 additions & 0 deletions src/model/channel/mod.rs
Expand Up @@ -25,6 +25,8 @@ use ::model::*;

#[cfg(feature="model")]
use ::builder::{CreateMessage, GetMessages};
#[cfg(feature="model")]
use ::http::AttachmentType;

/// A container for any channel.
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -293,11 +295,37 @@ impl Channel {
/// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong
/// [Attach Files]: permissions/constant.ATTACH_FILES.html
/// [Send Messages]: permissions/constant.SEND_MESSAGES.html
#[deprecated(since="0.2.0", note="Please use `send_files` instead.")]
#[allow(deprecated)]
pub fn send_file<F, R>(&self, file: R, filename: &str, f: F) -> Result<Message>
where F: FnOnce(CreateMessage) -> CreateMessage, R: Read {
self.id().send_file(file, filename, f)
}

/// Sends (a) file(s) along with optional message contents.
///
/// Refer to [`ChannelId::send_file`] for examples and more information.
///
/// The [Attach Files] and [Send Messages] permissions are required.
///
/// **Note**: Message contents must be under 2000 unicode code points.
///
/// # Errors
///
/// If the content of the message is over the above limit, then a
/// [`ClientError::MessageTooLong`] will be returned, containing the number
/// of unicode code points over the limit.
///
/// [`ChannelId::send_file`]: struct.ChannelId.html#method.send_file
/// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong
/// [Attach Files]: permissions/constant.ATTACH_FILES.html
/// [Send Messages]: permissions/constant.SEND_MESSAGES.html
#[inline]
pub fn send_files<F, T: Into<AttachmentType>>(&self, files: Vec<T>, f: F) -> Result<Message>
where F: FnOnce(CreateMessage) -> CreateMessage {
self.id().send_files(files, f)
}

/// Sends a message to the channel.
///
/// Refer to the documentation for [`CreateMessage`] for more information
Expand Down

0 comments on commit 46b79dd

Please sign in to comment.