1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.CommonAPI do
10 alias Pleroma.ThreadMute
11 alias Pleroma.Web.ActivityPub.ActivityPub
12 alias Pleroma.Web.ActivityPub.Utils
13 alias Pleroma.Formatter
15 import Pleroma.Web.CommonAPI.Utils
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]),
26 {:ok, follower, followed, activity}
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
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)
47 {:error, "Could not repeat"}
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)
57 {:error, "Could not unrepeat"}
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)
68 {:error, "Could not favorite"}
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)
78 {:error, "Could not unfavorite"}
82 def get_visibility(%{"visibility" => visibility})
83 when visibility in ~w{public unlisted private direct},
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
92 Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"])
96 def get_visibility(_), do: "public"
98 def post(user, %{"status" => status} = data) do
99 visibility = get_visibility(data)
100 limit = Pleroma.Config.get([:instance, :limit])
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} <-
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),
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}")
138 ActivityPub.create(%{
143 additional: %{"cc" => cc, "directMessage" => visibility == "direct"}
150 # Updates the emojis for a user based on their profile
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
164 ActivityPub.update(%{
166 to: [user.follower_address],
169 object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
173 def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
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),
188 Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
189 {:ok, _user} <- User.update_and_set_cache(changeset) do
192 %{errors: [pinned_activities: {err, _}]} ->
196 {:error, "Could not pin"}
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),
205 Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
206 {:ok, _user} <- User.update_and_set_cache(changeset) do
209 %{errors: [pinned_activities: {err, _}]} ->
213 {:error, "Could not unpin"}
217 def add_mute(user, activity) do
218 with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
221 {:error, _} -> {:error, "conversation is already muted"}
225 def remove_mute(user, activity) do
226 ThreadMute.remove_mute(user.id, activity.data["context"])
230 def thread_muted?(%{id: nil} = _user, _activity), do: false
232 def thread_muted?(user, activity) do
233 with [] <- ThreadMute.check_muted(user.id, activity.data["context"]) do
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),
247 context: Utils.generate_context_id(),
251 content: content_html
253 Enum.each(User.all_superusers(), fn superuser ->
255 |> Pleroma.AdminEmail.report(user, account, statuses, content_html)
256 |> Pleroma.Mailer.deliver_async()
261 {:error, err} -> {:error, err}
262 {:account_id, %{}} -> {:error, "Valid `account_id` required"}
263 {:account, nil} -> {:error, "Account not found"}