extern crate serde; // 1.0.68 extern crate serde_derive; // 1.0.68 use std::{ collections::HashMap, fmt::{Display, Formatter}, }; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; use time::{ format_description::well_known::{iso8601, Iso8601}, OffsetDateTime, }; use url::Url; use uuid::Uuid; const FORMAT: Iso8601<6651332276412969266533270467398074368> = Iso8601::< { iso8601::Config::DEFAULT .set_year_is_six_digits(false) .encode() }, >; time::serde::format_description!(iso_versia, OffsetDateTime, FORMAT); fn sort_alphabetically( value: &T, serializer: S, ) -> Result { let value = serde_json::to_value(value).map_err(serde::ser::Error::custom)?; value.serialize(serializer) } #[derive(Serialize)] pub struct SortAlphabetically(#[serde(serialize_with = "sort_alphabetically")] pub T); #[derive(Debug, Serialize, Deserialize, Clone)] pub enum VersiaType { User, Note, Patch, Like, Dislike, Follow, FollowAccept, FollowReject, Undo, Extension, ServerMetadata, } #[derive(Debug, Serialize, Deserialize, Clone)] pub enum CategoryType { Microblog, Forum, Blog, Image, Video, Audio, Messaging, } #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "snake_case")] pub enum VisibilityType { Public, Unlisted, Followers, Direct, } #[derive(Debug, Serialize, Deserialize, Clone)] pub enum VersiaExtensions { #[serde(rename = "org.versia:microblogging/Announce")] Announce, #[serde(rename = "org.versia:custom_emojis")] CustomEmojis, #[serde(rename = "org.versia:reactions/Reaction")] Reaction, #[serde(rename = "org.versia:reactions")] Reactions, #[serde(rename = "org.versia:polls")] Polls, #[serde(rename = "org.versia:is_cat")] IsCat, #[serde(rename = "org.versia:server_endorsement/Endorsement")] Endorsement, #[serde(rename = "org.versia:server_endorsement")] EndorsementCollection, #[serde(rename = "org.versia:reports/Report")] Report, #[serde(rename = "org.versia:vanity")] Vanity, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct PublicKey { pub public_key: String, pub actor: Url, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ContentHash { md5: Option, sha1: Option, sha256: Option, sha512: Option, } #[derive(Debug, Clone, Default)] pub struct ContentFormat { pub x: HashMap, } impl ContentFormat { pub async fn select_rich_text(&self) -> anyhow::Result { if let Some(entry) = self.x.get("text/x.misskeymarkdown") { return Ok(entry.content.clone()); } if let Some(entry) = self.x.get("text/html") { return Ok(entry.content.clone()); } if let Some(entry) = self.x.get("text/markdown") { return Ok(entry.content.clone()); } if let Some(entry) = self.x.get("text/plain") { return Ok(entry.content.clone()); } Ok(self.x.clone().values().next().unwrap().content.clone()) } pub async fn select_rich_img(&self) -> anyhow::Result { if let Some(entry) = self.x.get("image/webp") { return Ok(entry.content.clone()); } if let Some(entry) = self.x.get("image/png") { return Ok(entry.content.clone()); } if let Some(entry) = self.x.get("image/avif") { return Ok(entry.content.clone()); } if let Some(entry) = self.x.get("image/jxl") { return Ok(entry.content.clone()); } if let Some(entry) = self.x.get("image/jpeg") { return Ok(entry.content.clone()); } if let Some(entry) = self.x.get("image/gif") { return Ok(entry.content.clone()); } if let Some(entry) = self.x.get("image/bmp") { return Ok(entry.content.clone()); } Ok(self.x.clone().values().next().unwrap().content.clone()) } pub async fn select_rich_img_touple(&self) -> anyhow::Result<(String, String)> { if let Some(entry) = self.x.get("image/webp") { return Ok(("image/webp".to_string(), entry.content.clone())); } if let Some(entry) = self.x.get("image/png") { return Ok(("image/png".to_string(), entry.content.clone())); } if let Some(entry) = self.x.get("image/avif") { return Ok(("image/avif".to_string(), entry.content.clone())); } if let Some(entry) = self.x.get("image/jxl") { return Ok(("image/jxl".to_string(), entry.content.clone())); } if let Some(entry) = self.x.get("image/jpeg") { return Ok(("image/jpeg".to_string(), entry.content.clone())); } if let Some(entry) = self.x.get("image/gif") { return Ok(("image/gif".to_string(), entry.content.clone())); } if let Some(entry) = self.x.get("image/bmp") { return Ok(("image/bmp".to_string(), entry.content.clone())); } let touple = self.x.iter().next().unwrap(); Ok((touple.0.clone(), touple.1.content.clone())) } } impl Serialize for ContentFormat { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut seq = serializer.serialize_map(Some(self.x.len()))?; for (k, v) in &self.x { seq.serialize_entry(&k.to_string(), &v)?; } seq.end() } } impl<'de> Deserialize<'de> for ContentFormat { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let map = HashMap::deserialize(deserializer)?; Ok(ContentFormat { x: map }) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct FieldKV { pub key: ContentFormat, pub value: ContentFormat, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ContentEntry { content: String, #[serde(skip_serializing_if = "Option::is_none")] description: Option, #[serde(skip_serializing_if = "Option::is_none")] size: Option, #[serde(skip_serializing_if = "Option::is_none")] hash: Option, #[serde(skip_serializing_if = "Option::is_none")] blurhash: Option, #[serde(skip_serializing_if = "Option::is_none")] fps: Option, #[serde(skip_serializing_if = "Option::is_none")] width: Option, #[serde(skip_serializing_if = "Option::is_none")] height: Option, #[serde(skip_serializing_if = "Option::is_none")] duration: Option, } impl ContentEntry { pub fn from_string(string: String) -> ContentEntry { ContentEntry { content: string, description: None, size: None, hash: None, blurhash: None, fps: None, width: None, height: None, duration: None, } } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct User { pub public_key: PublicKey, #[serde(rename = "type")] pub rtype: VersiaType, pub id: Uuid, pub uri: Url, #[serde(with = "iso_versia")] pub created_at: OffsetDateTime, #[serde(skip_serializing_if = "Option::is_none")] pub display_name: Option, pub inbox: Url, pub outbox: Url, pub featured: Url, pub followers: Url, pub following: Url, pub likes: Url, pub dislikes: Url, pub username: String, #[serde(skip_serializing_if = "Option::is_none")] pub bio: Option, #[serde(skip_serializing_if = "Option::is_none")] pub avatar: Option, #[serde(skip_serializing_if = "Option::is_none")] pub header: Option, #[serde(skip_serializing_if = "Option::is_none")] pub fields: Option>, pub indexable: bool, #[serde(skip_serializing_if = "Option::is_none")] pub extensions: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ExtensionSpecs { #[serde(rename = "org.versia:custom_emojis")] #[serde(skip_serializing_if = "Option::is_none")] pub custom_emojis: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct CustomEmojis { pub emojis: Vec, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct CustomEmoji { pub name: String, pub url: ContentFormat, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct DeviceInfo { name: String, version: String, url: Url, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct LinkPreview { description: String, title: String, link: Url, #[serde(skip_serializing_if = "Option::is_none")] image: Option, #[serde(skip_serializing_if = "Option::is_none")] icon: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Note { #[serde(rename = "type")] pub rtype: VersiaType, pub id: Uuid, pub uri: Url, pub author: Url, #[serde(with = "iso_versia")] pub created_at: OffsetDateTime, #[serde(skip_serializing_if = "Option::is_none")] pub category: Option, #[serde(skip_serializing_if = "Option::is_none")] pub content: Option, #[serde(skip_serializing_if = "Option::is_none")] pub device: Option, #[serde(skip_serializing_if = "Option::is_none")] pub previews: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub group: Option, #[serde(skip_serializing_if = "Option::is_none")] pub attachments: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub replies_to: Option, #[serde(skip_serializing_if = "Option::is_none")] pub quotes: Option, #[serde(skip_serializing_if = "Option::is_none")] pub mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub subject: Option, #[serde(skip_serializing_if = "Option::is_none")] pub is_sensitive: Option, #[serde(skip_serializing_if = "Option::is_none")] pub visibility: Option, //TODO extensions } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Patch { #[serde(rename = "type")] pub rtype: VersiaType, pub id: Uuid, pub uri: Url, pub author: Url, #[serde(with = "iso_versia")] pub created_at: OffsetDateTime, #[serde(with = "iso_versia")] pub patched_at: OffsetDateTime, #[serde(skip_serializing_if = "Option::is_none")] pub category: Option, #[serde(skip_serializing_if = "Option::is_none")] pub content: Option, #[serde(skip_serializing_if = "Option::is_none")] pub device: Option, #[serde(skip_serializing_if = "Option::is_none")] pub previews: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub group: Option, #[serde(skip_serializing_if = "Option::is_none")] pub attachments: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub replies_to: Option, #[serde(skip_serializing_if = "Option::is_none")] pub quotes: Option, #[serde(skip_serializing_if = "Option::is_none")] pub mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub subject: Option, #[serde(skip_serializing_if = "Option::is_none")] pub is_sensitive: Option, #[serde(skip_serializing_if = "Option::is_none")] pub visibility: Option, //TODO extensions } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Outbox { pub first: Url, pub last: Url, #[serde(skip_serializing_if = "Option::is_none")] pub next: Option, #[serde(skip_serializing_if = "Option::is_none")] pub prev: Option, pub items: Vec, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Follow { #[serde(rename = "type")] pub rtype: VersiaType, pub id: Uuid, pub uri: Url, pub author: Url, #[serde(with = "iso_versia")] pub created_at: OffsetDateTime, pub followee: Url, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct FollowResult { #[serde(rename = "type")] pub rtype: VersiaType, pub id: Uuid, pub uri: Url, pub author: Url, #[serde(with = "iso_versia")] pub created_at: OffsetDateTime, pub follower: Url, }