utils.ex 6.4 KB
Newer Older
1
defmodule Pleroma.Web.CommonAPI.Utils do
feld's avatar
feld committed
2
  alias Pleroma.{Repo, Object, Formatter, Activity}
lain's avatar
lain committed
3
  alias Pleroma.Web.ActivityPub.Utils
4
  alias Pleroma.Web.Endpoint
5
  alias Pleroma.Web.MediaProxy
6
  alias Pleroma.User
dtluna's avatar
dtluna committed
7
  alias Calendar.Strftime
8
  alias Comeonin.Pbkdf2
lain's avatar
lain committed
9

10
11
12
  # 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
13

14
15
16
17
18
19
    activity &&
      if activity.data["type"] == "Create" do
        activity
      else
        Activity.get_create_activity_by_object_ap_id(activity.data["object"])
      end
20
21
  end

lain's avatar
lain committed
22
23
  def get_replied_to_activity(""), do: nil

24
25
26
  def get_replied_to_activity(id) when not is_nil(id) do
    Repo.get(Activity, id)
  end
lain's avatar
lain committed
27

28
29
  def get_replied_to_activity(_), do: nil

lain's avatar
lain committed
30
  def attachments_from_ids(ids) do
lain's avatar
lain committed
31
    Enum.map(ids || [], fn media_id ->
lain's avatar
lain committed
32
33
34
35
      Repo.get(Object, media_id).data
    end)
  end

36
  def to_for_user_and_mentions(user, mentions, inReplyTo, "public") do
lain's avatar
lain committed
37
    mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)
38
39
40

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

42
    if inReplyTo do
43
      {Enum.uniq([inReplyTo.data["actor"] | to]), cc}
lain's avatar
lain committed
44
    else
45
46
47
48
49
      {to, cc}
    end
  end

  def to_for_user_and_mentions(user, mentions, inReplyTo, "unlisted") do
50
51
52
53
54
55
56
57
58
59
    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
60
61
62
63
64
65
66
  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

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

70
71
72
73
    if inReplyTo do
      {Enum.uniq([inReplyTo.data["actor"] | mentioned_users]), []}
    else
      {mentioned_users, []}
lain's avatar
lain committed
74
75
76
    end
  end

77
78
79
80
81
82
83
84
  def make_content_html(
        status,
        mentions,
        attachments,
        tags,
        content_type,
        no_attachment_links \\ false
      ) do
85
    status
86
    |> format_input(mentions, tags, content_type)
eal's avatar
eal committed
87
    |> maybe_add_attachments(attachments, no_attachment_links)
88
89
90
  end

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

feld's avatar
feld committed
93
  def maybe_add_attachments(text, _attachments, _no_links = true), do: text
lain's avatar
lain committed
94

eal's avatar
eal committed
95
96
97
  def maybe_add_attachments(text, attachments, _no_links) do
    add_attachments(text, attachments)
  end
lain's avatar
lain committed
98

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

        _ ->
          ""
      end)

eal's avatar
eal committed
111
    Enum.join([text | attachment_text], "<br>")
lain's avatar
lain committed
112
113
  end

114
  def format_input(text, mentions, tags, "text/plain") do
eal's avatar
eal committed
115
    text
116
    |> Formatter.html_escape("text/plain")
117
    |> String.replace(~r/\r?\n/, "<br>")
lain's avatar
lain committed
118
119
    |> (&{[], &1}).()
    |> Formatter.add_links()
lain's avatar
lain committed
120
121
    |> Formatter.add_user_links(mentions)
    |> Formatter.add_hashtag_links(tags)
lain's avatar
lain committed
122
    |> Formatter.finalize()
lain's avatar
lain committed
123
124
  end

Maksim's avatar
Maksim committed
125
  def format_input(text, mentions, _tags, "text/html") do
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
    text
    |> Formatter.html_escape("text/html")
    |> String.replace(~r/\r?\n/, "<br>")
    |> (&{[], &1}).()
    |> Formatter.add_user_links(mentions)
    |> Formatter.finalize()
  end

  def format_input(text, mentions, tags, "text/markdown") do
    text
    |> Earmark.as_html!()
    |> Formatter.html_escape("text/html")
    |> String.replace(~r/\r?\n/, "")
    |> (&{[], &1}).()
    |> Formatter.add_user_links(mentions)
141
    |> Formatter.add_hashtag_links(tags)
142
143
144
    |> Formatter.finalize()
  end

lain's avatar
lain committed
145
  def add_tag_links(text, tags) do
lain's avatar
lain committed
146
147
148
    tags =
      tags
      |> Enum.sort_by(fn {tag, _} -> -String.length(tag) end)
lain's avatar
lain committed
149

lain's avatar
lain committed
150
    Enum.reduce(tags, text, fn {full, tag}, text ->
kaniini's avatar
kaniini committed
151
      url = "<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>##{tag}</a>"
lain's avatar
lain committed
152
153
      String.replace(text, full, url)
    end)
lain's avatar
lain committed
154
155
  end

lain's avatar
lain committed
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
  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,
176
      "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
lain's avatar
lain committed
177
    }
lain's avatar
lain committed
178
179
180
181
182
183
184
185
186

    if inReplyTo do
      object
      |> Map.put("inReplyTo", inReplyTo.data["object"]["id"])
      |> Map.put("inReplyToStatusId", inReplyTo.id)
    else
      object
    end
  end
dtluna's avatar
dtluna committed
187
188
189
190
191
192
193
194
195
196

  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
197
    with {:ok, date, _offset} <- date |> DateTime.from_iso8601() do
dtluna's avatar
dtluna committed
198
      format_asctime(date)
lain's avatar
lain committed
199
200
    else
      _e ->
dtluna's avatar
dtluna committed
201
202
203
        ""
    end
  end
204

205
206
  def to_masto_date(%NaiveDateTime{} = date) do
    date
lain's avatar
lain committed
207
    |> NaiveDateTime.to_iso8601()
208
209
210
211
212
213
    |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
  end

  def to_masto_date(date) do
    try do
      date
lain's avatar
lain committed
214
215
      |> NaiveDateTime.from_iso8601!()
      |> NaiveDateTime.to_iso8601()
216
217
218
219
220
221
      |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
    rescue
      _e -> ""
    end
  end

222
223
224
225
226
227
228
  defp shortname(name) do
    if String.length(name) < 30 do
      name
    else
      String.slice(name, 0..30) <> "…"
    end
  end
229

230
  def confirm_current_password(user, password) do
231
    with %User{local: true} = db_user <- Repo.get(User, user.id),
232
         true <- Pbkdf2.checkpw(password, db_user.password_hash) do
233
234
235
      {:ok, db_user}
    else
      _ -> {:error, "Invalid password."}
236
237
    end
  end
238

Maksim's avatar
Maksim committed
239
  def emoji_from_profile(%{info: _info} = user) do
240
241
242
243
    (Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name))
    |> Enum.map(fn {shortcode, url} ->
      %{
        "type" => "Emoji",
eal's avatar
eal committed
244
        "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"},
245
246
247
248
        "name" => ":#{shortcode}:"
      }
    end)
  end
lain's avatar
lain committed
249
end