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 [email protected] 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 [email protected] 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.