Merge branch 'safe-mentions' into 'develop'

Add safe dm mode option.

See merge request pleroma/pleroma!958
This commit is contained in:
kaniini 2019-03-22 00:50:24 +00:00
commit 1c3d3d0c2b
9 changed files with 102 additions and 9 deletions

View file

@ -172,7 +172,8 @@
no_attachment_links: false, no_attachment_links: false,
welcome_user_nickname: nil, welcome_user_nickname: nil,
welcome_message: nil, welcome_message: nil,
max_report_comment_size: 1000 max_report_comment_size: 1000,
safe_dm_mentions: false
config :pleroma, :markup, config :pleroma, :markup,
# XXX - unfortunately, inline images must be enabled by default right now, because # XXX - unfortunately, inline images must be enabled by default right now, because

View file

@ -101,7 +101,8 @@ config :pleroma, Pleroma.Mailer,
* `no_attachment_links`: Set to true to disable automatically adding attachment link text to statuses * `no_attachment_links`: Set to true to disable automatically adding attachment link text to statuses
* `welcome_message`: A message that will be send to a newly registered users as a direct message. * `welcome_message`: A message that will be send to a newly registered users as a direct message.
* `welcome_user_nickname`: The nickname of the local user that sends the welcome message. * `welcome_user_nickname`: The nickname of the local user that sends the welcome message.
* `max_report_size`: The maximum size of the report comment (Default: `1000`) * `max_report_comment_size`: The maximum size of the report comment (Default: `1000`)
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`)
## :logger ## :logger
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog * `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog

View file

