matrix-notify 0.4.2

A command line tool for sending messages to matrix chatrooms.
Documentation
#![forbid(unsafe_code)]

use std::fs;

use clap::arg;
use clap::crate_name;
use clap::crate_version;
use clap::Command;
use matrix_notify::api::join_room;
use matrix_notify::api::login;
use matrix_notify::api::send_message;
use matrix_notify::api::verify_in_room;
use matrix_notify::api::verify_token;
use matrix_notify::api::ApiError;
use matrix_notify::config::Config;

const CONFIG_FILE: &str = "matrix-notify.toml";

fn perform_generate() -> Result<(), ApiError> {
    if fs::metadata(CONFIG_FILE).is_ok() {
        eprintln!("{} already exists, if you intend to generate the example config, please remove this file first", CONFIG_FILE);
        return Err(ApiError::ConfigAlreadyExists);
    }
    let config = Config {
        base_url: "https://example.org".to_owned(),
        local_username: "matrix-bot".to_owned(),
        full_username: "@matrix-bot:example.org".to_owned(),
        password: Some("Plaintext password, can be omitted if you have a token already".to_owned()),
        token: Some(
            "access_token from previous api calls, remove to populate via password driven login"
                .to_owned(),
        ),
    };
    config.save(CONFIG_FILE)?;
    println!("An example config has been generated, see {}", CONFIG_FILE);

    Ok(())
}

async fn perform_send_message(room: &str, message: &str) -> Result<(), ApiError> {
    let mut config = Config::load(CONFIG_FILE)?;
    let client = reqwest::Client::new();

    let valid_token = get_token(&config, &client).await?;
    config.token = Some(valid_token);
    config.save(CONFIG_FILE)?;

    if !verify_in_room(room, &config, &client).await? {
        join_room(room, &config, &client).await?
    }

    if let Err(e) = send_message(message, room, &config, &client).await {
        eprintln!("Failed to send message: {}", e);
    }

    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), ApiError> {
    let m = Command::new(crate_name!())
        .version(crate_version!())
        .about("A command line tool for sending messages to a matrix chatroom")
        .arg(arg!(-r --room <ROOM_ID> "Room ID, typically in the format !roomid:matrix.org"))
        .arg(arg!(-m --message <MESSAGE> "Text to be sent"))
        .subcommand(Command::new("generate").about("Generates an example config file"))
        .get_matches();
    if m.subcommand_matches("generate").is_some() {
        perform_generate()
    } else {
        let room = m
            .get_one::<String>("room")
            .expect("ROOM_ID must be provided, please see --help");
        let message = m
            .get_one::<String>("message")
            .expect("MESSAGE must be provided, please see --help");
        perform_send_message(room.as_str(), message.as_str()).await
    }
}

async fn get_token(config: &Config, client: &reqwest::Client) -> Result<String, ApiError> {
    match config.token.clone() {
        Some(found_token) => match verify_token(found_token.as_str(), config, client).await {
            Ok(valid_token) => Ok(valid_token),
            Err(e) => {
                eprintln!("Failed to verify token: {}", e);
                login(config, client).await
            }
        },
        None => login(config, client).await,
    }
}