Skip to main content

fatline_rs/
cast_builder.rs

1use crate::posts::{Embed, Mention, Parent};
2use derive_builder::Builder;
3use ed25519_dalek::{SigningKey, VerifyingKey};
4use crate::proto::{CastAddBody, CastType, FarcasterNetwork, HashScheme, Message, MessageData, MessageType, SignatureScheme};
5use crate::proto::message_data::Body;
6use crate::to_messages::ToMessages;
7use crate::utils::{message_hash, now, sign_hash};
8
9#[derive(Builder, Default, Clone)]
10#[builder(setter(strip_option), default, build_fn(validate = "validate_cast"))]
11pub struct CastCreate {
12    body: String,
13    mentions: Option<Vec<Mention>>,
14    embeds: Option<Vec<Embed>>,
15    parent: Option<Parent>,
16}
17
18fn validate_cast(builder: &CastCreateBuilder) -> Result<(), String> {
19    if let Some(body) = &builder.body {
20        // TODO: maybe add utf-8 validation rule
21        if body.len() > 1024 {
22            return Err("CastCreate body text was larger than 1024 bytes".to_string())
23        }
24    };
25    if let Some(Some(embeds)) = &builder.embeds {
26        if embeds.len() > 2 {
27            return Err("CastCreate contains more than two embeds".to_string())
28        }
29    };
30    Ok(())
31}
32
33#[inline]
34fn fid_from_mention(mention: &Mention) -> u64 {
35    mention.fid
36}
37
38#[inline]
39fn start_pos_from_mention(mention: &Mention) -> u32 {
40    mention.start_pos
41}
42
43#[inline]
44fn extract_mentions(mention: Mention) -> (u64, u32) {
45    (fid_from_mention(&mention), start_pos_from_mention(&mention))
46}
47
48impl ToMessages for CastCreate {
49    fn to_messages(self, fid: u64, signing_key: &SigningKey) -> Vec<Message> {
50        let (mentions, mentions_positions) = self.mentions.map_or(
51            (Vec::new(), Vec::new()),
52            | mentions| mentions.into_iter().map(extract_mentions).collect()
53        );
54
55        let cast_type = if self.body.len() > 320 {
56            CastType::LongCast
57        } else {
58            CastType::Cast
59        };
60
61        let cast = CastAddBody {
62            embeds_deprecated: vec![],
63            mentions,
64            text: self.body,
65            mentions_positions,
66            embeds: self.embeds.map_or(Vec::new(), |embeds| embeds.into_iter().map(|e| e.into()).collect()),
67            r#type: cast_type.into(),
68            parent: None,
69        };
70
71        let data = MessageData {
72            r#type: MessageType::CastAdd.into(),
73            fid,
74            timestamp: now(),
75            network: FarcasterNetwork::Mainnet.into(),
76            body: Some(Body::CastAddBody(cast)),
77        };
78
79        let hash = message_hash(&data);
80        let signature = sign_hash(signing_key, &hash);
81
82        let message = Message {
83            data: Some(data),
84            hash: hash.to_vec(),
85            hash_scheme: HashScheme::Blake3.into(),
86            signature: signature.to_vec(),
87            signature_scheme: SignatureScheme::Ed25519.into(),
88            signer: VerifyingKey::from(signing_key).as_bytes().to_vec(),
89            data_bytes: None,
90        };
91
92        vec![message]
93    }
94}
95
96#[cfg(test)]
97mod test {
98    use ed25519_dalek::{Signature, SigningKey, Verifier};
99    use rand::rngs::OsRng;
100    use crate::cast_builder::CastCreateBuilder;
101    use crate::posts::{Embed, Mention};
102    use crate::proto::MessageType;
103    use crate::to_messages::ToMessages;
104
105    #[test]
106    pub fn test_cast_builder() {
107        let update = CastCreateBuilder::create_empty()
108            .body("@Farcaster cool cast".to_string())
109            .embeds(vec![Embed::Url("https://something.com".to_string())])
110            .mentions(vec![Mention { fid: 1, start_pos: 0 }])
111            .build().expect("Couldn't build cast create");
112
113        let signing_key = SigningKey::generate(&mut OsRng);
114
115        let messages = update.to_messages(0, &signing_key);
116
117        let verifying_key = signing_key.verifying_key();
118
119        for message in &messages {
120            assert_eq!(message.signer, verifying_key.as_bytes());
121            assert!(verifying_key.verify(&message.hash, &Signature::from_slice(&message.signature).unwrap()).is_ok());
122        }
123        assert_eq!(messages.first().unwrap().data.clone().unwrap().r#type(), MessageType::CastAdd);
124    }
125
126}