]> Untitled Git - lemmy.git/commitdiff
Refactor apub code, split up large files
authorFelix Ableitner <me@nutomic.com>
Mon, 12 Oct 2020 14:10:09 +0000 (16:10 +0200)
committerFelix Ableitner <me@nutomic.com>
Mon, 12 Oct 2020 14:10:09 +0000 (16:10 +0200)
40 files changed:
lemmy_apub/src/activities/mod.rs [new file with mode: 0644]
lemmy_apub/src/activities/receive/announce.rs [moved from lemmy_apub/src/inbox/activities/announce.rs with 92% similarity]
lemmy_apub/src/activities/receive/create.rs [moved from lemmy_apub/src/inbox/activities/create.rs with 96% similarity]
lemmy_apub/src/activities/receive/delete.rs [moved from lemmy_apub/src/inbox/activities/delete.rs with 98% similarity]
lemmy_apub/src/activities/receive/dislike.rs [moved from lemmy_apub/src/inbox/activities/dislike.rs with 96% similarity]
lemmy_apub/src/activities/receive/like.rs [moved from lemmy_apub/src/inbox/activities/like.rs with 96% similarity]
lemmy_apub/src/activities/receive/mod.rs [new file with mode: 0644]
lemmy_apub/src/activities/receive/remove.rs [moved from lemmy_apub/src/inbox/activities/remove.rs with 96% similarity]
lemmy_apub/src/activities/receive/undo.rs [new file with mode: 0644]
lemmy_apub/src/activities/receive/undo_comment.rs [new file with mode: 0644]
lemmy_apub/src/activities/receive/undo_post.rs [new file with mode: 0644]
lemmy_apub/src/activities/receive/update.rs [moved from lemmy_apub/src/inbox/activities/update.rs with 96% similarity]
lemmy_apub/src/activities/send/comment.rs [moved from lemmy_apub/src/comment.rs with 69% similarity]
lemmy_apub/src/activities/send/community.rs [new file with mode: 0644]
lemmy_apub/src/activities/send/mod.rs [new file with mode: 0644]
lemmy_apub/src/activities/send/post.rs [moved from lemmy_apub/src/post.rs with 55% similarity]
lemmy_apub/src/activities/send/private_message.rs [moved from lemmy_apub/src/private_message.rs with 55% similarity]
lemmy_apub/src/activities/send/user.rs [new file with mode: 0644]
lemmy_apub/src/activity_queue.rs
lemmy_apub/src/community.rs [deleted file]
lemmy_apub/src/fetcher.rs
lemmy_apub/src/http/comment.rs [new file with mode: 0644]
lemmy_apub/src/http/community.rs [new file with mode: 0644]
lemmy_apub/src/http/mod.rs [new file with mode: 0644]
lemmy_apub/src/http/post.rs [new file with mode: 0644]
lemmy_apub/src/http/user.rs [new file with mode: 0644]
lemmy_apub/src/inbox/activities/mod.rs [deleted file]
lemmy_apub/src/inbox/activities/undo.rs [deleted file]
lemmy_apub/src/inbox/community_inbox.rs
lemmy_apub/src/inbox/mod.rs
lemmy_apub/src/inbox/shared_inbox.rs
lemmy_apub/src/inbox/user_inbox.rs
lemmy_apub/src/lib.rs
lemmy_apub/src/objects/comment.rs [new file with mode: 0644]
lemmy_apub/src/objects/community.rs [new file with mode: 0644]
lemmy_apub/src/objects/mod.rs [new file with mode: 0644]
lemmy_apub/src/objects/post.rs [new file with mode: 0644]
lemmy_apub/src/objects/private_message.rs [new file with mode: 0644]
lemmy_apub/src/objects/user.rs [moved from lemmy_apub/src/user.rs with 56% similarity]
src/routes/federation.rs

