extern crate gstreamer as gst; extern crate gstreamer_webrtc as gst_webrtc; use crossbeam_channel::{Receiver, Sender}; use async_trait::async_trait; use tokio::time::{Duration, Instant}; use tracing::{error, warn}; use matrix_sdk::{ self, events::{ call::invite::InviteEventContent, call::SessionDescription, room::member::MemberEventContent, room::message::{MessageEventContent, TextMessageEventContent}, AnySyncMessageEvent::{CallAnswer, CallCandidates, CallHangup, CallInvite}, AnySyncRoomEvent::Message, AnyToDeviceEvent, StrippedStateEvent, SyncMessageEvent, }, identifiers::{DeviceId, RoomId, UserId}, locks::RwLock, Client, ClientConfig, EventEmitter, JsonStore, Room, Sas, SyncRoom, SyncSettings, }; use std::sync::Arc; use url::Url; use crate::config; use crate::gstream; // get the functions i need from my gstream module struct CommandBot { /// This clone of the `Client` will send requests to the server, /// while the other keeps us in sync with the server using `sync_forever`. client: Client, } impl CommandBot { pub fn new(client: Client) -> Self { Self { client } } // TODO figure out a better way to refer to this type async fn handle_command(&self, room: Arc>, sender: String, message: String) { println!("<{}> {} ", sender, message); if message.contains("!party") { let content = MessageEventContent::Text(TextMessageEventContent { body: "🎉🎊🥳 let's PARTY!! 🥳🎊🎉".to_string(), formatted: None, relates_to: None, }); // we clone here to hold the lock for as little time as possible. let room_id = room.read().await.room_id.clone(); println!("sending"); &self .client // send our message to the room we found the "!party" command in // the last parameter is an optional Uuid which we don't care about. .room_send(&room_id, content, None) .await .unwrap(); println!("message sent"); } } } async fn wait_for_confirmation(sas: Sas) { println!("Emoji: {:?}", sas.emoji()); // TODO make this verify things somehow, I can't do it interactively so I think send them in a message? // maybe render them into an image and send that, so it's less interceptable sas.confirm().await.unwrap(); } fn print_result(sas: Sas) { let device = sas.other_device(); println!( "Successfully verified device {} {} {:?}", device.user_id(), device.device_id(), device.trust_state() ); } async fn handle_call_invite( client: &Client, room_id: &RoomId, _call_id: String, sender: String, _server: String, _offer: SessionDescription, ) { let myself = client.user_id().await.unwrap(); println!("Incoming call from {}", sender); let message = MessageEventContent::Text(TextMessageEventContent { body: format!("🎉🎊🥳 let's PARTY!! 🥳🎊🎉 {:?}", myself), // This is renderng weirdly in vscode, ignore the spaces for my sanity formatted: None, relates_to: None, }); //client.share_group_session(room_id); let encrypted = { let room = client.get_joined_room(room_id).await; match room { Some(r) => r.read().await.is_encrypted(), None => false, } }; println!("Encrypted?: {:?}", encrypted); client.room_send(room_id, message, None).await.unwrap(); } async fn create_call_invite(client: &Client, room_id: &RoomId) { //let fakesdp = SessionDescription {}; } #[async_trait] impl EventEmitter for CommandBot { async fn on_room_message(&self, room: SyncRoom, event: &SyncMessageEvent) { if let SyncRoom::Joined(room) = room { println!("Syncroom joined: {:?}", room); if let SyncMessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), sender, .. } = event { let user_id = sender.localpart(); let user_server = sender.server_name(); let myself = self.client.user_id().await.unwrap(); if (myself.localpart() == user_id && myself.server_name() == user_server) { println!("Saw my own message, ignoring it"); } else { if (user_server == "matrix.voots.org") { // TODO make this configurable on which servers &self .handle_command(room, user_id.to_string(), msg_body.to_string()) .await; } } } } } async fn on_stripped_state_member( &self, room: SyncRoom, room_member: &StrippedStateEvent, event: Option, ) { println!("STRIPPED: {:?}", event); if room_member.state_key != self.client.user_id().await.unwrap() { return; } if let SyncRoom::Invited(room) = room { let room = room.read().await; println!("Autojoining room {}", room.display_name()); self.client .join_room_by_id(&room.room_id) .await .expect("Can't join room"); } } } pub async fn login_and_sync( botname: String, _sender: Sender, _receiver: Receiver, ) -> Result<(), anyhow::Error> { // the location for `JsonStore` to save files to // homeserver_url: String, username: String, password: String //let {homeserver_url: String, username: String, password: String} = config.matrix; let config: config::Config = config::load_config(&botname)?; let homeserver_url = config.matrix.homeserver_url; let username = config.matrix.username; let password = config.matrix.password; let store_dir = config::get_config_directory("party_bot")?; let store = JsonStore::open(&store_dir)?; let client_config = ClientConfig::new().state_store(Box::new(store)); let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); // create a new Client with the given homeserver url and config let mut client = Client::new_with_config(homeserver_url, client_config).unwrap(); let commandbot = Box::new(CommandBot::new(client.clone())); client .login(username.clone(), password, None, Some(botname)) .await?; println!("logged in as {}", username); // An initial sync to set up state and so our bot doesn't respond to old messages. // If the `StateStore` finds saved state in the location given the initial sync will // be skipped in favor of loading state from the store client.sync(SyncSettings::default()).await.unwrap(); // add our CommandBot to be notified of incoming messages, we do this after the initial // sync to avoid responding to messages before the bot was running. client.add_event_emitter(commandbot).await; // since we called sync before we `sync_forever` we must pass that sync token to // `sync_forever` let mut settings = SyncSettings::default().token(client.sync_token().await.unwrap()); let client_ref = &client; client .sync_forever(settings, async move |response| { let client = &client_ref; for event in &response.to_device.events { let e_maybe = event.deserialize(); match e_maybe { Ok(AnyToDeviceEvent::KeyVerificationStart(e)) => { let sas = client .get_verification(&e.content.transaction_id) .await .expect("Sas object wasn't created"); sas.accept().await.unwrap(); } Ok(AnyToDeviceEvent::KeyVerificationKey(e)) => { let sas = client .get_verification(&e.content.transaction_id) .await .expect("Sas object wasn't created"); tokio::spawn(wait_for_confirmation(sas)); } Ok(AnyToDeviceEvent::KeyVerificationMac(e)) => { let sas = client .get_verification(&e.content.transaction_id) .await .expect("Sas object wasn't created"); if sas.is_done() { print_result(sas); } } Ok(_) => (), // Unknown or unhandled event Err(error) => { eprintln!( "Couldn't deserialize to to-device event: {:?} from {:?}", error, event ); } } } for (room_id, room) in response.rooms.join { for rawevent in &room.timeline.events { let event = rawevent.deserialize().unwrap(); // Check if it's a message type if let Message(msg) = event { match msg { CallCandidates(_e) => {} CallInvite(e) => { println!("incoming debug: {:?}", e); let SyncMessageEvent { content: InviteEventContent { call_id, offer, .. }, sender, .. } = e; let user_id = sender.localpart(); let user_server = sender.server_name(); println!("Content: {:?}", call_id); handle_call_invite( client, &room_id, call_id, user_id.to_string(), user_server.to_string(), offer, ) .await; } CallHangup(_e) => {} CallAnswer(_e) => {} e => { println!("unhandled debug: {:?}", e); } }; } else { println!("Got unknown event type here: {:?}", event); } } } }) .await; Ok(()) }