add custom emoji reaction support
authorFloatingGhost <hannah@coffee-and-dreams.uk>
Wed, 8 Jun 2022 01:42:44 +0000 (02:42 +0100)
committerFloatingGhost <hannah@coffee-and-dreams.uk>
Wed, 8 Jun 2022 01:42:44 +0000 (02:42 +0100)
lib/pleroma/emoji.ex
lib/pleroma/web/activity_pub/object_validator.ex
lib/pleroma/web/activity_pub/object_validators/common_fields.ex
lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
lib/pleroma/web/activity_pub/pipeline.ex
lib/pleroma/web/activity_pub/side_effects.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/mastodon_api/views/status_view.ex
lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex
lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex

index f077fe5b44ec4567cbb9014d8eb788c1c9d5f6b0..12485011407ef257cd07b1ea89ed22edf2138b12 100644 (file)
@@ -115,7 +115,7 @@ defmodule Pleroma.Emoji do
     |> String.split("\n")
     |> Enum.filter(fn line ->
       line != "" and not String.starts_with?(line, "#") and
-        String.contains?(line, "fully-qualified")
+        String.contains?(line, "qualified")
     end)
     |> Enum.map(fn line ->
       line
index 187cd0cfd6010145ef6c1f2ed4fedaf0082d871e..867c945b0a4b1f3980fb561afc9c02cd9e0378a8 100644 (file)
@@ -144,6 +144,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   def validate(%{"type" => type} = object, meta)
       when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
       ChatMessage Answer] do
+    IO.inspect(object)
     validator =
       case type do
         "Accept" -> AcceptRejectValidator
index 872f80ec3e429d7c42179d23c9df9f660fe9b1c5..011cf66ca709dab2c69adcc72c1c5d06cfb7063b 100644 (file)
@@ -65,4 +65,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
       field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
     end
   end
+
+  defmacro tag_fields do
+    quote bind_quoted: binding() do
+      embeds_many(:tag, TagValidator)
+    end
+  end
 end
index 9eaaf831929fe75b49ecdb4bf23c6ae2e3085698..45c28beedbe040a8dbf68c0d8f53ae60c95c39d3 100644 (file)
@@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
   import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
 
   @primary_key false
+  @emoji_regex ~r/:[A-Za-z_-]+:/
 
   embedded_schema do
     quote do
@@ -19,6 +20,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
         import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
         message_fields()
         activity_fields()
+        tag_fields()
       end
     end
 
@@ -43,7 +45,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
 
   def changeset(struct, data) do
     struct
-    |> cast(data, __schema__(:fields))
+    |> cast(data, __schema__(:fields) -- [:tag])
+    |> cast_embed(:tag)
   end
 
   defp fix(data) do
@@ -52,6 +55,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
       |> CommonFixes.fix_actor()
       |> CommonFixes.fix_activity_addressing()
 
+    data = if Map.has_key?(data, "tag") do
+        data
+    else
+        Map.put(data, "tag", [])
+    end
+
     with %Object{} = object <- Object.normalize(data["object"]) do
       data
       |> CommonFixes.fix_activity_context(object)
@@ -63,12 +72,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
 
   defp validate_emoji(cng) do
     content = get_field(cng, :content)
-
-    if Pleroma.Emoji.is_unicode_emoji?(content) do
+    IO.inspect(Pleroma.Emoji.is_unicode_emoji?(content))
+    if Pleroma.Emoji.is_unicode_emoji?(content) || Regex.match?(@emoji_regex, content) do
       cng
     else
       cng
-      |> add_error(:content, "must be a single character emoji")
+      |> add_error(:content, "is not a valid emoji")
     end
   end
 
index ed61d850c6c8077d8c7d86d1c04c973b9e321784..214647dbffe8326be6d8f286bde86e0750ed3534 100644 (file)
@@ -77,7 +77,8 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
         {:ok, :not_federated}
       end
     else
-      _e -> {:error, :badarg}
+      _e ->
+        {:error, :badarg}
     end
   end
 end
