Allow custom emoji reactions: Fix tests, mixed custom and unicode reactions
This commit is contained in:
parent
787e30c5fd
commit
4b85d1c617
|
@ -51,6 +51,15 @@ def reload 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
|
||||
|
||||
case :ets.lookup(@ets, name) do
|
||||
[{_, path}] -> path
|
||||
_ -> nil
|
||||
|
@ -139,6 +148,51 @@ def is_unicode_emoji?(unquote(emoji)), do: true
|
|||
|
||||
def is_unicode_emoji?(_), do: false
|
||||
|
||||
def stripped_name(name) when is_binary(name) do
|
||||
name
|
||||
|> String.replace_leading(":", "")
|
||||
|> String.replace_trailing(":", "")
|
||||
end
|
||||
|
||||
def stripped_name(name), do: name
|
||||
|
||||
def maybe_quote(name) when is_binary(name) do
|
||||
if is_unicode_emoji?(name) do
|
||||
name
|
||||
else
|
||||
if String.starts_with?(name, ":") do
|
||||
name
|
||||
else
|
||||
":#{name}:"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_quote(name), do: name
|
||||
|
||||
def emoji_url(%{"type" => "EmojiReact", "content" => _, "tag" => []}), do: nil
|
||||
|
||||
def emoji_url(%{"type" => "EmojiReact", "content" => emoji, "tag" => tags}) do
|
||||
tag =
|
||||
tags
|
||||
|> Enum.find(fn tag -> tag["type"] == "Emoji" && tag["name"] == stripped_name(emoji) end)
|
||||
|
||||
if is_nil(tag) do
|
||||
nil
|
||||
else
|
||||
tag
|
||||
|> Map.get("icon")
|
||||
|> Map.get("url")
|
||||
end
|
||||
end
|
||||
|
||||
def emoji_url(_), do: nil
|
||||
|
||||
def emoji_name_with_instance(name, url) do
|
||||
url = url |> URI.parse() |> Map.get(:host)
|
||||
"#{name}@#{url}"
|
||||
end
|
||||
|
||||
emoji_qualification_map =
|
||||
emojis
|
||||
|> Enum.filter(&String.contains?(&1, "\uFE0F"))
|
||||
|
|
|
@ -90,9 +90,11 @@ defp remote_custom_emoji_react(
|
|||
Enum.find(
|
||||
existing_reactions,
|
||||
fn [name, _, url] ->
|
||||
if url != nil do
|
||||
url = URI.parse(url)
|
||||
url.host == instance && name == emoji_code
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
if matching_reaction do
|
||||
|
|
|
@ -58,10 +58,17 @@ defmacro status_object_fields 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
|
||||
|
|
|
@ -14,6 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
|||
alias Pleroma.Web.AdminAPI.Report
|
||||
alias Pleroma.Web.AdminAPI.ReportView
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
@ -145,7 +146,9 @@ defp put_report(response, activity) do
|
|||
end
|
||||
|
||||
defp put_emoji(response, activity) do
|
||||
Map.put(response, :emoji, activity.data["content"])
|
||||
response
|
||||
|> Map.put(:emoji, activity.data["content"])
|
||||
|> Map.put(:emoji_url, MediaProxy.url(Pleroma.Emoji.emoji_url(activity.data)))
|
||||
end
|
||||
|
||||
defp put_chat_message(response, activity, reading_user, opts) do
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"Hashtag": "as:Hashtag"
|
||||
}
|
||||
],
|
||||
"type": "Like",
|
||||
"id": "https://misskey.local.live/likes/917ocsybgp",
|
||||
"actor": "https://misskey.local.live/users/8x8yep20u2",
|
||||
"object": "https://pleroma.local.live/objects/89937a53-2692-4631-bb62-770091267391",
|
||||
"content": ":hanapog:",
|
||||
"_misskey_reaction": ":hanapog:",
|
||||
"tag": [
|
||||
{
|
||||
"id": "https://misskey.local.live/emojis/hanapog",
|
||||
"type": "Emoji",
|
||||
"name": ":hanapog:",
|
||||
"updated": "2022-06-07T12:00:05.773Z",
|
||||
"icon": {
|
||||
"type": "Image",
|
||||
"mediaType": "image/png",
|
||||
"url": "https://misskey.local.live/files/webpublic-8f8a9768-7264-4171-88d6-2356aabeadcd"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -38,16 +38,70 @@ test "it is not valid without a 'content' field", %{valid_emoji_react: valid_emo
|
|||
assert {:content, {"can't be blank", [validation: :required]}} in cng.errors
|
||||
end
|
||||
|
||||
test "it is not valid with a non-emoji content field", %{valid_emoji_react: valid_emoji_react} do
|
||||
test "it is valid when custom emoji is used", %{valid_emoji_react: valid_emoji_react} do
|
||||
without_emoji_content =
|
||||
valid_emoji_react
|
||||
|> Map.put("content", "x")
|
||||
|> Map.put("content", ":hello:")
|
||||
|> Map.put("tag", [
|
||||
%{
|
||||
"type" => "Emoji",
|
||||
"name" => ":hello:",
|
||||
"icon" => %{"url" => "http://somewhere", "type" => "Image"}
|
||||
}
|
||||
])
|
||||
|
||||
{:ok, _, _} = ObjectValidator.validate(without_emoji_content, [])
|
||||
end
|
||||
|
||||
test "it is not valid when custom emoji don't have a matching tag", %{
|
||||
valid_emoji_react: valid_emoji_react
|
||||
} do
|
||||
without_emoji_content =
|
||||
valid_emoji_react
|
||||
|> Map.put("content", ":hello:")
|
||||
|> Map.put("tag", [
|
||||
%{
|
||||
"type" => "Emoji",
|
||||
"name" => ":whoops:",
|
||||
"icon" => %{"url" => "http://somewhere", "type" => "Image"}
|
||||
}
|
||||
])
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(without_emoji_content, [])
|
||||
|
||||
refute cng.valid?
|
||||
|
||||
assert {:content, {"must be a single character emoji", []}} in cng.errors
|
||||
assert {:tag, {"does not contain an Emoji tag", []}} in cng.errors
|
||||
end
|
||||
|
||||
test "it is not valid when custom emoji have no tags", %{
|
||||
valid_emoji_react: valid_emoji_react
|
||||
} do
|
||||
without_emoji_content =
|
||||
valid_emoji_react
|
||||
|> Map.put("content", ":hello:")
|
||||
|> Map.put("tag", [])
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(without_emoji_content, [])
|
||||
|
||||
refute cng.valid?
|
||||
|
||||
assert {:tag, {"does not contain an Emoji tag", []}} in cng.errors
|
||||
end
|
||||
|
||||
test "it is not valid when custom emoji doesn't match a shortcode format", %{
|
||||
valid_emoji_react: valid_emoji_react
|
||||
} do
|
||||
without_emoji_content =
|
||||
valid_emoji_react
|
||||
|> Map.put("content", "hello")
|
||||
|> Map.put("tag", [])
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(without_emoji_content, [])
|
||||
|
||||
refute cng.valid?
|
||||
|
||||
assert {:tag, {"does not contain an Emoji tag", []}} in cng.errors
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -453,7 +453,7 @@ test "adds the reaction to the object", %{emoji_react: emoji_react, user: user}
|
|||
object = Object.get_by_ap_id(emoji_react.data["object"])
|
||||
|
||||
assert object.data["reaction_count"] == 1
|
||||
assert ["👌", [user.ap_id]] in object.data["reactions"]
|
||||
assert ["👌", [user.ap_id], nil] in object.data["reactions"]
|
||||
end
|
||||
|
||||
test "creates a notification", %{emoji_react: emoji_react, poster: poster} do
|
||||
|
|
|
@ -34,7 +34,56 @@ test "it works for incoming emoji reactions" do
|
|||
object = Object.get_by_ap_id(data["object"])
|
||||
|
||||
assert object.data["reaction_count"] == 1
|
||||
assert match?([["👌", _]], object.data["reactions"])
|
||||
assert match?([["👌", _, nil]], object.data["reactions"])
|
||||
end
|
||||
|
||||
test "it works for incoming custom emoji reactions" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user, local: false)
|
||||
{:ok, activity} = CommonAPI.post(user, %{status: "hello"})
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/custom-emoji-reaction.json")
|
||||
|> Jason.decode!()
|
||||
|> Map.put("object", activity.data["object"])
|
||||
|> Map.put("actor", other_user.ap_id)
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert data["actor"] == other_user.ap_id
|
||||
assert data["type"] == "EmojiReact"
|
||||
assert data["id"] == "https://misskey.local.live/likes/917ocsybgp"
|
||||
assert data["object"] == activity.data["object"]
|
||||
assert data["content"] == ":hanapog:"
|
||||
|
||||
assert data["tag"] == [
|
||||
%{
|
||||
"id" => "https://misskey.local.live/emojis/hanapog",
|
||||
"type" => "Emoji",
|
||||
"name" => "hanapog",
|
||||
"updated" => "2022-06-07T12:00:05.773Z",
|
||||
"icon" => %{
|
||||
"type" => "Image",
|
||||
"url" =>
|
||||
"https://misskey.local.live/files/webpublic-8f8a9768-7264-4171-88d6-2356aabeadcd"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
object = Object.get_by_ap_id(data["object"])
|
||||
|
||||
assert object.data["reaction_count"] == 1
|
||||
|
||||
assert match?(
|
||||
[
|
||||
[
|
||||
"hanapog",
|
||||
_,
|
||||
"https://misskey.local.live/files/webpublic-8f8a9768-7264-4171-88d6-2356aabeadcd"
|
||||
]
|
||||
],
|
||||
object.data["reactions"]
|
||||
)
|
||||
end
|
||||
|
||||
test "it works for incoming unqualified emoji reactions" do
|
||||
|
@ -65,7 +114,7 @@ test "it works for incoming unqualified emoji reactions" do
|
|||
object = Object.get_by_ap_id(data["object"])
|
||||
|
||||
assert object.data["reaction_count"] == 1
|
||||
assert match?([[^emoji, _]], object.data["reactions"])
|
||||
assert match?([[^emoji, _, _]], object.data["reactions"])
|
||||
end
|
||||
|
||||
test "it reject invalid emoji reactions" do
|
||||
|
|
|
@ -190,7 +190,8 @@ test "EmojiReact notification" do
|
|||
emoji: "☕",
|
||||
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)
|
||||
created_at: Utils.to_masto_date(notification.inserted_at),
|
||||
emoji_url: nil
|
||||
}
|
||||
|
||||
test_notifications_rendering([notification], user, [expected])
|
||||
|
|
|
@ -35,16 +35,26 @@ test "has an emoji reaction list" do
|
|||
{:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
|
||||
|
||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "☕")
|
||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, user, ":dinosaur:")
|
||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵")
|
||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, ":dinosaur:")
|
||||
|
||||
activity = Repo.get(Activity, activity.id)
|
||||
status = StatusView.render("show.json", activity: activity)
|
||||
|
||||
assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
|
||||
|
||||
assert status[:pleroma][:emoji_reactions] == [
|
||||
%{name: "☕", count: 2, me: false},
|
||||
%{name: "🍵", count: 1, me: false}
|
||||
%{name: "☕", count: 2, me: false, url: nil, account_ids: [other_user.id, user.id]},
|
||||
%{
|
||||
count: 2,
|
||||
me: false,
|
||||
name: "dinosaur",
|
||||
url: "http://localhost:4001/emoji/dino walking.gif",
|
||||
account_ids: [other_user.id, user.id]
|
||||
},
|
||||
%{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
|
||||
]
|
||||
|
||||
status = StatusView.render("show.json", activity: activity, for: user)
|
||||
|
@ -52,8 +62,15 @@ test "has an emoji reaction list" do
|
|||
assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
|
||||
|
||||
assert status[:pleroma][:emoji_reactions] == [
|
||||
%{name: "☕", count: 2, me: true},
|
||||
%{name: "🍵", count: 1, me: false}
|
||||
%{name: "☕", count: 2, me: true, url: nil, account_ids: [other_user.id, user.id]},
|
||||
%{
|
||||
count: 2,
|
||||
me: true,
|
||||
name: "dinosaur",
|
||||
url: "http://localhost:4001/emoji/dino walking.gif",
|
||||
account_ids: [other_user.id, user.id]
|
||||
},
|
||||
%{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -66,11 +83,10 @@ test "works correctly with badly formatted emojis" do
|
|||
|> Object.update_data(%{"reactions" => %{"☕" => [user.ap_id], "x" => 1}})
|
||||
|
||||
activity = Activity.get_by_id(activity.id)
|
||||
|
||||
status = StatusView.render("show.json", activity: activity, for: user)
|
||||
|
||||
assert status[:pleroma][:emoji_reactions] == [
|
||||
%{name: "☕", count: 1, me: true}
|
||||
%{name: "☕", count: 1, me: true, url: nil, account_ids: [user.id]}
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -90,7 +106,7 @@ test "doesn't show reactions from muted and blocked users" do
|
|||
status = StatusView.render("show.json", activity: activity)
|
||||
|
||||
assert status[:pleroma][:emoji_reactions] == [
|
||||
%{name: "☕", count: 1, me: false}
|
||||
%{name: "☕", count: 1, me: false, url: nil, account_ids: [other_user.id]}
|
||||
]
|
||||
|
||||
status = StatusView.render("show.json", activity: activity, for: user)
|
||||
|
@ -102,19 +118,25 @@ test "doesn't show reactions from muted and blocked users" do
|
|||
status = StatusView.render("show.json", activity: activity)
|
||||
|
||||
assert status[:pleroma][:emoji_reactions] == [
|
||||
%{name: "☕", count: 2, me: false}
|
||||
%{
|
||||
name: "☕",
|
||||
count: 2,
|
||||
me: false,
|
||||
url: nil,
|
||||
account_ids: [third_user.id, other_user.id]
|
||||
}
|
||||
]
|
||||
|
||||
status = StatusView.render("show.json", activity: activity, for: user)
|
||||
|
||||
assert status[:pleroma][:emoji_reactions] == [
|
||||
%{name: "☕", count: 1, me: false}
|
||||
%{name: "☕", count: 1, me: false, url: nil, account_ids: [third_user.id]}
|
||||
]
|
||||
|
||||
status = StatusView.render("show.json", activity: activity, for: other_user)
|
||||
|
||||
assert status[:pleroma][:emoji_reactions] == [
|
||||
%{name: "☕", count: 1, me: true}
|
||||
%{name: "☕", count: 1, me: true, url: nil, account_ids: [other_user.id]}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue