peteris.rocks

Messenger PhantomJS

Send messages on Facebook Messenger as yourself with PhantomJS or facebook-chat-api

Last updated on

Messenger Platform

It looks like there is an official API for chat bots called the Messenger Platform.

But it appears that you need to create a new Facebook page for your bot but I need it to look like the messages come from me.

I can't use this.

PhantomJS

If all you have is a hammer, everything looks like a nail.

In my last post, I wrote about using PhantomJS, a headless browser, to configure websites (like deactivating all plugins during a WordPress installation and then upgrading the blog to a network).

So my first thought was to try it for sending Messenger messages as well.

First, some ground work.

var system = require('system')
var page = require('webpage').create()

var email = system.args[1]
var pass = system.args[2]
var conversation = system.args[3]
var text = system.args[4]

var steps = [] // array of functions

You will need to provide your email as well as your password to log in.

conversation is the conversation (or thread) ID that you can see in the URL in your browser (e.g. https://www.messenger.com/t/firstname.lastname). It is usually firstname.lastname or sometimes john.smith.99.

Let's go to messenger.com

function() {
  console.log('Opening messenger.com')
  page.open('https://www.messenger.com/')
}

and log in

function() {
  console.log('Logging in')
  page.evaluate(function(email, pass) {
    document.querySelector('input[name=email]').value = email
    document.querySelector('input[name=pass]').value = pass
    document.querySelector('#loginbutton').click()
  }, email, pass)
},

What I did was right click on the input fields in Google Chrome, click Inspect and look at the <input> elements for email and password.

Then I fill them out programmatically with JavaScript.

I can verify that it works by copy & pasting the document.querySelector code lines in the Google Chrome console.

If I can do that in the Google Chrome console, I can do that in PhantomJS as well.

Everything in page.evaluate(function() { ... }) is executed on the currently open web page. Since email and pass are my variables, they do not exist on messenger.com so I need to pass them as parameters. You can also return a value from page.evaluate.

Next, go to the conversation.

function() {
  page.open('https://www.messenger.com/t/' + conversation)
},

function() {
  console.log('Talking to', page.evaluate(function() {
    return document.querySelector('div[role="main"] h2').innerText
  }))
}

Finally, send the message.

function() {
  text.split('\n').forEach(function(line) {
    page.sendEvent('keypress', line)
    page.sendEvent('keypress', page.event.key.Enter, null, null, 0x02000000 /* shift */)
  })
  page.sendEvent('keypress', page.event.key.Enter)
}

I could not figure out how to fake the keyup or paste event (messenger.com uses React and a custom component instead of an <input>). But the input text box is focused when you open the page and I can just simulate the key presses.

There is no \n key so I need to press Shift+Enter to start a new line with the keyboard.

Here is the whole script with some code at the end to execute the array of functions.

var system = require('system')
var page = require('webpage').create()

var email = system.args[1]
var pass = system.args[2]
var conversation = system.args[3]
var text = system.args[4]

var steps = [
  function() {
    console.log('Opening messenger.com')
    page.open('https://www.messenger.com/')
  },
  function() {
    console.log('Logging in')
    page.evaluate(function(email, pass) {
      document.querySelector('input[name=email]').value = email
      document.querySelector('input[name=pass]').value = pass
      document.querySelector('#loginbutton').click()
    }, email, pass)
  },
  function() {
    console.log(page.evaluate(function() {
      return document.querySelector('div[role="banner"] a[href="/new"]')
        ? "Logged in" : "Could not log in"
    }))
  },
  function() {
    page.open('https://www.messenger.com/t/' + conversation)
  },
  function() {
    console.log('Talking to', page.evaluate(function() {
      return document.querySelector('div[role="main"] h2').innerText
    }))
  },
  function() {
    text.split('\n').forEach(function(line) {
      page.sendEvent('keypress', line)
      page.sendEvent('keypress', page.event.key.Enter, null, null, 0x02000000 /* shift */)
    })
    page.sendEvent('keypress', page.event.key.Enter)
  },
  function() {
    page.evaluate(function() {
      console.log('Done')
    })
  },
  function() {
    setTimeout(function() { phantom.exit() }, 2000)
  }
]

var stepindex = 0
var loading = false
setInterval(executeRequestsStepByStep, 50)

function executeRequestsStepByStep() {
  if (loading == false && steps[stepindex]) {
    steps[stepindex]()
    stepindex++
  }
}

page.onLoadStarted = function() { loading = true }
page.onLoadFinished = function() { loading = false }
page.onConsoleMessage = function(msg) { console.log(msg) }

Run it

$ phantomjs messenger.js your@email.com password conversation.name "your message here"
Opening messenger.com
Logging in
Logged in
Talking to John Smith
Done

If you don't have PhantomJS installed already, this will download and extract a static binary phantomjs to /usr/local/bin.

curl -L https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 | \
  sudo tar xj -C /usr/local/bin phantomjs-2.1.1-linux-x86_64/bin/phantomjs --strip-components 2

PhantomJS also works on Windows.

facebook-chat-api

But there's more than one way to skin a cat.

Another way would be to write a script that makes the same HTTP requests that the Messenger client does.

But I had a suspicion that there would be a node.js module for this already. Let's try to use facebook-chat-api.

npm install facebook-chat-api

The script

var login = require('facebook-chat-api')

var email = process.argv[2]
var pass = process.argv[3]
var conversation = process.argv[4]
var text = process.argv[5]

login({ email: email, password: pass}, (err, api) => {
  if (err) throw err

  api.getFriendsList((err, profiles) => {
    if (err) throw err

    var friendProfile = profiles.filter(p => p.vanity == conversation)[0]
    if (friendProfile) {
      api.sendMessage({ body: text }, friendProfile.userID)
    } else {
      throw 'Friend not found'
    }
  })
})

Run it

$ node fchat.js your@email.com password conversation.name "your message here"
info Logging in...
info Logged in
info Request to reconnect
info Request to pull 1
info Request to pull 2
info Request to thread_sync
info Done logging in.

Creating an echo bot is just as simple.

This example is taken from the docs. I modified it for brevity.

var login = require('facebook-chat-api')

var email = process.argv[2]
var pass = process.argv[3]

login({ email: email, password: pass }, (err, api) => {
  if (err) throw err

  api.listen((err, event, stopListening) => {
    if (err) return console.error(err)

    if (event.type === 'message') {
      api.sendMessage(event.body, event.threadID)
    }
  })
})

Final remarks

I should point out that doing this is probably against the Terms of Service.

Nevertheless, it was fun playing with PhantomJS.

While the facebook-chat-api code is much shorter and there are other people maintaining the underlying code, if I had to write a quick one-off script like this, I would probably use PhantomJS since it took me very little time to script what I did in the browser.