@@ -23,17 +23,28 @@ defmodule Pleroma.Web.ActivityPub.Builder 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()} | |||
@@ -63,11 +63,18 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator 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) | |||
@@ -0,0 +1,72 @@ | |||
# 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.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 |
@@ -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 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator 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 | |||
@@ -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 |
@@ -236,7 +236,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation 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: %{ | |||
@@ -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 |
@@ -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])}, | |||
@@ -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) | |||
@@ -23,7 +23,10 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageView do | |||
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 | |||
@@ -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 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest 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", %{ | |||
@@ -1,3 +1,7 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.ObjectValidators.Types.ObjectIDTest do | |||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID | |||
use Pleroma.DataCase | |||
@@ -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 @@ defmodule Pleroma.Web.PleromaAPI.ChatControllerTest 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 | |||
@@ -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 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageViewTest 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 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageViewTest 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 |