From 00591d90163ec860dc5de24f3045f977980806bd Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 28 Dec 2022 14:17:21 -0500 Subject: [PATCH 01/38] Add support for *Map attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../object_validators/map_of_string.ex | 29 +++++++++++++ .../object_validators/attachment_validator.ex | 3 +- .../object_validators/common_fields.ex | 2 + .../question_options_validator.ex | 5 ++- .../object_validators/map_of_string_test.exs | 43 +++++++++++++++++++ .../article_note_page_validator_test.exs | 25 +++++++++++ .../attachment_validator_test.exs | 20 +++++++++ .../question_options_validator_test.exs | 27 ++++++++++++ 8 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex create mode 100644 test/pleroma/ecto_type/activity_pub/object_validators/map_of_string_test.exs create mode 100644 test/pleroma/web/activity_pub/object_validators/question_options_validator_test.exs diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex new file mode 100644 index 000000000..9c610a64b --- /dev/null +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex @@ -0,0 +1,29 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString do + use Ecto.Type + + def type, do: :map + + def cast(object) when is_map(object) do + data = + object + |> Enum.reduce(%{}, fn + {lang, value}, acc when is_binary(lang) and is_binary(value) -> + Map.put(acc, lang, value) + + _, acc -> + acc + end) + + {:ok, data} + end + + def cast(_), do: :error + + def dump(data), do: {:ok, data} + + def load(data), do: {:ok, data} +end diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex index 01960da83..c96717e78 100644 --- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex @@ -15,6 +15,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do field(:type, :string, default: "Link") field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream") field(:name, :string) + field(:nameMap, ObjectValidators.MapOfString) field(:blurhash, :string) embeds_many :url, UrlObjectValidator, primary_key: false do @@ -44,7 +45,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do |> fix_url() struct - |> cast(data, [:id, :type, :mediaType, :name, :blurhash]) + |> cast(data, [:id, :type, :mediaType, :name, :nameMap, :blurhash]) |> cast_embed(:url, with: &url_changeset/2, required: true) end diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex index 22cf0cc05..b626ccca6 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex @@ -50,7 +50,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do embeds_many(:tag, TagValidator) field(:name, :string) + field(:nameMap, ObjectValidators.MapOfString) field(:summary, :string) + field(:summaryMap, ObjectValidators.MapOfString) field(:context, :string) diff --git a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex index 8d7f7b9fa..bbd1b25cc 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex @@ -7,10 +7,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator do import Ecto.Changeset + alias Pleroma.EctoType.ActivityPub.ObjectValidators + @primary_key false embedded_schema do field(:name, :string) + field(:nameMap, ObjectValidators.MapOfString) embeds_one :replies, Replies, primary_key: false do field(:totalItems, :integer) @@ -22,7 +25,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator do def changeset(struct, data) do struct - |> cast(data, [:name, :type]) + |> cast(data, [:name, :nameMap, :type]) |> cast_embed(:replies, with: &replies_changeset/2) |> validate_inclusion(:type, ["Note"]) |> validate_required([:name, :type]) diff --git a/test/pleroma/ecto_type/activity_pub/object_validators/map_of_string_test.exs b/test/pleroma/ecto_type/activity_pub/object_validators/map_of_string_test.exs new file mode 100644 index 000000000..aecf482e3 --- /dev/null +++ b/test/pleroma/ecto_type/activity_pub/object_validators/map_of_string_test.exs @@ -0,0 +1,43 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfStringTest do + alias Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString + use Pleroma.DataCase, async: true + + test "it validates" do + data = %{ + "en-US" => "mew mew", + "en-GB" => "meow meow" + } + + assert {:ok, ^data} = MapOfString.cast(data) + end + + test "it validates empty strings" do + data = %{ + "en-US" => "mew mew", + "en-GB" => "" + } + + assert {:ok, ^data} = MapOfString.cast(data) + end + + test "it ignores non-strings within the map" do + data = %{ + "en-US" => "mew mew", + "en-GB" => 123 + } + + assert {:ok, validated_data} = MapOfString.cast(data) + + assert validated_data == %{"en-US" => "mew mew"} + end + + test "it complains with non-map data" do + assert :error = MapOfString.cast("mew") + assert :error = MapOfString.cast(["mew"]) + assert :error = MapOfString.cast([%{"en-US" => "mew"}]) + end +end diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index 511637e1d..dedd38951 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -37,6 +37,31 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest note = insert(:note) %{valid?: true} = ArticleNotePageValidator.cast_and_validate(note.data) end + + test "Note with contentMap and summaryMap", %{note: note} do + summary_map = %{ + "en-US" => "mew", + "en-GB" => "meow" + } + + content_map = %{ + "en-US" => "mew mew", + "en-GB" => "meow meow" + } + + note = + note + |> Map.put("summaryMap", summary_map) + |> Map.put("contentMap", content_map) + + assert %{ + valid?: true, + changes: %{ + summaryMap: ^summary_map, + contentMap: ^content_map + } + } = ArticleNotePageValidator.cast_and_validate(note) + end end describe "Note with history" do diff --git a/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs index a615c1d9a..e433e9cd9 100644 --- a/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs @@ -42,6 +42,26 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidatorTest do assert attachment.mediaType == "application/octet-stream" end + test "works with nameMap" do + attachment_data = %{ + "mediaType" => "", + "name" => "", + "nameMap" => %{ + "en-US" => "mew mew", + "en-GB" => "meow meow" + }, + "summary" => "298p3RG7j27tfsZ9RQ.jpg", + "type" => "Document", + "url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg" + } + + assert {:ok, attachment} = + AttachmentValidator.cast_and_validate(attachment_data) + |> Ecto.Changeset.apply_action(:insert) + + assert attachment.nameMap == attachment_data["nameMap"] + end + test "works with an unknown but valid mime type" do attachment = %{ "mediaType" => "x-custom/x-type", diff --git a/test/pleroma/web/activity_pub/object_validators/question_options_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/question_options_validator_test.exs new file mode 100644 index 000000000..87a0bafa0 --- /dev/null +++ b/test/pleroma/web/activity_pub/object_validators/question_options_validator_test.exs @@ -0,0 +1,27 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidatorTest do + use Pleroma.DataCase, async: true + + alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator + + describe "Validates Question options" do + test "" do + name_map = %{ + "en-US" => "mew", + "en-GB" => "meow" + } + + data = %{ + "type" => "Note", + "name" => "mew", + "nameMap" => name_map + } + + assert %{valid?: true, changes: %{nameMap: ^name_map}} = + QuestionOptionsValidator.changeset(%QuestionOptionsValidator{}, data) + end + end +end From 8822dc1191b5947ae553b54c4bcd381887ed8685 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 28 Dec 2022 15:13:34 -0500 Subject: [PATCH 02/38] Add functions to process multi-language content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- config/config.exs | 6 ++++ lib/pleroma/multi_language.ex | 48 ++++++++++++++++++++++++++ test/pleroma/multi_language_test.exs | 50 ++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 lib/pleroma/multi_language.ex create mode 100644 test/pleroma/multi_language_test.exs diff --git a/config/config.exs b/config/config.exs index 6b1cea8cd..2e3f20a96 100644 --- a/config/config.exs +++ b/config/config.exs @@ -931,6 +931,12 @@ config :pleroma, ConcurrentLimiter, [ config :pleroma, Pleroma.Web.WebFinger, domain: nil, update_nickname_on_user_fetch: true +config :pleroma, Pleroma.MultiLanguage, + template: "
{content}
", + separator: "


", + single_line_template: "[{code}] {content}", + single_line_separator: " | " + config :pleroma, Pleroma.Language.Translation, allow_unauthenticated: false, allow_remote: true config :geospatial, Geospatial.Service, service: Geospatial.Providers.Nominatim diff --git a/lib/pleroma/multi_language.ex b/lib/pleroma/multi_language.ex new file mode 100644 index 000000000..4e0b735e2 --- /dev/null +++ b/lib/pleroma/multi_language.ex @@ -0,0 +1,48 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.MultiLanguage do + defp template(:multi), do: Pleroma.Config.get([__MODULE__, :template]) + defp template(:single), do: Pleroma.Config.get([__MODULE__, :single_line_template]) + + defp sep(:multi), do: Pleroma.Config.get([__MODULE__, :separator]) + defp sep(:single), do: Pleroma.Config.get([__MODULE__, :single_line_separator]) + + def map_to_str(data, opts \\ []) do + map_to_str_impl(data, if(opts[:multiline], do: :multi, else: :single)) + end + + defp map_to_str_impl(data, mode) do + with ks <- Map.keys(data), + [_, _ | _] <- ks, + ks <- Enum.sort(ks) do + template = template(mode) + + ks + |> Enum.map(fn lang -> + format_template(template, %{code: lang, content: data[lang]}) + end) + |> Enum.join(sep(mode)) + else + [lang] -> data[lang] + _ -> "" + end + end + + def str_to_map(data) do + %{"und" => data} + end + + def format_template(template, %{code: code, content: content}) do + template + |> String.replace( + ["{code}", "{content}"], + fn + "{code}" -> code + "{content}" -> content + end, + global: true + ) + end +end diff --git a/test/pleroma/multi_language_test.exs b/test/pleroma/multi_language_test.exs new file mode 100644 index 000000000..15634d370 --- /dev/null +++ b/test/pleroma/multi_language_test.exs @@ -0,0 +1,50 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.MultiLanguageTest do + use Pleroma.DataCase, async: true + + alias Pleroma.MultiLanguage + + describe "map_to_str" do + setup do + %{ + data: %{ + "en-US" => "mew", + "en-GB" => "meow" + } + } + end + + test "single line", %{data: data} do + assert MultiLanguage.map_to_str(data) == "[en-GB] meow | [en-US] mew" + end + + test "multi line", %{data: data} do + assert MultiLanguage.map_to_str(data, multiline: true) == + "
meow



mew
" + end + + test "only one language" do + data = %{"some" => "foo"} + assert MultiLanguage.map_to_str(data) == "foo" + assert MultiLanguage.map_to_str(data, multiline: true) == "foo" + end + + test "resistent to tampering" do + data = %{ + "en-US" => "mew {code} {content}", + "en-GB" => "meow {code} {content}" + } + + assert MultiLanguage.map_to_str(data) == "[en-GB] meow {code} {content} | [en-US] mew {code} {content}" + end + end + + describe "str_to_map" do + test "" do + assert MultiLanguage.str_to_map("foo") == %{"und" => "foo"} + end + end +end From dbebd7fbf44a8629bfc3a1b6ccc625aedf20ad17 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 28 Dec 2022 15:23:58 -0500 Subject: [PATCH 03/38] Validate language codes in MapOfString --- .../object_validators/map_of_string.ex | 20 ++++++++++++++++++- .../object_validators/map_of_string_test.exs | 12 +++++++++++ test/pleroma/multi_language_test.exs | 3 ++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex index 9c610a64b..441c8c181 100644 --- a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex @@ -12,7 +12,11 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString do object |> Enum.reduce(%{}, fn {lang, value}, acc when is_binary(lang) and is_binary(value) -> - Map.put(acc, lang, value) + if is_good_locale_code?(lang) do + Map.put(acc, lang, value) + else + acc + end _, acc -> acc @@ -21,6 +25,20 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString do {:ok, data} end + defp is_good_locale_code?(code) do + code + |> String.codepoints() + |> Enum.all?(&valid_char?/1) + end + + # [a-zA-Z0-9-] + defp valid_char?(char) do + ("a" <= char and char <= "z") or + ("A" <= char and char <= "Z") or + ("0" <= char and char <= "9") or + char == "-" + end + def cast(_), do: :error def dump(data), do: {:ok, data} diff --git a/test/pleroma/ecto_type/activity_pub/object_validators/map_of_string_test.exs b/test/pleroma/ecto_type/activity_pub/object_validators/map_of_string_test.exs index aecf482e3..4ee179dc8 100644 --- a/test/pleroma/ecto_type/activity_pub/object_validators/map_of_string_test.exs +++ b/test/pleroma/ecto_type/activity_pub/object_validators/map_of_string_test.exs @@ -35,6 +35,18 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfStringTest do assert validated_data == %{"en-US" => "mew mew"} end + test "it ignores bad locale codes" do + data = %{ + "en-US" => "mew mew", + "en_GB" => "meow meow", + "en<<#@!$#!@%!GB" => "meow meow" + } + + assert {:ok, validated_data} = MapOfString.cast(data) + + assert validated_data == %{"en-US" => "mew mew"} + end + test "it complains with non-map data" do assert :error = MapOfString.cast("mew") assert :error = MapOfString.cast(["mew"]) diff --git a/test/pleroma/multi_language_test.exs b/test/pleroma/multi_language_test.exs index 15634d370..aea25f117 100644 --- a/test/pleroma/multi_language_test.exs +++ b/test/pleroma/multi_language_test.exs @@ -38,7 +38,8 @@ defmodule Pleroma.MultiLanguageTest do "en-GB" => "meow {code} {content}" } - assert MultiLanguage.map_to_str(data) == "[en-GB] meow {code} {content} | [en-US] mew {code} {content}" + assert MultiLanguage.map_to_str(data) == + "[en-GB] meow {code} {content} | [en-US] mew {code} {content}" end end From d86a1e73e97f494243e26fb2e5d5227ec41c2536 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 28 Dec 2022 15:29:29 -0500 Subject: [PATCH 04/38] Fix compile warning --- .../ecto_type/activity_pub/object_validators/map_of_string.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex index 441c8c181..dc071cbfc 100644 --- a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex @@ -25,6 +25,8 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString do {:ok, data} end + def cast(_), do: :error + defp is_good_locale_code?(code) do code |> String.codepoints() @@ -39,8 +41,6 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString do char == "-" end - def cast(_), do: :error - def dump(data), do: {:ok, data} def load(data), do: {:ok, data} From 4bcae3597f906af57e2a15bd06d6e24be0223942 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 28 Dec 2022 15:39:37 -0500 Subject: [PATCH 05/38] Treat empty maps as nils --- .../object_validators/map_of_string.ex | 6 +++++- lib/pleroma/web/activity_pub/transmogrifier.ex | 3 ++- .../article_note_page_validator_test.exs | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex index dc071cbfc..6a971f27e 100644 --- a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex @@ -22,7 +22,11 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString do acc end) - {:ok, data} + if data == %{} do + {:ok, nil} + else + {:ok, data} + end end def cast(_), do: :error diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index c5871d2fc..63192aefe 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -345,7 +345,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_content_map(%{"content" => content} = object) when not_empty_string(content), do: object # content map usually only has one language so this will do for now. - def fix_content_map(%{"contentMap" => content_map} = object) do + def fix_content_map(%{"contentMap" => content_map} = object) + when is_map(content_map) and content_map != %{} do content_groups = Map.to_list(content_map) {_, content} = Enum.at(content_groups, 0) diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index dedd38951..f92f95c6e 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -62,6 +62,21 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest } } = ArticleNotePageValidator.cast_and_validate(note) end + + test "Note with empty *Map", %{note: note} do + note = + note + |> Map.put("summaryMap", %{"und" => "mew"}) + |> Map.put("contentMap", %{}) + + assert %{ + valid?: true, + changes: changes + } = ArticleNotePageValidator.cast_and_validate(note) + + assert Map.has_key?(changes, :summaryMap) + refute Map.has_key?(changes, :contentMap) + end end describe "Note with history" do From 5c62c50e9b5309f0368ca66b3b3566868594d3b5 Mon Sep 17 00:00:00 2001 From: tusooa Date: Thu, 29 Dec 2022 12:13:58 -0500 Subject: [PATCH 06/38] Generate * from *Map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/multi_language.ex | 2 +- .../object_validators/article_note_page_validator.ex | 4 +++- .../object_validators/audio_image_video_validator.ex | 3 +++ .../web/activity_pub/object_validators/common_fixes.ex | 9 +++++++++ .../activity_pub/object_validators/question_validator.ex | 3 +++ .../article_note_page_validator_test.exs | 6 ++++++ 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/multi_language.ex b/lib/pleroma/multi_language.ex index 4e0b735e2..0c02757ee 100644 --- a/lib/pleroma/multi_language.ex +++ b/lib/pleroma/multi_language.ex @@ -26,7 +26,7 @@ defmodule Pleroma.MultiLanguage do |> Enum.join(sep(mode)) else [lang] -> data[lang] - _ -> "" + _ -> nil end end diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex index 15d0b7dcd..bd2667b2c 100644 --- a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex @@ -86,7 +86,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do |> fix_attachments() |> CommonFixes.fix_quote_url() |> Transmogrifier.fix_emoji() - |> Transmogrifier.fix_content_map() + |> CommonFixes.fix_multilang_field("content", "contentMap", multiline: true) + |> CommonFixes.fix_multilang_field("summary", "summaryMap", multiline: false) + |> CommonFixes.fix_multilang_field("name", "nameMap", multiline: false) |> CommonFixes.maybe_add_language() |> CommonFixes.maybe_add_content_map() end diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex index 65ac6bb93..c500bfd52 100644 --- a/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex @@ -102,6 +102,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator do |> CommonFixes.fix_quote_url() |> Transmogrifier.fix_emoji() |> fix_url() + |> CommonFixes.fix_multilang_field("content", "contentMap", multiline: true) + |> CommonFixes.fix_multilang_field("summary", "summaryMap", multiline: false) + |> CommonFixes.fix_multilang_field("name", "nameMap", multiline: false) |> fix_content() end diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex index 3357f57a5..ea90e0819 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -194,4 +194,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do end def maybe_add_content_map(object), do: object + + def fix_multilang_field(data, str_field, map_field, opts \\ []) do + with %{} = map <- data[map_field], + str when is_binary(str) <- Pleroma.MultiLanguage.map_to_str(map, opts) do + Map.put(data, str_field, str) + else + _ -> data + end + end end diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex index b28f096e5..4996a90aa 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -67,6 +67,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do |> CommonFixes.fix_quote_url() |> Transmogrifier.fix_emoji() |> fix_closed() + |> CommonFixes.fix_multilang_field("content", "contentMap", multiline: true) + |> CommonFixes.fix_multilang_field("summary", "summaryMap", multiline: false) + |> CommonFixes.fix_multilang_field("name", "nameMap", multiline: false) end def changeset(struct, data) do diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index f92f95c6e..1f2c5a04c 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -54,9 +54,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest |> Map.put("summaryMap", summary_map) |> Map.put("contentMap", content_map) + expected_summary = Pleroma.MultiLanguage.map_to_str(summary_map, multiline: false) + expected_content = Pleroma.MultiLanguage.map_to_str(content_map, multiline: true) + assert %{ valid?: true, changes: %{ + summary: ^expected_summary, + content: ^expected_content, summaryMap: ^summary_map, contentMap: ^content_map } @@ -74,6 +79,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest changes: changes } = ArticleNotePageValidator.cast_and_validate(note) + assert changes.content == note["content"] assert Map.has_key?(changes, :summaryMap) refute Map.has_key?(changes, :contentMap) end From 4cfd7b4f799746b1d7f6e5f1ab11f7a0d6618332 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 1 Jan 2023 21:46:57 -0500 Subject: [PATCH 07/38] Convert nameMap in question options --- .../object_validators/question_options_validator.ex | 12 +++++++++++- .../question_options_validator_test.exs | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex index bbd1b25cc..2f80fb9b9 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex @@ -8,11 +8,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator do import Ecto.Changeset alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes @primary_key false embedded_schema do field(:name, :string) + field(:nameRendered, :string) field(:nameMap, ObjectValidators.MapOfString) embeds_one :replies, Replies, primary_key: false do @@ -23,9 +25,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator do field(:type, :string, default: "Note") end + defp fix(data) do + data + # name is used in Answers, so better not change it + |> CommonFixes.fix_multilang_field("nameRendered", "nameMap", multiline: false) + end + def changeset(struct, data) do + data = fix(data) + struct - |> cast(data, [:name, :nameMap, :type]) + |> cast(data, [:name, :nameRendered, :nameMap, :type]) |> cast_embed(:replies, with: &replies_changeset/2) |> validate_inclusion(:type, ["Note"]) |> validate_required([:name, :type]) diff --git a/test/pleroma/web/activity_pub/object_validators/question_options_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/question_options_validator_test.exs index 87a0bafa0..a45c205cf 100644 --- a/test/pleroma/web/activity_pub/object_validators/question_options_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/question_options_validator_test.exs @@ -20,7 +20,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidatorTest "nameMap" => name_map } - assert %{valid?: true, changes: %{nameMap: ^name_map}} = + assert %{valid?: true, changes: %{nameMap: ^name_map, nameRendered: _}} = QuestionOptionsValidator.changeset(%QuestionOptionsValidator{}, data) end end From 39cdde78e1d240f447008c7d4a71e3a391856bdd Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 1 Jan 2023 23:23:17 -0500 Subject: [PATCH 08/38] Render status with multilang maps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/api_spec/helpers.ex | 14 ++ lib/pleroma/web/api_spec/schemas/status.ex | 45 +++++ .../web/mastodon_api/views/status_view.ex | 114 ++++++++++--- lib/pleroma/web/rich_media/parser/card.ex | 155 ------------------ .../mastodon_api/views/status_view_test.exs | 35 ++++ 5 files changed, 189 insertions(+), 174 deletions(-) delete mode 100644 lib/pleroma/web/rich_media/parser/card.ex diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index 7257253ba..6d45c9806 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -82,4 +82,18 @@ defmodule Pleroma.Web.ApiSpec.Helpers do def no_content_response do Operation.response("No Content", "application/json", %Schema{type: :string, example: ""}) end + + def multilang_map_of(embedded_schema) do + %Schema{ + type: :object, + title: + if embedded_schema.title do + "MultiLang map of #{embedded_schema.title}" + else + "MultiLang map" + end, + additionalProperties: embedded_schema, + description: "Map from a BCP47 language tag to a string in that language." + } + end end diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 104f4da5b..5623d793c 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.Attachment alias Pleroma.Web.ApiSpec.Schemas.Emoji @@ -68,6 +69,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do } }, content: %Schema{type: :string, format: :html, description: "HTML-encoded status content"}, + content_map: + Helpers.multilang_map_of(%Schema{ + type: :string, + format: :html, + description: "HTML-encoded status content" + }), text: %Schema{ type: :string, description: "Original unformatted content in plain text", @@ -157,6 +164,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do description: "A map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`" }, + content_map: %Schema{ + type: :object, + additionalProperties: Helpers.multilang_map_of(%Schema{type: :string}), + description: + "A map consisting of alternate representations of the `content_map` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`" + }, content_type: %Schema{ type: :string, nullable: true, @@ -246,6 +259,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do description: "A map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`." }, + spoiler_text_map: %Schema{ + type: :object, + additionalProperties: Helpers.multilang_map_of(%Schema{type: :string}), + description: + "A map consisting of alternate representations of the `spoiler_text_map` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`." + }, thread_muted: %Schema{ type: :boolean, description: "`true` if the thread the post belongs to is muted" @@ -287,6 +306,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do description: "Subject or summary line, below which status content is collapsed until expanded" }, + spoiler_text_map: + Helpers.multilang_map_of(%Schema{ + type: :string, + description: + "Subject or summary line, below which status content is collapsed until expanded" + }), tags: %Schema{type: :array, items: Tag}, uri: %Schema{ type: :string, @@ -364,6 +389,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do "bookmarked" => false, "card" => nil, "content" => "foobar", + "content_map" => %{ + "en" => "mew mew", + "cmn" => "喵喵" + }, "created_at" => "2020-04-07T19:48:51.000Z", "emojis" => [], "favourited" => false, @@ -378,6 +407,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do "pinned" => false, "pleroma" => %{ "content" => %{"text/plain" => "foobar"}, + "content_map" => %{ + "text/plain" => %{ + "en" => "mew mew", + "cmn" => "喵喵" + } + }, "context" => "http://localhost:4001/objects/8b4c0c80-6a37-4d2a-b1b9-05a19e3875aa", "conversation_id" => 345_972, "direct_conversation_id" => nil, @@ -386,6 +421,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do "in_reply_to_account_acct" => nil, "local" => true, "spoiler_text" => %{"text/plain" => ""}, + "spoiler_text_map" => %{ + "text/plain" => %{ + "en" => "", + "cmn" => "" + } + }, "thread_muted" => false, "quotes_count" => 0 }, @@ -396,6 +437,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do "replies_count" => 0, "sensitive" => false, "spoiler_text" => "", + "spoiler_text_map" => %{ + "en" => "", + "cmn" => "" + }, "tags" => [], "uri" => "http://localhost:4001/objects/0f5dad44-0e9e-4610-b377-a2631e499190", "url" => "http://localhost:4001/notice/9toJCu5YZW7O7gfvH6", diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 8eb499abe..3b8baeda5 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -211,6 +211,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do in_reply_to_account_id: nil, reblog: reblogged, content: reblogged[:content] || "", + content_map: reblogged[:content_map] || %{}, created_at: created_at, reblogs_count: 0, replies_count: 0, @@ -340,26 +341,28 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do nil end - content = - object - |> render_content() + {content_html, content_html_map} = + get_content_and_map(%{ + type: :html, + user: opts[:for], + activity: activity, + object: object, + chrono_order: chrono_order + }) - content_html = - content - |> Activity.HTML.get_cached_scrubbed_html_for_activity( - User.html_filter_policy(opts[:for]), - activity, - "mastoapi:content:#{chrono_order}" - ) + {content_plaintext, content_plaintext_map} = + get_content_and_map(%{ + type: :plain, + user: opts[:for], + activity: activity, + object: object, + chrono_order: chrono_order + }) - content_plaintext = - content - |> Activity.HTML.get_cached_stripped_html_for_activity( - activity, - "mastoapi:content:#{chrono_order}" - ) + content_languages = Map.keys(content_html_map) summary = object.data["summary"] || "" + summary_map = object.data["summaryMap"] || %{} card = case Card.get_by_activity(activity) do @@ -426,6 +429,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do reblog: nil, card: card, content: content_html, + content_map: content_html_map, text: opts[:with_source] && get_source_text(object.data["source"]), created_at: created_at, edited_at: edited_at, @@ -439,6 +443,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do pinned: pinned?, sensitive: sensitive, spoiler_text: summary, + spoiler_text_map: summary_map, visibility: get_visibility(object), media_attachments: attachments, poll: render(PollView, "show.json", object: object, for: opts[:for]), @@ -457,7 +462,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do quote_url: object.data["quoteUrl"], quote_visible: visible_for_user?(quote_activity, opts[:for]), content: %{"text/plain" => content_plaintext}, + content_map: %{"text/plain" => content_plaintext_map}, spoiler_text: %{"text/plain" => summary}, + spoiler_text_map: %{"text/plain" => summary_map}, expires_at: expires_at, direct_conversation_id: direct_conversation_id, thread_muted: thread_muted?, @@ -717,14 +724,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end - def render_content(%{data: %{"name" => name, "type" => type}} = object) + def render_content(object) do + render_content(object, object.data["name"], object.data["content"]) + end + + def render_content(%{data: %{"type" => type}} = object, name, content) when not is_nil(name) and name != "" and type != "Event" do url = object.data["url"] || object.data["id"] - "

