Search code examples
javascriptnode.jstwitterforever

Node forever says program is running, but evidence suggests not


I'm attempting to run a simple twitterbot using the twit library and forever. The bot essentially watches another twitter account, and copies its tweets with some modifications.

I have been logging the tweets that are received from the streaming API, and also logging any errors.

However, the bot will often stop working, while logging no error, while forever list still shows the program as running.

info:    Forever processes running
data:        uid  command         script forever pid   id logfile                        uptime        
data:    [0] QOmt /usr/bin/nodejs bot.js 13642   13649    /home/ubuntu/.forever/QOmt.log 0:5:25:34.733

At first, I thought this could be a problem with the twitter streaming API - i.e., that information had stopped being sent "down the pipe", as the log files had ceased to record new messages from the twitter streaming API.

In order to verify that the bot was indeed still running, I created a heartbeat that logs every minute.

// Heartbeat to make sure the process is still running
setInterval(function () {
  console.logWithDate('Program heartbeat');
}, 60*1000);

When the bot stops functioning, the heartbeat also stops logging. Yet forever still says the bot is running.

Any help would be very much appreciated. I post the full code of my bot below in case it is useful.

#!/usr/bin/env node
"use strict"

const fs = require('fs');
const util = require('util');
const Twit = require('twit');

// Logging
const error_log_file = fs.createWriteStream(__dirname + '/debug.log', {flags : 'a'});
const log_stdout = process.stdout;

console.error = function(d) {
  error_log_file.write(util.format("\n%s> %s", new Date(), d) + '\n');
  log_stdout.write(util.format("\n%s> %s", new Date(), d) + '\n');
};

console.logWithDate = function(d) {
    log_stdout.write(util.format("\n%s> %s", new Date(), d) + '\n');
}

// Heartbeat to make sure the process is still running
setInterval(function () {
  console.logWithDate('Program heartbeat');
}, 60*1000);


// Read in twitter secrets file
const twitter_secrets = JSON.parse(fs.readFileSync("twitter_secrets.json"));


// Connect to twitter
const client = new Twit({
    consumer_key: twitter_secrets.TWITTER_CONSUMER_KEY,
    consumer_secret: twitter_secrets.TWITTER_CONSUMER_SECRET,
    access_token: twitter_secrets.TWITTER_ACCESS_TOKEN_KEY,
    access_token_secret: twitter_secrets.TWITTER_ACCESS_TOKEN_SECRET,
    timeouts_ms: 60*1000
});

// Main
const stream = client.stream('statuses/filter', {follow: 87818409});
stream.on('tweet', function(event) {
    if (event.user.id === 87818409) {
        console.logWithDate("Guardian tweet: " + event.text)
        client.post(
            'statuses/update',
            {status: misspellRandomWords(event.text)},
            function(error, tweet, response) {
                if (error) {
                    console.error(error);
                } else {
                    console.logWithDate("Bot tweet: " + tweet.text);  // Tweet body.
                    //console.log(response);  // Raw response object.
                }
            }
        );
    } else {
        console.logWithDate("Guardian-related tweet: " + event.text)
    }
});

// Log various types of messages for debugging
stream.on('limit', function(error) {
    console.error(error);
});

stream.on('disconnect', function(error) {
    console.error(error);
});

stream.on('error', function(error) {
    console.error(error);
});

stream.on('connect', function (conn) {
  console.logWithDate('connecting')
})

stream.on('reconnect', function (reconn, res, interval) {
  console.logWithDate('reconnecting. statusCode:', res.statusCode)
})


/* Helper functions */

function swapRandomLetters(word) {
    const limit = word.length;
    const iFirstLetter = Math.floor(Math.random() * limit);
    var iSecondLetter = Math.floor(Math.random() * limit);

    while (iFirstLetter === iSecondLetter) {
        iSecondLetter = Math.floor(Math.random() * limit);
    }

    let letters = word.split("");
    letters[iFirstLetter] = word[iSecondLetter];
    letters[iSecondLetter] = word[iFirstLetter];
    return letters.join("");
}

function isLink(word) {
    // Very crude URL check
    return word.substring(0,4) === "http";
}

function misspellRandomWords(sentence) {
    let words = sentence.split(" ");
    const limit = words.length;

    // Choose a first word, filtering out urls
    var iFirstWord = Math.floor(Math.random() * limit);
    while (isLink(words[iFirstWord]) || words[iFirstWord][0] === "@" ) {
        iFirstWord = Math.floor(Math.random() * limit);
    }

    // Choose second misspelled word, and make sure it isn't the first or an URL
    var iSecondWord = Math.floor(Math.random() * limit);
    while (isLink(words[iSecondWord]) ||
            iSecondWord === iFirstWord ||
            words[iSecondWord][0] === "@") {
        iSecondWord = Math.floor(Math.random() * limit);
    }

    words[iFirstWord] = swapRandomLetters(words[iFirstWord]);
    words[iSecondWord] = swapRandomLetters(words[iSecondWord]);

    return words.join(" ");
}

Solution

  • If all the words of a tweet start with @, you have an infinite loop, thus blocking the main thread.