697 lines
26 KiB
Rust
697 lines
26 KiB
Rust
//! # go-sbotcli-rs
|
|
//!
|
|
//! Rust wrapper around the Go `sbotcli` ScuttleButt tool ([cryptoscope/ssb](https://github.com/cryptoscope/ssb)), allowing interaction with a `gosbot` instance.
|
|
//!
|
|
//! ## Example
|
|
//!
|
|
//! ```rust
|
|
//! use go_sbotcli_rs::{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 = sbot.follow(id)?;
|
|
//! let block_ref = sbot.block(id)?;
|
|
//!
|
|
//! let invite_code = sbot.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/go-sbotcli-rs.git
|
|
//! cd peach-sbotcli
|
|
//! cargo doc --no-deps --open
|
|
//! ```
|
|
//!
|
|
//! ## License
|
|
//!
|
|
//! LGPL-3.0.
|
|
use serde_json::Value;
|
|
use serde::{Serialize, Deserialize};
|
|
use ssb_multiformats::multihash::Multihash;
|
|
use ssb_legacy_msg_data::LegacyF64;
|
|
|
|
pub mod error;
|
|
mod utils;
|
|
|
|
pub use crate::error::SbotCliError;
|
|
use std::{ffi::OsString, path::PathBuf, process::Command, result::Result};
|
|
|
|
|
|
/// 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
|
|
|
|
/// 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,
|
|
}
|
|
|
|
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 the file path to the sbotcli binary
|
|
/// * `sbot_working_dir` - an optional string slice representing a directory path where sbotcli stores its data
|
|
///
|
|
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(),
|
|
})
|
|
}
|
|
|
|
/* 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 to the blob to be added
|
|
///
|
|
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)))
|
|
}
|
|
}
|
|
|
|
/* CONTACTS */
|
|
|
|
/// 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.
|
|
///
|
|
/// 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`
|
|
/// 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
|
|
/// * `following` - a boolean representing whether to follow this account or not
|
|
/// * `blocking` - a boolean representing whether to block this account or not
|
|
///
|
|
pub fn set_relationship(&self, id: &str, following: bool, blocking: bool) -> Result<String, SbotCliError> {
|
|
let mut command = Command::new(&self.sbotcli_path);
|
|
command
|
|
.arg("publish")
|
|
.arg("contact");
|
|
if following {
|
|
command.arg("--following");
|
|
}
|
|
if blocking {
|
|
command.arg("--blocking");
|
|
}
|
|
let output = command
|
|
.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 setting relationship with peer: {}",
|
|
stderr
|
|
)))
|
|
}
|
|
}
|
|
|
|
/// Get the latest value of a current relationship with a peer.
|
|
///
|
|
/// We determine the value of the relationship by looking up the most recent contact
|
|
/// message made about that peer.
|
|
///
|
|
/// If no contact message is found, then a relationship of following=false blocking=false
|
|
/// is returned.
|
|
///
|
|
/// Calls `sbotcli bytype --limit 1 --reverse contact`.
|
|
///
|
|
/// 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
|
|
let output = Command::new(&self.sbotcli_path)
|
|
.arg("bytype")
|
|
.arg("--limit")
|
|
.arg("1")
|
|
.arg("--reverse")
|
|
.arg("contact")
|
|
.output()?;
|
|
if output.status.success() {
|
|
let stdout = String::from_utf8(output.stdout)?;
|
|
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)
|
|
} else {
|
|
let stderr = std::str::from_utf8(&output.stderr)?;
|
|
Err(SbotCliError::GetRelationship(format!(
|
|
"error getting relationship to peer: {}",
|
|
stderr
|
|
)))
|
|
}
|
|
}
|
|
|
|
/// 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)
|
|
}
|
|
|
|
/* GET ABOUT MESSAGES */
|
|
|
|
/// Get the value of an about message with the given key.
|
|
///
|
|
/// Calls `sbotcli bytype --limit 10 --reverse about`. On success: parses the `stdout` to extract the
|
|
/// `key` and returns it. On error: returns the `stderr` output with a description.
|
|
///
|
|
/// # 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> {
|
|
let output = Command::new(&self.sbotcli_path)
|
|
.arg("bytype")
|
|
.arg("--limit")
|
|
.arg("1")
|
|
.arg("--reverse")
|
|
.arg("about")
|
|
.output()?;
|
|
if output.status.success() {
|
|
// TODO: figure out how to take a stream of messages, and filter down to just ones with the value we are looking for
|
|
// this code is not real dont look at it
|
|
let stdout = String::from_utf8(output.stdout)?;
|
|
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 {
|
|
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)))
|
|
}
|
|
} 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
|
|
)))
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
///
|
|
/// 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> {
|
|
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)
|
|
}
|
|
|
|
|
|
/* 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
|
|
)))
|
|
}
|
|
}
|
|
|
|
/// 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
|
|
)))
|
|
}
|
|
}
|
|
|
|
/* 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
|
|
)))
|
|
}
|
|
}
|
|
|
|
// 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
|
|
)))
|
|
}
|
|
}
|
|
|
|
/// 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 the id (profile reference / public key)
|
|
/// of the profile being named
|
|
///
|
|
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
|
|
)))
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// 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)
|
|
}
|
|
|
|
/// 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
|
|
)))
|
|
}
|
|
}
|
|
|
|
/* 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<String, 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)?;
|
|
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 => {
|
|
Err(SbotCliError::WhoAmI("Error calling whoami: failed to capture the id value using regex".to_string()))
|
|
}
|
|
}
|
|
} 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);
|
|
}
|
|
}
|