From ad0b8c095c43a37b9f1b9e57fc9e64462907d983 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 8 Jun 2022 02:42:44 +0100 Subject: [PATCH] add custom emoji reaction support --- lib/pleroma/emoji.ex | 2 +- .../web/activity_pub/object_validator.ex | 1 + .../object_validators/common_fields.ex | 6 +++ .../emoji_react_validator.ex | 17 +++++++-- lib/pleroma/web/activity_pub/pipeline.ex | 3 +- lib/pleroma/web/activity_pub/side_effects.ex | 1 - .../web/activity_pub/transmogrifier.ex | 38 +++++++++---------- lib/pleroma/web/activity_pub/utils.ex | 34 ++++++++++++----- .../web/mastodon_api/views/status_view.ex | 8 ++-- .../controllers/emoji_reaction_controller.ex | 8 ++-- .../pleroma_api/views/emoji_reaction_view.ex | 4 +- 11 files changed, 76 insertions(+), 46 deletions(-) diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index f077fe5b4..124850114 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -115,7 +115,7 @@ defp update_emojis(emojis) 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 diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 187cd0cfd..867c945b0 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -144,6 +144,7 @@ def validate(%{"type" => type} = object, meta) 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 diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex index 872f80ec3..011cf66ca 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex @@ -65,4 +65,10 @@ defmacro status_object_fields 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 diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex index 9eaaf8319..45c28beed 100644 --- a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex @@ -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 @@ def cast_data(data) 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 @@ defp fix(data) 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 @@ defp fix(data) 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 diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex index ed61d850c..214647dbf 100644 --- a/lib/pleroma/web/activity_pub/pipeline.ex +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -77,7 +77,8 @@ defp maybe_federate(%Activity{} = activity, meta) do {:ok, :not_federated} end else - _e -> {:error, :badarg} + _e -> + {:error, :badarg} end end end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 9b17eba95..9c2f89e72 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -269,7 +269,6 @@ def handle(%{data: %{"type" => "Undo", "object" => undone_object}} = object, met 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} diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 142af1a13..25aaba1a6 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -415,31 +415,31 @@ def handle_incoming( 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 @@ def handle_incoming( 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 diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index c1f6b2b49..9c68d800f 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -344,21 +344,22 @@ def update_element_in_object(property, element, object, count \\ nil) 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 @@ def add_emoji_reaction_to_object( 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 @@ def remove_emoji_reaction_from_object( 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 @@ def remove_emoji_reaction_from_object( 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) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 463f34198..e4057f108 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -304,7 +304,6 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} _e -> nil end - emoji_reactions = object.data |> Map.get("reactions", []) @@ -312,8 +311,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} 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 @@ defp pin_data(%Object{data: %{"id" => object_id}}, %User{pinned_objects: pinned_ 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 diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex index da5f2474f..d89b12794 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex @@ -50,17 +50,17 @@ def filter_allowed_users(reactions, user, with_muted) 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) diff --git a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex index c94527e6d..3575f50da 100644 --- a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex +++ b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex @@ -11,13 +11,13 @@ def render("index.json", %{emoji_reactions: emoji_reactions} = opts) 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