Skip to content

Commit

Permalink
Allow forwarding files
Browse files Browse the repository at this point in the history
  • Loading branch information
exul committed Jun 10, 2018
1 parent 4559cc8 commit 7624479
Show file tree
Hide file tree
Showing 19 changed files with 690 additions and 136 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions assets/translations.yaml
Expand Up @@ -89,6 +89,7 @@ en:
rocketchat_server_not_found: "Rocket.Chat server ${rocketchat_url} not found, it is probably not connected."
rocketchat_server_unreachable: "Could not reach Rocket.Chat server ${rocketchat_url}"
rocketchat_join_first: "You have to join the channel or group ${rocketchat_room_name} on the Rocket.Chat server before you can bridge it."
rocketchat_server_upload_failed: "Uploading file ${url} to Rocket.Chat failed with '${err}'."
room_already_connected: "This room is already connected"
room_assocaited_with_aliases: "Cannot unbdrige room ${rocketchat_room_name}, because aliases (${aliases}) are still associated with the room. All aliases have to be removed before the room can be unbridged."
room_not_connected: "This room is not connected to a Rocket.Chat server, you have to connect it first to be able to execute the command, type `help` for further instructions on how to connect this room"
Expand Down
12 changes: 7 additions & 5 deletions src/matrix-rocketchat/api/matrix/mod.rs
@@ -1,10 +1,12 @@
use std::collections::HashMap;

