From 1c7a3ec39c9fcf4b77d2ec6aef8a94b9980bf0cc Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 4 Nov 2021 16:57:27 +0200 Subject: [PATCH] refactor to use Sbot object --- src/lib.rs | 711 +++++++++++++++++++++++++++++------------------------ 1 file changed, 386 insertions(+), 325 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4b4bb33..08fa036 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,15 +5,18 @@ //! ## Example //! //! ```rust -//! use peach_sbotcli::SbotCliError; +//! use peach_sbotcli::{Sbot, SbotCliError}; //! //! fn example() -> Result<(), SbotCliError> { +//! // uses default paths for `sbotcli` and `go-sbot` working directory +//! let sbot = Sbot::init(None, None)?; +//! //! let id = "@p13zSAiOpguI9nsawkGijsnMfWmFd5rlUNpzekEE+vI=.ed25519"; //! -//! let follow_ref = peach_sbotcli::follow(id)?; -//! let block_ref = peach_sbotcli::block(id)?; +//! let follow_ref = sbot.follow(id)?; +//! let block_ref = sbot.block(id)?; //! -//! let invite_code = peach_sbotcli::create_invite()?; +//! let invite_code = sbot.create_invite()?; //! //! Ok(()) //! } @@ -36,365 +39,423 @@ pub mod error; mod utils; -use std::{process::Command, result::Result}; - pub use crate::error::SbotCliError; +use std::{ffi::OsString, path::PathBuf, process::Command, result::Result}; -/* BLOBS */ - -// TODO: file an issue -// - doesn't seem to be implemented in sbotcli yet -// - unsure of input type (`file_path`) -// - unsure about using `-` to open `stdin` -// -/// Add a file to the blob store. -/// -/// Calls `sbotcli blobs add [file_path]`. On success: trims the trailing whitespace from `stdout` and returns the blob reference. On error: returns the `stderr` output with a description. -/// -/// # Arguments -/// -/// * `file_path` - A string slice representing a file path -/// -pub fn add_blob(file_path: &str) -> Result { - let output = Command::new("sbotcli") - .arg("blobs") - .arg("add") - .arg(file_path) - .output()?; - if output.status.success() { - let stdout = String::from_utf8(output.stdout)?; - let msg_ref = stdout.trim_end().to_string(); - - Ok(msg_ref) - } else { - let stderr = std::str::from_utf8(&output.stderr)?; - Err(SbotCliError::Blob(format!("Error adding blob: {}", stderr))) - } +/// An `sbotcli` instance with associated methods for querying a Go sbot server. +pub struct Sbot { + /// The path to the `sbotcli` binary. + pub sbotcli_path: OsString, + /// The working directory of the `go-sbot` instance (where the Scuttlebutt database is stored). + pub sbot_working_dir: OsString, } -/* CONTACTS */ +impl Sbot { + /// Initialise an `sbotcli` instance. Sets default path parameters for the `sbotcli` binary and + /// `go-sbot` working directory if none are provided. Alternatively, uses the provided + /// parameter(s) to define the path(s). + /// + /// # Arguments + /// + /// * `sbotcli_path` - an optional string slice representing a file path + /// * `sbot_working_dir` - an optional string slice representing a directory path + /// + pub fn init( + sbotcli_path: Option<&str>, + sbot_working_dir: Option<&str>, + ) -> Result { + // set default path for sbotcli + let mut path = PathBuf::from(r"/usr/bin/sbotcli"); -/// Follow a peer. -/// -/// Calls `sbotcli publish contact --following [id]`. On success: trims the trailing whitespace from `stdout` -/// and returns the message reference. On error: returns the `stderr` output with a description. -/// -/// # Arguments -/// -/// * `id` - A string slice representing an id (profile reference / public key) -/// -pub fn follow(id: &str) -> Result { - let output = Command::new("sbotcli") - .arg("publish") - .arg("contact") - .arg("--following") - .arg(id) - .output()?; - if output.status.success() { - let stdout = String::from_utf8(output.stdout)?; - let msg_ref = stdout.trim_end().to_string(); + // overwrite the default path if one has been provided via the `sbotcli_path` arg + if let Some(p) = sbotcli_path { + path = PathBuf::from(p); + }; - Ok(msg_ref) - } else { - let stderr = std::str::from_utf8(&output.stderr)?; - Err(SbotCliError::Contact(format!( - "Error following peer: {}", - stderr - ))) + let mut dir = PathBuf::new(); + if let Some(d) = sbot_working_dir { + // define the `sbot_working_dir` using the provided arg + dir.push(d) + } else { + // set default path for go-sbot working directory if no arg is provided + // returns `Option` + let home_dir = dirs::home_dir(); + // it's possible that the home directory cannot be determined, hence the `match` + match home_dir { + Some(home_dir_path) => { + dir.push(home_dir_path); + dir.push(".ssb-go"); + } + // return an error if the home directory could not be determined + None => return Err(SbotCliError::NoHomeDir), + } + }; + + Ok(Self { + sbotcli_path: path.into_os_string(), + sbot_working_dir: dir.into_os_string(), + }) } -} -/// Block a peer. -/// -/// Calls `sbotcli publish contact --blocking [id]`. On success: trims the trailing whitespace from `stdout` and returns the message reference. On error: returns the `stderr` output with a description. -/// -/// # Arguments -/// -/// * `id` - A string slice representing an id (profile reference / public key) -/// -pub fn block(id: &str) -> Result { - let output = Command::new("sbotcli") - .arg("publish") - .arg("contact") - .arg("--blocking") - .arg(id) - .output()?; - if output.status.success() { - let stdout = String::from_utf8(output.stdout)?; - let msg_ref = stdout.trim_end().to_string(); + /* BLOBS */ - Ok(msg_ref) - } else { - let stderr = std::str::from_utf8(&output.stderr)?; - Err(SbotCliError::Contact(format!( - "Error blocking peer: {}", - stderr - ))) + // TODO: file an issue + // - doesn't seem to be implemented in sbotcli yet + // - unsure of input type (`file_path`) + // - unsure about using `-` to open `stdin` + // + /// Add a file to the blob store. + /// + /// Calls `sbotcli blobs add [file_path]`. On success: trims the trailing whitespace from `stdout` and returns the blob reference. On error: returns the `stderr` output with a description. + /// + /// # Arguments + /// + /// * `file_path` - A string slice representing a file path + /// + pub fn add_blob(&self, file_path: &str) -> Result { + let output = Command::new(&self.sbotcli_path) + .arg("blobs") + .arg("add") + .arg(file_path) + .output()?; + if output.status.success() { + let stdout = String::from_utf8(output.stdout)?; + let msg_ref = stdout.trim_end().to_string(); + + Ok(msg_ref) + } else { + let stderr = std::str::from_utf8(&output.stderr)?; + Err(SbotCliError::Blob(format!("Error adding blob: {}", stderr))) + } } -} -/* GET NAME */ + /* CONTACTS */ -/// Return latest name assignment from `about` msgs (the name in this case is for the public key -/// associated with the local sbot instance). -/// -/// Calls `sbotcli bytype --limit 10 --reverse about`. On success: parses the `stdout` to extract the -/// `name` and returns it. On error: returns the `stderr` output with a description. -/// -pub fn get_name() -> Result, SbotCliError> { - let output = Command::new("sbotcli") - .arg("bytype") - .arg("--limit") - .arg("10") - .arg("--reverse") - .arg("about") - .output()?; - if output.status.success() { - let stdout = String::from_utf8(output.stdout)?; - let name = utils::regex_finder(r#""name": "(.*)""#, &stdout)?; + /// Follow a peer. + /// + /// Calls `sbotcli publish contact --following [id]`. On success: trims the trailing whitespace from `stdout` + /// and returns the message reference. On error: returns the `stderr` output with a description. + /// + /// # Arguments + /// + /// * `id` - A string slice representing an id (profile reference / public key) + /// + pub fn follow(&self, id: &str) -> Result { + let output = Command::new(&self.sbotcli_path) + .arg("publish") + .arg("contact") + .arg("--following") + .arg(id) + .output()?; + if output.status.success() { + let stdout = String::from_utf8(output.stdout)?; + let msg_ref = stdout.trim_end().to_string(); - Ok(name) - } else { - let stderr = std::str::from_utf8(&output.stderr)?; - // TODO: create a more generic error variant - Err(SbotCliError::GetAboutMsgs(format!( - "Error fetching about messages: {}", - stderr - ))) + Ok(msg_ref) + } else { + let stderr = std::str::from_utf8(&output.stderr)?; + Err(SbotCliError::Contact(format!( + "Error following peer: {}", + stderr + ))) + } } -} -/* INVITES */ + /// Block a peer. + /// + /// Calls `sbotcli publish contact --blocking [id]`. On success: trims the trailing whitespace from `stdout` and returns the message reference. On error: returns the `stderr` output with a description. + /// + /// # Arguments + /// + /// * `id` - A string slice representing an id (profile reference / public key) + /// + pub fn block(&self, id: &str) -> Result { + let output = Command::new(&self.sbotcli_path) + .arg("publish") + .arg("contact") + .arg("--blocking") + .arg(id) + .output()?; + if output.status.success() { + let stdout = String::from_utf8(output.stdout)?; + let msg_ref = stdout.trim_end().to_string(); -/// Accept an invite code (trigger a mutual follow with the peer who generated the invite). -/// -/// Calls `sbotcli invite accept [invite_code]`. On success: trims the trailing whitespace from `stdout` and returns the follow message reference. On error: returns the `stderr` output with a description. -/// -/// # Arguments -/// -/// * `invite` - A string slice representing an invite code -/// -pub fn accept_invite(invite: &str) -> Result { - let output = Command::new("sbotcli") - .arg("invite") - .arg("accept") - .arg(invite) - .output()?; - if output.status.success() { - let stdout = String::from_utf8(output.stdout)?; - let msg_ref = stdout.trim_end().to_string(); - Ok(msg_ref) - } else { - let stderr = std::str::from_utf8(&output.stderr)?; - Err(SbotCliError::Invite(format!( - "Error accepting invite: {}", - stderr - ))) + Ok(msg_ref) + } else { + let stderr = std::str::from_utf8(&output.stderr)?; + Err(SbotCliError::Contact(format!( + "Error blocking peer: {}", + stderr + ))) + } } -} -/// Return an invite code from go-sbot. -/// -/// Calls `sbotcli invite create`. On success: trims the trailing whitespace from `stdout` and returns the invite code. On error: returns the `stderr` output with a description. -/// -pub fn create_invite() -> Result { - let output = Command::new("sbotcli") - .arg("invite") - .arg("create") - .output()?; - if output.status.success() { - let stdout = String::from_utf8(output.stdout)?; - let invite = stdout.trim_end().to_string(); + /* GET NAME */ - Ok(invite) - } else { - let stderr = std::str::from_utf8(&output.stderr)?; - Err(SbotCliError::Invite(format!( - "Error creating invite: {}", - stderr - ))) + /// Return latest name assignment from `about` msgs (the name in this case is for the public key + /// associated with the local sbot instance). + /// + /// Calls `sbotcli bytype --limit 10 --reverse about`. On success: parses the `stdout` to extract the + /// `name` and returns it. On error: returns the `stderr` output with a description. + /// + pub fn get_name(&self) -> Result, SbotCliError> { + let output = Command::new(&self.sbotcli_path) + .arg("bytype") + .arg("--limit") + .arg("10") + .arg("--reverse") + .arg("about") + .output()?; + if output.status.success() { + let stdout = String::from_utf8(output.stdout)?; + let name = utils::regex_finder(r#""name": "(.*)""#, &stdout)?; + + Ok(name) + } else { + let stderr = std::str::from_utf8(&output.stderr)?; + // TODO: create a more generic error variant + Err(SbotCliError::GetAboutMsgs(format!( + "Error fetching about messages: {}", + stderr + ))) + } } -} -/* PUBLISH */ + /* INVITES */ -/// Publish an about message with an image. -/// -/// Calls `sbotcli publish about --image [blob_reference] [id]`. On success: trims the trailing whitespace from `stdout` and returns the message reference. On error: returns the `stderr` output with a description. -/// -/// # Arguments -/// -/// * `blob_ref` - A string slice representing a blob reference -/// * `id` - A string slice representing an id (profile reference / public key) -/// -pub fn publish_image(blob_ref: &str, id: &str) -> Result { - let output = Command::new("sbotcli") - .arg("publish") - .arg("about") - .arg("--image") - .arg(blob_ref) - .arg(id) - .output()?; - if output.status.success() { - let stdout = String::from_utf8(output.stdout)?; - let msg_ref = stdout.trim_end().to_string(); - - Ok(msg_ref) - } else { - let stderr = std::str::from_utf8(&output.stderr)?; - Err(SbotCliError::Publish(format!( - "Error publishing image: {}", - stderr - ))) + /// Accept an invite code (trigger a mutual follow with the peer who generated the invite). + /// + /// Calls `sbotcli invite accept [invite_code]`. On success: trims the trailing whitespace from `stdout` and returns the follow message reference. On error: returns the `stderr` output with a description. + /// + /// # Arguments + /// + /// * `invite` - A string slice representing an invite code + /// + pub fn accept_invite(&self, invite: &str) -> Result { + let output = Command::new(&self.sbotcli_path) + .arg("invite") + .arg("accept") + .arg(invite) + .output()?; + if output.status.success() { + let stdout = String::from_utf8(output.stdout)?; + let msg_ref = stdout.trim_end().to_string(); + Ok(msg_ref) + } else { + let stderr = std::str::from_utf8(&output.stderr)?; + Err(SbotCliError::Invite(format!( + "Error accepting invite: {}", + stderr + ))) + } } -} -// TODO: file an issue -// - doesn't seem to be implemented in sbotcli yet -// -/// Publish an about message with a description. -/// -/// Calls `sbotcli publish about --description [description] [id]`. On success: trims the trailing whitespace from `stdout` and returns the message reference. On error: returns the `stderr` output with a description. -/// -/// # Arguments -/// -/// * `description` - A string slice representing a profile description (bio) -/// * `id` - A string slice representing an id (profile reference / public key) -/// -pub fn publish_description(description: &str, id: &str) -> Result { - let output = Command::new("sbotcli") - .arg("publish") - .arg("about") - .arg("--description") - .arg(description) - .arg(id) - .output()?; - if output.status.success() { - let stdout = String::from_utf8(output.stdout)?; - let msg_ref = stdout.trim_end().to_string(); + /// Return an invite code from go-sbot. + /// + /// Calls `sbotcli invite create`. On success: trims the trailing whitespace from `stdout` and returns the invite code. On error: returns the `stderr` output with a description. + /// + pub fn create_invite(&self) -> Result { + let output = Command::new(&self.sbotcli_path) + .arg("invite") + .arg("create") + .output()?; + if output.status.success() { + let stdout = String::from_utf8(output.stdout)?; + let invite = stdout.trim_end().to_string(); - Ok(msg_ref) - } else { - let stderr = std::str::from_utf8(&output.stderr)?; - Err(SbotCliError::Publish(format!( - "Error publishing description: {}", - stderr - ))) + Ok(invite) + } else { + let stderr = std::str::from_utf8(&output.stderr)?; + Err(SbotCliError::Invite(format!( + "Error creating invite: {}", + stderr + ))) + } } -} -/// Publish an about message with a name. -/// -/// Calls `sbotcli publish about --name [name] [id]`. On success: trims the trailing whitespace from `stdout` and returns the message reference. On error: returns the `stderr` output with a description. -/// -/// # Arguments -/// -/// * `name` - A string slice representing a profile name (bio) -/// * `id` - A string slice representing an id (profile reference / public key) -/// -pub fn publish_name(id: &str, name: &str) -> Result { - let output = Command::new("sbotcli") - .arg("publish") - .arg("about") - .arg("--name") - .arg(name) - .arg(id) - .output()?; - if output.status.success() { - let stdout = String::from_utf8(output.stdout)?; - let msg_ref = stdout.trim_end().to_string(); + /* PUBLISH */ - Ok(msg_ref) - } else { - let stderr = std::str::from_utf8(&output.stderr)?; - Err(SbotCliError::Publish(format!( - "Error publishing name: {}", - stderr - ))) + /// Publish an about message with an image. + /// + /// Calls `sbotcli publish about --image [blob_reference] [id]`. On success: trims the trailing whitespace from `stdout` and returns the message reference. On error: returns the `stderr` output with a description. + /// + /// # Arguments + /// + /// * `blob_ref` - A string slice representing a blob reference + /// * `id` - A string slice representing an id (profile reference / public key) + /// + pub fn publish_image(&self, blob_ref: &str, id: &str) -> Result { + let output = Command::new(&self.sbotcli_path) + .arg("publish") + .arg("about") + .arg("--image") + .arg(blob_ref) + .arg(id) + .output()?; + if output.status.success() { + let stdout = String::from_utf8(output.stdout)?; + let msg_ref = stdout.trim_end().to_string(); + + Ok(msg_ref) + } else { + let stderr = std::str::from_utf8(&output.stderr)?; + Err(SbotCliError::Publish(format!( + "Error publishing image: {}", + stderr + ))) + } } -} -/// Publish a post (public message). -/// -/// Calls `sbotcli publish post [text]". On success: trims the trailing whitespace from `stdout` and returns the message reference. On error: returns the `stderr` output with a description. -/// -/// # Arguments -/// -/// * `text` - A string slice representing a post (public message) -/// -pub fn publish_post(text: &str) -> Result { - let output = Command::new("sbotcli") - .arg("publish") - .arg("post") - .arg(text) - .output()?; - if output.status.success() { - let stdout = String::from_utf8(output.stdout)?; - let msg_ref = stdout.trim_end().to_string(); + // TODO: file an issue + // - doesn't seem to be implemented in sbotcli yet + // + /// Publish an about message with a description. + /// + /// Calls `sbotcli publish about --description [description] [id]`. On success: trims the trailing whitespace from `stdout` and returns the message reference. On error: returns the `stderr` output with a description. + /// + /// # Arguments + /// + /// * `description` - A string slice representing a profile description (bio) + /// * `id` - A string slice representing an id (profile reference / public key) + /// + pub fn publish_description(&self, description: &str, id: &str) -> Result { + let output = Command::new(&self.sbotcli_path) + .arg("publish") + .arg("about") + .arg("--description") + .arg(description) + .arg(id) + .output()?; + if output.status.success() { + let stdout = String::from_utf8(output.stdout)?; + let msg_ref = stdout.trim_end().to_string(); - Ok(msg_ref) - } else { - let stderr = std::str::from_utf8(&output.stderr)?; - Err(SbotCliError::Publish(format!( - "Error publishing public post: {}", - stderr - ))) + Ok(msg_ref) + } else { + let stderr = std::str::from_utf8(&output.stderr)?; + Err(SbotCliError::Publish(format!( + "Error publishing description: {}", + stderr + ))) + } } -} -/// Publish a private message. Currently only supports sending the message to a single recipient -/// (multi-recipient support to be added later). -/// -/// Calls `sbotcli publish post --recps [id] [msg]`. On success: trims the trailing whitespace from `stdout` -/// and returns the message reference. On error: returns the `stderr` output with a description. -/// -/// # Arguments -/// -/// * `id` - A string slice representing an id (profile reference / public key) -/// * `msg` - A string slice representing a private message -/// -pub fn publish_private_message(id: &str, msg: &str) -> Result { - let output = Command::new("sbotcli") - .arg("publish") - .arg("post") - .arg("--recps") - .arg(id) - .arg(msg) - .output()?; - if output.status.success() { - let stdout = String::from_utf8(output.stdout)?; - let msg_ref = stdout.trim_end().to_string(); + /// Publish an about message with a name. + /// + /// Calls `sbotcli publish about --name [name] [id]`. On success: trims the trailing whitespace from `stdout` and returns the message reference. On error: returns the `stderr` output with a description. + /// + /// # Arguments + /// + /// * `name` - A string slice representing a profile name (bio) + /// * `id` - A string slice representing an id (profile reference / public key) + /// + pub fn publish_name(&self, id: &str, name: &str) -> Result { + let output = Command::new(&self.sbotcli_path) + .arg("publish") + .arg("about") + .arg("--name") + .arg(name) + .arg(id) + .output()?; + if output.status.success() { + let stdout = String::from_utf8(output.stdout)?; + let msg_ref = stdout.trim_end().to_string(); - Ok(msg_ref) - } else { - let stderr = std::str::from_utf8(&output.stderr)?; - Err(SbotCliError::Publish(format!( - "Error publishing private message: {}", - stderr - ))) + Ok(msg_ref) + } else { + let stderr = std::str::from_utf8(&output.stderr)?; + Err(SbotCliError::Publish(format!( + "Error publishing name: {}", + stderr + ))) + } } -} -/* WHOAMI */ + /// Publish a post (public message). + /// + /// Calls `sbotcli publish post [text]". On success: trims the trailing whitespace from `stdout` and returns the message reference. On error: returns the `stderr` output with a description. + /// + /// # Arguments + /// + /// * `text` - A string slice representing a post (public message) + /// + pub fn publish_post(&self, text: &str) -> Result { + let output = Command::new(&self.sbotcli_path) + .arg("publish") + .arg("post") + .arg(text) + .output()?; + if output.status.success() { + let stdout = String::from_utf8(output.stdout)?; + let msg_ref = stdout.trim_end().to_string(); -/// Return the Scuttlebutt ID from go-sbot using `whoami`. -/// -/// Calls `sbotcli call whoami`. On success: parses the `stdout` to extract the ID and returns it. -/// On error: returns the `stderr` output with a description. -/// -pub fn whoami() -> Result, SbotCliError> { - let output = Command::new("sbotcli").arg("call").arg("whoami").output()?; - if output.status.success() { - let stdout = String::from_utf8(output.stdout)?; - let id = utils::regex_finder(r#""id": "(.*)"\n"#, &stdout)?; + Ok(msg_ref) + } else { + let stderr = std::str::from_utf8(&output.stderr)?; + Err(SbotCliError::Publish(format!( + "Error publishing public post: {}", + stderr + ))) + } + } - Ok(id) - } else { - let stderr = std::str::from_utf8(&output.stderr)?; - Err(SbotCliError::WhoAmI(format!( - "Error calling whoami: {}", - stderr - ))) + /// Publish a private message. Currently only supports sending the message to a single recipient + /// (multi-recipient support to be added later). + /// + /// Calls `sbotcli publish post --recps [id] [msg]`. On success: trims the trailing whitespace from `stdout` + /// and returns the message reference. On error: returns the `stderr` output with a description. + /// + /// # Arguments + /// + /// * `id` - A string slice representing an id (profile reference / public key) + /// * `msg` - A string slice representing a private message + /// + pub fn publish_private_message(&self, id: &str, msg: &str) -> Result { + let output = Command::new(&self.sbotcli_path) + .arg("publish") + .arg("post") + .arg("--recps") + .arg(id) + .arg(msg) + .output()?; + if output.status.success() { + let stdout = String::from_utf8(output.stdout)?; + let msg_ref = stdout.trim_end().to_string(); + + Ok(msg_ref) + } else { + let stderr = std::str::from_utf8(&output.stderr)?; + Err(SbotCliError::Publish(format!( + "Error publishing private message: {}", + stderr + ))) + } + } + + /* WHOAMI */ + + /// Return the Scuttlebutt ID from go-sbot using `whoami`. + /// + /// Calls `sbotcli call whoami`. On success: parses the `stdout` to extract the ID and returns it. + /// On error: returns the `stderr` output with a description. + /// + pub fn whoami(&self) -> Result, SbotCliError> { + let output = Command::new(&self.sbotcli_path) + .arg("call") + .arg("whoami") + .output()?; + if output.status.success() { + let stdout = String::from_utf8(output.stdout)?; + let id = utils::regex_finder(r#""id": "(.*)"\n"#, &stdout)?; + + Ok(id) + } else { + let stderr = std::str::from_utf8(&output.stderr)?; + Err(SbotCliError::WhoAmI(format!( + "Error calling whoami: {}", + stderr + ))) + } } }