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_lysand, 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 LysandType { 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 LysandExtensions { #[serde(rename = "org.lysand:microblogging/Announce")] Announce, #[serde(rename = "org.lysand:custom_emojis")] CustomEmojis, #[serde(rename = "org.lysand:reactions/Reaction")] Reaction, #[serde(rename = "org.lysand:reactions")] Reactions, #[serde(rename = "org.lysand:polls")] Polls, #[serde(rename = "org.lysand:is_cat")] IsCat, #[serde(rename = "org.lysand:server_endorsement/Endorsement")] Endorsement, #[serde(rename = "org.lysand:server_endorsement")] EndorsementCollection, #[serde(rename = "org.lysand:reports/Report")] Report, #[serde(rename = "org.lysand: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()) } } 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 { key: ContentFormat, value: ContentFormat, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ContentEntry { content: String, description: Option, size: Option, hash: Option, blurhash: Option, fps: Option, width: Option, height: Option, 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: LysandType, pub id: Uuid, pub uri: Url, #[serde(with = "iso_lysand")] pub created_at: OffsetDateTime, pub display_name: Option, // TODO bio: 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, pub bio: Option, pub avatar: Option, pub header: Option, pub fields: Option>, pub indexable: bool, } #[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, image: Option, icon: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Note { #[serde(rename = "type")] pub rtype: LysandType, pub id: Uuid, pub uri: Url, pub author: Url, #[serde(with = "iso_lysand")] pub created_at: OffsetDateTime, pub category: Option, pub content: Option, pub device: Option, pub previews: Option>, pub group: Option, pub attachments: Option>, pub replies_to: Option, pub quotes: Option, pub mentions: Option>, pub subject: Option, pub is_sensitive: Option, pub visibility: Option, //TODO extensions } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Outbox { pub first: Url, pub last: Url, pub next: Option, pub prev: Option, pub items: Vec, }