index 9b17eba953382a2d5d9c215900b4fab8d07240b7..9c2f89e72e290752360de257cf61def2040f869c 100644 (file)
@@ -269,7 +269,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
   def handle(%{data: %{"type" => "EmojiReact"}} = object, meta) do
     reacted_object = Object.get_by_ap_id(object.data["object"])
     Utils.add_emoji_reaction_to_object(object, reacted_object)
-
     Notification.create_notifications(object)
 
     {:ok, object, meta}
index 142af1a13bb9ca0e6e212332ee15323979c282b5..25aaba1a6a947cb6aa4a9e4cbd39b1fcccc24d46 100644 (file)
@@ -415,31 +415,31 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
-  @misskey_reactions %{
-    "like" => "👍",
-    "love" => "❤️",
-    "laugh" => "😆",
-    "hmm" => "🤔",
-    "surprise" => "😮",
-    "congrats" => "🎉",
-    "angry" => "💢",
-    "confused" => "😥",
-    "rip" => "😇",
-    "pudding" => "🍮",
-    "star" => "⭐"
-  }
-
   @doc "Rewrite misskey likes into EmojiReacts"
   def handle_incoming(
         %{
           "type" => "Like",
-          "_misskey_reaction" => reaction
+          "_misskey_reaction" => reaction,
+          "tag" => _
         } = data,
         options
       ) do
     data
     |> Map.put("type", "EmojiReact")
-    |> Map.put("content", @misskey_reactions[reaction] || reaction)
+    |> Map.put("content", reaction)
+    |> handle_incoming(options)
+  end
+
+  def handle_incoming(
+        %{
+          "type" => "Like",
+          "_misskey_reaction" => reaction,
+        } = data,
+        options
+      ) do
+    data
+    |> Map.put("type", "EmojiReact")
+    |> Map.put("content", reaction)
     |> handle_incoming(options)
   end
 
@@ -472,11 +472,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   def handle_incoming(%{"type" => type} = data, _options)
       when type in ~w{Like EmojiReact Announce Add Remove} do
     with :ok <- ObjectValidator.fetch_actor_and_object(data),
-         {:ok, activity, _meta} <-
-           Pipeline.common_pipeline(data, local: false) do
+         {:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
       {:ok, activity}
     else
-      e -> {:error, e}
+      e ->
+        {:error, e}
     end
   end
 
index c1f6b2b49838d6bbcf6c07f0002f496e553ce397..9c68d800f4a4ff5f99fdd286108a45ccacb737fb 100644 (file)
@@ -344,21 +344,22 @@ defmodule Pleroma.Web.ActivityPub.Utils do
           {:ok, Object.t()} | {:error, Ecto.Changeset.t()}
 
   def add_emoji_reaction_to_object(
-        %Activity{data: %{"content" => emoji, "actor" => actor}},
+        %Activity{data: %{"content" => emoji, "actor" => actor}} = activity,
         object
       ) do
+    IO.inspect(emoji)
     reactions = get_cached_emoji_reactions(object)
-
+    emoji = stripped_emoji_name(emoji)
     new_reactions =
-      case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do
+      case Enum.find_index(reactions, fn [candidate, _, _] -> emoji == candidate end) do
         nil ->
-          reactions ++ [[emoji, [actor]]]
+          reactions ++ [[emoji, [actor], emoji_url(emoji, activity)]]
 
         index ->
           List.update_at(
             reactions,
             index,
-            fn [emoji, users] -> [emoji, Enum.uniq([actor | users])] end
+            fn [emoji, users, url] -> [emoji, Enum.uniq([actor | users]), url] end
           )
       end
 
@@ -367,8 +368,22 @@ defmodule Pleroma.Web.ActivityPub.Utils do
     update_element_in_object("reaction", new_reactions, object, count)
   end
 
+  defp stripped_emoji_name(name) do
+    name
+    |> String.replace_leading(":", "")
+    |> String.replace_trailing(":", "")
+  end
+
+  defp emoji_url(name,
+    %Activity{
+        data: %{"tag" => [
+            %{"type" => "Emoji", "name" => name, "icon" => %{"url" => url}}
+        ]}
+    }), do: url
+  defp emoji_url(_, _), do: nil
+
   def emoji_count(reactions_list) do
-    Enum.reduce(reactions_list, 0, fn [_, users], acc -> acc + length(users) end)
+    Enum.reduce(reactions_list, 0, fn [_, users, _], acc -> acc + length(users) end)
   end
 
   def remove_emoji_reaction_from_object(
@@ -376,9 +391,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
         object
       ) do
     reactions = get_cached_emoji_reactions(object)
-
     new_reactions =
-      case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do
+      case Enum.find_index(reactions, fn [candidate, _, _] -> emoji == candidate end) do
         nil ->
           reactions
 
@@ -386,9 +400,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do
           List.update_at(
             reactions,
             index,
-            fn [emoji, users] -> [emoji, List.delete(users, actor)] end
+            fn [emoji, users, url] -> [emoji, List.delete(users, actor), url] end
           )
-          |> Enum.reject(fn [_, users] -> Enum.empty?(users) end)
+          |> Enum.reject(fn [_, users, _] -> Enum.empty?(users) end)
       end
 
     count = emoji_count(new_reactions)
index 463f3419855f75d0c4960f09b43d9b3900c87abb..e4057f108850c27bbaa6dd9378685c5250c0f238 100644 (file)
@@ -304,7 +304,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         _e ->
           nil
       end
-
     emoji_reactions =
       object.data
       |> Map.get("reactions", [])
@@ -312,8 +311,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         opts[:for],
         Map.get(opts, :with_muted, false)
       )
