get_post,
send_local_notifs,
};
-use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
+use lemmy_apub::{
+ activities::comment::create::CreateComment as CreateApubComment,
+ 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?;
+ CreateApubComment::send(&updated_comment, &local_user_view.person, 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::update::UpdateComment;
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?;
+ UpdateComment::send(&updated_comment, &local_user_view.person, context).await?;
// Do the mentions / recipients
let updated_comment_content = updated_comment.content.to_owned();
use crate::{
activities::{
- comment::{get_notif_recipients, send_websocket_message},
+ 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,
},
- objects::FromApub,
+ activity_queue::send_to_community_new,
+ extensions::context::lemmy_context,
+ objects::{comment::Note, FromApub, ToApub},
ActorType,
- NoteExt,
};
-use activitystreams::{activity::kind::CreateType, base::BaseExt};
+use activitystreams::{activity::kind::CreateType, link::Mention};
+use lemmy_api_common::blocking;
use lemmy_apub_lib::{
values::PublicUrl,
- verify_domains_match_opt,
+ verify_domains_match,
ActivityCommonFields,
ActivityHandler,
};
-use lemmy_db_schema::source::comment::Comment;
+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 url::Url;
#[serde(rename_all = "camelCase")]
pub struct CreateComment {
to: PublicUrl,
- object: NoteExt,
+ object: Note,
cc: Vec<Url>,
+ tag: Vec<Mention>,
#[serde(rename = "type")]
kind: CreateType,
#[serde(flatten)]
common: ActivityCommonFields,
}
+impl CreateComment {
+ pub async fn send(
+ comment: &Comment,
+ actor: &Person,
+ context: &LemmyContext,
+ ) -> Result<(), LemmyError> {
+ // TODO: would 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(CreateType::Create)?;
+ let maa = collect_non_local_mentions(comment, &community, context).await?;
+
+ let create = CreateComment {
+ to: PublicUrl::Public,
+ object: comment.to_apub(context.pool()).await?,
+ cc: maa.ccs,
+ tag: maa.tags,
+ kind: Default::default(),
+ common: ActivityCommonFields {
+ context: lemmy_context(),
+ id: id.clone(),
+ actor: actor.actor_id(),
+ unparsed: Default::default(),
+ },
+ };
+
+ let activity = AnnouncableActivities::CreateComment(create);
+ send_to_community_new(activity, &id, actor, &community, maa.inboxes, context).await
+ }
+}
+
#[async_trait::async_trait(?Send)]
impl ActivityHandler for CreateComment {
async fn verify(
request_counter,
)
.await?;
- verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
+ 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(())
}
-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;
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
- }
-}
use crate::{
activities::{
- comment::{get_notif_recipients, send_websocket_message},
+ 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,
},
- objects::FromApub,
+ activity_queue::send_to_community_new,
+ extensions::context::lemmy_context,
+ objects::{comment::Note, FromApub, ToApub},
ActorType,
- NoteExt,
};
-use activitystreams::{activity::kind::UpdateType, base::BaseExt};
+use activitystreams::{activity::kind::UpdateType, link::Mention};
+use lemmy_api_common::blocking;
use lemmy_apub_lib::{
values::PublicUrl,
- verify_domains_match_opt,
+ verify_domains_match,
ActivityCommonFields,
ActivityHandler,
};
-use lemmy_db_schema::source::comment::Comment;
+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 url::Url;
#[serde(rename_all = "camelCase")]
pub struct UpdateComment {
to: PublicUrl,
- object: NoteExt,
+ object: Note,
cc: Vec<Url>,
+ tag: Vec<Mention>,
#[serde(rename = "type")]
kind: UpdateType,
#[serde(flatten)]
common: ActivityCommonFields,
}
+impl UpdateComment {
+ pub async fn send(
+ comment: &Comment,
+ actor: &Person,
+ context: &LemmyContext,
+ ) -> Result<(), LemmyError> {
+ // TODO: would 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(UpdateType::Update)?;
+ let maa = collect_non_local_mentions(comment, &community, context).await?;
+
+ let update = UpdateComment {
+ to: PublicUrl::Public,
+ object: comment.to_apub(context.pool()).await?,
+ cc: maa.ccs,
+ tag: maa.tags,
+ kind: Default::default(),
+ common: ActivityCommonFields {
+ context: lemmy_context(),
+ id: id.clone(),
+ actor: actor.actor_id(),
+ unparsed: Default::default(),
+ },
+ };
+
+ let activity = AnnouncableActivities::UpdateComment(update);
+ send_to_community_new(activity, &id, actor, &community, maa.inboxes, context).await
+ }
+}
+
#[async_trait::async_trait(?Send)]
impl ActivityHandler for UpdateComment {
async fn verify(
request_counter,
)
.await?;
- verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
+ verify_domains_match(&self.common.actor, &self.object.id)?;
+ self.object.verify(context, request_counter).await?;
Ok(())
}
/// 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,
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())
-}
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.
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(())
}
}