rdzobot 0.1.0

Modular, but monolithic Matrix bot
Documentation
// SPDX-License-Identifier: AGPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Wojtek Porczyk <woju@hackerspace.pl>

//! Module `autojoin`: automatically join on invite and leave when owner sends `!leave`

use matrix_sdk::ruma::OwnedRoomOrAliasId;
use matrix_sdk::ruma::events::room::member::StrippedRoomMemberEvent;

use tokio::time::{
    Duration,
    sleep,
};

use crate::prelude::*;
use crate::utils;


#[derive(Debug, Deserialize)]
#[serde(default)]
#[doc(hidden)]
pub struct Config {
    enabled: bool,
}

impl Default for Config {
    fn default() -> Self { Self { enabled: true } }
}

#[doc(hidden)]
pub fn load(bot: Rdzobot) {
    if !bot.config().module.autojoin.enabled {
        return;
    }

    bot.add_command(LeaveCommand::command(), on_cmd_leave);
    bot.add_event_handler(on_invite);
}


#[derive(clap::Parser)]
#[command(name = "!leave")]
#[command(about = "Causes the bot to leave this or specified room [owner only]")]
struct LeaveCommand {
    room: Option<OwnedRoomOrAliasId>,
}

async fn on_cmd_leave(
    mut arg_matches: clap::ArgMatches,
    event: OriginalSyncRoomMessageEvent,
    client: Client,
    room: Room,
    bot: Rdzobot,
) -> anyhow::Result<()> {
    let args = LeaveCommand::from_arg_matches_mut(&mut arg_matches).unwrap();
    let room_to_leave =
        match utils::resolve_room_alias_with_default(client, args.room, Some(&room)).await {
            Ok(Some(r)) => r,
            Ok(None) => unreachable!(),
            Err(err) => {
                tracing::debug!("no room to leave: {:?}", err);
                nie_zesraj_się(&event, &room).await?;
                return Ok(());
            }
        };

    if bot.config().owner.as_ref().is_some_and(|owner| &event.sender == owner) {
        tracing::warn!(
            "leaving room {}{}",
            room_to_leave.room_id(),
            if let Some(name) = room_to_leave.cached_display_name() {
                format!(" ({name})")
            } else {
                "".to_string()
            },
        );
        room_to_leave.leave().await?;
    } else {
        tracing::debug!("refusing to leave, not the owner");
        nie_zesraj_się(&event, &room).await?;
    }

    Ok(())
}

/* https://github.com/matrix-org/matrix-rust-sdk/blob/main/examples/autojoin/src/main.rs */
async fn on_invite(event: StrippedRoomMemberEvent, client: Client, room: Room) {
    if event.state_key != client.user_id().unwrap() {
        return;
    }

    tokio::spawn(async move {
        tracing::warn!("joining room {}", room.room_id());
        let mut delay = 2;
        while let Err(err) = room.join().await {
            if delay > 3600 {
                tracing::error!("failed to join room {}", room.room_id());
                break;
            }
            tracing::info!(
                "failed to join room {} ({err:?}), retrying in {delay} s",
                room.room_id(),
            );
            sleep(Duration::from_secs(delay)).await;
            delay *= 2;
        }
        tracing::warn!("joined room {}", room.room_id());
    });
}