├── .editorconfig ├── Procfile ├── README.md ├── package.json ├── public ├── app │ ├── console-style.js │ ├── index.js │ └── utils │ │ └── check-image-exists.js ├── config.js └── index.html └── server ├── config.js ├── index.js ├── manifest.js └── plugins ├── ConsoleChat └── index.js ├── SlackBot └── index.js └── StaticFiles └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | [*.json] 13 | indent_size = 2 14 | [config.js] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node server 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ConsoleBot 2 | Allows savvy developers to chat to a website owner via the browser console. 3 | 4 | ### Setup 5 | 1. Fork/Clone this repo locally 6 | 2. `npm install && jspm install` 7 | 3. **Localy**: Edit the `./server/config.js` file and add in your Slack Token and Slack Channel you want to watch. 8 | **Deploying**: You need to set 3 environment variables (`NODE_ENV=production`, `SLACK_TOKEN=`, and `SLACK_CHANNEL=`). 9 | 4. And then run `node server` to start everything up. 10 | 11 | #### Original idea by: [@metakungfu](https://twitter.com/metakungfu) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "console-bot", 3 | "version": "1.0.0", 4 | "description": "Allows savvy developers to chat to a website owner via the browser console.", 5 | "main": "server/index.js", 6 | "scripts": { 7 | "start": "foreman start web", 8 | "postinstall": "jspm install" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git@github.com:meenie/console-bot.git" 13 | }, 14 | "author": "Cody Lundquist", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/meenie/console-bot/issues" 18 | }, 19 | "homepage": "https://github.com/meenie/console-bot", 20 | "jspm": { 21 | "directories": { 22 | "baseURL": "public", 23 | "lib": "public", 24 | "packages": "public/jspm_packages" 25 | }, 26 | "dependencies": { 27 | "Automattic/socket.io-client": "github:Automattic/socket.io-client@^1.3.5", 28 | "q": "npm:q@^2.0.3" 29 | } 30 | }, 31 | "devDependencies": { 32 | "nodemon": "^1.3.7" 33 | }, 34 | "dependencies": { 35 | "confidence": "^1.0.0", 36 | "glue": "^2.0.0", 37 | "good": "^5.1.2", 38 | "good-console": "^4.1.0", 39 | "hapi": "^8.4.0", 40 | "hapio": "^1.1.0", 41 | "hoek": "^2.11.1", 42 | "joi": "^6.0.8", 43 | "jspm": "^0.14.0", 44 | "slack-client": "^1.4.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /public/app/console-style.js: -------------------------------------------------------------------------------- 1 | import Q from 'q'; 2 | import {checkImageExists} from './utils/check-image-exists'; 3 | 4 | RegExp.quote = function(str) { 5 | return str.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); 6 | }; 7 | 8 | let cachedEmojis = []; 9 | 10 | export function style(name, msg) { 11 | let ret = [], 12 | emojiUrl = 'http://www.emoji-cheat-sheet.com/graphics/emojis/', 13 | emojiRegex = /:(.+?):/g, 14 | emojis = msg.match(emojiRegex), 15 | promises = []; 16 | 17 | ret.push('%c' + name + ': %c' + msg); 18 | ret.push('color: green; font-weight: bold'); 19 | ret.push('color: black'); 20 | 21 | if (emojis !== null) { 22 | for (let emoji of emojis) { 23 | emoji = emoji.substr(1, emoji.length - 2); 24 | let emojiSrc = `${emojiUrl}${encodeURIComponent(emoji)}.png`; 25 | 26 | if (cachedEmojis.indexOf(emoji) !== -1) { 27 | promises.push(Q(emoji)); 28 | } else { 29 | let promise = checkImageExists(emojiSrc).then(() => { 30 | if (cachedEmojis.indexOf(emoji) === -1) { 31 | cachedEmojis.push(emoji); 32 | } 33 | 34 | return emoji; 35 | }); 36 | 37 | promises.push(promise); 38 | } 39 | } 40 | } 41 | 42 | return Q.allSettled(promises).then((emojis) => { 43 | for (let emoji of emojis) { 44 | if (emoji.state === 'rejected') { 45 | continue; 46 | } 47 | 48 | let emojiSrc = `${emojiUrl}${encodeURIComponent(emoji.value)}.png`, 49 | emojiStyle = `background-image: url("${emojiSrc}"); background-size: cover`, 50 | emojiRegex = new RegExp(`:${RegExp.quote(emoji.value)}:\s*`); 51 | 52 | ret[0] = ret[0].replace(emojiRegex, '%c %c'); 53 | ret.push(emojiStyle); 54 | ret.push('color: black'); 55 | } 56 | 57 | return ret; 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /public/app/index.js: -------------------------------------------------------------------------------- 1 | import io from 'socket.io-client'; 2 | import {style} from './console-style'; 3 | 4 | export function init() { 5 | let socket = io(), 6 | myName; 7 | 8 | socket.on('slackMsg', function(data) { 9 | style(data.name, data.msg).then((styledConsole) => { 10 | console.log.apply(console, styledConsole); 11 | }); 12 | }); 13 | 14 | say = (...args) => { 15 | return sendMsg.apply(null, args); 16 | }; 17 | 18 | say.toString = () => { 19 | return 'Use this to send messages. For example say("Hi there!")'; 20 | }; 21 | 22 | setName = (...args) => { 23 | return initChat.apply(null, args); 24 | }; 25 | 26 | setName.toString = () => { 27 | return 'Use this to set your nickname. For example setName("John D.")'; 28 | }; 29 | 30 | help = () => {}; 31 | 32 | help.toString = () => { 33 | return `Welcome to the ConsoleBot chat system. Below are the commands you can use: 34 | setName: Set the nickname you want to use for chatting 35 | say: Send messages to other users`; 36 | 37 | }; 38 | 39 | function initChat(name) { 40 | myName = name; 41 | socket.emit('initChat', {name: name}); 42 | } 43 | 44 | function sendMsg(msg) { 45 | if (myName === undefined) { 46 | console.info('You must use setName("My Name") first.'); 47 | } else { 48 | socket.emit('msg', {name: myName, msg: msg}); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /public/app/utils/check-image-exists.js: -------------------------------------------------------------------------------- 1 | import Q from 'q'; 2 | 3 | export function checkImageExists(src) { 4 | let deferred = Q.defer(), 5 | img = new Image(); 6 | 7 | img.onload = () => { 8 | deferred.resolve(true); 9 | }; 10 | 11 | img.onerror = () => { 12 | deferred.reject('no image'); 13 | }; 14 | 15 | img.src = src; 16 | 17 | return deferred.promise; 18 | } 19 | -------------------------------------------------------------------------------- /public/config.js: -------------------------------------------------------------------------------- 1 | System.config({ 2 | "baseURL": "/", 3 | "transpiler": "babel", 4 | "paths": { 5 | "*": "*.js", 6 | "github:*": "jspm_packages/github/*.js", 7 | "npm:*": "jspm_packages/npm/*.js" 8 | } 9 | }); 10 | 11 | System.config({ 12 | "map": { 13 | "Automattic/socket.io-client": "github:Automattic/socket.io-client@1.3.5", 14 | "q": "npm:q@2.0.3", 15 | "socket.io-client": "github:Automattic/socket.io-client@1.3.5/socket.io", 16 | "github:jspm/nodelibs-domain@0.1.0": { 17 | "domain-browser": "npm:domain-browser@1.1.4" 18 | }, 19 | "github:jspm/nodelibs-events@0.1.0": { 20 | "events-browserify": "npm:events-browserify@0.0.1" 21 | }, 22 | "github:jspm/nodelibs-process@0.1.1": { 23 | "process": "npm:process@0.10.1" 24 | }, 25 | "npm:asap@2.0.1": { 26 | "domain": "github:jspm/nodelibs-domain@0.1.0", 27 | "process": "github:jspm/nodelibs-process@0.1.1" 28 | }, 29 | "npm:domain-browser@1.1.4": { 30 | "events": "github:jspm/nodelibs-events@0.1.0" 31 | }, 32 | "npm:events-browserify@0.0.1": { 33 | "process": "github:jspm/nodelibs-process@0.1.1" 34 | }, 35 | "npm:q@2.0.3": { 36 | "asap": "npm:asap@2.0.1", 37 | "pop-iterate": "npm:pop-iterate@1.0.1", 38 | "process": "github:jspm/nodelibs-process@0.1.1", 39 | "weak-map": "npm:weak-map@1.0.5" 40 | } 41 | } 42 | }); 43 | 44 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ConsoleBot 6 | 7 | 8 |

