diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index 243cd72eef..61cf2bd17c 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 @@ def poll_params 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 4a5f508cda..e5dafad0f1 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -147,22 +147,34 @@ def make_poll_data(%{"poll" => %{"expires_in" => expires_in}} = data) |> 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 @@ def make_poll_data(%{poll: %{options: options, expires_in: expires_in}} = data) 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 @@ defp validate_poll_options_amount(options, %{max_options: max_options}) 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 7adc55499d..9b31ae54d0 100644 --- a/test/pleroma/web/common_api/utils_test.exs +++ b/test/pleroma/web/common_api/utils_test.exs @@ -729,4 +729,26 @@ test "adds attachments to parsed results" 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 20b772b106..b6966fb051 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 @@ test "posting a poll", %{conn: conn} 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])