commit: 2c2ea16b50a7b77b1d3f58722e45720dcf3c51b9
parent 8d3b29aaba1a1879c9495ae15178b6e16ffc5faf
Author: Alexander Tumin <iamtakingiteasy@eientei.org>
Date: Thu, 2 Mar 2023 10:09:13 +0300
Allow custom emoji reactions: Add pleroma_custom_emoji_reactions feature, review changes
Diffstat:
8 files changed, 83 insertions(+), 60 deletions(-)
diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex
@@ -51,14 +51,7 @@ defmodule Pleroma.Emoji do
@doc "Returns the path of the emoji `name`."
@spec get(String.t()) :: String.t() | nil
def get(name) do
- name =
- if String.starts_with?(name, ":") do
- name
- |> String.replace_leading(":", "")
- |> String.replace_trailing(":", "")
- else
- name
- end
+ name = maybe_strip_name(name)
case :ets.lookup(@ets, name) do
[{_, path}] -> path
@@ -148,13 +141,15 @@ defmodule Pleroma.Emoji do
def is_unicode_emoji?(_), do: false
- def stripped_name(name) when is_binary(name) do
- name
- |> String.replace_leading(":", "")
- |> String.replace_trailing(":", "")
- end
+ @emoji_regex ~r/:[A-Za-z0-9_-]+(@.+)?:/
- def stripped_name(name), do: name
+ def is_custom_emoji?(s) when is_binary(s), do: Regex.match?(@emoji_regex, s)
+
+ def is_custom_emoji?(_), do: false
+
+ def maybe_strip_name(name) when is_binary(name), do: String.trim(name, ":")
+
+ def maybe_strip_name(name), do: name
def maybe_quote(name) when is_binary(name) do
if is_unicode_emoji?(name) do
@@ -173,9 +168,13 @@ defmodule Pleroma.Emoji do
def emoji_url(%{"type" => "EmojiReact", "content" => _, "tag" => []}), do: nil
def emoji_url(%{"type" => "EmojiReact", "content" => emoji, "tag" => tags}) do
+ emoji = maybe_strip_name(emoji)
+
tag =
tags
- |> Enum.find(fn tag -> tag["type"] == "Emoji" && tag["name"] == stripped_name(emoji) end)
+ |> Enum.find(fn tag ->
+ tag["type"] == "Emoji" && !is_nil(tag["name"]) && tag["name"] == emoji
+ end)
if is_nil(tag) do
nil
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
@@ -62,21 +62,22 @@ defmodule Pleroma.Web.ActivityPub.Builder do
end
defp add_emoji_content(data, emoji, url) do
+ tag = [
+ %{
+ "id" => url,
+ "type" => "Emoji",
+ "name" => Emoji.maybe_quote(emoji),
+ "icon" => %{
+ "type" => "Image",
+ "url" => url
+ }
+ }
+ ]
+
data
|> Map.put("content", Emoji.maybe_quote(emoji))
|> Map.put("type", "EmojiReact")
- |> Map.put("tag", [
- %{}
- |> Map.put("id", url)
- |> Map.put("type", "Emoji")
- |> Map.put("name", Emoji.maybe_quote(emoji))
- |> Map.put(
- "icon",
- %{}
- |> Map.put("type", "Image")
- |> Map.put("url", url)
- )
- ])
+ |> Map.put("tag", tag)
end
defp remote_custom_emoji_react(
@@ -84,7 +85,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
data,
emoji
) do
- [emoji_code, instance] = String.split(Emoji.stripped_name(emoji), "@")
+ [emoji_code, instance] = String.split(Emoji.maybe_strip_name(emoji), "@")
matching_reaction =
Enum.find(
@@ -110,8 +111,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
end
defp local_custom_emoji_react(data, emoji) do
- with %{} = emojo <- Emoji.get(emoji) do
- path = emojo |> Map.get(:file)
+ with %{file: path} = emojo <- Emoji.get(emoji) do
url = "#{Endpoint.url()}#{path}"
add_emoji_content(data, emojo.code, url)
else
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex
@@ -58,17 +58,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
- field(:quoteUri, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
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
@@ -8,12 +8,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
alias Pleroma.Emoji
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+ alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@primary_key false
- @emoji_regex ~r/:[A-Za-z0-9_-]+(@.+)?:/
embedded_schema do
quote do
@@ -21,7 +21,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
activity_fields()
- tag_fields()
+ embeds_many(:tag, TagValidator)
end
end
@@ -57,12 +57,7 @@ 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
+ data = Map.put_new(data, "tag", [])
case Object.normalize(data["object"]) do
%Object{} = object ->
@@ -92,13 +87,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
defp fix_emoji_qualification(data), do: data
- defp matches_shortcode?(nil), do: false
- defp matches_shortcode?(s), do: Regex.match?(@emoji_regex, s)
-
defp validate_emoji(cng) do
content = get_field(cng, :content)
- if Emoji.is_unicode_emoji?(content) || matches_shortcode?(content) do
+ if Emoji.is_unicode_emoji?(content) || Emoji.is_custom_emoji?(content) do
cng
else
cng
@@ -113,7 +105,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
cng
else
tag = get_field(cng, :tag)
- emoji_name = Emoji.stripped_name(content)
+ emoji_name = Emoji.maybe_strip_name(content)
case tag do
[%{name: ^emoji_name, type: "Emoji", icon: %{url: _}}] ->
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
@@ -329,8 +329,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
object
) do
reactions = get_cached_emoji_reactions(object)
- emoji = Pleroma.Emoji.stripped_name(emoji)
- url = emoji_url(emoji, activity)
+ emoji = Pleroma.Emoji.maybe_strip_name(emoji)
+ url = maybe_emoji_url(emoji, activity)
new_reactions =
case Enum.find_index(reactions, fn [candidate, _, candidate_url] ->
@@ -356,7 +356,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
update_element_in_object("reaction", new_reactions, object, count)
end
- defp emoji_url(
+ defp maybe_emoji_url(
name,
%Activity{
data: %{
@@ -368,7 +368,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
),
do: url
- defp emoji_url(_, _), do: nil
+ defp maybe_emoji_url(_, _), do: nil
def emoji_count(reactions_list) do
Enum.reduce(reactions_list, 0, fn [_, users, _], acc -> acc + length(users) end)
@@ -378,9 +378,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do
%Activity{data: %{"content" => emoji, "actor" => actor}} = activity,
object
) do
- emoji = Pleroma.Emoji.stripped_name(emoji)
+ emoji = Pleroma.Emoji.maybe_strip_name(emoji)
reactions = get_cached_emoji_reactions(object)
- url = emoji_url(emoji, activity)
+ url = maybe_emoji_url(emoji, activity)
new_reactions =
case Enum.find_index(reactions, fn [candidate, _, candidate_url] ->
@@ -533,9 +533,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do
defp custom_emoji_discriminator(query, emoji) do
if String.contains?(emoji, "@") do
- stripped = Pleroma.Emoji.stripped_name(emoji)
+ stripped = Pleroma.Emoji.maybe_strip_name(emoji)
[name, domain] = String.split(stripped, "@")
- domain_pattern = "%" <> domain <> "%"
+ domain_pattern = "%/" <> domain <> "/%"
emoji_pattern = Pleroma.Emoji.maybe_quote(name)
query
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -92,6 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
"safe_dm_mentions"
end,
"pleroma_emoji_reactions",
+ "pleroma_custom_emoji_reactions",
"pleroma_chat_messages",
if Config.get([:instance, :show_reactions]) do
"exposable_reactions"
diff --git a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionView do
alias Pleroma.Web.MastodonAPI.AccountView
def emoji_name(emoji, nil), do: emoji
- alias Pleroma.Web.MediaProxy
def emoji_name(emoji, url) do
url = URI.parse(url)
@@ -31,7 +30,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionView do
name: emoji_name(emoji, url),
count: length(users),
accounts: render(AccountView, "index.json", users: users, for: user),
- url: MediaProxy.url(url),
+ url: Pleroma.Web.MediaProxy.url(url),
me: !!(user && user.ap_id in user_ap_ids)
}
end
diff --git a/test/pleroma/web/mastodon_api/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs
@@ -197,6 +197,45 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
test_notifications_rendering([notification], user, [expected])
end
+ test "EmojiReact custom emoji notification" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ note =
+ insert(:note,
+ user: user,
+ data: %{
+ "reactions" => [
+ ["👍", [user.ap_id], nil],
+ ["dinosaur", [user.ap_id], "http://localhost:4001/emoji/dino walking.gif"]
+ ]
+ }
+ )
+
+ activity = insert(:note_activity, note: note, user: user)
+
+ {:ok, _activity} = CommonAPI.react_with_emoji(activity.id, other_user, "dinosaur")
+
+ activity = Repo.get(Activity, activity.id)
+
+ [notification] = Notification.for_user(user)
+
+ assert notification
+
+ expected = %{
+ id: to_string(notification.id),
+ pleroma: %{is_seen: false, is_muted: false},
+ type: "pleroma:emoji_reaction",
+ emoji: ":dinosaur:",
+ account: AccountView.render("show.json", %{user: other_user, for: user}),
+ status: StatusView.render("show.json", %{activity: activity, for: user}),
+ created_at: Utils.to_masto_date(notification.inserted_at),
+ emoji_url: "http://localhost:4001/emoji/dino walking.gif"
+ }
+
+ test_notifications_rendering([notification], user, [expected])
+ end
+
test "Poll notification" do
user = insert(:user)
activity = insert(:question_activity, user: user)