ConsoleBot

9 |

To join in the fun, open up your console and type in help and hit enter.

10 | 11 | 12 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /server/config.js: -------------------------------------------------------------------------------- 1 | var Confidence = require('confidence'), 2 | Path = require('path'), 3 | criteria, 4 | store, 5 | config; 6 | 7 | config = { 8 | $meta: 'Config file', 9 | server: { 10 | $filter: 'env', 11 | production: { 12 | host: process.env.HOST, 13 | port: process.env.PORT, 14 | apiHost: process.env.API_HOST, 15 | apiPort: process.env.API_PORT 16 | }, 17 | development: { 18 | host: process.env.HOST || 'localhost', 19 | port: process.env.PORT || 3000 20 | } 21 | }, 22 | good: { 23 | $filter: 'env', 24 | production: { 25 | opsInterval: 1000, 26 | reporters: [{ 27 | reporter: require('good-console'), 28 | args: [{log: '*', request: '*'}] 29 | }] 30 | }, 31 | $default: { 32 | opsInterval: 1000, 33 | reporters: [{ 34 | reporter: require('good-console'), 35 | args: [{log: '*', request: '*'}] 36 | }] 37 | } 38 | }, 39 | yar: { 40 | cookieOptions: { 41 | $filter: 'env', 42 | production: { 43 | password: process.env.SESSION_COOKIE_PASSWORD 44 | }, 45 | development: { 46 | password: 'password', 47 | isSecure: false 48 | } 49 | } 50 | }, 51 | slackBot: { 52 | $filter: 'env', 53 | production: { 54 | token: process.env.SLACK_TOKEN, 55 | channel: process.env.SLACK_CHANNEL 56 | }, 57 | development: { 58 | token: '[set token here]', 59 | channel: 'console-bot' 60 | } 61 | } 62 | }; 63 | 64 | 65 | criteria = { 66 | env: process.env.NODE_ENV || 'development' 67 | }; 68 | 69 | store = new Confidence.Store(config); 70 | 71 | exports.get = function(key) { 72 | 73 | return store.get(key, criteria); 74 | }; 75 | 76 | exports.meta = function(key) { 77 | 78 | return store.meta(key, criteria); 79 | }; 80 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | var Glue = require('glue'), 2 | manifest = require('./manifest'); 3 | 4 | Glue.compose(manifest.get('/'), {relativeTo: __dirname}, function (err, server) { 5 | server.start(function () { 6 | server.log('info', 'Server running at: ' + server.connections[0].info.uri); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /server/manifest.js: -------------------------------------------------------------------------------- 1 | var Confidence = require('confidence'), 2 | config = require('./config'), 3 | criteria = { 4 | env: process.env.NODE_ENV || 'development' 5 | }, 6 | store, 7 | manifest; 8 | 9 | manifest = { 10 | $meta: 'ConsoleChat', 11 | connections: [{ 12 | host: config.get('/server/host'), 13 | port: config.get('/server/port'), 14 | options: config.get('/server/options') 15 | }], 16 | plugins: { 17 | // Third Party Plugins 18 | 'good': config.get('/good'), 19 | 'hapio': {}, 20 | 21 | // Local Plugins 22 | './plugins/ConsoleChat': {}, 23 | './plugins/SlackBot': config.get('/slackBot'), 24 | './plugins/StaticFiles': {} 25 | } 26 | }; 27 | 28 | store = new Confidence.Store(manifest); 29 | 30 | exports.get = function (key) { 31 | 32 | return store.get(key, criteria); 33 | }; 34 | 35 | exports.meta = function (key) { 36 | 37 | return store.meta(key, criteria); 38 | }; 39 | -------------------------------------------------------------------------------- /server/plugins/ConsoleChat/index.js: -------------------------------------------------------------------------------- 1 | module.exports.register = function(server, options, next) { 2 | server.plugins.hapio.io.on('connection', function(socket) { 3 | socket.on('msg', function(data) { 4 | if (server.methods.slackMessage) { 5 | server.methods.slackMessage(data.name, data.msg); 6 | } 7 | server.methods.consoleMessage(data.name, data.msg, socket); 8 | }); 9 | 10 | socket.on('initChat', function(data) { 11 | server.methods.consoleMessage('Info', data.name + ' has joined the chat.'); 12 | server.methods.slackMessage('Info', data.name + ' has joined the chat.'); 13 | }); 14 | }); 15 | 16 | server.method('consoleMessage', function(name, msg) { 17 | server.plugins.hapio.io.emit('slackMsg', {name: name, msg: msg}); 18 | }); 19 | 20 | next(); 21 | }; 22 | 23 | module.exports.register.attributes = { 24 | name: 'ConsoleChat', 25 | version: '1.0.0' 26 | }; 27 | -------------------------------------------------------------------------------- /server/plugins/SlackBot/index.js: -------------------------------------------------------------------------------- 1 | var internals = {}, 2 | SlackClient = require('slack-client'); 3 | 4 | module.exports.register = function(server, options, next) { 5 | internals.initBot(server, options); 6 | 7 | next(); 8 | }; 9 | 10 | module.exports.register.attributes = { 11 | name: 'SlackBot', 12 | version: '1.0.0' 13 | }; 14 | 15 | internals.initBot = function(server, options) { 16 | var slack = new SlackClient(options.token), 17 | channel; 18 | 19 | slack.on('open', function() { 20 | var channels = slack.channels; 21 | 22 | for (var id in channels) { 23 | if (channels.hasOwnProperty(id) && channels[id].name === options.channel) { 24 | channel = channels[id]; 25 | console.log('Channel Set!!'); 26 | } 27 | } 28 | 29 | if (! channel) { 30 | throw new Error('No channel available to attach to.'); 31 | } 32 | 33 | server.method('slackMessage', function(name, msg) { 34 | channel.send('*' + name + '*: ' + msg); 35 | }); 36 | }); 37 | 38 | slack.on('message', function(message) { 39 | if (! message) { 40 | return; 41 | } 42 | 43 | var channel = slack.getChannelGroupOrDMByID(message.channel), 44 | userId = message.user ? message.user : message.message.user, 45 | user = slack.getUserByID(userId); 46 | 47 | if (channel.name === options.channel) { 48 | if (! user) { 49 | console.log(message); 50 | } else { 51 | if (message.text) { 52 | server.methods.consoleMessage(user.name, message.text); 53 | } 54 | } 55 | } 56 | }); 57 | 58 | slack.login(); 59 | }; 60 | -------------------------------------------------------------------------------- /server/plugins/StaticFiles/index.js: -------------------------------------------------------------------------------- 1 | var internals = {}; 2 | 3 | module.exports.register = function(server, options, next) { 4 | server.route({ 5 | method: 'GET', 6 | path: '/{param*}', 7 | handler: { 8 | directory: { 9 | path: 'public' 10 | } 11 | } 12 | }); 13 | 14 | next(); 15 | }; 16 | 17 | module.exports.register.attributes = { 18 | name: 'StaticFiles', 19 | version: '1.0.0' 20 | }; 21 | --------------------------------------------------------------------------------