#{name}

#{object.data["content"]}" + "

#{name}

#{content}" end - def render_content(object), do: object.data["content"] || "" + def render_content(_object, _name, content), do: content || "" @doc """ Builds a dictionary tags. @@ -915,6 +926,71 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do def build_source_location(_), do: nil + defp get_content(%{ + type: type, + content: content, + user: user, + activity: activity, + chrono_order: chrono_order, + language: language + }) + when type in [:html, :plain] do + language = language || "und" + cache_key = "mastoapi:content:#{chrono_order}:#{language}" + + if type == :html do + content + |> Activity.HTML.get_cached_scrubbed_html_for_activity( + User.html_filter_policy(user), + activity, + cache_key + ) + else + content + |> Activity.HTML.get_cached_stripped_html_for_activity( + activity, + cache_key + ) + end + end + + defp get_content_and_map(%{ + type: type, + user: user, + activity: activity, + object: object, + chrono_order: chrono_order + }) do + content_und = + get_content(%{ + type: type, + user: user, + activity: activity, + content: render_content(object), + chrono_order: chrono_order, + language: "und" + }) + + content_map = + (object.data["contentMap"] || %{}) + |> Enum.reduce(%{}, fn {lang, content}, acc -> + Map.put( + acc, + lang, + get_content(%{ + type: type, + user: user, + activity: activity, + content: render_content(object, object.data["nameMap"][lang], content), + chrono_order: chrono_order, + language: lang + }) + ) + end) + + {content_und, content_map} + end + defp get_language(%{data: %{"language" => "und"}}), do: nil defp get_language(object), do: object.data["language"] diff --git a/lib/pleroma/web/rich_media/parser/card.ex b/lib/pleroma/web/rich_media/parser/card.ex deleted file mode 100644 index 5bd5823d1..000000000 --- a/lib/pleroma/web/rich_media/parser/card.ex +++ /dev/null @@ -1,155 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.RichMedia.Parser.Card do - alias Pleroma.Web.RichMedia.Parser.Card - alias Pleroma.Web.RichMedia.Parser.Embed - - @types ["link", "photo", "video", "rich"] - - # https://docs.joinmastodon.org/entities/card/ - defstruct url: nil, - title: nil, - description: "", - type: "link", - author_name: "", - author_url: "", - provider_name: "", - provider_url: "", - html: "", - width: 0, - height: 0, - image: nil, - image_description: "", - embed_url: "", - blurhash: nil - - def parse(%Embed{url: url, oembed: %{"type" => type, "title" => title} = oembed} = embed) - when type in @types and is_binary(url) do - uri = URI.parse(url) - - %Card{ - url: url, - title: title, - description: get_description(embed), - type: oembed["type"], - author_name: oembed["author_name"], - author_url: oembed["author_url"], - provider_name: oembed["provider_name"] || uri.host, - provider_url: oembed["provider_url"] || "#{uri.scheme}://#{uri.host}", - html: sanitize_html(oembed["html"]), - width: oembed["width"], - height: oembed["height"], - image: get_image(oembed) |> fix_uri(url) |> proxy(), - image_description: get_image_description(embed), - embed_url: oembed["url"] |> fix_uri(url) |> proxy() - } - |> IO.inspect - |> validate() - end - - def parse(%Embed{url: url} = embed) when is_binary(url) do - uri = URI.parse(url) - - %Card{ - url: url, - title: get_title(embed), - description: get_description(embed), - type: "link", - provider_name: uri.host, - provider_url: "#{uri.scheme}://#{uri.host}", - image: get_image(embed) |> fix_uri(url) |> proxy(), - image_description: get_image_description(embed), - } - |> validate() - end - - def parse(card), do: {:error, {:invalid_metadata, card}} - - defp get_title(embed) do - case embed do - %{meta: %{"twitter:title" => title}} when is_binary(title) and title != "" -> title - %{meta: %{"og:title" => title}} when is_binary(title) and title != "" -> title - %{title: title} when is_binary(title) and title != "" -> title - _ -> nil - end - end - - defp get_description(%{meta: meta}) do - case meta do - %{"twitter:description" => desc} when is_binary(desc) and desc != "" -> desc - %{"og:description" => desc} when is_binary(desc) and desc != "" -> desc - %{"description" => desc} when is_binary(desc) and desc != "" -> desc - _ -> "" - end - end - - defp get_image(%{meta: meta}) do - case meta do - %{"twitter:image" => image} when is_binary(image) and image != "" -> image - %{"og:image" => image} when is_binary(image) and image != "" -> image - _ -> "" - end - end - - defp get_image(%{"thumbnail_url" => image}) when is_binary(image) and image != "", do: image - defp get_image(%{"type" => "photo", "url" => image}), do: image - defp get_image(_), do: "" - - defp get_image_description(%{meta: %{"og:image:alt" => image_description}}), do: image_description - defp get_image_description(_), do: "" - - defp sanitize_html(html) do - with {:ok, html} <- FastSanitize.Sanitizer.scrub(html, Pleroma.HTML.Scrubber.OEmbed), - {:ok, [{"iframe", _, _}]} <- Floki.parse_fragment(html) do - html - else - _ -> "" - end - end - - def to_map(%Card{} = card) do - card - |> Map.from_struct() - |> stringify_keys() - end - - def to_map(%{} = card), do: stringify_keys(card) - - defp stringify_keys(%{} = map), do: Map.new(map, fn {k, v} -> {Atom.to_string(k), v} end) - - def fix_uri("http://" <> _ = uri, _base_uri), do: uri - def fix_uri("https://" <> _ = uri, _base_uri), do: uri - def fix_uri("/" <> _ = uri, base_uri), do: URI.merge(base_uri, uri) |> URI.to_string() - def fix_uri("", _base_uri), do: nil - - def fix_uri(uri, base_uri) when is_binary(uri), - do: URI.merge(base_uri, "/#{uri}") |> URI.to_string() - - def fix_uri(_uri, _base_uri), do: nil - - defp proxy(url) when is_binary(url), do: Pleroma.Web.MediaProxy.url(url) - defp proxy(_), do: nil - - def validate(%Card{type: type, html: html} = card) - when type in ["video", "rich"] and (is_binary(html) == false or html == "") do - card - |> Map.put(:type, "link") - |> validate() - end - - def validate(%Card{type: type, title: title} = card) - when type in @types and is_binary(title) and title != "" do - {:ok, card} - end - - def validate(%Embed{} = embed) do - case Card.parse(embed) do - {:ok, %Card{} = card} -> validate(card) - card -> {:error, {:invalid_metadata, card}} - end - end - - def validate(card), do: {:error, {:invalid_metadata, card}} -end diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index 3992c0a56..b0a277ba0 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -292,6 +292,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do card: nil, reblog: nil, content: HTML.filter_tags(object_data["content"]), + content_map: %{}, text: nil, created_at: created_at, edited_at: nil, @@ -306,6 +307,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do sensitive: false, poll: nil, spoiler_text: HTML.filter_tags(object_data["summary"]), + spoiler_text_map: %{}, visibility: "public", media_attachments: [], mentions: [], @@ -335,7 +337,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do quote_url: nil, quote_visible: false, content: %{"text/plain" => HTML.strip_tags(object_data["content"])}, + content_map: %{"text/plain" => %{}}, spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])}, + spoiler_text_map: %{"text/plain" => %{}}, expires_at: nil, direct_conversation_id: nil, thread_muted: false, @@ -353,6 +357,37 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec()) end + test "a note activity with multiple languages" do + user = insert(:user) + + note_obj = + insert(:note, + data: %{ + "content" => "mew mew", + "contentMap" => %{"en" => "mew mew", "cmn" => "喵喵"}, + "summary" => "mew", + "summaryMap" => %{"en" => "mew", "cmn" => "喵"} + } + ) + + note = insert(:note_activity, note: note_obj, user: user) + + status = StatusView.render("show.json", %{activity: note}) + + assert %{ + content: "mew mew", + content_map: %{"en" => "mew mew", "cmn" => "喵喵"}, + spoiler_text: "mew", + spoiler_text_map: %{"en" => "mew", "cmn" => "喵"}, + pleroma: %{ + content: %{"text/plain" => "mew mew"}, + content_map: %{"text/plain" => %{"en" => "mew mew", "cmn" => "喵喵"}}, + spoiler_text: %{"text/plain" => "mew"}, + spoiler_text_map: %{"text/plain" => %{"en" => "mew", "cmn" => "喵"}} + } + } = status + end + test "tells if the message is muted for some reason" do user = insert(:user) other_user = insert(:user) From ad8f47fe8faad81b2e1ee452f839b26aa0468eb5 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 1 Jan 2023 23:37:49 -0500 Subject: [PATCH 09/38] Display multilang in history items --- .../api_spec/operations/status_operation.ex | 13 ++++++++ .../web/mastodon_api/views/status_view.ex | 24 +++++++------- .../mastodon_api/views/status_view_test.exs | 33 +++++++++++++++++++ 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index 3cec590da..c56f56911 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.AccountOperation + alias Pleroma.Web.ApiSpec.Helpers alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.Attachment @@ -769,6 +770,12 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do format: :html, description: "HTML-encoded status content" }, + content_map: + Helpers.multilang_map_of(%Schema{ + type: :string, + format: :html, + description: "HTML-encoded status content" + }), sensitive: %Schema{ type: :boolean, description: "Is this status marked as sensitive content?" @@ -778,6 +785,12 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do description: "Subject or summary line, below which status content is collapsed until expanded" }, + spoiler_text_map: + Helpers.multilang_map_of(%Schema{ + type: :string, + description: + "Subject or summary line, below which status content is collapsed until expanded" + }), created_at: %Schema{ type: :string, format: "date-time", diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 3b8baeda5..80e51ebae 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -359,8 +359,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do chrono_order: chrono_order }) - content_languages = Map.keys(content_html_map) - summary = object.data["summary"] || "" summary_map = object.data["summaryMap"] || %{} @@ -536,19 +534,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do created_at = Utils.to_masto_date(object.data["updated"] || object.data["published"]) - content = - object - |> render_content() - - content_html = - content - |> Activity.HTML.get_cached_scrubbed_html_for_activity( - User.html_filter_policy(opts[:for]), - activity, - "mastoapi:content:#{chrono_order}" - ) + {content_html, content_html_map} = + get_content_and_map(%{ + type: :html, + user: opts[:for], + activity: activity, + object: object, + chrono_order: chrono_order + }) summary = object.data["summary"] || "" + summary_map = object.data["summaryMap"] || %{} %{ account: @@ -557,8 +553,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do for: opts[:for] }), content: content_html, + content_map: content_html_map, sensitive: sensitive, spoiler_text: summary, + spoiler_text_map: summary_map, created_at: created_at, media_attachments: attachments, emojis: build_emojis(object.data["emoji"]), diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index b0a277ba0..d14a90e36 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -1024,4 +1024,37 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do assert status.content_type == "text/plain" end end + + describe "history items" do + test "renders multilang" do + user = insert(:user) + + note_obj = + insert(:note, + data: %{ + "content" => "mew mew", + "contentMap" => %{"en" => "mew mew", "cmn" => "喵喵"}, + "summary" => "mew", + "summaryMap" => %{"en" => "mew", "cmn" => "喵"} + } + ) + + note = insert(:note_activity, note: note_obj, user: user) + + status = + StatusView.render("history_item.json", %{ + activity: note, + user: user, + hashtags: [], + item: %{object: note_obj, chrono_order: 0} + }) + + assert %{ + content: "mew mew", + content_map: %{"en" => "mew mew", "cmn" => "喵喵"}, + spoiler_text: "mew", + spoiler_text_map: %{"en" => "mew", "cmn" => "喵"} + } = status + end + end end From 64db6ce87123b7fb1715cc6804f549916a378284 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 2 Jan 2023 00:05:58 -0500 Subject: [PATCH 10/38] Render multilang of sources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/api_spec/helpers.ex | 27 ++++++++------- .../api_spec/operations/status_operation.ex | 11 ++++++ lib/pleroma/web/api_spec/schemas/status.ex | 8 +++++ .../web/mastodon_api/views/status_view.ex | 9 +++++ .../mastodon_api/views/status_view_test.exs | 34 +++++++++++++++++++ 5 files changed, 77 insertions(+), 12 deletions(-) diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index 6d45c9806..1d1a42bbd 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -83,17 +83,20 @@ defmodule Pleroma.Web.ApiSpec.Helpers do Operation.response("No Content", "application/json", %Schema{type: :string, example: ""}) end - def multilang_map_of(embedded_schema) do - %Schema{ - type: :object, - title: - if embedded_schema.title do - "MultiLang map of #{embedded_schema.title}" - else - "MultiLang map" - end, - additionalProperties: embedded_schema, - description: "Map from a BCP47 language tag to a string in that language." - } + def multilang_map_of(embedded_schema, opts \\ []) do + struct( + %Schema{ + type: :object, + title: + if embedded_schema.title do + "MultiLang map of #{embedded_schema.title}" + else + "MultiLang map" + end, + additionalProperties: embedded_schema, + description: "Map from a BCP47 language tag to a string in that language." + }, + opts + ) end end diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index c56f56911..bbc114f81 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -829,11 +829,22 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do type: :string, description: "Raw source of status content" }, + text_map: + Helpers.multilang_map_of(%Schema{ + type: :string, + description: "Raw source of status content" + }), spoiler_text: %Schema{ type: :string, description: "Subject or summary line, below which status content is collapsed until expanded" }, + spoiler_text_map: + Helpers.multilang_map_of(%Schema{ + type: :string, + description: + "Subject or summary line, below which status content is collapsed until expanded" + }), content_type: %Schema{ type: :string, description: "The content type of the source" diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 5623d793c..6f7f35cdf 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -80,6 +80,14 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do description: "Original unformatted content in plain text", nullable: true }, + text_map: + Helpers.multilang_map_of( + %Schema{ + type: :string, + description: "Original unformatted content in plain text" + }, + nullable: true + ), created_at: %Schema{ type: :string, format: "date-time", diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 80e51ebae..64f938333 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -429,6 +429,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do content: content_html, content_map: content_html_map, text: opts[:with_source] && get_source_text(object.data["source"]), + text_map: opts[:with_source] && get_source_text_map(Map.get(object.data, "source", "")), created_at: created_at, edited_at: edited_at, reblogs_count: announcement_count, @@ -570,7 +571,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do %{ id: activity.id, text: get_source_text(Map.get(object.data, "source", "")), + text_map: get_source_text_map(Map.get(object.data, "source", "")), spoiler_text: Map.get(object.data, "summary", ""), + spoiler_text_map: Map.get(object.data, "summaryMap", %{}), content_type: get_source_content_type(object.data["source"]), location: build_source_location(object.data) } @@ -896,6 +899,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do "" end + defp get_source_text_map(%{"contentMap" => %{} = content_map} = _source) do + content_map + end + + defp get_source_text_map(_), do: %{} + defp get_source_content_type(%{"mediaType" => type} = _source) do type end diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index d14a90e36..d4a57bea2 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -294,6 +294,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do content: HTML.filter_tags(object_data["content"]), content_map: %{}, text: nil, + text_map: nil, created_at: created_at, edited_at: nil, reblogs_count: 0, @@ -1057,4 +1058,37 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do } = status end end + + describe "source" do + test "renders multilang" do + user = insert(:user) + + note_obj = + insert(:note, + data: %{ + "source" => %{ + "content" => "mew mew", + "contentMap" => %{"en" => "mew mew", "cmn" => "喵喵"}, + "mediaType" => "text/plain" + }, + "summary" => "mew", + "summaryMap" => %{"en" => "mew", "cmn" => "喵"} + } + ) + + note = insert(:note_activity, note: note_obj, user: user) + + status = + StatusView.render("source.json", %{ + activity: note + }) + + assert %{ + text: "mew mew", + text_map: %{"en" => "mew mew", "cmn" => "喵喵"}, + spoiler_text: "mew", + spoiler_text_map: %{"en" => "mew", "cmn" => "喵"} + } = status + end + end end From 6b20df1e2d839e48596f749d6ddfb0c4d7fa55e0 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 2 Jan 2023 00:17:09 -0500 Subject: [PATCH 11/38] Render multilang for attachments --- .../web/api_spec/schemas/attachment.ex | 9 +++++++ .../web/mastodon_api/views/status_view.ex | 1 + .../mastodon_api/views/status_view_test.exs | 24 ++++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/schemas/attachment.ex b/lib/pleroma/web/api_spec/schemas/attachment.ex index 2871b5f99..61712c079 100644 --- a/lib/pleroma/web/api_spec/schemas/attachment.ex +++ b/lib/pleroma/web/api_spec/schemas/attachment.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Attachment do alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers require OpenApiSpex @@ -42,6 +43,13 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Attachment do description: "Alternate text that describes what is in the media attachment, to be used for the visually impaired or when media attachments do not load" }, + description_map: + Helpers.multilang_map_of(%Schema{ + type: :string, + nullable: true, + description: + "Alternate text that describes what is in the media attachment, to be used for the visually impaired or when media attachments do not load" + }), type: %Schema{ type: :string, enum: ["image", "video", "audio", "unknown"], @@ -62,6 +70,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Attachment do preview_url: "someurl", text_url: "someurl", description: nil, + description_map: %{}, pleroma: %{mime_type: "image/png"} } }) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 64f938333..a54d6a01f 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -644,6 +644,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do text_url: href, type: type, description: attachment["name"], + description_map: attachment["nameMap"] || %{}, pleroma: %{mime_type: media_type}, blurhash: attachment["blurhash"] } diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index d4a57bea2..39130988d 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -654,7 +654,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do description: nil, pleroma: %{mime_type: "image/png"}, meta: %{original: %{width: 200, height: 100, aspect: 2}}, - blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn" + blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn", + description_map: %{} } api_spec = Pleroma.Web.ApiSpec.spec() @@ -670,6 +671,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do assert_schema(result, "Attachment", api_spec) end + test "attachments with multilang" do + object = %{ + "type" => "Image", + "url" => [ + %{ + "mediaType" => "image/png", + "href" => "someurl", + "width" => 200, + "height" => 100 + } + ], + "name" => "mew mew", + "nameMap" => %{"en" => "mew mew", "cmn" => "喵喵"}, + "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn", + "uuid" => 6 + } + + assert %{description: "mew mew", description_map: %{"en" => "mew mew", "cmn" => "喵喵"}} = + StatusView.render("attachment.json", %{attachment: object}) + end + test "put the url advertised in the Activity in to the url attribute" do id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810" [activity] = Activity.search(nil, id) From e11d4b69235f24854603b3454e2d32f631d8bfc4 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 2 Jan 2023 00:30:47 -0500 Subject: [PATCH 12/38] Render multilang in polls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/api_spec/schemas/poll.ex | 4 +++ .../web/mastodon_api/views/poll_view.ex | 3 +- .../web/mastodon_api/views/poll_view_test.exs | 33 ++++++++++++++++--- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex index 20cf5b061..584bb4862 100644 --- a/lib/pleroma/web/api_spec/schemas/poll.ex +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers alias Pleroma.Web.ApiSpec.Schemas.Emoji alias Pleroma.Web.ApiSpec.Schemas.FlakeID @@ -52,6 +53,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do type: :object, properties: %{ title: %Schema{type: :string}, + title_map: Helpers.multilang_map_of(%Schema{type: :string}), votes_count: %Schema{type: :integer} } }, @@ -81,10 +83,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do options: [ %{ title: "accept", + title_map: %{"en" => "accept", "cmn" => "接受"}, votes_count: 6 }, %{ title: "deny", + title_map: %{"en" => "deny", "cmn" => "拒绝"}, votes_count: 4 } ], diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex index 5bc482c8d..a256e3bc8 100644 --- a/lib/pleroma/web/mastodon_api/views/poll_view.ex +++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex @@ -65,7 +65,8 @@ defmodule Pleroma.Web.MastodonAPI.PollView do current_count = option["replies"]["totalItems"] || 0 {%{ - title: name, + title: option["nameRendered"] || name, + title_map: option["nameMap"] || %{}, votes_count: current_count }, current_count + count} end) diff --git a/test/pleroma/web/mastodon_api/views/poll_view_test.exs b/test/pleroma/web/mastodon_api/views/poll_view_test.exs index e3508d075..71ecd2116 100644 --- a/test/pleroma/web/mastodon_api/views/poll_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/poll_view_test.exs @@ -37,10 +37,10 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do id: to_string(object.id), multiple: false, options: [ - %{title: "absolutely!", votes_count: 0}, - %{title: "sure", votes_count: 0}, - %{title: "yes", votes_count: 0}, - %{title: "why are you even asking?", votes_count: 0} + %{title: "absolutely!", title_map: %{}, votes_count: 0}, + %{title: "sure", title_map: %{}, votes_count: 0}, + %{title: "yes", title_map: %{}, votes_count: 0}, + %{title: "why are you even asking?", title_map: %{}, votes_count: 0} ], votes_count: 0, voters_count: 0, @@ -167,6 +167,31 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do } = PollView.render("show.json", %{object: object}) end + describe "multilang" do + test "renders multilang" do + object = %Object{ + id: 123, + data: %{ + "oneOf" => [ + %{ + "name" => "mew", + "nameMap" => %{"en" => "mew", "cmn" => "喵"}, + "nameRendered" => "mew | 喵" + }, + %{"name" => "mew mew", "nameMap" => %{"en" => "mew mew", "cmn" => "喵喵"}} + ] + } + } + + assert %{ + options: [ + %{title: "mew | 喵", title_map: %{"en" => "mew", "cmn" => "喵"}}, + %{title: "mew mew", title_map: %{"en" => "mew mew", "cmn" => "喵喵"}} + ] + } = PollView.render("show.json", %{object: object}) + end + end + test "displays correct voters count" do object = Object.normalize("https://friends.grishka.me/posts/54642", fetch: true) result = PollView.render("show.json", %{object: object}) From 0d96c04019da5fd86ab30d418636c754e873a24d Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 2 Jan 2023 21:40:46 -0500 Subject: [PATCH 13/38] Accept map of strings in ActivityDraft MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/activity_pub/builder.ex | 25 +++++++- .../api_spec/operations/status_operation.ex | 29 +++++++++- lib/pleroma/web/common_api/activity_draft.ex | 58 +++++++++++++++++-- lib/pleroma/web/common_api/utils.ex | 36 +++++++++++- .../pleroma/web/activity_pub/builder_test.exs | 26 +++++++++ .../web/common_api/activity_draft_test.exs | 26 ++++++++- test/pleroma/web/common_api/utils_test.exs | 41 +++++++++++++ 7 files changed, 228 insertions(+), 13 deletions(-) diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index de1777f36..afa34ec3e 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -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 @@ defmodule Pleroma.Web.ActivityPub.Builder 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) diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index bbc114f81..243cd72ee 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -561,6 +561,12 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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{ diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index a77f415b4..804a2162d 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -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 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft 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 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft 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 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft 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 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft 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 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft 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 diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index accbe4a72..4a5f508cd 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -231,7 +231,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do [] end - draft.status + draft |> format_input(content_type, options) |> maybe_add_attachments(draft.attachments, attachment_links) end @@ -253,6 +253,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do 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 @@ defmodule Pleroma.Web.CommonAPI.Utils 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 """ diff --git a/test/pleroma/web/activity_pub/builder_test.exs b/test/pleroma/web/activity_pub/builder_test.exs index 52058a0a3..cb154ced4 100644 --- a/test/pleroma/web/activity_pub/builder_test.exs +++ b/test/pleroma/web/activity_pub/builder_test.exs @@ -45,6 +45,32 @@ defmodule Pleroma.Web.ActivityPub.BuilderTest 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) diff --git a/test/pleroma/web/common_api/activity_draft_test.exs b/test/pleroma/web/common_api/activity_draft_test.exs index 02bc6cf3b..d2f4109e4 100644 --- a/test/pleroma/web/common_api/activity_draft_test.exs +++ b/test/pleroma/web/common_api/activity_draft_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors +# Copyright © 2017-2022 Pleroma Authors # 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) diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs index 27b1da1e3..7adc55499 100644 --- a/test/pleroma/web/common_api/utils_test.exs +++ b/test/pleroma/web/common_api/utils_test.exs @@ -81,6 +81,47 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest 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 = "

