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")
|
||||
|> 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue