fatline-rs 0.2.2

Farcaster grpc client and utility library
Documentation
//! Types that are used by the various frontend implementations for posts / casts and their replies

use crate::action::{CastAction, ReactionAction, ReactionInfo};
use crate::proto::reactions_by_target_request::Target;
use crate::proto::{CastsByParentRequest, ReactionType, ReactionsByTargetRequest};
use crate::proto::embed::Embed as ProtoEmbed;
use crate::utils::{cast_action_from_message, reaction_from_message};
use crate::HubService;
use async_trait::async_trait;
use eyre::Result;
use serde::{Deserialize, Serialize};

/// The combined cast, holding all necessary values to display and query against
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
pub struct Cast {
    pub cast_id: CastId,
    pub parent: Option<Parent>,
    pub mentions: Vec<Mention>,
    pub embeds: Vec<Embed>,
    pub text: String,
    /// In seconds
    pub cast_time: u32
}

impl Cast {

    fn new(text: Option<String>) -> Self {
        Cast {
            text: text.unwrap_or_default(),
            ..Default::default()
        }
    }




}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Mention {
    pub fid: u64,
    pub start_pos: u32
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum Embed {
    Url(String),
    CastId(CastId)
}

impl Into<crate::proto::Embed> for Embed {
    fn into(self) -> crate::proto::Embed {
        let proto_embed = match self {
            Embed::Url(string) => ProtoEmbed::Url(string),
            Embed::CastId(cast) => ProtoEmbed::CastId(cast.into())
        };
        crate::proto::Embed {
            embed: Some(proto_embed)
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum Parent {
    Url(String),
    CastId(CastId)
}

impl Into<Target> for Parent {
    fn into(self) -> Target {
        match self {
            Parent::Url(url) => {
                Target::TargetUrl(url)
            }
            Parent::CastId(cast_id) => {
                Target::TargetCastId(cast_id.into())
            }
        }
    }
}

impl Into<crate::proto::casts_by_parent_request::Parent> for Parent {
    fn into(self) -> crate::proto::casts_by_parent_request::Parent {
        match self {
            Parent::Url(url) => {
                crate::proto::casts_by_parent_request::Parent::ParentUrl(url)
            }
            Parent::CastId(id) => {
                crate::proto::casts_by_parent_request::Parent::ParentCastId(
                    crate::proto::CastId {
                        fid: id.fid,
                        hash: id.hash
                    }
                )
            }
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Default)]
pub struct CastId {
    pub fid: u64,
    pub hash: Vec<u8>
}

impl Into<crate::proto::CastId> for CastId {
    fn into(self) -> crate::proto::CastId {
        crate::proto::CastId {
            fid: self.fid,
            hash: self.hash,
        }
    }
}

/// A service to get merged user info form a hubble grpc service
#[async_trait]
pub trait PostService {
    async fn get_post(&mut self, cast_id: CastId) -> Option<Cast>;
    /// Get paged posts by a parent / url / cast hash.
    ///
    /// returns: `(the paged response, next page token)`
    async fn get_posts_by_parent(&mut self, parent: Parent, page_token: Option<Vec<u8>>, page_size: Option<u32>, reverse: Option<bool>) -> Result<(Vec<Cast>, Option<Vec<u8>>)>;
    async fn get_reacts_by_parent(&mut self, reaction_type: ReactionType, parent: Parent, page_token: Option<Vec<u8>>, page_size: Option<u32>, reverse: Option<bool>) -> Result<(Vec<ReactionInfo>, Option<Vec<u8>>)>;
}

#[async_trait]
impl PostService for HubService {
    async fn get_post(&mut self, cast_id: CastId) -> Option<Cast> {
        let proto = crate::proto::CastId {
            fid: cast_id.fid,
            hash: cast_id.hash
        };
        let res = self.get_cast(proto).await.ok()?.into_inner();
        match cast_action_from_message(res)? {
            CastAction::CastAdd(cast) => Some(cast),
            CastAction::CastRemove(_) => None
        }
    }

    async fn get_posts_by_parent(&mut self, parent: Parent, page_token: Option<Vec<u8>>, page_size: Option<u32>, reverse: Option<bool>) -> Result<(Vec<Cast>, Option<Vec<u8>>)> {
        let request = CastsByParentRequest {
            page_size,
            page_token,
            reverse,
            parent: Some(parent.into()),
        };
        let res = self.get_casts_by_parent(request).await?.into_inner();
        let mut messages = Vec::new();
        for message in res.messages {
            if let Some(CastAction::CastAdd(add)) = cast_action_from_message(message) {
                messages.push(add);
            }
        }
        Ok((messages, res.next_page_token))
    }

    async fn get_reacts_by_parent(&mut self, reaction_type: ReactionType, parent: Parent, page_token: Option<Vec<u8>>, page_size: Option<u32>, reverse: Option<bool>) -> Result<(Vec<ReactionInfo>, Option<Vec<u8>>)> {
        let request = ReactionsByTargetRequest {
            reaction_type: Some(reaction_type.into()),
            page_size,
            page_token,
            reverse,
            target: Some(parent.into()),
        };

        let res = self.get_reactions_by_target(request).await?.into_inner();
        let mut reactions = Vec::new();
        for message in res.messages {
            if let Some(react) = reaction_from_message(message) {
                if let ReactionAction::Add(info) = react {
                    reactions.push(info);
                }
            }
        }
        Ok((reactions, res.next_page_token))
    }
}