Accept map of strings in ActivityDraft

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
tusooa 2023-01-02 21:40:46 -05:00 committed by marcin mikołajczak
parent e11d4b6923
commit 0d96c04019
7 changed files with 228 additions and 13 deletions

View file

@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
alias Pleroma.Activity
alias Pleroma.Emoji
alias Pleroma.MultiLanguage
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay
@ -216,19 +217,39 @@ def create(actor, object, recipients) do
@spec note(ActivityDraft.t()) :: {:ok, map(), keyword()}
def note(%ActivityDraft{} = draft) do
content_fields =
if draft.content_html_map do
%{
"contentMap" => draft.content_html_map,
"content" => MultiLanguage.map_to_str(draft.content_html_map, multiline: true)
}
else
%{"content" => draft.content_html}
end
summary_fields =
if draft.summary_map do
%{
"summaryMap" => draft.summary_map,
"summary" => MultiLanguage.map_to_str(draft.summary_map, multiline: false)
}
else
%{"summary" => draft.summary}
end
data =
%{
"type" => "Note",
"to" => draft.to,
"cc" => draft.cc,
"content" => draft.content_html,
"summary" => draft.summary,
"sensitive" => draft.sensitive,
"context" => draft.context,
"attachment" => draft.attachments,
"actor" => draft.user.ap_id,
"tag" => Keyword.values(draft.tags) |> Enum.uniq()
}
|> Map.merge(content_fields)
|> Map.merge(summary_fields)
|> add_in_reply_to(draft.in_reply_to)
|> add_quote(draft.quote_post)
|> Map.merge(draft.extra)

View file

@ -561,6 +561,12 @@ defp create_request do
description:
"Text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided."
},
status_map:
Helpers.multilang_map_of(%Schema{
type: :string,
description:
"Text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided."
}),
media_ids: %Schema{
nullable: true,
type: :array,
@ -584,6 +590,12 @@ defp create_request do
description:
"Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
},
spoiler_text_map:
Helpers.multilang_map_of(%Schema{
type: :string,
description:
"Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
}),
scheduled_at: %Schema{
type: :string,
format: :"date-time",
@ -592,9 +604,20 @@ defp create_request do
"ISO 8601 Datetime at which to schedule a status. Providing this parameter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future."
},
language: %Schema{
type: :string,
nullable: true,
description: "ISO 639 language code for this status."
oneOf: [
%Schema{
type: :string,
nullable: true,
description: "ISO 639 language code for this status."
},
%Schema{
type: :array,
items: %Schema{
type: :string,
description: "ISO 639 language code for this status."
}
}
]
},
# Pleroma-specific properties:
preview: %Schema{

View file

@ -26,7 +26,9 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
user: nil,
params: %{},
status: nil,
status_map: nil,
summary: nil,
summary_map: nil,
full_payload: nil,
attachments: [],
in_reply_to: nil,
@ -37,6 +39,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
extra: nil,
emoji: %{},
content_html: nil,
content_html_map: nil,
mentions: [],
tags: [],
to: [],
@ -149,14 +152,33 @@ defp put_params(draft, params) do
%__MODULE__{draft | params: params}
end
defp status(%{params: %{status_map: status_map}} = draft) do
%__MODULE__{draft | status_map: status_map}
end
defp status(%{params: %{status: status}} = draft) do
%__MODULE__{draft | status: String.trim(status)}
end
defp summary(%{params: %{spoiler_text_map: spoiler_text_map}} = draft) do
%__MODULE__{draft | summary_map: spoiler_text_map}
end
defp summary(%{params: params} = draft) do
%__MODULE__{draft | summary: Map.get(params, :spoiler_text, "")}
end
defp full_payload(%{status_map: status_map, summary_map: summary_map} = draft) do
status = status_map |> Enum.reduce("", fn {_lang, content}, acc -> acc <> content end)
summary = summary_map |> Enum.reduce("", fn {_lang, content}, acc -> acc <> content end)
full_payload = String.trim(status <> summary)
case Utils.validate_character_limit(full_payload, draft.attachments) do
:ok -> %__MODULE__{draft | full_payload: full_payload}
{:error, message} -> add_error(draft, message)
end
end
defp full_payload(%{status: status, summary: summary} = draft) do
full_payload = String.trim(status <> summary)
@ -265,7 +287,9 @@ defp poll(draft) do
end
defp content(%{mentions: mentions} = draft) do
{content_html, mentioned_users, tags} = Utils.make_content_html(draft)
{content_html_or_map, mentioned_users, tags} = Utils.make_content_html(draft)
{content_html, content_html_map} = differentiate_string_map(content_html_or_map)
mentioned_ap_ids =
Enum.map(mentioned_users, fn {_, mentioned_user} -> mentioned_user.ap_id end)
@ -275,7 +299,13 @@ defp content(%{mentions: mentions} = draft) do
|> Kernel.++(mentioned_ap_ids)
|> Utils.get_addressed_users(draft.params[:to])
%__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags}
%__MODULE__{
draft
| content_html: content_html,
content_html_map: content_html_map,
mentions: mentions,
tags: tags
}
end
defp to_and_cc(draft) do
@ -341,10 +371,12 @@ defp object(draft) do
object =
note_data
|> Map.put("emoji", emoji)
|> Map.put("source", %{
"content" => draft.status,
"mediaType" => Utils.get_content_type(draft.params[:content_type])
})
|> Map.put(
"source",
Map.merge(get_source_map(draft), %{
"mediaType" => Utils.get_content_type(draft.params[:content_type])
})
)
|> Map.put("generator", draft.params[:generator])
|> Map.put("content_type", draft.params[:content_type])
|> Map.put("language", draft.language)
@ -461,4 +493,18 @@ defp add_error(draft, message) do
defp validate(%{valid?: true} = draft), do: {:ok, draft}
defp validate(%{errors: [message | _]}), do: {:error, message}
defp differentiate_string_map(%{} = map), do: {nil, map}
defp differentiate_string_map(str) when is_binary(str), do: {str, nil}
defp get_source_map(%{status_map: status_map} = _draft) do
%{
"content" => Pleroma.MultiLanguage.map_to_str(status_map, mutiline: true),
"contentMap" => status_map
}
end
defp get_source_map(%{status: status} = _draft) do
%{"content" => status}
end
end

