use matrix_sdk::{ async_trait, room::Room, ruma::{ events::{ room::{ message::{MessageEventContent, MessageFormat, MessageType, Relation}, redaction::RedactionEventContent, }, AnyMessageEventContent, AnySyncRoomEvent, SyncMessageEvent, }, EventId, UserId, }, ClientConfig, EventHandler, SyncSettings, }; pub use matrix_sdk::{ruma::RoomId, Client}; use log::info; use tokio::sync::mpsc; use url::Url; use crate::{ message_ast::{convert_matrix, convert_plain, format_discord, format_matrix}, messages::{MessageAuthor, MessageReference, SentMessage}, }; impl From<(&RoomId, &EventId)> for MessageReference { fn from((room_id, event_id): (&RoomId, &EventId)) -> Self { let room_string = room_id.as_str().to_string(); let event_string = event_id.as_str().to_string(); Self::Matrix(room_string, event_string) } } fn _find_content(event: &AnySyncRoomEvent) -> Option { match event { AnySyncRoomEvent::Message(message) => Some(message.content()), AnySyncRoomEvent::RedactedMessage(message) => { if let Some(ref redaction_event) = message.unsigned().redacted_because { Some(AnyMessageEventContent::RoomRedaction( redaction_event.content.clone(), )) } else { Some(AnyMessageEventContent::RoomRedaction( RedactionEventContent::new(), )) } } AnySyncRoomEvent::RedactedState(state) => { if let Some(ref redaction_event) = state.unsigned().redacted_because { Some(AnyMessageEventContent::RoomRedaction( redaction_event.content.clone(), )) } else { Some(AnyMessageEventContent::RoomRedaction( RedactionEventContent::new(), )) } } _ => None, } } struct MatrixHandler { message_tx: mpsc::UnboundedSender, current_user_id: UserId, } #[async_trait] impl EventHandler for MatrixHandler { async fn on_room_message(&self, room: Room, event: &SyncMessageEvent) { if event.sender == self.current_user_id { return; } if let Room::Joined(room) = room { let message_ref = MessageReference::from((room.room_id(), &event.event_id)); let message_type = if let Some(Relation::Replacement(replacement)) = &event.content.relates_to { &replacement.new_content.msgtype } else { &event.content.msgtype }; match message_type { MessageType::Text(text) => { let content = if let Some(html) = text .formatted .as_ref() .filter(|f| f.format == MessageFormat::Html) .map(|f| &f.body) { // TODO: Parse html_body into MessageContent AST convert_matrix(html) } else { convert_plain(&text.body) }; if let Ok(Some(sender)) = room.get_member(&event.sender).await { let _ = self.message_tx.send(SentMessage { source: message_ref, content, author: MessageAuthor { display_name: sender .display_name() .unwrap_or_else(|| sender.name()) .to_string(), }, }); } } MessageType::Emote(_emote) => { // TODO } _ => {} }; } } } pub async fn forward_to_matrix( client: &Client, room_id: RoomId, message: &SentMessage, ) -> Option { if let Some(room) = client.get_joined_room(&room_id) { let event = room .send( AnyMessageEventContent::RoomMessage(MessageEventContent::text_html( format_discord(&message.content), format_matrix(&message.content), )), None, ) .await .ok()?; return Some(MessageReference::from((&room_id, &event.event_id))); } None } pub async fn create_matrix_client( homeserver_url: String, username: String, password: String, message_tx: mpsc::UnboundedSender, ) -> Client { let client_config = ClientConfig::new().store_path("./data/matrix_state"); let homeserver_url = Url::parse(&homeserver_url).expect("Failed to parse the matrix homeserver URL"); let client = Client::new_with_config(homeserver_url, client_config).unwrap(); info!("Matrix logging in…"); client .login(&username, &password, None, Some("phoebe")) .await .expect("Failed to log in"); info!("Matrix starting…"); client.sync_once(SyncSettings::default()).await.unwrap(); let current_user_id = client.user_id().await.unwrap(); let event_handler = MatrixHandler { message_tx, current_user_id, }; client.set_event_handler(Box::new(event_handler)).await; client }