-      |> Stream.map(fn {emoji, users} ->
-        build_emoji_map(emoji, users, opts[:for])
+      |> Stream.map(fn {emoji, users, url} ->
+        build_emoji_map(emoji, users, url, opts[:for])
       end)
       |> Enum.to_list()
 
@@ -569,10 +568,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
     end
   end
 
-  defp build_emoji_map(emoji, users, current_user) do
+  defp build_emoji_map(emoji, users, url, current_user) do
     %{
       name: emoji,
       count: length(users),
+      url: url,
       me: !!(current_user && current_user.ap_id in users)
     }
   end
index da5f2474f34ebecbb3a967cac45fb1392f05dd00..d89b1279468b5cf6c333237db9864b989200a2ac 100644 (file)
@@ -50,17 +50,17 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
           if not with_muted, do: User.cached_muted_users_ap_ids(user), else: []
       end
 
-    filter_emoji = fn emoji, users ->
+    filter_emoji = fn emoji, users, url ->
       case Enum.reject(users, &(&1 in exclude_ap_ids)) do
         [] -> nil
-        users -> {emoji, users}
+        users -> {emoji, users, url}
       end
     end
 
     reactions
     |> Stream.map(fn
-      [emoji, users] when is_list(users) -> filter_emoji.(emoji, users)
-      {emoji, users} when is_list(users) -> filter_emoji.(emoji, users)
+      [emoji, users, url] when is_list(users) -> filter_emoji.(emoji, users, url)
+      {emoji, users, url} when is_list(users) -> filter_emoji.(emoji, users, url)
       _ -> nil
     end)
     |> Stream.reject(&is_nil/1)
index c94527e6d284929e0879483cdd3fa0409250e1fb..3575f50da48770ba4994b6aac6087f84f903a54f 100644 (file)
@@ -11,13 +11,13 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionView do
     render_many(emoji_reactions, __MODULE__, "show.json", opts)
   end
 
-  def render("show.json", %{emoji_reaction: {emoji, user_ap_ids}, user: user}) do
+  def render("show.json", %{emoji_reaction: {emoji, user_ap_ids, url}, user: user}) do
     users = fetch_users(user_ap_ids)
-
     %{
       name: emoji,
       count: length(users),
       accounts: render(AccountView, "index.json", users: users, for: user),
+      url: url,
       me: !!(user && user.ap_id in user_ap_ids)
     }
   end