From 623d2d22604e4aa8f89efe8932e4939ed6eaa005 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 7 Feb 2022 16:09:51 +0200 Subject: [PATCH 01/27] shorten module descriptions (remove 'golgi') --- src/error.rs | 2 +- src/messages.rs | 4 ++-- src/sbot/mod.rs | 2 +- src/utils.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/error.rs b/src/error.rs index 1c34248..da22787 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -//! Custom error type for `golgi`. +//! Custom error type. use std::io::Error as IoError; use std::str::Utf8Error; diff --git a/src/messages.rs b/src/messages.rs index 35413f2..7a52a6a 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -1,4 +1,4 @@ -//! Message types and conversion methods for `golgi`. +//! Message types and conversion methods. use kuska_ssb::api::dto::content::TypedMessage; use serde::{Deserialize, Serialize}; @@ -77,7 +77,7 @@ impl SsbMessageValue { /// using the "type" field as a tag to select which variant of the enum /// to deserialize into. /// - /// See more info on this here https://serde.rs/enum-representations.html#internally-tagged + /// 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 { let m: SsbMessageContent = serde_json::from_value(self.content)?; Ok(m) diff --git a/src/sbot/mod.rs b/src/sbot/mod.rs index a20918a..4350ecd 100644 --- a/src/sbot/mod.rs +++ b/src/sbot/mod.rs @@ -1,4 +1,4 @@ -//! This module contains the golgi API for interacting with a running go-sbot instance. +//! API for interacting with a running go-sbot instance. mod about; mod friends; mod get_subset; diff --git a/src/utils.rs b/src/utils.rs index 97f8323..de49d60 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,4 @@ -//! Utility methods for `golgi`. +//! Utility methods. use std::fmt::Debug; use async_std::{io::Read, net::TcpStream, stream::Stream}; From 518f2cbb1218167b9f63020e2d81d960603a52f7 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 7 Feb 2022 16:10:08 +0200 Subject: [PATCH 02/27] add features and extend example usage section --- src/lib.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ca26ae3..da03f9c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,24 +2,68 @@ //! # golgi //! -//! _The Golgi complex (aka. Golgi apparatus or Golgi body) packages proteins into membrane-bound vesicles inside the cell before the vesicles are sent to their destination._ +//! _The Golgi complex (aka. Golgi apparatus or Golgi body) packages proteins +//! into membrane-bound vesicles inside the cell before the vesicles are sent +//! to their destination._ //! //! ----- //! -//! Golgi is an experimental Scuttlebutt client which uses the [kuska-ssb](https://github.com/Kuska-ssb) libraries and aims to provide a high-level API for interacting with an sbot instance. Development efforts are currently oriented towards [go-sbot](https://github.com/cryptoscope/ssb) interoperability. +//! ## Introduction +//! +//! Golgi is an experimental Scuttlebutt client which uses the +//! [kuska-ssb](https://github.com/Kuska-ssb) libraries and aims to provide a +//! high-level API for interacting with an sbot instance. Development efforts +//! are currently oriented towards [go-sbot](https://github.com/cryptoscope/ssb) +//! interoperability. +//! +//! The primary means of interacting with the `golgi` library is the +//! [`Sbot`](crate::sbot::Sbot) `struct`. `Sbot` offers the ability to connect +//! to an sbot instance and exposes a number of RPC and convenience methods. +//! +//! ## Features +//! +//! `golgi` offers the ability to invoke individual RPC methods while also +//! providing a number of convenience methods which may involve multiple RPC +//! calls and / or the processing of data received from those calls. +//! +//! Features include the ability to publish messages of various kinds; to +//! retrieve messages (e.g. `about` and `description` messages) and formulate +//! queries; to follow, unfollow, block and unblock a peers; to query the social +//! graph; and to generate pub invite codes. //! //! ## Example Usage //! +//! Basic usage is demonstrated below. Visit the [examples directory](https://git.coopcloud.tech/golgi-ssb/golgi/src/branch/main/examples) in the `golgi` repository for +//! more comprehensive examples. +//! //! ```rust //! use golgi::GolgiError; //! use golgi::sbot::Sbot; //! //! pub async fn run() -> Result<(), GolgiError> { +//! // Attempt to connect to an sbot instance using the default IP address, +//! // port and network key (aka. capabilities key). //! let mut sbot_client = Sbot::connect(None, None).await?; //! +//! // Call the `whoami` RPC method to retrieve the public key for the sbot +//! // identity. //! let id = sbot_client.whoami().await?; +//! +//! // Print the public key (identity) to `stdout`. //! println!("{}", id); //! +//! // Compose an SSB post message type. +//! let post = SsbMessageContent::Post { +//! text: "Biology, eh?!".to_string(), +//! mentions: None, +//! }; +//! +//! // Publish the post. +//! let post_msg_reference = sbot_client.publish(post).await?; +//! +//! // Print the reference (sigil-link) for the published post. +//! println!("{}", post_msg_reference); +//! //! Ok(()) //! } //! ``` From 5cd2cae3efa0aa827e6e10d711183fa30baa5337 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 8 Feb 2022 09:22:59 +0200 Subject: [PATCH 03/27] refine overview docs and copy to readme --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++----- src/lib.rs | 23 +++++++++++----------- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index fe6088f..521277c 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,66 @@ # golgi -_The Golgi complex (aka. Golgi apparatus or Golgi body) packages proteins into membrane-bound vesicles inside the cell before the vesicles are sent to their destination._ +_The Golgi complex (aka. Golgi apparatus or Golgi body) packages proteins +into membrane-bound vesicles inside the cell before the vesicles are sent +to their destination._ ----- -Golgi is an experimental Scuttlebutt client which uses the [kuska-ssb](https://github.com/Kuska-ssb) libraries and aims to provide a high-level API for interacting with an sbot instance. Development efforts are currently oriented towards [go-sbot](https://github.com/cryptoscope/ssb) interoperability. +## Introduction + +Golgi is an asynchronous, experimental Scuttlebutt client that aims to +facilitate Scuttlebutt application development. It provides a high-level +API for interacting with an sbot instance and uses the +[kuska-ssb](https://github.com/Kuska-ssb) libraries to make RPC calls. +Development efforts are currently oriented towards +[go-sbot](https://github.com/cryptoscope/ssb) interoperability. + +## Features + +Golgi offers the ability to invoke individual RPC methods while also +providing a number of convenience methods which may involve multiple RPC +calls and / or the processing of data received from those calls. The +[`Sbot`](crate::sbot::Sbot) `struct` is the primary means of interacting +with the library. + +Features include the ability to publish messages of various kinds; to +retrieve messages (e.g. `about` and `description` messages) and formulate +queries; to follow, unfollow, block and unblock a peer; to query the social +graph; and to generate pub invite codes. ## Example Usage -```rust -pub async fn run() -> Result<(), GolgiError> { - let mut sbot_client = Sbot::init(None, None).await?; +Basic usage is demonstrated below. Visit the [examples directory](https://git.coopcloud.tech/golgi-ssb/golgi/src/branch/main/examples) in the `golgi` repository for +more comprehensive examples. +```rust +use golgi::GolgiError; +use golgi::sbot::Sbot; + +pub async fn run() -> Result<(), GolgiError> { + // Attempt to connect to an sbot instance using the default IP address, + // port and network key (aka. capabilities key). + let mut sbot_client = Sbot::connect(None, None).await?; + + // Call the `whoami` RPC method to retrieve the public key for the sbot + // identity. let id = sbot_client.whoami().await?; + + // Print the public key (identity) to `stdout`. println!("{}", id); + + // Compose an SSB post message type. + let post = SsbMessageContent::Post { + text: "Biology, eh?!".to_string(), + mentions: None, + }; + + // Publish the post. + let post_msg_reference = sbot_client.publish(post).await?; + + // Print the reference (sigil-link) for the published post. + println!("{}", post_msg_reference); + + Ok(()) } ``` diff --git a/src/lib.rs b/src/lib.rs index da03f9c..1fd0361 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,25 +10,24 @@ //! //! ## Introduction //! -//! Golgi is an experimental Scuttlebutt client which uses the -//! [kuska-ssb](https://github.com/Kuska-ssb) libraries and aims to provide a -//! high-level API for interacting with an sbot instance. Development efforts -//! are currently oriented towards [go-sbot](https://github.com/cryptoscope/ssb) -//! interoperability. -//! -//! The primary means of interacting with the `golgi` library is the -//! [`Sbot`](crate::sbot::Sbot) `struct`. `Sbot` offers the ability to connect -//! to an sbot instance and exposes a number of RPC and convenience methods. +//! Golgi is an asynchronous, experimental Scuttlebutt client that aims to +//! facilitate Scuttlebutt application development. It provides a high-level +//! API for interacting with an sbot instance and uses the +//! [kuska-ssb](https://github.com/Kuska-ssb) libraries to make RPC calls. +//! Development efforts are currently oriented towards +//! [go-sbot](https://github.com/cryptoscope/ssb) interoperability. //! //! ## Features //! -//! `golgi` offers the ability to invoke individual RPC methods while also +//! Golgi offers the ability to invoke individual RPC methods while also //! providing a number of convenience methods which may involve multiple RPC -//! calls and / or the processing of data received from those calls. +//! calls and / or the processing of data received from those calls. The +//! [`Sbot`](crate::sbot::Sbot) `struct` is the primary means of interacting +//! with the library. //! //! Features include the ability to publish messages of various kinds; to //! retrieve messages (e.g. `about` and `description` messages) and formulate -//! queries; to follow, unfollow, block and unblock a peers; to query the social +//! queries; to follow, unfollow, block and unblock a peer; to query the social //! graph; and to generate pub invite codes. //! //! ## Example Usage From f40cc793f065c4d694b59d6c4b077212f82bc4d3 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 8 Feb 2022 10:02:17 +0200 Subject: [PATCH 04/27] rename sbot dir to api --- src/{sbot => api}/about.rs | 6 +- src/{sbot => api}/friends.rs | 10 ++- src/{sbot => api}/get_subset.rs | 3 +- src/{sbot => api}/history_stream.rs | 7 +- src/{sbot => api}/invite.rs | 2 +- src/api/mod.rs | 12 +++ src/{sbot => api}/publish.rs | 2 +- src/api/whoami.rs | 25 ++++++ src/sbot/mod.rs | 15 ---- src/sbot/sbot_connection.rs | 135 ---------------------------- 10 files changed, 51 insertions(+), 166 deletions(-) rename src/{sbot => api}/about.rs (98%) rename src/{sbot => api}/friends.rs (95%) rename src/{sbot => api}/get_subset.rs (90%) rename src/{sbot => api}/history_stream.rs (81%) rename src/{sbot => api}/invite.rs (93%) create mode 100644 src/api/mod.rs rename src/{sbot => api}/publish.rs (98%) create mode 100644 src/api/whoami.rs delete mode 100644 src/sbot/mod.rs delete mode 100644 src/sbot/sbot_connection.rs diff --git a/src/sbot/about.rs b/src/api/about.rs similarity index 98% rename from src/sbot/about.rs rename to src/api/about.rs index 3a8837a..08b802c 100644 --- a/src/sbot/about.rs +++ b/src/api/about.rs @@ -4,12 +4,10 @@ use async_std::stream::{Stream, StreamExt}; use futures::pin_mut; use crate::{ + api::get_subset::{SubsetQuery, SubsetQueryOptions}, error::GolgiError, messages::{SsbMessageContentType, SsbMessageValue}, - sbot::{ - get_subset::{SubsetQuery, SubsetQueryOptions}, - sbot_connection::Sbot, - }, + sbot::Sbot, }; impl Sbot { diff --git a/src/sbot/friends.rs b/src/api/friends.rs similarity index 95% rename from src/sbot/friends.rs rename to src/api/friends.rs index f663eb0..3fbad2a 100644 --- a/src/sbot/friends.rs +++ b/src/api/friends.rs @@ -1,6 +1,10 @@ -use kuska_ssb::api::dto::content::{FriendsHops, RelationshipQuery}; - -use crate::{error::GolgiError, messages::SsbMessageContent, sbot::sbot_connection::Sbot, utils}; +use crate::{ + api::{FriendsHops, RelationshipQuery}, + error::GolgiError, + messages::SsbMessageContent, + sbot::Sbot, + utils, +}; impl Sbot { /// Convenience method to set a relationship with following: true, blocking: false. diff --git a/src/sbot/get_subset.rs b/src/api/get_subset.rs similarity index 90% rename from src/sbot/get_subset.rs rename to src/api/get_subset.rs index abdc3d0..c9a47fb 100644 --- a/src/sbot/get_subset.rs +++ b/src/api/get_subset.rs @@ -1,8 +1,7 @@ use async_std::stream::Stream; use crate::{ - error::GolgiError, messages::SsbMessageValue, sbot::sbot_connection::Sbot, utils, - utils::get_source_stream, + error::GolgiError, messages::SsbMessageValue, sbot::Sbot, utils, utils::get_source_stream, }; pub use kuska_ssb::api::dto::content::{SubsetQuery, SubsetQueryOptions}; diff --git a/src/sbot/history_stream.rs b/src/api/history_stream.rs similarity index 81% rename from src/sbot/history_stream.rs rename to src/api/history_stream.rs index 3497322..7233d71 100644 --- a/src/sbot/history_stream.rs +++ b/src/api/history_stream.rs @@ -1,10 +1,7 @@ use async_std::stream::Stream; use kuska_ssb::api::dto::CreateHistoryStreamIn; -use crate::{ - error::GolgiError, messages::SsbMessageValue, sbot::sbot_connection::Sbot, utils, - utils::get_source_stream, -}; +use crate::{error::GolgiError, messages::SsbMessageValue, sbot::Sbot, utils}; impl Sbot { /// Call the `createHistoryStream` RPC method @@ -19,7 +16,7 @@ impl Sbot { .client .create_history_stream_req_send(&args) .await?; - let history_stream = get_source_stream( + let history_stream = utils::get_source_stream( sbot_connection.rpc_reader, req_id, utils::ssb_message_res_parse, diff --git a/src/sbot/invite.rs b/src/api/invite.rs similarity index 93% rename from src/sbot/invite.rs rename to src/api/invite.rs index 8b04437..d6c1636 100644 --- a/src/sbot/invite.rs +++ b/src/api/invite.rs @@ -1,4 +1,4 @@ -use crate::{error::GolgiError, sbot::sbot_connection::Sbot, utils}; +use crate::{error::GolgiError, sbot::Sbot, utils}; impl Sbot { /// Call the `invite create` RPC method and return the created invite diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..6227e3e --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,12 @@ +//! API for interacting with a running go-sbot instance. +mod about; +mod friends; +mod get_subset; +mod history_stream; +mod invite; +mod publish; +mod whoami; + +pub use crate::sbot::*; + +pub use kuska_ssb::api::dto::content::{FriendsHops, RelationshipQuery}; diff --git a/src/sbot/publish.rs b/src/api/publish.rs similarity index 98% rename from src/sbot/publish.rs rename to src/api/publish.rs index d6a8174..b8275ff 100644 --- a/src/sbot/publish.rs +++ b/src/api/publish.rs @@ -1,4 +1,4 @@ -use crate::{error::GolgiError, messages::SsbMessageContent, sbot::sbot_connection::Sbot, utils}; +use crate::{error::GolgiError, messages::SsbMessageContent, sbot::Sbot, utils}; impl Sbot { /// Call the `publish` RPC method and return a message reference. diff --git a/src/api/whoami.rs b/src/api/whoami.rs new file mode 100644 index 0000000..640cdfe --- /dev/null +++ b/src/api/whoami.rs @@ -0,0 +1,25 @@ +//! Sbot type and associated methods. + +use crate::{error::GolgiError, sbot::Sbot, utils}; + +impl Sbot { + /// Call the `whoami` RPC method and return an `id`. + pub async fn whoami(&mut self) -> Result { + let mut sbot_connection = self.get_sbot_connection().await?; + let req_id = sbot_connection.client.whoami_req_send().await?; + + let result = utils::get_async( + &mut sbot_connection.rpc_reader, + req_id, + utils::json_res_parse, + ) + .await?; + + let id = result + .get("id") + .ok_or_else(|| GolgiError::Sbot("id key not found on whoami call".to_string()))? + .as_str() + .ok_or_else(|| GolgiError::Sbot("whoami returned non-string value".to_string()))?; + Ok(id.to_string()) + } +} diff --git a/src/sbot/mod.rs b/src/sbot/mod.rs deleted file mode 100644 index 4350ecd..0000000 --- a/src/sbot/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! API for interacting with a running go-sbot instance. -mod about; -mod friends; -mod get_subset; -mod history_stream; -mod invite; -mod publish; -mod sbot_connection; - -pub use sbot_connection::*; - -// re-export types from kuska -pub use kuska_ssb::api::dto::content::{ - FriendsHops, RelationshipQuery, SubsetQuery, SubsetQueryOptions, -}; diff --git a/src/sbot/sbot_connection.rs b/src/sbot/sbot_connection.rs deleted file mode 100644 index 0d06f63..0000000 --- a/src/sbot/sbot_connection.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! Sbot type and associated methods. -use async_std::net::TcpStream; - -use kuska_handshake::async_std::BoxStream; -use kuska_sodiumoxide::crypto::{auth, sign::ed25519}; -use kuska_ssb::{ - api::ApiCaller, - discovery, keystore, - keystore::OwnedIdentity, - rpc::{RpcReader, RpcWriter}, -}; - -use crate::{error::GolgiError, utils}; - -/// A struct representing a connection with a running sbot. -/// A client and an rpc_reader can together be used to make requests to the sbot -/// and read the responses. -/// Note there can be multiple SbotConnection at the same time. -pub struct SbotConnection { - /// client for writing requests to go-bot - pub client: ApiCaller, - /// RpcReader object for reading responses from go-sbot - pub rpc_reader: RpcReader, -} - -/// The Scuttlebutt identity, keys and configuration parameters for connecting to a local sbot -pub struct Sbot { - /// The ID (public key value) of the account associated with the local sbot instance. - pub id: String, - public_key: ed25519::PublicKey, - private_key: ed25519::SecretKey, - address: String, - // aka caps key (scuttleverse identifier) - network_id: auth::Key, -} - -impl Sbot { - /// Initiate a connection with an sbot instance. Define the IP address, port and network key - /// for the sbot, then retrieve the public key, private key (secret) and identity from the - /// `.ssb-go/secret` file. Open a TCP stream to the sbot and perform the secret handshake. If successful, create a box stream and split it into a writer and reader. Return RPC handles to the sbot as part of the `struct` output. - pub async fn connect( - ip_port: Option, - net_id: Option, - ) -> Result { - let address = if ip_port.is_none() { - "127.0.0.1:8008".to_string() - } else { - ip_port.unwrap() - }; - - let network_id = if net_id.is_none() { - discovery::ssb_net_id() - } else { - auth::Key::from_slice(&hex::decode(net_id.unwrap()).unwrap()).unwrap() - }; - - let OwnedIdentity { pk, sk, id } = keystore::from_gosbot_local() - .await - .expect("couldn't read local secret"); - - Ok(Self { - id, - public_key: pk, - private_key: sk, - address, - network_id, - }) - } - - /// Creates a new connection with the sbot, - /// using the address, network_id, public_key and private_key supplied when Sbot was initialized. - /// - /// Note that a single Sbot can have multiple SbotConnection at the same time. - pub async fn get_sbot_connection(&self) -> Result { - let address = self.address.clone(); - let network_id = self.network_id.clone(); - let public_key = self.public_key; - let private_key = self.private_key.clone(); - Sbot::_get_sbot_connection_helper(address, network_id, public_key, private_key).await - } - - /// Private helper function which creates a new connection with sbot, - /// but with all variables passed as arguments. - async fn _get_sbot_connection_helper( - address: String, - network_id: auth::Key, - public_key: ed25519::PublicKey, - private_key: ed25519::SecretKey, - ) -> Result { - let socket = TcpStream::connect(&address) - .await - .map_err(|source| GolgiError::Io { - source, - context: "socket error; failed to initiate tcp stream connection".to_string(), - })?; - - let handshake = kuska_handshake::async_std::handshake_client( - &mut &socket, - network_id.clone(), - public_key, - private_key.clone(), - public_key, - ) - .await - .map_err(GolgiError::Handshake)?; - - let (box_stream_read, box_stream_write) = - BoxStream::from_handshake(socket.clone(), socket, handshake, 0x8000).split_read_write(); - - let rpc_reader = RpcReader::new(box_stream_read); - let client = ApiCaller::new(RpcWriter::new(box_stream_write)); - let sbot_connection = SbotConnection { rpc_reader, client }; - Ok(sbot_connection) - } - - /// Call the `whoami` RPC method and return an `id`. - pub async fn whoami(&mut self) -> Result { - let mut sbot_connection = self.get_sbot_connection().await?; - let req_id = sbot_connection.client.whoami_req_send().await?; - - let result = utils::get_async( - &mut sbot_connection.rpc_reader, - req_id, - utils::json_res_parse, - ) - .await?; - - let id = result - .get("id") - .ok_or_else(|| GolgiError::Sbot("id key not found on whoami call".to_string()))? - .as_str() - .ok_or_else(|| GolgiError::Sbot("whoami returned non-string value".to_string()))?; - Ok(id.to_string()) - } -} From ebb92aba24b0fea70bf667f0b4dc754b17a80731 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 8 Feb 2022 10:02:42 +0200 Subject: [PATCH 05/27] move main sbot type definition to top-level --- src/sbot.rs | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/sbot.rs diff --git a/src/sbot.rs b/src/sbot.rs new file mode 100644 index 0000000..16c20fa --- /dev/null +++ b/src/sbot.rs @@ -0,0 +1,115 @@ +//! Sbot type and connection-related methods. +use async_std::net::TcpStream; + +use kuska_handshake::async_std::BoxStream; +use kuska_sodiumoxide::crypto::{auth, sign::ed25519}; +use kuska_ssb::{ + api::ApiCaller, + discovery, keystore, + keystore::OwnedIdentity, + rpc::{RpcReader, RpcWriter}, +}; + +use crate::error::GolgiError; + +/// A struct representing a connection with a running sbot. +/// A client and an rpc_reader can together be used to make requests to the sbot +/// and read the responses. +/// Note there can be multiple SbotConnection at the same time. +pub struct SbotConnection { + /// client for writing requests to go-bot + pub client: ApiCaller, + /// RpcReader object for reading responses from go-sbot + pub rpc_reader: RpcReader, +} + +/// The Scuttlebutt identity, keys and configuration parameters for connecting to a local sbot +pub struct Sbot { + /// The ID (public key value) of the account associated with the local sbot instance. + pub id: String, + public_key: ed25519::PublicKey, + private_key: ed25519::SecretKey, + address: String, + // aka caps key (scuttleverse identifier) + network_id: auth::Key, +} + +impl Sbot { + /// Initiate a connection with an sbot instance. Define the IP address, port and network key + /// for the sbot, then retrieve the public key, private key (secret) and identity from the + /// `.ssb-go/secret` file. Open a TCP stream to the sbot and perform the secret handshake. If successful, create a box stream and split it into a writer and reader. Return RPC handles to the sbot as part of the `struct` output. + pub async fn connect( + ip_port: Option, + net_id: Option, + ) -> Result { + let address = if ip_port.is_none() { + "127.0.0.1:8008".to_string() + } else { + ip_port.unwrap() + }; + + let network_id = if net_id.is_none() { + discovery::ssb_net_id() + } else { + auth::Key::from_slice(&hex::decode(net_id.unwrap()).unwrap()).unwrap() + }; + + let OwnedIdentity { pk, sk, id } = keystore::from_gosbot_local() + .await + .expect("couldn't read local secret"); + + Ok(Self { + id, + public_key: pk, + private_key: sk, + address, + network_id, + }) + } + + /// Creates a new connection with the sbot, + /// using the address, network_id, public_key and private_key supplied when Sbot was initialized. + /// + /// Note that a single Sbot can have multiple SbotConnection at the same time. + pub async fn get_sbot_connection(&self) -> Result { + let address = self.address.clone(); + let network_id = self.network_id.clone(); + let public_key = self.public_key; + let private_key = self.private_key.clone(); + Sbot::_get_sbot_connection_helper(address, network_id, public_key, private_key).await + } + + /// Private helper function which creates a new connection with sbot, + /// but with all variables passed as arguments. + async fn _get_sbot_connection_helper( + address: String, + network_id: auth::Key, + public_key: ed25519::PublicKey, + private_key: ed25519::SecretKey, + ) -> Result { + let socket = TcpStream::connect(&address) + .await + .map_err(|source| GolgiError::Io { + source, + context: "socket error; failed to initiate tcp stream connection".to_string(), + })?; + + let handshake = kuska_handshake::async_std::handshake_client( + &mut &socket, + network_id.clone(), + public_key, + private_key.clone(), + public_key, + ) + .await + .map_err(GolgiError::Handshake)?; + + let (box_stream_read, box_stream_write) = + BoxStream::from_handshake(socket.clone(), socket, handshake, 0x8000).split_read_write(); + + let rpc_reader = RpcReader::new(box_stream_read); + let client = ApiCaller::new(RpcWriter::new(box_stream_write)); + let sbot_connection = SbotConnection { rpc_reader, client }; + Ok(sbot_connection) + } +} From de6689220edd72d9b2b803989b50cb947d82f4a5 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 8 Feb 2022 11:52:52 +0200 Subject: [PATCH 06/27] add docs for each api module --- src/api/about.rs | 47 +++++++++++++++++++++++++-------------- src/api/friends.rs | 40 +++++++++++++++++++++------------ src/api/get_subset.rs | 13 +++++++---- src/api/history_stream.rs | 6 +++++ src/api/invite.rs | 7 ++++++ src/api/mod.rs | 16 ++++++------- src/api/publish.rs | 9 ++++++++ src/api/whoami.rs | 6 ++++- 8 files changed, 99 insertions(+), 45 deletions(-) diff --git a/src/api/about.rs b/src/api/about.rs index 08b802c..2b86a85 100644 --- a/src/api/about.rs +++ b/src/api/about.rs @@ -1,3 +1,15 @@ +//! Retrieve data about a peer. +//! +//! Implements the following methods: +//! +//! - [`Sbot::get_about_info`] +//! - [`Sbot::get_about_message_stream`] +//! - [`Sbot::get_description`] +//! - [`Sbot::get_latest_about_message`] +//! - [`Sbot::get_name`] +//! - [`Sbot::get_name_and_image`] +//! - [`Sbot::get_profile_info`] + use std::collections::HashMap; use async_std::stream::{Stream, StreamExt}; @@ -11,7 +23,7 @@ use crate::{ }; impl Sbot { - /// Get the about messages for a particular user in order of recency. + /// Get the about messages for a particular peer in order of recency. pub async fn get_about_message_stream( &mut self, ssb_id: &str, @@ -39,7 +51,7 @@ impl Sbot { Ok(about_message_stream) } - /// Get value of latest about message with given key from given user + /// Get value of latest about message with given key for given peer. pub async fn get_latest_about_message( &mut self, ssb_id: &str, @@ -75,7 +87,7 @@ impl Sbot { Ok(latest_about_value) } - /// Get HashMap of profile info for given user + /// Get HashMap of profile info for a peer as defined by the given ID. pub async fn get_profile_info( &mut self, ssb_id: &str, @@ -84,8 +96,8 @@ impl Sbot { self.get_about_info(ssb_id, keys_to_search_for).await } - /// Get HashMap of name and image for given user - /// (this is can be used to display profile images of a list of users) + /// Get HashMap of name and image for given peer + /// (this is can be used to display profile images of a list of users). pub async fn get_name_and_image( &mut self, ssb_id: &str, @@ -94,17 +106,18 @@ impl Sbot { self.get_about_info(ssb_id, keys_to_search_for).await } - /// Get HashMap of about keys to values for given user - /// by iteratively searching through a stream of about messages, - /// in order of recency, - /// until we find all about messages for all needed info - /// or reach the end of the stream. + /// Get HashMap of about keys to values for given user by iteratively + /// searching through a stream of about messages, in order of recency, + /// until we find all about messages for all needed info or reach the end + /// of the stream. /// /// # Arguments /// - /// * `ssb_id` - A reference to a string slice which represents the id of the user to get info about. - /// * `keys_to_search_for` - A mutable vector of string slice, which represent the about keys - /// that will be searched for. As they are found, keys are removed from the vector. + /// * `ssb_id` - A reference to a string slice which represents the id of + /// the user to get info about. + /// * `keys_to_search_for` - A mutable vector of string slice, which + /// represent the about keys that will be searched for. As they are found, + /// keys are removed from the vector. pub async fn get_about_info( &mut self, ssb_id: &str, @@ -153,21 +166,21 @@ impl Sbot { Ok(profile_info) } - /// Get latest about name from given user + /// Get latest name for the given peer. /// /// # Arguments /// - /// * `ssb_id` - A reference to a string slice which represents the ssb user + /// * `ssb_id` - A reference to a string slice which represents the SSB ID /// to lookup the about name for. pub async fn get_name(&mut self, ssb_id: &str) -> Result, GolgiError> { self.get_latest_about_message(ssb_id, "name").await } - /// Get latest about description from given user + /// Get latest description for the given peer. /// /// # Arguments /// - /// * `ssb_id` - A reference to a string slice which represents the ssb user + /// * `ssb_id` - A reference to a string slice which represents the SSB ID /// to lookup the about description for. pub async fn get_description(&mut self, ssb_id: &str) -> Result, GolgiError> { self.get_latest_about_message(ssb_id, "description").await diff --git a/src/api/friends.rs b/src/api/friends.rs index 3fbad2a..14d1268 100644 --- a/src/api/friends.rs +++ b/src/api/friends.rs @@ -1,23 +1,35 @@ -use crate::{ - api::{FriendsHops, RelationshipQuery}, - error::GolgiError, - messages::SsbMessageContent, - sbot::Sbot, - utils, -}; +//! Define peer relationships and query the social graph. +//! +//! Implements the following methods: +//! +//! - [`Sbot::block`] +//! - [`Sbot::follow`] +//! - [`Sbot::friends_hops`] +//! - [`Sbot::friends_is_blocking`] +//! - [`Sbot::friends_is_following`] +//! - [`Sbot::get_follows`] +//! - [`Sbot::set_relationship`] + +use crate::{error::GolgiError, messages::SsbMessageContent, sbot::Sbot, utils}; + +// re-export friends-related kuska types +pub use kuska_ssb::api::dto::content::{FriendsHops, RelationshipQuery}; impl Sbot { - /// Convenience method to set a relationship with following: true, blocking: false. + /// Convenience method to set a relationship with following: `true`, + /// blocking: `false`. pub async fn follow(&mut self, contact: &str) -> Result { self.set_relationship(contact, true, false).await } - /// Convenience method to set a relationship with following: false, blocking: true. + /// Convenience method to set a relationship with following: `false`, + /// blocking: `true`. pub async fn block(&mut self, contact: &str) -> Result { self.set_relationship(contact, false, true).await } - /// Publishes a contact relationship to the given user (with ssb_id) with the given state. + /// Publish a contact relationship to the given peer (with ssb_id) with + /// the given state. pub async fn set_relationship( &mut self, contact: &str, @@ -34,7 +46,7 @@ impl Sbot { } /// Call the `friends isFollowing` RPC method and return a message reference. - /// Returns true if src_id is following dest_id and false otherwise. + /// Returns `true` if `src_id` is following `dest_id` and `false` otherwise. pub async fn friends_is_following( &mut self, args: RelationshipQuery, @@ -54,7 +66,7 @@ impl Sbot { } /// Call the `friends isblocking` RPC method and return a message reference. - /// Returns true if src_id is blocking dest_id and false otherwise. + /// Returns `true` if `src_id` is blocking `dest_id` and `false` otherwise. pub async fn friends_is_blocking( &mut self, args: RelationshipQuery, @@ -86,7 +98,7 @@ impl Sbot { // Gets a Vec where each element is a peer who follows you /// TODO: currently this method is not working /// go-sbot does not seem to listen to the reverse=True parameter - /// and just returns follows + /// and just returns follows. async fn _get_followers(&mut self) -> Result, GolgiError> { self.friends_hops(FriendsHops { max: 1, @@ -100,7 +112,7 @@ impl Sbot { /// where each element of the vector is the ssb_id of a peer. /// /// When opts.reverse = True, it should return peers who are following you - /// (but this is currently not working) + /// (but this is currently not working). pub async fn friends_hops(&mut self, args: FriendsHops) -> Result, GolgiError> { let mut sbot_connection = self.get_sbot_connection().await?; let req_id = sbot_connection.client.friends_hops_req_send(args).await?; diff --git a/src/api/get_subset.rs b/src/api/get_subset.rs index c9a47fb..61d00f3 100644 --- a/src/api/get_subset.rs +++ b/src/api/get_subset.rs @@ -1,9 +1,14 @@ +//! Perform subset queries. +//! +//! Implements the following methods: +//! +//! - [`Sbot::get_subset_stream`] + use async_std::stream::Stream; -use crate::{ - error::GolgiError, messages::SsbMessageValue, sbot::Sbot, utils, utils::get_source_stream, -}; +use crate::{error::GolgiError, messages::SsbMessageValue, sbot::Sbot, utils}; +// re-export subset-related kuska types pub use kuska_ssb::api::dto::content::{SubsetQuery, SubsetQueryOptions}; impl Sbot { @@ -25,7 +30,7 @@ impl Sbot { .client .getsubset_req_send(query, options) .await?; - let get_subset_stream = get_source_stream( + let get_subset_stream = utils::get_source_stream( sbot_connection.rpc_reader, req_id, utils::ssb_message_res_parse, diff --git a/src/api/history_stream.rs b/src/api/history_stream.rs index 7233d71..69fc241 100644 --- a/src/api/history_stream.rs +++ b/src/api/history_stream.rs @@ -1,3 +1,9 @@ +//! Return a history stream. +//! +//! Implements the following methods: +//! +//! - [`Sbot::create_history_stream`] + use async_std::stream::Stream; use kuska_ssb::api::dto::CreateHistoryStreamIn; diff --git a/src/api/invite.rs b/src/api/invite.rs index d6c1636..74a186e 100644 --- a/src/api/invite.rs +++ b/src/api/invite.rs @@ -1,3 +1,10 @@ +//! Create and use invite codes. +//! +//! Implements the following methods: +//! +//! - [`Sbot::invite_create`] +//! - [`Sbot::invite_use`] + use crate::{error::GolgiError, sbot::Sbot, utils}; impl Sbot { diff --git a/src/api/mod.rs b/src/api/mod.rs index 6227e3e..ef9ba50 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,12 +1,10 @@ //! API for interacting with a running go-sbot instance. -mod about; -mod friends; -mod get_subset; -mod history_stream; -mod invite; -mod publish; -mod whoami; +pub mod about; +pub mod friends; +pub mod get_subset; +pub mod history_stream; +pub mod invite; +pub mod publish; +pub mod whoami; pub use crate::sbot::*; - -pub use kuska_ssb::api::dto::content::{FriendsHops, RelationshipQuery}; diff --git a/src/api/publish.rs b/src/api/publish.rs index b8275ff..fb2ddb8 100644 --- a/src/api/publish.rs +++ b/src/api/publish.rs @@ -1,3 +1,12 @@ +//! Publish Scuttlebutt messages. +//! +//! Implements the following methods: +//! +//! - [`Sbot::publish`] +//! - [`Sbot::publish_description`] +//! - [`Sbot::publish_name`] +//! - [`Sbot::publish_post`] + use crate::{error::GolgiError, messages::SsbMessageContent, sbot::Sbot, utils}; impl Sbot { diff --git a/src/api/whoami.rs b/src/api/whoami.rs index 640cdfe..156a5a2 100644 --- a/src/api/whoami.rs +++ b/src/api/whoami.rs @@ -1,4 +1,8 @@ -//! Sbot type and associated methods. +//! Return the SSB ID of the local sbot instance. +//! +//! Implements the following methods: +//! +//! - [`Sbot::whoami`] use crate::{error::GolgiError, sbot::Sbot, utils}; From 5bcdbfa7bddef071349a9004cb54ee2697053d31 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 8 Feb 2022 11:53:27 +0200 Subject: [PATCH 07/27] export Sbot as top-level and make api module public --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1fd0361..05bc007 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,8 +36,7 @@ //! more comprehensive examples. //! //! ```rust -//! use golgi::GolgiError; -//! use golgi::sbot::Sbot; +//! use golgi::{messages::SsbMessageContent, GolgiError, Sbot}; //! //! pub async fn run() -> Result<(), GolgiError> { //! // Attempt to connect to an sbot instance using the default IP address, @@ -67,9 +66,10 @@ //! } //! ``` +pub mod api; pub mod error; pub mod messages; pub mod sbot; pub mod utils; -pub use crate::error::GolgiError; +pub use crate::{error::GolgiError, sbot::Sbot}; From b6fd2e2da5aba6bb1eabed28cec4ce34f3e4acb9 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 8 Feb 2022 11:54:02 +0200 Subject: [PATCH 08/27] shorten doc comment line lengths --- src/sbot.rs | 16 +- src/sbot.rs_bak | 558 ------------------------------------------------ 2 files changed, 10 insertions(+), 564 deletions(-) delete mode 100644 src/sbot.rs_bak diff --git a/src/sbot.rs b/src/sbot.rs index 16c20fa..5c640a5 100644 --- a/src/sbot.rs +++ b/src/sbot.rs @@ -23,7 +23,8 @@ pub struct SbotConnection { pub rpc_reader: RpcReader, } -/// The Scuttlebutt identity, keys and configuration parameters for connecting to a local sbot +/// Holds the Scuttlebutt identity, keys and configuration parameters for +/// connecting to a local sbot and implements all Golgi API methods. pub struct Sbot { /// The ID (public key value) of the account associated with the local sbot instance. pub id: String, @@ -35,9 +36,12 @@ pub struct Sbot { } impl Sbot { - /// Initiate a connection with an sbot instance. Define the IP address, port and network key - /// for the sbot, then retrieve the public key, private key (secret) and identity from the - /// `.ssb-go/secret` file. Open a TCP stream to the sbot and perform the secret handshake. If successful, create a box stream and split it into a writer and reader. Return RPC handles to the sbot as part of the `struct` output. + /// Initiate a connection with an sbot instance. Define the IP address, + /// port and network key for the sbot, then retrieve the public key, + /// private key (secret) and identity from the `.ssb-go/secret` file. + /// Open a TCP stream to the sbot and perform the secret handshake. If + /// successful, create a box stream and split it into a writer and reader. + /// Return RPC handles to the sbot as part of the `struct` output. pub async fn connect( ip_port: Option, net_id: Option, @@ -67,8 +71,8 @@ impl Sbot { }) } - /// Creates a new connection with the sbot, - /// using the address, network_id, public_key and private_key supplied when Sbot was initialized. + /// Creates a new connection with the sbot, using the address, network_id, + /// public_key and private_key supplied when Sbot was initialized. /// /// Note that a single Sbot can have multiple SbotConnection at the same time. pub async fn get_sbot_connection(&self) -> Result { diff --git a/src/sbot.rs_bak b/src/sbot.rs_bak deleted file mode 100644 index bca4a9e..0000000 --- a/src/sbot.rs_bak +++ /dev/null @@ -1,558 +0,0 @@ -//! Sbot type and associated methods. -use async_std::{ - net::TcpStream, - stream::{Stream, StreamExt}, -}; -use futures::pin_mut; -use std::collections::HashMap; - -use kuska_handshake::async_std::BoxStream; -use kuska_sodiumoxide::crypto::{auth, sign::ed25519}; -use kuska_ssb::{ - api::{dto::CreateHistoryStreamIn, ApiCaller}, - discovery, keystore, - keystore::OwnedIdentity, - rpc::{RpcReader, RpcWriter}, -}; - -use crate::error::GolgiError; -use crate::messages::{SsbMessageContent, SsbMessageContentType, SsbMessageKVT, SsbMessageValue}; -use crate::utils; -use crate::utils::get_source_stream; - -// re-export types from kuska -pub use kuska_ssb::api::dto::content::{ - FriendsHops, RelationshipQuery, SubsetQuery, SubsetQueryOptions, -}; - -/// A struct representing a connection with a running sbot. -/// A client and an rpc_reader can together be used to make requests to the sbot -/// and read the responses. -/// Note there can be multiple SbotConnection at the same time. -pub struct SbotConnection { - client: ApiCaller, - rpc_reader: RpcReader, -} - -/// The Scuttlebutt identity, keys and configuration parameters for connecting to a local sbot -pub struct Sbot { - pub id: String, - public_key: ed25519::PublicKey, - private_key: ed25519::SecretKey, - address: String, - // aka caps key (scuttleverse identifier) - network_id: auth::Key, -} - -impl Sbot { - /// Initiate a connection with an sbot instance. Define the IP address, port and network key - /// for the sbot, then retrieve the public key, private key (secret) and identity from the - /// `.ssb-go/secret` file. Open a TCP stream to the sbot and perform the secret handshake. If successful, create a box stream and split it into a writer and reader. Return RPC handles to the sbot as part of the `struct` output. - pub async fn init(ip_port: Option, net_id: Option) -> Result { - let address = if ip_port.is_none() { - "127.0.0.1:8008".to_string() - } else { - ip_port.unwrap() - }; - - let network_id = if net_id.is_none() { - discovery::ssb_net_id() - } else { - auth::Key::from_slice(&hex::decode(net_id.unwrap()).unwrap()).unwrap() - }; - - let OwnedIdentity { pk, sk, id } = keystore::from_gosbot_local() - .await - .expect("couldn't read local secret"); - - Ok(Self { - id, - public_key: pk, - private_key: sk, - address, - network_id, - }) - } - - /// Creates a new connection with the sbot, - /// using the address, network_id, public_key and private_key supplied when Sbot was initialized. - /// - /// Note that a single Sbot can have multiple SbotConnection at the same time. - pub async fn get_sbot_connection(&self) -> Result { - let address = self.address.clone(); - let network_id = self.network_id.clone(); - let public_key = self.public_key; - let private_key = self.private_key.clone(); - Sbot::_get_sbot_connection_helper(address, network_id, public_key, private_key).await - } - - /// Private helper function which creates a new connection with sbot, - /// but with all variables passed as arguments. - async fn _get_sbot_connection_helper( - address: String, - network_id: auth::Key, - public_key: ed25519::PublicKey, - private_key: ed25519::SecretKey, - ) -> Result { - let socket = TcpStream::connect(&address) - .await - .map_err(|source| GolgiError::Io { - source, - context: "socket error; failed to initiate tcp stream connection".to_string(), - })?; - - let handshake = kuska_handshake::async_std::handshake_client( - &mut &socket, - network_id.clone(), - public_key, - private_key.clone(), - public_key, - ) - .await - .map_err(GolgiError::Handshake)?; - - let (box_stream_read, box_stream_write) = - BoxStream::from_handshake(socket.clone(), socket, handshake, 0x8000).split_read_write(); - - let rpc_reader = RpcReader::new(box_stream_read); - let client = ApiCaller::new(RpcWriter::new(box_stream_write)); - let sbot_connection = SbotConnection { rpc_reader, client }; - Ok(sbot_connection) - } - - /// Call the `partialReplication getSubset` RPC method - /// and return a Stream of Result - /// - /// # Arguments - /// - /// * `query` - A `SubsetQuery` which specifies what filters to use. - /// * `option` - An Option<`SubsetQueryOptions`> which, if provided, adds additional - /// specifications to the query, such as specifying page limit and/or descending. - pub async fn get_subset_stream( - &mut self, - query: SubsetQuery, - options: Option, - ) -> Result>, GolgiError> { - let mut sbot_connection = self.get_sbot_connection().await?; - let req_id = sbot_connection - .client - .getsubset_req_send(query, options) - .await?; - let get_subset_stream = get_source_stream( - sbot_connection.rpc_reader, - req_id, - utils::ssb_message_res_parse, - ) - .await; - Ok(get_subset_stream) - } - - /// Call the `whoami` RPC method and return an `id`. - pub async fn whoami(&mut self) -> Result { - let mut sbot_connection = self.get_sbot_connection().await?; - let req_id = sbot_connection.client.whoami_req_send().await?; - - let result = utils::get_async( - &mut sbot_connection.rpc_reader, - req_id, - utils::json_res_parse, - ) - .await?; - - let id = result - .get("id") - .ok_or_else(|| GolgiError::Sbot("id key not found on whoami call".to_string()))? - .as_str() - .ok_or_else(|| GolgiError::Sbot("whoami returned non-string value".to_string()))?; - Ok(id.to_string()) - } - - /// Call the `publish` RPC method and return a message reference. - /// - /// # Arguments - /// - /// * `msg` - A `SsbMessageContent` `enum` whose variants include `Pub`, `Post`, `Contact`, `About`, - /// `Channel` and `Vote`. See the `kuska_ssb` documentation for further details such as field - /// names and accepted values for each variant. - pub async fn publish(&mut self, msg: SsbMessageContent) -> Result { - let mut sbot_connection = self.get_sbot_connection().await?; - let req_id = sbot_connection.client.publish_req_send(msg).await?; - - utils::get_async( - &mut sbot_connection.rpc_reader, - req_id, - utils::string_res_parse, - ) - .await - } - - // Convenience method to set a relationship with following: true, blocking: false - pub async fn follow(&mut self, contact: &str) -> Result { - self.set_relationship(contact, true, false).await - } - - // Convenience method to set a relationship with following: false, blocking: true - pub async fn block(&mut self, contact: &str) -> Result { - self.set_relationship(contact, false, true).await - } - - /// Publishes a contact relationship to the given user (with ssb_id) with the given state. - pub async fn set_relationship( - &mut self, - contact: &str, - following: bool, - blocking: bool, - ) -> Result { - let msg = SsbMessageContent::Contact { - contact: Some(contact.to_string()), - following: Some(following), - blocking: Some(blocking), - autofollow: None, - }; - self.publish(msg).await - } - - /* - /// Call the `friends isFollowing` RPC method and return a message reference. - /// Returns true if src_id is following dest_id and false otherwise. - pub async fn friends_is_following( - &mut self, - args: RelationshipQuery, - ) -> Result { - let mut sbot_connection = self.get_sbot_connection().await?; - let req_id = sbot_connection - .client - .friends_is_following_req_send(args) - .await?; - - utils::get_async( - &mut sbot_connection.rpc_reader, - req_id, - utils::string_res_parse, - ) - .await - } - - /// Call the `friends isblocking` RPC method and return a message reference. - /// Returns true if src_id is blocking dest_id and false otherwise. - pub async fn friends_is_blocking( - &mut self, - args: RelationshipQuery, - ) -> Result { - let mut sbot_connection = self.get_sbot_connection().await?; - let req_id = sbot_connection - .client - .friends_is_blocking_req_send(args) - .await?; - - utils::get_async( - &mut sbot_connection.rpc_reader, - req_id, - utils::string_res_parse, - ) - .await - } - - // Gets a Vec where each element is a peer you are following - pub async fn get_follows(&mut self) -> Result, GolgiError> { - self.friends_hops(FriendsHops { - max: 1, - start: None, - reverse: Some(false), - }) - .await - } - - // Gets a Vec where each element is a peer who follows you - /// TODO: currently this method is not working - /// go-sbot does not seem to listen to the reverse=True parameter - /// and just returns follows - async fn get_followers(&mut self) -> Result, GolgiError> { - self.friends_hops(FriendsHops { - max: 1, - start: None, - reverse: Some(true), - }) - .await - } - - /// Call the `friends hops` RPC method and return a Vector - /// where each element of the vector is the ssb_id of a peer. - /// - /// When opts.reverse = True, it should return peers who are following you - /// (but this is currently not working) - pub async fn friends_hops(&mut self, args: FriendsHops) -> Result, GolgiError> { - let mut sbot_connection = self.get_sbot_connection().await?; - let req_id = sbot_connection.client.friends_hops_req_send(args).await?; - utils::get_source_until_eof( - &mut sbot_connection.rpc_reader, - req_id, - utils::string_res_parse, - ) - .await - } - */ - - /// Call the `invite create` RPC method and return the created invite - pub async fn invite_create(&mut self, uses: u16) -> Result { - let mut sbot_connection = self.get_sbot_connection().await?; - let req_id = sbot_connection.client.invite_create_req_send(uses).await?; - - utils::get_async( - &mut sbot_connection.rpc_reader, - req_id, - utils::string_res_parse, - ) - .await - } - - /// Call the `invite use` RPC method and return a reference to the message. - pub async fn invite_use(&mut self, invite_code: &str) -> Result { - let mut sbot_connection = self.get_sbot_connection().await?; - let req_id = sbot_connection - .client - .invite_use_req_send(invite_code) - .await?; - - utils::get_async( - &mut sbot_connection.rpc_reader, - req_id, - utils::string_res_parse, - ) - .await - } - - /// Wrapper for publish which constructs and publishes a post message appropriately from a string. - /// - /// # Arguments - /// - /// * `text` - A reference to a string slice which represents the text to be published in the post - pub async fn publish_post(&mut self, text: &str) -> Result { - let msg = SsbMessageContent::Post { - text: text.to_string(), - mentions: None, - }; - self.publish(msg).await - } - - /// Wrapper for publish which constructs and publishes an about description message appropriately from a string. - /// - /// # Arguments - /// - /// * `description` - A reference to a string slice which represents the text to be published as an about description. - pub async fn publish_description(&mut self, description: &str) -> Result { - let msg = SsbMessageContent::About { - about: self.id.to_string(), - name: None, - title: None, - branch: None, - image: None, - description: Some(description.to_string()), - location: None, - start_datetime: None, - }; - self.publish(msg).await - } - - /// Wrapper for publish which constructs and publishes an about name message appropriately from a string. - /// - /// # Arguments - /// - /// * `name` - A reference to a string slice which represents the text to be published as an about name. - pub async fn publish_name(&mut self, name: &str) -> Result { - let msg = SsbMessageContent::About { - about: self.id.to_string(), - name: Some(name.to_string()), - title: None, - branch: None, - image: None, - description: None, - location: None, - start_datetime: None, - }; - self.publish(msg).await - } - - /// Get the about messages for a particular user in order of recency. - pub async fn get_about_message_stream( - &mut self, - ssb_id: &str, - ) -> Result>, GolgiError> { - let query = SubsetQuery::Author { - op: "author".to_string(), - feed: ssb_id.to_string(), - }; - // specify that most recent messages should be returned first - let query_options = SubsetQueryOptions { - descending: Some(true), - keys: None, - page_limit: None, - }; - let get_subset_stream = self.get_subset_stream(query, Some(query_options)).await?; - // TODO: after fixing sbot regression, - // change this subset query to filter by type about in addition to author - // and remove this filter section - // filter down to about messages - let about_message_stream = get_subset_stream.filter(|msg| match msg { - Ok(val) => val.is_message_type(SsbMessageContentType::About), - Err(_err) => false, - }); - // return about message stream - Ok(about_message_stream) - } - - /// Get value of latest about message with given key from given user - pub async fn get_latest_about_message( - &mut self, - ssb_id: &str, - key: &str, - ) -> Result, GolgiError> { - // get about_message_stream - let about_message_stream = self.get_about_message_stream(ssb_id).await?; - // now we have a stream of about messages with most recent at the front of the vector - pin_mut!(about_message_stream); - // iterate through the vector looking for most recent about message with the given key - let latest_about_message_res: Option> = - about_message_stream - // find the first msg that contains the field `key` - .find(|res| match res { - Ok(msg) => msg.content.get(key).is_some(), - Err(_) => false, - }) - .await; - // Option> -> Option - let latest_about_message = latest_about_message_res.and_then(|msg| msg.ok()); - // Option -> Option - let latest_about_value = latest_about_message.and_then(|msg| { - msg - // SsbMessageValue -> Option<&Value> - .content - .get(key) - // Option<&Value> -> - .and_then(|value| value.as_str()) - // Option<&str> -> Option - .map(|value| value.to_string()) - }); - // return value is either `Ok(Some(String))` or `Ok(None)` - Ok(latest_about_value) - } - - /// Get HashMap of profile info for given user - pub async fn get_profile_info( - &mut self, - ssb_id: &str, - ) -> Result, GolgiError> { - let mut keys_to_search_for = vec!["name", "description", "image"]; - self.get_about_info(ssb_id, keys_to_search_for).await - } - - /// Get HashMap of name and image for given user - /// (this is can be used to display profile images of a list of users) - pub async fn get_name_and_image( - &mut self, - ssb_id: &str, - ) -> Result, GolgiError> { - let mut keys_to_search_for = vec!["name", "image"]; - self.get_about_info(ssb_id, keys_to_search_for).await - } - - /// Get HashMap of about keys to values for given user - /// by iteratively searching through a stream of about messages, - /// in order of recency, - /// until we find all about messages for all needed info - /// or reach the end of the stream. - /// - /// # Arguments - /// - /// * `ssb_id` - A reference to a string slice which represents the id of the user to get info about. - /// * `keys_to_search_for` - A mutable vector of string slice, which represent the about keys - /// that will be searched for. As they are found, keys are removed from the vector. - pub async fn get_about_info( - &mut self, - ssb_id: &str, - mut keys_to_search_for: Vec<&str>, - ) -> Result, GolgiError> { - // get about_message_stream - let about_message_stream = self.get_about_message_stream(ssb_id).await?; - // now we have a stream of about messages with most recent at the front of the vector - pin_mut!(about_message_stream); // needed for iteration - let mut profile_info: HashMap = HashMap::new(); - // iterate through the stream while it still has more values and - // we still have keys we are looking for - while let Some(res) = about_message_stream.next().await { - // if there are no more keys we are looking for, then we are done - if keys_to_search_for.len() == 0 { - break; - } - // if there are still keys we are looking for, then continue searching - match res { - Ok(msg) => { - // for each key we are searching for, check if this about - // message contains a value for that key - for key in &keys_to_search_for.clone() { - let option_val = msg - .content - .get(key) - .and_then(|val| val.as_str()) - .map(|val| val.to_string()); - match option_val { - Some(val) => { - // if a value is found, then insert it - profile_info.insert(key.to_string(), val); - // remove this key fom keys_to_search_for, since we are no longer searching for it - keys_to_search_for.retain(|val| val != key) - } - None => continue, - } - } - } - Err(err) => { - // skip errors - continue; - } - } - } - Ok(profile_info) - } - - /// Get latest about name from given user - /// - /// # Arguments - /// - /// * `ssb_id` - A reference to a string slice which represents the ssb user - /// to lookup the about name for. - pub async fn get_name(&mut self, ssb_id: &str) -> Result, GolgiError> { - self.get_latest_about_message(ssb_id, "name").await - } - - /// Get latest about description from given user - /// - /// # Arguments - /// - /// * `ssb_id` - A reference to a string slice which represents the ssb user - /// to lookup the about description for. - pub async fn get_description(&mut self, ssb_id: &str) -> Result, GolgiError> { - self.get_latest_about_message(ssb_id, "description").await - } - - /// Call the `createHistoryStream` RPC method - /// and return a Stream of Result - pub async fn create_history_stream( - &mut self, - id: String, - ) -> Result>, GolgiError> { - let mut sbot_connection = self.get_sbot_connection().await?; - let args = CreateHistoryStreamIn::new(id); - let req_id = sbot_connection - .client - .create_history_stream_req_send(&args) - .await?; - let history_stream = get_source_stream( - sbot_connection.rpc_reader, - req_id, - utils::ssb_message_res_parse, - ) - .await; - Ok(history_stream) - } -} From d9839f1d06a71c424e78028f3849adf349b6bfb7 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 8 Feb 2022 11:54:21 +0200 Subject: [PATCH 09/27] update use paths --- examples/ssb-client.rs | 4 +--- examples/ssb-friends.rs | 7 ++++--- examples/ssb-invite.rs | 4 +--- examples/ssb-stream-example.rs | 7 ++++--- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/examples/ssb-client.rs b/examples/ssb-client.rs index a5c0a3b..5a02f16 100644 --- a/examples/ssb-client.rs +++ b/examples/ssb-client.rs @@ -1,8 +1,6 @@ use std::process; -use golgi::error::GolgiError; -use golgi::messages::SsbMessageContent; -use golgi::sbot::Sbot; +use golgi::{messages::SsbMessageContent, GolgiError, Sbot}; async fn run() -> Result<(), GolgiError> { let mut sbot_client = Sbot::connect(None, None).await?; diff --git a/examples/ssb-friends.rs b/examples/ssb-friends.rs index 68b382c..8a1fceb 100644 --- a/examples/ssb-friends.rs +++ b/examples/ssb-friends.rs @@ -1,8 +1,9 @@ use std::process; -use golgi::error::GolgiError; -use golgi::sbot::Sbot; -use golgi::sbot::{FriendsHops, RelationshipQuery}; +use golgi::{ + api::friends::{FriendsHops, RelationshipQuery}, + GolgiError, Sbot, +}; async fn run() -> Result<(), GolgiError> { let mut sbot_client = Sbot::connect(None, None).await?; diff --git a/examples/ssb-invite.rs b/examples/ssb-invite.rs index 17db7c8..42b054c 100644 --- a/examples/ssb-invite.rs +++ b/examples/ssb-invite.rs @@ -2,9 +2,7 @@ use std::process; use kuska_ssb::api::dto::content::PubAddress; -use golgi::error::GolgiError; -use golgi::messages::SsbMessageContent; -use golgi::sbot::Sbot; +use golgi::{messages::SsbMessageContent, GolgiError, Sbot}; async fn run() -> Result<(), GolgiError> { let mut sbot_client = Sbot::connect(None, None).await?; diff --git a/examples/ssb-stream-example.rs b/examples/ssb-stream-example.rs index cf4a223..fd1b4c2 100644 --- a/examples/ssb-stream-example.rs +++ b/examples/ssb-stream-example.rs @@ -3,9 +3,10 @@ use std::process; use async_std::stream::StreamExt; use futures::{pin_mut, TryStreamExt}; -use golgi::error::GolgiError; -use golgi::messages::{SsbMessageContentType, SsbMessageValue}; -use golgi::sbot::Sbot; +use golgi::{ + messages::{SsbMessageContentType, SsbMessageValue}, + GolgiError, Sbot, +}; async fn run() -> Result<(), GolgiError> { let mut sbot_client = Sbot::connect(None, None).await?; From 81e329b40ea0816264241f33058566976dcd1b52 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 8 Feb 2022 14:17:30 +0200 Subject: [PATCH 10/27] add handshake error text --- src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index da22787..010c834 100644 --- a/src/error.rs +++ b/src/error.rs @@ -68,7 +68,7 @@ impl std::fmt::Display for GolgiError { // TODO: add context (what were we trying to decode?) GolgiError::DecodeBase64(_) => write!(f, "Failed to decode base64"), GolgiError::Io { ref context, .. } => write!(f, "IO error: {}", context), - GolgiError::Handshake(ref err) => write!(f, "{}", err), + GolgiError::Handshake(ref err) => write!(f, "Handshake failure: {}", err), GolgiError::Api(ref err) => write!(f, "SSB API failure: {}", err), GolgiError::Feed(ref err) => write!(f, "SSB feed error: {}", err), // TODO: improve this variant with a context message From d2a0c38f68eee039c6086c1304a46b636d7be116 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 8 Feb 2022 14:17:46 +0200 Subject: [PATCH 11/27] revert connect fn to init --- src/sbot.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/sbot.rs b/src/sbot.rs index 5c640a5..33a27d6 100644 --- a/src/sbot.rs +++ b/src/sbot.rs @@ -39,13 +39,7 @@ impl Sbot { /// Initiate a connection with an sbot instance. Define the IP address, /// port and network key for the sbot, then retrieve the public key, /// private key (secret) and identity from the `.ssb-go/secret` file. - /// Open a TCP stream to the sbot and perform the secret handshake. If - /// successful, create a box stream and split it into a writer and reader. - /// Return RPC handles to the sbot as part of the `struct` output. - pub async fn connect( - ip_port: Option, - net_id: Option, - ) -> Result { + pub async fn init(ip_port: Option, net_id: Option) -> Result { let address = if ip_port.is_none() { "127.0.0.1:8008".to_string() } else { @@ -85,6 +79,10 @@ impl Sbot { /// Private helper function which creates a new connection with sbot, /// but with all variables passed as arguments. + /// + /// Open a TCP stream to the sbot and perform the secret handshake. If + /// successful, create a box stream and split it into a writer and reader. + /// Return RPC handles to the sbot as part of the `struct` output. async fn _get_sbot_connection_helper( address: String, network_id: auth::Key, From 7aa32e24c73f367b9c58b81a9d09c80d15167789 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 8 Feb 2022 15:04:44 +0200 Subject: [PATCH 12/27] silly me; revert to --- api_design_notes | 65 ++++++++++++++++++++++++++++++++++ examples/ssb-client.rs | 4 +-- examples/ssb-friends.rs | 2 +- examples/ssb-invite.rs | 2 +- examples/ssb-stream-example.rs | 2 +- muxrpc_notes | 33 +++++++++++++++++ notes | 34 ++++++++++++++++++ src/lib.rs | 2 +- src/sbot.rs | 4 +-- 9 files changed, 140 insertions(+), 8 deletions(-) create mode 100644 api_design_notes create mode 100644 muxrpc_notes create mode 100644 notes diff --git a/api_design_notes b/api_design_notes new file mode 100644 index 0000000..d8af7b9 --- /dev/null +++ b/api_design_notes @@ -0,0 +1,65 @@ +Higher level functions: + +```rust +let mut sbot_client = Sbot::connect(); + +// second parameter is `Option`; if `None`, return our relationship with `id` +// if second parameter is `Some(dest_id)`, return id's relationship with dest_id +match sbot_client.get_relationship(id, None) { + Relationship::Following => , + Relationship::NotFollowing => , + Relationship::Blocking => , + Relationship::Muting => , +} + +sbot_client.set_relationship(id, +``` + +data structures + +```rust +enum Relationship { + Following, + NotFollowing, + Blocking, + Muting +} + +struct Profile { + id: String, + name: String, + description: String, + // blob reference (maybe?) + image: String, +} +``` + +----- + +whoami - id: String + +name(id) -> name: String +description(id) -> description: String + +publish_description(id, text) -> msg_ref: String +publish_name(id, text) -> msg_ref: String +publish_post(text) -> msg_ref: String + +follows(id) -> Vec +followers(id) -> Vec +friends(id) -> Vec +blocks(id) -> Vec + +follow(id) -> msg_ref +unfollow(id) -> msg_ref +block(id) -> msg_ref + +profile_image(id) + +----- + +``` +let mut sbot_client = Sbot::init(); +``` + +----- \ No newline at end of file diff --git a/examples/ssb-client.rs b/examples/ssb-client.rs index 5a02f16..d7ae0b4 100644 --- a/examples/ssb-client.rs +++ b/examples/ssb-client.rs @@ -3,7 +3,7 @@ use std::process; use golgi::{messages::SsbMessageContent, GolgiError, Sbot}; async fn run() -> Result<(), GolgiError> { - let mut sbot_client = Sbot::connect(None, None).await?; + let mut sbot_client = Sbot::init(None, None).await?; let id = sbot_client.whoami().await?; println!("whoami: {}", id); @@ -31,7 +31,7 @@ async fn run() -> Result<(), GolgiError> { println!("post_msg_ref: {}", post_msg_ref); let post_msg_ref = sbot_client - .publish_description("this is a description7") + .publish_description("this is a description") .await?; println!("description: {}", post_msg_ref); diff --git a/examples/ssb-friends.rs b/examples/ssb-friends.rs index 8a1fceb..137d2d2 100644 --- a/examples/ssb-friends.rs +++ b/examples/ssb-friends.rs @@ -6,7 +6,7 @@ use golgi::{ }; async fn run() -> Result<(), GolgiError> { - let mut sbot_client = Sbot::connect(None, None).await?; + let mut sbot_client = Sbot::init(None, None).await?; let id = sbot_client.whoami().await?; println!("whoami: {}", id); diff --git a/examples/ssb-invite.rs b/examples/ssb-invite.rs index 42b054c..5268194 100644 --- a/examples/ssb-invite.rs +++ b/examples/ssb-invite.rs @@ -5,7 +5,7 @@ use kuska_ssb::api::dto::content::PubAddress; use golgi::{messages::SsbMessageContent, GolgiError, Sbot}; async fn run() -> Result<(), GolgiError> { - let mut sbot_client = Sbot::connect(None, None).await?; + let mut sbot_client = Sbot::init(None, None).await?; let id = sbot_client.whoami().await?; println!("whoami: {}", id); diff --git a/examples/ssb-stream-example.rs b/examples/ssb-stream-example.rs index fd1b4c2..f896e4d 100644 --- a/examples/ssb-stream-example.rs +++ b/examples/ssb-stream-example.rs @@ -9,7 +9,7 @@ use golgi::{ }; async fn run() -> Result<(), GolgiError> { - let mut sbot_client = Sbot::connect(None, None).await?; + let mut sbot_client = Sbot::init(None, None).await?; let id = sbot_client.whoami().await?; println!("whoami: {}", id); diff --git a/muxrpc_notes b/muxrpc_notes new file mode 100644 index 0000000..7218136 --- /dev/null +++ b/muxrpc_notes @@ -0,0 +1,33 @@ + + +[ how to use subset query ] + +name of the muxrpc call: `paritalReplication.getSubset` + +https://github.com/cryptoscope/ssb/blob/a781ad4ee51523df1d3858d9859c285d0eeb2fb1/sbot/manifest.go#L75-L76 + +subset query tests + +https://github.com/cryptoscope/ssb/blob/a781ad4ee51523df1d3858d9859c285d0eeb2fb1/query/subsetquery_test.go + +[ subset query args ] + +get all msgs of type `foo`: + +{"op":"type","string":"foo"} + +get all msgs by author `feed`: + +{"op":"author","feed":"@AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=.ed25519"} + +get all msgs of type `foo` by author `feed`: + +{"op":"and","args":[{"op":"author","feed":"@AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=.ed25519"},{"op":"type","string":"foo"}]} + +get all msgs of type `foo` or `bar`: + +{"op":"or","args":[{"op":"type","string":"foo"},{"op":"type","string":"bar"}]} + +get all msgs of type `foo` and `bar` by author `feed`: + +{"op":"and","args":[{"op":"author","feed":"@AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=.ed25519"},{"op":"or","args":[{"op":"type","string":"foo"},{"op":"type","string":"bar"}]}]} diff --git a/notes b/notes new file mode 100644 index 0000000..5dc3752 --- /dev/null +++ b/notes @@ -0,0 +1,34 @@ + +[ current ] + +src +├── error.rs +├── lib.rs +├── messages.rs +├── sbot +│ ├── about.rs +│ ├── friends.rs +│ ├── get_subset.rs +│ ├── history_stream.rs +│ ├── invite.rs +│ ├── mod.rs +│ ├── publish.rs +│ └── sbot_connection.rs +└── utils.rs + +[ experiment ] + +src +├── api +│ ├── about.rs +│ ├── friends.rs +│ ├── get_subset.rs +│ ├── history_stream.rs +│ ├── invite.rs +│ ├── mod.rs +│ └── publish.rs +├── error.rs +├── lib.rs +├── messages.rs +├── sbot.rs +└── utils.rs diff --git a/src/lib.rs b/src/lib.rs index 05bc007..84321e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,7 @@ //! pub async fn run() -> Result<(), GolgiError> { //! // Attempt to connect to an sbot instance using the default IP address, //! // port and network key (aka. capabilities key). -//! let mut sbot_client = Sbot::connect(None, None).await?; +//! let mut sbot_client = Sbot::init(None, None).await?; //! //! // Call the `whoami` RPC method to retrieve the public key for the sbot //! // identity. diff --git a/src/sbot.rs b/src/sbot.rs index 33a27d6..40b958d 100644 --- a/src/sbot.rs +++ b/src/sbot.rs @@ -17,7 +17,7 @@ use crate::error::GolgiError; /// and read the responses. /// Note there can be multiple SbotConnection at the same time. pub struct SbotConnection { - /// client for writing requests to go-bot + /// Client for writing requests to go-bot pub client: ApiCaller, /// RpcReader object for reading responses from go-sbot pub rpc_reader: RpcReader, @@ -93,7 +93,7 @@ impl Sbot { .await .map_err(|source| GolgiError::Io { source, - context: "socket error; failed to initiate tcp stream connection".to_string(), + context: "failed to initiate tcp stream connection".to_string(), })?; let handshake = kuska_handshake::async_std::handshake_client( From 1c434193eb8555e79d176e29a9904c8b8ebe8717 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 9 Feb 2022 08:55:11 +0200 Subject: [PATCH 13/27] remove notes which were accidentally committed --- api_design_notes | 65 ------------------------------------------------ muxrpc_notes | 33 ------------------------ notes | 34 ------------------------- 3 files changed, 132 deletions(-) delete mode 100644 api_design_notes delete mode 100644 muxrpc_notes delete mode 100644 notes diff --git a/api_design_notes b/api_design_notes deleted file mode 100644 index d8af7b9..0000000 --- a/api_design_notes +++ /dev/null @@ -1,65 +0,0 @@ -Higher level functions: - -```rust -let mut sbot_client = Sbot::connect(); - -// second parameter is `Option`; if `None`, return our relationship with `id` -// if second parameter is `Some(dest_id)`, return id's relationship with dest_id -match sbot_client.get_relationship(id, None) { - Relationship::Following => , - Relationship::NotFollowing => , - Relationship::Blocking => , - Relationship::Muting => , -} - -sbot_client.set_relationship(id, -``` - -data structures - -```rust -enum Relationship { - Following, - NotFollowing, - Blocking, - Muting -} - -struct Profile { - id: String, - name: String, - description: String, - // blob reference (maybe?) - image: String, -} -``` - ------ - -whoami - id: String - -name(id) -> name: String -description(id) -> description: String - -publish_description(id, text) -> msg_ref: String -publish_name(id, text) -> msg_ref: String -publish_post(text) -> msg_ref: String - -follows(id) -> Vec -followers(id) -> Vec -friends(id) -> Vec -blocks(id) -> Vec - -follow(id) -> msg_ref -unfollow(id) -> msg_ref -block(id) -> msg_ref - -profile_image(id) - ------ - -``` -let mut sbot_client = Sbot::init(); -``` - ------ \ No newline at end of file diff --git a/muxrpc_notes b/muxrpc_notes deleted file mode 100644 index 7218136..0000000 --- a/muxrpc_notes +++ /dev/null @@ -1,33 +0,0 @@ - - -[ how to use subset query ] - -name of the muxrpc call: `paritalReplication.getSubset` - -https://github.com/cryptoscope/ssb/blob/a781ad4ee51523df1d3858d9859c285d0eeb2fb1/sbot/manifest.go#L75-L76 - -subset query tests - -https://github.com/cryptoscope/ssb/blob/a781ad4ee51523df1d3858d9859c285d0eeb2fb1/query/subsetquery_test.go - -[ subset query args ] - -get all msgs of type `foo`: - -{"op":"type","string":"foo"} - -get all msgs by author `feed`: - -{"op":"author","feed":"@AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=.ed25519"} - -get all msgs of type `foo` by author `feed`: - -{"op":"and","args":[{"op":"author","feed":"@AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=.ed25519"},{"op":"type","string":"foo"}]} - -get all msgs of type `foo` or `bar`: - -{"op":"or","args":[{"op":"type","string":"foo"},{"op":"type","string":"bar"}]} - -get all msgs of type `foo` and `bar` by author `feed`: - -{"op":"and","args":[{"op":"author","feed":"@AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=.ed25519"},{"op":"or","args":[{"op":"type","string":"foo"},{"op":"type","string":"bar"}]}]} diff --git a/notes b/notes deleted file mode 100644 index 5dc3752..0000000 --- a/notes +++ /dev/null @@ -1,34 +0,0 @@ - -[ current ] - -src -├── error.rs -├── lib.rs -├── messages.rs -├── sbot -│ ├── about.rs -│ ├── friends.rs -│ ├── get_subset.rs -│ ├── history_stream.rs -│ ├── invite.rs -│ ├── mod.rs -│ ├── publish.rs -│ └── sbot_connection.rs -└── utils.rs - -[ experiment ] - -src -├── api -│ ├── about.rs -│ ├── friends.rs -│ ├── get_subset.rs -│ ├── history_stream.rs -│ ├── invite.rs -│ ├── mod.rs -│ └── publish.rs -├── error.rs -├── lib.rs -├── messages.rs -├── sbot.rs -└── utils.rs From 9c959346f1084dc69e046c5ce6a034d4699c6c6f Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 14 Feb 2022 12:23:10 +0200 Subject: [PATCH 14/27] clarify api descriptions for about msgs --- src/api/about.rs | 120 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 32 deletions(-) diff --git a/src/api/about.rs b/src/api/about.rs index 2b86a85..c408b86 100644 --- a/src/api/about.rs +++ b/src/api/about.rs @@ -23,7 +23,36 @@ use crate::{ }; impl Sbot { - /// Get the about messages for a particular peer in order of recency. + /// Get all the `about` type messages for a peer in order of recency + /// (ie. most recent messages first). + /// + /// # Example + /// + /// ```rust + /// use async_std::stream::{Stream, StreamExt}; + /// use futures::pin_mut; + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn about_message_stream() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519"; + /// + /// let about_message_stream = sbot_client.get_about_message_stream(ssb_id).await?; + /// + /// // Make the stream into an iterator. + /// pin_mut!(about_message_stream); + /// + /// about_message_stream.for_each(|msg| { + /// match msg { + /// Ok(val) => println!("msg value: {:?}", val), + /// Err(e) => eprintln!("error: {}", e), + /// } + /// }).await; + /// + /// Ok(()) + /// } + /// ``` pub async fn get_about_message_stream( &mut self, ssb_id: &str, @@ -32,13 +61,16 @@ impl Sbot { op: "author".to_string(), feed: ssb_id.to_string(), }; + // specify that most recent messages should be returned first let query_options = SubsetQueryOptions { descending: Some(true), keys: None, page_limit: None, }; + let get_subset_stream = self.get_subset_stream(query, Some(query_options)).await?; + // TODO: after fixing sbot regression, // change this subset query to filter by type about in addition to author // and remove this filter section @@ -47,11 +79,13 @@ impl Sbot { Ok(val) => val.is_message_type(SsbMessageContentType::About), Err(_err) => false, }); + // return about message stream Ok(about_message_stream) } - /// Get value of latest about message with given key for given peer. + /// Get the value of the latest `about` type message, containing the given + /// `key`, for a peer. pub async fn get_latest_about_message( &mut self, ssb_id: &str, @@ -59,9 +93,13 @@ impl Sbot { ) -> Result, GolgiError> { // get about_message_stream let about_message_stream = self.get_about_message_stream(ssb_id).await?; - // now we have a stream of about messages with most recent at the front of the vector + + // now we have a stream of about messages with most recent at the front + // of the vector pin_mut!(about_message_stream); - // iterate through the vector looking for most recent about message with the given key + + // iterate through the vector looking for most recent about message with + // the given key let latest_about_message_res: Option> = about_message_stream // find the first msg that contains the field `key` @@ -70,8 +108,10 @@ impl Sbot { Err(_) => false, }) .await; + // Option> -> Option let latest_about_message = latest_about_message_res.and_then(|msg| msg.ok()); + // Option -> Option let latest_about_value = latest_about_message.and_then(|msg| { msg @@ -83,11 +123,13 @@ impl Sbot { // Option<&str> -> Option .map(|value| value.to_string()) }); + // return value is either `Ok(Some(String))` or `Ok(None)` Ok(latest_about_value) } - /// Get HashMap of profile info for a peer as defined by the given ID. + /// Get the latest `name`, `description` and `image` values for a peer, + /// as defined in their `about` type messages. pub async fn get_profile_info( &mut self, ssb_id: &str, @@ -96,8 +138,8 @@ impl Sbot { self.get_about_info(ssb_id, keys_to_search_for).await } - /// Get HashMap of name and image for given peer - /// (this is can be used to display profile images of a list of users). + /// Get the latest `name` and `image` values for a peer. This method can + /// be used to display profile images of a list of users. pub async fn get_name_and_image( &mut self, ssb_id: &str, @@ -106,18 +148,34 @@ impl Sbot { self.get_about_info(ssb_id, keys_to_search_for).await } - /// Get HashMap of about keys to values for given user by iteratively - /// searching through a stream of about messages, in order of recency, - /// until we find all about messages for all needed info or reach the end - /// of the stream. + /// Get the latest values for the provided keys from the `about` type + /// messages of a peer. The method will return once a value has been + /// found for each key, or once all messages have been checked. /// - /// # Arguments + /// # Example /// - /// * `ssb_id` - A reference to a string slice which represents the id of - /// the user to get info about. - /// * `keys_to_search_for` - A mutable vector of string slice, which - /// represent the about keys that will be searched for. As they are found, - /// keys are removed from the vector. + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn about_info() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519"; + /// let keys_to_search_for = vec!["name", "description"]; + /// + /// let about_info = sbot_client.get_about_info(ssb_id, keys_to_search_for).await?; + /// + /// for (name, desc) in about_info { + /// println!( + /// "peer {} is named {} and describes themself as follows: {}", + /// ssb_id, name, desc, + /// ); + /// } + /// + /// + /// Ok(()) + /// } + /// ``` pub async fn get_about_info( &mut self, ssb_id: &str, @@ -125,9 +183,15 @@ impl Sbot { ) -> Result, GolgiError> { // get about_message_stream let about_message_stream = self.get_about_message_stream(ssb_id).await?; - // now we have a stream of about messages with most recent at the front of the vector - pin_mut!(about_message_stream); // needed for iteration + + // now we have a stream of about messages with most recent at the front + // of the vector + + // `pin_mut!` is needed for iteration + pin_mut!(about_message_stream); + let mut profile_info: HashMap = HashMap::new(); + // iterate through the stream while it still has more values and // we still have keys we are looking for while let Some(res) = about_message_stream.next().await { @@ -150,7 +214,8 @@ impl Sbot { Some(val) => { // if a value is found, then insert it profile_info.insert(key.to_string(), val); - // remove this key fom keys_to_search_for, since we are no longer searching for it + // remove this key from keys_to_search_for, + // since we are no longer searching for it keys_to_search_for.retain(|val| val != key) } None => continue, @@ -163,25 +228,16 @@ impl Sbot { } } } + Ok(profile_info) } - /// Get latest name for the given peer. - /// - /// # Arguments - /// - /// * `ssb_id` - A reference to a string slice which represents the SSB ID - /// to lookup the about name for. + /// Get the latest `name` value for a peer. pub async fn get_name(&mut self, ssb_id: &str) -> Result, GolgiError> { self.get_latest_about_message(ssb_id, "name").await } - /// Get latest description for the given peer. - /// - /// # Arguments - /// - /// * `ssb_id` - A reference to a string slice which represents the SSB ID - /// to lookup the about description for. + /// Get the latest `description` value for a peer. pub async fn get_description(&mut self, ssb_id: &str) -> Result, GolgiError> { self.get_latest_about_message(ssb_id, "description").await } From e0de8ec0d9ae30ada6282824c6c0b3f38d3b6e6f Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 14 Feb 2022 12:23:34 +0200 Subject: [PATCH 15/27] update kuska path and readme --- Cargo.toml | 5 ++--- src/lib.rs | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7fc8728..a7f11a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,13 +6,12 @@ edition = "2021" [dependencies] async-std = "1.10.0" +async-stream = "0.3.2" base64 = "0.13.0" futures = "0.3.18" hex = "0.4.3" kuska-handshake = { version = "0.2.0", features = ["async_std"] } kuska-sodiumoxide = "0.2.5-0" -# waiting for a pr merge upstream -kuska-ssb = { path = "../ssb" } +kuska-ssb = { git = "https://github.com/Kuska-ssb/ssb" } serde = { version = "1", features = ["derive"] } serde_json = "1" -async-stream = "0.3.2" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 84321e5..1869e24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,8 @@ //! queries; to follow, unfollow, block and unblock a peer; to query the social //! graph; and to generate pub invite codes. //! +//! Visit the [API modules](crate::api) to view the available methods. +//! //! ## Example Usage //! //! Basic usage is demonstrated below. Visit the [examples directory](https://git.coopcloud.tech/golgi-ssb/golgi/src/branch/main/examples) in the `golgi` repository for From 6d2115607bd369a56a9aac0d8803b38430ffefb3 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 14 Feb 2022 14:31:30 +0200 Subject: [PATCH 16/27] add an example for each about method --- src/api/about.rs | 155 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 149 insertions(+), 6 deletions(-) diff --git a/src/api/about.rs b/src/api/about.rs index c408b86..c5b4def 100644 --- a/src/api/about.rs +++ b/src/api/about.rs @@ -86,6 +86,28 @@ impl Sbot { /// Get the value of the latest `about` type message, containing the given /// `key`, for a peer. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn name_info() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519"; + /// let key = "name"; + /// + /// let name_info = sbot_client.get_latest_about_message(ssb_id, key).await?; + /// + /// match name_info { + /// Some(name) => println!("peer {} is named {}", ssb_id, name), + /// None => println!("no name found for peer {}", ssb_id) + /// } + /// + /// Ok(()) + /// } + /// ``` pub async fn get_latest_about_message( &mut self, ssb_id: &str, @@ -130,6 +152,38 @@ impl Sbot { /// Get the latest `name`, `description` and `image` values for a peer, /// as defined in their `about` type messages. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn profile_info() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519"; + /// + /// let profile_info = sbot_client.get_profile_info(ssb_id).await?; + /// + /// let name = profile_info.get("name"); + /// let description = profile_info.get("description"); + /// let image = profile_info.get("image"); + /// + /// match (name, description, image) { + /// (Some(name), Some(desc), Some(image)) => { + /// println!( + /// "peer {} is named {}. their profile image blob reference is {} and they describe themself as follows: {}", + /// ssb_id, name, image, desc, + /// ) + /// }, + /// (_, _, _) => { + /// eprintln!("failed to retrieve all profile info values") + /// } + /// } + /// + /// Ok(()) + /// } + /// ``` pub async fn get_profile_info( &mut self, ssb_id: &str, @@ -140,6 +194,42 @@ impl Sbot { /// Get the latest `name` and `image` values for a peer. This method can /// be used to display profile images of a list of users. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn name_and_image_info() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519"; + /// + /// let profile_info = sbot_client.get_name_and_image(ssb_id).await?; + /// + /// let name = profile_info.get("name"); + /// let image = profile_info.get("image"); + /// + /// match (name, image) { + /// (Some(name), Some(image)) => { + /// println!( + /// "peer {} is named {}. their profile image blob reference is {}.", + /// ssb_id, name, image, + /// ) + /// }, + /// (Some(name), None) => { + /// println!( + /// "peer {} is named {}. no image blob reference was found for them.", + /// ssb_id, name, + /// ) + /// }, + /// (_, _) => { + /// eprintln!("failed to retrieve all profile info values") + /// } + /// } + /// + /// Ok(()) + /// } pub async fn get_name_and_image( &mut self, ssb_id: &str, @@ -165,13 +255,26 @@ impl Sbot { /// /// let about_info = sbot_client.get_about_info(ssb_id, keys_to_search_for).await?; /// - /// for (name, desc) in about_info { - /// println!( - /// "peer {} is named {} and describes themself as follows: {}", - /// ssb_id, name, desc, - /// ); - /// } + /// let name = about_info.get("name"); + /// let description = about_info.get("description"); /// + /// match (name, description) { + /// (Some(name), Some(desc)) => { + /// println!( + /// "peer {} is named {}. they describe themself as: {}", + /// ssb_id, name, desc, + /// ) + /// }, + /// (Some(name), None) => { + /// println!( + /// "peer {} is named {}. no description was found for them.", + /// ssb_id, name, + /// ) + /// }, + /// (_, _) => { + /// eprintln!("failed to retrieve all profile info values") + /// } + /// } /// /// Ok(()) /// } @@ -233,11 +336,51 @@ impl Sbot { } /// Get the latest `name` value for a peer. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn name_info() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519"; + /// + /// if let Some(name) = sbot_client.get_name(ssb_id).await? { + /// println!("peer {} is named {}", ssb_id, name) + /// } else { + /// eprintln!("no name found for peer {}", ssb_id) + /// } + /// + /// Ok(()) + /// } + /// ``` pub async fn get_name(&mut self, ssb_id: &str) -> Result, GolgiError> { self.get_latest_about_message(ssb_id, "name").await } /// Get the latest `description` value for a peer. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn description_info() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519"; + /// + /// if let Some(desc) = sbot_client.get_description(ssb_id).await? { + /// println!("peer {} describes themself as follows: {}", ssb_id, desc) + /// } else { + /// eprintln!("no description found for peer {}", ssb_id) + /// } + /// + /// Ok(()) + /// } + /// ``` pub async fn get_description(&mut self, ssb_id: &str) -> Result, GolgiError> { self.get_latest_about_message(ssb_id, "description").await } From 635ebb2a8c7f2b70d562cad2ba9ade10ee4edc69 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 14 Feb 2022 15:52:23 +0200 Subject: [PATCH 17/27] add doc comment examples for friends api --- src/api/friends.rs | 236 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 218 insertions(+), 18 deletions(-) diff --git a/src/api/friends.rs b/src/api/friends.rs index 14d1268..63c73af 100644 --- a/src/api/friends.rs +++ b/src/api/friends.rs @@ -16,20 +16,88 @@ use crate::{error::GolgiError, messages::SsbMessageContent, sbot::Sbot, utils}; pub use kuska_ssb::api::dto::content::{FriendsHops, RelationshipQuery}; impl Sbot { - /// Convenience method to set a relationship with following: `true`, - /// blocking: `false`. + /// Follow a peer. + /// + /// This is a convenience method to publish a contact message with + /// following: `true` and blocking: `false`. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn follow_peer() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519"; + /// + /// match sbot_client.follow(ssb_id).await { + /// Ok(msg_ref) => { + /// println!("follow msg reference is: {}", msg_ref) + /// }, + /// Err(e) => eprintln!("failed to follow {}: {}", ssb_id, e) + /// } + /// + /// Ok(()) + /// } + /// ``` pub async fn follow(&mut self, contact: &str) -> Result { self.set_relationship(contact, true, false).await } - /// Convenience method to set a relationship with following: `false`, - /// blocking: `true`. + /// Block a peer. + /// + /// This is a convenience method to publish a contact message with + /// following: `false` and blocking: `true`. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn block_peer() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519"; + /// + /// match sbot_client.block(ssb_id).await { + /// Ok(msg_ref) => { + /// println!("block msg reference is: {}", msg_ref) + /// }, + /// Err(e) => eprintln!("failed to block {}: {}", ssb_id, e) + /// } + /// + /// Ok(()) + /// } + /// ``` pub async fn block(&mut self, contact: &str) -> Result { self.set_relationship(contact, false, true).await } - /// Publish a contact relationship to the given peer (with ssb_id) with - /// the given state. + /// Publish a contact message defining the relationship for a peer. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn relationship() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519"; + /// let following = true; + /// let blocking = false; + /// + /// match sbot_client.set_relationship(ssb_id, following, blocking).await { + /// Ok(msg_ref) => { + /// println!("contact msg reference is: {}", msg_ref) + /// }, + /// Err(e) => eprintln!("failed to set relationship for {}: {}", ssb_id, e) + /// } + /// + /// Ok(()) + /// } + /// ``` pub async fn set_relationship( &mut self, contact: &str, @@ -45,8 +113,38 @@ impl Sbot { self.publish(msg).await } - /// Call the `friends isFollowing` RPC method and return a message reference. - /// Returns `true` if `src_id` is following `dest_id` and `false` otherwise. + /// Get the follow status of two peers (ie. does one peer follow the other?). + /// + /// A `RelationshipQuery` `struct` must be defined and passed into this method. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError, api::friends::RelationshipQuery}; + /// + /// async fn relationship() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let peer_a = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519"; + /// let peer_b = "@3QoWCcy46X9a4jTnOl8m3+n1gKfbsukWuODDxNGN0W8=.ed25519"; + /// + /// let query = RelationshipQuery { + /// source: peer_a.to_string(), + /// dest: peer_b.to_string(), + /// }; + /// + /// match sbot_client.friends_is_following(query).await { + /// Ok(following) if following == "true" => { + /// println!("{} is following {}", peer_a, peer_b) + /// }, + /// Ok(_) => println!("{} is not following {}", peer_a, peer_b), + /// Err(e) => eprintln!("failed to query relationship status for {} and {}: {}", peer_a, + /// peer_b, e) + /// } + /// + /// Ok(()) + /// } + /// ``` pub async fn friends_is_following( &mut self, args: RelationshipQuery, @@ -65,8 +163,38 @@ impl Sbot { .await } - /// Call the `friends isblocking` RPC method and return a message reference. - /// Returns `true` if `src_id` is blocking `dest_id` and `false` otherwise. + /// Get the block status of two peers (ie. does one peer block the other?). + /// + /// A `RelationshipQuery` `struct` must be defined and passed into this method. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError, api::friends::RelationshipQuery}; + /// + /// async fn relationship() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let peer_a = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519"; + /// let peer_b = "@3QoWCcy46X9a4jTnOl8m3+n1gKfbsukWuODDxNGN0W8=.ed25519"; + /// + /// let query = RelationshipQuery { + /// source: peer_a.to_string(), + /// dest: peer_b.to_string(), + /// }; + /// + /// match sbot_client.friends_is_blocking(query).await { + /// Ok(blocking) if blocking == "true" => { + /// println!("{} is blocking {}", peer_a, peer_b) + /// }, + /// Ok(_) => println!("{} is not blocking {}", peer_a, peer_b), + /// Err(e) => eprintln!("failed to query relationship status for {} and {}: {}", peer_a, + /// peer_b, e) + /// } + /// + /// Ok(()) + /// } + /// ``` pub async fn friends_is_blocking( &mut self, args: RelationshipQuery, @@ -85,7 +213,27 @@ impl Sbot { .await } - /// Return a Vec where each element is a peer you are following. + /// Get a list of peers followed by the local peer. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn follows() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let follows = sbot_client.get_follows().await?; + /// + /// if follows.is_empty() { + /// println!("we do not follow any peers") + /// } else { + /// follows.iter().for_each(|peer| println!("we follow {}", peer)) + /// } + /// + /// Ok(()) + /// } + /// ``` pub async fn get_follows(&mut self) -> Result, GolgiError> { self.friends_hops(FriendsHops { max: 1, @@ -95,10 +243,33 @@ impl Sbot { .await } - // Gets a Vec where each element is a peer who follows you - /// TODO: currently this method is not working - /// go-sbot does not seem to listen to the reverse=True parameter - /// and just returns follows. + /// Get a list of peers following the local peer. + /// + /// NOTE: this method is not currently working as expected. + /// + /// go-sbot does not currently implement the `reverse=True` parameter. + /// As a result, the parameter is ignored and this method returns follows + /// instead of followers. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn followers() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// // let followers = sbot_client.get_followers().await?; + /// + /// // if followers.is_empty() { + /// // println!("no peers follow us") + /// // } else { + /// // followers.iter().for_each(|peer| println!("{} is following us", peer)) + /// // } + /// + /// Ok(()) + /// } + /// ``` async fn _get_followers(&mut self) -> Result, GolgiError> { self.friends_hops(FriendsHops { max: 1, @@ -108,11 +279,40 @@ impl Sbot { .await } - /// Call the `friends hops` RPC method and return a Vector - /// where each element of the vector is the ssb_id of a peer. + /// Get a list of peers within the specified hops range. + /// + /// A `RelationshipQuery` `struct` must be defined and passed into this method. /// /// When opts.reverse = True, it should return peers who are following you - /// (but this is currently not working). + /// (but this is not currently working). + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError, api::friends::FriendsHops}; + /// + /// async fn peers_within_range() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let hops = 2; + /// + /// let query = FriendsHops { + /// max: hops, + /// reverse: Some(false), + /// start: None, + /// }; + /// + /// let peers = sbot_client.friends_hops(query).await?; + /// + /// if peers.is_empty() { + /// println!("no peers found within {} hops", hops) + /// } else { + /// peers.iter().for_each(|peer| println!("{} is within {} hops", peer, hops)) + /// } + /// + /// Ok(()) + /// } + /// ``` pub async fn friends_hops(&mut self, args: FriendsHops) -> Result, GolgiError> { let mut sbot_connection = self.get_sbot_connection().await?; let req_id = sbot_connection.client.friends_hops_req_send(args).await?; From 29d19271041b0c618139f75cff5acd78572115e8 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 14 Feb 2022 15:52:39 +0200 Subject: [PATCH 18/27] add doc comment examples for get_subset api --- src/api/get_subset.rs | 55 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/src/api/get_subset.rs b/src/api/get_subset.rs index 61d00f3..8461835 100644 --- a/src/api/get_subset.rs +++ b/src/api/get_subset.rs @@ -12,14 +12,59 @@ use crate::{error::GolgiError, messages::SsbMessageValue, sbot::Sbot, utils}; pub use kuska_ssb::api::dto::content::{SubsetQuery, SubsetQueryOptions}; impl Sbot { - /// Call the `partialReplication getSubset` RPC method - /// and return a Stream of Result + /// Make a subset query, as defined by the [Subset replication for SSB specification](https://github.com/ssb-ngi-pointer/ssb-subset-replication-spec). + /// + /// Calls the `partialReplication. getSubset` RPC method. /// /// # Arguments /// - /// * `query` - A `SubsetQuery` which specifies what filters to use. - /// * `option` - An Option<`SubsetQueryOptions`> which, if provided, adds additional - /// specifications to the query, such as specifying page limit and/or descending. + /// * `query` - A `SubsetQuery` which specifies the desired query filters. + /// * `options` - An Option<`SubsetQueryOptions`> which, if provided, adds + /// additional specifications to the query, such as page limit and/or + /// descending results. + /// + /// # Example + /// + /// ```rust + /// use async_std::stream::StreamExt; + /// use golgi::{ + /// Sbot, + /// GolgiError, + /// api::get_subset::{ + /// SubsetQuery, + /// SubsetQueryOptions + /// } + /// }; + /// + /// async fn query() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let post_query = SubsetQuery::Type { + /// op: "type".to_string(), + /// string: "post".to_string() + /// }; + /// + /// let post_query_opts = SubsetQueryOptions { + /// descending: Some(true), + /// keys: None, + /// page_limit: Some(5), + /// }; + /// + /// // Return 5 `post` type messages from any author in descending order. + /// let query_stream = sbot_client + /// .get_subset_stream(post_query, Some(post_query_opts)) + /// .await?; + /// + /// // Iterate over the stream and pretty-print each returned message + /// // value while ignoring any errors. + /// query_stream.for_each(|msg| match msg { + /// Ok(val) => println!("{:#?}", val), + /// Err(_) => (), + /// }); + /// + /// Ok(()) + /// } + /// ``` pub async fn get_subset_stream( &mut self, query: SubsetQuery, From b714bda988f0554243acef03fdd08fc28c5af4e1 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 14 Feb 2022 16:13:19 +0200 Subject: [PATCH 19/27] add doc comment examples for history api --- src/api/history_stream.rs | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/api/history_stream.rs b/src/api/history_stream.rs index 69fc241..e59316e 100644 --- a/src/api/history_stream.rs +++ b/src/api/history_stream.rs @@ -10,8 +10,38 @@ use kuska_ssb::api::dto::CreateHistoryStreamIn; use crate::{error::GolgiError, messages::SsbMessageValue, sbot::Sbot, utils}; impl Sbot { - /// Call the `createHistoryStream` RPC method - /// and return a Stream of Result. + /// Call the `createHistoryStream` RPC method. + /// + /// # Example + /// + /// ```rust + /// use async_std::stream::StreamExt; + /// use golgi::{ + /// Sbot, + /// GolgiError, + /// api::get_subset::{ + /// SubsetQuery, + /// SubsetQueryOptions + /// } + /// }; + /// + /// async fn history() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519".to_string(); + /// + /// let history_stream = sbot_client.create_history_stream(ssb_id).await?; + /// + /// history_stream.for_each(|msg| { + /// match msg { + /// Ok(val) => println!("msg value: {:?}", val), + /// Err(e) => eprintln!("error: {}", e), + /// } + /// }).await; + /// + /// Ok(()) + /// } + /// ``` pub async fn create_history_stream( &mut self, id: String, From d0f68086a16fe0417a1ecb7a83e37ee553a6ed87 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 14 Feb 2022 16:13:29 +0200 Subject: [PATCH 20/27] add doc comment examples for invite api --- src/api/invite.rs | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/api/invite.rs b/src/api/invite.rs index 74a186e..f9379f0 100644 --- a/src/api/invite.rs +++ b/src/api/invite.rs @@ -8,7 +8,25 @@ use crate::{error::GolgiError, sbot::Sbot, utils}; impl Sbot { - /// Call the `invite create` RPC method and return the created invite + /// Generate an invite code. + /// + /// Calls the `invite.create` RPC method and returns the code. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn invite_code_generator() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let invite_code = sbot_client.invite_create(5).await?; + /// + /// println!("this invite code can be used 5 times: {}", invite_code); + /// + /// Ok(()) + /// } + /// ``` pub async fn invite_create(&mut self, uses: u16) -> Result { let mut sbot_connection = self.get_sbot_connection().await?; let req_id = sbot_connection.client.invite_create_req_send(uses).await?; @@ -21,7 +39,29 @@ impl Sbot { .await } - /// Call the `invite use` RPC method and return a reference to the message. + /// Use an invite code. + /// + /// Calls the `invite.use` RPC method and returns a reference to the follow + /// message. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn invite_code_consumer() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let invite_code = "127.0.0.1:8008:@0iMa+vP7B2aMrV3dzRxlch/iqZn/UM3S3Oo2oVeILY8=.ed25519~ZHNjeajPB/84NjjsrglZInlh46W55RcNDPcffTPgX/Q="; + /// + /// match sbot_client.invite_use(invite_code).await { + /// Ok(msg_ref) => println!("consumed invite code. msg reference: {}", msg_ref), + /// Err(e) => eprintln!("failed to consume the invite code: {}", e), + /// } + /// + /// Ok(()) + /// } + /// ``` pub async fn invite_use(&mut self, invite_code: &str) -> Result { let mut sbot_connection = self.get_sbot_connection().await?; let req_id = sbot_connection From 1bb9e1f7e176ec9436b3594c2ac29f28f37ca40a Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 14 Feb 2022 16:40:48 +0200 Subject: [PATCH 21/27] add doc comment examples for publish api --- src/api/publish.rs | 98 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 85 insertions(+), 13 deletions(-) diff --git a/src/api/publish.rs b/src/api/publish.rs index fb2ddb8..33c8361 100644 --- a/src/api/publish.rs +++ b/src/api/publish.rs @@ -10,13 +10,34 @@ use crate::{error::GolgiError, messages::SsbMessageContent, sbot::Sbot, utils}; impl Sbot { - /// Call the `publish` RPC method and return a message reference. + /// Publish a message. /// /// # Arguments /// - /// * `msg` - A `SsbMessageContent` `enum` whose variants include `Pub`, `Post`, `Contact`, `About`, - /// `Channel` and `Vote`. See the `kuska_ssb` documentation for further details such as field - /// names and accepted values for each variant. + /// * `msg` - A `SsbMessageContent` `enum` whose variants include `Pub`, + /// `Post`, `Contact`, `About`, `Channel` and `Vote`. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError, messages::SsbMessageContent}; + /// + /// async fn publish_a_msg() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// // Construct an SSB message of type `post`. + /// let post = SsbMessageContent::Post { + /// text: "And then those vesicles, filled with the Golgi products, move to the rest of the cell".to_string(), + /// mentions: None, + /// }; + /// + /// let msg_ref = sbot_client.publish(post).await?; + /// + /// println!("msg reference for the golgi post: {}", msg_ref); + /// + /// Ok(()) + /// } + /// ``` pub async fn publish(&mut self, msg: SsbMessageContent) -> Result { let mut sbot_connection = self.get_sbot_connection().await?; let req_id = sbot_connection.client.publish_req_send(msg).await?; @@ -29,11 +50,28 @@ impl Sbot { .await } - /// Wrapper for publish which constructs and publishes a post message appropriately from a string. + /// Publish a post. /// - /// # Arguments + /// Convenient wrapper around the `publish` method which constructs and + /// publishes a `post` type message appropriately from a string. /// - /// * `text` - A reference to a string slice which represents the text to be published in the post + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn publish_a_post() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let text = "The Golgi is located right near the nucleus."; + /// + /// let msg_ref = sbot_client.publish_post(text).await?; + /// + /// println!("msg reference for the golgi post: {}", msg_ref); + /// + /// Ok(()) + /// } + /// ``` pub async fn publish_post(&mut self, text: &str) -> Result { let msg = SsbMessageContent::Post { text: text.to_string(), @@ -42,11 +80,28 @@ impl Sbot { self.publish(msg).await } - /// Wrapper for publish which constructs and publishes an about description message appropriately from a string. + /// Publish a description for the local identity. /// - /// # Arguments + /// Convenient wrapper around the `publish` method which constructs and + /// publishes an `about` type description message appropriately from a string. /// - /// * `description` - A reference to a string slice which represents the text to be published as an about description. + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn publish_a_description() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let description = "The Golgi apparatus was identified by the Italian scientist Camillo Golgi in 1897."; + /// + /// let msg_ref = sbot_client.publish_description(description).await?; + /// + /// println!("msg reference for the golgi description: {}", msg_ref); + /// + /// Ok(()) + /// } + /// ``` pub async fn publish_description(&mut self, description: &str) -> Result { let msg = SsbMessageContent::About { about: self.id.to_string(), @@ -61,11 +116,28 @@ impl Sbot { self.publish(msg).await } - /// Wrapper for publish which constructs and publishes an about name message appropriately from a string. + /// Publish a name for the local identity. /// - /// # Arguments + /// Convenient wrapper around the `publish` method which constructs and + /// publishes an `about` type name message appropriately from a string. /// - /// * `name` - A reference to a string slice which represents the text to be published as an about name. + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn publish_a_name() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let name = "glyphski_golgionikus"; + /// + /// let msg_ref = sbot_client.publish_name(name).await?; + /// + /// println!("msg reference: {}", msg_ref); + /// + /// Ok(()) + /// } + /// ``` pub async fn publish_name(&mut self, name: &str) -> Result { let msg = SsbMessageContent::About { about: self.id.to_string(), From 32bd924e7efce4ccaa1a904bbf9c00f5f9380f57 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 14 Feb 2022 16:41:00 +0200 Subject: [PATCH 22/27] add doc comment examples for whoami api --- src/api/whoami.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/api/whoami.rs b/src/api/whoami.rs index 156a5a2..8e29871 100644 --- a/src/api/whoami.rs +++ b/src/api/whoami.rs @@ -7,7 +7,23 @@ use crate::{error::GolgiError, sbot::Sbot, utils}; impl Sbot { - /// Call the `whoami` RPC method and return an `id`. + /// Get the public key of the local identity. + /// + /// # Example + /// + /// ```rust + /// use golgi::{Sbot, GolgiError}; + /// + /// async fn fetch_id() -> Result<(), GolgiError> { + /// let mut sbot_client = Sbot::init(None, None).await?; + /// + /// let pub_key = sbot_client.whoami().await?; + /// + /// println!("local ssb id: {}", pub_key); + /// + /// Ok(()) + /// } + /// ``` pub async fn whoami(&mut self) -> Result { let mut sbot_connection = self.get_sbot_connection().await?; let req_id = sbot_connection.client.whoami_req_send().await?; From 8466510fee7046a5fee120e28ab044c27f2bba0c Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 14 Feb 2022 17:00:00 +0200 Subject: [PATCH 23/27] polish the message type docs --- src/messages.rs | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/messages.rs b/src/messages.rs index 7a52a6a..c26da6e 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -7,14 +7,17 @@ use std::fmt::Debug; use crate::error::GolgiError; -/// This is an alias to TypedMessage in kuska, -/// which is renamed as SsbMessageContent in golgi to fit the naming convention -/// of the other types (SsbMessageKVT and SsbMessageValue) +/// `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; -/// 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). +/// 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)] @@ -28,7 +31,7 @@ pub struct SsbMessageValue { pub signature: String, } -// Enum representing the different possible message content types +/// Message content types. #[derive(Debug)] #[allow(missing_docs)] pub enum SsbMessageContentType { @@ -40,9 +43,13 @@ pub enum SsbMessageContentType { } impl SsbMessageValue { - /// Gets 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) + /// 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 { let msg_type = self .content @@ -61,8 +68,8 @@ impl SsbMessageValue { 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 + /// 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 { @@ -73,8 +80,8 @@ impl SsbMessageValue { } } - /// Converts the content json value into an SsbMessageContent enum, - /// using the "type" field as a tag to select which variant of the enum + /// 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. @@ -84,9 +91,10 @@ impl SsbMessageValue { } } -/// 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). +/// 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)] From 74b87b904bc63cc9aa03febaf42d66cbf0810f40 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 14 Feb 2022 17:00:10 +0200 Subject: [PATCH 24/27] polish the utility function docs --- src/utils.rs | 67 +++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index de49d60..5871549 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -9,7 +9,7 @@ use serde_json::Value; use crate::error::GolgiError; use crate::messages::{SsbMessageKVT, SsbMessageValue}; -/// Function to parse an array of bytes (returned by an rpc call) into a KVT. +/// Parse an array of bytes (returned by an rpc call) into a `KVT`. /// /// # Arguments /// @@ -20,7 +20,7 @@ pub fn kvt_res_parse(body: &[u8]) -> Result { Ok(kvt) } -/// Function to parse an array of bytes (returned by an rpc call) into a String. +/// Parse an array of bytes (returned by an rpc call) into a `String`. /// /// # Arguments /// @@ -29,7 +29,7 @@ pub fn string_res_parse(body: &[u8]) -> Result { Ok(std::str::from_utf8(body)?.to_string()) } -/// Function to parse an array of bytes (returned by an rpc call) into a serde_json::Value. +/// Parse an array of bytes (returned by an rpc call) into a `serde_json::Value`. /// /// # Arguments /// @@ -39,7 +39,7 @@ pub fn json_res_parse(body: &[u8]) -> Result { Ok(message) } -/// Function to parse an array of bytes (returned by an rpc call) into an SsbMessageValue +/// Parse an array of bytes (returned by an rpc call) into an `SsbMessageValue`. /// /// # Arguments /// @@ -49,17 +49,18 @@ pub fn ssb_message_res_parse(body: &[u8]) -> Result Ok(message) } -/// Takes in an rpc request number, and a handling function, -/// and waits for an rpc response which matches the request number, -/// and then calls the handling function on the response. +/// Take in an RPC request number along with a handling function and wait for +/// an RPC response which matches the request number. Then, call the handling +/// function on the response. /// /// # Arguments /// /// * `rpc_reader` - A `RpcReader` which can return Messages in a loop /// * `req_no` - A `RequestNo` of the response to listen for -/// * `f` - A function which takes in an array of u8 and returns a Result. -/// This is a function which parses the response from the RpcReader. T is a generic type, -/// so this parse function can return multiple possible types (String, json, custom struct etc.) +/// * `f` - A function which takes in an array of `u8` and returns a +/// `Result`. This is a function which parses the response from +/// the `RpcReader`. `T` is a generic type, so this parse function can return +/// multiple possible types (`String`, JSON, custom struct etc.) pub async fn get_async<'a, R, T, F>( rpc_reader: &mut RpcReader, req_no: RequestNo, @@ -91,19 +92,19 @@ where } } -/// Takes in an rpc request number, and a handling function, -/// and calls the handling function on all RPC responses which match the request number, -/// appending the result of each parsed message to a vector, -/// until a CancelStreamResponse is found, marking the end of the stream, -/// and then finally a result is returned. +/// Take in an RPC request number along with a handling function and call +/// the handling function on all RPC responses which match the request number, +/// appending the result of each parsed message to a vector until a +/// `CancelStreamResponse` is found (marking the end of the stream). /// /// # Arguments /// /// * `rpc_reader` - A `RpcReader` which can return Messages in a loop /// * `req_no` - A `RequestNo` of the response to listen for -/// * `f` - A function which takes in an array of u8 and returns a Result. -/// This is a function which parses the response from the RpcReader. T is a generic type, -/// so this parse function can return multiple possible types (String, json, custom struct etc.) +/// * `f` - A function which takes in an array of `u8` and returns a +/// `Result`. This is a function which parses the response from +/// the `RpcReader`. `T` is a generic type, so this parse function can return +/// multiple possible types (`String`, JSON, custom struct etc.) pub async fn get_source_until_eof<'a, R, T, F>( rpc_reader: &mut RpcReader, req_no: RequestNo, @@ -141,19 +142,20 @@ where Ok(messages) } -/// Takes in an rpc request number, and a handling function, -/// and calls the handling function on all responses which match the request number, -/// and prints out the result of the handling function. +/// Take in an RPC request number along with a handling function and call the +/// handling function on all responses which match the request number. Then, +/// prints out the result of the handling function. /// -/// This is a function useful for debugging, and only prints the output. +/// This function useful for debugging and only prints the output. /// /// # Arguments /// /// * `rpc_reader` - A `RpcReader` which can return Messages in a loop /// * `req_no` - A `RequestNo` of the response to listen for -/// * `f` - A function which takes in an array of u8 and returns a Result. -/// This is a function which parses the response from the RpcReader. T is a generic type, -/// so this parse function can return multiple possible types (String, json, custom struct etc.) +/// * `f` - A function which takes in an array of `u8` and returns a +/// `Result`. This is a function which parses the response from +/// the `RpcReader`. `T` is a generic type, so this parse function can return +/// multiple possible types (`String`, JSON, custom struct etc.) pub async fn print_source_until_eof<'a, R, T, F>( rpc_reader: &mut RpcReader, req_no: RequestNo, @@ -183,17 +185,18 @@ where Ok(()) } -/// Takes in an rpc request number, and a handling function (parsing results of type T), -/// and produces an async_std::stream::Stream -/// of results of type T where the handling function is called -/// on all rpc_reader responses which match the request number. +/// Take in an RPC request number along with a handling function (parsing +/// results of type `T`) and produce an `async_std::stream::Stream` of results +/// of type `T`, where the handling function is called on all `RpcReader` +/// responses which match the request number. /// /// # Arguments /// /// * `req_no` - A `RequestNo` of the response to listen for -/// * `f` - A function which takes in an array of u8 and returns a Result. -/// This is a function which parses the response from the RpcReader. T is a generic type, -/// so this parse function can return multiple possible types (String, json, custom struct etc.) +/// * `f` - A function which takes in an array of `u8` and returns a +/// `Result`. This is a function which parses the response from +/// the `RpcReader`. `T` is a generic type, so this parse function can return +/// multiple possible types (`String`, JSON, custom struct etc.) pub async fn get_source_stream<'a, F, T>( mut rpc_reader: RpcReader, req_no: RequestNo, From 506ebec7e440398344a8a9dea0f11e5e32b1a7e8 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 15 Feb 2022 10:26:21 +0200 Subject: [PATCH 25/27] use full path for pin_mut macro --- src/api/about.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/api/about.rs b/src/api/about.rs index c5b4def..328b803 100644 --- a/src/api/about.rs +++ b/src/api/about.rs @@ -13,7 +13,6 @@ use std::collections::HashMap; use async_std::stream::{Stream, StreamExt}; -use futures::pin_mut; use crate::{ api::get_subset::{SubsetQuery, SubsetQueryOptions}, @@ -30,7 +29,6 @@ impl Sbot { /// /// ```rust /// use async_std::stream::{Stream, StreamExt}; - /// use futures::pin_mut; /// use golgi::{Sbot, GolgiError}; /// /// async fn about_message_stream() -> Result<(), GolgiError> { @@ -41,7 +39,7 @@ impl Sbot { /// let about_message_stream = sbot_client.get_about_message_stream(ssb_id).await?; /// /// // Make the stream into an iterator. - /// pin_mut!(about_message_stream); + /// futures::pin_mut!(about_message_stream); /// /// about_message_stream.for_each(|msg| { /// match msg { @@ -118,7 +116,7 @@ impl Sbot { // now we have a stream of about messages with most recent at the front // of the vector - pin_mut!(about_message_stream); + futures::pin_mut!(about_message_stream); // iterate through the vector looking for most recent about message with // the given key @@ -291,7 +289,7 @@ impl Sbot { // of the vector // `pin_mut!` is needed for iteration - pin_mut!(about_message_stream); + futures::pin_mut!(about_message_stream); let mut profile_info: HashMap = HashMap::new(); From 6a1aa0abdad2339fd1fa1f721ea4d975a3964218 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 15 Feb 2022 10:26:51 +0200 Subject: [PATCH 26/27] extend documentation for all examples --- examples/ssb-client.rs | 38 +++++++++++++++++++++ examples/ssb-friends.rs | 56 ++++++++++++++++++++++++++----- examples/ssb-invite.rs | 33 +++++++++++++++++- examples/ssb-stream-example.rs | 61 ++++++++++++++++++++++++++++------ 4 files changed, 167 insertions(+), 21 deletions(-) diff --git a/examples/ssb-client.rs b/examples/ssb-client.rs index d7ae0b4..0ab02c0 100644 --- a/examples/ssb-client.rs +++ b/examples/ssb-client.rs @@ -2,12 +2,28 @@ use std::process; use golgi::{messages::SsbMessageContent, GolgiError, Sbot}; +// Golgi is an asynchronous library so we must call it from within an +// async function. The `GolgiError` type encapsulates all possible +// error variants for the library. async fn run() -> Result<(), GolgiError> { + // Attempt to connect to an sbot instance using the default IP address, + // port and network key (aka. capabilities key). let mut sbot_client = Sbot::init(None, None).await?; + // Alternatively, we could specify a non-standard IP and port. + // let ip_port = "127.0.0.1:8021".to_string(); + // let mut sbot_client = Sbot::init(Some(ip_port), None).await?; + + // Call the `whoami` RPC method to retrieve the public key for the sbot + // identity. This is our 'local' public key. let id = sbot_client.whoami().await?; + + // Print the public key (identity) to `stdout`. println!("whoami: {}", id); + // Compose an SSB `about` message type. + // The `SsbMessageContent` type has many variants and allows for a high + // degree of control when creating messages. let name = SsbMessageContent::About { about: id.clone(), name: Some("golgi".to_string()), @@ -19,27 +35,46 @@ async fn run() -> Result<(), GolgiError> { start_datetime: None, }; + // Publish the name message. The `publish` method returns a reference to + // the published message. let name_msg_ref = sbot_client.publish(name).await?; + + // Print the message reference to `stdout`. println!("name_msg_ref: {}", name_msg_ref); + // Compose an SSB `post` message type. let post = SsbMessageContent::Post { text: "golgi go womp womp".to_string(), mentions: None, }; + // Publish the post. let post_msg_ref = sbot_client.publish(post).await?; + + // Print the post reference to `stdout`. println!("post_msg_ref: {}", post_msg_ref); + // Golgi also exposes convenience methods for some of the most common + // message types. Here we see an example of a convenience method for + // posting a description message. The description is for the local + // identity, ie. we are publishing this about "ourself". let post_msg_ref = sbot_client .publish_description("this is a description") .await?; + + // Print the description message reference to `stdout`. println!("description: {}", post_msg_ref); let author: String = id.clone(); println!("author: {:?}", author); + + // Retrieve the description for the given public key (identity). let description = sbot_client.get_description(&author).await?; + + // Print the description to `stdout`. println!("found description: {:?}", description); + // Compose and publish another `post` message type. let post = SsbMessageContent::Post { text: "golgi go womp womp2".to_string(), mentions: None, @@ -51,6 +86,9 @@ async fn run() -> Result<(), GolgiError> { Ok(()) } +// Enable an async main function and execute the `run()` function, +// catching any errors and printing them to `stderr` before exiting the +// process. #[async_std::main] async fn main() { if let Err(e) = run().await { diff --git a/examples/ssb-friends.rs b/examples/ssb-friends.rs index 137d2d2..759d792 100644 --- a/examples/ssb-friends.rs +++ b/examples/ssb-friends.rs @@ -5,76 +5,114 @@ use golgi::{ GolgiError, Sbot, }; +// Golgi is an asynchronous library so we must call it from within an +// async function. The `GolgiError` type encapsulates all possible +// error variants for the library. async fn run() -> Result<(), GolgiError> { + // Attempt to connect to an sbot instance using the default IP address, + // port and network key (aka. capabilities key). let mut sbot_client = Sbot::init(None, None).await?; + // Call the `whoami` RPC method to retrieve the public key for the sbot + // identity. This is our 'local' public key. let id = sbot_client.whoami().await?; + + // Print the public key (identity) to `stdout`. println!("whoami: {}", id); - // test ids to follow and block + // Define IDs (public keys) to follow and block. let to_follow = String::from("@5Pt3dKy2HTJ0mWuS78oIiklIX0gBz6BTfEnXsbvke9c=.ed25519"); let to_block = String::from("@7Y4nwfQmVtAilEzi5knXdS2gilW7cGKSHXdXoT086LM=.ed25519"); - // follow to_follow + // Set the relationship of the local identity to the `to_follow` identity. + // In this case, the `set_relationship` method publishes a `contact` + // message which defines following as `true` and blocking as `false`. + // A message reference is returned for the published `contact` message. let response = sbot_client .set_relationship(&to_follow, true, false) .await?; + + // Print the message reference to `stdout`. println!("follow_response: {:?}", response); - // block to_block + // Set the relationship of the local identity to the `to_block` identity. + // In this case, the `set_relationship` method publishes a `contact` + // message which defines following as `false` and blocking as `true`. + // A message reference is returned for the published `contact` message. let response = sbot_client.set_relationship(&to_block, false, true).await?; + + // Print the message reference to `stdout`. println!("follow_response: {:?}", response); - // print all users you are following + // Golgi also exposes convenience methods for following and blocking. + // Here is an example of a simpler way to follow an identity. + let _follow_response = sbot_client.follow(&to_follow).await?; + + // Blocking can be achieved in a similar fashion. + let _block_response = sbot_client.block(&to_block).await?; + + // Get a list of peers within 1 hop of the local identity. let follows = sbot_client .friends_hops(FriendsHops { max: 1, start: None, - // doesnt seem like reverse does anything, currently + // The `reverse` parameter is not currently implemented in `go-sbot`. reverse: Some(false), }) .await?; + + // Print the list of peers to `stdout`. println!("follows: {:?}", follows); - // print if you are following to_follow (should be true) + // Determine if an identity (`source`) is following a second identity (`dest`). + // This method will return `true` or `false`. let mref = sbot_client .friends_is_following(RelationshipQuery { source: id.clone(), dest: to_follow.clone(), }) .await?; + + // Print the follow status to `stdout`. println!("isfollowingmref: {}", mref); - // print if you are blocking to_block (should be true) + // Determine if an identity (`source`) is blocking a second identity (`dest`). let mref = sbot_client .friends_is_blocking(RelationshipQuery { source: id.clone(), dest: to_block.clone(), }) .await?; + + // Print the block status to `stdout`. println!("isblockingmref: {}", mref); - // print if you are blocking to_follow (should be false) let mref = sbot_client .friends_is_blocking(RelationshipQuery { source: id.clone(), dest: to_follow, }) .await?; + + // Output should be `false`. println!("isblockingmref(should be false): {}", mref); - // print if you are following to_block (should be false) let mref = sbot_client .friends_is_following(RelationshipQuery { source: id, dest: to_block.clone(), }) .await?; + + // Output should be `false`. println!("isfollowingmref(should be false): {}", mref); Ok(()) } +// Enable an async main function and execute the `run()` function, +// catching any errors and printing them to `stderr` before exiting the +// process. #[async_std::main] async fn main() { if let Err(e) = run().await { diff --git a/examples/ssb-invite.rs b/examples/ssb-invite.rs index 5268194..5d5881f 100644 --- a/examples/ssb-invite.rs +++ b/examples/ssb-invite.rs @@ -4,38 +4,69 @@ use kuska_ssb::api::dto::content::PubAddress; use golgi::{messages::SsbMessageContent, GolgiError, Sbot}; +// Golgi is an asynchronous library so we must call it from within an +// async function. The `GolgiError` type encapsulates all possible +// error variants for the library. async fn run() -> Result<(), GolgiError> { + // Attempt to connect to an sbot instance using the default IP address, + // port and network key (aka. capabilities key). let mut sbot_client = Sbot::init(None, None).await?; + // Call the `whoami` RPC method to retrieve the public key for the sbot + // identity. This is our 'local' public key. let id = sbot_client.whoami().await?; + + // Print the public key (identity) to `stdout`. println!("whoami: {}", id); - // publish a pub address message (in order to test accepting invite from other client) + // Compose a `pub` address type message. let pub_address_msg = SsbMessageContent::Pub { address: Some(PubAddress { + // IP address. host: Some("127.0.0.1".to_string()), + // Port. port: 8009, + // Public key. key: id, }), }; + // Publish the `pub` address message. + // This step is required for successful invite code creation. let pub_msg_ref = sbot_client.publish(pub_address_msg).await?; + + // Print the message reference to `stdout`. println!("pub_msg_ref: {}", pub_msg_ref); + // Generate an invite code that can be used 1 time. let invite_code = sbot_client.invite_create(1).await?; + + // Print the invite code to `stdout`. println!("invite (1 use): {:?}", invite_code); + // Generate an invite code that can be used 7 times. let invite_code_2 = sbot_client.invite_create(7).await?; + + // Print the invite code to `stdout`. println!("invite (7 uses): {:?}", invite_code_2); + // Define an invite code. let test_invite = "net:ssbroom2.commoninternet.net:8009~shs:wm8a1zHWjtESv4XSKMWU/rPRhnAoAiSAe4hQSY0UF5A="; + + // Redeem an invite code (initiating a mutual follow between the local + // identity and the identity which generated the code (`wm8a1z...`). let mref = sbot_client.invite_use(test_invite).await?; + + // Print the message reference to `stdout`. println!("mref: {:?}", mref); Ok(()) } +// Enable an async main function and execute the `run()` function, +// catching any errors and printing them to `stderr` before exiting the +// process. #[async_std::main] async fn main() { if let Err(e) = run().await { diff --git a/examples/ssb-stream-example.rs b/examples/ssb-stream-example.rs index f896e4d..8d05342 100644 --- a/examples/ssb-stream-example.rs +++ b/examples/ssb-stream-example.rs @@ -1,56 +1,85 @@ use std::process; use async_std::stream::StreamExt; -use futures::{pin_mut, TryStreamExt}; +use futures::TryStreamExt; use golgi::{ messages::{SsbMessageContentType, SsbMessageValue}, GolgiError, Sbot, }; +// Golgi is an asynchronous library so we must call it from within an +// async function. The `GolgiError` type encapsulates all possible +// error variants for the library. async fn run() -> Result<(), GolgiError> { + // Attempt to connect to an sbot instance using the default IP address, + // port and network key (aka. capabilities key). let mut sbot_client = Sbot::init(None, None).await?; + // Call the `whoami` RPC method to retrieve the public key for the sbot + // identity. This is our 'local' public key. let id = sbot_client.whoami().await?; + + // Print the public key (identity) to `stdout`. println!("whoami: {}", id); let author = id.clone(); - // create a history stream + // Create an ordered stream of all messages authored by the `author` + // identity. let history_stream = sbot_client .create_history_stream(author.to_string()) .await?; - // loop through the results until the end of the stream - pin_mut!(history_stream); // needed for iteration + // Pin the stream to the stack to allow polling of the `future`. + futures::pin_mut!(history_stream); + println!("looping through stream"); + + // Iterate through each element in the stream and match on the `Result`. + // In this case, each element has type `Result`. while let Some(res) = history_stream.next().await { match res { Ok(value) => { + // Print the `SsbMessageValue` of this element to `stdout`. println!("value: {:?}", value); } Err(err) => { - println!("err: {:?}", err); + // Print the `GolgiError` of this element to `stderr`. + eprintln!("err: {:?}", err); } } } + println!("reached end of stream"); - // create a history stream and convert it into a Vec using try_collect - // (if there is any error in the results, it will be raised) + // Create an ordered stream of all messages authored by the `author` + // identity. let history_stream = sbot_client .create_history_stream(author.to_string()) .await?; + + // Collect the stream elements into a `Vec` using + // `try_collect`. A `GolgiError` will be returned from the `run` + // function if any element contains an error. let results: Vec = history_stream.try_collect().await?; + + // Loop through the `SsbMessageValue` elements, printing each one + // to `stdout`. for x in results { println!("x: {:?}", x); } - // example to create a history stream and use a map to convert stream of SsbMessageValue - // into a stream of tuples of (String, SsbMessageContentType) + // Create an ordered stream of all messages authored by the `author` + // identity. let history_stream = sbot_client .create_history_stream(author.to_string()) .await?; + + // Iterate through the elements in the stream and use `map` to convert + // each `SsbMessageValue` element into a tuple of + // `(String, SsbMessageContentType)`. This is an example of stream + // conversion. let type_stream = history_stream.map(|msg| match msg { Ok(val) => { let message_type = val.get_message_type()?; @@ -59,8 +88,15 @@ async fn run() -> Result<(), GolgiError> { } Err(err) => Err(err), }); - pin_mut!(type_stream); // needed for iteration + + // Pin the stream to the stack to allow polling of the `future`. + futures::pin_mut!(type_stream); + println!("looping through type stream"); + + // Iterate through each element in the stream and match on the `Result`. + // In this case, each element has type + // `Result<(String, SsbMessageContentType), GolgiError>`. while let Some(res) = type_stream.next().await { match res { Ok(value) => { @@ -71,12 +107,15 @@ async fn run() -> Result<(), GolgiError> { } } } + println!("reached end of type stream"); - // return Ok Ok(()) } +// Enable an async main function and execute the `run()` function, +// catching any errors and printing them to `stderr` before exiting the +// process. #[async_std::main] async fn main() { if let Err(e) = run().await { From d4263b967f91d38a3794cef832ac2b035dcb0d7b Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 15 Feb 2022 10:27:34 +0200 Subject: [PATCH 27/27] rename example files --- examples/{ssb-client.rs => basic.rs} | 0 examples/{ssb-friends.rs => friends.rs} | 0 examples/{ssb-invite.rs => invite.rs} | 0 examples/{ssb-stream-example.rs => streams.rs} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename examples/{ssb-client.rs => basic.rs} (100%) rename examples/{ssb-friends.rs => friends.rs} (100%) rename examples/{ssb-invite.rs => invite.rs} (100%) rename examples/{ssb-stream-example.rs => streams.rs} (100%) diff --git a/examples/ssb-client.rs b/examples/basic.rs similarity index 100% rename from examples/ssb-client.rs rename to examples/basic.rs diff --git a/examples/ssb-friends.rs b/examples/friends.rs similarity index 100% rename from examples/ssb-friends.rs rename to examples/friends.rs diff --git a/examples/ssb-invite.rs b/examples/invite.rs similarity index 100% rename from examples/ssb-invite.rs rename to examples/invite.rs diff --git a/examples/ssb-stream-example.rs b/examples/streams.rs similarity index 100% rename from examples/ssb-stream-example.rs rename to examples/streams.rs