309 lines
11 KiB
Rust
309 lines
11 KiB
Rust
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<RwLock<Room>>, 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<MessageEventContent>) {
|
|
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<MemberEventContent>,
|
|
event: Option<MemberEventContent>,
|
|
) {
|
|
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<String>,
|
|
_receiver: Receiver<String>,
|
|
) -> 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(())
|
|
}
|