common_api.ex 8.42 KB
Newer Older
1
# Pleroma: A lightweight social networking server
kaniini's avatar
kaniini committed
2
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 4
# SPDX-License-Identifier: AGPL-3.0-only

lain's avatar
lain committed
5
defmodule Pleroma.Web.CommonAPI do
Haelwenn's avatar
Haelwenn committed
6 7 8 9
  alias Pleroma.User
  alias Pleroma.Repo
  alias Pleroma.Activity
  alias Pleroma.Object
10
  alias Pleroma.ThreadMute
lain's avatar
lain committed
11
  alias Pleroma.Web.ActivityPub.ActivityPub
12
  alias Pleroma.Web.ActivityPub.Utils
13 14 15
  alias Pleroma.Formatter

  import Pleroma.Web.CommonAPI.Utils
lain's avatar
lain committed
16 17 18

  def delete(activity_id, user) do
    with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id),
19
         %Object{} = object <- Object.normalize(object_id),
lain's avatar
lain committed
20
         true <- user.info.is_moderator || user.ap_id == object.data["actor"],
minibikini's avatar
minibikini committed
21
         {:ok, _} <- unpin(activity_id, user),
22
         {:ok, delete} <- ActivityPub.delete(object) do
lain's avatar
lain committed
23 24 25
      {:ok, delete}
    end
  end
lain's avatar
lain committed
26 27 28

  def repeat(id_or_ap_id, user) do
    with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
29 30
         object <- Object.normalize(activity.data["object"]["id"]),
         nil <- Utils.get_existing_announce(user.ap_id, object) do
lain's avatar
lain committed
31 32 33 34 35 36 37
      ActivityPub.announce(user, object)
    else
      _ ->
        {:error, "Could not repeat"}
    end
  end

normandy's avatar
normandy committed
38 39
  def unrepeat(id_or_ap_id, user) do
    with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
40
         object <- Object.normalize(activity.data["object"]["id"]) do
normandy's avatar
normandy committed
41 42 43 44 45 46 47
      ActivityPub.unannounce(user, object)
    else
      _ ->
        {:error, "Could not unrepeat"}
    end
  end

lain's avatar
lain committed
48 49
  def favorite(id_or_ap_id, user) do
    with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
50 51
         object <- Object.normalize(activity.data["object"]["id"]),
         nil <- Utils.get_existing_like(user.ap_id, object) do
lain's avatar
lain committed
52 53 54 55 56 57 58
      ActivityPub.like(user, object)
    else
      _ ->
        {:error, "Could not favorite"}
    end
  end

lain's avatar
lain committed
59 60
  def unfavorite(id_or_ap_id, user) do
    with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
61
         object <- Object.normalize(activity.data["object"]["id"]) do
lain's avatar
lain committed
62 63 64 65 66 67 68
      ActivityPub.unlike(user, object)
    else
      _ ->
        {:error, "Could not unfavorite"}
    end
  end

lain's avatar
lain committed
69 70 71 72
  def get_visibility(%{"visibility" => visibility})
      when visibility in ~w{public unlisted private direct},
      do: visibility

lain's avatar
lain committed
73
  def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do
74 75 76 77 78 79 80
    case get_replied_to_activity(status_id) do
      nil ->
        "public"

      inReplyTo ->
        Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"])
    end
81
  end
lain's avatar
lain committed
82

83 84
  def get_visibility(_), do: "public"

href's avatar
href committed
85 86 87 88 89 90 91
  defp get_content_type(content_type) do
    if Enum.member?(Pleroma.Config.get([:instance, :allowed_post_formats]), content_type) do
      content_type
    else
      "text/plain"
    end
  end
92

93
  def post(user, %{"status" => status} = data) do
94
    visibility = get_visibility(data)
href's avatar
href committed
95
    limit = Pleroma.Config.get([:instance, :limit])
lain's avatar
lain committed
96

97
    with status <- String.trim(status),
98
         attachments <- attachments_from_ids(data),
99 100
         mentions <- Formatter.parse_mentions(status),
         inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]),
101
         {to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility),
102
         tags <- Formatter.parse_tags(status, data),
lain's avatar
lain committed
103
         content_html <-
104 105 106 107 108
           make_content_html(
             status,
             mentions,
             attachments,
             tags,
109
             get_content_type(data["content_type"]),
feld's avatar
feld committed
110 111 112 113 114 115 116 117
             Enum.member?(
               [true, "true"],
               Map.get(
                 data,
                 "no_attachment_links",
                 Pleroma.Config.get([:instance, :no_attachment_links], false)
               )
             )
118
           ),
lain's avatar
lain committed
119
         context <- make_context(inReplyTo),
lain's avatar
lain committed
120
         cw <- data["spoiler_text"],
