forked from AkkomaGang/akkoma
add custom emoji reaction support
This commit is contained in:
parent
f19c93cdaa
commit
ad0b8c095c
11 changed files with 76 additions and 46 deletions
|
@ -115,7 +115,7 @@ defp update_emojis(emojis) do
|
||||||
|> String.split("\n")
|
|> String.split("\n")
|
||||||
|> Enum.filter(fn line ->
|
|> Enum.filter(fn line ->
|
||||||
line != "" and not String.starts_with?(line, "#") and
|
line != "" and not String.starts_with?(line, "#") and
|
||||||
String.contains?(line, "fully-qualified")
|
String.contains?(line, "qualified")
|
||||||
end)
|
end)
|
||||||
|> Enum.map(fn line ->
|
|> Enum.map(fn line ->
|
||||||
line
|
line
|
||||||
|
|
|
@ -144,6 +144,7 @@ def validate(%{"type" => type} = object, meta)
|
||||||
def validate(%{"type" => type} = object, meta)
|
def validate(%{"type" => type} = object, meta)
|
||||||
when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
|
when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
|
||||||
ChatMessage Answer] do
|
ChatMessage Answer] do
|
||||||
|
IO.inspect(object)
|
||||||
validator =
|
validator =
|
||||||
case type do
|
case type do
|
||||||
"Accept" -> AcceptRejectValidator
|
"Accept" -> AcceptRejectValidator
|
||||||
|
|
|
@ -65,4 +65,10 @@ defmacro status_object_fields do
|
||||||
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
|
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defmacro tag_fields do
|
||||||
|
quote bind_quoted: binding() do
|
||||||
|
embeds_many(:tag, TagValidator)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
||||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
@emoji_regex ~r/:[A-Za-z_-]+:/
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
quote do
|
quote do
|
||||||
|
@ -19,6 +20,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()
|
||||||
|
tag_fields()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -43,7 +45,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
|
||||||
|
@ -52,6 +55,12 @@ defp fix(data) do
|
||||||
|> CommonFixes.fix_actor()
|
|> CommonFixes.fix_actor()
|
||||||
|> CommonFixes.fix_activity_addressing()
|
|> 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
|
with %Object{} = object <- Object.normalize(data["object"]) do
|
||||||
data
|
data
|
||||||
|> CommonFixes.fix_activity_context(object)
|
|> CommonFixes.fix_activity_context(object)
|
||||||
|
@ -63,12 +72,12 @@ defp fix(data) do
|
||||||
|
|
||||||
defp validate_emoji(cng) do
|
defp validate_emoji(cng) do
|
||||||
content = get_field(cng, :content)
|
content = get_field(cng, :content)
|
||||||
|
IO.inspect(Pleroma.Emoji.is_unicode_emoji?(content))
|
||||||
if Pleroma.Emoji.is_unicode_emoji?(content) do
|
if Pleroma.Emoji.is_unicode_emoji?(content) || Regex.match?(@emoji_regex, 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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,8 @@ defp maybe_federate(%Activity{} = activity, meta) do
|
||||||
{:ok, :not_federated}
|
{:ok, :not_federated}
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
_e -> {:error, :badarg}
|
_e ->
|
||||||
|
{:error, :badarg}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -269,7 +269,6 @@ def handle(%{data: %{"type" => "Undo", "object" => undone_object}} = object, met
|
||||||
def handle(%{data: %{"type" => "EmojiReact"}} = object, meta) do
|
def handle(%{data: %{"type" => "EmojiReact"}} = object, meta) do
|
||||||
reacted_object = Object.get_by_ap_id(object.data["object"])
|
reacted_object = Object.get_by_ap_id(object.data["object"])
|
||||||
Utils.add_emoji_reaction_to_object(object, reacted_object)
|
Utils.add_emoji_reaction_to_object(object, reacted_object)
|
||||||
|
|
||||||
Notification.create_notifications(object)
|
Notification.create_notifications(object)
|
||||||
|
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
|
|
|
@ -415,31 +415,31 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@misskey_reactions %{
|
|
||||||
"like" => "👍",
|
|
||||||
"love" => "❤️",
|
|
||||||
"laugh" => "😆",
|
|
||||||
"hmm" => "🤔",
|
|
||||||
"surprise" => "😮",
|
|
||||||
"congrats" => "🎉",
|
|
||||||
"angry" => "💢",
|
|
||||||
"confused" => "😥",
|
|
||||||
"rip" => "😇",
|
|
||||||
"pudding" => "🍮",
|
|
||||||
"star" => "⭐"
|
|
||||||
}
|
|
||||||
|
|
||||||
@doc "Rewrite misskey likes into EmojiReacts"
|
@doc "Rewrite misskey likes into EmojiReacts"
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{
|
%{
|
||||||
"type" => "Like",
|
"type" => "Like",
|
||||||
"_misskey_reaction" => reaction
|
"_misskey_reaction" => reaction,
|
||||||
|
"tag" => _
|
||||||
} = data,
|
} = data,
|
||||||
options
|
options
|
||||||
) do
|
) do
|
||||||
data
|
data
|
||||||
|> Map.put("type", "EmojiReact")
|
|> 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)
|
|> handle_incoming(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -472,11 +472,11 @@ def handle_incoming(
|
||||||
def handle_incoming(%{"type" => type} = data, _options)
|
def handle_incoming(%{"type" => type} = data, _options)
|
||||||
when type in ~w{Like EmojiReact Announce Add Remove} do
|
when type in ~w{Like EmojiReact Announce Add Remove} do
|
||||||
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
||||||
{:ok, activity, _meta} <-
|
{:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
|
||||||
Pipeline.common_pipeline(data, local: false) do
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
e -> {:error, e}
|
e ->
|
||||||
|
{:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -344,21 +344,22 @@ 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
|
||||||
|
IO.inspect(emoji)
|
||||||
reactions = get_cached_emoji_reactions(object)
|
reactions = get_cached_emoji_reactions(object)
|
||||||
|
emoji = stripped_emoji_name(emoji)
|
||||||
new_reactions =
|
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 ->
|
nil ->
|
||||||
reactions ++ [[emoji, [actor]]]
|
reactions ++ [[emoji, [actor], emoji_url(emoji, activity)]]
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
@ -367,8 +368,22 @@ 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 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
|
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(
|
||||||
|
@ -376,9 +391,8 @@ def remove_emoji_reaction_from_object(
|
||||||
object
|
object
|
||||||
) do
|
) do
|
||||||
reactions = get_cached_emoji_reactions(object)
|
reactions = get_cached_emoji_reactions(object)
|
||||||
|
|
||||||
new_reactions =
|
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 ->
|
nil ->
|
||||||
reactions
|
reactions
|
||||||
|
|
||||||
|
@ -386,9 +400,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)
|
||||||
|
|
|
@ -304,7 +304,6 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
_e ->
|
_e ->
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
emoji_reactions =
|
emoji_reactions =
|
||||||
object.data
|
object.data
|
||||||
|> Map.get("reactions", [])
|
|> Map.get("reactions", [])
|
||||||
|
@ -312,8 +311,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()
|
||||||
|
|
||||||
|
@ -569,10 +568,11 @@ 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: emoji,
|
||||||
count: length(users),
|
count: length(users),
|
||||||
|
url: url,
|
||||||
me: !!(current_user && current_user.ap_id in users)
|
me: !!(current_user && current_user.ap_id in users)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -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: []
|
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)
|
||||||
_ -> nil
|
_ -> nil
|
||||||
end)
|
end)
|
||||||
|> Stream.reject(&is_nil/1)
|
|> Stream.reject(&is_nil/1)
|
||||||
|
|
|
@ -11,13 +11,13 @@ 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,
|
||||||
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: url,
|
||||||
me: !!(user && user.ap_id in user_ap_ids)
|
me: !!(user && user.ap_id in user_ap_ids)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue