use std::path::Path;
use anyhow::Result;
use crate::history::{self, LogEntry};
use crate::state::{load_channels, load_friends};
#[derive(Debug)]
pub struct Opts {
pub recipient: Option<String>,
pub limit: usize,
pub all: bool,
pub json: bool,
}
pub fn run(home: &Path, opts: Opts) -> Result<()> {
let entries = match opts.recipient.as_deref() {
Some(handle) => entries_for_handle(home, handle)?,
None => history::read_all(home)?,
};
let total = entries.len();
let to_show: Vec<&LogEntry> = if opts.all {
entries.iter().collect()
} else {
let n = opts.limit.min(entries.len());
entries.iter().skip(entries.len() - n).collect()
};
if opts.json {
let payload = serde_json::json!({
"total": total,
"shown": to_show.len(),
"entries": to_show.iter().collect::<Vec<_>>(),
});
println!("{}", serde_json::to_string_pretty(&payload)?);
return Ok(());
}
if to_show.is_empty() {
println!("(no history)");
return Ok(());
}
for e in &to_show {
let when = format_ts(e.ts);
let arrow = if e.direction == "in" { "←" } else { "→" };
let who = e
.counterparty_handle
.clone()
.unwrap_or_else(|| short(&e.counterparty_pubkey));
match e.kind.as_str() {
"file" => {
let size = e.size.unwrap_or(0);
let saved = e.saved_to.as_deref().unwrap_or("");
let extra = if saved.is_empty() {
String::new()
} else {
format!(" → {saved}")
};
println!(
"{when} {arrow} {who} [file] {} ({size} bytes){extra}",
e.body
);
}
_ => {
println!("{when} {arrow} {who} {}", e.body);
}
}
}
if !opts.all && total > to_show.len() {
println!(
"\n({total} entries total; showing last {}; use --all for full)",
to_show.len()
);
}
Ok(())
}
fn entries_for_handle(home: &Path, handle: &str) -> Result<Vec<LogEntry>> {
let friends = load_friends(home).unwrap_or_default();
let pubkey = friends
.resolve(handle)
.or_else(|| {
if handle.len() == 43 {
Some(handle.to_string())
} else {
None
}
})
.ok_or_else(|| {
anyhow::anyhow!(
"unknown recipient `{handle}` — not a local alias and not a 43-char pubkey. \
Run `parley friends list` or send them a message first to populate the alias."
)
})?;
let channels = load_channels(home).unwrap_or_default();
let entry = channels.by_friend.get(&pubkey).ok_or_else(|| {
anyhow::anyhow!("no channel with `{handle}` yet — send them a message first")
})?;
history::read(home, &entry.channel_id)
}
fn format_ts(unix: i64) -> String {
use time::OffsetDateTime;
OffsetDateTime::from_unix_timestamp(unix)
.ok()
.and_then(|t| {
t.format(&time::format_description::well_known::Rfc3339)
.ok()
})
.unwrap_or_else(|| unix.to_string())
}
fn short(pubkey_b64: &str) -> String {
let n = pubkey_b64.len().min(8);
format!("{}…", &pubkey_b64[..n])
}