Compare commits
4 Commits
main
...
tilde-inte
Author | SHA1 | Date | |
---|---|---|---|
|
22e32a5715 | ||
|
6bbfb454de | ||
|
b4e2dd2683 | ||
|
2c26200867 |
799
Cargo.lock
generated
799
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@ members = [
|
||||
"peach-monitor",
|
||||
"peach-stats",
|
||||
"peach-jsonrpc-server",
|
||||
"peach-dyndns-updater"
|
||||
"peach-dyndns-updater",
|
||||
"tilde-client"
|
||||
]
|
||||
|
||||
|
@ -9,10 +9,12 @@ async-std = "1.10"
|
||||
chrono = "0.4"
|
||||
dirs = "4.0"
|
||||
fslock="0.1"
|
||||
golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi.git" }
|
||||
kuska-ssb = { git = "https://github.com/Kuska-ssb/ssb" }
|
||||
tilde-client = { path = "../tilde-client" }
|
||||
jsonrpc-client-core = "0.5"
|
||||
jsonrpc-client-http = "0.5"
|
||||
jsonrpc-core = "8.0"
|
||||
jsonrpc_client = "0.7"
|
||||
log = "0.4"
|
||||
nanorand = { version = "0.6", features = ["getrandom"] }
|
||||
regex = "1"
|
||||
@ -22,3 +24,4 @@ serde_yaml = "0.8"
|
||||
toml = "0.5"
|
||||
sha3 = "0.10"
|
||||
lazy_static = "1.4"
|
||||
anyhow = "1.0.86"
|
||||
|
@ -59,8 +59,8 @@ pub fn get_peach_config_defaults() -> HashMap<String, String> {
|
||||
("SSB_ADMIN_IDS", ""),
|
||||
("ADMIN_PASSWORD_HASH", "47"),
|
||||
("TEMPORARY_PASSWORD_HASH", ""),
|
||||
("GO_SBOT_DATADIR", "/home/peach/.ssb-go"),
|
||||
("GO_SBOT_SERVICE", "go-sbot.service"),
|
||||
("TILDE_SBOT_DATADIR", "/home/notplants/.local/share/tildefriends/"),
|
||||
("TILDE_SBOT_SERVICE", "tilde-sbot.service"),
|
||||
("PEACH_CONFIGDIR", "/var/lib/peachcloud"),
|
||||
("PEACH_HOMEDIR", "/home/peach"),
|
||||
("PEACH_WEBDIR", "/usr/share/peach-web"),
|
||||
|
@ -1,9 +1,9 @@
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! Error handling for various aspects of the PeachCloud system, including the network, OLED, stats and dyndns JSON-RPC clients, as well as the configuration manager, sbot client and password utilities.
|
||||
|
||||
use golgi::GolgiError;
|
||||
use std::{io, str, string};
|
||||
use jsonrpc_client::JsonRpcError;
|
||||
use anyhow::Error; // Add the anyhow crate for errors
|
||||
|
||||
/// This type represents all possible errors that can occur when interacting with the PeachCloud library.
|
||||
#[derive(Debug)]
|
||||
@ -104,8 +104,15 @@ pub enum PeachError {
|
||||
path: String,
|
||||
},
|
||||
|
||||
/// Represents a Golgi error
|
||||
Golgi(GolgiError),
|
||||
/// Represents a JsonRpcError with Solar
|
||||
JsonRpcError(JsonRpcError),
|
||||
|
||||
/// Represents an Anyhow error with Solar
|
||||
SolarClientError(String),
|
||||
|
||||
/// Represents an error with encoding or decoding an SsbMessage
|
||||
SsbMessageError(String),
|
||||
|
||||
}
|
||||
|
||||
impl std::error::Error for PeachError {
|
||||
@ -134,7 +141,9 @@ impl std::error::Error for PeachError {
|
||||
PeachError::Utf8ToStr(_) => None,
|
||||
PeachError::Utf8ToString(_) => None,
|
||||
PeachError::Write { ref source, .. } => Some(source),
|
||||
PeachError::Golgi(_) => None,
|
||||
PeachError::JsonRpcError(_) => None,
|
||||
PeachError::SolarClientError(_) => None,
|
||||
PeachError::SsbMessageError(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -192,7 +201,9 @@ impl std::fmt::Display for PeachError {
|
||||
PeachError::Write { ref path, .. } => {
|
||||
write!(f, "Write error: {}", path)
|
||||
}
|
||||
PeachError::Golgi(ref err) => err.fmt(f),
|
||||
PeachError::JsonRpcError(ref err) => err.fmt(f),
|
||||
PeachError::SolarClientError(ref err) => err.fmt(f),
|
||||
PeachError::SsbMessageError(ref err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -263,8 +274,15 @@ impl From<string::FromUtf8Error> for PeachError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GolgiError> for PeachError {
|
||||
fn from(err: GolgiError) -> PeachError {
|
||||
PeachError::Golgi(err)
|
||||
impl From<JsonRpcError> for PeachError {
|
||||
fn from(err: JsonRpcError) -> PeachError {
|
||||
PeachError::JsonRpcError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for PeachError {
|
||||
fn from(error: anyhow::Error) -> Self {
|
||||
// TODO: include whole error somehow?
|
||||
PeachError::SolarClientError(error.to_string())
|
||||
}
|
||||
}
|
@ -6,9 +6,11 @@ pub mod oled_client;
|
||||
pub mod password_utils;
|
||||
pub mod sbot;
|
||||
pub mod stats_client;
|
||||
pub mod ssb_messages;
|
||||
|
||||
// re-export error types
|
||||
pub use jsonrpc_client_core;
|
||||
pub use jsonrpc_core;
|
||||
pub use serde_json;
|
||||
pub use serde_yaml;
|
||||
pub use tilde_client;
|
||||
|
@ -1,9 +1,9 @@
|
||||
use async_std::task;
|
||||
use golgi::{sbot::Keystore, Sbot};
|
||||
use log::debug;
|
||||
use nanorand::{Rng, WyRand};
|
||||
use sha3::{Digest, Sha3_256};
|
||||
|
||||
use crate::sbot::init_sbot;
|
||||
use crate::{config_manager, error::PeachError, sbot::SbotConfig};
|
||||
|
||||
/// Returns Ok(()) if the supplied password is correct,
|
||||
@ -122,22 +122,15 @@ async fn publish_private_msg(msg: &str, recipient: &str) -> Result<(), String> {
|
||||
let recipient = vec![recipient.to_string()];
|
||||
|
||||
// initialise sbot connection with ip:port and shscap from config file
|
||||
let mut sbot_client = match sbot_config {
|
||||
// TODO: panics if we pass `Some(conf.shscap)` as second arg
|
||||
Some(conf) => {
|
||||
let ip_port = conf.lis.clone();
|
||||
Sbot::init(Keystore::GoSbot, Some(ip_port), None)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
}
|
||||
None => Sbot::init(Keystore::GoSbot, None, None)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?,
|
||||
};
|
||||
let mut sbot_client = init_sbot();
|
||||
|
||||
debug!("Publishing a Scuttlebutt private message with temporary password");
|
||||
match sbot_client.publish_private(msg, recipient).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(format!("Failed to publish private message: {}", e)),
|
||||
}
|
||||
// TODO: implement publish private message in solar, and then implement this
|
||||
Err(format!("Failed to publish private message: \
|
||||
private publishing is not yet implemented in solar_client: \
|
||||
the message meant to be sent was: {}", msg))
|
||||
// match sbot_client.publish_private(msg, recipient).await {
|
||||
// Ok(_) => Ok(()),
|
||||
// Err(e) => Err(format!("Failed to publish private message: {}", e)),
|
||||
// }
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
//! Data types and associated methods for monitoring and configuring go-sbot.
|
||||
//! Data types and associated methods for monitoring and configuring solar-sbot.
|
||||
|
||||
use std::{fs, fs::File, io, io::Write, path::PathBuf, process::Command, str};
|
||||
|
||||
use golgi::{sbot::Keystore, Sbot};
|
||||
use std::os::linux::raw::ino_t;
|
||||
use tilde_client::{TildeClient, get_sbot_client};
|
||||
use log::debug;
|
||||
|
||||
use crate::config_manager;
|
||||
@ -30,7 +30,7 @@ fn dir_size(path: impl Into<PathBuf>) -> io::Result<u64> {
|
||||
|
||||
/* SBOT-RELATED TYPES AND METHODS */
|
||||
|
||||
/// go-sbot process status.
|
||||
/// solar-sbot process status.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SbotStatus {
|
||||
/// Current process state.
|
||||
@ -62,15 +62,16 @@ impl Default for SbotStatus {
|
||||
}
|
||||
|
||||
impl SbotStatus {
|
||||
/// Retrieve statistics for the go-sbot systemd process by querying `systemctl`.
|
||||
/// Retrieve statistics for the solar-sbot systemd process by querying `systemctl`.
|
||||
pub fn read() -> Result<Self, PeachError> {
|
||||
let mut status = SbotStatus::default();
|
||||
|
||||
// note this command does not need to be run as sudo
|
||||
// because non-privileged users are able to run systemctl show
|
||||
let service_name = config_manager::get_config_value("TILDE_SBOT_SERVICE")?;
|
||||
let info_output = Command::new("systemctl")
|
||||
.arg("show")
|
||||
.arg(config_manager::get_config_value("GO_SBOT_SERVICE")?)
|
||||
.arg(service_name)
|
||||
.arg("--no-page")
|
||||
.output()?;
|
||||
|
||||
@ -92,7 +93,7 @@ impl SbotStatus {
|
||||
// because non-privileged users are able to run systemctl status
|
||||
let status_output = Command::new("systemctl")
|
||||
.arg("status")
|
||||
.arg(config_manager::get_config_value("GO_SBOT_SERVICE")?)
|
||||
.arg(config_manager::get_config_value("TILDE_SBOT_SERVICE")?)
|
||||
.output()?;
|
||||
|
||||
let service_status = str::from_utf8(&status_output.stdout)?;
|
||||
@ -100,7 +101,7 @@ impl SbotStatus {
|
||||
|
||||
for line in service_status.lines() {
|
||||
// example of the output line we're looking for:
|
||||
// `Loaded: loaded (/home/glyph/.config/systemd/user/go-sbot.service; enabled; vendor
|
||||
// `Loaded: loaded (/home/glyph/.config/systemd/user/solar-sbot.service; enabled; vendor
|
||||
// preset: enabled)`
|
||||
if line.contains("Loaded:") {
|
||||
let before_boot_state = line.find(';');
|
||||
@ -130,10 +131,15 @@ impl SbotStatus {
|
||||
}
|
||||
}
|
||||
|
||||
// TOOD restore this
|
||||
// get path to blobstore
|
||||
// let blobstore_path = format!(
|
||||
// "{}/blobs/sha256",
|
||||
// config_manager::get_config_value("TILDE_SBOT_DATADIR")?
|
||||
// );
|
||||
let blobstore_path = format!(
|
||||
"{}/blobs/sha256",
|
||||
config_manager::get_config_value("GO_SBOT_DATADIR")?
|
||||
"{}",
|
||||
config_manager::get_config_value("TILDE_SBOT_DATADIR")?
|
||||
);
|
||||
|
||||
// determine the size of the blobstore directory in bytes
|
||||
@ -143,10 +149,10 @@ impl SbotStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// go-sbot configuration parameters.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
/// solar-sbot configuration parameters.
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct SbotConfig {
|
||||
pub struct Config {
|
||||
// TODO: maybe define as a Path type?
|
||||
/// Directory path for the log and indexes.
|
||||
pub repo: String,
|
||||
@ -180,7 +186,27 @@ pub struct SbotConfig {
|
||||
pub repair: bool,
|
||||
}
|
||||
|
||||
/// Default configuration values for go-sbot.
|
||||
// TODO: make this real
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct SbotConfig {
|
||||
pub repo: String,
|
||||
pub debugdir: String,
|
||||
pub shscap: String,
|
||||
pub hmac: String,
|
||||
pub hops: i8,
|
||||
pub lis: String,
|
||||
pub wslis: String,
|
||||
pub debuglis: String,
|
||||
pub localadv: bool,
|
||||
pub localdiscov: bool,
|
||||
pub enable_ebt: bool,
|
||||
pub promisc: bool,
|
||||
pub nounixsock: bool,
|
||||
pub repair: bool,
|
||||
}
|
||||
|
||||
/// Default configuration values for solar-sbot.
|
||||
impl Default for SbotConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@ -203,12 +229,12 @@ impl Default for SbotConfig {
|
||||
}
|
||||
|
||||
impl SbotConfig {
|
||||
/// Read the go-sbot `config.toml` file from file and deserialize into `SbotConfig`.
|
||||
/// Read the solar-sbot `config.toml` file from file and deserialize into `SbotConfig`.
|
||||
pub fn read() -> Result<Self, PeachError> {
|
||||
// determine path of user's go-sbot config.toml
|
||||
// determine path of user's solar-sbot config.toml
|
||||
let config_path = format!(
|
||||
"{}/config.toml",
|
||||
config_manager::get_config_value("GO_SBOT_DATADIR")?
|
||||
config_manager::get_config_value("SOLAR_SBOT_DATADIR")?
|
||||
);
|
||||
|
||||
let config_contents = fs::read_to_string(config_path)?;
|
||||
@ -218,17 +244,17 @@ impl SbotConfig {
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Write the given `SbotConfig` to the go-sbot `config.toml` file.
|
||||
/// Write the given `SbotConfig` to the solar-sbot `config.toml` file.
|
||||
pub fn write(config: SbotConfig) -> Result<(), PeachError> {
|
||||
let repo_comment = "# For details about go-sbot configuration, please visit the repo: https://github.com/cryptoscope/ssb\n".to_string();
|
||||
let repo_comment = "# For details about solar-sbot configuration, please visit the repo: https://github.com/cryptoscope/ssb\n".to_string();
|
||||
|
||||
// convert the provided `SbotConfig` instance to a string
|
||||
let config_string = toml::to_string(&config)?;
|
||||
|
||||
// determine path of user's go-sbot config.toml
|
||||
// determine path of user's solar-sbot config.toml
|
||||
let config_path = format!(
|
||||
"{}/config.toml",
|
||||
config_manager::get_config_value("GO_SBOT_DATADIR")?
|
||||
config_manager::get_config_value("SOLAR_SBOT_DATADIR")?
|
||||
);
|
||||
|
||||
// open config file for writing
|
||||
@ -245,7 +271,7 @@ impl SbotConfig {
|
||||
}
|
||||
|
||||
/// Initialise an sbot client
|
||||
pub async fn init_sbot() -> Result<Sbot, PeachError> {
|
||||
pub async fn init_sbot() -> Result<TildeClient, PeachError> {
|
||||
// read sbot config from config.toml
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
@ -253,15 +279,10 @@ pub async fn init_sbot() -> Result<Sbot, PeachError> {
|
||||
// initialise sbot connection with ip:port and shscap from config file
|
||||
let key_path = format!(
|
||||
"{}/secret",
|
||||
config_manager::get_config_value("GO_SBOT_DATADIR")?
|
||||
config_manager::get_config_value("SOLAR_SBOT_DATADIR")?
|
||||
);
|
||||
let sbot_client = match sbot_config {
|
||||
// TODO: panics if we pass `Some(conf.shscap)` as second arg
|
||||
Some(conf) => {
|
||||
let ip_port = conf.lis.clone();
|
||||
Sbot::init(Keystore::CustomGoSbot(key_path), Some(ip_port), None).await?
|
||||
}
|
||||
None => Sbot::init(Keystore::CustomGoSbot(key_path), None, None).await?,
|
||||
};
|
||||
// TODO: read this from config
|
||||
const SERVER_ADDR: &str = "http://127.0.0.1:3030";
|
||||
let sbot_client = get_sbot_client();
|
||||
Ok(sbot_client)
|
||||
}
|
||||
|
104
peach-lib/src/ssb_messages.rs
Normal file
104
peach-lib/src/ssb_messages.rs
Normal file
@ -0,0 +1,104 @@
|
||||
//! Message types and conversion methods.
|
||||
|
||||
use kuska_ssb::api::dto::content::TypedMessage;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::fmt::Debug;
|
||||
use crate::error::PeachError;
|
||||
use crate::error::PeachError::SsbMessageError;
|
||||
|
||||
/// `SsbMessageContent` is a type alias for `TypedMessage` from the `kuska_ssb` library.
|
||||
/// It is aliased in golgi to fit the naming convention of the other message
|
||||
/// types: `SsbMessageKVT` and `SsbMessageValue`.
|
||||
///
|
||||
/// See the [kuska source code](https://github.com/Kuska-ssb/ssb/blob/master/src/api/dto/content.rs#L103) for the type definition of `TypedMessage`.
|
||||
pub type SsbMessageContent = TypedMessage;
|
||||
|
||||
/// The `value` of an SSB message (the `V` in `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)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct SsbMessageValue {
|
||||
pub previous: Option<String>,
|
||||
pub author: String,
|
||||
pub sequence: u64,
|
||||
pub timestamp: f64,
|
||||
pub hash: String,
|
||||
pub content: Value,
|
||||
pub signature: String,
|
||||
}
|
||||
|
||||
/// Message content types.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum SsbMessageContentType {
|
||||
About,
|
||||
Vote,
|
||||
Post,
|
||||
Contact,
|
||||
Unrecognized,
|
||||
}
|
||||
|
||||
impl SsbMessageValue {
|
||||
/// Get the type field of the message content as an enum, if found.
|
||||
///
|
||||
/// If no `type` field is found or the `type` field is not a string,
|
||||
/// it returns an `Err(GolgiError::ContentType)`.
|
||||
///
|
||||
/// If a `type` field is found but with an unknown string,
|
||||
/// it returns an `Ok(SsbMessageContentType::Unrecognized)`.
|
||||
pub fn get_message_type(&self) -> Result<SsbMessageContentType, PeachError> {
|
||||
let msg_type = self
|
||||
.content
|
||||
.get("type")
|
||||
.ok_or_else(|| SsbMessageError("type field not found".to_string()))?;
|
||||
let mtype_str: &str = msg_type.as_str().ok_or_else(|| {
|
||||
SsbMessageError("type field value is not a string as expected".to_string())
|
||||
})?;
|
||||
let enum_type = match mtype_str {
|
||||
"about" => SsbMessageContentType::About,
|
||||
"post" => SsbMessageContentType::Post,
|
||||
"vote" => SsbMessageContentType::Vote,
|
||||
"contact" => SsbMessageContentType::Contact,
|
||||
_ => SsbMessageContentType::Unrecognized,
|
||||
};
|
||||
Ok(enum_type)
|
||||
}
|
||||
|
||||
/// Helper function which returns `true` if this message is of the given type,
|
||||
/// and `false` if the type does not match or is not found.
|
||||
pub fn is_message_type(&self, message_type: SsbMessageContentType) -> bool {
|
||||
let self_message_type = self.get_message_type();
|
||||
match self_message_type {
|
||||
Ok(mtype) => mtype == message_type,
|
||||
Err(_err) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the content JSON value into an `SsbMessageContent` `enum`,
|
||||
/// using the `type` field as a tag to select which variant of the `enum`
|
||||
/// to deserialize into.
|
||||
///
|
||||
/// See the [Serde docs on internally-tagged enum representations](https://serde.rs/enum-representations.html#internally-tagged) for further details.
|
||||
pub fn into_ssb_message_content(self) -> Result<SsbMessageContent, PeachError> {
|
||||
let m: SsbMessageContent = serde_json::from_value(self.content)?;
|
||||
Ok(m)
|
||||
}
|
||||
}
|
||||
|
||||
/// An SSB message represented as a key-value-timestamp (`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)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct SsbMessageKVT {
|
||||
pub key: String,
|
||||
pub value: SsbMessageValue,
|
||||
pub timestamp: Option<f64>,
|
||||
pub rts: Option<f64>,
|
||||
}
|
@ -33,13 +33,12 @@ travis-ci = { repository = "peachcloud/peach-web", branch = "master" }
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[dependencies]
|
||||
async-std = "1.10"
|
||||
async-std = { version = "1", features=["attributes", "tokio1"] }
|
||||
base64 = "0.13"
|
||||
chrono = "0.4"
|
||||
dirs = "4.0"
|
||||
env_logger = "0.8"
|
||||
futures = "0.3"
|
||||
golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi.git" }
|
||||
lazy_static = "1.4"
|
||||
log = "0.4"
|
||||
maud = "0.23"
|
||||
@ -50,3 +49,5 @@ rouille = { version = "3.5", default-features = false }
|
||||
temporary = "0.6"
|
||||
vnstat_parse = "0.1.0"
|
||||
xdg = "2.2"
|
||||
jsonrpc_client = { version = "0.7", features = ["macros", "reqwest"] }
|
||||
reqwest = "0.11.24"
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
use std::io::Error as IoError;
|
||||
|
||||
use golgi::GolgiError;
|
||||
use peach_lib::error::PeachError;
|
||||
use peach_lib::{serde_json, serde_yaml};
|
||||
use serde_json::error::Error as JsonError;
|
||||
@ -12,26 +11,26 @@ use serde_yaml::Error as YamlError;
|
||||
#[derive(Debug)]
|
||||
pub enum PeachWebError {
|
||||
FailedToRegisterDynDomain(String),
|
||||
Golgi(GolgiError),
|
||||
HomeDir,
|
||||
Io(IoError),
|
||||
Json(JsonError),
|
||||
OsString,
|
||||
PeachLib { source: PeachError, msg: String },
|
||||
Yaml(YamlError),
|
||||
NotYetImplemented,
|
||||
}
|
||||
|
||||
impl std::error::Error for PeachWebError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match *self {
|
||||
PeachWebError::FailedToRegisterDynDomain(_) => None,
|
||||
PeachWebError::Golgi(ref source) => Some(source),
|
||||
PeachWebError::HomeDir => None,
|
||||
PeachWebError::Io(ref source) => Some(source),
|
||||
PeachWebError::Json(ref source) => Some(source),
|
||||
PeachWebError::OsString => None,
|
||||
PeachWebError::PeachLib { ref source, .. } => Some(source),
|
||||
PeachWebError::Yaml(ref source) => Some(source),
|
||||
PeachWebError::NotYetImplemented => None
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -42,7 +41,6 @@ impl std::fmt::Display for PeachWebError {
|
||||
PeachWebError::FailedToRegisterDynDomain(ref msg) => {
|
||||
write!(f, "DYN DNS error: {}", msg)
|
||||
}
|
||||
PeachWebError::Golgi(ref source) => write!(f, "Golgi error: {}", source),
|
||||
PeachWebError::HomeDir => write!(
|
||||
f,
|
||||
"Filesystem error: failed to determine home directory path"
|
||||
@ -55,16 +53,11 @@ impl std::fmt::Display for PeachWebError {
|
||||
),
|
||||
PeachWebError::PeachLib { ref source, .. } => write!(f, "{}", source),
|
||||
PeachWebError::Yaml(ref source) => write!(f, "Serde YAML error: {}", source),
|
||||
PeachWebError::NotYetImplemented => write!(f, "Not yet implemented"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GolgiError> for PeachWebError {
|
||||
fn from(err: GolgiError) -> PeachWebError {
|
||||
PeachWebError::Golgi(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IoError> for PeachWebError {
|
||||
fn from(err: IoError) -> PeachWebError {
|
||||
PeachWebError::Io(err)
|
||||
|
@ -205,7 +205,7 @@ pub fn handle_form(request: &Request, restart: bool) -> Response {
|
||||
debugdir: String,
|
||||
shscap: String,
|
||||
hmac: String,
|
||||
hops: u8,
|
||||
hops: i8,
|
||||
lis_ip: String,
|
||||
lis_port: String,
|
||||
wslis: String,
|
||||
|
@ -11,29 +11,25 @@ use std::{
|
||||
use async_std::task;
|
||||
use dirs;
|
||||
use futures::stream::TryStreamExt;
|
||||
use golgi::{
|
||||
api::{friends::RelationshipQuery, history_stream::CreateHistoryStream},
|
||||
blobs,
|
||||
messages::SsbMessageKVT,
|
||||
sbot::Keystore,
|
||||
Sbot,
|
||||
};
|
||||
use log::debug;
|
||||
use peach_lib::config_manager;
|
||||
use peach_lib::sbot::SbotConfig;
|
||||
use peach_lib::sbot::init_sbot;
|
||||
use peach_lib::ssb_messages::SsbMessageKVT;
|
||||
use rouille::input::post::BufferedFile;
|
||||
use temporary::Directory;
|
||||
|
||||
use peach_lib::serde_json::json;
|
||||
use peach_lib::tilde_client::TildeClient;
|
||||
use crate::{error::PeachWebError, utils::sbot};
|
||||
|
||||
// SBOT HELPER FUNCTIONS
|
||||
|
||||
/// Executes a systemctl command for the go-sbot.service process.
|
||||
/// Executes a systemctl command for the solar-sbot.service process.
|
||||
pub fn systemctl_sbot_cmd(cmd: &str) -> Result<Output, PeachWebError> {
|
||||
let output = Command::new("sudo")
|
||||
.arg("systemctl")
|
||||
.arg(cmd)
|
||||
.arg(config_manager::get_config_value("GO_SBOT_SERVICE")?)
|
||||
.arg(config_manager::get_config_value("TILDE_SBOT_SERVICE")?)
|
||||
.output()?;
|
||||
Ok(output)
|
||||
}
|
||||
@ -41,7 +37,7 @@ pub fn systemctl_sbot_cmd(cmd: &str) -> Result<Output, PeachWebError> {
|
||||
/// Executes a systemctl stop command followed by start command.
|
||||
/// Returns a redirect with a flash message stating the output of the restart attempt.
|
||||
pub fn restart_sbot_process() -> (String, String) {
|
||||
debug!("Restarting go-sbot.service");
|
||||
debug!("Restarting solar-sbot.service");
|
||||
match systemctl_sbot_cmd("stop") {
|
||||
// if stop was successful, try to start the process
|
||||
Ok(_) => match systemctl_sbot_cmd("start") {
|
||||
@ -68,23 +64,14 @@ pub fn restart_sbot_process() -> (String, String) {
|
||||
}
|
||||
|
||||
/// Initialise an sbot client with the given configuration parameters.
|
||||
pub async fn init_sbot_with_config(
|
||||
sbot_config: &Option<SbotConfig>,
|
||||
) -> Result<Sbot, PeachWebError> {
|
||||
pub async fn init_sbot_client() -> Result<TildeClient, PeachWebError> {
|
||||
debug!("Initialising an sbot client with configuration parameters");
|
||||
// initialise sbot connection with ip:port and shscap from config file
|
||||
let key_path = format!(
|
||||
"{}/secret",
|
||||
config_manager::get_config_value("GO_SBOT_DATADIR")?
|
||||
"{}/secret.toml",
|
||||
config_manager::get_config_value("TILDE_SBOT_DATADIR")?
|
||||
);
|
||||
let sbot_client = match sbot_config {
|
||||
// TODO: panics if we pass `Some(conf.shscap)` as second arg
|
||||
Some(conf) => {
|
||||
let ip_port = conf.lis.clone();
|
||||
Sbot::init(Keystore::CustomGoSbot(key_path), Some(ip_port), None).await?
|
||||
}
|
||||
None => Sbot::init(Keystore::CustomGoSbot(key_path), None, None).await?,
|
||||
};
|
||||
let sbot_client = init_sbot().await?;
|
||||
Ok(sbot_client)
|
||||
}
|
||||
|
||||
@ -127,50 +114,53 @@ pub fn validate_public_key(public_key: &str) -> Result<(), String> {
|
||||
/// reverses the list and reads the sequence number of the most recently
|
||||
/// authored message. This gives us the size of the database in terms of
|
||||
/// the total number of locally-authored messages.
|
||||
pub fn latest_sequence_number() -> Result<u64, Box<dyn Error>> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
pub fn latest_sequence_number() -> Result<u64, PeachWebError> {
|
||||
// retrieve latest solar-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config).await?;
|
||||
let mut sbot_client = init_sbot_client().await?;
|
||||
|
||||
Err(PeachWebError::NotYetImplemented)
|
||||
|
||||
// retrieve the local id
|
||||
let id = sbot_client.whoami().await?;
|
||||
// let id = sbot_client.whoami().await?;
|
||||
|
||||
let args = CreateHistoryStream::new(id).keys_values(true, true);
|
||||
let history_stream = sbot_client.create_history_stream(args).await?;
|
||||
let mut msgs: Vec<SsbMessageKVT> = history_stream.try_collect().await?;
|
||||
// let history_stream = sbot_client.feed(&id).await?;
|
||||
|
||||
// there will be zero messages when the sbot is run for the first time
|
||||
if msgs.is_empty() {
|
||||
Ok(0)
|
||||
} else {
|
||||
// reverse the list of messages so we can easily reference the latest one
|
||||
msgs.reverse();
|
||||
|
||||
// return the sequence number of the latest msg
|
||||
Ok(msgs[0].value.sequence)
|
||||
}
|
||||
// let mut msgs: Vec<SsbMessageKVT> = history_stream.try_collect().await?;
|
||||
//
|
||||
// // there will be zero messages when the sbot is run for the first time
|
||||
// if msgs.is_empty() {
|
||||
// Ok(0)
|
||||
// } else {
|
||||
// // reverse the list of messages so we can easily reference the latest one
|
||||
// msgs.reverse();
|
||||
//
|
||||
// // return the sequence number of the latest msg
|
||||
// Ok(msgs[0].value.sequence)
|
||||
// }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_invite(uses: u16) -> Result<String, Box<dyn Error>> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
pub fn create_invite(uses: u16) -> Result<String, PeachWebError> {
|
||||
// retrieve latest solar-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config).await?;
|
||||
let mut sbot_client = init_sbot_client().await?;
|
||||
|
||||
debug!("Generating Scuttlebutt invite code");
|
||||
let mut invite_code = sbot_client.invite_create(uses).await?;
|
||||
|
||||
// insert domain into invite if one is configured
|
||||
let domain = config_manager::get_config_value("EXTERNAL_DOMAIN")?;
|
||||
if !domain.is_empty() {
|
||||
invite_code = domain + &invite_code[4..];
|
||||
}
|
||||
|
||||
Ok(invite_code)
|
||||
Err(PeachWebError::NotYetImplemented)
|
||||
// let mut invite_code = sbot_client.invite_create(uses).await?;
|
||||
//
|
||||
// // insert domain into invite if one is configured
|
||||
// let domain = config_manager::get_config_value("EXTERNAL_DOMAIN")?;
|
||||
// if !domain.is_empty() {
|
||||
// invite_code = domain + &invite_code[4..];
|
||||
// }
|
||||
//
|
||||
// Ok(invite_code)
|
||||
})
|
||||
}
|
||||
|
||||
@ -210,11 +200,9 @@ impl Profile {
|
||||
|
||||
/// Retrieve the profile info for the given public key.
|
||||
pub fn get_profile_info(ssb_id: Option<String>) -> Result<Profile, Box<dyn Error>> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config).await?;
|
||||
let sbot_client = init_sbot_client().await?;
|
||||
|
||||
let local_id = sbot_client.whoami().await?;
|
||||
|
||||
@ -226,32 +214,11 @@ pub fn get_profile_info(ssb_id: Option<String>) -> Result<Profile, Box<dyn Error
|
||||
// we are not dealing with the local profile
|
||||
profile.is_local_profile = false;
|
||||
|
||||
// determine relationship between peer and local id
|
||||
let follow_query = RelationshipQuery {
|
||||
source: local_id.clone(),
|
||||
dest: peer_id.clone(),
|
||||
};
|
||||
|
||||
// query follow state
|
||||
profile.following = match sbot_client.friends_is_following(follow_query).await {
|
||||
Ok(following) if following == "true" => Some(true),
|
||||
Ok(following) if following == "false" => Some(false),
|
||||
_ => None,
|
||||
};
|
||||
profile.following = Some(sbot_client.is_following(&local_id, &peer_id).await?);
|
||||
|
||||
// TODO: i don't like that we have to instantiate the same query object
|
||||
// twice. see if we can streamline this in golgi
|
||||
let block_query = RelationshipQuery {
|
||||
source: local_id.clone(),
|
||||
dest: peer_id.clone(),
|
||||
};
|
||||
|
||||
// query block state
|
||||
profile.blocking = match sbot_client.friends_is_blocking(block_query).await {
|
||||
Ok(blocking) if blocking == "true" => Some(true),
|
||||
Ok(blocking) if blocking == "false" => Some(false),
|
||||
_ => None,
|
||||
};
|
||||
// TODO: implement this check in solar_client so that this can be a real value
|
||||
profile.blocking = Some(false);
|
||||
|
||||
peer_id
|
||||
} else {
|
||||
@ -262,7 +229,7 @@ pub fn get_profile_info(ssb_id: Option<String>) -> Result<Profile, Box<dyn Error
|
||||
};
|
||||
|
||||
// retrieve the profile info for the given id
|
||||
let info = sbot_client.get_profile_info(&id).await?;
|
||||
let info = get_peer_info(&id).await?;
|
||||
// set each profile field accordingly
|
||||
for (key, val) in info {
|
||||
match key.as_str() {
|
||||
@ -272,26 +239,27 @@ pub fn get_profile_info(ssb_id: Option<String>) -> Result<Profile, Box<dyn Error
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// assign the ssb public key
|
||||
// (could be for the local profile or a peer)
|
||||
profile.id = Some(id);
|
||||
|
||||
// determine the path to the blob defined by the value of `profile.image`
|
||||
if let Some(ref blob_id) = profile.image {
|
||||
profile.blob_path = match blobs::get_blob_path(blob_id) {
|
||||
Ok(path) => {
|
||||
// if we get the path, check if the blob is in the blobstore.
|
||||
// this allows us to default to a placeholder image in the template
|
||||
if let Ok(exists) = blob_is_stored_locally(&path).await {
|
||||
profile.blob_exists = exists
|
||||
};
|
||||
|
||||
Some(path)
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
// TODO: blobs support
|
||||
// // determine the path to the blob defined by the value of `profile.image`
|
||||
// if let Some(ref blob_id) = profile.image {
|
||||
// profile.blob_path = match blobs::get_blob_path(blob_id) {
|
||||
// Ok(path) => {
|
||||
// // if we get the path, check if the blob is in the blobstore.
|
||||
// // this allows us to default to a placeholder image in the template
|
||||
// if let Ok(exists) = blob_is_stored_locally(&path).await {
|
||||
// profile.blob_exists = exists
|
||||
// };
|
||||
//
|
||||
// Some(path)
|
||||
// }
|
||||
// Err(_) => None,
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(profile)
|
||||
})
|
||||
@ -306,123 +274,119 @@ pub fn update_profile_info(
|
||||
new_name: Option<String>,
|
||||
new_description: Option<String>,
|
||||
image: Option<BufferedFile>,
|
||||
) -> Result<String, String> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
) -> Result<String, PeachWebError> {
|
||||
// retrieve latest solar-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mut sbot_client = init_sbot_client()
|
||||
.await?;
|
||||
|
||||
// track whether the name, description or image have been updated
|
||||
let mut name_updated: bool = false;
|
||||
let mut description_updated: bool = false;
|
||||
let mut image_updated: bool = false;
|
||||
|
||||
// check if a new_name value has been submitted in the form
|
||||
if let Some(name) = new_name {
|
||||
// only update the name if it has changed
|
||||
if name != current_name {
|
||||
debug!("Publishing a new Scuttlebutt profile name");
|
||||
if let Err(e) = sbot_client.publish_name(&name).await {
|
||||
return Err(format!("Failed to update name: {}", e));
|
||||
} else {
|
||||
name_updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(description) = new_description {
|
||||
// only update the description if it has changed
|
||||
if description != current_description {
|
||||
debug!("Publishing a new Scuttlebutt profile description");
|
||||
if let Err(e) = sbot_client.publish_description(&description).await {
|
||||
return Err(format!("Failed to update description: {}", e));
|
||||
} else {
|
||||
description_updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only update the image if a file was uploaded
|
||||
if let Some(img) = image {
|
||||
// only write the blob if it has a filename and data > 0 bytes
|
||||
if img.filename.is_some() && !img.data.is_empty() {
|
||||
match write_blob_to_store(img).await {
|
||||
Ok(blob_id) => {
|
||||
// if the file was successfully added to the blobstore,
|
||||
// publish an about image message with the blob id
|
||||
if let Err(e) = sbot_client.publish_image(&blob_id).await {
|
||||
return Err(format!("Failed to update image: {}", e));
|
||||
} else {
|
||||
image_updated = true
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(format!("Failed to add image to blobstore: {}", e)),
|
||||
}
|
||||
} else {
|
||||
image_updated = false
|
||||
}
|
||||
}
|
||||
|
||||
if name_updated || description_updated || image_updated {
|
||||
Ok("Profile updated".to_string())
|
||||
} else {
|
||||
// no updates were made but no errors were encountered either
|
||||
Ok("Profile info unchanged".to_string())
|
||||
}
|
||||
Err(PeachWebError::NotYetImplemented)
|
||||
// // track whether the name, description or image have been updated
|
||||
// let mut name_updated: bool = false;
|
||||
// let mut description_updated: bool = false;
|
||||
// let mut image_updated: bool = false;
|
||||
//
|
||||
// // check if a new_name value has been submitted in the form
|
||||
// if let Some(name) = new_name {
|
||||
// // only update the name if it has changed
|
||||
// if name != current_name {
|
||||
// debug!("Publishing a new Scuttlebutt profile name");
|
||||
// if let Err(e) = sbot_client.publish_name(&name).await {
|
||||
// return Err(format!("Failed to update name: {}", e));
|
||||
// } else {
|
||||
// name_updated = true
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if let Some(description) = new_description {
|
||||
// // only update the description if it has changed
|
||||
// if description != current_description {
|
||||
// debug!("Publishing a new Scuttlebutt profile description");
|
||||
// if let Err(e) = sbot_client.publish_description(&description).await {
|
||||
// return Err(format!("Failed to update description: {}", e));
|
||||
// } else {
|
||||
// description_updated = true
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // only update the image if a file was uploaded
|
||||
// if let Some(img) = image {
|
||||
// // only write the blob if it has a filename and data > 0 bytes
|
||||
// if img.filename.is_some() && !img.data.is_empty() {
|
||||
// match write_blob_to_store(img).await {
|
||||
// Ok(blob_id) => {
|
||||
// // if the file was successfully added to the blobstore,
|
||||
// // publish an about image message with the blob id
|
||||
// if let Err(e) = sbot_client.publish_image(&blob_id).await {
|
||||
// return Err(format!("Failed to update image: {}", e));
|
||||
// } else {
|
||||
// image_updated = true
|
||||
// }
|
||||
// }
|
||||
// Err(e) => return Err(format!("Failed to add image to blobstore: {}", e)),
|
||||
// }
|
||||
// } else {
|
||||
// image_updated = false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if name_updated || description_updated || image_updated {
|
||||
// Ok("Profile updated".to_string())
|
||||
// } else {
|
||||
// // no updates were made but no errors were encountered either
|
||||
// Ok("Profile info unchanged".to_string())
|
||||
// }
|
||||
})
|
||||
}
|
||||
|
||||
/// Follow a peer.
|
||||
pub fn follow_peer(public_key: &str) -> Result<String, String> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
pub fn follow_peer(public_key: &str) -> Result<String, PeachWebError> {
|
||||
// retrieve latest solar-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mut sbot_client = init_sbot_client()
|
||||
.await?;
|
||||
|
||||
debug!("Following a Scuttlebutt peer");
|
||||
match sbot_client.follow(public_key).await {
|
||||
Ok(_) => Ok("Followed peer".to_string()),
|
||||
Err(e) => Err(format!("Failed to follow peer: {}", e)),
|
||||
}
|
||||
Err(PeachWebError::NotYetImplemented)
|
||||
})
|
||||
}
|
||||
|
||||
/// Unfollow a peer.
|
||||
pub fn unfollow_peer(public_key: &str) -> Result<String, String> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
pub fn unfollow_peer(public_key: &str) -> Result<String, PeachWebError> {
|
||||
// retrieve latest solar-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mut sbot_client = init_sbot_client()
|
||||
.await?;
|
||||
|
||||
debug!("Unfollowing a Scuttlebutt peer");
|
||||
match sbot_client.unfollow(public_key).await {
|
||||
Ok(_) => Ok("Unfollowed peer".to_string()),
|
||||
Err(e) => Err(format!("Failed to unfollow peer: {}", e)),
|
||||
}
|
||||
Err(PeachWebError::NotYetImplemented)
|
||||
// debug!("Unfollowing a Scuttlebutt peer");
|
||||
// match sbot_client.unfollow(public_key).await {
|
||||
// Ok(_) => Ok("Unfollowed peer".to_string()),
|
||||
// Err(e) => Err(format!("Failed to unfollow peer: {}", e)),
|
||||
// }
|
||||
})
|
||||
}
|
||||
|
||||
/// Block a peer.
|
||||
pub fn block_peer(public_key: &str) -> Result<String, String> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
// retrieve latest solar-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config)
|
||||
let mut sbot_client = init_sbot_client()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
debug!("Blocking a Scuttlebutt peer");
|
||||
match sbot_client.block(public_key).await {
|
||||
match sbot_client.create_block(public_key).await {
|
||||
Ok(_) => Ok("Blocked peer".to_string()),
|
||||
Err(e) => Err(format!("Failed to block peer: {}", e)),
|
||||
}
|
||||
@ -430,176 +394,159 @@ pub fn block_peer(public_key: &str) -> Result<String, String> {
|
||||
}
|
||||
|
||||
/// Unblock a peer.
|
||||
pub fn unblock_peer(public_key: &str) -> Result<String, String> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
pub fn unblock_peer(public_key: &str) -> Result<String, PeachWebError> {
|
||||
// retrieve latest solar-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mut sbot_client = init_sbot_client()
|
||||
.await?;
|
||||
|
||||
debug!("Unblocking a Scuttlebutt peer");
|
||||
match sbot_client.unblock(public_key).await {
|
||||
Ok(_) => Ok("Unblocked peer".to_string()),
|
||||
Err(e) => Err(format!("Failed to unblock peer: {}", e)),
|
||||
}
|
||||
Err(PeachWebError::NotYetImplemented)
|
||||
// match sbot_client.unblock(public_key).await {
|
||||
// Ok(_) => Ok("Unblocked peer".to_string()),
|
||||
// Err(e) => Err(format!("Failed to unblock peer: {}", e)),
|
||||
// }
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieve a list of peers blocked by the local public key.
|
||||
pub fn get_blocks_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
// populate this vec to return
|
||||
let mut to_return: Vec<HashMap<String, String>> = Vec::new();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config).await?;
|
||||
let mut sbot_client = init_sbot_client().await?;
|
||||
|
||||
let blocks = sbot_client.get_blocks().await?;
|
||||
let self_id = sbot_client.whoami().await?;
|
||||
|
||||
// we'll use this to store the profile info for each peer whom we block
|
||||
let mut peer_list = Vec::new();
|
||||
let blocks = sbot_client.get_blocks(&self_id).await?;
|
||||
|
||||
if !blocks.is_empty() {
|
||||
for peer in blocks.iter() {
|
||||
// trim whitespace (including newline characters) and
|
||||
// remove the inverted-commas around the id
|
||||
// TODO: is this necessary?
|
||||
let key = peer.trim().replace('"', "");
|
||||
// retrieve the profile info for the given peer
|
||||
let mut peer_info = sbot_client.get_profile_info(&key).await?;
|
||||
// insert the public key of the peer into the info hashmap
|
||||
peer_info.insert("id".to_string(), key.to_string());
|
||||
// we do not even attempt to find the blob for a blocked peer,
|
||||
// since it may be vulgar to cause distress to the local peer.
|
||||
peer_info.insert("blob_exists".to_string(), "false".to_string());
|
||||
let peer_info = get_peer_info(&key).await?;
|
||||
|
||||
// push profile info to peer_list vec
|
||||
peer_list.push(peer_info)
|
||||
to_return.push(peer_info)
|
||||
}
|
||||
}
|
||||
|
||||
// return the list of blocked peers
|
||||
Ok(peer_list)
|
||||
// return the list of peers
|
||||
Ok(to_return)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
pub async fn get_peer_info(key: &str) -> Result<HashMap<String, String>, Box<dyn Error>> {
|
||||
let mut sbot_client = init_sbot_client().await?;
|
||||
// key,value dict of info about this peer
|
||||
let mut peer_info = HashMap::new();
|
||||
// retrieve the profile info for the given peer
|
||||
// TODO: get all profile info not just latest_name
|
||||
// TODO: latest_name throws an error
|
||||
// TODO: just show as "error" isntead of aborting, if some field doesn't fetch
|
||||
// let latest_name = sbot_client.latest_name(&key).await?;
|
||||
let latest_name = "latest name".to_string();
|
||||
if let Some(latest_description) = sbot_client.latest_description(&key).await.ok() {
|
||||
peer_info.insert("description".to_string(), latest_description);
|
||||
}
|
||||
// insert the public key of the peer into the info hashmap
|
||||
peer_info.insert("id".to_string(), key.to_string());
|
||||
peer_info.insert("name".to_string(), latest_name);
|
||||
// retrieve the profile image blob id for the given peer
|
||||
// TODO: blob support
|
||||
// if let Some(blob_id) = peer_info.get("image") {
|
||||
// // look-up the path for the image blob
|
||||
// if let Ok(blob_path) = blobs::get_blob_path(blob_id) {
|
||||
// // insert the image blob path of the peer into the info hashmap
|
||||
// peer_info.insert("blob_path".to_string(), blob_path.to_string());
|
||||
// // check if the blob is in the blobstore
|
||||
// // set a flag in the info hashmap
|
||||
// match blob_is_stored_locally(&blob_path).await {
|
||||
// Ok(exists) if exists => {
|
||||
// peer_info.insert("blob_exists".to_string(), "true".to_string())
|
||||
// }
|
||||
// _ => peer_info.insert("blob_exists".to_string(), "false".to_string()),
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
Ok(peer_info)
|
||||
}
|
||||
|
||||
/// Retrieve a list of peers followed by the local public key.
|
||||
pub fn get_follows_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
// populate this vec to return
|
||||
let mut to_return: Vec<HashMap<String, String>> = Vec::new();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config).await?;
|
||||
let mut sbot_client = init_sbot_client().await?;
|
||||
|
||||
let follows = sbot_client.get_follows().await?;
|
||||
let self_id = sbot_client.whoami().await?;
|
||||
|
||||
// we'll use this to store the profile info for each peer who follows us
|
||||
let mut peer_list = Vec::new();
|
||||
let follows = sbot_client.get_follows(&self_id).await?;
|
||||
|
||||
if !follows.is_empty() {
|
||||
for peer in follows.iter() {
|
||||
// trim whitespace (including newline characters) and
|
||||
// remove the inverted-commas around the id
|
||||
// TODO: is this necessary?
|
||||
let key = peer.trim().replace('"', "");
|
||||
// retrieve the profile info for the given peer
|
||||
let mut peer_info = sbot_client.get_profile_info(&key).await?;
|
||||
// insert the public key of the peer into the info hashmap
|
||||
peer_info.insert("id".to_string(), key.to_string());
|
||||
// retrieve the profile image blob id for the given peer
|
||||
if let Some(blob_id) = peer_info.get("image") {
|
||||
// look-up the path for the image blob
|
||||
if let Ok(blob_path) = blobs::get_blob_path(blob_id) {
|
||||
// insert the image blob path of the peer into the info hashmap
|
||||
peer_info.insert("blob_path".to_string(), blob_path.to_string());
|
||||
// check if the blob is in the blobstore
|
||||
// set a flag in the info hashmap
|
||||
match blob_is_stored_locally(&blob_path).await {
|
||||
Ok(exists) if exists => {
|
||||
peer_info.insert("blob_exists".to_string(), "true".to_string())
|
||||
}
|
||||
_ => peer_info.insert("blob_exists".to_string(), "false".to_string()),
|
||||
};
|
||||
}
|
||||
}
|
||||
let peer_info = get_peer_info(&key).await?;
|
||||
|
||||
// push profile info to peer_list vec
|
||||
peer_list.push(peer_info)
|
||||
to_return.push(peer_info)
|
||||
}
|
||||
}
|
||||
|
||||
// return the list of peers
|
||||
Ok(peer_list)
|
||||
Ok(to_return)
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieve a list of peers friended by the local public key.
|
||||
pub fn get_friends_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
// populate this vec to return
|
||||
let mut to_return: Vec<HashMap<String, String>> = Vec::new();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config).await?;
|
||||
let mut sbot_client = init_sbot_client().await?;
|
||||
|
||||
let local_id = sbot_client.whoami().await?;
|
||||
let self_id = sbot_client.whoami().await?;
|
||||
|
||||
let follows = sbot_client.get_follows().await?;
|
||||
let friends = sbot_client.get_friends(&self_id).await?;
|
||||
|
||||
// we'll use this to store the profile info for each friend
|
||||
let mut peer_list = Vec::new();
|
||||
|
||||
if !follows.is_empty() {
|
||||
for peer in follows.iter() {
|
||||
if !friends.is_empty() {
|
||||
for peer in friends.iter() {
|
||||
// trim whitespace (including newline characters) and
|
||||
// remove the inverted-commas around the id
|
||||
let peer_id = peer.trim().replace('"', "");
|
||||
// retrieve the profile info for the given peer
|
||||
let mut peer_info = sbot_client.get_profile_info(&peer_id).await?;
|
||||
// insert the public key of the peer into the info hashmap
|
||||
peer_info.insert("id".to_string(), peer_id.to_string());
|
||||
// retrieve the profile image blob id for the given peer
|
||||
if let Some(blob_id) = peer_info.get("image") {
|
||||
// look-up the path for the image blob
|
||||
if let Ok(blob_path) = blobs::get_blob_path(blob_id) {
|
||||
// insert the image blob path of the peer into the info hashmap
|
||||
peer_info.insert("blob_path".to_string(), blob_path.to_string());
|
||||
// check if the blob is in the blobstore
|
||||
// set a flag in the info hashmap
|
||||
match sbot::blob_is_stored_locally(&blob_path).await {
|
||||
Ok(exists) if exists => {
|
||||
peer_info.insert("blob_exists".to_string(), "true".to_string())
|
||||
}
|
||||
_ => peer_info.insert("blob_exists".to_string(), "false".to_string()),
|
||||
};
|
||||
}
|
||||
}
|
||||
// TODO: is this necessary?
|
||||
let key = peer.trim().replace('"', "");
|
||||
let peer_info = get_peer_info(&key).await?;
|
||||
|
||||
// check if the peer follows us (making us friends)
|
||||
let follow_query = RelationshipQuery {
|
||||
source: peer_id.to_string(),
|
||||
dest: local_id.clone(),
|
||||
};
|
||||
|
||||
// query follow state
|
||||
match sbot_client.friends_is_following(follow_query).await {
|
||||
Ok(following) if following == "true" => {
|
||||
// only push profile info to peer_list vec if they follow us
|
||||
peer_list.push(peer_info)
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
// push profile info to peer_list vec
|
||||
to_return.push(peer_info)
|
||||
}
|
||||
}
|
||||
|
||||
// return the list of peers
|
||||
Ok(peer_list)
|
||||
Ok(to_return)
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieve the local public key (id).
|
||||
pub fn get_local_id() -> Result<String, Box<dyn Error>> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
// retrieve latest solar-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config).await?;
|
||||
let mut sbot_client = init_sbot_client().await?;
|
||||
|
||||
let local_id = sbot_client.whoami().await?;
|
||||
|
||||
@ -609,16 +556,20 @@ pub fn get_local_id() -> Result<String, Box<dyn Error>> {
|
||||
|
||||
/// Publish a public post.
|
||||
pub fn publish_public_post(text: String) -> Result<String, String> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
// retrieve latest solar-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config)
|
||||
let mut sbot_client = init_sbot_client()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
debug!("Publishing a new Scuttlebutt public post");
|
||||
match sbot_client.publish_post(&text).await {
|
||||
let post = json!({
|
||||
"type": "post",
|
||||
"text": &text,
|
||||
});
|
||||
match sbot_client.publish(post).await {
|
||||
Ok(_) => Ok("Published post".to_string()),
|
||||
Err(e) => Err(format!("Failed to publish post: {}", e)),
|
||||
}
|
||||
@ -626,23 +577,23 @@ pub fn publish_public_post(text: String) -> Result<String, String> {
|
||||
}
|
||||
|
||||
/// Publish a private message.
|
||||
pub fn publish_private_msg(text: String, recipients: Vec<String>) -> Result<String, String> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
pub fn publish_private_msg(text: String, recipients: Vec<String>) -> Result<String, PeachWebError> {
|
||||
// retrieve latest solar-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mut sbot_client = init_sbot_client()
|
||||
.await?;
|
||||
|
||||
debug!("Publishing a new Scuttlebutt private message");
|
||||
match sbot_client
|
||||
.publish_private(text.to_string(), recipients)
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok("Published private message".to_string()),
|
||||
Err(e) => Err(format!("Failed to publish private message: {}", e)),
|
||||
}
|
||||
Err(PeachWebError::NotYetImplemented)
|
||||
// debug!("Publishing a new Scuttlebutt private message");
|
||||
// match sbot_client
|
||||
// .publish_private(text.to_string(), recipients)
|
||||
// .await
|
||||
// {
|
||||
// Ok(_) => Ok("Published private message".to_string()),
|
||||
// Err(e) => Err(format!("Failed to publish private message: {}", e)),
|
||||
// }
|
||||
})
|
||||
}
|
||||
|
||||
@ -696,20 +647,23 @@ pub async fn write_blob_to_store(image: BufferedFile) -> Result<String, PeachWeb
|
||||
let mut buffer = Vec::new();
|
||||
file.read_to_end(&mut buffer)?;
|
||||
|
||||
// hash the bytes representing the file
|
||||
let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?;
|
||||
Err(PeachWebError::NotYetImplemented)
|
||||
// TODO: not yet implemented
|
||||
|
||||
// define the blobstore path and blob filename
|
||||
let (blob_dir, blob_filename) = hex_hash.split_at(2);
|
||||
let go_ssb_path = get_go_ssb_path()?;
|
||||
let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir);
|
||||
|
||||
// create the blobstore sub-directory
|
||||
fs::create_dir_all(&blobstore_sub_dir)?;
|
||||
|
||||
// copy the file to the blobstore
|
||||
let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename);
|
||||
fs::copy(temp_path, blob_path)?;
|
||||
|
||||
Ok(blob_id)
|
||||
// // hash the bytes representing the file
|
||||
// let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?;
|
||||
//
|
||||
// // define the blobstore path and blob filename
|
||||
// let (blob_dir, blob_filename) = hex_hash.split_at(2);
|
||||
// let go_ssb_path = get_go_ssb_path()?;
|
||||
// let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir);
|
||||
//
|
||||
// // create the blobstore sub-directory
|
||||
// fs::create_dir_all(&blobstore_sub_dir)?;
|
||||
//
|
||||
// // copy the file to the blobstore
|
||||
// let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename);
|
||||
// fs::copy(temp_path, blob_path)?;
|
||||
//
|
||||
// Ok(blob_id)
|
||||
}
|
||||
|
2
tilde-client/.gitignore
vendored
Normal file
2
tilde-client/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
12
tilde-client/Cargo.toml
Normal file
12
tilde-client/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "tilde-client"
|
||||
version = "0.0.1"
|
||||
authors = ["Max Fowler <max@mfowler.info>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
async-std = "1.10"
|
||||
anyhow = "1.0.86"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.8"
|
37
tilde-client/README.md
Normal file
37
tilde-client/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# peach-lib
|
||||
|
||||

|
||||
|
||||
JSON-RPC client library for the PeachCloud ecosystem.
|
||||
|
||||
`peach-lib` offers the ability to programmatically interact with the `peach-network`, `peach-oled` and `peach-stats` microservices.
|
||||
|
||||
## Overview
|
||||
|
||||
The `peach-lib` crate bundles JSON-RPC client code for making requests to the three PeachCloud microservices which expose JSON-RPC servers (`peach-network`, `peach-oled` and `peach-menu`). The full list of available RPC APIs can be found in the READMEs of the respective microservices ([peach-network](https://github.com/peachcloud/peach-network), [peach-oled](https://github.com/peachcloud/peach-oled), [peach-menu](https://github.com/peachcloud/peach-menu)), or in the [developer documentation for PeachCloud](http://docs.peachcloud.org/software/microservices/index.html).
|
||||
|
||||
The library also includes a custom error type, `PeachError`, which bundles the underlying error types into three variants: `JsonRpcHttp`, `JsonRpcCore` and `Serde`. When used as the returned error type in a `Result` function response, this allows convenient use of the `?` operator (as illustrated in the example usage code below).
|
||||
|
||||
## Usage
|
||||
|
||||
Define the dependency in your `Cargo.toml` file:
|
||||
|
||||
`peach-lib = { git = "https://github.com/peachcloud/peach-lib", branch = "main" }`
|
||||
|
||||
Import the required client from the library:
|
||||
|
||||
```rust
|
||||
use peach_lib::network_client;
|
||||
```
|
||||
|
||||
Call one of the exposed methods:
|
||||
|
||||
```rust
|
||||
network_client::ip("wlan0")?;
|
||||
```
|
||||
|
||||
Further example usage can be found in the [`peach-menu`](https://github.com/peachcloud/peach-menu) code (see `src/states.rs`).
|
||||
|
||||
## Licensing
|
||||
|
||||
AGPL-3.0
|
18
tilde-client/src/error.rs
Normal file
18
tilde-client/src/error.rs
Normal file
@ -0,0 +1,18 @@
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
/// all tilde client errors
|
||||
#[derive(Debug)]
|
||||
pub struct TildeError {
|
||||
pub(crate) message: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for TildeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for TildeError {}
|
73
tilde-client/src/lib.rs
Normal file
73
tilde-client/src/lib.rs
Normal file
@ -0,0 +1,73 @@
|
||||
// methods for interacting with tilde sbot
|
||||
|
||||
use crate::error::TildeError;
|
||||
|
||||
use serde_json::Value;
|
||||
use std::process::{Command, exit};
|
||||
|
||||
mod error;
|
||||
|
||||
pub struct TildeClient {
|
||||
name: String,
|
||||
port: String
|
||||
}
|
||||
pub fn init_sbot() {
|
||||
println!("++ init sbot!");
|
||||
}
|
||||
|
||||
pub fn get_sbot_client() -> TildeClient {
|
||||
TildeClient {
|
||||
name: "name".to_string(),
|
||||
port: "8009".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl TildeClient {
|
||||
|
||||
pub fn run_tilde_command(&self, command: &str) -> Result<String, TildeError> {
|
||||
let output = Command::new("out/release/tildefriends.standalone")
|
||||
.arg(command)
|
||||
.output().map_err(|e| TildeError {
|
||||
message: format!("Command execution failed: {}", e),
|
||||
})?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(TildeError { message: format!("Command failed with status: {}", output.status) })
|
||||
}
|
||||
|
||||
let result = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
println!("Command output: {}", result);
|
||||
Ok(result)
|
||||
}
|
||||
pub async fn latest_description(&self, key: &str) -> Result<String, TildeError> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
pub async fn whoami(&self) -> Result<String, TildeError> {
|
||||
self.run_tilde_command("get_identity")
|
||||
}
|
||||
|
||||
pub async fn is_following(&self, from_id: &str, to_id: &str) -> Result<bool, TildeError> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
pub async fn create_block(&self, key: &str) -> Result<bool, TildeError> {
|
||||
todo!();
|
||||
}
|
||||
pub async fn get_blocks(&self, key: &str) -> Result<Vec<String>, TildeError> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
pub async fn get_follows(&self, key: &str) -> Result<Vec<String>, TildeError> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
pub async fn get_friends(&self, key: &str) -> Result<Vec<String>, TildeError> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
pub async fn publish(&self, post: Value) -> Result<Vec<String>, TildeError> {
|
||||
todo!();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user