ChatMessage: Basic incoming handling.
authorlain <lain@soykaf.club>
Wed, 8 Apr 2020 13:55:43 +0000 (15:55 +0200)
committerlain <lain@soykaf.club>
Wed, 8 Apr 2020 13:55:43 +0000 (15:55 +0200)
12 files changed:
lib/pleroma/chat.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/object_validator.ex
lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex [moved from lib/pleroma/web/activity_pub/object_validators/create_validator.ex with 100% similarity]
lib/pleroma/web/activity_pub/object_validators/types/recipients.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/transmogrifier/chat_message_handling.ex [new file with mode: 0644]
test/fixtures/create-chat-message.json [new file with mode: 0644]
test/web/activity_pub/object_validators/types/recipients_test.exs [new file with mode: 0644]
test/web/activity_pub/transmogrifier/chat_message_test.exs [new file with mode: 0644]

index e2a8b8eba32faad170b3f99cf33eb1d76be11382..07ad62b97fbc63c0d63d782f7117800f1facc3cb 100644 (file)
@@ -10,7 +10,7 @@ defmodule Pleroma.Chat do
   alias Pleroma.Repo
 
   @moduledoc """
-  Chat keeps a reference to DirectMessage conversations between a user and an recipient. The recipient can be a user (for now) or a group (not implemented yet).
+  Chat keeps a reference to ChatMessage conversations between a user and an recipient. The recipient can be a user (for now) or a group (not implemented yet).
 
   It is a helper only, to make it easy to display a list of chats with other people, ordered by last bump. The actual messages are retrieved by querying the recipients of the ChatMessages.
   """
index 19286fd01a29f9f75dfb8ed78e9a2d94694368d3..0b48925016c57bc06bfe1b18dd0fdb11d0a93799 100644 (file)
@@ -397,6 +397,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
+  # TODO: Is this even used now?
   # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
   @spec like(User.t(), Object.t(), String.t() | nil, boolean()) ::
           {:ok, Activity.t(), Object.t()} | {:error, any()}
index dc4bce0595a12c409475206b2aed0e7222b7ce18..49cc7256127778ad738073ef443528a04840376f 100644 (file)
@@ -12,18 +12,46 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   alias Pleroma.Object
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
 
   @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
   def validate(object, meta)
 
   def validate(%{"type" => "Like"} = object, meta) do
     with {:ok, object} <-
-           object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do
+           object
+           |> LikeValidator.cast_and_validate()
+           |> Ecto.Changeset.apply_action(:insert) do
       object = stringify_keys(object |> Map.from_struct())
       {:ok, object, meta}
     end
   end
 
+  def validate(%{"type" => "ChatMessage"} = object, meta) do
+    with {:ok, object} <-
+           object
+           |> ChatMessageValidator.cast_and_apply() do
+      object = stringify_keys(object)
+      {:ok, object, meta}
+    end
+  end
+
+  def validate(%{"type" => "Create"} = object, meta) do
+    with {:ok, object} <-
+           object
+           |> CreateChatMessageValidator.cast_and_apply() do
+      object = stringify_keys(object)
+      {:ok, object, meta}
+    end
+  end
+
+  def stringify_keys(%{__struct__: _} = object) do
+    object
+    |> Map.from_struct()
+    |> stringify_keys
+  end
+
   def stringify_keys(object) do
     object
     |> Map.new(fn {key, val} -> {to_string(key), val} end)
