docs, errors and basic api
This commit is contained in:
commit
97fc6cd3ff
|
@ -0,0 +1,3 @@
|
|||
notes
|
||||
/target
|
||||
Cargo.lock
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "peach-sbotcli"
|
||||
version = "0.1.0"
|
||||
authors = ["Andrew Reid <glyph@mycelial.technology>"]
|
||||
edition = "2018"
|
||||
description = "A wrapper around the Go sbotcli tool."
|
||||
homepage = "https://peachcloud.org"
|
||||
repository = "https://git.coopcloud.tech/PeachCloud/peach-sbotcli"
|
||||
readme = "README.md"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
[dependencies]
|
||||
regex = "1.5.4"
|
|
@ -0,0 +1,28 @@
|
|||
# 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
|
||||
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()?;
|
||||
```
|
||||
|
||||
## 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
|
|
@ -0,0 +1,86 @@
|
|||
use core::str::Utf8Error;
|
||||
use regex::Error as RegexError;
|
||||
use std::{error, fmt, io::Error as IoError, string::FromUtf8Error};
|
||||
|
||||
/// A custom error type encapsulating all possible errors for this library.
|
||||
/// `From` implementations are provided for external error types, allowing
|
||||
/// the `?` operator to be used on function which return `Result<_, SbotCliError>`.
|
||||
#[derive(Debug)]
|
||||
pub enum SbotCliError {
|
||||
// sbotcli errors
|
||||
Blob(String),
|
||||
Contact(String),
|
||||
GetAboutMsgs(String),
|
||||
Invite(String),
|
||||
Publish(String),
|
||||
WhoAmI(String),
|
||||
// std errors
|
||||
CommandIo(IoError),
|
||||
ConvertUtf8(FromUtf8Error),
|
||||
InvalidUtf8(Utf8Error),
|
||||
// external errors
|
||||
InvalidRegex(RegexError),
|
||||
}
|
||||
|
||||
impl error::Error for SbotCliError {}
|
||||
|
||||
impl fmt::Display for SbotCliError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
SbotCliError::Blob(ref err) => {
|
||||
write!(f, "{}", err)
|
||||
}
|
||||
SbotCliError::Contact(ref err) => {
|
||||
write!(f, "{}", err)
|
||||
}
|
||||
SbotCliError::GetAboutMsgs(ref err) => {
|
||||
write!(f, "{}", err)
|
||||
}
|
||||
SbotCliError::Invite(ref err) => {
|
||||
write!(f, "{}", err)
|
||||
}
|
||||
SbotCliError::Publish(ref err) => {
|
||||
write!(f, "{}", err)
|
||||
}
|
||||
SbotCliError::WhoAmI(ref err) => {
|
||||
write!(f, "{}", err)
|
||||
}
|
||||
SbotCliError::CommandIo(ref err) => {
|
||||
write!(f, "{}", err)
|
||||
}
|
||||
SbotCliError::ConvertUtf8(ref err) => {
|
||||
write!(f, "{}", err)
|
||||
}
|
||||
SbotCliError::InvalidUtf8(ref err) => {
|
||||
write!(f, "{}", err)
|
||||
}
|
||||
SbotCliError::InvalidRegex(ref err) => {
|
||||
write!(f, "{}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IoError> for SbotCliError {
|
||||
fn from(err: IoError) -> SbotCliError {
|
||||
SbotCliError::CommandIo(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FromUtf8Error> for SbotCliError {
|
||||
fn from(err: FromUtf8Error) -> SbotCliError {
|
||||
SbotCliError::ConvertUtf8(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Utf8Error> for SbotCliError {
|
||||
fn from(err: Utf8Error) -> SbotCliError {
|
||||
SbotCliError::InvalidUtf8(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RegexError> for SbotCliError {
|
||||
fn from(err: RegexError) -> SbotCliError {
|
||||
SbotCliError::InvalidRegex(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,408 @@
|
|||
//! # 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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
use regex::Regex;
|
||||
|
||||
use crate::SbotCliError;
|
||||
|
||||
/// Return matches for a given Regex pattern and text.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pattern` - A string slice representing a regular expression
|
||||
/// * `input` - A string slice representing the input to be matched on
|
||||
///
|
||||
pub fn regex_finder(pattern: &str, input: &str) -> Result<Option<String>, SbotCliError> {
|
||||
let re = Regex::new(pattern)?;
|
||||
let caps = re.captures(input);
|
||||
let result = caps.map(|caps| caps[1].to_string());
|
||||
|
||||
Ok(result)
|
||||
}
|
Loading…
Reference in New Issue