//! 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}; use crate::{ api::get_subset::{SubsetQuery, SubsetQueryOptions}, error::GolgiError, messages::{SsbMessageContentType, SsbMessageValue}, sbot::Sbot, }; impl Sbot { /// 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 golgi::{Sbot, GolgiError, sbot::Keystore}; /// /// async fn about_message_stream() -> Result<(), GolgiError> { /// let mut sbot_client = Sbot::init(Keystore::Patchwork, 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. /// futures::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, ) -> 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 the value of the latest `about` type message, containing the given /// `key`, for a peer. /// /// # 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 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, 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 futures::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 the latest `name`, `description` and `image` values for a peer, /// as defined in their `about` type messages. /// /// # Example /// /// ```rust /// use golgi::{Sbot, GolgiError, sbot::Keystore}; /// /// async fn profile_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 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, ) -> Result, GolgiError> { let keys_to_search_for = vec!["name", "description", "image"]; self.get_about_info(ssb_id, keys_to_search_for).await } /// 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, sbot::Keystore}; /// /// async fn name_and_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 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, ) -> Result, GolgiError> { let keys_to_search_for = vec!["name", "image"]; self.get_about_info(ssb_id, keys_to_search_for).await } /// 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. /// /// # Example /// /// ```rust /// use golgi::{Sbot, GolgiError, sbot::Keystore}; /// /// async fn about_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 keys_to_search_for = vec!["name", "description"]; /// /// let about_info = sbot_client.get_about_info(ssb_id, keys_to_search_for).await?; /// /// 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(()) /// } /// ``` 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!` is needed for iteration futures::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 { // if there are no more keys we are looking for, then we are done if keys_to_search_for.is_empty() { 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 about_val = msg.content.get("about").and_then(|val| val.as_str()); let option_val = msg .content .get(key) .and_then(|val| val.as_str()) .map(|val| val.to_string()); match option_val { // only return val if this msg is about the given ssb_id Some(val) if about_val == Some(ssb_id) => { // if a value is found, then insert it profile_info.insert(key.to_string(), val); // remove this key from keys_to_search_for, // since we are no longer searching for it keys_to_search_for.retain(|val| val != key) } _ => continue, } } } Err(_err) => { // skip errors continue; } } } Ok(profile_info) } /// Get the latest `name` value for a peer. /// /// # 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"; /// /// 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, sbot::Keystore}; /// /// async fn description_info() -> Result<(), GolgiError> { /// let mut sbot_client = Sbot::init(Keystore::Patchwork, 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 } }