├── README.md ├── config.sample.js └── index.js /README.md: -------------------------------------------------------------------------------- 1 | # What is it? 2 | Zuck is a wrapper for Facebook Messenger. It can be used to build a bot on top of Facebook Messenger for a personal account. 3 | 4 | Code is not pretty, and so is this "documentation" (? ;-) ), I made this hack for my own needs and would be very happy to maintain it and make it cleaner if I see a raising interest. 5 | 6 | ![screenshot](http://i.imgur.com/yQTNXlW.png "Demo Screenshot") 7 | 8 | # How does it work? 9 | Facebook doesn't provide an API for Messenger and reverse engineering the whole thing constantly would be so annoying for me. This is dirty but it works and it's all I need atm. Zuck uses a headless browser to log into your Fb account, then listen and reply to your messages using the function you defined. 10 | 11 | # Requirements 12 | Tested on Ubuntu 14.04 13 | - `sudo npm install -g casperjs slimerjs` 14 | - `sudo apt-get install xvfb firefox` 15 | - `Xvfb :99 &` 16 | - `export DISPLAY=:99` 17 | 18 | # Get started 19 | Make sure to read the requirements before starting. 20 | 21 | - `git clone https://github.com/louisondumont/zuck` 22 | - edit config.sample.js 23 | - `mv config.sample.js config.js` 24 | - `sudo casperjs index.js --engine=slimerjs` 25 | 26 | # Customize 27 | You'll find everything you need in index.js, you can customize the `answer` function. 28 | 29 | 30 | -------------------------------------------------------------------------------- /config.sample.js: -------------------------------------------------------------------------------- 1 | // Nothing is sent anywhere, check the source code if you don't trust me. This is only used in index.js by the headless browser to log into your account, from your machine. 2 | 3 | module.exports = { 4 | userId: "your_uid", 5 | email: "your_email", 6 | pass: "your_pass" }; 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var config = require('./config.js'), 2 | utils = require('utils'); 3 | 4 | /*! 5 | * globals 6 | */ 7 | 8 | var _dyn= ''; 9 | var cookies = ''; 10 | 11 | /*! 12 | * Main Casper 13 | * Logs into Facebook and stays there forever, listening for new messages 14 | */ 15 | 16 | 17 | var casper = require('casper').create({ 18 | verbose:true, 19 | logLevel:"debug", 20 | pageSettings: { 21 | loadImages: false, 22 | loadPlugins: false, 23 | captureContent: [ /.*/ ], // should be optimized 24 | userAgent: 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36' 25 | } 26 | }); 27 | // Open Facebook 28 | casper.start().thenOpen("https://facebook.com", function() { 29 | console.dir("Facebook website opened"); 30 | }); 31 | casper.page.captureContent = [/application.*/] 32 | // Populate username and password, and submit the form 33 | casper.then(function(){ 34 | console.log("Login using username and password"); 35 | this.evaluate(function(email, pass){ 36 | document.getElementById("email").value=email; 37 | document.getElementById("pass").value=pass; 38 | document.getElementById("loginbutton").children[0].click(); 39 | }, config.email, config.pass); 40 | }); 41 | 42 | // Wait forever 43 | casper.then(function(){ 44 | //console.log('COOKIES'); 45 | //console.log(phantom.cookies) 46 | console.log('Now listening.'); 47 | 48 | // Wait at this stage 10k days. Should find "forever" function. 49 | this.wait(864000000000); 50 | }); 51 | 52 | /*! 53 | * Spy Casper 54 | * Send message to myself every day to extract from "UI requested" msg send request dynamic variables and use them in our manually generated send_msg queries 55 | */ 56 | 57 | var casperSpy = require('casper').create({ 58 | verbose:true, logLevel:"debug", 59 | pageSettings: { 60 | loadImages: false, 61 | loadPlugins: false, 62 | captureContent: [ /.*/ ], 63 | userAgent: 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36' 64 | } 65 | }); 66 | 67 | // Init, could probably be sexier 68 | casperSpy.start().thenOpen("https://www.google.com", function() { 69 | this.wait(10000) 70 | }); 71 | 72 | // Send msg loop. will check how to do it better later 73 | var numTimes = 10000, count = 1; 74 | 75 | casperSpy.repeat(numTimes, function() { 76 | 77 | // loop 78 | this.thenEvaluate(function(count) { 79 | nextPage(count); 80 | }, ++count); 81 | 82 | // open "message myself" page 83 | this.thenOpen("https://www.facebook.com/messages/" + config.userId, function() { 84 | 85 | console.log("Message myself"); 86 | 87 | // actually message myself 88 | this.evaluate(function(){ 89 | document.getElementsByName("message_body")[0].value="test"; 90 | document.getElementsByClassName("uiButtonConfirm")[0].children[0].click(); 91 | }); 92 | }); 93 | 94 | // stop until tomorrow 95 | this.then(function() { 96 | this.wait(86400000); // erry day 97 | }); 98 | }); 99 | 100 | casperSpy.run(); 101 | 102 | // listen to send message request 103 | casperSpy.options.onResourceRequested = function(C, requestData, request) { 104 | 105 | if(requestData.url.indexOf('send') > 0) { 106 | 107 | // update globals 108 | cookies = requestData.headers[9].value; 109 | _dyn = requestData.postData.substring(requestData.postData.indexOf('&__dyn=')) 110 | } 111 | }; 112 | 113 | function send(msg, to, casp) { 114 | 115 | // dirty too, we could inject the function client side instead. 116 | casp.evaluate(function(_dyn, uid, msg, to) { 117 | var dyn = _dyn; 118 | function send(msg, to) { 119 | var timestamp = Date.now(); 120 | var messageId = Math.floor(1000000000000000000 + Math.random() * 7000000000000000000); // they seem to not like big numbers 121 | 122 | var dataString = 'client=mercury&action_type=ma-type%3Auser-generated-message&body=' + msg + '&ephemeral_ttl_mode=0&has_attachment=false&message_id=' + messageId + '&offline_threading_id=' + messageId + '&other_user_fbid=' + to + '&source=source%3Amessenger%3Aweb&specific_to_list[0]=fbid%3A' + to + '&specific_to_list[1]=fbid%3A' + uid + '×tamp=' + timestamp + '&__user=' + uid + '&__a=1' + dyn + '&__req=h&__be=-1&__pc=PHASED%3Amessengerdotcom_pkg'; 123 | var url = "https://www.facebook.com/messaging/send/?dpr=1"; 124 | 125 | var http = new XMLHttpRequest(); 126 | var params = dataString; 127 | http.open("POST", url, true); 128 | http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 129 | http.send(params); 130 | } 131 | send(msg, to); 132 | }, _dyn, config.userId, msg, to); 133 | } 134 | 135 | // Listen to incoming messages 136 | casper.options.onResourceReceived = function(C, response) { 137 | if(response.url.indexOf('facebook.com/pull?channel=') > 0) { 138 | utils.dump(response.body); 139 | if(response.body.indexOf('for (;;);') >= 0) { 140 | var obj = JSON.parse(response.body.substring(10)); 141 | if(obj.t === 'msg' && obj.ms && obj.ms[0] && obj.ms[0].delta && obj.ms[0].delta.body) { 142 | var body = obj.ms[0].delta.body; 143 | var author = obj.ms[0].delta.messageMetadata.actorFbId; 144 | console.log('Received "' + body + '" from ' + author); 145 | 146 | // send answer 147 | if(author !== config.userId) send(answer(body), author, this); 148 | // messages I send in a conv to someone are sent from... me. In this case, reply not to me, but to my friend, if I mentionned @majordome only. 149 | else { 150 | if(body.toLowerCase().indexOf('@zuck') !== -1) { 151 | send(answer(body), obj.ms[0].delta.messageMetadata.threadKey.otherUserFbId, this); 152 | } 153 | } 154 | } 155 | } 156 | } 157 | }; 158 | 159 | casper.run(); 160 | 161 | // this is where you plug your AI or whatever 162 | function answer(q) { 163 | q = q.toLowerCase(); 164 | var answer = ''; 165 | if(q === 'hi' || q === 'hello' || q === 'yo' || q === 'hey' || q === 'salut' || q === 'bonjour') 166 | answer += "Hi." 167 | if(q === ':)') 168 | answer += ':D' 169 | if(q === '❤' || q === '<3' || q === '@zuck ❤️') 170 | answer += '<3' 171 | if(q.indexOf('how are you') !== -1) 172 | answer += "I'm doing great. "; 173 | if(q.indexOf('love you') !== -1) 174 | answer += "Me too, so much."; 175 | if(q.indexOf('good night') !== -1) 176 | answer += "Sweet dreams. "; 177 | if(q.indexOf('buy bitcoin') !== -1) { 178 | answer += "Use Coinbase. :)"; 179 | } 180 | if(answer === '') 181 | answer += "I can't reply to that. Wait until my creator comes back. - Zuck"; 182 | return answer; 183 | } 184 | --------------------------------------------------------------------------------