forked from AkkomaGang/akkoma
Transmogrifier: Move emoji reactions to common pipeline.
This commit is contained in:
parent
142bf0957c
commit
ad771546d8
8 changed files with 193 additions and 24 deletions
|
@ -10,6 +10,18 @@ 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
|
||||||
|
|
||||||
|
@spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
|
||||||
|
def emoji_react(actor, object, emoji) do
|
||||||
|
with {:ok, data, meta} <- like(actor, object) do
|
||||||
|
data =
|
||||||
|
data
|
||||||
|
|> Map.put("content", emoji)
|
||||||
|
|> Map.put("type", "EmojiReact")
|
||||||
|
|
||||||
|
{:ok, data, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
@spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
||||||
def like(actor, object) do
|
def like(actor, object) do
|
||||||
object_actor = User.get_cached_by_ap_id(object.data["actor"])
|
object_actor = User.get_cached_by_ap_id(object.data["actor"])
|
||||||
|
|
|
@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
|
||||||
|
|
||||||
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
||||||
def validate(object, meta)
|
def validate(object, meta)
|
||||||
|
@ -24,6 +25,16 @@ def validate(%{"type" => "Like"} = object, meta) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate(%{"type" => "EmojiReact"} = object, meta) do
|
||||||
|
with {:ok, object} <-
|
||||||
|
object
|
||||||
|
|> EmojiReactValidator.cast_and_validate()
|
||||||
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
|
object = stringify_keys(object |> Map.from_struct())
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def stringify_keys(object) do
|
def stringify_keys(object) do
|
||||||
object
|
object
|
||||||
|> Map.new(fn {key, val} -> {to_string(key), val} end)
|
|> Map.new(fn {key, val} -> {to_string(key), val} end)
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:id, Types.ObjectID, primary_key: true)
|
||||||
|
field(:type, :string)
|
||||||
|
field(:object, Types.ObjectID)
|
||||||
|
field(:actor, Types.ObjectID)
|
||||||
|
field(:context, :string)
|
||||||
|
field(:content, :string)
|
||||||
|
field(:to, {:array, :string}, default: [])
|
||||||
|
field(:cc, {:array, :string}, default: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
data
|
||||||
|
|> cast_data()
|
||||||
|
|> validate_data()
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> changeset(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, data) do
|
||||||
|
struct
|
||||||
|
|> cast(data, __schema__(:fields))
|
||||||
|
|> fix_after_cast()
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_after_cast(cng) do
|
||||||
|
cng
|
||||||
|
|> fix_context()
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_context(cng) do
|
||||||
|
object = get_field(cng, :object)
|
||||||
|
|
||||||
|
with nil <- get_field(cng, :context),
|
||||||
|
%Object{data: %{"context" => context}} <- Object.get_cached_by_ap_id(object) do
|
||||||
|
cng
|
||||||
|
|> put_change(:context, context)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
cng
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_emoji(cng) do
|
||||||
|
content = get_field(cng, :content)
|
||||||
|
|
||||||
|
if Pleroma.Emoji.is_unicode_emoji?(content) do
|
||||||
|
cng
|
||||||
|
else
|
||||||
|
cng
|
||||||
|
|> add_error(:content, "must be a single character emoji")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_data(data_cng) do
|
||||||
|
data_cng
|
||||||
|
|> validate_inclusion(:type, ["EmojiReact"])
|
||||||
|
|> validate_required([:id, :type, :object, :actor, :context, :to, :cc, :content])
|
||||||
|
|> validate_actor_presence()
|
||||||
|
|> validate_object_presence()
|
||||||
|
|> validate_emoji()
|
||||||
|
end
|
||||||
|
end
|
|
@ -23,6 +23,18 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Tasks this handles:
|
||||||
|
# - Add reaction to object
|
||||||
|
# - Set up notification
|
||||||
|
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
|
||||||
|
|
||||||
# Nothing to do
|
# Nothing to do
|
||||||
def handle(object, meta) do
|
def handle(object, meta) do
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
|
|
|
@ -656,7 +656,7 @@ def handle_incoming(
|
||||||
|> handle_incoming(options)
|
|> handle_incoming(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(%{"type" => "Like"} = data, _options) do
|
def handle_incoming(%{"type" => type} = data, _options) when type in ["Like", "EmojiReact"] 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
|
||||||
|
@ -666,27 +666,6 @@ def handle_incoming(%{"type" => "Like"} = data, _options) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
|
||||||
%{
|
|
||||||
"type" => "EmojiReact",
|
|
||||||
"object" => object_id,
|
|
||||||
"actor" => _actor,
|
|
||||||
"id" => id,
|
|
||||||
"content" => emoji
|
|
||||||
} = data,
|
|
||||||
_options
|
|
||||||
) do
|
|
||||||
with actor <- Containment.get_actor(data),
|
|
||||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
|
||||||
{:ok, object} <- get_obj_helper(object_id),
|
|
||||||
{:ok, activity, _object} <-
|
|
||||||
ActivityPub.react_with_emoji(actor, object, emoji, activity_id: id, local: false) do
|
|
||||||
{:ok, activity}
|
|
||||||
else
|
|
||||||
_e -> :error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data,
|
%{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data,
|
||||||
_options
|
_options
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
@ -8,6 +9,46 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "EmojiReacts" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"})
|
||||||
|
|
||||||
|
object = Pleroma.Object.get_by_ap_id(post_activity.data["object"])
|
||||||
|
|
||||||
|
{:ok, valid_emoji_react, []} = Builder.emoji_react(user, object, "👌")
|
||||||
|
|
||||||
|
%{user: user, post_activity: post_activity, valid_emoji_react: valid_emoji_react}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it validates a valid EmojiReact", %{valid_emoji_react: valid_emoji_react} do
|
||||||
|
assert {:ok, _, _} = ObjectValidator.validate(valid_emoji_react, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it is not valid without a 'content' field", %{valid_emoji_react: valid_emoji_react} do
|
||||||
|
without_content =
|
||||||
|
valid_emoji_react
|
||||||
|
|> Map.delete("content")
|
||||||
|
|
||||||
|
{:error, cng} = ObjectValidator.validate(without_content, [])
|
||||||
|
|
||||||
|
refute cng.valid?
|
||||||
|
assert {:content, {"can't be blank", [validation: :required]}} in cng.errors
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it is not valid with a non-emoji content field", %{valid_emoji_react: valid_emoji_react} do
|
||||||
|
without_emoji_content =
|
||||||
|
valid_emoji_react
|
||||||
|
|> Map.put("content", "x")
|
||||||
|
|
||||||
|
{:error, cng} = ObjectValidator.validate(without_emoji_content, [])
|
||||||
|
|
||||||
|
refute cng.valid?
|
||||||
|
|
||||||
|
assert {:content, {"must be a single character emoji", []}} in cng.errors
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "likes" do
|
describe "likes" do
|
||||||
setup do
|
setup do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
|
@ -15,6 +15,33 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "EmojiReact objects" do
|
||||||
|
setup do
|
||||||
|
poster = insert(:user)
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, post} = CommonAPI.post(poster, %{"status" => "hey"})
|
||||||
|
|
||||||
|
{:ok, emoji_react_data, []} = Builder.emoji_react(user, post.object, "👌")
|
||||||
|
{:ok, emoji_react, _meta} = ActivityPub.persist(emoji_react_data, local: true)
|
||||||
|
|
||||||
|
%{emoji_react: emoji_react, user: user, poster: poster}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "adds the reaction to the object", %{emoji_react: emoji_react, user: user} do
|
||||||
|
{:ok, emoji_react, _} = SideEffects.handle(emoji_react)
|
||||||
|
object = Object.get_by_ap_id(emoji_react.data["object"])
|
||||||
|
|
||||||
|
assert object.data["reaction_count"] == 1
|
||||||
|
assert ["👌", [user.ap_id]] in object.data["reactions"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "creates a notification", %{emoji_react: emoji_react, poster: poster} do
|
||||||
|
{:ok, emoji_react, _} = SideEffects.handle(emoji_react)
|
||||||
|
assert Repo.get_by(Notification, user_id: poster.id, activity_id: emoji_react.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "like objects" do
|
describe "like objects" do
|
||||||
setup do
|
setup do
|
||||||
poster = insert(:user)
|
poster = insert(:user)
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiReactHandlingTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Object
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
@ -29,6 +30,11 @@ test "it works for incoming emoji reactions" do
|
||||||
assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2"
|
assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2"
|
||||||
assert data["object"] == activity.data["object"]
|
assert data["object"] == activity.data["object"]
|
||||||
assert data["content"] == "👌"
|
assert data["content"] == "👌"
|
||||||
|
|
||||||
|
object = Object.get_by_ap_id(data["object"])
|
||||||
|
|
||||||
|
assert object.data["reaction_count"] == 1
|
||||||
|
assert match?([["👌", _]], object.data["reactions"])
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it reject invalid emoji reactions" do
|
test "it reject invalid emoji reactions" do
|
||||||
|
@ -42,7 +48,7 @@ test "it reject invalid emoji reactions" do
|
||||||
|> Map.put("object", activity.data["object"])
|
|> Map.put("object", activity.data["object"])
|
||||||
|> Map.put("actor", other_user.ap_id)
|
|> Map.put("actor", other_user.ap_id)
|
||||||
|
|
||||||
assert :error = Transmogrifier.handle_incoming(data)
|
assert {:error, _} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
data =
|
data =
|
||||||
File.read!("test/fixtures/emoji-reaction-no-emoji.json")
|
File.read!("test/fixtures/emoji-reaction-no-emoji.json")
|
||||||
|
@ -50,6 +56,6 @@ test "it reject invalid emoji reactions" do
|
||||||
|> Map.put("object", activity.data["object"])
|
|> Map.put("object", activity.data["object"])
|
||||||
|> Map.put("actor", other_user.ap_id)
|
|> Map.put("actor", other_user.ap_id)
|
||||||
|
|
||||||
assert :error = Transmogrifier.handle_incoming(data)
|
assert {:error, _} = Transmogrifier.handle_incoming(data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue