diff options
| author | dcc <dcc@logografos.com> | 2024-05-15 00:57:23 -0700 |
|---|---|---|
| committer | dcc <dcc@logografos.com> | 2024-05-15 00:57:23 -0700 |
| commit | b31a934a804aed3f35442ceafe2080b0955e7317 (patch) | |
| tree | 947b13a0388ecea81e05dd980baa10f7546860b9 /static/modules | |
| parent | ea33a0d3427f8b30b82a6ddbc0ff7429cfaf8d91 (diff) | |
| download | anni-b31a934a804aed3f35442ceafe2080b0955e7317.tar.gz anni-b31a934a804aed3f35442ceafe2080b0955e7317.tar.bz2 anni-b31a934a804aed3f35442ceafe2080b0955e7317.zip | |
Diffstat (limited to 'static/modules')
| -rw-r--r--[-rwxr-xr-x] | static/modules/high_roller_policy.ex | 335 |
1 files changed, 181 insertions, 154 deletions
diff --git a/static/modules/high_roller_policy.ex b/static/modules/high_roller_policy.ex index 50b21ea..01ed179 100755..100644 --- a/static/modules/high_roller_policy.ex +++ b/static/modules/high_roller_policy.ex @@ -1,4 +1,4 @@ -# Three-in-one policy for block, report and unfollow notifications. +# Five-in-one policy for block, report, unfollow and follow reject/remove notifications. # Credits for the individual parts: # Yukkuri for the combined block/report MRF # https://gitlab.eientei.org/eientei/pleroma/-/blob/eientei/lib/pleroma/web/activity_pub/mrf/block_bot_policy.ex @@ -8,27 +8,57 @@ # https://eientei.org/notice/AL6nnjih8H6Lco8QoS # Pete for the example of Cachex-based rate limiting # https://freespeechextremist.com/objects/9f24a3e4-2e34-4fcb-a0d1-42229e27da3e +# Phnt for force follower removal MRF +# https://git.fluffytail.org/phnt/pleroma/commit/1c801703297bf41ca5bca84af9e47b514aba995b defmodule Pleroma.Web.ActivityPub.MRF.HighRollerPolicy do alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Config + alias Pleroma.Activity require Logger @moduledoc "Notify local users upon the block, report or unfollow." @behaviour Pleroma.Web.ActivityPub.MRF.Policy - defp is_block_or_unblock(%{"type" => "Block", "object" => object}), - do: {true, "blocked", object} + defp check_cache(message, actor, recepient, action) do + with {:error, _} <- Cachex.stats(:highroller), do: Cachex.start(:highroller, [ stats: true ]) + systime = :os.system_time(:seconds) - defp is_block_or_unblock(%{ - "type" => "Undo", - "object" => %{"type" => "Block", "object" => object} - }), - do: {true, "unblocked", object} + {_, actiontime} = Cachex.fetch(:highroller, actor<>","<>recepient<>","<>action, fn(_i) -> {:commit, :os.system_time(:seconds)-1} end) + {_, globalcount} = Cachex.fetch(:highroller, "global:"<>actor, fn(_i) -> {:commit, 0} end) + + if (systime > actiontime && globalcount < Config.get([:mrf_high_roller, :global_threshold])) do + Cachex.incr(:highroller, "global:"<>actor, globalcount+1) + Cachex.put(:highroller, actor<>","<>recepient<>","<>action, systime+Config.get([:mrf_high_roller, :timeout])) + true + else + Logger.warning("Rate-limited incoming "<>action<>" notif! #{inspect(message)}") + Cachex.incr(:highroller, "global:"<>actor, globalcount+1) + Cachex.incr(:highroller, actor<>","<>recepient<>","<>action, 30*(1+(systime-actiontime))) + false + end + end + + defp check_action(%{"type" => "Reject", "object" => object}) do + activity = Activity.normalize(object) + case activity.data do + %{"type" => "Follow", "state" => "accept"} -> {true, "follow_remove", Config.get([:mrf_high_roller, :follow_remove_message]), Config.get([:mrf_high_roller, :follow_remove_visibility])} + %{"type" => "Follow", "state" => "pending"} -> {true, "follow_reject", Config.get([:mrf_high_roller, :follow_reject_message]), Config.get([:mrf_high_roller, :follow_reject_visibility])} + _ -> {false, nil, nil, nil} + end + end - defp is_block_or_unblock(_), do: {false, nil, nil} + defp check_action(message) do + case message do + %{"type" => "Block"} -> {true, "block", Config.get([:mrf_high_roller, :block_message]), Config.get([:mrf_high_roller, :block_visibility])} + %{"type" => "Flag"} -> {true, "report", Config.get([:mrf_high_roller, :report_message]), Config.get([:mrf_high_roller, :report_visibility])} + %{"type" => "Undo", "object" => %{"type" => "Block"}} -> {true, "unblock", Config.get([:mrf_high_roller, :unblock_message]), Config.get([:mrf_high_roller, :unblock_visibility])} + %{"type" => "Undo", "object" => %{"type" => "Follow"}} -> {true, "unfollow", Config.get([:mrf_high_roller, :unfollow_message]), Config.get([:mrf_high_roller, :unfollow_visibility])} + _ -> {false, nil, nil, nil} + end + end defp is_report(%{"type" => "Flag", "object" => objects}) do case objects do @@ -40,190 +70,124 @@ defmodule Pleroma.Web.ActivityPub.MRF.HighRollerPolicy do defp is_report(_), do: {false, [], nil} - defp extract_reported_post(post) do - case post do - %{"id" => id} -> id - s when is_binary(s) -> s - _ -> nil + defp check_recepient(action, message) when action == "report" do + with {true, _, to} <- is_report(message) do + User.get_cached_by_ap_id(to) end end - defp is_unfollow(%{ - "type" => "Undo", - "object" => %{"type" => "Follow", "object" => object} - }), - do: {true, object} - - defp is_unfollow(_), do: {false, nil, nil} - - @impl true - def filter(message) do - with {:error, _} <- Cachex.stats(:highroller), do: Cachex.start(:highroller, [ stats: true ]) - systime = :os.system_time(:seconds) + defp check_recepient(_, %{"type" => "Undo", "object" => %{"object" => object}}) do + User.get_cached_by_ap_id(object) + end - with {true, action, object} <- is_block_or_unblock(message), - %User{} = actor <- User.get_cached_by_ap_id(message["actor"]), - %User{} = recipient <- User.get_cached_by_ap_id(object), - false <- Enum.member?(Config.get([:mrf_high_roller, :actor_blacklist]), message["actor"]), - false <- Enum.member?(Config.get([:mrf_high_roller, :domain_blacklist]), URI.parse(message["actor"]).host), - true <- recipient.local do + defp check_recepient(_, %{"type" => "Reject", "to" => to}) do + User.get_cached_by_ap_id(to) + end - {_, actiontime} = Cachex.fetch(:highroller, actor.nickname<>","<>recipient.nickname<>","<>action, fn(_i) -> {:commit, :os.system_time(:seconds)-1} end) - {_, globalcount} = Cachex.fetch(:highroller, "global:"<>actor.nickname, fn(_i) -> {:commit, 0} end) + defp check_recepient(_, %{"object" => object}) do + User.get_cached_by_ap_id(object) + end - blocker = if(Config.get([:mrf_high_roller, :tag_blocking_actor]) && !Enum.member?(Config.get([:mrf_high_roller, :domain_greylist]), URI.parse(message["actor"]).host)) do - "@" <> actor.nickname - else - actor.nickname - end + defp check_recepient(_, _) do + nil + end - replacements = %{ - "actor" => blocker, - "target" => "@" <> recipient.nickname, - "action" => action - } + defp check_tag(host, actor, action) do + mention = case action do + "block" -> Config.get([:mrf_high_roller, :tag_blocking_actor]) + "unblock" -> Config.get([:mrf_high_roller, :tag_unblocking_actor]) + "report" -> Config.get([:mrf_high_roller, :tag_reporting_actor]) + "unfollow" -> Config.get([:mrf_high_roller, :tag_unfollowing_actor]) + "follow_remove" -> Config.get([:mrf_high_roller, :tag_follow_remove_actor]) + "follow_reject" -> Config.get([:mrf_high_roller, :tag_follow_reject_actor]) + _ -> false + end - msg = - Regex.replace( - ~r/{([a-z]+)?}/, - Config.get([:mrf_high_roller, :block_message]), - fn _, match -> - replacements[match] - end - ) + if(mention && !Enum.member?(Config.get([:mrf_high_roller, :domain_greylist]), host)) do + "@" <> actor.nickname + else + actor.nickname + end + end - if (systime > actiontime && globalcount < Config.get([:mrf_high_roller, :global_threshold])) do - Cachex.incr(:highroller, "global:"<>actor.nickname, globalcount+1) - Cachex.put(:highroller, actor.nickname<>","<>recipient.nickname<>","<>action, systime+Config.get([:mrf_high_roller, :timeout])) - CommonAPI.post(User.get_cached_by_nickname(Config.get([:mrf_high_roller, :user])), %{ - status: msg, - visibility: Config.get([:mrf_high_roller, :block_visibility]) - }) - else - Logger.warn("Rate-limited incoming block notif! #{inspect(message)}") - Cachex.incr(:highroller, "global:"<>actor.nickname, globalcount+1) - Cachex.incr(:highroller, actor.nickname<>","<>recipient.nickname<>","<>action, 30*(1+(systime-actiontime))) - end + defp extract_reported_post(post) do + case post do + %{"id" => id} -> id + s when is_binary(s) -> s + _ -> nil end + end - with {true, objects, to} <- is_report(message), + @impl true + def filter(%{"type" => type} = message) when type in ["Block", "Undo", "Flag", "Reject"] do + with {true, action, template, visibility} <- check_action(message), %User{} = actor <- User.get_cached_by_ap_id(message["actor"]), - %User{} = recipient <- User.get_cached_by_ap_id(to), + %User{} = recepient <- check_recepient(action, message), + tag <- check_tag(URI.parse(message["actor"]).host, actor, action), false <- Enum.member?(Config.get([:mrf_high_roller, :actor_blacklist]), message["actor"]), false <- Enum.member?(Config.get([:mrf_high_roller, :domain_blacklist]), URI.parse(message["actor"]).host), - true <- recipient.local do - - {_, actiontime} = Cachex.fetch(:highroller, actor.nickname<>","<>recipient.nickname<>",report", fn(_i) -> {:commit, :os.system_time(:seconds)-1} end) - {_, globalcount} = Cachex.fetch(:highroller, "global:"<>actor.nickname, fn(_i) -> {:commit, 0} end) - - reporter = if(Config.get([:mrf_high_roller, :tag_reporting_actor]) && !Enum.member?(Config.get([:mrf_high_roller, :domain_greylist]), URI.parse(message["actor"]).host)) do - "@" <> actor.nickname - else - actor.nickname - end + true <- recepient.local, + true <- check_cache(message, actor.nickname, recepient.nickname, action) do replacements = %{ - "actor" => reporter, - "target" => "@" <> recipient.nickname + "actor" => tag, + "target" => "@" <> recepient.nickname } msg = Regex.replace( ~r/{([a-z]+)?}/, - Pleroma.Config.get([:mrf_high_roller, :report_message]), + template, fn _, match -> replacements[match] end ) - posts = - objects - |> Enum.map(&extract_reported_post/1) - |> Enum.reject(&is_nil/1) - |> Enum.map(fn s -> "- " <> s end) - |> Enum.join("\n") - |> (fn s -> - case s do - "" -> "" - s -> "\n\nReported objects:\n" <> s - end - end).() - - comment = - case message["content"] do - "" -> "" - s when is_binary(s) -> "\n\nReport message:\n" <> s - _ -> "" - end - - if (systime > actiontime && globalcount < Config.get([:mrf_high_roller, :global_threshold])) do - Cachex.incr(:highroller, "global:"<>actor.nickname, globalcount+1) - Cachex.put(:highroller, actor.nickname<>","<>recipient.nickname<>",report", systime+Config.get([:mrf_high_roller, :timeout])) - CommonAPI.post(User.get_cached_by_nickname(Config.get([:mrf_high_roller, :user])), %{ - status: msg <> comment <> posts, - visibility: Config.get([:mrf_high_roller, :report_visibility]) - }) - else - Logger.warn("Rate-limited incoming report notif! #{inspect(message)}") - Cachex.incr(:highroller, "global:"<>actor.nickname, globalcount+1) - Cachex.incr(:highroller, actor.nickname<>","<>recipient.nickname<>",report", 30*(1+(systime-actiontime))) - end - end - - with {true, object} <- is_unfollow(message), - %User{} = actor <- User.get_cached_by_ap_id(message["actor"]), - %User{} = recipient <- User.get_cached_by_ap_id(object), - false <- Enum.member?(Config.get([:mrf_high_roller, :actor_blacklist]), message["actor"]), - false <- Enum.member?(Config.get([:mrf_high_roller, :domain_blacklist]), URI.parse(message["actor"]).host), - true <- recipient.local do - - {_, actiontime} = Cachex.fetch(:highroller, actor.nickname<>","<>recipient.nickname<>",unfollow", fn(_i) -> {:commit, :os.system_time(:seconds)-1} end) - {_, globalcount} = Cachex.fetch(:highroller, "global:"<>actor.nickname, fn(_i) -> {:commit, 0} end) - - unfollower = if(Config.get([:mrf_high_roller, :tag_unfollowing_actor]) && !Enum.member?(Config.get([:mrf_high_roller, :domain_greylist]), URI.parse(message["actor"]).host)) do - "@" <> actor.nickname - else - actor.nickname - end - - replacements = %{ - "actor" => unfollower, - "target" => "@" <> recipient.nickname - } - - msg = - Regex.replace( - ~r/{([a-z]+)?}/, - Pleroma.Config.get([:mrf_high_roller, :unfollow_message]), - fn _, match -> - replacements[match] + info = with {true, objects, _} <- is_report(message) do + posts = + objects + |> Enum.map(&extract_reported_post/1) + |> Enum.reject(&is_nil/1) + |> Enum.map(fn s -> "- " <> s end) + |> Enum.join("\n") + |> (fn s -> + case s do + "" -> "" + s -> "\n\nReported objects:\n" <> s + end + end).() + + comment = + case message["content"] do + "" -> "" + s when is_binary(s) -> "\n\nReport message:\n" <> s + _ -> "" end - ) - if (systime > actiontime && globalcount < Config.get([:mrf_high_roller, :global_threshold])) do - Cachex.incr(:highroller, "global:"<>actor.nickname, globalcount+1) - Cachex.put(:highroller, actor.nickname<>","<>recipient.nickname<>",unfollow", systime+Config.get([:mrf_high_roller, :timeout])) - CommonAPI.post(User.get_cached_by_nickname(Config.get([:mrf_high_roller, :user])), %{ - status: msg, - visibility: Config.get([:mrf_high_roller, :unfollow_visibility]) - }) + comment <> posts else - Logger.warn("Rate-limited incoming unfollow notif! #{inspect(message)}") - Cachex.incr(:highroller, "global:"<>actor.nickname, globalcount+1) - Cachex.incr(:highroller, actor.nickname<>","<>recipient.nickname<>",unfollow", 30*(1+(systime-actiontime))) + _ -> "" end + + CommonAPI.post(User.get_cached_by_nickname(Config.get([:mrf_high_roller, :user])), %{ + status: msg <> info, + visibility: visibility + }) end {:ok, message} end @impl true + def filter(message), do: {:ok, message} + + @impl true def config_description do %{ key: :mrf_high_roller, related_policy: "Pleroma.Web.ActivityPub.MRF.HighRollerPolicy", label: "High Roller Policy", - description: "Three-in-one policy for notifying users upon being blocked, unfollowed or reported", + description: "Five-in-one policy for notifying users upon being blocked, unfollowed, reported, having their follow rejected or force-removed", children: [ %{ key: :user, @@ -272,8 +236,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.HighRollerPolicy do type: :string, label: "Block message", description: - "The message to send when someone is blocked or unblocked; use {actor}, {target}, and {action} variables", - suggestions: ["{target} you have been {action} by {actor}"] + "The message to send when someone is blocked; use {actor} and {target} variables", + suggestions: ["{target} you have been blocked by {actor}"] }, %{ key: :tag_blocking_actor, @@ -289,6 +253,27 @@ defmodule Pleroma.Web.ActivityPub.MRF.HighRollerPolicy do suggestions: ["public", "unlisted", "private", "direct"] }, %{ + key: :unblock_message, + type: :string, + label: "Unblock message", + description: + "The message to send when someone is blocked or unblocked; use {actor} and {target} variables", + suggestions: ["{target} you have been unblocked by {actor}"] + }, + %{ + key: :tag_unblocking_actor, + type: :boolean, + label: "Tag unblocking actor", + description: "Whether to tag the unblocking actor or not" + }, + %{ + key: :unblock_visibility, + type: :string, + label: "Unblock visibility", + description: "Visibility of the unblock notification", + suggestions: ["public", "unlisted", "private", "direct"] + }, + %{ key: :report_message, type: :string, label: "Report message", @@ -329,6 +314,48 @@ defmodule Pleroma.Web.ActivityPub.MRF.HighRollerPolicy do label: "Unfollow visibility", description: "Visibility of the unfollow notification", suggestions: ["public", "unlisted", "private", "direct"] + }, + %{ + key: :follow_remove_message, + type: :string, + label: "Follower removal message", + description: + "The message to send when someone is being removed from followers; use {actor} and {target} variables", + suggestions: ["{target} you have been removed from followers by {actor}"] + }, + %{ + key: :tag_follow_remove_actor, + type: :boolean, + label: "Tag actor removing follower", + description: "Whether to tag the actor removing followers or not" + }, + %{ + key: :follow_remove_visibility, + type: :string, + label: "Follower removal visibility", + description: "Visibility of the follower removal notification", + suggestions: ["public", "unlisted", "private", "direct"] + }, + %{ + key: :follow_reject_message, + type: :string, + label: "Follower rejection message", + description: + "The message to send when someone is being rejectd from followers; use {actor} and {target} variables", + suggestions: ["{target} your follow request has been rejected by {actor}"] + }, + %{ + key: :tag_follow_reject_actor, + type: :boolean, + label: "Tag actor removing follower", + description: "Whether to tag the actor removing followers or not" + }, + %{ + key: :follow_reject_visibility, + type: :string, + label: "Follower rejection visibility", + description: "Visibility of the follower rejection notification", + suggestions: ["public", "unlisted", "private", "direct"] } ] } |