hello world!

" expected = "

hello world!

" From 72a2b3329ec18757cba4a87f8929f9572804f5c9 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 2 Jan 2023 22:13:44 -0500 Subject: [PATCH 14/38] Accept status_map and spoiler_text_map in POST statuses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/common_api/activity_draft.ex | 19 +++-- .../controllers/status_controller.ex | 20 +++++ .../controllers/status_controller_test.exs | 75 +++++++++++++++++++ 3 files changed, 104 insertions(+), 10 deletions(-) diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 804a2162d..967123288 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -168,18 +168,17 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft 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) + defp full_payload(%{status: status, status_map: nil} = draft) do + full_payload(%__MODULE__{draft | status_map: %{"und" => status}}) + end - case Utils.validate_character_limit(full_payload, draft.attachments) do - :ok -> %__MODULE__{draft | full_payload: full_payload} - {:error, message} -> add_error(draft, message) - end + defp full_payload(%{summary: summary, summary_map: nil} = draft) do + full_payload(%__MODULE__{draft | summary_map: %{"und" => summary}}) end - defp full_payload(%{status: status, summary: summary} = draft) do + 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 @@ -497,7 +496,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do 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 + defp get_source_map(%{status_map: %{} = status_map} = _draft) do %{ "content" => Pleroma.MultiLanguage.map_to_str(status_map, mutiline: true), "contentMap" => status_map diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index a4085db61..efc1d9fd7 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -232,6 +232,26 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end end + def create( + %{ + assigns: %{user: _user}, + private: %{open_api_spex: %{body_params: %{status_map: _}}} = params + } = conn, + _ + ) do + create(conn |> put_in([:private, :open_api_spex, :body_params, :status], ""), %{}) + end + + def create( + %{ + assigns: %{user: _user}, + private: %{open_api_spex: %{body_params: %{media_ids: _}}} = params + } = conn, + _ + ) do + create(conn |> put_in([:private, :open_api_spex, :body_params, :status], ""), %{}) + end + @doc "GET /api/v1/statuses/:id/history" def show_history( %{assigns: assigns, private: %{open_api_spex: %{params: %{id: id} = params}}} = conn, diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index a534890e1..20b772b10 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -139,6 +139,81 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do ) end + test "posting a multilang status", %{conn: conn} do + idempotency_key = "Pikachu rocks!" + + conn_one = + conn + |> put_req_header("content-type", "application/json") + |> put_req_header("idempotency-key", idempotency_key) + |> post("/api/v1/statuses", %{ + "status_map" => %{"a" => "mew mew", "b" => "lol lol"}, + "spoiler_text_map" => %{"a" => "mew", "b" => "lol"}, + "sensitive" => "0" + }) + + assert %{ + "content" => _content, + "content_map" => %{"a" => "mew mew", "b" => "lol lol"}, + "id" => id, + "spoiler_text" => _spoiler_text, + "spoiler_text_map" => %{"a" => "mew", "b" => "lol"}, + "sensitive" => false + } = json_response_and_validate_schema(conn_one, 200) + + assert Activity.get_by_id(id) + end + + test "posting a multilang status with singlelang summary", %{conn: conn} do + idempotency_key = "Pikachu rocks!" + + conn_one = + conn + |> put_req_header("content-type", "application/json") + |> put_req_header("idempotency-key", idempotency_key) + |> post("/api/v1/statuses", %{ + "status_map" => %{"a" => "mew mew", "b" => "lol lol"}, + "spoiler_text" => "mewlol", + "sensitive" => "0" + }) + + assert %{ + "content" => _content, + "content_map" => %{"a" => "mew mew", "b" => "lol lol"}, + "id" => id, + "spoiler_text" => "mewlol", + "spoiler_text_map" => %{}, + "sensitive" => false + } = json_response_and_validate_schema(conn_one, 200) + + assert Activity.get_by_id(id) + end + + test "posting a multilang summary with singlelang status", %{conn: conn} do + idempotency_key = "Pikachu rocks!" + + conn_one = + conn + |> put_req_header("content-type", "application/json") + |> put_req_header("idempotency-key", idempotency_key) + |> post("/api/v1/statuses", %{ + "spoiler_text_map" => %{"a" => "mew mew", "b" => "lol lol"}, + "status" => "mewlol", + "sensitive" => "0" + }) + + assert %{ + "content" => "mewlol", + "content_map" => %{}, + "id" => id, + "spoiler_text" => _, + "spoiler_text_map" => %{"a" => "mew mew", "b" => "lol lol"}, + "sensitive" => false + } = json_response_and_validate_schema(conn_one, 200) + + assert Activity.get_by_id(id) + end + test "posting a quote post", %{conn: conn} do user = insert(:user) From b1bdbdcf050921d42cef1d615d81e62081c92e9c Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 3 Jan 2023 01:29:29 -0500 Subject: [PATCH 15/38] Accept multilang polls on MastoAPI --- .../api_spec/operations/status_operation.ex | 9 +++- lib/pleroma/web/common_api/utils.ex | 48 ++++++++++++++----- test/pleroma/web/common_api/utils_test.exs | 22 +++++++++ .../controllers/status_controller_test.exs | 37 ++++++++++++++ 4 files changed, 102 insertions(+), 14 deletions(-) diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index 243cd72ee..61cf2bd17 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -735,18 +735,23 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do %Schema{ nullable: true, type: :object, - required: [:options, :expires_in], + required: [:expires_in], properties: %{ options: %Schema{ type: :array, items: %Schema{type: :string}, description: "Array of possible answers. Must be provided with `poll[expires_in]`." }, + options_map: %Schema{ + type: :array, + items: Helpers.multilang_map_of(%Schema{type: :string}), + description: "Array of possible answers. Must be provided with `poll[expires_in]`." + }, expires_in: %Schema{ type: :integer, nullable: true, description: - "Duration the poll should be open, in seconds. Must be provided with `poll[options]`" + "Duration the poll should be open, in seconds. Must be provided with `poll[options]` or `poll[options_map]`" }, multiple: %Schema{ allOf: [BooleanLike], diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 4a5f508cd..e5dafad0f 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -147,22 +147,34 @@ defmodule Pleroma.Web.CommonAPI.Utils do |> make_poll_data() end - def make_poll_data(%{poll: %{options: options, expires_in: expires_in}} = data) - when is_list(options) do + def make_poll_data(%{poll: %{options_map: options_map, expires_in: expires_in}} = data) + when is_list(options_map) do limits = Config.get([:instance, :poll_limits]) + is_single_language = data.poll[:is_single_language] options = options |> Enum.uniq() with :ok <- validate_poll_expiration(expires_in, limits), - :ok <- validate_poll_options_amount(options, limits), - :ok <- validate_poll_options_length(options, limits) do + :ok <- validate_poll_options_amount(options_map, limits), + :ok <- validate_poll_options_length(options_map, limits) do {option_notes, emoji} = - Enum.map_reduce(options, %{}, fn option, emoji -> - note = %{ - "name" => option, - "type" => "Note", - "replies" => %{"type" => "Collection", "totalItems" => 0} - } + Enum.map_reduce(options_map, %{}, fn option, emoji -> + name_attrs = + if is_single_language do + %{"name" => option["und"]} + else + %{ + "name" => Pleroma.MultiLanguage.map_to_str(option, multiline: false), + "nameMap" => option + } + end + + note = + %{ + "type" => "Note", + "replies" => %{"type" => "Collection", "totalItems" => 0} + } + |> Map.merge(name_attrs) {note, Map.merge(emoji, Pleroma.Emoji.Formatter.get_emoji_map(option))} end) @@ -179,6 +191,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do end end + def make_poll_data(%{poll: %{options: options}} = data) when is_list(options) do + new_poll = Map.put(data.poll, :options_map, Enum.map(options, &%{"und" => &1})) + + data + |> Map.put(:poll, new_poll) + |> Map.put(:is_single_language, true) + |> make_poll_data() + end + def make_poll_data(%{"poll" => poll}) when is_map(poll) do {:error, "Invalid poll"} end @@ -200,8 +221,11 @@ defmodule Pleroma.Web.CommonAPI.Utils do end end - defp validate_poll_options_length(options, %{max_option_chars: max_option_chars}) do - if Enum.any?(options, &(String.length(&1) > max_option_chars)) do + defp validate_poll_options_length(options_map, %{max_option_chars: max_option_chars}) do + if Enum.any?(options_map, fn option -> + Enum.reduce(option, 0, fn {_lang, cur}, acc -> acc + String.length(cur) end) + |> Kernel.>(max_option_chars) + end) do {:error, "Poll options cannot be longer than #{max_option_chars} characters each"} else :ok diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs index 7adc55499..9b31ae54d 100644 --- a/test/pleroma/web/common_api/utils_test.exs +++ b/test/pleroma/web/common_api/utils_test.exs @@ -729,4 +729,26 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do } end end + + describe "make_poll_data/1" do + test "multilang support" do + {:ok, {poll, _}} = + Utils.make_poll_data(%{ + poll: %{ + options_map: [ + %{"a" => "foo", "b" => "1"}, + %{"a" => "bar", "c" => "2"} + ], + expires_in: 600 + } + }) + + assert %{"oneOf" => choices} = poll + + assert [ + %{"name" => _, "nameMap" => %{"a" => "foo", "b" => "1"}}, + %{"name" => _, "nameMap" => %{"a" => "bar", "c" => "2"}} + ] = choices + end + end end diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index 20b772b10..b6966fb05 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -676,6 +676,43 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert question.data["closed"] =~ "Z" end + test "posting a multilang poll", %{conn: conn} do + time = NaiveDateTime.utc_now() + + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses", %{ + "status" => "Who is the #bestgrill?", + "poll" => %{ + "options_map" => [ + %{"a" => "Rei", "b" => "1"}, + %{"a" => "Asuka", "b" => "2"}, + %{"a" => "Misato", "b" => "3"} + ], + "expires_in" => 420 + } + }) + + response = json_response_and_validate_schema(conn, 200) + + assert Enum.all?(response["poll"]["options"], fn %{"title_map" => title} -> + title in [ + %{"a" => "Rei", "b" => "1"}, + %{"a" => "Asuka", "b" => "2"}, + %{"a" => "Misato", "b" => "3"} + ] + end) + + assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430 + assert response["poll"]["expired"] == false + + question = Object.get_by_id(response["poll"]["id"]) + + # closed contains utc timezone + assert question.data["closed"] =~ "Z" + end + test "option limit is enforced", %{conn: conn} do limit = Config.get([:instance, :poll_limits, :max_options]) From 7222cebdb6ed883d1e8f05415aca9eae56d63d3a Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 3 Jan 2023 01:35:31 -0500 Subject: [PATCH 16/38] Use MultiLanguage func to convert str to map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/common_api/activity_draft.ex | 5 +++-- lib/pleroma/web/common_api/utils.ex | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 967123288..1207c481b 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do alias Pleroma.Activity alias Pleroma.Conversation.Participation alias Pleroma.Language.LanguageDetector + alias Pleroma.MultiLanguage alias Pleroma.Object alias Pleroma.Repo alias Pleroma.Web.ActivityPub.Builder @@ -169,11 +170,11 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do end defp full_payload(%{status: status, status_map: nil} = draft) do - full_payload(%__MODULE__{draft | status_map: %{"und" => status}}) + full_payload(%__MODULE__{draft | status_map: MultiLanguage.str_to_map(status)}) end defp full_payload(%{summary: summary, summary_map: nil} = draft) do - full_payload(%__MODULE__{draft | summary_map: %{"und" => summary}}) + full_payload(%__MODULE__{draft | summary_map: MultiLanguage.str_to_map(summary)}) end defp full_payload(%{status_map: %{} = status_map, summary_map: %{} = summary_map} = draft) do diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index e5dafad0f..c175baf3f 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do alias Pleroma.Config alias Pleroma.Conversation.Participation alias Pleroma.Formatter + alias Pleroma.MultiLanguage alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User @@ -164,7 +165,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do %{"name" => option["und"]} else %{ - "name" => Pleroma.MultiLanguage.map_to_str(option, multiline: false), + "name" => MultiLanguage.map_to_str(option, multiline: false), "nameMap" => option } end @@ -192,7 +193,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do end def make_poll_data(%{poll: %{options: options}} = data) when is_list(options) do - new_poll = Map.put(data.poll, :options_map, Enum.map(options, &%{"und" => &1})) + new_poll = Map.put(data.poll, :options_map, Enum.map(options, &MultiLanguage.str_to_map/1)) data |> Map.put(:poll, new_poll) From 4d2813ae41c60db3027cd1e44c97aa147b00001d Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 3 Jan 2023 02:31:23 -0500 Subject: [PATCH 17/38] Accept multilang descriptions when uploading attachments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/upload.ex | 42 +++++++++++++++---- .../api_spec/operations/media_operation.ex | 10 +++++ .../controllers/media_controller.ex | 6 ++- .../web/activity_pub/activity_pub_test.exs | 7 ++++ .../controllers/media_controller_test.exs | 36 ++++++++++++++++ 5 files changed, 90 insertions(+), 11 deletions(-) diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index e6c484548..d61d4391c 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -62,6 +62,7 @@ defmodule Pleroma.Upload do height: integer(), blurhash: String.t(), description: String.t(), + description_map: map(), path: String.t() } defstruct [ @@ -73,21 +74,44 @@ defmodule Pleroma.Upload do :height, :blurhash, :description, + :description_map, :path ] @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) defp get_description(upload) do - case {upload.description, Pleroma.Config.get([Pleroma.Upload, :default_description])} do - {description, _} when is_binary(description) -> description + case {upload, Pleroma.Config.get([Pleroma.Upload, :default_description])} do + {%{description_map: %{} = description_map}, _} -> description_map + {%{description: description}, _} when is_binary(description) -> description {_, :filename} -> upload.name {_, str} when is_binary(str) -> str _ -> "" end end - @spec store(source, options :: [option()]) :: {:ok, map()} | {:error, any()} + defp validate_description_limit(%{} = description) do + len = Enum.reduce(description, 0, fn {_, content}, acc -> String.length(content) + acc end) + + len <= Pleroma.Config.get([:instance, :description_limit]) + end + + defp validate_description_limit(description) when is_binary(description) do + String.length(description) <= Pleroma.Config.get([:instance, :description_limit]) + end + + defp description_fields(%{} = description) do + %{ + "name" => Pleroma.MultiLanguage.map_to_str(description, multiline: false), + "nameMap" => description + } + end + + defp description_fields(description) when is_binary(description) do + %{"name" => description} + end + + @spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()} @doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct." def store(upload, opts \\ []) do opts = get_opts(opts) @@ -96,9 +120,7 @@ defmodule Pleroma.Upload do upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"}, {:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload), description = get_description(upload), - {_, true} <- - {:description_limit, - String.length(description) <= Pleroma.Config.get([:instance, :description_limit])}, + {_, true} <- {:description_limit, validate_description_limit(description)}, {:ok, url_spec} <- Pleroma.Uploaders.Uploader.put_file(opts.uploader, upload) do {:ok, %{ @@ -113,9 +135,9 @@ defmodule Pleroma.Upload do } |> Maps.put_if_present("width", upload.width) |> Maps.put_if_present("height", upload.height) - ], - "name" => description + ] } + |> Map.merge(description_fields(description)) |> Maps.put_if_present("blurhash", upload.blurhash)} else {:description_limit, _} -> @@ -156,6 +178,7 @@ defmodule Pleroma.Upload do uploader: Keyword.get(opts, :uploader, Pleroma.Config.get([__MODULE__, :uploader])), filters: Keyword.get(opts, :filters, Pleroma.Config.get([__MODULE__, :filters])), description: Keyword.get(opts, :description), + description_map: Keyword.get(opts, :description_map), base_url: base_url() } end @@ -168,7 +191,8 @@ defmodule Pleroma.Upload do name: file.filename, tempfile: file.path, content_type: file.content_type, - description: opts.description + description: opts.description, + description_map: opts.description_map }} end end diff --git a/lib/pleroma/web/api_spec/operations/media_operation.ex b/lib/pleroma/web/api_spec/operations/media_operation.ex index e6df21246..4aa58f2a8 100644 --- a/lib/pleroma/web/api_spec/operations/media_operation.ex +++ b/lib/pleroma/web/api_spec/operations/media_operation.ex @@ -47,6 +47,11 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do type: :string, description: "A plain-text description of the media, for accessibility purposes." }, + description_map: + Helpers.multilang_map_of(%Schema{ + type: :string, + description: "A plain-text description of the media, for accessibility purposes." + }), focus: %Schema{ type: :string, description: "Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0." @@ -88,6 +93,11 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do type: :string, description: "A plain-text description of the media, for accessibility purposes." }, + description_map: + Helpers.multilang_map_of(%Schema{ + type: :string, + description: "A plain-text description of the media, for accessibility purposes." + }), focus: %Schema{ type: :string, description: "Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0." diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index a861273a8..fb6794339 100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -28,7 +28,8 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do ActivityPub.upload( file, actor: user.ap_id, - description: Map.get(data, :description) + description: Map.get(data, :description), + description_map: Map.get(data, :description_map) ) do attachment_data = Map.put(object.data, "id", object.id) @@ -48,7 +49,8 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do ActivityPub.upload( file, actor: user.ap_id, - description: Map.get(data, :description) + description: Map.get(data, :description), + description_map: Map.get(data, :description_map) ) do attachment_data = Map.put(object.data, "id", object.id) diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index 8e91d00e0..ec18507bb 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -1410,6 +1410,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert object.data["name"] == "a cool file" end + test "sets a multilang description if given", %{test_file: file} do + {:ok, %Object{} = object} = + ActivityPub.upload(file, description_map: %{"a" => "mew", "b" => "lol"}) + + assert object.data["nameMap"] == %{"a" => "mew", "b" => "lol"} + end + test "it sets the default description depending on the configuration", %{test_file: file} do clear_config([Pleroma.Upload, :default_description]) diff --git a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs index b92fd8afa..7b886da6a 100644 --- a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs @@ -49,6 +49,24 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do assert object.data["actor"] == User.ap_id(conn.assigns[:user]) end + test "/api/v1/media, multilang", %{conn: conn, image: image} do + media = + conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/v1/media", %{ + "file" => image, + "description_map" => %{"a" => "mew", "b" => "lol"} + }) + |> json_response_and_validate_schema(:ok) + + assert media["type"] == "image" + assert media["description_map"] == %{"a" => "mew", "b" => "lol"} + assert media["id"] + + object = Object.get_by_id(media["id"]) + assert object.data["actor"] == User.ap_id(conn.assigns[:user]) + end + test "/api/v2/media", %{conn: conn, user: user, image: image} do desc = "Description of the image" @@ -75,6 +93,24 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do assert object.data["actor"] == user.ap_id end + test "/api/v2/media, multilang", %{conn: conn, image: image} do + media = + conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/v2/media", %{ + "file" => image, + "description_map" => %{"a" => "mew", "b" => "lol"} + }) + |> json_response_and_validate_schema(202) + + assert media["type"] == "image" + assert media["description_map"] == %{"a" => "mew", "b" => "lol"} + assert media["id"] + + object = Object.get_by_id(media["id"]) + assert object.data["actor"] == User.ap_id(conn.assigns[:user]) + end + test "/api/v2/media, upload_limit", %{conn: conn, user: user} do desc = "Description of the binary" From 88ff45e0643c0bbfec7a3bec06bc8db78ab517de Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 3 Jan 2023 18:22:00 -0500 Subject: [PATCH 18/38] Fix unit tests --- lib/pleroma/emoji/formatter.ex | 4 ++++ lib/pleroma/web/common_api/utils.ex | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/emoji/formatter.ex b/lib/pleroma/emoji/formatter.ex index 87fd35f13..b87cdc00b 100644 --- a/lib/pleroma/emoji/formatter.ex +++ b/lib/pleroma/emoji/formatter.ex @@ -48,5 +48,9 @@ defmodule Pleroma.Emoji.Formatter do end) end + def get_emoji_map(%{} = map) do + Enum.reduce(map, %{}, fn {_, content}, acc -> Map.merge(acc, get_emoji_map(content)) end) + end + def get_emoji_map(_), do: %{} end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index c175baf3f..5983ba9c6 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -193,11 +193,13 @@ defmodule Pleroma.Web.CommonAPI.Utils do end def make_poll_data(%{poll: %{options: options}} = data) when is_list(options) do - new_poll = Map.put(data.poll, :options_map, Enum.map(options, &MultiLanguage.str_to_map/1)) + new_poll = + data.poll + |> Map.put(:options_map, Enum.map(options, &MultiLanguage.str_to_map/1)) + |> Map.put(:is_single_language, true) data |> Map.put(:poll, new_poll) - |> Map.put(:is_single_language, true) |> make_poll_data() end From 023eae28d683b56d4a6f92a9365a7550b4235ee4 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 3 Jan 2023 18:34:57 -0500 Subject: [PATCH 19/38] Move validating multilang map into MultiLanguage --- .../object_validators/map_of_string.ex | 42 ++++--------------- lib/pleroma/multi_language.ex | 38 +++++++++++++++++ 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex index 6a971f27e..f365772ba 100644 --- a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex @@ -5,44 +5,18 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString do use Ecto.Type - def type, do: :map - - def cast(object) when is_map(object) do - data = - object - |> Enum.reduce(%{}, fn - {lang, value}, acc when is_binary(lang) and is_binary(value) -> - if is_good_locale_code?(lang) do - Map.put(acc, lang, value) - else - acc - end + alias Pleroma.MultiLanguage - _, acc -> - acc - end) + def type, do: :map - if data == %{} do - {:ok, nil} - else + def cast(object) do + with {status, %{} = data} when status in [:modified, :ok] <- MultiLanguage.validate_map(object) do {:ok, data} - end - end - - def cast(_), do: :error - - defp is_good_locale_code?(code) do - code - |> String.codepoints() - |> Enum.all?(&valid_char?/1) - end + else + {:modified, nil} -> {:ok, nil} - # [a-zA-Z0-9-] - defp valid_char?(char) do - ("a" <= char and char <= "z") or - ("A" <= char and char <= "Z") or - ("0" <= char and char <= "9") or - char == "-" + {:error, _} -> :error + end end def dump(data), do: {:ok, data} diff --git a/lib/pleroma/multi_language.ex b/lib/pleroma/multi_language.ex index 0c02757ee..14d56cea1 100644 --- a/lib/pleroma/multi_language.ex +++ b/lib/pleroma/multi_language.ex @@ -9,6 +9,44 @@ defmodule Pleroma.MultiLanguage do defp sep(:multi), do: Pleroma.Config.get([__MODULE__, :separator]) defp sep(:single), do: Pleroma.Config.get([__MODULE__, :single_line_separator]) + defp is_good_locale_code?(code) do + code + |> String.codepoints() + |> Enum.all?(&valid_char?/1) + end + + # [a-zA-Z0-9-] + defp valid_char?(char) do + ("a" <= char and char <= "z") or + ("A" <= char and char <= "Z") or + ("0" <= char and char <= "9") or + char == "-" + end + + def validate_map(%{} = object) do + {status, data} = + object + |> Enum.reduce({:ok, %{}}, fn + {lang, value}, {status, acc} when is_binary(lang) and is_binary(value) -> + if is_good_locale_code?(lang) do + {status, Map.put(acc, lang, value)} + else + {:modified, acc} + end + + _, {_status, acc} -> + {:modified, acc} + end) + + if data == %{} do + {status, nil} + else + {status, data} + end + end + + def validate_map(_), do: {:error, nil} + def map_to_str(data, opts \\ []) do map_to_str_impl(data, if(opts[:multiline], do: :multi, else: :single)) end From a9b1589528023c9d71575c936cd1bd52daebbe5e Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 3 Jan 2023 21:31:07 -0500 Subject: [PATCH 20/38] Validate multilang map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../object_validators/map_of_string.ex | 4 +- lib/pleroma/web/activity_pub/activity_pub.ex | 18 ++- lib/pleroma/web/common_api/activity_draft.ex | 20 +++- lib/pleroma/web/common_api/utils.ex | 15 +++ .../controllers/media_controller.ex | 10 ++ .../controllers/media_controller_test.exs | 42 ++++++- .../controllers/status_controller_test.exs | 110 ++++++++++++++++++ 7 files changed, 209 insertions(+), 10 deletions(-) diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex index f365772ba..8b6f1741e 100644 --- a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex @@ -10,11 +10,11 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString do def type, do: :map def cast(object) do - with {status, %{} = data} when status in [:modified, :ok] <- MultiLanguage.validate_map(object) do + with {status, %{} = data} when status in [:modified, :ok] <- + MultiLanguage.validate_map(object) do {:ok, data} else {:modified, nil} -> {:ok, nil} - {:error, _} -> :error end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index fb8e36bbf..f72102afc 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -33,6 +33,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do import Ecto.Query import Pleroma.Web.ActivityPub.Utils import Pleroma.Web.ActivityPub.Visibility + import Pleroma.Web.Gettext import Pleroma.Webhook.Notify, only: [trigger_webhooks: 2] require Logger @@ -1591,12 +1592,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> Enum.reverse() end + defp validate_media_description_map(%{} = map) do + with {:ok, %{}} <- Pleroma.MultiLanguage.validate_map(map) do + :ok + else + _ -> :error + end + end + + defp validate_media_description_map(nil), do: :ok + defp validate_media_description_map(_), do: :error + @spec upload(Upload.source(), keyword()) :: {:ok, Object.t()} | {:error, any()} def upload(file, opts \\ []) do - with {:ok, data} <- Upload.store(sanitize_upload_file(file), opts) do + with {_, :ok} <- {:description_map, validate_media_description_map(opts[:description_map])}, + {:ok, data} <- Upload.store(sanitize_upload_file(file), opts) do obj_data = Maps.put_if_present(data, "actor", opts[:actor]) Repo.insert(%Object{data: obj_data}) + else + {:description_map, _} -> {:error, dgettext("errors", "description_map invalid")} + e -> e end end diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 1207c481b..589f14f70 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -68,7 +68,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> status() |> summary() |> with_valid(&attachments/1) - |> full_payload() + |> with_valid(&full_payload/1) |> expires_at() |> poll() |> with_valid(&in_reply_to/1) @@ -76,7 +76,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> with_valid("e_post/1) |> with_valid(&visibility/1) |> with_valid("ing_visibility/1) - |> content() + |> with_valid(&content/1) |> with_valid(&to_and_cc/1) |> with_valid(&context/1) |> with_valid(&language/1) @@ -153,16 +153,24 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do %__MODULE__{draft | params: params} end - defp status(%{params: %{status_map: status_map}} = draft) do - %__MODULE__{draft | status_map: status_map} + defp status(%{params: %{status_map: %{} = status_map}} = draft) do + with {:ok, %{}} <- MultiLanguage.validate_map(status_map) do + %__MODULE__{draft | status_map: status_map} + else + _ -> add_error(draft, dgettext("errors", "status_map is not a valid multilang map")) + end 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} + defp summary(%{params: %{spoiler_text_map: %{} = spoiler_text_map}} = draft) do + with {:ok, %{}} <- MultiLanguage.validate_map(spoiler_text_map) do + %__MODULE__{draft | summary_map: spoiler_text_map} + else + _ -> add_error(draft, dgettext("errors", "spoiler_text_map is not a valid multilang map")) + end end defp summary(%{params: params} = draft) do diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 5983ba9c6..772bc4476 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -156,6 +156,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do options = options |> Enum.uniq() with :ok <- validate_poll_expiration(expires_in, limits), + :ok <- validate_poll_options_map(options_map), :ok <- validate_poll_options_amount(options_map, limits), :ok <- validate_poll_options_length(options_map, limits) do {option_notes, emoji} = @@ -211,6 +212,20 @@ defmodule Pleroma.Web.CommonAPI.Utils do {:ok, {%{}, %{}}} end + defp validate_poll_options_map(options) do + if Enum.all?(options, fn opt -> + with {:ok, %{}} <- MultiLanguage.validate_map(opt) do + true + else + _ -> false + end + end) do + :ok + else + {:error, dgettext("errors", "Poll option map not valid")} + end + end + defp validate_poll_options_amount(options, %{max_options: max_options}) do cond do Enum.count(options) < 2 -> diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index fb6794339..9101fa1b6 100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -34,6 +34,11 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do attachment_data = Map.put(object.data, "id", object.id) render(conn, "attachment.json", %{attachment: attachment_data}) + else + {:error, e} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: e}) end end @@ -57,6 +62,11 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do conn |> put_status(202) |> render("attachment.json", %{attachment: attachment_data}) + else + {:error, e} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: e}) end end diff --git a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs index 7b886da6a..39c44443d 100644 --- a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs @@ -67,6 +67,26 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do assert object.data["actor"] == User.ap_id(conn.assigns[:user]) end + test "/api/v1/media, multilang, invalid description_map", %{conn: conn, image: image} do + conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/v1/media", %{ + "file" => image, + "description_map" => %{"a" => "mew", "b_" => "lol"} + }) + |> json_response_and_validate_schema(422) + end + + test "/api/v1/media, multilang, empty description_map", %{conn: conn, image: image} do + conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/v1/media", %{ + "file" => image, + "description_map" => %{} + }) + |> json_response_and_validate_schema(422) + end + test "/api/v2/media", %{conn: conn, user: user, image: image} do desc = "Description of the image" @@ -111,6 +131,26 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do assert object.data["actor"] == User.ap_id(conn.assigns[:user]) end + test "/api/v2/media, multilang, invalid description_map", %{conn: conn, image: image} do + conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/v2/media", %{ + "file" => image, + "description_map" => %{"a" => "mew", "b_" => "lol"} + }) + |> json_response_and_validate_schema(422) + end + + test "/api/v2/media, multilang, empty description_map", %{conn: conn, image: image} do + conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/v2/media", %{ + "file" => image, + "description_map" => %{} + }) + |> json_response_and_validate_schema(422) + end + test "/api/v2/media, upload_limit", %{conn: conn, user: user} do desc = "Description of the binary" @@ -133,7 +173,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do "file" => large_binary, "description" => desc }) - |> json_response_and_validate_schema(400) + |> json_response_and_validate_schema(422) end) =~ "[error] Elixir.Pleroma.Upload store (using Pleroma.Uploaders.Local) failed: :file_too_large" diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index b6966fb05..aac7d938c 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -164,6 +164,78 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert Activity.get_by_id(id) end + test "posting a multilang status, invalid language code in status_map", %{conn: conn} do + idempotency_key = "Pikachu rocks!" + + conn_one = + conn + |> put_req_header("content-type", "application/json") + |> put_req_header("idempotency-key", idempotency_key) + |> post("/api/v1/statuses", %{ + "status_map" => %{"a" => "mew mew", "b_" => "lol lol"}, + "spoiler_text_map" => %{"a" => "mew", "b" => "lol"}, + "sensitive" => "0" + }) + + assert %{ + "error" => _ + } = json_response_and_validate_schema(conn_one, 422) + end + + test "posting a multilang status, empty status_map", %{conn: conn} do + idempotency_key = "Pikachu rocks!" + + conn_one = + conn + |> put_req_header("content-type", "application/json") + |> put_req_header("idempotency-key", idempotency_key) + |> post("/api/v1/statuses", %{ + "status_map" => %{}, + "spoiler_text_map" => %{"a" => "mew", "b" => "lol"}, + "sensitive" => "0" + }) + + assert %{ + "error" => _ + } = json_response_and_validate_schema(conn_one, 422) + end + + test "posting a multilang status, invalid language code in spoiler_text_map", %{conn: conn} do + idempotency_key = "Pikachu rocks!" + + conn_one = + conn + |> put_req_header("content-type", "application/json") + |> put_req_header("idempotency-key", idempotency_key) + |> post("/api/v1/statuses", %{ + "status_map" => %{"a" => "mew mew", "b" => "lol lol"}, + "spoiler_text_map" => %{"a" => "mew", "b_" => "lol"}, + "sensitive" => "0" + }) + + assert %{ + "error" => _ + } = json_response_and_validate_schema(conn_one, 422) + end + + test "posting a multilang status, empty spoiler_text_map", %{conn: conn} do + idempotency_key = "Pikachu rocks!" + + conn_one = + conn + |> put_req_header("content-type", "application/json") + |> put_req_header("idempotency-key", idempotency_key) + |> post("/api/v1/statuses", %{ + "status_map" => %{"a" => "mew mew", "b" => "lol lol"}, + "spoiler_text_map" => %{}, + "sensitive" => "0" + }) + + assert %{ + "error" => _ + } = json_response_and_validate_schema(conn_one, 422) + end + test "posting a multilang status with singlelang summary", %{conn: conn} do idempotency_key = "Pikachu rocks!" @@ -713,6 +785,44 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert question.data["closed"] =~ "Z" end + test "posting a multilang poll, invalid lang code", %{conn: conn} do + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses", %{ + "status" => "Who is the #bestgrill?", + "poll" => %{ + "options_map" => [ + %{"a" => "Rei", "b" => "1"}, + %{"a" => "Asuka", "b_" => "2"}, + %{"a" => "Misato", "b" => "3"} + ], + "expires_in" => 420 + } + }) + + assert %{"error" => _} = json_response_and_validate_schema(conn, 422) + end + + test "posting a multilang poll, empty map", %{conn: conn} do + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses", %{ + "status" => "Who is the #bestgrill?", + "poll" => %{ + "options_map" => [ + %{"a" => "Rei", "b" => "1"}, + %{}, + %{"a" => "Misato", "b" => "3"} + ], + "expires_in" => 420 + } + }) + + assert %{"error" => _} = json_response_and_validate_schema(conn, 422) + end + test "option limit is enforced", %{conn: conn} do limit = Config.get([:instance, :poll_limits, :max_options]) From c34a4d816f7039e94e34c6f8c33bd358ed1b5da3 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 3 Jan 2023 21:54:13 -0500 Subject: [PATCH 21/38] Accept description_map when updating media MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../controllers/media_controller.ex | 29 +++++++++++++++++++ .../controllers/media_controller_test.exs | 29 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index 9101fa1b6..079dcf732 100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -73,6 +73,35 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do def create2(_conn, _data), do: {:error, :bad_request} @doc "PUT /api/v1/media/:id" + def update( + %{ + assigns: %{user: user}, + private: %{ + open_api_spex: %{ + body_params: %{description_map: %{} = description_map}, + params: %{id: id} + } + } + } = conn, + _ + ) do + with %Object{} = object <- Object.get_by_id(id), + :ok <- Object.authorize_access(object, user), + {_, {:ok, %{}}} <- + {:description_map, Pleroma.MultiLanguage.validate_map(description_map)}, + {:ok, %Object{data: data}} <- + Object.update_data(object, %{ + "name" => Pleroma.MultiLanguage.map_to_str(description_map), + "nameMap" => description_map + }) do + attachment_data = Map.put(data, "id", object.id) + + render(conn, "attachment.json", %{attachment: attachment_data}) + else + {:description_map, _} -> render_error(conn, 422, "description_map not valid") + end + end + def update( %{ assigns: %{user: user}, diff --git a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs index 39c44443d..a171815a8 100644 --- a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs @@ -255,6 +255,35 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do assert media["description"] == "test-media" assert refresh_record(object).data["name"] == "test-media" end + + test "/api/v1/media/:id description_map", %{conn: conn, object: object} do + media = + conn + |> put_req_header("content-type", "multipart/form-data") + |> put("/api/v1/media/#{object.id}", %{ + "description_map" => %{"a" => "test-media", "b" => "xxx"} + }) + |> json_response_and_validate_schema(:ok) + + assert media["description_map"] == %{"a" => "test-media", "b" => "xxx"} + assert refresh_record(object).data["nameMap"] == %{"a" => "test-media", "b" => "xxx"} + end + + test "/api/v1/media/:id description_map, invalid", %{conn: conn, object: object} do + conn + |> put_req_header("content-type", "multipart/form-data") + |> put("/api/v1/media/#{object.id}", %{ + "description_map" => %{"a" => "test-media", "b_" => "xxx"} + }) + |> json_response_and_validate_schema(422) + end + + test "/api/v1/media/:id description_map, empty", %{conn: conn, object: object} do + conn + |> put_req_header("content-type", "multipart/form-data") + |> put("/api/v1/media/#{object.id}", %{"description_map" => %{}}) + |> json_response_and_validate_schema(422) + end end describe "Get media by id (/api/v1/media/:id)" do From afab62252047975d197a5e5d9e9e066dae015604 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 3 Jan 2023 22:09:46 -0500 Subject: [PATCH 22/38] Fix article_note_page_validator_test --- .../ecto_type/activity_pub/object_validators/map_of_string.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex index 8b6f1741e..a5de64154 100644 --- a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex @@ -14,7 +14,7 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString do MultiLanguage.validate_map(object) do {:ok, data} else - {:modified, nil} -> {:ok, nil} + {_, nil} -> {:ok, nil} {:error, _} -> :error end end From c4cec5fd6a895c01e96d411088247bb69ac63bb3 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 4 Jan 2023 11:03:11 -0500 Subject: [PATCH 23/38] Render language attr of a status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../web/mastodon_api/views/status_view.ex | 6 ++-- .../mastodon_api/views/status_view_test.exs | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index a54d6a01f..751b38f3c 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -228,7 +228,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do mentions: mentions, tags: reblogged[:tags] || [], application: build_application(object.data["generator"]), - language: get_language(object), + language: get_language(object.data), emojis: [], pleroma: %{ local: activity.local, @@ -999,7 +999,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do {content_und, content_map} end - defp get_language(%{data: %{"language" => "und"}}), do: nil + defp get_language(%{"language" => "und"}), do: nil - defp get_language(object), do: object.data["language"] + defp get_language(data), do: data["language"] end diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index 39130988d..dd01425ec 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -380,6 +380,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do content_map: %{"en" => "mew mew", "cmn" => "喵喵"}, spoiler_text: "mew", spoiler_text_map: %{"en" => "mew", "cmn" => "喵"}, + language: "mul", pleroma: %{ content: %{"text/plain" => "mew mew"}, content_map: %{"text/plain" => %{"en" => "mew mew", "cmn" => "喵喵"}}, @@ -389,6 +390,38 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do } = status end + test "a note activity with single language" do + user = insert(:user) + + note_obj = + insert(:note, + data: %{ + "content" => "mew mew", + "contentMap" => %{"en" => "mew mew"}, + "summary" => "mew", + "summaryMap" => %{"en" => "mew"} + } + ) + + note = insert(:note_activity, note: note_obj, user: user) + + status = StatusView.render("show.json", %{activity: note}) + + assert %{ + content: "mew mew", + content_map: %{"en" => "mew mew"}, + spoiler_text: "mew", + spoiler_text_map: %{"en" => "mew"}, + language: "en", + pleroma: %{ + content: %{"text/plain" => "mew mew"}, + content_map: %{"text/plain" => %{"en" => "mew mew"}}, + spoiler_text: %{"text/plain" => "mew"}, + spoiler_text_map: %{"text/plain" => %{"en" => "mew"}} + } + } = status + end + test "tells if the message is muted for some reason" do user = insert(:user) other_user = insert(:user) From 9555e630fd85997860e8601a46f8df4405e7d6f6 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 4 Jan 2023 11:43:25 -0500 Subject: [PATCH 24/38] Post single-language status with language attribute --- lib/pleroma/multi_language.ex | 12 ++- lib/pleroma/web/activity_pub/builder.ex | 28 ++++-- lib/pleroma/web/common_api/activity_draft.ex | 29 +++++- lib/pleroma/web/common_api/utils.ex | 9 +- .../controllers/status_controller_test.exs | 95 ++++++++++++++++++- 5 files changed, 153 insertions(+), 20 deletions(-) diff --git a/lib/pleroma/multi_language.ex b/lib/pleroma/multi_language.ex index 14d56cea1..ca69b7981 100644 --- a/lib/pleroma/multi_language.ex +++ b/lib/pleroma/multi_language.ex @@ -9,7 +9,7 @@ defmodule Pleroma.MultiLanguage do defp sep(:multi), do: Pleroma.Config.get([__MODULE__, :separator]) defp sep(:single), do: Pleroma.Config.get([__MODULE__, :single_line_separator]) - defp is_good_locale_code?(code) do + def is_good_locale_code?(code) do code |> String.codepoints() |> Enum.all?(&valid_char?/1) @@ -68,8 +68,14 @@ defmodule Pleroma.MultiLanguage do end end - def str_to_map(data) do - %{"und" => data} + def str_to_map(data, opts \\ []) do + with lang when is_binary(lang) <- opts[:lang], + true <- is_good_locale_code?(lang) do + %{lang => data} + else + _ -> + %{"und" => data} + end end def format_template(template, %{code: code, content: content}) do diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index afa34ec3e..a2993d28a 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -219,20 +219,32 @@ defmodule Pleroma.Web.ActivityPub.Builder do 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) - } + case Map.keys(draft.content_html_map) do + ["und"] -> + %{"content" => MultiLanguage.map_to_str(draft.content_html_map, multiline: true)} + + _ -> + %{ + "contentMap" => draft.content_html_map, + "content" => MultiLanguage.map_to_str(draft.content_html_map, multiline: true) + } + end 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) - } + case Map.keys(draft.summary_map) do + ["und"] -> + %{"summary" => MultiLanguage.map_to_str(draft.summary_map, multiline: false)} + + _ -> + %{ + "summaryMap" => draft.summary_map, + "summary" => MultiLanguage.map_to_str(draft.summary_map, multiline: false) + } + end else %{"summary" => draft.summary} end diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 589f14f70..391fd81c4 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -26,6 +26,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do errors: [], user: nil, params: %{}, + language: nil, status: nil, status_map: nil, summary: nil, @@ -65,6 +66,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do def create(user, params) do user |> new(params) + |> language() |> status() |> summary() |> with_valid(&attachments/1) @@ -153,6 +155,16 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do %__MODULE__{draft | params: params} end + defp language(%{params: %{language: language}} = draft) do + if MultiLanguage.is_good_locale_code?(language) do + %__MODULE__{draft | language: language} + else + add_error(draft, dgettext("errors", "language \"%{language}\" is invalid", language: language)) + end + end + + defp language(draft), do: draft + defp status(%{params: %{status_map: %{} = status_map}} = draft) do with {:ok, %{}} <- MultiLanguage.validate_map(status_map) do %__MODULE__{draft | status_map: status_map} @@ -178,16 +190,25 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do end defp full_payload(%{status: status, status_map: nil} = draft) do - full_payload(%__MODULE__{draft | status_map: MultiLanguage.str_to_map(status)}) + full_payload(%__MODULE__{ + draft + | status_map: MultiLanguage.str_to_map(status, lang: draft.language) + }) end defp full_payload(%{summary: summary, summary_map: nil} = draft) do - full_payload(%__MODULE__{draft | summary_map: MultiLanguage.str_to_map(summary)}) + full_payload(%__MODULE__{ + draft + | summary_map: MultiLanguage.str_to_map(summary, lang: draft.language) + }) 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) + 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 diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 772bc4476..d908dcde1 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -151,7 +151,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do def make_poll_data(%{poll: %{options_map: options_map, expires_in: expires_in}} = data) when is_list(options_map) do limits = Config.get([:instance, :poll_limits]) - is_single_language = data.poll[:is_single_language] options = options |> Enum.uniq() @@ -161,6 +160,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do :ok <- validate_poll_options_length(options_map, limits) do {option_notes, emoji} = Enum.map_reduce(options_map, %{}, fn option, emoji -> + is_single_language = Map.keys(option) == ["und"] + name_attrs = if is_single_language do %{"name" => option["und"]} @@ -196,8 +197,10 @@ defmodule Pleroma.Web.CommonAPI.Utils do def make_poll_data(%{poll: %{options: options}} = data) when is_list(options) do new_poll = data.poll - |> Map.put(:options_map, Enum.map(options, &MultiLanguage.str_to_map/1)) - |> Map.put(:is_single_language, true) + |> Map.put( + :options_map, + Enum.map(options, &MultiLanguage.str_to_map(&1, lang: data[:language])) + ) data |> Map.put(:poll, new_poll) diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index aac7d938c..bdbcd157b 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -83,8 +83,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do "sensitive" => "0" }) - assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} = - json_response_and_validate_schema(conn_one, 200) + assert %{ + "content" => "cofe", + "id" => id, + "spoiler_text" => "2hu", + "sensitive" => false, + "language" => nil + } = json_response_and_validate_schema(conn_one, 200) assert Activity.get_by_id(id) @@ -139,6 +144,52 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do ) end + test "posting a single lang status ", %{conn: conn} do + idempotency_key = "Pikachu rocks!" + + conn_one = + conn + |> put_req_header("content-type", "application/json") + |> put_req_header("idempotency-key", idempotency_key) + |> post("/api/v1/statuses", %{ + "status" => "mew mew", + "spoiler_text" => "mew", + "sensitive" => "0", + "language" => "a" + }) + + assert %{ + "content" => "mew mew", + "content_map" => %{"a" => "mew mew"}, + "id" => id, + "spoiler_text" => "mew", + "spoiler_text_map" => %{"a" => "mew"}, + "sensitive" => false, + "language" => "a" + } = json_response_and_validate_schema(conn_one, 200) + + assert Activity.get_by_id(id) + end + + test "posting a single lang status, bad language code", %{conn: conn} do + idempotency_key = "Pikachu rocks!" + + conn_one = + conn + |> put_req_header("content-type", "application/json") + |> put_req_header("idempotency-key", idempotency_key) + |> post("/api/v1/statuses", %{ + "status" => "mew mew", + "spoiler_text" => "mew", + "sensitive" => "0", + "language" => "a_" + }) + + assert %{ + "error" => _ + } = json_response_and_validate_schema(conn_one, 422) + end + test "posting a multilang status", %{conn: conn} do idempotency_key = "Pikachu rocks!" @@ -748,6 +799,46 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert question.data["closed"] =~ "Z" end + test "posting a single-language poll", %{conn: conn} do + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses", %{ + "status" => "Who is the #bestgrill?", + "poll" => %{ + "options" => ["Rei", "Asuka", "Misato"], + "expires_in" => 420 + }, + "language" => "a" + }) + + response = json_response_and_validate_schema(conn, 200) + + assert Enum.all?(response["poll"]["options"], fn %{"title_map" => title} -> + title in [ + %{"a" => "Rei"}, + %{"a" => "Asuka"}, + %{"a" => "Misato"} + ] + end) + end + + test "posting a single-language poll, invalid language code", %{conn: conn} do + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses", %{ + "status" => "Who is the #bestgrill?", + "poll" => %{ + "options" => ["Rei", "Asuka", "Misato"], + "expires_in" => 420 + }, + "language" => "a_" + }) + + json_response_and_validate_schema(conn, 422) + end + test "posting a multilang poll", %{conn: conn} do time = NaiveDateTime.utc_now() From 792e4951803d99b255272f450c9f3eb277d45e6b Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 4 Jan 2023 12:01:58 -0500 Subject: [PATCH 25/38] Fix tests --- .../activity_pub/object_validators/map_of_string.ex | 4 +++- lib/pleroma/web/common_api/activity_draft.ex | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex index a5de64154..c36fe00b5 100644 --- a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex @@ -9,7 +9,7 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString do def type, do: :map - def cast(object) do + def cast(%{} = object) do with {status, %{} = data} when status in [:modified, :ok] <- MultiLanguage.validate_map(object) do {:ok, data} @@ -19,6 +19,8 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString do end end + def cast(_), do: :error + def dump(data), do: {:ok, data} def load(data), do: {:ok, data} diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 391fd81c4..1ba33a435 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -159,7 +159,10 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do if MultiLanguage.is_good_locale_code?(language) do %__MODULE__{draft | language: language} else - add_error(draft, dgettext("errors", "language \"%{language}\" is invalid", language: language)) + add_error( + draft, + dgettext("errors", "language \"%{language}\" is invalid", language: language) + ) end end From cd0260f309433e0c19ec6fee48bf96e30d6f78a4 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 15 Jan 2023 14:11:15 -0500 Subject: [PATCH 26/38] Make ensure_re_prepended multilang-aware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../activity_pub/mrf/ensure_re_prepended.ex | 42 ++++++++++++++++--- .../mrf/ensure_re_prepended_test.exs | 38 ++++++++++++++++- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex index a148cc1e7..74f7d9858 100644 --- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex +++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex @@ -12,18 +12,34 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do def history_awareness, do: :auto + def filter_by_summary( + %{data: %{"summaryMap" => %{} = parent_summary_map}} = _in_reply_to, + %{"summaryMap" => %{} = child_summary_map} = child + ) do + fixed_summary_map = + Enum.reduce(child_summary_map, %{}, fn {lang, cur}, acc -> + with {:ok, fixed_cur} <- fix_one(cur, parent_summary_map[lang]) do + Map.put(acc, lang, fixed_cur) + else + _ -> Map.put(acc, lang, cur) + end + end) + + child + |> Map.put("summaryMap", fixed_summary_map) + |> Map.put("summary", Pleroma.MultiLanguage.map_to_str(fixed_summary_map, multiline: false)) + end + def filter_by_summary( %{data: %{"summary" => parent_summary}} = _in_reply_to, %{"summary" => child_summary} = child ) when not is_nil(child_summary) and byte_size(child_summary) > 0 and not is_nil(parent_summary) and byte_size(parent_summary) > 0 do - if (child_summary == parent_summary and not Regex.match?(@reply_prefix, child_summary)) or - (Regex.match?(@reply_prefix, parent_summary) && - Regex.replace(@reply_prefix, parent_summary, "") == child_summary) do - Map.put(child, "summary", "re: " <> child_summary) + with {:ok, fixed_child_summary} <- fix_one(child_summary, parent_summary) do + Map.put(child, "summary", fixed_child_summary) else - child + _ -> child end end @@ -44,4 +60,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do def filter(object), do: {:ok, object} def describe, do: {:ok, %{}} + + defp fix_one(child_summary, parent_summary) + when is_binary(child_summary) and child_summary != "" and is_binary(parent_summary) and + parent_summary != "" do + if (child_summary == parent_summary and not Regex.match?(@reply_prefix, child_summary)) or + (Regex.match?(@reply_prefix, parent_summary) && + Regex.replace(@reply_prefix, parent_summary, "") == child_summary) do + {:ok, "re: " <> child_summary} + else + {:nochange, nil} + end + end + + defp fix_one(_, _) do + {:nochange, nil} + end end diff --git a/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs b/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs index 5afab0cf9..93273e39f 100644 --- a/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs +++ b/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs @@ -24,8 +24,44 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrependedTest do assert res["object"]["summary"] == "re: object-summary" end - test "it adds `re:` to summary object when child summary contains re-subject of parent summary " do + test "it adds `re:` to summaryMap object when child summary and parent summary for some language equal" do message = %{ + "type" => "Create", + "object" => %{ + "summary" => "object-summary", + "summaryMap" => %{ + "a" => "object-summary", + "b" => "some-object-summary", + "c" => "another-object-summary" + }, + "inReplyTo" => %Activity{ + object: %Object{ + data: %{ + "summary" => "object-summary", + "summaryMap" => %{ + "a" => "unrelated-summary", + "b" => "some-object-summary" + } + } + } + } + } + } + + assert {:ok, res} = EnsureRePrepended.filter(message) + + assert res["object"]["summaryMap"] == %{ + "a" => "object-summary", + "b" => "re: some-object-summary", + "c" => "another-object-summary" + } + + assert res["object"]["summary"] == + Pleroma.MultiLanguage.map_to_str(res["object"]["summaryMap"], multiline: false) + end + + test "it adds `re:` to summary object when child summary contains re-subject of parent summary " do +s message = %{ "type" => "Create", "object" => %{ "summary" => "object-summary", From 2d66faf9a4fc64465b12b3c16d0a655389f82787 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 15 Jan 2023 14:29:13 -0500 Subject: [PATCH 27/38] Make force_mentions_in_content multilang aware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../mrf/force_mentions_in_content.ex | 73 ++++++++++++------- .../mrf/force_mentions_in_content_test.exs | 52 +++++++++++++ 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex index 5532093cb..8b2cbdc58 100644 --- a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex +++ b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex @@ -78,23 +78,55 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do def filter( %{ "type" => type, - "object" => %{"type" => "Note", "to" => to, "inReplyTo" => in_reply_to} - } = object + "object" => %{"type" => "Note", "to" => to, "inReplyTo" => in_reply_to} = object + } = activity ) when type in ["Create", "Update"] and is_list(to) and is_binary(in_reply_to) do - # image-only posts from pleroma apparently reach this MRF without the content field - content = object["object"]["content"] || "" - # Get the replied-to user for sorting - replied_to_user = get_replied_to_user(object["object"]) + replied_to_user = get_replied_to_user(object) mention_users = to - |> clean_recipients(object) + |> clean_recipients(activity) |> Enum.map(&User.get_cached_by_ap_id/1) |> Enum.reject(&is_nil/1) |> sort_replied_user(replied_to_user) + fixed_object = + with %{} = content_map <- object["contentMap"] do + fixed_content_map = + Enum.reduce(content_map, %{}, fn {lang, content}, acc -> + fixed_content = fix_content(content, mention_users) + + Map.put(acc, lang, fixed_content) + end) + + object + |> Map.put("contentMap", fixed_content_map) + |> Map.put( + "content", + Pleroma.MultiLanguage.map_to_str(fixed_content_map, multiline: true) + ) + else + _ -> + # image-only posts from pleroma apparently reach this MRF without the content field + content = object["content"] || "" + + fixed_content = fix_content(content, mention_users) + + Map.put(object, "content", fixed_content) + end + + {:ok, put_in(activity["object"], fixed_object)} + end + + @impl true + def filter(object), do: {:ok, object} + + @impl true + def describe, do: {:ok, %{}} + + defp fix_content(content, mention_users) do explicitly_mentioned_uris = extract_mention_uris_from_content(content) |> MapSet.new() @@ -113,25 +145,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do do: "#{added_mentions}", else: "" - content = - cond do - # For Markdown posts, insert the mentions inside the first

tag - recipients_inline != "" && String.starts_with?(content, "

") -> - "

" <> recipients_inline <> String.trim_leading(content, "

") - - recipients_inline != "" -> - recipients_inline <> content + cond do + # For Markdown posts, insert the mentions inside the first

tag + recipients_inline != "" && String.starts_with?(content, "

") -> + "

" <> recipients_inline <> String.trim_leading(content, "

") - true -> - content - end + recipients_inline != "" -> + recipients_inline <> content - {:ok, put_in(object["object"]["content"], content)} + true -> + content + end end - - @impl true - def filter(object), do: {:ok, object} - - @impl true - def describe, do: {:ok, %{}} end diff --git a/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs b/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs index 811ef105c..7eb8d5163 100644 --- a/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs +++ b/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs @@ -87,6 +87,58 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContentTest do "@luigi @mario WHA-HA!" end + test "supports mulitlang" do + [mario, luigi, wario] = [ + insert(:user, nickname: "mario"), + insert(:user, nickname: "luigi"), + insert(:user, nickname: "wario") + ] + + {:ok, post1} = CommonAPI.post(mario, %{status: "Letsa go!"}) + + {:ok, post2} = + CommonAPI.post(luigi, %{status: "Oh yaah", in_reply_to_id: post1.id, to: [mario.ap_id]}) + + activity = %{ + "type" => "Create", + "actor" => wario.ap_id, + "object" => %{ + "type" => "Note", + "actor" => wario.ap_id, + "content" => "WHA-HA!", + "contentMap" => %{ + "a" => "mew mew", + "b" => "lol lol" + }, + "to" => [ + mario.ap_id, + luigi.ap_id, + Constants.as_public() + ], + "inReplyTo" => Object.normalize(post2).data["id"] + } + } + + {:ok, + %{ + "object" => %{ + "content" => content, + "contentMap" => + %{ + "a" => content_a, + "b" => content_b + } = content_map + } + }} = ForceMentionsInContent.filter(activity) + + mentions_part = + "@luigi @mario " + + assert content_a == mentions_part <> "mew mew" + assert content_b == mentions_part <> "lol lol" + assert content == Pleroma.MultiLanguage.map_to_str(content_map, multiline: true) + end + test "don't mention self" do mario = insert(:user, nickname: "mario") From 9354ee31d2c7eb554049c415efee64dc35dbd35d Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 15 Jan 2023 14:49:45 -0500 Subject: [PATCH 28/38] Make keyword_policy multilang-aware --- .../web/activity_pub/mrf/keyword_policy.ex | 48 ++++++++++++++----- .../activity_pub/mrf/keyword_policy_test.exs | 42 ++++++++++++++++ 2 files changed, 79 insertions(+), 11 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex index 729da4e9c..a7e17a29f 100644 --- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex @@ -85,18 +85,36 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do end defp check_replace(%{"object" => %{} = object} = message) do + config = Pleroma.Config.get([:mrf_keyword, :replace]) + replace_kw = fn object -> - ["content", "name", "summary"] - |> Enum.filter(fn field -> Map.has_key?(object, field) && object[field] end) - |> Enum.reduce(object, fn field, object -> - data = - Enum.reduce( - Pleroma.Config.get([:mrf_keyword, :replace]), - object[field], - fn {pat, repl}, acc -> String.replace(acc, pat, repl) end - ) - - Map.put(object, field, data) + [ + {"content", [multiline: true]}, + {"name", [multiline: false]}, + {"summary", [multiline: false]} + ] + |> Enum.filter(fn {field, _} -> + is_map(object[field <> "Map"]) or + (Map.has_key?(object, field) && object[field]) + end) + |> Enum.reduce(object, fn {field, opts}, object -> + field_name_map = field <> "Map" + + with %{} = data_map <- object[field_name_map] do + fixed_data_map = + Enum.reduce(data_map, %{}, fn {lang, content}, acc -> + Map.put(acc, lang, replace_keyword(content, config)) + end) + + object + |> Map.put(field_name_map, fixed_data_map) + |> Map.put(field, Pleroma.MultiLanguage.map_to_str(fixed_data_map, opts)) + else + _ -> + data = replace_keyword(object[field], config) + + Map.put(object, field, data) + end end) |> (fn object -> {:ok, object} end).() end @@ -108,6 +126,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do {:ok, message} end + defp replace_keyword(data, config) do + Enum.reduce( + config, + data, + fn {pat, repl}, acc -> String.replace(acc, pat, repl) end + ) + end + @impl true def filter(%{"type" => type, "object" => %{"content" => _content}} = message) when type in ["Create", "Update"] do diff --git a/test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs b/test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs index a0e77d7b9..a40d5374c 100644 --- a/test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs @@ -295,6 +295,48 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do end) end + test "replaces keyword in *Map" do + clear_config([:mrf_keyword, :replace], [{"opensource", "free software"}]) + + message = %{ + "type" => "Create", + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "object" => %{ + "content" => "unrelevant", + "contentMap" => %{ + "a" => "ZFS is opensource", + "b" => "mew mew is also opensource" + }, + "summary" => "unrelevant", + "summaryMap" => %{ + "a" => "ZFS is very opensource", + "b" => "mew mew is also very opensource" + } + } + } + + {:ok, + %{ + "object" => %{ + "content" => content, + "contentMap" => + %{ + "a" => "ZFS is free software", + "b" => "mew mew is also free software" + } = content_map, + "summary" => summary, + "summaryMap" => + %{ + "a" => "ZFS is very free software", + "b" => "mew mew is also very free software" + } = summary_map + } + }} = KeywordPolicy.filter(message) + + assert content == Pleroma.MultiLanguage.map_to_str(content_map, multiline: true) + assert summary == Pleroma.MultiLanguage.map_to_str(summary_map, multiline: false) + end + test "replaces keyword if string matches in history" do clear_config([:mrf_keyword, :replace], [{"opensource", "free software"}]) From e958ec57dbe6dbc59dc3671c62c1e1b46e427809 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 15 Jan 2023 14:58:27 -0500 Subject: [PATCH 29/38] Make no_empty_policy multilang-aware --- .../web/activity_pub/mrf/no_empty_policy.ex | 12 +++- .../activity_pub/mrf/no_empty_policy_test.exs | 60 +++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex index 12bf4ddd2..ee08a48bc 100644 --- a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex @@ -43,8 +43,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do defp only_mentions?(%{"object" => %{"type" => "Note", "source" => source}}) do source = case source do - %{"content" => text} -> text - _ -> source + %{"contentMap" => %{} = text_map} -> + text_map + |> Enum.map(fn {_, content} -> content end) + |> Enum.join("\n") + + %{"content" => text} -> + text + + _ -> + source end non_mentions = diff --git a/test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs b/test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs index 386ed395f..3d5fa3a47 100644 --- a/test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs @@ -129,6 +129,66 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicyTest do assert NoEmptyPolicy.filter(message) == {:reject, "[NoEmptyPolicy]"} end + test "Notes with only mentions in source.contentMap are denied" do + message = %{ + "actor" => "http://localhost:4001/users/testuser", + "cc" => ["http://localhost:4001/users/testuser/followers"], + "object" => %{ + "actor" => "http://localhost:4001/users/testuser", + "attachment" => [], + "cc" => ["http://localhost:4001/users/testuser/followers"], + "source" => %{ + "contentMap" => %{ + "a" => "@user2", + "b" => "@user2" + } + }, + "to" => [ + "https://www.w3.org/ns/activitystreams#Public", + "http://localhost:4001/users/user2" + ], + "type" => "Note" + }, + "to" => [ + "https://www.w3.org/ns/activitystreams#Public", + "http://localhost:4001/users/user2" + ], + "type" => "Create" + } + + assert NoEmptyPolicy.filter(message) == {:reject, "[NoEmptyPolicy]"} + end + + test "Notes with mentions and other content in source.contentMap are allowed" do + message = %{ + "actor" => "http://localhost:4001/users/testuser", + "cc" => ["http://localhost:4001/users/testuser/followers"], + "object" => %{ + "actor" => "http://localhost:4001/users/testuser", + "attachment" => [], + "cc" => ["http://localhost:4001/users/testuser/followers"], + "source" => %{ + "contentMap" => %{ + "a" => "@user2", + "b" => "@user2 lol" + } + }, + "to" => [ + "https://www.w3.org/ns/activitystreams#Public", + "http://localhost:4001/users/user2" + ], + "type" => "Note" + }, + "to" => [ + "https://www.w3.org/ns/activitystreams#Public", + "http://localhost:4001/users/user2" + ], + "type" => "Create" + } + + assert {:ok, _} = NoEmptyPolicy.filter(message) + end + test "Notes with no content are denied" do message = %{ "actor" => "http://localhost:4001/users/testuser", From fb2688952f58dfbd8f823fb05e1c50726a6bfaac Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 15 Jan 2023 15:08:14 -0500 Subject: [PATCH 30/38] Make normalize_markup multilang aware --- .../web/activity_pub/mrf/normalize_markup.ex | 24 +++++++++++++++---- .../mrf/normalize_markup_test.exs | 19 +++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex index 2dfc9a901..d3f077179 100644 --- a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex +++ b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex @@ -16,11 +16,27 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do when type in ["Create", "Update"] do scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy]) - content = - child_object["content"] - |> HTML.filter_tags(scrub_policy) + object = + with %{} = content_map <- child_object["contentMap"] do + fixed_content_map = + Enum.reduce(content_map, %{}, fn {lang, content}, acc -> + Map.put(acc, lang, HTML.filter_tags(content, scrub_policy)) + end) - object = put_in(object, ["object", "content"], content) + object + |> put_in(["object", "contentMap"], fixed_content_map) + |> put_in( + ["object", "content"], + Pleroma.MultiLanguage.map_to_str(fixed_content_map, multiline: true) + ) + else + _ -> + content = + child_object["content"] + |> HTML.filter_tags(scrub_policy) + + put_in(object, ["object", "content"], content) + end {:ok, object} end diff --git a/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs b/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs index 530c5f4a0..0bac9c3fd 100644 --- a/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs +++ b/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs @@ -38,6 +38,25 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkupTest do assert res["object"]["content"] == @expected end + test "multilang-aware" do + message = %{ + "type" => "Create", + "object" => %{ + "content" => "some", + "contentMap" => %{ + "a" => @html_sample, + "b" => @html_sample + } + } + } + + assert {:ok, res} = NormalizeMarkup.filter(message) + assert res["object"]["contentMap"] == %{"a" => @expected, "b" => @expected} + + assert res["object"]["content"] == + Pleroma.MultiLanguage.map_to_str(res["object"]["contentMap"], multiline: true) + end + test "history-aware" do message = %{ "type" => "Create", From 3d3162f3e67d65243d3e92d333a39722fe22b0c3 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 15 Jan 2023 15:19:32 -0500 Subject: [PATCH 31/38] Make no_placeholder_text_policy multilang-aware --- .../mrf/no_placeholder_text_policy.ex | 42 ++++++++++++++++++- .../mrf/no_placeholder_text_policy_test.exs | 24 +++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex index f81e9e52a..2dab4f44f 100644 --- a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex @@ -6,9 +6,49 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do @moduledoc "Ensure no content placeholder is present (such as the dot from mastodon)" @behaviour Pleroma.Web.ActivityPub.MRF.Policy + @placeholders [".", "

.

"] + @impl true def history_awareness, do: :auto + @impl true + def filter( + %{ + "type" => type, + "object" => %{"contentMap" => %{} = content_map, "attachment" => _} = _child_object + } = object + ) + when type in ["Create", "Update"] do + fixed_content_map = + Enum.reduce(content_map, %{}, fn {lang, content}, acc -> + if content in @placeholders do + acc + else + Map.put(acc, lang, content) + end + end) + + fixed_object = + if fixed_content_map == %{} do + Map.put( + object, + "object", + object["object"] + |> Map.drop(["contentMap"]) + |> Map.put("content", "") + ) + else + object + |> put_in(["object", "contentMap"], fixed_content_map) + |> put_in( + ["object", "content"], + Pleroma.MultiLanguage.map_to_str(fixed_content_map, multiline: true) + ) + end + + {:ok, fixed_object} + end + @impl true def filter( %{ @@ -16,7 +56,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do "object" => %{"content" => content, "attachment" => _} = _child_object } = object ) - when type in ["Create", "Update"] and content in [".", "

.

"] do + when type in ["Create", "Update"] and content in @placeholders do {:ok, put_in(object, ["object", "content"], "")} end diff --git a/test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs b/test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs index 3533c2bc8..6fb5e656d 100644 --- a/test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs @@ -21,6 +21,30 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicyTest do assert res["object"]["content"] == "" end + test "multilang aware" do + message = %{ + "type" => "Create", + "object" => %{ + "content" => ".", + "contentMap" => %{"a" => ".", "b" => "lol"}, + "attachment" => "image" + } + } + + assert {:ok, res} = NoPlaceholderTextPolicy.filter(message) + assert res["object"]["content"] == "lol" + assert res["object"]["contentMap"] == %{"b" => "lol"} + + message = %{ + "type" => "Create", + "object" => %{"content" => ".", "contentMap" => %{"a" => "."}, "attachment" => "image"} + } + + assert {:ok, res} = NoPlaceholderTextPolicy.filter(message) + assert res["object"]["content"] == "" + assert res["object"]["contentMap"] == nil + end + test "history-aware" do message = %{ "type" => "Create", From 548473a818d2417dc9e6b43fdea4dae7cd6742ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Tue, 14 May 2024 23:58:29 +0200 Subject: [PATCH 32/38] Remove unused and fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../object_validators/content_language_map.ex | 28 +--------- .../object_validators/language_code.ex | 8 +-- .../object_validators/map_of_string.ex | 27 --------- lib/pleroma/multi_language.ex | 14 +---- .../object_validators/attachment_validator.ex | 2 +- .../object_validators/common_fields.ex | 4 +- .../object_validators/common_fixes.ex | 6 +- .../question_options_validator.ex | 2 +- lib/pleroma/web/common_api/activity_draft.ex | 43 ++++++--------- lib/pleroma/web/common_api/utils.ex | 2 +- .../controllers/status_controller.ex | 40 +++++++------- .../web/mastodon_api/views/status_view.ex | 2 +- .../object_validators/map_of_string_test.exs | 55 ------------------- 13 files changed, 52 insertions(+), 181 deletions(-) delete mode 100644 lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex delete mode 100644 test/pleroma/ecto_type/activity_pub/object_validators/map_of_string_test.exs diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/content_language_map.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/content_language_map.ex index 2cc0fda00..0271c0b7e 100644 --- a/lib/pleroma/ecto_type/activity_pub/object_validators/content_language_map.ex +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/content_language_map.ex @@ -5,13 +5,13 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.ContentLanguageMap do use Ecto.Type - import Pleroma.EctoType.ActivityPub.ObjectValidators.LanguageCode, - only: [is_good_locale_code?: 1] + alias Pleroma.MultiLanguage def type, do: :map def cast(%{} = object) do - with {status, %{} = data} when status in [:modified, :ok] <- validate_map(object) do + with {status, %{} = data} when status in [:modified, :ok] <- + MultiLanguage.validate_map(object) do {:ok, data} else {_, nil} -> {:ok, nil} @@ -24,26 +24,4 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.ContentLanguageMap do def dump(data), do: {:ok, data} def load(data), do: {:ok, data} - - defp validate_map(%{} = object) do - {status, data} = - object - |> Enum.reduce({:ok, %{}}, fn - {lang, value}, {status, acc} when is_binary(lang) and is_binary(value) -> - if is_good_locale_code?(lang) do - {status, Map.put(acc, lang, value)} - else - {:modified, acc} - end - - _, {_status, acc} -> - {:modified, acc} - end) - - if data == %{} do - {status, nil} - else - {status, data} - end - end end diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/language_code.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/language_code.ex index b15e9ec5e..0cf72f404 100644 --- a/lib/pleroma/ecto_type/activity_pub/object_validators/language_code.ex +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/language_code.ex @@ -5,10 +5,12 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.LanguageCode do use Ecto.Type + alias Pleroma.MultiLanguage + def type, do: :string def cast(language) when is_binary(language) do - if is_good_locale_code?(language) do + if MultiLanguage.is_good_locale_code?(language) do {:ok, language} else {:error, :invalid_language} @@ -20,8 +22,4 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.LanguageCode do def dump(data), do: {:ok, data} def load(data), do: {:ok, data} - - def is_good_locale_code?(code) when is_binary(code), do: code =~ ~r<^[a-zA-Z0-9\-]+$> - - def is_good_locale_code?(_code), do: false end diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex deleted file mode 100644 index c36fe00b5..000000000 --- a/lib/pleroma/ecto_type/activity_pub/object_validators/map_of_string.ex +++ /dev/null @@ -1,27 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString do - use Ecto.Type - - alias Pleroma.MultiLanguage - - def type, do: :map - - def cast(%{} = object) do - with {status, %{} = data} when status in [:modified, :ok] <- - MultiLanguage.validate_map(object) do - {:ok, data} - else - {_, nil} -> {:ok, nil} - {:error, _} -> :error - end - end - - def cast(_), do: :error - - def dump(data), do: {:ok, data} - - def load(data), do: {:ok, data} -end diff --git a/lib/pleroma/multi_language.ex b/lib/pleroma/multi_language.ex index ca69b7981..5d74c19b9 100644 --- a/lib/pleroma/multi_language.ex +++ b/lib/pleroma/multi_language.ex @@ -9,19 +9,9 @@ defmodule Pleroma.MultiLanguage do defp sep(:multi), do: Pleroma.Config.get([__MODULE__, :separator]) defp sep(:single), do: Pleroma.Config.get([__MODULE__, :single_line_separator]) - def is_good_locale_code?(code) do - code - |> String.codepoints() - |> Enum.all?(&valid_char?/1) - end + def is_good_locale_code?(code) when is_binary(code), do: code =~ ~r<^[a-zA-Z0-9\-]+$> - # [a-zA-Z0-9-] - defp valid_char?(char) do - ("a" <= char and char <= "z") or - ("A" <= char and char <= "Z") or - ("0" <= char and char <= "9") or - char == "-" - end + def is_good_locale_code?(_code), do: false def validate_map(%{} = object) do {status, data} = diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex index c96717e78..288fa2afa 100644 --- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do field(:type, :string, default: "Link") field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream") field(:name, :string) - field(:nameMap, ObjectValidators.MapOfString) + field(:nameMap, ObjectValidators.ContentLanguageMap) field(:blurhash, :string) embeds_many :url, UrlObjectValidator, primary_key: false do diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex index b626ccca6..881ab35a6 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex @@ -50,9 +50,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do embeds_many(:tag, TagValidator) field(:name, :string) - field(:nameMap, ObjectValidators.MapOfString) + field(:nameMap, ObjectValidators.ContentLanguageMap) field(:summary, :string) - field(:summaryMap, ObjectValidators.MapOfString) + field(:summaryMap, ObjectValidators.ContentLanguageMap) field(:context, :string) diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex index ea90e0819..aba393232 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Language.LanguageDetector alias Pleroma.Maps + alias Pleroma.MultiLanguage alias Pleroma.Object alias Pleroma.Object.Containment alias Pleroma.User @@ -14,9 +15,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do require Pleroma.Constants - import Pleroma.EctoType.ActivityPub.ObjectValidators.LanguageCode, - only: [is_good_locale_code?: 1] - import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do @@ -149,7 +147,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do get_language_from_content_map(object), get_language_from_content(object) ] - |> Enum.find(&is_good_locale_code?(&1)) + |> Enum.find(&MultiLanguage.is_good_locale_code?(&1)) if language do Map.put(object, "language", language) diff --git a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex index 2f80fb9b9..6927cd71f 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator do embedded_schema do field(:name, :string) field(:nameRendered, :string) - field(:nameMap, ObjectValidators.MapOfString) + field(:nameMap, ObjectValidators.ContentLanguageMap) embeds_one :replies, Replies, primary_key: false do field(:totalItems, :integer) diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 1ba33a435..67238c41a 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -14,9 +14,6 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils - import Pleroma.EctoType.ActivityPub.ObjectValidators.LanguageCode, - only: [is_good_locale_code?: 1] - import Pleroma.Web.Gettext import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] @@ -48,7 +45,6 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do cc: [], context: nil, sensitive: false, - language: nil, object: nil, preview?: false, changes: %{}, @@ -66,7 +62,6 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do def create(user, params) do user |> new(params) - |> language() |> status() |> summary() |> with_valid(&attachments/1) @@ -81,9 +76,9 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> with_valid(&content/1) |> with_valid(&to_and_cc/1) |> with_valid(&context/1) - |> with_valid(&language/1) |> sensitive() |> with_valid(&object/1) + |> with_valid(&language/1) |> preview?() |> with_valid(&changes/1) |> validate() @@ -155,19 +150,27 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do %__MODULE__{draft | params: params} end - defp language(%{params: %{language: language}} = draft) do + defp language(draft) do + language = + draft.params[:language] || + LanguageDetector.detect( + draft.content_html <> " " <> (draft.summary || draft.params[:name]) + ) + if MultiLanguage.is_good_locale_code?(language) do %__MODULE__{draft | language: language} else - add_error( - draft, - dgettext("errors", "language \"%{language}\" is invalid", language: language) - ) + if draft.params[:language] do + add_error( + draft, + dgettext("errors", "language \"%{language}\" is invalid", language: language) + ) + else + draft + end end end - defp language(draft), do: draft - defp status(%{params: %{status_map: %{} = status_map}} = draft) do with {:ok, %{}} <- MultiLanguage.validate_map(status_map) do %__MODULE__{draft | status_map: status_map} @@ -355,20 +358,6 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do %__MODULE__{draft | sensitive: sensitive} end - defp language(draft) do - language = - draft.params[:language] || - LanguageDetector.detect( - draft.content_html <> " " <> (draft.summary || draft.params[:name]) - ) - - if is_good_locale_code?(language) do - %__MODULE__{draft | language: language} - else - draft - end - end - defp object(draft) do emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji) diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index d908dcde1..db805889d 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -152,7 +152,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do when is_list(options_map) do limits = Config.get([:instance, :poll_limits]) - options = options |> Enum.uniq() + options_map = options_map |> Enum.uniq() with :ok <- validate_poll_expiration(expires_in, limits), :ok <- validate_poll_options_map(options_map), diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index efc1d9fd7..575f7188f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -205,6 +205,26 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do |> do_create end + def create( + %{ + assigns: %{user: _user}, + private: %{open_api_spex: %{body_params: %{status_map: _}}} + } = conn, + _ + ) do + create(conn |> put_in([:private, :open_api_spex, :body_params, :status], ""), %{}) + end + + def create( + %{ + assigns: %{user: _user}, + private: %{open_api_spex: %{body_params: %{media_ids: _}}} + } = conn, + _ + ) do + create(conn |> put_in([:private, :open_api_spex, :body_params, :status], ""), %{}) + end + defp do_create( %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: params}}} = conn ) do @@ -232,26 +252,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end end - def create( - %{ - assigns: %{user: _user}, - private: %{open_api_spex: %{body_params: %{status_map: _}}} = params - } = conn, - _ - ) do - create(conn |> put_in([:private, :open_api_spex, :body_params, :status], ""), %{}) - end - - def create( - %{ - assigns: %{user: _user}, - private: %{open_api_spex: %{body_params: %{media_ids: _}}} = params - } = conn, - _ - ) do - create(conn |> put_in([:private, :open_api_spex, :body_params, :status], ""), %{}) - end - @doc "GET /api/v1/statuses/:id/history" def show_history( %{assigns: assigns, private: %{open_api_spex: %{params: %{id: id} = params}}} = conn, diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 751b38f3c..7b0cf0ca8 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -449,7 +449,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do mentions: mentions, tags: build_tags(tags), application: build_application(object.data["generator"]), - language: get_language(object), + language: get_language(object.data), emojis: build_emojis(object.data["emoji"]), pleroma: %{ local: activity.local, diff --git a/test/pleroma/ecto_type/activity_pub/object_validators/map_of_string_test.exs b/test/pleroma/ecto_type/activity_pub/object_validators/map_of_string_test.exs deleted file mode 100644 index 4ee179dc8..000000000 --- a/test/pleroma/ecto_type/activity_pub/object_validators/map_of_string_test.exs +++ /dev/null @@ -1,55 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfStringTest do - alias Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString - use Pleroma.DataCase, async: true - - test "it validates" do - data = %{ - "en-US" => "mew mew", - "en-GB" => "meow meow" - } - - assert {:ok, ^data} = MapOfString.cast(data) - end - - test "it validates empty strings" do - data = %{ - "en-US" => "mew mew", - "en-GB" => "" - } - - assert {:ok, ^data} = MapOfString.cast(data) - end - - test "it ignores non-strings within the map" do - data = %{ - "en-US" => "mew mew", - "en-GB" => 123 - } - - assert {:ok, validated_data} = MapOfString.cast(data) - - assert validated_data == %{"en-US" => "mew mew"} - end - - test "it ignores bad locale codes" do - data = %{ - "en-US" => "mew mew", - "en_GB" => "meow meow", - "en<<#@!$#!@%!GB" => "meow meow" - } - - assert {:ok, validated_data} = MapOfString.cast(data) - - assert validated_data == %{"en-US" => "mew mew"} - end - - test "it complains with non-map data" do - assert :error = MapOfString.cast("mew") - assert :error = MapOfString.cast(["mew"]) - assert :error = MapOfString.cast([%{"en-US" => "mew"}]) - end -end From 56f19a7e56d51f676b3d845618feb7d62e0beb74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 16 May 2024 22:29:01 +0200 Subject: [PATCH 33/38] Fix language dectection when creating posts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/common_api/activity_draft.ex | 33 ++++++++++---------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 67238c41a..ec101c8ef 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -65,6 +65,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> status() |> summary() |> with_valid(&attachments/1) + |> with_valid(&language/1) |> with_valid(&full_payload/1) |> expires_at() |> poll() @@ -78,7 +79,6 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> with_valid(&context/1) |> sensitive() |> with_valid(&object/1) - |> with_valid(&language/1) |> preview?() |> with_valid(&changes/1) |> validate() @@ -150,24 +150,25 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do %__MODULE__{draft | params: params} end - defp language(draft) do - language = - draft.params[:language] || - LanguageDetector.detect( - draft.content_html <> " " <> (draft.summary || draft.params[:name]) - ) - + defp language(%{params: %{language: language}} = draft) when not is_nil(language) do if MultiLanguage.is_good_locale_code?(language) do %__MODULE__{draft | language: language} else - if draft.params[:language] do - add_error( - draft, - dgettext("errors", "language \"%{language}\" is invalid", language: language) - ) - else - draft - end + add_error( + draft, + dgettext("errors", "language \"%{language}\" is invalid", language: language) + ) + end + end + + defp language(draft) do + detected_language = + LanguageDetector.detect(draft.status <> " " <> (draft.summary || draft.params[:summary])) + + if MultiLanguage.is_good_locale_code?(detected_language) do + %__MODULE__{draft | language: detected_language} + else + draft end end From 8dfd0797f368e3028e54ecba92b153571aebf264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 18 May 2024 22:42:09 +0200 Subject: [PATCH 34/38] Fix multilanguage, add feature to InstanceView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/common_api/activity_draft.ex | 4 +++- lib/pleroma/web/mastodon_api/views/instance_view.ex | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index ec101c8ef..190d1bd94 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -161,7 +161,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do end end - defp language(draft) do + defp language(%{status: status} = draft) when is_binary(status) do detected_language = LanguageDetector.detect(draft.status <> " " <> (draft.summary || draft.params[:summary])) @@ -172,6 +172,8 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do end end + defp language(draft), do: draft + defp status(%{params: %{status_map: %{} = status_map}} = draft) do with {:ok, %{}} <- MultiLanguage.validate_map(status_map) do %__MODULE__{draft | status_map: status_map} diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 4ba119742..c2cec8d4d 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -178,7 +178,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "translation" end, "events", - "multitenancy" + "multitenancy", + "pleroma:multi_language" ] |> Enum.filter(& &1) end From 5b8ad04f8b7887c0b3f1af01a5a4efc93e36b98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Tue, 28 May 2024 16:45:35 +0200 Subject: [PATCH 35/38] Fix notification types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/notification.ex | 1 + lib/pleroma/web/api_spec/operations/notification_operation.ex | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index f50675fc4..b7c45f886 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -74,6 +74,7 @@ defmodule Pleroma.Notification do reblog poll status + update pleroma:participation_accepted pleroma:participation_request pleroma:event_reminder diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex index a6aa3b7f2..993ea08a9 100644 --- a/lib/pleroma/web/api_spec/operations/notification_operation.ex +++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex @@ -208,6 +208,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do "follow_request", "poll", "status", + "update", "pleroma:participation_accepted", "pleroma:participation_request", "pleroma:event_reminder", @@ -227,6 +228,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do - `pleroma:chat_mention` - Someone mentioned you in a chat message - `pleroma:report` - Someone was reported - `status` - Someone you are subscribed to created a status + - `update` - A status you interacted with was updated - `pleroma:event_reminder` – An event you are participating in or created is taking place soon - `pleroma:event_update` – An event you are participating in was edited - `pleroma:participation_request - Someone wants to participate in your event From 9b0c05f70c9dafdb8e987a07bcabb06a818e0a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 29 May 2024 23:28:04 +0200 Subject: [PATCH 36/38] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/activity_pub/builder.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index a2993d28a..82d47c02b 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -221,12 +221,12 @@ defmodule Pleroma.Web.ActivityPub.Builder do if draft.content_html_map do case Map.keys(draft.content_html_map) do ["und"] -> - %{"content" => MultiLanguage.map_to_str(draft.content_html_map, multiline: true)} + %{"content" => Map.get(draft.content_html_map, "und")} _ -> %{ "contentMap" => draft.content_html_map, - "content" => MultiLanguage.map_to_str(draft.content_html_map, multiline: true) + "content" => Map.get(draft.content_html_map, draft.language) } end else @@ -237,12 +237,12 @@ defmodule Pleroma.Web.ActivityPub.Builder do if draft.summary_map do case Map.keys(draft.summary_map) do ["und"] -> - %{"summary" => MultiLanguage.map_to_str(draft.summary_map, multiline: false)} + %{"summary" => Map.get(draft.summary_map, "und")} _ -> %{ "summaryMap" => draft.summary_map, - "summary" => MultiLanguage.map_to_str(draft.summary_map, multiline: false) + "summary" => Map.get(draft.summary_map, draft.language) } end else From 48be78b375405da99b362f6320310125ef6b3e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 31 May 2024 23:07:28 +0200 Subject: [PATCH 37/38] MultiLanguage: Remove map_to_str MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- config/config.exs | 6 --- lib/pleroma/multi_language.ex | 39 ------------------ lib/pleroma/upload.ex | 20 +++++---- lib/pleroma/web/activity_pub/activity_pub.ex | 24 +++++++---- lib/pleroma/web/activity_pub/builder.ex | 1 - .../activity_pub/mrf/ensure_re_prepended.ex | 11 ++++- .../mrf/force_mentions_in_content.ex | 5 +-- .../web/activity_pub/mrf/keyword_policy.ex | 12 ++---- .../mrf/no_placeholder_text_policy.ex | 14 ++++--- .../web/activity_pub/mrf/normalize_markup.ex | 5 +-- .../article_note_page_validator.ex | 3 -- .../audio_image_video_validator.ex | 3 -- .../object_validators/common_fixes.ex | 9 ---- .../question_options_validator.ex | 9 ---- .../object_validators/question_validator.ex | 3 -- .../api_spec/operations/media_operation.ex | 5 +++ lib/pleroma/web/common_api/activity_draft.ex | 10 +++-- lib/pleroma/web/common_api/utils.ex | 2 +- .../controllers/media_controller.ex | 41 +++++++++++++++---- test/pleroma/multi_language_test.exs | 36 ---------------- .../mrf/ensure_re_prepended_test.exs | 5 +-- .../mrf/force_mentions_in_content_test.exs | 11 +++-- .../activity_pub/mrf/keyword_policy_test.exs | 23 ++++------- .../mrf/no_placeholder_text_policy_test.exs | 2 +- .../mrf/normalize_markup_test.exs | 3 +- .../article_note_page_validator_test.exs | 5 --- 26 files changed, 115 insertions(+), 192 deletions(-) diff --git a/config/config.exs b/config/config.exs index d43471f01..35b1716a6 100644 --- a/config/config.exs +++ b/config/config.exs @@ -940,12 +940,6 @@ config :pleroma, ConcurrentLimiter, [ config :pleroma, Pleroma.Web.WebFinger, domain: nil, update_nickname_on_user_fetch: true -config :pleroma, Pleroma.MultiLanguage, - template: "
{content}
", - separator: "


", - single_line_template: "[{code}] {content}", - single_line_separator: " | " - config :pleroma, Pleroma.Language.Translation, allow_unauthenticated: false, allow_remote: true config :geospatial, Geospatial.Service, service: Geospatial.Providers.Nominatim diff --git a/lib/pleroma/multi_language.ex b/lib/pleroma/multi_language.ex index 629d925f2..ad6a28b39 100644 --- a/lib/pleroma/multi_language.ex +++ b/lib/pleroma/multi_language.ex @@ -3,12 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.MultiLanguage do - defp template(:multi), do: Pleroma.Config.get([__MODULE__, :template]) - defp template(:single), do: Pleroma.Config.get([__MODULE__, :single_line_template]) - - defp sep(:multi), do: Pleroma.Config.get([__MODULE__, :separator]) - defp sep(:single), do: Pleroma.Config.get([__MODULE__, :single_line_separator]) - def good_locale_code?(code) when is_binary(code), do: code =~ ~r<^[a-zA-Z0-9\-]+$> def good_locale_code?(_code), do: false @@ -37,27 +31,6 @@ defmodule Pleroma.MultiLanguage do def validate_map(_), do: {:error, nil} - def map_to_str(data, opts \\ []) do - map_to_str_impl(data, if(opts[:multiline], do: :multi, else: :single)) - end - - defp map_to_str_impl(data, mode) do - with ks <- Map.keys(data), - [_, _ | _] <- ks, - ks <- Enum.sort(ks) do - template = template(mode) - - ks - |> Enum.map(fn lang -> - format_template(template, %{code: lang, content: data[lang]}) - end) - |> Enum.join(sep(mode)) - else - [lang] -> data[lang] - _ -> nil - end - end - def str_to_map(data, opts \\ []) do with lang when is_binary(lang) <- opts[:lang], true <- good_locale_code?(lang) do @@ -67,16 +40,4 @@ defmodule Pleroma.MultiLanguage do %{"und" => data} end end - - def format_template(template, %{code: code, content: content}) do - template - |> String.replace( - ["{code}", "{content}"], - fn - "{code}" -> code - "{content}" -> content - end, - global: true - ) - end end diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index c57ebc4f9..a3dd238b2 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -91,23 +91,23 @@ defmodule Pleroma.Upload do end defp validate_description_limit(%{} = description) do - len = Enum.reduce(description, 0, fn {_, content}, acc -> String.length(content) + acc end) - - len <= Pleroma.Config.get([:instance, :description_limit]) + Enum.each(description, fn content -> + String.length(content) <= Pleroma.Config.get([:instance, :description_limit]) + end) end defp validate_description_limit(description) when is_binary(description) do String.length(description) <= Pleroma.Config.get([:instance, :description_limit]) end - defp description_fields(%{} = description) do + defp description_fields(%{} = description, language) do %{ - "name" => Pleroma.MultiLanguage.map_to_str(description, multiline: false), + "name" => Map.get(description, language), "nameMap" => description } end - defp description_fields(description) when is_binary(description) do + defp description_fields(description, _) when is_binary(description) do %{"name" => description} end @@ -121,6 +121,9 @@ defmodule Pleroma.Upload do {:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload), description = get_description(upload), {_, true} <- {:description_limit, validate_description_limit(description)}, + {_, true} <- + {:valid_locale, + opts[:language] == nil or Pleroma.MultiLanguage.good_locale_code?(opts[:language])}, {:ok, url_spec} <- Pleroma.Uploaders.Uploader.put_file(opts.uploader, upload) do {:ok, %{ @@ -137,8 +140,9 @@ defmodule Pleroma.Upload do |> Maps.put_if_present("height", upload.height) ] } - |> Map.merge(description_fields(description)) - |> Maps.put_if_present("blurhash", upload.blurhash)} + |> Map.merge(description_fields(description, opts[:language])) + |> Maps.put_if_present("blurhash", upload.blurhash) + |> Maps.put_if_present("language", opts[:language])} else {:description_limit, _} -> {:error, :description_too_long} diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 64185220c..b5d8a9e47 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1593,27 +1593,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> Enum.reverse() end - defp validate_media_description_map(%{} = map) do - with {:ok, %{}} <- Pleroma.MultiLanguage.validate_map(map) do + defp validate_media_description_map(%{} = map, language) do + with {:ok, %{}} <- Pleroma.MultiLanguage.validate_map(map), + true <- Pleroma.MultiLanguage.good_locale_code?(language) do :ok else + false -> :invalid_language _ -> :error end end - defp validate_media_description_map(nil), do: :ok - defp validate_media_description_map(_), do: :error + defp validate_media_description_map(nil, _), do: :ok + defp validate_media_description_map(_, _), do: :error @spec upload(Upload.source(), keyword()) :: {:ok, Object.t()} | {:error, any()} def upload(file, opts \\ []) do - with {_, :ok} <- {:description_map, validate_media_description_map(opts[:description_map])}, + with {_, :ok} <- + {:description_map, + validate_media_description_map(opts[:description_map], opts[:language])}, {:ok, data} <- Upload.store(sanitize_upload_file(file), opts) do obj_data = Maps.put_if_present(data, "actor", opts[:actor]) Repo.insert(%Object{data: obj_data}) else - {:description_map, _} -> {:error, dgettext("errors", "description_map invalid")} - e -> e + {:description_map, :invalid_language} -> + {:error, dgettext("errors", "valid language must be provided with description_map")} + + {:description_map, _} -> + {:error, dgettext("errors", "description_map invalid")} + + e -> + e end end diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 82d47c02b..0901c7d58 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -11,7 +11,6 @@ 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 diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex index 74f7d9858..c02c49fb9 100644 --- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex +++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do def history_awareness, do: :auto def filter_by_summary( - %{data: %{"summaryMap" => %{} = parent_summary_map}} = _in_reply_to, + %{data: %{"summaryMap" => %{} = parent_summary_map} = parent}, %{"summaryMap" => %{} = child_summary_map} = child ) do fixed_summary_map = @@ -25,9 +25,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do end end) + fixed_summary = + with {:ok, fixed} <- fix_one(child["summary"], parent["summary"]) do + fixed + else + _ -> child["summary"] + end + child |> Map.put("summaryMap", fixed_summary_map) - |> Map.put("summary", Pleroma.MultiLanguage.map_to_str(fixed_summary_map, multiline: false)) + |> Map.put("summary", fixed_summary) end def filter_by_summary( diff --git a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex index 8b2cbdc58..eae12b095 100644 --- a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex +++ b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex @@ -103,10 +103,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do object |> Map.put("contentMap", fixed_content_map) - |> Map.put( - "content", - Pleroma.MultiLanguage.map_to_str(fixed_content_map, multiline: true) - ) + |> Map.put("content", fix_content(object["content"] || "", mention_users)) else _ -> # image-only posts from pleroma apparently reach this MRF without the content field diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex index a7e17a29f..1ee04a099 100644 --- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex @@ -88,16 +88,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do config = Pleroma.Config.get([:mrf_keyword, :replace]) replace_kw = fn object -> - [ - {"content", [multiline: true]}, - {"name", [multiline: false]}, - {"summary", [multiline: false]} - ] - |> Enum.filter(fn {field, _} -> + ["content", "name", "summary"] + |> Enum.filter(fn field -> is_map(object[field <> "Map"]) or (Map.has_key?(object, field) && object[field]) end) - |> Enum.reduce(object, fn {field, opts}, object -> + |> Enum.reduce(object, fn field, object -> field_name_map = field <> "Map" with %{} = data_map <- object[field_name_map] do @@ -108,7 +104,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do object |> Map.put(field_name_map, fixed_data_map) - |> Map.put(field, Pleroma.MultiLanguage.map_to_str(fixed_data_map, opts)) + |> Map.put(field, replace_keyword(object[field], config)) else _ -> data = replace_keyword(object[field], config) diff --git a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex index 2dab4f44f..23b5324ae 100644 --- a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do def filter( %{ "type" => type, - "object" => %{"contentMap" => %{} = content_map, "attachment" => _} = _child_object + "object" => %{"contentMap" => %{} = content_map, "attachment" => _} = child_object } = object ) when type in ["Create", "Update"] do @@ -28,6 +28,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do end end) + fixed_content = + if child_object["content"] in @placeholders do + "" + else + child_object["content"] + end + fixed_object = if fixed_content_map == %{} do Map.put( @@ -40,10 +47,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do else object |> put_in(["object", "contentMap"], fixed_content_map) - |> put_in( - ["object", "content"], - Pleroma.MultiLanguage.map_to_str(fixed_content_map, multiline: true) - ) + |> put_in(["object", "content"], fixed_content) end {:ok, fixed_object} diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex index d3f077179..2493f4574 100644 --- a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex +++ b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex @@ -25,10 +25,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do object |> put_in(["object", "contentMap"], fixed_content_map) - |> put_in( - ["object", "content"], - Pleroma.MultiLanguage.map_to_str(fixed_content_map, multiline: true) - ) + |> put_in(["object", "content"], HTML.filter_tags(child_object["content"], scrub_policy)) else _ -> content = diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex index bd2667b2c..a534e95fa 100644 --- a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex @@ -86,9 +86,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do |> fix_attachments() |> CommonFixes.fix_quote_url() |> Transmogrifier.fix_emoji() - |> CommonFixes.fix_multilang_field("content", "contentMap", multiline: true) - |> CommonFixes.fix_multilang_field("summary", "summaryMap", multiline: false) - |> CommonFixes.fix_multilang_field("name", "nameMap", multiline: false) |> CommonFixes.maybe_add_language() |> CommonFixes.maybe_add_content_map() end diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex index c500bfd52..65ac6bb93 100644 --- a/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex @@ -102,9 +102,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator do |> CommonFixes.fix_quote_url() |> Transmogrifier.fix_emoji() |> fix_url() - |> CommonFixes.fix_multilang_field("content", "contentMap", multiline: true) - |> CommonFixes.fix_multilang_field("summary", "summaryMap", multiline: false) - |> CommonFixes.fix_multilang_field("name", "nameMap", multiline: false) |> fix_content() end diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex index 359beaf64..12c759b42 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -194,13 +194,4 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do end def maybe_add_content_map(object), do: object - - def fix_multilang_field(data, str_field, map_field, opts \\ []) do - with %{} = map <- data[map_field], - str when is_binary(str) <- Pleroma.MultiLanguage.map_to_str(map, opts) do - Map.put(data, str_field, str) - else - _ -> data - end - end end diff --git a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex index 6927cd71f..b8e0518ee 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator do import Ecto.Changeset alias Pleroma.EctoType.ActivityPub.ObjectValidators - alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes @primary_key false @@ -25,15 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator do field(:type, :string, default: "Note") end - defp fix(data) do - data - # name is used in Answers, so better not change it - |> CommonFixes.fix_multilang_field("nameRendered", "nameMap", multiline: false) - end - def changeset(struct, data) do - data = fix(data) - struct |> cast(data, [:name, :nameRendered, :nameMap, :type]) |> cast_embed(:replies, with: &replies_changeset/2) diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex index 4996a90aa..b28f096e5 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -67,9 +67,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do |> CommonFixes.fix_quote_url() |> Transmogrifier.fix_emoji() |> fix_closed() - |> CommonFixes.fix_multilang_field("content", "contentMap", multiline: true) - |> CommonFixes.fix_multilang_field("summary", "summaryMap", multiline: false) - |> CommonFixes.fix_multilang_field("name", "nameMap", multiline: false) end def changeset(struct, data) do diff --git a/lib/pleroma/web/api_spec/operations/media_operation.ex b/lib/pleroma/web/api_spec/operations/media_operation.ex index 4aa58f2a8..4e535a426 100644 --- a/lib/pleroma/web/api_spec/operations/media_operation.ex +++ b/lib/pleroma/web/api_spec/operations/media_operation.ex @@ -52,6 +52,11 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do type: :string, description: "A plain-text description of the media, for accessibility purposes." }), + language: %Schema{ + type: :string, + nullable: true, + description: "ISO 639 language code for this status." + }, focus: %Schema{ type: :string, description: "Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0." diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 77ba82854..0656d085c 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -166,7 +166,11 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do LanguageDetector.detect(draft.status <> " " <> (draft.summary || draft.params[:summary])) if MultiLanguage.good_locale_code?(detected_language) do - %__MODULE__{draft | language: detected_language} + %__MODULE__{ + draft + | params: Map.put(draft.params, :language, detected_language), + language: detected_language + } else draft end @@ -529,9 +533,9 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do 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 + defp get_source_map(%{status_map: %{} = status_map} = draft) do %{ - "content" => Pleroma.MultiLanguage.map_to_str(status_map, mutiline: true), + "content" => Map.get(status_map, draft.language), "contentMap" => status_map } end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index db805889d..23159d2d3 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -167,7 +167,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do %{"name" => option["und"]} else %{ - "name" => MultiLanguage.map_to_str(option, multiline: false), + "name" => Map.get(option, data.language), "nameMap" => option } end diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index 079dcf732..ab2f9636f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do use Pleroma.Web, :controller + alias Pleroma.MultiLanguage alias Pleroma.Object alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.Plugs.OAuthScopesPlug @@ -24,17 +25,25 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do conn, _ ) do - with {:ok, object} <- + with language <- Map.get(data, :language), + {_, true} <- + {:valid_locale, + Map.get(data, :description_map) == nil or MultiLanguage.good_locale_code?(language)}, + {:ok, object} <- ActivityPub.upload( file, actor: user.ap_id, description: Map.get(data, :description), - description_map: Map.get(data, :description_map) + description_map: Map.get(data, :description_map), + language: language ) do attachment_data = Map.put(object.data, "id", object.id) render(conn, "attachment.json", %{attachment: attachment_data}) else + {:valid_locale, _} -> + render_error(conn, 422, "valid language must be provided with description_map") + {:error, e} -> conn |> put_status(:unprocessable_entity) @@ -50,12 +59,17 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do conn, _ ) do - with {:ok, object} <- + with language <- Map.get(data, :language), + {_, true} <- + {:valid_locale, + Map.get(data, :description_map) == nil or MultiLanguage.good_locale_code?(language)}, + {:ok, object} <- ActivityPub.upload( file, actor: user.ap_id, description: Map.get(data, :description), - description_map: Map.get(data, :description_map) + description_map: Map.get(data, :description_map), + language: language ) do attachment_data = Map.put(object.data, "id", object.id) @@ -63,6 +77,9 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do |> put_status(202) |> render("attachment.json", %{attachment: attachment_data}) else + {:valid_locale, _} -> + render_error(conn, 422, "valid language must be provided with description_map") + {:error, e} -> conn |> put_status(:unprocessable_entity) @@ -78,7 +95,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do assigns: %{user: user}, private: %{ open_api_spex: %{ - body_params: %{description_map: %{} = description_map}, + body_params: %{description_map: %{} = description_map} = body_params, params: %{id: id} } } @@ -87,18 +104,24 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do ) do with %Object{} = object <- Object.get_by_id(id), :ok <- Object.authorize_access(object, user), - {_, {:ok, %{}}} <- - {:description_map, Pleroma.MultiLanguage.validate_map(description_map)}, + language = Map.get(body_params, :language, object["language"]), + {_, true} <- + {:valid_locale, description_map == nil or MultiLanguage.good_locale_code?(language)}, + {_, {:ok, %{}}} <- {:description_map, MultiLanguage.validate_map(description_map)}, {:ok, %Object{data: data}} <- Object.update_data(object, %{ - "name" => Pleroma.MultiLanguage.map_to_str(description_map), + "name" => Map.get(description_map, language), "nameMap" => description_map }) do attachment_data = Map.put(data, "id", object.id) render(conn, "attachment.json", %{attachment: attachment_data}) else - {:description_map, _} -> render_error(conn, 422, "description_map not valid") + {:valid_locale, _} -> + render_error(conn, 422, "valid language must be provided with description_map") + + {:description_map, _} -> + render_error(conn, 422, "description_map not valid") end end diff --git a/test/pleroma/multi_language_test.exs b/test/pleroma/multi_language_test.exs index aea25f117..3db87be65 100644 --- a/test/pleroma/multi_language_test.exs +++ b/test/pleroma/multi_language_test.exs @@ -7,42 +7,6 @@ defmodule Pleroma.MultiLanguageTest do alias Pleroma.MultiLanguage - describe "map_to_str" do - setup do - %{ - data: %{ - "en-US" => "mew", - "en-GB" => "meow" - } - } - end - - test "single line", %{data: data} do - assert MultiLanguage.map_to_str(data) == "[en-GB] meow | [en-US] mew" - end - - test "multi line", %{data: data} do - assert MultiLanguage.map_to_str(data, multiline: true) == - "
meow



mew
" - end - - test "only one language" do - data = %{"some" => "foo"} - assert MultiLanguage.map_to_str(data) == "foo" - assert MultiLanguage.map_to_str(data, multiline: true) == "foo" - end - - test "resistent to tampering" do - data = %{ - "en-US" => "mew {code} {content}", - "en-GB" => "meow {code} {content}" - } - - assert MultiLanguage.map_to_str(data) == - "[en-GB] meow {code} {content} | [en-US] mew {code} {content}" - end - end - describe "str_to_map" do test "" do assert MultiLanguage.str_to_map("foo") == %{"und" => "foo"} diff --git a/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs b/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs index 93273e39f..f56e0faf3 100644 --- a/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs +++ b/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs @@ -56,12 +56,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrependedTest do "c" => "another-object-summary" } - assert res["object"]["summary"] == - Pleroma.MultiLanguage.map_to_str(res["object"]["summaryMap"], multiline: false) + assert res["object"]["summary"] == "re: object-summary" end test "it adds `re:` to summary object when child summary contains re-subject of parent summary " do -s message = %{ + message = %{ "type" => "Create", "object" => %{ "summary" => "object-summary", diff --git a/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs b/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs index 7eb8d5163..5e4963701 100644 --- a/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs +++ b/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs @@ -123,11 +123,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContentTest do %{ "object" => %{ "content" => content, - "contentMap" => - %{ - "a" => content_a, - "b" => content_b - } = content_map + "contentMap" => %{ + "a" => content_a, + "b" => content_b + } } }} = ForceMentionsInContent.filter(activity) @@ -136,7 +135,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContentTest do assert content_a == mentions_part <> "mew mew" assert content_b == mentions_part <> "lol lol" - assert content == Pleroma.MultiLanguage.map_to_str(content_map, multiline: true) + assert content == mentions_part <> "WHA-HA!" end test "don't mention self" do diff --git a/test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs b/test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs index a40d5374c..65a184032 100644 --- a/test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs @@ -318,23 +318,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do {:ok, %{ "object" => %{ - "content" => content, - "contentMap" => - %{ - "a" => "ZFS is free software", - "b" => "mew mew is also free software" - } = content_map, - "summary" => summary, - "summaryMap" => - %{ - "a" => "ZFS is very free software", - "b" => "mew mew is also very free software" - } = summary_map + "contentMap" => %{ + "a" => "ZFS is free software", + "b" => "mew mew is also free software" + }, + "summaryMap" => %{ + "a" => "ZFS is very free software", + "b" => "mew mew is also very free software" + } } }} = KeywordPolicy.filter(message) - - assert content == Pleroma.MultiLanguage.map_to_str(content_map, multiline: true) - assert summary == Pleroma.MultiLanguage.map_to_str(summary_map, multiline: false) end test "replaces keyword if string matches in history" do diff --git a/test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs b/test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs index 6fb5e656d..cf7a3bce1 100644 --- a/test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs @@ -32,7 +32,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicyTest do } assert {:ok, res} = NoPlaceholderTextPolicy.filter(message) - assert res["object"]["content"] == "lol" + assert res["object"]["content"] == "" assert res["object"]["contentMap"] == %{"b" => "lol"} message = %{ diff --git a/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs b/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs index 0bac9c3fd..7478eac94 100644 --- a/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs +++ b/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs @@ -53,8 +53,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkupTest do assert {:ok, res} = NormalizeMarkup.filter(message) assert res["object"]["contentMap"] == %{"a" => @expected, "b" => @expected} - assert res["object"]["content"] == - Pleroma.MultiLanguage.map_to_str(res["object"]["contentMap"], multiline: true) + assert res["object"]["content"] == "some" end test "history-aware" do diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index 1f2c5a04c..608807234 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -54,14 +54,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest |> Map.put("summaryMap", summary_map) |> Map.put("contentMap", content_map) - expected_summary = Pleroma.MultiLanguage.map_to_str(summary_map, multiline: false) - expected_content = Pleroma.MultiLanguage.map_to_str(content_map, multiline: true) - assert %{ valid?: true, changes: %{ - summary: ^expected_summary, - content: ^expected_content, summaryMap: ^summary_map, contentMap: ^content_map } From a2f2d635d8b5d5ff7b180428d10355735e262a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 31 May 2024 23:39:58 +0200 Subject: [PATCH 38/38] fix tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/upload.ex | 2 +- .../article_note_page_validator.ex | 1 + .../question_options_validator.ex | 3 +-- .../web/api_spec/operations/media_operation.ex | 5 +++++ lib/pleroma/web/common_api/activity_draft.ex | 6 ++++-- .../controllers/media_controller.ex | 2 +- .../controllers/status_controller.ex | 18 ++++++++++++++++-- .../web/mastodon_api/views/poll_view.ex | 2 +- .../web/activity_pub/activity_pub_test.exs | 2 +- test/pleroma/web/activity_pub/builder_test.exs | 3 ++- .../question_options_validator_test.exs | 2 +- .../transmogrifier/note_handling_test.exs | 2 +- .../web/common_api/activity_draft_test.exs | 3 ++- test/pleroma/web/common_api/utils_test.exs | 3 ++- .../controllers/media_controller_test.exs | 9 ++++++--- .../controllers/status_controller_test.exs | 4 +++- .../web/mastodon_api/views/poll_view_test.exs | 5 ++--- .../mastodon_api/views/status_view_test.exs | 6 ++++-- 18 files changed, 54 insertions(+), 24 deletions(-) diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index a3dd238b2..87290c6c2 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -91,7 +91,7 @@ defmodule Pleroma.Upload do end defp validate_description_limit(%{} = description) do - Enum.each(description, fn content -> + Enum.all?(description, fn {_, content} -> String.length(content) <= Pleroma.Config.get([:instance, :description_limit]) end) end diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex index a534e95fa..15d0b7dcd 100644 --- a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex @@ -86,6 +86,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do |> fix_attachments() |> CommonFixes.fix_quote_url() |> Transmogrifier.fix_emoji() + |> Transmogrifier.fix_content_map() |> CommonFixes.maybe_add_language() |> CommonFixes.maybe_add_content_map() end diff --git a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex index b8e0518ee..eaddebb40 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex @@ -13,7 +13,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator do embedded_schema do field(:name, :string) - field(:nameRendered, :string) field(:nameMap, ObjectValidators.ContentLanguageMap) embeds_one :replies, Replies, primary_key: false do @@ -26,7 +25,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator do def changeset(struct, data) do struct - |> cast(data, [:name, :nameRendered, :nameMap, :type]) + |> cast(data, [:name, :nameMap, :type]) |> cast_embed(:replies, with: &replies_changeset/2) |> validate_inclusion(:type, ["Note"]) |> validate_required([:name, :type]) diff --git a/lib/pleroma/web/api_spec/operations/media_operation.ex b/lib/pleroma/web/api_spec/operations/media_operation.ex index 4e535a426..9bfdeaca9 100644 --- a/lib/pleroma/web/api_spec/operations/media_operation.ex +++ b/lib/pleroma/web/api_spec/operations/media_operation.ex @@ -103,6 +103,11 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do type: :string, description: "A plain-text description of the media, for accessibility purposes." }), + language: %Schema{ + type: :string, + nullable: true, + description: "ISO 639 language code for this status." + }, focus: %Schema{ type: :string, description: "Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0." diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 0656d085c..ffd48a972 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -163,7 +163,9 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do defp language(%{status: status} = draft) when is_binary(status) do detected_language = - LanguageDetector.detect(draft.status <> " " <> (draft.summary || draft.params[:summary])) + LanguageDetector.detect( + draft.status <> " " <> (draft.summary || draft.params[:summary] || draft.params[:name]) + ) if MultiLanguage.good_locale_code?(detected_language) do %__MODULE__{ @@ -535,7 +537,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do defp get_source_map(%{status_map: %{} = status_map} = draft) do %{ - "content" => Map.get(status_map, draft.language), + "content" => Map.get(draft, :status), "contentMap" => status_map } end diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index ab2f9636f..19ae28700 100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -104,7 +104,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do ) do with %Object{} = object <- Object.get_by_id(id), :ok <- Object.authorize_access(object, user), - language = Map.get(body_params, :language, object["language"]), + language = Map.get(body_params, :language, object.data["language"]), {_, true} <- {:valid_locale, description_map == nil or MultiLanguage.good_locale_code?(language)}, {_, {:ok, %{}}} <- {:description_map, MultiLanguage.validate_map(description_map)}, diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 575f7188f..8607add47 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -212,7 +212,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do } = conn, _ ) do - create(conn |> put_in([:private, :open_api_spex, :body_params, :status], ""), %{}) + create( + put_in( + conn, + [Access.key(:private), Access.key(:open_api_spex), Access.key(:body_params), :status], + "" + ), + %{} + ) end def create( @@ -222,7 +229,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do } = conn, _ ) do - create(conn |> put_in([:private, :open_api_spex, :body_params, :status], ""), %{}) + create( + put_in( + conn, + [Access.key(:private), Access.key(:open_api_spex), Access.key(:body_params), :status], + "" + ), + %{} + ) end defp do_create( diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex index a256e3bc8..ddf78ac0a 100644 --- a/lib/pleroma/web/mastodon_api/views/poll_view.ex +++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex @@ -65,7 +65,7 @@ defmodule Pleroma.Web.MastodonAPI.PollView do current_count = option["replies"]["totalItems"] || 0 {%{ - title: option["nameRendered"] || name, + title: name, title_map: option["nameMap"] || %{}, votes_count: current_count }, current_count + count} diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index ec18507bb..d4ca14c2c 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -1412,7 +1412,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do test "sets a multilang description if given", %{test_file: file} do {:ok, %Object{} = object} = - ActivityPub.upload(file, description_map: %{"a" => "mew", "b" => "lol"}) + ActivityPub.upload(file, description_map: %{"a" => "mew", "b" => "lol"}, language: "a") assert object.data["nameMap"] == %{"a" => "mew", "b" => "lol"} end diff --git a/test/pleroma/web/activity_pub/builder_test.exs b/test/pleroma/web/activity_pub/builder_test.exs index cb154ced4..a7ba1ba2b 100644 --- a/test/pleroma/web/activity_pub/builder_test.exs +++ b/test/pleroma/web/activity_pub/builder_test.exs @@ -56,7 +56,8 @@ defmodule Pleroma.Web.ActivityPub.BuilderTest do tags: [], summary_map: %{"a" => "mew", "b" => "lol"}, cc: [], - extra: %{} + extra: %{}, + language: "a" } assert {:ok, diff --git a/test/pleroma/web/activity_pub/object_validators/question_options_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/question_options_validator_test.exs index a45c205cf..8c2c51f9b 100644 --- a/test/pleroma/web/activity_pub/object_validators/question_options_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/question_options_validator_test.exs @@ -20,7 +20,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidatorTest "nameMap" => name_map } - assert %{valid?: true, changes: %{nameMap: ^name_map, nameRendered: _}} = + assert %{valid?: true, changes: %{nameMap: ^name_map, name: _}} = QuestionOptionsValidator.changeset(%QuestionOptionsValidator{}, data) end end diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs index 85dce57db..9bc3d4a11 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs @@ -251,7 +251,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do assert object.data["content"] == "Hi" end - test "it works for incoming notices with a nil contentMap (firefish)" do + test "it works for incoming notices with a nil content (firefish)" do data = File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Jason.decode!() diff --git a/test/pleroma/web/common_api/activity_draft_test.exs b/test/pleroma/web/common_api/activity_draft_test.exs index d2f4109e4..a56320de1 100644 --- a/test/pleroma/web/common_api/activity_draft_test.exs +++ b/test/pleroma/web/common_api/activity_draft_test.exs @@ -19,7 +19,8 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraftTest do {:ok, draft} = ActivityDraft.create(user, %{ status_map: %{"a" => "mew mew", "b" => "lol lol"}, - spoiler_text_map: %{"a" => "mew", "b" => "lol"} + spoiler_text_map: %{"a" => "mew", "b" => "lol"}, + language: "a" }) assert %{ diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs index 9b31ae54d..b02fa2687 100644 --- a/test/pleroma/web/common_api/utils_test.exs +++ b/test/pleroma/web/common_api/utils_test.exs @@ -740,7 +740,8 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do %{"a" => "bar", "c" => "2"} ], expires_in: 600 - } + }, + language: "a" }) assert %{"oneOf" => choices} = poll diff --git a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs index a171815a8..d4912691a 100644 --- a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs @@ -55,7 +55,8 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do |> put_req_header("content-type", "multipart/form-data") |> post("/api/v1/media", %{ "file" => image, - "description_map" => %{"a" => "mew", "b" => "lol"} + "description_map" => %{"a" => "mew", "b" => "lol"}, + "language" => "a" }) |> json_response_and_validate_schema(:ok) @@ -119,7 +120,8 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do |> put_req_header("content-type", "multipart/form-data") |> post("/api/v2/media", %{ "file" => image, - "description_map" => %{"a" => "mew", "b" => "lol"} + "description_map" => %{"a" => "mew", "b" => "lol"}, + "language" => "a" }) |> json_response_and_validate_schema(202) @@ -261,7 +263,8 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do conn |> put_req_header("content-type", "multipart/form-data") |> put("/api/v1/media/#{object.id}", %{ - "description_map" => %{"a" => "test-media", "b" => "xxx"} + "description_map" => %{"a" => "test-media", "b" => "xxx"}, + "language" => "a" }) |> json_response_and_validate_schema(:ok) diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index ec0982253..a456c5c1d 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -321,6 +321,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do |> put_req_header("idempotency-key", idempotency_key) |> post("/api/v1/statuses", %{ "spoiler_text_map" => %{"a" => "mew mew", "b" => "lol lol"}, + "language" => "a", "status" => "mewlol", "sensitive" => "0" }) @@ -864,7 +865,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do %{"a" => "Misato", "b" => "3"} ], "expires_in" => 420 - } + }, + "language" => "a" }) response = json_response_and_validate_schema(conn, 200) diff --git a/test/pleroma/web/mastodon_api/views/poll_view_test.exs b/test/pleroma/web/mastodon_api/views/poll_view_test.exs index 71ecd2116..2bde8f3e4 100644 --- a/test/pleroma/web/mastodon_api/views/poll_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/poll_view_test.exs @@ -175,8 +175,7 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do "oneOf" => [ %{ "name" => "mew", - "nameMap" => %{"en" => "mew", "cmn" => "喵"}, - "nameRendered" => "mew | 喵" + "nameMap" => %{"en" => "mew", "cmn" => "喵"} }, %{"name" => "mew mew", "nameMap" => %{"en" => "mew mew", "cmn" => "喵喵"}} ] @@ -185,7 +184,7 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do assert %{ options: [ - %{title: "mew | 喵", title_map: %{"en" => "mew", "cmn" => "喵"}}, + %{title: "mew", title_map: %{"en" => "mew", "cmn" => "喵"}}, %{title: "mew mew", title_map: %{"en" => "mew mew", "cmn" => "喵喵"}} ] } = PollView.render("show.json", %{object: object}) diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index b3f51a8c1..2ef1a3c00 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -380,7 +380,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do content_map: %{"en" => "mew mew", "cmn" => "喵喵"}, spoiler_text: "mew", spoiler_text_map: %{"en" => "mew", "cmn" => "喵"}, - language: "mul", pleroma: %{ content: %{"text/plain" => "mew mew"}, content_map: %{"text/plain" => %{"en" => "mew mew", "cmn" => "喵喵"}}, @@ -399,7 +398,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do "content" => "mew mew", "contentMap" => %{"en" => "mew mew"}, "summary" => "mew", - "summaryMap" => %{"en" => "mew"} + "summaryMap" => %{"en" => "mew"}, + "language" => "en" } ) @@ -686,6 +686,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do preview_url: "someurl", text_url: "someurl", description: nil, + description_map: %{}, pleroma: %{mime_type: "image/png"}, meta: %{original: %{width: 200, height: 100, aspect: 2}}, blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn" @@ -720,6 +721,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do expected = %{ blurhash: nil, description: "they have played us for absolute fools.", + description_map: %{}, id: "1638338801", pleroma: %{mime_type: "image/png", name: "fool.jpeg"}, preview_url: "someurl",