relay-cli 0.1.2

CLI interface for Relay Agents.
use anyhow::Result;
use serde::{Deserialize, Serialize};

use crate::{
    args::Args, parsers::parse_address, progress::Progress, records::get_agent_record,
    sign::sign_request, storage::Storage, transport::agent_base_url,
};
use relay_lib::prelude::{Address, InboxId, MetaEnvelope, PrivateEnvelope, e2e};

#[derive(Serialize)]
struct InboxSingleReq {
    pub inbox: Option<InboxId>,
    pub gid: String,
}

#[derive(Deserialize)]
struct InboxSingleResp {
    pub message: MetaEnvelope,
}

#[derive(Debug, Clone, clap::Parser)]
#[clap(about = "Read a message by its GID")]
pub struct ReadCmd {
    #[arg(value_parser = parse_address)]
    pub address: Address,

    pub gid: String,

    #[clap(short, long, default_value_t = false)]
    pub silent: bool,

    #[clap(short, long, default_value_t = false)]
    pub raw: bool,
}

impl ReadCmd {
    pub fn execute(&self, _: Args, storage: &mut Storage) -> Result<()> {
        let mut progress = Progress::new(self.silent || self.raw);

        progress.step("Fetching agent keys", "Fetched agent keys");
        let agent_record = get_agent_record(self.address.agent()).map_err(|_| {
            progress.abort("Failed fetch get agent record");
        })?;

        progress.step("Reading identity", "Read identity");
        let identity = storage.root.get_identity(&self.address).unwrap_or_else(|| {
            progress.abort("No identity found for the given address");
        });

        progress.step("Fetching message", "Fetched message");
        let request = sign_request(
            &self.address.canonical(),
            InboxSingleReq {
                inbox: self.address.inbox().cloned(),
                gid: self.gid.clone(),
            },
            &agent_record,
            &identity.signing_key(),
        )
        .map_err(|_| {
            progress.abort("Failed to sign messages request");
        })?;

        let resp = reqwest::blocking::Client::new()
            .get(agent_base_url(self.address.agent())?.join("/inbox/single")?)
            .json(&request)
            .send()
            .map_err(|_| {
                progress.abort("Failed to send inbox single message request");
            })?;

        progress.commit();

        if resp.status().as_u16() == 404 {
            progress.abort("Message not found");
        }

        if !resp.status().is_success() {
            let status = resp.status();
            progress.abort(&format!(
                "Failed to fetch message. Status: {}",
                status.as_u16()
            ));
        }
        let response = resp.json::<InboxSingleResp>()?;

        progress.step("Decrypting message", "Decrypted message");
        let payload = e2e::decrypt(
            &identity.static_secret(),
            &response.message.crypto,
            &response.message.payload.payload,
            &[],
        )
        .expect("Decryption failed");

        progress.step("Parsing message", "Parsed message");
        let private = serde_json::from_slice::<PrivateEnvelope>(&payload)
            .expect("Failed to parse private envelope");

        progress.step("Displaying message", "Displayed message");
        if private.content_type != "plain/text" && !self.raw {
            progress.abort(&format!(
                "Unsupported content type: {}. Pass `--raw` to see dump content.",
                private.content_type
            ));
        }

        if !self.silent && !self.raw {
            progress.nested_section(0, &format!("Message {}", response.message.gid));
            progress.nested(1, &format!("From {}", response.message.from));
            progress.nested(1, &format!("To {}", response.message.to));
            progress.nested(1, &format!("Sent at: {}", response.message.timestamp));
            progress.skip_line();
        }

        println!("{}", String::from_utf8_lossy(&private.message));

        Ok(())
    }
}