@ -8,6 +8,7 @@ defmodule Pleroma.Formatter do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
@safe_mention_regex ~r/^(\s*(?<mentions>@.+?\s+)+)(?<rest>.*)/
@markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/ @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/
@link_regex ~r{((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+}ui @link_regex ~r{((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+}ui
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
@ -45,16 +46,29 @@ def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
@doc """ @doc """
Parses a text and replace plain text links with HTML. Returns a tuple with a result text, mentions, and hashtags. Parses a text and replace plain text links with HTML. Returns a tuple with a result text, mentions, and hashtags.
If the 'safe_mention' option is given, only consecutive mentions at the start the post are actually mentioned.
""" """
@spec linkify(String.t(), keyword()) :: @spec linkify(String.t(), keyword()) ::
{String.t(), [{String.t(), User.t()}], [{String.t(), String.t()}]} {String.t(), [{String.t(), User.t()}], [{String.t(), String.t()}]}
def linkify(text, options \\ []) do def linkify(text, options \\ []) do
options = options ++ @auto_linker_config options = options ++ @auto_linker_config
if options[:safe_mention] && Regex.named_captures(@safe_mention_regex, text) do
%{"mentions" => mentions, "rest" => rest} = Regex.named_captures(@safe_mention_regex, text)
acc = %{mentions: MapSet.new(), tags: MapSet.new()}
{text_mentions, %{mentions: mentions}} = AutoLinker.link_map(mentions, acc, options)
{text_rest, %{tags: tags}} = AutoLinker.link_map(rest, acc, options)
{text_mentions <> text_rest, MapSet.to_list(mentions), MapSet.to_list(tags)}
else
acc = %{mentions: MapSet.new(), tags: MapSet.new()} acc = %{mentions: MapSet.new(), tags: MapSet.new()}
{text, %{mentions: mentions, tags: tags}} = AutoLinker.link_map(text, acc, options) {text, %{mentions: mentions, tags: tags}} = AutoLinker.link_map(text, acc, options)
{text, MapSet.to_list(mentions), MapSet.to_list(tags)} {text, MapSet.to_list(mentions), MapSet.to_list(tags)}
end end
end
def emojify(text) do def emojify(text) do
emojify(text, Emoji.get_all()) emojify(text, Emoji.get_all())

View file

@ -142,7 +142,8 @@ def post(user, %{"status" => status} = data) do
make_content_html( make_content_html(
status, status,
attachments, attachments,
data data,
visibility
), ),
{to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility), {to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),
context <- make_context(in_reply_to), context <- make_context(in_reply_to),

View file

@ -101,7 +101,8 @@ def to_for_user_and_mentions(_user, mentions, inReplyTo, "direct") do
def make_content_html( def make_content_html(
status, status,
attachments, attachments,
data data,
visibility
) do ) do
no_attachment_links = no_attachment_links =
data data
@ -110,8 +111,15 @@ def make_content_html(
content_type = get_content_type(data["content_type"]) content_type = get_content_type(data["content_type"])
options =
if visibility == "direct" && Config.get([:instance, :safe_dm_mentions]) do
[safe_mention: true]
else
[]
end
status status
|> format_input(content_type) |> format_input(content_type, options)
|> maybe_add_attachments(attachments, no_attachment_links) |> maybe_add_attachments(attachments, no_attachment_links)
|> maybe_add_nsfw_tag(data) |> maybe_add_nsfw_tag(data)
end end

View file

@ -197,7 +197,9 @@ def config(conn, _params) do
vapidPublicKey: vapid_public_key, vapidPublicKey: vapid_public_key,
accountActivationRequired: accountActivationRequired:
if(Keyword.get(instance, :account_activation_required, false), do: "1", else: "0"), if(Keyword.get(instance, :account_activation_required, false), do: "1", else: "0"),
invitesEnabled: if(Keyword.get(instance, :invites_enabled, false), do: "1", else: "0") invitesEnabled: if(Keyword.get(instance, :invites_enabled, false), do: "1", else: "0"),
safeDMMentionsEnabled:
if(Pleroma.Config.get([:instance, :safe_dm_mentions]), do: "1", else: "0")
} }
pleroma_fe = pleroma_fe =

View file

@ -181,6 +181,31 @@ test "does not give a replacement for single-character local nicknames who don't
expected_text = "@a hi" expected_text = "@a hi"
assert {^expected_text, [] = _mentions, [] = _tags} = Formatter.linkify(text) assert {^expected_text, [] = _mentions, [] = _tags} = Formatter.linkify(text)
end end
test "given the 'safe_mention' option, it will only mention people in the beginning" do
user = insert(:user)
_other_user = insert(:user)
third_user = insert(:user)
text = " @#{user.nickname} hey dude i hate @#{third_user.nickname}"
{expected_text, mentions, [] = _tags} = Formatter.linkify(text, safe_mention: true)
assert mentions == [{"@#{user.nickname}", user}]
assert expected_text ==
"<span class='h-card'><a data-user='#{user.id}' class='u-url mention' href='#{
user.ap_id
}'>@<span>#{user.nickname}</span></a></span> hey dude i hate <span class='h-card'><a data-user='#{
third_user.id
}' class='u-url mention' href='#{third_user.ap_id}'>@<span>#{third_user.nickname}</span></a></span>"
end
test "given the 'safe_mention' option, it will still work without any mention" do
text = "A post without any mention"
{expected_text, mentions, [] = _tags} = Formatter.linkify(text, safe_mention: true)
assert mentions == []
assert expected_text == text
end
end end
describe ".parse_tags" do describe ".parse_tags" do

View file

@ -10,6 +10,24 @@ defmodule Pleroma.Web.CommonAPITest do
import Pleroma.Factory import Pleroma.Factory
test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
har = insert(:user)
jafnhar = insert(:user)
tridi = insert(:user)
option = Pleroma.Config.get([:instance, :safe_dm_mentions])
Pleroma.Config.put([:instance, :safe_dm_mentions], true)
{:ok, activity} =
CommonAPI.post(har, %{
"status" => "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
"visibility" => "direct"
})
refute tridi.ap_id in activity.recipients
assert jafnhar.ap_id in activity.recipients
Pleroma.Config.put([:instance, :safe_dm_mentions], option)
end
test "it de-duplicates tags" do test "it de-duplicates tags" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu #2HU"}) {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu #2HU"})

View file

@ -75,6 +75,29 @@ test "it marks a single notification as read", %{conn: conn} do
end end
describe "GET /api/statusnet/config.json" do describe "GET /api/statusnet/config.json" do
test "returns the state of safe_dm_mentions flag", %{conn: conn} do
option = Pleroma.Config.get([:instance, :safe_dm_mentions])
Pleroma.Config.put([:instance, :safe_dm_mentions], true)
response =
conn
|> get("/api/statusnet/config.json")
|> json_response(:ok)
assert response["site"]["safeDMMentionsEnabled"] == "1"
Pleroma.Config.put([:instance, :safe_dm_mentions], false)
response =
conn
|> get("/api/statusnet/config.json")
|> json_response(:ok)
assert response["site"]["safeDMMentionsEnabled"] == "0"
Pleroma.Config.put([:instance, :safe_dm_mentions], option)
end
test "it returns the managed config", %{conn: conn} do test "it returns the managed config", %{conn: conn} do
Pleroma.Config.put([:instance, :managed_config], false) Pleroma.Config.put([:instance, :managed_config], false)
Pleroma.Config.put([:fe], theme: "rei-ayanami-towel") Pleroma.Config.put([:fe], theme: "rei-ayanami-towel")