Merge branch 'pleroma-akkoma-emoji-port' into 'develop'
Custom emoji reactions support See merge request pleroma/pleroma!3845
This commit is contained in:
commit
353538d16c
|
@ -51,6 +51,8 @@ def reload do
|
||||||
@doc "Returns the path of the emoji `name`."
|
@doc "Returns the path of the emoji `name`."
|
||||||
@spec get(String.t()) :: String.t() | nil
|
@spec get(String.t()) :: String.t() | nil
|
||||||
def get(name) do
|
def get(name) do
|
||||||
|
name = maybe_strip_name(name)
|
||||||
|
|
||||||
case :ets.lookup(@ets, name) do
|
case :ets.lookup(@ets, name) do
|
||||||
[{_, path}] -> path
|
[{_, path}] -> path
|
||||||
_ -> nil
|
_ -> nil
|
||||||
|
@ -139,6 +141,57 @@ def is_unicode_emoji?(unquote(emoji)), do: true
|
||||||
|
|
||||||
def is_unicode_emoji?(_), do: false
|
def is_unicode_emoji?(_), do: false
|
||||||
|
|
||||||
|
@emoji_regex ~r/:[A-Za-z0-9_-]+(@.+)?:/
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
emoji = maybe_strip_name(emoji)
|
||||||
|
|
||||||
|
tag =
|
||||||
|
tags
|
||||||
|
|> Enum.find(fn tag ->
|
||||||
|
tag["type"] == "Emoji" && !is_nil(tag["name"]) && tag["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 =
|
emoji_qualification_map =
|
||||||
emojis
|
emojis
|
||||||
|> Enum.filter(&String.contains?(&1, "\uFE0F"))
|
|> Enum.filter(&String.contains?(&1, "\uFE0F"))
|
||||||
|
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.CommonAPI.ActivityDraft
|
alias Pleroma.Web.CommonAPI.ActivityDraft
|
||||||
|
alias Pleroma.Web.Endpoint
|
||||||
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
@ -54,13 +55,87 @@ def follow(follower, followed) do
|
||||||
{:ok, data, []}
|
{:ok, data, []}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp unicode_emoji_react(_object, data, emoji) do
|
||||||
|
data
|
||||||
|
|> Map.put("content", emoji)
|
||||||
|
|> Map.put("type", "EmojiReact")
|
||||||
|
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", tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp remote_custom_emoji_react(
|
||||||
|
%{data: %{"reactions" => existing_reactions}},
|
||||||
|
data,
|
||||||
|
emoji
|
||||||
|
) do
|
||||||
|
[emoji_code, instance] = String.split(Emoji.maybe_strip_name(emoji), "@")
|
||||||
|
|
||||||
|
matching_reaction =
|
||||||
|
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
|
||||||
|
[name, _, url] = matching_reaction
|
||||||
|
add_emoji_content(data, name, url)
|
||||||
|
else
|
||||||
|
{:error, "Could not react"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp remote_custom_emoji_react(_object, _data, _emoji) do
|
||||||
|
{:error, "Could not react"}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp local_custom_emoji_react(data, emoji) do
|
||||||
|
with %{file: path} = emojo <- Emoji.get(emoji) do
|
||||||
|
url = "#{Endpoint.url()}#{path}"
|
||||||
|
add_emoji_content(data, emojo.code, url)
|
||||||
|
else
|
||||||
|
_ -> {:error, "Emoji does not exist"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp custom_emoji_react(object, data, emoji) do
|
||||||
|
if String.contains?(emoji, "@") do
|
||||||
|
remote_custom_emoji_react(object, data, emoji)
|
||||||
|
else
|
||||||
|
local_custom_emoji_react(data, emoji)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
|
@spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
|
||||||
def emoji_react(actor, object, emoji) do
|
def emoji_react(actor, object, emoji) do
|
||||||
with {:ok, data, meta} <- object_action(actor, object) do
|
with {:ok, data, meta} <- object_action(actor, object) do
|
||||||
data =
|
data =
|
||||||
data
|
if Emoji.is_unicode_emoji?(emoji) do
|
||||||
|> Map.put("content", emoji)
|
unicode_emoji_react(object, data, emoji)
|
||||||
|> Map.put("type", "EmojiReact")
|
else
|
||||||
|
custom_emoji_react(object, data, emoji)
|
||||||
|
end
|
||||||
|
|
||||||
{:ok, data, meta}
|
{:ok, data, meta}
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,8 +5,10 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Emoji
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
@ -19,6 +21,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
||||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
message_fields()
|
message_fields()
|
||||||
activity_fields()
|
activity_fields()
|
||||||
|
embeds_many(:tag, TagValidator)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -43,7 +46,8 @@ def cast_data(data) do
|
||||||
|
|
||||||
def changeset(struct, data) do
|
def changeset(struct, data) do
|
||||||
struct
|
struct
|
||||||
|> cast(data, __schema__(:fields))
|
|> cast(data, __schema__(:fields) -- [:tag])
|
||||||
|
|> cast_embed(:tag)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fix(data) do
|
defp fix(data) do
|
||||||
|
@ -53,12 +57,16 @@ defp fix(data) do
|
||||||
|> CommonFixes.fix_actor()
|
|> CommonFixes.fix_actor()
|
||||||
|> CommonFixes.fix_activity_addressing()
|
|> CommonFixes.fix_activity_addressing()
|
||||||
|
|
||||||
with %Object{} = object <- Object.normalize(data["object"]) do
|
data = Map.put_new(data, "tag", [])
|
||||||
data
|
|
||||||
|> CommonFixes.fix_activity_context(object)
|
case Object.normalize(data["object"]) do
|
||||||
|> CommonFixes.fix_object_action_recipients(object)
|
%Object{} = object ->
|
||||||
else
|
data
|
||||||
_ -> data
|
|> CommonFixes.fix_activity_context(object)
|
||||||
|
|> CommonFixes.fix_object_action_recipients(object)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
data
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -82,11 +90,31 @@ defp fix_emoji_qualification(data), do: data
|
||||||
defp validate_emoji(cng) do
|
defp validate_emoji(cng) do
|
||||||
content = get_field(cng, :content)
|
content = get_field(cng, :content)
|
||||||
|
|
||||||
if Pleroma.Emoji.is_unicode_emoji?(content) do
|
if Emoji.is_unicode_emoji?(content) || Emoji.is_custom_emoji?(content) do
|
||||||
cng
|
cng
|
||||||
else
|
else
|
||||||
cng
|
cng
|
||||||
|> add_error(:content, "must be a single character emoji")
|
|> add_error(:content, "is not a valid emoji")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_validate_tag_presence(cng) do
|
||||||
|
content = get_field(cng, :content)
|
||||||
|
|
||||||
|
if Emoji.is_unicode_emoji?(content) do
|
||||||
|
cng
|
||||||
|
else
|
||||||
|
tag = get_field(cng, :tag)
|
||||||
|
emoji_name = Emoji.maybe_strip_name(content)
|
||||||
|
|
||||||
|
case tag do
|
||||||
|
[%{name: ^emoji_name, type: "Emoji", icon: %{url: _}}] ->
|
||||||
|
cng
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
cng
|
||||||
|
|> add_error(:tag, "does not contain an Emoji tag")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -97,5 +125,6 @@ defp validate_data(data_cng) do
|
||||||
|> validate_actor_presence()
|
|> validate_actor_presence()
|
||||||
|> validate_object_presence()
|
|> validate_object_presence()
|
||||||
|> validate_emoji()
|
|> validate_emoji()
|
||||||
|
|> maybe_validate_tag_presence()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -325,21 +325,29 @@ def update_element_in_object(property, element, object, count \\ nil) do
|
||||||
{:ok, Object.t()} | {:error, Ecto.Changeset.t()}
|
{:ok, Object.t()} | {:error, Ecto.Changeset.t()}
|
||||||
|
|
||||||
def add_emoji_reaction_to_object(
|
def add_emoji_reaction_to_object(
|
||||||
%Activity{data: %{"content" => emoji, "actor" => actor}},
|
%Activity{data: %{"content" => emoji, "actor" => actor}} = activity,
|
||||||
object
|
object
|
||||||
) do
|
) do
|
||||||
reactions = get_cached_emoji_reactions(object)
|
reactions = get_cached_emoji_reactions(object)
|
||||||
|
emoji = Pleroma.Emoji.maybe_strip_name(emoji)
|
||||||
|
url = maybe_emoji_url(emoji, activity)
|
||||||
|
|
||||||
new_reactions =
|
new_reactions =
|
||||||
case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do
|
case Enum.find_index(reactions, fn [candidate, _, candidate_url] ->
|
||||||
|
if is_nil(candidate_url) do
|
||||||
|
emoji == candidate
|
||||||
|
else
|
||||||
|
url == candidate_url
|
||||||
|
end
|
||||||
|
end) do
|
||||||
nil ->
|
nil ->
|
||||||
reactions ++ [[emoji, [actor]]]
|
reactions ++ [[emoji, [actor], url]]
|
||||||
|
|
||||||
index ->
|
index ->
|
||||||
List.update_at(
|
List.update_at(
|
||||||
reactions,
|
reactions,
|
||||||
index,
|
index,
|
||||||
fn [emoji, users] -> [emoji, Enum.uniq([actor | users])] end
|
fn [emoji, users, url] -> [emoji, Enum.uniq([actor | users]), url] end
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -348,18 +356,40 @@ def add_emoji_reaction_to_object(
|
||||||
update_element_in_object("reaction", new_reactions, object, count)
|
update_element_in_object("reaction", new_reactions, object, count)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_emoji_url(
|
||||||
|
name,
|
||||||
|
%Activity{
|
||||||
|
data: %{
|
||||||
|
"tag" => [
|
||||||
|
%{"type" => "Emoji", "name" => name, "icon" => %{"url" => url}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
do: url
|
||||||
|
|
||||||
|
defp maybe_emoji_url(_, _), do: nil
|
||||||
|
|
||||||
def emoji_count(reactions_list) do
|
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
|
end
|
||||||
|
|
||||||
def remove_emoji_reaction_from_object(
|
def remove_emoji_reaction_from_object(
|
||||||
%Activity{data: %{"content" => emoji, "actor" => actor}},
|
%Activity{data: %{"content" => emoji, "actor" => actor}} = activity,
|
||||||
object
|
object
|
||||||
) do
|
) do
|
||||||
|
emoji = Pleroma.Emoji.maybe_strip_name(emoji)
|
||||||
reactions = get_cached_emoji_reactions(object)
|
reactions = get_cached_emoji_reactions(object)
|
||||||
|
url = maybe_emoji_url(emoji, activity)
|
||||||
|
|
||||||
new_reactions =
|
new_reactions =
|
||||||
case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do
|
case Enum.find_index(reactions, fn [candidate, _, candidate_url] ->
|
||||||
|
if is_nil(candidate_url) do
|
||||||
|
emoji == candidate
|
||||||
|
else
|
||||||
|
url == candidate_url
|
||||||
|
end
|
||||||
|
end) do
|
||||||
nil ->
|
nil ->
|
||||||
reactions
|
reactions
|
||||||
|
|
||||||
|
@ -367,9 +397,9 @@ def remove_emoji_reaction_from_object(
|
||||||
List.update_at(
|
List.update_at(
|
||||||
reactions,
|
reactions,
|
||||||
index,
|
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
|
end
|
||||||
|
|
||||||
count = emoji_count(new_reactions)
|
count = emoji_count(new_reactions)
|
||||||
|
@ -489,17 +519,37 @@ def fetch_latest_undo(%User{ap_id: ap_id}) do
|
||||||
|
|
||||||
def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do
|
def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do
|
||||||
%{data: %{"object" => object_ap_id}} = Activity.get_by_id(internal_activity_id)
|
%{data: %{"object" => object_ap_id}} = Activity.get_by_id(internal_activity_id)
|
||||||
|
emoji = Pleroma.Emoji.maybe_quote(emoji)
|
||||||
|
|
||||||
"EmojiReact"
|
"EmojiReact"
|
||||||
|> Activity.Queries.by_type()
|
|> Activity.Queries.by_type()
|
||||||
|> where(actor: ^ap_id)
|
|> where(actor: ^ap_id)
|
||||||
|> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji))
|
|> custom_emoji_discriminator(emoji)
|
||||||
|> Activity.Queries.by_object_id(object_ap_id)
|
|> Activity.Queries.by_object_id(object_ap_id)
|
||||||
|> order_by([activity], fragment("? desc nulls last", activity.id))
|
|> order_by([activity], fragment("? desc nulls last", activity.id))
|
||||||
|> limit(1)
|
|> limit(1)
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp custom_emoji_discriminator(query, emoji) do
|
||||||
|
if String.contains?(emoji, "@") do
|
||||||
|
stripped = Pleroma.Emoji.maybe_strip_name(emoji)
|
||||||
|
[name, domain] = String.split(stripped, "@")
|
||||||
|
domain_pattern = "%/" <> domain <> "/%"
|
||||||
|
emoji_pattern = Pleroma.Emoji.maybe_quote(name)
|
||||||
|
|
||||||
|
query
|
||||||
|
|> where([activity], fragment("?->>'content' = ?
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT FROM jsonb_array_elements(?->'tag') elem
|
||||||
|
WHERE elem->>'id' ILIKE ?
|
||||||
|
)", activity.data, ^emoji_pattern, activity.data, ^domain_pattern))
|
||||||
|
else
|
||||||
|
query
|
||||||
|
|> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
#### Announce-related helpers
|
#### Announce-related helpers
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -92,6 +92,7 @@ def features do
|
||||||
"safe_dm_mentions"
|
"safe_dm_mentions"
|
||||||
end,
|
end,
|
||||||
"pleroma_emoji_reactions",
|
"pleroma_emoji_reactions",
|
||||||
|
"pleroma_custom_emoji_reactions",
|
||||||
"pleroma_chat_messages",
|
"pleroma_chat_messages",
|
||||||
if Config.get([:instance, :show_reactions]) do
|
if Config.get([:instance, :show_reactions]) do
|
||||||
"exposable_reactions"
|
"exposable_reactions"
|
||||||
|
|
|
@ -17,6 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
alias Pleroma.Web.MediaProxy
|
||||||
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
|
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
|
||||||
|
|
||||||
defp object_id_for(%{data: %{"object" => %{"id" => id}}}) when is_binary(id), do: id
|
defp object_id_for(%{data: %{"object" => %{"id" => id}}}) when is_binary(id), do: id
|
||||||
|
@ -145,7 +146,9 @@ defp put_report(response, activity) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_emoji(response, activity) do
|
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
|
end
|
||||||
|
|
||||||
defp put_chat_message(response, activity, reading_user, opts) do
|
defp put_chat_message(response, activity, reading_user, opts) do
|
||||||
|
|
|
@ -340,8 +340,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
opts[:for],
|
opts[:for],
|
||||||
Map.get(opts, :with_muted, false)
|
Map.get(opts, :with_muted, false)
|
||||||
)
|
)
|
||||||
|> Stream.map(fn {emoji, users} ->
|
|> Stream.map(fn {emoji, users, url} ->
|
||||||
build_emoji_map(emoji, users, opts[:for])
|
build_emoji_map(emoji, users, url, opts[:for])
|
||||||
end)
|
end)
|
||||||
|> Enum.to_list()
|
|> Enum.to_list()
|
||||||
|
|
||||||
|
@ -702,11 +702,13 @@ defp pin_data(%Object{data: %{"id" => object_id}}, %User{pinned_objects: pinned_
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_emoji_map(emoji, users, current_user) do
|
defp build_emoji_map(emoji, users, url, current_user) do
|
||||||
%{
|
%{
|
||||||
name: emoji,
|
name: Pleroma.Web.PleromaAPI.EmojiReactionView.emoji_name(emoji, url),
|
||||||
count: length(users),
|
count: length(users),
|
||||||
me: !!(current_user && current_user.ap_id in users)
|
url: MediaProxy.url(url),
|
||||||
|
me: !!(current_user && current_user.ap_id in users),
|
||||||
|
account_ids: Enum.map(users, fn user -> User.get_cached_by_ap_id(user).id end)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -50,29 +50,35 @@ def filter_allowed_users(reactions, user, with_muted) do
|
||||||
if not with_muted, do: User.cached_muted_users_ap_ids(user), else: []
|
if not with_muted, do: User.cached_muted_users_ap_ids(user), else: []
|
||||||
end
|
end
|
||||||
|
|
||||||
filter_emoji = fn emoji, users ->
|
filter_emoji = fn emoji, users, url ->
|
||||||
case Enum.reject(users, &(&1 in exclude_ap_ids)) do
|
case Enum.reject(users, &(&1 in exclude_ap_ids)) do
|
||||||
[] -> nil
|
[] -> nil
|
||||||
users -> {emoji, users}
|
users -> {emoji, users, url}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
reactions
|
reactions
|
||||||
|> Stream.map(fn
|
|> Stream.map(fn
|
||||||
[emoji, users] when is_list(users) -> filter_emoji.(emoji, users)
|
[emoji, users, url] when is_list(users) -> filter_emoji.(emoji, users, url)
|
||||||
{emoji, users} when is_list(users) -> filter_emoji.(emoji, users)
|
{emoji, users, url} when is_list(users) -> filter_emoji.(emoji, users, url)
|
||||||
|
{emoji, users} when is_list(users) -> filter_emoji.(emoji, users, nil)
|
||||||
_ -> nil
|
_ -> nil
|
||||||
end)
|
end)
|
||||||
|> Stream.reject(&is_nil/1)
|
|> Stream.reject(&is_nil/1)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp filter(reactions, %{emoji: emoji}) when is_binary(emoji) do
|
defp filter(reactions, %{emoji: emoji}) when is_binary(emoji) do
|
||||||
Enum.filter(reactions, fn [e, _] -> e == emoji end)
|
Enum.filter(reactions, fn [e, _, _] -> e == emoji end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp filter(reactions, _), do: reactions
|
defp filter(reactions, _), do: reactions
|
||||||
|
|
||||||
def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
|
def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
|
||||||
|
emoji =
|
||||||
|
emoji
|
||||||
|
|> Pleroma.Emoji.fully_qualify_emoji()
|
||||||
|
|> Pleroma.Emoji.maybe_quote()
|
||||||
|
|
||||||
with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do
|
with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do
|
||||||
activity = Activity.get_by_id(activity_id)
|
activity = Activity.get_by_id(activity_id)
|
||||||
|
|
||||||
|
@ -83,6 +89,11 @@ def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) d
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
|
def delete(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
|
||||||
|
emoji =
|
||||||
|
emoji
|
||||||
|
|> Pleroma.Emoji.fully_qualify_emoji()
|
||||||
|
|> Pleroma.Emoji.maybe_quote()
|
||||||
|
|
||||||
with {:ok, _activity} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji) do
|
with {:ok, _activity} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji) do
|
||||||
activity = Activity.get_by_id(activity_id)
|
activity = Activity.get_by_id(activity_id)
|
||||||
|
|
||||||
|
|
|
@ -7,17 +7,30 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionView do
|
||||||
|
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
|
|
||||||
|
def emoji_name(emoji, nil), do: emoji
|
||||||
|
|
||||||
|
def emoji_name(emoji, url) do
|
||||||
|
url = URI.parse(url)
|
||||||
|
|
||||||
|
if url.host == Pleroma.Web.Endpoint.host() do
|
||||||
|
emoji
|
||||||
|
else
|
||||||
|
"#{emoji}@#{url.host}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def render("index.json", %{emoji_reactions: emoji_reactions} = opts) do
|
def render("index.json", %{emoji_reactions: emoji_reactions} = opts) do
|
||||||
render_many(emoji_reactions, __MODULE__, "show.json", opts)
|
render_many(emoji_reactions, __MODULE__, "show.json", opts)
|
||||||
end
|
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)
|
users = fetch_users(user_ap_ids)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
name: emoji,
|
name: emoji_name(emoji, url),
|
||||||
count: length(users),
|
count: length(users),
|
||||||
accounts: render(AccountView, "index.json", users: users, for: user),
|
accounts: render(AccountView, "index.json", users: users, for: user),
|
||||||
|
url: Pleroma.Web.MediaProxy.url(url),
|
||||||
me: !!(user && user.ap_id in user_ap_ids)
|
me: !!(user && user.ap_id in user_ap_ids)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
assert {:content, {"can't be blank", [validation: :required]}} in cng.errors
|
||||||
end
|
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 =
|
without_emoji_content =
|
||||||
valid_emoji_react
|
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, [])
|
{:error, cng} = ObjectValidator.validate(without_emoji_content, [])
|
||||||
|
|
||||||
refute cng.valid?
|
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
|
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"])
|
object = Object.get_by_ap_id(emoji_react.data["object"])
|
||||||
|
|
||||||
assert object.data["reaction_count"] == 1
|
assert object.data["reaction_count"] == 1
|
||||||
assert ["👌", [user.ap_id]] in object.data["reactions"]
|
assert ["👌", [user.ap_id], nil] in object.data["reactions"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "creates a notification", %{emoji_react: emoji_react, poster: poster} do
|
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"])
|
object = Object.get_by_ap_id(data["object"])
|
||||||
|
|
||||||
assert object.data["reaction_count"] == 1
|
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
|
end
|
||||||
|
|
||||||
test "it works for incoming unqualified emoji reactions" do
|
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"])
|
object = Object.get_by_ap_id(data["object"])
|
||||||
|
|
||||||
assert object.data["reaction_count"] == 1
|
assert object.data["reaction_count"] == 1
|
||||||
assert match?([[^emoji, _]], object.data["reactions"])
|
assert match?([[^emoji, _, _]], object.data["reactions"])
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it reject invalid emoji reactions" do
|
test "it reject invalid emoji reactions" do
|
||||||
|
|
|
@ -190,7 +190,47 @@ test "EmojiReact notification" do
|
||||||
emoji: "☕",
|
emoji: "☕",
|
||||||
account: AccountView.render("show.json", %{user: other_user, for: user}),
|
account: AccountView.render("show.json", %{user: other_user, for: user}),
|
||||||
status: StatusView.render("show.json", %{activity: activity, 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])
|
||||||
|
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])
|
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, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
|
||||||
|
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "☕")
|
{: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, third_user, "🍵")
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_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)
|
activity = Repo.get(Activity, activity.id)
|
||||||
status = StatusView.render("show.json", activity: activity)
|
status = StatusView.render("show.json", activity: activity)
|
||||||
|
|
||||||
assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
|
assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
assert status[:pleroma][:emoji_reactions] == [
|
||||||
%{name: "☕", count: 2, me: false},
|
%{name: "☕", count: 2, me: false, url: nil, account_ids: [other_user.id, user.id]},
|
||||||
%{name: "🍵", count: 1, me: false}
|
%{
|
||||||
|
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)
|
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_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
assert status[:pleroma][:emoji_reactions] == [
|
||||||
%{name: "☕", count: 2, me: true},
|
%{name: "☕", count: 2, me: true, url: nil, account_ids: [other_user.id, user.id]},
|
||||||
%{name: "🍵", count: 1, me: false}
|
%{
|
||||||
|
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
|
end
|
||||||
|
|
||||||
|
@ -66,11 +83,10 @@ test "works correctly with badly formatted emojis" do
|
||||||
|> Object.update_data(%{"reactions" => %{"☕" => [user.ap_id], "x" => 1}})
|
|> Object.update_data(%{"reactions" => %{"☕" => [user.ap_id], "x" => 1}})
|
||||||
|
|
||||||
activity = Activity.get_by_id(activity.id)
|
activity = Activity.get_by_id(activity.id)
|
||||||
|
|
||||||
status = StatusView.render("show.json", activity: activity, for: user)
|
status = StatusView.render("show.json", activity: activity, for: user)
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
assert status[:pleroma][:emoji_reactions] == [
|
||||||
%{name: "☕", count: 1, me: true}
|
%{name: "☕", count: 1, me: true, url: nil, account_ids: [user.id]}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -90,7 +106,7 @@ test "doesn't show reactions from muted and blocked users" do
|
||||||
status = StatusView.render("show.json", activity: activity)
|
status = StatusView.render("show.json", activity: activity)
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
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)
|
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)
|
status = StatusView.render("show.json", activity: activity)
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
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)
|
status = StatusView.render("show.json", activity: activity, for: user)
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
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)
|
status = StatusView.render("show.json", activity: activity, for: other_user)
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
assert status[:pleroma][:emoji_reactions] == [
|
||||||
%{name: "☕", count: 1, me: true}
|
%{name: "☕", count: 1, me: true, url: nil, account_ids: [other_user.id]}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -17,23 +17,113 @@ test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
|
note = insert(:note, user: user, data: %{"reactions" => [["👍", [other_user.ap_id], nil]]})
|
||||||
|
activity = insert(:note_activity, note: note, user: user)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
|> assign(:user, other_user)
|
|> assign(:user, other_user)
|
||||||
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
|
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
|
||||||
|> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/☕")
|
|> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/\u26A0")
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
# We return the status, but this our implementation detail.
|
|
||||||
assert %{"id" => id} = result
|
assert %{"id" => id} = result
|
||||||
assert to_string(activity.id) == id
|
assert to_string(activity.id) == id
|
||||||
|
|
||||||
assert result["pleroma"]["emoji_reactions"] == [
|
assert result["pleroma"]["emoji_reactions"] == [
|
||||||
%{"name" => "☕", "count" => 1, "me" => true}
|
%{
|
||||||
|
"name" => "👍",
|
||||||
|
"count" => 1,
|
||||||
|
"me" => true,
|
||||||
|
"url" => nil,
|
||||||
|
"account_ids" => [other_user.id]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"name" => "\u26A0\uFE0F",
|
||||||
|
"count" => 1,
|
||||||
|
"me" => true,
|
||||||
|
"url" => nil,
|
||||||
|
"account_ids" => [other_user.id]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
|
||||||
|
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
# Reacting with a custom emoji
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> assign(:user, other_user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
|
||||||
|
|> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/:dinosaur:")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert %{"id" => id} = result
|
||||||
|
assert to_string(activity.id) == id
|
||||||
|
|
||||||
|
assert result["pleroma"]["emoji_reactions"] == [
|
||||||
|
%{
|
||||||
|
"name" => "dinosaur",
|
||||||
|
"count" => 1,
|
||||||
|
"me" => true,
|
||||||
|
"url" => "http://localhost:4001/emoji/dino walking.gif",
|
||||||
|
"account_ids" => [other_user.id]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Reacting with a remote emoji
|
||||||
|
note =
|
||||||
|
insert(:note,
|
||||||
|
user: user,
|
||||||
|
data: %{
|
||||||
|
"reactions" => [
|
||||||
|
["👍", [other_user.ap_id], nil],
|
||||||
|
["wow", [other_user.ap_id], "https://remote/emoji/wow"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
activity = insert(:note_activity, note: note, user: user)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"]))
|
||||||
|
|> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/:wow@remote:")
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert result["pleroma"]["emoji_reactions"] == [
|
||||||
|
%{
|
||||||
|
"account_ids" => [other_user.id],
|
||||||
|
"count" => 1,
|
||||||
|
"me" => false,
|
||||||
|
"name" => "👍",
|
||||||
|
"url" => nil
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"name" => "wow@remote",
|
||||||
|
"count" => 2,
|
||||||
|
"me" => true,
|
||||||
|
"url" => "https://remote/emoji/wow",
|
||||||
|
"account_ids" => [user.id, other_user.id]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Reacting with a remote custom emoji that hasn't been reacted with yet
|
||||||
|
note =
|
||||||
|
insert(:note,
|
||||||
|
user: user
|
||||||
|
)
|
||||||
|
|
||||||
|
activity = insert(:note_activity, note: note, user: user)
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"]))
|
||||||
|
|> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/:wow@remote:")
|
||||||
|
|> json_response(400)
|
||||||
|
|
||||||
# Reacting with a non-emoji
|
# Reacting with a non-emoji
|
||||||
assert conn
|
assert conn
|
||||||
|> assign(:user, other_user)
|
|> assign(:user, other_user)
|
||||||
|
@ -46,8 +136,21 @@ test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
|
note =
|
||||||
|
insert(:note,
|
||||||
|
user: user,
|
||||||
|
data: %{"reactions" => [["wow", [user.ap_id], "https://remote/emoji/wow"]]}
|
||||||
|
)
|
||||||
|
|
||||||
|
activity = insert(:note_activity, note: note, user: user)
|
||||||
|
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
{:ok, _reaction_activity} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
{:ok, _reaction_activity} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
||||||
|
{:ok, _reaction_activity} = CommonAPI.react_with_emoji(activity.id, other_user, ":dinosaur:")
|
||||||
|
|
||||||
|
{:ok, _reaction_activity} =
|
||||||
|
CommonAPI.react_with_emoji(activity.id, other_user, ":wow@remote:")
|
||||||
|
|
||||||
ObanHelpers.perform_all()
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
@ -60,11 +163,47 @@ test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|
||||||
assert %{"id" => id} = json_response_and_validate_schema(result, 200)
|
assert %{"id" => id} = json_response_and_validate_schema(result, 200)
|
||||||
assert to_string(activity.id) == id
|
assert to_string(activity.id) == id
|
||||||
|
|
||||||
|
# Remove custom emoji
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> assign(:user, other_user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
|
||||||
|
|> delete("/api/v1/pleroma/statuses/#{activity.id}/reactions/:dinosaur:")
|
||||||
|
|
||||||
|
assert %{"id" => id} = json_response_and_validate_schema(result, 200)
|
||||||
|
assert to_string(activity.id) == id
|
||||||
|
|
||||||
ObanHelpers.perform_all()
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
object = Object.get_by_ap_id(activity.data["object"])
|
object = Object.get_by_ap_id(activity.data["object"])
|
||||||
|
|
||||||
assert object.data["reaction_count"] == 0
|
assert object.data["reaction_count"] == 2
|
||||||
|
|
||||||
|
# Remove custom remote emoji
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> assign(:user, other_user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
|
||||||
|
|> delete("/api/v1/pleroma/statuses/#{activity.id}/reactions/:wow@remote:")
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert result["pleroma"]["emoji_reactions"] == [
|
||||||
|
%{
|
||||||
|
"name" => "wow@remote",
|
||||||
|
"count" => 1,
|
||||||
|
"me" => false,
|
||||||
|
"url" => "https://remote/emoji/wow",
|
||||||
|
"account_ids" => [user.id]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Remove custom remote emoji that hasn't been reacted with yet
|
||||||
|
assert conn
|
||||||
|
|> assign(:user, other_user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
|
||||||
|
|> delete("/api/v1/pleroma/statuses/#{activity.id}/reactions/:zoop@remote:")
|
||||||
|
|> json_response(400)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do
|
test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do
|
||||||
|
@ -181,7 +320,15 @@ test "GET /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
||||||
|
|
||||||
assert [%{"name" => "🎅", "count" => 1, "accounts" => [represented_user], "me" => false}] =
|
assert [
|
||||||
|
%{
|
||||||
|
"name" => "🎅",
|
||||||
|
"count" => 1,
|
||||||
|
"accounts" => [represented_user],
|
||||||
|
"me" => false,
|
||||||
|
"url" => nil
|
||||||
|
}
|
||||||
|
] =
|
||||||
conn
|
conn
|
||||||
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅")
|
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅")
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
Loading…
Reference in New Issue