Browse Source

Merge branch 'feature/mrf_auto_subject' into 'develop'

Draft: MRF to automate CW/subject based on post content

See merge request pleroma/pleroma!3130
merge-requests/3130/merge
feld 2 years ago
parent
commit
897304a96f
3 changed files with 249 additions and 0 deletions
  1. +2
    -0
      config/config.exs
  2. +124
    -0
      lib/pleroma/web/activity_pub/mrf/auto_subject_policy.ex
  3. +123
    -0
      test/pleroma/web/activity_pub/mrf/auto_subject_policy_test.exs

+ 2
- 0
config/config.exs View File

@@ -400,6 +400,8 @@ config :pleroma, :mrf_vocabulary,
accept: [],
reject: []

config :pleroma, :mrf_auto_subject, match: []

# threshold of 7 days
config :pleroma, :mrf_object_age,
threshold: 604_800,


+ 124
- 0
lib/pleroma/web/activity_pub/mrf/auto_subject_policy.ex View File

@@ -0,0 +1,124 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.ActivityPub.MRF.AutoSubjectPolicy do
@moduledoc "Apply Subject to local posts matching certain keywords."

@behaviour Pleroma.Web.ActivityPub.MRF

alias Pleroma.User

require Pleroma.Constants
require Logger

@trim_regex Regex.compile!("[.?!:;]+$")

@impl true
def filter(%{"type" => "Create", "actor" => actor, "object" => _object} = message) do
with {:ok, %User{local: true}} <- User.get_or_fetch_by_ap_id(actor),
{:ok, message} <- check_subject(message),
{:ok, message} <- check_match(message) do
{:ok, message}
else
{:ok, %User{local: false}} ->
{:ok, message}

{:error, :has_subject} ->
{:ok, message}

{:error, _} ->
{:reject, "[AutoSubjectPolicy] Failed to get or fetch user by ap_id"}

e ->
{:reject, "[AutoSubjectPolicy] Unhandled error #{inspect(e)}"}
end
end

@impl true
def filter(message), do: {:ok, message}

defp check_subject(%{"object" => %{"summary" => subject}} = message) do
subject = String.trim(subject)

if String.length(subject) == 0 do
{:ok, message}
else
{:error, :has_subject}
end
end

defp check_subject(message), do: {:ok, message}

defp string_matches?(content, keywords) when is_list(keywords) do
wordlist = content |> make_wordlist |> trim_punct
Enum.any?(keywords, fn match -> String.downcase(match) in wordlist end)
end

defp string_matches?(content, keyword) when is_binary(keyword) do
wordlist = content |> make_wordlist |> trim_punct
String.downcase(keyword) in wordlist
end

defp check_match(%{"object" => %{} = object} = message) do
match_settings = Pleroma.Config.get([:mrf_auto_subject, :match])

auto_summary =
Enum.reduce(match_settings, [], fn {keywords, subject}, acc ->
if string_matches?(object["content"], keywords) do
[subject | acc]
else
acc
end
end)
|> Enum.join(", ")

message = put_in(message["object"]["summary"], auto_summary)

{:ok, message}
end

defp make_wordlist(content),
do:
content
|> String.downcase()
|> String.split(" ", trim: true)
|> Enum.uniq()

defp trim_punct(wordlist) when is_list(wordlist),
do: wordlist |> Enum.map(fn word -> String.replace(word, @trim_regex, "") end)

@impl true
def describe do
mrf_autosubject =
:mrf_auto_subject
|> Pleroma.Config.get()
|> Enum.into(%{})

{:ok, %{mrf_auto_subject: mrf_autosubject}}
end

@impl true
def config_description do
%{
key: :mrf_auto_subject,
related_policy: "Pleroma.Web.ActivityPub.MRF.AutoSubjectPolicy",
label: "MRF AutoSubject",
description:
"Adds subject to messages matching a keyword or list of keywords if no subject is defined.",
children: [
%{
key: :match,
type: {:keyword, :string},
description: """
**Keyword**: a string or list of keywords. E.g., ["cat", "dog"] to match on both "cat" and "dog".

**Subject**: a string to insert into the subject field.

Note: the keyword matching is case-insensitive and matches only the whole word.
"""
}
]
}
end
end

