diff --git a/README.md b/README.md index 5587fe8..34cb75e 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,15 @@ The bot is currently a big dangle-on script running in your browser. **Run `book If you wish, you can also run it headlessly on top of phantomjs and node: -* Install [phantomjs 2](http://phantomjs.org/) (yes, it has to be 2 and above). Differs from platform to platform. -* Install nightmare: `npm install nightmare` -* Edit your credentials into `run-headless.js` -* Hit the road: `env DEBUG=nightmare node run-headless.js` +1. Install [phantomjs 2](http://phantomjs.org/) (yes, it has to be 2 and above). Differs from platform to platform. +2. Install webdriverio: `npm install webdriverio@3.4.0` (4.x is broken) +3. Copy `run-headless.config.json.SAMPLE` into `run-headless.config.json` (update if necessary), and edit your credentials into it. +4. Hit the road: + + ```sh +screen -mdS pjs-server phantomjs --webdriver=4444 +screen -mdS bot node run-headless-webdriver.js +``` ###Building### diff --git a/package.json b/package.json index 097c102..9ca8c27 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,9 @@ "name": "so-chatbot", "version": "0.2.0", "description": "A chatbot for StackOverflow.", - "main": "run-headless.js", + "main": "run-headless-webdriver.js", "dependencies": { - "nightmare": "^1.8.1" - }, + "webdriverio": "3.4.0" "devDependencies": { "browserify": "^13.0.0", "uglify-js": "^2.4.15" diff --git a/run-headless-webdriver.js b/run-headless-webdriver.js new file mode 100644 index 0000000..f5def7b --- /dev/null +++ b/run-headless-webdriver.js @@ -0,0 +1,146 @@ +var cleanupAndExit = function(exitCode) { + var exit = function() { + console.log('Exiting.'); + process.exit(exitCode); + }; + + console.log('Cleaning up...'); + setTimeout(function() { + console.warn('Failed to clean up in 5000ms. Some browser instances may remain.'); + exit(); + }, 5000); + if (client) { + if (dumpBrowserConsoleLoop) { + clearInterval(dumpBrowserConsoleLoop); + } + client.end().then(function() { + console.log('Cleaned up.'); + exit(); + }).catch(function(err) { + console.error(err.name, err.message); + console.warn('Cleanup failed. Some browser instances may remain.'); + exit(); + }); + } else { + exit(); + } +}; + +var signals = { + 'SIGHUP': 1, + 'SIGINT': 2, + 'SIGTERM': 15, + 'SIGBREAK': 21 +}; +for (var signal in signals) { + process.on(signal, function() { + console.log(signal + ' caught'); + cleanupAndExit(128 + signals[signal]); // posix signal 128 + signal number + }); +} +process.on('uncaughtException', function(err) { + console.error(err); + cleanupAndExit(1); +}); + +var config = require('./run-headless.config.json'); + +var webdriverio = require('webdriverio'); + +var client = webdriverio.remote(config.driverOptions); + +var logLevels = { + 'SEVERE': console.error, + 'WARNING': console.warn, + 'INFO': console.info, + 'DEBUG': console.debug +}; +var dumpBrowserConsole = function(limit) { + client.log('browser').then(function(logs) { + (limit ? logs.value.slice(-limit) : logs.value).forEach(function(log) { + (logLevels[log.value] || console.log).call(console, log.message); + }); + }); +}; +var dumpBrowserConsoleLoop = null; +// phantomjs does not clear browser logs correctly +// dumping in a loop floods console cause they repeat +//var dumpBrowserConsoleLoop = setInterval(dumpBrowserConsole, 5000); + +client + .init() + .url(config.loginUrl) + .getUrl() + .then(function(url) { + console.log('Loaded ' + url); + }) + .getText('=log in') + .then(function() { + console.log('Logging in...'); + return client + .execute(function() { + openid.signin('stack_exchange'); + }) + .waitForExist('#affiliate-signin-iframe', 10000) + .frame('affiliate-signin-iframe') + .waitForExist('#email', 10000) + .setValue('#email', config.email) + .setValue('#password', config.password) + .submitForm('.login-form form') + .frame() + .getUrl() + .then(function(url) { + console.log('Login submitted; loaded ' + url); + }); + }, function(err) { + console.log('Already logged in; skipping login'); + }) + .url(config.roomUrl) + .getUrl() + .then(function(url) { + console.log('Loaded chatroom ' + url); + }) + .execute(function(scriptUrl) { + var script = document.createElement('script'); + script.src = scriptUrl; + script.onload = function() { + //bot.activateDevMode(); + console.log('Loaded bot'); + bot.adapter.out.add('I have just been restarted! This happens daily automatically, or when my owner restarts me. Ready for commands.'); + }; + document.head.appendChild(script); + }, config.scriptUrl) + .then(function() { + console.log('Chatbot injected'); + }) + .then(function() { + var readline = require('readline'); + var repl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + repl.on('SIGINT', function() { + process.emit('SIGINT'); + }); + repl.setPrompt('> '); + + console.log('You are now in a REPL with the remote page. Have fun!'); + + repl.on('line', function(data) { + console.log('Attempting to run "' + data + '"'); + client.executeAsync(function(code) { + return eval(code); + }, data).then(function(ret) { + console.log('$', ret.value); + }).catch(function(err) { + console.log('!', err.name, err.message); + }).finally(function() { + // because phantomjs does not clear browser logs correctly + // we only display the last 5 to avoid flooding + dumpBrowserConsole(5); + repl.prompt(); + }); + }); + + repl.prompt(); + }); \ No newline at end of file diff --git a/run-headless.config.json.SAMPLE b/run-headless.config.json.SAMPLE index 46773e1..7548278 100644 --- a/run-headless.config.json.SAMPLE +++ b/run-headless.config.json.SAMPLE @@ -1,7 +1,16 @@ { - "email": "you can guess", - "password": "what these are", + "email": "you can guess", + "password": "what these are", - "siteUrl": "https://stackoverflow.com", - "roomUrl": "https://chat.stackoverflow.com/rooms/1" + "siteUrl": "https://stackoverflow.com", + "roomUrl": "https://chat.stackoverflow.com/rooms/1", + "loginUrl": "https://stackexchange.com/users/login", + + "scriptUrl": "http://localhost/master.js", + + "driverOptions": { + "desiredCapabilities": { + "browserName": "phantomjs" + } + } } \ No newline at end of file