use reqwest::header::ContentType;
use ruma_client_api::unversioned::get_supported_versions::{
Endpoint as GetSupportedVersionsEndpoint, Response as GetSupportedVersionsResponse,
};
use ruma_client_api::Endpoint;
use ruma_client_api::unversioned::get_supported_versions::{Endpoint as GetSupportedVersionsEndpoint,
Response as GetSupportedVersionsResponse};
use ruma_events::room::member::MemberEvent;
use ruma_events::room::message::MessageType;
use ruma_identifiers::{RoomAliasId, RoomId, UserId};
use serde_json;
use slog::Logger;
Expand Down Expand Up @@ -55,9 +57,9 @@ pub trait MatrixApi: Send + Sync + MatrixApiClone {
/// Register a user.
fn register(&self, user_id_local_part: String) -> Result<()>;
/// Send a text message to a room.
fn send_text_message_event(&self, room_id: RoomId, user_id: UserId, body: String) -> Result<()>;
/// Send an image message to a room.
fn send_image_message_event(&self, room_id: RoomId, user_id: UserId, body: String, url: String) -> Result<()>;
fn send_text_message(&self, room_id: RoomId, user_id: UserId, body: String) -> Result<()>;
/// Send an data message (audio, file, image, video) to a room.
fn send_data_message(&self, room_id: RoomId, user_id: UserId, body: String, url: String, mtype: MessageType) -> Result<()>;
/// Set the default power levels for a room. Only the bot will be able to control the room.
/// The power levels for invite, kick, ban, and redact are all set to 50.
fn set_default_powerlevels(&self, room_id: RoomId, room_creator_user_id: UserId) -> Result<()>;
Expand Down
8 changes: 4 additions & 4 deletions src/matrix-rocketchat/api/matrix/r0.rs
Expand Up @@ -478,7 +478,7 @@ impl super::MatrixApi for MatrixApi {
Ok(())
}

fn send_text_message_event(&self, room_id: RoomId, user_id: UserId, body: String) -> Result<()> {
fn send_text_message(&self, room_id: RoomId, user_id: UserId, body: String) -> Result<()> {
let formatted_body = render_markdown(&body);
let mut message = Map::new();
message.insert("body".to_string(), json!(body));
Expand Down Expand Up @@ -507,10 +507,10 @@ impl super::MatrixApi for MatrixApi {
Ok(())
}

fn send_image_message_event(&self, room_id: RoomId, user_id: UserId, body: String, url: String) -> Result<()> {
fn send_data_message(&self, room_id: RoomId, user_id: UserId, body: String, url: String, mtype: MessageType) -> Result<()> {
let mut message = Map::new();
message.insert("body".to_string(), json!(body));
message.insert("msgtype".to_string(), json!(MessageType::Image));
message.insert("msgtype".to_string(), json!(mtype));
message.insert("url".to_string(), json!(url));
let payload = serde_json::to_string(&message).chain_err(|| body_params_error!("send image message"))?;
let txn_id = EventId::new(&self.base_url).chain_err(|| ErrorKind::EventIdGenerationFailed)?;
Expand All @@ -530,7 +530,7 @@ impl super::MatrixApi for MatrixApi {
return Err(build_error(&endpoint, &body, &status_code));
}

debug!(self.logger, "User {} successfully sent an image message to room {}", user_id, room_id);
debug!(self.logger, "User {} successfully sent a file message to room {}", user_id, room_id);
Ok(())
}

Expand Down
13 changes: 12 additions & 1 deletion src/matrix-rocketchat/api/rocketchat/mod.rs
Expand Up @@ -61,10 +61,19 @@ pub struct Message {
pub id: String,
/// The text content of the message
pub msg: String,
/// Optional file, only present when a file is attached to the message
pub file: Option<File>,
/// A list of attachments that are associated with the message
pub attachments: Option<Vec<MessageAttachment>>,
}

/// A file attached to a message
#[derive(Clone, Deserialize, Debug)]
pub struct File {
/// The file's MIME type
pub mimetype: String,
}

/// Metadata for a file that is uploaded to Rocket.Chat
#[derive(Clone, Debug)]
pub struct MessageAttachment {
Expand All @@ -74,6 +83,8 @@ pub struct MessageAttachment {
pub image_url: Option<String>,
/// An optional title for the file
pub title: String,
/// Link to file
pub title_link: String,
}

/// A Rocket.Chat user
Expand Down Expand Up @@ -130,7 +141,7 @@ pub trait RocketchatApi {
/// Get current user information
fn me(&self) -> Result<User>;
/// Post a message with an attachment
fn rooms_upload(&self, file: Vec<u8>, filename: &str, mime_type: Mime, room_id: &str) -> Result<()>;
fn rooms_upload(&self, file: Vec<u8>, filename: &str, mimetype: Mime, room_id: &str) -> Result<()>;
/// Get information like user_id, status, etc. about a user
fn users_info(&self, username: &str) -> Result<User>;
/// Set credentials that are used for all API calls that need authentication
Expand Down
112 changes: 66 additions & 46 deletions src/matrix-rocketchat/api/rocketchat/v1.rs
Expand Up @@ -9,7 +9,8 @@ use serde_json;
use slog::Logger;

use api::rocketchat::{
Attachment as RocketchatAttachment, Channel, Endpoint, Message as RocketchatMessage, MessageAttachment, User,
Attachment as RocketchatAttachment, Channel, Endpoint, File as RocketchatFile, Message as RocketchatMessage,
MessageAttachment, User,
};
use api::{RequestData, RestApi};
use errors::*;
Expand Down Expand Up @@ -60,16 +61,33 @@ pub struct Message {
pub mentions: Vec<serde_json::Value>,
/// A list of channels
pub channels: Vec<serde_json::Value>,
/// Optional file, only present when a file is attached to the message
pub file: Option<File>,
/// The timestamp when the message was updated the last time
#[serde(rename = "_updatedAt")]
pub updated_at: String,
}

/// A file attached to a message
#[derive(Deserialize, Debug, Serialize, Clone, Default)]
pub struct File {
/// The file's MIME type
#[serde(rename = "type")]
pub mimetype: String,
}

impl Message {
/// The content the of the attachment
pub fn content_type(&self) -> Result<ContentType> {
let mimetype = self.file.clone().unwrap_or_default().mimetype;
let mime: Mime = mimetype.parse().chain_err(|| ErrorKind::UnknownMimeType(mimetype))?;
Ok(ContentType(mime))
}
}

/// Metadata for a file that is uploaded to Rocket.Chat
#[derive(Deserialize, Debug, Serialize)]
pub struct Attachment {
/// An optional title for the file
pub title: String,
/// An optinal description of the file
pub description: String,
/// URL to download the image, it's only present when the attachment is an image
Expand All @@ -78,6 +96,13 @@ pub struct Attachment {
pub image_type: Option<String>,
/// The size of the uploaded image in bytes, it's only present when the attachment is an image
pub image_size: Option<i64>,
/// The files MIME type
#[serde(rename = "type")]
pub mimetype: String,
/// An optional title for the file
pub title: String,
/// Link to file
pub title_link: String,
}

#[derive(Deserialize, Debug, Serialize)]
Expand All @@ -92,17 +117,6 @@ pub struct UserInfo {
pub name: String,
}

impl Attachment {
/// The content the of the attachment
pub fn content_type(&self) -> Result<ContentType> {
match self.image_type {
Some(ref content_type) if content_type == "image/jpeg" => Ok(ContentType::jpeg()),
Some(ref content_type) if content_type == "image/png" => Ok(ContentType::png()),
_ => bail_error!(ErrorKind::UnknownContentType(self.image_type.clone().unwrap_or_default())),
}
}
}

/// V1 get endpoints that require authentication
pub struct GetWithAuthEndpoint<'a> {
base_url: String,
Expand Down Expand Up @@ -292,7 +306,7 @@ pub struct RoomsUploadEndpoint<'a> {
pub struct PostFileMessagePayload<'a> {
file: Vec<u8>,
filename: &'a str,
mime_type: Mime,
mimetype: Mime,
}

impl<'a> Endpoint<String> for RoomsUploadEndpoint<'a> {
Expand All @@ -309,7 +323,7 @@ impl<'a> Endpoint<String> for RoomsUploadEndpoint<'a> {
c.write_all(&self.payload.file).unwrap();
c.seek(SeekFrom::Start(0)).unwrap();

let part = Part::reader(c).file_name(self.payload.filename.to_owned()).mime(self.payload.mime_type.clone());
let part = Part::reader(c).file_name(self.payload.filename.to_owned()).mime(self.payload.mimetype.clone());
let form = Form::new().part("file", part);
Ok(RequestData::MultipartForm(form))
}
Expand Down Expand Up @@ -507,34 +521,32 @@ impl super::RocketchatApi for RocketchatApi {

if let Some(attachments) = message.attachments {
for attachment in attachments {
if let Some(ref image_url) = attachment.image_url {
debug!(self.logger, "Getting file {}", image_url);

let mut get_file_endpoint = GetFileEndpoint {
base_url: self.base_url.clone(),
user_id: self.user_id.clone(),
auth_token: self.auth_token.clone(),
path: image_url,
query_params: HashMap::new(),
};

let mut resp = RestApi::get_rocketchat_file(&get_file_endpoint)?;

if !resp.status().is_success() {
let mut body = String::new();
resp.read_to_string(&mut body).chain_err(|| ErrorKind::ApiCallFailed(image_url.to_owned()))?;
return Err(build_error(&get_file_endpoint.url(), &body, &resp.status()));
}

let mut buffer = Vec::new();
resp.read_to_end(&mut buffer).chain_err(|| ErrorKind::InternalServerError)?;
let rocketchat_attachment = RocketchatAttachment {
content_type: attachment.content_type,
data: buffer,
title: attachment.title,
};
files.push(rocketchat_attachment);
debug!(self.logger, "Getting file {}", attachment.title_link);

let mut get_file_endpoint = GetFileEndpoint {
base_url: self.base_url.clone(),
user_id: self.user_id.clone(),
auth_token: self.auth_token.clone(),
path: &attachment.title_link,
query_params: HashMap::new(),
};

let mut resp = RestApi::get_rocketchat_file(&get_file_endpoint)?;

if !resp.status().is_success() {
let mut body = String::new();
resp.read_to_string(&mut body).chain_err(|| ErrorKind::ApiCallFailed(attachment.title_link.clone()))?;
return Err(build_error(&get_file_endpoint.url(), &body, &resp.status()));
}

let mut buffer = Vec::new();
resp.read_to_end(&mut buffer).chain_err(|| ErrorKind::InternalServerError)?;
let rocketchat_attachment = RocketchatAttachment {
content_type: attachment.content_type,
data: buffer,
title: attachment.title,
};
files.push(rocketchat_attachment);
}
} else {
info!(self.logger, "No attachments found for message ID {}", message_id);
Expand Down Expand Up @@ -643,23 +655,31 @@ impl super::RocketchatApi for RocketchatApi {
})?;

let mut message_attachments_option: Option<Vec<MessageAttachment>> = None;
// Rocket.Chat stores the proper content type (for example 'text/plain') only in the message,
// the attachment contains a type as well, but it's set to 'file' most of the time.
let content_type = message_response.message.content_type()?;
if let Some(attachments) = message_response.message.attachments {
let mut message_attachments = Vec::new();
for attachment in attachments {
message_attachments.push(MessageAttachment {
content_type: attachment.content_type()?,
content_type: content_type.clone(),
image_url: attachment.image_url,
title: attachment.title,
title_link: attachment.title_link,
})
}

message_attachments_option = Some(message_attachments)
};

let file = message_response.message.file.as_ref().map(|f| RocketchatFile {
mimetype: f.mimetype.clone(),
});
let message = RocketchatMessage {
attachments: message_attachments_option,
id: message_response.message.id,
msg: message_response.message.msg,
file,
};

Ok(message)
Expand Down Expand Up @@ -808,7 +828,7 @@ impl super::RocketchatApi for RocketchatApi {
Ok(user)
}

fn rooms_upload(&self, file: Vec<u8>, filename: &str, mime_type: Mime, room_id: &str) -> Result<()> {
fn rooms_upload(&self, file: Vec<u8>, filename: &str, mimetype: Mime, room_id: &str) -> Result<()> {
debug!(self.logger, "Uploading file to room {}", room_id);

let post_file_message_endpoint = RoomsUploadEndpoint {
Expand All @@ -818,7 +838,7 @@ impl super::RocketchatApi for RocketchatApi {
payload: PostFileMessagePayload {
file,
filename,
mime_type,
mimetype,
},
room_id,
};
Expand Down
14 changes: 7 additions & 7 deletions src/matrix-rocketchat/errors.rs
Expand Up @@ -256,6 +256,11 @@ error_chain!{
display("Bridging the channel {} failed, because the user hasn't joined it on Rocket.Chat", channel_name)
}

RocketchatUploadFailed(url: String, err: String) {
description("Uploading file to Rocket.Chat failed")
display("Uploading file {} to Rocket.Chat failed: {}", url, err)
}

UnbridgeOfNotBridgedRoom(display_name: String) {
description("Room with the given display name could not be found")
display("No room with display_name {} found", display_name)
Expand Down Expand Up @@ -346,14 +351,9 @@ error_chain!{
display("Deleting record from the database failed")
}

UnknownContentType(content_type: String) {
description("The content type of the file is unknown")
display("Don't know how to handle content type {}", content_type)
}

UnknownMimeType(mime_type: String) {
UnknownMimeType(mimetype: String) {
description("The mime type of the file is unknown")
display("Don't know how to handle mime type {}", mime_type)
display("Don't know how to handle mime type '{}'", mimetype)
}

MissingMimeType {
Expand Down
6 changes: 3 additions & 3 deletions src/matrix-rocketchat/handlers/error_notifier.rs
@@ -1,10 +1,10 @@
use ruma_identifiers::RoomId;
use slog::Logger;

use i18n::*;
use api::MatrixApi;
use config::Config;
use errors::*;
use i18n::*;

/// Notifies the user about errors
pub struct ErrorNotifier<'a> {
Expand Down Expand Up @@ -34,10 +34,10 @@ impl<'a> ErrorNotifier<'a> {
None => {
error!(self.logger, "{}", msg);
let user_msg = t!(["defaults", "internal_error"]).l(DEFAULT_LANGUAGE);
return self.matrix_api.send_text_message_event(room_id, matrix_bot_id, user_msg);
return self.matrix_api.send_text_message(room_id, matrix_bot_id, user_msg);
}
};

self.matrix_api.send_text_message_event(room_id, matrix_bot_id, user_message.l(DEFAULT_LANGUAGE))
self.matrix_api.send_text_message(room_id, matrix_bot_id, user_message.l(DEFAULT_LANGUAGE))
}
}
2 changes: 1 addition & 1 deletion src/matrix-rocketchat/handlers/iron/transactions.rs
@@ -1,8 +1,8 @@
use std::io::Read;

use iron::{status, Handler};
use iron::prelude::*;
use iron::request::Body;
use iron::{status, Handler};
use serde_json;

use api::MatrixApi;
Expand Down

0 comments on commit 7624479

Please sign in to comment.