Use actual ISO8601 timestamps for masto API (#425)
[akkoma] / lib / pleroma / web / common_api / utils.ex
index 4ba31a8b86900534e479e426bcec78b7331ccd02..345c5d10d2a15ecb9b59ffb577b900c5a13c20cd 100644 (file)
@@ -17,8 +17,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.CommonAPI.ActivityDraft
   alias Pleroma.Web.MediaProxy
-  alias Pleroma.Web.Params
-  alias Pleroma.Web.Plugs.AuthenticationPlug
+  alias Pleroma.Web.Utils.Params
 
   require Logger
   require Pleroma.Constants
@@ -37,7 +36,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
 
   def attachments_from_ids_no_descs(ids) do
     Enum.map(ids, fn media_id ->
-      case Repo.get(Object, media_id) do
+      case get_attachment(media_id) do
         %Object{data: data} -> data
         _ -> nil
       end
@@ -51,13 +50,17 @@ defmodule Pleroma.Web.CommonAPI.Utils do
     {_, descs} = Jason.decode(descs_str)
 
     Enum.map(ids, fn media_id ->
-      with %Object{data: data} <- Repo.get(Object, media_id) do
+      with %Object{data: data} <- get_attachment(media_id) do
         Map.put(data, "name", descs[media_id])
       end
     end)
     |> Enum.reject(&is_nil/1)
   end
 
+  defp get_attachment(media_id) do
+    Repo.get(Object, media_id)
+  end
+
   @spec get_to_and_cc(ActivityDraft.t()) :: {list(String.t()), list(String.t())}
 
   def get_to_and_cc(%{in_reply_to_conversation: %Participation{} = participation}) do
@@ -219,7 +222,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
     |> maybe_add_attachments(draft.attachments, attachment_links)
   end
 
-  defp get_content_type(content_type) do
+  def get_content_type(content_type) do
     if Enum.member?(Config.get([:instance, :allowed_post_formats]), content_type) do
       content_type
     else
@@ -227,12 +230,13 @@ defmodule Pleroma.Web.CommonAPI.Utils do
     end
   end
 
-  def make_context(_, %Participation{} = participation) do
+  def make_context(%{in_reply_to_conversation: %Participation{} = participation}) do
     Repo.preload(participation, :conversation).conversation.ap_id
   end
 
-  def make_context(%Activity{data: %{"context" => context}}, _), do: context
-  def make_context(_, _), do: Utils.generate_context_id()
+  def make_context(%{in_reply_to: %Activity{data: %{"context" => context}}}), do: context
+  def make_context(%{quote: %Activity{data: %{"context" => context}}}), do: context
+  def make_context(_), do: Utils.generate_context_id()
 
   def maybe_add_attachments(parsed, _attachments, false = _no_links), do: parsed
 
@@ -283,39 +287,21 @@ defmodule Pleroma.Web.CommonAPI.Utils do
     |> Formatter.linkify(options)
   end
 
-  def format_input(text, "text/markdown", options) do
+  def format_input(text, "text/x.misskeymarkdown", options) do
     text
-    |> Formatter.mentions_escape(options)
     |> Formatter.markdown_to_html()
+    |> MfmParser.Parser.parse()
+    |> MfmParser.Encoder.to_html()
     |> Formatter.linkify(options)
     |> Formatter.html_escape("text/html")
   end
 
-  def make_note_data(%ActivityDraft{} = draft) do
-    %{
-      "type" => "Note",
-      "to" => draft.to,
-      "cc" => draft.cc,
-      "content" => draft.content_html,
-      "summary" => draft.summary,
-      "sensitive" => draft.sensitive,
-      "context" => draft.context,
-      "attachment" => draft.attachments,
-      "actor" => draft.user.ap_id,
-      "tag" => Keyword.values(draft.tags) |> Enum.uniq()
-    }
-    |> add_in_reply_to(draft.in_reply_to)
-    |> Map.merge(draft.extra)
-  end
-
-  defp add_in_reply_to(object, nil), do: object
-
-  defp add_in_reply_to(object, in_reply_to) do
-    with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
-      Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
-    else
-      _ -> object
-    end
+  def format_input(text, "text/markdown", options) do
+    text
+    |> Formatter.mentions_escape(options)
+    |> Formatter.markdown_to_html()
+    |> Formatter.linkify(options)
+    |> Formatter.html_escape("text/html")
   end
 
   def format_naive_asctime(date) do
@@ -342,20 +328,27 @@ defmodule Pleroma.Web.CommonAPI.Utils do
   end
 
   def to_masto_date(%NaiveDateTime{} = date) do
-    date
-    |> NaiveDateTime.to_iso8601()
-    |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
+    # NOTE: Elixir’s ISO 8601 format is a superset of the real standard
+    # It supports negative years for example.
+    # ISO8601 only supports years before 1583 with mutual agreement
+    if date.year < 1583 do
+      "1970-01-01T00:00:00Z"
+    else
+      date
+      |> NaiveDateTime.to_iso8601()
+      |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
+    end
   end
 
   def to_masto_date(date) when is_binary(date) do
     with {:ok, date} <- NaiveDateTime.from_iso8601(date) do
       to_masto_date(date)
     else
-      _ -> ""
+      _ -> "1970-01-01T00:00:00Z"
     end
   end
 
-  def to_masto_date(_), do: ""
+  def to_masto_date(_), do: "1970-01-01T00:00:00Z"
 
   defp shortname(name) do
     with max_length when max_length > 0 <-
@@ -370,7 +363,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
   @spec confirm_current_password(User.t(), String.t()) :: {:ok, User.t()} | {:error, String.t()}
   def confirm_current_password(user, password) do
     with %User{local: true} = db_user <- User.get_cached_by_id(user.id),
-         true <- AuthenticationPlug.checkpw(password, db_user.password_hash) do
+         true <- Pleroma.Password.checkpw(password, db_user.password_hash) do
       {:ok, db_user}
     else
       _ -> {:error, dgettext("errors", "Invalid password.")}
@@ -412,19 +405,14 @@ defmodule Pleroma.Web.CommonAPI.Utils do
 
   def maybe_notify_mentioned_recipients(recipients, _), do: recipients
 
-  # Do not notify subscribers if author is making a reply
-  def maybe_notify_subscribers(recipients, %Activity{
-        object: %Object{data: %{"inReplyTo" => _ap_id}}
-      }) do
-    recipients
-  end
-
   def maybe_notify_subscribers(
         recipients,
-        %Activity{data: %{"actor" => actor, "type" => type}} = activity
-      )
-      when type == "Create" do
-    with %User{} = user <- User.get_cached_by_ap_id(actor) do
+        %Activity{data: %{"actor" => actor, "type" => "Create"}} = activity
+      ) do
+    # Do not notify subscribers if author is making a reply
+    with %Object{data: object} <- Object.normalize(activity, fetch: false),
+         nil <- object["inReplyTo"],
+         %User{} = user <- User.get_cached_by_ap_id(actor) do
       subscriber_ids =
         user
         |> User.subscriber_users()
@@ -481,35 +469,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
 
   def get_report_statuses(_, _), do: {:ok, nil}
 
-  # DEPRECATED mostly, context objects are now created at insertion time.
-  def context_to_conversation_id(context) do
-    with %Object{id: id} <- Object.get_cached_by_ap_id(context) do
-      id
-    else
-      _e ->
-        changeset = Object.context_mapping(context)
-
-        case Repo.insert(changeset) do
-          {:ok, %{id: id}} ->
-            id
-
-          # This should be solved by an upsert, but it seems ecto
-          # has problems accessing the constraint inside the jsonb.
-          {:error, _} ->
-            Object.get_cached_by_ap_id(context).id
-        end
-    end
-  end
-
-  def conversation_id_to_context(id) do
-    with %Object{data: %{"id" => context}} <- Repo.get(Object, id) do
-      context
-    else
-      _e ->
-        {:error, dgettext("errors", "No such conversation")}
-    end
-  end
-
   def validate_character_limit("" = _full_payload, [] = _attachments) do
     {:error, dgettext("errors", "Cannot post an empty status without attachments")}
   end