alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Endpoint
alias Pleroma.Web.MediaProxy
+ require Logger
+
# This is a hack for twidere.
def get_by_id_or_ap_id(id) do
activity =
end)
end
- def to_for_user_and_mentions(user, mentions, inReplyTo, "public") do
- mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)
-
+ @spec get_to_and_cc(User.t(), list(String.t()), Activity.t() | nil, String.t()) ::
+ {list(String.t()), list(String.t())}
+ def get_to_and_cc(user, mentioned_users, inReplyTo, "public") do
to = ["https://www.w3.org/ns/activitystreams#Public" | mentioned_users]
cc = [user.follower_address]
end
end
- def to_for_user_and_mentions(user, mentions, inReplyTo, "unlisted") do
- mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)
-
+ def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted") do
to = [user.follower_address | mentioned_users]
cc = ["https://www.w3.org/ns/activitystreams#Public"]
end
end
- def to_for_user_and_mentions(user, mentions, inReplyTo, "private") do
- {to, cc} = to_for_user_and_mentions(user, mentions, inReplyTo, "direct")
+ def get_to_and_cc(user, mentioned_users, inReplyTo, "private") do
+ {to, cc} = get_to_and_cc(user, mentioned_users, inReplyTo, "direct")
{[user.follower_address | to], cc}
end
- def to_for_user_and_mentions(_user, mentions, inReplyTo, "direct") do
- mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)
-
+ def get_to_and_cc(_user, mentioned_users, inReplyTo, "direct") do
if inReplyTo do
{Enum.uniq([inReplyTo.data["actor"] | mentioned_users]), []}
else
end
end
+ def get_addressed_users(_, to) when is_list(to) do
+ User.get_ap_ids_by_nicknames(to)
+ end
+
+ def get_addressed_users(mentioned_users, _), do: mentioned_users
+
+ 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,
end).()
end
+ @doc """
+ Formatting text as BBCode.
+ """
+ def format_input(text, "text/bbcode", options) do
+ text
+ |> String.replace(~r/\r/, "")
+ |> Formatter.html_escape("text/plain")
+ |> BBCode.to_html()
+ |> (fn {:ok, html} -> html end).()
+ |> Formatter.linkify(options)
+ end
+
@doc """
Formatting text to html.
"""
Formatting text to markdown.
"""
def format_input(text, "text/markdown", options) do
- options = Keyword.put(options, :mentions_escape, true)
-
text
+ |> Formatter.mentions_escape(options)
+ |> Earmark.as_html!()
|> Formatter.linkify(options)
- |> (fn {text, mentions, tags} -> {Earmark.as_html!(text), mentions, tags} end).()
|> Formatter.html_escape("text/html")
end
context,
content_html,
attachments,
- inReplyTo,
+ in_reply_to,
tags,
cw \\ nil,
- cc \\ []
+ cc \\ [],
+ sensitive \\ false,
+ merge \\ %{}
) do
object = %{
"type" => "Note",
"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 inReplyTo do
- object
- |> Map.put("inReplyTo", inReplyTo.data["object"]["id"])
- |> Map.put("inReplyToStatusId", inReplyTo.id)
- else
- object
- end
+ 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
+
+ Map.merge(object, merge)
end
def format_naive_asctime(date) do
Strftime.strftime!(date, "%a %b %d %H:%M:%S %z %Y")
end
- def date_to_asctime(date) do
- with {:ok, date, _offset} <- date |> DateTime.from_iso8601() do
+ def date_to_asctime(date) when is_binary(date) do
+ with {:ok, date, _offset} <- DateTime.from_iso8601(date) do
format_asctime(date)
else
_e ->
+ Logger.warn("Date #{date} in wrong format, must be ISO 8601")
""
end
end
+ def date_to_asctime(date) do
+ Logger.warn("Date #{date} in wrong format, must be ISO 8601")
+ ""
+ end
+
def to_masto_date(%NaiveDateTime{} = date) do
date
|> NaiveDateTime.to_iso8601()
end
def confirm_current_password(user, password) do
- with %User{local: true} = db_user <- User.get_by_id(user.id),
+ with %User{local: true} = db_user <- User.get_cached_by_id(user.id),
true <- Pbkdf2.checkpw(password, db_user.password_hash) do
{:ok, db_user}
else
def maybe_notify_mentioned_recipients(recipients, _), do: recipients
+ def maybe_notify_subscribers(
+ recipients,
+ %Activity{data: %{"actor" => actor, "type" => type}} = activity
+ )
+ when type == "Create" do
+ with %User{} = user <- User.get_cached_by_ap_id(actor) do
+ subscriber_ids =
+ user
+ |> User.subscribers()
+ |> Enum.filter(&Visibility.visible_for_user?(activity, &1))
+ |> Enum.map(& &1.ap_id)
+
+ recipients ++ subscriber_ids
+ end
+ end
+
+ def maybe_notify_subscribers(recipients, _), do: recipients
+
def maybe_extract_mentions(%{"tag" => tag}) do
tag
|> Enum.filter(fn x -> is_map(x) end)
{: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