[[package]]
name = "activitystreams-kinds"
-version = "0.1.2"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0784e99afd032199d3ed70cefb8eb3a8d1aef15f7f2c4e68d033c4e12bb6079e"
+checksum = "6d014a4fb8828870b7b46bee6257b9a89d06188ae8d435381ba94f14c8c697d8"
dependencies = [
"serde",
"url",
lemmy_api_common = { version = "=0.16.2", path = "../api_common" }
lemmy_websocket = { version = "=0.16.2", path = "../websocket" }
diesel = "1.4.8"
-activitystreams-kinds = "0.1.2"
+activitystreams-kinds = "0.2.1"
bcrypt = "0.10.1"
chrono = { version = "0.4.19", features = ["serde"] }
serde_json = { version = "1.0.72", features = ["preserve_order"] }
"mediaType": "text/markdown"
},
"url": "https://lemmy.ml/pictrs/image/xl8W7FZfk9.jpg",
+ "attachment": [
+ {
+ "type": "Link",
+ "href": "https://lemmy.ml/pictrs/image/xl8W7FZfk9.jpg"
+ }
+ ],
"commentsEnabled": true,
"sensitive": false,
"stickied": false,
"mediaType": "text/markdown"
},
"url": "https://lemmy.ml/pictrs/image/xl8W7FZfk9.jpg",
+ "attachment": [
+ {
+ "type": "Link",
+ "href": "https://lemmy.ml/pictrs/image/xl8W7FZfk9.jpg"
+ }
+ ],
"commentsEnabled": true,
"sensitive": false,
"stickied": false,
"mediaType": "text/markdown"
},
"url": "https://enterprise.lemmy.ml/pictrs/image/eOtYb9iEiB.png",
+ "attachment": [
+ {
+ "type": "Link",
+ "href": "https://enterprise.lemmy.ml/pictrs/image/eOtYb9iEiB.png"
+ }
+ ],
"image": {
"type": "Image",
"url": "https://enterprise.lemmy.ml/pictrs/image/eOtYb9iEiB.png"
let create_or_update = CreateOrUpdatePost::new(post, actor, &community, kind, context).await?;
let id = create_or_update.id.clone();
- let activity = AnnouncableActivities::CreateOrUpdatePost(create_or_update);
+ let activity = AnnouncableActivities::CreateOrUpdatePost(Box::new(create_or_update));
send_activity_in_community(activity, &id, actor, &community, vec![], context).await
}
}
#[activity_handler(LemmyContext)]
pub enum AnnouncableActivities {
CreateOrUpdateComment(CreateOrUpdateComment),
- CreateOrUpdatePost(CreateOrUpdatePost),
+ CreateOrUpdatePost(Box<CreateOrUpdatePost>),
Vote(Vote),
UndoVote(UndoVote),
Delete(Delete),
#[derive(Deserialize)]
#[serde(untagged)]
pub enum PageOrNote {
- Page(Page),
+ Page(Box<Page>),
Note(Note),
}
) -> Result<Self, LemmyError> {
Ok(match apub {
PageOrNote::Page(p) => PostOrComment::Post(Box::new(
- ApubPost::from_apub(p, context, request_counter).await?,
+ ApubPost::from_apub(*p, context, request_counter).await?,
)),
PageOrNote::Note(n) => PostOrComment::Comment(Box::new(
ApubComment::from_apub(n, context, request_counter).await?,
})
}
+pub(crate) fn deserialize_skip_error<'de, T, D>(deserializer: D) -> Result<T, D::Error>
+where
+ T: Deserialize<'de> + Default,
+ D: Deserializer<'de>,
+{
+ let result = Deserialize::deserialize(deserializer);
+ Ok(match result {
+ Ok(o) => o,
+ Err(_) => Default::default(),
+ })
+}
+
pub enum EndpointType {
Community,
Person,
objects::read_from_string_or_source,
protocol::{
objects::{note::Note, tombstone::Tombstone},
- SourceCompat,
+ Source,
},
PostOrComment,
};
cc: maa.ccs,
content: markdown_to_html(&self.content),
media_type: Some(MediaTypeHtml::Html),
- source: Some(SourceCompat::new(self.content.clone())),
+ source: Some(Source::new(self.content.clone())),
in_reply_to,
published: Some(convert_datetime(self.published)),
updated: self.updated.map(convert_datetime),
protocol::{
objects::{group::Group, tombstone::Tombstone, Endpoints},
ImageObject,
- SourceCompat,
+ Source,
},
};
use activitystreams_kinds::actor::GroupType;
preferred_username: self.name.clone(),
name: Some(self.title.clone()),
summary: self.description.as_ref().map(|b| markdown_to_html(b)),
- source: self.description.clone().map(SourceCompat::new),
+ source: self.description.clone().map(Source::new),
icon: self.icon.clone().map(ImageObject::new),
image: self.banner.clone().map(ImageObject::new),
sensitive: Some(self.nsfw),
use crate::{
check_is_apub_id_valid,
objects::{read_from_string_or_source_opt, verify_image_domain_matches},
- protocol::{objects::instance::Instance, ImageObject, SourceCompat},
+ protocol::{objects::instance::Instance, ImageObject, Source},
};
use activitystreams_kinds::actor::ServiceType;
use chrono::NaiveDateTime;
id: ObjectId::new(self.actor_id()),
name: self.name.clone(),
content: self.sidebar.as_ref().map(|d| markdown_to_html(d)),
- source: self.sidebar.clone().map(SourceCompat::new),
+ source: self.sidebar.clone().map(Source::new),
summary: self.description.clone(),
media_type: self.sidebar.as_ref().map(|_| MediaTypeHtml::Html),
icon: self.icon.clone().map(ImageObject::new),
-use crate::protocol::{ImageObject, SourceCompat};
+use crate::protocol::{ImageObject, Source};
use html2md::parse_html;
use lemmy_apub_lib::verify::verify_domains_match;
use lemmy_utils::LemmyError;
pub mod post;
pub mod private_message;
-pub(crate) fn read_from_string_or_source(raw: &str, source: &Option<SourceCompat>) -> String {
- if let Some(SourceCompat::Lemmy(s)) = source {
+pub(crate) fn read_from_string_or_source(raw: &str, source: &Option<Source>) -> String {
+ if let Some(s) = source {
s.content.clone()
} else {
parse_html(raw)
pub(crate) fn read_from_string_or_source_opt(
raw: &Option<String>,
- source: &Option<SourceCompat>,
+ source: &Option<Source>,
) -> Option<String> {
- if let Some(SourceCompat::Lemmy(s2)) = source {
+ if let Some(s2) = source {
Some(s2.content.clone())
} else {
raw.as_ref().map(|s| parse_html(s))
Endpoints,
},
ImageObject,
- SourceCompat,
+ Source,
},
};
use chrono::NaiveDateTime;
preferred_username: self.name.clone(),
name: self.display_name.clone(),
summary: self.bio.as_ref().map(|b| markdown_to_html(b)),
- source: self.bio.clone().map(SourceCompat::new),
+ source: self.bio.clone().map(Source::new),
icon: self.avatar.clone().map(ImageObject::new),
image: self.banner.clone().map(ImageObject::new),
matrix_user_id: self.matrix_user_id.clone(),
objects::read_from_string_or_source_opt,
protocol::{
objects::{
- page::{Page, PageType},
+ page::{Attachment, Page, PageType},
tombstone::Tombstone,
},
ImageObject,
- SourceCompat,
+ Source,
},
};
use activitystreams_kinds::public;
name: self.name.clone(),
content: self.body.as_ref().map(|b| markdown_to_html(b)),
media_type: Some(MediaTypeHtml::Html),
- source: self.body.clone().map(SourceCompat::new),
+ source: self.body.clone().map(Source::new),
url: self.url.clone().map(|u| u.into()),
+ attachment: self.url.clone().map(Attachment::new).into_iter().collect(),
image: self.thumbnail_url.clone().map(ImageObject::new),
comments_enabled: Some(!self.locked),
sensitive: Some(self.nsfw),
.await?;
let community = page.extract_community(context, request_counter).await?;
+ let url = if let Some(attachment) = page.attachment.first() {
+ Some(attachment.href.clone())
+ } else {
+ page.url
+ };
let thumbnail_url: Option<Url> = page.image.map(|i| i.url);
- let (metadata_res, pictrs_thumbnail) = if let Some(url) = &page.url {
+ let (metadata_res, pictrs_thumbnail) = if let Some(url) = &url {
fetch_site_data(context.client(), &context.settings(), Some(url)).await
} else {
(None, thumbnail_url)
let body_slurs_removed = read_from_string_or_source_opt(&page.content, &page.source)
.map(|s| remove_slurs(&s, &context.settings().slur_regex()));
let form = PostForm {
- name: page.name,
- url: page.url.map(|u| u.into()),
+ name: page.name.clone(),
+ url: url.map(Into::into),
body: body_slurs_removed,
creator_id: creator.id,
community_id: community.id,
objects::read_from_string_or_source,
protocol::{
objects::chat_message::{ChatMessage, ChatMessageType},
- SourceCompat,
+ Source,
},
};
use chrono::NaiveDateTime;
to: [ObjectId::new(recipient.actor_id)],
content: markdown_to_html(&self.content),
media_type: Some(MediaTypeHtml::Html),
- source: Some(SourceCompat::new(self.content.clone())),
+ source: Some(Source::new(self.content.clone())),
published: Some(convert_datetime(self.published)),
updated: self.updated.map(convert_datetime),
};
use activitystreams_kinds::object::ImageType;
-use serde::{Deserialize, Serialize};
-use url::Url;
-
use lemmy_apub_lib::values::MediaTypeMarkdown;
use lemmy_db_schema::newtypes::DbUrl;
-use serde_json::Value;
+use serde::{Deserialize, Serialize};
use std::collections::HashMap;
+use url::Url;
pub mod activities;
pub(crate) mod collections;
pub(crate) media_type: MediaTypeMarkdown,
}
-#[derive(Clone, Debug, Deserialize, Serialize)]
-#[serde(rename_all = "camelCase")]
-#[serde(untagged)]
-pub(crate) enum SourceCompat {
- Lemmy(Source),
- Other(Value),
-}
-
-impl SourceCompat {
+impl Source {
pub(crate) fn new(content: String) -> Self {
- SourceCompat::Lemmy(Source {
+ Source {
content,
media_type: MediaTypeMarkdown::Markdown,
- })
+ }
}
}
use crate::{
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
- protocol::SourceCompat,
+ protocol::Source,
};
use chrono::{DateTime, FixedOffset};
use lemmy_apub_lib::{object_id::ObjectId, values::MediaTypeHtml};
pub(crate) content: String,
pub(crate) media_type: Option<MediaTypeHtml>,
- pub(crate) source: Option<SourceCompat>,
+ #[serde(default)]
+ #[serde(deserialize_with = "crate::deserialize_skip_error")]
+ pub(crate) source: Option<Source>,
pub(crate) published: Option<DateTime<FixedOffset>>,
pub(crate) updated: Option<DateTime<FixedOffset>>,
}
read_from_string_or_source_opt,
verify_image_domain_matches,
},
- protocol::{objects::Endpoints, ImageObject, SourceCompat},
+ protocol::{objects::Endpoints, ImageObject, Source},
};
use activitystreams_kinds::actor::GroupType;
use chrono::{DateTime, FixedOffset};
/// title
pub(crate) name: Option<String>,
pub(crate) summary: Option<String>,
- pub(crate) source: Option<SourceCompat>,
+ #[serde(default)]
+ #[serde(deserialize_with = "crate::deserialize_skip_error")]
+ pub(crate) source: Option<Source>,
pub(crate) icon: Option<ImageObject>,
/// banner
pub(crate) image: Option<ImageObject>,
use crate::{
objects::instance::ApubSite,
- protocol::{ImageObject, SourceCompat},
+ protocol::{ImageObject, Source},
};
use activitystreams_kinds::actor::ServiceType;
use chrono::{DateTime, FixedOffset};
// sidebar
pub(crate) content: Option<String>,
- pub(crate) source: Option<SourceCompat>,
+ #[serde(default)]
+ #[serde(deserialize_with = "crate::deserialize_skip_error")]
+ pub(crate) source: Option<Source>,
// short instance description
pub(crate) summary: Option<String>,
pub(crate) media_type: Option<MediaTypeHtml>,
fetcher::post_or_comment::PostOrComment,
mentions::Mention,
objects::{comment::ApubComment, person::ApubPerson, post::ApubPost},
- protocol::SourceCompat,
+ protocol::Source,
};
use activitystreams_kinds::object::NoteType;
use chrono::{DateTime, FixedOffset};
pub(crate) in_reply_to: ObjectId<PostOrComment>,
pub(crate) media_type: Option<MediaTypeHtml>,
- pub(crate) source: Option<SourceCompat>,
+ #[serde(default)]
+ #[serde(deserialize_with = "crate::deserialize_skip_error")]
+ pub(crate) source: Option<Source>,
pub(crate) published: Option<DateTime<FixedOffset>>,
pub(crate) updated: Option<DateTime<FixedOffset>>,
#[serde(default)]
use crate::{
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
- protocol::{ImageObject, SourceCompat},
+ protocol::{ImageObject, Source},
};
+use activitystreams_kinds::link::LinkType;
use chrono::{DateTime, FixedOffset};
use itertools::Itertools;
use lemmy_apub_lib::{
traits::{ActivityHandler, ApubObject},
values::MediaTypeHtml,
};
+use lemmy_db_schema::newtypes::DbUrl;
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
pub(crate) cc: Vec<Url>,
pub(crate) content: Option<String>,
pub(crate) media_type: Option<MediaTypeHtml>,
- pub(crate) source: Option<SourceCompat>,
+ #[serde(default)]
+ #[serde(deserialize_with = "crate::deserialize_skip_error")]
+ pub(crate) source: Option<Source>,
+ /// deprecated, use attachment field
pub(crate) url: Option<Url>,
+ /// most software uses array type for attachment field, so we do the same. nevertheless, we only
+ /// use the first item
+ #[serde(default)]
+ pub(crate) attachment: Vec<Attachment>,
pub(crate) image: Option<ImageObject>,
pub(crate) comments_enabled: Option<bool>,
pub(crate) sensitive: Option<bool>,
pub(crate) updated: Option<DateTime<FixedOffset>>,
}
+#[derive(Clone, Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Attachment {
+ pub(crate) href: Url,
+ pub(crate) r#type: LinkType,
+}
+
impl Page {
/// Only mods can change the post's stickied/locked status. So if either of these is changed from
/// the current value, it is a mod action and needs to be verified as such.
}
}
+impl Attachment {
+ pub(crate) fn new(url: DbUrl) -> Attachment {
+ Attachment {
+ href: url.into(),
+ r#type: Default::default(),
+ }
+ }
+}
+
// Used for community outbox, so that it can be compatible with Pleroma/Mastodon.
#[async_trait::async_trait(?Send)]
impl ActivityHandler for Page {
use crate::{
objects::person::ApubPerson,
- protocol::{objects::Endpoints, ImageObject, SourceCompat},
+ protocol::{objects::Endpoints, ImageObject, Source},
};
use chrono::{DateTime, FixedOffset};
use lemmy_apub_lib::{object_id::ObjectId, signatures::PublicKey};
/// displayname
pub(crate) name: Option<String>,
pub(crate) summary: Option<String>,
- pub(crate) source: Option<SourceCompat>,
+ #[serde(default)]
+ #[serde(deserialize_with = "crate::deserialize_skip_error")]
+ pub(crate) source: Option<Source>,
/// user avatar
pub(crate) icon: Option<ImageObject>,
/// user banner