Compare commits
50 Commits
gethistory
...
main
Author | SHA1 | Date |
---|---|---|
glyph | 15c5e77da6 | |
glyph | a42478c169 | |
glyph | 0c083b5ce4 | |
glyph | ea7fc86ee1 | |
glyph | f6b561ebde | |
glyph | 8dac8b1a62 | |
glyph | 87f75fae94 | |
glyph | a08f3adbef | |
glyph | 5c4b92a8bf | |
glyph | 195eeb523c | |
glyph | 2b37f19a27 | |
decentral1se | 7024797e0f | |
glyph | 9981b64bb2 | |
glyph | ad3a5ed932 | |
glyph | 457dda6d2d | |
glyph | ec5666fe02 | |
glyph | 1335aeb544 | |
glyph | c556373a96 | |
glyph | 80e99deb6f | |
glyph | e16f567c19 | |
glyph | 821198a400 | |
glyph | 05e2540403 | |
glyph | 3fe1a4bbea | |
notplants | 3c37297018 | |
notplants | b24f2e4a06 | |
glyph | 31b432165e | |
notplants | 48beb4a2e5 | |
notplants | e9667a57be | |
glyph | ab86d4fe0b | |
glyph | 22d12f31ac | |
notplants | fbe7072995 | |
notplants | f41f8b55d2 | |
glyph | 15acebbbfa | |
glyph | 1c44f0e56a | |
glyph | 89a98dd6ab | |
glyph | e03729907f | |
glyph | 175add2c72 | |
glyph | bf7d574064 | |
glyph | 914ffb70cc | |
glyph | 2abe4b5e10 | |
glyph | bb07839691 | |
notplants | ca4c1114dd | |
notplants | 1651b73426 | |
notplants | 1fc56ac8f1 | |
glyph | 2e1e9a79f1 | |
notplants | 51dc82280a | |
notplants | 6daddeab9e | |
notplants | fb115b280f | |
notplants | b4987d514a | |
notplants | 3f3c18b8b7 |
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
Cargo.lock
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "golgi"
|
name = "golgi"
|
||||||
version = "0.1.1"
|
version = "0.2.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Max Fowler <max@mfowler.info>", "Andrew Reid <glyph@mycelial.technology>"]
|
authors = ["Max Fowler <max@mfowler.info>", "Andrew Reid <glyph@mycelial.technology>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -16,6 +16,7 @@ async-std = "1.10.0"
|
||||||
async-stream = "0.3.2"
|
async-stream = "0.3.2"
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
futures = "0.3.21"
|
futures = "0.3.21"
|
||||||
|
log = "0.4"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
kuska-handshake = { version = "0.2.0", features = ["async_std"] }
|
kuska-handshake = { version = "0.2.0", features = ["async_std"] }
|
||||||
kuska-sodiumoxide = "0.2.5-0"
|
kuska-sodiumoxide = "0.2.5-0"
|
||||||
|
|
|
@ -4,8 +4,11 @@ use async_std::stream::StreamExt;
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
|
|
||||||
use golgi::{
|
use golgi::{
|
||||||
api::get_subset::{SubsetQuery, SubsetQueryOptions},
|
api::{
|
||||||
messages::{SsbMessageContentType, SsbMessageValue},
|
get_subset::{SubsetQuery, SubsetQueryOptions},
|
||||||
|
history_stream::CreateHistoryStream,
|
||||||
|
},
|
||||||
|
messages::{SsbMessageContentType, SsbMessageKVT},
|
||||||
sbot::Keystore,
|
sbot::Keystore,
|
||||||
GolgiError, Sbot,
|
GolgiError, Sbot,
|
||||||
};
|
};
|
||||||
|
@ -30,10 +33,11 @@ async fn run() -> Result<(), GolgiError> {
|
||||||
|
|
||||||
/* HISTORY STREAM EXAMPLE */
|
/* HISTORY STREAM EXAMPLE */
|
||||||
|
|
||||||
|
let history_stream_args = CreateHistoryStream::new(author.to_string());
|
||||||
// Create an ordered stream of all messages authored by the `author`
|
// Create an ordered stream of all messages authored by the `author`
|
||||||
// identity.
|
// identity. Messages are returned as KVTs (Key Value Timestamp).
|
||||||
let history_stream = sbot_client
|
let history_stream = sbot_client
|
||||||
.create_history_stream(author.to_string())
|
.create_history_stream(history_stream_args)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Pin the stream to the stack to allow polling of the `future`.
|
// Pin the stream to the stack to allow polling of the `future`.
|
||||||
|
@ -42,12 +46,12 @@ async fn run() -> Result<(), GolgiError> {
|
||||||
println!("looping through stream");
|
println!("looping through stream");
|
||||||
|
|
||||||
// Iterate through each element in the stream and match on the `Result`.
|
// Iterate through each element in the stream and match on the `Result`.
|
||||||
// In this case, each element has type `Result<SsbMessageValue, GolgiError>`.
|
// In this case, each element has type `Result<SsbMessageKVT, GolgiError>`.
|
||||||
while let Some(res) = history_stream.next().await {
|
while let Some(res) = history_stream.next().await {
|
||||||
match res {
|
match res {
|
||||||
Ok(value) => {
|
Ok(kvt) => {
|
||||||
// Print the `SsbMessageValue` of this element to `stdout`.
|
// Print the `SsbMessageKVT` of this element to `stdout`.
|
||||||
println!("value: {:?}", value);
|
println!("kvt: {:?}", kvt);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// Print the `GolgiError` of this element to `stderr`.
|
// Print the `GolgiError` of this element to `stderr`.
|
||||||
|
@ -61,15 +65,15 @@ async fn run() -> Result<(), GolgiError> {
|
||||||
// Create an ordered stream of all messages authored by the `author`
|
// Create an ordered stream of all messages authored by the `author`
|
||||||
// identity.
|
// identity.
|
||||||
let history_stream = sbot_client
|
let history_stream = sbot_client
|
||||||
.create_history_stream(author.to_string())
|
.create_history_stream(CreateHistoryStream::new(author.to_string()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Collect the stream elements into a `Vec<SsbMessageValue>` using
|
// Collect the stream elements into a `Vec<SsbMessageKVT>` using
|
||||||
// `try_collect`. A `GolgiError` will be returned from the `run`
|
// `try_collect`. A `GolgiError` will be returned from the `run`
|
||||||
// function if any element contains an error.
|
// function if any element contains an error.
|
||||||
let results: Vec<SsbMessageValue> = history_stream.try_collect().await?;
|
let results: Vec<SsbMessageKVT> = history_stream.try_collect().await?;
|
||||||
|
|
||||||
// Loop through the `SsbMessageValue` elements, printing each one
|
// Loop through the `SsbMessageKVT` elements, printing each one
|
||||||
// to `stdout`.
|
// to `stdout`.
|
||||||
for x in results {
|
for x in results {
|
||||||
println!("x: {:?}", x);
|
println!("x: {:?}", x);
|
||||||
|
@ -78,17 +82,18 @@ async fn run() -> Result<(), GolgiError> {
|
||||||
// Create an ordered stream of all messages authored by the `author`
|
// Create an ordered stream of all messages authored by the `author`
|
||||||
// identity.
|
// identity.
|
||||||
let history_stream = sbot_client
|
let history_stream = sbot_client
|
||||||
.create_history_stream(author.to_string())
|
.create_history_stream(CreateHistoryStream::new(author.to_string()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Iterate through the elements in the stream and use `map` to convert
|
// Iterate through the elements in the stream and use `map` to convert
|
||||||
// each `SsbMessageValue` element into a tuple of
|
// each `SsbMessageKVT` element into a tuple of
|
||||||
// `(String, SsbMessageContentType)`. This is an example of stream
|
// `(String, SsbMessageContentType)`. This is an example of stream
|
||||||
// conversion.
|
// conversion.
|
||||||
let type_stream = history_stream.map(|msg| match msg {
|
let type_stream = history_stream.map(|msg| match msg {
|
||||||
Ok(val) => {
|
Ok(kvt) => {
|
||||||
let message_type = val.get_message_type()?;
|
let message_type = kvt.value.get_message_type()?;
|
||||||
let tuple: (String, SsbMessageContentType) = (val.signature, message_type);
|
// Return the message key and type.
|
||||||
|
let tuple: (String, SsbMessageContentType) = (kvt.key, message_type);
|
||||||
Ok(tuple)
|
Ok(tuple)
|
||||||
}
|
}
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
use async_std::stream::StreamExt;
|
||||||
|
|
||||||
|
use golgi::{api::tangles::TanglesThread, sbot::Keystore, 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 initialise a connection to an sbot instance using the
|
||||||
|
// secret file at the Patchwork path and the default IP address, port
|
||||||
|
// and network key (aka. capabilities key).
|
||||||
|
//let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||||
|
let mut sbot_client =
|
||||||
|
Sbot::init(Keystore::GoSbot, Some("127.0.0.1:8021".to_string()), 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);
|
||||||
|
|
||||||
|
/* TANGLES THREAD EXAMPLE */
|
||||||
|
|
||||||
|
// Define the message key that will be used to generate the tangle stream.
|
||||||
|
// This must be the key of the root message in the thread (ie. the first
|
||||||
|
// message of the thread).
|
||||||
|
let msg_key = "%vY53ethgEG1tNsKqmLm6isNSbsu+jwMf/UNGMfUiLm0=.sha256".to_string();
|
||||||
|
|
||||||
|
// Instantiate the arguments for the `tangles.thread` RPC by instantiating
|
||||||
|
// a new instant of the `TanglesThread` struct.
|
||||||
|
let args = TanglesThread::new(msg_key).keys_values(true, true);
|
||||||
|
|
||||||
|
// It's also possible to define the maximum number of messages
|
||||||
|
// returned from the stream by setting the `limit` value:
|
||||||
|
// Note: a limit of 3 will return a maximum of 4 messages! The root
|
||||||
|
// message + 3 other messages in the thread.
|
||||||
|
// let args = TanglesThread::new(msg_key).keys_values(true, true).limit(3);
|
||||||
|
|
||||||
|
// Create an ordered stream of all messages comprising the thread
|
||||||
|
// in which the given message resides. Messages are returned as KVTs
|
||||||
|
// (Key Value Timestamp).
|
||||||
|
let thread_stream = sbot_client.tangles_thread(args).await?;
|
||||||
|
|
||||||
|
// Pin the stream to the stack to allow polling of the `future`.
|
||||||
|
futures::pin_mut!(thread_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<SsbMessageKVT, GolgiError>`.
|
||||||
|
while let Some(res) = thread_stream.next().await {
|
||||||
|
match res {
|
||||||
|
Ok(kvt) => {
|
||||||
|
// Print the `SsbMessageKVT` of this element to `stdout`.
|
||||||
|
println!("kvt: {:?}", kvt);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
// Print the `GolgiError` of this element to `stderr`.
|
||||||
|
eprintln!("err: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("reached end of stream");
|
||||||
|
|
||||||
|
// Take a look at the `examples/streams.rs` file in this repo for more
|
||||||
|
// ways of handling streams.
|
||||||
|
|
||||||
|
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 {
|
||||||
|
eprintln!("Application error: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
110
src/api/about.rs
110
src/api/about.rs
|
@ -5,10 +5,12 @@
|
||||||
//! - [`Sbot::get_about_info`]
|
//! - [`Sbot::get_about_info`]
|
||||||
//! - [`Sbot::get_about_message_stream`]
|
//! - [`Sbot::get_about_message_stream`]
|
||||||
//! - [`Sbot::get_description`]
|
//! - [`Sbot::get_description`]
|
||||||
|
//! - [`Sbot::get_image`]
|
||||||
//! - [`Sbot::get_latest_about_message`]
|
//! - [`Sbot::get_latest_about_message`]
|
||||||
//! - [`Sbot::get_name`]
|
//! - [`Sbot::get_name`]
|
||||||
//! - [`Sbot::get_name_and_image`]
|
//! - [`Sbot::get_name_and_image`]
|
||||||
//! - [`Sbot::get_profile_info`]
|
//! - [`Sbot::get_profile_info`]
|
||||||
|
//! - [`Sbot::get_signifier`]
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
@ -19,6 +21,7 @@ use crate::{
|
||||||
error::GolgiError,
|
error::GolgiError,
|
||||||
messages::{SsbMessageContentType, SsbMessageValue},
|
messages::{SsbMessageContentType, SsbMessageValue},
|
||||||
sbot::Sbot,
|
sbot::Sbot,
|
||||||
|
utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Sbot {
|
impl Sbot {
|
||||||
|
@ -82,6 +85,40 @@ impl Sbot {
|
||||||
Ok(about_message_stream)
|
Ok(about_message_stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the latest `image` value for a peer. The value is a blob reference.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||||
|
///
|
||||||
|
/// async fn image_info() -> Result<(), GolgiError> {
|
||||||
|
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||||
|
///
|
||||||
|
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||||
|
///
|
||||||
|
/// let image = sbot_client.get_image(ssb_id).await?;
|
||||||
|
///
|
||||||
|
/// println!("peer {} is identified by {}", ssb_id, image);
|
||||||
|
///
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub async fn get_image(&mut self, ssb_id: &str) -> Result<String, GolgiError> {
|
||||||
|
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||||
|
let req_id = sbot_connection
|
||||||
|
.client
|
||||||
|
.names_get_image_for_req_send(ssb_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
utils::get_async(
|
||||||
|
&mut sbot_connection.rpc_reader,
|
||||||
|
req_id,
|
||||||
|
utils::string_res_parse,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the value of the latest `about` type message, containing the given
|
/// Get the value of the latest `about` type message, containing the given
|
||||||
/// `key`, for a peer.
|
/// `key`, for a peer.
|
||||||
///
|
///
|
||||||
|
@ -186,8 +223,16 @@ impl Sbot {
|
||||||
&mut self,
|
&mut self,
|
||||||
ssb_id: &str,
|
ssb_id: &str,
|
||||||
) -> Result<HashMap<String, String>, GolgiError> {
|
) -> Result<HashMap<String, String>, GolgiError> {
|
||||||
let keys_to_search_for = vec!["name", "description", "image"];
|
let key_to_search_for = vec!["description"];
|
||||||
self.get_about_info(ssb_id, keys_to_search_for).await
|
let description = self.get_about_info(ssb_id, key_to_search_for).await?;
|
||||||
|
|
||||||
|
let mut profile_info = self.get_name_and_image(ssb_id).await?;
|
||||||
|
// extend the `profile_info` HashMap by adding the key-value from the
|
||||||
|
// `description` HashMap; `profile_info` now contains all three
|
||||||
|
// key-value pairs
|
||||||
|
profile_info.extend(description.into_iter());
|
||||||
|
|
||||||
|
Ok(profile_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the latest `name` and `image` values for a peer. This method can
|
/// Get the latest `name` and `image` values for a peer. This method can
|
||||||
|
@ -232,8 +277,15 @@ impl Sbot {
|
||||||
&mut self,
|
&mut self,
|
||||||
ssb_id: &str,
|
ssb_id: &str,
|
||||||
) -> Result<HashMap<String, String>, GolgiError> {
|
) -> Result<HashMap<String, String>, GolgiError> {
|
||||||
let keys_to_search_for = vec!["name", "image"];
|
let mut name_and_image: HashMap<String, String> = HashMap::new();
|
||||||
self.get_about_info(ssb_id, keys_to_search_for).await
|
|
||||||
|
let name = self.get_name(ssb_id).await?;
|
||||||
|
let image = self.get_image(ssb_id).await?;
|
||||||
|
|
||||||
|
name_and_image.insert("name".to_string(), name);
|
||||||
|
name_and_image.insert("image".to_string(), image);
|
||||||
|
|
||||||
|
Ok(name_and_image)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the latest values for the provided keys from the `about` type
|
/// Get the latest values for the provided keys from the `about` type
|
||||||
|
@ -335,7 +387,8 @@ impl Sbot {
|
||||||
Ok(profile_info)
|
Ok(profile_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the latest `name` value for a peer.
|
/// Get the latest `name` value for a peer. The public key of the peer
|
||||||
|
/// will be returned if no `name` value is found.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -347,17 +400,15 @@ impl Sbot {
|
||||||
///
|
///
|
||||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||||
///
|
///
|
||||||
/// if let Some(name) = sbot_client.get_name(ssb_id).await? {
|
/// let name = sbot_client.get_name(ssb_id).await?;
|
||||||
/// println!("peer {} is named {}", ssb_id, name)
|
///
|
||||||
/// } else {
|
/// println!("peer {} is named {}", ssb_id, name);
|
||||||
/// eprintln!("no name found for peer {}", ssb_id)
|
|
||||||
/// }
|
|
||||||
///
|
///
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn get_name(&mut self, ssb_id: &str) -> Result<Option<String>, GolgiError> {
|
pub async fn get_name(&mut self, ssb_id: &str) -> Result<String, GolgiError> {
|
||||||
self.get_latest_about_message(ssb_id, "name").await
|
self.get_signifier(ssb_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the latest `description` value for a peer.
|
/// Get the latest `description` value for a peer.
|
||||||
|
@ -384,4 +435,39 @@ impl Sbot {
|
||||||
pub async fn get_description(&mut self, ssb_id: &str) -> Result<Option<String>, GolgiError> {
|
pub async fn get_description(&mut self, ssb_id: &str) -> Result<Option<String>, GolgiError> {
|
||||||
self.get_latest_about_message(ssb_id, "description").await
|
self.get_latest_about_message(ssb_id, "description").await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the latest `name` value (signifier) for a peer. The public key of
|
||||||
|
/// the peer will be returned if no `name` value is found.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||||
|
///
|
||||||
|
/// async fn name_info() -> Result<(), GolgiError> {
|
||||||
|
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||||
|
///
|
||||||
|
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||||
|
///
|
||||||
|
/// let name = sbot_client.get_signifier(ssb_id).await?;
|
||||||
|
///
|
||||||
|
/// println!("peer {} is named {}", ssb_id, name);
|
||||||
|
///
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub async fn get_signifier(&mut self, ssb_id: &str) -> Result<String, GolgiError> {
|
||||||
|
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||||
|
let req_id = sbot_connection
|
||||||
|
.client
|
||||||
|
.names_get_signifier_req_send(ssb_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
utils::get_async(
|
||||||
|
&mut sbot_connection.rpc_reader,
|
||||||
|
req_id,
|
||||||
|
utils::string_res_parse,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::{error::GolgiError, messages::SsbMessageValue, sbot::Sbot, utils};
|
||||||
pub use kuska_ssb::api::dto::content::{SubsetQuery, SubsetQueryOptions};
|
pub use kuska_ssb::api::dto::content::{SubsetQuery, SubsetQueryOptions};
|
||||||
|
|
||||||
impl Sbot {
|
impl Sbot {
|
||||||
/// Make a subset query, as defined by the [Subset replication for SSB specification](https://github.com/ssb-ngi-pointer/ssb-subset-replication-spec).
|
/// Make a subset query, as defined by the [Subset replication for SSB specification](https://github.com/ssbc/ssb-subset-replication-spec).
|
||||||
///
|
///
|
||||||
/// Calls the `partialReplication. getSubset` RPC method.
|
/// Calls the `partialReplication. getSubset` RPC method.
|
||||||
///
|
///
|
||||||
|
|
|
@ -5,25 +5,32 @@
|
||||||
//! - [`Sbot::create_history_stream`]
|
//! - [`Sbot::create_history_stream`]
|
||||||
|
|
||||||
use async_std::stream::Stream;
|
use async_std::stream::Stream;
|
||||||
use kuska_ssb::api::dto::CreateHistoryStreamIn;
|
pub use kuska_ssb::api::dto::CreateHistoryStreamIn as CreateHistoryStream;
|
||||||
|
|
||||||
use crate::{error::GolgiError, messages::SsbMessageValue, sbot::Sbot, utils};
|
use crate::{error::GolgiError, messages::SsbMessageKVT, sbot::Sbot, utils};
|
||||||
|
|
||||||
impl Sbot {
|
impl Sbot {
|
||||||
/// Call the `createHistoryStream` RPC method.
|
/// Call the `createHistoryStream` RPC method. Returns messages in the form
|
||||||
|
/// of KVTs (Key Value Timestamp).
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use async_std::stream::StreamExt;
|
/// use async_std::stream::StreamExt;
|
||||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
/// use golgi::{
|
||||||
|
/// Sbot,
|
||||||
|
/// GolgiError,
|
||||||
|
/// sbot::Keystore,
|
||||||
|
/// api::history_stream::CreateHistoryStream
|
||||||
|
/// };
|
||||||
///
|
///
|
||||||
/// async fn history() -> Result<(), GolgiError> {
|
/// async fn history() -> Result<(), GolgiError> {
|
||||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||||
///
|
///
|
||||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519".to_string();
|
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519".to_string();
|
||||||
///
|
///
|
||||||
/// let history_stream = sbot_client.create_history_stream(ssb_id).await?;
|
/// let args = CreateHistoryStream::new(ssb_id);
|
||||||
|
/// let history_stream = sbot_client.create_history_stream(args).await?;
|
||||||
///
|
///
|
||||||
/// history_stream.for_each(|msg| {
|
/// history_stream.for_each(|msg| {
|
||||||
/// match msg {
|
/// match msg {
|
||||||
|
@ -37,20 +44,16 @@ impl Sbot {
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn create_history_stream(
|
pub async fn create_history_stream(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: String,
|
args: CreateHistoryStream,
|
||||||
) -> Result<impl Stream<Item = Result<SsbMessageValue, GolgiError>>, GolgiError> {
|
) -> Result<impl Stream<Item = Result<SsbMessageKVT, GolgiError>>, GolgiError> {
|
||||||
let mut sbot_connection = self.get_sbot_connection().await?;
|
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||||
let args = CreateHistoryStreamIn::new(id);
|
|
||||||
let req_id = sbot_connection
|
let req_id = sbot_connection
|
||||||
.client
|
.client
|
||||||
.create_history_stream_req_send(&args)
|
.create_history_stream_req_send(&args)
|
||||||
.await?;
|
.await?;
|
||||||
let history_stream = utils::get_source_stream(
|
let history_stream =
|
||||||
sbot_connection.rpc_reader,
|
utils::get_source_stream(sbot_connection.rpc_reader, req_id, utils::kvt_res_parse)
|
||||||
req_id,
|
.await;
|
||||||
utils::ssb_message_res_parse,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
Ok(history_stream)
|
Ok(history_stream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ pub mod history_stream;
|
||||||
pub mod invite;
|
pub mod invite;
|
||||||
pub mod private;
|
pub mod private;
|
||||||
pub mod publish;
|
pub mod publish;
|
||||||
|
pub mod tangles;
|
||||||
pub mod whoami;
|
pub mod whoami;
|
||||||
|
|
||||||
pub use crate::sbot::*;
|
pub use crate::sbot::*;
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
//! Take a reference to the root message of a thread and return a stream of all
|
||||||
|
//! messages in the thread. This includes the root message and all replies.
|
||||||
|
//!
|
||||||
|
//! Implements the following methods:
|
||||||
|
//!
|
||||||
|
//! - [`Sbot::tangles_thread`]
|
||||||
|
|
||||||
|
use async_std::stream::Stream;
|
||||||
|
pub use kuska_ssb::api::dto::TanglesThread;
|
||||||
|
|
||||||
|
use crate::{error::GolgiError, messages::SsbMessageKVT, sbot::Sbot, utils};
|
||||||
|
|
||||||
|
impl Sbot {
|
||||||
|
/// Call the `tanglesThread` RPC method. Returns messages in the form
|
||||||
|
/// of KVTs (Key Value Timestamp).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use async_std::stream::StreamExt;
|
||||||
|
/// use golgi::{
|
||||||
|
/// Sbot,
|
||||||
|
/// GolgiError,
|
||||||
|
/// api::tangles::TanglesThread,
|
||||||
|
/// sbot::Keystore
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// async fn tangle() -> Result<(), GolgiError> {
|
||||||
|
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||||
|
///
|
||||||
|
/// let msg_key = "%kmXb3MXtBJaNugcEL/Q7G40DgcAkMNTj3yhmxKHjfCM=.sha256";
|
||||||
|
///
|
||||||
|
/// let args = TanglesThread::new(msg_key).keys_values(true, true);
|
||||||
|
/// let tangles_thread = sbot_client.tangles_thread(msg_id).await?;
|
||||||
|
///
|
||||||
|
/// tangles_thread.for_each(|msg| {
|
||||||
|
/// match msg {
|
||||||
|
/// Ok(kvt) => println!("msg kvt: {:?}", kvt),
|
||||||
|
/// Err(e) => eprintln!("error: {}", e),
|
||||||
|
/// }
|
||||||
|
/// }).await;
|
||||||
|
///
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub async fn tangles_thread(
|
||||||
|
&mut self,
|
||||||
|
args: TanglesThread,
|
||||||
|
) -> Result<impl Stream<Item = Result<SsbMessageKVT, GolgiError>>, GolgiError> {
|
||||||
|
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||||
|
let req_id = sbot_connection
|
||||||
|
.client
|
||||||
|
.tangles_thread_req_send(&args)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let thread_stream =
|
||||||
|
utils::get_source_stream(sbot_connection.rpc_reader, req_id, utils::kvt_res_parse)
|
||||||
|
.await;
|
||||||
|
Ok(thread_stream)
|
||||||
|
}
|
||||||
|
}
|
|
@ -77,7 +77,7 @@ impl std::fmt::Display for GolgiError {
|
||||||
// TODO: improve this variant with a context message
|
// TODO: improve this variant with a context message
|
||||||
// then have the core display msg be: "SSB RPC error: {}", context
|
// then have the core display msg be: "SSB RPC error: {}", context
|
||||||
GolgiError::Rpc(ref err) => write!(f, "SSB RPC failure: {}", err),
|
GolgiError::Rpc(ref err) => write!(f, "SSB RPC failure: {}", err),
|
||||||
GolgiError::Sbot(ref err) => write!(f, "Sbot returned an error response: {}", err),
|
GolgiError::Sbot(ref err) => write!(f, "Sbot encountered an error: {}", err),
|
||||||
GolgiError::SigilLink(ref context) => write!(f, "SSB blob ID error: {}", context),
|
GolgiError::SigilLink(ref context) => write!(f, "SSB blob ID error: {}", context),
|
||||||
GolgiError::SerdeJson(_) => write!(f, "Failed to serialize JSON slice"),
|
GolgiError::SerdeJson(_) => write!(f, "Failed to serialize JSON slice"),
|
||||||
//GolgiError::WhoAmI(ref err) => write!(f, "{}", err),
|
//GolgiError::WhoAmI(ref err) => write!(f, "{}", err),
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
//! API for interacting with an sbot instance and uses the
|
//! API for interacting with an sbot instance and uses the
|
||||||
//! [kuska-ssb](https://github.com/Kuska-ssb) libraries to make RPC calls.
|
//! [kuska-ssb](https://github.com/Kuska-ssb) libraries to make RPC calls.
|
||||||
//! Development efforts are currently oriented towards
|
//! Development efforts are currently oriented towards
|
||||||
//! [go-sbot](https://github.com/cryptoscope/ssb) interoperability.
|
//! [go-sbot](https://github.com/ssbc/go-ssb) interoperability.
|
||||||
//!
|
//!
|
||||||
//! ## Features
|
//! ## Features
|
||||||
//!
|
//!
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
//! // Call the `whoami` RPC method to retrieve the public key for the sbot
|
//! // Call the `whoami` RPC method to retrieve the public key for the sbot
|
||||||
//! // identity.
|
//! // identity.
|
||||||
//! let id = sbot_client.whoami().await?;
|
//! let id = sbot_client.whoami().await?;
|
||||||
//!
|
//!
|
||||||
//! // Print the public key (identity) to `stdout`.
|
//! // Print the public key (identity) to `stdout`.
|
||||||
//! println!("{}", id);
|
//! println!("{}", id);
|
||||||
//!
|
//!
|
||||||
|
@ -76,3 +76,4 @@ pub mod sbot;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
pub use crate::{error::GolgiError, sbot::Sbot};
|
pub use crate::{error::GolgiError, sbot::Sbot};
|
||||||
|
pub use kuska_ssb;
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub struct SsbMessageValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Message content types.
|
/// Message content types.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub enum SsbMessageContentType {
|
pub enum SsbMessageContentType {
|
||||||
About,
|
About,
|
||||||
|
@ -99,6 +99,6 @@ impl SsbMessageValue {
|
||||||
pub struct SsbMessageKVT {
|
pub struct SsbMessageKVT {
|
||||||
pub key: String,
|
pub key: String,
|
||||||
pub value: SsbMessageValue,
|
pub value: SsbMessageValue,
|
||||||
pub timestamp: f64,
|
pub timestamp: Option<f64>,
|
||||||
pub rts: Option<f64>,
|
pub rts: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
46
src/sbot.rs
46
src/sbot.rs
|
@ -20,6 +20,10 @@ pub enum Keystore {
|
||||||
Patchwork,
|
Patchwork,
|
||||||
/// GoSbot default keystore path: `.ssb-go/secret` in the user's home directory.
|
/// GoSbot default keystore path: `.ssb-go/secret` in the user's home directory.
|
||||||
GoSbot,
|
GoSbot,
|
||||||
|
/// GoSbot keystore in a custom location
|
||||||
|
CustomGoSbot(String),
|
||||||
|
/// Patchwork keystore in a custom location
|
||||||
|
CustomPatchwork(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A struct representing a connection with a running sbot.
|
/// A struct representing a connection with a running sbot.
|
||||||
|
@ -54,12 +58,16 @@ impl Sbot {
|
||||||
ip_port: Option<String>,
|
ip_port: Option<String>,
|
||||||
net_id: Option<String>,
|
net_id: Option<String>,
|
||||||
) -> Result<Sbot, GolgiError> {
|
) -> Result<Sbot, GolgiError> {
|
||||||
let address = if ip_port.is_none() {
|
let mut address = if ip_port.is_none() {
|
||||||
"127.0.0.1:8008".to_string()
|
"127.0.0.1:8008".to_string()
|
||||||
} else {
|
} else {
|
||||||
ip_port.unwrap()
|
ip_port.unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if address.starts_with(':') {
|
||||||
|
address = format!("127.0.0.1{}", address);
|
||||||
|
}
|
||||||
|
|
||||||
let network_id = if net_id.is_none() {
|
let network_id = if net_id.is_none() {
|
||||||
discovery::ssb_net_id()
|
discovery::ssb_net_id()
|
||||||
} else {
|
} else {
|
||||||
|
@ -67,12 +75,36 @@ impl Sbot {
|
||||||
};
|
};
|
||||||
|
|
||||||
let OwnedIdentity { pk, sk, id } = match keystore {
|
let OwnedIdentity { pk, sk, id } = match keystore {
|
||||||
Keystore::Patchwork => keystore::from_patchwork_local()
|
Keystore::Patchwork => keystore::from_patchwork_local().await.map_err(|_err| {
|
||||||
.await
|
GolgiError::Sbot(
|
||||||
.expect("couldn't read local secret"),
|
"sbot initialization error: couldn't read local patchwork secret from default location".to_string(),
|
||||||
Keystore::GoSbot => keystore::from_gosbot_local()
|
)
|
||||||
.await
|
})?,
|
||||||
.expect("couldn't read local secret"),
|
Keystore::GoSbot => keystore::from_gosbot_local().await.map_err(|_err| {
|
||||||
|
GolgiError::Sbot(
|
||||||
|
"sbot initialization error: couldn't read local go-sbot secret from default location".to_string(),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
Keystore::CustomGoSbot(key_path) => {
|
||||||
|
keystore::from_custom_gosbot_keypath(key_path.to_string())
|
||||||
|
.await
|
||||||
|
.map_err(|_err| {
|
||||||
|
GolgiError::Sbot(format!(
|
||||||
|
"sbot initialization error: couldn't read local go-sbot secret from: {}",
|
||||||
|
key_path
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
Keystore::CustomPatchwork(key_path) => {
|
||||||
|
keystore::from_custom_patchwork_keypath(key_path.to_string())
|
||||||
|
.await
|
||||||
|
.map_err(|_err| {
|
||||||
|
GolgiError::Sbot(format!(
|
||||||
|
"sbot initialization error: couldn't read local patchwork secret from: {}",
|
||||||
|
key_path
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
|
@ -76,7 +76,7 @@ where
|
||||||
if id == req_no {
|
if id == req_no {
|
||||||
match msg {
|
match msg {
|
||||||
RecvMsg::RpcResponse(_type, body) => {
|
RecvMsg::RpcResponse(_type, body) => {
|
||||||
return f(&body).map_err(|err| err);
|
return f(&body);
|
||||||
}
|
}
|
||||||
RecvMsg::ErrorResponse(message) => {
|
RecvMsg::ErrorResponse(message) => {
|
||||||
return Err(GolgiError::Sbot(message));
|
return Err(GolgiError::Sbot(message));
|
||||||
|
|
Loading…
Reference in New Issue