commit c2fb6ccf5f953a20fb1d1b0d84b7cd3ef9eda75f Author: none Date: Sun Jun 4 01:19:00 2023 +1000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dcad5b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +config.toml +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fd20335 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "frogbot" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +matrix-sdk = {version = "0.6.2", features = ["anyhow", "e2e-encryption", "socks"]} +anyhow = "1.0.71" +clap = "4.2.7" +toml = "0.7.4" +log = "0.4.17" +env_logger = "0.10.0" +tokio = {version = "1.28.1", features = ["parking_lot", "rt-multi-thread", "macros"]} +serde = {version = "1.0.163", features = ["derive"]} +tracing-subscriber = "0.3.17" diff --git a/config.toml.example b/config.toml.example new file mode 100644 index 0000000..824e601 --- /dev/null +++ b/config.toml.example @@ -0,0 +1,5 @@ +homeserver = "https://myserver.example.com/" +display_name = "MyBot" +username = "mybot" +# Change this, seriously, do it +password = "supersecretpassword" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2d3cd68 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,111 @@ +use anyhow; +use toml; +use tokio; +use log::*; +use serde::{Serialize, Deserialize}; +use matrix_sdk::{ + Client, + config::SyncSettings, + room::Room, + + ruma::OwnedDeviceId, + ruma::api::client::uiaa, + ruma::events::room::member::StrippedRoomMemberEvent, +}; + +#[derive(Serialize, Deserialize, Debug)] +struct TomlConfig { + homeserver: String, + username: String, + display_name: String, + password: String, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // init logging + tracing_subscriber::fmt::init(); + let config = load_config(); + let client = Client::builder() + .homeserver_url(&config.homeserver) + .handle_refresh_tokens() + .build() + .await?; + + // try login + client + .login_username(&config.username, &config.password) + .initial_device_display_name(&config.display_name) + .send() + .await?; + + info!("Logged in successfully!"); + info!("server: '{}', username: '{}', display name: '{}'", &config.homeserver, &config.username, &config.display_name); + + // sync client once so we get latest events to work on before we continue + client.sync_once(SyncSettings::default()).await?; + + info!("Deleting old encryption devices"); + let current_device_id = client.device_id().expect("Failed to get device ID"); + let old_devices: Vec = client.devices().await?.devices.iter().filter(|d| d.device_id != current_device_id).map(|d| d.device_id.to_owned()).collect(); + + // Deleting these devices needs "user interaction" or something, so we just send password again + // and it works :D + if let Err(e) = client.delete_devices(&old_devices, None).await { + if let Some(info) = e.uiaa_response() { + let mut password = uiaa::Password::new( + uiaa::UserIdentifier::UserIdOrLocalpart(&config.username), + &config.password, + ); + password.session = info.session.as_deref(); + client + .delete_devices(&old_devices, Some(uiaa::AuthData::Password(password))) + .await?; + } + } + info!("Finished deleting old encryption devices"); + info!("Rejecting stale invites"); + for room in client.invited_rooms() { + room.reject_invitation().await.unwrap_or_default(); + } + info!("Finished rejecting stale invites"); + + // Add handler to deal with new room invites + // TODO: Add code to filter rooms and only accept invites for rooms in config file + client.add_event_handler(|ev: StrippedRoomMemberEvent, room: Room, client: Client| async move { + info!("Processing room member event, room type: {:?}", room.room_type()); + if let Room::Invited(invited_room) = room { + let room_name = ev.content.displayname.unwrap_or(String::from("")); + let is_dm = ev.content.is_direct.unwrap_or(true); + let is_bad_room = is_dm || invited_room.is_space() || room_name.is_empty(); + info!("Got invite to room: '{}' sent by '{}'", room_name, ev.sender); + if is_bad_room { + info!("This room is probably a DM, ignoring!"); + if let Err(e) = invited_room.reject_invitation().await { + warn!("Failed to reject invite with error: {}", e); + } + return (); + } else { + if let Err(e) = invited_room.accept_invitation().await { + warn!("Failed to accept room invite with error: {}", e); + } + info!("Joining room!"); + if let Err(e) = client.join_room_by_id(invited_room.room_id()).await { + warn!("Failed to join room with id: {} and error: {}", invited_room.room_id(), e); + } + } + } + }); + + // Now keep on syncing forever. `sync()` will use the latest sync token automatically. + info!("Starting sync loop"); + client.sync(SyncSettings::default()).await?; + Ok(()) +} + +fn load_config() -> TomlConfig { + // fuck error handling, it's too early in the program execution for that shit + let config: TomlConfig = toml::from_str(&std::fs::read_to_string("./config.toml").unwrap()).unwrap(); + return config; // see, so clean! + +}