Add get_latest_about_message #8
@ -36,6 +36,8 @@ pub enum GolgiError {
|
||||
Sbot(String),
|
||||
/// JSON serialization or deserialization error.
|
||||
SerdeJson(JsonError),
|
||||
/// Error decoding typed ssb message from content.
|
||||
ContentType(String),
|
||||
/// Error decoding UTF8 string from bytes
|
||||
Utf8Parse {
|
||||
/// The underlying parse error.
|
||||
@ -54,6 +56,7 @@ impl std::error::Error for GolgiError {
|
||||
GolgiError::Rpc(ref err) => Some(err),
|
||||
GolgiError::Sbot(_) => None,
|
||||
GolgiError::SerdeJson(ref err) => Some(err),
|
||||
GolgiError::ContentType(_) => None,
|
||||
GolgiError::Utf8Parse{ ref source} => Some(source),
|
||||
}
|
||||
}
|
||||
@ -74,6 +77,11 @@ impl std::fmt::Display for GolgiError {
|
||||
GolgiError::Sbot(ref err) => write!(f, "Sbot returned an error response: {}", err),
|
||||
GolgiError::SerdeJson(_) => write!(f, "Failed to serialize JSON slice"),
|
||||
//GolgiError::WhoAmI(ref err) => write!(f, "{}", err),
|
||||
GolgiError::ContentType(ref err) => write!(
|
||||
f,
|
||||
"Failed to decode typed message from ssb message content: {}",
|
||||
err
|
||||
),
|
||||
GolgiError::Utf8Parse{ source } => write!(f, "Failed to deserialize UTF8 from bytes: {}", source),
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,16 +29,49 @@ pub struct SsbMessageValue {
|
||||
pub signature: String,
|
||||
}
|
||||
|
||||
// Enum representing the different possible message content types
|
||||
#[derive(Debug)]
|
||||
pub enum SsbMessageContentType {
|
||||
About,
|
||||
Vote,
|
||||
Post,
|
||||
Contact,
|
||||
Unrecognized
|
||||
}
|
||||
|
||||
impl SsbMessageValue {
|
||||
/// Gets the type field of the message content if found,
|
||||
/// and if not returns "none"
|
||||
pub fn get_message_type(&self) -> String {
|
||||
let msg_type: String = self
|
||||
.content
|
||||
.get("type")
|
||||
.map(|msg_type| msg_type.to_string())
|
||||
.unwrap_or_else(|| "none".to_string());
|
||||
msg_type
|
||||
|
||||
/// 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)
|
||||
pub fn get_message_type(&self) -> Result<SsbMessageContentType, GolgiError> {
|
||||
let msg_type = self
|
||||
.content
|
||||
.get("type")
|
||||
.ok_or(GolgiError::ContentType("type field not found".to_string()))?;
|
||||
let mtype_str: &str = msg_type.as_str().ok_or(GolgiError::ContentType("type field value is not a string as expected".to_string()))?;
|
||||
let enum_type = match mtype_str {
|
||||
"about" => SsbMessageContentType::About,
|
||||
"post" => SsbMessageContentType::Post,
|
||||
"vote" => SsbMessageContentType::Vote,
|
||||
"contact" => SsbMessageContentType::Contact,
|
||||
_ => SsbMessageContentType::Unrecognized,
|
||||
};
|
||||
Ok(enum_type)
|
||||
}
|
||||
|
||||
/// Helper function which returns true if this message is of the given type,
|
||||
/// and false if the type does not match or is not found
|
||||
pub fn is_message_type(&self, message_type: SsbMessageContentType) -> bool {
|
||||
let self_message_type = self.get_message_type();
|
||||
match self_message_type {
|
||||
Ok(mtype) => {
|
||||
matches!(mtype, message_type)
|
||||
}
|
||||
Err(_err) => {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the content json value into an SsbMessageContent enum,
|
||||
|
||||
81
src/sbot.rs
81
src/sbot.rs
@ -5,7 +5,7 @@ use kuska_handshake::async_std::BoxStream;
|
||||
use kuska_sodiumoxide::crypto::{auth, sign::ed25519};
|
||||
use kuska_ssb::{
|
||||
api::{
|
||||
dto::{content::SubsetQuery, CreateHistoryStreamIn},
|
||||
dto::{CreateHistoryStreamIn},
|
||||
ApiCaller,
|
||||
},
|
||||
discovery, keystore,
|
||||
@ -14,9 +14,12 @@ use kuska_ssb::{
|
||||
};
|
||||
|
||||
use crate::error::GolgiError;
|
||||
use crate::messages::{SsbMessageKVT, SsbMessageContent, SsbMessageValue};
|
||||
use crate::messages::{SsbMessageKVT, SsbMessageContent, SsbMessageValue, SsbMessageContentType};
|
||||
use crate::utils;
|
||||
|
||||
// re-export types from kuska
|
||||
pub use kuska_ssb::api::dto::content::SubsetQuery;
|
||||
|
||||
/// The Scuttlebutt identity, keys and configuration parameters for connecting to a local sbot
|
||||
/// instance, as well as handles for calling RPC methods and receiving responses.
|
||||
pub struct Sbot {
|
||||
@ -146,6 +149,80 @@ impl Sbot {
|
||||
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<String, GolgiError> {
|
||||
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_messages(&mut self, ssb_id: &str) -> Result<Vec<SsbMessageValue>, GolgiError> {
|
||||
let query = SubsetQuery::Author{
|
||||
op: "author".to_string(),
|
||||
feed: ssb_id.to_string(),
|
||||
};
|
||||
let kvts: Vec<SsbMessageKVT> = self.get_subset(query).await?;
|
||||
let messages: Vec<SsbMessageValue> = kvts.into_iter().map(|kvt| kvt.value).collect();
|
||||
// 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 mut about_messages: Vec<SsbMessageValue> = messages.into_iter().filter(|msg| {
|
||||
msg.is_message_type(SsbMessageContentType::About)
|
||||
}).collect();
|
||||
// TODO: use subset query to order messages instead of doing it this way
|
||||
about_messages.sort_by(|a, b| {
|
||||
b.timestamp.partial_cmp(&a.timestamp).unwrap()
|
||||
});
|
||||
// return about messages
|
||||
Ok(about_messages)
|
||||
}
|
||||
|
||||
/// 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<Option<String>, GolgiError> {
|
||||
// vector of about messages with most recent at the front of the vector
|
||||
let about_messages = self.get_about_messages(ssb_id).await?;
|
||||
// iterate through the vector looking for most recent about message with the given key
|
||||
let latest_about = about_messages
|
||||
.iter()
|
||||
// find the first msg that contains the field `key`
|
||||
.find(|msg| msg.content.get(key).is_some())
|
||||
// map the found msg (`Some(SsbMessageValue)`) to a `Some(Some(&Value))`
|
||||
.map(|msg| msg.content.get(key))
|
||||
// flatten `Some(Some(&Value))` into `Some(&Value)`
|
||||
.flatten()
|
||||
// map `Some(&Value)` to `Some(Some(&str))`
|
||||
.map(|msg_val| msg_val.as_str())
|
||||
// flatten `Some(Some(&str))` to `Some(&str)`
|
||||
.flatten()
|
||||
// map `Some(&str))` to `Some(String)`
|
||||
.map(|msg_str| msg_str.to_string());
|
||||
|
||||
// return value is either `Ok(Some(String))` or `Ok(None)`
|
||||
Ok(latest_about)
|
||||
}
|
||||
|
||||
pub async fn get_name(&mut self, ssb_id: &str) -> Result<Option<String>, GolgiError> {
|
||||
self.get_latest_about_message(ssb_id, "name").await
|
||||
}
|
||||
|
||||
pub async fn get_description(&mut self, ssb_id: &str) -> Result<Option<String>, GolgiError> {
|
||||
self.get_latest_about_message(ssb_id, "description").await
|
||||
}
|
||||
|
||||
/// Call the `createHistoryStream` RPC method and return a vector
|
||||
/// of SsbMessageValue.
|
||||
pub async fn create_history_stream(
|
||||
|
||||
@ -140,6 +140,8 @@ 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.
|
||||
|
||||
Reference in New Issue
Block a user