diff --git a/src/data.rs b/src/data.rs index dffb1e0..583a986 100644 --- a/src/data.rs +++ b/src/data.rs @@ -60,6 +60,56 @@ impl Data { .map(|bytes| utils::string_from_bytes(&bytes)) } + /// Removes a displayname. + pub fn displayname_remove(&self, user_id: &UserId) { + self.db + .userid_displayname + .remove(user_id.to_string()) + .unwrap(); + } + + /// Set a new displayname. + pub fn displayname_set(&self, user_id: &UserId, displayname: Option) { + self.db + .userid_displayname + .insert(user_id.to_string(), &*displayname.unwrap_or_default()) + .unwrap(); + } + + /// Get a the displayname of a user. + pub fn displayname_get(&self, user_id: &UserId) -> Option { + self.db + .userid_displayname + .get(user_id.to_string()) + .unwrap() + .map(|bytes| utils::string_from_bytes(&bytes)) + } + + /// Removes a avatar_url. + pub fn avatar_url_remove(&self, user_id: &UserId) { + self.db + .userid_avatarurl + .remove(user_id.to_string()) + .unwrap(); + } + + /// Set a new avatar_url. + pub fn avatar_url_set(&self, user_id: &UserId, avatar_url: String) { + self.db + .userid_avatarurl + .insert(user_id.to_string(), &*avatar_url) + .unwrap(); + } + + /// Get a the avatar_url of a user. + pub fn avatar_url_get(&self, user_id: &UserId) -> Option { + self.db + .userid_avatarurl + .get(user_id.to_string()) + .unwrap() + .map(|bytes| utils::string_from_bytes(&bytes)) + } + /// Add a new device to a user. pub fn device_add(&self, user_id: &UserId, device_id: &str) { if self diff --git a/src/database.rs b/src/database.rs index 0fe8e52..a19ec43 100644 --- a/src/database.rs +++ b/src/database.rs @@ -52,6 +52,8 @@ impl MultiValue { pub struct Database { pub userid_password: sled::Tree, pub userid_deviceids: MultiValue, + pub userid_displayname: sled::Tree, + pub userid_avatarurl: sled::Tree, pub deviceid_token: sled::Tree, pub token_userid: sled::Tree, pub pduid_pdus: sled::Tree, @@ -75,6 +77,8 @@ impl Database { Self { userid_password: db.open_tree("userid_password").unwrap(), userid_deviceids: MultiValue(db.open_tree("userid_deviceids").unwrap()), + userid_displayname: db.open_tree("userid_displayname").unwrap(), + userid_avatarurl: db.open_tree("userid_avatarurl").unwrap(), deviceid_token: db.open_tree("deviceid_token").unwrap(), token_userid: db.open_tree("token_userid").unwrap(), pduid_pdus: db.open_tree("pduid_pdus").unwrap(), @@ -103,6 +107,22 @@ impl Database { String::from_utf8_lossy(&v), ); } + println!("# UserId -> Displayname:"); + for (k, v) in self.userid_displayname.iter().map(|r| r.unwrap()) { + println!( + "{:?} -> {:?}", + String::from_utf8_lossy(&k), + String::from_utf8_lossy(&v), + ); + } + println!("# UserId -> AvatarURL:"); + for (k, v) in self.userid_avatarurl.iter().map(|r| r.unwrap()) { + println!( + "{:?} -> {:?}", + String::from_utf8_lossy(&k), + String::from_utf8_lossy(&v), + ); + } println!("\n# DeviceId -> Token:"); for (k, v) in self.deviceid_token.iter().map(|r| r.unwrap()) { println!( diff --git a/src/main.rs b/src/main.rs index 519d32b..4b1f72e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ #![feature(proc_macro_hygiene, decl_macro)] + mod data; mod database; mod pdu; @@ -26,6 +27,9 @@ use ruma_client_api::{ membership::{join_room_by_id, join_room_by_id_or_alias}, message::create_message_event, presence::set_presence, + profile::{ + get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name, + }, push::get_pushrules_all, room::create_room, session::{get_login_types, login}, @@ -39,7 +43,12 @@ use ruma_events::{collections::only::Event, EventType}; use ruma_identifiers::{RoomId, RoomIdOrAliasId, UserId}; use ruma_wrapper::{MatrixResult, Ruma}; use serde_json::json; -use std::{collections::HashMap, convert::TryInto, path::PathBuf, time::Duration}; +use std::{ + collections::HashMap, + convert::{TryFrom, TryInto}, + path::PathBuf, + time::Duration, +}; const GUEST_NAME_LENGTH: usize = 10; const DEVICE_ID_LENGTH: usize = 10; @@ -282,6 +291,151 @@ fn get_global_account_data_route( })) } +#[put("/_matrix/client/r0/profile/<_user_id>/displayname", data = "")] +fn set_displayname_route( + data: State, + body: Ruma, + _user_id: String, +) -> MatrixResult { + let user_id = body.user_id.clone().expect("user is authenticated"); + + // Send error on None + // Synapse returns a parsing error but the spec doesn't require this + if body.displayname.is_none() { + debug!("Request was missing the displayname payload."); + return MatrixResult(Err(Error { + kind: ErrorKind::MissingParam, + message: "Missing displayname.".to_owned(), + status_code: http::StatusCode::BAD_REQUEST, + })); + } + + if let Some(displayname) = &body.displayname { + // Some("") will clear the displayname + if displayname == "" { + data.displayname_remove(&user_id); + } else { + data.displayname_set(&user_id, body.displayname.clone()); + // TODO send a new m.room.member join event with the updated displayname + // TODO send a new m.presence event with the updated displayname + } + } + + MatrixResult(Ok(set_display_name::Response)) +} + +#[get( + "/_matrix/client/r0/profile//displayname", + data = "" +)] +fn get_displayname_route( + data: State, + body: Ruma, + user_id_raw: String, +) -> MatrixResult { + let user_id = (*body).user_id.clone(); + if !data.user_exists(&user_id) { + // Return 404 if we don't have a profile for this id + debug!("Profile was not found."); + return MatrixResult(Err(Error { + kind: ErrorKind::NotFound, + message: "Profile was not found.".to_owned(), + status_code: http::StatusCode::NOT_FOUND, + })); + } + if let Some(displayname) = data.displayname_get(&user_id) { + return MatrixResult(Ok(get_display_name::Response { + displayname: Some(displayname), + })); + } + + // The user has no displayname + MatrixResult(Ok(get_display_name::Response { displayname: None })) +} + +#[put("/_matrix/client/r0/profile/<_user_id>/avatar_url", data = "")] +fn set_avatar_url_route( + data: State, + body: Ruma, + _user_id: String, +) -> MatrixResult { + let user_id = body.user_id.clone().expect("user is authenticated"); + + if !body.avatar_url.starts_with("mxc://") { + debug!("Request contains an invalid avatar_url."); + return MatrixResult(Err(Error { + kind: ErrorKind::InvalidParam, + message: "avatar_url has to start with mxc://.".to_owned(), + status_code: http::StatusCode::BAD_REQUEST, + })); + } + + // TODO in the future when we can handle media uploads make sure that this url is our own server + // TODO also make sure this is valid mxc:// format (not only starting with it) + + if body.avatar_url == "" { + data.avatar_url_remove(&user_id); + } else { + data.avatar_url_set(&user_id, body.avatar_url.clone()); + // TODO send a new m.room.member join event with the updated avatar_url + // TODO send a new m.presence event with the updated avatar_url + } + + MatrixResult(Ok(set_avatar_url::Response)) +} + +#[get("/_matrix/client/r0/profile//avatar_url", data = "")] +fn get_avatar_url_route( + data: State, + body: Ruma, + user_id_raw: String, +) -> MatrixResult { + let user_id = (*body).user_id.clone(); + if !data.user_exists(&user_id) { + // Return 404 if we don't have a profile for this id + debug!("Profile was not found."); + return MatrixResult(Err(Error { + kind: ErrorKind::NotFound, + message: "Profile was not found.".to_owned(), + status_code: http::StatusCode::NOT_FOUND, + })); + } + if let Some(avatar_url) = data.avatar_url_get(&user_id) { + return MatrixResult(Ok(get_avatar_url::Response { + avatar_url: Some(avatar_url), + })); + } + + // The user has no avatar + MatrixResult(Ok(get_avatar_url::Response { avatar_url: None })) +} + +#[get("/_matrix/client/r0/profile/", data = "")] +fn get_profile_route( + data: State, + body: Ruma, + user_id_raw: String, +) -> MatrixResult { + let user_id = (*body).user_id.clone(); + let avatar_url = data.avatar_url_get(&user_id); + let displayname = data.displayname_get(&user_id); + + if avatar_url.is_some() || displayname.is_some() { + return MatrixResult(Ok(get_profile::Response { + avatar_url, + displayname, + })); + } + + // Return 404 if we don't have a profile for this id + debug!("Profile was not found."); + MatrixResult(Err(Error { + kind: ErrorKind::NotFound, + message: "Profile was not found.".to_owned(), + status_code: http::StatusCode::NOT_FOUND, + })) +} + #[put("/_matrix/client/r0/presence/<_user_id>/status", data = "")] fn set_presence_route( body: Ruma, @@ -634,6 +788,11 @@ fn main() { create_filter_route, set_global_account_data_route, get_global_account_data_route, + set_displayname_route, + get_displayname_route, + set_avatar_url_route, + get_avatar_url_route, + get_profile_route, set_presence_route, get_keys_route, upload_keys_route,