diff --git a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
new file mode 100644 (file)
index 0000000..ab5be35
--- /dev/null
@@ -0,0 +1,58 @@
+# 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.ChatMessageValidator do
+  use Ecto.Schema
+
+  alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+
+  import Ecto.Changeset
+
+  @primary_key false
+  @derive Jason.Encoder
+
+  embedded_schema do
+    field(:id, Types.ObjectID, primary_key: true)
+    field(:to, Types.Recipients, default: [])
+    field(:type, :string)
+    field(:content, :string)
+    field(:actor, Types.ObjectID)
+    field(:published, Types.DateTime)
+  end
+
+  def cast_and_apply(data) do
+    data
+    |> cast_data
+    |> apply_action(:insert)
+  end
+
+  def cast_and_validate(data) do
+    data
+    |> cast_data()
+    |> validate_data()
+  end
+
+  def cast_data(data) do
+    %__MODULE__{}
+    |> changeset(data)
+  end
+
+  def fix(data) do
+    data
+    |> Map.put_new("actor", data["attributedTo"])
+  end
+
+  def changeset(struct, data) do
+    data = fix(data)
+
+    struct
+    |> cast(data, __schema__(:fields))
+  end
+
+  def validate_data(data_cng) do
+    data_cng
+    |> validate_inclusion(:type, ["ChatMessage"])
+    |> validate_required([:id, :actor, :to, :type, :content])
+  end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex
new file mode 100644 (file)
index 0000000..6593114
--- /dev/null
@@ -0,0 +1,35 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+# NOTES
+# - Can probably be a generic create validator
+# - doesn't embed, will only get the object id
+# - object has to be validated first, maybe with some meta info from the surrounding create
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do
+  use Ecto.Schema
+
+  alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+
+  import Ecto.Changeset
+
+  @primary_key false
+
+  embedded_schema do
+    field(:id, Types.ObjectID, primary_key: true)
+    field(:actor, Types.ObjectID)
+    field(:type, :string)
+    field(:to, Types.Recipients, default: [])
+    field(:object, Types.ObjectID)
+  end
+
+  def cast_and_apply(data) do
+    data
+    |> cast_data
+    |> apply_action(:insert)
+  end
+
+  def cast_data(data) do
+    cast(%__MODULE__{}, data, __schema__(:fields))
+  end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/types/recipients.ex b/lib/pleroma/web/activity_pub/object_validators/types/recipients.ex
new file mode 100644 (file)
index 0000000..5a30408
--- /dev/null
@@ -0,0 +1,23 @@
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.Recipients do
+  use Ecto.Type
+
+  def type, do: {:array, :string}
+
+  def cast(object) when is_binary(object) do
+    cast([object])
+  end
+
+  def cast([_ | _] = data), do: {:ok, data}
+
+  def cast(_) do
+    :error
+  end
+
+  def dump(data) do
+    {:ok, data}
+  end
+
+  def load(data) do
+    {:ok, data}
+  end
+end
index 0a8ad62ad28c2ceb04398b707fa34d65b63e6c10..becc35ea3b32768c591652a5a6537fcef3857fc2 100644 (file)
@@ -16,6 +16,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   alias Pleroma.Web.ActivityPub.ObjectValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
   alias Pleroma.Web.ActivityPub.Pipeline
+  alias Pleroma.Web.ActivityPub.Transmogrifier.ChatMessageHandling
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.Federator
@@ -612,6 +613,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> handle_incoming(options)
   end
 