+ 123
- 0
test/pleroma/web/activity_pub/mrf/auto_subject_policy_test.exs View File

@@ -0,0 +1,123 @@
defmodule Pleroma.Web.ActivityPub.MRF.AutoSubjectPolicyTest do
use Pleroma.DataCase

import Pleroma.Factory

alias Pleroma.Web.ActivityPub.MRF.AutoSubjectPolicy

describe "filter/1" do
setup do
user = insert(:user)
[user: user]
end

test "pattern as string, matches case insensitive", %{user: user} do
clear_config([:mrf_auto_subject, :match], [{"senate", "uspol"}])

assert {:ok,
%{"object" => %{"content" => "The Senate is now in recess.", "summary" => "uspol"}}} =
AutoSubjectPolicy.filter(%{
"type" => "Create",
"actor" => user.ap_id,
"object" => %{"content" => "The Senate is now in recess.", "summary" => ""}
})
end

test "pattern as list", %{user: user} do
clear_config([:mrf_auto_subject, :match], [{["dinner", "sandwich"], "food"}])

assert {:ok,
%{
"object" => %{
"content" => "I decided to eat leftovers for dinner again.",
"summary" => "food"
}
}} =
AutoSubjectPolicy.filter(%{
"type" => "Create",
"actor" => user.ap_id,
"object" => %{"content" => "I decided to eat leftovers for dinner again."}
})
end

test "multiple matches and punctuation trimming", %{user: user} do
clear_config([:mrf_auto_subject, :match], [{["dog", "cat"], "pets"}, {"Torvalds", "Linux"}])

assert {:ok,
%{
"object" => %{
"content" => "A long time ago I named my dog after Linus Torvalds.",
"summary" => "Linux, pets"
}
}} =
AutoSubjectPolicy.filter(%{
"type" => "Create",
"actor" => user.ap_id,
"object" => %{
"content" => "A long time ago I named my dog after Linus Torvalds."
}
})
end

test "with no match", %{user: user} do
clear_config([:mrf_auto_subject, :match], [{"puppy", "pets"}])

assert {:ok, %{"object" => %{"content" => "I have a kitten", "summary" => ""}}} =
AutoSubjectPolicy.filter(%{
"type" => "Create",
"actor" => user.ap_id,
"object" => %{"content" => "I have a kitten", "summary" => ""}
})
end

test "user is not local" do
user = insert(:user, local: false)
clear_config([:mrf_auto_subject, :match], [{"puppy", "pets"}])

assert {:ok, %{"object" => %{"content" => "We just got a puppy", "summary" => ""}}} =
AutoSubjectPolicy.filter(%{
"type" => "Create",
"actor" => user.ap_id,
"object" => %{"content" => "We just got a puppy", "summary" => ""}
})
end

test "subject is already set", %{user: user} do
clear_config([:mrf_auto_subject, :match], [{"election", "politics"}])

assert {:ok,
%{
"object" => %{
"content" => "If your election lasts more than 4 hours you should see a doctor",
"summary" => "uspol, humor"
}
}} =
AutoSubjectPolicy.filter(%{
"type" => "Create",
"actor" => user.ap_id,
"object" => %{
"content" =>
"If your election lasts more than 4 hours you should see a doctor",
"summary" => "uspol, humor"
}
})
end
end

test "describe/0" do
clear_config([:mrf_auto_subject, :match], [{"dog", "pets"}])

assert AutoSubjectPolicy.describe() ==
{:ok,
%{
mrf_auto_subject: %{
match: [{"dog", "pets"}]
}
}}
end

test "config_description/0" do
assert %{key: _, related_policy: _, label: _, description: _} =
AutoSubjectPolicy.config_description()
end
end

Loading…
Cancel
Save