Skip to content
Snippets Groups Projects
main.rs 24.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • timokoesters's avatar
    timokoesters committed
    #![feature(proc_macro_hygiene, decl_macro)]
    
    timokoesters's avatar
    timokoesters committed
    mod data;
    
    timokoesters's avatar
    timokoesters committed
    mod pdu;
    
    timokoesters's avatar
    timokoesters committed
    mod ruma_wrapper;
    
    mod utils;
    
    timokoesters's avatar
    timokoesters committed
    
    
    pub use data::Data;
    
    pub use database::Database;
    
    timokoesters's avatar
    timokoesters committed
    pub use pdu::PduEvent;
    
    timokoesters's avatar
    timokoesters committed
    use log::debug;
    
    timokoesters's avatar
    timokoesters committed
    use rocket::{get, options, post, put, routes, State};
    
    timokoesters's avatar
    timokoesters committed
    use ruma_client_api::{
        error::{Error, ErrorKind},
        r0::{
    
            account::{
                register, AuthenticationFlow, UserInteractiveAuthenticationInfo,
                UserInteractiveAuthenticationResponse,
            },
    
    timokoesters's avatar
    timokoesters committed
            alias::get_alias,
    
    timokoesters's avatar
    timokoesters committed
            config::{get_global_account_data, set_global_account_data},
            directory::{self, get_public_rooms_filtered},
    
    timokoesters's avatar
    timokoesters committed
            filter::{self, create_filter, get_filter},
    
    timokoesters's avatar
    timokoesters committed
            keys::{get_keys, upload_keys},
    
    timokoesters's avatar
    timokoesters committed
            membership::{join_room_by_id, join_room_by_id_or_alias},
    
    timokoesters's avatar
    timokoesters committed
            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,
    
    timokoesters's avatar
    timokoesters committed
            room::create_room,
    
            session::{get_login_types, login},
    
    timokoesters's avatar
    timokoesters committed
            state::{create_state_event_for_empty_key, create_state_event_for_key},
            sync::sync_events,
    
    timokoesters's avatar
    timokoesters committed
            thirdparty::get_protocols,
    
    timokoesters's avatar
    timokoesters committed
        unversioned::get_supported_versions,
    
    timokoesters's avatar
    timokoesters committed
    };
    
    timokoesters's avatar
    timokoesters committed
    use ruma_events::{collections::only::Event, EventType};
    
    timokoesters's avatar
    timokoesters committed
    use ruma_identifiers::{RoomId, RoomIdOrAliasId, UserId};
    
    timokoesters's avatar
    timokoesters committed
    use ruma_wrapper::{MatrixResult, Ruma};
    
    timokoesters's avatar
    timokoesters committed
    use serde_json::json;
    
    use std::{
        collections::HashMap,
        convert::{TryFrom, TryInto},
        path::PathBuf,
        time::Duration,
    };
    
    timokoesters's avatar
    timokoesters committed
    
    
    const GUEST_NAME_LENGTH: usize = 10;
    const DEVICE_ID_LENGTH: usize = 10;
    const SESSION_ID_LENGTH: usize = 256;
    const TOKEN_LENGTH: usize = 256;
    
    #[get("/_matrix/client/versions")]
    fn get_supported_versions_route() -> MatrixResult<get_supported_versions::Response> {
        MatrixResult(Ok(get_supported_versions::Response {
    
    timokoesters's avatar
    timokoesters committed
            versions: vec!["r0.6.0".to_owned()],
    
    timokoesters's avatar
    timokoesters committed
            unstable_features: HashMap::new(),
    
    timokoesters's avatar
    timokoesters committed
    #[post("/_matrix/client/r0/register", data = "<body>")]
    
    timokoesters's avatar
    timokoesters committed
    fn register_route(
    
    timokoesters's avatar
    timokoesters committed
        data: State<Data>,
    
    timokoesters's avatar
    timokoesters committed
        body: Ruma<register::Request>,
    
    ) -> MatrixResult<register::Response, UserInteractiveAuthenticationResponse> {
    
    timokoesters's avatar
    timokoesters committed
        if body.auth.is_none() {
    
            return MatrixResult(Err(UserInteractiveAuthenticationResponse::AuthResponse(
                UserInteractiveAuthenticationInfo {
                    flows: vec![AuthenticationFlow {
                        stages: vec!["m.login.dummy".to_owned()],
                    }],
                    completed: vec![],
                    params: json!({}),
                    session: Some(utils::random_string(SESSION_ID_LENGTH)),
                },
            )));
    
    timokoesters's avatar
    timokoesters committed
        }
    
    timokoesters's avatar
    timokoesters committed
    
    
    timokoesters's avatar
    timokoesters committed
        // Validate user id
    
    timokoesters's avatar
    timokoesters committed
        let user_id: UserId = match (*format!(
    
    timokoesters's avatar
    timokoesters committed
            "@{}:{}",
    
            body.username
                .clone()
                .unwrap_or_else(|| utils::random_string(GUEST_NAME_LENGTH)),
    
    timokoesters's avatar
    timokoesters committed
            data.hostname()
    
        ))
        .try_into()
        {
            Err(_) => {
    
    timokoesters's avatar
    timokoesters committed
                debug!("Username invalid");
    
                return MatrixResult(Err(UserInteractiveAuthenticationResponse::MatrixError(
                    Error {
                        kind: ErrorKind::InvalidUsername,
                        message: "Username was invalid.".to_owned(),
                        status_code: http::StatusCode::BAD_REQUEST,
                    },
                )));
    
            }
            Ok(user_id) => user_id,
        };
    
    
    timokoesters's avatar
    timokoesters committed
        // Check if username is creative enough
    
    timokoesters's avatar
    timokoesters committed
        if data.user_exists(&user_id) {
    
    timokoesters's avatar
    timokoesters committed
            debug!("ID already taken");
    
            return MatrixResult(Err(UserInteractiveAuthenticationResponse::MatrixError(
                Error {
                    kind: ErrorKind::UserInUse,
                    message: "Desired user ID is already taken.".to_owned(),
                    status_code: http::StatusCode::BAD_REQUEST,
                },
            )));
    
    timokoesters's avatar
    timokoesters committed
        }
    
    
    timokoesters's avatar
    timokoesters committed
        // Create user
        data.user_add(&user_id, body.password.clone());
    
        // Generate new device id if the user didn't specify one
        let device_id = body
            .device_id
            .clone()
    
            .unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH));
    
    timokoesters's avatar
    timokoesters committed
    
        // Add device
        data.device_add(&user_id, &device_id);
    
        // Generate new token for the device
    
        let token = utils::random_string(TOKEN_LENGTH);
    
    timokoesters's avatar
    timokoesters committed
        data.token_replace(&user_id, &device_id, token.clone());
    
    timokoesters's avatar
    timokoesters committed
    
    
        MatrixResult(Ok(register::Response {
    
    Jonas Platte's avatar
    Jonas Platte committed
            access_token: Some(token),
    
            user_id,
    
    Jonas Platte's avatar
    Jonas Platte committed
            device_id: Some(device_id),
    
    #[get("/_matrix/client/r0/login", data = "<_body>")]
    
    fn get_login_route(
        _body: Ruma<get_login_types::Request>,
    ) -> MatrixResult<get_login_types::Response> {
    
        MatrixResult(Ok(get_login_types::Response {
            flows: vec![get_login_types::LoginType::Password],
        }))
    }
    
    
    timokoesters's avatar
    timokoesters committed
    #[post("/_matrix/client/r0/login", data = "<body>")]
    
    timokoesters's avatar
    timokoesters committed
    fn login_route(data: State<Data>, body: Ruma<login::Request>) -> MatrixResult<login::Response> {
    
    timokoesters's avatar
    timokoesters committed
        // Validate login method
        let user_id =
            if let (login::UserInfo::MatrixId(mut username), login::LoginInfo::Password { password }) =
                (body.user.clone(), body.login_info.clone())
            {
                if !username.contains(':') {
                    username = format!("@{}:{}", username, data.hostname());
                }
                if let Ok(user_id) = (*username).try_into() {
                    if !data.user_exists(&user_id) {}
    
                    // Check password
                    if let Some(correct_password) = data.password_get(&user_id) {
                        if password == correct_password {
                            // Success!
                            user_id
                        } else {
                            debug!("Invalid password.");
                            return MatrixResult(Err(Error {
    
                                kind: ErrorKind::Forbidden,
    
    timokoesters's avatar
    timokoesters committed
                                message: "".to_owned(),
                                status_code: http::StatusCode::FORBIDDEN,
                            }));
                        }
                    } else {
                        debug!("UserId does not exist (has no assigned password). Can't log in.");
                        return MatrixResult(Err(Error {
                            kind: ErrorKind::Forbidden,
                            message: "".to_owned(),
                            status_code: http::StatusCode::FORBIDDEN,
                        }));
                    }
                } else {
                    debug!("Invalid UserId.");
    
    timokoesters's avatar
    timokoesters committed
                    return MatrixResult(Err(Error {
    
                        kind: ErrorKind::InvalidUsername,
                        message: "Bad user id.".to_owned(),
    
    timokoesters's avatar
    timokoesters committed
                        status_code: http::StatusCode::BAD_REQUEST,
                    }));
                }
            } else {
    
    timokoesters's avatar
    timokoesters committed
                debug!("Bad login type");
    
    timokoesters's avatar
    timokoesters committed
                return MatrixResult(Err(Error {
    
    timokoesters's avatar
    timokoesters committed
                    kind: ErrorKind::Unknown,
                    message: "Bad login type.".to_owned(),
    
    timokoesters's avatar
    timokoesters committed
                    status_code: http::StatusCode::BAD_REQUEST,
                }));
    
    timokoesters's avatar
    timokoesters committed
            };
    
        // Generate new device id if the user didn't specify one
        let device_id = body
            .device_id
            .clone()
    
            .unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH));
    
    timokoesters's avatar
    timokoesters committed
    
    
    timokoesters's avatar
    timokoesters committed
        data.device_add(&user_id, &device_id);
    
        // Generate a new token for the device
    
        let token = utils::random_string(TOKEN_LENGTH);
    
    timokoesters's avatar
    timokoesters committed
        data.token_replace(&user_id, &device_id, token.clone());
    
    timokoesters's avatar
    timokoesters committed
    
    
        MatrixResult(Ok(login::Response {
    
    timokoesters's avatar
    timokoesters committed
            user_id,
            access_token: token,
    
            home_server: Some(data.hostname().to_owned()),
    
    timokoesters's avatar
    timokoesters committed
            device_id,
    
    timokoesters's avatar
    timokoesters committed
            well_known: None,
    
    timokoesters's avatar
    timokoesters committed
    }
    
    
    #[get("/_matrix/client/r0/pushrules")]
    fn get_pushrules_all_route() -> MatrixResult<get_pushrules_all::Response> {
        // TODO
        MatrixResult(Ok(get_pushrules_all::Response {
            global: HashMap::new(),
        }))
    }
    
    
    timokoesters's avatar
    timokoesters committed
    #[get(
        "/_matrix/client/r0/user/<_user_id>/filter/<_filter_id>",
        data = "<body>"
    )]
    fn get_filter_route(
        body: Ruma<get_filter::Request>,
        _user_id: String,
        _filter_id: String,
    ) -> MatrixResult<get_filter::Response> {
        // TODO
        MatrixResult(Ok(get_filter::Response {
            filter: filter::FilterDefinition {
                event_fields: None,
                event_format: None,
                account_data: None,
                room: None,
                presence: None,
            },
        }))
    }
    
    
    #[post("/_matrix/client/r0/user/<_user_id>/filter", data = "<body>")]
    fn create_filter_route(
        body: Ruma<create_filter::Request>,
        _user_id: String,
    ) -> MatrixResult<create_filter::Response> {
        // TODO
        MatrixResult(Ok(create_filter::Response {
            filter_id: utils::random_string(10),
        }))
    }
    
    
    timokoesters's avatar
    timokoesters committed
    #[put(
        "/_matrix/client/r0/user/<_user_id>/account_data/<_type>",
        data = "<body>"
    )]
    fn set_global_account_data_route(
        body: Ruma<set_global_account_data::Request>,
        _user_id: String,
        _type: String,
    ) -> MatrixResult<set_global_account_data::Response> {
        // TODO
        MatrixResult(Ok(set_global_account_data::Response))
    }
    
    #[get(
        "/_matrix/client/r0/user/<_user_id>/account_data/<_type>",
        data = "<body>"
    )]
    fn get_global_account_data_route(
        body: Ruma<get_global_account_data::Request>,
        _user_id: String,
        _type: String,
    ) -> MatrixResult<get_global_account_data::Response> {
        // TODO
        MatrixResult(Err(Error {
            kind: ErrorKind::NotFound,
            message: "Data not found.".to_owned(),
            status_code: http::StatusCode::NOT_FOUND,
        }))
    }
    
    
    #[put("/_matrix/client/r0/profile/<_user_id>/displayname", data = "<body>")]
    fn set_displayname_route(
        data: State<Data>,
        body: Ruma<set_display_name::Request>,
        _user_id: String,
    ) -> MatrixResult<set_display_name::Response> {
        let user_id = body.user_id.clone().expect("user is authenticated");
        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,
            }));
        }
    
        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/<user_id_raw>/displayname",
        data = "<body>"
    )]
    fn get_displayname_route(
        data: State<Data>,
        body: Ruma<get_display_name::Request>,
        user_id_raw: String,
    ) -> MatrixResult<get_display_name::Response> {
        let user_id = (*body).user_id.clone();
        if let Some(displayname) = data.displayname_get(&user_id) {
            return MatrixResult(Ok(get_display_name::Response {
                displayname: Some(displayname),
            }));
        }
    
        // Return 404 if we don't have any
        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/profile/<_user_id>/avatar_url", data = "<body>")]
    fn set_avatar_url_route(
        data: State<Data>,
        body: Ruma<set_avatar_url::Request>,
        _user_id: String,
    ) -> MatrixResult<set_avatar_url::Response> {
        let user_id = body.user_id.clone().expect("user is authenticated");
        if body.avatar_url == "" {
            debug!("Request was missing the avatar_url payload.");
            return MatrixResult(Err(Error {
                kind: ErrorKind::MissingParam,
                message: "Missing avatar_url".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 mxc:// format
    
        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/<user_id_raw>/avatar_url", data = "<body>")]
    fn get_avatar_url_route(
        data: State<Data>,
        body: Ruma<get_avatar_url::Request>,
        user_id_raw: String,
    ) -> MatrixResult<get_avatar_url::Response> {
        let user_id = (*body).user_id.clone();
        if let Some(avatar_url) = data.avatar_url_get(&user_id) {
            return MatrixResult(Ok(get_avatar_url::Response {
                avatar_url: Some(avatar_url),
            }));
        }
    
        // 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,
        }))
    }
    
    #[get("/_matrix/client/r0/profile/<user_id_raw>", data = "<body>")]
    fn get_profile_route(
        data: State<Data>,
        body: Ruma<get_profile::Request>,
        user_id_raw: String,
    ) -> MatrixResult<get_profile::Response> {
        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 = "<body>")]
    fn set_presence_route(
        body: Ruma<set_presence::Request>,
        _user_id: String,
    ) -> MatrixResult<set_presence::Response> {
        // TODO
        MatrixResult(Ok(set_presence::Response))
    }
    
    #[post("/_matrix/client/r0/keys/query", data = "<body>")]
    fn get_keys_route(body: Ruma<get_keys::Request>) -> MatrixResult<get_keys::Response> {
        // TODO
        MatrixResult(Ok(get_keys::Response {
            failures: HashMap::new(),
            device_keys: HashMap::new(),
        }))
    }
    
    
    timokoesters's avatar
    timokoesters committed
    #[post("/_matrix/client/r0/keys/upload", data = "<body>")]
    fn upload_keys_route(
        data: State<Data>,
        body: Ruma<upload_keys::Request>,
    ) -> MatrixResult<upload_keys::Response> {
        MatrixResult(Ok(upload_keys::Response {
            one_time_key_counts: HashMap::new(),
        }))
    }
    
    
    #[post("/_matrix/client/r0/createRoom", data = "<body>")]
    fn create_room_route(
        data: State<Data>,
        body: Ruma<create_room::Request>,
    ) -> MatrixResult<create_room::Response> {
        // TODO: check if room is unique
        let room_id = RoomId::new(data.hostname()).expect("host is valid");
    
    timokoesters's avatar
    timokoesters committed
        let user_id = body.user_id.clone().expect("user is authenticated");
    
    
        data.pdu_append(
            room_id.clone(),
    
    timokoesters's avatar
    timokoesters committed
            user_id.clone(),
            EventType::RoomCreate,
            json!({ "creator": user_id }),
    
    timokoesters's avatar
    timokoesters committed
            None,
    
    timokoesters's avatar
    timokoesters committed
            Some("".to_owned()),
        );
    
    timokoesters's avatar
    timokoesters committed
    
        if let Some(name) = &body.name {
            data.pdu_append(
                room_id.clone(),
                user_id.clone(),
                EventType::RoomName,
                json!({ "name": name }),
                None,
                Some("".to_owned()),
            );
        }
    
        if let Some(topic) = &body.topic {
            data.pdu_append(
                room_id.clone(),
                user_id.clone(),
                EventType::RoomTopic,
                json!({ "topic": topic }),
                None,
                Some("".to_owned()),
            );
        }
    
    timokoesters's avatar
    timokoesters committed
        data.room_join(&room_id, &user_id);
    
    
        MatrixResult(Ok(create_room::Response { room_id }))
    }
    
    
    #[get("/_matrix/client/r0/directory/room/<room_alias>")]
    fn get_alias_route(room_alias: String) -> MatrixResult<get_alias::Response> {
    
    timokoesters's avatar
    timokoesters committed
        // TODO
    
        let room_id = match &*room_alias {
            "#room:localhost" => "!xclkjvdlfj:localhost",
            _ => {
    
    timokoesters's avatar
    timokoesters committed
                debug!("Room not found.");
    
                return MatrixResult(Err(Error {
                    kind: ErrorKind::NotFound,
                    message: "Room not found.".to_owned(),
                    status_code: http::StatusCode::NOT_FOUND,
    
    timokoesters's avatar
    timokoesters committed
                }));
    
            }
        }
        .try_into()
        .unwrap();
    
        MatrixResult(Ok(get_alias::Response {
            room_id,
            servers: vec!["localhost".to_owned()],
        }))
    }
    
    #[post("/_matrix/client/r0/rooms/<_room_id>/join", data = "<body>")]
    fn join_room_by_id_route(
    
        data: State<Data>,
    
        body: Ruma<join_room_by_id::Request>,
    
    ) -> MatrixResult<join_room_by_id::Response> {
    
    timokoesters's avatar
    timokoesters committed
        if data.room_join(
    
            &body.room_id,
            body.user_id.as_ref().expect("user is authenticated"),
    
    timokoesters's avatar
    timokoesters committed
        ) {
            MatrixResult(Ok(join_room_by_id::Response {
                room_id: body.room_id.clone(),
            }))
        } else {
            MatrixResult(Err(Error {
                kind: ErrorKind::NotFound,
                message: "Room not found.".to_owned(),
                status_code: http::StatusCode::NOT_FOUND,
            }))
        }
    
    timokoesters's avatar
    timokoesters committed
    #[post("/_matrix/client/r0/join/<_room_id_or_alias>", data = "<body>")]
    fn join_room_by_id_or_alias_route(
        data: State<Data>,
        body: Ruma<join_room_by_id_or_alias::Request>,
        _room_id_or_alias: String,
    ) -> MatrixResult<join_room_by_id_or_alias::Response> {
        let room_id = match &body.room_id_or_alias {
            RoomIdOrAliasId::RoomAliasId(alias) => match alias.alias() {
                "#room:localhost" => "!xclkjvdlfj:localhost".try_into().unwrap(),
                _ => {
                    debug!("Room not found.");
                    return MatrixResult(Err(Error {
                        kind: ErrorKind::NotFound,
                        message: "Room not found.".to_owned(),
                        status_code: http::StatusCode::NOT_FOUND,
                    }));
                }
            },
    
            RoomIdOrAliasId::RoomId(id) => id.clone(),
        };
    
    
    timokoesters's avatar
    timokoesters committed
        if data.room_join(
    
    timokoesters's avatar
    timokoesters committed
            &room_id,
            body.user_id.as_ref().expect("user is authenticated"),
    
    timokoesters's avatar
    timokoesters committed
        ) {
            MatrixResult(Ok(join_room_by_id_or_alias::Response { room_id }))
        } else {
            MatrixResult(Err(Error {
                kind: ErrorKind::NotFound,
                message: "Room not found.".to_owned(),
                status_code: http::StatusCode::NOT_FOUND,
            }))
        }
    
    timokoesters's avatar
    timokoesters committed
    #[post("/_matrix/client/r0/publicRooms", data = "<body>")]
    fn get_public_rooms_filtered_route(
    
    timokoesters's avatar
    timokoesters committed
        data: State<Data>,
    
    timokoesters's avatar
    timokoesters committed
        body: Ruma<get_public_rooms_filtered::Request>,
    ) -> MatrixResult<get_public_rooms_filtered::Response> {
    
    timokoesters's avatar
    timokoesters committed
        let chunk = data
            .rooms_all()
            .into_iter()
            .map(|room_id| directory::PublicRoomsChunk {
                aliases: None,
                canonical_alias: None,
                name: None,
                num_joined_members: data.room_users(&room_id).into(),
                room_id,
                topic: None,
                world_readable: false,
                guest_can_join: true,
                avatar_url: None,
            })
            .collect::<Vec<_>>();
    
        let total_room_count_estimate = (chunk.len() as u32).into();
    
    
    timokoesters's avatar
    timokoesters committed
        MatrixResult(Ok(get_public_rooms_filtered::Response {
            chunk,
    
    timokoesters's avatar
    timokoesters committed
            prev_batch: None,
            next_batch: None,
            total_room_count_estimate: Some(total_room_count_estimate),
        }))
    }
    
    
    timokoesters's avatar
    timokoesters committed
    #[get("/_matrix/client/r0/thirdparty/protocols", data = "<body>")]
    fn get_protocols_route(
        body: Ruma<get_protocols::Request>,
    ) -> MatrixResult<get_protocols::Response> {
        MatrixResult(Ok(dbg!(get_protocols::Response {
            protocols: HashMap::new(),
        })))
    }
    
    
    #[put(
        "/_matrix/client/r0/rooms/<_room_id>/send/<_event_type>/<_txn_id>",
        data = "<body>"
    )]
    fn create_message_event_route(
    
        data: State<Data>,
    
        _room_id: String,
        _event_type: String,
        _txn_id: String,
    
        body: Ruma<create_message_event::Request>,
    
    ) -> MatrixResult<create_message_event::Response> {
    
    timokoesters's avatar
    timokoesters committed
        let mut unsigned = serde_json::Map::new();
        unsigned.insert("transaction_id".to_owned(), body.txn_id.clone().into());
    
    
        let event_id = data.pdu_append(
            body.room_id.clone(),
            body.user_id.clone().expect("user is authenticated"),
            body.event_type.clone(),
    
    timokoesters's avatar
    timokoesters committed
            body.json_body.clone(),
            Some(unsigned),
    
    timokoesters's avatar
    timokoesters committed
            None,
    
        );
        MatrixResult(Ok(create_message_event::Response { event_id }))
    
    timokoesters's avatar
    timokoesters committed
    }
    
    
    timokoesters's avatar
    timokoesters committed
    #[put(
        "/_matrix/client/r0/rooms/<_room_id>/state/<_event_type>/<_state_key>",
        data = "<body>"
    )]
    fn create_state_event_for_key_route(
        data: State<Data>,
        _room_id: String,
        _event_type: String,
        _state_key: String,
        body: Ruma<create_state_event_for_key::Request>,
    ) -> MatrixResult<create_state_event_for_key::Response> {
        // Reponse of with/without key is the same
        let event_id = data.pdu_append(
            body.room_id.clone(),
            body.user_id.clone().expect("user is authenticated"),
            body.event_type.clone(),
            body.json_body.clone(),
    
    timokoesters's avatar
    timokoesters committed
            None,
    
    timokoesters's avatar
    timokoesters committed
            Some(body.state_key.clone()),
        );
        MatrixResult(Ok(create_state_event_for_key::Response { event_id }))
    }
    
    #[put(
        "/_matrix/client/r0/rooms/<_room_id>/state/<_event_type>",
        data = "<body>"
    )]
    fn create_state_event_for_empty_key_route(
        data: State<Data>,
        _room_id: String,
        _event_type: String,
        body: Ruma<create_state_event_for_empty_key::Request>,
    ) -> MatrixResult<create_state_event_for_empty_key::Response> {
        // Reponse of with/without key is the same
        let event_id = data.pdu_append(
            body.room_id.clone(),
            body.user_id.clone().expect("user is authenticated"),
            body.event_type.clone(),
            body.json_body,
    
    timokoesters's avatar
    timokoesters committed
            None,
    
    timokoesters's avatar
    timokoesters committed
            Some("".to_owned()),
        );
        MatrixResult(Ok(create_state_event_for_empty_key::Response { event_id }))
    }
    
    
    #[get("/_matrix/client/r0/sync", data = "<body>")]
    
    fn sync_route(
        data: State<Data>,
    
        body: Ruma<sync_events::Request>,
    
    ) -> MatrixResult<sync_events::Response> {
    
    timokoesters's avatar
    timokoesters committed
        std::thread::sleep(Duration::from_millis(200));
        let next_batch = data.last_pdu_index().to_string();
    
    
    timokoesters's avatar
    timokoesters committed
        let mut joined_rooms = HashMap::new();
    
        let joined_roomids = data.rooms_joined(body.user_id.as_ref().expect("user is authenticated"));
        for room_id in joined_roomids {
    
    timokoesters's avatar
    timokoesters committed
            let pdus = if let Some(since) = body.since.clone().and_then(|string| string.parse().ok()) {
                data.pdus_since(&room_id, since)
            } else {
                data.pdus_all(&room_id)
            };
            let room_events = pdus.into_iter().map(|pdu| pdu.to_room_event()).collect();
    
    timokoesters's avatar
    timokoesters committed
    
            joined_rooms.insert(
    
    timokoesters's avatar
    timokoesters committed
                room_id.try_into().unwrap(),
    
    timokoesters's avatar
    timokoesters committed
                sync_events::JoinedRoom {
                    account_data: sync_events::AccountData { events: Vec::new() },
                    summary: sync_events::RoomSummary {
                        heroes: Vec::new(),
                        joined_member_count: None,
                        invited_member_count: None,
                    },
                    unread_notifications: sync_events::UnreadNotificationsCount {
                        highlight_count: None,
                        notification_count: None,
                    },
                    timeline: sync_events::Timeline {
    
                        limited: Some(false),
                        prev_batch: Some("".to_owned()),
    
    timokoesters's avatar
    timokoesters committed
                        events: room_events,
                    },
                    state: sync_events::State { events: Vec::new() },
                    ephemeral: sync_events::Ephemeral { events: Vec::new() },
    
    timokoesters's avatar
    timokoesters committed
                },
    
    timokoesters's avatar
    timokoesters committed
            );
        }
    
    timokoesters's avatar
    timokoesters committed
    
        MatrixResult(Ok(sync_events::Response {
    
    timokoesters's avatar
    timokoesters committed
            next_batch,
    
    timokoesters's avatar
    timokoesters committed
            rooms: sync_events::Rooms {
                leave: Default::default(),
                join: joined_rooms,
                invite: Default::default(),
            },
            presence: sync_events::Presence { events: Vec::new() },
            device_lists: Default::default(),
            device_one_time_keys_count: Default::default(),
            to_device: sync_events::ToDevice { events: Vec::new() },
        }))
    }
    
    #[options("/<_segments..>")]
    fn options_route(_segments: PathBuf) -> MatrixResult<create_message_event::Response> {
        MatrixResult(Err(Error {
            kind: ErrorKind::NotFound,
    
            message: "This is the options route.".to_owned(),
    
    timokoesters's avatar
    timokoesters committed
            status_code: http::StatusCode::OK,
    
    timokoesters's avatar
    timokoesters committed
    fn main() {
    
    timokoesters's avatar
    timokoesters committed
        // Log info by default
        if let Err(_) = std::env::var("RUST_LOG") {
    
    timokoesters's avatar
    timokoesters committed
            std::env::set_var("RUST_LOG", "matrixserver=debug,info");
    
    timokoesters's avatar
    timokoesters committed
        pretty_env_logger::init();
    
        let data = Data::load_or_create("localhost");
        data.debug();
    
    timokoesters's avatar
    timokoesters committed
    
    
    timokoesters's avatar
    timokoesters committed
        rocket::ignite()
    
            .mount(
                "/",
                routes![
                    get_supported_versions_route,
                    register_route,
    
    timokoesters's avatar
    timokoesters committed
                    login_route,
    
                    get_pushrules_all_route,
    
    timokoesters's avatar
    timokoesters committed
                    get_filter_route,
    
    timokoesters's avatar
    timokoesters committed
                    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,
    
    timokoesters's avatar
    timokoesters committed
                    upload_keys_route,
    
                    create_room_route,
    
                    get_alias_route,
                    join_room_by_id_route,
    
    timokoesters's avatar
    timokoesters committed
                    join_room_by_id_or_alias_route,
    
    timokoesters's avatar
    timokoesters committed
                    get_public_rooms_filtered_route,
                    get_protocols_route,
    
                    create_message_event_route,
    
    timokoesters's avatar
    timokoesters committed
                    create_state_event_for_key_route,
                    create_state_event_for_empty_key_route,
    
    timokoesters's avatar
    timokoesters committed
                    sync_route,
                    options_route,
    
    timokoesters's avatar
    timokoesters committed
            .manage(data)
    
            .launch()
            .unwrap();
    
    timokoesters's avatar
    timokoesters committed
    }