aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbryan newbold <bnewbold@robocracy.org>2023-02-20 02:03:17 -0800
committerbryan newbold <bnewbold@robocracy.org>2023-02-20 02:03:17 -0800
commitae9b55206d174a89a84453c7499ca87b3ce6df0f (patch)
treea9689caeae98d016123054569bf4a6ea8efd4052
parentb1b437ce1f83ebfb8808964846c964cac5e907c2 (diff)
downloadadenosine-ae9b55206d174a89a84453c7499ca87b3ce6df0f.tar.gz
adenosine-ae9b55206d174a89a84453c7499ca87b3ce6df0f.zip
cli: initial support for pretty-printing feeds
-rw-r--r--adenosine-cli/src/bin/adenosine.rs40
-rw-r--r--adenosine-cli/src/lib.rs2
-rw-r--r--adenosine-cli/src/pretty.rs98
3 files changed, 136 insertions, 4 deletions
diff --git a/adenosine-cli/src/bin/adenosine.rs b/adenosine-cli/src/bin/adenosine.rs
index 169e5d7..aae6a87 100644
--- a/adenosine-cli/src/bin/adenosine.rs
+++ b/adenosine-cli/src/bin/adenosine.rs
@@ -1,3 +1,4 @@
+use adenosine::app_bsky;
use adenosine::auth::parse_did_from_jwt;
use adenosine::created_at_now;
use adenosine::identifiers::*;
@@ -312,7 +313,7 @@ fn require_auth_did(opt: &Opt, xrpc_client: &mut XrpcClient) -> Result<Did> {
xrpc_client.auth_login(handle, passwd)?;
} else {
return Err(anyhow!(
- "command requires auth, but have neither token orhandle/password"
+ "command requires auth, but have neither token or handle/password"
));
}
xrpc_client.auth_did()
@@ -586,16 +587,47 @@ fn run(opt: Opt) -> Result<()> {
.map(|v| v.to_string())
.unwrap_or(require_auth_did(&opt, &mut xrpc_client)?.to_string());
params.insert("author".to_string(), name);
- xrpc_client.get(
+ let resp = xrpc_client.get(
&Nsid::from_str("app.bsky.feed.getAuthorFeed")?,
Some(params),
- )?
+ )?;
+ let resp = resp.ok_or(anyhow!("expected resp from getAuthorFeed"))?;
+ if atty::is(atty::Stream::Stdout) {
+ for val in resp["feed"]
+ .as_array()
+ .ok_or(anyhow!("expected feed from getAuthorFeed"))?
+ .iter()
+ {
+ let val: serde_json::Value = val.clone();
+ let fi: app_bsky::FeedPostView = serde_json::from_value(val)?;
+ pretty::pp_post_view(&fi.post)?;
+ }
+ None
+ } else {
+ Some(resp)
+ }
}
Command::Bsky {
cmd: BskyCommand::Timeline,
} => {
require_auth_did(&opt, &mut xrpc_client)?;
- xrpc_client.get(&Nsid::from_str("app.bsky.feed.getTimeline")?, None)?
+ let resp = xrpc_client.get(&Nsid::from_str("app.bsky.feed.getTimeline")?, None)?;
+ let resp = resp.ok_or(anyhow!("expected resp from getTimeline"))?;
+ if atty::is(atty::Stream::Stdout) {
+ for val in resp["feed"]
+ .as_array()
+ .ok_or(anyhow!("expected feed from getTimeline"))?
+ .iter()
+ {
+ let val: serde_json::Value = val.clone();
+ //print_result_json(Some(val.clone()))?;
+ let fi: app_bsky::FeedPostView = serde_json::from_value(val)?;
+ pretty::pp_post_view(&fi.post)?;
+ }
+ None
+ } else {
+ Some(resp)
+ }
}
Command::Bsky {
cmd: BskyCommand::Notifications,
diff --git a/adenosine-cli/src/lib.rs b/adenosine-cli/src/lib.rs
index 3eb56cc..b21ed98 100644
--- a/adenosine-cli/src/lib.rs
+++ b/adenosine-cli/src/lib.rs
@@ -6,6 +6,8 @@ use serde_json::Value;
use std::collections::HashMap;
use std::str::FromStr;
+pub mod pretty;
+
/// Represents fields/content specified on the command line.
///
/// Sort of like HTTPie. Query parameters are '==', body values (JSON) are '='. Only single-level
diff --git a/adenosine-cli/src/pretty.rs b/adenosine-cli/src/pretty.rs
new file mode 100644
index 0000000..306c056
--- /dev/null
+++ b/adenosine-cli/src/pretty.rs
@@ -0,0 +1,98 @@
+use adenosine::app_bsky::PostView;
+use anyhow::Result;
+use std::io::Write;
+use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
+
+pub fn pp_post_view(pv: &PostView) -> Result<()> {
+ let mut stdout = StandardStream::stdout(ColorChoice::Always);
+ stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)).set_bold(true))?;
+
+ write!(&mut stdout, "@{:<54.54}", pv.author.handle)?;
+ stdout.reset()?;
+ stdout.set_color(ColorSpec::new().set_dimmed(true))?;
+ writeln!(&mut stdout, "{}", pv.indexedAt)?;
+ stdout.reset()?;
+
+ write!(&mut stdout, " ")?;
+ if let Some(entities) = &pv.record.entities {
+ let mut cur: usize = 0;
+ for ent in entities {
+ write!(
+ &mut stdout,
+ "{}",
+ &pv.record.text[cur..ent.index.start as usize]
+ )?;
+ match ent.r#type.as_str() {
+ "mention" => stdout
+ .set_color(ColorSpec::new().set_fg(Some(Color::Magenta)).set_bold(true))?,
+ "hashtag" => {
+ stdout.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true))?
+ }
+ "link" => stdout.set_color(
+ ColorSpec::new()
+ .set_fg(Some(Color::Blue))
+ .set_underline(true),
+ )?,
+ _ => {}
+ }
+ write!(
+ &mut stdout,
+ "{}",
+ &pv.record.text[ent.index.start as usize..ent.index.end as usize]
+ )?;
+ stdout.reset()?;
+ cur = ent.index.end as usize;
+ }
+ writeln!(&mut stdout, "{}", &pv.record.text[cur..])?;
+ } else if !pv.record.text.is_empty() {
+ writeln!(&mut stdout, "{}", &pv.record.text)?;
+ }
+
+ if let Some(embed) = &pv.embed {
+ if let Some(ext) = &embed.external {
+ let desc = format!("{}: {}", ext.title, ext.description);
+ stdout.set_color(
+ ColorSpec::new()
+ .set_fg(Some(Color::Green))
+ .set_dimmed(true)
+ .set_underline(false),
+ )?;
+ writeln!(&mut stdout, " {:<70.70}", desc)?;
+ write!(&mut stdout, " ")?;
+ stdout.set_color(
+ ColorSpec::new()
+ .set_fg(Some(Color::Green))
+ .set_dimmed(true)
+ .set_underline(true),
+ )?;
+ writeln!(&mut stdout, "{}", &ext.uri)?;
+ stdout.reset()?;
+ }
+ if let Some(images) = &embed.images {
+ for img in images.iter() {
+ if !img.alt.is_empty() {
+ stdout.set_color(
+ ColorSpec::new()
+ .set_fg(Some(Color::Green))
+ .set_dimmed(true)
+ .set_underline(false),
+ )?;
+ writeln!(&mut stdout, " {:<70.70}", img.alt)?;
+ }
+ write!(&mut stdout, " ")?;
+ stdout.set_color(
+ ColorSpec::new()
+ .set_fg(Some(Color::Green))
+ .set_dimmed(true)
+ .set_underline(true),
+ )?;
+ writeln!(&mut stdout, "{}", &img.fullsize)?;
+ stdout.reset()?;
+ }
+ }
+ }
+
+ writeln!(&mut stdout, "\n")?;
+ stdout.reset()?;
+ Ok(())
+}