diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index 7f9c071b3..67e65c7b9 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -23,17 +23,28 @@ def create(actor, object, recipients) do
}, []}
end
- def chat_message(actor, recipient, content) do
- {:ok,
- %{
- "id" => Utils.generate_object_id(),
- "actor" => actor.ap_id,
- "type" => "ChatMessage",
- "to" => [recipient],
- "content" => content,
- "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
- "emoji" => Emoji.Formatter.get_emoji_map(content)
- }, []}
+ def chat_message(actor, recipient, content, opts \\ []) do
+ basic = %{
+ "id" => Utils.generate_object_id(),
+ "actor" => actor.ap_id,
+ "type" => "ChatMessage",
+ "to" => [recipient],
+ "content" => content,
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "emoji" => Emoji.Formatter.get_emoji_map(content)
+ }
+
+ case opts[:attachment] do
+ %Object{data: attachment_data} ->
+ {
+ :ok,
+ Map.put(basic, "attachment", attachment_data),
+ []
+ }
+
+ _ ->
+ {:ok, basic, []}
+ end
end
@spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 20c7cceb6..d6c14f7b8 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -63,11 +63,18 @@ def stringify_keys(%{__struct__: _} = object) do
|> stringify_keys
end
- def stringify_keys(object) do
+ def stringify_keys(object) when is_map(object) do
object
- |> Map.new(fn {key, val} -> {to_string(key), val} end)
+ |> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)
end
+ def stringify_keys(object) when is_list(object) do
+ object
+ |> Enum.map(&stringify_keys/1)
+ end
+
+ def stringify_keys(object), do: object
+
def fetch_actor(object) do
with {:ok, actor} <- Types.ObjectID.cast(object["actor"]) do
User.get_or_fetch_by_ap_id(actor)
diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
new file mode 100644
index 000000000..16ed49051
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -0,0 +1,72 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
+ use Ecto.Schema
+
+ alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator
+
+ import Ecto.Changeset
+
+ @primary_key false
+ embedded_schema do
+ field(:type, :string)
+ field(:mediaType, :string)
+ field(:name, :string)
+
+ embeds_many(:url, UrlObjectValidator)
+ 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
+ data =
+ data
+ |> fix_media_type()
+ |> fix_url()
+
+ struct
+ |> cast(data, [:type, :mediaType, :name])
+ |> cast_embed(:url, required: true)
+ end
+
+ def fix_media_type(data) do
+ data
+ |> Map.put_new("mediaType", data["mimeType"])
+ end
+
+ def fix_url(data) do
+ case data["url"] do
+ url when is_binary(url) ->
+ data
+ |> Map.put(
+ "url",
+ [
+ %{
+ "href" => url,
+ "type" => "Link",
+ "mediaType" => data["mediaType"]
+ }
+ ]
+ )
+
+ _ ->
+ data
+ end
+ end
+
+ def validate_data(cng) do
+ cng
+ |> validate_required([:mediaType, :url, :type])
+ end
+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
index e87c1ac2e..99ffeba28 100644
--- a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
import Ecto.Changeset
import Pleroma.Web.ActivityPub.Transmogrifier, only: [fix_emoji: 1]
@@ -22,6 +23,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
field(:actor, Types.ObjectID)
field(:published, Types.DateTime)
field(:emoji, :map, default: %{})
+
+ embeds_one(:attachment, AttachmentValidator)
end
def cast_and_apply(data) do
@@ -51,7 +54,8 @@ def changeset(struct, data) do
data = fix(data)
struct
- |> cast(data, __schema__(:fields))
+ |> cast(data, List.delete(__schema__(:fields), :attachment))
+ |> cast_embed(:attachment)
end
def validate_data(data_cng) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
new file mode 100644
index 000000000..47e231150
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
@@ -0,0 +1,20 @@
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator do
+ use Ecto.Schema
+
+ alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+
+ import Ecto.Changeset
+ @primary_key false
+
+ embedded_schema do
+ field(:type, :string)
+ field(:href, Types.Uri)
+ field(:mediaType, :string)
+ end
+
+ def changeset(struct, data) do
+ struct
+ |> cast(data, __schema__(:fields))
+ |> validate_required([:type, :href, :mediaType])
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex
index 0fe0e07b2..8b9dc2e44 100644
--- a/lib/pleroma/web/api_spec/operations/chat_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/chat_operation.ex
@@ -236,7 +236,8 @@ def chat_message_create do
description: "POST body for creating an chat message",
type: :object,
properties: %{
- content: %Schema{type: :string, description: "The content of your message"}
+ content: %Schema{type: :string, description: "The content of your message"},
+ media_id: %Schema{type: :string, description: "The id of an upload"}
},
required: [:content],
example: %{
diff --git a/lib/pleroma/web/api_spec/schemas/chat_message.ex b/lib/pleroma/web/api_spec/schemas/chat_message.ex
index 7c93b0c83..89e062ddd 100644
--- a/lib/pleroma/web/api_spec/schemas/chat_message.ex
+++ b/lib/pleroma/web/api_spec/schemas/chat_message.ex
@@ -17,7 +17,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do
chat_id: %Schema{type: :string},
content: %Schema{type: :string},
created_at: %Schema{type: :string, format: :"date-time"},
- emojis: %Schema{type: :array}
+ emojis: %Schema{type: :array},
+ attachment: %Schema{type: :object, nullable: true}
},
example: %{
"account_id" => "someflakeid",
@@ -32,7 +33,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do
"url" => "https://dontbulling.me/emoji/Firefox.gif"
}
],
- "id" => "14"
+ "id" => "14",
+ "attachment" => nil
}
})
end
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index e428cc17d..38b5c6f7c 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -25,14 +25,16 @@ defmodule Pleroma.Web.CommonAPI do
require Pleroma.Constants
require Logger
- def post_chat_message(%User{} = user, %User{} = recipient, content) do
+ def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
with :ok <- validate_chat_content_length(content),
+ maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
{_, {:ok, chat_message_data, _meta}} <-
{:build_object,
Builder.chat_message(
user,
recipient.ap_id,
- content |> Formatter.html_escape("text/plain")
+ content |> Formatter.html_escape("text/plain"),
+ attachment: maybe_attachment
)},
{_, {:ok, create_activity_data, _meta}} <-
{:build_create_activity, Builder.create(user, chat_message_data, [recipient.ap_id])},
diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
index bedae73bd..450d85332 100644
--- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
@@ -36,14 +36,16 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation
def post_chat_message(
- %{body_params: %{content: content}, assigns: %{user: %{id: user_id} = user}} = conn,
+ %{body_params: %{content: content} = params, assigns: %{user: %{id: user_id} = user}} =
+ conn,
%{
id: id
}
) do
with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id),
%User{} = recipient <- User.get_cached_by_ap_id(chat.recipient),
- {:ok, activity} <- CommonAPI.post_chat_message(user, recipient, content),
+ {:ok, activity} <-
+ CommonAPI.post_chat_message(user, recipient, content, media_id: params[:media_id]),
message <- Object.normalize(activity) do
conn
|> put_view(ChatMessageView)
diff --git a/lib/pleroma/web/pleroma_api/views/chat_message_view.ex b/lib/pleroma/web/pleroma_api/views/chat_message_view.ex
index a821479ab..b088a8734 100644
--- a/lib/pleroma/web/pleroma_api/views/chat_message_view.ex
+++ b/lib/pleroma/web/pleroma_api/views/chat_message_view.ex
@@ -23,7 +23,10 @@ def render(
chat_id: chat_id |> to_string(),
account_id: User.get_cached_by_ap_id(chat_message["actor"]).id,
created_at: Utils.to_masto_date(chat_message["published"]),
- emojis: StatusView.build_emojis(chat_message["emoji"])
+ emojis: StatusView.build_emojis(chat_message["emoji"]),
+ attachment:
+ chat_message["attachment"] &&
+ StatusView.render("attachment.json", attachment: chat_message["attachment"])
}
end
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs
index 60db7187f..951ed7800 100644
--- a/test/web/activity_pub/object_validator_test.exs
+++ b/test/web/activity_pub/object_validator_test.exs
@@ -2,14 +2,41 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
use Pleroma.DataCase
alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.ObjectValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
+ describe "attachments" do
+ test "it turns mastodon attachments into our attachments" do
+ attachment = %{
+ "url" =>
+ "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
+ "type" => "Document",
+ "name" => nil,
+ "mediaType" => "image/jpeg"
+ }
+
+ {:ok, attachment} =
+ AttachmentValidator.cast_and_validate(attachment)
+ |> Ecto.Changeset.apply_action(:insert)
+
+ assert [
+ %{
+ href:
+ "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
+ type: "Link",
+ mediaType: "image/jpeg"
+ }
+ ] = attachment.url
+ end
+ end
+
describe "chat message create activities" do
test "it is invalid if the object already exists" do
user = insert(:user)
@@ -52,7 +79,28 @@ test "it is invalid if the object data has a different `to` or `actor` field" do
test "validates for a basic object we build", %{valid_chat_message: valid_chat_message} do
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
- assert object == valid_chat_message
+ assert Map.put(valid_chat_message, "attachment", nil) == object
+ end
+
+ test "validates for a basic object with an attachment", %{
+ valid_chat_message: valid_chat_message,
+ user: user
+ } do
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
+
+ valid_chat_message =
+ valid_chat_message
+ |> Map.put("attachment", attachment.data)
+
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert object["attachment"]
end
test "does not validate if the message is longer than the remote_limit", %{
diff --git a/test/web/activity_pub/object_validators/types/object_id_test.exs b/test/web/activity_pub/object_validators/types/object_id_test.exs
index 834213182..c8911948e 100644
--- a/test/web/activity_pub/object_validators/types/object_id_test.exs
+++ b/test/web/activity_pub/object_validators/types/object_id_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ObjectValidators.Types.ObjectIDTest do
alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID
use Pleroma.DataCase
diff --git a/test/web/pleroma_api/controllers/chat_controller_test.exs b/test/web/pleroma_api/controllers/chat_controller_test.exs
index cdb2683c8..72a9a91ff 100644
--- a/test/web/pleroma_api/controllers/chat_controller_test.exs
+++ b/test/web/pleroma_api/controllers/chat_controller_test.exs
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do
alias Pleroma.Chat
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.ActivityPub.ActivityPub
import Pleroma.Factory
@@ -49,6 +50,32 @@ test "it posts a message to the chat", %{conn: conn, user: user} do
assert result["content"] == "Hallo!!"
assert result["chat_id"] == chat.id |> to_string()
end
+
+ test "it works with an attachment", %{conn: conn, user: user} do
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
+
+ other_user = insert(:user)
+
+ {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
+
+ result =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/pleroma/chats/#{chat.id}/messages", %{
+ "content" => "Hallo!!",
+ "media_id" => to_string(upload.id)
+ })
+ |> json_response_and_validate_schema(200)
+
+ assert result["content"] == "Hallo!!"
+ assert result["chat_id"] == chat.id |> to_string()
+ end
end
describe "GET /api/v1/pleroma/chats/:id/messages" do
diff --git a/test/web/pleroma_api/views/chat_message_view_test.exs b/test/web/pleroma_api/views/chat_message_view_test.exs
index 5c4c8b0d5..a13a41daa 100644
--- a/test/web/pleroma_api/views/chat_message_view_test.exs
+++ b/test/web/pleroma_api/views/chat_message_view_test.exs
@@ -9,12 +9,21 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageViewTest do
alias Pleroma.Object
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.PleromaAPI.ChatMessageView
+ alias Pleroma.Web.ActivityPub.ActivityPub
import Pleroma.Factory
test "it displays a chat message" do
user = insert(:user)
recipient = insert(:user)
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
{:ok, activity} = CommonAPI.post_chat_message(user, recipient, "kippis :firefox:")
chat = Chat.get(user.id, recipient.ap_id)
@@ -30,7 +39,7 @@ test "it displays a chat message" do
assert chat_message[:created_at]
assert match?([%{shortcode: "firefox"}], chat_message[:emojis])
- {:ok, activity} = CommonAPI.post_chat_message(recipient, user, "gkgkgk")
+ {:ok, activity} = CommonAPI.post_chat_message(recipient, user, "gkgkgk", media_id: upload.id)
object = Object.normalize(activity)
@@ -40,5 +49,6 @@ test "it displays a chat message" do
assert chat_message_two[:content] == "gkgkgk"
assert chat_message_two[:account_id] == recipient.id
assert chat_message_two[:chat_id] == chat_message[:chat_id]
+ assert chat_message_two[:attachment]
end
end