View file

@ -231,7 +231,7 @@ def make_content_html(%ActivityDraft{} = draft) do
[]
end
draft.status
draft
|> format_input(content_type, options)
|> maybe_add_attachments(draft.attachments, attachment_links)
end
@ -253,6 +253,15 @@ def make_context(_, _), do: Utils.generate_context_id()
def maybe_add_attachments(parsed, _attachments, false = _no_links), do: parsed
def maybe_add_attachments({%{} = text_map, mentions, tags}, attachments, _no_links) do
text_map =
Enum.reduce(text_map, %{}, fn {lang, text}, acc ->
Map.put(acc, lang, add_attachments(text, attachments))
end)
{text_map, mentions, tags}
end
def maybe_add_attachments({text, mentions, tags}, attachments, _no_links) do
text = add_attachments(text, attachments)
{text, mentions, tags}
@ -273,6 +282,31 @@ defp build_attachment_link(_), do: ""
def format_input(text, format, options \\ [])
def format_input(%ActivityDraft{status_map: status_map} = _draft, format, options)
when is_map(status_map) do
{content_map, mentions, tags} =
Enum.reduce(
status_map,
{%{}, [], []},
fn {lang, status}, {content_map, mentions, tags} ->
{cur_content, cur_mentions, cur_tags} = format_input(status, format, options)
{
Map.put(content_map, lang, cur_content),
mentions ++ cur_mentions,
tags ++ cur_tags
}
end
)
{content_map, Enum.uniq(mentions), Enum.uniq(tags)}
end
def format_input(%ActivityDraft{status: status} = _draft, format, options)
when is_binary(status) do
format_input(status, format, options)
end
@doc """
Formatting text to plain text, BBCode, HTML, or Markdown
"""

View file

@ -45,6 +45,32 @@ test "returns note data" do
assert {:ok, ^expected, []} = Builder.note(draft)
end
test "accepts multilang" do
user = insert(:user)
draft = %ActivityDraft{
user: user,
to: [user.ap_id],
context: "2hu",
content_html_map: %{"a" => "mew", "b" => "lol"},
tags: [],
summary_map: %{"a" => "mew", "b" => "lol"},
cc: [],
extra: %{}
}
assert {:ok,
%{
"contentMap" => %{"a" => "mew", "b" => "lol"},
"content" => content,
"summaryMap" => %{"a" => "mew", "b" => "lol"},
"summary" => summary
}, []} = Builder.note(draft)
assert is_binary(content)
assert is_binary(summary)
end
test "quote post" do
user = insert(:user)
note = insert(:note)

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI.ActivityDraftTest do
@ -10,6 +10,30 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraftTest do
import Pleroma.Factory
describe "multilang processing" do
setup do
[user: insert(:user)]
end
test "content", %{user: user} do
{:ok, draft} =
ActivityDraft.create(user, %{
status_map: %{"a" => "mew mew", "b" => "lol lol"},
spoiler_text_map: %{"a" => "mew", "b" => "lol"}
})
assert %{
"contentMap" => %{"a" => "mew mew", "b" => "lol lol"},
"content" => content,
"summaryMap" => %{"a" => "mew", "b" => "lol"},
"summary" => summary
} = draft.object
assert is_binary(content)
assert is_binary(summary)
end
end
test "create/2 with a quote post" do
user = insert(:user)
another_user = insert(:user)

View file

@ -81,6 +81,47 @@ test "works for bare text/plain" do
assert output == expected
end
test "works for multilang" do
draft = %ActivityDraft{
status_map: %{
"a" => "mew",
"b" => "lol"
}
}
expected = %{"a" => "mew", "b" => "lol"}
{output, [], []} = Utils.format_input(draft, "text/plain")
assert output == expected
end
test "works for multilang, mentions and tags" do
user1 = insert(:user)
user2 = insert(:user)
user3 = insert(:user)
draft = %ActivityDraft{
status_map: %{
"a" => "mew, @#{user1.nickname} @#{user2.nickname} #foo #bar",
"b" => "lol, @#{user2.nickname} @#{user3.nickname} #bar #lol"
}
}
{_, mentions, tags} = Utils.format_input(draft, "text/plain")
mentions = Enum.map(mentions, fn {_, user} -> user.ap_id end)
tags = Enum.map(tags, fn {_, tag} -> tag end)
assert [_, _, _] = mentions
assert user1.ap_id in mentions
assert user2.ap_id in mentions
assert user3.ap_id in mentions
assert [_, _, _] = tags
assert "foo" in tags
assert "bar" in tags
assert "lol" in tags
end
test "works for bare text/html" do
text = "<p>hello world!</p>"
expected = "<p>hello world!</p>"