utils.ex 6.84 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

5
defmodule Pleroma.Web.CommonAPI.Utils do
Maksim's avatar
Maksim committed
6 7 8 9 10
  alias Calendar.Strftime
  alias Comeonin.Pbkdf2
  alias Pleroma.{Activity, Formatter, Object, Repo}
  alias Pleroma.User
  alias Pleroma.Web
lain's avatar
lain committed
11
  alias Pleroma.Web.ActivityPub.Utils
12
  alias Pleroma.Web.Endpoint
13
  alias Pleroma.Web.MediaProxy
lain's avatar
lain committed
14

15 16 17
  # This is a hack for twidere.
  def get_by_id_or_ap_id(id) do
    activity = Repo.get(Activity, id) || Activity.get_create_activity_by_object_ap_id(id)
lain's avatar
lain committed
18

19 20 21 22 23 24
    activity &&
      if activity.data["type"] == "Create" do
        activity
      else
        Activity.get_create_activity_by_object_ap_id(activity.data["object"])
      end
25 26
  end

27 28
  def get_replied_to_activity(""), do: nil

29 30 31
  def get_replied_to_activity(id) when not is_nil(id) do
    Repo.get(Activity, id)
  end
lain's avatar
lain committed
32

33 34
  def get_replied_to_activity(_), do: nil

lain's avatar
lain committed
35
  def attachments_from_ids(ids) do
lain's avatar
lain committed
36
    Enum.map(ids || [], fn media_id ->
lain's avatar
lain committed
37 38 39 40
      Repo.get(Object, media_id).data
    end)
  end

41
  def to_for_user_and_mentions(user, mentions, inReplyTo, "public") do
lain's avatar
lain committed
42
    mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)
43 44 45

    to = ["https://www.w3.org/ns/activitystreams#Public" | mentioned_users]
    cc = [user.follower_address]
lain's avatar
lain committed
46

47
    if inReplyTo do
48
      {Enum.uniq([inReplyTo.data["actor"] | to]), cc}
lain's avatar
lain committed
49
    else
50 51 52 53 54
      {to, cc}
    end
  end

  def to_for_user_and_mentions(user, mentions, inReplyTo, "unlisted") do
55 56 57 58 59 60 61 62 63 64
    mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)

    to = [user.follower_address | mentioned_users]
    cc = ["https://www.w3.org/ns/activitystreams#Public"]

    if inReplyTo do
      {Enum.uniq([inReplyTo.data["actor"] | to]), cc}
    else
      {to, cc}
    end
65 66 67 68 69 70 71
  end

  def to_for_user_and_mentions(user, mentions, inReplyTo, "private") do
    {to, cc} = to_for_user_and_mentions(user, mentions, inReplyTo, "direct")
    {[user.follower_address | to], cc}
  end

72
  def to_for_user_and_mentions(_user, mentions, inReplyTo, "direct") do
lain's avatar
lain committed
73 74
    mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)

75 76 77 78
    if inReplyTo do
      {Enum.uniq([inReplyTo.data["actor"] | mentioned_users]), []}
    else
      {mentioned_users, []}
lain's avatar
lain committed
79 80 81
    end
  end

82 83 84 85 86 87 88 89
  def make_content_html(
        status,
        mentions,
        attachments,
        tags,
        content_type,
        no_attachment_links \\ false
      ) do
90
    status
91
    |> format_input(mentions, tags, content_type)
eal's avatar
eal committed
92
    |> maybe_add_attachments(attachments, no_attachment_links)
93 94 95
  end

  def make_context(%Activity{data: %{"context" => context}}), do: context
lain's avatar
lain committed
96
  def make_context(_), do: Utils.generate_context_id()
97

98
  def maybe_add_attachments(text, _attachments, _no_links = true), do: text
lain's avatar
lain committed
99

eal's avatar
eal committed
100 101 102
  def maybe_add_attachments(text, attachments, _no_links) do
    add_attachments(text, attachments)
  end
lain's avatar
lain committed
103

lain's avatar
lain committed
104
  def add_attachments(text, attachments) do
lain's avatar
lain committed
105 106
    attachment_text =
      Enum.map(attachments, fn
107 108
        %{"url" => [%{"href" => href} | _]} = attachment ->
          name = attachment["name"] || URI.decode(Path.basename(href))
109
          href = MediaProxy.url(href)
lain's avatar
lain committed
110 111 112 113 114 115
          "<a href=\"#{href}\" class='attachment'>#{shortname(name)}</a>"

        _ ->
          ""
      end)

116
    Enum.join([text | attachment_text], "<br>")
lain's avatar
lain committed
117 118
  end

119 120
  def format_input(text, mentions, tags, format, options \\ [])

Maksim's avatar
Maksim committed
121 122 123
  @doc """
  Formatting text to plain text.
  """
124
  def format_input(text, mentions, tags, "text/plain", options) do
eal's avatar
eal committed
125
    text
