09fd7d7c9d7450bcec2004f5d008561ab4c7bee8
[akkoma] / lib / pleroma / web / activity_pub / side_effects.ex
1 defmodule Pleroma.Web.ActivityPub.SideEffects do
2 @moduledoc """
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
6 collection, and so on.
7 """
8 alias Pleroma.Activity
9 alias Pleroma.Chat
10 alias Pleroma.Chat.MessageReference
11 alias Pleroma.Notification
12 alias Pleroma.Object
13 alias Pleroma.Repo
14 alias Pleroma.User
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.ActivityPub.Pipeline
17 alias Pleroma.Web.ActivityPub.Utils
18 alias Pleroma.Web.Push
19 alias Pleroma.Web.Streamer
20
21 def handle(object, meta \\ [])
22
23 # Tasks this handles:
24 # Update the user
25 def handle(%{data: %{"type" => "Update", "object" => updated_object}} = object, meta) do
26 {:ok, new_user_data} = ActivityPub.user_data_from_user_object(updated_object)
27
28 User.get_by_ap_id(updated_object["id"])
29 |> User.remote_user_changeset(new_user_data)
30 |> User.update_and_set_cache()
31
32 {:ok, object, meta}
33 end
34
35 # Tasks this handles:
36 # - Add like to object
37 # - Set up notification
38 def handle(%{data: %{"type" => "Like"}} = object, meta) do
39 liked_object = Object.get_by_ap_id(object.data["object"])
40 Utils.add_like_to_object(object, liked_object)
41
42 Notification.create_notifications(object)
43
44 {:ok, object, meta}
45 end
46
47 # Tasks this handles
48 # - Actually create object
49 # - Rollback if we couldn't create it
50 # - Set up notifications
51 def handle(%{data: %{"type" => "Create"}} = activity, meta) do
52 with {:ok, _object, meta} <- handle_object_creation(meta[:object_data], meta) do
53 {:ok, notifications} = Notification.create_notifications(activity, do_send: false)
54
55 meta =
56 meta
57 |> add_notifications(notifications)
58
59 {:ok, activity, meta}
60 else
61 e -> Repo.rollback(e)
62 end
63 end
64
65 # Tasks this handles:
66 # - Add announce to object
67 # - Set up notification
68 # - Stream out the announce
69 def handle(%{data: %{"type" => "Announce"}} = object, meta) do
70 announced_object = Object.get_by_ap_id(object.data["object"])
71 user = User.get_cached_by_ap_id(object.data["actor"])
72
73 Utils.add_announce_to_object(object, announced_object)
74
75 if !User.is_internal_user?(user) do
76 Notification.create_notifications(object)
77 ActivityPub.stream_out(object)
78 end
79
80 {:ok, object, meta}
81 end
82
83 def handle(%{data: %{"type" => "Undo", "object" => undone_object}} = object, meta) do
84 with undone_object <- Activity.get_by_ap_id(undone_object),
85 :ok <- handle_undoing(undone_object) do
86 {:ok, object, meta}
87 end
88 end
89
90 # Tasks this handles:
91 # - Add reaction to object
92 # - Set up notification
93 def handle(%{data: %{"type" => "EmojiReact"}} = object, meta) do
94 reacted_object = Object.get_by_ap_id(object.data["object"])
95 Utils.add_emoji_reaction_to_object(object, reacted_object)
96
97 Notification.create_notifications(object)
98
99 {:ok, object, meta}
100 end
101
102 # Tasks this handles:
103 # - Delete and unpins the create activity
104 # - Replace object with Tombstone
105 # - Set up notification
106 # - Reduce the user note count
107 # - Reduce the reply count
108 # - Stream out the activity
109 def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do
110 deleted_object =
111 Object.normalize(deleted_object, false) || User.get_cached_by_ap_id(deleted_object)
112
113 result =
114 case deleted_object do
115 %Object{} ->
116 with {:ok, deleted_object, activity} <- Object.delete(deleted_object),
117 %User{} = user <- User.get_cached_by_ap_id(deleted_object.data["actor"]) do
118 User.remove_pinnned_activity(user, activity)
119
120 {:ok, user} = ActivityPub.decrease_note_count_if_public(user, deleted_object)
121
122 if in_reply_to = deleted_object.data["inReplyTo"] do
123 Object.decrease_replies_count(in_reply_to)
124 end
125
126 MessageReference.delete_for_object(deleted_object)
127
128 ActivityPub.stream_out(object)
129 ActivityPub.stream_out_participations(deleted_object, user)
130 :ok
131 end
132
133 %User{} ->
134 with {:ok, _} <- User.delete(deleted_object) do
135 :ok
136 end
137 end
138
139 if result == :ok do
140 Notification.create_notifications(object)
141 {:ok, object, meta}
142 else
143 {:error, result}
144 end
145 end
146
147 # Nothing to do
148 def handle(object, meta) do
149 {:ok, object, meta}
150 end
151
152 def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do
153 with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
154 actor = User.get_cached_by_ap_id(object.data["actor"])
155 recipient = User.get_cached_by_ap_id(hd(object.data["to"]))
156
157 streamables =
158 [[actor, recipient], [recipient, actor]]
159 |> Enum.map(fn [user, other_user] ->
160 if user.local do
161 {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
162 {:ok, cm_ref} = MessageReference.create(chat, object, user.ap_id != actor.ap_id)
163
164 {
165 ["user", "user:pleroma_chat"],
166 {user, %{cm_ref | chat: chat, object: object}}
167 }
168 end
169 end)
170 |> Enum.filter(& &1)
171
172 meta =
173 meta
174 |> add_streamables(streamables)
175
176 {:ok, object, meta}
177 end
178 end
179
180 # Nothing to do
181 def handle_object_creation(object) do
182 {:ok, object}
183 end
184
185 def handle_undoing(%{data: %{"type" => "Like"}} = object) do
186 with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
187 {:ok, _} <- Utils.remove_like_from_object(object, liked_object),
188 {:ok, _} <- Repo.delete(object) do
189 :ok
190 end
191 end
192
193 def handle_undoing(%{data: %{"type" => "EmojiReact"}} = object) do
194 with %Object{} = reacted_object <- Object.get_by_ap_id(object.data["object"]),
195 {:ok, _} <- Utils.remove_emoji_reaction_from_object(object, reacted_object),
196 {:ok, _} <- Repo.delete(object) do
197 :ok
198 end
199 end
200
201 def handle_undoing(%{data: %{"type" => "Announce"}} = object) do
202 with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
203 {:ok, _} <- Utils.remove_announce_from_object(object, liked_object),
204 {:ok, _} <- Repo.delete(object) do
205 :ok
206 end
207 end
208
209 def handle_undoing(
210 %{data: %{"type" => "Block", "actor" => blocker, "object" => blocked}} = object
211 ) do
212 with %User{} = blocker <- User.get_cached_by_ap_id(blocker),
213 %User{} = blocked <- User.get_cached_by_ap_id(blocked),
214 {:ok, _} <- User.unblock(blocker, blocked),
215 {:ok, _} <- Repo.delete(object) do
216 :ok
217 end
218 end
219
220 def handle_undoing(object), do: {:error, ["don't know how to handle", object]}
221
222 defp send_notifications(meta) do
223 Keyword.get(meta, :notifications, [])
224 |> Enum.each(fn notification ->
225 Streamer.stream(["user", "user:notification"], notification)
226 Push.send(notification)
227 end)
228
229 meta
230 end
231
232 defp send_streamables(meta) do
233 Keyword.get(meta, :streamables, [])
234 |> Enum.each(fn {topics, items} ->
235 Streamer.stream(topics, items)
236 end)
237
238 meta
239 end
240
241 defp add_streamables(meta, streamables) do
242 existing = Keyword.get(meta, :streamables, [])
243
244 meta
245 |> Keyword.put(:streamables, streamables ++ existing)
246 end
247
248 defp add_notifications(meta, notifications) do
249 existing = Keyword.get(meta, :notifications, [])
250
251 meta
252 |> Keyword.put(:notifications, notifications ++ existing)
253 end
254
255 def handle_after_transaction(meta) do
256 meta
257 |> send_notifications()
258 |> send_streamables()
259 end
260 end