diff --git a/lemmy_apub/src/activities/mod.rs b/lemmy_apub/src/activities/mod.rs
new file mode 100644 (file)
index 0000000..afea56e
--- /dev/null
@@ -0,0 +1,2 @@
+pub mod receive;
+pub mod send;
similarity index 92%
rename from lemmy_apub/src/inbox/activities/announce.rs
rename to lemmy_apub/src/activities/receive/announce.rs
index d861e5f27231ca602fbf796e440612c78030abd3..a553c190dbc7176f09d78c9993dd6d29541e6a07 100644 (file)
@@ -1,14 +1,15 @@
-use crate::inbox::{
-  activities::{
+use crate::{
+  activities::receive::{
     create::receive_create,
     delete::receive_delete,
     dislike::receive_dislike,
     like::receive_like,
+    receive_unhandled_activity,
     remove::receive_remove,
     undo::receive_undo,
     update::receive_update,
   },
-  shared_inbox::{get_community_id_from_activity, receive_unhandled_activity},
+  inbox::shared_inbox::get_community_id_from_activity,
 };
 use activitystreams::{
   activity::*,
similarity index 96%
rename from lemmy_apub/src/inbox/activities/create.rs
rename to lemmy_apub/src/activities/receive/create.rs
index e25fdd979a28e8fbf00d8463251359df4aa7caee..4911088d041b2b5868caf116c84c566ef612efc7 100644 (file)
@@ -1,9 +1,6 @@
 use crate::{
-  inbox::shared_inbox::{
-    announce_if_community_is_local,
-    get_user_from_activity,
-    receive_unhandled_activity,
-  },
+  activities::receive::{announce_if_community_is_local, receive_unhandled_activity},
+  inbox::shared_inbox::get_user_from_activity,
   ActorType,
   FromApub,
   PageExt,
similarity index 98%
rename from lemmy_apub/src/inbox/activities/delete.rs
rename to lemmy_apub/src/activities/receive/delete.rs
index 2c3760e4280c8764c1f53869dfb80eb82024e688..1490ce9182b2896abe24b5d2da4eb1723ab4bf6c 100644 (file)
@@ -1,10 +1,7 @@
 use crate::{
+  activities::receive::{announce_if_community_is_local, receive_unhandled_activity},
   fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
-  inbox::shared_inbox::{
-    announce_if_community_is_local,
-    get_user_from_activity,
-    receive_unhandled_activity,
-  },
+  inbox::shared_inbox::get_user_from_activity,
   ActorType,
   FromApub,
   GroupExt,
similarity index 96%
rename from lemmy_apub/src/inbox/activities/dislike.rs
rename to lemmy_apub/src/activities/receive/dislike.rs
index 06a7a00666490b7dd5521d283fad27cf03b15a8d..e794148041be12e1c6ab5e71bf968a64ac8e748b 100644 (file)
@@ -1,10 +1,7 @@
 use crate::{
+  activities::receive::{announce_if_community_is_local, receive_unhandled_activity},
   fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
-  inbox::shared_inbox::{
-    announce_if_community_is_local,
-    get_user_from_activity,
-    receive_unhandled_activity,
-  },
+  inbox::shared_inbox::get_user_from_activity,
   FromApub,
   PageExt,
 };
similarity index 96%
rename from lemmy_apub/src/inbox/activities/like.rs
rename to lemmy_apub/src/activities/receive/like.rs
index 7b56867b4f0aa707c8f3cfda00d3a0e223feb911..3000e512d18fd72b35e7f9972fe8d28d27511967 100644 (file)
@@ -1,10 +1,7 @@
 use crate::{
+  activities::receive::{announce_if_community_is_local, receive_unhandled_activity},
   fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
-  inbox::shared_inbox::{
-    announce_if_community_is_local,
-    get_user_from_activity,
-    receive_unhandled_activity,
-  },
+  inbox::shared_inbox::get_user_from_activity,
   FromApub,
   PageExt,
 };
diff --git a/lemmy_apub/src/activities/receive/mod.rs b/lemmy_apub/src/activities/receive/mod.rs
new file mode 100644 (file)
index 0000000..b765b03
--- /dev/null
@@ -0,0 +1,65 @@
+use crate::{fetcher::get_or_fetch_and_upsert_community, ActorType};
+use activitystreams::{
+  base::{Extends, ExtendsExt},
+  object::{AsObject, ObjectExt},
+};
+use actix_web::HttpResponse;
+use anyhow::Context;
+use lemmy_db::user::User_;
+use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::LemmyContext;
+use log::debug;
+use serde::Serialize;
+use std::fmt::Debug;
+use url::Url;
+
+pub mod announce;
+pub mod create;
+pub mod delete;
+pub mod dislike;
+pub mod like;
+pub mod remove;
+pub mod undo;
+mod undo_comment;
+mod undo_post;
+pub mod update;
+
+fn receive_unhandled_activity<A>(activity: A) -> Result<HttpResponse, LemmyError>
+where
+  A: Debug,
+{
+  debug!("received unhandled activity type: {:?}", activity);
+  Ok(HttpResponse::NotImplemented().finish())
+}
+
+async fn announce_if_community_is_local<T, Kind>(
+  activity: T,
+  user: &User_,
+  context: &LemmyContext,
+) -> Result<(), LemmyError>
+where
+  T: AsObject<Kind>,
+  T: Extends<Kind>,
+  Kind: Serialize,
+  <T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
+{
+  let cc = activity.cc().context(location_info!())?;
+  let cc = cc.as_many().context(location_info!())?;
+  let community_followers_uri = cc
+    .first()
+    .context(location_info!())?
+    .as_xsd_any_uri()
+    .context(location_info!())?;
+  // TODO: this is hacky but seems to be the only way to get the community ID
+  let community_uri = community_followers_uri
+    .to_string()
+    .replace("/followers", "");
+  let community = get_or_fetch_and_upsert_community(&Url::parse(&community_uri)?, context).await?;
+
+  if community.local {
+    community
+      .send_announce(activity.into_any_base()?, &user, context)
+      .await?;
+  }
+  Ok(())
+}
similarity index 96%
rename from lemmy_apub/src/inbox/activities/remove.rs
rename to lemmy_apub/src/activities/receive/remove.rs
index 27a7775e6485bea5a851e69921450857f4c76e3c..09cfd081385981dad4dca5a23f52efc0b3724642 100644 (file)
@@ -1,11 +1,7 @@
 use crate::{
+  activities::receive::{announce_if_community_is_local, receive_unhandled_activity},
   fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
-  inbox::shared_inbox::{
-    announce_if_community_is_local,
-    get_community_id_from_activity,
-    get_user_from_activity,
-    receive_unhandled_activity,
-  },
+  inbox::shared_inbox::{get_community_id_from_activity, get_user_from_activity},
   ActorType,
   FromApub,
   GroupExt,
@@ -45,7 +41,7 @@ pub async fn receive_remove(
   let actor = get_user_from_activity(&remove, context).await?;
   let community = get_community_id_from_activity(&remove)?;
   if actor.actor_id()?.domain() != community.domain() {
-    return Err(anyhow!("Remove activities are only allowed on local objects").into());
+    return Err(anyhow!("Remove receive are only allowed on local objects").into());
   }
 
   match remove.object().as_single_kind_str() {
diff --git a/lemmy_apub/src/activities/receive/undo.rs b/lemmy_apub/src/activities/receive/undo.rs
new file mode 100644 (file)
index 0000000..0880c3f
--- /dev/null
@@ -0,0 +1,269 @@
+use crate::{
+  activities::receive::{
+    announce_if_community_is_local,
+    receive_unhandled_activity,
+    undo_comment::*,
+    undo_post::*,
+  },
+  inbox::shared_inbox::get_user_from_activity,
+  ActorType,
+  FromApub,
+  GroupExt,
+};
+use activitystreams::{
+  activity::*,
+  base::{AnyBase, AsBase},
+  prelude::*,
+};
+use actix_web::HttpResponse;
+use anyhow::{anyhow, Context};
+use lemmy_db::{
+  community::{Community, CommunityForm},
+  community_view::CommunityView,
+  naive_now,
+  Crud,
+};
+use lemmy_structs::{blocking, community::CommunityResponse};
+use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
+
+pub async fn receive_undo(
+  activity: AnyBase,
+  context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+  let undo = Undo::from_any_base(activity)?.context(location_info!())?;
+  match undo.object().as_single_kind_str() {
+    Some("Delete") => receive_undo_delete(undo, context).await,
+    Some("Remove") => receive_undo_remove(undo, context).await,
+    Some("Like") => receive_undo_like(undo, context).await,
+    Some("Dislike") => receive_undo_dislike(undo, context).await,
+    _ => receive_unhandled_activity(undo),
+  }
+}
+
+fn check_is_undo_valid<T, A>(outer_activity: &Undo, inner_activity: &T) -> Result<(), LemmyError>
+where
+  T: AsBase<A> + ActorAndObjectRef,
+{
+  let outer_actor = outer_activity.actor()?;
+  let outer_actor_uri = outer_actor
+    .as_single_xsd_any_uri()
+    .context(location_info!())?;
+
+  let inner_actor = inner_activity.actor()?;
+  let inner_actor_uri = inner_actor
+    .as_single_xsd_any_uri()
+    .context(location_info!())?;
+
+  if outer_actor_uri.domain() != inner_actor_uri.domain() {
+    Err(anyhow!("Cant undo receive from a different instance").into())
+  } else {
+    Ok(())
+  }
+}
+
+async fn receive_undo_delete(
+  undo: Undo,
+  context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+  let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
+    .context(location_info!())?;
+  check_is_undo_valid(&undo, &delete)?;
+  let type_ = delete
+    .object()
+    .as_single_kind_str()
+    .context(location_info!())?;
+  match type_ {
+    "Note" => receive_undo_delete_comment(undo, &delete, context).await,
+    "Page" => receive_undo_delete_post(undo, &delete, context).await,
+    "Group" => receive_undo_delete_community(undo, &delete, context).await,
+    d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
+  }
+}
+
+async fn receive_undo_remove(
+  undo: Undo,
+  context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+  let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
+    .context(location_info!())?;
+  check_is_undo_valid(&undo, &remove)?;
+
+  let type_ = remove
+    .object()
+    .as_single_kind_str()
+    .context(location_info!())?;
+  match type_ {
+    "Note" => receive_undo_remove_comment(undo, &remove, context).await,
+    "Page" => receive_undo_remove_post(undo, &remove, context).await,
+    "Group" => receive_undo_remove_community(undo, &remove, context).await,
+    d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
+  }
+}
+
+async fn receive_undo_like(undo: Undo, context: &LemmyContext) -> Result<HttpResponse, LemmyError> {
+  let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
+    .context(location_info!())?;
+  check_is_undo_valid(&undo, &like)?;
+
+  let type_ = like
+    .object()
+    .as_single_kind_str()
+    .context(location_info!())?;
+  match type_ {
+    "Note" => receive_undo_like_comment(undo, &like, context).await,
+    "Page" => receive_undo_like_post(undo, &like, context).await,
+    d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
+  }
+}
+
+async fn receive_undo_dislike(
+  undo: Undo,
+  context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+  let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
+    .context(location_info!())?;
+  check_is_undo_valid(&undo, &dislike)?;
+
+  let type_ = dislike
+    .object()
+    .as_single_kind_str()
+    .context(location_info!())?;
+  match type_ {
+    "Note" => receive_undo_dislike_comment(undo, &dislike, context).await,
+    "Page" => receive_undo_dislike_post(undo, &dislike, context).await,
+    d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
+  }
+}
+
+async fn receive_undo_delete_community(
+  undo: Undo,
+  delete: &Delete,
+  context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+  let user = get_user_from_activity(delete, context).await?;
+  let group = GroupExt::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
+    .context(location_info!())?;
+
+  let community_actor_id = CommunityForm::from_apub(&group, context, Some(user.actor_id()?))
+    .await?
+    .actor_id
+    .context(location_info!())?;
+
+  let community = blocking(context.pool(), move |conn| {
+    Community::read_from_actor_id(conn, &community_actor_id)
+  })
+  .await??;
+
+  let community_form = CommunityForm {
+    name: community.name.to_owned(),
+    title: community.title.to_owned(),
+    description: community.description.to_owned(),
+    category_id: community.category_id, // Note: need to keep this due to foreign key constraint
+    creator_id: community.creator_id,   // Note: need to keep this due to foreign key constraint
+    removed: None,
+    published: None,
+    updated: Some(naive_now()),
+    deleted: Some(false),
+    nsfw: community.nsfw,
+    actor_id: Some(community.actor_id),
+    local: community.local,
+    private_key: community.private_key,
+    public_key: community.public_key,
+    last_refreshed_at: None,
+    icon: Some(community.icon.to_owned()),
+    banner: Some(community.banner.to_owned()),
+  };
+
+  let community_id = community.id;
+  blocking(context.pool(), move |conn| {
+    Community::update(conn, community_id, &community_form)
+  })
+  .await??;
+
+  let community_id = community.id;
+  let res = CommunityResponse {
+    community: blocking(context.pool(), move |conn| {
+      CommunityView::read(conn, community_id, None)
+    })
+    .await??,
+  };
+
+  let community_id = res.community.id;
+
+  context.chat_server().do_send(SendCommunityRoomMessage {
+    op: UserOperation::EditCommunity,
+    response: res,
+    community_id,
+    websocket_id: None,
+  });
+
+  announce_if_community_is_local(undo, &user, context).await?;
+  Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_undo_remove_community(
+  undo: Undo,
+  remove: &Remove,
+  context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+  let mod_ = get_user_from_activity(remove, context).await?;
+  let group = GroupExt::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
+    .context(location_info!())?;
+
+  let community_actor_id = CommunityForm::from_apub(&group, context, Some(mod_.actor_id()?))
+    .await?
+    .actor_id
+    .context(location_info!())?;
+
+  let community = blocking(context.pool(), move |conn| {
+    Community::read_from_actor_id(conn, &community_actor_id)
+  })
+  .await??;
+
+  let community_form = CommunityForm {
+    name: community.name.to_owned(),
+    title: community.title.to_owned(),
+    description: community.description.to_owned(),
+    category_id: community.category_id, // Note: need to keep this due to foreign key constraint
+    creator_id: community.creator_id,   // Note: need to keep this due to foreign key constraint
+    removed: Some(false),
+    published: None,
+    updated: Some(naive_now()),
+    deleted: None,
+    nsfw: community.nsfw,
+    actor_id: Some(community.actor_id),
+    local: community.local,
+    private_key: community.private_key,
+    public_key: community.public_key,
+    last_refreshed_at: None,
+    icon: Some(community.icon.to_owned()),
+    banner: Some(community.banner.to_owned()),
+  };
+
+  let community_id = community.id;
+  blocking(context.pool(), move |conn| {
+    Community::update(conn, community_id, &community_form)
+  })
+  .await??;
+
+  let community_id = community.id;
+  let res = CommunityResponse {
+    community: blocking(context.pool(), move |conn| {
+      CommunityView::read(conn, community_id, None)
+    })
+    .await??,
+  };
+
+  let community_id = res.community.id;
+
+  context.chat_server().do_send(SendCommunityRoomMessage {
+    op: UserOperation::EditCommunity,
+    response: res,
+    community_id,
+    websocket_id: None,
+  });
+
+  announce_if_community_is_local(undo, &mod_, context).await?;
+  Ok(HttpResponse::Ok().finish())
+}
diff --git a/lemmy_apub/src/activities/receive/undo_comment.rs b/lemmy_apub/src/activities/receive/undo_comment.rs
new file mode 100644 (file)
index 0000000..8ff805e
--- /dev/null
@@ -0,0 +1,234 @@
+use crate::{
+  activities::receive::announce_if_community_is_local,
+  fetcher::get_or_fetch_and_insert_comment,
+  inbox::shared_inbox::get_user_from_activity,
+  ActorType,
+  FromApub,
+};
+use activitystreams::{activity::*, object::Note, prelude::*};
+use actix_web::HttpResponse;
+use anyhow::Context;
+use lemmy_db::{
+  comment::{Comment, CommentForm, CommentLike},
+  comment_view::CommentView,
+  naive_now,
+  Crud,
+  Likeable,
+};
+use lemmy_structs::{blocking, comment::CommentResponse};
+use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
+
+pub(crate) async fn receive_undo_like_comment(
+  undo: Undo,
+  like: &Like,
+  context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+  let user = get_user_from_activity(like, context).await?;
+  let note = Note::from_any_base(like.object().to_owned().one().context(location_info!())?)?
+    .context(location_info!())?;
+
+  let comment = CommentForm::from_apub(&note, context, None).await?;
+
+  let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
+    .await?
+    .id;
+
+  let user_id = user.id;
+  blocking(context.pool(), move |conn| {
+    CommentLike::remove(conn, user_id, comment_id)
+  })
+  .await??;
+
+  // Refetch the view
+  let comment_view = blocking(context.pool(), move |conn| {
+    CommentView::read(conn, comment_id, None)
+  })
+  .await??;
+
+  // TODO get those recipient actor ids from somewhere
+  let recipient_ids = vec![];
+  let res = CommentResponse {
+    comment: comment_view,
+    recipient_ids,
+    form_id: None,
+  };
+
+  context.chat_server().do_send(SendComment {
+    op: UserOperation::CreateCommentLike,
+    comment: res,
+    websocket_id: None,
+  });
+
+  announce_if_community_is_local(undo, &user, context).await?;
+  Ok(HttpResponse::Ok().finish())
+}
+
+pub(crate) async fn receive_undo_dislike_comment(
+  undo: Undo,
+  dislike: &Dislike,
+  context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+  let user = get_user_from_activity(dislike, context).await?;
+  let note = Note::from_any_base(
+    dislike
+      .object()
+      .to_owned()
+      .one()
+      .context(location_info!())?,
+  )?
+  .context(location_info!())?;
+
+  let comment = CommentForm::from_apub(&note, context, None).await?;
+
+  let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
+    .await?
+    .id;
+
+  let user_id = user.id;
+  blocking(context.pool(), move |conn| {
+    CommentLike::remove(conn, user_id, comment_id)
+  })
+  .await??;
+
+  // Refetch the view
+  let comment_view = blocking(context.pool(), move |conn| {
+    CommentView::read(conn, comment_id, None)
+  })
+  .await??;
+
+  // TODO get those recipient actor ids from somewhere
+  let recipient_ids = vec![];
+  let res = CommentResponse {
+    comment: comment_view,
+    recipient_ids,
+    form_id: None,
+  };
+
+  context.chat_server().do_send(SendComment {
+    op: UserOperation::CreateCommentLike,
+    comment: res,
+    websocket_id: None,
+  });
+
+  announce_if_community_is_local(undo, &user, context).await?;
+  Ok(HttpResponse::Ok().finish())
+}
+
+pub(crate) async fn receive_undo_delete_comment(
+  undo: Undo,
+  delete: &Delete,
+  context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+  let user = get_user_from_activity(delete, context).await?;
+  let note = Note::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
+    .context(location_info!())?;
+
+  let comment_ap_id = CommentForm::from_apub(&note, context, Some(user.actor_id()?))
+    .await?
+    .get_ap_id()?;
+
+  let comment = get_or_fetch_and_insert_comment(&comment_ap_id, context).await?;
+
+  let comment_form = CommentForm {
+    content: comment.content.to_owned(),
+    parent_id: comment.parent_id,
+    post_id: comment.post_id,
+    creator_id: comment.creator_id,
+    removed: None,
+    deleted: Some(false),
+    read: None,
+    published: None,
+    updated: Some(naive_now()),
+    ap_id: Some(comment.ap_id),
+    local: comment.local,
+  };
+  let comment_id = comment.id;
+  blocking(context.pool(), move |conn| {
+    Comment::update(conn, comment_id, &comment_form)
+  })
+  .await??;
+
+  // Refetch the view
+  let comment_id = comment.id;
+  let comment_view = blocking(context.pool(), move |conn| {
+    CommentView::read(conn, comment_id, None)
+  })
+  .await??;
+
+  // TODO get those recipient actor ids from somewhere
+  let recipient_ids = vec![];
+  let res = CommentResponse {
+    comment: comment_view,
+    recipient_ids,
+    form_id: None,
+  };
+
+  context.chat_server().do_send(SendComment {
+    op: UserOperation::EditComment,
+    comment: res,
+    websocket_id: None,
+  });
+
+  announce_if_community_is_local(undo, &user, context).await?;
+  Ok(HttpResponse::Ok().finish())
+}
+
+pub(crate) async fn receive_undo_remove_comment(
+  undo: Undo,
+  remove: &Remove,
+  context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+  let mod_ = get_user_from_activity(remove, context).await?;
+  let note = Note::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
+    .context(location_info!())?;
+
+  let comment_ap_id = CommentForm::from_apub(&note, context, None)
+    .await?
+    .get_ap_id()?;
+
+  let comment = get_or_fetch_and_insert_comment(&comment_ap_id, context).await?;
+
+  let comment_form = CommentForm {
+    content: comment.content.to_owned(),
+    parent_id: comment.parent_id,
+    post_id: comment.post_id,
+    creator_id: comment.creator_id,
+    removed: Some(false),
+    deleted: None,
+    read: None,
+    published: None,
+    updated: Some(naive_now()),
+    ap_id: Some(comment.ap_id),
+    local: comment.local,
+  };
+  let comment_id = comment.id;
+  blocking(context.pool(), move |conn| {
+    Comment::update(conn, comment_id, &comment_form)
+  })
+  .await??;
+
+  // Refetch the view
+  let comment_id = comment.id;
+  let comment_view = blocking(context.pool(), move |conn| {
+    CommentView::read(conn, comment_id, None)
+  })
+  .await??;
+
+  // TODO get those recipient actor ids from somewhere
+  let recipient_ids = vec![];
+  let res = CommentResponse {
+    comment: comment_view,
+    recipient_ids,
+    form_id: None,
+  };
+
+  context.chat_server().do_send(SendComment {
+    op: UserOperation::EditComment,
+    comment: res,
+    websocket_id: None,
+  });
+
+  announce_if_community_is_local(undo, &mod_, context).await?;
+  Ok(HttpResponse::Ok().finish())
+}
diff --git a/lemmy_apub/src/activities/receive/undo_post.rs b/lemmy_apub/src/activities/receive/undo_post.rs
new file mode 100644 (file)
index 0000000..00527a1
--- /dev/null
@@ -0,0 +1,225 @@
+use crate::{
+  activities::receive::announce_if_community_is_local,
+  fetcher::get_or_fetch_and_insert_post,
+  inbox::shared_inbox::get_user_from_activity,
+  ActorType,
+  FromApub,
+  PageExt,
+};
+use activitystreams::{activity::*, prelude::*};
+use actix_web::HttpResponse;
+use anyhow::Context;
+use lemmy_db::{
+  naive_now,
+  post::{Post, PostForm, PostLike},
+  post_view::PostView,
+  Crud,
+  Likeable,
+};
+use lemmy_structs::{blocking, post::PostResponse};
+use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
+
+pub(crate) async fn receive_undo_like_post(
+  undo: Undo,
+  like: &Like,
+  context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+  let user = get_user_from_activity(like, context).await?;
+  let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
+    .context(location_info!())?;
+
+  let post = PostForm::from_apub(&page, context, None).await?;
+
+  let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
+    .await?
+    .id;
+
+  let user_id = user.id;
+  blocking(context.pool(), move |conn| {
+    PostLike::remove(conn, user_id, post_id)
+  })
+  .await??;
+
+  // Refetch the view
+  let post_view = blocking(context.pool(), move |conn| {
+    PostView::read(conn, post_id, None)
+  })
+  .await??;
+
+  let res = PostResponse { post: post_view };
+
+  context.chat_server().do_send(SendPost {
+    op: UserOperation::CreatePostLike,
+    post: res,
+    websocket_id: None,
+  });
+
+  announce_if_community_is_local(undo, &user, context).await?;
+  Ok(HttpResponse::Ok().finish())
+}
+
+pub(crate) async fn receive_undo_dislike_post(
+  undo: Undo,
+  dislike: &Dislike,
+  context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+  let user = get_user_from_activity(dislike, context).await?;
+  let page = PageExt::from_any_base(
+    dislike
+      .object()
+      .to_owned()
+      .one()
+      .context(location_info!())?,
+  )?
+  .context(location_info!())?;
+
+  let post = PostForm::from_apub(&page, context, None).await?;
+
+  let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
+    .await?
+    .id;
+
+  let user_id = user.id;
+  blocking(context.pool(), move |conn| {
+    PostLike::remove(conn, user_id, post_id)
+  })
+  .await??;
+
+  // Refetch the view
+  let post_view = blocking(context.pool(), move |conn| {
+    PostView::read(conn, post_id, None)
+  })
+  .await??;
+
+  let res = PostResponse { post: post_view };
+
+  context.chat_server().do_send(SendPost {
+    op: UserOperation::CreatePostLike,
+    post: res,
+    websocket_id: None,
+  });
+
+  announce_if_community_is_local(undo, &user, context).await?;
+  Ok(HttpResponse::Ok().finish())
+}
+
+pub(crate) async fn receive_undo_delete_post(
+  undo: Undo,
+  delete: &Delete,
+  context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+  let user = get_user_from_activity(delete, context).await?;
+  let page = PageExt::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
+    .context(location_info!())?;
+
+  let post_ap_id = PostForm::from_apub(&page, context, Some(user.actor_id()?))
+    .await?
+    .get_ap_id()?;
+
+  let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
+
+  let post_form = PostForm {
+    name: post.name.to_owned(),
+    url: post.url.to_owned(),
+    body: post.body.to_owned(),
+    creator_id: post.creator_id.to_owned(),
+    community_id: post.community_id,
+    removed: None,
+    deleted: Some(false),
+    nsfw: post.nsfw,
+    locked: None,
+    stickied: None,
+    updated: Some(naive_now()),
+    embed_title: post.embed_title,
+    embed_description: post.embed_description,
+    embed_html: post.embed_html,
+    thumbnail_url: post.thumbnail_url,
+    ap_id: Some(post.ap_id),
+    local: post.local,
+    published: None,
+  };
+  let post_id = post.id;
+  blocking(context.pool(), move |conn| {
+    Post::update(conn, post_id, &post_form)
+  })
+  .await??;
+
+  // Refetch the view
+  let post_id = post.id;
+  let post_view = blocking(context.pool(), move |conn| {
+    PostView::read(conn, post_id, None)
+  })
+  .await??;
+
+  let res = PostResponse { post: post_view };
+
+  context.chat_server().do_send(SendPost {
+    op: UserOperation::EditPost,
+    post: res,
+    websocket_id: None,
+  });
+
+  announce_if_community_is_local(undo, &user, context).await?;
+  Ok(HttpResponse::Ok().finish())
+}
+
+pub(crate) async fn receive_undo_remove_post(
+  undo: Undo,
+  remove: &Remove,
+  context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+  let mod_ = get_user_from_activity(remove, context).await?;
+  let page = PageExt::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
+    .context(location_info!())?;
+
+  let post_ap_id = PostForm::from_apub(&page, context, None)
+    .await?
+    .get_ap_id()?;
+
+  let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
+
+  let post_form = PostForm {
+    name: post.name.to_owned(),
+    url: post.url.to_owned(),
+    body: post.body.to_owned(),
+    creator_id: post.creator_id.to_owned(),
+    community_id: post.community_id,
+    removed: Some(false),
+    deleted: None,
+    nsfw: post.nsfw,
+    locked: None,
+    stickied: None,
+    updated: Some(naive_now()),
+    embed_title: post.embed_title,
+    embed_description: post.embed_description,
+    embed_html: post.embed_html,
+    thumbnail_url: post.thumbnail_url,
+    ap_id: Some(post.ap_id),
+    local: post.local,
+    published: None,
+  };
+  let post_id = post.id;
+  blocking(context.pool(), move |conn| {
+    Post::update(conn, post_id, &post_form)
+  })
+  .await??;
+
+  // Refetch the view
+  let post_id = post.id;
+  let post_view = blocking(context.pool(), move |conn| {
+    PostView::read(conn, post_id, None)
+  })
+  .await??;
+
+  let res = PostResponse { post: post_view };
+
+  context.chat_server().do_send(SendPost {
+    op: UserOperation::EditPost,
+    post: res,
+    websocket_id: None,
+  });
+
+  announce_if_community_is_local(undo, &mod_, context).await?;
+  Ok(HttpResponse::Ok().finish())
+}
similarity index 96%
rename from lemmy_apub/src/inbox/activities/update.rs
rename to lemmy_apub/src/activities/receive/update.rs
index 17d9d7084f7c14eb59053a2f5e1563dcbaf9c73a..a18c6fe9220f5612c547e0a2c9fd46ee3ff5eb99 100644 (file)
@@ -1,10 +1,7 @@
 use crate::{
+  activities::receive::{announce_if_community_is_local, receive_unhandled_activity},
   fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
-  inbox::shared_inbox::{
-    announce_if_community_is_local,
-    get_user_from_activity,
-    receive_unhandled_activity,
-  },
+  inbox::shared_inbox::get_user_from_activity,
   ActorType,
   FromApub,
   PageExt,
similarity index 69%
rename from lemmy_apub/src/comment.rs
rename to lemmy_apub/src/activities/send/comment.rs
index e06fe71eac8f918aae1c4698a0cf7379d3e7dc5c..af31dc433a6b562cfd9e622750fd68a9b6a7c347 100644 (file)
@@ -1,20 +1,10 @@
 use crate::{
+  activities::send::generate_activity_id,
   activity_queue::{send_comment_mentions, send_to_community},
-  check_actor_domain,
-  create_apub_response,
-  create_apub_tombstone_response,
-  create_tombstone,
-  fetch_webfinger_url,
-  fetcher::{
-    get_or_fetch_and_insert_comment,
-    get_or_fetch_and_insert_post,
-    get_or_fetch_and_upsert_user,
-  },
-  generate_activity_id,
+  fetcher::get_or_fetch_and_upsert_user,
   ActorType,
   ApubLikeableType,
   ApubObjectType,
-  FromApub,
   ToApub,
 };
 use activitystreams::{
@@ -30,174 +20,25 @@ use activitystreams::{
   },
   base::AnyBase,
   link::Mention,
-  object::{kind::NoteType, Note, Tombstone},
   prelude::*,
   public,
 };
-use actix_web::{body::Body, web, web::Path, HttpResponse};
-use anyhow::Context;
-use diesel::result::Error::NotFound;
+use anyhow::anyhow;
 use itertools::Itertools;
-use lemmy_db::{
-  comment::{Comment, CommentForm},
-  community::Community,
-  post::Post,
-  user::User_,
-  Crud,
-  DbPool,
-};
-use lemmy_structs::blocking;
+use lemmy_db::{comment::Comment, community::Community, post::Post, user::User_, Crud};
+use lemmy_structs::{blocking, WebFingerResponse};
 use lemmy_utils::{
-  location_info,
-  utils::{convert_datetime, remove_slurs, scrape_text_for_mentions, MentionData},
+  request::{retry, RecvError},
+  settings::Settings,
+  utils::{scrape_text_for_mentions, MentionData},
   LemmyError,
 };
 use lemmy_websocket::LemmyContext;
 use log::debug;
-use serde::Deserialize;
+use reqwest::Client;
 use serde_json::Error;
 use url::Url;
 
-#[derive(Deserialize)]
-pub struct CommentQuery {
-  comment_id: String,
-}
-
-/// Return the post json over HTTP.
-pub async fn get_apub_comment(
-  info: Path<CommentQuery>,
-  context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
-  let id = info.comment_id.parse::<i32>()?;
-  let comment = blocking(context.pool(), move |conn| Comment::read(conn, id)).await??;
-  if !comment.local {
-    return Err(NotFound.into());
-  }
-
-  if !comment.deleted {
-    Ok(create_apub_response(
-      &comment.to_apub(context.pool()).await?,
-    ))
-  } else {
-    Ok(create_apub_tombstone_response(&comment.to_tombstone()?))
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl ToApub for Comment {
-  type Response = Note;
-
-  async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
-    let mut comment = Note::new();
-
-    let creator_id = self.creator_id;
-    let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
-
-    let post_id = self.post_id;
-    let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
-
-    let community_id = post.community_id;
-    let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
-    // Add a vector containing some important info to the "in_reply_to" field
-    // [post_ap_id, Option(parent_comment_ap_id)]
-    let mut in_reply_to_vec = vec![post.ap_id];
-
-    if let Some(parent_id) = self.parent_id {
-      let parent_comment = blocking(pool, move |conn| Comment::read(conn, parent_id)).await??;
-
-      in_reply_to_vec.push(parent_comment.ap_id);
-    }
-
-    comment
-      // Not needed when the Post is embedded in a collection (like for community outbox)
-      .set_context(activitystreams::context())
-      .set_id(Url::parse(&self.ap_id)?)
-      .set_published(convert_datetime(self.published))
-      .set_to(community.actor_id)
-      .set_many_in_reply_tos(in_reply_to_vec)
-      .set_content(self.content.to_owned())
-      .set_attributed_to(creator.actor_id);
-
-    if let Some(u) = self.updated {
-      comment.set_updated(convert_datetime(u));
-    }
-
-    Ok(comment)
-  }
-
-  fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
-    create_tombstone(self.deleted, &self.ap_id, self.updated, NoteType::Note)
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl FromApub for CommentForm {
-  type ApubType = Note;
-
-  /// Parse an ActivityPub note received from another instance into a Lemmy comment
-  async fn from_apub(
-    note: &Note,
-    context: &LemmyContext,
-    expected_domain: Option<Url>,
-  ) -> Result<CommentForm, LemmyError> {
-    let creator_actor_id = &note
-      .attributed_to()
-      .context(location_info!())?
-      .as_single_xsd_any_uri()
-      .context(location_info!())?;
-
-    let creator = get_or_fetch_and_upsert_user(creator_actor_id, context).await?;
-
-    let mut in_reply_tos = note
-      .in_reply_to()
-      .as_ref()
-      .context(location_info!())?
-      .as_many()
-      .context(location_info!())?
-      .iter()
-      .map(|i| i.as_xsd_any_uri().context(""));
-    let post_ap_id = in_reply_tos.next().context(location_info!())??;
-
-    // This post, or the parent comment might not yet exist on this server yet, fetch them.
-    let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
-
-    // The 2nd item, if it exists, is the parent comment apub_id
-    // For deeply nested comments, FromApub automatically gets called recursively
-    let parent_id: Option<i32> = match in_reply_tos.next() {
-      Some(parent_comment_uri) => {
-        let parent_comment_ap_id = &parent_comment_uri?;
-        let parent_comment =
-          get_or_fetch_and_insert_comment(&parent_comment_ap_id, context).await?;
-
-        Some(parent_comment.id)
-      }
-      None => None,
-    };
-    let content = note
-      .content()
-      .context(location_info!())?
-      .as_single_xsd_string()
-      .context(location_info!())?
-      .to_string();
-    let content_slurs_removed = remove_slurs(&content);
-
-    Ok(CommentForm {
-      creator_id: creator.id,
-      post_id: post.id,
-      parent_id,
-      content: content_slurs_removed,
-      removed: None,
-      read: None,
-      published: note.published().map(|u| u.to_owned().naive_local()),
-      updated: note.updated().map(|u| u.to_owned().naive_local()),
-      deleted: None,
-      ap_id: Some(check_actor_domain(note, expected_domain)?),
-      local: false,
-    })
-  }
-}
-
 #[async_trait::async_trait(?Send)]
 impl ApubObjectType for Comment {
   /// Send out information about a newly created comment, to the followers of the community.
@@ -518,3 +359,33 @@ async fn collect_non_local_mentions_and_addresses(
     tags,
   })
 }
+
+async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<Url, LemmyError> {
+  let fetch_url = format!(
+    "{}://{}/.well-known/webfinger?resource=acct:{}@{}",
+    Settings::get().get_protocol_string(),
+    mention.domain,
+    mention.name,
+    mention.domain
+  );
+  debug!("Fetching webfinger url: {}", &fetch_url);
+
+  let response = retry(|| client.get(&fetch_url).send()).await?;
+
+  let res: WebFingerResponse = response
+    .json()
+    .await
+    .map_err(|e| RecvError(e.to_string()))?;
+
+  let link = res
+    .links
+    .iter()
+    .find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
+    .ok_or_else(|| anyhow!("No application/activity+json link found."))?;
+  link
+    .href
+    .to_owned()
+    .map(|u| Url::parse(&u))
+    .transpose()?
+    .ok_or_else(|| anyhow!("No href found.").into())
+}
diff --git a/lemmy_apub/src/activities/send/community.rs b/lemmy_apub/src/activities/send/community.rs
new file mode 100644 (file)
index 0000000..ab34f33
--- /dev/null
@@ -0,0 +1,224 @@
+use crate::{
+  activities::send::generate_activity_id,
+  activity_queue::{send_activity_single_dest, send_to_community_followers},
+  check_is_apub_id_valid,
+  fetcher::get_or_fetch_and_upsert_actor,
+  ActorType,
+  ToApub,
+};
+use activitystreams::{
+  activity::{
+    kind::{AcceptType, AnnounceType, DeleteType, LikeType, RemoveType, UndoType},
+    Accept,
+    ActorAndObjectRefExt,
+    Announce,
+    Delete,
+    Follow,
+    Remove,
+    Undo,
+  },
+  base::{AnyBase, BaseExt, ExtendsExt},
+  object::ObjectExt,
+  public,
+};
+use anyhow::Context;
+use itertools::Itertools;
+use lemmy_db::{community::Community, community_view::CommunityFollowerView, user::User_, DbPool};
+use lemmy_structs::blocking;
+use lemmy_utils::{location_info, settings::Settings, LemmyError};
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[async_trait::async_trait(?Send)]
+impl ActorType for Community {
+  fn actor_id_str(&self) -> String {
+    self.actor_id.to_owned()
+  }
+
+  fn public_key(&self) -> Option<String> {
+    self.public_key.to_owned()
+  }
+  fn private_key(&self) -> Option<String> {
+    self.private_key.to_owned()
+  }
+
+  fn user_id(&self) -> i32 {
+    self.creator_id
+  }
+
+  async fn send_follow(
+    &self,
+    _follow_actor_id: &Url,
+    _context: &LemmyContext,
+  ) -> Result<(), LemmyError> {
+    unimplemented!()
+  }
+
+  async fn send_unfollow(
+    &self,
+    _follow_actor_id: &Url,
+    _context: &LemmyContext,
+  ) -> Result<(), LemmyError> {
+    unimplemented!()
+  }
+
+  /// As a local community, accept the follow request from a remote user.
+  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 actor = get_or_fetch_and_upsert_actor(actor_uri, context).await?;
+
+    let mut accept = Accept::new(self.actor_id.to_owned(), follow.into_any_base()?);
+    let to = actor.get_inbox_url()?;
+    accept
+      .set_context(activitystreams::context())
+      .set_id(generate_activity_id(AcceptType::Accept)?)
+      .set_to(to.clone());
+
+    send_activity_single_dest(accept, self, to, context).await?;
+    Ok(())
+  }
+
+  async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+    let group = self.to_apub(context.pool()).await?;
+
+    let mut delete = Delete::new(creator.actor_id.to_owned(), group.into_any_base()?);
+    delete
+      .set_context(activitystreams::context())
+      .set_id(generate_activity_id(DeleteType::Delete)?)
+      .set_to(public())
+      .set_many_ccs(vec![self.get_followers_url()?]);
+
+    send_to_community_followers(delete, self, context, None).await?;
+    Ok(())
+  }
+
+  async fn send_undo_delete(
+    &self,
+    creator: &User_,
+    context: &LemmyContext,
+  ) -> Result<(), LemmyError> {
+    let group = self.to_apub(context.pool()).await?;
+
+    let mut delete = Delete::new(creator.actor_id.to_owned(), group.into_any_base()?);
+    delete
+      .set_context(activitystreams::context())
+      .set_id(generate_activity_id(DeleteType::Delete)?)
+      .set_to(public())
+      .set_many_ccs(vec![self.get_followers_url()?]);
+
+    let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
+    undo
+      .set_context(activitystreams::context())
+      .set_id(generate_activity_id(UndoType::Undo)?)
+      .set_to(public())
+      .set_many_ccs(vec![self.get_followers_url()?]);
+
+    send_to_community_followers(undo, self, context, None).await?;
+    Ok(())
+  }
+
+  async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+    let group = self.to_apub(context.pool()).await?;
+
+    let mut remove = Remove::new(mod_.actor_id.to_owned(), group.into_any_base()?);
+    remove
+      .set_context(activitystreams::context())
+      .set_id(generate_activity_id(RemoveType::Remove)?)
+      .set_to(public())
+      .set_many_ccs(vec![self.get_followers_url()?]);
+
+    send_to_community_followers(remove, self, context, None).await?;
+    Ok(())
+  }
+
+  async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+    let group = self.to_apub(context.pool()).await?;
+
+    let mut remove = Remove::new(mod_.actor_id.to_owned(), group.into_any_base()?);
+    remove
+      .set_context(activitystreams::context())
+      .set_id(generate_activity_id(RemoveType::Remove)?)
+      .set_to(public())
+      .set_many_ccs(vec![self.get_followers_url()?]);
+
+    // Undo that fake activity
+    let mut undo = Undo::new(mod_.actor_id.to_owned(), remove.into_any_base()?);
+    undo
+      .set_context(activitystreams::context())
+      .set_id(generate_activity_id(LikeType::Like)?)
+      .set_to(public())
+      .set_many_ccs(vec![self.get_followers_url()?]);
+
+    send_to_community_followers(undo, self, context, None).await?;
+    Ok(())
+  }
+
+  async fn send_announce(
+    &self,
+    activity: AnyBase,
+    sender: &User_,
+    context: &LemmyContext,
+  ) -> Result<(), LemmyError> {
+    let mut announce = Announce::new(self.actor_id.to_owned(), activity);
+    announce
+      .set_context(activitystreams::context())
+      .set_id(generate_activity_id(AnnounceType::Announce)?)
+      .set_to(public())
+      .set_many_ccs(vec![self.get_followers_url()?]);
+
+    send_to_community_followers(
+      announce,
+      self,
+      context,
+      Some(sender.get_shared_inbox_url()?),
+    )
+    .await?;
+
+    Ok(())
+  }
+
+  /// For a given community, returns the inboxes of all followers.
+  ///
+  /// TODO: this function is very badly implemented, we should just store shared_inbox_url in
+  ///       CommunityFollowerView
+  async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError> {
+    let id = self.id;
+
+    let inboxes = blocking(pool, move |conn| {
+      CommunityFollowerView::for_community(conn, id)
+    })
+    .await??;
+    let inboxes = inboxes
+      .into_iter()
+      .filter(|i| !i.user_local)
+      .map(|u| -> Result<Url, LemmyError> {
+        let url = Url::parse(&u.user_actor_id)?;
+        let domain = url.domain().context(location_info!())?;
+        let port = if let Some(port) = url.port() {
+          format!(":{}", port)
+        } else {
+          "".to_string()
+        };
+        Ok(Url::parse(&format!(
+          "{}://{}{}/inbox",
+          Settings::get().get_protocol_string(),
+          domain,
+          port,
+        ))?)
+      })
+      .filter_map(Result::ok)
+      // Don't send to blocked instances
+      .filter(|inbox| check_is_apub_id_valid(inbox).is_ok())
+      .unique()
+      .collect();
+
+    Ok(inboxes)
+  }
+}
diff --git a/lemmy_apub/src/activities/send/mod.rs b/lemmy_apub/src/activities/send/mod.rs
new file mode 100644 (file)
index 0000000..22cc10f
--- /dev/null
@@ -0,0 +1,22 @@
+use lemmy_utils::settings::Settings;
+use url::{ParseError, Url};
+use uuid::Uuid;
+
+pub mod comment;
+pub mod community;
+pub mod post;
+pub mod private_message;
+pub mod user;
+
+fn generate_activity_id<T>(kind: T) -> Result<Url, ParseError>
+where
+  T: ToString,
+{
+  let id = format!(
+    "{}/receive/{}/{}",
+    Settings::get().get_protocol_and_hostname(),
+    kind.to_string().to_lowercase(),
+    Uuid::new_v4()
+  );
+  Url::parse(&id)
+}
similarity index 55%
rename from lemmy_apub/src/post.rs
rename to lemmy_apub/src/activities/send/post.rs
index 8dd8357dc85621052050ea6e199ddbb63916f5f0..f7c27964cb6b8fef533fac652002f0b0f72e6863 100644 (file)
@@ -1,17 +1,9 @@
 use crate::{
+  activities::send::generate_activity_id,
   activity_queue::send_to_community,
-  check_actor_domain,
-  create_apub_response,
-  create_apub_tombstone_response,
-  create_tombstone,
-  extensions::page_extension::PageExtension,
-  fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
-  generate_activity_id,
   ActorType,
   ApubLikeableType,
   ApubObjectType,
-  FromApub,
-  PageExt,
   ToApub,
 };
 use activitystreams::{
@@ -25,223 +17,13 @@ use activitystreams::{
     Undo,
     Update,
   },
-  object::{kind::PageType, Image, Page, Tombstone},
   prelude::*,
   public,
 };
-use activitystreams_ext::Ext1;
-use actix_web::{body::Body, web, HttpResponse};
-use anyhow::Context;
-use diesel::result::Error::NotFound;
-use lemmy_db::{
-  community::Community,
-  post::{Post, PostForm},
-  user::User_,
-  Crud,
-  DbPool,
-};
+use lemmy_db::{community::Community, post::Post, user::User_, Crud};
 use lemmy_structs::blocking;
-use lemmy_utils::{
-  location_info,
-  request::fetch_iframely_and_pictrs_data,
-  utils::{check_slurs, convert_datetime, remove_slurs},
-  LemmyError,
-};
+use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
-use serde::Deserialize;
-use url::Url;
-
-#[derive(Deserialize)]
-pub struct PostQuery {
-  post_id: String,
-}
-
-/// Return the post json over HTTP.
-pub async fn get_apub_post(
-  info: web::Path<PostQuery>,
-  context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
-  let id = info.post_id.parse::<i32>()?;
-  let post = blocking(context.pool(), move |conn| Post::read(conn, id)).await??;
-  if !post.local {
-    return Err(NotFound.into());
-  }
-
-  if !post.deleted {
-    Ok(create_apub_response(&post.to_apub(context.pool()).await?))
-  } else {
-    Ok(create_apub_tombstone_response(&post.to_tombstone()?))
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl ToApub for Post {
-  type Response = PageExt;
-
-  // Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
-  async fn to_apub(&self, pool: &DbPool) -> Result<PageExt, LemmyError> {
-    let mut page = Page::new();
-
-    let creator_id = self.creator_id;
-    let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
-
-    let community_id = self.community_id;
-    let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
-    page
-      // Not needed when the Post is embedded in a collection (like for community outbox)
-      // TODO: need to set proper context defining sensitive/commentsEnabled fields
-      // https://git.asonix.dog/Aardwolf/activitystreams/issues/5
-      .set_context(activitystreams::context())
-      .set_id(self.ap_id.parse::<Url>()?)
-      // Use summary field to be consistent with mastodon content warning.
-      // https://mastodon.xyz/@Louisa/103987265222901387.json
-      .set_summary(self.name.to_owned())
-      .set_published(convert_datetime(self.published))
-      .set_to(community.actor_id)
-      .set_attributed_to(creator.actor_id);
-
-    if let Some(body) = &self.body {
-      page.set_content(body.to_owned());
-    }
-
-    // TODO: hacky code because we get self.url == Some("")
-    // https://github.com/LemmyNet/lemmy/issues/602
-    let url = self.url.as_ref().filter(|u| !u.is_empty());
-    if let Some(u) = url {
-      page.set_url(u.to_owned());
-    }
-
-    if let Some(thumbnail_url) = &self.thumbnail_url {
-      let mut image = Image::new();
-      image.set_url(thumbnail_url.to_string());
-      page.set_image(image.into_any_base()?);
-    }
-
-    if let Some(u) = self.updated {
-      page.set_updated(convert_datetime(u));
-    }
-
-    let ext = PageExtension {
-      comments_enabled: !self.locked,
-      sensitive: self.nsfw,
-      stickied: self.stickied,
-    };
-    Ok(Ext1::new(page, ext))
-  }
-
-  fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
-    create_tombstone(self.deleted, &self.ap_id, self.updated, PageType::Page)
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl FromApub for PostForm {
-  type ApubType = PageExt;
-
-  /// Parse an ActivityPub page received from another instance into a Lemmy post.
-  async fn from_apub(
-    page: &PageExt,
-    context: &LemmyContext,
-    expected_domain: Option<Url>,
-  ) -> Result<PostForm, LemmyError> {
-    let ext = &page.ext_one;
-    let creator_actor_id = page
-      .inner
-      .attributed_to()
-      .as_ref()
-      .context(location_info!())?
-      .as_single_xsd_any_uri()
-      .context(location_info!())?;
-
-    let creator = get_or_fetch_and_upsert_user(creator_actor_id, context).await?;
-
-    let community_actor_id = page
-      .inner
-      .to()
-      .as_ref()
-      .context(location_info!())?
-      .as_single_xsd_any_uri()
-      .context(location_info!())?;
-
-    let community = get_or_fetch_and_upsert_community(community_actor_id, context).await?;
-
-    let thumbnail_url = match &page.inner.image() {
-      Some(any_image) => Image::from_any_base(
-        any_image
-          .to_owned()
-          .as_one()
-          .context(location_info!())?
-          .to_owned(),
-      )?
-      .context(location_info!())?
-      .url()
-      .context(location_info!())?
-      .as_single_xsd_any_uri()
-      .map(|u| u.to_string()),
-      None => None,
-    };
-    let url = page
-      .inner
-      .url()
-      .map(|u| u.as_single_xsd_any_uri())
-      .flatten()
-      .map(|s| s.to_string());
-
-    let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
-      if let Some(url) = &url {
-        fetch_iframely_and_pictrs_data(context.client(), Some(url.to_owned())).await
-      } else {
-        (None, None, None, thumbnail_url)
-      };
-
-    let name = page
-      .inner
-      .summary()
-      .as_ref()
-      .context(location_info!())?
-      .as_single_xsd_string()
-      .context(location_info!())?
-      .to_string();
-    let body = page
-      .inner
-      .content()
-      .as_ref()
-      .map(|c| c.as_single_xsd_string())
-      .flatten()
-      .map(|s| s.to_string());
-    check_slurs(&name)?;
-    let body_slurs_removed = body.map(|b| remove_slurs(&b));
-    Ok(PostForm {
-      name,
-      url,
-      body: body_slurs_removed,
-      creator_id: creator.id,
-      community_id: community.id,
-      removed: None,
-      locked: Some(!ext.comments_enabled),
-      published: page
-        .inner
-        .published()
-        .as_ref()
-        .map(|u| u.to_owned().naive_local()),
-      updated: page
-        .inner
-        .updated()
-        .as_ref()
-        .map(|u| u.to_owned().naive_local()),
-      deleted: None,
-      nsfw: ext.sensitive,
-      stickied: Some(ext.stickied),
-      embed_title: iframely_title,
-      embed_description: iframely_description,
-      embed_html: iframely_html,
-      thumbnail_url: pictrs_thumbnail,
-      ap_id: Some(check_actor_domain(page, expected_domain)?),
-      local: false,
-    })
-  }
-}
 
 #[async_trait::async_trait(?Send)]
 impl ApubObjectType for Post {
similarity index 55%
rename from lemmy_apub/src/private_message.rs
rename to lemmy_apub/src/activities/send/private_message.rs
index fd8e6c6b015467d80c06a6c3fa2641686923f769..8c3f5aa9ce91be982b1b166d3112a73fa9279f35 100644 (file)
@@ -1,13 +1,8 @@
 use crate::{
+  activities::send::generate_activity_id,
   activity_queue::send_activity_single_dest,
-  check_actor_domain,
-  check_is_apub_id_valid,
-  create_tombstone,
-  fetcher::get_or_fetch_and_upsert_user,
-  generate_activity_id,
   ActorType,
   ApubObjectType,
-  FromApub,
   ToApub,
 };
 use activitystreams::{
@@ -18,100 +13,12 @@ use activitystreams::{
     Undo,
     Update,
   },
-  object::{kind::NoteType, Note, Tombstone},
   prelude::*,
 };
-use anyhow::Context;
-use lemmy_db::{
-  private_message::{PrivateMessage, PrivateMessageForm},
-  user::User_,
-  Crud,
-  DbPool,
-};
+use lemmy_db::{private_message::PrivateMessage, user::User_, Crud};
 use lemmy_structs::blocking;
-use lemmy_utils::{location_info, utils::convert_datetime, LemmyError};
+use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
-use url::Url;
-
-#[async_trait::async_trait(?Send)]
-impl ToApub for PrivateMessage {
-  type Response = Note;
-
-  async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
-    let mut private_message = Note::new();
-
-    let creator_id = self.creator_id;
-    let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
-
-    let recipient_id = self.recipient_id;
-    let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
-
-    private_message
-      .set_context(activitystreams::context())
-      .set_id(Url::parse(&self.ap_id.to_owned())?)
-      .set_published(convert_datetime(self.published))
-      .set_content(self.content.to_owned())
-      .set_to(recipient.actor_id)
-      .set_attributed_to(creator.actor_id);
-
-    if let Some(u) = self.updated {
-      private_message.set_updated(convert_datetime(u));
-    }
-
-    Ok(private_message)
-  }
-
-  fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
-    create_tombstone(self.deleted, &self.ap_id, self.updated, NoteType::Note)
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl FromApub for PrivateMessageForm {
-  type ApubType = Note;
-
-  /// Parse an ActivityPub note received from another instance into a Lemmy Private message
-  async fn from_apub(
-    note: &Note,
-    context: &LemmyContext,
-    expected_domain: Option<Url>,
-  ) -> Result<PrivateMessageForm, LemmyError> {
-    let creator_actor_id = note
-      .attributed_to()
-      .context(location_info!())?
-      .clone()
-      .single_xsd_any_uri()
-      .context(location_info!())?;
-
-    let creator = get_or_fetch_and_upsert_user(&creator_actor_id, context).await?;
-    let recipient_actor_id = note
-      .to()
-      .context(location_info!())?
-      .clone()
-      .single_xsd_any_uri()
-      .context(location_info!())?;
-    let recipient = get_or_fetch_and_upsert_user(&recipient_actor_id, context).await?;
-    let ap_id = note.id_unchecked().context(location_info!())?.to_string();
-    check_is_apub_id_valid(&Url::parse(&ap_id)?)?;
-
-    Ok(PrivateMessageForm {
-      creator_id: creator.id,
-      recipient_id: recipient.id,
-      content: note
-        .content()
-        .context(location_info!())?
-        .as_single_xsd_string()
-        .context(location_info!())?
-        .to_string(),
-      published: note.published().map(|u| u.to_owned().naive_local()),
-      updated: note.updated().map(|u| u.to_owned().naive_local()),
-      deleted: None,
-      read: None,
-      ap_id: Some(check_actor_domain(note, expected_domain)?),
-      local: false,
-    })
-  }
-}
 
 #[async_trait::async_trait(?Send)]
 impl ApubObjectType for PrivateMessage {
diff --git a/lemmy_apub/src/activities/send/user.rs b/lemmy_apub/src/activities/send/user.rs
new file mode 100644 (file)
index 0000000..08f74d9
--- /dev/null
@@ -0,0 +1,122 @@
+use crate::{
+  activities::send::generate_activity_id,
+  activity_queue::send_activity_single_dest,
+  fetcher::get_or_fetch_and_upsert_actor,
+  ActorType,
+};
+use activitystreams::{
+  activity::{
+    kind::{FollowType, UndoType},
+    Follow,
+    Undo,
+  },
+  base::{AnyBase, BaseExt, ExtendsExt},
+};
+use lemmy_db::{user::User_, DbPool};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[async_trait::async_trait(?Send)]
+impl ActorType for User_ {
+  fn actor_id_str(&self) -> String {
+    self.actor_id.to_owned()
+  }
+
+  fn public_key(&self) -> Option<String> {
+    self.public_key.to_owned()
+  }
+
+  fn private_key(&self) -> Option<String> {
+    self.private_key.to_owned()
+  }
+
+  fn user_id(&self) -> i32 {
+    self.id
+  }
+
+  /// As a given local user, send out a follow request to a remote community.
+  async fn send_follow(
+    &self,
+    follow_actor_id: &Url,
+    context: &LemmyContext,
+  ) -> Result<(), LemmyError> {
+    let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id.as_str());
+    follow
+      .set_context(activitystreams::context())
+      .set_id(generate_activity_id(FollowType::Follow)?);
+    let follow_actor = get_or_fetch_and_upsert_actor(follow_actor_id, context).await?;
+    let to = follow_actor.get_inbox_url()?;
+
+    send_activity_single_dest(follow, self, to, context).await?;
+    Ok(())
+  }
+
+  async fn send_unfollow(
+    &self,
+    follow_actor_id: &Url,
+    context: &LemmyContext,
+  ) -> Result<(), LemmyError> {
+    let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id.as_str());
+    follow
+      .set_context(activitystreams::context())
+      .set_id(generate_activity_id(FollowType::Follow)?);
+    let follow_actor = get_or_fetch_and_upsert_actor(follow_actor_id, context).await?;
+
+    let to = follow_actor.get_inbox_url()?;
+
+    // Undo that fake activity
+    let mut undo = Undo::new(Url::parse(&self.actor_id)?, follow.into_any_base()?);
+    undo
+      .set_context(activitystreams::context())
+      .set_id(generate_activity_id(UndoType::Undo)?);
+
+    send_activity_single_dest(undo, self, to, context).await?;
+    Ok(())
+  }
+
+  async fn send_accept_follow(
+    &self,
+    _follow: Follow,
+    _context: &LemmyContext,
+  ) -> Result<(), LemmyError> {
+    unimplemented!()
+  }
+
+  async fn send_delete(&self, _creator: &User_, _context: &LemmyContext) -> Result<(), LemmyError> {
+    unimplemented!()
+  }
+
+  async fn send_undo_delete(
+    &self,
+    _creator: &User_,
+    _context: &LemmyContext,
+  ) -> Result<(), LemmyError> {
+    unimplemented!()
+  }
+
+  async fn send_remove(&self, _creator: &User_, _context: &LemmyContext) -> Result<(), LemmyError> {
+    unimplemented!()
+  }
+
+  async fn send_undo_remove(
+    &self,
+    _creator: &User_,
+    _context: &LemmyContext,
+  ) -> Result<(), LemmyError> {
+    unimplemented!()
+  }
+
+  async fn send_announce(
+    &self,
+    _activity: AnyBase,
+    _sender: &User_,
+    _context: &LemmyContext,
+  ) -> Result<(), LemmyError> {
+    unimplemented!()
+  }
+
+  async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result<Vec<Url>, LemmyError> {
+    unimplemented!()
+  }
+}
index 0e018c8d829037f8c8669efe755b56468e878f0f..379618f298c2bb35543cd75ffde6f708868f4ec1 100644 (file)
@@ -1,6 +1,5 @@
 use crate::{
   check_is_apub_id_valid,
-  community::do_announce,
   extensions::signatures::sign_and_send,
   insert_activity,
   ActorType,
@@ -112,7 +111,9 @@ where
 {
   // if this is a local community, we need to do an announce from the community instead
   if community.local {
-    do_announce(activity.into_any_base()?, &community, creator, context).await?;
+    community
+      .send_announce(activity.into_any_base()?, creator, context)
+      .await?;
   } else {
     let inbox = community.get_shared_inbox_url()?;
     check_is_apub_id_valid(&inbox)?;
diff --git a/lemmy_apub/src/community.rs b/lemmy_apub/src/community.rs
deleted file mode 100644 (file)
index 474a63f..0000000
+++ /dev/null
@@ -1,496 +0,0 @@
-use crate::{
-  activity_queue::{send_activity_single_dest, send_to_community_followers},
-  check_actor_domain,
-  check_is_apub_id_valid,
-  create_apub_response,
-  create_apub_tombstone_response,
-  create_tombstone,
-  extensions::group_extensions::GroupExtension,
-  fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_user},
-  generate_activity_id,
-  ActorType,
-  FromApub,
-  GroupExt,
-  ToApub,
-};
-use activitystreams::{
-  activity::{
-    kind::{AcceptType, AnnounceType, DeleteType, LikeType, RemoveType, UndoType},
-    Accept,
-    Announce,
-    Delete,
-    Follow,
-    Remove,
-    Undo,
-  },
-  actor::{kind::GroupType, ApActor, Endpoints, Group},
-  base::{AnyBase, BaseExt},
-  collection::{OrderedCollection, UnorderedCollection},
-  object::{Image, Tombstone},
-  prelude::*,
-  public,
-};
-use activitystreams_ext::Ext2;
-use actix_web::{body::Body, web, HttpResponse};
-use anyhow::Context;
-use itertools::Itertools;
-use lemmy_db::{
-  community::{Community, CommunityForm},
-  community_view::{CommunityFollowerView, CommunityModeratorView},
-  naive_now,
-  post::Post,
-  user::User_,
-  DbPool,
-};
-use lemmy_structs::blocking;
-use lemmy_utils::{
-  location_info,
-  settings::Settings,
-  utils::{check_slurs, check_slurs_opt, convert_datetime},
-  LemmyError,
-};
-use lemmy_websocket::LemmyContext;
-use serde::Deserialize;
-use url::Url;
-
-#[derive(Deserialize)]
-pub struct CommunityQuery {
-  community_name: String,
-}
-
-#[async_trait::async_trait(?Send)]
-impl ToApub for Community {
-  type Response = GroupExt;
-
-  // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network.
-  async fn to_apub(&self, pool: &DbPool) -> Result<GroupExt, LemmyError> {
-    // The attributed to, is an ordered vector with the creator actor_ids first,
-    // then the rest of the moderators
-    // TODO Technically the instance admins can mod the community, but lets
-    // ignore that for now
-    let id = self.id;
-    let moderators = blocking(pool, move |conn| {
-      CommunityModeratorView::for_community(&conn, id)
-    })
-    .await??;
-    let moderators: Vec<String> = moderators.into_iter().map(|m| m.user_actor_id).collect();
-
-    let mut group = Group::new();
-    group
-      .set_context(activitystreams::context())
-      .set_id(Url::parse(&self.actor_id)?)
-      .set_name(self.name.to_owned())
-      .set_published(convert_datetime(self.published))
-      .set_many_attributed_tos(moderators);
-
-    if let Some(u) = self.updated.to_owned() {
-      group.set_updated(convert_datetime(u));
-    }
-    if let Some(d) = self.description.to_owned() {
-      // TODO: this should be html, also add source field with raw markdown
-      //       -> same for post.content and others
-      group.set_content(d);
-    }
-
-    if let Some(icon) = &self.icon {
-      let mut image = Image::new();
-      image.set_url(icon.to_owned());
-      group.set_icon(image.into_any_base()?);
-    }
-
-    if let Some(banner_url) = &self.banner {
-      let mut image = Image::new();
-      image.set_url(banner_url.to_owned());
-      group.set_image(image.into_any_base()?);
-    }
-
-    let mut ap_actor = ApActor::new(self.get_inbox_url()?, group);
-    ap_actor
-      .set_preferred_username(self.title.to_owned())
-      .set_outbox(self.get_outbox_url()?)
-      .set_followers(self.get_followers_url()?)
-      .set_endpoints(Endpoints {
-        shared_inbox: Some(self.get_shared_inbox_url()?),
-        ..Default::default()
-      });
-
-    let nsfw = self.nsfw;
-    let category_id = self.category_id;
-    let group_extension = blocking(pool, move |conn| {
-      GroupExtension::new(conn, category_id, nsfw)
-    })
-    .await??;
-
-    Ok(Ext2::new(
-      ap_actor,
-      group_extension,
-      self.get_public_key_ext()?,
-    ))
-  }
-
-  fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
-    create_tombstone(self.deleted, &self.actor_id, self.updated, GroupType::Group)
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl ActorType for Community {
-  fn actor_id_str(&self) -> String {
-    self.actor_id.to_owned()
-  }
-
-  fn public_key(&self) -> Option<String> {
-    self.public_key.to_owned()
-  }
-  fn private_key(&self) -> Option<String> {
-    self.private_key.to_owned()
-  }
-
-  /// As a local community, accept the follow request from a remote user.
-  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 actor = get_or_fetch_and_upsert_actor(actor_uri, context).await?;
-
-    let mut accept = Accept::new(self.actor_id.to_owned(), follow.into_any_base()?);
-    let to = actor.get_inbox_url()?;
-    accept
-      .set_context(activitystreams::context())
-      .set_id(generate_activity_id(AcceptType::Accept)?)
-      .set_to(to.clone());
-
-    send_activity_single_dest(accept, self, to, context).await?;
-    Ok(())
-  }
-
-  async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
-    let group = self.to_apub(context.pool()).await?;
-
-    let mut delete = Delete::new(creator.actor_id.to_owned(), group.into_any_base()?);
-    delete
-      .set_context(activitystreams::context())
-      .set_id(generate_activity_id(DeleteType::Delete)?)
-      .set_to(public())
-      .set_many_ccs(vec![self.get_followers_url()?]);
-
-    send_to_community_followers(delete, self, context, None).await?;
-    Ok(())
-  }
-
-  async fn send_undo_delete(
-    &self,
-    creator: &User_,
-    context: &LemmyContext,
-  ) -> Result<(), LemmyError> {
-    let group = self.to_apub(context.pool()).await?;
-
-    let mut delete = Delete::new(creator.actor_id.to_owned(), group.into_any_base()?);
-    delete
-      .set_context(activitystreams::context())
-      .set_id(generate_activity_id(DeleteType::Delete)?)
-      .set_to(public())
-      .set_many_ccs(vec![self.get_followers_url()?]);
-
-    let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
-    undo
-      .set_context(activitystreams::context())
-      .set_id(generate_activity_id(UndoType::Undo)?)
-      .set_to(public())
-      .set_many_ccs(vec![self.get_followers_url()?]);
-
-    send_to_community_followers(undo, self, context, None).await?;
-    Ok(())
-  }
-
-  async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
-    let group = self.to_apub(context.pool()).await?;
-
-    let mut remove = Remove::new(mod_.actor_id.to_owned(), group.into_any_base()?);
-    remove
-      .set_context(activitystreams::context())
-      .set_id(generate_activity_id(RemoveType::Remove)?)
-      .set_to(public())
-      .set_many_ccs(vec![self.get_followers_url()?]);
-
-    send_to_community_followers(remove, self, context, None).await?;
-    Ok(())
-  }
-
-  async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
-    let group = self.to_apub(context.pool()).await?;
-
-    let mut remove = Remove::new(mod_.actor_id.to_owned(), group.into_any_base()?);
-    remove
-      .set_context(activitystreams::context())
-      .set_id(generate_activity_id(RemoveType::Remove)?)
-      .set_to(public())
-      .set_many_ccs(vec![self.get_followers_url()?]);
-
-    // Undo that fake activity
-    let mut undo = Undo::new(mod_.actor_id.to_owned(), remove.into_any_base()?);
-    undo
-      .set_context(activitystreams::context())
-      .set_id(generate_activity_id(LikeType::Like)?)
-      .set_to(public())
-      .set_many_ccs(vec![self.get_followers_url()?]);
-
-    send_to_community_followers(undo, self, context, None).await?;
-    Ok(())
-  }
-
-  /// For a given community, returns the inboxes of all followers.
-  ///
-  /// TODO: this function is very badly implemented, we should just store shared_inbox_url in
-  ///       CommunityFollowerView
-  async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError> {
-    let id = self.id;
-
-    let inboxes = blocking(pool, move |conn| {
-      CommunityFollowerView::for_community(conn, id)
-    })
-    .await??;
-    let inboxes = inboxes
-      .into_iter()
-      .filter(|i| !i.user_local)
-      .map(|u| -> Result<Url, LemmyError> {
-        let url = Url::parse(&u.user_actor_id)?;
-        let domain = url.domain().context(location_info!())?;
-        let port = if let Some(port) = url.port() {
-          format!(":{}", port)
-        } else {
-          "".to_string()
-        };
-        Ok(Url::parse(&format!(
-          "{}://{}{}/inbox",
-          Settings::get().get_protocol_string(),
-          domain,
-          port,
-        ))?)
-      })
-      .filter_map(Result::ok)
-      // Don't send to blocked instances
-      .filter(|inbox| check_is_apub_id_valid(inbox).is_ok())
-      .unique()
-      .collect();
-
-    Ok(inboxes)
-  }
-
-  async fn send_follow(
-    &self,
-    _follow_actor_id: &Url,
-    _context: &LemmyContext,
-  ) -> Result<(), LemmyError> {
-    unimplemented!()
-  }
-
-  async fn send_unfollow(
-    &self,
-    _follow_actor_id: &Url,
-    _context: &LemmyContext,
-  ) -> Result<(), LemmyError> {
-    unimplemented!()
-  }
-
-  fn user_id(&self) -> i32 {
-    self.creator_id
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl FromApub for CommunityForm {
-  type ApubType = GroupExt;
-
-  /// Parse an ActivityPub group received from another instance into a Lemmy community.
-  async fn from_apub(
-    group: &GroupExt,
-    context: &LemmyContext,
-    expected_domain: Option<Url>,
-  ) -> Result<Self, LemmyError> {
-    let creator_and_moderator_uris = group.inner.attributed_to().context(location_info!())?;
-    let creator_uri = creator_and_moderator_uris
-      .as_many()
-      .context(location_info!())?
-      .iter()
-      .next()
-      .context(location_info!())?
-      .as_xsd_any_uri()
-      .context(location_info!())?;
-
-    let creator = get_or_fetch_and_upsert_user(creator_uri, context).await?;
-    let name = group
-      .inner
-      .name()
-      .context(location_info!())?
-      .as_one()
-      .context(location_info!())?
-      .as_xsd_string()
-      .context(location_info!())?
-      .to_string();
-    let title = group
-      .inner
-      .preferred_username()
-      .context(location_info!())?
-      .to_string();
-    // TODO: should be parsed as html and tags like <script> removed (or use markdown source)
-    //       -> same for post.content etc
-    let description = group
-      .inner
-      .content()
-      .map(|s| s.as_single_xsd_string())
-      .flatten()
-      .map(|s| s.to_string());
-    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_string()),
-      ),
-      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_string()),
-      ),
-      None => None,
-    };
-
-    Ok(CommunityForm {
-      name,
-      title,
-      description,
-      category_id: group.ext_one.category.identifier.parse::<i32>()?,
-      creator_id: creator.id,
-      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: group.ext_one.sensitive,
-      actor_id: Some(check_actor_domain(group, expected_domain)?),
-      local: 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,
-    })
-  }
-}
-
-/// Return the community json over HTTP.
-pub async fn get_apub_community_http(
-  info: web::Path<CommunityQuery>,
-  context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
-  let community = blocking(context.pool(), move |conn| {
-    Community::read_from_name(conn, &info.community_name)
-  })
-  .await??;
-
-  if !community.deleted {
-    let apub = community.to_apub(context.pool()).await?;
-
-    Ok(create_apub_response(&apub))
-  } else {
-    Ok(create_apub_tombstone_response(&community.to_tombstone()?))
-  }
-}
-
-/// Returns an empty followers collection, only populating the size (for privacy).
-pub async fn get_apub_community_followers(
-  info: web::Path<CommunityQuery>,
-  context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
-  let community = blocking(context.pool(), move |conn| {
-    Community::read_from_name(&conn, &info.community_name)
-  })
-  .await??;
-
-  let community_id = community.id;
-  let community_followers = blocking(context.pool(), move |conn| {
-    CommunityFollowerView::for_community(&conn, community_id)
-  })
-  .await??;
-
-  let mut collection = UnorderedCollection::new();
-  collection
-    .set_context(activitystreams::context())
-    .set_id(community.get_followers_url()?)
-    .set_total_items(community_followers.len() as u64);
-  Ok(create_apub_response(&collection))
-}
-
-pub async fn get_apub_community_outbox(
-  info: web::Path<CommunityQuery>,
-  context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
-  let community = blocking(context.pool(), move |conn| {
-    Community::read_from_name(&conn, &info.community_name)
-  })
-  .await??;
-
-  let community_id = community.id;
-  let posts = blocking(context.pool(), move |conn| {
-    Post::list_for_community(conn, community_id)
-  })
-  .await??;
-
-  let mut pages: Vec<AnyBase> = vec![];
-  for p in posts {
-    pages.push(p.to_apub(context.pool()).await?.into_any_base()?);
-  }
-
-  let len = pages.len();
-  let mut collection = OrderedCollection::new();
-  collection
-    .set_many_items(pages)
-    .set_context(activitystreams::context())
-    .set_id(community.get_outbox_url()?)
-    .set_total_items(len as u64);
-  Ok(create_apub_response(&collection))
-}
-
-pub async fn do_announce(
-  activity: AnyBase,
-  community: &Community,
-  sender: &User_,
-  context: &LemmyContext,
-) -> Result<(), LemmyError> {
-  let mut announce = Announce::new(community.actor_id.to_owned(), activity);
-  announce
-    .set_context(activitystreams::context())
-    .set_id(generate_activity_id(AnnounceType::Announce)?)
-    .set_to(public())
-    .set_many_ccs(vec![community.get_followers_url()?]);
-
-  send_to_community_followers(
-    announce,
-    community,
-    context,
-    Some(sender.get_shared_inbox_url()?),
-  )
-  .await?;
-
-  Ok(())
-}
index 58b6a1bf50e9d49482669000bce350686c099dbe..bd85ddc8eaf0c93916fc269579352fbbe47ff199 100644 (file)
@@ -44,10 +44,7 @@ static ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG: i64 = 10;
 
 /// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation,
 /// timeouts etc.
-pub async fn fetch_remote_object<Response>(
-  client: &Client,
-  url: &Url,
-) -> Result<Response, LemmyError>
+async fn fetch_remote_object<Response>(client: &Client, url: &Url) -> Result<Response, LemmyError>
 where
   Response: for<'de> Deserialize<'de>,
 {
@@ -194,7 +191,7 @@ pub async fn search_by_apub_id(
   Ok(response)
 }
 
-pub async fn get_or_fetch_and_upsert_actor(
+pub(crate) async fn get_or_fetch_and_upsert_actor(
   apub_id: &Url,
   context: &LemmyContext,
 ) -> Result<Box<dyn ActorType>, LemmyError> {
@@ -207,7 +204,7 @@ pub async fn get_or_fetch_and_upsert_actor(
 }
 
 /// Check if a remote user exists, create if not found, if its too old update it.Fetch a user, insert/update it in the database and return the user.
-pub async fn get_or_fetch_and_upsert_user(
+pub(crate) async fn get_or_fetch_and_upsert_user(
   apub_id: &Url,
   context: &LemmyContext,
 ) -> Result<User_, LemmyError> {
@@ -259,7 +256,7 @@ fn should_refetch_actor(last_refreshed: NaiveDateTime) -> bool {
 }
 
 /// Check if a remote community exists, create if not found, if its too old update it.Fetch a community, insert/update it in the database and return the community.
-pub async fn get_or_fetch_and_upsert_community(
+pub(crate) async fn get_or_fetch_and_upsert_community(
   apub_id: &Url,
   context: &LemmyContext,
 ) -> Result<Community, LemmyError> {
@@ -361,7 +358,7 @@ async fn fetch_remote_community(
   Ok(community)
 }
 
-pub async fn get_or_fetch_and_insert_post(
+pub(crate) async fn get_or_fetch_and_insert_post(
   post_ap_id: &Url,
   context: &LemmyContext,
 ) -> Result<Post, LemmyError> {
@@ -386,7 +383,7 @@ pub async fn get_or_fetch_and_insert_post(
   }
 }
 
-pub async fn get_or_fetch_and_insert_comment(
+pub(crate) async fn get_or_fetch_and_insert_comment(
   comment_ap_id: &Url,
   context: &LemmyContext,
 ) -> Result<Comment, LemmyError> {
diff --git a/lemmy_apub/src/http/comment.rs b/lemmy_apub/src/http/comment.rs
new file mode 100644 (file)
index 0000000..040be2b
--- /dev/null
@@ -0,0 +1,36 @@
+use crate::{
+  http::{create_apub_response, create_apub_tombstone_response},
+  ToApub,
+};
+use actix_web::{body::Body, web, web::Path, HttpResponse};
+use diesel::result::Error::NotFound;
+use lemmy_db::{comment::Comment, Crud};
+use lemmy_structs::blocking;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use serde::Deserialize;
+
+#[derive(Deserialize)]
+pub struct CommentQuery {
+  comment_id: String,
+}
+
+/// Return the post json over HTTP.
+pub async fn get_apub_comment(
+  info: Path<CommentQuery>,
+  context: web::Data<LemmyContext>,
+) -> Result<HttpResponse<Body>, LemmyError> {
+  let id = info.comment_id.parse::<i32>()?;
+  let comment = blocking(context.pool(), move |conn| Comment::read(conn, id)).await??;
+  if !comment.local {
+    return Err(NotFound.into());
+  }
+
+  if !comment.deleted {
+    Ok(create_apub_response(
+      &comment.to_apub(context.pool()).await?,
+    ))
+  } else {
+    Ok(create_apub_tombstone_response(&comment.to_tombstone()?))
+  }
+}
diff --git a/lemmy_apub/src/http/community.rs b/lemmy_apub/src/http/community.rs
new file mode 100644 (file)
index 0000000..0559536
--- /dev/null
@@ -0,0 +1,93 @@
+use crate::{
+  http::{create_apub_response, create_apub_tombstone_response},
+  ActorType,
+  ToApub,
+};
+use activitystreams::{
+  base::{AnyBase, BaseExt, ExtendsExt},
+  collection::{CollectionExt, OrderedCollection, UnorderedCollection},
+};
+use actix_web::{body::Body, web, HttpResponse};
+use lemmy_db::{community::Community, community_view::CommunityFollowerView, post::Post};
+use lemmy_structs::blocking;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use serde::Deserialize;
+
+#[derive(Deserialize)]
+pub struct CommunityQuery {
+  community_name: String,
+}
+
+/// Return the community json over HTTP.
+pub async fn get_apub_community_http(
+  info: web::Path<CommunityQuery>,
+  context: web::Data<LemmyContext>,
+) -> Result<HttpResponse<Body>, LemmyError> {
+  let community = blocking(context.pool(), move |conn| {
+    Community::read_from_name(conn, &info.community_name)
+  })
+  .await??;
+
+  if !community.deleted {
+    let apub = community.to_apub(context.pool()).await?;
+
+    Ok(create_apub_response(&apub))
+  } else {
+    Ok(create_apub_tombstone_response(&community.to_tombstone()?))
+  }
+}
+
+/// Returns an empty followers collection, only populating the size (for privacy).
+pub async fn get_apub_community_followers(
+  info: web::Path<CommunityQuery>,
+  context: web::Data<LemmyContext>,
+) -> Result<HttpResponse<Body>, LemmyError> {
+  let community = blocking(context.pool(), move |conn| {
+    Community::read_from_name(&conn, &info.community_name)
+  })
+  .await??;
+
+  let community_id = community.id;
+  let community_followers = blocking(context.pool(), move |conn| {
+    CommunityFollowerView::for_community(&conn, community_id)
+  })
+  .await??;
+
+  let mut collection = UnorderedCollection::new();
+  collection
+    .set_context(activitystreams::context())
+    .set_id(community.get_followers_url()?)
+    .set_total_items(community_followers.len() as u64);
+  Ok(create_apub_response(&collection))
+}
+
+pub async fn get_apub_community_outbox(
+  info: web::Path<CommunityQuery>,
+  context: web::Data<LemmyContext>,
+) -> Result<HttpResponse<Body>, LemmyError> {
+  let community = blocking(context.pool(), move |conn| {
+    Community::read_from_name(&conn, &info.community_name)
+  })
+  .await??;
+
+  let community_id = community.id;
+  let posts = blocking(context.pool(), move |conn| {
+    Post::list_for_community(conn, community_id)
+  })
+  .await??;
+
+  let mut pages: Vec<AnyBase> = vec![];
+  for p in posts {
+    pages.push(p.to_apub(context.pool()).await?.into_any_base()?);
+  }
+
+  let len = pages.len();
+  let mut collection = OrderedCollection::new();
+  collection
+    .set_many_items(pages)
+    .set_context(activitystreams::context())
+    .set_id(community.get_outbox_url()?)
+    .set_total_items(len as u64);
+  Ok(create_apub_response(&collection))
+}
diff --git a/lemmy_apub/src/http/mod.rs b/lemmy_apub/src/http/mod.rs
new file mode 100644 (file)
index 0000000..89d0b33
--- /dev/null
@@ -0,0 +1,28 @@
+use crate::APUB_JSON_CONTENT_TYPE;
+use actix_web::{body::Body, HttpResponse};
+use serde::Serialize;
+
+pub mod comment;
+pub mod community;
+pub mod post;
+pub mod user;
+
+/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
+/// headers.
+fn create_apub_response<T>(data: &T) -> HttpResponse<Body>
+where
+  T: Serialize,
+{
+  HttpResponse::Ok()
+    .content_type(APUB_JSON_CONTENT_TYPE)
+    .json(data)
+}
+
+fn create_apub_tombstone_response<T>(data: &T) -> HttpResponse<Body>
+where
+  T: Serialize,
+{
+  HttpResponse::Gone()
+    .content_type(APUB_JSON_CONTENT_TYPE)
+    .json(data)
+}
diff --git a/lemmy_apub/src/http/post.rs b/lemmy_apub/src/http/post.rs
new file mode 100644 (file)
index 0000000..b3dd8ce
--- /dev/null
@@ -0,0 +1,34 @@
+use crate::{
+  http::{create_apub_response, create_apub_tombstone_response},
+  ToApub,
+};
+use actix_web::{body::Body, web, HttpResponse};
+use diesel::result::Error::NotFound;
+use lemmy_db::post::Post;
+use lemmy_structs::blocking;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use serde::Deserialize;
+
+#[derive(Deserialize)]
+pub struct PostQuery {
+  post_id: String,
+}
+
+/// Return the post json over HTTP.
+pub async fn get_apub_post(
+  info: web::Path<PostQuery>,
+  context: web::Data<LemmyContext>,
+) -> Result<HttpResponse<Body>, LemmyError> {
+  let id = info.post_id.parse::<i32>()?;
+  let post = blocking(context.pool(), move |conn| Post::read(conn, id)).await??;
+  if !post.local {
+    return Err(NotFound.into());
+  }
+
+  if !post.deleted {
+    Ok(create_apub_response(&post.to_apub(context.pool()).await?))
+  } else {
+    Ok(create_apub_tombstone_response(&post.to_tombstone()?))
+  }
+}
diff --git a/lemmy_apub/src/http/user.rs b/lemmy_apub/src/http/user.rs
new file mode 100644 (file)
index 0000000..9158eed
--- /dev/null
@@ -0,0 +1,26 @@
+use crate::{http::create_apub_response, ToApub};
+use actix_web::{body::Body, web, HttpResponse};
+use lemmy_db::user::User_;
+use lemmy_structs::blocking;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use serde::Deserialize;
+
+#[derive(Deserialize)]
+pub struct UserQuery {
+  user_name: String,
+}
+
+/// Return the user json over HTTP.
+pub async fn get_apub_user_http(
+  info: web::Path<UserQuery>,
+  context: web::Data<LemmyContext>,
+) -> Result<HttpResponse<Body>, LemmyError> {
+  let user_name = info.into_inner().user_name;
+  let user = blocking(context.pool(), move |conn| {
+    User_::find_by_email_or_username(conn, &user_name)
+  })
+  .await??;
+  let u = user.to_apub(context.pool()).await?;
+  Ok(create_apub_response(&u))
+}
diff --git a/lemmy_apub/src/inbox/activities/mod.rs b/lemmy_apub/src/inbox/activities/mod.rs
deleted file mode 100644 (file)
index aa50cd1..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-pub mod announce;
-pub mod create;
-pub mod delete;
-pub mod dislike;
-pub mod like;
-pub mod remove;
-pub mod undo;
-pub mod update;
diff --git a/lemmy_apub/src/inbox/activities/undo.rs b/lemmy_apub/src/inbox/activities/undo.rs
deleted file mode 100644 (file)
index 9a421ee..0000000
+++ /dev/null
@@ -1,702 +0,0 @@
-use crate::{
-  fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
-  inbox::shared_inbox::{
-    announce_if_community_is_local,
-    get_user_from_activity,
-    receive_unhandled_activity,
-  },
-  ActorType,
-  FromApub,
-  GroupExt,
-  PageExt,
-};
-use activitystreams::{
-  activity::*,
-  base::{AnyBase, AsBase},
-  object::Note,
-  prelude::*,
-};
-use actix_web::HttpResponse;
-use anyhow::{anyhow, Context};
-use lemmy_db::{
-  comment::{Comment, CommentForm, CommentLike},
-  comment_view::CommentView,
-  community::{Community, CommunityForm},
-  community_view::CommunityView,
-  naive_now,
-  post::{Post, PostForm, PostLike},
-  post_view::PostView,
-  Crud,
-  Likeable,
-};
-use lemmy_structs::{
-  blocking,
-  comment::CommentResponse,
-  community::CommunityResponse,
-  post::PostResponse,
-};
-use lemmy_utils::{location_info, LemmyError};
-use lemmy_websocket::{
-  messages::{SendComment, SendCommunityRoomMessage, SendPost},
-  LemmyContext,
-  UserOperation,
-};
-
-pub async fn receive_undo(
-  activity: AnyBase,
-  context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
-  let undo = Undo::from_any_base(activity)?.context(location_info!())?;
-  match undo.object().as_single_kind_str() {
-    Some("Delete") => receive_undo_delete(undo, context).await,
-    Some("Remove") => receive_undo_remove(undo, context).await,
-    Some("Like") => receive_undo_like(undo, context).await,
-    Some("Dislike") => receive_undo_dislike(undo, context).await,
-    _ => receive_unhandled_activity(undo),
-  }
-}
-
-fn check_is_undo_valid<T, A>(outer_activity: &Undo, inner_activity: &T) -> Result<(), LemmyError>
-where
-  T: AsBase<A> + ActorAndObjectRef,
-{
-  let outer_actor = outer_activity.actor()?;
-  let outer_actor_uri = outer_actor
-    .as_single_xsd_any_uri()
-    .context(location_info!())?;
-
-  let inner_actor = inner_activity.actor()?;
-  let inner_actor_uri = inner_actor
-    .as_single_xsd_any_uri()
-    .context(location_info!())?;
-
-  if outer_actor_uri.domain() != inner_actor_uri.domain() {
-    Err(anyhow!("Cant undo activities from a different instance").into())
-  } else {
-    Ok(())
-  }
-}
-
-async fn receive_undo_delete(
-  undo: Undo,
-  context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
-  let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-  check_is_undo_valid(&undo, &delete)?;
-  let type_ = delete
-    .object()
-    .as_single_kind_str()
-    .context(location_info!())?;
-  match type_ {
-    "Note" => receive_undo_delete_comment(undo, &delete, context).await,
-    "Page" => receive_undo_delete_post(undo, &delete, context).await,
-    "Group" => receive_undo_delete_community(undo, &delete, context).await,
-    d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
-  }
-}
-
-async fn receive_undo_remove(
-  undo: Undo,
-  context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
-  let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-  check_is_undo_valid(&undo, &remove)?;
-
-  let type_ = remove
-    .object()
-    .as_single_kind_str()
-    .context(location_info!())?;
-  match type_ {
-    "Note" => receive_undo_remove_comment(undo, &remove, context).await,
-    "Page" => receive_undo_remove_post(undo, &remove, context).await,
-    "Group" => receive_undo_remove_community(undo, &remove, context).await,
-    d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
-  }
-}
-
-async fn receive_undo_like(undo: Undo, context: &LemmyContext) -> Result<HttpResponse, LemmyError> {
-  let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-  check_is_undo_valid(&undo, &like)?;
-
-  let type_ = like
-    .object()
-    .as_single_kind_str()
-    .context(location_info!())?;
-  match type_ {
-    "Note" => receive_undo_like_comment(undo, &like, context).await,
-    "Page" => receive_undo_like_post(undo, &like, context).await,
-    d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
-  }
-}
-
-async fn receive_undo_dislike(
-  undo: Undo,
-  context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
-  let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-  check_is_undo_valid(&undo, &dislike)?;
-
-  let type_ = dislike
-    .object()
-    .as_single_kind_str()
-    .context(location_info!())?;
-  match type_ {
-    "Note" => receive_undo_dislike_comment(undo, &dislike, context).await,
-    "Page" => receive_undo_dislike_post(undo, &dislike, context).await,
-    d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
-  }
-}
-
-async fn receive_undo_delete_comment(
-  undo: Undo,
-  delete: &Delete,
-  context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
-  let user = get_user_from_activity(delete, context).await?;
-  let note = Note::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-
-  let comment_ap_id = CommentForm::from_apub(&note, context, Some(user.actor_id()?))
-    .await?
-    .get_ap_id()?;
-
-  let comment = get_or_fetch_and_insert_comment(&comment_ap_id, context).await?;
-
-  let comment_form = CommentForm {
-    content: comment.content.to_owned(),
-    parent_id: comment.parent_id,
-    post_id: comment.post_id,
-    creator_id: comment.creator_id,
-    removed: None,
-    deleted: Some(false),
-    read: None,
-    published: None,
-    updated: Some(naive_now()),
-    ap_id: Some(comment.ap_id),
-    local: comment.local,
-  };
-  let comment_id = comment.id;
-  blocking(context.pool(), move |conn| {
-    Comment::update(conn, comment_id, &comment_form)
-  })
-  .await??;
-
-  // Refetch the view
-  let comment_id = comment.id;
-  let comment_view = blocking(context.pool(), move |conn| {
-    CommentView::read(conn, comment_id, None)
-  })
-  .await??;
-
-  // TODO get those recipient actor ids from somewhere
-  let recipient_ids = vec![];
-  let res = CommentResponse {
-    comment: comment_view,
-    recipient_ids,
-    form_id: None,
-  };
-
-  context.chat_server().do_send(SendComment {
-    op: UserOperation::EditComment,
-    comment: res,
-    websocket_id: None,
-  });
-
-  announce_if_community_is_local(undo, &user, context).await?;
-  Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_remove_comment(
-  undo: Undo,
-  remove: &Remove,
-  context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
-  let mod_ = get_user_from_activity(remove, context).await?;
-  let note = Note::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-
-  let comment_ap_id = CommentForm::from_apub(&note, context, None)
-    .await?
-    .get_ap_id()?;
-
-  let comment = get_or_fetch_and_insert_comment(&comment_ap_id, context).await?;
-
-  let comment_form = CommentForm {
-    content: comment.content.to_owned(),
-    parent_id: comment.parent_id,
-    post_id: comment.post_id,
-    creator_id: comment.creator_id,
-    removed: Some(false),
-    deleted: None,
-    read: None,
-    published: None,
-    updated: Some(naive_now()),
-    ap_id: Some(comment.ap_id),
-    local: comment.local,
-  };
-  let comment_id = comment.id;
-  blocking(context.pool(), move |conn| {
-    Comment::update(conn, comment_id, &comment_form)
-  })
-  .await??;
-
-  // Refetch the view
-  let comment_id = comment.id;
-  let comment_view = blocking(context.pool(), move |conn| {
-    CommentView::read(conn, comment_id, None)
-  })
-  .await??;
-
-  // TODO get those recipient actor ids from somewhere
-  let recipient_ids = vec![];
-  let res = CommentResponse {
-    comment: comment_view,
-    recipient_ids,
-    form_id: None,
-  };
-
-  context.chat_server().do_send(SendComment {
-    op: UserOperation::EditComment,
-    comment: res,
-    websocket_id: None,
-  });
-
-  announce_if_community_is_local(undo, &mod_, context).await?;
-  Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_delete_post(
-  undo: Undo,
-  delete: &Delete,
-  context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
-  let user = get_user_from_activity(delete, context).await?;
-  let page = PageExt::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-
-  let post_ap_id = PostForm::from_apub(&page, context, Some(user.actor_id()?))
-    .await?
-    .get_ap_id()?;
-
-  let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
-
-  let post_form = PostForm {
-    name: post.name.to_owned(),
-    url: post.url.to_owned(),
-    body: post.body.to_owned(),
-    creator_id: post.creator_id.to_owned(),
-    community_id: post.community_id,
-    removed: None,
-    deleted: Some(false),
-    nsfw: post.nsfw,
-    locked: None,
-    stickied: None,
-    updated: Some(naive_now()),
-    embed_title: post.embed_title,
-    embed_description: post.embed_description,
-    embed_html: post.embed_html,
-    thumbnail_url: post.thumbnail_url,
-    ap_id: Some(post.ap_id),
-    local: post.local,
-    published: None,
-  };
-  let post_id = post.id;
-  blocking(context.pool(), move |conn| {
-    Post::update(conn, post_id, &post_form)
-  })
-  .await??;
-
-  // Refetch the view
-  let post_id = post.id;
-  let post_view = blocking(context.pool(), move |conn| {
-    PostView::read(conn, post_id, None)
-  })
-  .await??;
-
-  let res = PostResponse { post: post_view };
-
-  context.chat_server().do_send(SendPost {
-    op: UserOperation::EditPost,
-    post: res,
-    websocket_id: None,
-  });
-
-  announce_if_community_is_local(undo, &user, context).await?;
-  Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_remove_post(
-  undo: Undo,
-  remove: &Remove,
-  context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
-  let mod_ = get_user_from_activity(remove, context).await?;
-  let page = PageExt::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-
-  let post_ap_id = PostForm::from_apub(&page, context, None)
-    .await?
-    .get_ap_id()?;
-
-  let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
-
-  let post_form = PostForm {
-    name: post.name.to_owned(),
-    url: post.url.to_owned(),
-    body: post.body.to_owned(),
-    creator_id: post.creator_id.to_owned(),
-    community_id: post.community_id,
-    removed: Some(false),
-    deleted: None,
-    nsfw: post.nsfw,
-    locked: None,
-    stickied: None,
-    updated: Some(naive_now()),
-    embed_title: post.embed_title,
-    embed_description: post.embed_description,
-    embed_html: post.embed_html,
-    thumbnail_url: post.thumbnail_url,
-    ap_id: Some(post.ap_id),
-    local: post.local,
-    published: None,
-  };
-  let post_id = post.id;
-  blocking(context.pool(), move |conn| {
-    Post::update(conn, post_id, &post_form)
-  })
-  .await??;
-
-  // Refetch the view
-  let post_id = post.id;
-  let post_view = blocking(context.pool(), move |conn| {
-    PostView::read(conn, post_id, None)
-  })
-  .await??;
-
-  let res = PostResponse { post: post_view };
-
-  context.chat_server().do_send(SendPost {
-    op: UserOperation::EditPost,
-    post: res,
-    websocket_id: None,
-  });
-
-  announce_if_community_is_local(undo, &mod_, context).await?;
-  Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_delete_community(
-  undo: Undo,
-  delete: &Delete,
-  context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
-  let user = get_user_from_activity(delete, context).await?;
-  let group = GroupExt::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-
-  let community_actor_id = CommunityForm::from_apub(&group, context, Some(user.actor_id()?))
-    .await?
-    .actor_id
-    .context(location_info!())?;
-
-  let community = blocking(context.pool(), move |conn| {
-    Community::read_from_actor_id(conn, &community_actor_id)
-  })
-  .await??;
-
-  let community_form = CommunityForm {
-    name: community.name.to_owned(),
-    title: community.title.to_owned(),
-    description: community.description.to_owned(),
-    category_id: community.category_id, // Note: need to keep this due to foreign key constraint
-    creator_id: community.creator_id,   // Note: need to keep this due to foreign key constraint
-    removed: None,
-    published: None,
-    updated: Some(naive_now()),
-    deleted: Some(false),
-    nsfw: community.nsfw,
-    actor_id: Some(community.actor_id),
-    local: community.local,
-    private_key: community.private_key,
-    public_key: community.public_key,
-    last_refreshed_at: None,
-    icon: Some(community.icon.to_owned()),
-    banner: Some(community.banner.to_owned()),
-  };
-
-  let community_id = community.id;
-  blocking(context.pool(), move |conn| {
-    Community::update(conn, community_id, &community_form)
-  })
-  .await??;
-
-  let community_id = community.id;
-  let res = CommunityResponse {
-    community: blocking(context.pool(), move |conn| {
-      CommunityView::read(conn, community_id, None)
-    })
-    .await??,
-  };
-
-  let community_id = res.community.id;
-
-  context.chat_server().do_send(SendCommunityRoomMessage {
-    op: UserOperation::EditCommunity,
-    response: res,
-    community_id,
-    websocket_id: None,
-  });
-
-  announce_if_community_is_local(undo, &user, context).await?;
-  Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_remove_community(
-  undo: Undo,
-  remove: &Remove,
-  context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
-  let mod_ = get_user_from_activity(remove, context).await?;
-  let group = GroupExt::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-
-  let community_actor_id = CommunityForm::from_apub(&group, context, Some(mod_.actor_id()?))
-    .await?
-    .actor_id
-    .context(location_info!())?;
-
-  let community = blocking(context.pool(), move |conn| {
-    Community::read_from_actor_id(conn, &community_actor_id)
-  })
-  .await??;
-
-  let community_form = CommunityForm {
-    name: community.name.to_owned(),
-    title: community.title.to_owned(),
-    description: community.description.to_owned(),
-    category_id: community.category_id, // Note: need to keep this due to foreign key constraint
-    creator_id: community.creator_id,   // Note: need to keep this due to foreign key constraint
-    removed: Some(false),
-    published: None,
-    updated: Some(naive_now()),
-    deleted: None,
-    nsfw: community.nsfw,
-    actor_id: Some(community.actor_id),
-    local: community.local,
-    private_key: community.private_key,
-    public_key: community.public_key,
-    last_refreshed_at: None,
-    icon: Some(community.icon.to_owned()),
-    banner: Some(community.banner.to_owned()),
-  };
-
-  let community_id = community.id;
-  blocking(context.pool(), move |conn| {
-    Community::update(conn, community_id, &community_form)
-  })
-  .await??;
-
-  let community_id = community.id;
-  let res = CommunityResponse {
-    community: blocking(context.pool(), move |conn| {
-      CommunityView::read(conn, community_id, None)
-    })
-    .await??,
-  };
-
-  let community_id = res.community.id;
-
-  context.chat_server().do_send(SendCommunityRoomMessage {
-    op: UserOperation::EditCommunity,
-    response: res,
-    community_id,
-    websocket_id: None,
-  });
-
-  announce_if_community_is_local(undo, &mod_, context).await?;
-  Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_like_comment(
-  undo: Undo,
-  like: &Like,
-  context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
-  let user = get_user_from_activity(like, context).await?;
-  let note = Note::from_any_base(like.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-
-  let comment = CommentForm::from_apub(&note, context, None).await?;
-
-  let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
-    .await?
-    .id;
-
-  let user_id = user.id;
-  blocking(context.pool(), move |conn| {
-    CommentLike::remove(conn, user_id, comment_id)
-  })
-  .await??;
-
-  // Refetch the view
-  let comment_view = blocking(context.pool(), move |conn| {
-    CommentView::read(conn, comment_id, None)
-  })
-  .await??;
-
-  // TODO get those recipient actor ids from somewhere
-  let recipient_ids = vec![];
-  let res = CommentResponse {
-    comment: comment_view,
-    recipient_ids,
-    form_id: None,
-  };
-
-  context.chat_server().do_send(SendComment {
-    op: UserOperation::CreateCommentLike,
-    comment: res,
-    websocket_id: None,
-  });
-
-  announce_if_community_is_local(undo, &user, context).await?;
-  Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_like_post(
-  undo: Undo,
-  like: &Like,
-  context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
-  let user = get_user_from_activity(like, context).await?;
-  let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
-    .context(location_info!())?;
-
-  let post = PostForm::from_apub(&page, context, None).await?;
-
-  let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
-    .await?
-    .id;
-
-  let user_id = user.id;
-  blocking(context.pool(), move |conn| {
-    PostLike::remove(conn, user_id, post_id)
-  })
-  .await??;
-
-  // Refetch the view
-  let post_view = blocking(context.pool(), move |conn| {
-    PostView::read(conn, post_id, None)
-  })
-  .await??;
-
-  let res = PostResponse { post: post_view };
-
-  context.chat_server().do_send(SendPost {
-    op: UserOperation::CreatePostLike,
-    post: res,
-    websocket_id: None,
-  });
-
-  announce_if_community_is_local(undo, &user, context).await?;
-  Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_dislike_comment(
-  undo: Undo,
-  dislike: &Dislike,
-  context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
-  let user = get_user_from_activity(dislike, context).await?;
-  let note = Note::from_any_base(
-    dislike
-      .object()
-      .to_owned()
-      .one()
-      .context(location_info!())?,
-  )?
-  .context(location_info!())?;
-
-  let comment = CommentForm::from_apub(&note, context, None).await?;
-
-  let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
-    .await?
-    .id;
-
-  let user_id = user.id;
-  blocking(context.pool(), move |conn| {
-    CommentLike::remove(conn, user_id, comment_id)
-  })
-  .await??;
-
-  // Refetch the view
-  let comment_view = blocking(context.pool(), move |conn| {
-    CommentView::read(conn, comment_id, None)
-  })
-  .await??;
-
-  // TODO get those recipient actor ids from somewhere
-  let recipient_ids = vec![];
-  let res = CommentResponse {
-    comment: comment_view,
-    recipient_ids,
-    form_id: None,
-  };
-
-  context.chat_server().do_send(SendComment {
-    op: UserOperation::CreateCommentLike,
-    comment: res,
-    websocket_id: None,
-  });
-
-  announce_if_community_is_local(undo, &user, context).await?;
-  Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_dislike_post(
-  undo: Undo,
-  dislike: &Dislike,
-  context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
-  let user = get_user_from_activity(dislike, context).await?;
-  let page = PageExt::from_any_base(
-    dislike
-      .object()
-      .to_owned()
-      .one()
-      .context(location_info!())?,
-  )?
-  .context(location_info!())?;
-
-  let post = PostForm::from_apub(&page, context, None).await?;
-
-  let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
-    .await?
-    .id;
-
-  let user_id = user.id;
-  blocking(context.pool(), move |conn| {
-    PostLike::remove(conn, user_id, post_id)
-  })
-  .await??;
-
-  // Refetch the view
-  let post_view = blocking(context.pool(), move |conn| {
-    PostView::read(conn, post_id, None)
-  })
-  .await??;
-
-  let res = PostResponse { post: post_view };
-
-  context.chat_server().do_send(SendPost {
-    op: UserOperation::CreatePostLike,
-    post: res,
-    websocket_id: None,
-  });
-
-  announce_if_community_is_local(undo, &user, context).await?;
-  Ok(HttpResponse::Ok().finish())
-}
index 9ac589c70ecbfee5993e25ffa0eab889947d3eca..5def86c22f9a2d3904c6b9855b333ce80a6f0279 100644 (file)
@@ -33,7 +33,7 @@ pub enum ValidTypes {
 
 pub type AcceptedActivities = ActorAndObject<ValidTypes>;
 
-/// Handler for all incoming activities to community inboxes.
+/// Handler for all incoming receive to community inboxes.
 pub async fn community_inbox(
   request: HttpRequest,
   input: web::Json<AcceptedActivities>,
index b9db61fa83662381ed65d82bfcb58c790ec184a2..41a1b7ffcec972a9fa0b49846fc170f56c6465dd 100644 (file)
@@ -1,4 +1,3 @@
-pub mod activities;
 pub mod community_inbox;
 pub mod shared_inbox;
 pub mod user_inbox;
index 2d69336fd26b4dcb854a1f42aab7f0e7ec9087a7..c8ce9cdfec88fffca789d001d98679aff9ac066b 100644 (file)
@@ -1,13 +1,5 @@
 use crate::{
-  check_is_apub_id_valid,
-  community::do_announce,
-  extensions::signatures::verify,
-  fetcher::{
-    get_or_fetch_and_upsert_actor,
-    get_or_fetch_and_upsert_community,
-    get_or_fetch_and_upsert_user,
-  },
-  inbox::activities::{
+  activities::receive::{
     announce::receive_announce,
     create::receive_create,
     delete::receive_delete,
@@ -17,11 +9,14 @@ use crate::{
     undo::receive_undo,
     update::receive_update,
   },
+  check_is_apub_id_valid,
+  extensions::signatures::verify,
+  fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_user},
   insert_activity,
 };
 use activitystreams::{
   activity::{ActorAndObject, ActorAndObjectRef},
-  base::{AsBase, Extends},
+  base::AsBase,
   object::AsObject,
   prelude::*,
 };
@@ -48,11 +43,11 @@ pub enum ValidTypes {
   Announce,
 }
 
-// TODO: this isnt entirely correct, cause some of these activities are not ActorAndObject,
+// TODO: this isnt entirely correct, cause some of these receive are not ActorAndObject,
 //       but it might still work due to the anybase conversion
 pub type AcceptedActivities = ActorAndObject<ValidTypes>;
 
-/// Handler for all incoming activities to user inboxes.
+/// Handler for all incoming receive to user inboxes.
 pub async fn shared_inbox(
   request: HttpRequest,
   input: web::Json<AcceptedActivities>,
@@ -95,17 +90,7 @@ pub async fn shared_inbox(
   res
 }
 
-pub(in crate::inbox) fn receive_unhandled_activity<A>(
-  activity: A,
-) -> Result<HttpResponse, LemmyError>
-where
-  A: Debug,
-{
-  debug!("received unhandled activity type: {:?}", activity);
-  Ok(HttpResponse::NotImplemented().finish())
-}
-
-pub(in crate::inbox) async fn get_user_from_activity<T, A>(
+pub(in crate) async fn get_user_from_activity<T, A>(
   activity: &T,
   context: &LemmyContext,
 ) -> Result<User_, LemmyError>
@@ -117,9 +102,7 @@ where
   get_or_fetch_and_upsert_user(&user_uri, context).await
 }
 
-pub(in crate::inbox) fn get_community_id_from_activity<T, A>(
-  activity: &T,
-) -> Result<Url, LemmyError>
+pub(in crate) fn get_community_id_from_activity<T, A>(activity: &T) -> Result<Url, LemmyError>
 where
   T: AsBase<A> + ActorAndObjectRef + AsObject<A>,
 {
@@ -133,33 +116,3 @@ where
       .to_owned(),
   )
 }
-
-pub(in crate::inbox) async fn announce_if_community_is_local<T, Kind>(
-  activity: T,
-  user: &User_,
-  context: &LemmyContext,
-) -> Result<(), LemmyError>
-where
-  T: AsObject<Kind>,
-  T: Extends<Kind>,
-  Kind: Serialize,
-  <T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
-{
-  let cc = activity.cc().context(location_info!())?;
-  let cc = cc.as_many().context(location_info!())?;
-  let community_followers_uri = cc
-    .first()
-    .context(location_info!())?
-    .as_xsd_any_uri()
-    .context(location_info!())?;
-  // TODO: this is hacky but seems to be the only way to get the community ID
-  let community_uri = community_followers_uri
-    .to_string()
-    .replace("/followers", "");
-  let community = get_or_fetch_and_upsert_community(&Url::parse(&community_uri)?, context).await?;
-
-  if community.local {
-    do_announce(activity.into_any_base()?, &community, &user, context).await?;
-  }
-  Ok(())
-}
index c411f5b7af8e5a4ff0af4933c0d59c5408065573..db6a6cea9c9e4e50af46b50bc56081c4a6531085 100644 (file)
@@ -41,7 +41,7 @@ pub enum ValidTypes {
 
 pub type AcceptedActivities = ActorAndObject<ValidTypes>;
 
-/// Handler for all incoming activities to user inboxes.
+/// Handler for all incoming receive to user inboxes.
 pub async fn user_inbox(
   request: HttpRequest,
   input: web::Json<AcceptedActivities>,
index f06c8c8a6023db28e5ebae8a8ca9b556f0b628d5..d088447e860a4b8b0ff8fa68f809283931f3f942 100644 (file)
@@ -1,15 +1,13 @@
 #[macro_use]
 extern crate lazy_static;
 
+pub mod activities;
 pub mod activity_queue;
-pub mod comment;
-pub mod community;
 pub mod extensions;
 pub mod fetcher;
+pub mod http;
 pub mod inbox;
-pub mod post;
-pub mod private_message;
-pub mod user;
+pub mod objects;
 
 use crate::extensions::{
   group_extensions::GroupExtension,
@@ -19,30 +17,19 @@ use crate::extensions::{
 use activitystreams::{
   activity::Follow,
   actor::{ApActor, Group, Person},
-  base::AsBase,
+  base::{AnyBase, AsBase},
   markers::Base,
   object::{Page, Tombstone},
   prelude::*,
 };
 use activitystreams_ext::{Ext1, Ext2};
-use actix_web::{body::Body, HttpResponse};
 use anyhow::{anyhow, Context};
-use chrono::NaiveDateTime;
 use lemmy_db::{activity::do_insert_activity, user::User_, DbPool};
-use lemmy_structs::{blocking, WebFingerResponse};
-use lemmy_utils::{
-  location_info,
-  request::{retry, RecvError},
-  settings::Settings,
-  utils::{convert_datetime, MentionData},
-  LemmyError,
-};
+use lemmy_structs::blocking;
+use lemmy_utils::{location_info, settings::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
-use log::debug;
-use reqwest::Client;
 use serde::Serialize;
 use url::{ParseError, Url};
-use uuid::Uuid;
 
 type GroupExt = Ext2<ApActor<Group>, GroupExtension, PublicKeyExtension>;
 type PersonExt = Ext1<ApActor<Person>, PublicKeyExtension>;
@@ -50,26 +37,6 @@ type PageExt = Ext1<Page, PageExtension>;
 
 pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
 
-/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
-/// headers.
-fn create_apub_response<T>(data: &T) -> HttpResponse<Body>
-where
-  T: Serialize,
-{
-  HttpResponse::Ok()
-    .content_type(APUB_JSON_CONTENT_TYPE)
-    .json(data)
-}
-
-fn create_apub_tombstone_response<T>(data: &T) -> HttpResponse<Body>
-where
-  T: Serialize,
-{
-  HttpResponse::Gone()
-    .content_type(APUB_JSON_CONTENT_TYPE)
-    .json(data)
-}
-
 // Checks if the ID has a valid format, correct scheme, and is in the allowed instance list.
 fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
   let settings = Settings::get();
@@ -105,7 +72,7 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
   if allowed_instances.is_empty() && blocked_instances.is_empty() {
     Ok(())
   } else if !allowed_instances.is_empty() {
-    // need to allow this explicitly because apub activities might contain objects from our local
+    // need to allow this explicitly because apub receive might contain objects from our local
     // instance. split is needed to remove the port in our federation test setup.
     allowed_instances.push(local_instance);
 
@@ -132,31 +99,6 @@ pub trait ToApub {
   fn to_tombstone(&self) -> Result<Tombstone, LemmyError>;
 }
 
-/// Updated is actually the deletion time
-fn create_tombstone<T>(
-  deleted: bool,
-  object_id: &str,
-  updated: Option<NaiveDateTime>,
-  former_type: T,
-) -> Result<Tombstone, LemmyError>
-where
-  T: ToString,
-{
-  if deleted {
-    if let Some(updated) = updated {
-      let mut tombstone = Tombstone::new();
-      tombstone.set_id(object_id.parse()?);
-      tombstone.set_former_type(former_type.to_string());
-      tombstone.set_deleted(convert_datetime(updated));
-      Ok(tombstone)
-    } else {
-      Err(anyhow!("Cant convert to tombstone because updated time was None.").into())
-    }
-  } else {
-    Err(anyhow!("Cant convert object to tombstone if it wasnt deleted").into())
-  }
-}
-
 #[async_trait::async_trait(?Send)]
 pub trait FromApub {
   type ApubType;
@@ -258,6 +200,13 @@ pub trait ActorType {
   async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
   async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
 
+  async fn send_announce(
+    &self,
+    activity: AnyBase,
+    sender: &User_,
+    context: &LemmyContext,
+  ) -> Result<(), LemmyError>;
+
   /// For a given community, returns the inboxes of all followers.
   async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError>;
 
@@ -305,39 +254,6 @@ pub trait ActorType {
   }
 }
 
-pub async fn fetch_webfinger_url(
-  mention: &MentionData,
-  client: &Client,
-) -> Result<Url, LemmyError> {
-  let fetch_url = format!(
-    "{}://{}/.well-known/webfinger?resource=acct:{}@{}",
-    Settings::get().get_protocol_string(),
-    mention.domain,
-    mention.name,
-    mention.domain
-  );
-  debug!("Fetching webfinger url: {}", &fetch_url);
-
-  let response = retry(|| client.get(&fetch_url).send()).await?;
-
-  let res: WebFingerResponse = response
-    .json()
-    .await
-    .map_err(|e| RecvError(e.to_string()))?;
-
-  let link = res
-    .links
-    .iter()
-    .find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
-    .ok_or_else(|| anyhow!("No application/activity+json link found."))?;
-  link
-    .href
-    .to_owned()
-    .map(|u| Url::parse(&u))
-    .transpose()?
-    .ok_or_else(|| anyhow!("No href found.").into())
-}
-
 pub async fn insert_activity<T>(
   user_id: i32,
   data: T,
@@ -353,16 +269,3 @@ where
   .await??;
   Ok(())
 }
-
-pub(in crate) fn generate_activity_id<T>(kind: T) -> Result<Url, ParseError>
-where
-  T: ToString,
-{
-  let id = format!(
-    "{}/activities/{}/{}",
-    Settings::get().get_protocol_and_hostname(),
-    kind.to_string().to_lowercase(),
-    Uuid::new_v4()
-  );
-  Url::parse(&id)
-}
diff --git a/lemmy_apub/src/objects/comment.rs b/lemmy_apub/src/objects/comment.rs
new file mode 100644 (file)
index 0000000..22f22f8
--- /dev/null
@@ -0,0 +1,147 @@
+use crate::{
+  check_actor_domain,
+  fetcher::{
+    get_or_fetch_and_insert_comment,
+    get_or_fetch_and_insert_post,
+    get_or_fetch_and_upsert_user,
+  },
+  objects::create_tombstone,
+  FromApub,
+  ToApub,
+};
+use activitystreams::{
+  object::{kind::NoteType, Note, Tombstone},
+  prelude::*,
+};
+use anyhow::Context;
+use lemmy_db::{
+  comment::{Comment, CommentForm},
+  community::Community,
+  post::Post,
+  user::User_,
+  Crud,
+  DbPool,
+};
+use lemmy_structs::blocking;
+use lemmy_utils::{
+  location_info,
+  utils::{convert_datetime, remove_slurs},
+  LemmyError,
+};
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[async_trait::async_trait(?Send)]
+impl ToApub for Comment {
+  type Response = Note;
+
+  async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
+    let mut comment = Note::new();
+
+    let creator_id = self.creator_id;
+    let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
+
+    let post_id = self.post_id;
+    let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
+
+    let community_id = post.community_id;
+    let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
+
+    // Add a vector containing some important info to the "in_reply_to" field
+    // [post_ap_id, Option(parent_comment_ap_id)]
+    let mut in_reply_to_vec = vec![post.ap_id];
+
+    if let Some(parent_id) = self.parent_id {
+      let parent_comment = blocking(pool, move |conn| Comment::read(conn, parent_id)).await??;
+
+      in_reply_to_vec.push(parent_comment.ap_id);
+    }
+
+    comment
+      // Not needed when the Post is embedded in a collection (like for community outbox)
+      .set_context(activitystreams::context())
+      .set_id(Url::parse(&self.ap_id)?)
+      .set_published(convert_datetime(self.published))
+      .set_to(community.actor_id)
+      .set_many_in_reply_tos(in_reply_to_vec)
+      .set_content(self.content.to_owned())
+      .set_attributed_to(creator.actor_id);
+
+    if let Some(u) = self.updated {
+      comment.set_updated(convert_datetime(u));
+    }
+
+    Ok(comment)
+  }
+
+  fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
+    create_tombstone(self.deleted, &self.ap_id, self.updated, NoteType::Note)
+  }
+}
+
+#[async_trait::async_trait(?Send)]
+impl FromApub for CommentForm {
+  type ApubType = Note;
+
+  /// Parse an ActivityPub note received from another instance into a Lemmy comment
+  async fn from_apub(
+    note: &Note,
+    context: &LemmyContext,
+    expected_domain: Option<Url>,
+  ) -> Result<CommentForm, LemmyError> {
+    let creator_actor_id = &note
+      .attributed_to()
+      .context(location_info!())?
+      .as_single_xsd_any_uri()
+      .context(location_info!())?;
+
+    let creator = get_or_fetch_and_upsert_user(creator_actor_id, context).await?;
+
+    let mut in_reply_tos = note
+      .in_reply_to()
+      .as_ref()
+      .context(location_info!())?
+      .as_many()
+      .context(location_info!())?
+      .iter()
+      .map(|i| i.as_xsd_any_uri().context(""));
+    let post_ap_id = in_reply_tos.next().context(location_info!())??;
+
+    // This post, or the parent comment might not yet exist on this server yet, fetch them.
+    let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
+
+    // The 2nd item, if it exists, is the parent comment apub_id
+    // For deeply nested comments, FromApub automatically gets called recursively
+    let parent_id: Option<i32> = match in_reply_tos.next() {
+      Some(parent_comment_uri) => {
+        let parent_comment_ap_id = &parent_comment_uri?;
+        let parent_comment =
+          get_or_fetch_and_insert_comment(&parent_comment_ap_id, context).await?;
+
+        Some(parent_comment.id)
+      }
+      None => None,
+    };
+    let content = note
+      .content()
+      .context(location_info!())?
+      .as_single_xsd_string()
+      .context(location_info!())?
+      .to_string();
+    let content_slurs_removed = remove_slurs(&content);
+
+    Ok(CommentForm {
+      creator_id: creator.id,
+      post_id: post.id,
+      parent_id,
+      content: content_slurs_removed,
+      removed: None,
+      read: None,
+      published: note.published().map(|u| u.to_owned().naive_local()),
+      updated: note.updated().map(|u| u.to_owned().naive_local()),
+      deleted: None,
+      ap_id: Some(check_actor_domain(note, expected_domain)?),
+      local: false,
+    })
+  }
+}
diff --git a/lemmy_apub/src/objects/community.rs b/lemmy_apub/src/objects/community.rs
new file mode 100644 (file)
index 0000000..0318171
--- /dev/null
@@ -0,0 +1,201 @@
+use crate::{
+  check_actor_domain,
+  extensions::group_extensions::GroupExtension,
+  fetcher::get_or_fetch_and_upsert_user,
+  objects::create_tombstone,
+  ActorType,
+  FromApub,
+  GroupExt,
+  ToApub,
+};
+use activitystreams::{
+  actor::{kind::GroupType, ApActor, Endpoints, Group},
+  base::BaseExt,
+  object::{Image, Tombstone},
+  prelude::*,
+};
+use activitystreams_ext::Ext2;
+use anyhow::Context;
+use lemmy_db::{
+  community::{Community, CommunityForm},
+  community_view::CommunityModeratorView,
+  naive_now,
+  DbPool,
+};
+use lemmy_structs::blocking;
+use lemmy_utils::{
+  location_info,
+  utils::{check_slurs, check_slurs_opt, convert_datetime},
+  LemmyError,
+};
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[async_trait::async_trait(?Send)]
+impl ToApub for Community {
+  type Response = GroupExt;
+
+  // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network.
+  async fn to_apub(&self, pool: &DbPool) -> Result<GroupExt, LemmyError> {
+    // The attributed to, is an ordered vector with the creator actor_ids first,
+    // then the rest of the moderators
+    // TODO Technically the instance admins can mod the community, but lets
+    // ignore that for now
+    let id = self.id;
+    let moderators = blocking(pool, move |conn| {
+      CommunityModeratorView::for_community(&conn, id)
+    })
+    .await??;
+    let moderators: Vec<String> = moderators.into_iter().map(|m| m.user_actor_id).collect();
+
+    let mut group = Group::new();
+    group
+      .set_context(activitystreams::context())
+      .set_id(Url::parse(&self.actor_id)?)
+      .set_name(self.name.to_owned())
+      .set_published(convert_datetime(self.published))
+      .set_many_attributed_tos(moderators);
+
+    if let Some(u) = self.updated.to_owned() {
+      group.set_updated(convert_datetime(u));
+    }
+    if let Some(d) = self.description.to_owned() {
+      // TODO: this should be html, also add source field with raw markdown
+      //       -> same for post.content and others
+      group.set_content(d);
+    }
+
+    if let Some(icon) = &self.icon {
+      let mut image = Image::new();
+      image.set_url(icon.to_owned());
+      group.set_icon(image.into_any_base()?);
+    }
+
+    if let Some(banner_url) = &self.banner {
+      let mut image = Image::new();
+      image.set_url(banner_url.to_owned());
+      group.set_image(image.into_any_base()?);
+    }
+
+    let mut ap_actor = ApActor::new(self.get_inbox_url()?, group);
+    ap_actor
+      .set_preferred_username(self.title.to_owned())
+      .set_outbox(self.get_outbox_url()?)
+      .set_followers(self.get_followers_url()?)
+      .set_endpoints(Endpoints {
+        shared_inbox: Some(self.get_shared_inbox_url()?),
+        ..Default::default()
+      });
+
+    let nsfw = self.nsfw;
+    let category_id = self.category_id;
+    let group_extension = blocking(pool, move |conn| {
+      GroupExtension::new(conn, category_id, nsfw)
+    })
+    .await??;
+
+    Ok(Ext2::new(
+      ap_actor,
+      group_extension,
+      self.get_public_key_ext()?,
+    ))
+  }
+
+  fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
+    create_tombstone(self.deleted, &self.actor_id, self.updated, GroupType::Group)
+  }
+}
+#[async_trait::async_trait(?Send)]
+impl FromApub for CommunityForm {
+  type ApubType = GroupExt;
+
+  /// Parse an ActivityPub group received from another instance into a Lemmy community.
+  async fn from_apub(
+    group: &GroupExt,
+    context: &LemmyContext,
+    expected_domain: Option<Url>,
+  ) -> Result<Self, LemmyError> {
+    let creator_and_moderator_uris = group.inner.attributed_to().context(location_info!())?;
+    let creator_uri = creator_and_moderator_uris
+      .as_many()
+      .context(location_info!())?
+      .iter()
+      .next()
+      .context(location_info!())?
+      .as_xsd_any_uri()
+      .context(location_info!())?;
+
+    let creator = get_or_fetch_and_upsert_user(creator_uri, context).await?;
+    let name = group
+      .inner
+      .name()
+      .context(location_info!())?
+      .as_one()
+      .context(location_info!())?
+      .as_xsd_string()
+      .context(location_info!())?
+      .to_string();
+    let title = group
+      .inner
+      .preferred_username()
+      .context(location_info!())?
+      .to_string();
+    // TODO: should be parsed as html and tags like <script> removed (or use markdown source)
+    //       -> same for post.content etc
+    let description = group
+      .inner
+      .content()
+      .map(|s| s.as_single_xsd_string())
+      .flatten()
+      .map(|s| s.to_string());
+    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_string()),
+      ),
+      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_string()),
+      ),
+      None => None,
+    };
+
+    Ok(CommunityForm {
+      name,
+      title,
+      description,
+      category_id: group.ext_one.category.identifier.parse::<i32>()?,
+      creator_id: creator.id,
+      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: group.ext_one.sensitive,
+      actor_id: Some(check_actor_domain(group, expected_domain)?),
+      local: 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,
+    })
+  }
+}
diff --git a/lemmy_apub/src/objects/mod.rs b/lemmy_apub/src/objects/mod.rs
new file mode 100644 (file)
index 0000000..f3a701c
--- /dev/null
@@ -0,0 +1,38 @@
+use activitystreams::{
+  base::BaseExt,
+  object::{Tombstone, TombstoneExt},
+};
+use anyhow::anyhow;
+use chrono::NaiveDateTime;
+use lemmy_utils::{utils::convert_datetime, LemmyError};
+
+pub mod comment;
+pub mod community;
+pub mod post;
+pub mod private_message;
+pub mod user;
+
+/// Updated is actually the deletion time
+fn create_tombstone<T>(
+  deleted: bool,
+  object_id: &str,
+  updated: Option<NaiveDateTime>,
+  former_type: T,
+) -> Result<Tombstone, LemmyError>
+where
+  T: ToString,
+{
+  if deleted {
+    if let Some(updated) = updated {
+      let mut tombstone = Tombstone::new();
+      tombstone.set_id(object_id.parse()?);
+      tombstone.set_former_type(former_type.to_string());
+      tombstone.set_deleted(convert_datetime(updated));
+      Ok(tombstone)
+    } else {
+      Err(anyhow!("Cant convert to tombstone because updated time was None.").into())
+    }
+  } else {
+    Err(anyhow!("Cant convert object to tombstone if it wasnt deleted").into())
+  }
+}
diff --git a/lemmy_apub/src/objects/post.rs b/lemmy_apub/src/objects/post.rs
new file mode 100644 (file)
index 0000000..3a979f5
--- /dev/null
@@ -0,0 +1,200 @@
+use crate::{
+  check_actor_domain,
+  extensions::page_extension::PageExtension,
+  fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
+  objects::create_tombstone,
+  FromApub,
+  PageExt,
+  ToApub,
+};
+use activitystreams::{
+  object::{kind::PageType, Image, Page, Tombstone},
+  prelude::*,
+};
+use activitystreams_ext::Ext1;
+use anyhow::Context;
+use lemmy_db::{
+  community::Community,
+  post::{Post, PostForm},
+  user::User_,
+  Crud,
+  DbPool,
+};
+use lemmy_structs::blocking;
+use lemmy_utils::{
+  location_info,
+  request::fetch_iframely_and_pictrs_data,
+  utils::{check_slurs, convert_datetime, remove_slurs},
+  LemmyError,
+};
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[async_trait::async_trait(?Send)]
+impl ToApub for Post {
+  type Response = PageExt;
+
+  // Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
+  async fn to_apub(&self, pool: &DbPool) -> Result<PageExt, LemmyError> {
+    let mut page = Page::new();
+
+    let creator_id = self.creator_id;
+    let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
+
+    let community_id = self.community_id;
+    let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
+
+    page
+      // Not needed when the Post is embedded in a collection (like for community outbox)
+      // TODO: need to set proper context defining sensitive/commentsEnabled fields
+      // https://git.asonix.dog/Aardwolf/activitystreams/issues/5
+      .set_context(activitystreams::context())
+      .set_id(self.ap_id.parse::<Url>()?)
+      // Use summary field to be consistent with mastodon content warning.
+      // https://mastodon.xyz/@Louisa/103987265222901387.json
+      .set_summary(self.name.to_owned())
+      .set_published(convert_datetime(self.published))
+      .set_to(community.actor_id)
+      .set_attributed_to(creator.actor_id);
+
+    if let Some(body) = &self.body {
+      page.set_content(body.to_owned());
+    }
+
+    // TODO: hacky code because we get self.url == Some("")
+    // https://github.com/LemmyNet/lemmy/issues/602
+    let url = self.url.as_ref().filter(|u| !u.is_empty());
+    if let Some(u) = url {
+      page.set_url(u.to_owned());
+    }
+
+    if let Some(thumbnail_url) = &self.thumbnail_url {
+      let mut image = Image::new();
+      image.set_url(thumbnail_url.to_string());
+      page.set_image(image.into_any_base()?);
+    }
+
+    if let Some(u) = self.updated {
+      page.set_updated(convert_datetime(u));
+    }
+
+    let ext = PageExtension {
+      comments_enabled: !self.locked,
+      sensitive: self.nsfw,
+      stickied: self.stickied,
+    };
+    Ok(Ext1::new(page, ext))
+  }
+
+  fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
+    create_tombstone(self.deleted, &self.ap_id, self.updated, PageType::Page)
+  }
+}
+
+#[async_trait::async_trait(?Send)]
+impl FromApub for PostForm {
+  type ApubType = PageExt;
+
+  /// Parse an ActivityPub page received from another instance into a Lemmy post.
+  async fn from_apub(
+    page: &PageExt,
+    context: &LemmyContext,
+    expected_domain: Option<Url>,
+  ) -> Result<PostForm, LemmyError> {
+    let ext = &page.ext_one;
+    let creator_actor_id = page
+      .inner
+      .attributed_to()
+      .as_ref()
+      .context(location_info!())?
+      .as_single_xsd_any_uri()
+      .context(location_info!())?;
+
+    let creator = get_or_fetch_and_upsert_user(creator_actor_id, context).await?;
+
+    let community_actor_id = page
+      .inner
+      .to()
+      .as_ref()
+      .context(location_info!())?
+      .as_single_xsd_any_uri()
+      .context(location_info!())?;
+
+    let community = get_or_fetch_and_upsert_community(community_actor_id, context).await?;
+
+    let thumbnail_url = match &page.inner.image() {
+      Some(any_image) => Image::from_any_base(
+        any_image
+          .to_owned()
+          .as_one()
+          .context(location_info!())?
+          .to_owned(),
+      )?
+      .context(location_info!())?
+      .url()
+      .context(location_info!())?
+      .as_single_xsd_any_uri()
+      .map(|u| u.to_string()),
+      None => None,
+    };
+    let url = page
+      .inner
+      .url()
+      .map(|u| u.as_single_xsd_any_uri())
+      .flatten()
+      .map(|s| s.to_string());
+
+    let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
+      if let Some(url) = &url {
+        fetch_iframely_and_pictrs_data(context.client(), Some(url.to_owned())).await
+      } else {
+        (None, None, None, thumbnail_url)
+      };
+
+    let name = page
+      .inner
+      .summary()
+      .as_ref()
+      .context(location_info!())?
+      .as_single_xsd_string()
+      .context(location_info!())?
+      .to_string();
+    let body = page
+      .inner
+      .content()
+      .as_ref()
+      .map(|c| c.as_single_xsd_string())
+      .flatten()
+      .map(|s| s.to_string());
+    check_slurs(&name)?;
+    let body_slurs_removed = body.map(|b| remove_slurs(&b));
+    Ok(PostForm {
+      name,
+      url,
+      body: body_slurs_removed,
+      creator_id: creator.id,
+      community_id: community.id,
+      removed: None,
+      locked: Some(!ext.comments_enabled),
+      published: page
+        .inner
+        .published()
+        .as_ref()
+        .map(|u| u.to_owned().naive_local()),
+      updated: page
+        .inner
+        .updated()
+        .as_ref()
+        .map(|u| u.to_owned().naive_local()),
+      deleted: None,
+      nsfw: ext.sensitive,
+      stickied: Some(ext.stickied),
+      embed_title: iframely_title,
+      embed_description: iframely_description,
+      embed_html: iframely_html,
+      thumbnail_url: pictrs_thumbnail,
+      ap_id: Some(check_actor_domain(page, expected_domain)?),
+      local: false,
+    })
+  }
+}
diff --git a/lemmy_apub/src/objects/private_message.rs b/lemmy_apub/src/objects/private_message.rs
new file mode 100644 (file)
index 0000000..cc01bcc
--- /dev/null
@@ -0,0 +1,103 @@
+use crate::{
+  check_actor_domain,
+  check_is_apub_id_valid,
+  fetcher::get_or_fetch_and_upsert_user,
+  objects::create_tombstone,
+  FromApub,
+  ToApub,
+};
+use activitystreams::{
+  object::{kind::NoteType, Note, Tombstone},
+  prelude::*,
+};
+use anyhow::Context;
+use lemmy_db::{
+  private_message::{PrivateMessage, PrivateMessageForm},
+  user::User_,
+  Crud,
+  DbPool,
+};
+use lemmy_structs::blocking;
+use lemmy_utils::{location_info, utils::convert_datetime, LemmyError};
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[async_trait::async_trait(?Send)]
+impl ToApub for PrivateMessage {
+  type Response = Note;
+
+  async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
+    let mut private_message = Note::new();
+
+    let creator_id = self.creator_id;
+    let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
+
+    let recipient_id = self.recipient_id;
+    let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
+
+    private_message
+      .set_context(activitystreams::context())
+      .set_id(Url::parse(&self.ap_id.to_owned())?)
+      .set_published(convert_datetime(self.published))
+      .set_content(self.content.to_owned())
+      .set_to(recipient.actor_id)
+      .set_attributed_to(creator.actor_id);
+
+    if let Some(u) = self.updated {
+      private_message.set_updated(convert_datetime(u));
+    }
+
+    Ok(private_message)
+  }
+
+  fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
+    create_tombstone(self.deleted, &self.ap_id, self.updated, NoteType::Note)
+  }
+}
+
+#[async_trait::async_trait(?Send)]
+impl FromApub for PrivateMessageForm {
+  type ApubType = Note;
+
+  /// Parse an ActivityPub note received from another instance into a Lemmy Private message
+  async fn from_apub(
+    note: &Note,
+    context: &LemmyContext,
+    expected_domain: Option<Url>,
+  ) -> Result<PrivateMessageForm, LemmyError> {
+    let creator_actor_id = note
+      .attributed_to()
+      .context(location_info!())?
+      .clone()
+      .single_xsd_any_uri()
+      .context(location_info!())?;
+
+    let creator = get_or_fetch_and_upsert_user(&creator_actor_id, context).await?;
+    let recipient_actor_id = note
+      .to()
+      .context(location_info!())?
+      .clone()
+      .single_xsd_any_uri()
+      .context(location_info!())?;
+    let recipient = get_or_fetch_and_upsert_user(&recipient_actor_id, context).await?;
+    let ap_id = note.id_unchecked().context(location_info!())?.to_string();
+    check_is_apub_id_valid(&Url::parse(&ap_id)?)?;
+
+    Ok(PrivateMessageForm {
+      creator_id: creator.id,
+      recipient_id: recipient.id,
+      content: note
+        .content()
+        .context(location_info!())?
+        .as_single_xsd_string()
+        .context(location_info!())?
+        .to_string(),
+      published: note.published().map(|u| u.to_owned().naive_local()),
+      updated: note.updated().map(|u| u.to_owned().naive_local()),
+      deleted: None,
+      read: None,
+      ap_id: Some(check_actor_domain(note, expected_domain)?),
+      local: false,
+    })
+  }
+}
similarity index 56%
rename from lemmy_apub/src/user.rs
rename to lemmy_apub/src/objects/user.rs
index 7b14d4a7b5fdc157ca66e631d580aee77e8501a7..96ece96865539f49acf89ac8b1327e95fc4b989b 100644 (file)
@@ -1,47 +1,24 @@
-use crate::{
-  activity_queue::send_activity_single_dest,
-  check_actor_domain,
-  create_apub_response,
-  fetcher::get_or_fetch_and_upsert_actor,
-  generate_activity_id,
-  ActorType,
-  FromApub,
-  PersonExt,
-  ToApub,
-};
+use crate::{check_actor_domain, ActorType, FromApub, PersonExt, ToApub};
 use activitystreams::{
-  activity::{
-    kind::{FollowType, UndoType},
-    Follow,
-    Undo,
-  },
   actor::{ApActor, Endpoints, Person},
   object::{Image, Tombstone},
   prelude::*,
 };
 use activitystreams_ext::Ext1;
-use actix_web::{body::Body, web, HttpResponse};
 use anyhow::Context;
 use lemmy_db::{
   naive_now,
   user::{UserForm, User_},
   DbPool,
 };
-use lemmy_structs::blocking;
 use lemmy_utils::{
   location_info,
   utils::{check_slurs, check_slurs_opt, convert_datetime},
   LemmyError,
 };
 use lemmy_websocket::LemmyContext;
-use serde::Deserialize;
 use url::Url;
 
-#[derive(Deserialize)]
-pub struct UserQuery {
-  user_name: String,
-}
-
 #[async_trait::async_trait(?Send)]
 impl ToApub for User_ {
   type Response = PersonExt;
@@ -93,101 +70,6 @@ impl ToApub for User_ {
   }
 }
 
-#[async_trait::async_trait(?Send)]
-impl ActorType for User_ {
-  fn actor_id_str(&self) -> String {
-    self.actor_id.to_owned()
-  }
-
-  fn public_key(&self) -> Option<String> {
-    self.public_key.to_owned()
-  }
-
-  fn private_key(&self) -> Option<String> {
-    self.private_key.to_owned()
-  }
-
-  /// As a given local user, send out a follow request to a remote community.
-  async fn send_follow(
-    &self,
-    follow_actor_id: &Url,
-    context: &LemmyContext,
-  ) -> Result<(), LemmyError> {
-    let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id.as_str());
-    follow
-      .set_context(activitystreams::context())
-      .set_id(generate_activity_id(FollowType::Follow)?);
-    let follow_actor = get_or_fetch_and_upsert_actor(follow_actor_id, context).await?;
-    let to = follow_actor.get_inbox_url()?;
-
-    send_activity_single_dest(follow, self, to, context).await?;
-    Ok(())
-  }
-
-  async fn send_unfollow(
-    &self,
-    follow_actor_id: &Url,
-    context: &LemmyContext,
-  ) -> Result<(), LemmyError> {
-    let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id.as_str());
-    follow
-      .set_context(activitystreams::context())
-      .set_id(generate_activity_id(FollowType::Follow)?);
-    let follow_actor = get_or_fetch_and_upsert_actor(follow_actor_id, context).await?;
-
-    let to = follow_actor.get_inbox_url()?;
-
-    // Undo that fake activity
-    let mut undo = Undo::new(Url::parse(&self.actor_id)?, follow.into_any_base()?);
-    undo
-      .set_context(activitystreams::context())
-      .set_id(generate_activity_id(UndoType::Undo)?);
-
-    send_activity_single_dest(undo, self, to, context).await?;
-    Ok(())
-  }
-
-  async fn send_delete(&self, _creator: &User_, _context: &LemmyContext) -> Result<(), LemmyError> {
-    unimplemented!()
-  }
-
-  async fn send_undo_delete(
-    &self,
-    _creator: &User_,
-    _context: &LemmyContext,
-  ) -> Result<(), LemmyError> {
-    unimplemented!()
-  }
-
-  async fn send_remove(&self, _creator: &User_, _context: &LemmyContext) -> Result<(), LemmyError> {
-    unimplemented!()
-  }
-
-  async fn send_undo_remove(
-    &self,
-    _creator: &User_,
-    _context: &LemmyContext,
-  ) -> Result<(), LemmyError> {
-    unimplemented!()
-  }
-
-  async fn send_accept_follow(
-    &self,
-    _follow: Follow,
-    _context: &LemmyContext,
-  ) -> Result<(), LemmyError> {
-    unimplemented!()
-  }
-
-  async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result<Vec<Url>, LemmyError> {
-    unimplemented!()
-  }
-
-  fn user_id(&self) -> i32 {
-    self.id
-  }
-}
-
 #[async_trait::async_trait(?Send)]
 impl FromApub for UserForm {
   type ApubType = PersonExt;
@@ -272,17 +154,3 @@ impl FromApub for UserForm {
     })
   }
 }
-
-/// Return the user json over HTTP.
-pub async fn get_apub_user_http(
-  info: web::Path<UserQuery>,
-  context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
-  let user_name = info.into_inner().user_name;
-  let user = blocking(context.pool(), move |conn| {
-    User_::find_by_email_or_username(conn, &user_name)
-  })
-  .await??;
-  let u = user.to_apub(context.pool()).await?;
-  Ok(create_apub_response(&u))
-}
index 36cfb85184b19d5f171d7c401463ef330cd1d7a0..900631e51d7fcb2f5bf02b5faa2df6cbcb8a75ef 100644 (file)
@@ -1,11 +1,13 @@
 use actix_web::*;
 use http_signature_normalization_actix::digest::middleware::VerifyDigest;
 use lemmy_apub::{
-  comment::get_apub_comment,
-  community::*,
+  http::{
+    comment::get_apub_comment,
+    community::{get_apub_community_followers, get_apub_community_http, get_apub_community_outbox},
+    post::get_apub_post,
+    user::get_apub_user_http,
+  },
   inbox::{community_inbox::community_inbox, shared_inbox::shared_inbox, user_inbox::user_inbox},
-  post::get_apub_post,
-  user::*,
   APUB_JSON_CONTENT_TYPE,
 };
 use lemmy_utils::settings::Settings;