summaryrefslogtreecommitdiff
path: root/src/matrix_bot.rs
diff options
context:
space:
mode:
authorstuebinm2024-03-27 23:10:02 +0100
committerstuebinm2024-03-27 23:10:02 +0100
commitbade89a506c380a7d4cab4fdd765e28686c14776 (patch)
tree451bd9fdee32b03c159bcbff71699afaaef48341 /src/matrix_bot.rs
simple bot to play around with
Diffstat (limited to 'src/matrix_bot.rs')
-rw-r--r--src/matrix_bot.rs134
1 files changed, 134 insertions, 0 deletions
diff --git a/src/matrix_bot.rs b/src/matrix_bot.rs
new file mode 100644
index 0000000..7618b0d
--- /dev/null
+++ b/src/matrix_bot.rs
@@ -0,0 +1,134 @@
+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<OwnedRoomId>,
+ state: Arc<Mutex<Vec<Room>>>
+) {
+ 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<OwnedRoomOrAliasId>) -> Vec<OwnedRoomId> {
+ 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<OwnedRoomId>) -> 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::<Vec<_>>();
+
+ let extra_rooms = joined_rooms
+ .iter()
+ .filter(|room| !configured_rooms.contains(&room.room_id().into()))
+ .collect::<Vec<_>>();
+
+ 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<String>) -> 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::<Vec<_>>();
+
+ 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(())
+}