use std::{sync::Arc, ops::Deref}; use tokio::sync::Mutex; use anyhow::Context; use matrix_sdk::{ ruma::{events::room::member::StrippedRoomMemberEvent, api::client::uiaa, OwnedRoomId, OwnedRoomOrAliasId}, Client, Room, }; use secrecy::{Secret, ExposeSecret}; use tokio::time::{sleep, Duration}; #[tracing::instrument(skip(client, configured_rooms, room, state))] pub async fn join_rooms_on_invite( event: StrippedRoomMemberEvent, client: Client, room: Room, configured_rooms: Vec, state: Arc>> ) { if event.state_key == client.user_id().unwrap() { let room_id = room.room_id().to_owned(); if configured_rooms.contains(&room.room_id().into()) { tokio::spawn(async move { join_room_with_cooldown(&room).await; state.lock().await.push(room); tracing::info!("Successfully joined room {room_id}"); }); } else { tracing::warn!("was invited to room {room_id} which is not configured"); if let Err(e) = room.leave().await { tracing::error!("error leaving room {room_id}: {e}"); } } } } async fn join_room_with_cooldown(room: &Room) { tracing::info!("Autojoining room {}", room.room_id()); let mut delay = 2; let room_id = room.room_id().to_owned(); while let Err(err) = room.join().await { // retry autojoin due to synapse sending invites, before the // invited user can join for more information see // https://github.com/matrix-org/synapse/issues/4345 tracing::error!("Failed to join room {room_id} ({err:?}), retrying in {delay}s"); sleep(Duration::from_secs(delay)).await; delay *= 2; if delay > 3600 { eprintln!("Can't join room {} ({err:?})", room.room_id()); break; } } } pub async fn resolve_room_aliases(client: &Client, aliases: &Vec) -> Vec { futures_util::future::join_all (aliases .iter() .map(|name| async {match name.deref().try_into() { Ok(alias) => Some(client.resolve_room_alias(alias).await.ok()?.room_id), Err(id) => Some(id.into()) }})) .await .into_iter() .filter_map(|o: Option<_>| o) .collect() } #[tracing::instrument(skip(client))] pub async fn join_configured_rooms(client: &Client, configured_rooms: &Vec) -> anyhow::Result<()> { let joined_rooms = client.joined_rooms(); let missing_rooms = configured_rooms .iter() .filter(|room_id| !joined_rooms.iter().any(|room| room.room_id() == *room_id)) .collect::>(); let extra_rooms = joined_rooms .iter() .filter(|room| !configured_rooms.contains(&room.room_id().into())) .collect::>(); for room_id in missing_rooms { tracing::info!("attempting to join room {room_id:?}"); if let Err(e) = client.join_room_by_id(room_id).await { tracing::error!("could not join room {}", e) } } for room in extra_rooms { tracing::info!("leaving room {}", room.room_id()); if let Err(e) = room.leave().await { tracing::error!("could not leave room {}", e) } } Ok(()) } #[tracing::instrument(skip(password))] pub async fn delete_other_devices(client: &Client, username: &str, password: &Secret) -> anyhow::Result<()> { let own_id = client .device_id() .with_context(|| "no own device id?")?; let devices = client.devices().await? .devices .into_iter() .map(|device| device.device_id) .filter(|id| id != own_id) .collect::>(); tracing::info!("deleting devices {:?}", devices); // deleting devices is a funny double-request thing since it requires the // user's password; see the documentation on delete_devices() if let Err(e) = client.delete_devices(&devices, None).await { if let Some(info) = e.as_uiaa_response() { let mut password = uiaa::Password::new( uiaa::UserIdentifier::UserIdOrLocalpart(username.to_owned()), password.expose_secret().to_owned(), ); password.session = info.session.clone(); client .delete_devices(&devices, Some(uiaa::AuthData::Password(password))) .await?; } } Ok(()) }