+  def handle_incoming(
+        %{"type" => "Create", "object" => %{"type" => "ChatMessage"}} = data,
+        options
+      ),
+      do: ChatMessageHandling.handle_incoming(data, options)
+
   def handle_incoming(%{"type" => "Like"} = data, _options) do
     with {_, {:ok, cast_data_sym}} <-
            {:casting_data,
diff --git a/lib/pleroma/web/activity_pub/transmogrifier/chat_message_handling.ex b/lib/pleroma/web/activity_pub/transmogrifier/chat_message_handling.ex
new file mode 100644 (file)
index 0000000..b584373
--- /dev/null
@@ -0,0 +1,30 @@
+# 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.Transmogrifier.ChatMessageHandling do
+  alias Pleroma.Object
+  alias Pleroma.Web.ActivityPub.ObjectValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
+  alias Pleroma.Web.ActivityPub.Pipeline
+
+  def handle_incoming(
+        %{"type" => "Create", "object" => %{"type" => "ChatMessage"} = object_data} = data,
+        _options
+      ) do
+    with {_, {:ok, cast_data_sym}} <-
+           {:casting_data, data |> CreateChatMessageValidator.cast_and_apply()},
+         cast_data = ObjectValidator.stringify_keys(cast_data_sym),
+         {_, {:ok, object_cast_data_sym}} <-
+           {:casting_object_data, object_data |> ChatMessageValidator.cast_and_apply()},
+         object_cast_data = ObjectValidator.stringify_keys(object_cast_data_sym),
+         {_, {:ok, validated_object, _meta}} <-
+           {:validate_object, ObjectValidator.validate(object_cast_data, %{})},
+         {_, {:ok, _created_object}} <- {:persist_object, Object.create(validated_object)},
+         {_, {:ok, activity, _meta}} <-
+           {:common_pipeline, Pipeline.common_pipeline(cast_data, local: false)} do
+      {:ok, activity}
+    end
+  end
+end
diff --git a/test/fixtures/create-chat-message.json b/test/fixtures/create-chat-message.json
new file mode 100644 (file)
index 0000000..4aa17f4
--- /dev/null
@@ -0,0 +1,19 @@
+{
+    "actor": "http://2hu.gensokyo/users/raymoo",
+    "id": "http://2hu.gensokyo/objects/1",
+    "object": {
+        "attributedTo": "http://2hu.gensokyo/users/raymoo",
+        "content": "You expected a cute girl? Too bad.",
+        "id": "http://2hu.gensokyo/objects/2",
+        "published": "2020-02-12T14:08:20Z",
+        "to": [
+           "http://2hu.gensokyo/users/marisa"
+        ],
+        "type": "ChatMessage"
+    },
+    "published": "2018-02-12T14:08:20Z",
+    "to": [
+        "http://2hu.gensokyo/users/marisa"
+    ],
+    "type": "Create"
+}
diff --git a/test/web/activity_pub/object_validators/types/recipients_test.exs b/test/web/activity_pub/object_validators/types/recipients_test.exs
new file mode 100644 (file)
index 0000000..2f92187
--- /dev/null
@@ -0,0 +1,15 @@
+defmodule Pleroma.Web.ObjectValidators.Types.RecipientsTest do
+  alias Pleroma.Web.ActivityPub.ObjectValidators.Types.Recipients
+  use Pleroma.DataCase
+
+  test "it works with a list" do
+    list = ["https://lain.com/users/lain"]
+    assert {:ok, list} == Recipients.cast(list)
+  end
+
+  test "it turns a single string into a list" do
+    recipient = "https://lain.com/users/lain"
+
+    assert {:ok, [recipient]} == Recipients.cast(recipient)
+  end
+end
diff --git a/test/web/activity_pub/transmogrifier/chat_message_test.exs b/test/web/activity_pub/transmogrifier/chat_message_test.exs
new file mode 100644 (file)
index 0000000..aed62c5
--- /dev/null
@@ -0,0 +1,32 @@
+# 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.Transmogrifier.ChatMessageTest do
+  use Pleroma.DataCase
+
+  import Pleroma.Factory
+
+  alias Pleroma.Activity
+  alias Pleroma.Object
+  alias Pleroma.Web.ActivityPub.Transmogrifier
+
+  describe "handle_incoming" do
+    test "it insert it" do
+      data =
+        File.read!("test/fixtures/create-chat-message.json")
+        |> Poison.decode!()
+
+      author = insert(:user, ap_id: data["actor"], local: false)
+      recipient = insert(:user, ap_id: List.first(data["to"]), local: false)
+
+      {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(data)
+
+      assert activity.actor == author.ap_id
+      assert activity.recipients == [recipient.ap_id, author.ap_id]
+
+      %Object{} = object = Object.get_by_ap_id(activity.data["object"])
+      assert object
+    end
+  end
+end