{"id":14109825,"url":"https://github.com/bclindner/ivory","last_synced_at":"2025-08-01T09:31:50.779Z","repository":{"id":35801288,"uuid":"192105859","full_name":"bclindner/ivory","owner":"bclindner","description":"A Mastodon automoderator.","archived":true,"fork":false,"pushed_at":"2022-12-08T06:35:30.000Z","size":121,"stargazers_count":26,"open_issues_count":7,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-02T12:06:58.283Z","etag":null,"topics":["automoderator","mastodon","python"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bclindner.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-06-15T17:26:58.000Z","updated_at":"2024-05-31T00:31:58.000Z","dependencies_parsed_at":"2023-01-16T06:43:47.995Z","dependency_job_id":null,"html_url":"https://github.com/bclindner/ivory","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/bclindner/ivory","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bclindner%2Fivory","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bclindner%2Fivory/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bclindner%2Fivory/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bclindner%2Fivory/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bclindner","download_url":"https://codeload.github.com/bclindner/ivory/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bclindner%2Fivory/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264502422,"owners_count":23618602,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automoderator","mastodon","python"],"created_at":"2024-08-14T10:02:30.953Z","updated_at":"2025-08-01T09:31:50.446Z","avatar_url":"https://github.com/bclindner.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# Ivory\n\nIvory is an automoderator and anti-spam tool for Mastodon admins. It automates\nhandling certain trivial reports and new user requests using *rules* -\nconfigurable tests that check reports and pending users for bad usernames,\nmalicious links, and more.\n\n## Installation Guide\n\nThis installation guide assumes you know your way around a Linux terminal,\nhave Python and Git installed, and maybe a little bit about common tech like\nPython and JSON formatting.\n\n### Installing\n\n\nIn a Linux terminal, the following commands will clone and install Ivory to\nwhichever folder you're in. Make sure you have Git and Python installed:\n\n```bash\ngit clone https://github.com/bclindner/ivory\ncd ivory\npython -m venv .\nsource bin/activate\npython -m pip install -r requirements.txt\n```\n\nThis repo also comes with a Dockerfile, so if you want to deploy with that, that\nworks too:\n\n```bash\ngit clone https://github.com/bclindner/ivory\ncd ivory\ndocker build -t ivory .\ndocker run -v /srv/ivory/ivory_config.json:/app/config.json ivory\n```\n\n### Configuration\n\nBefore starting Ivory, you need to create a new application in the Preferences\nmenu. Don't worry about setting redirect URIs or anything that isn't required -\njust make sure you enable all of the `admin:` scopes. Once you've created the\napplication, you'll want to grab its access token to place in the configuration\nfile (example below).\n\n*Be* ***EXTREMELY*** *careful with handling the access token this generates -\nthis key has a lot of power and in the wrong hands, this could mean someone\ncompletely obliterating your instance.*\n\nOnce you've done that, it's time to set up your config file. Configuring Ivory\nis done with JSON; a sample is below:\n\n```json\n{\n  \"token\": \"\u003cYOUR_ACCESS_TOKEN_HERE\u003e\",\n  \"instanceURL\": \"\u003cYOUR_INSTANCE_URL_HERE\u003e\",\n  \"waitTime\": 300,\n  \"reports\": {\n    \"rules\": [\n      {\n        \"name\": \"No spam links\",\n        \"type\": \"link_resolver\",\n        \"blocked\": [\"evilspam\\\\.website\", \"dontmarry\\\\.com\"],\n        \"severity\": 2,\n        \"punishment\": {\n          \"type\": \"suspend\",\n          \"message\": \"Your account has been suspended for spamming.\"\n        }\n      },\n      {\n        \"name\": \"No link-in-bio spammers\",\n        \"type\": \"bio_content\",\n        \"blocked\": [\"sexie.ru\"],\n        \"severity\": 1,\n        \"punishment\": {\n          \"type\": \"disable\",\n          \"message\": \"Your account has been disabled for spamming.\"\n        }\n      }\n    ]\n  },\n  \"pendingAccounts\": {\n    \"rules\": [\n      {\n        \"name\": \"No \u003ca\u003e tags\",\n        \"_comment\": \"Because honestly, you're definitely a bot if you're putting \u003ca\u003e tags into the field\",\n        \"type\": \"message_content\",\n        \"blocked\": [\"\u003ca href=\\\".*\\\"\u003e.*\u003c/a\u003e\"],\n        \"severity\": 1,\n        \"punishment\": {\n          \"type\": \"reject\"\n        }\n      },\n      {\n        \"name\": \"StopForumSpam test\",\n        \"type\": \"stopforumspam\",\n        \"threshold\": 95,\n        \"severity\": 1,\n        \"punishment\": {\n          \"type\": \"reject\"\n        }\n      }\n    ]\n  }\n}\n```\n\nA more [in-depth guide to Ivory configuration](https://github.com/bclindner/ivory/wiki/Getting-Started)\nand the list of [rules](https://github.com/bclindner/ivory/wiki/Rules) and\n[punishments](https://github.com/bclindner/ivory/wiki/Punishments)\ncan be found on the wiki.\n\nIdeally you only have to change this once in a blue moon, but if you do, you can\nuse the `\"dryRun\": true` option to prevent Ivory from taking action, so you can\ntest some rules on recent live moderation queues.\n\n### Running\n\nAfter you've set up a config file, run the following in a Linux terminal:\n\n```\n# if you're running in the same terminal session you installed from, you can\n# skip this next line:\nsource bin/activate\npython .\n```\n\nHopefully, no errors will be thrown and Ivory will start up and begin its first\nmoderation pass, reading the first page of active reports and pending users and\napplying your set rules. Ivory will handle these queues every 300 seconds,\nor 5 minutes. (This is controlled by the `waitTime` part of the above config\nfile - if you wanted 10 minutes, you could set it to 600!)\n\nIf you'd rather run it on some other schedule via a proper task scheduler like\ncron or a systemd .timer unit, you can use `python . oneshot` which will run\nIvory only once. This sample cron line will run Ivory every 5 minutes and output\nto a log file:\n\n```cron\n*/5 * * * * cd /absolute/path/to/ivory; ./bin/python . oneshot \u003e\u003e ivory.log\n```\n\n## Extending (custom rules)\n\nYou'll notice the `rules/` folder is a flat folder of Python scripts, one per\nIvory rule. If you've got a little Python experience, you can easily create your\nown rules by just dropping in a new Python file and using one of the other files\nin the folder as a jumping-off point.\n\nThe reports and pending accounts that Ivory rules receive are the same as what\nMastodon.py provides for\n[reports](https://mastodonpy.readthedocs.io/en/stable/#report-dicts) and [admin\naccounts](https://mastodonpy.readthedocs.io/en/stable/#admin-account-dicts),\nrespectively.\n\n**Don't forget to use `dryRun` in your config when testing your new rule!**\n\nOnce you've finished writing up your custom rule, say as\n`rules/filename_of_your_rule.py`, you can address it by its filename in your\nconfig:\n\n```json\n...\n\"pendingAccounts\": {\n  \"rules\": [\n    {\n      \"name\": \"An instance of my cool new rule\",\n      \"type\": \"filename_of_your_rule\",\n      \"custom_option\": true,\n      \"severity\": 5,\n      \"punishment\": {\n        \"type\": \"reject\"\n      }\n    },\n  ]\n}\n...\n```\n\nIf you come up with any useful rules and wouldn't mind writing a schema and some\ntests for it, making a pull request to include it in Ivory's main release would\nbe highly appreciated! The more rules Ivory gets, the more tools are\ncollectively available to other admins for dealing with spammers and other\nthreats to the Fediverse at large.\n\n## Bugs \u0026 Contributing\n\nIf you have any issues with Ivory not working as expected, please file a GitHub\nissue.\n\nContributions are also welcome - send in pull requests if you have anything new\nto add.\n\nIf you have any other questions, go ahead and [ping me on\nMastodon](https://mastodon.technology/@bclindner) and I might be able to answer\nthem.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbclindner%2Fivory","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbclindner%2Fivory","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbclindner%2Fivory/lists"}