121
         full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
href's avatar
href committed
122
         length when length in 1..limit <- String.length(full_payload),
lain's avatar
lain committed
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
         object <-
           make_note_data(
             user.ap_id,
             to,
             context,
             content_html,
             attachments,
             inReplyTo,
             tags,
             cw,
             cc
           ),
         object <-
           Map.put(
             object,
             "emoji",
scarlett's avatar
scarlett committed
139
             (Formatter.get_emoji(status) ++ Formatter.get_emoji(data["spoiler_text"]))
lain's avatar
lain committed
140 141 142 143 144 145 146 147 148 149
             |> Enum.reduce(%{}, fn {name, file}, acc ->
               Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
             end)
           ) do
      res =
        ActivityPub.create(%{
          to: to,
          actor: user,
          context: context,
          object: object,
150
          additional: %{"cc" => cc, "directMessage" => visibility == "direct"}
lain's avatar
lain committed
151 152
        })

153
      res
lain's avatar
lain committed
154 155
    end
  end
lain's avatar
lain committed
156

lain's avatar
lain committed
157
  # Updates the emojis for a user based on their profile
lain's avatar
lain committed
158
  def update(user) do
159 160
    user =
      with emoji <- emoji_from_profile(user),
lain's avatar
lain committed
161 162 163
           source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji),
           info_cng <- Pleroma.User.Info.set_source_data(user.info, source_data),
           change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
164 165 166 167 168 169 170
           {:ok, user} <- User.update_and_set_cache(change) do
        user
      else
        _e ->
          user
      end

lain's avatar
lain committed
171 172 173 174 175 176 177
    ActivityPub.update(%{
      local: true,
      to: [user.follower_address],
      cc: [],
      actor: user.ap_id,
      object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
    })
lain's avatar
lain committed
178
  end
minibikini's avatar
minibikini committed
179

180 181 182 183 184 185 186 187 188 189 190 191
  def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
    with %Activity{
           actor: ^user_ap_id,
           data: %{
             "type" => "Create",
             "object" => %{
               "to" => object_to,
               "type" => "Note"
             }
           }
         } = activity <- get_by_id_or_ap_id(id_or_ap_id),
         true <- Enum.member?(object_to, "https://www.w3.org/ns/activitystreams#Public"),
minibikini's avatar
minibikini committed
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
         %{valid?: true} = info_changeset <-
           Pleroma.User.Info.add_pinnned_activity(user.info, activity),
         changeset <-
           Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
         {:ok, _user} <- User.update_and_set_cache(changeset) do
      {:ok, activity}
    else
      %{errors: [pinned_activities: {err, _}]} ->
        {:error, err}

      _ ->
        {:error, "Could not pin"}
    end
  end

  def unpin(id_or_ap_id, user) do
    with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
         %{valid?: true} = info_changeset <-
           Pleroma.User.Info.remove_pinnned_activity(user.info, activity),
         changeset <-
           Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
         {:ok, _user} <- User.update_and_set_cache(changeset) do
      {:ok, activity}
    else
      %{errors: [pinned_activities: {err, _}]} ->
        {:error, err}

      _ ->
        {:error, "Could not unpin"}
    end
  end
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245

  def add_mute(user, activity) do
    with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
      {:ok, activity}
    else
      {:error, _} -> {:error, "conversation is already muted"}
    end
  end

  def remove_mute(user, activity) do
    ThreadMute.remove_mute(user.id, activity.data["context"])
    {:ok, activity}
  end

  def thread_muted?(%{id: nil} = _user, _activity), do: false

  def thread_muted?(user, activity) do
    with [] <- ThreadMute.check_muted(user.id, activity.data["context"]) do
      false
    else
      _ -> true
    end
  end
minibikini's avatar
Reports  
minibikini committed
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272

  def report(user, data) do
    with {:account_id, %{"account_id" => account_id}} <- {:account_id, data},
         {:account, %User{} = account} <- {:account, User.get_by_id(account_id)},
         {:ok, content_html} <- make_report_content_html(data["comment"]),
         {:ok, statuses} <- get_report_statuses(account, data),
         {:ok, activity} <-
           ActivityPub.flag(%{
             context: Utils.generate_context_id(),
             actor: user,
             account: account,
             statuses: statuses,
             content: content_html
           }) do
      Enum.each(User.all_superusers(), fn superuser ->
        superuser
        |> Pleroma.AdminEmail.report(user, account, statuses, content_html)
        |> Pleroma.Mailer.deliver_async()
      end)

      {:ok, activity}
    else
      {:error, err} -> {:error, err}
      {:account_id, %{}} -> {:error, "Valid `account_id` required"}
      {:account, nil} -> {:error, "Account not found"}
    end
  end
lain's avatar
lain committed
273
end