use anyhow::{Context, 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 InboxMessagesReq {
pub inbox: Option<InboxId>,
pub start: Option<u32>,
pub amount: Option<u32>,
}
#[derive(Deserialize)]
struct InboxMessagesResp {
#[serde(rename = "inbox")]
pub _inbox: Option<InboxId>,
#[serde(rename = "start")]
pub _start: Option<u32>,
pub messages: Vec<MetaEnvelope>,
}
#[derive(Debug, Clone, clap::Parser)]
#[clap(about = "Read messages")]
pub struct MessagesCmd {
#[arg(value_parser = parse_address)]
pub address: Address,
#[clap(short, long, default_value_t = false)]
pub contents: bool,
#[clap(long, default_value_t = false)]
pub raw: bool,
}
impl MessagesCmd {
pub fn execute(&self, _: Args, storage: &mut Storage) -> Result<()> {
let mut progress = Progress::new(self.raw);
progress.step("Fetching agent keys", "Fetched agent keys");
let agent_record = get_agent_record(self.address.agent()).map_err(|_| {
progress.abort("Failed to fetch agent record");
})?;
progress.step("Reading identity", "Read identity");
let identity = match storage.root.get_identity(&self.address) {
Some(id) => id.clone(),
None => {
progress.abort("No identity found for the given address");
}
};
progress.step("Fetching messages", "Fetched messages");
let request = sign_request(
&self.address.canonical(),
InboxMessagesReq {
inbox: self.address.inbox().cloned(),
start: None,
amount: None,
},
&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/messages")?)
.json(&request)
.send()
.context("Failed to send messages request")
.map_err(|_| {
progress.abort("Failed to send messages request");
})?;
if resp.status().as_u16() == 404 {
progress.abort("Inbox not found on the Agent.");
}
if !resp.status().is_success() {
let status = resp.status();
progress.abort(&format!("Failed to list messages. Status: {}", status));
}
progress.step("Processing response", "Processed response");
let mut response = resp.json::<InboxMessagesResp>()?;
if response.messages.is_empty() && !self.raw {
progress.warn_end("No messages found.");
return Ok(());
}
progress.commit();
response.messages.sort_by_key(|m| m.timestamp);
if self.raw {
for message in response.messages {
println!("{}", message.gid);
}
return Ok(());
}
for (i, message) in response.messages.iter().enumerate() {
progress.nested_section(0, &format!("Message {}", message.gid));
progress.nested(1, &format!("From {}", message.from));
progress.nested(1, &format!("To {}", message.to));
progress.nested(1, &format!("Sent at: {}", message.timestamp));
if self.contents {
let payload = e2e::decrypt(
&identity.static_secret(),
&message.crypto,
&message.payload.payload,
&[],
)
.map_err(|_| {
progress.abort("Decryption failed");
})?;
let private =
serde_json::from_slice::<PrivateEnvelope>(&payload).map_err(|_| {
progress.abort("Failed to parse private envelope");
})?;
if private.content_type != "plain/text" {
progress.abort(&format!("Unsupported content type: {}. Read the message with the `read` command and pass in `--raw` to dump all contents.", private.content_type));
}
progress.skip_line();
println!("{}", String::from_utf8_lossy(&private.message));
if i != response.messages.len() - 1 {
progress.skip_line();
}
}
}
Ok(())
}
}