Initial import
This commit is contained in:
commit
514250f9da
4 changed files with 280 additions and 0 deletions
27
flake.lock
Normal file
27
flake.lock
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731531548,
|
||||||
|
"narHash": "sha256-sz8/v17enkYmfpgeeuyzniGJU0QQBfmAjlemAUYhfy8=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "24f0d4acd634792badd6470134c387a3b039dace",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
24
flake.nix
Normal file
24
flake.nix
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
outputs = { self, nixpkgs, ... }: let
|
||||||
|
inherit (nixpkgs) lib;
|
||||||
|
in {
|
||||||
|
nixosModules = {
|
||||||
|
default = import ./module.nix;
|
||||||
|
florp = args: {
|
||||||
|
services.akkoma.moderation = import ./florp.nix args;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
checks = lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (system: {
|
||||||
|
default = (lib.nixosSystem {
|
||||||
|
inherit system;
|
||||||
|
modules = [ {
|
||||||
|
system.stateVersion = "24.11";
|
||||||
|
fileSystems."/".device = "nodev";
|
||||||
|
boot.loader.grub.enable = false;
|
||||||
|
} ] ++ (with self.nixosModules; [ default florp ]);
|
||||||
|
}).config.system.build.toplevel;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
13
florp.nix
Normal file
13
florp.nix
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{ ... }: {
|
||||||
|
hashtags = {
|
||||||
|
lewd = {
|
||||||
|
reason = "Reduce visibility of lewd posts.";
|
||||||
|
sensitive = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
nsfw = {
|
||||||
|
reason = "Reduce visibility of lewd posts.";
|
||||||
|
sensitive = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
216
module.nix
Normal file
216
module.nix
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
{ 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 user‐specific 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;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue