1 defmodule Pleroma.Web.ActivityPub.SideEffects do
3 This module looks at an inserted object and executes the side effects that it
4 implies. For example, a `Like` activity will increase the like count on the
5 liked object, a `Follow` activity will add the user to the follower
9 alias Pleroma.Activity.Ir.Topics
11 alias Pleroma.Chat.MessageReference
12 alias Pleroma.FollowingRelationship
13 alias Pleroma.Notification
17 alias Pleroma.Web.ActivityPub.ActivityPub
18 alias Pleroma.Web.ActivityPub.Pipeline
19 alias Pleroma.Web.ActivityPub.Utils
20 alias Pleroma.Web.Push
21 alias Pleroma.Web.Streamer
23 def handle(object, meta \\ [])
26 # - Follows if possible
27 # - Sends a notification
28 # - Generates accept or reject if appropriate
34 "object" => followed_user,
35 "actor" => following_user
40 with %User{} = follower <- User.get_cached_by_ap_id(following_user),
41 %User{} = followed <- User.get_cached_by_ap_id(followed_user),
42 {_, {:ok, _}, _, _} <-
43 {:following, User.follow(follower, followed, :follow_pending), follower, followed} do
44 if followed.local && !followed.locked do
45 Utils.update_follow_state_for_all(object, "accept")
46 FollowingRelationship.update(follower, followed, :follow_accept)
47 User.update_follower_count(followed)
48 User.update_following_count(follower)
56 |> ActivityPub.accept()
59 {:following, {:error, _}, follower, followed} ->
60 Utils.update_follow_state_for_all(object, "reject")
61 FollowingRelationship.update(follower, followed, :follow_reject)
70 |> ActivityPub.reject()
77 {:ok, notifications} = Notification.create_notifications(object, do_send: false)
81 |> add_notifications(notifications)
83 updated_object = Activity.get_by_ap_id(follow_id)
85 {:ok, updated_object, meta}
89 # - Unfollow and block
91 %{data: %{"type" => "Block", "object" => blocked_user, "actor" => blocking_user}} =
95 with %User{} = blocker <- User.get_cached_by_ap_id(blocking_user),
96 %User{} = blocked <- User.get_cached_by_ap_id(blocked_user) do
97 User.block(blocker, blocked)
103 # Tasks this handles:
106 # For a local user, we also get a changeset with the full information, so we
107 # can update non-federating, non-activitypub settings as well.
108 def handle(%{data: %{"type" => "Update", "object" => updated_object}} = object, meta) do
109 if changeset = Keyword.get(meta, :user_update_changeset) do
111 |> User.update_and_set_cache()
113 {:ok, new_user_data} = ActivityPub.user_data_from_user_object(updated_object)
115 User.get_by_ap_id(updated_object["id"])
116 |> User.remote_user_changeset(new_user_data)
117 |> User.update_and_set_cache()
123 # Tasks this handles:
124 # - Add like to object
125 # - Set up notification
126 def handle(%{data: %{"type" => "Like"}} = object, meta) do
127 liked_object = Object.get_by_ap_id(object.data["object"])
128 Utils.add_like_to_object(object, liked_object)
130 Notification.create_notifications(object)
136 # - Actually create object
137 # - Rollback if we couldn't create it
138 # - Set up notifications
139 def handle(%{data: %{"type" => "Create"}} = activity, meta) do
140 with {:ok, _object, meta} <- handle_object_creation(meta[:object_data], meta) do
141 {:ok, notifications} = Notification.create_notifications(activity, do_send: false)
145 |> add_notifications(notifications)
147 {:ok, activity, meta}
149 e -> Repo.rollback(e)
153 # Tasks this handles:
154 # - Add announce to object
155 # - Set up notification
156 # - Stream out the announce
157 def handle(%{data: %{"type" => "Announce"}} = object, meta) do
158 announced_object = Object.get_by_ap_id(object.data["object"])
159 user = User.get_cached_by_ap_id(object.data["actor"])
161 Utils.add_announce_to_object(object, announced_object)
163 if !User.is_internal_user?(user) do
164 Notification.create_notifications(object)
167 |> Topics.get_activity_topics()
168 |> Streamer.stream(object)
174 def handle(%{data: %{"type" => "Undo", "object" => undone_object}} = object, meta) do
175 with undone_object <- Activity.get_by_ap_id(undone_object),
176 :ok <- handle_undoing(undone_object) do
179 |> Keyword.put(:embedded_object, undone_object.data)
185 # Tasks this handles:
186 # - Add reaction to object
187 # - Set up notification
188 def handle(%{data: %{"type" => "EmojiReact"}} = object, meta) do
189 reacted_object = Object.get_by_ap_id(object.data["object"])
190 Utils.add_emoji_reaction_to_object(object, reacted_object)
192 Notification.create_notifications(object)
197 # Tasks this handles:
198 # - Delete and unpins the create activity
199 # - Replace object with Tombstone
200 # - Set up notification
201 # - Reduce the user note count
202 # - Reduce the reply count
203 # - Stream out the activity
204 def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do
206 Object.normalize(deleted_object, false) || User.get_cached_by_ap_id(deleted_object)
209 case deleted_object do
211 with {:ok, deleted_object, activity} <- Object.delete(deleted_object),
212 %User{} = user <- User.get_cached_by_ap_id(deleted_object.data["actor"]) do
213 User.remove_pinnned_activity(user, activity)
215 {:ok, user} = ActivityPub.decrease_note_count_if_public(user, deleted_object)
217 if in_reply_to = deleted_object.data["inReplyTo"] do
218 Object.decrease_replies_count(in_reply_to)
221 MessageReference.delete_for_object(deleted_object)
223 ActivityPub.stream_out(object)
224 ActivityPub.stream_out_participations(deleted_object, user)
229 with {:ok, _} <- User.delete(deleted_object) do
235 Notification.create_notifications(object)
243 def handle(object, meta) do
247 def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do
248 with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
249 actor = User.get_cached_by_ap_id(object.data["actor"])
250 recipient = User.get_cached_by_ap_id(hd(object.data["to"]))
253 [[actor, recipient], [recipient, actor]]
254 |> Enum.map(fn [user, other_user] ->
256 {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
257 {:ok, cm_ref} = MessageReference.create(chat, object, user.ap_id != actor.ap_id)
260 ["user", "user:pleroma_chat"],
261 {user, %{cm_ref | chat: chat, object: object}}
269 |> add_streamables(streamables)
276 def handle_object_creation(object) do
280 defp undo_like(nil, object), do: delete_object(object)
282 defp undo_like(%Object{} = liked_object, object) do
283 with {:ok, _} <- Utils.remove_like_from_object(object, liked_object) do
284 delete_object(object)
288 def handle_undoing(%{data: %{"type" => "Like"}} = object) do
289 object.data["object"]
290 |> Object.get_by_ap_id()
294 def handle_undoing(%{data: %{"type" => "EmojiReact"}} = object) do
295 with %Object{} = reacted_object <- Object.get_by_ap_id(object.data["object"]),
296 {:ok, _} <- Utils.remove_emoji_reaction_from_object(object, reacted_object),
297 {:ok, _} <- Repo.delete(object) do
302 def handle_undoing(%{data: %{"type" => "Announce"}} = object) do
303 with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
304 {:ok, _} <- Utils.remove_announce_from_object(object, liked_object),
305 {:ok, _} <- Repo.delete(object) do
311 %{data: %{"type" => "Block", "actor" => blocker, "object" => blocked}} = object
313 with %User{} = blocker <- User.get_cached_by_ap_id(blocker),
314 %User{} = blocked <- User.get_cached_by_ap_id(blocked),
315 {:ok, _} <- User.unblock(blocker, blocked),
316 {:ok, _} <- Repo.delete(object) do
321 def handle_undoing(object), do: {:error, ["don't know how to handle", object]}
323 @spec delete_object(Object.t()) :: :ok | {:error, Ecto.Changeset.t()}
324 defp delete_object(object) do
325 with {:ok, _} <- Repo.delete(object), do: :ok
328 defp send_notifications(meta) do
329 Keyword.get(meta, :notifications, [])
330 |> Enum.each(fn notification ->
331 Streamer.stream(["user", "user:notification"], notification)
332 Push.send(notification)
338 defp send_streamables(meta) do
339 Keyword.get(meta, :streamables, [])
340 |> Enum.each(fn {topics, items} ->
341 Streamer.stream(topics, items)
347 defp add_streamables(meta, streamables) do
348 existing = Keyword.get(meta, :streamables, [])
351 |> Keyword.put(:streamables, streamables ++ existing)
354 defp add_notifications(meta, notifications) do
355 existing = Keyword.get(meta, :notifications, [])
358 |> Keyword.put(:notifications, notifications ++ existing)
361 def handle_after_transaction(meta) do
363 |> send_notifications()
364 |> send_streamables()