moderation/module.nix

225 lines
6.5 KiB
Nix
Raw Permalink Normal View History

2024-11-14 15:53:38 +01:00
{ config, lib, pkgs, ... }: let
cfg = config.services.akkoma.moderation;
reason = lib.mkOption {
type = lib.types.nonEmptyStr;
description = ''
Explanation for moderation decision.
'';
};
in {
options.services.akkoma.moderation = {
instances = lib.mkOption {
type = with lib.types; attrsOf (submodule {
2024-11-14 19:11:10 +01:00
options = {
inherit reason;
activities = lib.mkOption {
type = with lib.types; (nullOr (enum [ "unlist" "restrict" "reject" "followersOnly" ]));
default = null;
description = ''
Activity moderation:
- `unlist`: Remove activities from federated timeline.
- `restrict`: Force activities to be visible to followers only.
- `reject`: Reject all activities except deletes.
'';
};
media = lib.mkOption {
type = with lib.types; (nullOr (enum [ "mark" "strip" ]));
default = null;
description = ''
Media attachment moderation:
- `mark`: Mark media attachments as sensitive.
- `strip`: Strip all media attachments.
'';
};
2024-11-14 15:53:38 +01:00
};
});
description = ''
Instance moderations.
'';
default = { };
example = {
"reallybadinstance.social" = {
reason = "Persistent harrassment of users, no effective moderation.";
activities = "reject";
};
"verylewd.xxx" = {
reason = "Lots of untagged lewd media.";
activities = "unlist";
media = "mark";
};
};
};
hashtags = lib.mkOption {
type = with lib.types; attrsOf (submodule {
2024-11-14 19:11:10 +01:00
options = {
inherit reason;
sensitive = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Mark tagged activities as sensitive.
'';
};
unlisted = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Remove tagged activities from federated timeline.
'';
};
2024-11-14 15:53:38 +01:00
};
});
description = ''
Hashtag moderations.
'';
default = { };
example = {
"lewd" = {
reason = "Reduce visibility of activities tagged as lewd.";
sensitive = true;
unlisted = true;
};
};
};
keywords = lib.mkOption {
2024-11-14 19:11:10 +01:00
type = with lib.types; attrsOf (submodule {
options = {
inherit reason;
action = lib.mkOption {
type = lib.types.bool;
description = ''
Activity moderation:
- `unlist`: Remove matching activities from federated timelines.
- `reject`: Reject matching activities.
'';
};
2024-11-14 15:53:38 +01:00
};
});
description = ''
Keyword moderations.
'';
default = { };
example = {
"/all hail( our glorious leader)? hypnotoad/i" = {
reason = "Reduce influence of Hypnotoad on our users.";
action = "unlist";
};
};
};
unknownUserDelay = lib.mkOption {
type = with lib.types; nullOr ints.unsigned;
default = 900;
description = ''
After initially encountering a user, reject all their posts for the
configured number of seconds. Set to `null` to disable.
'';
};
hellthread = {
delist = lib.mkOption {
type = lib.types.ints.unsigned;
default = 8;
description = ''
Number of mentioned users beyond which a message gets delisted. It
will not show up in public timelines and mentioned users will not get
notified.
'';
};
reject = lib.mkOption {
type = lib.types.ints.unsigned;
default = 16;
description = ''
Number of mentioned users beyond which a message will be rejected.
'';
};
};
};
config.services.akkoma.config.":pleroma" = let
elixir = pkgs.formats.elixirConf { };
in {
":mrf" = {
transparency = lib.mkDefault true;
policies = map elixir.lib.mkRaw [
"Pleroma.Web.ActivityPub.MRF.SimplePolicy"
"Pleroma.Web.ActivityPub.MRF.KeywordPolicy"
"Pleroma.Web.ActivityPub.MRF.HellthreadPolicy"
"Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy"
# Spam reduction
"Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy"
"Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy"
# Local policies
"Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy"
"Pleroma.Web.ActivityPub.MRF.TagPolicy" # Local userspecific moderation
# Always enabled:
# Pleroma.Web.ActivityPub.MRF.DirectMessageDisabledPolicy
# Pleroma.Web.ActivityPub.MRF.HashtagPolicy
# Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy
# Pleroma.Web.ActivityPub.MRF.NormalizeMarkup
];
};
":mrf_simple" = let
rules = fun: attrs: lib.mapAttrsToList
(name: rule: elixir.lib.mkTuple [ name rule.reason ])
(lib.filterAttrs (name: rule: fun rule) attrs);
in {
media_removal = rules (rule: rule.media == "strip") cfg.instances;
media_nsfw = rules (rule: rule.media == "mark") cfg.instances;
federated_timeline_removal = rules (rule: rule.activities == "unlist") cfg.instances;
followers_only = rules (rule: rule.activities == "followersOnly") cfg.instances;
reject = rules (rule: rule.activities == "reject") cfg.instances;
};
":mrf_hashtag" = let
rules = fun: attrs: lib.attrNames (lib.filterAttrs (name: rule: fun rule) attrs);
in {
sensitive = rules (rule: rule.sensitive) cfg.hashtags;
federated_timeline_removal = rules (rule: rule.unlisted) cfg.hashtags;
};
":mrf_keyword" = let
rules = fun: attrs: lib.mapAttrsToList
(name: rule: elixir.lib.mkRaw "~r${name}")
(lib.filterAttrs (name: rule: fun rule) attrs);
in {
reject = rules (rule: rule.action == "reject") cfg.keywords;
federated_timeline_removal = rules (rule: rule.action == "unlist") cfg.keywords;
};
":mrf_hellthread" = {
delist_threshold = cfg.hellthread.delist;
reject_threshold = cfg.hellthread.reject;
};
":mrf_object_age".threshold = lib.mkDefault (90 * 24 * 3600); # 90 days
2024-11-14 15:53:38 +01:00
":mrf_reject_newly_created_account_notes".age =
lib.mkIf (cfg.unknownUserDelay != null) cfg.unknownUserDelay;
};
}