use std::{ cell::RefCell, str::FromStr, sync::{Arc, Mutex}, }; use discord::create_discord_client; use matrix::create_matrix_client; use matrix_sdk::{ ruma::{ events::{room::message::MessageEventContent, AnyMessageEventContent}, RoomId, }, SyncSettings, }; use serde::{Deserialize, Serialize}; mod discord; mod matrix; mod message_ast; use message_ast::MessageContent; use serenity::model::id::ChannelId; use tokio::sync::mpsc; #[derive(Serialize, Deserialize)] pub enum MessageReference { Discord(u64, u64), Matrix(String, String), } pub struct SentMessage { pub source: MessageReference, pub content: MessageContent, } struct Bridgers { discord: Mutex>>, matrix: Mutex>>, } impl Bridgers { fn new() -> Self { Self { discord: Mutex::new(RefCell::new(None)), matrix: Mutex::new(RefCell::new(None)), } } async fn send_message( &self, source: MessageReference, content: MessageContent, ) -> Vec { let mut created_messages = Vec::new(); if let Some(discord) = self.discord.lock().unwrap().borrow().as_ref() { // We probably want a function that returns an Option taking the source match &source { MessageReference::Matrix(_room_id, _event_id) => { let channel_id = ChannelId(885690775193661463); // TODO: Look up linked channel let discord_message = channel_id .send_message(&discord.http, |m| { m.content(message_ast::format_discord(&content)) }) .await .expect("Failed to send discord message"); created_messages.push(MessageReference::from(&discord_message)); } _ => {} }; } if let Some(matrix) = self.matrix.lock().unwrap().borrow().as_ref() { match &source { MessageReference::Discord(_, _) => { let room_id = RoomId::from_str("asdfghj").unwrap(); // TODO: Get a room id if let Some(room) = matrix.get_joined_room(&room_id) { let event = room .send( AnyMessageEventContent::RoomMessage( MessageEventContent::text_plain(message_ast::format_discord( &content, // TODO: Format as HTML )), ), None, ) .await .unwrap(); created_messages.push(MessageReference::from((&room_id, &event.event_id))); } } _ => {} } } created_messages } } async fn setup_discord( token: String, bridgers: Arc, discord_tx: mpsc::UnboundedSender, ) { let (discord_ctx_tx, mut discord_ctx_rx) = mpsc::unbounded_channel::(); tokio::spawn(async move { let mut discord = create_discord_client(discord_ctx_tx, discord_tx, &token).await; discord.start().await.unwrap(); }); // Hack to grab the Context object when discord is ready tokio::spawn(async move { while let Some(discord) = discord_ctx_rx.recv().await { bridgers.discord.lock().unwrap().replace(Some(discord)); } }); } async fn setup_matrix( homeserver_url: String, username: String, password: String, bridgers: Arc, message_tx: mpsc::UnboundedSender, ) { let client = create_matrix_client(homeserver_url, username, password, message_tx).await; let settings = SyncSettings::default().token(client.sync_token().await.unwrap()); bridgers .matrix .lock() .unwrap() .replace(Some(client.clone())); tokio::spawn(async move { client.sync(settings).await; }); } #[tokio::main] async fn main() { let bridgers = Arc::new(Bridgers::new()); let (message_tx, mut message_rx) = tokio::sync::mpsc::unbounded_channel::(); setup_discord( "token".to_string(), Arc::clone(&bridgers), message_tx.clone(), ) .await; setup_matrix( "https://matrix.org".to_string(), "username".to_string(), "password".to_string(), Arc::clone(&bridgers), message_tx.clone(), ) .await; while let Some(message) = message_rx.recv().await { let _ = bridgers.send_message(message.source, message.content).await; } }