pub mod digest; pub mod send; pub mod fetch; mod error; pub use error::Error; use std::collections::HashMap; use serde::{Deserialize, Serialize}; const MIME_TYPE: &str = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Actor { #[serde(rename = "@context")] pub jsonld_context: Option, #[serde(rename = "type")] pub actor_type: String, pub id: String, pub url: Option, pub name: String, pub icon: Option, pub inbox: String, pub outbox: String, #[serde(rename = "publicKey")] pub public_key: ActorPublicKey, #[serde(rename = "preferredUsername")] pub preferred_username: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ActorPublicKey { pub id: String, pub owner: Option, #[serde(rename = "publicKeyPem")] pub pem: String, } /// `ActivityPub` "activity" #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Action { #[serde(rename = "@context")] pub jsonld_context: Option, #[serde(rename = "type")] pub action_type: String, pub id: String, pub actor: String, pub to: Option, pub object: O, } impl Action { pub fn object_id(&self) -> Option<&str> { if let Some(id) = self.object.as_str() { Some(id) } else if let Some(object) = self.object.as_object() { object.get("id").and_then(|id| id.as_str()) } else { None } } } // hack for misskey fn media_default_content_type() -> String { "image/jpeg".to_string() } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Media { #[serde(rename = "type")] pub media_type: String, #[serde(rename = "mediaType", default = "media_default_content_type")] pub content_type: String, pub url: String, } /// This is unfortunately strictly separate from the Client/Server /// data schema in `cave::feed::Post` #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Post { pub published: String, pub id: String, pub url: Option, #[serde(rename = "attributedTo")] pub attributed_to: Option, #[serde(default = "String::new")] pub content: String, #[serde(rename = "contentMap", default = "HashMap::new")] pub content_map: HashMap, #[serde(default)] pub tag: Vec, pub sensitive: Option, #[serde(rename = "attachment", default = "Vec::new")] pub attachments: Vec, } impl Post { pub fn language(&self) -> Option<&str> { self.content_map.keys() .next().map(|s| s.as_str()) } /// Translate ActivityPub post to Mastodon client API post format pub fn to_feed_post(self, actor: Actor) -> super::feed::Post { let language = self.language() .map(|s| s.to_string()); super::feed::Post { created_at: self.published, uri: self.url.unwrap_or(self.id), content: self.content, account: super::feed::Account { username: actor.preferred_username, display_name: actor.name, url: actor.url.unwrap_or(actor.id), bot: actor.actor_type != "Person", }, tags: self.tag.into_iter().filter_map(|mut tag| { while tag.name.chars().next() == Some('#') { tag.name.remove(0); } if tag.name.chars().next() == Some('@') { // why do these occur? None } else if tag.name.len() > 0 { Some(tag) } else { None } }).collect(), sensitive: self.sensitive, mentions: vec![], language, media_attachments: self.attachments.into_iter().map(|media| { super::feed::MediaAttachment { media_type: media.media_type, remote_url: Some(media.url), } }).collect(), reblog: None, } } }