move to 2.5.5
[anni] / lib / pleroma / web / activity_pub / mrf / hashtag_policy.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
6   require Pleroma.Constants
7
8   alias Pleroma.Config
9   alias Pleroma.Object
10
11   @moduledoc """
12   Reject, TWKN-remove or Set-Sensitive messsages with specific hashtags (without the leading #)
13
14   Note: This MRF Policy is always enabled, if you want to disable it you have to set empty lists.
15   """
16
17   @behaviour Pleroma.Web.ActivityPub.MRF.Policy
18
19   @impl true
20   def history_awareness, do: :manual
21
22   defp check_reject(message, hashtags) do
23     if Enum.any?(Config.get([:mrf_hashtag, :reject]), fn match -> match in hashtags end) do
24       {:reject, "[HashtagPolicy] Matches with rejected keyword"}
25     else
26       {:ok, message}
27     end
28   end
29
30   defp check_ftl_removal(%{"to" => to} = message, hashtags) do
31     if Pleroma.Constants.as_public() in to and
32          Enum.any?(Config.get([:mrf_hashtag, :federated_timeline_removal]), fn match ->
33            match in hashtags
34          end) do
35       to = List.delete(to, Pleroma.Constants.as_public())
36       cc = [Pleroma.Constants.as_public() | message["cc"] || []]
37
38       message =
39         message
40         |> Map.put("to", to)
41         |> Map.put("cc", cc)
42         |> Kernel.put_in(["object", "to"], to)
43         |> Kernel.put_in(["object", "cc"], cc)
44
45       {:ok, message}
46     else
47       {:ok, message}
48     end
49   end
50
51   defp check_ftl_removal(message, _hashtags), do: {:ok, message}
52
53   defp check_sensitive(message) do
54     {:ok, new_object} =
55       Object.Updater.do_with_history(message["object"], fn object ->
56         hashtags = Object.hashtags(%Object{data: object})
57
58         if Enum.any?(Config.get([:mrf_hashtag, :sensitive]), fn match -> match in hashtags end) do
59           {:ok, Map.put(object, "sensitive", true)}
60         else
61           {:ok, object}
62         end
63       end)
64
65     {:ok, Map.put(message, "object", new_object)}
66   end
67
68   @impl true
69   def filter(%{"type" => type, "object" => object} = message) when type in ["Create", "Update"] do
70     history_items =
71       with %{"formerRepresentations" => %{"orderedItems" => items}} <- object do
72         items
73       else
74         _ -> []
75       end
76
77     historical_hashtags =
78       Enum.reduce(history_items, [], fn item, acc ->
79         acc ++ Object.hashtags(%Object{data: item})
80       end)
81
82     hashtags = Object.hashtags(%Object{data: object}) ++ historical_hashtags
83
84     if hashtags != [] do
85       with {:ok, message} <- check_reject(message, hashtags),
86            {:ok, message} <-
87              (if "type" == "Create" do
88                 check_ftl_removal(message, hashtags)
89               else
90                 {:ok, message}
91               end),
92            {:ok, message} <- check_sensitive(message) do
93         {:ok, message}
94       end
95     else
96       {:ok, message}
97     end
98   end
99
100   @impl true
101   def filter(message), do: {:ok, message}
102
103   @impl true
104   def describe do
105     mrf_hashtag =
106       Config.get(:mrf_hashtag)
107       |> Enum.into(%{})
108
109     {:ok, %{mrf_hashtag: mrf_hashtag}}
110   end
111
112   @impl true
113   def config_description do
114     %{
115       key: :mrf_hashtag,
116       related_policy: "Pleroma.Web.ActivityPub.MRF.HashtagPolicy",
117       label: "MRF Hashtag",
118       description: @moduledoc,
119       children: [
120         %{
121           key: :reject,
122           type: {:list, :string},
123           description: "A list of hashtags which result in message being rejected.",
124           suggestions: ["foo"]
125         },
126         %{
127           key: :federated_timeline_removal,
128           type: {:list, :string},
129           description:
130             "A list of hashtags which result in message being removed from federated timelines (a.k.a unlisted).",
131           suggestions: ["foo"]
132         },
133         %{
134           key: :sensitive,
135           type: {:list, :string},
136           description:
137             "A list of hashtags which result in message being set as sensitive (a.k.a NSFW/R-18)",
138           suggestions: ["nsfw", "r18"]
139         }
140       ]
141     }
142   end
143 end