use anyhow::{Context, Result};
use serde::Serialize;
use crate::{
args::Args,
parsers::parse_address,
progress::Progress,
records::{get_agent_record, get_user_record},
sign::sign_request,
storage::Storage,
transport::agent_base_url,
};
use relay_lib::{
core::{Uuid, Version, chrono::Utc},
prelude::{Address, MetaEnvelope, PrivateEnvelope, e2e, sign},
};
#[derive(Serialize)]
struct SendReq {
pub envelope: MetaEnvelope,
}
#[derive(Debug, Clone, clap::Parser)]
#[clap(about = "Send a message to a user")]
pub struct SendCmd {
#[clap(value_parser = parse_address)]
pub address: Address,
#[clap(short, long, value_parser = parse_address)]
pub to: Address,
#[clap(short, long)]
pub message: Option<String>,
}
impl SendCmd {
pub fn execute(&self, _: Args, storage: &mut Storage) -> Result<()> {
let mut progress = Progress::new(false);
progress.step("Fetching agent keys", "Fetched agent keys");
let agent = get_agent_record(self.address.agent()).map_err(|_| {
progress.abort("Failed to fetch agent record");
})?;
progress.step("Fetching recipient keys", "Fetched recipient keys");
let recipient = get_user_record(self.to.agent(), self.to.user().clone()).map_err(|_| {
progress.abort("Failed to fetch recipient user record");
})?;
progress.step("Reading identity", "Read identity");
let identity = match storage.root.get_identity(&self.address) {
Some(id) => id.clone(),
None => {
println!("No identity found for address: {}", self.address);
return Ok(());
}
};
progress.step("Composing message", "Composed message");
let message = match self.message.clone() {
Some(msg) => msg,
None => {
progress.pause();
let message = edit::edit(
"\n# Please write your message here.\n# Lines starting with '#' will be ignored\n",
)?;
progress.resume();
message
.trim()
.split('\n')
.filter(|line| !line.starts_with('#'))
.collect::<Vec<&str>>()
.join("\n")
}
};
let private = PrivateEnvelope {
content_type: "plain/text".to_string(),
message: message.as_bytes().to_vec(),
};
progress.step("Encrypting message", "Encrypted message");
let rng = relay_lib::crypto::OsRng;
let payload = serde_json::to_vec(&private).expect("Failed to serialize payload");
let (meta, ciphertext) =
e2e::encrypt(rng, &recipient, &payload, &[]).context("encrypting")?;
progress.step("Signing message", "Signed message");
let signed = sign::sign(&ciphertext, &identity.signing_key());
let envelope = MetaEnvelope {
gid: Uuid::new_v4(),
version: Version::parse("0.0.1").unwrap(),
from: self.address.clone(),
to: self.to.clone(),
timestamp: Utc::now(),
crypto: meta,
payload: signed,
};
progress.step("Sending message", "Sent message");
let request = sign_request(
&self.address.canonical(),
SendReq { envelope },
&agent,
&identity.signing_key(),
)?;
let resp = reqwest::blocking::Client::new()
.post(agent_base_url(self.address.agent())?.join("/send/user")?)
.json(&request)
.send()
.context("Failed to send messages request")?;
progress.commit();
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 send message. Status: {}", status));
}
progress.arrow(&format!(
"Message successfully sent to `{}`",
self.to.canonical()
));
Ok(())
}
}