2 activities::receive::verify_activity_domains_valid,
4 assert_activity_not_local,
6 get_activity_to_and_cc,
7 inbox_verify_http_signature,
8 is_activity_already_known,
9 receive_for_community::{
10 receive_add_for_community,
11 receive_create_for_community,
12 receive_delete_for_community,
13 receive_dislike_for_community,
14 receive_like_for_community,
15 receive_remove_for_community,
16 receive_undo_for_community,
17 receive_update_for_community,
19 verify_is_addressed_to_public,
25 use activitystreams::{
26 activity::{kind::FollowType, ActorAndObject, Follow, Undo},
30 use actix_web::{web, HttpRequest, HttpResponse};
31 use anyhow::{anyhow, Context};
32 use lemmy_api_structs::blocking;
33 use lemmy_db_queries::{source::community::Community_, ApubObject, DbPool, Followable};
34 use lemmy_db_schema::{
36 community::{Community, CommunityFollower, CommunityFollowerForm},
41 use lemmy_db_views_actor::community_person_ban_view::CommunityPersonBanView;
42 use lemmy_utils::{location_info, LemmyError};
43 use lemmy_websocket::LemmyContext;
45 use serde::{Deserialize, Serialize};
49 /// Allowed activities for community inbox.
50 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
51 #[serde(rename_all = "PascalCase")]
52 pub enum CommunityValidTypes {
53 Follow, // follow request from a person
54 Undo, // unfollow from a person
55 Create, // create post or comment
56 Update, // update post or comment
57 Like, // upvote post or comment
58 Dislike, // downvote post or comment
59 Delete, // post or comment deleted by creator
60 Remove, // post or comment removed by mod or admin, or mod removed from community
61 Add, // mod added to community
64 pub type CommunityAcceptedActivities = ActorAndObject<CommunityValidTypes>;
66 /// Handler for all incoming receive to community inboxes.
67 pub async fn community_inbox(
69 input: web::Json<CommunityAcceptedActivities>,
70 path: web::Path<String>,
71 context: web::Data<LemmyContext>,
72 ) -> Result<HttpResponse, LemmyError> {
73 let activity = input.into_inner();
74 // First of all check the http signature
75 let request_counter = &mut 0;
76 let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
78 // Do nothing if we received the same activity before
79 let activity_id = get_activity_id(&activity, &actor.actor_id())?;
80 if is_activity_already_known(context.pool(), &activity_id).await? {
81 return Ok(HttpResponse::Ok().finish());
84 // Check if the activity is actually meant for us
85 let path = path.into_inner();
86 let community = blocking(&context.pool(), move |conn| {
87 Community::read_from_name(&conn, &path)
90 let to_and_cc = get_activity_to_and_cc(&activity);
91 if !to_and_cc.contains(&&community.actor_id()) {
92 return Err(anyhow!("Activity delivered to wrong community").into());
95 assert_activity_not_local(&activity)?;
96 insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
99 "Community {} received activity {:?} from {}",
101 &activity.id_unchecked(),
105 community_receive_message(
115 /// Receives Follow, Undo/Follow, post actions, comment actions (including votes)
116 pub(crate) async fn community_receive_message(
117 activity: CommunityAcceptedActivities,
118 to_community: Community,
119 actor: &dyn ActorType,
120 context: &LemmyContext,
121 request_counter: &mut i32,
122 ) -> Result<HttpResponse, LemmyError> {
123 // Only persons can send activities to the community, so we can get the actor as person
125 let actor_id = actor.actor_id();
126 let person = blocking(&context.pool(), move |conn| {
127 Person::read_from_apub_id(&conn, &actor_id.into())
130 check_community_or_site_ban(&person, to_community.id, context.pool()).await?;
132 let any_base = activity.clone().into_any_base()?;
133 let actor_url = actor.actor_id();
134 let activity_kind = activity.kind().context(location_info!())?;
135 let do_announce = match activity_kind {
136 CommunityValidTypes::Follow => {
137 Box::pin(handle_follow(
146 CommunityValidTypes::Undo => {
147 Box::pin(handle_undo(
156 CommunityValidTypes::Create => {
157 Box::pin(receive_create_for_community(
166 CommunityValidTypes::Update => {
167 Box::pin(receive_update_for_community(
177 CommunityValidTypes::Like => {
178 Box::pin(receive_like_for_community(
187 CommunityValidTypes::Dislike => {
188 Box::pin(receive_dislike_for_community(
197 CommunityValidTypes::Delete => {
198 Box::pin(receive_delete_for_community(
208 CommunityValidTypes::Add => {
209 Box::pin(receive_add_for_community(
218 CommunityValidTypes::Remove => {
219 Box::pin(receive_remove_for_community(
231 // Check again that the activity is public, just to be sure
232 verify_is_addressed_to_public(&activity)?;
234 .send_announce(activity.into_any_base()?, context)
238 Ok(HttpResponse::Ok().finish())
241 /// Handle a follow request from a remote person, adding the person as follower and returning an
243 async fn handle_follow(
246 community: &Community,
247 context: &LemmyContext,
248 ) -> Result<HttpResponse, LemmyError> {
249 let follow = Follow::from_any_base(activity)?.context(location_info!())?;
250 verify_activity_domains_valid(&follow, &person.actor_id(), false)?;
252 let community_follower_form = CommunityFollowerForm {
253 community_id: community.id,
254 person_id: person.id,
258 // This will fail if they're already a follower, but ignore the error.
259 blocking(&context.pool(), move |conn| {
260 CommunityFollower::follow(&conn, &community_follower_form).ok()
264 community.send_accept_follow(follow, context).await?;
266 Ok(HttpResponse::Ok().finish())
269 async fn handle_undo(
270 context: &LemmyContext,
271 activity: CommunityAcceptedActivities,
273 to_community: &Community,
274 request_counter: &mut i32,
275 ) -> Result<bool, LemmyError> {
276 let inner_kind = activity
278 .is_single_kind(&FollowType::Follow.to_string());
279 let any_base = activity.into_any_base()?;
281 handle_undo_follow(any_base, actor_url, to_community, &context).await?;
284 receive_undo_for_community(context, any_base, None, &actor_url, request_counter).await?;
289 /// Handle `Undo/Follow` from a person, removing the person from followers list.
290 async fn handle_undo_follow(
293 community: &Community,
294 context: &LemmyContext,
295 ) -> Result<(), LemmyError> {
296 let undo = Undo::from_any_base(activity)?.context(location_info!())?;
297 verify_activity_domains_valid(&undo, &person_url, true)?;
299 let object = undo.object().to_owned().one().context(location_info!())?;
300 let follow = Follow::from_any_base(object)?.context(location_info!())?;
301 verify_activity_domains_valid(&follow, &person_url, false)?;
303 let person = blocking(&context.pool(), move |conn| {
304 Person::read_from_apub_id(&conn, &person_url.into())
307 let community_follower_form = CommunityFollowerForm {
308 community_id: community.id,
309 person_id: person.id,
313 // This will fail if they aren't a follower, but ignore the error.
314 blocking(&context.pool(), move |conn| {
315 CommunityFollower::unfollow(&conn, &community_follower_form).ok()
322 pub(crate) async fn check_community_or_site_ban(
324 community_id: CommunityId,
326 ) -> Result<(), LemmyError> {
328 return Err(anyhow!("Person is banned from site").into());
330 let person_id = person.id;
332 move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
333 if blocking(pool, is_banned).await? {
334 return Err(anyhow!("Person is banned from community").into());