Merge branch 'feature/add-roles-to-users-admin-api' into 'develop'
[akkoma] / lib / pleroma / web / common_api / common_api.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.CommonAPI do
6 alias Pleroma.User
7 alias Pleroma.Repo
8 alias Pleroma.Activity
9 alias Pleroma.Object
10 alias Pleroma.ThreadMute
11 alias Pleroma.Web.ActivityPub.ActivityPub
12 alias Pleroma.Web.ActivityPub.Utils
13 alias Pleroma.Formatter
14
15 import Pleroma.Web.CommonAPI.Utils
16
17 def follow(follower, followed) do
18 with {:ok, follower} <- User.maybe_direct_follow(follower, followed),
19 {:ok, activity} <- ActivityPub.follow(follower, followed),
20 {:ok, follower, followed} <-
21 User.wait_and_refresh(
22 Pleroma.Config.get([:activitypub, :follow_handshake_timeout]),
23 follower,
24 followed
25 ) do
26 {:ok, follower, followed, activity}
27 end
28 end
29
30 def delete(activity_id, user) do
31 with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id),
32 %Object{} = object <- Object.normalize(object_id),
33 true <- User.superuser?(user) || user.ap_id == object.data["actor"],
34 {:ok, _} <- unpin(activity_id, user),
35 {:ok, delete} <- ActivityPub.delete(object) do
36 {:ok, delete}
37 end
38 end
39
40 def repeat(id_or_ap_id, user) do
41 with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
42 object <- Object.normalize(activity.data["object"]["id"]),
43 nil <- Utils.get_existing_announce(user.ap_id, object) do
44 ActivityPub.announce(user, object)
45 else
46 _ ->
47 {:error, "Could not repeat"}
48 end
49 end
50
51 def unrepeat(id_or_ap_id, user) do
52 with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
53 object <- Object.normalize(activity.data["object"]["id"]) do
54 ActivityPub.unannounce(user, object)
55 else
56 _ ->
57 {:error, "Could not unrepeat"}
58 end
59 end
60
61 def favorite(id_or_ap_id, user) do
62 with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
63 object <- Object.normalize(activity.data["object"]["id"]),
64 nil <- Utils.get_existing_like(user.ap_id, object) do
65 ActivityPub.like(user, object)
66 else
67 _ ->
68 {:error, "Could not favorite"}
69 end
70 end
71
72 def unfavorite(id_or_ap_id, user) do
73 with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
74 object <- Object.normalize(activity.data["object"]["id"]) do
75 ActivityPub.unlike(user, object)
76 else
77 _ ->
78 {:error, "Could not unfavorite"}
79 end
80 end
81
82 def get_visibility(%{"visibility" => visibility})
83 when visibility in ~w{public unlisted private direct},
84 do: visibility
85
86 def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do
87 case get_replied_to_activity(status_id) do
88 nil ->
89 "public"
90
91 inReplyTo ->
92 Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"])
93 end
94 end
95
96 def get_visibility(_), do: "public"
97
98 def post(user, %{"status" => status} = data) do
99 visibility = get_visibility(data)
100 limit = Pleroma.Config.get([:instance, :limit])
101
102 with status <- String.trim(status),
103 attachments <- attachments_from_ids(data),
104 inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]),
105 {content_html, mentions, tags} <-
106 make_content_html(
107 status,
108 attachments,
109 data
110 ),
111 {to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility),
112 context <- make_context(inReplyTo),
113 cw <- data["spoiler_text"],
114 full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
115 length when length in 1..limit <- String.length(full_payload),
116 object <-
117 make_note_data(
118 user.ap_id,
119 to,
120 context,
121 content_html,
122 attachments,
123 inReplyTo,
124 tags,
125 cw,
126 cc
127 ),
128 object <-
129 Map.put(
130 object,
131 "emoji",
132 (Formatter.get_emoji(status) ++ Formatter.get_emoji(data["spoiler_text"]))
133 |> Enum.reduce(%{}, fn {name, file}, acc ->
134 Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
135 end)
136 ) do
137 res =
138 ActivityPub.create(%{
139 to: to,
140 actor: user,
141 context: context,
142 object: object,
143 additional: %{"cc" => cc, "directMessage" => visibility == "direct"}
144 })
145
146 res
147 end
148 end
149
150 # Updates the emojis for a user based on their profile
151 def update(user) do
152 user =
153 with emoji <- emoji_from_profile(user),
154 source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji),
155 info_cng <- Pleroma.User.Info.set_source_data(user.info, source_data),
156 change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
157 {:ok, user} <- User.update_and_set_cache(change) do
158 user
159 else
160 _e ->
161 user
162 end
163
164 ActivityPub.update(%{
165 local: true,
166 to: [user.follower_address],
167 cc: [],
168 actor: user.ap_id,
169 object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
170 })
171 end
172
173 def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
174 with %Activity{
175 actor: ^user_ap_id,
176 data: %{
177 "type" => "Create",
178 "object" => %{
179 "to" => object_to,
180 "type" => "Note"
181 }
182 }
183 } = activity <- get_by_id_or_ap_id(id_or_ap_id),
184 true <- Enum.member?(object_to, "https://www.w3.org/ns/activitystreams#Public"),
185 %{valid?: true} = info_changeset <-
186 Pleroma.User.Info.add_pinnned_activity(user.info, activity),
187 changeset <-
188 Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
189 {:ok, _user} <- User.update_and_set_cache(changeset) do
190 {:ok, activity}
191 else
192 %{errors: [pinned_activities: {err, _}]} ->
193 {:error, err}
194
195 _ ->
196 {:error, "Could not pin"}
197 end
198 end
199
200 def unpin(id_or_ap_id, user) do
201 with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
202 %{valid?: true} = info_changeset <-
203 Pleroma.User.Info.remove_pinnned_activity(user.info, activity),
204 changeset <-
205 Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
206 {:ok, _user} <- User.update_and_set_cache(changeset) do
207 {:ok, activity}
208 else
209 %{errors: [pinned_activities: {err, _}]} ->
210 {:error, err}
211
212 _ ->
213 {:error, "Could not unpin"}
214 end
215 end
216
217 def add_mute(user, activity) do
218 with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
219 {:ok, activity}
220 else
221 {:error, _} -> {:error, "conversation is already muted"}
222 end
223 end
224
225 def remove_mute(user, activity) do
226 ThreadMute.remove_mute(user.id, activity.data["context"])
227 {:ok, activity}
228 end
229
230 def thread_muted?(%{id: nil} = _user, _activity), do: false
231
232 def thread_muted?(user, activity) do
233 with [] <- ThreadMute.check_muted(user.id, activity.data["context"]) do
234 false
235 else
236 _ -> true
237 end
238 end
239
240 def report(user, data) do
241 with {:account_id, %{"account_id" => account_id}} <- {:account_id, data},
242 {:account, %User{} = account} <- {:account, User.get_by_id(account_id)},
243 {:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]),
244 {:ok, statuses} <- get_report_statuses(account, data),
245 {:ok, activity} <-
246 ActivityPub.flag(%{
247 context: Utils.generate_context_id(),
248 actor: user,
249 account: account,
250 statuses: statuses,
251 content: content_html
252 }) do
253 Enum.each(User.all_superusers(), fn superuser ->
254 superuser
255 |> Pleroma.AdminEmail.report(user, account, statuses, content_html)
256 |> Pleroma.Mailer.deliver_async()
257 end)
258
259 {:ok, activity}
260 else
261 {:error, err} -> {:error, err}
262 {:account_id, %{}} -> {:error, "Valid `account_id` required"}
263 {:account, nil} -> {:error, "Account not found"}
264 end
265 end
266 end