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(())
}
}