go-sbotcli-rs/src/lib.rs

409 lines
12 KiB
Rust

//! # peach-sbotcli
//!
//! Rust wrapper around the Go `sbotcli` ScuttleButt tool ([cryptoscope/ssb](https://github.com/cryptoscope/ssb)), allowing interaction with a `gosbot` instance.
//!
//! ## Example
//!
//! ```rust
//! use peach_sbotcli::SbotCliError;
//!
//! fn example() -> Result<(), SbotCliError> {
//! let id = "@p13zSAiOpguI9nsawkGijsnMfWmFd5rlUNpzekEE+vI=.ed25519";
//!
//! let follow_ref = peach_sbotcli::follow(id)?;
//! let block_ref = peach_sbotcli::block(id)?;
//!
//! let invite_code = peach_sbotcli::create_invite()?;
//!
//! Ok(())
//! }
//! ```
//!
//! ## Documentation
//!
//! Use `cargo doc` to generate and serve the Rust documentation for this library:
//!
//! ```bash
//! git clone https://git.coopcloud.tech/PeachCloud/peach-sbotcli.git
//! cd peach-sbotcli
//! cargo doc --no-deps --open
//! ```
//!
//! ## License
//!
//! AGPL-3.0.
pub mod error;
mod utils;
use std::{process::Command, result::Result};
pub use crate::error::SbotCliError;
/* 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<String, SbotCliError> {
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)))
}
}
/* CONTACTS */
/// 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<String, SbotCliError> {
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();
Ok(msg_ref)
} else {
let stderr = std::str::from_utf8(&output.stderr)?;
Err(SbotCliError::Contact(format!(
"Error following peer: {}",
stderr
)))
}
}
/// 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<String, SbotCliError> {
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();
Ok(msg_ref)
} else {
let stderr = std::str::from_utf8(&output.stderr)?;
Err(SbotCliError::Contact(format!(
"Error blocking peer: {}",
stderr
)))
}
}
/* GET NAME */
/// 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<Option<String>, 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)?;
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
)))
}
}
/* INVITES */
/// 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<String, SbotCliError> {
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
)))
}
}
/// 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<String, SbotCliError> {
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();
Ok(invite)
} else {
let stderr = std::str::from_utf8(&output.stderr)?;
Err(SbotCliError::Invite(format!(
"Error creating invite: {}",
stderr
)))
}
}
/* PUBLISH */
/// 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<String, SbotCliError> {
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
)))
}
}
// 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<String, SbotCliError> {
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();
Ok(msg_ref)
} else {
let stderr = std::str::from_utf8(&output.stderr)?;
Err(SbotCliError::Publish(format!(
"Error publishing description: {}",
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<String, SbotCliError> {
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();
Ok(msg_ref)
} else {
let stderr = std::str::from_utf8(&output.stderr)?;
Err(SbotCliError::Publish(format!(
"Error publishing name: {}",
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<String, SbotCliError> {
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();
Ok(msg_ref)
} else {
let stderr = std::str::from_utf8(&output.stderr)?;
Err(SbotCliError::Publish(format!(
"Error publishing public post: {}",
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<String, SbotCliError> {
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();
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() -> Result<Option<String>, 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(id)
} else {
let stderr = std::str::from_utf8(&output.stderr)?;
Err(SbotCliError::WhoAmI(format!(
"Error calling whoami: {}",
stderr
)))
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}