|> 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
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
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
end
end
+
+ defmacro tag_fields do
+ quote bind_quoted: binding() do
+ embeds_many(:tag, TagValidator)
+ end
+ end
end
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@primary_key false
+ @emoji_regex ~r/:[A-Za-z_-]+:/
embedded_schema do
quote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
activity_fields()
+ tag_fields()
end
end
def changeset(struct, data) do
struct
- |> cast(data, __schema__(:fields))
+ |> cast(data, __schema__(:fields) -- [:tag])
+ |> cast_embed(:tag)
end
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)
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
{:ok, :not_federated}
end
else
- _e -> {:error, :badarg}
+ _e ->
+ {:error, :badarg}
end
end
end
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}
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
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
{: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
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(
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
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)
_e ->
nil
end
-
emoji_reactions =
object.data
|> Map.get("reactions", [])
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()
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
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)
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