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 `rcb`: generating fake RCB alerts

use pyo3::exceptions::PyAttributeError;
use pyo3::prelude::*;
use pyo3::types::PyTuple;

use crate::prelude::*;


#[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.rcb.enabled {
        return;
    }

    pyo3::prepare_freethreaded_python();

    bot.add_command(RcbCommand::command().name("!rcb"), on_cmd_rcb);
    bot.add_command(RcbCommand::command().name("!pis"), on_cmd_pis);
}


#[derive(clap::Parser, Debug)]
struct RcbCommand {
    template: Option<String>,
}

async fn on_cmd_rcb(
    arg_matches: clap::ArgMatches,
    event: OriginalSyncRoomMessageEvent,
    client: Client,
    room: Room,
    bot: Rdzobot,
) -> anyhow::Result<()> {
    on_cmd_inner("rcb", arg_matches, event, client, room, bot).await
}

async fn on_cmd_pis(
    arg_matches: clap::ArgMatches,
    event: OriginalSyncRoomMessageEvent,
    client: Client,
    room: Room,
    bot: Rdzobot,
) -> anyhow::Result<()> {
    on_cmd_inner("pis", arg_matches, event, client, room, bot).await
}

async fn on_cmd_inner(
    module: &str,
    mut arg_matches: clap::ArgMatches,
    event: OriginalSyncRoomMessageEvent,
    _client: Client,
    room: Room,
    _bot: Rdzobot,
) -> anyhow::Result<()> {
    let args = RcbCommand::from_arg_matches_mut(&mut arg_matches).unwrap();
    if let Ok(alert) = get_alert(module, args.template.as_deref()) {
        let content = RoomMessageEventContent::notice_plain(alert);
        room.send(content).await?;
    } else {
        nie_zesraj_się(&event, &room).await?;
    }
    Ok(())
}

fn get_alert_inner(py: Python, module: &str, template: Option<&str>) -> PyResult<String> {
    let render = PyModule::import(py, "alertrcb.tlib")?.getattr("render")?;
    let modname = String::from("alertrcb.templates_") + module;
    let templates = PyModule::import(py, modname.as_str())?;
    let mut attrname = String::from("TEMPLATE");
    if let Some(tname) = template {
        attrname.push('_');
        attrname.push_str(tname);
    }
    let template = templates.getattr(attrname.as_str())?;
    let args = PyTuple::new(py, &[template])?;
    let result: String = render.call1(args)?.extract()?;
    Ok(result)
}

/// Generate fake “RCB Alert”.
///
/// * `module` — python module from alertrcb package that will contain the respective template
/// * `template` — the particular template (if `None`, will use `TEMPLATE`)
pub fn get_alert(module: &str, template: Option<&str>) -> Result<String, String> {
    Python::with_gil(|py| match get_alert_inner(py, module, template) {
        Ok(result) => Ok(result),
        Err(error) if error.is_instance_of::<PyAttributeError>(py) => {
            Err("Nie zesraj się.".to_string())
        }
        Err(error) => Err(error.to_string()),
    })
}