Rewrite activitypub following, person, community, pm (#1692)
* Rewrite following activities * Rewrite person apub * Rewrite community apub * Rewrite private message activity sending
This commit is contained in:
parent
44716c2f24
commit
353a1fe0a0
45 changed files with 750 additions and 1159 deletions
84
Cargo.lock
generated
84
Cargo.lock
generated
|
@ -795,8 +795,18 @@ version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
|
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.10.2",
|
||||||
"darling_macro",
|
"darling_macro 0.10.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.13.0",
|
||||||
|
"darling_macro 0.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -809,7 +819,21 @@ dependencies = [
|
||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2 1.0.27",
|
"proc-macro2 1.0.27",
|
||||||
"quote 1.0.9",
|
"quote 1.0.9",
|
||||||
"strsim",
|
"strsim 0.9.3",
|
||||||
|
"syn 1.0.73",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2 1.0.27",
|
||||||
|
"quote 1.0.9",
|
||||||
|
"strsim 0.10.0",
|
||||||
"syn 1.0.73",
|
"syn 1.0.73",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -819,7 +843,18 @@ version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
|
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.10.2",
|
||||||
|
"quote 1.0.9",
|
||||||
|
"syn 1.0.73",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.13.0",
|
||||||
"quote 1.0.9",
|
"quote 1.0.9",
|
||||||
"syn 1.0.73",
|
"syn 1.0.73",
|
||||||
]
|
]
|
||||||
|
@ -840,7 +875,7 @@ version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
|
checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.10.2",
|
||||||
"derive_builder_core",
|
"derive_builder_core",
|
||||||
"proc-macro2 1.0.27",
|
"proc-macro2 1.0.27",
|
||||||
"quote 1.0.9",
|
"quote 1.0.9",
|
||||||
|
@ -853,7 +888,7 @@ version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
|
checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.10.2",
|
||||||
"proc-macro2 1.0.27",
|
"proc-macro2 1.0.27",
|
||||||
"quote 1.0.9",
|
"quote 1.0.9",
|
||||||
"syn 1.0.73",
|
"syn 1.0.73",
|
||||||
|
@ -1661,7 +1696,6 @@ name = "lemmy_apub"
|
||||||
version = "0.11.3"
|
version = "0.11.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitystreams",
|
"activitystreams",
|
||||||
"activitystreams-ext",
|
|
||||||
"actix",
|
"actix",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
@ -1695,6 +1729,7 @@ dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_with",
|
||||||
"sha2",
|
"sha2",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
|
@ -2732,6 +2767,12 @@ dependencies = [
|
||||||
"webpki",
|
"webpki",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
|
@ -2861,6 +2902,29 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_with"
|
||||||
|
version = "1.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ad9fdbb69badc8916db738c25efd04f0a65297d26c2f8de4b62e57b8c12bc72"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
"serde",
|
||||||
|
"serde_with_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_with_macros"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1569374bd54623ec8bd592cf22ba6e03c0f177ff55fbc8c29a49e296e7adecf"
|
||||||
|
dependencies = [
|
||||||
|
"darling 0.13.0",
|
||||||
|
"proc-macro2 1.0.27",
|
||||||
|
"quote 1.0.9",
|
||||||
|
"syn 1.0.73",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serial_test"
|
name = "serial_test"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -3056,6 +3120,12 @@ version = "0.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "strum"
|
||||||
version = "0.21.0"
|
version = "0.21.0"
|
||||||
|
|
|
@ -8,7 +8,13 @@ use lemmy_api_common::{
|
||||||
get_local_user_view_from_jwt,
|
get_local_user_view_from_jwt,
|
||||||
is_mod_or_admin,
|
is_mod_or_admin,
|
||||||
};
|
};
|
||||||
use lemmy_apub::{ActorType, CommunityType, UserType};
|
use lemmy_apub::{
|
||||||
|
activities::following::{
|
||||||
|
follow::FollowCommunity as FollowCommunityApub,
|
||||||
|
undo::UndoFollowCommunity,
|
||||||
|
},
|
||||||
|
CommunityType,
|
||||||
|
};
|
||||||
use lemmy_db_queries::{
|
use lemmy_db_queries::{
|
||||||
source::{comment::Comment_, community::CommunityModerator_, post::Post_},
|
source::{comment::Comment_, community::CommunityModerator_, post::Post_},
|
||||||
Bannable,
|
Bannable,
|
||||||
|
@ -74,15 +80,9 @@ impl Perform for FollowCommunity {
|
||||||
} else if data.follow {
|
} else if data.follow {
|
||||||
// Dont actually add to the community followers here, because you need
|
// Dont actually add to the community followers here, because you need
|
||||||
// to wait for the accept
|
// to wait for the accept
|
||||||
local_user_view
|
FollowCommunityApub::send(&local_user_view.person, &community, context).await?;
|
||||||
.person
|
|
||||||
.send_follow(&community.actor_id(), context)
|
|
||||||
.await?;
|
|
||||||
} else {
|
} else {
|
||||||
local_user_view
|
UndoFollowCommunity::send(&local_user_view.person, &community, context).await?;
|
||||||
.person
|
|
||||||
.send_unfollow(&community.actor_id(), context)
|
|
||||||
.await?;
|
|
||||||
let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
|
let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
|
||||||
if blocking(context.pool(), unfollow).await?.is_err() {
|
if blocking(context.pool(), unfollow).await?.is_err() {
|
||||||
return Err(ApiError::err("community_follower_already_exists").into());
|
return Err(ApiError::err("community_follower_already_exists").into());
|
||||||
|
|
|
@ -5,7 +5,10 @@ use lemmy_api_common::{
|
||||||
get_local_user_view_from_jwt,
|
get_local_user_view_from_jwt,
|
||||||
person::{DeletePrivateMessage, PrivateMessageResponse},
|
person::{DeletePrivateMessage, PrivateMessageResponse},
|
||||||
};
|
};
|
||||||
use lemmy_apub::ApubObjectType;
|
use lemmy_apub::activities::private_message::{
|
||||||
|
delete::DeletePrivateMessage as DeletePrivateMessageApub,
|
||||||
|
undo_delete::UndoDeletePrivateMessage,
|
||||||
|
};
|
||||||
use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud, DeleteableOrRemoveable};
|
use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud, DeleteableOrRemoveable};
|
||||||
use lemmy_db_schema::source::private_message::PrivateMessage;
|
use lemmy_db_schema::source::private_message::PrivateMessage;
|
||||||
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
|
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
|
||||||
|
@ -45,13 +48,14 @@ impl PerformCrud for DeletePrivateMessage {
|
||||||
|
|
||||||
// Send the apub update
|
// Send the apub update
|
||||||
if data.deleted {
|
if data.deleted {
|
||||||
updated_private_message
|
DeletePrivateMessageApub::send(
|
||||||
.blank_out_deleted_or_removed_info()
|
&local_user_view.person,
|
||||||
.send_delete(&local_user_view.person, context)
|
&updated_private_message.blank_out_deleted_or_removed_info(),
|
||||||
.await?;
|
context,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
updated_private_message
|
UndoDeletePrivateMessage::send(&local_user_view.person, &updated_private_message, context)
|
||||||
.send_undo_delete(&local_user_view.person, context)
|
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,11 +21,11 @@ lemmy_api_common = { version = "=0.11.3", path = "../api_common" }
|
||||||
lemmy_websocket = { version = "=0.11.3", path = "../websocket" }
|
lemmy_websocket = { version = "=0.11.3", path = "../websocket" }
|
||||||
diesel = "1.4.7"
|
diesel = "1.4.7"
|
||||||
activitystreams = "0.7.0-alpha.11"
|
activitystreams = "0.7.0-alpha.11"
|
||||||
activitystreams-ext = "0.1.0-alpha.2"
|
|
||||||
bcrypt = "0.10.0"
|
bcrypt = "0.10.0"
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
serde_json = { version = "1.0.64", features = ["preserve_order"] }
|
serde_json = { version = "1.0.64", features = ["preserve_order"] }
|
||||||
serde = { version = "1.0.126", features = ["derive"] }
|
serde = { version = "1.0.126", features = ["derive"] }
|
||||||
|
serde_with = "1.9.4"
|
||||||
actix = "0.12.0"
|
actix = "0.12.0"
|
||||||
actix-web = { version = "4.0.0-beta.8", default-features = false }
|
actix-web = { version = "4.0.0-beta.8", default-features = false }
|
||||||
actix-rt = { version = "2.2.0", default-features = false }
|
actix-rt = { version = "2.2.0", default-features = false }
|
||||||
|
|
|
@ -96,7 +96,7 @@ impl ActivityHandler for CreateOrUpdateComment {
|
||||||
request_counter,
|
request_counter,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
verify_domains_match(&self.common.actor, &self.object.id)?;
|
verify_domains_match(&self.common.actor, self.object.id_unchecked())?;
|
||||||
// TODO: should add a check that the correct community is in cc (probably needs changes to
|
// TODO: should add a check that the correct community is in cc (probably needs changes to
|
||||||
// comment deserialization)
|
// comment deserialization)
|
||||||
self.object.verify(context, request_counter).await?;
|
self.object.verify(context, request_counter).await?;
|
||||||
|
@ -104,18 +104,12 @@ impl ActivityHandler for CreateOrUpdateComment {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
let comment = Comment::from_apub(
|
let comment =
|
||||||
&self.object,
|
Comment::from_apub(&self.object, context, &self.common.actor, request_counter).await?;
|
||||||
context,
|
|
||||||
self.common.actor.clone(),
|
|
||||||
request_counter,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let recipients =
|
let recipients =
|
||||||
get_notif_recipients(&self.common.actor, &comment, context, request_counter).await?;
|
get_notif_recipients(&self.common.actor, &comment, context, request_counter).await?;
|
||||||
let notif_type = match self.kind {
|
let notif_type = match self.kind {
|
||||||
|
|
|
@ -45,7 +45,7 @@ impl ActivityHandler for AddMod {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
@ -71,7 +71,7 @@ impl ActivityHandler for AddMod {
|
||||||
.await??;
|
.await??;
|
||||||
}
|
}
|
||||||
if community.local {
|
if community.local {
|
||||||
let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(self)?)?;
|
let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(&self)?)?;
|
||||||
community
|
community
|
||||||
.send_announce(anybase, Some(self.object.clone()), context)
|
.send_announce(anybase, Some(self.object.clone()), context)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -110,7 +110,7 @@ impl ActivityHandler for AnnounceActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
|
|
@ -42,7 +42,7 @@ impl ActivityHandler for BlockUserFromCommunity {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
|
|
@ -43,7 +43,7 @@ impl ActivityHandler for UndoBlockUserFromCommunity {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
|
|
@ -5,8 +5,7 @@ use crate::{
|
||||||
verify_mod_action,
|
verify_mod_action,
|
||||||
verify_person_in_community,
|
verify_person_in_community,
|
||||||
},
|
},
|
||||||
objects::FromApubToForm,
|
objects::community::Group,
|
||||||
GroupExt,
|
|
||||||
};
|
};
|
||||||
use activitystreams::activity::kind::UpdateType;
|
use activitystreams::activity::kind::UpdateType;
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
|
@ -23,7 +22,7 @@ use url::Url;
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct UpdateCommunity {
|
pub struct UpdateCommunity {
|
||||||
to: PublicUrl,
|
to: PublicUrl,
|
||||||
object: GroupExt,
|
object: Group,
|
||||||
cc: [Url; 1],
|
cc: [Url; 1],
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
kind: UpdateType,
|
kind: UpdateType,
|
||||||
|
@ -45,9 +44,9 @@ impl ActivityHandler for UpdateCommunity {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
_request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
let cc = self.cc[0].clone().into();
|
let cc = self.cc[0].clone().into();
|
||||||
let community = blocking(context.pool(), move |conn| {
|
let community = blocking(context.pool(), move |conn| {
|
||||||
|
@ -55,14 +54,8 @@ impl ActivityHandler for UpdateCommunity {
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
let updated_community = CommunityForm::from_apub(
|
let updated_community =
|
||||||
&self.object,
|
Group::from_apub_to_form(&self.object, &community.actor_id.clone().into()).await?;
|
||||||
context,
|
|
||||||
community.actor_id.clone().into(),
|
|
||||||
request_counter,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let cf = CommunityForm {
|
let cf = CommunityForm {
|
||||||
name: updated_community.name,
|
name: updated_community.name,
|
||||||
title: updated_community.title,
|
title: updated_community.title,
|
||||||
|
|
|
@ -74,7 +74,7 @@ impl ActivityHandler for DeletePostCommentOrCommunity {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
|
|
@ -62,7 +62,7 @@ impl ActivityHandler for UndoDeletePostCommentOrCommunity {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::{following::follow::FollowCommunity, verify_activity, verify_community},
|
activities::{
|
||||||
|
following::follow::FollowCommunity,
|
||||||
|
generate_activity_id,
|
||||||
|
verify_activity,
|
||||||
|
verify_community,
|
||||||
|
},
|
||||||
|
activity_queue::send_activity_new,
|
||||||
|
extensions::context::lemmy_context,
|
||||||
fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person},
|
fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person},
|
||||||
|
ActorType,
|
||||||
};
|
};
|
||||||
use activitystreams::activity::kind::AcceptType;
|
use activitystreams::activity::kind::AcceptType;
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler};
|
use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler};
|
||||||
use lemmy_db_queries::Followable;
|
use lemmy_db_queries::{ApubObject, Followable};
|
||||||
use lemmy_db_schema::source::community::CommunityFollower;
|
use lemmy_db_schema::source::{
|
||||||
|
community::{Community, CommunityFollower},
|
||||||
|
person::Person,
|
||||||
|
};
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -22,6 +33,35 @@ pub struct AcceptFollowCommunity {
|
||||||
common: ActivityCommonFields,
|
common: ActivityCommonFields,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AcceptFollowCommunity {
|
||||||
|
pub async fn send(follow: FollowCommunity, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||||
|
let community_id = follow.object.clone();
|
||||||
|
let community = blocking(context.pool(), move |conn| {
|
||||||
|
Community::read_from_apub_id(conn, &community_id.into())
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
let person_id = follow.common.actor.clone();
|
||||||
|
let person = blocking(context.pool(), move |conn| {
|
||||||
|
Person::read_from_apub_id(conn, &person_id.into())
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let id = generate_activity_id(AcceptType::Accept)?;
|
||||||
|
let accept = AcceptFollowCommunity {
|
||||||
|
to: person.actor_id(),
|
||||||
|
object: follow,
|
||||||
|
kind: AcceptType::Accept,
|
||||||
|
common: ActivityCommonFields {
|
||||||
|
context: lemmy_context(),
|
||||||
|
id: id.clone(),
|
||||||
|
actor: community.actor_id(),
|
||||||
|
unparsed: Default::default(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let inbox = vec![person.inbox_url.into()];
|
||||||
|
send_activity_new(context, &accept, &id, &community, inbox, true).await
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Handle accepted follows
|
/// Handle accepted follows
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ActivityHandler for AcceptFollowCommunity {
|
impl ActivityHandler for AcceptFollowCommunity {
|
||||||
|
@ -39,7 +79,7 @@ impl ActivityHandler for AcceptFollowCommunity {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::{verify_activity, verify_person},
|
activities::{
|
||||||
|
following::accept::AcceptFollowCommunity,
|
||||||
|
generate_activity_id,
|
||||||
|
verify_activity,
|
||||||
|
verify_person,
|
||||||
|
},
|
||||||
|
activity_queue::send_activity_new,
|
||||||
|
extensions::context::lemmy_context,
|
||||||
fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person},
|
fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person},
|
||||||
CommunityType,
|
ActorType,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::activity::kind::FollowType;
|
||||||
activity::{kind::FollowType, Follow},
|
|
||||||
base::{AnyBase, ExtendsExt},
|
|
||||||
};
|
|
||||||
use anyhow::Context;
|
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler};
|
use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler};
|
||||||
use lemmy_db_queries::Followable;
|
use lemmy_db_queries::Followable;
|
||||||
use lemmy_db_schema::source::community::{CommunityFollower, CommunityFollowerForm};
|
use lemmy_db_schema::source::{
|
||||||
use lemmy_utils::{location_info, LemmyError};
|
community::{Community, CommunityFollower, CommunityFollowerForm},
|
||||||
|
person::Person,
|
||||||
|
};
|
||||||
|
use lemmy_utils::LemmyError;
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -22,11 +28,44 @@ pub struct FollowCommunity {
|
||||||
pub(in crate::activities::following) to: Url,
|
pub(in crate::activities::following) to: Url,
|
||||||
pub(in crate::activities::following) object: Url,
|
pub(in crate::activities::following) object: Url,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
kind: FollowType,
|
pub(in crate::activities::following) kind: FollowType,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub(in crate::activities::following) common: ActivityCommonFields,
|
pub(in crate::activities::following) common: ActivityCommonFields,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FollowCommunity {
|
||||||
|
pub async fn send(
|
||||||
|
actor: &Person,
|
||||||
|
community: &Community,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let community_follower_form = CommunityFollowerForm {
|
||||||
|
community_id: community.id,
|
||||||
|
person_id: actor.id,
|
||||||
|
pending: true,
|
||||||
|
};
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
CommunityFollower::follow(conn, &community_follower_form).ok()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let id = generate_activity_id(FollowType::Follow)?;
|
||||||
|
let follow = FollowCommunity {
|
||||||
|
to: community.actor_id(),
|
||||||
|
object: community.actor_id(),
|
||||||
|
kind: FollowType::Follow,
|
||||||
|
common: ActivityCommonFields {
|
||||||
|
context: lemmy_context(),
|
||||||
|
id: id.clone(),
|
||||||
|
actor: actor.actor_id(),
|
||||||
|
unparsed: Default::default(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let inbox = vec![community.inbox_url.clone().into()];
|
||||||
|
send_activity_new(context, &follow, &id, actor, inbox, true).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ActivityHandler for FollowCommunity {
|
impl ActivityHandler for FollowCommunity {
|
||||||
async fn verify(
|
async fn verify(
|
||||||
|
@ -41,7 +80,7 @@ impl ActivityHandler for FollowCommunity {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
@ -61,10 +100,7 @@ impl ActivityHandler for FollowCommunity {
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// TODO: avoid the conversion and pass our own follow struct directly
|
AcceptFollowCommunity::send(self, context).await
|
||||||
let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(self)?)?;
|
|
||||||
let anybase = Follow::from_any_base(anybase)?.context(location_info!())?;
|
|
||||||
community.send_accept_follow(anybase, context).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn common(&self) -> &ActivityCommonFields {
|
fn common(&self) -> &ActivityCommonFields {
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::{following::follow::FollowCommunity, verify_activity, verify_person},
|
activities::{
|
||||||
|
following::follow::FollowCommunity,
|
||||||
|
generate_activity_id,
|
||||||
|
verify_activity,
|
||||||
|
verify_person,
|
||||||
|
},
|
||||||
|
activity_queue::send_activity_new,
|
||||||
|
extensions::context::lemmy_context,
|
||||||
fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person},
|
fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person},
|
||||||
|
ActorType,
|
||||||
};
|
};
|
||||||
use activitystreams::activity::kind::UndoType;
|
use activitystreams::activity::kind::{FollowType, UndoType};
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler};
|
use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler};
|
||||||
use lemmy_db_queries::Followable;
|
use lemmy_db_queries::Followable;
|
||||||
use lemmy_db_schema::source::community::{CommunityFollower, CommunityFollowerForm};
|
use lemmy_db_schema::source::{
|
||||||
|
community::{Community, CommunityFollower, CommunityFollowerForm},
|
||||||
|
person::Person,
|
||||||
|
};
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -22,6 +33,39 @@ pub struct UndoFollowCommunity {
|
||||||
common: ActivityCommonFields,
|
common: ActivityCommonFields,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UndoFollowCommunity {
|
||||||
|
pub async fn send(
|
||||||
|
actor: &Person,
|
||||||
|
community: &Community,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let id = generate_activity_id(UndoType::Undo)?;
|
||||||
|
let undo = UndoFollowCommunity {
|
||||||
|
to: community.actor_id(),
|
||||||
|
object: FollowCommunity {
|
||||||
|
to: community.actor_id(),
|
||||||
|
object: community.actor_id(),
|
||||||
|
kind: FollowType::Follow,
|
||||||
|
common: ActivityCommonFields {
|
||||||
|
context: lemmy_context(),
|
||||||
|
id: generate_activity_id(FollowType::Follow)?,
|
||||||
|
actor: actor.actor_id(),
|
||||||
|
unparsed: Default::default(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kind: UndoType::Undo,
|
||||||
|
common: ActivityCommonFields {
|
||||||
|
context: lemmy_context(),
|
||||||
|
id: id.clone(),
|
||||||
|
actor: actor.actor_id(),
|
||||||
|
unparsed: Default::default(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let inbox = vec![community.get_shared_inbox_or_inbox_url()];
|
||||||
|
send_activity_new(context, &undo, &id, actor, inbox, true).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ActivityHandler for UndoFollowCommunity {
|
impl ActivityHandler for UndoFollowCommunity {
|
||||||
async fn verify(
|
async fn verify(
|
||||||
|
@ -38,7 +82,7 @@ impl ActivityHandler for UndoFollowCommunity {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
|
|
@ -87,7 +87,7 @@ impl ActivityHandler for CreateOrUpdatePost {
|
||||||
verify_person_in_community(&self.common.actor, &community_id, context, request_counter).await?;
|
verify_person_in_community(&self.common.actor, &community_id, context, request_counter).await?;
|
||||||
match self.kind {
|
match self.kind {
|
||||||
CreateOrUpdateType::Create => {
|
CreateOrUpdateType::Create => {
|
||||||
verify_domains_match(&self.common.actor, &self.object.id)?;
|
verify_domains_match(&self.common.actor, self.object.id_unchecked())?;
|
||||||
verify_urls_match(&self.common.actor, &self.object.attributed_to)?;
|
verify_urls_match(&self.common.actor, &self.object.attributed_to)?;
|
||||||
// Check that the post isnt locked or stickied, as that isnt possible for newly created posts.
|
// Check that the post isnt locked or stickied, as that isnt possible for newly created posts.
|
||||||
// However, when fetching a remote post we generate a new create activity with the current
|
// However, when fetching a remote post we generate a new create activity with the current
|
||||||
|
@ -104,7 +104,7 @@ impl ActivityHandler for CreateOrUpdatePost {
|
||||||
if is_mod_action {
|
if is_mod_action {
|
||||||
verify_mod_action(&self.common.actor, community_id, context).await?;
|
verify_mod_action(&self.common.actor, community_id, context).await?;
|
||||||
} else {
|
} else {
|
||||||
verify_domains_match(&self.common.actor, &self.object.id)?;
|
verify_domains_match(&self.common.actor, self.object.id_unchecked())?;
|
||||||
verify_urls_match(&self.common.actor, &self.object.attributed_to)?;
|
verify_urls_match(&self.common.actor, &self.object.attributed_to)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,20 +114,13 @@ impl ActivityHandler for CreateOrUpdatePost {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
let actor =
|
let actor =
|
||||||
get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?;
|
get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?;
|
||||||
let post = Post::from_apub(
|
let post = Post::from_apub(&self.object, context, &actor.actor_id(), request_counter).await?;
|
||||||
&self.object,
|
|
||||||
context,
|
|
||||||
actor.actor_id(),
|
|
||||||
request_counter,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let notif_type = match self.kind {
|
let notif_type = match self.kind {
|
||||||
CreateOrUpdateType::Create => UserOperationCrud::CreatePost,
|
CreateOrUpdateType::Create => UserOperationCrud::CreatePost,
|
||||||
|
|
|
@ -53,15 +53,8 @@ impl CreateOrUpdatePrivateMessage {
|
||||||
unparsed: Default::default(),
|
unparsed: Default::default(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
send_activity_new(
|
let inbox = vec![recipient.get_shared_inbox_or_inbox_url()];
|
||||||
context,
|
send_activity_new(context, &create_or_update, &id, actor, inbox, true).await
|
||||||
&create_or_update,
|
|
||||||
&id,
|
|
||||||
actor,
|
|
||||||
vec![recipient.get_shared_inbox_or_inbox_url()],
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
|
@ -73,24 +66,18 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage {
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
verify_activity(self.common())?;
|
verify_activity(self.common())?;
|
||||||
verify_person(&self.common.actor, context, request_counter).await?;
|
verify_person(&self.common.actor, context, request_counter).await?;
|
||||||
verify_domains_match(&self.common.actor, &self.object.id)?;
|
verify_domains_match(&self.common.actor, self.object.id_unchecked())?;
|
||||||
self.object.verify(context, request_counter).await?;
|
self.object.verify(context, request_counter).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
let private_message = PrivateMessage::from_apub(
|
let private_message =
|
||||||
&self.object,
|
PrivateMessage::from_apub(&self.object, context, &self.common.actor, request_counter).await?;
|
||||||
context,
|
|
||||||
self.common.actor.clone(),
|
|
||||||
request_counter,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let notif_type = match self.kind {
|
let notif_type = match self.kind {
|
||||||
CreateOrUpdateType::Create => UserOperationCrud::CreatePrivateMessage,
|
CreateOrUpdateType::Create => UserOperationCrud::CreatePrivateMessage,
|
||||||
|
|
|
@ -1,9 +1,19 @@
|
||||||
use crate::activities::{private_message::send_websocket_message, verify_activity, verify_person};
|
use crate::{
|
||||||
|
activities::{
|
||||||
|
generate_activity_id,
|
||||||
|
private_message::send_websocket_message,
|
||||||
|
verify_activity,
|
||||||
|
verify_person,
|
||||||
|
},
|
||||||
|
activity_queue::send_activity_new,
|
||||||
|
extensions::context::lemmy_context,
|
||||||
|
ActorType,
|
||||||
|
};
|
||||||
use activitystreams::activity::kind::DeleteType;
|
use activitystreams::activity::kind::DeleteType;
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandler};
|
use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandler};
|
||||||
use lemmy_db_queries::{source::private_message::PrivateMessage_, ApubObject};
|
use lemmy_db_queries::{source::private_message::PrivateMessage_, ApubObject, Crud};
|
||||||
use lemmy_db_schema::source::private_message::PrivateMessage;
|
use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage};
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -11,14 +21,41 @@ use url::Url;
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DeletePrivateMessage {
|
pub struct DeletePrivateMessage {
|
||||||
to: Url,
|
pub(in crate::activities::private_message) to: Url,
|
||||||
pub(in crate::activities::private_message) object: Url,
|
pub(in crate::activities::private_message) object: Url,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
kind: DeleteType,
|
pub(in crate::activities::private_message) kind: DeleteType,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub(in crate::activities::private_message) common: ActivityCommonFields,
|
pub(in crate::activities::private_message) common: ActivityCommonFields,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DeletePrivateMessage {
|
||||||
|
pub async fn send(
|
||||||
|
actor: &Person,
|
||||||
|
pm: &PrivateMessage,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let recipient_id = pm.recipient_id;
|
||||||
|
let recipient =
|
||||||
|
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
|
||||||
|
|
||||||
|
let id = generate_activity_id(DeleteType::Delete)?;
|
||||||
|
let delete = DeletePrivateMessage {
|
||||||
|
to: actor.actor_id(),
|
||||||
|
object: pm.ap_id.clone().into(),
|
||||||
|
kind: DeleteType::Delete,
|
||||||
|
common: ActivityCommonFields {
|
||||||
|
context: lemmy_context(),
|
||||||
|
id: id.clone(),
|
||||||
|
actor: actor.actor_id(),
|
||||||
|
unparsed: Default::default(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let inbox = vec![recipient.get_shared_inbox_or_inbox_url()];
|
||||||
|
send_activity_new(context, &delete, &id, actor, inbox, true).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ActivityHandler for DeletePrivateMessage {
|
impl ActivityHandler for DeletePrivateMessage {
|
||||||
async fn verify(
|
async fn verify(
|
||||||
|
@ -33,7 +70,7 @@ impl ActivityHandler for DeletePrivateMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
_request_counter: &mut i32,
|
_request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
use crate::activities::{
|
use crate::{
|
||||||
private_message::{delete::DeletePrivateMessage, send_websocket_message},
|
activities::{
|
||||||
verify_activity,
|
generate_activity_id,
|
||||||
verify_person,
|
private_message::{delete::DeletePrivateMessage, send_websocket_message},
|
||||||
|
verify_activity,
|
||||||
|
verify_person,
|
||||||
|
},
|
||||||
|
activity_queue::send_activity_new,
|
||||||
|
extensions::context::lemmy_context,
|
||||||
|
ActorType,
|
||||||
};
|
};
|
||||||
use activitystreams::activity::kind::UndoType;
|
use activitystreams::activity::kind::{DeleteType, UndoType};
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_apub_lib::{
|
use lemmy_apub_lib::{
|
||||||
verify_domains_match,
|
verify_domains_match,
|
||||||
|
@ -11,8 +17,8 @@ use lemmy_apub_lib::{
|
||||||
ActivityCommonFields,
|
ActivityCommonFields,
|
||||||
ActivityHandler,
|
ActivityHandler,
|
||||||
};
|
};
|
||||||
use lemmy_db_queries::{source::private_message::PrivateMessage_, ApubObject};
|
use lemmy_db_queries::{source::private_message::PrivateMessage_, ApubObject, Crud};
|
||||||
use lemmy_db_schema::source::private_message::PrivateMessage;
|
use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage};
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -28,6 +34,45 @@ pub struct UndoDeletePrivateMessage {
|
||||||
common: ActivityCommonFields,
|
common: ActivityCommonFields,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UndoDeletePrivateMessage {
|
||||||
|
pub async fn send(
|
||||||
|
actor: &Person,
|
||||||
|
pm: &PrivateMessage,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let recipient_id = pm.recipient_id;
|
||||||
|
let recipient =
|
||||||
|
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
|
||||||
|
|
||||||
|
let object = DeletePrivateMessage {
|
||||||
|
to: recipient.actor_id(),
|
||||||
|
object: pm.ap_id.clone().into(),
|
||||||
|
kind: DeleteType::Delete,
|
||||||
|
common: ActivityCommonFields {
|
||||||
|
context: lemmy_context(),
|
||||||
|
id: generate_activity_id(DeleteType::Delete)?,
|
||||||
|
actor: actor.actor_id(),
|
||||||
|
unparsed: Default::default(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let id = generate_activity_id(UndoType::Undo)?;
|
||||||
|
let undo = UndoDeletePrivateMessage {
|
||||||
|
to: recipient.actor_id(),
|
||||||
|
object,
|
||||||
|
kind: UndoType::Undo,
|
||||||
|
common: ActivityCommonFields {
|
||||||
|
context: lemmy_context(),
|
||||||
|
id: id.clone(),
|
||||||
|
actor: actor.actor_id(),
|
||||||
|
unparsed: Default::default(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let inbox = vec![recipient.get_shared_inbox_or_inbox_url()];
|
||||||
|
send_activity_new(context, &undo, &id, actor, inbox, true).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ActivityHandler for UndoDeletePrivateMessage {
|
impl ActivityHandler for UndoDeletePrivateMessage {
|
||||||
async fn verify(
|
async fn verify(
|
||||||
|
@ -44,7 +89,7 @@ impl ActivityHandler for UndoDeletePrivateMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
_request_counter: &mut i32,
|
_request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
|
|
@ -77,7 +77,7 @@ impl ActivityHandler for RemovePostCommentCommunityOrMod {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
@ -115,7 +115,7 @@ impl ActivityHandler for RemovePostCommentCommunityOrMod {
|
||||||
CommunityModerator::leave(conn, &form)
|
CommunityModerator::leave(conn, &form)
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(self)?)?;
|
let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(&self)?)?;
|
||||||
community
|
community
|
||||||
.send_announce(anybase, Some(self.object.clone()), context)
|
.send_announce(anybase, Some(self.object.clone()), context)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -62,7 +62,7 @@ impl ActivityHandler for UndoRemovePostCommentOrCommunity {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::generate_activity_id,
|
activities::generate_activity_id,
|
||||||
activity_queue::{send_activity_single_dest, send_to_community, send_to_community_followers},
|
activity_queue::{send_to_community, send_to_community_followers},
|
||||||
check_is_apub_id_valid,
|
check_is_apub_id_valid,
|
||||||
extensions::context::lemmy_context,
|
extensions::context::lemmy_context,
|
||||||
fetcher::{get_or_fetch_and_upsert_actor, person::get_or_fetch_and_upsert_person},
|
fetcher::get_or_fetch_and_upsert_actor,
|
||||||
generate_moderators_url,
|
generate_moderators_url,
|
||||||
insert_activity,
|
insert_activity,
|
||||||
objects::ToApub,
|
objects::ToApub,
|
||||||
|
@ -13,7 +13,6 @@ use crate::{
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{
|
activity::{
|
||||||
kind::{
|
kind::{
|
||||||
AcceptType,
|
|
||||||
AddType,
|
AddType,
|
||||||
AnnounceType,
|
AnnounceType,
|
||||||
BlockType,
|
BlockType,
|
||||||
|
@ -23,13 +22,10 @@ use activitystreams::{
|
||||||
UndoType,
|
UndoType,
|
||||||
UpdateType,
|
UpdateType,
|
||||||
},
|
},
|
||||||
Accept,
|
|
||||||
ActorAndObjectRefExt,
|
|
||||||
Add,
|
Add,
|
||||||
Announce,
|
Announce,
|
||||||
Block,
|
Block,
|
||||||
Delete,
|
Delete,
|
||||||
Follow,
|
|
||||||
OptTargetRefExt,
|
OptTargetRefExt,
|
||||||
Remove,
|
Remove,
|
||||||
Undo,
|
Undo,
|
||||||
|
@ -81,31 +77,6 @@ impl CommunityType for Community {
|
||||||
self.followers_url.clone().into()
|
self.followers_url.clone().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// As a local community, accept the follow request from a remote person.
|
|
||||||
async fn send_accept_follow(
|
|
||||||
&self,
|
|
||||||
follow: Follow,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
let actor_uri = follow
|
|
||||||
.actor()?
|
|
||||||
.as_single_xsd_any_uri()
|
|
||||||
.context(location_info!())?;
|
|
||||||
let person = get_or_fetch_and_upsert_person(actor_uri, context, &mut 0).await?;
|
|
||||||
|
|
||||||
let mut accept = Accept::new(
|
|
||||||
self.actor_id.to_owned().into_inner(),
|
|
||||||
follow.into_any_base()?,
|
|
||||||
);
|
|
||||||
accept
|
|
||||||
.set_many_contexts(lemmy_context())
|
|
||||||
.set_id(generate_activity_id(AcceptType::Accept)?)
|
|
||||||
.set_to(person.actor_id());
|
|
||||||
|
|
||||||
send_activity_single_dest(accept, self, person.inbox_url.into(), context).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If a remote community is updated by a local mod, send the updated info to the community's
|
/// If a remote community is updated by a local mod, send the updated info to the community's
|
||||||
/// instance.
|
/// instance.
|
||||||
async fn send_update(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
async fn send_update(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||||
|
@ -114,7 +85,7 @@ impl CommunityType for Community {
|
||||||
} else {
|
} else {
|
||||||
let mut update = Update::new(
|
let mut update = Update::new(
|
||||||
mod_.actor_id(),
|
mod_.actor_id(),
|
||||||
self.to_apub(context.pool()).await?.into_any_base()?,
|
AnyBase::from_arbitrary_json(self.to_apub(context.pool()).await?)?,
|
||||||
);
|
);
|
||||||
update
|
update
|
||||||
.set_many_contexts(lemmy_context())
|
.set_many_contexts(lemmy_context())
|
||||||
|
|
|
@ -2,4 +2,3 @@ pub(crate) mod comment;
|
||||||
pub(crate) mod community;
|
pub(crate) mod community;
|
||||||
pub(crate) mod person;
|
pub(crate) mod person;
|
||||||
pub(crate) mod post;
|
pub(crate) mod post;
|
||||||
pub(crate) mod private_message;
|
|
||||||
|
|
|
@ -1,27 +1,5 @@
|
||||||
use crate::{
|
use crate::ActorType;
|
||||||
activities::generate_activity_id,
|
use lemmy_db_schema::source::person::Person;
|
||||||
activity_queue::send_activity_single_dest,
|
|
||||||
extensions::context::lemmy_context,
|
|
||||||
ActorType,
|
|
||||||
UserType,
|
|
||||||
};
|
|
||||||
use activitystreams::{
|
|
||||||
activity::{
|
|
||||||
kind::{FollowType, UndoType},
|
|
||||||
Follow,
|
|
||||||
Undo,
|
|
||||||
},
|
|
||||||
base::{BaseExt, ExtendsExt},
|
|
||||||
object::ObjectExt,
|
|
||||||
};
|
|
||||||
use lemmy_api_common::blocking;
|
|
||||||
use lemmy_db_queries::{ApubObject, Followable};
|
|
||||||
use lemmy_db_schema::source::{
|
|
||||||
community::{Community, CommunityFollower, CommunityFollowerForm},
|
|
||||||
person::Person,
|
|
||||||
};
|
|
||||||
use lemmy_utils::LemmyError;
|
|
||||||
use lemmy_websocket::LemmyContext;
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
impl ActorType for Person {
|
impl ActorType for Person {
|
||||||
|
@ -51,69 +29,3 @@ impl ActorType for Person {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl UserType for Person {
|
|
||||||
/// As a given local person, send out a follow request to a remote community.
|
|
||||||
async fn send_follow(
|
|
||||||
&self,
|
|
||||||
follow_actor_id: &Url,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
let follow_actor_id = follow_actor_id.to_owned();
|
|
||||||
let community = blocking(context.pool(), move |conn| {
|
|
||||||
Community::read_from_apub_id(conn, &follow_actor_id.into())
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let community_follower_form = CommunityFollowerForm {
|
|
||||||
community_id: community.id,
|
|
||||||
person_id: self.id,
|
|
||||||
pending: true,
|
|
||||||
};
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
CommunityFollower::follow(conn, &community_follower_form).ok()
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut follow = Follow::new(self.actor_id(), community.actor_id());
|
|
||||||
follow
|
|
||||||
.set_many_contexts(lemmy_context())
|
|
||||||
.set_id(generate_activity_id(FollowType::Follow)?)
|
|
||||||
.set_to(community.actor_id());
|
|
||||||
|
|
||||||
send_activity_single_dest(follow, self, community.inbox_url.into(), context).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_unfollow(
|
|
||||||
&self,
|
|
||||||
follow_actor_id: &Url,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
let follow_actor_id = follow_actor_id.to_owned();
|
|
||||||
let community = blocking(context.pool(), move |conn| {
|
|
||||||
Community::read_from_apub_id(conn, &follow_actor_id.into())
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let mut follow = Follow::new(self.actor_id(), community.actor_id());
|
|
||||||
follow
|
|
||||||
.set_many_contexts(lemmy_context())
|
|
||||||
.set_id(generate_activity_id(FollowType::Follow)?)
|
|
||||||
.set_to(community.actor_id());
|
|
||||||
|
|
||||||
// Undo that fake activity
|
|
||||||
let mut undo = Undo::new(
|
|
||||||
self.actor_id.to_owned().into_inner(),
|
|
||||||
follow.into_any_base()?,
|
|
||||||
);
|
|
||||||
undo
|
|
||||||
.set_many_contexts(lemmy_context())
|
|
||||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
|
||||||
.set_to(community.actor_id());
|
|
||||||
|
|
||||||
send_activity_single_dest(undo, self, community.inbox_url.into(), context).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
use crate::{
|
|
||||||
activities::generate_activity_id,
|
|
||||||
activity_queue::send_activity_single_dest,
|
|
||||||
extensions::context::lemmy_context,
|
|
||||||
ActorType,
|
|
||||||
ApubObjectType,
|
|
||||||
};
|
|
||||||
use activitystreams::{
|
|
||||||
activity::{
|
|
||||||
kind::{DeleteType, UndoType},
|
|
||||||
Delete,
|
|
||||||
Undo,
|
|
||||||
},
|
|
||||||
base::{BaseExt, ExtendsExt},
|
|
||||||
object::ObjectExt,
|
|
||||||
};
|
|
||||||
use lemmy_api_common::blocking;
|
|
||||||
use lemmy_db_queries::Crud;
|
|
||||||
use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage};
|
|
||||||
use lemmy_utils::LemmyError;
|
|
||||||
use lemmy_websocket::LemmyContext;
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl ApubObjectType for PrivateMessage {
|
|
||||||
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
|
||||||
let recipient_id = self.recipient_id;
|
|
||||||
let recipient =
|
|
||||||
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
|
|
||||||
|
|
||||||
let mut delete = Delete::new(
|
|
||||||
creator.actor_id.to_owned().into_inner(),
|
|
||||||
self.ap_id.to_owned().into_inner(),
|
|
||||||
);
|
|
||||||
delete
|
|
||||||
.set_many_contexts(lemmy_context())
|
|
||||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
|
||||||
.set_to(recipient.actor_id());
|
|
||||||
|
|
||||||
send_activity_single_dest(delete, creator, recipient.inbox_url.into(), context).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_undo_delete(
|
|
||||||
&self,
|
|
||||||
creator: &Person,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
let recipient_id = self.recipient_id;
|
|
||||||
let recipient =
|
|
||||||
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
|
|
||||||
|
|
||||||
let mut delete = Delete::new(
|
|
||||||
creator.actor_id.to_owned().into_inner(),
|
|
||||||
self.ap_id.to_owned().into_inner(),
|
|
||||||
);
|
|
||||||
delete
|
|
||||||
.set_many_contexts(lemmy_context())
|
|
||||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
|
||||||
.set_to(recipient.actor_id());
|
|
||||||
|
|
||||||
// Undo that fake activity
|
|
||||||
let mut undo = Undo::new(
|
|
||||||
creator.actor_id.to_owned().into_inner(),
|
|
||||||
delete.into_any_base()?,
|
|
||||||
);
|
|
||||||
undo
|
|
||||||
.set_many_contexts(lemmy_context())
|
|
||||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
|
||||||
.set_to(recipient.actor_id());
|
|
||||||
|
|
||||||
send_activity_single_dest(undo, creator, recipient.inbox_url.into(), context).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_remove(&self, _mod_: &Person, _context: &LemmyContext) -> Result<(), LemmyError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_undo_remove(
|
|
||||||
&self,
|
|
||||||
_mod_: &Person,
|
|
||||||
_context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -101,7 +101,7 @@ impl ActivityHandler for UndoVote {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
|
|
@ -113,7 +113,7 @@ impl ActivityHandler for Vote {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
|
|
@ -31,34 +31,6 @@ use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::BTreeMap, env, fmt::Debug, future::Future, pin::Pin};
|
use std::{collections::BTreeMap, env, fmt::Debug, future::Future, pin::Pin};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
/// Sends a local activity to a single, remote actor.
|
|
||||||
///
|
|
||||||
/// * `activity` the apub activity to be sent
|
|
||||||
/// * `creator` the local actor which created the activity
|
|
||||||
/// * `inbox` the inbox url where the activity should be delivered to
|
|
||||||
pub(crate) async fn send_activity_single_dest<T, Kind>(
|
|
||||||
activity: T,
|
|
||||||
creator: &dyn ActorType,
|
|
||||||
inbox: Url,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError>
|
|
||||||
where
|
|
||||||
T: AsObject<Kind> + Extends<Kind> + Debug + BaseExt<Kind>,
|
|
||||||
Kind: Serialize,
|
|
||||||
<T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
if check_is_apub_id_valid(&inbox, false).is_ok() {
|
|
||||||
debug!(
|
|
||||||
"Sending activity {:?} to {}",
|
|
||||||
&activity.id_unchecked().map(ToString::to_string),
|
|
||||||
&inbox
|
|
||||||
);
|
|
||||||
send_activity_internal(context, activity, creator, vec![inbox], true, true).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// From a local community, send activity to all remote followers.
|
/// From a local community, send activity to all remote followers.
|
||||||
///
|
///
|
||||||
/// * `activity` the apub activity to send
|
/// * `activity` the apub activity to send
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
use activitystreams::unparsed::UnparsedMutExt;
|
|
||||||
use activitystreams_ext::UnparsedExtension;
|
|
||||||
use lemmy_utils::LemmyError;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// Activitystreams extension to allow (de)serializing additional Community field
|
|
||||||
/// `sensitive` (called 'nsfw' in Lemmy).
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct GroupExtension {
|
|
||||||
pub sensitive: Option<bool>,
|
|
||||||
pub moderators: Option<Url>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GroupExtension {
|
|
||||||
pub fn new(sensitive: bool, moderators_url: Url) -> Result<GroupExtension, LemmyError> {
|
|
||||||
Ok(GroupExtension {
|
|
||||||
sensitive: Some(sensitive),
|
|
||||||
moderators: Some(moderators_url),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<U> UnparsedExtension<U> for GroupExtension
|
|
||||||
where
|
|
||||||
U: UnparsedMutExt,
|
|
||||||
{
|
|
||||||
type Error = serde_json::Error;
|
|
||||||
|
|
||||||
fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
|
|
||||||
Ok(GroupExtension {
|
|
||||||
sensitive: unparsed_mut.remove("sensitive")?,
|
|
||||||
moderators: unparsed_mut.remove("moderators")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
|
|
||||||
unparsed_mut.insert("sensitive", self.sensitive)?;
|
|
||||||
unparsed_mut.insert("moderators", self.moderators)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,2 @@
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub(crate) mod group_extension;
|
|
||||||
pub(crate) mod person_extension;
|
|
||||||
pub mod signatures;
|
pub mod signatures;
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
use activitystreams::unparsed::UnparsedMutExt;
|
|
||||||
use activitystreams_ext::UnparsedExtension;
|
|
||||||
use lemmy_utils::LemmyError;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// Activitystreams extension to allow (de)serializing additional Person field
|
|
||||||
/// `also_known_as` (used for Matrix profile link).
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct PersonExtension {
|
|
||||||
pub matrix_user_id: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PersonExtension {
|
|
||||||
pub fn new(matrix_user_id: Option<String>) -> Result<PersonExtension, LemmyError> {
|
|
||||||
Ok(PersonExtension { matrix_user_id })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<U> UnparsedExtension<U> for PersonExtension
|
|
||||||
where
|
|
||||||
U: UnparsedMutExt,
|
|
||||||
{
|
|
||||||
type Error = serde_json::Error;
|
|
||||||
|
|
||||||
fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
|
|
||||||
Ok(PersonExtension {
|
|
||||||
matrix_user_id: unparsed_mut.remove("matrix_user_id")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
|
|
||||||
unparsed_mut.insert("matrix_user_id", self.matrix_user_id)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,3 @@
|
||||||
use activitystreams::unparsed::UnparsedMutExt;
|
|
||||||
use activitystreams_ext::UnparsedExtension;
|
|
||||||
use actix_web::HttpRequest;
|
use actix_web::HttpRequest;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use http::{header::HeaderName, HeaderMap, HeaderValue};
|
use http::{header::HeaderName, HeaderMap, HeaderValue};
|
||||||
|
@ -90,15 +88,6 @@ pub fn verify_signature(request: &HttpRequest, public_key: &str) -> Result<(), L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extension for actor public key, which is needed on person and community for HTTP signatures.
|
|
||||||
///
|
|
||||||
/// Taken from: https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct PublicKeyExtension {
|
|
||||||
pub public_key: PublicKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PublicKey {
|
pub struct PublicKey {
|
||||||
|
@ -106,29 +95,3 @@ pub struct PublicKey {
|
||||||
pub owner: Url,
|
pub owner: Url,
|
||||||
pub public_key_pem: String,
|
pub public_key_pem: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PublicKey {
|
|
||||||
pub fn to_ext(&self) -> PublicKeyExtension {
|
|
||||||
PublicKeyExtension {
|
|
||||||
public_key: self.to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<U> UnparsedExtension<U> for PublicKeyExtension
|
|
||||||
where
|
|
||||||
U: UnparsedMutExt,
|
|
||||||
{
|
|
||||||
type Error = serde_json::Error;
|
|
||||||
|
|
||||||
fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
|
|
||||||
Ok(PublicKeyExtension {
|
|
||||||
public_key: unparsed_mut.remove("publicKey")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
|
|
||||||
unparsed_mut.insert("publicKey", self.public_key)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,13 +6,9 @@ use crate::{
|
||||||
person::get_or_fetch_and_upsert_person,
|
person::get_or_fetch_and_upsert_person,
|
||||||
should_refetch_actor,
|
should_refetch_actor,
|
||||||
},
|
},
|
||||||
objects::FromApub,
|
objects::{community::Group, FromApub},
|
||||||
GroupExt,
|
|
||||||
};
|
|
||||||
use activitystreams::{
|
|
||||||
actor::ApActorExt,
|
|
||||||
collection::{CollectionExt, OrderedCollection},
|
|
||||||
};
|
};
|
||||||
|
use activitystreams::collection::{CollectionExt, OrderedCollection};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use diesel::result::Error::NotFound;
|
use diesel::result::Error::NotFound;
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
|
@ -63,7 +59,7 @@ async fn fetch_remote_community(
|
||||||
old_community: Option<Community>,
|
old_community: Option<Community>,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<Community, LemmyError> {
|
) -> Result<Community, LemmyError> {
|
||||||
let group = fetch_remote_object::<GroupExt>(context.client(), apub_id, request_counter).await;
|
let group = fetch_remote_object::<Group>(context.client(), apub_id, request_counter).await;
|
||||||
|
|
||||||
if let Some(c) = old_community.to_owned() {
|
if let Some(c) = old_community.to_owned() {
|
||||||
if is_deleted(&group) {
|
if is_deleted(&group) {
|
||||||
|
@ -78,22 +74,20 @@ async fn fetch_remote_community(
|
||||||
}
|
}
|
||||||
|
|
||||||
let group = group?;
|
let group = group?;
|
||||||
let community =
|
let community = Community::from_apub(&group, context, apub_id, request_counter).await?;
|
||||||
Community::from_apub(&group, context, apub_id.to_owned(), request_counter, false).await?;
|
|
||||||
|
|
||||||
update_community_mods(&group, &community, context, request_counter).await?;
|
update_community_mods(&group, &community, context, request_counter).await?;
|
||||||
|
|
||||||
// only fetch outbox for new communities, otherwise this can create an infinite loop
|
// only fetch outbox for new communities, otherwise this can create an infinite loop
|
||||||
if old_community.is_none() {
|
if old_community.is_none() {
|
||||||
let outbox = group.inner.outbox()?.context(location_info!())?;
|
fetch_community_outbox(context, &group.outbox, request_counter).await?
|
||||||
fetch_community_outbox(context, outbox, request_counter).await?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(community)
|
Ok(community)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_community_mods(
|
async fn update_community_mods(
|
||||||
group: &GroupExt,
|
group: &Group,
|
||||||
community: &Community,
|
community: &Community,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
|
@ -168,10 +162,10 @@ async fn fetch_community_outbox(
|
||||||
|
|
||||||
pub(crate) async fn fetch_community_mods(
|
pub(crate) async fn fetch_community_mods(
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
group: &GroupExt,
|
group: &Group,
|
||||||
recursion_counter: &mut i32,
|
recursion_counter: &mut i32,
|
||||||
) -> Result<Vec<Url>, LemmyError> {
|
) -> Result<Vec<Url>, LemmyError> {
|
||||||
if let Some(mods_url) = &group.ext_one.moderators {
|
if let Some(mods_url) = &group.moderators {
|
||||||
let mods =
|
let mods =
|
||||||
fetch_remote_object::<OrderedCollection>(context.client(), mods_url, recursion_counter)
|
fetch_remote_object::<OrderedCollection>(context.client(), mods_url, recursion_counter)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -34,14 +34,7 @@ pub async fn get_or_fetch_and_insert_post(
|
||||||
debug!("Fetching and creating remote post: {}", post_ap_id);
|
debug!("Fetching and creating remote post: {}", post_ap_id);
|
||||||
let page =
|
let page =
|
||||||
fetch_remote_object::<Page>(context.client(), post_ap_id, recursion_counter).await?;
|
fetch_remote_object::<Page>(context.client(), post_ap_id, recursion_counter).await?;
|
||||||
let post = Post::from_apub(
|
let post = Post::from_apub(&page, context, post_ap_id, recursion_counter).await?;
|
||||||
&page,
|
|
||||||
context,
|
|
||||||
post_ap_id.to_owned(),
|
|
||||||
recursion_counter,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(post)
|
Ok(post)
|
||||||
}
|
}
|
||||||
|
@ -73,14 +66,7 @@ pub async fn get_or_fetch_and_insert_comment(
|
||||||
);
|
);
|
||||||
let comment =
|
let comment =
|
||||||
fetch_remote_object::<Note>(context.client(), comment_ap_id, recursion_counter).await?;
|
fetch_remote_object::<Note>(context.client(), comment_ap_id, recursion_counter).await?;
|
||||||
let comment = Comment::from_apub(
|
let comment = Comment::from_apub(&comment, context, comment_ap_id, recursion_counter).await?;
|
||||||
&comment,
|
|
||||||
context,
|
|
||||||
comment_ap_id.to_owned(),
|
|
||||||
recursion_counter,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let post_id = comment.post_id;
|
let post_id = comment.post_id;
|
||||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
fetcher::{fetch::fetch_remote_object, is_deleted, should_refetch_actor},
|
fetcher::{fetch::fetch_remote_object, is_deleted, should_refetch_actor},
|
||||||
objects::FromApub,
|
objects::{person::Person as ApubPerson, FromApub},
|
||||||
PersonExt,
|
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use diesel::result::Error::NotFound;
|
use diesel::result::Error::NotFound;
|
||||||
|
@ -33,7 +32,7 @@ pub async fn get_or_fetch_and_upsert_person(
|
||||||
Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => {
|
Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => {
|
||||||
debug!("Fetching and updating from remote person: {}", apub_id);
|
debug!("Fetching and updating from remote person: {}", apub_id);
|
||||||
let person =
|
let person =
|
||||||
fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await;
|
fetch_remote_object::<ApubPerson>(context.client(), apub_id, recursion_counter).await;
|
||||||
|
|
||||||
if is_deleted(&person) {
|
if is_deleted(&person) {
|
||||||
// TODO: use Person::update_deleted() once implemented
|
// TODO: use Person::update_deleted() once implemented
|
||||||
|
@ -46,14 +45,7 @@ pub async fn get_or_fetch_and_upsert_person(
|
||||||
return Ok(u);
|
return Ok(u);
|
||||||
}
|
}
|
||||||
|
|
||||||
let person = Person::from_apub(
|
let person = Person::from_apub(&person?, context, apub_id, recursion_counter).await?;
|
||||||
&person?,
|
|
||||||
context,
|
|
||||||
apub_id.to_owned(),
|
|
||||||
recursion_counter,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let person_id = person.id;
|
let person_id = person.id;
|
||||||
blocking(context.pool(), move |conn| {
|
blocking(context.pool(), move |conn| {
|
||||||
|
@ -67,16 +59,9 @@ pub async fn get_or_fetch_and_upsert_person(
|
||||||
Err(NotFound {}) => {
|
Err(NotFound {}) => {
|
||||||
debug!("Fetching and creating remote person: {}", apub_id);
|
debug!("Fetching and creating remote person: {}", apub_id);
|
||||||
let person =
|
let person =
|
||||||
fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await?;
|
fetch_remote_object::<ApubPerson>(context.client(), apub_id, recursion_counter).await?;
|
||||||
|
|
||||||
let person = Person::from_apub(
|
let person = Person::from_apub(&person, context, apub_id, recursion_counter).await?;
|
||||||
&person,
|
|
||||||
context,
|
|
||||||
apub_id.to_owned(),
|
|
||||||
recursion_counter,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(person)
|
Ok(person)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,10 @@ use crate::{
|
||||||
is_deleted,
|
is_deleted,
|
||||||
},
|
},
|
||||||
find_object_by_id,
|
find_object_by_id,
|
||||||
objects::{comment::Note, post::Page, FromApub},
|
objects::{comment::Note, community::Group, person::Person as ApubPerson, post::Page, FromApub},
|
||||||
GroupExt,
|
|
||||||
Object,
|
Object,
|
||||||
PersonExt,
|
|
||||||
};
|
};
|
||||||
use activitystreams::base::BaseExt;
|
use anyhow::anyhow;
|
||||||
use anyhow::{anyhow, Context};
|
|
||||||
use lemmy_api_common::{blocking, site::SearchResponse};
|
use lemmy_api_common::{blocking, site::SearchResponse};
|
||||||
use lemmy_db_queries::{
|
use lemmy_db_queries::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -42,8 +39,8 @@ use url::Url;
|
||||||
#[derive(serde::Deserialize, Debug)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
enum SearchAcceptedObjects {
|
enum SearchAcceptedObjects {
|
||||||
Person(Box<PersonExt>),
|
Person(Box<ApubPerson>),
|
||||||
Group(Box<GroupExt>),
|
Group(Box<Group>),
|
||||||
Page(Box<Page>),
|
Page(Box<Page>),
|
||||||
Comment(Box<Note>),
|
Comment(Box<Note>),
|
||||||
}
|
}
|
||||||
|
@ -109,7 +106,6 @@ async fn build_response(
|
||||||
recursion_counter: &mut i32,
|
recursion_counter: &mut i32,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> Result<SearchResponse, LemmyError> {
|
) -> Result<SearchResponse, LemmyError> {
|
||||||
let domain = query_url.domain().context("url has no domain")?;
|
|
||||||
let mut response = SearchResponse {
|
let mut response = SearchResponse {
|
||||||
type_: SearchType::All.to_string(),
|
type_: SearchType::All.to_string(),
|
||||||
comments: vec![],
|
comments: vec![],
|
||||||
|
@ -120,9 +116,8 @@ async fn build_response(
|
||||||
|
|
||||||
match fetch_response {
|
match fetch_response {
|
||||||
SearchAcceptedObjects::Person(p) => {
|
SearchAcceptedObjects::Person(p) => {
|
||||||
let person_uri = p.inner.id(domain)?.context("person has no id")?;
|
let person_id = p.id(&query_url)?;
|
||||||
|
let person = get_or_fetch_and_upsert_person(person_id, context, recursion_counter).await?;
|
||||||
let person = get_or_fetch_and_upsert_person(person_uri, context, recursion_counter).await?;
|
|
||||||
|
|
||||||
response.users = vec![
|
response.users = vec![
|
||||||
blocking(context.pool(), move |conn| {
|
blocking(context.pool(), move |conn| {
|
||||||
|
@ -132,8 +127,7 @@ async fn build_response(
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
SearchAcceptedObjects::Group(g) => {
|
SearchAcceptedObjects::Group(g) => {
|
||||||
let community_uri = g.inner.id(domain)?.context("group has no id")?;
|
let community_uri = g.id(&query_url)?;
|
||||||
|
|
||||||
let community =
|
let community =
|
||||||
get_or_fetch_and_upsert_community(community_uri, context, recursion_counter).await?;
|
get_or_fetch_and_upsert_community(community_uri, context, recursion_counter).await?;
|
||||||
|
|
||||||
|
@ -145,13 +139,13 @@ async fn build_response(
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
SearchAcceptedObjects::Page(p) => {
|
SearchAcceptedObjects::Page(p) => {
|
||||||
let p = Post::from_apub(&p, context, query_url, recursion_counter, false).await?;
|
let p = Post::from_apub(&p, context, &query_url, recursion_counter).await?;
|
||||||
|
|
||||||
response.posts =
|
response.posts =
|
||||||
vec![blocking(context.pool(), move |conn| PostView::read(conn, p.id, None)).await??];
|
vec![blocking(context.pool(), move |conn| PostView::read(conn, p.id, None)).await??];
|
||||||
}
|
}
|
||||||
SearchAcceptedObjects::Comment(c) => {
|
SearchAcceptedObjects::Comment(c) => {
|
||||||
let c = Comment::from_apub(&c, context, query_url, recursion_counter, false).await?;
|
let c = Comment::from_apub(&c, context, &query_url, recursion_counter).await?;
|
||||||
|
|
||||||
response.comments = vec![
|
response.comments = vec![
|
||||||
blocking(context.pool(), move |conn| {
|
blocking(context.pool(), move |conn| {
|
||||||
|
|
|
@ -9,21 +9,8 @@ pub mod http;
|
||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
pub mod objects;
|
pub mod objects;
|
||||||
|
|
||||||
use crate::{
|
use crate::extensions::signatures::PublicKey;
|
||||||
extensions::{
|
use activitystreams::base::AnyBase;
|
||||||
group_extension::GroupExtension,
|
|
||||||
person_extension::PersonExtension,
|
|
||||||
signatures::{PublicKey, PublicKeyExtension},
|
|
||||||
},
|
|
||||||
fetcher::community::get_or_fetch_and_upsert_community,
|
|
||||||
};
|
|
||||||
use activitystreams::{
|
|
||||||
activity::Follow,
|
|
||||||
actor,
|
|
||||||
base::AnyBase,
|
|
||||||
object::{ApObject, AsObject, ObjectExt},
|
|
||||||
};
|
|
||||||
use activitystreams_ext::Ext2;
|
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use diesel::NotFound;
|
use diesel::NotFound;
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
|
@ -47,20 +34,6 @@ use serde::Serialize;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use url::{ParseError, Url};
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
/// Activitystreams type for community
|
|
||||||
pub type GroupExt =
|
|
||||||
Ext2<actor::ApActor<ApObject<actor::Group>>, GroupExtension, PublicKeyExtension>;
|
|
||||||
/// Activitystreams type for person
|
|
||||||
type PersonExt =
|
|
||||||
Ext2<actor::ApActor<ApObject<actor::Actor<UserTypes>>>, PersonExtension, PublicKeyExtension>;
|
|
||||||
pub type SiteExt = actor::ApActor<ApObject<actor::Service>>;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize, PartialEq)]
|
|
||||||
pub enum UserTypes {
|
|
||||||
Person,
|
|
||||||
Service,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
|
pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
|
||||||
|
|
||||||
/// Checks if the ID is allowed for sending or receiving.
|
/// Checks if the ID is allowed for sending or receiving.
|
||||||
|
@ -71,7 +44,10 @@ pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
|
||||||
/// - URL being in the allowlist (if it is active)
|
/// - URL being in the allowlist (if it is active)
|
||||||
/// - URL not being in the blocklist (if it is active)
|
/// - URL not being in the blocklist (if it is active)
|
||||||
///
|
///
|
||||||
pub fn check_is_apub_id_valid(apub_id: &Url, use_strict_allowlist: bool) -> Result<(), LemmyError> {
|
pub(crate) fn check_is_apub_id_valid(
|
||||||
|
apub_id: &Url,
|
||||||
|
use_strict_allowlist: bool,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
let settings = Settings::get();
|
let settings = Settings::get();
|
||||||
let domain = apub_id.domain().context(location_info!())?.to_string();
|
let domain = apub_id.domain().context(location_info!())?.to_string();
|
||||||
let local_instance = settings.get_hostname_without_port()?;
|
let local_instance = settings.get_hostname_without_port()?;
|
||||||
|
@ -169,15 +145,12 @@ pub trait ActorType {
|
||||||
Ok(Url::parse(&format!("{}/outbox", &self.actor_id()))?)
|
Ok(Url::parse(&format!("{}/outbox", &self.actor_id()))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_public_key_ext(&self) -> Result<PublicKeyExtension, LemmyError> {
|
fn get_public_key(&self) -> Result<PublicKey, LemmyError> {
|
||||||
Ok(
|
Ok(PublicKey {
|
||||||
PublicKey {
|
id: format!("{}#main-key", self.actor_id()),
|
||||||
id: format!("{}#main-key", self.actor_id()),
|
owner: self.actor_id(),
|
||||||
owner: self.actor_id(),
|
public_key_pem: self.public_key().context(location_info!())?,
|
||||||
public_key_pem: self.public_key().context(location_info!())?,
|
})
|
||||||
}
|
|
||||||
.to_ext(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,11 +158,6 @@ pub trait ActorType {
|
||||||
pub trait CommunityType {
|
pub trait CommunityType {
|
||||||
fn followers_url(&self) -> Url;
|
fn followers_url(&self) -> Url;
|
||||||
async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError>;
|
async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError>;
|
||||||
async fn send_accept_follow(
|
|
||||||
&self,
|
|
||||||
follow: Follow,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError>;
|
|
||||||
|
|
||||||
async fn send_update(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError>;
|
async fn send_update(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||||
async fn send_delete(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError>;
|
async fn send_delete(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||||
|
@ -232,20 +200,6 @@ pub trait CommunityType {
|
||||||
) -> Result<(), LemmyError>;
|
) -> Result<(), LemmyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
pub trait UserType {
|
|
||||||
async fn send_follow(
|
|
||||||
&self,
|
|
||||||
follow_actor_id: &Url,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError>;
|
|
||||||
async fn send_unfollow(
|
|
||||||
&self,
|
|
||||||
follow_actor_id: &Url,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<(), LemmyError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum EndpointType {
|
pub enum EndpointType {
|
||||||
Community,
|
Community,
|
||||||
Person,
|
Person,
|
||||||
|
@ -255,7 +209,7 @@ pub enum EndpointType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates an apub endpoint for a given domain, IE xyz.tld
|
/// Generates an apub endpoint for a given domain, IE xyz.tld
|
||||||
pub fn generate_apub_endpoint_for_domain(
|
pub(crate) fn generate_apub_endpoint_for_domain(
|
||||||
endpoint_type: EndpointType,
|
endpoint_type: EndpointType,
|
||||||
name: &str,
|
name: &str,
|
||||||
domain: &str,
|
domain: &str,
|
||||||
|
@ -332,7 +286,7 @@ pub fn build_actor_id_from_shortname(
|
||||||
|
|
||||||
/// Store a sent or received activity in the database, for logging purposes. These records are not
|
/// Store a sent or received activity in the database, for logging purposes. These records are not
|
||||||
/// persistent.
|
/// persistent.
|
||||||
pub async fn insert_activity<T>(
|
pub(crate) async fn insert_activity<T>(
|
||||||
ap_id: &Url,
|
ap_id: &Url,
|
||||||
activity: T,
|
activity: T,
|
||||||
local: bool,
|
local: bool,
|
||||||
|
@ -368,7 +322,7 @@ impl PostOrComment {
|
||||||
/// Tries to find a post or comment in the local database, without any network requests.
|
/// Tries to find a post or comment in the local database, without any network requests.
|
||||||
/// This is used to handle deletions and removals, because in case we dont have the object, we can
|
/// This is used to handle deletions and removals, because in case we dont have the object, we can
|
||||||
/// simply ignore the activity.
|
/// simply ignore the activity.
|
||||||
pub async fn find_post_or_comment_by_id(
|
pub(crate) async fn find_post_or_comment_by_id(
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
apub_id: Url,
|
apub_id: Url,
|
||||||
) -> Result<PostOrComment, LemmyError> {
|
) -> Result<PostOrComment, LemmyError> {
|
||||||
|
@ -402,7 +356,10 @@ pub enum Object {
|
||||||
PrivateMessage(Box<PrivateMessage>),
|
PrivateMessage(Box<PrivateMessage>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_object_by_id(context: &LemmyContext, apub_id: Url) -> Result<Object, LemmyError> {
|
pub(crate) async fn find_object_by_id(
|
||||||
|
context: &LemmyContext,
|
||||||
|
apub_id: Url,
|
||||||
|
) -> Result<Object, LemmyError> {
|
||||||
let ap_id = apub_id.clone();
|
let ap_id = apub_id.clone();
|
||||||
if let Ok(pc) = find_post_or_comment_by_id(context, ap_id.to_owned()).await {
|
if let Ok(pc) = find_post_or_comment_by_id(context, ap_id.to_owned()).await {
|
||||||
return Ok(match pc {
|
return Ok(match pc {
|
||||||
|
@ -440,7 +397,7 @@ pub async fn find_object_by_id(context: &LemmyContext, apub_id: Url) -> Result<O
|
||||||
Err(NotFound.into())
|
Err(NotFound.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn check_community_or_site_ban(
|
pub(crate) async fn check_community_or_site_ban(
|
||||||
person: &Person,
|
person: &Person,
|
||||||
community_id: CommunityId,
|
community_id: CommunityId,
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
|
@ -457,48 +414,3 @@ pub async fn check_community_or_site_ban(
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_activity_to_and_cc<T, Kind>(activity: &T) -> Vec<Url>
|
|
||||||
where
|
|
||||||
T: AsObject<Kind>,
|
|
||||||
{
|
|
||||||
let mut to_and_cc = vec![];
|
|
||||||
if let Some(to) = activity.to() {
|
|
||||||
let to = to.to_owned().unwrap_to_vec();
|
|
||||||
let mut to = to
|
|
||||||
.iter()
|
|
||||||
.map(|t| t.as_xsd_any_uri())
|
|
||||||
.flatten()
|
|
||||||
.map(|t| t.to_owned())
|
|
||||||
.collect();
|
|
||||||
to_and_cc.append(&mut to);
|
|
||||||
}
|
|
||||||
if let Some(cc) = activity.cc() {
|
|
||||||
let cc = cc.to_owned().unwrap_to_vec();
|
|
||||||
let mut cc = cc
|
|
||||||
.iter()
|
|
||||||
.map(|c| c.as_xsd_any_uri())
|
|
||||||
.flatten()
|
|
||||||
.map(|c| c.to_owned())
|
|
||||||
.collect();
|
|
||||||
to_and_cc.append(&mut cc);
|
|
||||||
}
|
|
||||||
to_and_cc
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_community_from_to_or_cc<T, Kind>(
|
|
||||||
activity: &T,
|
|
||||||
context: &LemmyContext,
|
|
||||||
request_counter: &mut i32,
|
|
||||||
) -> Result<Community, LemmyError>
|
|
||||||
where
|
|
||||||
T: AsObject<Kind>,
|
|
||||||
{
|
|
||||||
for cid in get_activity_to_and_cc(activity) {
|
|
||||||
let community = get_or_fetch_and_upsert_community(&cid, context, request_counter).await;
|
|
||||||
if community.is_ok() {
|
|
||||||
return community;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(NotFound.into())
|
|
||||||
}
|
|
||||||
|
|
|
@ -41,16 +41,18 @@ use lemmy_utils::{
|
||||||
};
|
};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Note {
|
pub struct Note {
|
||||||
#[serde(rename = "@context")]
|
#[serde(rename = "@context")]
|
||||||
context: OneOrMany<AnyBase>,
|
context: OneOrMany<AnyBase>,
|
||||||
r#type: NoteType,
|
r#type: NoteType,
|
||||||
pub(crate) id: Url,
|
id: Url,
|
||||||
pub(crate) attributed_to: Url,
|
pub(crate) attributed_to: Url,
|
||||||
/// Indicates that the object is publicly readable. Unlike [`Post.to`], this one doesn't contain
|
/// Indicates that the object is publicly readable. Unlike [`Post.to`], this one doesn't contain
|
||||||
/// the community ID, as it would be incompatible with Pleroma (and we can get the community from
|
/// the community ID, as it would be incompatible with Pleroma (and we can get the community from
|
||||||
|
@ -67,6 +69,14 @@ pub struct Note {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Note {
|
impl Note {
|
||||||
|
pub(crate) fn id_unchecked(&self) -> &Url {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
|
||||||
|
verify_domains_match(&self.id, expected_domain)?;
|
||||||
|
Ok(&self.id)
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_parents(
|
async fn get_parents(
|
||||||
&self,
|
&self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
|
@ -212,10 +222,10 @@ impl FromApub for Comment {
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
note: &Note,
|
note: &Note,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
_expected_domain: Url,
|
expected_domain: &Url,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
_mod_action_allowed: bool,
|
|
||||||
) -> Result<Comment, LemmyError> {
|
) -> Result<Comment, LemmyError> {
|
||||||
|
let ap_id = Some(note.id(expected_domain)?.clone().into());
|
||||||
let creator =
|
let creator =
|
||||||
get_or_fetch_and_upsert_person(¬e.attributed_to, context, request_counter).await?;
|
get_or_fetch_and_upsert_person(¬e.attributed_to, context, request_counter).await?;
|
||||||
let (post, parent_comment_id) = note.get_parents(context, request_counter).await?;
|
let (post, parent_comment_id) = note.get_parents(context, request_counter).await?;
|
||||||
|
@ -233,7 +243,7 @@ impl FromApub for Comment {
|
||||||
published: Some(note.published.naive_local()),
|
published: Some(note.published.naive_local()),
|
||||||
updated: note.updated.map(|u| u.to_owned().naive_local()),
|
updated: note.updated.map(|u| u.to_owned().naive_local()),
|
||||||
deleted: None,
|
deleted: None,
|
||||||
ap_id: Some(note.id.clone().into()),
|
ap_id,
|
||||||
local: Some(false),
|
local: Some(false),
|
||||||
};
|
};
|
||||||
Ok(blocking(context.pool(), move |conn| Comment::upsert(conn, &form)).await??)
|
Ok(blocking(context.pool(), move |conn| Comment::upsert(conn, &form)).await??)
|
||||||
|
|
|
@ -1,101 +1,157 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
extensions::{context::lemmy_context, group_extension::GroupExtension},
|
extensions::{context::lemmy_context, signatures::PublicKey},
|
||||||
fetcher::community::fetch_community_mods,
|
fetcher::community::fetch_community_mods,
|
||||||
generate_moderators_url,
|
generate_moderators_url,
|
||||||
objects::{
|
objects::{create_tombstone, FromApub, ImageObject, Source, ToApub},
|
||||||
check_object_domain,
|
|
||||||
create_tombstone,
|
|
||||||
get_object_from_apub,
|
|
||||||
get_source_markdown_value,
|
|
||||||
set_content_and_source,
|
|
||||||
FromApub,
|
|
||||||
FromApubToForm,
|
|
||||||
ToApub,
|
|
||||||
},
|
|
||||||
ActorType,
|
ActorType,
|
||||||
GroupExt,
|
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
actor::{kind::GroupType, ApActor, Endpoints, Group},
|
actor::{kind::GroupType, Endpoints},
|
||||||
base::BaseExt,
|
base::AnyBase,
|
||||||
object::{ApObject, Image, Tombstone},
|
object::{kind::ImageType, Tombstone},
|
||||||
prelude::*,
|
primitives::OneOrMany,
|
||||||
|
unparsed::Unparsed,
|
||||||
};
|
};
|
||||||
use activitystreams_ext::Ext2;
|
use chrono::{DateTime, FixedOffset};
|
||||||
use anyhow::Context;
|
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::DbPool;
|
use lemmy_apub_lib::{
|
||||||
|
values::{MediaTypeHtml, MediaTypeMarkdown},
|
||||||
|
verify_domains_match,
|
||||||
|
};
|
||||||
|
use lemmy_db_queries::{ApubObject, DbPool};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
naive_now,
|
naive_now,
|
||||||
source::community::{Community, CommunityForm},
|
source::community::{Community, CommunityForm},
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
|
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
location_info,
|
utils::{check_slurs, check_slurs_opt, convert_datetime, markdown_to_html},
|
||||||
utils::{check_slurs, check_slurs_opt, convert_datetime},
|
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Group {
|
||||||
|
#[serde(rename = "@context")]
|
||||||
|
context: OneOrMany<AnyBase>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
kind: GroupType,
|
||||||
|
id: Url,
|
||||||
|
/// username, set at account creation and can never be changed
|
||||||
|
preferred_username: String,
|
||||||
|
/// title (can be changed at any time)
|
||||||
|
name: String,
|
||||||
|
content: Option<String>,
|
||||||
|
media_type: Option<MediaTypeHtml>,
|
||||||
|
source: Option<Source>,
|
||||||
|
icon: Option<ImageObject>,
|
||||||
|
/// banner
|
||||||
|
image: Option<ImageObject>,
|
||||||
|
// lemmy extension
|
||||||
|
sensitive: Option<bool>,
|
||||||
|
// lemmy extension
|
||||||
|
pub(crate) moderators: Option<Url>,
|
||||||
|
inbox: Url,
|
||||||
|
pub(crate) outbox: Url,
|
||||||
|
followers: Url,
|
||||||
|
endpoints: Endpoints<Url>,
|
||||||
|
public_key: PublicKey,
|
||||||
|
published: DateTime<FixedOffset>,
|
||||||
|
updated: Option<DateTime<FixedOffset>>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
unparsed: Unparsed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Group {
|
||||||
|
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
|
||||||
|
verify_domains_match(&self.id, expected_domain)?;
|
||||||
|
Ok(&self.id)
|
||||||
|
}
|
||||||
|
pub(crate) async fn from_apub_to_form(
|
||||||
|
group: &Group,
|
||||||
|
expected_domain: &Url,
|
||||||
|
) -> Result<CommunityForm, LemmyError> {
|
||||||
|
let actor_id = Some(group.id(expected_domain)?.clone().into());
|
||||||
|
let name = group.preferred_username.clone();
|
||||||
|
let title = group.name.clone();
|
||||||
|
let description = group.source.clone().map(|s| s.content);
|
||||||
|
let shared_inbox = group.endpoints.shared_inbox.clone().map(|s| s.into());
|
||||||
|
|
||||||
|
check_slurs(&name)?;
|
||||||
|
check_slurs(&title)?;
|
||||||
|
check_slurs_opt(&description)?;
|
||||||
|
|
||||||
|
Ok(CommunityForm {
|
||||||
|
name,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
removed: None,
|
||||||
|
published: Some(group.published.naive_local()),
|
||||||
|
updated: group.updated.map(|u| u.naive_local()),
|
||||||
|
deleted: None,
|
||||||
|
nsfw: Some(group.sensitive.unwrap_or(false)),
|
||||||
|
actor_id,
|
||||||
|
local: Some(false),
|
||||||
|
private_key: None,
|
||||||
|
public_key: Some(group.public_key.public_key_pem.clone()),
|
||||||
|
last_refreshed_at: Some(naive_now()),
|
||||||
|
icon: Some(group.icon.clone().map(|i| i.url.into())),
|
||||||
|
banner: Some(group.image.clone().map(|i| i.url.into())),
|
||||||
|
followers_url: Some(group.followers.clone().into()),
|
||||||
|
inbox_url: Some(group.inbox.clone().into()),
|
||||||
|
shared_inbox_url: Some(shared_inbox),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ToApub for Community {
|
impl ToApub for Community {
|
||||||
type ApubType = GroupExt;
|
type ApubType = Group;
|
||||||
|
|
||||||
async fn to_apub(&self, pool: &DbPool) -> Result<GroupExt, LemmyError> {
|
async fn to_apub(&self, _pool: &DbPool) -> Result<Group, LemmyError> {
|
||||||
let id = self.id;
|
let source = self.description.clone().map(|bio| Source {
|
||||||
let moderators = blocking(pool, move |conn| {
|
content: bio,
|
||||||
CommunityModeratorView::for_community(conn, id)
|
media_type: MediaTypeMarkdown::Markdown,
|
||||||
})
|
});
|
||||||
.await??;
|
let icon = self.icon.clone().map(|url| ImageObject {
|
||||||
let moderators: Vec<Url> = moderators
|
kind: ImageType::Image,
|
||||||
.into_iter()
|
url: url.into(),
|
||||||
.map(|m| m.moderator.actor_id.into_inner())
|
});
|
||||||
.collect();
|
let image = self.banner.clone().map(|url| ImageObject {
|
||||||
|
kind: ImageType::Image,
|
||||||
|
url: url.into(),
|
||||||
|
});
|
||||||
|
|
||||||
let mut group = ApObject::new(Group::new());
|
let group = Group {
|
||||||
group
|
context: lemmy_context(),
|
||||||
.set_many_contexts(lemmy_context())
|
kind: GroupType::Group,
|
||||||
.set_id(self.actor_id.to_owned().into())
|
id: self.actor_id(),
|
||||||
.set_name(self.title.to_owned())
|
preferred_username: self.name.clone(),
|
||||||
.set_published(convert_datetime(self.published))
|
name: self.title.clone(),
|
||||||
// NOTE: included attritubed_to field for compatibility with lemmy v0.9.9
|
content: self.description.as_ref().map(|b| markdown_to_html(b)),
|
||||||
.set_many_attributed_tos(moderators);
|
media_type: self.description.as_ref().map(|_| MediaTypeHtml::Html),
|
||||||
|
source,
|
||||||
if let Some(u) = self.updated.to_owned() {
|
icon,
|
||||||
group.set_updated(convert_datetime(u));
|
image,
|
||||||
}
|
sensitive: Some(self.nsfw),
|
||||||
if let Some(d) = self.description.to_owned() {
|
moderators: Some(generate_moderators_url(&self.actor_id)?.into()),
|
||||||
set_content_and_source(&mut group, &d)?;
|
inbox: self.inbox_url.clone().into(),
|
||||||
}
|
outbox: self.get_outbox_url()?,
|
||||||
|
followers: self.followers_url.clone().into(),
|
||||||
if let Some(icon_url) = &self.icon {
|
endpoints: Endpoints {
|
||||||
let mut image = Image::new();
|
shared_inbox: self.shared_inbox_url.clone().map(|s| s.into()),
|
||||||
image.set_url::<Url>(icon_url.to_owned().into());
|
|
||||||
group.set_icon(image.into_any_base()?);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(banner_url) = &self.banner {
|
|
||||||
let mut image = Image::new();
|
|
||||||
image.set_url::<Url>(banner_url.to_owned().into());
|
|
||||||
group.set_image(image.into_any_base()?);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ap_actor = ApActor::new(self.inbox_url.clone().into(), group);
|
|
||||||
ap_actor
|
|
||||||
.set_preferred_username(self.name.to_owned())
|
|
||||||
.set_outbox(self.get_outbox_url()?)
|
|
||||||
.set_followers(self.followers_url.clone().into())
|
|
||||||
.set_endpoints(Endpoints {
|
|
||||||
shared_inbox: Some(self.get_shared_inbox_or_inbox_url()),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
|
public_key: self.get_public_key()?,
|
||||||
Ok(Ext2::new(
|
published: convert_datetime(self.published),
|
||||||
ap_actor,
|
updated: self.updated.map(convert_datetime),
|
||||||
GroupExtension::new(self.nsfw, generate_moderators_url(&self.actor_id)?.into())?,
|
unparsed: Default::default(),
|
||||||
self.get_public_key_ext()?,
|
};
|
||||||
))
|
Ok(group)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
||||||
|
@ -110,116 +166,19 @@ impl ToApub for Community {
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl FromApub for Community {
|
impl FromApub for Community {
|
||||||
type ApubType = GroupExt;
|
type ApubType = Group;
|
||||||
|
|
||||||
/// Converts a `Group` to `Community`, inserts it into the database and updates moderators.
|
/// Converts a `Group` to `Community`, inserts it into the database and updates moderators.
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
group: &GroupExt,
|
group: &Group,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
expected_domain: Url,
|
expected_domain: &Url,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
mod_action_allowed: bool,
|
|
||||||
) -> Result<Community, LemmyError> {
|
) -> Result<Community, LemmyError> {
|
||||||
get_object_from_apub(
|
|
||||||
group,
|
|
||||||
context,
|
|
||||||
expected_domain,
|
|
||||||
request_counter,
|
|
||||||
mod_action_allowed,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl FromApubToForm<GroupExt> for CommunityForm {
|
|
||||||
async fn from_apub(
|
|
||||||
group: &GroupExt,
|
|
||||||
context: &LemmyContext,
|
|
||||||
expected_domain: Url,
|
|
||||||
request_counter: &mut i32,
|
|
||||||
_mod_action_allowed: bool,
|
|
||||||
) -> Result<Self, LemmyError> {
|
|
||||||
fetch_community_mods(context, group, request_counter).await?;
|
fetch_community_mods(context, group, request_counter).await?;
|
||||||
|
let form = Group::from_apub_to_form(group, expected_domain).await?;
|
||||||
|
|
||||||
let name = group
|
let community = blocking(context.pool(), move |conn| Community::upsert(conn, &form)).await??;
|
||||||
.inner
|
Ok(community)
|
||||||
.preferred_username()
|
|
||||||
.context(location_info!())?
|
|
||||||
.to_string();
|
|
||||||
let title = group
|
|
||||||
.inner
|
|
||||||
.name()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_one()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_xsd_string()
|
|
||||||
.context(location_info!())?
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let description = get_source_markdown_value(group)?;
|
|
||||||
|
|
||||||
check_slurs(&name)?;
|
|
||||||
check_slurs(&title)?;
|
|
||||||
check_slurs_opt(&description)?;
|
|
||||||
|
|
||||||
let icon = match group.icon() {
|
|
||||||
Some(any_image) => Some(
|
|
||||||
Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
|
|
||||||
.context(location_info!())?
|
|
||||||
.context(location_info!())?
|
|
||||||
.url()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_single_xsd_any_uri()
|
|
||||||
.map(|u| u.to_owned().into()),
|
|
||||||
),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
let banner = match group.image() {
|
|
||||||
Some(any_image) => Some(
|
|
||||||
Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
|
|
||||||
.context(location_info!())?
|
|
||||||
.context(location_info!())?
|
|
||||||
.url()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_single_xsd_any_uri()
|
|
||||||
.map(|u| u.to_owned().into()),
|
|
||||||
),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
let shared_inbox = group
|
|
||||||
.inner
|
|
||||||
.endpoints()?
|
|
||||||
.map(|e| e.shared_inbox)
|
|
||||||
.flatten()
|
|
||||||
.map(|s| s.to_owned().into());
|
|
||||||
|
|
||||||
Ok(CommunityForm {
|
|
||||||
name,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
removed: None,
|
|
||||||
published: group.inner.published().map(|u| u.to_owned().naive_local()),
|
|
||||||
updated: group.inner.updated().map(|u| u.to_owned().naive_local()),
|
|
||||||
deleted: None,
|
|
||||||
nsfw: Some(group.ext_one.sensitive.unwrap_or(false)),
|
|
||||||
actor_id: Some(check_object_domain(group, expected_domain, true)?),
|
|
||||||
local: Some(false),
|
|
||||||
private_key: None,
|
|
||||||
public_key: Some(group.ext_two.to_owned().public_key.public_key_pem),
|
|
||||||
last_refreshed_at: Some(naive_now()),
|
|
||||||
icon,
|
|
||||||
banner,
|
|
||||||
followers_url: Some(
|
|
||||||
group
|
|
||||||
.inner
|
|
||||||
.followers()?
|
|
||||||
.context(location_info!())?
|
|
||||||
.to_owned()
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
inbox_url: Some(group.inner.inbox()?.to_owned().into()),
|
|
||||||
shared_inbox_url: Some(shared_inbox),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,13 @@
|
||||||
use crate::{check_is_apub_id_valid, fetcher::person::get_or_fetch_and_upsert_person};
|
use crate::fetcher::person::get_or_fetch_and_upsert_person;
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
base::{AsBase, BaseExt, ExtendsExt},
|
base::BaseExt,
|
||||||
markers::Base,
|
object::{kind::ImageType, Tombstone, TombstoneExt},
|
||||||
mime::{FromStrError, Mime},
|
|
||||||
object::{ApObjectExt, Object, ObjectExt, Tombstone, TombstoneExt},
|
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::anyhow;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use lemmy_api_common::blocking;
|
|
||||||
use lemmy_apub_lib::values::MediaTypeMarkdown;
|
use lemmy_apub_lib::values::MediaTypeMarkdown;
|
||||||
use lemmy_db_queries::{ApubObject, Crud, DbPool};
|
use lemmy_db_queries::DbPool;
|
||||||
use lemmy_db_schema::DbUrl;
|
use lemmy_utils::{utils::convert_datetime, LemmyError};
|
||||||
use lemmy_utils::{
|
|
||||||
location_info,
|
|
||||||
settings::structs::Settings,
|
|
||||||
utils::{convert_datetime, markdown_to_html},
|
|
||||||
LemmyError,
|
|
||||||
};
|
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -46,22 +37,8 @@ pub trait FromApub {
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
apub: &Self::ApubType,
|
apub: &Self::ApubType,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
expected_domain: Url,
|
expected_domain: &Url,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
mod_action_allowed: bool,
|
|
||||||
) -> Result<Self, LemmyError>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
pub trait FromApubToForm<ApubType> {
|
|
||||||
async fn from_apub(
|
|
||||||
apub: &ApubType,
|
|
||||||
context: &LemmyContext,
|
|
||||||
expected_domain: Url,
|
|
||||||
request_counter: &mut i32,
|
|
||||||
mod_action_allowed: bool,
|
|
||||||
) -> Result<Self, LemmyError>
|
) -> Result<Self, LemmyError>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
|
@ -74,6 +51,14 @@ pub struct Source {
|
||||||
media_type: MediaTypeMarkdown,
|
media_type: MediaTypeMarkdown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ImageObject {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
kind: ImageType,
|
||||||
|
url: Url,
|
||||||
|
}
|
||||||
|
|
||||||
/// Updated is actually the deletion time
|
/// Updated is actually the deletion time
|
||||||
fn create_tombstone<T>(
|
fn create_tombstone<T>(
|
||||||
deleted: bool,
|
deleted: bool,
|
||||||
|
@ -98,120 +83,3 @@ where
|
||||||
Err(anyhow!("Cant convert object to tombstone if it wasnt deleted").into())
|
Err(anyhow!("Cant convert object to tombstone if it wasnt deleted").into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate::objects) fn check_object_domain<T, Kind>(
|
|
||||||
apub: &T,
|
|
||||||
expected_domain: Url,
|
|
||||||
use_strict_allowlist: bool,
|
|
||||||
) -> Result<DbUrl, LemmyError>
|
|
||||||
where
|
|
||||||
T: Base + AsBase<Kind>,
|
|
||||||
{
|
|
||||||
let domain = expected_domain.domain().context(location_info!())?;
|
|
||||||
let object_id = apub.id(domain)?.context(location_info!())?;
|
|
||||||
check_is_apub_id_valid(object_id, use_strict_allowlist)?;
|
|
||||||
Ok(object_id.to_owned().into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::objects) fn set_content_and_source<T, Kind1, Kind2>(
|
|
||||||
object: &mut T,
|
|
||||||
markdown_text: &str,
|
|
||||||
) -> Result<(), LemmyError>
|
|
||||||
where
|
|
||||||
T: ApObjectExt<Kind1> + ObjectExt<Kind2> + AsBase<Kind2>,
|
|
||||||
{
|
|
||||||
let mut source = Object::<()>::new_none_type();
|
|
||||||
source
|
|
||||||
.set_content(markdown_text)
|
|
||||||
.set_media_type(mime_markdown()?);
|
|
||||||
object.set_source(source.into_any_base()?);
|
|
||||||
|
|
||||||
object.set_content(markdown_to_html(markdown_text));
|
|
||||||
object.set_media_type(mime_html()?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::objects) fn get_source_markdown_value<T, Kind1, Kind2>(
|
|
||||||
object: &T,
|
|
||||||
) -> Result<Option<String>, LemmyError>
|
|
||||||
where
|
|
||||||
T: ApObjectExt<Kind1> + ObjectExt<Kind2> + AsBase<Kind2>,
|
|
||||||
{
|
|
||||||
let content = object
|
|
||||||
.content()
|
|
||||||
.map(|s| s.as_single_xsd_string().map(|s2| s2.to_string()))
|
|
||||||
.flatten();
|
|
||||||
if content.is_some() {
|
|
||||||
let source = object.source().context(location_info!())?;
|
|
||||||
let source = Object::<()>::from_any_base(source.to_owned())?.context(location_info!())?;
|
|
||||||
check_is_markdown(source.media_type())?;
|
|
||||||
let source_content = source
|
|
||||||
.content()
|
|
||||||
.map(|s| s.as_single_xsd_string().map(|s2| s2.to_string()))
|
|
||||||
.flatten()
|
|
||||||
.context(location_info!())?;
|
|
||||||
return Ok(Some(source_content));
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mime_markdown() -> Result<Mime, FromStrError> {
|
|
||||||
"text/markdown".parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mime_html() -> Result<Mime, FromStrError> {
|
|
||||||
"text/html".parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::objects) fn check_is_markdown(mime: Option<&Mime>) -> Result<(), LemmyError> {
|
|
||||||
let mime = mime.context(location_info!())?;
|
|
||||||
if !mime.eq(&mime_markdown()?) {
|
|
||||||
Err(LemmyError::from(anyhow!(
|
|
||||||
"Lemmy only supports markdown content"
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts an ActivityPub object (eg `Note`) to a database object (eg `Comment`). If an object
|
|
||||||
/// with the same ActivityPub ID already exists in the database, it is returned directly. Otherwise
|
|
||||||
/// the apub object is parsed, inserted and returned.
|
|
||||||
pub async fn get_object_from_apub<From, Kind, To, ToForm, IdType>(
|
|
||||||
from: &From,
|
|
||||||
context: &LemmyContext,
|
|
||||||
expected_domain: Url,
|
|
||||||
request_counter: &mut i32,
|
|
||||||
is_mod_action: bool,
|
|
||||||
) -> Result<To, LemmyError>
|
|
||||||
where
|
|
||||||
From: BaseExt<Kind>,
|
|
||||||
To: ApubObject<ToForm> + Crud<ToForm, IdType> + Send + 'static,
|
|
||||||
ToForm: FromApubToForm<From> + Send + 'static,
|
|
||||||
{
|
|
||||||
let object_id = from.id_unchecked().context(location_info!())?.to_owned();
|
|
||||||
let domain = object_id.domain().context(location_info!())?;
|
|
||||||
|
|
||||||
// if its a local object, return it directly from the database
|
|
||||||
if Settings::get().hostname == domain {
|
|
||||||
let object = blocking(context.pool(), move |conn| {
|
|
||||||
To::read_from_apub_id(conn, &object_id.into())
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
Ok(object)
|
|
||||||
}
|
|
||||||
// otherwise parse and insert, assuring that it comes from the right domain
|
|
||||||
else {
|
|
||||||
let to_form = ToForm::from_apub(
|
|
||||||
from,
|
|
||||||
context,
|
|
||||||
expected_domain,
|
|
||||||
request_counter,
|
|
||||||
is_mod_action,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let to = blocking(context.pool(), move |conn| To::upsert(conn, &to_form)).await??;
|
|
||||||
Ok(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,95 +1,129 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
extensions::{context::lemmy_context, person_extension::PersonExtension},
|
check_is_apub_id_valid,
|
||||||
objects::{
|
extensions::{context::lemmy_context, signatures::PublicKey},
|
||||||
check_object_domain,
|
objects::{FromApub, ImageObject, Source, ToApub},
|
||||||
get_source_markdown_value,
|
|
||||||
set_content_and_source,
|
|
||||||
FromApub,
|
|
||||||
FromApubToForm,
|
|
||||||
ToApub,
|
|
||||||
},
|
|
||||||
ActorType,
|
ActorType,
|
||||||
PersonExt,
|
|
||||||
UserTypes,
|
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
actor::{Actor, ApActor, ApActorExt, Endpoints},
|
actor::Endpoints,
|
||||||
base::{BaseExt, ExtendsExt},
|
base::AnyBase,
|
||||||
object::{ApObject, Image, Object, ObjectExt, Tombstone},
|
chrono::{DateTime, FixedOffset},
|
||||||
|
object::{kind::ImageType, Tombstone},
|
||||||
|
primitives::OneOrMany,
|
||||||
|
unparsed::Unparsed,
|
||||||
};
|
};
|
||||||
use activitystreams_ext::Ext2;
|
|
||||||
use anyhow::Context;
|
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
|
use lemmy_apub_lib::{
|
||||||
|
values::{MediaTypeHtml, MediaTypeMarkdown},
|
||||||
|
verify_domains_match,
|
||||||
|
};
|
||||||
use lemmy_db_queries::{ApubObject, DbPool};
|
use lemmy_db_queries::{ApubObject, DbPool};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
naive_now,
|
naive_now,
|
||||||
source::person::{Person as DbPerson, PersonForm},
|
source::person::{Person as DbPerson, PersonForm},
|
||||||
};
|
};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
location_info,
|
utils::{check_slurs, check_slurs_opt, convert_datetime, markdown_to_html},
|
||||||
settings::structs::Settings,
|
|
||||||
utils::{check_slurs, check_slurs_opt, convert_datetime},
|
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
pub enum UserTypes {
|
||||||
|
Person,
|
||||||
|
Service,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Person {
|
||||||
|
#[serde(rename = "@context")]
|
||||||
|
context: OneOrMany<AnyBase>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
kind: UserTypes,
|
||||||
|
id: Url,
|
||||||
|
/// username, set at account creation and can never be changed
|
||||||
|
preferred_username: String,
|
||||||
|
/// displayname (can be changed at any time)
|
||||||
|
name: Option<String>,
|
||||||
|
content: Option<String>,
|
||||||
|
media_type: Option<MediaTypeHtml>,
|
||||||
|
source: Option<Source>,
|
||||||
|
/// user avatar
|
||||||
|
icon: Option<ImageObject>,
|
||||||
|
/// user banner
|
||||||
|
image: Option<ImageObject>,
|
||||||
|
matrix_user_id: Option<String>,
|
||||||
|
inbox: Url,
|
||||||
|
/// mandatory field in activitypub, currently empty in lemmy
|
||||||
|
outbox: Url,
|
||||||
|
endpoints: Endpoints<Url>,
|
||||||
|
public_key: PublicKey,
|
||||||
|
published: DateTime<FixedOffset>,
|
||||||
|
updated: Option<DateTime<FixedOffset>>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
unparsed: Unparsed,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: can generate this with a derive macro
|
||||||
|
impl Person {
|
||||||
|
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
|
||||||
|
verify_domains_match(&self.id, expected_domain)?;
|
||||||
|
Ok(&self.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ToApub for DbPerson {
|
impl ToApub for DbPerson {
|
||||||
type ApubType = PersonExt;
|
type ApubType = Person;
|
||||||
|
|
||||||
async fn to_apub(&self, _pool: &DbPool) -> Result<PersonExt, LemmyError> {
|
async fn to_apub(&self, _pool: &DbPool) -> Result<Person, LemmyError> {
|
||||||
let object = Object::<UserTypes>::new_none_type();
|
|
||||||
let mut actor = Actor(object);
|
|
||||||
let kind = if self.bot_account {
|
let kind = if self.bot_account {
|
||||||
UserTypes::Service
|
UserTypes::Service
|
||||||
} else {
|
} else {
|
||||||
UserTypes::Person
|
UserTypes::Person
|
||||||
};
|
};
|
||||||
actor.set_kind(kind);
|
let source = self.bio.clone().map(|bio| Source {
|
||||||
let mut person = ApObject::new(actor);
|
content: bio,
|
||||||
|
media_type: MediaTypeMarkdown::Markdown,
|
||||||
|
});
|
||||||
|
let icon = self.avatar.clone().map(|url| ImageObject {
|
||||||
|
kind: ImageType::Image,
|
||||||
|
url: url.into(),
|
||||||
|
});
|
||||||
|
let image = self.banner.clone().map(|url| ImageObject {
|
||||||
|
kind: ImageType::Image,
|
||||||
|
url: url.into(),
|
||||||
|
});
|
||||||
|
|
||||||
person
|
let person = Person {
|
||||||
.set_many_contexts(lemmy_context())
|
context: lemmy_context(),
|
||||||
.set_id(self.actor_id.to_owned().into_inner())
|
kind,
|
||||||
.set_published(convert_datetime(self.published));
|
id: self.actor_id.to_owned().into_inner(),
|
||||||
|
preferred_username: self.name.clone(),
|
||||||
if let Some(u) = self.updated {
|
name: self.display_name.clone(),
|
||||||
person.set_updated(convert_datetime(u));
|
content: self.bio.as_ref().map(|b| markdown_to_html(b)),
|
||||||
}
|
media_type: self.bio.as_ref().map(|_| MediaTypeHtml::Html),
|
||||||
|
source,
|
||||||
if let Some(avatar_url) = &self.avatar {
|
icon,
|
||||||
let mut image = Image::new();
|
image,
|
||||||
image.set_url::<Url>(avatar_url.to_owned().into());
|
matrix_user_id: self.matrix_user_id.clone(),
|
||||||
person.set_icon(image.into_any_base()?);
|
published: convert_datetime(self.published),
|
||||||
}
|
outbox: self.get_outbox_url()?,
|
||||||
|
endpoints: Endpoints {
|
||||||
if let Some(banner_url) = &self.banner {
|
shared_inbox: self.shared_inbox_url.clone().map(|s| s.into()),
|
||||||
let mut image = Image::new();
|
|
||||||
image.set_url::<Url>(banner_url.to_owned().into());
|
|
||||||
person.set_image(image.into_any_base()?);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(bio) = &self.bio {
|
|
||||||
set_content_and_source(&mut person, bio)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In apub, the "name" is a display name
|
|
||||||
if let Some(i) = self.display_name.to_owned() {
|
|
||||||
person.set_name(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ap_actor = ApActor::new(self.inbox_url.clone().into(), person);
|
|
||||||
ap_actor
|
|
||||||
.set_preferred_username(self.name.to_owned())
|
|
||||||
.set_outbox(self.get_outbox_url()?)
|
|
||||||
.set_endpoints(Endpoints {
|
|
||||||
shared_inbox: Some(self.get_shared_inbox_or_inbox_url()),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
|
public_key: self.get_public_key()?,
|
||||||
let person_ext = PersonExtension::new(self.matrix_user_id.to_owned())?;
|
updated: self.updated.map(convert_datetime),
|
||||||
Ok(Ext2::new(ap_actor, person_ext, self.get_public_key_ext()?))
|
unparsed: Default::default(),
|
||||||
|
inbox: self.inbox_url.clone().into(),
|
||||||
|
};
|
||||||
|
Ok(person)
|
||||||
}
|
}
|
||||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
|
@ -98,118 +132,54 @@ impl ToApub for DbPerson {
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl FromApub for DbPerson {
|
impl FromApub for DbPerson {
|
||||||
type ApubType = PersonExt;
|
type ApubType = Person;
|
||||||
|
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
person: &PersonExt,
|
person: &Person,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
expected_domain: Url,
|
expected_domain: &Url,
|
||||||
request_counter: &mut i32,
|
|
||||||
mod_action_allowed: bool,
|
|
||||||
) -> Result<DbPerson, LemmyError> {
|
|
||||||
let person_id = person.id_unchecked().context(location_info!())?.to_owned();
|
|
||||||
let domain = person_id.domain().context(location_info!())?;
|
|
||||||
if domain == Settings::get().hostname {
|
|
||||||
let person = blocking(context.pool(), move |conn| {
|
|
||||||
DbPerson::read_from_apub_id(conn, &person_id.into())
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
Ok(person)
|
|
||||||
} else {
|
|
||||||
let person_form = PersonForm::from_apub(
|
|
||||||
person,
|
|
||||||
context,
|
|
||||||
expected_domain,
|
|
||||||
request_counter,
|
|
||||||
mod_action_allowed,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let person = blocking(context.pool(), move |conn| {
|
|
||||||
DbPerson::upsert(conn, &person_form)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
Ok(person)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl FromApubToForm<PersonExt> for PersonForm {
|
|
||||||
async fn from_apub(
|
|
||||||
person: &PersonExt,
|
|
||||||
_context: &LemmyContext,
|
|
||||||
expected_domain: Url,
|
|
||||||
_request_counter: &mut i32,
|
_request_counter: &mut i32,
|
||||||
_mod_action_allowed: bool,
|
) -> Result<DbPerson, LemmyError> {
|
||||||
) -> Result<Self, LemmyError> {
|
let actor_id = Some(person.id(expected_domain)?.clone().into());
|
||||||
let avatar = match person.icon() {
|
let name = person.preferred_username.clone();
|
||||||
Some(any_image) => Some(
|
let display_name: Option<String> = person.name.clone();
|
||||||
Image::from_any_base(any_image.as_one().context(location_info!())?.clone())?
|
let bio = person.source.clone().map(|s| s.content);
|
||||||
.context(location_info!())?
|
let shared_inbox = person.endpoints.shared_inbox.clone().map(|s| s.into());
|
||||||
.url()
|
let bot_account = match person.kind {
|
||||||
.context(location_info!())?
|
UserTypes::Person => false,
|
||||||
.as_single_xsd_any_uri()
|
UserTypes::Service => true,
|
||||||
.map(|url| url.to_owned()),
|
|
||||||
),
|
|
||||||
None => None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let banner = match person.image() {
|
|
||||||
Some(any_image) => Some(
|
|
||||||
Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
|
|
||||||
.context(location_info!())?
|
|
||||||
.context(location_info!())?
|
|
||||||
.url()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_single_xsd_any_uri()
|
|
||||||
.map(|url| url.to_owned()),
|
|
||||||
),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let name: String = person
|
|
||||||
.inner
|
|
||||||
.preferred_username()
|
|
||||||
.context(location_info!())?
|
|
||||||
.to_string();
|
|
||||||
let display_name: Option<String> = person
|
|
||||||
.name()
|
|
||||||
.map(|n| n.one())
|
|
||||||
.flatten()
|
|
||||||
.map(|n| n.to_owned().xsd_string())
|
|
||||||
.flatten();
|
|
||||||
let bio = get_source_markdown_value(person)?;
|
|
||||||
let shared_inbox = person
|
|
||||||
.inner
|
|
||||||
.endpoints()?
|
|
||||||
.map(|e| e.shared_inbox)
|
|
||||||
.flatten()
|
|
||||||
.map(|s| s.to_owned().into());
|
|
||||||
|
|
||||||
check_slurs(&name)?;
|
check_slurs(&name)?;
|
||||||
check_slurs_opt(&display_name)?;
|
check_slurs_opt(&display_name)?;
|
||||||
check_slurs_opt(&bio)?;
|
check_slurs_opt(&bio)?;
|
||||||
|
check_is_apub_id_valid(&person.id, false)?;
|
||||||
|
|
||||||
Ok(PersonForm {
|
let person_form = PersonForm {
|
||||||
name,
|
name,
|
||||||
display_name: Some(display_name),
|
display_name: Some(display_name),
|
||||||
banned: None,
|
banned: None,
|
||||||
deleted: None,
|
deleted: None,
|
||||||
avatar: avatar.map(|o| o.map(|i| i.into())),
|
avatar: Some(person.icon.clone().map(|i| i.url.into())),
|
||||||
banner: banner.map(|o| o.map(|i| i.into())),
|
banner: Some(person.image.clone().map(|i| i.url.into())),
|
||||||
published: person.inner.published().map(|u| u.to_owned().naive_local()),
|
published: Some(person.published.naive_local()),
|
||||||
updated: person.updated().map(|u| u.to_owned().naive_local()),
|
updated: person.updated.map(|u| u.clone().naive_local()),
|
||||||
actor_id: Some(check_object_domain(person, expected_domain, false)?),
|
actor_id,
|
||||||
bio: Some(bio),
|
bio: Some(bio),
|
||||||
local: Some(false),
|
local: Some(false),
|
||||||
admin: Some(false),
|
admin: Some(false),
|
||||||
bot_account: Some(person.inner.is_kind(&UserTypes::Service)),
|
bot_account: Some(bot_account),
|
||||||
private_key: None,
|
private_key: None,
|
||||||
public_key: Some(Some(person.ext_two.public_key.to_owned().public_key_pem)),
|
public_key: Some(Some(person.public_key.public_key_pem.clone())),
|
||||||
last_refreshed_at: Some(naive_now()),
|
last_refreshed_at: Some(naive_now()),
|
||||||
inbox_url: Some(person.inner.inbox()?.to_owned().into()),
|
inbox_url: Some(person.inbox.to_owned().into()),
|
||||||
shared_inbox_url: Some(shared_inbox),
|
shared_inbox_url: Some(shared_inbox),
|
||||||
matrix_user_id: Some(person.ext_one.matrix_user_id.to_owned()),
|
matrix_user_id: Some(person.matrix_user_id.clone()),
|
||||||
|
};
|
||||||
|
let person = blocking(context.pool(), move |conn| {
|
||||||
|
DbPerson::upsert(conn, &person_form)
|
||||||
})
|
})
|
||||||
|
.await??;
|
||||||
|
Ok(person)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
activities::{extract_community, verify_person_in_community},
|
activities::{extract_community, verify_person_in_community},
|
||||||
extensions::context::lemmy_context,
|
extensions::context::lemmy_context,
|
||||||
fetcher::person::get_or_fetch_and_upsert_person,
|
fetcher::person::get_or_fetch_and_upsert_person,
|
||||||
objects::{create_tombstone, FromApub, Source, ToApub},
|
objects::{create_tombstone, FromApub, ImageObject, Source, ToApub},
|
||||||
ActorType,
|
ActorType,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
|
@ -37,15 +37,17 @@ use lemmy_utils::{
|
||||||
};
|
};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
#[serde(rename = "@context")]
|
#[serde(rename = "@context")]
|
||||||
context: OneOrMany<AnyBase>,
|
context: OneOrMany<AnyBase>,
|
||||||
r#type: PageType,
|
r#type: PageType,
|
||||||
pub(crate) id: Url,
|
id: Url,
|
||||||
pub(crate) attributed_to: Url,
|
pub(crate) attributed_to: Url,
|
||||||
to: [Url; 2],
|
to: [Url; 2],
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -63,14 +65,15 @@ pub struct Page {
|
||||||
unparsed: Unparsed,
|
unparsed: Unparsed,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ImageObject {
|
|
||||||
content: ImageType,
|
|
||||||
url: Url,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
|
pub(crate) fn id_unchecked(&self) -> &Url {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
|
||||||
|
verify_domains_match(&self.id, expected_domain)?;
|
||||||
|
Ok(&self.id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Only mods can change the post's stickied/locked status. So if either of these is changed from
|
/// 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.
|
/// the current value, it is a mod action and needs to be verified as such.
|
||||||
///
|
///
|
||||||
|
@ -126,7 +129,7 @@ impl ToApub for Post {
|
||||||
media_type: MediaTypeMarkdown::Markdown,
|
media_type: MediaTypeMarkdown::Markdown,
|
||||||
});
|
});
|
||||||
let image = self.thumbnail_url.clone().map(|thumb| ImageObject {
|
let image = self.thumbnail_url.clone().map(|thumb| ImageObject {
|
||||||
content: ImageType::Image,
|
kind: ImageType::Image,
|
||||||
url: thumb.into(),
|
url: thumb.into(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -169,10 +172,17 @@ impl FromApub for Post {
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
page: &Page,
|
page: &Page,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
_expected_domain: Url,
|
expected_domain: &Url,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
_mod_action_allowed: bool,
|
|
||||||
) -> Result<Post, LemmyError> {
|
) -> Result<Post, LemmyError> {
|
||||||
|
// We can't verify the domain in case of mod action, because the mod may be on a different
|
||||||
|
// instance from the post author.
|
||||||
|
let ap_id = if page.is_mod_action(context.pool()).await? {
|
||||||
|
page.id_unchecked()
|
||||||
|
} else {
|
||||||
|
page.id(expected_domain)?
|
||||||
|
};
|
||||||
|
let ap_id = Some(ap_id.clone().into());
|
||||||
let creator =
|
let creator =
|
||||||
get_or_fetch_and_upsert_person(&page.attributed_to, context, request_counter).await?;
|
get_or_fetch_and_upsert_person(&page.attributed_to, context, request_counter).await?;
|
||||||
let community = extract_community(&page.to, context, request_counter).await?;
|
let community = extract_community(&page.to, context, request_counter).await?;
|
||||||
|
@ -205,7 +215,7 @@ impl FromApub for Post {
|
||||||
embed_description,
|
embed_description,
|
||||||
embed_html,
|
embed_html,
|
||||||
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
||||||
ap_id: Some(page.id.clone().into()),
|
ap_id,
|
||||||
local: Some(false),
|
local: Some(false),
|
||||||
};
|
};
|
||||||
Ok(blocking(context.pool(), move |conn| Post::upsert(conn, &form)).await??)
|
Ok(blocking(context.pool(), move |conn| Post::upsert(conn, &form)).await??)
|
||||||
|
|
|
@ -24,15 +24,17 @@ use lemmy_db_schema::source::{
|
||||||
use lemmy_utils::{utils::convert_datetime, LemmyError};
|
use lemmy_utils::{utils::convert_datetime, LemmyError};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Note {
|
pub struct Note {
|
||||||
#[serde(rename = "@context")]
|
#[serde(rename = "@context")]
|
||||||
context: OneOrMany<AnyBase>,
|
context: OneOrMany<AnyBase>,
|
||||||
r#type: NoteType,
|
r#type: NoteType,
|
||||||
pub(crate) id: Url,
|
id: Url,
|
||||||
pub(crate) attributed_to: Url,
|
pub(crate) attributed_to: Url,
|
||||||
to: Url,
|
to: Url,
|
||||||
content: String,
|
content: String,
|
||||||
|
@ -45,6 +47,14 @@ pub struct Note {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Note {
|
impl Note {
|
||||||
|
pub(crate) fn id_unchecked(&self) -> &Url {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
|
||||||
|
verify_domains_match(&self.id, expected_domain)?;
|
||||||
|
Ok(&self.id)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn verify(
|
pub(crate) async fn verify(
|
||||||
&self,
|
&self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
|
@ -107,10 +117,10 @@ impl FromApub for PrivateMessage {
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
note: &Note,
|
note: &Note,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
_expected_domain: Url,
|
expected_domain: &Url,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
_mod_action_allowed: bool,
|
|
||||||
) -> Result<PrivateMessage, LemmyError> {
|
) -> Result<PrivateMessage, LemmyError> {
|
||||||
|
let ap_id = Some(note.id(expected_domain)?.clone().into());
|
||||||
let creator =
|
let creator =
|
||||||
get_or_fetch_and_upsert_person(¬e.attributed_to, context, request_counter).await?;
|
get_or_fetch_and_upsert_person(¬e.attributed_to, context, request_counter).await?;
|
||||||
let recipient = get_or_fetch_and_upsert_person(¬e.to, context, request_counter).await?;
|
let recipient = get_or_fetch_and_upsert_person(¬e.to, context, request_counter).await?;
|
||||||
|
@ -123,7 +133,7 @@ impl FromApub for PrivateMessage {
|
||||||
updated: note.updated.map(|u| u.to_owned().naive_local()),
|
updated: note.updated.map(|u| u.to_owned().naive_local()),
|
||||||
deleted: None,
|
deleted: None,
|
||||||
read: None,
|
read: None,
|
||||||
ap_id: Some(note.id.clone().into()),
|
ap_id,
|
||||||
local: Some(false),
|
local: Some(false),
|
||||||
};
|
};
|
||||||
Ok(
|
Ok(
|
||||||
|
|
|
@ -39,7 +39,7 @@ pub trait ActivityHandler {
|
||||||
) -> Result<(), LemmyError>;
|
) -> Result<(), LemmyError>;
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError>;
|
) -> Result<(), LemmyError>;
|
||||||
|
|
|
@ -92,7 +92,7 @@ pub fn derive_activity_handler(input: proc_macro::TokenStream) -> proc_macro::To
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
|
Loading…
Reference in a new issue