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 is_addressed_to_public,
10 receive_for_community::{
11 receive_create_for_community,
12 receive_delete_for_community,
13 receive_dislike_for_community,
14 receive_like_for_community,
15 receive_undo_for_community,
16 receive_update_for_community,
22 use activitystreams::{
23 activity::{kind::FollowType, ActorAndObject, Follow, Undo},
27 use actix_web::{web, HttpRequest, HttpResponse};
28 use anyhow::{anyhow, Context};
30 community::{Community, CommunityFollower, CommunityFollowerForm},
31 community_view::CommunityUserBanView,
37 use lemmy_structs::blocking;
38 use lemmy_utils::{location_info, LemmyError};
39 use lemmy_websocket::LemmyContext;
41 use serde::{Deserialize, Serialize};
45 /// Allowed activities for community inbox.
46 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
47 #[serde(rename_all = "PascalCase")]
48 pub enum CommunityValidTypes {
49 Follow, // follow request from a user
50 Undo, // unfollow from a user
51 Create, // create post or comment
52 Update, // update post or comment
53 Like, // upvote post or comment
54 Dislike, // downvote post or comment
55 Delete, // post or comment deleted by creator
56 Remove, // post or comment removed by mod or admin
59 pub type CommunityAcceptedActivities = ActorAndObject<CommunityValidTypes>;
61 /// Handler for all incoming receive to community inboxes.
62 pub async fn community_inbox(
64 input: web::Json<CommunityAcceptedActivities>,
65 path: web::Path<String>,
66 context: web::Data<LemmyContext>,
67 ) -> Result<HttpResponse, LemmyError> {
68 let activity = input.into_inner();
69 // First of all check the http signature
70 let request_counter = &mut 0;
71 let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
73 // Do nothing if we received the same activity before
74 let activity_id = get_activity_id(&activity, &actor.actor_id()?)?;
75 if is_activity_already_known(context.pool(), &activity_id).await? {
76 return Ok(HttpResponse::Ok().finish());
79 // Check if the activity is actually meant for us
80 let path = path.into_inner();
81 let community = blocking(&context.pool(), move |conn| {
82 Community::read_from_name(&conn, &path)
85 let to_and_cc = get_activity_to_and_cc(&activity)?;
86 if !to_and_cc.contains(&&community.actor_id()?) {
87 return Err(anyhow!("Activity delivered to wrong community").into());
90 assert_activity_not_local(&activity)?;
91 insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
94 "Community {} received activity {:?} from {}",
96 &activity.id_unchecked(),
100 community_receive_message(
110 /// Receives Follow, Undo/Follow, post actions, comment actions (including votes)
111 pub(crate) async fn community_receive_message(
112 activity: CommunityAcceptedActivities,
113 to_community: Community,
114 actor: &dyn ActorType,
115 context: &LemmyContext,
116 request_counter: &mut i32,
117 ) -> Result<HttpResponse, LemmyError> {
118 // Only users can send activities to the community, so we can get the actor as user
120 let actor_id = actor.actor_id_str();
121 let user = blocking(&context.pool(), move |conn| {
122 User_::read_from_apub_id(&conn, &actor_id)
125 check_community_or_site_ban(&user, &to_community, context.pool()).await?;
127 let any_base = activity.clone().into_any_base()?;
128 let actor_url = actor.actor_id()?;
129 let activity_kind = activity.kind().context(location_info!())?;
130 let do_announce = match activity_kind {
131 CommunityValidTypes::Follow => {
132 handle_follow(any_base.clone(), user, &to_community, &context).await?;
135 CommunityValidTypes::Undo => {
145 CommunityValidTypes::Create => {
146 receive_create_for_community(context, any_base.clone(), &actor_url, request_counter).await?;
149 CommunityValidTypes::Update => {
150 receive_update_for_community(context, any_base.clone(), &actor_url, request_counter).await?;
153 CommunityValidTypes::Like => {
154 receive_like_for_community(context, any_base.clone(), &actor_url, request_counter).await?;
157 CommunityValidTypes::Dislike => {
158 receive_dislike_for_community(context, any_base.clone(), &actor_url, request_counter).await?;
161 CommunityValidTypes::Delete => {
162 receive_delete_for_community(context, any_base.clone(), &actor_url).await?;
165 CommunityValidTypes::Remove => {
166 // TODO: we dont support remote mods, so this is ignored for now
167 //receive_remove_for_community(context, any_base.clone(), &user_url).await?
173 // Check again that the activity is public, just to be sure
174 is_addressed_to_public(&activity)?;
176 .send_announce(activity.into_any_base()?, context)
180 return Ok(HttpResponse::Ok().finish());
183 /// Handle a follow request from a remote user, adding the user as follower and returning an
185 async fn handle_follow(
188 community: &Community,
189 context: &LemmyContext,
190 ) -> Result<HttpResponse, LemmyError> {
191 let follow = Follow::from_any_base(activity)?.context(location_info!())?;
192 verify_activity_domains_valid(&follow, &user.actor_id()?, false)?;
194 let community_follower_form = CommunityFollowerForm {
195 community_id: community.id,
200 // This will fail if they're already a follower, but ignore the error.
201 blocking(&context.pool(), move |conn| {
202 CommunityFollower::follow(&conn, &community_follower_form).ok()
206 community.send_accept_follow(follow, context).await?;
208 Ok(HttpResponse::Ok().finish())
211 async fn handle_undo(
212 context: &LemmyContext,
213 activity: CommunityAcceptedActivities,
215 to_community: &Community,
216 request_counter: &mut i32,
217 ) -> Result<bool, LemmyError> {
218 let inner_kind = activity
220 .is_single_kind(&FollowType::Follow.to_string());
221 let any_base = activity.into_any_base()?;
223 handle_undo_follow(any_base, actor_url, to_community, &context).await?;
226 receive_undo_for_community(context, any_base, &actor_url, request_counter).await?;
231 /// Handle `Undo/Follow` from a user, removing the user from followers list.
232 async fn handle_undo_follow(
235 community: &Community,
236 context: &LemmyContext,
237 ) -> Result<(), LemmyError> {
238 let undo = Undo::from_any_base(activity)?.context(location_info!())?;
239 verify_activity_domains_valid(&undo, &user_url, true)?;
241 let object = undo.object().to_owned().one().context(location_info!())?;
242 let follow = Follow::from_any_base(object)?.context(location_info!())?;
243 verify_activity_domains_valid(&follow, &user_url, false)?;
245 let user = blocking(&context.pool(), move |conn| {
246 User_::read_from_apub_id(&conn, user_url.as_str())
249 let community_follower_form = CommunityFollowerForm {
250 community_id: community.id,
255 // This will fail if they aren't a follower, but ignore the error.
256 blocking(&context.pool(), move |conn| {
257 CommunityFollower::unfollow(&conn, &community_follower_form).ok()
264 pub(crate) async fn check_community_or_site_ban(
266 community: &Community,
268 ) -> Result<(), LemmyError> {
270 return Err(anyhow!("User is banned from site").into());
272 let user_id = user.id;
273 let community_id = community.id;
274 let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
275 if blocking(pool, is_banned).await? {
276 return Err(anyhow!("User is banned from community").into());