Fediverse to anything reposter // by @hatkidchan https://gitlab.vapronva.pw/vapronva/mastoposter-oss_images
Go to file
Vladimir (vapronva) 5a519505fa
add: some variables were missing for container analysis
2022-09-30 05:00:41 +03:00
mastoposter Added proper README 2022-09-23 19:28:08 +03:00
.deepsource.toml add: basic ci docker image building, deepsource sast 2022-09-30 04:08:24 +03:00
.dockerignore add: `dockerignore`; change: df layers handling (🤓) 2022-09-30 02:23:18 +03:00
.gitignore Pleroma: fallback value in account.discoverable 2022-08-30 13:25:05 +03:00
.gitlab-ci.yml add: some variables were missing for container analysis 2022-09-30 05:00:41 +03:00
Dockerfile add: `dockerignore`; change: df layers handling (🤓) 2022-09-30 02:23:18 +03:00
README.md Added proper README 2022-09-23 19:28:08 +03:00
TODO Added polls and reconnect. Closes #1 & #7, I hope- 2022-08-26 18:37:36 +03:00
config.ini Added note that Pleroma is not properly supported 2022-09-18 16:55:13 +03:00
requirements.txt Custom formatting? 2022-08-31 16:19:39 +03:00


mastoposter - easy-to-use mastodon-to-[everything] reposter!

Mastoposter is a simple zero-headache* service that forwards your toots from any Mastodon-compatible Fediverse software (Pleroma also works!) to any of your other services! For now it supports only Discord webhooks and Telegram, but it can be easily extended to support pretty much anything!


You can run it either on your host machine, or inside a Docker container. In any case, you have to clone that repo first in order to do anything:

git clone https://github.com/hatkidchan/mastoposter && cd mastoposter

After that, you can either run it in Docker, set up a standalone systemd service, or just run it as it is!


docker build -t mastoposter .
docker run --restart=always -dv /path/to/config.ini:/config.ini:ro --name mastoposter mastoposter

And you should be good to go


Let's say that you've cloned that repo to the $MASTOPOSTER_ROOT, then configuration should look something like that:

Description=Crossposter from Mastodon

ExecStart=/usr/bin/python3 -m mastoposter config.ini


Before running it though, don't forget to install dependencies from the ./requirements.txt, but it's a good idea to use a virtual environment for that. Though, that's outside of the scope of that, so I won't cover it here.

Running manually

Just be in the folder with it, have dependencies installed and run: python3 -m mastoposter config.ini


Configuration file is just a regular INI file with a couple sections.


Section main contains settings of your account (ie, your instance, list ID, user ID, access token), as well as list of modules to load.


This is your instance. It should be written without the https:// part, so, for example, mastodon.social.


This is your access token.

On Mastodon, you can acquire it by creating an application with the minimum of read:statuses and read:lists permissions.

On Pleroma you're out of luck and have to manually lure your token out of the frontend you're using. For example, in Pleroma FE you can look in the "Network" tab of the devtools and look for chats request. Inside the request headers, there should be Authorization: Bearer XXXXXXXXXXX header. That's your token.


It's still not properly tested, but you could just leave it as auto for now.

In case it fails, on Mastodon you can get your user ID by looking at your profile picture URL. The part between "/avatars/" and "original/" without all of the slashes is your user ID.

On Pleroma you're out of luck again, I don't remember how I got mine. Just hope that "auto" will work, lol.


That's the main problem of this crossposter: it requires a list to be created to function properly. Both Pleroma and Mastodon support them, so it shouldn't be a big deal. Just create a list, add yourself into it and copy its ID (it should be in the address bar).

List is required to filter incoming events. You can't just listen for home timeline 'cause some events are not guaranteed to be there (boosts at least).


You can set it to either yes or no. When set to yes, it will reconnect on any websocket error, but not on any error related to modules (even if it's a connection error!!!)


More about them later


There's two types of modules supported at this point: telegram and discord. Both of them are self-explanatory, but we'll go over them real quick.

Each module should contain at least type property and its name should start with the module/. filters field is also can be specified. Check the corresponding section to learn more about them.

To use module, add it to the modules field in the main section. It should not have the module/ prefix since it's always there. You can use multiple modules and separate them using spaces.

type = telegram

Module with that type will work in Telegram mode. It requires your Bot token to be set in the token field, as well as chat to be set with your chat ID. You can use @username if the chat is public. Also there's a silent field, when it's set to true, it'll set disable_notification flag on every post sent.

template field contains your template for the message. It's pretty much Jinja2 template. Since we use parse_mode=html, your template should be formatted appropriately. Template itself has only status variable exposed, which contains the status/post/toot itself. There's also some handy properties such as reblog_or_status which points to either reblog, or status itself. Or name_emojiless which contains the name without emojis. Or name which contains either display_name or username, if first one is empty.

type = discord

Module for Discord webhooks. The only required parameter (besides the type) is webhook. It should have wait=true set. You can also use thread_id as a GET parameter to that. You also can use filters, nothing special about that.


Filters are the most powerful feature of this crossposter. They allow you to...

Filter out where posts should and shouldn't go! It's that easy!

There's a couple of filters with different types and options, but all of them should be contained in sections with names starting with filter/, as well as have a type field with filter type.

Also, you can specify multiple filters and they'll be chained together using AND operator. You can also prefix filter name with either ~ or ! to invert its behavior.

type = boost

Simple filter that passes through posts that are boosted from someone.

It also has an optional list property where you can specify the list of accounts to check from. You can use globbing, but be aware, that it uses fnmatch function to glob stuff, so @* doesn't mean "any local user", but rather it means "any user". NOTE that his behavior is not intended and may be changed to more appropriate one later. If list is empty, any boost will trigger that filter. If list is not empty, it will allow only users from that list.

type = mention

This filter is kinda similar to the boost one, but works with mentions. Also has list property, yada yada you got the idea, same deal with fnmatch.

type = spoiler

Matches posts with spoilers/content-warnings.

Has an optional regexp parameter that will allow you to specify regular expression to match your spoiler text.

type = content

Filter to match post content against either a regular expression, or a list of tags. Matching is done on the plaintext version of the post.

mode property determines the type of operation. Can be either regexp, tag or hashtag (two last ones are the same).

In mode = regexp you have to specify the regexp option with a regular expression to match against.

In mode = tag or mode = hashtag, tags option should contain a space-separated list of tags. If any of the tags are present in that list, filter will be triggered.

type = visibility

Simple filter that just checks for post visibility. Has a single property options that is a space-separated list of allowed visibility levels. Note that direct visibility is always ignored so cannot be used here.

type = combined

The most powerful filter 'cause it allows you to combine multiple filters using different operations.

filters option should contain space-separated list of filters. You also can negate them using ! or ~ prefixes.

operator is a type of operation to be used. Can be either all, any or single. all means that all of the filters should be used. any means that if any filter is triggered, this one will also trigger. single means that only one filter should be triggered. Think of it as an XOR operation of some sort.

Sample configurations

For Telegram:

modules = tg
instance = expired.mentality.rip
token = haha-no
list = 42
user = auto

type = telegram
token = 12345:blahblahblah
chat = 12345

For Telegram with a separate shitpost channel:

modules = tg tg-shitpost
instance = expired.mentality.rip
token = haha-no
list = 42
user = auto

type = telegram
token = 12345:blahblahblah
chat = 12345
filters = !shitpost

type = telegram
token = 12345:blahblahblah
chat = @shitposting
filters = shitpost

type = content
mode = tag
tags = shitpost