daydream

A small matrix web client written in rust
git clone git://archive.git.mtrnord.blog/daydream-mx/daydream.git
Log | Files | Refs | README | LICENSE

commit 76fc33e66a85a7bc7b30fab5675842d246b679f1
parent b8d005b9bb1f55bf0c9e0e72c5a179e94a214462
Author: Marcel <mtrnord1@gmail.com>
Date:   Fri, 29 May 2020 18:36:42 +0200

Add parsing of image and notice events

Took 1 hour 7 minutes

Diffstat:
Msrc/app/components/event_list.rs | 35++++++++++++++++++++++++++++++++---
Msrc/app/components/room_list.rs | 4++--
Msrc/app/matrix.rs | 69++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/app/matrix/sync.rs | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/app/matrix/types.rs | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/errors.rs | 2++
6 files changed, 211 insertions(+), 59 deletions(-)

diff --git a/src/app/components/event_list.rs b/src/app/components/event_list.rs @@ -3,7 +3,6 @@ use log::*; use matrix_sdk::identifiers::{EventId, RoomId}; use serde::{Deserialize, Serialize}; use yew::prelude::*; -use yewtil::NeqAssign; use crate::app::matrix::types::MessageWrapper; use crate::app::matrix::{MatrixAgent, Request, Response}; @@ -189,9 +188,39 @@ impl Component for EventList { } impl EventList { + // Typeinspection of IDEA breaks with this :D + //noinspection RsTypeCheck fn get_event(&self, event: MessageWrapper) -> Html { - html! { - <p>{event.sender_displayname.unwrap_or(event.sender.to_string()).clone()}{": "}{event.content.clone()}</p> + if event.event_type == "m.text" { + html! { + <p>{event.sender_displayname.unwrap_or(event.sender.to_string()).clone()}{": "}{event.content.clone()}</p> + } + } else if event.event_type == "m.notice" { + html! { + <p style="opacity: .6;">{event.sender_displayname.unwrap_or(event.sender.to_string()).clone()}{": "}{event.content.clone()}</p> + } + } else if event.event_type == "m.image" { + let caption = format!( + "{}: {}", + event + .sender_displayname + .unwrap_or(event.sender.to_string()) + .clone(), + event.content.clone() + ); + let thumbnail = match event.info.clone().unwrap().thumbnail_url { + None => event.info.clone().unwrap().url.clone().unwrap(), + Some(v) => v, + }; + html! { + <div uk-lightbox=""> + <a class="uk-inline" href=event.info.clone().unwrap().url.clone().unwrap() data-caption=caption.clone() > + <img src=thumbnail.clone() alt=caption.clone() /> + </a> + </div> + } + } else { + html! {} } } } diff --git a/src/app/components/room_list.rs b/src/app/components/room_list.rs @@ -3,11 +3,11 @@ use std::collections::HashMap; use log::*; use matrix_sdk::{identifiers::RoomId, js_int::UInt}; use serde::{Deserialize, Serialize}; +use wasm_bindgen::JsCast; +use web_sys::HtmlElement; use yew::prelude::*; use yew::{Bridge, Bridged, Component, ComponentLink, Html}; use yewtil::NeqAssign; -use web_sys::HtmlElement; -use wasm_bindgen::JsCast; use crate::app::matrix::types::SmallRoom; use crate::app::matrix::{MatrixAgent, Request, Response}; diff --git a/src/app/matrix.rs b/src/app/matrix.rs @@ -20,7 +20,7 @@ use yew::format::Json; use yew::services::{storage::Area, StorageService}; use yew::worker::*; -use crate::app::matrix::types::{MessageWrapper, SmallRoom}; +use crate::app::matrix::types::{ImageInfoWrapper, MessageWrapper, SmallRoom}; use crate::constants::AUTH_KEY; use crate::errors::MatrixError; use comrak::{format_html, parse_document, Arena, ComrakOptions}; @@ -285,24 +285,59 @@ impl Agent for MatrixAgent { for event in messsages.chunk.iter().rev() { if let Ok(event) = event.deserialize() { match event { - RoomEvent::RoomMessage(event) => match event.content { - MessageEventContent::Text(_) => { - let mut wrapped_event: MessageWrapper = - event.try_into().expect("m.room.message"); - - if wrapped_event.room_id.is_none() { - wrapped_event.room_id = Some(room_id.clone()); + RoomEvent::RoomMessage(event) => { + let wrapped_event_result: Result<MessageWrapper, MatrixError> = + event.try_into(); + match wrapped_event_result { + Ok(mut wrapped_event) => { + if wrapped_event.room_id.is_none() { + wrapped_event.room_id = Some(room_id.clone()); + } + + wrapped_event.sender_displayname = Some( + wrapped_event.get_displayname(client.clone()).await, + ); + + // Convert mxc URLs + if wrapped_event.info.is_some() { + let mxc_url = wrapped_event + .info + .clone() + .unwrap() + .url + .clone() + .unwrap(); + let download_url = wrapped_event + .get_media_download_url( + client.clone(), + mxc_url, + ); + let mxc_thumbnail_url = wrapped_event + .info + .clone() + .unwrap() + .thumbnail_url + .clone() + .unwrap(); + let thumbnail_download_url = wrapped_event + .get_media_download_url( + client.clone(), + mxc_thumbnail_url, + ); + wrapped_event.info = Some(ImageInfoWrapper { + url: Some(download_url), + thumbnail_url: Some(thumbnail_download_url), + }); + } + + wrapped_messages.insert(wrapped_event); + } + Err(_) => { + // Ignore events we cant parse + continue; } - - wrapped_event.sender_displayname = Some( - wrapped_event.get_displayname(client.clone()).await, - ); - wrapped_messages.insert(wrapped_event); - } - _ => { - return; } - }, + } _ => { continue; } diff --git a/src/app/matrix/sync.rs b/src/app/matrix/sync.rs @@ -3,11 +3,12 @@ use std::convert::TryInto; use log::*; use matrix_sdk::{ api::r0::sync::sync_events::Response as SyncResponse, events::collections::all::RoomEvent, - events::room::message::MessageEventContent, identifiers::RoomId, Client, SyncSettings, + identifiers::RoomId, Client, SyncSettings, }; -use crate::app::matrix::types::MessageWrapper; +use crate::app::matrix::types::{MessageWrapper, ImageInfoWrapper}; use crate::app::matrix::Response; +use crate::errors::MatrixError; use yew::Callback; pub struct Sync { @@ -48,27 +49,60 @@ impl Sync { // TODO handle all messages... match event { - RoomEvent::RoomMessage(event) => match event.content { - MessageEventContent::Text(_) => { - let mut wrapped_event: MessageWrapper = - event.try_into().expect("m.room.message"); + RoomEvent::RoomMessage(event) => { + let wrapped_event_result: Result<MessageWrapper, MatrixError> = event.try_into(); + match wrapped_event_result { + Ok(mut wrapped_event) => { + if wrapped_event.room_id.is_none() { + wrapped_event.room_id = Some(room_id.clone()); + } - if wrapped_event.room_id.is_none() { - wrapped_event.room_id = Some(room_id.clone()); - } + wrapped_event.sender_displayname = Some( + wrapped_event + .get_displayname(self.matrix_client.clone()) + .await, + ); - wrapped_event.sender_displayname = Some( - wrapped_event - .get_displayname(self.matrix_client.clone()) - .await, - ); - let resp = Response::Sync(wrapped_event); - self.callback.emit(resp); - } - _ => { - return; + // Convert mxc URLs + if wrapped_event.info.is_some() { + let mxc_url = wrapped_event + .info + .clone() + .unwrap() + .url + .clone() + .unwrap(); + let download_url = wrapped_event + .get_media_download_url( + self.matrix_client.clone(), + mxc_url, + ); + let mxc_thumbnail_url = wrapped_event + .info + .clone() + .unwrap() + .thumbnail_url + .clone() + .unwrap(); + let thumbnail_download_url = wrapped_event + .get_media_download_url( + self.matrix_client.clone(), + mxc_thumbnail_url, + ); + wrapped_event.info = Some(ImageInfoWrapper { + url: Some(download_url), + thumbnail_url: Some(thumbnail_download_url), + }); + } + let resp = Response::Sync(wrapped_event); + self.callback.emit(resp); + } + Err(_) => { + // Ignore events we cant parse + return; + } } - }, + } _ => { return; } diff --git a/src/app/matrix/types.rs b/src/app/matrix/types.rs @@ -3,13 +3,15 @@ use std::sync::Arc; use futures_locks::RwLock; use matrix_sdk::{ - events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, + events::room::message::{MessageEvent, MessageEventContent}, identifiers::{EventId, RoomId, UserId}, js_int::UInt, Client, Room, }; use serde::{Deserialize, Serialize}; +use crate::errors::MatrixError; + // TODO: Add Into trait #[derive(Serialize, Deserialize, Debug, Clone)] pub struct SmallRoom { @@ -20,13 +22,21 @@ pub struct SmallRoom { } #[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)] +pub struct ImageInfoWrapper { + pub(crate) url: Option<String>, + pub(crate) thumbnail_url: Option<String>, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)] pub struct MessageWrapper { pub(crate) sender_displayname: Option<String>, pub(crate) room_id: Option<RoomId>, pub(crate) event_id: EventId, + pub(crate) event_type: String, pub(crate) sender: UserId, // TODO use ruma structs pub(crate) content: String, + pub(crate) info: Option<ImageInfoWrapper>, } impl MessageWrapper { @@ -48,27 +58,69 @@ impl MessageWrapper { .map(ToString::to_string) .unwrap_or(self.sender.clone().to_string()) } + + pub fn get_media_download_url(&self, client: Client, mxc_url: String) -> String { + let url_parts_raw = mxc_url.clone().replace("mxc://", ""); + let url_parts: Vec<&str> = url_parts_raw.split("/").collect(); + let server_name = url_parts.first().unwrap().to_string(); + let media_id = url_parts.last().unwrap().to_string(); + let new_path = format!( + "_matrix/media/r0/download/{}/{}/fix.jpg", + server_name.clone(), + media_id.clone() + ); + let mut new_url = client.clone().homeserver().clone(); + new_url.set_path(new_path.as_str()); + new_url.to_string() + } } impl TryFrom<MessageEvent> for MessageWrapper { - type Error = (); + type Error = MatrixError; fn try_from(event: MessageEvent) -> Result<Self, Self::Error> { - return if let MessageEvent { - content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), - sender, - .. - } = event - { - Ok(MessageWrapper { - sender_displayname: None, // We cant get it without a Client and therefor cant calculate it here - room_id: event.room_id, - event_id: event.event_id, - sender, - content: msg_body, - }) - } else { - Err(()) + return match event.content { + MessageEventContent::Text(text_event) => { + Ok(MessageWrapper { + sender_displayname: None, // We cant get it without a Client and therefor cant calculate it here + room_id: event.room_id, + event_id: event.event_id, + event_type: "m.text".to_string(), + sender: event.sender, + content: text_event.body, + info: None, + }) + } + MessageEventContent::Image(image_event) => { + let thumbnail_url = match image_event.info { + None => None, + Some(i) => i.thumbnail_url, + }; + Ok(MessageWrapper { + sender_displayname: None, // We cant get it without a Client and therefor cant calculate it here + room_id: event.room_id, + event_id: event.event_id, + event_type: "m.image".to_string(), + sender: event.sender, + content: image_event.body, + info: Some(ImageInfoWrapper { + url: image_event.url, + thumbnail_url, + }), + }) + } + MessageEventContent::Notice(notice_event) => { + Ok(MessageWrapper { + sender_displayname: None, // We cant get it without a Client and therefor cant calculate it here + room_id: event.room_id, + event_id: event.event_id, + event_type: "m.notice".to_string(), + sender: event.sender, + content: notice_event.body, + info: None, + }) + } + _ => Err(MatrixError::UnsupportedEvent), }; } } diff --git a/src/errors.rs b/src/errors.rs @@ -7,4 +7,6 @@ pub enum MatrixError { MissingClient, #[error("Missing required Data")] MissingFields, + #[error("This Matrix Event is not yet supported by Daydream")] + UnsupportedEvent, }