ChatMessage: Basic incoming handling.

This commit is contained in:
lain 2020-04-08 15:55:43 +02:00
parent 19335be24c
commit 3775683a04
12 changed files with 250 additions and 2 deletions

View 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.
"""

View file

@ -397,6 +397,7 @@ defp do_unreact_with_emoji(user, reaction_id, options) 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()}

View 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)

View file

@ -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

View file

@ -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

View file

@ -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

View 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 @@ def handle_incoming(
|> 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,

View file

@ -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

19
test/fixtures/create-chat-message.json vendored Normal file
View file

@ -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"
}

View file

@ -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

View file

@ -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