From 1d70a861d8c60dcd6c1d2a2ad086832d143fefd9 Mon Sep 17 00:00:00 2001 From: Fabian Schmidt Date: Tue, 30 Aug 2022 13:37:25 +0200 Subject: [PATCH 1/7] refactor code & send img from threema to matrix --- Cargo.lock | 27 +- Cargo.toml | 12 +- src/incoming_message_handler/matrix.rs | 147 +++++++++++ src/incoming_message_handler/mod.rs | 2 + src/incoming_message_handler/threema.rs | 223 +++++++++++++++++ src/lib.rs | 315 +----------------------- src/main.rs | 18 +- src/matrix/errors.rs | 26 ++ src/matrix/matrix_client_impl.rs | 83 +++++++ src/matrix/mod.rs | 66 +++-- src/threema/mod.rs | 72 ++++-- src/threema/types.rs | 75 +++++- 12 files changed, 681 insertions(+), 385 deletions(-) create mode 100644 src/incoming_message_handler/matrix.rs create mode 100644 src/incoming_message_handler/mod.rs create mode 100644 src/incoming_message_handler/threema.rs create mode 100644 src/matrix/errors.rs create mode 100644 src/matrix/matrix_client_impl.rs diff --git a/Cargo.lock b/Cargo.lock index 1bee635..232d63a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -323,9 +323,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -466,9 +466,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "bytestring" @@ -1065,6 +1065,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hkdf" version = "0.12.3" @@ -2314,9 +2320,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa", "ryu", @@ -2547,8 +2553,6 @@ dependencies = [ [[package]] name = "threema-gateway" version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5155a6752d66396b91b85b7a2c14bc02cd843212a219db067468ed2b7bc691b7" dependencies = [ "byteorder", "data-encoding", @@ -2568,13 +2572,20 @@ name = "threematrix" version = "0.1.0" dependencies = [ "actix-web", + "async-trait", + "bytes", + "data-encoding", "flexi_logger", "futures", + "hex", "log", "matrix-sdk", + "mime", "rand 0.8.5", + "reqwest", "serde", "serde_derive", + "serde_json", "signal-hook", "signal-hook-tokio", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 0d42ab3..39ad275 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -threema-gateway = "0.15.1" +#threema-gateway = { git = "https://github.com/bitbetterde/threema-gateway-rs.git" } +threema-gateway = { path = "../threema-gateway-rs" } tokio = { version = "1", features = ["full"] } actix-web = "4" rand = "0.8.5" @@ -17,4 +18,11 @@ signal-hook-tokio = { features = ["futures-v0_3"], version = "0.3.1" } futures = "0.3.21" log = "0.4.17" flexi_logger = "0.22.5" -thiserror="1.0.31" \ No newline at end of file +thiserror="1.0.31" +serde_json = "1.0.83" +data-encoding = "2.3.2" +reqwest = "0.11.11" +bytes = "1.2.1" +hex = "0.4.3" +mime = "0.3.16" +async-trait = "0.1.57" diff --git a/src/incoming_message_handler/matrix.rs b/src/incoming_message_handler/matrix.rs new file mode 100644 index 0000000..d80027d --- /dev/null +++ b/src/incoming_message_handler/matrix.rs @@ -0,0 +1,147 @@ +use log::{debug, error, info, warn}; +use matrix_sdk::{Client, ruma::events::room::member::StrippedRoomMemberEvent}; +use matrix_sdk::event_handler::Ctx; +use matrix_sdk::room::{Joined, Room}; +use matrix_sdk::ruma::events::OriginalSyncMessageLikeEvent; +use matrix_sdk::ruma::events::room::message::{ + MessageType, RoomMessageEventContent, TextMessageEventContent, +}; +use matrix_sdk::ruma::TransactionId; +use tokio::time::{Duration, sleep}; + +use crate::matrix::util::{ + get_threematrix_room_state, +}; +use crate::threema::ThreemaClient; +use crate::threema::util::convert_group_id_from_readable_string; + +pub async fn matrix_incoming_message_handler( + event: OriginalSyncMessageLikeEvent, + room: Room, + threema_client: Ctx, + matrix_client: Client, +) -> () { + match room { + Room::Joined(room) => { + if let OriginalSyncMessageLikeEvent { + content: + RoomMessageEventContent { + msgtype: MessageType::Text(TextMessageEventContent { body: msg_body, .. }), + .. + }, + sender, + .. + } = event + { + debug!("Matrix: Incoming message: {}", msg_body); + + let sender_member = room.get_member(&sender).await; + match sender_member { + Ok(Some(sender_member)) => { + let sender_name = sender_member + .display_name() + .unwrap_or_else(|| sender_member.user_id().as_str()); + + // Filter out messages coming from our own bridge user + if sender != matrix_client.user_id().await.unwrap() { + match get_threematrix_room_state(&room).await { + Ok(None) => { + let err_txt = format!("Room {} does not have proper room state. Have you bound the room to a Threema group?", + &room.display_name().await.unwrap_or(matrix_sdk::DisplayName::Named("UNKNOWN".to_owned()))); + send_error_message_to_matrix_room(&room, err_txt, false).await; + } + Ok(Some(threematrix_state)) => { + let group_id = convert_group_id_from_readable_string( + threematrix_state.threematrix_threema_group_id.as_str(), + ); + + if let Ok(group_id) = group_id { + if let Err(e) = threema_client + .send_group_msg_by_group_id( + format!("*{}*: {}", sender_name, msg_body).as_str(), + group_id.as_slice(), + ) + .await + { + let err_txt = format!( + "Couldn't send message to Threema group: {}", + e + ); + send_error_message_to_matrix_room(&room, err_txt, true) + .await; + } + } + } + Err(e) => { + let err_txt = format!("Could not retrieve room state: {}", e); + send_error_message_to_matrix_room(&room, err_txt, true).await; + } + } + } + } + _ => { + error!("Matrix: Could not resolve room member!"); + } + } + } + } + _ => { + // If bot not member of room, ignore incoming message + } + } +} + +async fn send_error_message_to_matrix_room(room: &Joined, err_txt: String, log_level_err: bool) { + if log_level_err { + error!("Matrix: {}", err_txt); + } else { + warn!("Matrix: {}", err_txt); + } + + let content = RoomMessageEventContent::text_plain(err_txt.clone()); + let txn_id = TransactionId::new(); + + if let Err(e) = room.send(content, Some(&txn_id)).await { + error!( + "Matrix: Could not send error message: \"{}\". {}", + err_txt, e + ) + } +} + +// Source: https://github.com/matrix-org/matrix-rust-sdk/blob/matrix-sdk-0.5.0/crates/matrix-sdk/examples/autojoin.rs +pub async fn on_stripped_state_member( + room_member: StrippedRoomMemberEvent, + client: Client, + room: Room, +) { + if room_member.state_key != client.user_id().await.unwrap() { + return; + } + + if let Room::Invited(room) = room { + debug!("Matrix: Autojoining room {}", room.room_id()); + let mut delay = 2; + + while let Err(err) = room.accept_invitation().await { + // retry autojoin due to synapse sending invites, before the + // invited user can join for more information see + // https://github.com/matrix-org/synapse/issues/4345 + error!( + "Matrix: Failed to join room {} ({:?}), retrying in {}s", + room.room_id(), + err, + delay + ); + + sleep(Duration::from_secs(delay)).await; + delay *= 2; + + if delay > 3600 { + error!("Matrix: Can't join room {} ({:?})", room.room_id(), err); + break; + } + } + info!("Matrix: Successfully joined room {}", room.room_id()); + } +} diff --git a/src/incoming_message_handler/mod.rs b/src/incoming_message_handler/mod.rs new file mode 100644 index 0000000..af36c26 --- /dev/null +++ b/src/incoming_message_handler/mod.rs @@ -0,0 +1,2 @@ +pub mod threema; +pub mod matrix; \ No newline at end of file diff --git a/src/incoming_message_handler/threema.rs b/src/incoming_message_handler/threema.rs new file mode 100644 index 0000000..b1cb489 --- /dev/null +++ b/src/incoming_message_handler/threema.rs @@ -0,0 +1,223 @@ +use std::io::Cursor; + +use actix_web::{http::header::ContentType, web, HttpResponse, Responder}; +use log::{debug, error, info, warn}; +use matrix_sdk::attachment::AttachmentConfig; +use threema_gateway::IncomingMessage; + +use crate::{AppState, Message}; +use crate::matrix::errors::{BindThreemaGroupToMatrixError, SendToMatrixRoomByThreemaGroupIdError}; +use crate::matrix::MatrixClient; + +use crate::matrix::util::{ + get_threematrix_room_state, +}; +use crate::threema::util::{ + convert_group_id_to_readable_string, +}; +use crate::threema::ThreemaClient; + +pub async fn threema_incoming_message_handler( + incoming_message: web::Form, + app_state: web::Data, +) -> impl Responder { + let threema_client = &app_state.threema_client; + let decrypted_message = threema_client.process_incoming_msg(&incoming_message).await; + + match decrypted_message { + Ok(message) => match message { + Message::GroupTextMessage(group_text_msg) => { + let matrix_client = app_state.matrix_client.lock().await; + + if group_text_msg.text.starts_with("!threematrix") { + let split_text: Vec<&str> = group_text_msg.text.split(" ").collect(); + match split_text.get(1).map(|str| *str) { + Some("bind") => { + let matrix_room_id = split_text.get(2); + if let Some(matrix_room_id) = matrix_room_id { + match matrix_client.bind_threema_group_to_matrix_room(&group_text_msg.group_id, matrix_room_id).await { + Ok(_) => { + let succ_text = format!("Group has been successfully bound to Matrix room: {}", matrix_room_id); + if let Err(e) = threema_client.send_group_msg_by_group_id( + succ_text.as_str(), group_text_msg.group_id.as_slice()).await + { + error!("Threema: Could not send bind text: {}", e) + } + } + Err(e) => { + match e { + BindThreemaGroupToMatrixError::InvalidGroupId(_) => { + error!("Threema: Group Id not valid!"); + } + BindThreemaGroupToMatrixError::MatrixError(e) => { + let err_text = format!("Could not set Matrix room state: {}", e); + send_error_message_to_threema_group( + threema_client, + err_text, + group_text_msg.group_id.as_slice(), + false, + ).await; + } + BindThreemaGroupToMatrixError::InvalidMatrixRoomId(e) => { + let err_text = format!("Invalid matrix room Id: {}", e); + send_error_message_to_threema_group( + threema_client, + err_text, + group_text_msg.group_id.as_slice(), + false, + ).await; + } + } + } + } + } else { + let err_text = format!("Missing Matrix room id!"); + send_error_message_to_threema_group( + threema_client, + err_text, + group_text_msg.group_id.as_slice(), + false, + ).await; + } + } + Some("help") => { + let help_txt = r#"To bind this Threema Group to a Matrix Room, please use the command "!threematrix bind !abc123:homeserver.org". +You can find the required room id in your Matrix client. Attention: This is NOT a "human readable" room alias, but an "internal" room id, which consists of random characters."#; + if let Err(e) = threema_client + .send_group_msg_by_group_id( + help_txt, + group_text_msg.group_id.as_slice(), + ) + .await + { + error!("Threema: Could not send help text: {}", e) + } + } + _ => { + let err_text = format!( + "Command not found! Use *!threematrix help* for more information" + ); + send_error_message_to_threema_group( + threema_client, + err_text, + group_text_msg.group_id.as_slice(), + false, + ) + .await; + } + } + } else { + let sender_name = group_text_msg + .base + .push_from_name + .unwrap_or("UNKNOWN".to_owned()); + + match matrix_client.get_joined_room_by_threema_group_id(&group_text_msg.group_id).await { + Ok(room) => { + if let Err(e) = matrix_client + .send_message_to_matrix_room( + &room, + sender_name.as_str(), + group_text_msg.text.as_str(), + group_text_msg.text.as_str(), + ).await + { + match e { + SendToMatrixRoomByThreemaGroupIdError::MatrixError(e) => { + let err_txt = format!("Could not send message to Matrix room: {}", e); + send_error_message_to_threema_group( + threema_client, + err_txt, + group_text_msg.group_id.as_slice(), + true, + ).await; + } + } + } + } + Err(_) => { + debug!("No Matrix room for Threema group id found. Maybe group is not bound to any room"); + } + } + } + } + Message::GroupFileMessage(group_file_msg) => { + let matrix_client = app_state.matrix_client.lock().await; + debug!("Threema: Start sending file to Matrix"); + let sender_name = group_file_msg + .base + .push_from_name + .unwrap_or("UNKNOWN".to_owned()); + + match matrix_client.get_joined_room_by_threema_group_id(&group_file_msg.group_id).await { + Ok(room) => { + if let Err(e) = matrix_client + .send_file_to_matrix_room( + &room, + sender_name.as_str(), + group_file_msg.file.as_slice(), + ).await + { + match e { + SendToMatrixRoomByThreemaGroupIdError::MatrixError(e) => { + let err_txt = format!("Could not send message to Matrix room: {}", e); + send_error_message_to_threema_group( + threema_client, + err_txt, + group_file_msg.group_id.as_slice(), + true, + ).await; + } + } + } + } + Err(_) => { + debug!("No Matrix room for Threema group id found. Maybe group is not bound to any room"); + } + } + } + Message::GroupCreateMessage(group_create_msg) => { + info!( + "Got group create message with members: {:?}", + group_create_msg.members + ); + } + Message::GroupRenameMessage(group_rename_msg) => { + info!( + "Got group rename message for: {:?}", + group_rename_msg.group_name + ); + } + _ => {} + }, + Err(err) => { + error!("Threema: Incoming Message Error: {}", err); + } + } + + HttpResponse::Ok() + .content_type(ContentType::plaintext()) + .body(()) +} + +async fn send_error_message_to_threema_group( + threema_client: &ThreemaClient, + err_text: String, + group_id: &[u8], + log_level_error: bool, +) { + if log_level_error { + error!("Threema: {}", err_text); + } else { + warn!("Threema: {}", err_text); + } + if let Err(e) = threema_client + .send_group_msg_by_group_id(err_text.as_str(), group_id) + .await + { + error!( + "Threema: Could not send error message: \"{}\". {}", + err_text, e + ) + } +} diff --git a/src/lib.rs b/src/lib.rs index a8371ad..5e37d0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,34 +1,18 @@ use std::env::var; use std::fs::read_to_string; -use actix_web::{http::header::ContentType, web, HttpResponse, Responder}; -use log::{debug, error, info, warn}; -use matrix_sdk::event_handler::Ctx; -use matrix_sdk::room::{Joined, Room}; -use matrix_sdk::ruma::events::room::message::{ - MessageType, RoomMessageEventContent, TextMessageEventContent, -}; -use matrix_sdk::ruma::events::OriginalSyncMessageLikeEvent; -use matrix_sdk::ruma::TransactionId; use matrix_sdk::Client; use serde_derive::{Deserialize, Serialize}; -use threema_gateway::IncomingMessage; use tokio::sync::Mutex; use threema::types::Message; - -use crate::matrix::util::{ - get_threematrix_room_state, set_threematrix_room_state, ThreematrixStateEventContent, -}; -use crate::threema::util::{ - convert_group_id_from_readable_string, convert_group_id_to_readable_string, -}; use crate::threema::ThreemaClient; pub mod errors; pub mod matrix; pub mod threema; pub mod util; +pub mod incoming_message_handler; pub struct AppState { pub threema_client: ThreemaClient, @@ -81,299 +65,4 @@ impl ThreematrixConfig { }; return config_from_file; } -} - -pub async fn threema_incoming_message_handler( - incoming_message: web::Form, - app_state: web::Data, -) -> impl Responder { - let threema_client = &app_state.threema_client; - let decrypted_message = threema_client.process_incoming_msg(&incoming_message).await; - - match decrypted_message { - Ok(message) => match message { - Message::GroupTextMessage(group_text_msg) => { - let matrix_client = app_state.matrix_client.lock().await; - - if group_text_msg.text.starts_with("!threematrix") { - let split_text: Vec<&str> = group_text_msg.text.split(" ").collect(); - match split_text.get(1).map(|str| *str) { - Some("bind") => { - let rooms = matrix_client.joined_rooms(); - let matrix_room_id = split_text.get(2); - - if let Some(matrix_room_id) = matrix_room_id { - if let Some(room) = - rooms.iter().find(|r| r.room_id() == matrix_room_id) - { - if let Ok(r) = convert_group_id_to_readable_string( - &group_text_msg.group_id, - ) { - let content: ThreematrixStateEventContent = - ThreematrixStateEventContent { - threematrix_threema_group_id: r, - }; - - if let Err(e) = - set_threematrix_room_state(content, room).await - { - let err_text = - format!("Could not set Matrix room state: {}", e); - send_error_message_to_threema_group( - threema_client, - err_text, - group_text_msg.group_id.as_slice(), - false, - ) - .await; - } else { - let succ_text = format!("Group has been successfully bound to Matrix room: {}", matrix_room_id); - if let Err(e) = threema_client - .send_group_msg_by_group_id( - succ_text.as_str(), - group_text_msg.group_id.as_slice(), - ) - .await - { - error!("Threema: Could not send bind text: {}", e) - } - }; - } else { - error!("Threema: Group Id not valid!"); - } - } else { - let err_text = format!("Matrix room not found. Maybe the bot is not invited or the room id has wrong format!"); - send_error_message_to_threema_group( - threema_client, - err_text, - group_text_msg.group_id.as_slice(), - false, - ) - .await; - } - } else { - let err_text = format!("Missing Matrix room id!"); - send_error_message_to_threema_group( - threema_client, - err_text, - group_text_msg.group_id.as_slice(), - false, - ) - .await; - } - } - Some("help") => { - let help_txt = r#"To bind this Threema Group to a Matrix Room, please use the command "!threematrix bind !abc123:homeserver.org". -You can find the required room id in your Matrix client. Attention: This is NOT a "human readable" room alias, but an "internal" room id, which consists of random characters."#; - if let Err(e) = threema_client - .send_group_msg_by_group_id( - help_txt, - group_text_msg.group_id.as_slice(), - ) - .await - { - error!("Threema: Could not send help text: {}", e) - } - } - _ => { - let err_text = format!( - "Command not found! Use *!threematrix help* for more information" - ); - send_error_message_to_threema_group( - threema_client, - err_text, - group_text_msg.group_id.as_slice(), - false, - ) - .await; - } - } - } else { - let sender_name = group_text_msg - .base - .push_from_name - .unwrap_or("UNKNOWN".to_owned()); - let content = RoomMessageEventContent::text_html( - format!("{}: {}", sender_name, group_text_msg.text.as_str()), - format!( - "{}: {}", - sender_name, - group_text_msg.text.as_str() - ), - ); - for room in matrix_client.joined_rooms() { - match get_threematrix_room_state(&room).await { - Ok(None) => debug!( - "Matrix: Room {:?} does not have proper room state", - &room.display_name().await.unwrap_or( - matrix_sdk::DisplayName::Named("UNKNOWN".to_owned()) - ) - ), - Ok(Some(state)) => { - if let Ok(group_id) = - convert_group_id_to_readable_string(&group_text_msg.group_id) - { - if state.threematrix_threema_group_id == group_id { - let txn_id = TransactionId::new(); - if let Err(e) = - room.send(content.clone(), Some(&txn_id)).await - { - let err_txt = format!( - "Could not send message to Matrix room: {}", - e - ); - send_error_message_to_threema_group( - threema_client, - err_txt, - group_text_msg.group_id.as_slice(), - true, - ) - .await; - } - } - } - } - Err(e) => warn!("Matrix: Could not retrieve room state: {}", e), - } - } - } - } - Message::GroupCreateMessage(group_create_msg) => { - info!( - "Got group create message with members: {:?}", - group_create_msg.members - ); - } - Message::GroupRenameMessage(group_rename_msg) => { - info!( - "Got group rename message for: {:?}", - group_rename_msg.group_name - ); - } - _ => {} - }, - Err(err) => { - error!("Threema: Incoming Message Error: {}", err); - } - } - - HttpResponse::Ok() - .content_type(ContentType::plaintext()) - .body(()) -} - -async fn send_error_message_to_threema_group( - threema_client: &ThreemaClient, - err_text: String, - group_id: &[u8], - log_level_error: bool, -) { - if log_level_error { - error!("Threema: {}", err_text); - } else { - warn!("Threema: {}", err_text); - } - if let Err(e) = threema_client - .send_group_msg_by_group_id(err_text.as_str(), group_id) - .await - { - error!( - "Threema: Could not send error message: \"{}\". {}", - err_text, e - ) - } -} - -pub async fn matrix_incoming_message_handler( - event: OriginalSyncMessageLikeEvent, - room: Room, - threema_client: Ctx, - matrix_client: Client, -) -> () { - match room { - Room::Joined(room) => { - if let OriginalSyncMessageLikeEvent { - content: - RoomMessageEventContent { - msgtype: MessageType::Text(TextMessageEventContent { body: msg_body, .. }), - .. - }, - sender, - .. - } = event - { - debug!("Matrix: Incoming message: {}", msg_body); - - let sender_member = room.get_member(&sender).await; - match sender_member { - Ok(Some(sender_member)) => { - let sender_name = sender_member - .display_name() - .unwrap_or_else(|| sender_member.user_id().as_str()); - - // Filter out messages coming from our own bridge user - if sender != matrix_client.user_id().await.unwrap() { - match get_threematrix_room_state(&room).await { - Ok(None) => { - let err_txt = format!("Room {} does not have proper room state. Have you bound the room to a Threema group?", - &room.display_name().await.unwrap_or(matrix_sdk::DisplayName::Named("UNKNOWN".to_owned()))); - send_error_message_to_matrix_room(&room, err_txt, false).await; - } - Ok(Some(threematrix_state)) => { - let group_id = convert_group_id_from_readable_string( - threematrix_state.threematrix_threema_group_id.as_str(), - ); - - if let Ok(group_id) = group_id { - if let Err(e) = threema_client - .send_group_msg_by_group_id( - format!("*{}*: {}", sender_name, msg_body).as_str(), - group_id.as_slice(), - ) - .await - { - let err_txt = format!( - "Couldn't send message to Threema group: {}", - e - ); - send_error_message_to_matrix_room(&room, err_txt, true) - .await; - } - } - } - Err(e) => { - let err_txt = format!("Could not retrieve room state: {}", e); - send_error_message_to_matrix_room(&room, err_txt, true).await; - } - } - } - } - _ => { - error!("Matrix: Could not resolve room member!"); - } - } - } - } - _ => { - // If bot not member of room, ignore incoming message - } - } -} - -async fn send_error_message_to_matrix_room(room: &Joined, err_txt: String, log_level_err: bool) { - if log_level_err { - error!("Matrix: {}", err_txt); - } else { - warn!("Matrix: {}", err_txt); - } - - let content = RoomMessageEventContent::text_plain(err_txt.clone()); - let txn_id = TransactionId::new(); - - if let Err(e) = room.send(content, Some(&txn_id)).await { - error!( - "Matrix: Could not send error message: \"{}\". {}", - err_txt, e - ) - } -} +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index dd16da0..40f8f10 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,12 +11,10 @@ use std::error::Error; use std::process; use tokio::sync::Mutex; -use threematrix::matrix::on_stripped_state_member; use threematrix::threema::ThreemaClient; -use threematrix::{ - matrix_incoming_message_handler, threema_incoming_message_handler, AppState, LoggerConfig, - ThreematrixConfig, -}; +use threematrix::{AppState, LoggerConfig, ThreematrixConfig}; +use threematrix::incoming_message_handler::matrix::{matrix_incoming_message_handler, on_stripped_state_member}; +use threematrix::incoming_message_handler::threema::threema_incoming_message_handler; const VERSION: &str = env!("CARGO_PKG_VERSION"); const CRATE_NAME: &str = env!("CARGO_CRATE_NAME"); @@ -89,11 +87,11 @@ async fn main() -> Result<(), Box> { web::post().to(threema_incoming_message_handler), ) }) - .bind(( - cfg.threema.host.unwrap_or("localhost".to_owned()), - cfg.threema.port.unwrap_or(443), - ))? - .run(), + .bind(( + cfg.threema.host.unwrap_or("localhost".to_owned()), + cfg.threema.port.unwrap_or(443), + ))? + .run(), ); let matrix_server = tokio::spawn(async move { matrix_client.sync(settings).await }); diff --git a/src/matrix/errors.rs b/src/matrix/errors.rs new file mode 100644 index 0000000..f262756 --- /dev/null +++ b/src/matrix/errors.rs @@ -0,0 +1,26 @@ +use thiserror::Error; +use matrix_sdk::{Error}; +use matrix_sdk::ruma::IdParseError; +use crate::errors::StringifyGroupIdError; + +#[derive(Debug, Error)] +pub enum FindMatrixRoomByThreemaGroupIdError { + #[error("No Matrix room for group id found.")] + NoRoomForGroupIdFoundError, +} + +#[derive(Debug, Error)] +pub enum SendToMatrixRoomByThreemaGroupIdError { + #[error("{0}")] + MatrixError(Error), +} + +#[derive(Debug, Error)] +pub enum BindThreemaGroupToMatrixError { + #[error("{0}")] + InvalidGroupId(StringifyGroupIdError), + #[error("{0}")] + MatrixError(Error), + #[error("{0}")] + InvalidMatrixRoomId(IdParseError), +} \ No newline at end of file diff --git a/src/matrix/matrix_client_impl.rs b/src/matrix/matrix_client_impl.rs new file mode 100644 index 0000000..c3ef3c1 --- /dev/null +++ b/src/matrix/matrix_client_impl.rs @@ -0,0 +1,83 @@ +use std::io::Cursor; +use log::{debug, warn}; +use matrix_sdk::Client; +use matrix_sdk::room::Joined; +use matrix_sdk::ruma::events::room::message::RoomMessageEventContent; +use matrix_sdk::ruma::{RoomId, TransactionId}; +use crate::matrix::errors::{BindThreemaGroupToMatrixError, FindMatrixRoomByThreemaGroupIdError, SendToMatrixRoomByThreemaGroupIdError}; +use crate::matrix::MatrixClient; +use crate::matrix::util::{get_threematrix_room_state, set_threematrix_room_state, ThreematrixStateEventContent}; +use crate::threema::util::convert_group_id_to_readable_string; +use async_trait::async_trait; +use matrix_sdk::attachment::AttachmentConfig; + +#[async_trait] +impl MatrixClient for Client { + async fn get_joined_room_by_threema_group_id(&self, threema_group_id: &[u8]) -> Result { + for room in self.joined_rooms() { + match get_threematrix_room_state(&room).await { + Ok(None) => debug!( + "Matrix: Room {:?} does not have proper room state", + &room + .display_name() + .await + .unwrap_or(matrix_sdk::DisplayName::Named("UNKNOWN".to_owned())) + ), + Ok(Some(state)) => { + if let Ok(group_id) = convert_group_id_to_readable_string(&threema_group_id) { + if state.threematrix_threema_group_id == group_id { + return Ok(room); + } + } + } + Err(e) => warn!("Matrix: Could not retrieve room state: {}", e), + } + } + Err(FindMatrixRoomByThreemaGroupIdError::NoRoomForGroupIdFoundError) + } + + async fn send_message_to_matrix_room(&self, room: &Joined, user_name: &str, body: &str, html_body: &str) -> Result<(), SendToMatrixRoomByThreemaGroupIdError> { + let content = RoomMessageEventContent::text_html( + format!("{}: {}", user_name, body).as_str(), + format!("{}: {}", user_name, html_body)); + let txn_id = TransactionId::new(); + room.send(content.clone(), Some(&txn_id)) + .await + .map_err(|e| { + SendToMatrixRoomByThreemaGroupIdError::MatrixError(e) + })?; + return Ok(()); + } + + async fn send_file_to_matrix_room(&self, room: &Joined, sender_name: &str, file: &[u8]) -> Result<(), SendToMatrixRoomByThreemaGroupIdError> { + let mut cursor = Cursor::new(file); + room.send_attachment(sender_name, &mime::IMAGE_JPEG, &mut cursor, AttachmentConfig::new()).await + .map_err(|e| { SendToMatrixRoomByThreemaGroupIdError::MatrixError(e) })?; + + return Ok(()); + } + + async fn bind_threema_group_to_matrix_room(&self, threema_group_id: &[u8], matrix_room_id: &str) -> Result<(), BindThreemaGroupToMatrixError> { + let room_id = <&RoomId>::try_from(matrix_room_id) + .map_err(|e| BindThreemaGroupToMatrixError::InvalidMatrixRoomId(e))?; + + let room = self.get_joined_room(room_id).unwrap(); + + match convert_group_id_to_readable_string(&threema_group_id) { + Ok(r) => { + let content: ThreematrixStateEventContent = ThreematrixStateEventContent { + threematrix_threema_group_id: r, + }; + + if let Err(e) = set_threematrix_room_state(content, &room).await { + return Err(BindThreemaGroupToMatrixError::MatrixError(e)); + } else { + return Ok(()); + }; + } + Err(e) => { + return Err(BindThreemaGroupToMatrixError::InvalidGroupId(e)); + } + } + } +} \ No newline at end of file diff --git a/src/matrix/mod.rs b/src/matrix/mod.rs index 2bcff54..1c16083 100644 --- a/src/matrix/mod.rs +++ b/src/matrix/mod.rs @@ -1,42 +1,34 @@ pub mod util; +pub mod errors; +pub mod matrix_client_impl; -use log::{debug, error, info}; -use matrix_sdk::{room::Room, ruma::events::room::member::StrippedRoomMemberEvent, Client}; -use tokio::time::{sleep, Duration}; +use async_trait::async_trait; +use matrix_sdk::room::Joined; +use crate::matrix::errors::{BindThreemaGroupToMatrixError, FindMatrixRoomByThreemaGroupIdError, SendToMatrixRoomByThreemaGroupIdError}; -// Source: https://github.com/matrix-org/matrix-rust-sdk/blob/matrix-sdk-0.5.0/crates/matrix-sdk/examples/autojoin.rs -pub async fn on_stripped_state_member( - room_member: StrippedRoomMemberEvent, - client: Client, - room: Room, -) { - if room_member.state_key != client.user_id().await.unwrap() { - return; - } - if let Room::Invited(room) = room { - debug!("Matrix: Autojoining room {}", room.room_id()); - let mut delay = 2; - - while let Err(err) = room.accept_invitation().await { - // retry autojoin due to synapse sending invites, before the - // invited user can join for more information see - // https://github.com/matrix-org/synapse/issues/4345 - error!( - "Matrix: Failed to join room {} ({:?}), retrying in {}s", - room.room_id(), - err, - delay - ); - - sleep(Duration::from_secs(delay)).await; - delay *= 2; - - if delay > 3600 { - error!("Matrix: Can't join room {} ({:?})", room.room_id(), err); - break; - } - } - info!("Matrix: Successfully joined room {}", room.room_id()); - } +#[async_trait] +pub trait MatrixClient { + async fn get_joined_room_by_threema_group_id( + &self, + threema_group_id: &[u8], + ) -> Result; + async fn send_message_to_matrix_room( + &self, + room: &Joined, + user_name: &str, + body: &str, + html_body: &str, + ) -> Result<(), SendToMatrixRoomByThreemaGroupIdError>; + async fn send_file_to_matrix_room( + &self, + room: &Joined, + user_name: &str, + file: &[u8], + ) -> Result<(), SendToMatrixRoomByThreemaGroupIdError>; + async fn bind_threema_group_to_matrix_room( + &self, + threema_group_id: &[u8], + matrix_room_id: &str, + ) -> Result<(), BindThreemaGroupToMatrixError>; } diff --git a/src/threema/mod.rs b/src/threema/mod.rs index a6785b5..6f42167 100644 --- a/src/threema/mod.rs +++ b/src/threema/mod.rs @@ -1,17 +1,16 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; -use threema_gateway::{ApiBuilder, E2eApi, IncomingMessage, PublicKey}; +use threema_gateway::{ApiBuilder, decrypt_blob, E2eApi, IncomingMessage, PublicKey}; use tokio::sync::Mutex; use crate::errors::{ProcessIncomingMessageError, SendGroupMessageError}; use log::{debug, info}; +use matrix_sdk::ruma::exports::serde_json; use threema_gateway::errors::{ApiBuilderError, ApiError}; use crate::threema::serialization::encrypt_group_sync_req_msg; -use crate::threema::types::{ - GroupCreateMessage, GroupRenameMessage, GroupTextMessage, MessageBase, MessageType, TextMessage, -}; +use crate::threema::types::{FileMessage, GroupCreateMessage, GroupFileMessage, GroupRenameMessage, GroupTextMessage, MessageBase, MessageType, TextMessage}; use crate::util::retry_request; use self::serialization::encrypt_group_text_msg; @@ -30,6 +29,8 @@ pub struct ThreemaClient { pub const GROUP_ID_NUM_BYTES: usize = 8; pub const GROUP_CREATOR_NUM_BYTES: usize = 8; pub const MESSAGE_TYPE_NUM_BYTES: usize = 1; +pub const BLOB_ID_LEN: usize = 16; +pub const BLOB_KEY_LEN: usize = 32; pub const THREEMA_ID_LENGTH: usize = 8; impl ThreemaClient { @@ -84,7 +85,7 @@ impl ThreemaClient { 20 * 1000, 6, ) - .await?; + .await?; debug!("Threema: Message sent successfully"); } return Ok(()); @@ -112,7 +113,7 @@ impl ThreemaClient { 20 * 1000, 6, ) - .await?; + .await?; debug!("Threema: Group sync message sent successfully"); return Ok(()); } @@ -121,11 +122,11 @@ impl ThreemaClient { &self, incoming_message: &IncomingMessage, ) -> Result { - let data; + let pubkey; { let api = self.api.lock().await; - let pubkey = self + pubkey = self .lookup_pubkey_with_retry(&incoming_message.from, &api) .await .map_err(|e| ProcessIncomingMessageError::ApiError(e))?; @@ -157,14 +158,14 @@ impl ThreemaClient { data[MESSAGE_TYPE_NUM_BYTES..MESSAGE_TYPE_NUM_BYTES + GROUP_CREATOR_NUM_BYTES] .to_vec(), ) - .map_err(|e| ProcessIncomingMessageError::Utf8ConvertError(e))?; + .map_err(|e| ProcessIncomingMessageError::Utf8ConvertError(e))?; let group_id = &data[MESSAGE_TYPE_NUM_BYTES + GROUP_CREATOR_NUM_BYTES ..MESSAGE_TYPE_NUM_BYTES + GROUP_CREATOR_NUM_BYTES + GROUP_ID_NUM_BYTES]; let text = String::from_utf8( data[MESSAGE_TYPE_NUM_BYTES + GROUP_CREATOR_NUM_BYTES + GROUP_ID_NUM_BYTES..] .to_vec(), ) - .map_err(|e| ProcessIncomingMessageError::Utf8ConvertError(e))?; + .map_err(|e| ProcessIncomingMessageError::Utf8ConvertError(e))?; // Show result debug!( @@ -189,6 +190,49 @@ impl ThreemaClient { group_id: group_id.to_vec(), })); } + MessageType::GroupFile => { + let mut i = MESSAGE_TYPE_NUM_BYTES; + let group_creator = String::from_utf8(data[i..i + GROUP_CREATOR_NUM_BYTES].to_vec()).unwrap(); + + i = i + GROUP_CREATOR_NUM_BYTES; + let group_id = &data[i..i + GROUP_ID_NUM_BYTES]; + + i = i + GROUP_ID_NUM_BYTES; + let file_data_json = String::from_utf8(data[i..].to_vec()).unwrap(); + let file_metadata = serde_json::from_str::(file_data_json.as_str()).unwrap(); + + + // Show result + debug!(" GroupCreator: {}", group_creator); + debug!(" groupId: {:?}", group_id); + debug!(" fileData: {:?}", file_metadata); + + + let file; + { + let api = self.api.lock().await; + let file_encrypted = api.blob_download(file_metadata.file_blob_id.as_str()).await.unwrap(); + let key = hex::decode(file_metadata.blob_encryption_key.as_str()).unwrap(); + file = decrypt_blob(&file_encrypted, key.try_into().unwrap()).unwrap(); + } + + { + let groups = self.groups.lock().await; + if let None = groups.get(group_id) { + debug!("Threema: Unknown group, sending sync req"); + self.send_group_sync_req_msg(group_id, group_creator.as_str()) + .await + .map_err(|e| ProcessIncomingMessageError::ApiError(e))?; + } + } + return Ok(Message::GroupFileMessage(GroupFileMessage { + base, + file_metadata, + group_creator, + group_id: group_id.to_vec(), + file, + })); + } MessageType::GroupCreate => { let group_id = &data[MESSAGE_TYPE_NUM_BYTES..MESSAGE_TYPE_NUM_BYTES + GROUP_CREATOR_NUM_BYTES]; @@ -199,8 +243,8 @@ impl ThreemaClient { for char in &data[MESSAGE_TYPE_NUM_BYTES + GROUP_CREATOR_NUM_BYTES..] { current_member_id = current_member_id + String::from_utf8(vec![*char]) - .map_err(|e| ProcessIncomingMessageError::Utf8ConvertError(e))? - .as_str(); + .map_err(|e| ProcessIncomingMessageError::Utf8ConvertError(e))? + .as_str(); counter = counter + 1; if counter == THREEMA_ID_LENGTH { members.insert(current_member_id.clone()); @@ -262,7 +306,7 @@ impl ThreemaClient { let group_name = String::from_utf8( data[MESSAGE_TYPE_NUM_BYTES + GROUP_CREATOR_NUM_BYTES..].to_vec(), ) - .map_err(|e| ProcessIncomingMessageError::Utf8ConvertError(e))?; + .map_err(|e| ProcessIncomingMessageError::Utf8ConvertError(e))?; { let mut groups = self.groups.lock().await; @@ -294,4 +338,4 @@ impl ThreemaClient { } } } -} +} \ No newline at end of file diff --git a/src/threema/types.rs b/src/threema/types.rs index 7a73ae7..8d7adff 100644 --- a/src/threema/types.rs +++ b/src/threema/types.rs @@ -1,3 +1,5 @@ +use serde::{Serialize, Deserialize}; + // Custom internal types #[derive(Debug)] pub struct MessageGroup { @@ -10,6 +12,7 @@ pub struct MessageGroup { pub enum Message { GroupTextMessage(GroupTextMessage), TextMessage(TextMessage), + GroupFileMessage(GroupFileMessage), GroupCreateMessage(GroupCreateMessage), GroupRenameMessage(GroupRenameMessage), } @@ -39,6 +42,14 @@ pub struct GroupTextMessage { pub group_id: Vec, } +pub struct GroupFileMessage { + pub base: MessageBase, + pub file_metadata: FileMessage, + pub group_creator: String, + pub group_id: Vec, + pub file: Vec, +} + #[derive(Clone)] pub struct MessageBase { pub from_identity: String, @@ -48,9 +59,69 @@ pub struct MessageBase { pub date: u64, } +#[derive(Debug, Serialize, Deserialize)] +pub struct FileMessage { + #[serde(rename = "b")] + pub file_blob_id: String, + #[serde(rename = "m")] + // #[serde(serialize_with = "serialize_to_string")] + pub file_media_type: String, + + #[serde(rename = "t")] + #[serde(skip_serializing_if = "Option::is_none")] + pub thumbnail_blob_id: Option, + #[serde(rename = "p")] + #[serde(skip_serializing_if = "Option::is_none")] + // #[serde(serialize_with = "serialize_opt_to_string")] + pub thumbnail_media_type: Option, + + #[serde(rename = "k")] + // #[serde(serialize_with = "key_to_hex")] + pub blob_encryption_key: String, + + #[serde(rename = "n")] + #[serde(skip_serializing_if = "Option::is_none")] + pub file_name: Option, + #[serde(rename = "s")] + pub file_size_bytes: u32, + #[serde(rename = "d")] + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + #[serde(rename = "j")] + pub rendering_type: u8, + #[serde(rename = "i")] + pub reserved: u8, + + // #[serde(rename = "x")] + // #[serde(skip_serializing_if = "Option::is_none")] + // metadata: Option, +} + +// /// Metadata for a file message (depending on media type). +// /// +// /// This data is intended to enhance the layout logic. +// #[derive(Debug, Serialize, Default)] +// struct FileMetadata { +// #[serde(rename = "a")] +// #[serde(skip_serializing_if = "Option::is_none")] +// animated: Option, +// #[serde(rename = "h")] +// #[serde(skip_serializing_if = "Option::is_none")] +// height: Option, +// #[serde(rename = "w")] +// #[serde(skip_serializing_if = "Option::is_none")] +// width: Option, +// #[serde(rename = "d")] +// #[serde(skip_serializing_if = "Option::is_none")] +// duration_seconds: Option, +// } + + pub enum MessageType { Text, GroupText, + GroupFile, GroupCreate, GroupRename, GroupRequestSync, @@ -65,6 +136,7 @@ impl From for MessageType { match value { 0x01 => MessageType::Text, 0x41 => MessageType::GroupText, + 0x46 => MessageType::GroupFile, 0x4a => MessageType::GroupCreate, 0x4b => MessageType::GroupRename, 0x51 => MessageType::GroupRequestSync, @@ -84,6 +156,7 @@ impl Into for MessageType { match self { MessageType::Text => 0x01, MessageType::GroupText => 0x41, + MessageType::GroupFile => 0x46, MessageType::GroupCreate => 0x4a, MessageType::GroupRename => 0x4b, MessageType::GroupRequestSync => 0x51, @@ -93,4 +166,4 @@ impl Into for MessageType { MessageType::DeliveryReceipt => 0x80, } } -} +} \ No newline at end of file From 10c6139aa9e373c2c481a990949b5f6a288643cf Mon Sep 17 00:00:00 2001 From: Fabian Schmidt Date: Tue, 30 Aug 2022 13:49:07 +0200 Subject: [PATCH 2/7] send name & description to matrix --- src/incoming_message_handler/threema.rs | 32 +++++++++++++++++-------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/incoming_message_handler/threema.rs b/src/incoming_message_handler/threema.rs index b1cb489..298fe80 100644 --- a/src/incoming_message_handler/threema.rs +++ b/src/incoming_message_handler/threema.rs @@ -1,20 +1,10 @@ -use std::io::Cursor; - use actix_web::{http::header::ContentType, web, HttpResponse, Responder}; use log::{debug, error, info, warn}; -use matrix_sdk::attachment::AttachmentConfig; use threema_gateway::IncomingMessage; use crate::{AppState, Message}; use crate::matrix::errors::{BindThreemaGroupToMatrixError, SendToMatrixRoomByThreemaGroupIdError}; use crate::matrix::MatrixClient; - -use crate::matrix::util::{ - get_threematrix_room_state, -}; -use crate::threema::util::{ - convert_group_id_to_readable_string, -}; use crate::threema::ThreemaClient; pub async fn threema_incoming_message_handler( @@ -151,6 +141,28 @@ You can find the required room id in your Matrix client. Attention: This is NOT match matrix_client.get_joined_room_by_threema_group_id(&group_file_msg.group_id).await { Ok(room) => { + let file_description = group_file_msg.file_metadata.description.unwrap_or("".to_string()); + if let Err(e) = matrix_client + .send_message_to_matrix_room( + &room, + sender_name.as_str(), + file_description.as_str(), + file_description.as_str(), + ).await + { + match e { + SendToMatrixRoomByThreemaGroupIdError::MatrixError(e) => { + let err_txt = format!("Could not send message to Matrix room: {}", e); + send_error_message_to_threema_group( + threema_client, + err_txt, + group_file_msg.group_id.as_slice(), + true, + ).await; + } + } + } + if let Err(e) = matrix_client .send_file_to_matrix_room( &room, From baef211b4d5aab11f8a9542178892a34b559d780 Mon Sep 17 00:00:00 2001 From: Fabian Schmidt Date: Tue, 30 Aug 2022 20:35:56 +0200 Subject: [PATCH 3/7] image from matrix to threema works --- src/incoming_message_handler/matrix.rs | 183 ++++++++++++++++--------- src/threema/mod.rs | 78 ++++++++++- src/threema/serialization.rs | 43 +++--- 3 files changed, 207 insertions(+), 97 deletions(-) diff --git a/src/incoming_message_handler/matrix.rs b/src/incoming_message_handler/matrix.rs index d80027d..30aa8de 100644 --- a/src/incoming_message_handler/matrix.rs +++ b/src/incoming_message_handler/matrix.rs @@ -1,17 +1,16 @@ use log::{debug, error, info, warn}; use matrix_sdk::{Client, ruma::events::room::member::StrippedRoomMemberEvent}; use matrix_sdk::event_handler::Ctx; +use matrix_sdk::media::MediaThumbnailSize; use matrix_sdk::room::{Joined, Room}; +use matrix_sdk::ruma::{TransactionId, UInt}; +use matrix_sdk::ruma::api::client::media::get_content_thumbnail::v3::Method; use matrix_sdk::ruma::events::OriginalSyncMessageLikeEvent; -use matrix_sdk::ruma::events::room::message::{ - MessageType, RoomMessageEventContent, TextMessageEventContent, -}; -use matrix_sdk::ruma::TransactionId; +use matrix_sdk::ruma::events::room::message::{ImageMessageEventContent, MessageType, RoomMessageEventContent, TextMessageEventContent}; +use threema_gateway::encrypt_file_data; use tokio::time::{Duration, sleep}; -use crate::matrix::util::{ - get_threematrix_room_state, -}; +use crate::matrix::util::get_threematrix_room_state; use crate::threema::ThreemaClient; use crate::threema::util::convert_group_id_from_readable_string; @@ -23,66 +22,121 @@ pub async fn matrix_incoming_message_handler( ) -> () { match room { Room::Joined(room) => { - if let OriginalSyncMessageLikeEvent { - content: - RoomMessageEventContent { - msgtype: MessageType::Text(TextMessageEventContent { body: msg_body, .. }), + match event { + OriginalSyncMessageLikeEvent { + content: + RoomMessageEventContent { + msgtype: MessageType::Text(TextMessageEventContent { body: msg_body, .. }), + .. + }, + sender, .. - }, - sender, - .. - } = event - { - debug!("Matrix: Incoming message: {}", msg_body); - - let sender_member = room.get_member(&sender).await; - match sender_member { - Ok(Some(sender_member)) => { - let sender_name = sender_member - .display_name() - .unwrap_or_else(|| sender_member.user_id().as_str()); - - // Filter out messages coming from our own bridge user - if sender != matrix_client.user_id().await.unwrap() { - match get_threematrix_room_state(&room).await { - Ok(None) => { - let err_txt = format!("Room {} does not have proper room state. Have you bound the room to a Threema group?", - &room.display_name().await.unwrap_or(matrix_sdk::DisplayName::Named("UNKNOWN".to_owned()))); - send_error_message_to_matrix_room(&room, err_txt, false).await; - } - Ok(Some(threematrix_state)) => { - let group_id = convert_group_id_from_readable_string( - threematrix_state.threematrix_threema_group_id.as_str(), - ); - - if let Ok(group_id) = group_id { - if let Err(e) = threema_client - .send_group_msg_by_group_id( - format!("*{}*: {}", sender_name, msg_body).as_str(), - group_id.as_slice(), - ) - .await - { - let err_txt = format!( - "Couldn't send message to Threema group: {}", - e + } => + { + debug!("Matrix: Incoming message: {}", msg_body); + + let sender_member = room.get_member(&sender).await; + match sender_member { + Ok(Some(sender_member)) => { + let sender_name = sender_member + .display_name() + .unwrap_or_else(|| sender_member.user_id().as_str()); + + // Filter out messages coming from our own bridge user + if sender != matrix_client.user_id().await.unwrap() { + match get_threematrix_room_state(&room).await { + Ok(None) => { + let err_txt = format!("Room {} does not have proper room state. Have you bound the room to a Threema group?", + &room.display_name().await.unwrap_or(matrix_sdk::DisplayName::Named("UNKNOWN".to_owned()))); + send_error_message_to_matrix_room(&room, err_txt, false).await; + } + Ok(Some(threematrix_state)) => { + let group_id = convert_group_id_from_readable_string( + threematrix_state.threematrix_threema_group_id.as_str(), ); - send_error_message_to_matrix_room(&room, err_txt, true) - .await; + + if let Ok(group_id) = group_id { + if let Err(e) = threema_client + .send_group_msg_by_group_id( + format!("*{}*: {}", sender_name, msg_body).as_str(), + group_id.as_slice(), + ) + .await + { + let err_txt = format!( + "Couldn't send message to Threema group: {}", + e + ); + send_error_message_to_matrix_room(&room, err_txt, true) + .await; + } + } + } + Err(e) => { + let err_txt = format!("Could not retrieve room state: {}", e); + send_error_message_to_matrix_room(&room, err_txt, true).await; } } } - Err(e) => { - let err_txt = format!("Could not retrieve room state: {}", e); - send_error_message_to_matrix_room(&room, err_txt, true).await; - } + } + _ => { + error!("Matrix: Could not resolve room member!"); } } } - _ => { - error!("Matrix: Could not resolve room member!"); + OriginalSyncMessageLikeEvent { + content: + RoomMessageEventContent { + msgtype: MessageType::Image(image), + .. + }, + sender, + .. + } => + { + let sender_member = room.get_member(&sender).await; + match sender_member { + Ok(Some(sender_member)) => { + let sender_name = sender_member + .display_name() + .unwrap_or_else(|| sender_member.user_id().as_str()); + + // Filter out messages coming from our own bridge user + if sender != matrix_client.user_id().await.unwrap() { + match get_threematrix_room_state(&room).await { + Ok(None) => { + let err_txt = format!("Room {} does not have proper room state. Have you bound the room to a Threema group?", + &room.display_name().await.unwrap_or(matrix_sdk::DisplayName::Named("UNKNOWN".to_owned()))); + send_error_message_to_matrix_room(&room, err_txt, false).await; + } + Ok(Some(threematrix_state)) => { + let group_id = convert_group_id_from_readable_string( + threematrix_state.threematrix_threema_group_id.as_str(), + ); + + if let Ok(group_id) = group_id { + let image_file = matrix_client.get_file(image.clone(), false).await.unwrap().unwrap(); + let thumbnail = matrix_client.get_thumbnail(image, MediaThumbnailSize { height: UInt::new(400).unwrap(), width: UInt::new(400).unwrap(), method: Method::Scale }, false).await.unwrap().unwrap(); + + debug!("Matrix image size: {} bytes", image_file.len()); + debug!("Matrix thumbnail size: {} bytes", thumbnail.len()); + + threema_client.send_group_file_by_group_id(&image_file, Some(&thumbnail), &group_id).await.unwrap(); + } + } + Err(e) => { + let err_txt = format!("Could not retrieve room state: {}", e); + send_error_message_to_matrix_room(&room, err_txt, true).await; + } + } + } + } + _ => { + error!("Matrix: Could not resolve room member!"); + } + } } - } + _ => {} } } _ => { @@ -91,6 +145,7 @@ pub async fn matrix_incoming_message_handler( } } + async fn send_error_message_to_matrix_room(room: &Joined, err_txt: String, log_level_err: bool) { if log_level_err { error!("Matrix: {}", err_txt); @@ -102,10 +157,7 @@ async fn send_error_message_to_matrix_room(room: &Joined, err_txt: String, log_l let txn_id = TransactionId::new(); if let Err(e) = room.send(content, Some(&txn_id)).await { - error!( - "Matrix: Could not send error message: \"{}\". {}", - err_txt, e - ) + error!("Matrix: Could not send error message: \"{}\". {}",err_txt, e) } } @@ -127,12 +179,7 @@ pub async fn on_stripped_state_member( // retry autojoin due to synapse sending invites, before the // invited user can join for more information see // https://github.com/matrix-org/synapse/issues/4345 - error!( - "Matrix: Failed to join room {} ({:?}), retrying in {}s", - room.room_id(), - err, - delay - ); + error!("Matrix: Failed to join room {} ({:?}), retrying in {}s",room.room_id(), err, delay); sleep(Duration::from_secs(delay)).await; delay *= 2; diff --git a/src/threema/mod.rs b/src/threema/mod.rs index 6f42167..ee3a05a 100644 --- a/src/threema/mod.rs +++ b/src/threema/mod.rs @@ -1,15 +1,14 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; -use threema_gateway::{ApiBuilder, decrypt_blob, E2eApi, IncomingMessage, PublicKey}; -use tokio::sync::Mutex; - -use crate::errors::{ProcessIncomingMessageError, SendGroupMessageError}; use log::{debug, info}; use matrix_sdk::ruma::exports::serde_json; +use threema_gateway::{ApiBuilder, decrypt_file_data, E2eApi, encrypt_file_data, IncomingMessage, PublicKey, RenderingType}; use threema_gateway::errors::{ApiBuilderError, ApiError}; +use tokio::sync::Mutex; -use crate::threema::serialization::encrypt_group_sync_req_msg; +use crate::errors::{ProcessIncomingMessageError, SendGroupMessageError}; +use crate::threema::serialization::{encrypt_group_file_msg, encrypt_group_sync_req_msg}; use crate::threema::types::{FileMessage, GroupCreateMessage, GroupFileMessage, GroupRenameMessage, GroupTextMessage, MessageBase, MessageType, TextMessage}; use crate::util::retry_request; @@ -65,6 +64,25 @@ impl ThreemaClient { } } + + pub async fn send_group_file_by_group_id( + &self, + file: &[u8], + thumbnail: Option<&[u8]>, + group_id: &[u8], + ) -> Result<(), SendGroupMessageError> { + let groups = self.groups.lock().await; + if let Some(group) = groups.get(group_id) { + let receiver: Vec<&str> = group.members.iter().map(|str| str.as_str()).collect(); + return self + .send_group_file(file, thumbnail, &group.group_creator, group_id, receiver.as_slice()) + .await + .map_err(|e| SendGroupMessageError::ApiError(e)); + } else { + return Err(SendGroupMessageError::GroupNotInCache); + } + } + pub async fn send_group_msg( &self, text: &str, @@ -91,6 +109,54 @@ impl ThreemaClient { return Ok(()); } + pub async fn send_group_file( + &self, + file: &[u8], + thumbnail: Option<&[u8]>, + group_creator: &str, + group_id: &[u8], + receivers: &[&str], + ) -> Result<(), ApiError> { + let (encrypted_file, encrypted_thumb, key) = encrypt_file_data(file, thumbnail); + + + let api = self.api.lock().await; + // Upload files to blob server + let file_blob_id = api.blob_upload_raw(&encrypted_file, false).await.unwrap(); + let thumb_blob_id = if let Some(et) = encrypted_thumb { + let blob_id = api.blob_upload_raw(&et, false).await.unwrap(); + let thumbnail_media_type = mime::IMAGE_JPEG; + Some((blob_id, thumbnail_media_type)) + } else { + None + }; + let file_message = threema_gateway::FileMessage::builder(file_blob_id, key, mime::IMAGE_JPEG, file.len() as u32) + .thumbnail_opt(thumb_blob_id) + .file_name_opt(Some("test.jpg")) + .description_opt(None as Option) + .rendering_type(RenderingType::Media) + .build() + .expect("Could not build FileMessage"); + + + for user_id in receivers { + debug!("Threema: Sending message to: {}", user_id); + let public_key = self.lookup_pubkey_with_retry(user_id, &api).await?; //TODO cache + + let encrypted_msg = + encrypt_group_file_msg(&file_message, group_creator, group_id, &public_key.into(), &api); + + retry_request( + || async { api.send(&user_id, &encrypted_msg, false).await }, + 20 * 1000, + 6, + ) + .await?; + debug!("Threema: Message sent successfully"); + } + return Ok(()); + } + async fn lookup_pubkey_with_retry( &self, user_id: &str, @@ -213,7 +279,7 @@ impl ThreemaClient { let api = self.api.lock().await; let file_encrypted = api.blob_download(file_metadata.file_blob_id.as_str()).await.unwrap(); let key = hex::decode(file_metadata.blob_encryption_key.as_str()).unwrap(); - file = decrypt_blob(&file_encrypted, key.try_into().unwrap()).unwrap(); + file = decrypt_file_data(&file_encrypted, key.try_into().unwrap()).unwrap(); } { diff --git a/src/threema/serialization.rs b/src/threema/serialization.rs index d268c2c..52f42e7 100644 --- a/src/threema/serialization.rs +++ b/src/threema/serialization.rs @@ -1,7 +1,7 @@ use std::iter::repeat; use rand::Rng; -use threema_gateway::{E2eApi, EncryptedMessage, RecipientKey}; +use threema_gateway::{E2eApi, EncryptedMessage, FileMessage, RecipientKey}; use crate::threema::types::MessageType; @@ -10,16 +10,7 @@ pub fn encrypt_group_sync_req_msg( recipient_key: &RecipientKey, threema_api: &E2eApi, ) -> EncryptedMessage { - let padding_amount = random_padding_amount(); - let padding = repeat(padding_amount).take(padding_amount as usize); - let msgtype_byte = repeat(MessageType::GroupRequestSync.into()).take(1); - - let padded_plaintext: Vec = msgtype_byte - .chain(group_id.iter().cloned()) - .chain(padding) - .collect(); - - threema_api.encrypt_raw(&padded_plaintext, &recipient_key) + return threema_api.encrypt(group_id, threema_gateway::MessageType::Other(MessageType::GroupRequestSync.into()), &recipient_key); } pub fn encrypt_group_text_msg( @@ -29,10 +20,6 @@ pub fn encrypt_group_text_msg( recipient_key: &RecipientKey, threema_api: &E2eApi, ) -> EncryptedMessage { - let padding_amount = random_padding_amount(); - let padding = repeat(padding_amount).take(padding_amount as usize); - let msgtype_byte = repeat(MessageType::GroupText.into()).take(1); - let data: Vec = group_creator .as_bytes() .iter() @@ -40,15 +27,25 @@ pub fn encrypt_group_text_msg( .chain(group_id.iter().cloned()) .chain(text.as_bytes().iter().cloned()) .collect(); - let padded_plaintext: Vec = msgtype_byte - .chain(data.iter().cloned()) - .chain(padding) - .collect(); - threema_api.encrypt_raw(&padded_plaintext, &recipient_key) + return threema_api.encrypt(data.as_slice(), threema_gateway::MessageType::Other(MessageType::GroupText.into()), &recipient_key); } -fn random_padding_amount() -> u8 { - let mut rng = rand::thread_rng(); - return rng.gen_range(1..255); +pub fn encrypt_group_file_msg( + msg: &FileMessage, + group_creator: &str, + group_id: &[u8], + recipient_key: &RecipientKey, + threema_api: &E2eApi, +) -> EncryptedMessage { + let file_msg_json = serde_json::to_string(msg).unwrap(); + let data: Vec = group_creator + .as_bytes() + .iter() + .cloned() + .chain(group_id.iter().cloned()) + .chain(file_msg_json.as_bytes().iter().cloned()) + .collect(); + + return threema_api.encrypt(data.as_slice(), threema_gateway::MessageType::Other(MessageType::GroupFile.into()), &recipient_key); } From fc26086a3fbdca81baf634f27372ceb2a9da2661 Mon Sep 17 00:00:00 2001 From: Fabian Schmidt Date: Tue, 30 Aug 2022 21:28:01 +0200 Subject: [PATCH 4/7] add description, mime, file_name to threema send file --- Cargo.lock | 3 --- Cargo.toml | 11 ++++------- src/incoming_message_handler/matrix.rs | 18 ++++++++++++++---- src/threema/mod.rs | 18 ++++++++++++------ src/threema/serialization.rs | 3 --- 5 files changed, 30 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 232d63a..98d5896 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2573,8 +2573,6 @@ version = "0.1.0" dependencies = [ "actix-web", "async-trait", - "bytes", - "data-encoding", "flexi_logger", "futures", "hex", @@ -2582,7 +2580,6 @@ dependencies = [ "matrix-sdk", "mime", "rand 0.8.5", - "reqwest", "serde", "serde_derive", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 39ad275..38a230c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "threematrix" -version = "0.1.0" +version = "0.2.0" edition = "2021" [dependencies] -#threema-gateway = { git = "https://github.com/bitbetterde/threema-gateway-rs.git" } -threema-gateway = { path = "../threema-gateway-rs" } +threema-gateway = { git = "https://github.com/bitbetterde/threema-gateway-rs.git" } +#threema-gateway = { path = "../threema-gateway-rs" } tokio = { version = "1", features = ["full"] } actix-web = "4" rand = "0.8.5" @@ -20,9 +20,6 @@ log = "0.4.17" flexi_logger = "0.22.5" thiserror="1.0.31" serde_json = "1.0.83" -data-encoding = "2.3.2" -reqwest = "0.11.11" -bytes = "1.2.1" -hex = "0.4.3" +hex="0.4.3" mime = "0.3.16" async-trait = "0.1.57" diff --git a/src/incoming_message_handler/matrix.rs b/src/incoming_message_handler/matrix.rs index 30aa8de..55d598f 100644 --- a/src/incoming_message_handler/matrix.rs +++ b/src/incoming_message_handler/matrix.rs @@ -1,3 +1,4 @@ +use std::str::FromStr; use log::{debug, error, info, warn}; use matrix_sdk::{Client, ruma::events::room::member::StrippedRoomMemberEvent}; use matrix_sdk::event_handler::Ctx; @@ -6,8 +7,7 @@ use matrix_sdk::room::{Joined, Room}; use matrix_sdk::ruma::{TransactionId, UInt}; use matrix_sdk::ruma::api::client::media::get_content_thumbnail::v3::Method; use matrix_sdk::ruma::events::OriginalSyncMessageLikeEvent; -use matrix_sdk::ruma::events::room::message::{ImageMessageEventContent, MessageType, RoomMessageEventContent, TextMessageEventContent}; -use threema_gateway::encrypt_file_data; +use matrix_sdk::ruma::events::room::message::{MessageType, RoomMessageEventContent, TextMessageEventContent}; use tokio::time::{Duration, sleep}; use crate::matrix::util::get_threematrix_room_state; @@ -116,12 +116,22 @@ pub async fn matrix_incoming_message_handler( if let Ok(group_id) = group_id { let image_file = matrix_client.get_file(image.clone(), false).await.unwrap().unwrap(); - let thumbnail = matrix_client.get_thumbnail(image, MediaThumbnailSize { height: UInt::new(400).unwrap(), width: UInt::new(400).unwrap(), method: Method::Scale }, false).await.unwrap().unwrap(); + let thumbnail = matrix_client.get_thumbnail(image.clone(), MediaThumbnailSize { height: UInt::new(400).unwrap(), width: UInt::new(400).unwrap(), method: Method::Scale }, false).await.unwrap().unwrap(); + let mime = mime::Mime::from_str(image.info.unwrap().mimetype.unwrap().as_ref()).unwrap(); + + let description = format!("*{}*: {}", sender_name, &image.body); debug!("Matrix image size: {} bytes", image_file.len()); debug!("Matrix thumbnail size: {} bytes", thumbnail.len()); - threema_client.send_group_file_by_group_id(&image_file, Some(&thumbnail), &group_id).await.unwrap(); + threema_client.send_group_file_by_group_id( + &image_file, + Some(&thumbnail), + Some(description.as_str()), + image.body.as_str(), + mime, + &group_id + ).await.unwrap(); } } Err(e) => { diff --git a/src/threema/mod.rs b/src/threema/mod.rs index ee3a05a..6eaf2f9 100644 --- a/src/threema/mod.rs +++ b/src/threema/mod.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use log::{debug, info}; use matrix_sdk::ruma::exports::serde_json; +use mime::Mime; use threema_gateway::{ApiBuilder, decrypt_file_data, E2eApi, encrypt_file_data, IncomingMessage, PublicKey, RenderingType}; use threema_gateway::errors::{ApiBuilderError, ApiError}; use tokio::sync::Mutex; @@ -69,13 +70,16 @@ impl ThreemaClient { &self, file: &[u8], thumbnail: Option<&[u8]>, + description: Option<&str>, + file_name: &str, + mime: Mime, group_id: &[u8], ) -> Result<(), SendGroupMessageError> { let groups = self.groups.lock().await; if let Some(group) = groups.get(group_id) { let receiver: Vec<&str> = group.members.iter().map(|str| str.as_str()).collect(); return self - .send_group_file(file, thumbnail, &group.group_creator, group_id, receiver.as_slice()) + .send_group_file(file, thumbnail, description, file_name, mime, &group.group_creator, group_id, receiver.as_slice()) .await .map_err(|e| SendGroupMessageError::ApiError(e)); } else { @@ -113,6 +117,9 @@ impl ThreemaClient { &self, file: &[u8], thumbnail: Option<&[u8]>, + description: Option<&str>, + file_name: &str, + mime: Mime, group_creator: &str, group_id: &[u8], receivers: &[&str], @@ -125,15 +132,14 @@ impl ThreemaClient { let file_blob_id = api.blob_upload_raw(&encrypted_file, false).await.unwrap(); let thumb_blob_id = if let Some(et) = encrypted_thumb { let blob_id = api.blob_upload_raw(&et, false).await.unwrap(); - let thumbnail_media_type = mime::IMAGE_JPEG; - Some((blob_id, thumbnail_media_type)) + Some((blob_id, mime.clone())) } else { None }; - let file_message = threema_gateway::FileMessage::builder(file_blob_id, key, mime::IMAGE_JPEG, file.len() as u32) + let file_message = threema_gateway::FileMessage::builder(file_blob_id, key, mime, file.len() as u32) .thumbnail_opt(thumb_blob_id) - .file_name_opt(Some("test.jpg")) - .description_opt(None as Option) + .file_name_opt(Some(file_name)) + .description_opt(description) .rendering_type(RenderingType::Media) .build() .expect("Could not build FileMessage"); diff --git a/src/threema/serialization.rs b/src/threema/serialization.rs index 52f42e7..f551af5 100644 --- a/src/threema/serialization.rs +++ b/src/threema/serialization.rs @@ -1,6 +1,3 @@ -use std::iter::repeat; - -use rand::Rng; use threema_gateway::{E2eApi, EncryptedMessage, FileMessage, RecipientKey}; use crate::threema::types::MessageType; From 10c791d3490a50d9a85f587b902ce5e00b5694c3 Mon Sep 17 00:00:00 2001 From: Fabian Schmidt Date: Tue, 30 Aug 2022 21:44:38 +0200 Subject: [PATCH 5/7] fix lock file --- Cargo.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 98d5896..c9c9d92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2553,6 +2553,7 @@ dependencies = [ [[package]] name = "threema-gateway" version = "0.15.1" +source = "git+https://github.com/bitbetterde/threema-gateway-rs.git#410c26030c8b4cd03418f9e769011000203a5079" dependencies = [ "byteorder", "data-encoding", @@ -2569,7 +2570,7 @@ dependencies = [ [[package]] name = "threematrix" -version = "0.1.0" +version = "0.2.0" dependencies = [ "actix-web", "async-trait", From 4acf816ee5e14fb0b5d2264084eeaea9ff4dd9a2 Mon Sep 17 00:00:00 2001 From: Fabian Schmidt Date: Tue, 30 Aug 2022 22:05:06 +0200 Subject: [PATCH 6/7] send filename as matrix body --- src/incoming_message_handler/threema.rs | 2 +- src/matrix/matrix_client_impl.rs | 4 ++-- src/matrix/mod.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/incoming_message_handler/threema.rs b/src/incoming_message_handler/threema.rs index 298fe80..b4ee11f 100644 --- a/src/incoming_message_handler/threema.rs +++ b/src/incoming_message_handler/threema.rs @@ -166,7 +166,7 @@ You can find the required room id in your Matrix client. Attention: This is NOT if let Err(e) = matrix_client .send_file_to_matrix_room( &room, - sender_name.as_str(), + group_file_msg.file_metadata.file_name.unwrap_or("".to_string()).as_ref(), group_file_msg.file.as_slice(), ).await { diff --git a/src/matrix/matrix_client_impl.rs b/src/matrix/matrix_client_impl.rs index c3ef3c1..6dd6fb7 100644 --- a/src/matrix/matrix_client_impl.rs +++ b/src/matrix/matrix_client_impl.rs @@ -49,9 +49,9 @@ impl MatrixClient for Client { return Ok(()); } - async fn send_file_to_matrix_room(&self, room: &Joined, sender_name: &str, file: &[u8]) -> Result<(), SendToMatrixRoomByThreemaGroupIdError> { + async fn send_file_to_matrix_room(&self, room: &Joined, body: &str, file: &[u8]) -> Result<(), SendToMatrixRoomByThreemaGroupIdError> { let mut cursor = Cursor::new(file); - room.send_attachment(sender_name, &mime::IMAGE_JPEG, &mut cursor, AttachmentConfig::new()).await + room.send_attachment(body, &mime::IMAGE_JPEG, &mut cursor, AttachmentConfig::new()).await .map_err(|e| { SendToMatrixRoomByThreemaGroupIdError::MatrixError(e) })?; return Ok(()); diff --git a/src/matrix/mod.rs b/src/matrix/mod.rs index 1c16083..db2f74d 100644 --- a/src/matrix/mod.rs +++ b/src/matrix/mod.rs @@ -23,7 +23,7 @@ pub trait MatrixClient { async fn send_file_to_matrix_room( &self, room: &Joined, - user_name: &str, + body: &str, file: &[u8], ) -> Result<(), SendToMatrixRoomByThreemaGroupIdError>; async fn bind_threema_group_to_matrix_room( From 58e6a15d4cc5ee48841676f36c54040e29792563 Mon Sep 17 00:00:00 2001 From: Fabian Schmidt Date: Thu, 1 Sep 2022 19:18:49 +0200 Subject: [PATCH 7/7] remove own FileMessage type after refactor --- Cargo.lock | 1 - Cargo.toml | 4 +- src/incoming_message_handler/threema.rs | 9 ++-- src/threema/mod.rs | 14 +++--- src/threema/types.rs | 60 +------------------------ 5 files changed, 14 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c9c9d92..ee77c4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2553,7 +2553,6 @@ dependencies = [ [[package]] name = "threema-gateway" version = "0.15.1" -source = "git+https://github.com/bitbetterde/threema-gateway-rs.git#410c26030c8b4cd03418f9e769011000203a5079" dependencies = [ "byteorder", "data-encoding", diff --git a/Cargo.toml b/Cargo.toml index 38a230c..b63a403 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ version = "0.2.0" edition = "2021" [dependencies] -threema-gateway = { git = "https://github.com/bitbetterde/threema-gateway-rs.git" } -#threema-gateway = { path = "../threema-gateway-rs" } +#threema-gateway = { git = "https://github.com/bitbetterde/threema-gateway-rs.git" } +threema-gateway = { path = "../threema-gateway-rs" } tokio = { version = "1", features = ["full"] } actix-web = "4" rand = "0.8.5" diff --git a/src/incoming_message_handler/threema.rs b/src/incoming_message_handler/threema.rs index b4ee11f..ff3266b 100644 --- a/src/incoming_message_handler/threema.rs +++ b/src/incoming_message_handler/threema.rs @@ -141,13 +141,13 @@ You can find the required room id in your Matrix client. Attention: This is NOT match matrix_client.get_joined_room_by_threema_group_id(&group_file_msg.group_id).await { Ok(room) => { - let file_description = group_file_msg.file_metadata.description.unwrap_or("".to_string()); + let file_description = group_file_msg.file_metadata.description().as_deref().unwrap_or(""); if let Err(e) = matrix_client .send_message_to_matrix_room( &room, sender_name.as_str(), - file_description.as_str(), - file_description.as_str(), + file_description, + file_description, ).await { match e { @@ -162,11 +162,10 @@ You can find the required room id in your Matrix client. Attention: This is NOT } } } - if let Err(e) = matrix_client .send_file_to_matrix_room( &room, - group_file_msg.file_metadata.file_name.unwrap_or("".to_string()).as_ref(), + group_file_msg.file_metadata.file_name().as_deref().unwrap_or(""), group_file_msg.file.as_slice(), ).await { diff --git a/src/threema/mod.rs b/src/threema/mod.rs index 6eaf2f9..4667c7e 100644 --- a/src/threema/mod.rs +++ b/src/threema/mod.rs @@ -4,13 +4,14 @@ use std::sync::Arc; use log::{debug, info}; use matrix_sdk::ruma::exports::serde_json; use mime::Mime; -use threema_gateway::{ApiBuilder, decrypt_file_data, E2eApi, encrypt_file_data, IncomingMessage, PublicKey, RenderingType}; +use threema_gateway::{ApiBuilder, decrypt_file_data, E2eApi, encrypt_file_data, FileMessage, IncomingMessage, PublicKey, RenderingType}; use threema_gateway::errors::{ApiBuilderError, ApiError}; use tokio::sync::Mutex; use crate::errors::{ProcessIncomingMessageError, SendGroupMessageError}; use crate::threema::serialization::{encrypt_group_file_msg, encrypt_group_sync_req_msg}; -use crate::threema::types::{FileMessage, GroupCreateMessage, GroupFileMessage, GroupRenameMessage, GroupTextMessage, MessageBase, MessageType, TextMessage}; +use crate::threema::types::{GroupCreateMessage, GroupFileMessage, GroupRenameMessage, GroupTextMessage, MessageBase, MessageType, TextMessage}; + use crate::util::retry_request; use self::serialization::encrypt_group_text_msg; @@ -136,7 +137,7 @@ impl ThreemaClient { } else { None }; - let file_message = threema_gateway::FileMessage::builder(file_blob_id, key, mime, file.len() as u32) + let file_message = FileMessage::builder(file_blob_id, key, mime, file.len() as u32) .thumbnail_opt(thumb_blob_id) .file_name_opt(Some(file_name)) .description_opt(description) @@ -273,7 +274,6 @@ impl ThreemaClient { let file_data_json = String::from_utf8(data[i..].to_vec()).unwrap(); let file_metadata = serde_json::from_str::(file_data_json.as_str()).unwrap(); - // Show result debug!(" GroupCreator: {}", group_creator); debug!(" groupId: {:?}", group_id); @@ -283,9 +283,9 @@ impl ThreemaClient { let file; { let api = self.api.lock().await; - let file_encrypted = api.blob_download(file_metadata.file_blob_id.as_str()).await.unwrap(); - let key = hex::decode(file_metadata.blob_encryption_key.as_str()).unwrap(); - file = decrypt_file_data(&file_encrypted, key.try_into().unwrap()).unwrap(); + let file_encrypted = api.blob_download(file_metadata.file_blob_id()).await.unwrap(); + // let key = hex::decode(file_metadata.blob_encryption_key()).unwrap(); + file = decrypt_file_data(&file_encrypted, file_metadata.blob_encryption_key()).unwrap(); } { diff --git a/src/threema/types.rs b/src/threema/types.rs index 8d7adff..632b504 100644 --- a/src/threema/types.rs +++ b/src/threema/types.rs @@ -1,4 +1,4 @@ -use serde::{Serialize, Deserialize}; +use threema_gateway::FileMessage; // Custom internal types #[derive(Debug)] @@ -59,64 +59,6 @@ pub struct MessageBase { pub date: u64, } -#[derive(Debug, Serialize, Deserialize)] -pub struct FileMessage { - #[serde(rename = "b")] - pub file_blob_id: String, - #[serde(rename = "m")] - // #[serde(serialize_with = "serialize_to_string")] - pub file_media_type: String, - - #[serde(rename = "t")] - #[serde(skip_serializing_if = "Option::is_none")] - pub thumbnail_blob_id: Option, - #[serde(rename = "p")] - #[serde(skip_serializing_if = "Option::is_none")] - // #[serde(serialize_with = "serialize_opt_to_string")] - pub thumbnail_media_type: Option, - - #[serde(rename = "k")] - // #[serde(serialize_with = "key_to_hex")] - pub blob_encryption_key: String, - - #[serde(rename = "n")] - #[serde(skip_serializing_if = "Option::is_none")] - pub file_name: Option, - #[serde(rename = "s")] - pub file_size_bytes: u32, - #[serde(rename = "d")] - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - - #[serde(rename = "j")] - pub rendering_type: u8, - #[serde(rename = "i")] - pub reserved: u8, - - // #[serde(rename = "x")] - // #[serde(skip_serializing_if = "Option::is_none")] - // metadata: Option, -} - -// /// Metadata for a file message (depending on media type). -// /// -// /// This data is intended to enhance the layout logic. -// #[derive(Debug, Serialize, Default)] -// struct FileMetadata { -// #[serde(rename = "a")] -// #[serde(skip_serializing_if = "Option::is_none")] -// animated: Option, -// #[serde(rename = "h")] -// #[serde(skip_serializing_if = "Option::is_none")] -// height: Option, -// #[serde(rename = "w")] -// #[serde(skip_serializing_if = "Option::is_none")] -// width: Option, -// #[serde(rename = "d")] -// #[serde(skip_serializing_if = "Option::is_none")] -// duration_seconds: Option, -// } - pub enum MessageType { Text,