126
    |> Formatter.html_escape("text/plain")
127
    |> String.replace(~r/\r?\n/, "<br>")
lain's avatar
lain committed
128 129
    |> (&{[], &1}).()
    |> Formatter.add_links()
130
    |> Formatter.add_user_links(mentions, options[:user_links] || [])
lain's avatar
lain committed
131
    |> Formatter.add_hashtag_links(tags)
lain's avatar
lain committed
132
    |> Formatter.finalize()
lain's avatar
lain committed
133 134
  end

Maksim's avatar
Maksim committed
135 136 137
  @doc """
  Formatting text to html.
  """
138
  def format_input(text, mentions, _tags, "text/html", options) do
139 140 141
    text
    |> Formatter.html_escape("text/html")
    |> (&{[], &1}).()
142
    |> Formatter.add_user_links(mentions, options[:user_links] || [])
143 144 145
    |> Formatter.finalize()
  end

Maksim's avatar
Maksim committed
146 147 148
  @doc """
  Formatting text to markdown.
  """
149
  def format_input(text, mentions, tags, "text/markdown", options) do
150
    text
Maksim's avatar
Maksim committed
151
    |> Formatter.mentions_escape(mentions)
152 153 154
    |> Earmark.as_html!()
    |> Formatter.html_escape("text/html")
    |> (&{[], &1}).()
155
    |> Formatter.add_user_links(mentions, options[:user_links] || [])
156
    |> Formatter.add_hashtag_links(tags)
157 158 159
    |> Formatter.finalize()
  end

lain's avatar
lain committed
160
  def add_tag_links(text, tags) do
lain's avatar
lain committed
161 162 163
    tags =
      tags
      |> Enum.sort_by(fn {tag, _} -> -String.length(tag) end)
lain's avatar
lain committed
164

lain's avatar
lain committed
165
    Enum.reduce(tags, text, fn {full, tag}, text ->
Maksim's avatar
Maksim committed
166
      url = "<a href='#{Web.base_url()}/tag/#{tag}' rel='tag'>##{tag}</a>"
lain's avatar
lain committed
167 168
      String.replace(text, full, url)
    end)
lain's avatar
lain committed
169 170
  end

lain's avatar
lain committed
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
  def make_note_data(
        actor,
        to,
        context,
        content_html,
        attachments,
        inReplyTo,
        tags,
        cw \\ nil,
        cc \\ []
      ) do
    object = %{
      "type" => "Note",
      "to" => to,
      "cc" => cc,
      "content" => content_html,
      "summary" => cw,
      "context" => context,
      "attachment" => attachments,
      "actor" => actor,
191
      "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
lain's avatar
lain committed
192
    }
lain's avatar
lain committed
193 194 195 196 197 198 199 200 201

    if inReplyTo do
      object
      |> Map.put("inReplyTo", inReplyTo.data["object"]["id"])
      |> Map.put("inReplyToStatusId", inReplyTo.id)
    else
      object
    end
  end
202 203 204 205 206 207 208 209 210 211

  def format_naive_asctime(date) do
    date |> DateTime.from_naive!("Etc/UTC") |> format_asctime
  end

  def format_asctime(date) do
    Strftime.strftime!(date, "%a %b %d %H:%M:%S %z %Y")
  end

  def date_to_asctime(date) do
lain's avatar
lain committed
212
    with {:ok, date, _offset} <- date |> DateTime.from_iso8601() do
213
      format_asctime(date)
lain's avatar
lain committed
214 215
    else
      _e ->
216 217 218
        ""
    end
  end
219

220 221
  def to_masto_date(%NaiveDateTime{} = date) do
    date
lain's avatar
lain committed
222
    |> NaiveDateTime.to_iso8601()
223 224 225 226 227 228
    |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
  end

  def to_masto_date(date) do
    try do
      date
lain's avatar
lain committed
229 230
      |> NaiveDateTime.from_iso8601!()
      |> NaiveDateTime.to_iso8601()
231 232 233 234 235 236
      |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
    rescue
      _e -> ""
    end
  end

237 238 239 240 241 242 243
  defp shortname(name) do
    if String.length(name) < 30 do
      name
    else
      String.slice(name, 0..30) <> "…"
    end
  end
244

245
  def confirm_current_password(user, password) do
246
    with %User{local: true} = db_user <- Repo.get(User, user.id),
247
         true <- Pbkdf2.checkpw(password, db_user.password_hash) do
248 249 250
      {:ok, db_user}
    else
      _ -> {:error, "Invalid password."}
251 252
    end
  end
253

Maksim's avatar
Maksim committed
254
  def emoji_from_profile(%{info: _info} = user) do
255 256 257 258
    (Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name))
    |> Enum.map(fn {shortcode, url} ->
      %{
        "type" => "Emoji",
eal's avatar
eal committed
259
        "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"},
260 261 262 263
        "name" => ":#{shortcode}:"
      }
    end)
  end
lain's avatar
lain committed
264
end