Pavel Nakonechnyy

How to make simple Mastodon bot

Published by Pavel Nakonechnyy on in Web development.

Today we’re going to write simple Twitter-Mastodon exporter, which listens to tweets from one or several accounts and then publishes new tweets to Mastodon page.

Project initialization

Firstly, make new npm project:

npm init
npm i --save twitter mastodon

I’ve decided to make this project in TS, so here’s our tsconfig.json:

{

    "compilerOptions": {

        "module": "commonjs",

        "noImplicitAny": true,

        "removeComments": true,

        "preserveConstEnums": true,

        "sourceMap": true,

        "outDir": "./bin"

    },

    "include": [

        "src/**/*"

    ],

    "exclude": [

        "node_modules",

        "**/*.spec.ts"

    ]

}

Now create ./src folder and main script in it. Let’s name it server.ts for example. After that edit your scripts in package.json

"scripts": {
    "start": "tsc && node bin/server.js"
  }

That’s all with project initialization. Let’s make some code.

Code

Firstly, we need to connect to Mastodon. Register account on Mastodon. In settings find “App”, create new app and copy your token.

Past it insted *** in code below

var Masto = require('mastodon')

var M = new Masto({
    access_token: '***',
    timeout_ms: 60 * 1000, // optional HTTP request timeout to apply to all requests.
    api_url: 'https://botsin.space/api/v1/', // optional, defaults to https://mastodon.social/api/v1/
})

Then we need to auth to Twitter to retrieve posts from there. Create Twitter app and copy 4 tokens from there

Paste them top-down in the code

var Twitter = require('twitter');

var client = new Twitter({
    consumer_key: '***',
    consumer_secret: '*****',
    access_token_key: "1312442-***",
    access_token_secret: "******"
});

Also we will need to save already posted tweets somewhere not to post them twice. Let’s do this in json file nearby.

var fs = require('fs');
var posted = JSON.parse(fs.readFileSync('posted.json', 'utf8')) as string[];

console.log("{Loaded " + posted.length + " collected tweets}");

After all preparations done, let’s collect some tweets

function collecttweets(source: string) {
    console.log("[" + new Date().toString() + "] Checked tweets");

    client.get('statuses/user_timeline', {
        screen_name: source,
        exclude_replies: "true",
        include_rts: "false",
        count: 50
    }, (error: boolean, tweets: any[], response: any) => {
        if (!error) {
            processtweets(tweets);
        }
        else {
            console.log(error);
        }
    });
}

We want it to be done regularly, so add some more code to the end of the file:

setInterval(madecollection, 1000 * 60 * 5);

Now we will make actual madecollection function, where we will collect tweets from different sources.

function madecollection() {
    var sources = ["unity3d", "madewithunity"];
    var rand = Math.floor(Math.random() * sources.length);
    collecttweets(sources[rand]);
}

This function will collect tweets from random twitter account in the list. As they tweet not so often, we will actually collect all the tweets within 1-20 minutes from it’s post date.

And now to the fun part – retweeting them to Mastodon account. We filter tweet not to be Retweet and not to start with mention. Also it shouldn’t been posted before. After 1 successful publication we stop processing longer to limit our rate to 1 post per 10 minutes at most.

function processtweets(tweets: any[]) {
    for (var i = 0; i < tweets.length; i++) {
        let tweet = tweets[i];

        if (posted.lastIndexOf(tweet.id) == -1 && tweet.text.indexOf("RT") == -1 && !tweet.text.startsWith("@")) {
            M.post('statuses', {
                status: tweet.text
            });
            posted.push(tweet.id);
            console.log("[" + new Date().toString() + "] Posted tweet: " + tweet.text);

            var json = JSON.stringify(posted);
            fs.writeFileSync("posted.json", json);
            break;
        }
    }
}

In the end, we should have this piece of code.

567