2021-11-05 11:46:35 +00:00
//! # go-sbotcli-rs
2021-11-03 13:52:35 +00:00
//!
//! Rust wrapper around the Go `sbotcli` ScuttleButt tool ([cryptoscope/ssb](https://github.com/cryptoscope/ssb)), allowing interaction with a `gosbot` instance.
//!
//! ## Example
//!
//! ```rust
2021-11-05 11:46:35 +00:00
//! use go_sbotcli_rs::{Sbot, SbotCliError};
2021-11-03 13:52:35 +00:00
//!
//! fn example() -> Result<(), SbotCliError> {
2021-11-04 14:57:27 +00:00
//! // uses default paths for `sbotcli` and `go-sbot` working directory
//! let sbot = Sbot::init(None, None)?;
//!
2021-11-03 13:52:35 +00:00
//! let id = "@p13zSAiOpguI9nsawkGijsnMfWmFd5rlUNpzekEE+vI=.ed25519";
//!
2021-11-04 14:57:27 +00:00
//! let follow_ref = sbot.follow(id)?;
//! let block_ref = sbot.block(id)?;
2021-11-03 13:52:35 +00:00
//!
2021-11-04 14:57:27 +00:00
//! let invite_code = sbot.create_invite()?;
2021-11-03 13:52:35 +00:00
//!
//! Ok(())
//! }
//! ```
//!
//! ## Documentation
//!
//! Use `cargo doc` to generate and serve the Rust documentation for this library:
//!
//! ```bash
2021-11-05 11:46:35 +00:00
//! git clone https://git.coopcloud.tech/PeachCloud/go-sbotcli-rs.git
2021-11-03 13:52:35 +00:00
//! cd peach-sbotcli
//! cargo doc --no-deps --open
//! ```
//!
//! ## License
//!
2021-11-05 11:46:35 +00:00
//! LGPL-3.0.
2021-11-16 14:41:20 +00:00
use serde_json ::Value ;
use serde ::{ Serialize , Deserialize } ;
use ssb_multiformats ::multihash ::Multihash ;
use ssb_legacy_msg_data ::LegacyF64 ;
2021-11-03 13:52:35 +00:00
pub mod error ;
mod utils ;
pub use crate ::error ::SbotCliError ;
2021-11-04 14:57:27 +00:00
use std ::{ ffi ::OsString , path ::PathBuf , process ::Command , result ::Result } ;
2021-11-16 14:41:20 +00:00
/// HELPER STRUCTS FOR GETTING AND SETTING VALUES FROM SBOT
/// A struct which represents any ssb message in the log.
///
/// Modified from https://github.com/sunrise-choir/ssb-validate.
///
/// Every ssb message in the log has a content field, as a first-level key.
/// This content field is further parsed based on the specific type of message.
///
/// Data type representing the `value` of a message object (`KVT`). More information concerning the
/// data model can be found
/// in the [`Metadata` documentation](https://spec.scuttlebutt.nz/feed/messages.html#metadata).
#[ derive(Serialize, Deserialize, Debug) ]
#[ serde(deny_unknown_fields) ]
pub struct SsbMessageValue {
pub previous : Option < Multihash > ,
pub author : String ,
pub sequence : u64 ,
pub timestamp : LegacyF64 ,
pub hash : String ,
pub content : Value ,
pub signature : String ,
}
/// A struct which represents a relationship with another user (peer).
/// Any combination of following and blocking is possible except
/// for following=true AND blocking=true.
#[ derive(Serialize, Deserialize, Debug) ]
pub struct PeerRelationship {
following : bool ,
blocking : bool
}
/// SBOT METHODS
2021-11-04 14:57:27 +00:00
/// 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 ,
}
2021-11-03 13:52:35 +00:00
2021-11-04 14:57:27 +00:00
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
///
2021-11-16 14:41:20 +00:00
/// * `sbotcli_path` - an optional string slice representing the file path to the sbotcli binary
/// * `sbot_working_dir` - an optional string slice representing a directory path where sbotcli stores its data
2021-11-04 14:57:27 +00:00
///
pub fn init (
sbotcli_path : Option < & str > ,
sbot_working_dir : Option < & str > ,
) -> Result < Self , SbotCliError > {
// set default path for sbotcli
let mut path = PathBuf ::from ( r "/usr/bin/sbotcli" ) ;
// overwrite the default path if one has been provided via the `sbotcli_path` arg
if let Some ( p ) = sbotcli_path {
path = PathBuf ::from ( p ) ;
} ;
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<PathBuf>`
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 ( ) ,
} )
2021-11-03 13:52:35 +00:00
}
2021-11-04 14:57:27 +00:00
/* 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
///
2021-11-16 14:41:20 +00:00
/// * `file_path` - A string slice representing a file path to the blob to be added
2021-11-04 14:57:27 +00:00
///
pub fn add_blob ( & self , file_path : & str ) -> Result < String , SbotCliError > {
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 ) ) )
}
2021-11-03 13:52:35 +00:00
}
2021-11-04 14:57:27 +00:00
/* CONTACTS */
2021-11-16 14:41:20 +00:00
/// Set relationship with a peer.
///
/// A relationship has two properties:
/// - following (either true or false)
/// - blocking (either true or false)
///
/// This is an idempotent function which sets the relationship with a peer for following and blocking,
/// based on the arguments that are passed in.
2021-11-04 14:57:27 +00:00
///
2021-11-16 14:41:20 +00:00
/// It uses `sbotcli publish contact [id]`. Passing the --following and/or --blocking flags,
/// if following or blocking is set to true, respectively.
///
/// On success: trims the trailing whitespace from `stdout`
2021-11-04 14:57:27 +00:00
/// and returns the message reference. On error: returns the `stderr` output with a description.
///
/// # Arguments
///
2021-11-16 14:41:20 +00:00
/// * `id` - A string slice representing an id (profile reference / public key) of the account to follow
/// * `following` - a boolean representing whether to follow this account or not
/// * `blocking` - a boolean representing whether to block this account or not
2021-11-04 14:57:27 +00:00
///
2021-11-16 14:41:20 +00:00
pub fn set_relationship ( & self , id : & str , following : bool , blocking : bool ) -> Result < String , SbotCliError > {
let mut command = Command ::new ( & self . sbotcli_path ) ;
command
2021-11-04 14:57:27 +00:00
. arg ( " publish " )
2021-11-16 14:41:20 +00:00
. arg ( " contact " ) ;
if following {
command . arg ( " --following " ) ;
}
if blocking {
command . arg ( " --blocking " ) ;
}
let output = command
2021-11-04 14:57:27 +00:00
. 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! (
2021-11-16 14:41:20 +00:00
" Error setting relationship with peer: {} " ,
2021-11-04 14:57:27 +00:00
stderr
) ) )
}
2021-11-03 13:52:35 +00:00
}
2021-11-16 14:41:20 +00:00
/// Get the latest value of a current relationship with a peer.
2021-11-04 14:57:27 +00:00
///
2021-11-16 14:41:20 +00:00
/// We determine the value of the relationship by looking up the most recent contact
/// message made about that peer.
2021-11-04 14:57:27 +00:00
///
2021-11-16 14:41:20 +00:00
/// If no contact message is found, then a relationship of following=false blocking=false
/// is returned.
2021-11-04 14:57:27 +00:00
///
2021-11-16 14:41:20 +00:00
/// Calls `sbotcli bytype --limit 1 --reverse contact`.
2021-11-04 14:57:27 +00:00
///
2021-11-16 14:41:20 +00:00
/// On success: parses a PeerRelationship from the ssb_message or returns one with default values.
/// On error: returns the `stderr` output with a description.
///
/// # arguments
///
/// * `id` - a string slice representing the id (profile reference / public key)
/// of the peer to look up the relationship with.
///
pub fn get_relationship ( & self , _id : & str ) -> Result < PeerRelationship , SbotCliError > {
// TODO: need to filter this query down to the actual user it should be for
// right now this is not a real query
2021-11-04 14:57:27 +00:00
let output = Command ::new ( & self . sbotcli_path )
2021-11-16 14:41:20 +00:00
. arg ( " bytype " )
. arg ( " --limit " )
. arg ( " 1 " )
. arg ( " --reverse " )
2021-11-04 14:57:27 +00:00
. arg ( " contact " )
. output ( ) ? ;
if output . status . success ( ) {
let stdout = String ::from_utf8 ( output . stdout ) ? ;
2021-11-16 14:41:20 +00:00
println! ( " stdout: {:?} " , stdout ) ;
let ssb_message : SsbMessageValue = serde_json ::from_str ( & stdout ) . map_err ( | err | {
SbotCliError ::GetRelationship ( format! ( " error deserializing ssb message while getting relationship to peer: {} " , err ) )
} ) ? ;
let relationship : PeerRelationship = serde_json ::from_value ( ssb_message . content ) . map_err ( | err | {
SbotCliError ::GetRelationship ( format! ( " error deserializing ssb message content while getting relationship to peer: {} " , err ) )
} ) ? ;
Ok ( relationship )
2021-11-04 14:57:27 +00:00
} else {
let stderr = std ::str ::from_utf8 ( & output . stderr ) ? ;
2021-11-16 14:41:20 +00:00
Err ( SbotCliError ::GetRelationship ( format! (
" error getting relationship to peer: {} " ,
2021-11-04 14:57:27 +00:00
stderr
) ) )
}
2021-11-03 13:52:35 +00:00
}
2021-11-16 14:41:20 +00:00
/// Follow a peer. Does not change whatever blocking relationship user already has with this peer.
///
/// 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) of the account to follow
///
pub fn follow ( & self , id : & str ) -> Result < String , SbotCliError > {
let current_relationship = self . get_relationship ( id ) ? ;
self . set_relationship ( id , true , current_relationship . blocking )
}
/// Block a peer. Does not change whatever following relationship user already has with this 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) of the account to block
///
pub fn block ( & self , id : & str ) -> Result < String , SbotCliError > {
let current_relationship = self . get_relationship ( id ) ? ;
self . set_relationship ( id , current_relationship . following , true )
}
/// Unfollow a peer. Does not change whatever blocking relationship user already has with this peer.
///
/// 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) of the account to follow
///
pub fn unfollow ( & self , id : & str ) -> Result < String , SbotCliError > {
let current_relationship = self . get_relationship ( id ) ? ;
self . set_relationship ( id , false , current_relationship . blocking )
}
/// Unblock a peer. Does not change whatever following relationship user already has with this 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) of the account to block
///
pub fn unblock ( & self , id : & str ) -> Result < String , SbotCliError > {
let current_relationship = self . get_relationship ( id ) ? ;
self . set_relationship ( id , current_relationship . following , false )
}
2021-11-15 14:14:40 +00:00
2021-11-15 11:23:48 +00:00
/* GET ABOUT MESSAGES */
2021-11-04 14:57:27 +00:00
2021-11-15 14:14:40 +00:00
/// Get the value of an about message with the given key.
2021-11-04 14:57:27 +00:00
///
/// Calls `sbotcli bytype --limit 10 --reverse about`. On success: parses the `stdout` to extract the
2021-11-15 14:14:40 +00:00
/// `key` and returns it. On error: returns the `stderr` output with a description.
2021-11-04 14:57:27 +00:00
///
2021-11-15 14:14:40 +00:00
/// # Arguments
///
/// * `key` - A string slice representing the name of they that is being retrieved
/// * `id` - A string slice representing the id (profile reference / public key)
/// of the user to get the about message from
///
pub fn get_about_message ( & self , key : & str , _id : & str ) -> Result < Option < String > , SbotCliError > {
2021-11-04 14:57:27 +00:00
let output = Command ::new ( & self . sbotcli_path )
. arg ( " bytype " )
. arg ( " --limit " )
2021-11-16 15:06:31 +00:00
. arg ( " 1 " )
2021-11-04 14:57:27 +00:00
. arg ( " --reverse " )
. arg ( " about " )
. output ( ) ? ;
if output . status . success ( ) {
2021-11-16 15:06:31 +00:00
// TODO: figure out how to take a stream of messages, and filter down to just ones with the value we are looking for
2021-11-16 15:15:50 +00:00
// this code is not real dont look at it
2021-11-04 14:57:27 +00:00
let stdout = String ::from_utf8 ( output . stdout ) ? ;
2021-11-16 15:06:31 +00:00
let ssb_message : SsbMessageValue = serde_json ::from_str ( & stdout ) . map_err ( | err | {
SbotCliError ::GetAboutMsgs ( format! ( " error deserializing ssb message while getting about message: {} " , err ) )
} ) ? ;
let value = ssb_message . content . get ( key ) ;
match value {
2021-11-16 15:15:50 +00:00
Some ( val ) = > {
let value_as_str = val . as_str ( ) ;
match value_as_str {
Some ( v ) = > Ok ( Some ( v . to_string ( ) ) ) ,
None = > Err ( SbotCliError ::GetAboutMsgs ( format! ( " error parsing {} from about message " , key ) ) )
}
} ,
None = > Err ( SbotCliError ::GetAboutMsgs ( format! ( " error parsing {} from about message " , key ) ) )
2021-11-16 15:06:31 +00:00
}
2021-11-04 14:57:27 +00:00
} 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
) ) )
}
2021-11-03 13:52:35 +00:00
}
2021-11-15 14:14:40 +00:00
/// Return latest name assignment from `about` msgs, 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 < Option < String > , SbotCliError > {
let self_id = self . whoami ( ) ? ;
self . get_about_message ( " name " , & self_id )
}
/// Return latest description assignment from `about` msgs, for the public key
/// associated with the local sbot instance.
2021-11-15 11:23:48 +00:00
///
/// Calls `sbotcli bytype --limit 10 --reverse about`. On success: parses the `stdout` to extract the
/// `description` and returns it. On error: returns the `stderr` output with a description.
///
pub fn get_description ( & self ) -> Result < Option < String > , SbotCliError > {
2021-11-15 14:14:40 +00:00
let self_id = self . whoami ( ) ? ;
self . get_about_message ( " description " , & self_id )
}
/// Return latest profile image assignment from `about` msgs, 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
/// `image` and returns it. On error: returns the `stderr` output with a description.
///
pub fn get_profile_image ( & self ) -> Result < Option < String > , SbotCliError > {
let self_id = self . whoami ( ) ? ;
self . get_about_message ( " image " , & self_id )
2021-11-15 11:23:48 +00:00
}
2021-11-04 14:57:27 +00:00
/* 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 ( & self , invite : & str ) -> Result < String , SbotCliError > {
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
) ) )
}
2021-11-03 13:52:35 +00:00
}
2021-11-04 14:57:27 +00:00
/// 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 < String , SbotCliError > {
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 ( invite )
} else {
let stderr = std ::str ::from_utf8 ( & output . stderr ) ? ;
Err ( SbotCliError ::Invite ( format! (
" Error creating invite: {} " ,
stderr
) ) )
}
2021-11-03 13:52:35 +00:00
}
2021-11-04 14:57:27 +00:00
/* 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 ( & self , blob_ref : & str , id : & str ) -> Result < String , SbotCliError > {
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
) ) )
}
2021-11-03 13:52:35 +00:00
}
2021-11-04 14:57:27 +00:00
// 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 < String , SbotCliError > {
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 description: {} " ,
stderr
) ) )
}
2021-11-03 13:52:35 +00:00
}
2021-11-04 14:57:27 +00:00
/// 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)
2021-11-15 13:15:06 +00:00
/// * `id` - A string slice representing the id (profile reference / public key)
/// of the profile being named
2021-11-04 14:57:27 +00:00
///
pub fn publish_name ( & self , id : & str , name : & str ) -> Result < String , SbotCliError > {
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 name: {} " ,
stderr
) ) )
}
2021-11-03 13:52:35 +00:00
}
2021-11-15 11:23:48 +00:00
2021-11-15 14:14:40 +00:00
2021-11-15 11:23:48 +00:00
/// Publish an about message with a name for one's own profile,
/// using whoami to find your own id.
///
/// Calls `sbotcli publish about --name [name] [self_id]`.
/// passing the id of the currently running sbot as self_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 of the new name you would like to self-identify with
///
pub fn publish_own_name ( & self , name : & str ) -> Result < String , SbotCliError > {
let self_id = & self . whoami ( ) ? ;
self . publish_name ( name , self_id )
}
/// Publish an about message with a description for one's own profile,
/// using whoami to find your own id.
///
/// Calls `sbotcli publish about --description [description] [self_id]`.
/// passing the id of the currently running sbot as self_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 of the description you would like to use
///
pub fn publish_own_description ( & self , description : & str ) -> Result < String , SbotCliError > {
let self_id = & self . whoami ( ) ? ;
self . publish_description ( description , self_id )
}
2021-11-04 14:57:27 +00:00
/// 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 < String , SbotCliError > {
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 ( ) ;
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 ( & self , id : & str , msg : & str ) -> Result < String , SbotCliError > {
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
) ) )
}
2021-11-03 13:52:35 +00:00
}
2021-11-04 14:57:27 +00:00
/* 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.
///
2021-11-15 11:23:48 +00:00
pub fn whoami ( & self ) -> Result < String , SbotCliError > {
2021-11-04 14:57:27 +00:00
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 ) ? ;
2021-11-15 11:23:48 +00:00
match id {
// if the regex matches, then return the result
Some ( id ) = > {
Ok ( id )
} ,
// if the regex does not match, then return an error
None = > {
2021-11-15 13:15:06 +00:00
Err ( SbotCliError ::WhoAmI ( " Error calling whoami: failed to capture the id value using regex " . to_string ( ) ) )
2021-11-15 11:23:48 +00:00
}
}
2021-11-04 14:57:27 +00:00
} else {
let stderr = std ::str ::from_utf8 ( & output . stderr ) ? ;
Err ( SbotCliError ::WhoAmI ( format! (
" Error calling whoami: {} " ,
stderr
) ) )
}
2021-11-03 13:52:35 +00:00
}
}
#[ cfg(test) ]
mod tests {
#[ test ]
fn it_works ( ) {
let result = 2 + 2 ;
assert_eq! ( result , 4 ) ;
}
}