fatline_rs/
cast_builder.rs1use 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 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}