moderation/module.nix
2024-11-14 15:53:38 +01:00

216 lines
6.2 KiB
Nix
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{ 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 {
inherit reason;
activities = lib.mkOption {
type = with lib.types; (nullOr enum [ "unlist" "restrict" "reject" ]);
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.
'';
};
});
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 {
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.
'';
};
});
description = ''
Hashtag moderations.
'';
default = { };
example = {
"lewd" = {
reason = "Reduce visibility of activities tagged as lewd.";
sensitive = true;
unlisted = true;
};
};
};
keywords = lib.mkOption {
type = with lib.types; attrsOf (submodule {
inherit reason;
action = lib.mkOption {
type = lib.types.bool;
description = ''
Activity moderation:
- `unlist`: Remove matching activities from federated timelines.
- `reject`: Reject matching activities.
'';
};
});
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_reject_newly_created_account_notes".age =
lib.mkIf (cfg.unknownUserDelay != null) cfg.unknownUserDelay;
};
}