Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85824185
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
30 KB
Referenced Files
None
Subscribers
None
View Options
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 327279bf8..b15e9ec5e 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
@@ -1,25 +1,27 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.LanguageCode do
use Ecto.Type
- import Pleroma.Web.CommonAPI.Utils, only: [is_good_locale_code?: 1]
-
def type, do: :string
def cast(language) when is_binary(language) do
if is_good_locale_code?(language) do
{:ok, language}
else
{:error, :invalid_language}
end
end
def cast(_), do: :error
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
index 96b7f2da6..2228edd24 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
@@ -1,48 +1,49 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MapOfString do
use Ecto.Type
- import Pleroma.Web.CommonAPI.Utils, only: [is_good_locale_code?: 1]
+ import Pleroma.EctoType.ActivityPub.ObjectValidators.LanguageCode,
+ only: [is_good_locale_code?: 1]
def type, do: :map
def cast(%{} = object) do
with {status, %{} = data} when status in [:modified, :ok] <- 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}
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/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
index ccc76beed..fa581eba4 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
@@ -1,129 +1,131 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
alias Pleroma.Object.Containment
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
- import Pleroma.Web.CommonAPI.Utils, only: [is_good_locale_code?: 1]
+ 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
{:ok, data} = ObjectValidators.Recipients.cast(message[field] || field_fallback)
data =
Enum.reject(data, fn x ->
String.ends_with?(x, "/followers") and x != follower_collection
end)
Map.put(message, field, data)
end
def fix_object_defaults(data) do
context =
Utils.maybe_create_context(
data["context"] || data["conversation"] || data["inReplyTo"] || data["id"]
)
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"])
data
|> Map.put("context", context)
|> cast_and_filter_recipients("to", follower_collection)
|> cast_and_filter_recipients("cc", follower_collection)
|> cast_and_filter_recipients("bto", follower_collection)
|> cast_and_filter_recipients("bcc", follower_collection)
|> Transmogrifier.fix_implicit_addressing(follower_collection)
end
def fix_activity_addressing(activity) do
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(activity["actor"])
activity
|> cast_and_filter_recipients("to", follower_collection)
|> cast_and_filter_recipients("cc", follower_collection)
|> cast_and_filter_recipients("bto", follower_collection)
|> cast_and_filter_recipients("bcc", follower_collection)
|> Transmogrifier.fix_implicit_addressing(follower_collection)
end
def fix_actor(data) do
actor =
data
|> Map.put_new("actor", data["attributedTo"])
|> Containment.get_actor()
data
|> Map.put("actor", actor)
|> Map.put("attributedTo", actor)
end
def fix_activity_context(data, %Object{data: %{"context" => object_context}}) do
data
|> Map.put("context", object_context)
end
def fix_object_action_recipients(%{"actor" => actor} = data, %Object{data: %{"actor" => actor}}) do
to = ((data["to"] || []) -- [actor]) |> Enum.uniq()
Map.put(data, "to", to)
end
def fix_object_action_recipients(data, %Object{data: %{"actor" => actor}}) do
to = ((data["to"] || []) ++ [actor]) |> Enum.uniq()
Map.put(data, "to", to)
end
def maybe_add_language(object, meta \\ []) do
language =
[
get_language_from_context(object),
get_language_from_context(Keyword.get(meta, :activity_data)),
get_language_from_content_map(object)
]
|> Enum.find(&is_good_locale_code?(&1))
if language do
Map.put(object, "language", language)
else
object
end
end
defp get_language_from_context(%{"@context" => context}) when is_list(context) do
case context
|> Enum.find(fn
%{"@language" => language} -> language != "und"
_ -> nil
end) do
%{"@language" => language} -> language
_ -> nil
end
end
defp get_language_from_context(_), do: nil
defp get_language_from_content_map(%{"contentMap" => content_map, "content" => source_content}) do
content_groups = Map.to_list(content_map)
case Enum.find(content_groups, fn {_, content} -> content == source_content end) do
{language, _} -> language
_ -> nil
end
end
defp get_language_from_content_map(_), do: nil
def maybe_add_content_map(%{"language" => language, "content" => content} = object)
when not_empty_string(language) do
Map.put(object, "contentMap", Map.put(%{}, language, content))
end
def maybe_add_content_map(object), do: object
end
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index bcbb134bb..1b6118cf8 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -1,286 +1,289 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI.ActivityDraft do
alias Pleroma.Activity
alias Pleroma.Conversation.Participation
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.Builder
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
defstruct valid?: true,
errors: [],
user: nil,
params: %{},
status: nil,
summary: nil,
full_payload: nil,
attachments: [],
in_reply_to: nil,
in_reply_to_conversation: nil,
visibility: nil,
expires_at: nil,
extra: nil,
emoji: %{},
content_html: nil,
mentions: [],
tags: [],
to: [],
cc: [],
context: nil,
sensitive: false,
language: nil,
object: nil,
preview?: false,
changes: %{}
def new(user, params) do
%__MODULE__{user: user}
|> put_params(params)
end
def create(user, params) do
user
|> new(params)
|> status()
|> summary()
|> with_valid(&attachments/1)
|> full_payload()
|> expires_at()
|> poll()
|> with_valid(&in_reply_to/1)
|> with_valid(&in_reply_to_conversation/1)
|> with_valid(&visibility/1)
|> content()
|> with_valid(&to_and_cc/1)
|> with_valid(&context/1)
|> with_valid(&language/1)
|> sensitive()
|> with_valid(&object/1)
|> preview?()
|> with_valid(&changes/1)
|> validate()
end
def listen(user, params) do
user
|> new(params)
|> visibility()
|> to_and_cc()
|> context()
|> listen_object()
|> with_valid(&changes/1)
|> validate()
end
defp listen_object(draft) do
object =
draft.params
|> Map.take([:album, :artist, :title, :length])
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|> Map.put("type", "Audio")
|> Map.put("to", draft.to)
|> Map.put("cc", draft.cc)
|> Map.put("actor", draft.user.ap_id)
%__MODULE__{draft | object: object}
end
defp put_params(draft, params) do
params = Map.put_new(params, :in_reply_to_status_id, params[:in_reply_to_id])
%__MODULE__{draft | params: params}
end
defp status(%{params: %{status: status}} = draft) do
%__MODULE__{draft | status: String.trim(status)}
end
defp summary(%{params: params} = draft) do
%__MODULE__{draft | summary: Map.get(params, :spoiler_text, "")}
end
defp full_payload(%{status: status, summary: summary} = draft) do
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 attachments(%{params: params} = draft) do
attachments = Utils.attachments_from_ids(params)
draft = %__MODULE__{draft | attachments: attachments}
case Utils.validate_attachments_count(attachments) do
:ok -> draft
{:error, message} -> add_error(draft, message)
end
end
defp in_reply_to(%{params: %{in_reply_to_status_id: ""}} = draft), do: draft
defp in_reply_to(%{params: %{in_reply_to_status_id: id}} = draft) when is_binary(id) do
%__MODULE__{draft | in_reply_to: Activity.get_by_id(id)}
end
defp in_reply_to(%{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft) do
%__MODULE__{draft | in_reply_to: in_reply_to}
end
defp in_reply_to(draft), do: draft
defp in_reply_to_conversation(draft) do
in_reply_to_conversation = Participation.get(draft.params[:in_reply_to_conversation_id])
%__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation}
end
defp visibility(%{params: params} = draft) do
case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do
{visibility, "direct"} when visibility != "direct" ->
add_error(draft, dgettext("errors", "The message visibility must be direct"))
{visibility, _} ->
%__MODULE__{draft | visibility: visibility}
end
end
defp expires_at(draft) do
case CommonAPI.check_expiry_date(draft.params[:expires_in]) do
{:ok, expires_at} -> %__MODULE__{draft | expires_at: expires_at}
{:error, message} -> add_error(draft, message)
end
end
defp poll(draft) do
case Utils.make_poll_data(draft.params) do
{:ok, {poll, poll_emoji}} ->
%__MODULE__{draft | extra: poll, emoji: Map.merge(draft.emoji, poll_emoji)}
{:error, message} ->
add_error(draft, message)
end
end
defp content(draft) do
{content_html, mentioned_users, tags} = Utils.make_content_html(draft)
mentions =
mentioned_users
|> Enum.map(fn {_, mentioned_user} -> mentioned_user.ap_id end)
|> Utils.get_addressed_users(draft.params[:to])
%__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags}
end
defp to_and_cc(draft) do
{to, cc} = Utils.get_to_and_cc(draft)
%__MODULE__{draft | to: to, cc: cc}
end
defp context(draft) do
context = Utils.make_context(draft.in_reply_to, draft.in_reply_to_conversation)
%__MODULE__{draft | context: context}
end
defp sensitive(draft) do
sensitive = draft.params[:sensitive]
%__MODULE__{draft | sensitive: sensitive}
end
defp language(draft) do
language = draft.params[:language]
- if Utils.is_good_locale_code?(language) do
+ 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)
# Sometimes people create posts with subject containing emoji,
# since subjects are usually copied this will result in a broken
# subject when someone replies from an instance that does not have
# the emoji or has it under different shortcode. This is an attempt
# to mitigate this by copying emoji from inReplyTo if they are present
# in the subject.
summary_emoji =
with %Activity{} <- draft.in_reply_to,
%Object{data: %{"tag" => [_ | _] = tag}} <- Object.normalize(draft.in_reply_to) do
Enum.reduce(tag, %{}, fn
%{"type" => "Emoji", "name" => name, "icon" => %{"url" => url}}, acc ->
if String.contains?(draft.summary, name) do
Map.put(acc, name, url)
else
acc
end
_, acc ->
acc
end)
else
_ -> %{}
end
emoji = Map.merge(emoji, summary_emoji)
{:ok, note_data, _meta} = Builder.note(draft)
object =
note_data
|> Map.put("emoji", emoji)
|> Map.put("source", %{
"content" => draft.status,
"mediaType" => Utils.get_content_type(draft.params[:content_type])
})
|> Map.put("generator", draft.params[:generator])
|> Map.put("language", draft.language)
%__MODULE__{draft | object: object}
end
defp preview?(draft) do
preview? = Pleroma.Web.Utils.Params.truthy_param?(draft.params[:preview])
%__MODULE__{draft | preview?: preview?}
end
defp changes(draft) do
direct? = draft.visibility == "direct"
additional = %{"cc" => draft.cc, "directMessage" => direct?}
additional =
case draft.expires_at do
%DateTime{} = expires_at -> Map.put(additional, "expires_at", expires_at)
_ -> additional
end
changes =
%{
to: draft.to,
actor: draft.user,
context: draft.context,
object: draft.object,
additional: additional
}
|> Utils.maybe_add_list_data(draft.user, draft.visibility)
%__MODULE__{draft | changes: changes}
end
defp with_valid(%{valid?: true} = draft, func), do: func.(draft)
defp with_valid(draft, _func), do: draft
defp add_error(draft, message) do
%__MODULE__{draft | valid?: false, errors: [message | draft.errors]}
end
defp validate(%{valid?: true} = draft), do: {:ok, draft}
defp validate(%{errors: [message | _]}), do: {:error, message}
end
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 05a5b818e..b9fe0224c 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -1,501 +1,497 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI.Utils do
import Pleroma.Web.Gettext
alias Calendar.Strftime
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Conversation.Participation
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI.ActivityDraft
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.Plugs.AuthenticationPlug
alias Pleroma.Web.Utils.Params
require Logger
require Pleroma.Constants
def attachments_from_ids(%{media_ids: ids, descriptions: desc}) do
attachments_from_ids_descs(ids, desc)
end
def attachments_from_ids(%{media_ids: ids}) do
attachments_from_ids_no_descs(ids)
end
def attachments_from_ids(_), do: []
def attachments_from_ids_no_descs([]), do: []
def attachments_from_ids_no_descs(ids) do
Enum.map(ids, fn media_id ->
case get_attachment(media_id) do
%Object{data: data} -> data
_ -> nil
end
end)
|> Enum.reject(&is_nil/1)
end
def attachments_from_ids_descs([], _), do: []
def attachments_from_ids_descs(ids, descs_str) do
{_, descs} = Jason.decode(descs_str)
Enum.map(ids, fn media_id ->
with %Object{data: data} <- get_attachment(media_id) do
Map.put(data, "name", descs[media_id])
end
end)
|> Enum.reject(&is_nil/1)
end
defp get_attachment(media_id) do
with %Object{data: data} = object <- Repo.get(Object, media_id),
%{"type" => type} when type in Pleroma.Constants.upload_object_types() <- data do
object
else
_ -> nil
end
end
@spec get_to_and_cc(ActivityDraft.t()) :: {list(String.t()), list(String.t())}
def get_to_and_cc(%{in_reply_to_conversation: %Participation{} = participation}) do
participation = Repo.preload(participation, :recipients)
{Enum.map(participation.recipients, & &1.ap_id), []}
end
def get_to_and_cc(%{visibility: visibility} = draft) when visibility in ["public", "local"] do
to =
case visibility do
"public" -> [Pleroma.Constants.as_public() | draft.mentions]
"local" -> [Utils.as_local_public() | draft.mentions]
end
cc = [draft.user.follower_address]
if draft.in_reply_to do
{Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc}
else
{to, cc}
end
end
def get_to_and_cc(%{visibility: "unlisted"} = draft) do
to = [draft.user.follower_address | draft.mentions]
cc = [Pleroma.Constants.as_public()]
if draft.in_reply_to do
{Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc}
else
{to, cc}
end
end
def get_to_and_cc(%{visibility: "private"} = draft) do
{to, cc} = get_to_and_cc(struct(draft, visibility: "direct"))
{[draft.user.follower_address | to], cc}
end
def get_to_and_cc(%{visibility: "direct"} = draft) do
# If the OP is a DM already, add the implicit actor.
if draft.in_reply_to && Visibility.is_direct?(draft.in_reply_to) do
{Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions]), []}
else
{draft.mentions, []}
end
end
def get_to_and_cc(%{visibility: {:list, _}, mentions: mentions}), do: {mentions, []}
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 maybe_add_list_data(activity_params, user, {:list, list_id}) do
case Pleroma.List.get(list_id, user) do
%Pleroma.List{} = list ->
activity_params
|> put_in([:additional, "bcc"], [list.ap_id])
|> put_in([:additional, "listMessage"], list.ap_id)
|> put_in([:object, "listMessage"], list.ap_id)
_ ->
activity_params
end
end
def maybe_add_list_data(activity_params, _, _), do: activity_params
def make_poll_data(%{"poll" => %{"expires_in" => expires_in}} = data)
when is_binary(expires_in) do
# In some cases mastofe sends out strings instead of integers
data
|> put_in(["poll", "expires_in"], String.to_integer(expires_in))
|> make_poll_data()
end
def make_poll_data(%{poll: %{options: options, expires_in: expires_in}} = data)
when is_list(options) do
limits = Config.get([:instance, :poll_limits])
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
{option_notes, emoji} =
Enum.map_reduce(options, %{}, fn option, emoji ->
note = %{
"name" => option,
"type" => "Note",
"replies" => %{"type" => "Collection", "totalItems" => 0}
}
{note, Map.merge(emoji, Pleroma.Emoji.Formatter.get_emoji_map(option))}
end)
end_time =
DateTime.utc_now()
|> DateTime.add(expires_in)
|> DateTime.to_iso8601()
key = if Params.truthy_param?(data.poll[:multiple]), do: "anyOf", else: "oneOf"
poll = %{"type" => "Question", key => option_notes, "closed" => end_time}
{:ok, {poll, emoji}}
end
end
def make_poll_data(%{"poll" => poll}) when is_map(poll) do
{:error, "Invalid poll"}
end
def make_poll_data(_data) do
{:ok, {%{}, %{}}}
end
defp validate_poll_options_amount(options, %{max_options: max_options}) do
cond do
Enum.count(options) < 2 ->
{:error, "Poll must contain at least 2 options"}
Enum.count(options) > max_options ->
{:error, "Poll can't contain more than #{max_options} options"}
true ->
:ok
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
{:error, "Poll options cannot be longer than #{max_option_chars} characters each"}
else
:ok
end
end
defp validate_poll_expiration(expires_in, %{min_expiration: min, max_expiration: max}) do
cond do
expires_in > max -> {:error, "Expiration date is too far in the future"}
expires_in < min -> {:error, "Expiration date is too soon"}
true -> :ok
end
end
def make_content_html(%ActivityDraft{} = draft) do
attachment_links =
draft.params
|> Map.get("attachment_links", Config.get([:instance, :attachment_links]))
|> Params.truthy_param?()
content_type = get_content_type(draft.params[:content_type])
options =
if draft.visibility == "direct" && Config.get([:instance, :safe_dm_mentions]) do
[safe_mention: true]
else
[]
end
draft.status
|> format_input(content_type, options)
|> maybe_add_attachments(draft.attachments, attachment_links)
end
def get_content_type(content_type) do
if Enum.member?(Config.get([:instance, :allowed_post_formats]), content_type) do
content_type
else
"text/plain"
end
end
def make_context(_, %Participation{} = participation) do
Repo.preload(participation, :conversation).conversation.ap_id
end
def make_context(%Activity{data: %{"context" => context}}, _), do: context
def make_context(_, _), do: Utils.generate_context_id()
def maybe_add_attachments(parsed, _attachments, false = _no_links), do: parsed
def maybe_add_attachments({text, mentions, tags}, attachments, _no_links) do
text = add_attachments(text, attachments)
{text, mentions, tags}
end
def add_attachments(text, attachments) do
attachment_text = Enum.map(attachments, &build_attachment_link/1)
Enum.join([text | attachment_text], "<br>")
end
defp build_attachment_link(%{"url" => [%{"href" => href} | _]} = attachment) do
name = attachment["name"] || URI.decode(Path.basename(href))
href = MediaProxy.url(href)
"<a href=\"#{href}\" class='attachment'>#{shortname(name)}</a>"
end
defp build_attachment_link(_), do: ""
def format_input(text, format, options \\ [])
@doc """
Formatting text to plain text, BBCode, HTML, or Markdown
"""
def format_input(text, "text/plain", options) do
text
|> Formatter.html_escape("text/plain")
|> Formatter.linkify(options)
|> (fn {text, mentions, tags} ->
{String.replace(text, ~r/\r?\n/, "<br>"), mentions, tags}
end).()
end
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
def format_input(text, "text/html", options) do
text
|> Formatter.html_escape("text/html")
|> Formatter.linkify(options)
end
def format_input(text, "text/markdown", options) do
text
|> Formatter.mentions_escape(options)
|> Formatter.markdown_to_html()
|> Formatter.linkify(options)
|> Formatter.html_escape("text/html")
end
def format_naive_asctime(date) do
date |> DateTime.from_naive!("Etc/UTC") |> format_asctime
end
def format_asctime(date) do
Strftime.strftime!(date, "%a %b %d %H:%M:%S %z %Y")
end
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()
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
end
def to_masto_date(date) when is_binary(date) do
with {:ok, date} <- NaiveDateTime.from_iso8601(date) do
to_masto_date(date)
else
_ -> ""
end
end
def to_masto_date(_), do: ""
defp shortname(name) do
with max_length when max_length > 0 <-
Config.get([Pleroma.Upload, :filename_display_max_length], 30),
true <- String.length(name) > max_length do
String.slice(name, 0..max_length) <> "…"
else
_ -> name
end
end
@spec confirm_current_password(User.t(), String.t()) :: {:ok, User.t()} | {:error, String.t()}
def confirm_current_password(user, password) do
with %User{local: true} = db_user <- User.get_cached_by_id(user.id),
true <- AuthenticationPlug.checkpw(password, db_user.password_hash) do
{:ok, db_user}
else
_ -> {:error, dgettext("errors", "Invalid password.")}
end
end
def maybe_notify_to_recipients(
recipients,
%Activity{data: %{"to" => to, "type" => _type}} = _activity
) do
recipients ++ to
end
def maybe_notify_to_recipients(recipients, _), do: recipients
def maybe_notify_mentioned_recipients(
recipients,
%Activity{data: %{"to" => _to, "type" => type} = data} = activity
)
when type == "Create" do
object = Object.normalize(activity, fetch: false)
object_data =
cond do
not is_nil(object) ->
object.data
is_map(data["object"]) ->
data["object"]
true ->
%{}
end
tagged_mentions = maybe_extract_mentions(object_data)
recipients ++ tagged_mentions
end
def maybe_notify_mentioned_recipients(recipients, _), do: recipients
def maybe_notify_subscribers(
recipients,
%Activity{data: %{"actor" => actor, "type" => "Create"}} = activity
) do
# Do not notify subscribers if author is making a reply
with %Object{data: object} <- Object.normalize(activity, fetch: false),
nil <- object["inReplyTo"],
%User{} = user <- User.get_cached_by_ap_id(actor) do
subscriber_ids =
user
|> User.subscriber_users()
|> Enum.filter(&Visibility.visible_for_user?(activity, &1))
|> Enum.map(& &1.ap_id)
recipients ++ subscriber_ids
else
_e -> recipients
end
end
def maybe_notify_subscribers(recipients, _), do: recipients
def maybe_notify_followers(recipients, %Activity{data: %{"type" => "Move"}} = activity) do
with %User{} = user <- User.get_cached_by_ap_id(activity.actor) do
user
|> User.get_followers()
|> Enum.map(& &1.ap_id)
|> Enum.concat(recipients)
else
_e -> recipients
end
end
def maybe_notify_followers(recipients, _), do: recipients
def maybe_extract_mentions(%{"tag" => tag}) do
tag
|> Enum.filter(fn x -> is_map(x) && x["type"] == "Mention" end)
|> Enum.map(fn x -> x["href"] end)
|> Enum.uniq()
end
def maybe_extract_mentions(_), do: []
def make_report_content_html(nil), do: {:ok, {nil, [], []}}
def make_report_content_html(comment) do
max_size = Config.get([:instance, :max_report_comment_size], 1000)
if String.length(comment) <= max_size do
{:ok, format_input(comment, "text/plain")}
else
{:error,
dgettext("errors", "Comment must be up to %{max_size} characters", max_size: max_size)}
end
end
def get_report_statuses(%User{ap_id: actor}, %{status_ids: status_ids})
when is_list(status_ids) do
{:ok, Activity.all_by_actor_and_id(actor, status_ids)}
end
def get_report_statuses(_, _), do: {:ok, nil}
def validate_character_limit("" = _full_payload, [] = _attachments) do
{:error, dgettext("errors", "Cannot post an empty status without attachments")}
end
def validate_character_limit(full_payload, _attachments) do
limit = Config.get([:instance, :limit])
length = String.length(full_payload)
if length <= limit do
:ok
else
{:error, dgettext("errors", "The status is over the character limit")}
end
end
def validate_attachments_count([] = _attachments) do
:ok
end
def validate_attachments_count(attachments) do
limit = Config.get([:instance, :max_media_attachments])
count = length(attachments)
if count <= limit do
:ok
else
{:error, dgettext("errors", "Too many attachments")}
end
end
-
- 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
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Oct 29, 12:47 PM (1 d, 2 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1798105
Default Alt Text
(30 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline