Skip to content

Commit

Permalink
Forward images from Matrix to Rocket.Chat
Browse files Browse the repository at this point in the history
  • Loading branch information
exul committed Jan 28, 2018
1 parent 1f7a6b7 commit 41a3e52
Show file tree
Hide file tree
Showing 20 changed files with 376 additions and 101 deletions.
2 changes: 2 additions & 0 deletions src/matrix-rocketchat/api/matrix/mod.rs
Expand Up @@ -24,6 +24,8 @@ pub trait MatrixApi: Send + Sync + MatrixApiClone {
fn delete_room_alias(&self, matrix_room_alias_id: RoomAliasId) -> Result<()>;
/// Forget a room.
fn forget_room(&self, room_id: RoomId, user_id: UserId) -> Result<()>;
/// Get content from the content repository.
fn get_content(&self, server_name: String, media_id: String) -> Result<Vec<u8>>;
/// Get the display name for a Matrix user ID. Returns `None` if the user doesn't exist.
fn get_display_name(&self, user_id: UserId) -> Result<Option<String>>;
/// Get all rooms a user joined.
Expand Down
65 changes: 46 additions & 19 deletions src/matrix-rocketchat/api/matrix/r0.rs
@@ -1,14 +1,16 @@
use std::collections::HashMap;
use std::convert::TryFrom;
use std::io::Read;

use pulldown_cmark::{html, Options, Parser};
use reqwest::{Method, StatusCode};
use reqwest::header::{ContentType, Headers};
use ruma_client_api::Endpoint;
use ruma_client_api::r0::alias::get_alias::{self, Endpoint as GetAliasEndpoint};
use ruma_client_api::r0::alias::delete_alias::Endpoint as DeleteAliasEndpoint;
use ruma_client_api::r0::media::create_content::{self, Endpoint as CreateContentEndpoint};
use ruma_client_api::r0::account::register::{self, Endpoint as RegisterEndpoint};
use ruma_client_api::r0::media::create_content::{self, Endpoint as CreateContentEndpoint};
use ruma_client_api::r0::media::get_content::{self, Endpoint as GetContentEndpoint};
use ruma_client_api::r0::membership::forget_room::{self, Endpoint as ForgetRoomEndpoint};
use ruma_client_api::r0::membership::invite_user::{self, Endpoint as InviteUserEndpoint};
use ruma_client_api::r0::membership::join_room_by_id::{self, Endpoint as JoinRoomByIdEndpoint};
Expand All @@ -31,7 +33,7 @@ use serde_json::{self, Map, Value};
use slog::Logger;
use url;

use api::RestApi;
use api::{RequestData, RestApi};
use config::Config;
use errors::*;

Expand Down Expand Up @@ -129,26 +131,25 @@ impl super::MatrixApi for MatrixApi {
Ok(())
}

fn get_joined_rooms(&self, user_id: UserId) -> Result<Vec<RoomId>> {
let endpoint = self.base_url.clone() + &SyncEventsEndpoint::request_path(());
let user_id = user_id.to_string();
let mut params = self.params_hash();
params.insert("user_id", &user_id);

let (body, status_code) = RestApi::call_matrix(&SyncEventsEndpoint::method(), &endpoint, "", &params)?;
fn get_content(&self, server_name: String, media_id: String) -> Result<Vec<u8>> {
let path_params = get_content::PathParams {
server_name: server_name,
media_id: media_id,
};
let endpoint = self.base_url.clone() + &GetContentEndpoint::request_path(path_params);
let params = self.params_hash();

if !status_code.is_success() {
return Err(build_error(&endpoint, &body, &status_code));
let mut resp = RestApi::get_matrix_file(&GetContentEndpoint::method(), &endpoint, "", &params)?;
if !resp.status().is_success() {
let mut body = String::new();
resp.read_to_string(&mut body).chain_err(|| ErrorKind::ApiCallFailed(endpoint.clone()))?;
return Err(build_error(&endpoint, &body, &resp.status()));
}

let sync_response: Value = serde_json::from_str(&body).chain_err(|| {
ErrorKind::InvalidJSON(format!("Could not deserialize response from Matrix sync_events API endpoint: `{}`", body))
})?;
let mut buffer = Vec::new();
resp.read_to_end(&mut buffer).chain_err(|| ErrorKind::InternalServerError)?;

let empty_rooms = Value::Object(Map::new());
let raw_rooms = sync_response.get("rooms").unwrap_or(&empty_rooms).get("join").unwrap_or(&empty_rooms);
let rooms: HashMap<RoomId, Value> = serde_json::from_value(raw_rooms.clone()).unwrap_or_default();
Ok(rooms.keys().map(|k| k.to_owned()).collect())
Ok(buffer)
}

fn get_display_name(&self, user_id: UserId) -> Result<Option<String>> {
Expand Down Expand Up @@ -177,6 +178,28 @@ impl super::MatrixApi for MatrixApi {
Ok(Some(get_display_name_response.displayname.unwrap_or_default()))
}

fn get_joined_rooms(&self, user_id: UserId) -> Result<Vec<RoomId>> {
let endpoint = self.base_url.clone() + &SyncEventsEndpoint::request_path(());
let user_id = user_id.to_string();
let mut params = self.params_hash();
params.insert("user_id", &user_id);

let (body, status_code) = RestApi::call_matrix(&SyncEventsEndpoint::method(), &endpoint, "", &params)?;

if !status_code.is_success() {
return Err(build_error(&endpoint, &body, &status_code));
}

let sync_response: Value = serde_json::from_str(&body).chain_err(|| {
ErrorKind::InvalidJSON(format!("Could not deserialize response from Matrix sync_events API endpoint: `{}`", body))
})?;

let empty_rooms = Value::Object(Map::new());
let raw_rooms = sync_response.get("rooms").unwrap_or(&empty_rooms).get("join").unwrap_or(&empty_rooms);
let rooms: HashMap<RoomId, Value> = serde_json::from_value(raw_rooms.clone()).unwrap_or_default();
Ok(rooms.keys().map(|k| k.to_owned()).collect())
}

fn get_room_alias(&self, matrix_room_alias_id: RoomAliasId) -> Result<Option<RoomId>> {
// the ruma client api path params cannot be used here, because they are not url encoded
let encoded_room_alias =
Expand Down Expand Up @@ -603,7 +626,7 @@ impl super::MatrixApi for MatrixApi {
let mut headers = Headers::new();
headers.set(content_type);

let (body, status_code) = RestApi::call(&Method::Post, &endpoint, data, &params, Some(headers))?;
let (body, status_code) = RestApi::call(&Method::Post, &endpoint, RequestData::Body(data), &params, Some(headers))?;
if !status_code.is_success() {
return Err(build_error(&endpoint, &body, &status_code));
}
Expand All @@ -621,6 +644,10 @@ impl super::MatrixApi for MatrixApi {
}

fn build_error(endpoint: &str, body: &str, status_code: &StatusCode) -> Error {
if status_code == &StatusCode::NotFound {
return Error::from(ErrorKind::MatrixError("Not found".to_string()));
}

let json_error_msg = format!(
"Could not deserialize error from Matrix API endpoint {} with status code {}: `{}`",
endpoint, status_code, body
Expand Down
2 changes: 1 addition & 1 deletion src/matrix-rocketchat/api/mod.rs
Expand Up @@ -8,5 +8,5 @@ mod rest_api;
pub mod rocketchat;

pub use self::matrix::MatrixApi;
pub use self::rest_api::RestApi;
pub use self::rest_api::{RequestData, RestApi};
pub use self::rocketchat::RocketchatApi;
51 changes: 38 additions & 13 deletions src/matrix-rocketchat/api/rest_api.rs
Expand Up @@ -4,11 +4,20 @@ use std::io::Read;
use url;
use reqwest::{Body, Client, Method, Response, StatusCode, Url};
use reqwest::header::Headers;
use reqwest::multipart::Form;
use ruma_client_api::Method as RumaHttpMethod;

use errors::*;
use api::rocketchat::Endpoint;

/// Request data types.
pub enum RequestData<T: Into<Body>> {
/// Any type that can be converted into a body.
Body(T),
/// A multipart form
MultipartForm(Form),
}

/// REST API
pub struct RestApi {}

Expand All @@ -27,30 +36,43 @@ impl RestApi {
RumaHttpMethod::Put => Method::Put,
};

RestApi::call(&method, url, payload, params, None)
let data = RequestData::Body(payload.into());
RestApi::call(&method, url, data, params, None)
}

/// Get a file that was uploaded to a Matrix homeserver
pub fn get_matrix_file<'a, T: Into<Body>>(
method: &RumaHttpMethod,
url: &str,
payload: T,
params: &HashMap<&str, &'a str>,
) -> Result<Response> {
let method = match *method {
RumaHttpMethod::Delete => Method::Delete,
RumaHttpMethod::Get => Method::Get,
RumaHttpMethod::Post => Method::Post,
RumaHttpMethod::Put => Method::Put,
};

let data = RequestData::Body(payload.into());
RestApi::call_raw(&method, url, data, params, None)
}

/// Call a Rocket.Chat API endpoint
pub fn call_rocketchat(endpoint: &Endpoint) -> Result<(String, StatusCode)> {
pub fn call_rocketchat<T: Into<Body>>(endpoint: &Endpoint<T>) -> Result<(String, StatusCode)> {
RestApi::call(&endpoint.method(), &endpoint.url(), endpoint.payload()?, &endpoint.query_params(), endpoint.headers())
}

/// Get a file that was uploaded to Rocket.Chat
pub fn get_rocketchat_file(endpoint: &Endpoint) -> Result<Response> {
RestApi::call_raw(
&endpoint.method(),
&endpoint.url(),
endpoint.payload()?,
&endpoint.query_params(),
endpoint.headers(),
)
pub fn get_rocketchat_file<T: Into<Body>>(endpoint: &Endpoint<T>) -> Result<Response> {
RestApi::call_raw(&endpoint.method(), &endpoint.url(), endpoint.payload()?, &endpoint.query_params(), endpoint.headers())
}

/// Call a REST API endpoint
pub fn call<'a, T: Into<Body>>(
method: &Method,
url: &str,
payload: T,
payload: RequestData<T>,
params: &HashMap<&str, &'a str>,
headers: Option<Headers>,
) -> Result<(String, StatusCode)> {
Expand All @@ -64,7 +86,7 @@ impl RestApi {
fn call_raw<'a, T: Into<Body>>(
method: &Method,
url: &str,
payload: T,
data: RequestData<T>,
params: &HashMap<&str, &'a str>,
headers: Option<Headers>,
) -> Result<Response> {
Expand All @@ -85,7 +107,10 @@ impl RestApi {
req.headers(headers);
}

req.body(payload);
match data {
RequestData::Body(body) => req.body(body),
RequestData::MultipartForm(form) => req.multipart(form),
};

let resp = req.send().chain_err(|| ErrorKind::ApiCallFailed(url.to_owned()))?;

Expand Down
20 changes: 13 additions & 7 deletions src/matrix-rocketchat/api/rocketchat/mod.rs
Expand Up @@ -2,25 +2,29 @@ use std::collections::HashMap;

use iron::typemap::Key;
use reqwest::header::{ContentType, Headers};
use reqwest::Method;
use reqwest::mime::Mime;
use reqwest::{Body, Method};
use serde_json;
use slog::Logger;

use api::RestApi;
use api::{RequestData, RestApi};
use errors::*;
use i18n::*;

/// Rocket.Chat REST API v1
pub mod v1;

const MIN_MAJOR_VERSION: i32 = 0;
const MIN_MINOR_VERSION: i32 = 60;

/// A Rocket.Chat REST API endpoint.
pub trait Endpoint {
pub trait Endpoint<T: Into<Body>> {
/// HTTP Method
fn method(&self) -> Method;
/// The URL of the endpoint
fn url(&self) -> String;
/// Payload that is sent to the server
fn payload(&self) -> Result<String>;
fn payload(&self) -> Result<RequestData<T>>;
/// Headers that are sent to the server
fn headers(&self) -> Option<Headers>;
/// The query parameters that are used when sending the request
Expand Down Expand Up @@ -94,6 +98,8 @@ pub trait RocketchatApi {
fn login(&self, username: &str, password: &str) -> Result<(String, String)>;
/// Post a chat message
fn post_chat_message(&self, text: &str, room_id: &str) -> Result<()>;
/// Post a message with an attchment
fn post_file_message(&self, file: Vec<u8>, filename: &str, mime_type: 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 All @@ -113,7 +119,7 @@ impl RocketchatApi {
let url = base_url.clone() + "/api/info";
let params = HashMap::new();

let (body, status_code) = match RestApi::call(&Method::Get, &url, "", &params, None) {
let (body, status_code) = match RestApi::call(&Method::Get, &url, RequestData::Body(""), &params, None) {
Ok((body, status_code)) => (body, status_code),
Err(err) => {
debug!(logger, "{}", err);
Expand Down Expand Up @@ -148,12 +154,12 @@ impl RocketchatApi {
let major: i32 = versions.next().unwrap_or("0").parse().unwrap_or(0);
let minor: i32 = versions.next().unwrap_or("0").parse().unwrap_or(0);

if major == 0 && minor >= 49 {
if major == MIN_MAJOR_VERSION && minor >= MIN_MINOR_VERSION {
let rocketchat_api = v1::RocketchatApi::new(base_url, logger);
return Ok(Box::new(rocketchat_api));
}

let min_version = "0.49".to_string();
let min_version = format!("{}.{}", MIN_MAJOR_VERSION, MIN_MINOR_VERSION);
Err(Error {
error_chain: ErrorKind::UnsupportedRocketchatApiVersion(min_version.clone(), version.clone()).into(),
user_message: Some(
Expand Down

0 comments on commit 41a3e52

Please sign in to comment.