First
[anni] / lib / pleroma / web / mastodon_api / views / poll_view.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.MastodonAPI.PollView do
6   use Pleroma.Web, :view
7
8   alias Pleroma.Web.CommonAPI.Utils
9
10   def render("show.json", %{object: object, multiple: multiple, options: options} = params) do
11     {end_time, expired} = end_time_and_expired(object)
12     {options, votes_count} = options_and_votes_count(options)
13
14     poll = %{
15       # Mastodon uses separate ids for polls, but an object can't have
16       # more than one poll embedded so object id is fine
17       id: to_string(object.id),
18       expires_at: end_time,
19       expired: expired,
20       multiple: multiple,
21       votes_count: votes_count,
22       voters_count: voters_count(object),
23       options: options,
24       emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"])
25     }
26
27     if params[:for] do
28       # when unauthenticated Mastodon doesn't include `voted` & `own_votes` keys in response
29       {voted, own_votes} = voted_and_own_votes(params, options)
30       Map.merge(poll, %{voted: voted, own_votes: own_votes})
31     else
32       poll
33     end
34   end
35
36   def render("show.json", %{object: object} = params) do
37     case object.data do
38       %{"anyOf" => [_ | _] = options} ->
39         render(__MODULE__, "show.json", Map.merge(params, %{multiple: true, options: options}))
40
41       %{"oneOf" => [_ | _] = options} ->
42         render(__MODULE__, "show.json", Map.merge(params, %{multiple: false, options: options}))
43
44       _ ->
45         nil
46     end
47   end
48
49   defp end_time_and_expired(object) do
50     if object.data["closed"] do
51       end_time = NaiveDateTime.from_iso8601!(object.data["closed"])
52       expired = NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) == :lt
53
54       {Utils.to_masto_date(end_time), expired}
55     else
56       {nil, false}
57     end
58   end
59
60   defp options_and_votes_count(options) do
61     Enum.map_reduce(options, 0, fn %{"name" => name} = option, count ->
62       current_count = option["replies"]["totalItems"] || 0
63
64       {%{
65          title: name,
66          votes_count: current_count
67        }, current_count + count}
68     end)
69   end
70
71   defp voters_count(%{data: %{"voters" => [_ | _] = voters}}) do
72     length(voters)
73   end
74
75   defp voters_count(_), do: 0
76
77   defp voted_and_own_votes(%{object: object} = params, options) do
78     if params[:for] do
79       existing_votes =
80         Pleroma.Web.ActivityPub.Utils.get_existing_votes(params[:for].ap_id, object)
81
82       voted = existing_votes != [] or params[:for].ap_id == object.data["actor"]
83
84       own_votes =
85         if voted do
86           titles = Enum.map(options, & &1[:title])
87
88           Enum.reduce(existing_votes, [], fn vote, acc ->
89             data = vote |> Map.get(:object) |> Map.get(:data)
90             index = Enum.find_index(titles, &(&1 == data["name"]))
91             [index | acc]
92           end)
93         else
94           []
95         end
96
97       {voted, own_votes}
98     else
99       {false, []}
100     end
101   end
102 end