diff --git a/crates/apub/assets/lotide/activities/create_note_reply.json b/crates/apub/assets/lotide/activities/create_note_reply.json
new file mode 100644
index 000000000..b6b2a13fa
--- /dev/null
+++ b/crates/apub/assets/lotide/activities/create_note_reply.json
@@ -0,0 +1,30 @@
+{
+ "actor": "https://c.tide.tk/users/1",
+ "object": {
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": "https://c.tide.tk/comments/52",
+ "type": "Note",
+ "mediaType": "text/html",
+ "source": {
+ "content": "test comment",
+ "mediaType": "text/markdown"
+ },
+ "attributedTo": "https://c.tide.tk/users/1",
+ "content": "
test comment
\n",
+ "published": "2021-09-16T01:20:27.558063+00:00",
+ "inReplyTo": "https://c.tide.tk/posts/51",
+ "to": "https://c.tide.tk/users/1",
+ "cc": [
+ "https://www.w3.org/ns/activitystreams#Public",
+ "https://c.tide.tk/communities/1"
+ ]
+ },
+ "to": "https://c.tide.tk/users/1",
+ "cc": [
+ "https://www.w3.org/ns/activitystreams#Public",
+ "https://c.tide.tk/communities/1"
+ ],
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": "https://c.tide.tk/comments/52/create",
+ "type": "Create"
+}
diff --git a/crates/apub/assets/lotide/activities/create_page.json b/crates/apub/assets/lotide/activities/create_page.json
new file mode 100644
index 000000000..d39e42553
--- /dev/null
+++ b/crates/apub/assets/lotide/activities/create_page.json
@@ -0,0 +1,20 @@
+{
+ "actor": "https://b.tide.tk/apub/users/1",
+ "object": {
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": "https://b.tide.tk/apub/posts/60",
+ "type": "Page",
+ "name": "test post from b",
+ "summary": "test post from b",
+ "to": "https://c.tide.tk/communities/1",
+ "cc": "https://www.w3.org/ns/activitystreams#Public",
+ "published": "2020-12-19T19:20:26.941381+00:00",
+ "attributedTo": "https://b.tide.tk/apub/users/1",
+ "url": "https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake.html"
+ },
+ "to": "https://c.tide.tk/communities/1",
+ "cc": "https://www.w3.org/ns/activitystreams#Public",
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": "https://b.tide.tk/apub/posts/60/create",
+ "type": "Create"
+}
diff --git a/crates/apub/src/context.rs b/crates/apub/src/context.rs
index 3ec444e33..9d2d3d42c 100644
--- a/crates/apub/src/context.rs
+++ b/crates/apub/src/context.rs
@@ -8,6 +8,7 @@ static CONTEXT: Lazy> = Lazy::new(|| {
#[derive(Serialize, Deserialize, Debug)]
pub(crate) struct WithContext {
#[serde(rename = "@context")]
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
context: Vec,
#[serde(flatten)]
inner: T,
diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs
index 984d722e8..b3c7eff72 100644
--- a/crates/apub/src/lib.rs
+++ b/crates/apub/src/lib.rs
@@ -3,6 +3,7 @@ use anyhow::{anyhow, Context};
use lemmy_api_common::blocking;
use lemmy_db_schema::{newtypes::DbUrl, source::activity::Activity, DbPool};
use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
+use serde::{Deserialize, Deserializer};
use std::net::IpAddr;
use url::{ParseError, Url};
@@ -85,6 +86,25 @@ pub(crate) fn check_is_apub_id_valid(
Ok(())
}
+pub(crate) fn deserialize_one_or_many<'de, T, D>(deserializer: D) -> Result, D::Error>
+where
+ T: Deserialize<'de>,
+ D: Deserializer<'de>,
+{
+ #[derive(Deserialize)]
+ #[serde(untagged)]
+ enum OneOrMany {
+ One(T),
+ Many(Vec),
+ }
+
+ let result: OneOrMany = Deserialize::deserialize(deserializer)?;
+ Ok(match result {
+ OneOrMany::Many(list) => list,
+ OneOrMany::One(value) => vec![value],
+ })
+}
+
pub enum EndpointType {
Community,
Person,
diff --git a/crates/apub/src/protocol/activities/community/add_mod.rs b/crates/apub/src/protocol/activities/community/add_mod.rs
index 0d6edc0a6..915c24668 100644
--- a/crates/apub/src/protocol/activities/community/add_mod.rs
+++ b/crates/apub/src/protocol/activities/community/add_mod.rs
@@ -8,9 +8,11 @@ use url::Url;
#[serde(rename_all = "camelCase")]
pub struct AddMod {
pub(crate) actor: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec,
pub(crate) object: ObjectId,
pub(crate) target: Url,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) cc: Vec,
#[serde(rename = "type")]
pub(crate) kind: AddType,
diff --git a/crates/apub/src/protocol/activities/community/announce.rs b/crates/apub/src/protocol/activities/community/announce.rs
index 11890c680..0bda9ebc5 100644
--- a/crates/apub/src/protocol/activities/community/announce.rs
+++ b/crates/apub/src/protocol/activities/community/announce.rs
@@ -12,8 +12,10 @@ use url::Url;
#[serde(rename_all = "camelCase")]
pub struct AnnounceActivity {
pub(crate) actor: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec,
pub(crate) object: AnnouncableActivities,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) cc: Vec,
#[serde(rename = "type")]
pub(crate) kind: AnnounceType,
diff --git a/crates/apub/src/protocol/activities/community/block_user.rs b/crates/apub/src/protocol/activities/community/block_user.rs
index c904fc218..ecde0ce3a 100644
--- a/crates/apub/src/protocol/activities/community/block_user.rs
+++ b/crates/apub/src/protocol/activities/community/block_user.rs
@@ -11,8 +11,10 @@ use url::Url;
#[serde(rename_all = "camelCase")]
pub struct BlockUserFromCommunity {
pub(crate) actor: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec,
pub(crate) object: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) cc: Vec,
pub(crate) target: ObjectId,
#[serde(rename = "type")]
diff --git a/crates/apub/src/protocol/activities/community/remove_mod.rs b/crates/apub/src/protocol/activities/community/remove_mod.rs
index dc456b0b0..74619c81d 100644
--- a/crates/apub/src/protocol/activities/community/remove_mod.rs
+++ b/crates/apub/src/protocol/activities/community/remove_mod.rs
@@ -8,8 +8,10 @@ use url::Url;
#[serde(rename_all = "camelCase")]
pub struct RemoveMod {
pub(crate) actor: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec,
pub(crate) object: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) cc: Vec,
#[serde(rename = "type")]
pub(crate) kind: RemoveType,
diff --git a/crates/apub/src/protocol/activities/community/undo_block_user.rs b/crates/apub/src/protocol/activities/community/undo_block_user.rs
index f08e0c352..02218367b 100644
--- a/crates/apub/src/protocol/activities/community/undo_block_user.rs
+++ b/crates/apub/src/protocol/activities/community/undo_block_user.rs
@@ -11,8 +11,10 @@ use url::Url;
#[serde(rename_all = "camelCase")]
pub struct UndoBlockUserFromCommunity {
pub(crate) actor: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec,
pub(crate) object: BlockUserFromCommunity,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) cc: Vec,
#[serde(rename = "type")]
pub(crate) kind: UndoType,
diff --git a/crates/apub/src/protocol/activities/community/update.rs b/crates/apub/src/protocol/activities/community/update.rs
index 9bdcded9a..bb9b56114 100644
--- a/crates/apub/src/protocol/activities/community/update.rs
+++ b/crates/apub/src/protocol/activities/community/update.rs
@@ -13,9 +13,11 @@ use url::Url;
#[serde(rename_all = "camelCase")]
pub struct UpdateCommunity {
pub(crate) actor: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec,
// TODO: would be nice to use a separate struct here, which only contains the fields updated here
pub(crate) object: Box,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) cc: Vec,
#[serde(rename = "type")]
pub(crate) kind: UpdateType,
diff --git a/crates/apub/src/protocol/activities/create_or_update/comment.rs b/crates/apub/src/protocol/activities/create_or_update/comment.rs
index deef4c862..091c0f97c 100644
--- a/crates/apub/src/protocol/activities/create_or_update/comment.rs
+++ b/crates/apub/src/protocol/activities/create_or_update/comment.rs
@@ -11,8 +11,10 @@ use url::Url;
#[serde(rename_all = "camelCase")]
pub struct CreateOrUpdateComment {
pub(crate) actor: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec,
pub(crate) object: Note,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) cc: Vec,
#[serde(default)]
pub(crate) tag: Vec,
diff --git a/crates/apub/src/protocol/activities/create_or_update/mod.rs b/crates/apub/src/protocol/activities/create_or_update/mod.rs
index 6506c283b..63ddcf967 100644
--- a/crates/apub/src/protocol/activities/create_or_update/mod.rs
+++ b/crates/apub/src/protocol/activities/create_or_update/mod.rs
@@ -26,5 +26,8 @@ mod tests {
file_to_json_object::("assets/pleroma/activities/create_note.json");
file_to_json_object::("assets/smithereen/activities/create_note.json");
file_to_json_object::("assets/mastodon/activities/create_note.json");
+
+ file_to_json_object::("assets/lotide/activities/create_page.json");
+ file_to_json_object::("assets/lotide/activities/create_note_reply.json");
}
}
diff --git a/crates/apub/src/protocol/activities/create_or_update/post.rs b/crates/apub/src/protocol/activities/create_or_update/post.rs
index db159c860..2061c8815 100644
--- a/crates/apub/src/protocol/activities/create_or_update/post.rs
+++ b/crates/apub/src/protocol/activities/create_or_update/post.rs
@@ -10,8 +10,10 @@ use url::Url;
#[serde(rename_all = "camelCase")]
pub struct CreateOrUpdatePost {
pub(crate) actor: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec,
pub(crate) object: Page,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) cc: Vec,
#[serde(rename = "type")]
pub(crate) kind: CreateOrUpdateType,
diff --git a/crates/apub/src/protocol/activities/deletion/delete.rs b/crates/apub/src/protocol/activities/deletion/delete.rs
index 49adb6e65..a999e58ec 100644
--- a/crates/apub/src/protocol/activities/deletion/delete.rs
+++ b/crates/apub/src/protocol/activities/deletion/delete.rs
@@ -13,6 +13,7 @@ use url::Url;
#[serde(rename_all = "camelCase")]
pub struct Delete {
pub(crate) actor: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec,
pub(crate) object: Tombstone,
#[serde(rename = "type")]
diff --git a/crates/apub/src/protocol/activities/deletion/undo_delete.rs b/crates/apub/src/protocol/activities/deletion/undo_delete.rs
index d76080170..ebd51f964 100644
--- a/crates/apub/src/protocol/activities/deletion/undo_delete.rs
+++ b/crates/apub/src/protocol/activities/deletion/undo_delete.rs
@@ -11,8 +11,10 @@ use url::Url;
#[serde(rename_all = "camelCase")]
pub struct UndoDelete {
pub(crate) actor: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec,
pub(crate) object: Delete,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) cc: Vec,
#[serde(rename = "type")]
pub(crate) kind: UndoType,
diff --git a/crates/apub/src/protocol/activities/voting/undo_vote.rs b/crates/apub/src/protocol/activities/voting/undo_vote.rs
index 9a1767f2a..d3f4fb337 100644
--- a/crates/apub/src/protocol/activities/voting/undo_vote.rs
+++ b/crates/apub/src/protocol/activities/voting/undo_vote.rs
@@ -11,8 +11,10 @@ use url::Url;
#[serde(rename_all = "camelCase")]
pub struct UndoVote {
pub(crate) actor: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec,
pub(crate) object: Vote,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) cc: Vec,
#[serde(rename = "type")]
pub(crate) kind: UndoType,
diff --git a/crates/apub/src/protocol/activities/voting/vote.rs b/crates/apub/src/protocol/activities/voting/vote.rs
index 065e3b31f..ba5c629ce 100644
--- a/crates/apub/src/protocol/activities/voting/vote.rs
+++ b/crates/apub/src/protocol/activities/voting/vote.rs
@@ -15,8 +15,10 @@ use url::Url;
#[serde(rename_all = "camelCase")]
pub struct Vote {
pub(crate) actor: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec,
pub(crate) object: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) cc: Vec,
#[serde(rename = "type")]
pub(crate) kind: VoteType,
diff --git a/crates/apub/src/protocol/objects/note.rs b/crates/apub/src/protocol/objects/note.rs
index be21f8f57..39c606b00 100644
--- a/crates/apub/src/protocol/objects/note.rs
+++ b/crates/apub/src/protocol/objects/note.rs
@@ -23,8 +23,10 @@ pub struct Note {
pub(crate) r#type: NoteType,
pub(crate) id: ObjectId,
pub(crate) attributed_to: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec,
#[serde(default)]
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) cc: Vec,
pub(crate) content: String,
pub(crate) media_type: Option,
diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs
index 2bd5cc6dd..1fca51e45 100644
--- a/crates/apub/src/protocol/objects/page.rs
+++ b/crates/apub/src/protocol/objects/page.rs
@@ -24,8 +24,10 @@ pub struct Page {
pub(crate) r#type: PageType,
pub(crate) id: ObjectId,
pub(crate) attributed_to: ObjectId,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec,
#[serde(default)]
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) cc: Vec,
pub(crate) name: String,
pub(crate) content: Option,