aboutsummaryrefslogtreecommitdiff
path: root/lib/pleroma/web/common_api
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pleroma/web/common_api')
-rw-r--r--lib/pleroma/web/common_api/common_api.ex157
-rw-r--r--lib/pleroma/web/common_api/utils.ex97
2 files changed, 222 insertions, 32 deletions
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index ecd183110..5212d5ce5 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -71,6 +71,9 @@ defmodule Pleroma.Web.CommonAPI do
{:ok, _} <- unpin(activity_id, user),
{:ok, delete} <- ActivityPub.delete(object) do
{:ok, delete}
+ else
+ _ ->
+ {:error, "Could not delete"}
end
end
@@ -116,32 +119,81 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- def get_visibility(%{"visibility" => visibility})
+ def vote(user, object, choices) do
+ with "Question" <- object.data["type"],
+ {:author, false} <- {:author, object.data["actor"] == user.ap_id},
+ {:existing_votes, []} <- {:existing_votes, Utils.get_existing_votes(user.ap_id, object)},
+ {options, max_count} <- get_options_and_max_count(object),
+ option_count <- Enum.count(options),
+ {:choice_check, {choices, true}} <-
+ {:choice_check, normalize_and_validate_choice_indices(choices, option_count)},
+ {:count_check, true} <- {:count_check, Enum.count(choices) <= max_count} do
+ answer_activities =
+ Enum.map(choices, fn index ->
+ answer_data = make_answer_data(user, object, Enum.at(options, index)["name"])
+
+ ActivityPub.create(%{
+ to: answer_data["to"],
+ actor: user,
+ context: object.data["context"],
+ object: answer_data,
+ additional: %{"cc" => answer_data["cc"]}
+ })
+ end)
+
+ object = Object.get_cached_by_ap_id(object.data["id"])
+ {:ok, answer_activities, object}
+ else
+ {:author, _} -> {:error, "Poll's author can't vote"}
+ {:existing_votes, _} -> {:error, "Already voted"}
+ {:choice_check, {_, false}} -> {:error, "Invalid indices"}
+ {:count_check, false} -> {:error, "Too many choices"}
+ end
+ end
+
+ defp get_options_and_max_count(object) do
+ if Map.has_key?(object.data, "anyOf") do
+ {object.data["anyOf"], Enum.count(object.data["anyOf"])}
+ else
+ {object.data["oneOf"], 1}
+ end
+ end
+
+ defp normalize_and_validate_choice_indices(choices, count) do
+ Enum.map_reduce(choices, true, fn index, valid ->
+ index = if is_binary(index), do: String.to_integer(index), else: index
+ {index, if(valid, do: index < count, else: valid)}
+ end)
+ end
+
+ def get_visibility(%{"visibility" => visibility}, in_reply_to)
when visibility in ~w{public unlisted private direct},
- do: visibility
+ do: {visibility, get_replied_to_visibility(in_reply_to)}
- def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do
- case get_replied_to_activity(status_id) do
- nil ->
- "public"
+ def get_visibility(_, in_reply_to) when not is_nil(in_reply_to) do
+ visibility = get_replied_to_visibility(in_reply_to)
+ {visibility, visibility}
+ end
- in_reply_to ->
- # XXX: these heuristics should be moved out of MastodonAPI.
- with %Object{} = object <- Object.normalize(in_reply_to) do
- Pleroma.Web.MastodonAPI.StatusView.get_visibility(object)
- end
+ def get_visibility(_, in_reply_to), do: {"public", get_replied_to_visibility(in_reply_to)}
+
+ def get_replied_to_visibility(nil), do: nil
+
+ def get_replied_to_visibility(activity) do
+ with %Object{} = object <- Object.normalize(activity) do
+ Pleroma.Web.ActivityPub.Visibility.get_visibility(object)
end
end
- def get_visibility(_), do: "public"
-
def post(user, %{"status" => status} = data) do
- visibility = get_visibility(data)
limit = Pleroma.Config.get([:instance, :limit])
with status <- String.trim(status),
attachments <- attachments_from_ids(data),
in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]),
+ {visibility, in_reply_to_visibility} <- get_visibility(data, in_reply_to),
+ {_, false} <-
+ {:private_to_public, in_reply_to_visibility == "direct" && visibility != "direct"},
{content_html, mentions, tags} <-
make_content_html(
status,
@@ -149,10 +201,12 @@ defmodule Pleroma.Web.CommonAPI do
data,
visibility
),
+ {poll, poll_emoji} <- make_poll_data(data),
{to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),
context <- make_context(in_reply_to),
- cw <- data["spoiler_text"],
- full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
+ cw <- data["spoiler_text"] || "",
+ sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}),
+ full_payload <- String.trim(status <> cw),
length when length in 1..limit <- String.length(full_payload),
object <-
make_note_data(
@@ -164,16 +218,15 @@ defmodule Pleroma.Web.CommonAPI do
in_reply_to,
tags,
cw,
- cc
+ cc,
+ sensitive,
+ poll
),
object <-
Map.put(
object,
"emoji",
- (Formatter.get_emoji(status) ++ Formatter.get_emoji(data["spoiler_text"]))
- |> Enum.reduce(%{}, fn {name, file, _}, acc ->
- Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
- end)
+ Map.merge(Formatter.get_emoji_map(full_payload), poll_emoji)
) do
res =
ActivityPub.create(
@@ -188,6 +241,8 @@ defmodule Pleroma.Web.CommonAPI do
)
res
+ else
+ e -> {:error, e}
end
end
@@ -196,7 +251,7 @@ defmodule Pleroma.Web.CommonAPI do
user =
with emoji <- emoji_from_profile(user),
source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji),
- info_cng <- Pleroma.User.Info.set_source_data(user.info, source_data),
+ info_cng <- User.Info.set_source_data(user.info, source_data),
change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
{:ok, user} <- User.update_and_set_cache(change) do
user
@@ -229,7 +284,7 @@ defmodule Pleroma.Web.CommonAPI do
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
true <- Enum.member?(object_to, "https://www.w3.org/ns/activitystreams#Public"),
%{valid?: true} = info_changeset <-
- Pleroma.User.Info.add_pinnned_activity(user.info, activity),
+ User.Info.add_pinnned_activity(user.info, activity),
changeset <-
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
{:ok, _user} <- User.update_and_set_cache(changeset) do
@@ -246,7 +301,7 @@ defmodule Pleroma.Web.CommonAPI do
def unpin(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
%{valid?: true} = info_changeset <-
- Pleroma.User.Info.remove_pinnned_activity(user.info, activity),
+ User.Info.remove_pinnned_activity(user.info, activity),
changeset <-
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
{:ok, _user} <- User.update_and_set_cache(changeset) do
@@ -314,6 +369,60 @@ defmodule Pleroma.Web.CommonAPI do
end
end
+ def update_report_state(activity_id, state) do
+ with %Activity{} = activity <- Activity.get_by_id(activity_id),
+ {:ok, activity} <- Utils.update_report_state(activity, state) do
+ {:ok, activity}
+ else
+ nil ->
+ {:error, :not_found}
+
+ {:error, reason} ->
+ {:error, reason}
+
+ _ ->
+ {:error, "Could not update state"}
+ end
+ end
+
+ def update_activity_scope(activity_id, opts \\ %{}) do
+ with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
+ {:ok, activity} <- toggle_sensitive(activity, opts),
+ {:ok, activity} <- set_visibility(activity, opts) do
+ {:ok, activity}
+ else
+ nil ->
+ {:error, :not_found}
+
+ {:error, reason} ->
+ {:error, reason}
+ end
+ end
+
+ defp toggle_sensitive(activity, %{"sensitive" => sensitive}) when sensitive in ~w(true false) do
+ toggle_sensitive(activity, %{"sensitive" => String.to_existing_atom(sensitive)})
+ end
+
+ defp toggle_sensitive(%Activity{object: object} = activity, %{"sensitive" => sensitive})
+ when is_boolean(sensitive) do
+ new_data = Map.put(object.data, "sensitive", sensitive)
+
+ {:ok, object} =
+ object
+ |> Object.change(%{data: new_data})
+ |> Object.update_and_set_cache()
+
+ {:ok, Map.put(activity, :object, object)}
+ end
+
+ defp toggle_sensitive(activity, _), do: {:ok, activity}
+
+ defp set_visibility(activity, %{"visibility" => visibility}) do
+ Utils.update_activity_visibility(activity, visibility)
+ end
+
+ defp set_visibility(activity, _), do: {:ok, activity}
+
def hide_reblogs(user, muted) do
ap_id = muted.ap_id
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 1dfe50b40..f35ed36ab 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -102,6 +102,72 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
end
+ def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_in}} = data)
+ when is_list(options) do
+ %{max_expiration: max_expiration, min_expiration: min_expiration} =
+ limits = Pleroma.Config.get([:instance, :poll_limits])
+
+ # XXX: There is probably a cleaner way of doing this
+ try do
+ # In some cases mastofe sends out strings instead of integers
+ expires_in = if is_binary(expires_in), do: String.to_integer(expires_in), else: expires_in
+
+ if Enum.count(options) > limits.max_options do
+ raise ArgumentError, message: "Poll can't contain more than #{limits.max_options} options"
+ end
+
+ {poll, emoji} =
+ Enum.map_reduce(options, %{}, fn option, emoji ->
+ if String.length(option) > limits.max_option_chars do
+ raise ArgumentError,
+ message:
+ "Poll options cannot be longer than #{limits.max_option_chars} characters each"
+ end
+
+ {%{
+ "name" => option,
+ "type" => "Note",
+ "replies" => %{"type" => "Collection", "totalItems" => 0}
+ }, Map.merge(emoji, Formatter.get_emoji_map(option))}
+ end)
+
+ case expires_in do
+ expires_in when expires_in > max_expiration ->
+ raise ArgumentError, message: "Expiration date is too far in the future"
+
+ expires_in when expires_in < min_expiration ->
+ raise ArgumentError, message: "Expiration date is too soon"
+
+ _ ->
+ :noop
+ end
+
+ end_time =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(expires_in)
+ |> NaiveDateTime.to_iso8601()
+
+ poll =
+ if Pleroma.Web.ControllerHelper.truthy_param?(data["poll"]["multiple"]) do
+ %{"type" => "Question", "anyOf" => poll, "closed" => end_time}
+ else
+ %{"type" => "Question", "oneOf" => poll, "closed" => end_time}
+ end
+
+ {poll, emoji}
+ rescue
+ e in ArgumentError -> e.message
+ end
+ end
+
+ def make_poll_data(%{"poll" => poll}) when is_map(poll) do
+ "Invalid poll"
+ end
+
+ def make_poll_data(_data) do
+ {%{}, %{}}
+ end
+
def make_content_html(
status,
attachments,
@@ -223,7 +289,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do
in_reply_to,
tags,
cw \\ nil,
- cc \\ []
+ cc \\ [],
+ sensitive \\ false,
+ merge \\ %{}
) do
object = %{
"type" => "Note",
@@ -231,20 +299,22 @@ defmodule Pleroma.Web.CommonAPI.Utils do
"cc" => cc,
"content" => content_html,
"summary" => cw,
+ "sensitive" => !Enum.member?(["false", "False", "0", false], sensitive),
"context" => context,
"attachment" => attachments,
"actor" => actor,
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
}
- if in_reply_to do
- in_reply_to_object = Object.normalize(in_reply_to)
+ object =
+ with false <- is_nil(in_reply_to),
+ %Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do
+ Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
+ else
+ _ -> object
+ end
- object
- |> Map.put("inReplyTo", in_reply_to_object.data["id"])
- else
- object
- end
+ Map.merge(object, merge)
end
def format_naive_asctime(date) do
@@ -421,4 +491,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do
{:error, "No such conversation"}
end
end
+
+ def make_answer_data(%User{ap_id: ap_id}, object, name) do
+ %{
+ "type" => "Answer",
+ "actor" => ap_id,
+ "cc" => [object.data["actor"]],
+ "to" => [],
+ "name" => name,
+ "inReplyTo" => object.data["id"]
+ }
+ end
end