add custom emoji reaction support

This commit is contained in:
FloatingGhost 2022-06-08 02:42:44 +01:00
parent f19c93cdaa
commit ad0b8c095c
11 changed files with 76 additions and 46 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View 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 @@ 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

View file

@ -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

View file

@ -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}

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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