From 514250f9da19b116836327165f529fa2a4b7290a Mon Sep 17 00:00:00 2001 From: Mikael Voss Date: Thu, 14 Nov 2024 15:53:38 +0100 Subject: [PATCH] Initial import --- flake.lock | 27 +++++++ flake.nix | 24 ++++++ florp.nix | 13 ++++ module.nix | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 280 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 florp.nix create mode 100644 module.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..5134ed1 --- /dev/null +++ b/flake.lock @@ -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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..a9ea2e1 --- /dev/null +++ b/flake.nix @@ -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; + }); + }; +} diff --git a/florp.nix b/florp.nix new file mode 100644 index 0000000..c2043d6 --- /dev/null +++ b/florp.nix @@ -0,0 +1,13 @@ +{ ... }: { + hashtags = { + lewd = { + reason = "Reduce visibility of lewd posts."; + sensitive = true; + }; + + nsfw = { + reason = "Reduce visibility of lewd posts."; + sensitive = true; + }; + }; +} diff --git a/module.nix b/module.nix new file mode 100644 index 0000000..fba8b52 --- /dev/null +++ b/module.nix @@ -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; + }; +}