mark_post_as_read,
post::*,
};
-use lemmy_apub::{activities::post::update::UpdatePost, ApubLikeableType};
+use lemmy_apub::{
+ activities::{post::create_or_update::CreateOrUpdatePost, CreateOrUpdateType},
+ ApubLikeableType,
+};
use lemmy_db_queries::{source::post::Post_, Crud, Likeable, Saveable};
use lemmy_db_schema::source::{moderator::*, post::*};
use lemmy_db_views::post_view::PostView;
blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??;
// apub updates
- UpdatePost::send(&updated_post, &local_user_view.person, context).await?;
+ CreateOrUpdatePost::send(
+ &updated_post,
+ &local_user_view.person,
+ CreateOrUpdateType::Update,
+ context,
+ )
+ .await?;
// Refetch the post
let post_id = data.post_id;
// Apub updates
// TODO stickied should pry work like locked for ease of use
- UpdatePost::send(&updated_post, &local_user_view.person, context).await?;
+ CreateOrUpdatePost::send(
+ &updated_post,
+ &local_user_view.person,
+ CreateOrUpdateType::Update,
+ context,
+ )
+ .await?;
// Refetch the post
let post_id = data.post_id;
get_post,
send_local_notifs,
};
-use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
+use lemmy_apub::{
+ activities::{comment::create_or_update::CreateOrUpdateComment, CreateOrUpdateType},
+ generate_apub_endpoint,
+ ApubLikeableType,
+ EndpointType,
+};
use lemmy_db_queries::{source::comment::Comment_, Crud, Likeable};
use lemmy_db_schema::source::comment::*;
use lemmy_db_views::comment_view::CommentView;
.await?
.map_err(|_| ApiError::err("couldnt_create_comment"))?;
- updated_comment
- .send_create(&local_user_view.person, context)
- .await?;
+ CreateOrUpdateComment::send(
+ &updated_comment,
+ &local_user_view.person,
+ CreateOrUpdateType::Create,
+ context,
+ )
+ .await?;
// Scan the comment for user mentions, add those rows
let post_id = post.id;
get_local_user_view_from_jwt,
send_local_notifs,
};
-use lemmy_apub::ApubObjectType;
+use lemmy_apub::activities::{
+ comment::create_or_update::CreateOrUpdateComment,
+ CreateOrUpdateType,
+};
use lemmy_db_queries::{source::comment::Comment_, DeleteableOrRemoveable};
use lemmy_db_schema::source::comment::*;
use lemmy_db_views::comment_view::CommentView;
.map_err(|_| ApiError::err("couldnt_update_comment"))?;
// Send the apub update
- updated_comment
- .send_update(&local_user_view.person, context)
- .await?;
+ CreateOrUpdateComment::send(
+ &updated_comment,
+ &local_user_view.person,
+ CreateOrUpdateType::Update,
+ context,
+ )
+ .await?;
// Do the mentions / recipients
let updated_comment_content = updated_comment.content.to_owned();
post::*,
};
use lemmy_apub::{
- activities::post::create::CreatePost as CreateApubPost,
+ activities::{post::create_or_update::CreateOrUpdatePost, CreateOrUpdateType},
generate_apub_endpoint,
ApubLikeableType,
EndpointType,
.await?
.map_err(|_| ApiError::err("couldnt_create_post"))?;
- CreateApubPost::send(&updated_post, &local_user_view.person, context).await?;
+ CreateOrUpdatePost::send(
+ &updated_post,
+ &local_user_view.person,
+ CreateOrUpdateType::Create,
+ context,
+ )
+ .await?;
// They like their own post by default
let person_id = local_user_view.person.id;
use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{blocking, check_community_ban, get_local_user_view_from_jwt, post::*};
-use lemmy_apub::activities::post::update::UpdatePost;
+use lemmy_apub::activities::{post::create_or_update::CreateOrUpdatePost, CreateOrUpdateType};
use lemmy_db_queries::{source::post::Post_, Crud, DeleteableOrRemoveable};
use lemmy_db_schema::{naive_now, source::post::*};
use lemmy_db_views::post_view::PostView;
};
// Send apub update
- UpdatePost::send(&updated_post, &local_user_view.person, context).await?;
+ CreateOrUpdatePost::send(
+ &updated_post,
+ &local_user_view.person,
+ CreateOrUpdateType::Update,
+ context,
+ )
+ .await?;
let post_id = data.post_id;
let mut post_view = blocking(context.pool(), move |conn| {
person::{CreatePrivateMessage, PrivateMessageResponse},
send_email_to_user,
};
-use lemmy_apub::{generate_apub_endpoint, ApubObjectType, EndpointType};
+use lemmy_apub::{
+ activities::{
+ private_message::create_or_update::CreateOrUpdatePrivateMessage,
+ CreateOrUpdateType,
+ },
+ generate_apub_endpoint,
+ EndpointType,
+};
use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud};
use lemmy_db_schema::source::private_message::{PrivateMessage, PrivateMessageForm};
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
.await?
.map_err(|_| ApiError::err("couldnt_create_private_message"))?;
- updated_private_message
- .send_create(&local_user_view.person, context)
- .await?;
+ CreateOrUpdatePrivateMessage::send(
+ &updated_private_message,
+ &local_user_view.person,
+ CreateOrUpdateType::Create,
+ context,
+ )
+ .await?;
let private_message_view = blocking(context.pool(), move |conn| {
PrivateMessageView::read(conn, inserted_private_message.id)
get_local_user_view_from_jwt,
person::{EditPrivateMessage, PrivateMessageResponse},
};
-use lemmy_apub::ApubObjectType;
+use lemmy_apub::activities::{
+ private_message::create_or_update::CreateOrUpdatePrivateMessage,
+ CreateOrUpdateType,
+};
use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud, DeleteableOrRemoveable};
use lemmy_db_schema::source::private_message::PrivateMessage;
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
.map_err(|_| ApiError::err("couldnt_update_private_message"))?;
// Send the apub update
- updated_private_message
- .send_update(&local_user_view.person, context)
- .await?;
+ CreateOrUpdatePrivateMessage::send(
+ &updated_private_message,
+ &local_user_view.person,
+ CreateOrUpdateType::Update,
+ context,
+ )
+ .await?;
let private_message_id = data.private_message_id;
let mut private_message_view = blocking(context.pool(), move |conn| {
+++ /dev/null
-use crate::{
- activities::{
- comment::{get_notif_recipients, send_websocket_message},
- extract_community,
- verify_activity,
- verify_person_in_community,
- },
- objects::FromApub,
- ActorType,
- NoteExt,
-};
-use activitystreams::{activity::kind::CreateType, base::BaseExt};
-use lemmy_apub_lib::{
- values::PublicUrl,
- verify_domains_match_opt,
- ActivityCommonFields,
- ActivityHandler,
-};
-use lemmy_db_schema::source::comment::Comment;
-use lemmy_utils::LemmyError;
-use lemmy_websocket::{LemmyContext, UserOperationCrud};
-use url::Url;
-
-#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CreateComment {
- to: PublicUrl,
- object: NoteExt,
- cc: Vec<Url>,
- #[serde(rename = "type")]
- kind: CreateType,
- #[serde(flatten)]
- common: ActivityCommonFields,
-}
-
-#[async_trait::async_trait(?Send)]
-impl ActivityHandler for CreateComment {
- async fn verify(
- &self,
- context: &LemmyContext,
- request_counter: &mut i32,
- ) -> Result<(), LemmyError> {
- let community = extract_community(&self.cc, context, request_counter).await?;
-
- verify_activity(self.common())?;
- verify_person_in_community(
- &self.common.actor,
- &community.actor_id(),
- context,
- request_counter,
- )
- .await?;
- verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
- // TODO: should add a check that the correct community is in cc (probably needs changes to
- // comment deserialization)
- Ok(())
- }
-
- async fn receive(
- &self,
- context: &LemmyContext,
- request_counter: &mut i32,
- ) -> Result<(), LemmyError> {
- let comment = Comment::from_apub(
- &self.object,
- context,
- self.common.actor.clone(),
- request_counter,
- false,
- )
- .await?;
- let recipients =
- get_notif_recipients(&self.common.actor, &comment, context, request_counter).await?;
- send_websocket_message(
- comment.id,
- recipients,
- UserOperationCrud::CreateComment,
- context,
- )
- .await
- }
-
- fn common(&self) -> &ActivityCommonFields {
- &self.common
- }
-}
--- /dev/null
+use crate::{
+ activities::{
+ comment::{collect_non_local_mentions, get_notif_recipients, send_websocket_message},
+ community::announce::AnnouncableActivities,
+ extract_community,
+ generate_activity_id,
+ verify_activity,
+ verify_person_in_community,
+ CreateOrUpdateType,
+ },
+ activity_queue::send_to_community_new,
+ extensions::context::lemmy_context,
+ objects::{comment::Note, FromApub, ToApub},
+ ActorType,
+};
+use activitystreams::link::Mention;
+use lemmy_api_common::blocking;
+use lemmy_apub_lib::{
+ values::PublicUrl,
+ verify_domains_match,
+ ActivityCommonFields,
+ ActivityHandler,
+};
+use lemmy_db_queries::Crud;
+use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use serde::{Deserialize, Serialize};
+use url::Url;
+
+#[derive(Clone, Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CreateOrUpdateComment {
+ to: PublicUrl,
+ object: Note,
+ cc: Vec<Url>,
+ tag: Vec<Mention>,
+ #[serde(rename = "type")]
+ kind: CreateOrUpdateType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+impl CreateOrUpdateComment {
+ pub async fn send(
+ comment: &Comment,
+ actor: &Person,
+ kind: CreateOrUpdateType,
+ context: &LemmyContext,
+ ) -> Result<(), LemmyError> {
+ // TODO: might be helpful to add a comment method to retrieve community directly
+ let post_id = comment.post_id;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
+ let community_id = post.community_id;
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
+
+ let id = generate_activity_id(kind.clone())?;
+ let maa = collect_non_local_mentions(comment, &community, context).await?;
+
+ let create_or_update = CreateOrUpdateComment {
+ to: PublicUrl::Public,
+ object: comment.to_apub(context.pool()).await?,
+ cc: maa.ccs,
+ tag: maa.tags,
+ kind,
+ common: ActivityCommonFields {
+ context: lemmy_context(),
+ id: id.clone(),
+ actor: actor.actor_id(),
+ unparsed: Default::default(),
+ },
+ };
+
+ let activity = AnnouncableActivities::CreateOrUpdateComment(create_or_update);
+ send_to_community_new(activity, &id, actor, &community, maa.inboxes, context).await
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for CreateOrUpdateComment {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let community = extract_community(&self.cc, context, request_counter).await?;
+
+ verify_activity(self.common())?;
+ verify_person_in_community(
+ &self.common.actor,
+ &community.actor_id(),
+ context,
+ request_counter,
+ )
+ .await?;
+ verify_domains_match(&self.common.actor, &self.object.id)?;
+ // TODO: should add a check that the correct community is in cc (probably needs changes to
+ // comment deserialization)
+ self.object.verify(context, request_counter).await?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let comment = Comment::from_apub(
+ &self.object,
+ context,
+ self.common.actor.clone(),
+ request_counter,
+ false,
+ )
+ .await?;
+ let recipients =
+ get_notif_recipients(&self.common.actor, &comment, context, request_counter).await?;
+ let notif_type = match self.kind {
+ CreateOrUpdateType::Create => UserOperationCrud::CreateComment,
+ CreateOrUpdateType::Update => UserOperationCrud::EditComment,
+ };
+ send_websocket_message(comment.id, recipients, notif_type, context).await
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
-use crate::fetcher::person::get_or_fetch_and_upsert_person;
-use lemmy_api_common::{blocking, comment::CommentResponse, send_local_notifs};
-use lemmy_db_queries::Crud;
+use crate::{fetcher::person::get_or_fetch_and_upsert_person, ActorType};
+use activitystreams::{
+ base::BaseExt,
+ link::{LinkExt, Mention},
+};
+use anyhow::anyhow;
+use itertools::Itertools;
+use lemmy_api_common::{blocking, comment::CommentResponse, send_local_notifs, WebFingerResponse};
+use lemmy_db_queries::{Crud, DbPool};
use lemmy_db_schema::{
- source::{comment::Comment, post::Post},
+ source::{comment::Comment, community::Community, person::Person, post::Post},
CommentId,
LocalUserId,
};
use lemmy_db_views::comment_view::CommentView;
-use lemmy_utils::{utils::scrape_text_for_mentions, LemmyError};
+use lemmy_utils::{
+ request::{retry, RecvError},
+ settings::structs::Settings,
+ utils::{scrape_text_for_mentions, MentionData},
+ LemmyError,
+};
use lemmy_websocket::{messages::SendComment, LemmyContext};
+use log::debug;
+use reqwest::Client;
use url::Url;
-pub mod create;
-pub mod update;
+pub mod create_or_update;
async fn get_notif_recipients(
actor: &Url,
Ok(())
}
+
+pub struct MentionsAndAddresses {
+ pub ccs: Vec<Url>,
+ pub inboxes: Vec<Url>,
+ pub tags: Vec<Mention>,
+}
+
+/// This takes a comment, and builds a list of to_addresses, inboxes,
+/// and mention tags, so they know where to be sent to.
+/// Addresses are the persons / addresses that go in the cc field.
+pub async fn collect_non_local_mentions(
+ comment: &Comment,
+ community: &Community,
+ context: &LemmyContext,
+) -> Result<MentionsAndAddresses, LemmyError> {
+ let parent_creator = get_comment_parent_creator(context.pool(), comment).await?;
+ let mut addressed_ccs = vec![community.actor_id(), parent_creator.actor_id()];
+ // Note: dont include community inbox here, as we send to it separately with `send_to_community()`
+ let mut inboxes = vec![parent_creator.get_shared_inbox_or_inbox_url()];
+
+ // Add the mention tag
+ let mut tags = Vec::new();
+
+ // Get the person IDs for any mentions
+ let mentions = scrape_text_for_mentions(&comment.content)
+ .into_iter()
+ // Filter only the non-local ones
+ .filter(|m| !m.is_local())
+ .collect::<Vec<MentionData>>();
+
+ for mention in &mentions {
+ // TODO should it be fetching it every time?
+ if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await {
+ debug!("mention actor_id: {}", actor_id);
+ addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
+
+ let mention_person = get_or_fetch_and_upsert_person(&actor_id, context, &mut 0).await?;
+ inboxes.push(mention_person.get_shared_inbox_or_inbox_url());
+
+ let mut mention_tag = Mention::new();
+ mention_tag.set_href(actor_id).set_name(mention.full_name());
+ tags.push(mention_tag);
+ }
+ }
+
+ let inboxes = inboxes.into_iter().unique().collect();
+
+ Ok(MentionsAndAddresses {
+ ccs: addressed_ccs,
+ inboxes,
+ tags,
+ })
+}
+
+/// Returns the apub ID of the person this comment is responding to. Meaning, in case this is a
+/// top-level comment, the creator of the post, otherwise the creator of the parent comment.
+async fn get_comment_parent_creator(
+ pool: &DbPool,
+ comment: &Comment,
+) -> Result<Person, LemmyError> {
+ let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id {
+ let parent_comment =
+ blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
+ parent_comment.creator_id
+ } else {
+ let parent_post_id = comment.post_id;
+ let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??;
+ parent_post.creator_id
+ };
+ Ok(blocking(pool, move |conn| Person::read(conn, parent_creator_id)).await??)
+}
+
+/// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
+/// using webfinger.
+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()
+ .ok_or_else(|| anyhow!("No href found.").into())
+}
+++ /dev/null
-use crate::{
- activities::{comment::send_websocket_message, verify_mod_action},
- check_is_apub_id_valid,
- fetcher::objects::get_or_fetch_and_insert_comment,
-};
-use activitystreams::activity::kind::RemoveType;
-use lemmy_api_common::blocking;
-use lemmy_apub_lib::{
- values::PublicUrl,
- verify_domains_match,
- ActivityCommonFields,
- ActivityHandlerNew,
-};
-use lemmy_db_queries::source::comment::Comment_;
-use lemmy_db_schema::source::comment::Comment;
-use lemmy_utils::LemmyError;
-use lemmy_websocket::{LemmyContext, UserOperationCrud};
-use url::Url;
-
-#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct RemoveComment {
- to: PublicUrl,
- pub(in crate::activities::comment) object: Url,
- cc: [Url; 1],
- #[serde(rename = "type")]
- kind: RemoveType,
- #[serde(flatten)]
- common: ActivityCommonFields,
-}
-
-#[async_trait::async_trait(?Send)]
-impl ActivityHandlerNew for RemoveComment {
- async fn verify(&self, context: &LemmyContext, _: &mut i32) -> Result<(), LemmyError> {
- verify_domains_match(&self.common.actor, self.common.id_unchecked())?;
- check_is_apub_id_valid(&self.common.actor, false)?;
- verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await
- }
-
- async fn receive(
- &self,
- context: &LemmyContext,
- request_counter: &mut i32,
- ) -> Result<(), LemmyError> {
- let comment = get_or_fetch_and_insert_comment(&self.object, context, request_counter).await?;
-
- let removed_comment = blocking(context.pool(), move |conn| {
- Comment::update_removed(conn, comment.id, true)
- })
- .await??;
-
- send_websocket_message(
- removed_comment.id,
- vec![],
- UserOperationCrud::EditComment,
- context,
- )
- .await
- }
-
- fn common(&self) -> &ActivityCommonFields {
- &self.common
- }
-}
+++ /dev/null
-use crate::{
- activities::{
- comment::{remove::RemoveComment, send_websocket_message},
- verify_mod_action,
- },
- check_is_apub_id_valid,
- fetcher::objects::get_or_fetch_and_insert_comment,
-};
-use activitystreams::activity::kind::UndoType;
-use lemmy_api_common::blocking;
-use lemmy_apub_lib::{
- values::PublicUrl,
- verify_domains_match,
- ActivityCommonFields,
- ActivityHandlerNew,
-};
-use lemmy_db_queries::source::comment::Comment_;
-use lemmy_db_schema::source::comment::Comment;
-use lemmy_utils::LemmyError;
-use lemmy_websocket::{LemmyContext, UserOperationCrud};
-use url::Url;
-
-#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct UndoRemoveComment {
- to: PublicUrl,
- object: RemoveComment,
- cc: [Url; 1],
- #[serde(rename = "type")]
- kind: UndoType,
- #[serde(flatten)]
- common: ActivityCommonFields,
-}
-
-#[async_trait::async_trait(?Send)]
-impl ActivityHandlerNew for UndoRemoveComment {
- async fn verify(
- &self,
- context: &LemmyContext,
- request_counter: &mut i32,
- ) -> Result<(), LemmyError> {
- verify_domains_match(&self.common.actor, self.common.id_unchecked())?;
- check_is_apub_id_valid(&self.common.actor, false)?;
- verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
- self.object.verify(context, request_counter).await
- }
-
- async fn receive(
- &self,
- context: &LemmyContext,
- request_counter: &mut i32,
- ) -> Result<(), LemmyError> {
- let comment =
- get_or_fetch_and_insert_comment(&self.object.object, context, request_counter).await?;
-
- let removed_comment = blocking(context.pool(), move |conn| {
- Comment::update_removed(conn, comment.id, false)
- })
- .await??;
-
- send_websocket_message(
- removed_comment.id,
- vec![],
- UserOperationCrud::EditComment,
- context,
- )
- .await
- }
-
- fn common(&self) -> &ActivityCommonFields {
- &self.common
- }
-}
+++ /dev/null
-use crate::{
- activities::{
- comment::{get_notif_recipients, send_websocket_message},
- extract_community,
- verify_activity,
- verify_person_in_community,
- },
- objects::FromApub,
- ActorType,
- NoteExt,
-};
-use activitystreams::{activity::kind::UpdateType, base::BaseExt};
-use lemmy_apub_lib::{
- values::PublicUrl,
- verify_domains_match_opt,
- ActivityCommonFields,
- ActivityHandler,
-};
-use lemmy_db_schema::source::comment::Comment;
-use lemmy_utils::LemmyError;
-use lemmy_websocket::{LemmyContext, UserOperationCrud};
-use url::Url;
-
-#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct UpdateComment {
- to: PublicUrl,
- object: NoteExt,
- cc: Vec<Url>,
- #[serde(rename = "type")]
- kind: UpdateType,
- #[serde(flatten)]
- common: ActivityCommonFields,
-}
-
-#[async_trait::async_trait(?Send)]
-impl ActivityHandler for UpdateComment {
- async fn verify(
- &self,
- context: &LemmyContext,
- request_counter: &mut i32,
- ) -> Result<(), LemmyError> {
- let community = extract_community(&self.cc, context, request_counter).await?;
-
- verify_activity(self.common())?;
- verify_person_in_community(
- &self.common.actor,
- &community.actor_id(),
- context,
- request_counter,
- )
- .await?;
- verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
- Ok(())
- }
-
- async fn receive(
- &self,
- context: &LemmyContext,
- request_counter: &mut i32,
- ) -> Result<(), LemmyError> {
- let comment = Comment::from_apub(
- &self.object,
- context,
- self.common.actor.clone(),
- request_counter,
- false,
- )
- .await?;
-
- let recipients =
- get_notif_recipients(&self.common.actor, &comment, context, request_counter).await?;
- send_websocket_message(
- comment.id,
- recipients,
- UserOperationCrud::EditComment,
- context,
- )
- .await
- }
-
- fn common(&self) -> &ActivityCommonFields {
- &self.common
- }
-}
use crate::{
activities::{
- comment::{create::CreateComment, update::UpdateComment},
+ comment::create_or_update::CreateOrUpdateComment,
community::{
add_mod::AddMod,
block_user::BlockUserFromCommunity,
undo_delete::UndoDeletePostCommentOrCommunity,
},
generate_activity_id,
- post::{create::CreatePost, update::UpdatePost},
+ post::create_or_update::CreateOrUpdatePost,
removal::{
remove::RemovePostCommentCommunityOrMod,
undo_remove::UndoRemovePostCommentOrCommunity,
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)]
#[serde(untagged)]
pub enum AnnouncableActivities {
- CreateComment(CreateComment),
- UpdateComment(UpdateComment),
- CreatePost(CreatePost),
- UpdatePost(UpdatePost),
+ CreateOrUpdateComment(CreateOrUpdateComment),
+ CreateOrUpdatePost(Box<CreateOrUpdatePost>),
LikePostOrComment(LikePostOrComment),
DislikePostOrComment(DislikePostOrComment),
UndoLikePostOrComment(UndoLikePostOrComment),
kind: AnnounceType::Announce,
common: ActivityCommonFields {
context: lemmy_context(),
- id: generate_activity_id(AnnounceType::Announce)?,
+ id: generate_activity_id(&AnnounceType::Announce)?,
actor: community.actor_id(),
unparsed: Default::default(),
},
use lemmy_db_views_actor::community_view::CommunityView;
use lemmy_utils::{settings::structs::Settings, LemmyError};
use lemmy_websocket::LemmyContext;
+use serde::{Deserialize, Serialize};
+use strum_macros::ToString;
use url::{ParseError, Url};
use uuid::Uuid;
pub mod send;
pub mod voting;
+#[derive(Clone, Debug, ToString, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub enum CreateOrUpdateType {
+ Create,
+ Update,
+}
+
/// Checks that the specified Url actually identifies a Person (by fetching it), and that the person
/// doesn't have a site ban.
async fn verify_person(
/// Fetches the person and community to verify their type, then checks if person is banned from site
/// or community.
-async fn verify_person_in_community(
+pub(crate) async fn verify_person_in_community(
person_id: &Url,
community_id: &Url,
context: &LemmyContext,
generate_activity_id,
post::send_websocket_message,
verify_activity,
+ verify_mod_action,
verify_person_in_community,
+ CreateOrUpdateType,
},
activity_queue::send_to_community_new,
extensions::context::lemmy_context,
objects::{post::Page, FromApub, ToApub},
ActorType,
};
-use activitystreams::activity::kind::CreateType;
use anyhow::anyhow;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
-pub struct CreatePost {
+pub struct CreateOrUpdatePost {
to: PublicUrl,
object: Page,
cc: [Url; 1],
- r#type: CreateType,
+ #[serde(rename = "type")]
+ kind: CreateOrUpdateType,
#[serde(flatten)]
common: ActivityCommonFields,
}
-impl CreatePost {
- pub async fn send(post: &Post, actor: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
+impl CreateOrUpdatePost {
+ pub async fn send(
+ post: &Post,
+ actor: &Person,
+ kind: CreateOrUpdateType,
+ context: &LemmyContext,
+ ) -> Result<(), LemmyError> {
let community_id = post.community_id;
let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
- let id = generate_activity_id(CreateType::Create)?;
- let create = CreatePost {
+ let id = generate_activity_id(kind.clone())?;
+ let create_or_update = CreateOrUpdatePost {
to: PublicUrl::Public,
object: post.to_apub(context.pool()).await?,
cc: [community.actor_id()],
- r#type: Default::default(),
+ kind,
common: ActivityCommonFields {
context: lemmy_context(),
id: id.clone(),
},
};
- let activity = AnnouncableActivities::CreatePost(create);
+ let activity = AnnouncableActivities::CreateOrUpdatePost(Box::new(create_or_update));
send_to_community_new(activity, &id, actor, &community, vec![], context).await
}
}
#[async_trait::async_trait(?Send)]
-impl ActivityHandler for CreatePost {
+impl ActivityHandler for CreateOrUpdatePost {
async fn verify(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
- let community = extract_community(&self.cc, context, request_counter).await?;
- let community_id = &community.actor_id();
-
verify_activity(self.common())?;
- verify_person_in_community(&self.common.actor, community_id, context, request_counter).await?;
- verify_domains_match(&self.common.actor, &self.object.id)?;
- verify_urls_match(&self.common.actor, &self.object.attributed_to)?;
- // Check that the post isnt locked or stickied, as that isnt possible for newly created posts.
- // However, when fetching a remote post we generate a new create activity with the current
- // locked/stickied value, so this check may fail. So only check if its a local community,
- // because then we will definitely receive all create and update activities separately.
- let is_stickied_or_locked =
- self.object.stickied == Some(true) || self.object.comments_enabled == Some(false);
- if community.local && is_stickied_or_locked {
- return Err(anyhow!("New post cannot be stickied or locked").into());
+ let community = extract_community(&self.cc, context, request_counter).await?;
+ let community_id = community.actor_id();
+ verify_person_in_community(&self.common.actor, &community_id, context, request_counter).await?;
+ match self.kind {
+ CreateOrUpdateType::Create => {
+ verify_domains_match(&self.common.actor, &self.object.id)?;
+ verify_urls_match(&self.common.actor, &self.object.attributed_to)?;
+ // Check that the post isnt locked or stickied, as that isnt possible for newly created posts.
+ // However, when fetching a remote post we generate a new create activity with the current
+ // locked/stickied value, so this check may fail. So only check if its a local community,
+ // because then we will definitely receive all create and update activities separately.
+ let is_stickied_or_locked =
+ self.object.stickied == Some(true) || self.object.comments_enabled == Some(false);
+ if community.local && is_stickied_or_locked {
+ return Err(anyhow!("New post cannot be stickied or locked").into());
+ }
+ }
+ CreateOrUpdateType::Update => {
+ let is_mod_action = self.object.is_mod_action(context.pool()).await?;
+ if is_mod_action {
+ verify_mod_action(&self.common.actor, community_id, context).await?;
+ } else {
+ verify_domains_match(&self.common.actor, &self.object.id)?;
+ verify_urls_match(&self.common.actor, &self.object.attributed_to)?;
+ }
+ }
}
self.object.verify(context, request_counter).await?;
Ok(())
)
.await?;
- send_websocket_message(post.id, UserOperationCrud::CreatePost, context).await
+ let notif_type = match self.kind {
+ CreateOrUpdateType::Create => UserOperationCrud::CreatePost,
+ CreateOrUpdateType::Update => UserOperationCrud::EditPost,
+ };
+ send_websocket_message(post.id, notif_type, context).await
}
fn common(&self) -> &ActivityCommonFields {
use lemmy_utils::LemmyError;
use lemmy_websocket::{messages::SendPost, LemmyContext};
-pub mod create;
-pub mod update;
+pub mod create_or_update;
pub(crate) async fn send_websocket_message<
OP: ToString + Send + lemmy_websocket::OperationType + 'static,
+++ /dev/null
-use crate::{
- activities::{
- community::announce::AnnouncableActivities,
- generate_activity_id,
- post::send_websocket_message,
- verify_activity,
- verify_mod_action,
- verify_person_in_community,
- },
- activity_queue::send_to_community_new,
- extensions::context::lemmy_context,
- fetcher::community::get_or_fetch_and_upsert_community,
- objects::{post::Page, FromApub, ToApub},
- ActorType,
-};
-use activitystreams::activity::kind::UpdateType;
-use lemmy_api_common::blocking;
-use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler};
-use lemmy_db_queries::Crud;
-use lemmy_db_schema::source::{community::Community, person::Person, post::Post};
-use lemmy_utils::LemmyError;
-use lemmy_websocket::{LemmyContext, UserOperationCrud};
-use url::Url;
-
-#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct UpdatePost {
- to: PublicUrl,
- object: Page,
- cc: [Url; 1],
- r#type: UpdateType,
- #[serde(flatten)]
- common: ActivityCommonFields,
-}
-
-impl UpdatePost {
- pub async fn send(post: &Post, actor: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
- let community_id = post.community_id;
- let community = blocking(context.pool(), move |conn| {
- Community::read(conn, community_id)
- })
- .await??;
-
- let id = generate_activity_id(UpdateType::Update)?;
- let update = UpdatePost {
- to: PublicUrl::Public,
- object: post.to_apub(context.pool()).await?,
- cc: [community.actor_id()],
- r#type: Default::default(),
- common: ActivityCommonFields {
- context: lemmy_context(),
- id: id.clone(),
- actor: actor.actor_id(),
- unparsed: Default::default(),
- },
- };
- let activity = AnnouncableActivities::UpdatePost(update);
- send_to_community_new(activity, &id, actor, &community, vec![], context).await
- }
-}
-
-#[async_trait::async_trait(?Send)]
-impl ActivityHandler for UpdatePost {
- async fn verify(
- &self,
- context: &LemmyContext,
- request_counter: &mut i32,
- ) -> Result<(), LemmyError> {
- let community_id = get_or_fetch_and_upsert_community(&self.cc[0], context, request_counter)
- .await?
- .actor_id();
- let is_mod_action = self.object.is_mod_action(context.pool()).await?;
-
- verify_activity(self.common())?;
- verify_person_in_community(&self.common.actor, &community_id, context, request_counter).await?;
- if is_mod_action {
- verify_mod_action(&self.common.actor, community_id, context).await?;
- } else {
- verify_urls_match(&self.common.actor, &self.object.attributed_to)?;
- }
- self.object.verify(context, request_counter).await?;
- Ok(())
- }
-
- async fn receive(
- &self,
- context: &LemmyContext,
- request_counter: &mut i32,
- ) -> Result<(), LemmyError> {
- let post = Post::from_apub(
- &self.object,
- context,
- self.common.actor.clone(),
- request_counter,
- // TODO: we already check here if the mod action is valid, can remove that check param
- true,
- )
- .await?;
-
- send_websocket_message(post.id, UserOperationCrud::EditPost, context).await
- }
-
- fn common(&self) -> &ActivityCommonFields {
- &self.common
- }
-}
+++ /dev/null
-use crate::{
- activities::{private_message::send_websocket_message, verify_activity, verify_person},
- objects::FromApub,
- NoteExt,
-};
-use activitystreams::{activity::kind::CreateType, base::BaseExt};
-use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler};
-use lemmy_db_schema::source::private_message::PrivateMessage;
-use lemmy_utils::LemmyError;
-use lemmy_websocket::{LemmyContext, UserOperationCrud};
-use url::Url;
-
-#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CreatePrivateMessage {
- to: Url,
- object: NoteExt,
- #[serde(rename = "type")]
- kind: CreateType,
- #[serde(flatten)]
- common: ActivityCommonFields,
-}
-
-#[async_trait::async_trait(?Send)]
-impl ActivityHandler for CreatePrivateMessage {
- async fn verify(
- &self,
- context: &LemmyContext,
- request_counter: &mut i32,
- ) -> Result<(), LemmyError> {
- verify_activity(self.common())?;
- verify_person(&self.common.actor, context, request_counter).await?;
- verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
- Ok(())
- }
-
- async fn receive(
- &self,
- context: &LemmyContext,
- request_counter: &mut i32,
- ) -> Result<(), LemmyError> {
- let private_message = PrivateMessage::from_apub(
- &self.object,
- context,
- self.common.actor.clone(),
- request_counter,
- false,
- )
- .await?;
-
- send_websocket_message(
- private_message.id,
- UserOperationCrud::CreatePrivateMessage,
- context,
- )
- .await?;
-
- Ok(())
- }
-
- fn common(&self) -> &ActivityCommonFields {
- &self.common
- }
-}
--- /dev/null
+use crate::{
+ activities::{
+ generate_activity_id,
+ private_message::send_websocket_message,
+ verify_activity,
+ verify_person,
+ CreateOrUpdateType,
+ },
+ activity_queue::send_activity_new,
+ extensions::context::lemmy_context,
+ objects::{private_message::Note, FromApub, ToApub},
+ ActorType,
+};
+use lemmy_api_common::blocking;
+use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandler};
+use lemmy_db_queries::Crud;
+use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CreateOrUpdatePrivateMessage {
+ to: Url,
+ object: Note,
+ #[serde(rename = "type")]
+ kind: CreateOrUpdateType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+impl CreateOrUpdatePrivateMessage {
+ pub async fn send(
+ private_message: &PrivateMessage,
+ actor: &Person,
+ kind: CreateOrUpdateType,
+ context: &LemmyContext,
+ ) -> Result<(), LemmyError> {
+ let recipient_id = private_message.recipient_id;
+ let recipient =
+ blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
+
+ let id = generate_activity_id(kind.clone())?;
+ let create_or_update = CreateOrUpdatePrivateMessage {
+ to: recipient.actor_id(),
+ object: private_message.to_apub(context.pool()).await?,
+ kind,
+ common: ActivityCommonFields {
+ context: lemmy_context(),
+ id: id.clone(),
+ actor: actor.actor_id(),
+ unparsed: Default::default(),
+ },
+ };
+ send_activity_new(
+ context,
+ &create_or_update,
+ &id,
+ actor,
+ vec![recipient.get_shared_inbox_or_inbox_url()],
+ true,
+ )
+ .await
+ }
+}
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for CreateOrUpdatePrivateMessage {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person(&self.common.actor, context, request_counter).await?;
+ verify_domains_match(&self.common.actor, &self.object.id)?;
+ self.object.verify(context, request_counter).await?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let private_message = PrivateMessage::from_apub(
+ &self.object,
+ context,
+ self.common.actor.clone(),
+ request_counter,
+ false,
+ )
+ .await?;
+
+ let notif_type = match self.kind {
+ CreateOrUpdateType::Create => UserOperationCrud::CreatePrivateMessage,
+ CreateOrUpdateType::Update => UserOperationCrud::EditPrivateMessage,
+ };
+ send_websocket_message(private_message.id, notif_type, context).await?;
+
+ Ok(())
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
use lemmy_utils::LemmyError;
use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperationCrud};
-pub mod create;
+pub mod create_or_update;
pub mod delete;
pub mod undo_delete;
-pub mod update;
async fn send_websocket_message(
private_message_id: PrivateMessageId,
+++ /dev/null
-use crate::{
- activities::{private_message::send_websocket_message, verify_activity, verify_person},
- objects::FromApub,
- NoteExt,
-};
-use activitystreams::{activity::kind::UpdateType, base::BaseExt};
-use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler};
-use lemmy_db_schema::source::private_message::PrivateMessage;
-use lemmy_utils::LemmyError;
-use lemmy_websocket::{LemmyContext, UserOperationCrud};
-use url::Url;
-
-#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct UpdatePrivateMessage {
- to: Url,
- object: NoteExt,
- #[serde(rename = "type")]
- kind: UpdateType,
- #[serde(flatten)]
- common: ActivityCommonFields,
-}
-
-#[async_trait::async_trait(?Send)]
-impl ActivityHandler for UpdatePrivateMessage {
- async fn verify(
- &self,
- context: &LemmyContext,
- request_counter: &mut i32,
- ) -> Result<(), LemmyError> {
- verify_activity(self.common())?;
- verify_person(&self.common.actor, context, request_counter).await?;
- verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
- Ok(())
- }
-
- async fn receive(
- &self,
- context: &LemmyContext,
- request_counter: &mut i32,
- ) -> Result<(), LemmyError> {
- let private_message = PrivateMessage::from_apub(
- &self.object,
- context,
- self.common.actor.clone(),
- request_counter,
- false,
- )
- .await?;
-
- send_websocket_message(
- private_message.id,
- UserOperationCrud::EditPrivateMessage,
- context,
- )
- .await?;
-
- Ok(())
- }
-
- fn common(&self) -> &ActivityCommonFields {
- &self.common
- }
-}
use crate::{
activities::generate_activity_id,
- activity_queue::{send_comment_mentions, send_to_community},
+ activity_queue::send_to_community,
extensions::context::lemmy_context,
- fetcher::person::get_or_fetch_and_upsert_person,
- objects::ToApub,
ActorType,
ApubLikeableType,
ApubObjectType,
};
use activitystreams::{
activity::{
- kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
- Create,
+ kind::{DeleteType, DislikeType, LikeType, RemoveType, UndoType},
Delete,
Dislike,
Like,
Remove,
Undo,
- Update,
},
- base::AnyBase,
- link::Mention,
prelude::*,
public,
};
-use anyhow::anyhow;
-use itertools::Itertools;
-use lemmy_api_common::{blocking, WebFingerResponse};
-use lemmy_db_queries::{Crud, DbPool};
+use lemmy_api_common::blocking;
+use lemmy_db_queries::Crud;
use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post};
-use lemmy_utils::{
- request::{retry, RecvError},
- settings::structs::Settings,
- utils::{scrape_text_for_mentions, MentionData},
- LemmyError,
-};
+use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
-use log::debug;
-use reqwest::Client;
-use serde_json::Error;
-use url::Url;
#[async_trait::async_trait(?Send)]
impl ApubObjectType for Comment {
- /// Send out information about a newly created comment, to the followers of the community and
- /// mentioned persons.
- async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
- let note = self.to_apub(context.pool()).await?;
-
- let post_id = self.post_id;
- let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
-
- let community_id = post.community_id;
- let community = blocking(context.pool(), move |conn| {
- Community::read(conn, community_id)
- })
- .await??;
-
- let maa = collect_non_local_mentions(self, &community, context).await?;
-
- let mut create = Create::new(
- creator.actor_id.to_owned().into_inner(),
- note.into_any_base()?,
- );
- create
- .set_many_contexts(lemmy_context())
- .set_id(generate_activity_id(CreateType::Create)?)
- .set_to(public())
- .set_many_ccs(maa.ccs.to_owned())
- // Set the mention tags
- .set_many_tags(maa.get_tags()?);
-
- send_to_community(create.clone(), creator, &community, None, context).await?;
- send_comment_mentions(creator, maa.inboxes, create, context).await?;
- Ok(())
+ async fn send_create(
+ &self,
+ _creator: &Person,
+ _context: &LemmyContext,
+ ) -> Result<(), LemmyError> {
+ unimplemented!()
}
- /// Send out information about an edited post, to the followers of the community and mentioned
- /// persons.
- async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
- let note = self.to_apub(context.pool()).await?;
-
- let post_id = self.post_id;
- let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
-
- let community_id = post.community_id;
- let community = blocking(context.pool(), move |conn| {
- Community::read(conn, community_id)
- })
- .await??;
-
- let maa = collect_non_local_mentions(self, &community, context).await?;
-
- let mut update = Update::new(
- creator.actor_id.to_owned().into_inner(),
- note.into_any_base()?,
- );
- update
- .set_many_contexts(lemmy_context())
- .set_id(generate_activity_id(UpdateType::Update)?)
- .set_to(public())
- .set_many_ccs(maa.ccs.to_owned())
- // Set the mention tags
- .set_many_tags(maa.get_tags()?);
-
- send_to_community(update.clone(), creator, &community, None, context).await?;
- send_comment_mentions(creator, maa.inboxes, update, context).await?;
- Ok(())
+ async fn send_update(
+ &self,
+ _creator: &Person,
+ _context: &LemmyContext,
+ ) -> Result<(), LemmyError> {
+ unimplemented!()
}
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
Ok(())
}
}
-
-struct MentionsAndAddresses {
- ccs: Vec<Url>,
- inboxes: Vec<Url>,
- tags: Vec<Mention>,
-}
-
-impl MentionsAndAddresses {
- fn get_tags(&self) -> Result<Vec<AnyBase>, Error> {
- self
- .tags
- .iter()
- .map(|t| t.to_owned().into_any_base())
- .collect::<Result<Vec<AnyBase>, Error>>()
- }
-}
-
-/// This takes a comment, and builds a list of to_addresses, inboxes,
-/// and mention tags, so they know where to be sent to.
-/// Addresses are the persons / addresses that go in the cc field.
-async fn collect_non_local_mentions(
- comment: &Comment,
- community: &Community,
- context: &LemmyContext,
-) -> Result<MentionsAndAddresses, LemmyError> {
- let parent_creator = get_comment_parent_creator(context.pool(), comment).await?;
- let mut addressed_ccs = vec![community.actor_id(), parent_creator.actor_id()];
- // Note: dont include community inbox here, as we send to it separately with `send_to_community()`
- let mut inboxes = vec![parent_creator.get_shared_inbox_or_inbox_url()];
-
- // Add the mention tag
- let mut tags = Vec::new();
-
- // Get the person IDs for any mentions
- let mentions = scrape_text_for_mentions(&comment.content)
- .into_iter()
- // Filter only the non-local ones
- .filter(|m| !m.is_local())
- .collect::<Vec<MentionData>>();
-
- for mention in &mentions {
- // TODO should it be fetching it every time?
- if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await {
- debug!("mention actor_id: {}", actor_id);
- addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
-
- let mention_person = get_or_fetch_and_upsert_person(&actor_id, context, &mut 0).await?;
- inboxes.push(mention_person.get_shared_inbox_or_inbox_url());
-
- let mut mention_tag = Mention::new();
- mention_tag.set_href(actor_id).set_name(mention.full_name());
- tags.push(mention_tag);
- }
- }
-
- let inboxes = inboxes.into_iter().unique().collect();
-
- Ok(MentionsAndAddresses {
- ccs: addressed_ccs,
- inboxes,
- tags,
- })
-}
-
-/// Returns the apub ID of the person this comment is responding to. Meaning, in case this is a
-/// top-level comment, the creator of the post, otherwise the creator of the parent comment.
-async fn get_comment_parent_creator(
- pool: &DbPool,
- comment: &Comment,
-) -> Result<Person, LemmyError> {
- let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id {
- let parent_comment =
- blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
- parent_comment.creator_id
- } else {
- let parent_post_id = comment.post_id;
- let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??;
- parent_post.creator_id
- };
- Ok(blocking(pool, move |conn| Person::read(conn, parent_creator_id)).await??)
-}
-
-/// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
-/// using webfinger.
-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()
- .ok_or_else(|| anyhow!("No href found.").into())
-}
self.local
}
fn actor_id(&self) -> Url {
- self.actor_id.to_owned().into_inner()
+ self.actor_id.to_owned().into()
}
fn name(&self) -> String {
self.name.clone()
#[async_trait::async_trait(?Send)]
impl CommunityType for Community {
fn followers_url(&self) -> Url {
- self.followers_url.clone().into_inner()
+ self.followers_url.clone().into()
}
/// As a local community, accept the follow request from a remote person.
activities::generate_activity_id,
activity_queue::send_activity_single_dest,
extensions::context::lemmy_context,
- objects::ToApub,
ActorType,
ApubObjectType,
};
use activitystreams::{
activity::{
- kind::{CreateType, DeleteType, UndoType, UpdateType},
- Create,
+ kind::{DeleteType, UndoType},
Delete,
Undo,
- Update,
},
- prelude::*,
+ base::{BaseExt, ExtendsExt},
+ object::ObjectExt,
};
use lemmy_api_common::blocking;
use lemmy_db_queries::Crud;
#[async_trait::async_trait(?Send)]
impl ApubObjectType for PrivateMessage {
- /// Send out information about a newly created private message
- async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
- let note = self.to_apub(context.pool()).await?;
-
- let recipient_id = self.recipient_id;
- let recipient =
- blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
-
- let mut create = Create::new(
- creator.actor_id.to_owned().into_inner(),
- note.into_any_base()?,
- );
-
- create
- .set_many_contexts(lemmy_context())
- .set_id(generate_activity_id(CreateType::Create)?)
- .set_to(recipient.actor_id());
-
- send_activity_single_dest(create, creator, recipient.inbox_url.into(), context).await?;
- Ok(())
+ async fn send_create(
+ &self,
+ _creator: &Person,
+ _context: &LemmyContext,
+ ) -> Result<(), LemmyError> {
+ unimplemented!()
}
- /// Send out information about an edited private message, to the followers of the community.
- async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
- let note = self.to_apub(context.pool()).await?;
-
- let recipient_id = self.recipient_id;
- let recipient =
- blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
-
- let mut update = Update::new(
- creator.actor_id.to_owned().into_inner(),
- note.into_any_base()?,
- );
- update
- .set_many_contexts(lemmy_context())
- .set_id(generate_activity_id(UpdateType::Update)?)
- .set_to(recipient.actor_id());
-
- send_activity_single_dest(update, creator, recipient.inbox_url.into(), context).await?;
- Ok(())
+ async fn send_update(
+ &self,
+ _creator: &Person,
+ _context: &LemmyContext,
+ ) -> Result<(), LemmyError> {
+ unimplemented!()
}
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
Ok(())
}
-/// Sends notification to any persons mentioned in a comment
-///
-/// * `creator` person who created the comment
-/// * `mentions` list of inboxes of persons which are mentioned in the comment
-/// * `activity` either a `Create/Note` or `Update/Note`
-pub(crate) async fn send_comment_mentions<T, Kind>(
- creator: &Person,
- mentions: Vec<Url>,
- activity: T,
- context: &LemmyContext,
-) -> Result<(), LemmyError>
-where
- T: AsObject<Kind> + Extends<Kind> + Debug + BaseExt<Kind>,
- Kind: Serialize,
- <T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
-{
- debug!(
- "Sending mentions activity {:?} to {:?}",
- &activity.id_unchecked(),
- &mentions
- );
- let mentions = mentions
- .iter()
- .filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok())
- .map(|i| i.to_owned())
- .collect();
- send_activity_internal(
- context, activity, creator, mentions, false, // Don't create a new DB row
- false,
- )
- .await?;
- Ok(())
-}
-
pub(crate) async fn send_to_community_new(
activity: AnnouncableActivities,
activity_id: &Url,
use crate::{
fetcher::fetch::fetch_remote_object,
- objects::{post::Page, FromApub},
- NoteExt,
+ objects::{comment::Note, post::Page, FromApub},
PostOrComment,
};
use anyhow::anyhow;
comment_ap_id
);
let comment =
- fetch_remote_object::<NoteExt>(context.client(), comment_ap_id, recursion_counter).await?;
+ fetch_remote_object::<Note>(context.client(), comment_ap_id, recursion_counter).await?;
let comment = Comment::from_apub(
&comment,
context,
is_deleted,
},
find_object_by_id,
- objects::{post::Page, FromApub},
+ objects::{comment::Note, post::Page, FromApub},
GroupExt,
- NoteExt,
Object,
PersonExt,
};
Person(Box<PersonExt>),
Group(Box<GroupExt>),
Page(Box<Page>),
- Comment(Box<NoteExt>),
+ Comment(Box<Note>),
}
/// Attempt to parse the query as URL, and fetch an ActivityPub object from it.
let moderators: Vec<Url> = moderators
.into_iter()
- .map(|m| m.moderator.actor_id.into_inner())
+ .map(|m| m.moderator.actor_id.into())
.collect();
let mut collection = OrderedCollection::new();
collection
use crate::activities::{
- comment::{create::CreateComment, update::UpdateComment},
+ comment::create_or_update::CreateOrUpdateComment,
community::{
add_mod::AddMod,
announce::AnnounceActivity,
},
deletion::{delete::DeletePostCommentOrCommunity, undo_delete::UndoDeletePostCommentOrCommunity},
following::{accept::AcceptFollowCommunity, follow::FollowCommunity, undo::UndoFollowCommunity},
- post::{create::CreatePost, update::UpdatePost},
+ post::create_or_update::CreateOrUpdatePost,
private_message::{
- create::CreatePrivateMessage,
+ create_or_update::CreateOrUpdatePrivateMessage,
delete::DeletePrivateMessage,
undo_delete::UndoDeletePrivateMessage,
- update::UpdatePrivateMessage,
},
removal::{
remove::RemovePostCommentCommunityOrMod,
#[serde(untagged)]
pub enum PersonInboxActivities {
AcceptFollowCommunity(AcceptFollowCommunity),
- CreatePrivateMessage(CreatePrivateMessage),
- UpdatePrivateMessage(UpdatePrivateMessage),
+ CreateOrUpdatePrivateMessage(CreateOrUpdatePrivateMessage),
DeletePrivateMessage(DeletePrivateMessage),
UndoDeletePrivateMessage(UndoDeletePrivateMessage),
AnnounceActivity(Box<AnnounceActivity>),
pub enum GroupInboxActivities {
FollowCommunity(FollowCommunity),
UndoFollowCommunity(UndoFollowCommunity),
- CreateComment(CreateComment),
- UpdateComment(UpdateComment),
- CreatePost(CreatePost),
- UpdatePost(UpdatePost),
+ CreateOrUpdateComment(CreateOrUpdateComment),
+ CreateOrUpdatePost(Box<CreateOrUpdatePost>),
LikePostOrComment(LikePostOrComment),
DislikePostOrComment(DislikePostOrComment),
UndoLikePostOrComment(UndoLikePostOrComment),
// received by group
FollowCommunity(FollowCommunity),
UndoFollowCommunity(UndoFollowCommunity),
- CreateComment(CreateComment),
- UpdateComment(UpdateComment),
- CreatePost(CreatePost),
- UpdatePost(UpdatePost),
+ CreateOrUpdateComment(CreateOrUpdateComment),
+ CreateOrUpdatePost(Box<CreateOrUpdatePost>),
LikePostOrComment(LikePostOrComment),
DislikePostOrComment(DislikePostOrComment),
UndoDislikePostOrComment(UndoDislikePostOrComment),
AcceptFollowCommunity(AcceptFollowCommunity),
// Note, pm activities need to be at the end, otherwise comments will end up here. We can probably
// avoid this problem by replacing createpm.object with our own struct, instead of NoteExt.
- CreatePrivateMessage(CreatePrivateMessage),
- UpdatePrivateMessage(UpdatePrivateMessage),
+ CreateOrUpdatePrivateMessage(CreateOrUpdatePrivateMessage),
DeletePrivateMessage(DeletePrivateMessage),
UndoDeletePrivateMessage(UndoDeletePrivateMessage),
AnnounceActivity(Box<AnnounceActivity>),
activity::Follow,
actor,
base::AnyBase,
- object::{ApObject, AsObject, Note, ObjectExt},
+ object::{ApObject, AsObject, ObjectExt},
};
use activitystreams_ext::Ext2;
use anyhow::{anyhow, Context};
type PersonExt =
Ext2<actor::ApActor<ApObject<actor::Actor<UserTypes>>>, PersonExtension, PublicKeyExtension>;
pub type SiteExt = actor::ApActor<ApObject<actor::Service>>;
-pub type NoteExt = ApObject<Note>;
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize, PartialEq)]
pub enum UserTypes {
}
pub fn generate_shared_inbox_url(actor_id: &DbUrl) -> Result<DbUrl, LemmyError> {
- let actor_id = actor_id.clone().into_inner();
+ let actor_id: Url = actor_id.clone().into();
let url = format!(
"{}://{}{}/inbox",
&actor_id.scheme(),
use crate::{
+ activities::verify_person_in_community,
extensions::context::lemmy_context,
fetcher::objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
- get_community_from_to_or_cc,
- objects::{
- check_object_domain,
- check_object_for_community_or_site_ban,
- create_tombstone,
- get_object_from_apub,
- get_or_fetch_and_upsert_person,
- get_source_markdown_value,
- set_content_and_source,
- FromApub,
- FromApubToForm,
- ToApub,
- },
- NoteExt,
+ objects::{create_tombstone, get_or_fetch_and_upsert_person, FromApub, Source, ToApub},
+ ActorType,
};
use activitystreams::{
- object::{kind::NoteType, ApObject, Note, Tombstone},
- prelude::*,
- public,
+ base::AnyBase,
+ object::{kind::NoteType, Tombstone},
+ primitives::OneOrMany,
+ unparsed::Unparsed,
};
use anyhow::{anyhow, Context};
+use chrono::{DateTime, FixedOffset};
use lemmy_api_common::blocking;
-use lemmy_db_queries::{Crud, DbPool};
+use lemmy_apub_lib::{
+ values::{MediaTypeHtml, MediaTypeMarkdown, PublicUrl},
+ verify_domains_match,
+};
+use lemmy_db_queries::{ApubObject, Crud, DbPool};
use lemmy_db_schema::{
source::{
comment::{Comment, CommentForm},
LemmyError,
};
use lemmy_websocket::LemmyContext;
+use serde::{Deserialize, Serialize};
use url::Url;
+#[derive(Clone, Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Note {
+ #[serde(rename = "@context")]
+ context: OneOrMany<AnyBase>,
+ r#type: NoteType,
+ pub(crate) id: Url,
+ pub(crate) attributed_to: Url,
+ /// Indicates that the object is publicly readable. Unlike [`Post.to`], this one doesn't contain
+ /// the community ID, as it would be incompatible with Pleroma (and we can get the community from
+ /// the post in [`in_reply_to`]).
+ to: PublicUrl,
+ content: String,
+ media_type: MediaTypeHtml,
+ source: Source,
+ in_reply_to: Vec<Url>,
+ published: DateTime<FixedOffset>,
+ updated: Option<DateTime<FixedOffset>>,
+ #[serde(flatten)]
+ unparsed: Unparsed,
+}
+
+impl Note {
+ async fn get_parents(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(Post, Option<CommentId>), LemmyError> {
+ // This post, or the parent comment might not yet exist on this server yet, fetch them.
+ let post_id = self.in_reply_to.get(0).context(location_info!())?;
+ let post = Box::pin(get_or_fetch_and_insert_post(
+ post_id,
+ context,
+ request_counter,
+ ))
+ .await?;
+
+ // The 2nd item, if it exists, is the parent comment apub_id
+ // Nested comments will automatically get fetched recursively
+ let parent_id: Option<CommentId> = match self.in_reply_to.get(1) {
+ Some(parent_comment_uri) => {
+ let parent_comment = Box::pin(get_or_fetch_and_insert_comment(
+ parent_comment_uri,
+ context,
+ request_counter,
+ ))
+ .await?;
+
+ Some(parent_comment.id)
+ }
+ None => None,
+ };
+
+ Ok((post, parent_id))
+ }
+
+ pub(crate) async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let (post, _parent_comment_id) = self.get_parents(context, request_counter).await?;
+ let community_id = post.community_id;
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
+
+ if post.locked {
+ return Err(anyhow!("Post is locked").into());
+ }
+ verify_domains_match(&self.attributed_to, &self.id)?;
+ verify_person_in_community(
+ &self.attributed_to,
+ &community.actor_id(),
+ context,
+ request_counter,
+ )
+ .await?;
+ Ok(())
+ }
+}
+
#[async_trait::async_trait(?Send)]
impl ToApub for Comment {
- type ApubType = NoteExt;
-
- async fn to_apub(&self, pool: &DbPool) -> Result<NoteExt, LemmyError> {
- let mut comment = ApObject::new(Note::new());
+ type ApubType = Note;
+ async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
let creator_id = self.creator_id;
let creator = blocking(pool, move |conn| Person::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.into_inner()];
in_reply_to_vec.push(parent_comment.ap_id.into_inner());
}
- comment
- // Not needed when the Post is embedded in a collection (like for community outbox)
- .set_many_contexts(lemmy_context())
- .set_id(self.ap_id.to_owned().into_inner())
- .set_published(convert_datetime(self.published))
- // NOTE: included community id for compatibility with lemmy v0.9.9
- .set_many_tos(vec![community.actor_id.into_inner(), public()])
- .set_many_in_reply_tos(in_reply_to_vec)
- .set_attributed_to(creator.actor_id.into_inner());
-
- set_content_and_source(&mut comment, &self.content)?;
-
- if let Some(u) = self.updated {
- comment.set_updated(convert_datetime(u));
- }
+ let note = Note {
+ context: lemmy_context(),
+ r#type: NoteType::Note,
+ id: self.ap_id.to_owned().into_inner(),
+ attributed_to: creator.actor_id.into_inner(),
+ to: PublicUrl::Public,
+ content: self.content.clone(),
+ media_type: MediaTypeHtml::Html,
+ source: Source {
+ content: self.content.clone(),
+ media_type: MediaTypeMarkdown::Markdown,
+ },
+ in_reply_to: in_reply_to_vec,
+ published: convert_datetime(self.published),
+ updated: self.updated.map(convert_datetime),
+ unparsed: Default::default(),
+ };
- Ok(comment)
+ Ok(note)
}
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
#[async_trait::async_trait(?Send)]
impl FromApub for Comment {
- type ApubType = NoteExt;
+ type ApubType = Note;
/// Converts a `Note` to `Comment`.
///
/// If the parent community, post and comment(s) are not known locally, these are also fetched.
async fn from_apub(
- note: &NoteExt,
+ note: &Note,
context: &LemmyContext,
- expected_domain: Url,
- request_counter: &mut i32,
- mod_action_allowed: bool,
- ) -> Result<Comment, LemmyError> {
- let comment: Comment = get_object_from_apub(
- note,
- context,
- expected_domain,
- request_counter,
- mod_action_allowed,
- )
- .await?;
-
- let post_id = comment.post_id;
- let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
- check_object_for_community_or_site_ban(note, post.community_id, context, request_counter)
- .await?;
- Ok(comment)
- }
-}
-
-#[async_trait::async_trait(?Send)]
-impl FromApubToForm<NoteExt> for CommentForm {
- async fn from_apub(
- note: &NoteExt,
- context: &LemmyContext,
- expected_domain: Url,
+ _expected_domain: Url,
request_counter: &mut i32,
_mod_action_allowed: bool,
- ) -> Result<CommentForm, LemmyError> {
- let community = get_community_from_to_or_cc(note, context, request_counter).await?;
- let ap_id = Some(check_object_domain(note, expected_domain, community.local)?);
- let creator_actor_id = ¬e
- .attributed_to()
- .context(location_info!())?
- .as_single_xsd_any_uri()
- .context(location_info!())?;
-
+ ) -> Result<Comment, LemmyError> {
let creator =
- get_or_fetch_and_upsert_person(creator_actor_id, context, request_counter).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!())??;
+ get_or_fetch_and_upsert_person(¬e.attributed_to, context, request_counter).await?;
+ let (post, parent_comment_id) = note.get_parents(context, request_counter).await?;
- // This post, or the parent comment might not yet exist on this server yet, fetch them.
- let post = Box::pin(get_or_fetch_and_insert_post(
- post_ap_id,
- context,
- request_counter,
- ))
- .await?;
- if post.locked {
- return Err(anyhow!("Post is locked").into());
- }
-
- // 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<CommentId> = match in_reply_tos.next() {
- Some(parent_comment_uri) => {
- let parent_comment_ap_id = &parent_comment_uri?;
- let parent_comment = Box::pin(get_or_fetch_and_insert_comment(
- parent_comment_ap_id,
- context,
- request_counter,
- ))
- .await?;
+ let content = ¬e.source.content;
+ let content_slurs_removed = remove_slurs(content);
- Some(parent_comment.id)
- }
- None => None,
- };
-
- let content = get_source_markdown_value(note)?.context(location_info!())?;
- let content_slurs_removed = remove_slurs(&content);
-
- Ok(CommentForm {
+ let form = CommentForm {
creator_id: creator.id,
post_id: post.id,
- parent_id,
+ parent_id: parent_comment_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()),
+ published: Some(note.published.naive_local()),
+ updated: note.updated.map(|u| u.to_owned().naive_local()),
deleted: None,
- ap_id,
+ ap_id: Some(note.id.clone().into()),
local: Some(false),
- })
+ };
+ Ok(blocking(context.pool(), move |conn| Comment::upsert(conn, &form)).await??)
}
}
-use crate::{
- check_community_or_site_ban,
- check_is_apub_id_valid,
- fetcher::person::get_or_fetch_and_upsert_person,
-};
+use crate::{check_is_apub_id_valid, fetcher::person::get_or_fetch_and_upsert_person};
use activitystreams::{
base::{AsBase, BaseExt, ExtendsExt},
markers::Base,
use lemmy_api_common::blocking;
use lemmy_apub_lib::values::MediaTypeMarkdown;
use lemmy_db_queries::{ApubObject, Crud, DbPool};
-use lemmy_db_schema::{CommunityId, DbUrl};
+use lemmy_db_schema::DbUrl;
use lemmy_utils::{
location_info,
settings::structs::Settings,
Ok(to)
}
}
-
-pub(in crate::objects) async fn check_object_for_community_or_site_ban<T, Kind>(
- object: &T,
- community_id: CommunityId,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError>
-where
- T: ObjectExt<Kind>,
-{
- let person_id = object
- .attributed_to()
- .context(location_info!())?
- .as_single_xsd_any_uri()
- .context(location_info!())?;
- let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?;
- check_community_or_site_ban(&person, community_id, context.pool()).await
-}
use crate::{
- activities::extract_community,
+ activities::{extract_community, verify_person_in_community},
extensions::context::lemmy_context,
fetcher::person::get_or_fetch_and_upsert_person,
objects::{create_tombstone, FromApub, Source, ToApub},
+ ActorType,
};
use activitystreams::{
base::AnyBase,
LemmyError,
};
use lemmy_websocket::LemmyContext;
+use serde::{Deserialize, Serialize};
use url::Url;
-#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Page {
#[serde(rename = "@context")]
pub(crate) stickied: Option<bool>,
published: DateTime<FixedOffset>,
updated: Option<DateTime<FixedOffset>>,
-
- // unparsed fields
#[serde(flatten)]
unparsed: Unparsed,
}
pub(crate) async fn verify(
&self,
- _context: &LemmyContext,
- _request_counter: &mut i32,
+ context: &LemmyContext,
+ request_counter: &mut i32,
) -> Result<(), LemmyError> {
+ let community = extract_community(&self.to, context, request_counter).await?;
+
check_slurs(&self.name)?;
verify_domains_match(&self.attributed_to, &self.id)?;
+ verify_person_in_community(
+ &self.attributed_to,
+ &community.actor_id(),
+ context,
+ request_counter,
+ )
+ .await?;
Ok(())
}
}
use crate::{
extensions::context::lemmy_context,
fetcher::person::get_or_fetch_and_upsert_person,
- objects::{
- check_object_domain,
- create_tombstone,
- get_object_from_apub,
- get_source_markdown_value,
- set_content_and_source,
- FromApub,
- FromApubToForm,
- ToApub,
- },
- NoteExt,
+ objects::{create_tombstone, FromApub, Source, ToApub},
};
use activitystreams::{
- object::{kind::NoteType, ApObject, Note, Tombstone},
- prelude::*,
+ base::AnyBase,
+ object::{kind::NoteType, Tombstone},
+ primitives::OneOrMany,
+ unparsed::Unparsed,
};
-use anyhow::Context;
+use anyhow::anyhow;
+use chrono::{DateTime, FixedOffset};
use lemmy_api_common::blocking;
-use lemmy_db_queries::{Crud, DbPool};
+use lemmy_apub_lib::{
+ values::{MediaTypeHtml, MediaTypeMarkdown},
+ verify_domains_match,
+};
+use lemmy_db_queries::{ApubObject, Crud, DbPool};
use lemmy_db_schema::source::{
person::Person,
private_message::{PrivateMessage, PrivateMessageForm},
};
-use lemmy_utils::{location_info, utils::convert_datetime, LemmyError};
+use lemmy_utils::{utils::convert_datetime, LemmyError};
use lemmy_websocket::LemmyContext;
+use serde::{Deserialize, Serialize};
use url::Url;
+#[derive(Clone, Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Note {
+ #[serde(rename = "@context")]
+ context: OneOrMany<AnyBase>,
+ r#type: NoteType,
+ pub(crate) id: Url,
+ pub(crate) attributed_to: Url,
+ to: Url,
+ content: String,
+ media_type: MediaTypeHtml,
+ source: Source,
+ published: DateTime<FixedOffset>,
+ updated: Option<DateTime<FixedOffset>>,
+ #[serde(flatten)]
+ unparsed: Unparsed,
+}
+
+impl Note {
+ pub(crate) async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_domains_match(&self.attributed_to, &self.id)?;
+ let person =
+ get_or_fetch_and_upsert_person(&self.attributed_to, context, request_counter).await?;
+ if person.banned {
+ return Err(anyhow!("Person is banned from site").into());
+ }
+ Ok(())
+ }
+}
+
#[async_trait::async_trait(?Send)]
impl ToApub for PrivateMessage {
- type ApubType = NoteExt;
-
- async fn to_apub(&self, pool: &DbPool) -> Result<NoteExt, LemmyError> {
- let mut private_message = ApObject::new(Note::new());
+ type ApubType = Note;
+ async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
let creator_id = self.creator_id;
let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??;
let recipient_id = self.recipient_id;
let recipient = blocking(pool, move |conn| Person::read(conn, recipient_id)).await??;
- private_message
- .set_many_contexts(lemmy_context())
- .set_id(self.ap_id.to_owned().into_inner())
- .set_published(convert_datetime(self.published))
- .set_to(recipient.actor_id.into_inner())
- .set_attributed_to(creator.actor_id.into_inner());
-
- set_content_and_source(&mut private_message, &self.content)?;
-
- if let Some(u) = self.updated {
- private_message.set_updated(convert_datetime(u));
- }
-
- Ok(private_message)
+ let note = Note {
+ context: lemmy_context(),
+ r#type: NoteType::Note,
+ id: self.ap_id.clone().into(),
+ attributed_to: creator.actor_id.into_inner(),
+ to: recipient.actor_id.into(),
+ content: self.content.clone(),
+ media_type: MediaTypeHtml::Html,
+ source: Source {
+ content: self.content.clone(),
+ media_type: MediaTypeMarkdown::Markdown,
+ },
+ published: convert_datetime(self.published),
+ updated: self.updated.map(convert_datetime),
+ unparsed: Default::default(),
+ };
+ Ok(note)
}
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
#[async_trait::async_trait(?Send)]
impl FromApub for PrivateMessage {
- type ApubType = NoteExt;
+ type ApubType = Note;
async fn from_apub(
- note: &NoteExt,
+ note: &Note,
context: &LemmyContext,
- expected_domain: Url,
- request_counter: &mut i32,
- mod_action_allowed: bool,
- ) -> Result<PrivateMessage, LemmyError> {
- get_object_from_apub(
- note,
- context,
- expected_domain,
- request_counter,
- mod_action_allowed,
- )
- .await
- }
-}
-
-#[async_trait::async_trait(?Send)]
-impl FromApubToForm<NoteExt> for PrivateMessageForm {
- async fn from_apub(
- note: &NoteExt,
- context: &LemmyContext,
- expected_domain: Url,
+ _expected_domain: Url,
request_counter: &mut i32,
_mod_action_allowed: bool,
- ) -> Result<PrivateMessageForm, LemmyError> {
- let creator_actor_id = note
- .attributed_to()
- .context(location_info!())?
- .clone()
- .single_xsd_any_uri()
- .context(location_info!())?;
-
+ ) -> Result<PrivateMessage, LemmyError> {
let creator =
- get_or_fetch_and_upsert_person(&creator_actor_id, context, request_counter).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_person(&recipient_actor_id, context, request_counter).await?;
- let ap_id = Some(check_object_domain(note, expected_domain, false)?);
-
- let content = get_source_markdown_value(note)?.context(location_info!())?;
+ get_or_fetch_and_upsert_person(¬e.attributed_to, context, request_counter).await?;
+ let recipient = get_or_fetch_and_upsert_person(¬e.to, context, request_counter).await?;
- Ok(PrivateMessageForm {
+ let form = PrivateMessageForm {
creator_id: creator.id,
recipient_id: recipient.id,
- content,
- published: note.published().map(|u| u.to_owned().naive_local()),
- updated: note.updated().map(|u| u.to_owned().naive_local()),
+ content: note.source.content.clone(),
+ published: Some(note.published.naive_local()),
+ updated: note.updated.map(|u| u.to_owned().naive_local()),
deleted: None,
read: None,
- ap_id,
+ ap_id: Some(note.id.clone().into()),
local: Some(false),
- })
+ };
+ Ok(
+ blocking(context.pool(), move |conn| {
+ PrivateMessage::upsert(conn, &form)
+ })
+ .await??,
+ )
}
}
}
impl DbUrl {
+ // TODO: remove this method and just use into()
pub fn into_inner(self) -> Url {
self.0
}
impl Display for DbUrl {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- self.to_owned().into_inner().fmt(f)
+ self.to_owned().0.fmt(f)
}
}