use crate::posts::{Embed, Mention, Parent};
use derive_builder::Builder;
use ed25519_dalek::{SigningKey, VerifyingKey};
use crate::proto::{CastAddBody, CastType, FarcasterNetwork, HashScheme, Message, MessageData, MessageType, SignatureScheme};
use crate::proto::message_data::Body;
use crate::to_messages::ToMessages;
use crate::utils::{message_hash, now, sign_hash};
#[derive(Builder, Default, Clone)]
#[builder(setter(strip_option), default, build_fn(validate = "validate_cast"))]
pub struct CastCreate {
body: String,
mentions: Option<Vec<Mention>>,
embeds: Option<Vec<Embed>>,
parent: Option<Parent>,
}
fn validate_cast(builder: &CastCreateBuilder) -> Result<(), String> {
if let Some(body) = &builder.body {
if body.len() > 1024 {
return Err("CastCreate body text was larger than 1024 bytes".to_string())
}
};
if let Some(Some(embeds)) = &builder.embeds {
if embeds.len() > 2 {
return Err("CastCreate contains more than two embeds".to_string())
}
};
Ok(())
}
#[inline]
fn fid_from_mention(mention: &Mention) -> u64 {
mention.fid
}
#[inline]
fn start_pos_from_mention(mention: &Mention) -> u32 {
mention.start_pos
}
#[inline]
fn extract_mentions(mention: Mention) -> (u64, u32) {
(fid_from_mention(&mention), start_pos_from_mention(&mention))
}
impl ToMessages for CastCreate {
fn to_messages(self, fid: u64, signing_key: &SigningKey) -> Vec<Message> {
let (mentions, mentions_positions) = self.mentions.map_or(
(Vec::new(), Vec::new()),
| mentions| mentions.into_iter().map(extract_mentions).collect()
);
let cast_type = if self.body.len() > 320 {
CastType::LongCast
} else {
CastType::Cast
};
let cast = CastAddBody {
embeds_deprecated: vec![],
mentions,
text: self.body,
mentions_positions,
embeds: self.embeds.map_or(Vec::new(), |embeds| embeds.into_iter().map(|e| e.into()).collect()),
r#type: cast_type.into(),
parent: None,
};
let data = MessageData {
r#type: MessageType::CastAdd.into(),
fid,
timestamp: now(),
network: FarcasterNetwork::Mainnet.into(),
body: Some(Body::CastAddBody(cast)),
};
let hash = message_hash(&data);
let signature = sign_hash(signing_key, &hash);
let message = Message {
data: Some(data),
hash: hash.to_vec(),
hash_scheme: HashScheme::Blake3.into(),
signature: signature.to_vec(),
signature_scheme: SignatureScheme::Ed25519.into(),
signer: VerifyingKey::from(signing_key).as_bytes().to_vec(),
data_bytes: None,
};
vec![message]
}
}
#[cfg(test)]
mod test {
use ed25519_dalek::{Signature, SigningKey, Verifier};
use rand::rngs::OsRng;
use crate::cast_builder::CastCreateBuilder;
use crate::posts::{Embed, Mention};
use crate::proto::MessageType;
use crate::to_messages::ToMessages;
#[test]
pub fn test_cast_builder() {
let update = CastCreateBuilder::create_empty()
.body("@Farcaster cool cast".to_string())
.embeds(vec![Embed::Url("https://something.com".to_string())])
.mentions(vec![Mention { fid: 1, start_pos: 0 }])
.build().expect("Couldn't build cast create");
let signing_key = SigningKey::generate(&mut OsRng);
let messages = update.to_messages(0, &signing_key);
let verifying_key = signing_key.verifying_key();
for message in &messages {
assert_eq!(message.signer, verifying_key.as_bytes());
assert!(verifying_key.verify(&message.hash, &Signature::from_slice(&message.signature).unwrap()).is_ok());
}
assert_eq!(messages.first().unwrap().data.clone().unwrap().r#type(), MessageType::CastAdd);
}
}