[
  {
    "path": ".gitignore",
    "content": "node_modules/\nstart.sh\nstart_button.sh\ndb/\n.DS_Store\n*/.DS_Store\n.env\n"
  },
  {
    "path": "README.md",
    "content": "## Slackshell\nBASH terminal for your slack team\n\n\n### Getting started\n\nClone the repository on to your desired server (Ubuntu 14.04 Recommended)\n\n``` shell\ngit clone https://github.com/dtesler/slackshell.git\n```\n\nnext, create a bot for your team at https://my.slack.com/services/new/bot\n\n![Creating a bot](http://i.imgur.com/M7XqlcT.gif)\n\nOnce you have created the bot, copy your token\n\n![Bot Token](http://i.imgur.com/cBmDAuV.png)\n\nand then execute the following in your project directory\n\n``` shell\ntoken={token} node bot.js\n```\n\nwhere *{token}* is your token that you previously copied\n\nYou can now execute terminal commands (each slack user is a separate linux user) by either sending a direct message to the bot or mentioning it in a channel where it is present.\n\n![Slackshell test](http://i.imgur.com/oqeX8KF.gif)\n\n###### please contribute to my crap code\n"
  },
  {
    "path": "bot.js",
    "content": "var c = require('child_process');\n\nif (!process.env.token) {\n    console.log('Error: Specify token in environment');\n    process.exit(1);\n}\n\nvar Botkit = require('./lib/Botkit.js');\nvar os = require('os');\n\nvar controller = Botkit.slackbot({\n    debug: false,\n});\n\nvar bot = controller.spawn({\n    token: process.env.token\n}).startRTM();\n\nfunction getUID(user, callback) {\n    c.exec('id ' + user, function (err, response, stderr) {\n        if (response.includes('no such user')) { // Half assed way to check if there's a user, someone, again, please fix this if you can spare the time\n            callback(false); // Basically\n        }\n        else {\n            callback(parseInt(response.replace('uid=', '').split('(')[0])); // half-assed way to pull user id, only tested on ubuntu 14.04, someone please confirm and/or fix this :D\n        }\n    });\n}\n\ncontroller.hears([''],'direct_message,direct_mention',function(bot, message) {\n\n    bot.api.reactions.add({ // Add a heart cause why the hell not\n        timestamp: message.ts,\n        channel: message.channel,\n        name: 'heart',\n    },function(err, res) {\n        if (err) {\n            bot.botkit.log('Failed to add emoji reaction :(',err);\n        }\n    });\n\n    getUID(message.user, function (uid) {\n        if (!uid) {\n            // Add user\n            c.exec('useradd -g users -s /bin/bash -p $(echo ' + parseInt(Math.random()*999999999999999).toString(36) + ' | openssl passwd -1 -stdin) ' + message.user, function (err, stdout, stderr) {\n                if (err) {\n                    bot.reply(message, err);\n                }\n                else {\n                    console.log('shell user created:', message.user);\n                    getUID(message.user, function (uid) {\n                        c.exec(message.text, {uid:uid}, function (err, stdout, stderr) {\n                            if (err) bot.reply(message, err);\n                            else bot.reply(message, stdout);\n                        });\n                    });\n                }\n            });\n        }\n        else {\n            c.exec(message.text, {uid:uid}, function (err, stdout, stderr) {\n                if (err) bot.reply(message, err);\n                else bot.reply(message, stdout);\n            });\n        }\n    });\n});\n"
  },
  {
    "path": "lib/Botkit.js",
    "content": "var CoreBot = require(__dirname + '/CoreBot.js');\nvar Slackbot = require(__dirname + '/SlackBot.js');\n\nmodule.exports = {\n    core: CoreBot,\n    slackbot: Slackbot,\n};\n"
  },
  {
    "path": "lib/CoreBot.js",
    "content": "/**\n * This is a module that makes a bot\n * It expects to receive messages via the botkit.receiveMessage function\n * These messages are expected to match Slack's message format.\n **/\nvar mustache = require('mustache');\nvar simple_storage = require(__dirname + '/storage/simple_storage.js');\nvar ConsoleLogger = require(__dirname + '/console_logger.js');\nvar LogLevels = ConsoleLogger.LogLevels;\n\nfunction Botkit(configuration) {\n    var botkit = {\n        events: {}, // this will hold event handlers\n        config: {}, // this will hold the configuration\n        tasks: [],\n        taskCount: 0,\n        convoCount: 0,\n        memory_store: {\n            users: {},\n            channels: {},\n            teams: {},\n        }\n    };\n\n    botkit.utterances = {\n        yes: new RegExp(/^(yes|yea|yup|yep|ya|sure|ok|y|yeah|yah)/i),\n        no: new RegExp(/^(no|nah|nope|n)/i),\n    };\n\n    function Conversation(task, message) {\n\n        this.messages = [];\n        this.sent = [];\n        this.transcript = [];\n\n        this.events = {};\n\n        this.vars = {};\n\n        this.topics = {};\n        this.topic = null;\n\n        this.status = 'new';\n        this.task = task;\n        this.source_message = message;\n        this.handler = null;\n        this.responses = {};\n        this.capture_options = {};\n        this.startTime = new Date();\n        this.lastActive = new Date();\n\n        this.capture = function(response) {\n            var capture_key = this.sent[this.sent.length - 1].text;\n\n            response.text = response.text.trim();\n\n            if (this.capture_options.key) {\n                capture_key = this.capture_options.key;\n            }\n\n            // capture the question that was asked\n            // if text is an array, get 1st\n            if (typeof(this.sent[this.sent.length - 1].text) == 'string') {\n                response.question = this.sent[this.sent.length - 1].text;\n            } else {\n                response.question = this.sent[this.sent.length - 1].text[0];\n            }\n\n            if (this.capture_options.multiple) {\n                if (!this.responses[capture_key]) {\n                    this.responses[capture_key] = [];\n                }\n                this.responses[capture_key].push(response);\n            } else {\n                this.responses[capture_key] = response;\n            }\n\n        };\n\n        this.handle = function(message) {\n\n            this.lastActive = new Date();\n            this.transcript.push(message);\n            botkit.debug('HANDLING MESSAGE IN CONVO', message);\n            // do other stuff like call custom callbacks\n            if (this.handler) {\n                this.capture(message);\n\n                // if the handler is a normal function, just execute it!\n                // NOTE: anyone who passes in their own handler has to call\n                // convo.next() to continue after completing whatever it is they want to do.\n                if (typeof(this.handler) == 'function') {\n                    this.handler(message, this);\n                } else {\n                    // handle might be a mapping of keyword to callback.\n                    // lets see if the message matches any of the keywords\n                    var match, patterns = this.handler;\n                    for (var p = 0; p < patterns.length; p++) {\n                        if (patterns[p].pattern && (match = message.text.match(patterns[p].pattern))) {\n                            message.match = match;\n                            patterns[p].callback(message, this);\n                            return;\n                        }\n                    }\n\n                    // none of the messages matched! What do we do?\n                    // if a default exists, fire it!\n                    for (var p = 0; p < patterns.length; p++) {\n                        if (patterns[p].default) {\n                            patterns[p].callback(message, this);\n                            return;\n                        }\n                    }\n\n                }\n            } else {\n                // do nothing\n            }\n        };\n\n        this.activate = function() {\n            this.status = 'active';\n        };\n\n        /**\n         * active includes both ACTIVE and ENDING\n         * in order to allow the timeout end scripts to play out\n         **/\n        this.isActive = function() {\n            return (this.status == 'active' || this.status == 'ending');\n        };\n\n        this.deactivate = function() {\n            this.status = 'inactive';\n        };\n\n        this.say = function(message) {\n            this.addMessage(message);\n        };\n\n        this.sayFirst = function(message) {\n            if (typeof(message) == 'string') {\n                message = {\n                    text: message,\n                    channel: this.source_message.channel,\n                };\n            } else {\n                message.channel = this.source_message.channel;\n            }\n            this.messages.unshift(message);\n        };\n\n\n        this.on = function(event, cb) {\n            botkit.debug('Setting up a handler for', event);\n            var events = event.split(/\\,/g);\n            for (var e in events) {\n                if (!this.events[events[e]]) {\n                    this.events[events[e]] = [];\n                }\n                this.events[events[e]].push(cb);\n            }\n            return this;\n        };\n\n        this.trigger = function(event, data) {\n            if (this.events[event]) {\n                for (var e = 0; e < this.events[event].length; e++) {\n                    var res = this.events[event][e].apply(this, data);\n                    if (res === false) {\n                        return;\n                    }\n                }\n            } else {\n                botkit.debug('No handler for', event);\n            }\n        };\n\n        // proceed to the next message after waiting for an answer\n        this.next = function() {\n            this.handler = null;\n        };\n\n        this.repeat = function() {\n            if (this.sent.length) {\n                this.messages.push(this.sent[this.sent.length - 1]);\n            } else {\n                // console.log('TRIED TO REPEAT, NOTHING TO SAY');\n            }\n        };\n\n        this.silentRepeat = function() {\n            return;\n        };\n\n        this.addQuestion = function(message, cb, capture_options, topic) {\n            if (typeof(message) == 'string') {\n                message = {\n                    text: message,\n                    channel: this.source_message.channel\n                };\n            } else {\n                message.channel = this.source_message.channel;\n            }\n\n            if (capture_options) {\n                message.capture_options = capture_options;\n            }\n\n            message.handler = cb;\n            this.addMessage(message, topic);\n        };\n\n\n        this.ask = function(message, cb, capture_options) {\n            this.addQuestion(message, cb, capture_options, this.topic || 'default');\n        };\n\n        this.addMessage = function(message, topic) {\n            if (!topic) {\n                topic = this.topic;\n            }\n            if (typeof(message) == 'string') {\n                message = {\n                    text: message,\n                    channel: this.source_message.channel,\n                };\n            } else {\n                message.channel = this.source_message.channel;\n            }\n\n            if (!this.topics[topic]) {\n                this.topics[topic] = [];\n            }\n            this.topics[topic].push(message);\n\n            // this is the current topic, so add it here as well\n            if (this.topic == topic) {\n                this.messages.push(message);\n            }\n        };\n\n        this.changeTopic = function(topic) {\n            this.topic = topic;\n\n            if (!this.topics[topic]) {\n                this.topics[topic] = [];\n            }\n            this.messages = this.topics[topic].slice();\n\n            this.handler = null;\n        };\n\n        this.combineMessages = function(messages) {\n            if (!messages) {\n                return '';\n            };\n            if (messages.length > 1) {\n                var txt = [];\n                var last_user = null;\n                var multi_users = false;\n                last_user = messages[0].user;\n                for (var x = 0; x < messages.length; x++) {\n                    if (messages[x].user != last_user) {\n                        multi_users = true;\n                    }\n                }\n                last_user = '';\n                for (var x = 0; x < messages.length; x++) {\n                    if (multi_users && messages[x].user != last_user) {\n                        last_user = messages[x].user;\n                        if (txt.length) {\n                            txt.push('');\n                        }\n                        txt.push('<@' + messages[x].user + '>:');\n                    }\n                    txt.push(messages[x].text);\n                }\n                return txt.join('\\n');\n            } else {\n                if (messages.length) {\n                    return messages[0].text;\n                } else {\n                    return messages.text;\n                }\n            }\n        };\n\n        this.getResponses = function() {\n\n            var res = {};\n            for (var key in this.responses) {\n\n                res[key] = {\n                    question: this.responses[key].length ?\n                     this.responses[key][0].question : this.responses[key].question,\n                    key: key,\n                    answer: this.extractResponse(key),\n                };\n            }\n            return res;\n        };\n\n        this.getResponsesAsArray = function() {\n\n            var res = [];\n            for (var key in this.responses) {\n\n                res.push({\n                    question: this.responses[key].length ?\n                     this.responses[key][0].question : this.responses[key].question,\n                    key: key,\n                    answer: this.extractResponse(key),\n                });\n            }\n            return res;\n        };\n\n\n        this.extractResponses = function() {\n\n            var res = {};\n            for (var key in this.responses) {\n                res[key] = this.extractResponse(key);\n            }\n            return res;\n        };\n\n        this.extractResponse = function(key) {\n            return this.combineMessages(this.responses[key]);\n        };\n\n        this.replaceTokens = function(text) {\n\n            var vars = {\n                identity: this.task.bot.identity,\n                responses: this.extractResponses(),\n                origin: this.task.source_message,\n                vars: this.vars,\n            };\n            return mustache.render(text, vars);\n        };\n\n        this.stop = function(status) {\n            this.handler = null;\n            this.messages = [];\n            this.status = status || 'stopped';\n            botkit.debug('Conversation is over!');\n            this.task.conversationEnded(this);\n        };\n\n        this.tick = function() {\n            var now = new Date();\n\n            if (this.isActive()) {\n                if (this.handler) {\n                    // check timeout!\n                    // how long since task started?\n                    var duration = (now.getTime() - this.task.startTime.getTime());\n                    // how long since last active?\n                    var lastActive = (now.getTime() - this.lastActive.getTime());\n\n                    if (this.task.timeLimit && // has a timelimit\n                        (duration > this.task.timeLimit) && // timelimit is up\n                        (lastActive > this.task.timeLimit) // nobody has typed for 60 seconds at least\n                    ) {\n\n                        if (this.topics.timeout) {\n                            this.status = 'ending';\n                            this.changeTopic('timeout');\n                        } else {\n                            this.stop('timeout');\n                        }\n                    }\n                    // otherwise do nothing\n                } else {\n                    if (this.messages.length) {\n                        if (typeof(this.messages[0].timestamp) == 'undefined' ||\n                            this.messages[0].timestamp <= now.getTime()) {\n                            var message = this.messages.shift();\n                            //console.log('HANDLING NEW MESSAGE',message);\n                            // make sure next message is delayed appropriately\n                            if (this.messages.length && this.messages[0].delay) {\n                                this.messages[0].timestamp = now.getTime() + this.messages[0].delay;\n                            }\n                            if (message.handler) {\n                                //console.log(\">>>>>> SET HANDLER IN TICK\");\n                                this.handler = message.handler;\n                            } else {\n                                this.handler = null;\n                                //console.log(\">>>>>>> CLEARING HANDLER BECAUSE NO HANDLER NEEDED\");\n                            }\n                            if (message.capture_options) {\n                                this.capture_options = message.capture_options;\n                            } else {\n                                this.capture_options = {};\n                            }\n\n                            this.sent.push(message);\n                            this.transcript.push(message);\n                            this.lastActive = new Date();\n\n                            if (message.text || message.attachments) {\n\n                                // clone this object so as not to modify source\n                                var outbound = JSON.parse(JSON.stringify(message));\n\n                                if (typeof(message.text) == 'string') {\n                                    outbound.text = this.replaceTokens(message.text);\n                                } else {\n                                    outbound.text = this.replaceTokens(\n                                        message.text[Math.floor(Math.random() * message.text.length)]\n                                    );\n                                }\n\n                                if (this.messages.length && !message.handler) {\n                                    outbound.continue_typing = true;\n                                }\n\n                                if (typeof(message.attachments) == 'function') {\n                                    outbound.attachments = message.attachments(this);\n                                }\n\n                                this.task.bot.say(outbound, function(err) {\n                                    if (err) {\n                                        botkit.log('An error occurred while sending a message: ', err);\n                                    }\n                                });\n                            }\n                            if (message.action) {\n                                if (typeof(message.action) == 'function') {\n                                    message.action(this);\n                                } else if (message.action == 'repeat') {\n                                    this.repeat();\n                                } else if (message.action == 'wait') {\n                                    this.silentRepeat();\n                                } else if (message.action == 'stop') {\n                                    this.stop();\n                                } else if (message.action == 'timeout') {\n                                    this.stop('timeout');\n                                } else if (this.topics[message.action]) {\n                                    this.changeTopic(message.action);\n                                }\n                            }\n                        } else {\n                            //console.log('Waiting to send next message...');\n                        }\n\n                        // end immediately instad of waiting til next tick.\n                        // if it hasn't already been ended by a message action!\n                        if (this.isActive() && !this.messages.length && !this.handler) {\n                            this.status = 'completed';\n                            botkit.debug('Conversation is over!');\n                            this.task.conversationEnded(this);\n                        }\n\n                    } else if (this.sent.length) { // sent at least 1 message\n                        this.status = 'completed';\n                        botkit.debug('Conversation is over!');\n                        this.task.conversationEnded(this);\n                    }\n                }\n            }\n        };\n\n        botkit.debug('CREATED A CONVO FOR', this.source_message.user, this.source_message.channel);\n        this.changeTopic('default');\n    };\n\n    function Task(bot, message, botkit) {\n\n        this.convos = [];\n        this.botkit = botkit;\n        this.bot = bot;\n\n        this.events = {};\n        this.source_message = message;\n        this.status = 'active';\n        this.startTime = new Date();\n\n        this.isActive = function() {\n            return this.status == 'active';\n        };\n\n        this.startConversation = function(message) {\n            var convo = new Conversation(this, message);\n            convo.id = botkit.convoCount++;\n\n            botkit.log('>   [Start] ', convo.id, ' Conversation with ', message.user, 'in', message.channel);\n\n            convo.activate();\n            this.convos.push(convo);\n            this.trigger('conversationStarted', [convo]);\n            return convo;\n        };\n\n        this.conversationEnded = function(convo) {\n            botkit.log('>   [End] ', convo.id, ' Conversation with ',\n                       convo.source_message.user, 'in', convo.source_message.channel);\n            this.trigger('conversationEnded', [convo]);\n            convo.trigger('end', [convo]);\n            var actives = 0;\n            for (var c = 0; c < this.convos.length; c++) {\n                if (this.convos[c].isActive()) {\n                    actives++;\n                }\n            }\n            if (actives == 0) {\n                this.taskEnded();\n            }\n\n        };\n\n        this.endImmediately = function(reason) {\n\n            for (var c = 0; c < this.convos.length; c++) {\n                if (this.convos[c].isActive()) {\n                    this.convos[c].stop(reason || 'stopped');\n                }\n            }\n\n        };\n\n        this.taskEnded = function() {\n            botkit.log('[End] ', this.id, ' Task for ',\n                this.source_message.user, 'in', this.source_message.channel);\n\n            this.status = 'completed';\n            this.trigger('end', [this]);\n\n        };\n\n        this.on = function(event, cb) {\n            botkit.debug('Setting up a handler for', event);\n            var events = event.split(/\\,/g);\n            for (var e in events) {\n                if (!this.events[events[e]]) {\n                    this.events[events[e]] = [];\n                }\n                this.events[events[e]].push(cb);\n            }\n            return this;\n        };\n\n        this.trigger = function(event, data) {\n            if (this.events[event]) {\n                for (var e = 0; e < this.events[event].length; e++) {\n                    var res = this.events[event][e].apply(this, data);\n                    if (res === false) {\n                        return;\n                    }\n                }\n            } else {\n                botkit.debug('No handler for', event);\n            }\n        };\n\n\n        this.getResponsesByUser = function() {\n\n            var users = {};\n\n            // go through all conversations\n            // extract normalized answers\n            for (var c = 0; c < this.convos.length; c++) {\n\n                var user = this.convos[c].source_message.user;\n                users[this.convos[c].source_message.user] = {};\n                var convo = this.convos[c];\n                users[user] = convo.extractResponses();\n            }\n\n            return users;\n\n        };\n\n        this.getResponsesBySubject = function() {\n            var answers = {};\n\n            // go through all conversations\n            // extract normalized answers\n            for (var c = 0; c < this.convos.length; c++) {\n                var convo = this.convos[c];\n\n                for (var key in convo.responses) {\n                    if (!answers[key]) {\n                        answers[key] = {};\n                    }\n                    answers[key][convo.source_message.user] = convo.extractResponse(key);\n                }\n            }\n\n            return answers;\n        };\n\n        this.tick = function() {\n\n            for (var c = 0; c < this.convos.length; c++) {\n                if (this.convos[c].isActive()) {\n                    this.convos[c].tick();\n                }\n            }\n        };\n    };\n\n    botkit.storage = {\n        teams: {\n            get: function(team_id, cb) {\n                cb(null, botkit.memory_store.teams[team_id]);\n            },\n            save: function(team, cb) {\n                botkit.log('Warning: using temporary storage. Data will be lost when process restarts.');\n                if (team.id) {\n                    botkit.memory_store.teams[team.id] = team;\n                    cb(null, team.id);\n                } else {\n                    cb('No ID specified');\n                }\n            },\n            all: function(cb) {\n                cb(null, botkit.memory_store.teams);\n            }\n        },\n        users: {\n            get: function(user_id, cb) {\n                cb(null, botkit.memory_store.users[user_id]);\n            },\n            save: function(user, cb) {\n                botkit.log('Warning: using temporary storage. Data will be lost when process restarts.');\n                if (user.id) {\n                    botkit.memory_store.users[user.id] = user;\n                    cb(null, user.id);\n                } else {\n                    cb('No ID specified');\n                }\n            },\n            all: function(cb) {\n                cb(null, botkit.memory_store.users);\n            }\n        },\n        channels: {\n            get: function(channel_id, cb) {\n                cb(null, botkit.memory_store.channels[channel_id]);\n            },\n            save: function(channel, cb) {\n                botkit.log('Warning: using temporary storage. Data will be lost when process restarts.');\n                if (channel.id) {\n                    botkit.memory_store.channels[channel.id] = channel;\n                    cb(null, channel.id);\n                } else {\n                    cb('No ID specified');\n                }\n            },\n            all: function(cb) {\n                cb(null, botkit.memory_store.channels);\n            }\n        }\n    };\n\n    botkit.debug = function() {\n        if (configuration.debug) {\n            var args = [];\n            for (var k = 0; k < arguments.length; k++) {\n                args.push(arguments[k]);\n            }\n            console.log.apply(null, args);\n        }\n    };\n\n    botkit.log = function() {\n        if (configuration.log || configuration.log === undefined) { //default to true\n            var args = [];\n            for (var k = 0; k < arguments.length; k++) {\n                args.push(arguments[k]);\n            }\n            console.log.apply(null, args);\n        }\n    };\n\n    botkit.hears = function(keywords, events, cb) {\n        if (typeof(keywords) == 'string') {\n            keywords = [keywords];\n        }\n        if (typeof(events) == 'string') {\n            events = events.split(/\\,/g);\n        }\n\n        var match;\n        for (var k = 0; k < keywords.length; k++) {\n            var keyword = keywords[k];\n            for (var e = 0; e < events.length; e++) {\n                (function(keyword) {\n                    botkit.on(events[e], function(bot, message) {\n                        if (message.text) {\n                            if (match = message.text.match(new RegExp(keyword, 'i'))) {\n                                botkit.debug('I HEARD', keyword);\n                                message.match = match;\n                                cb.apply(this, [bot, message]);\n                                return false;\n                            }\n                        }\n                    });\n                })(keyword);\n            }\n        }\n        return this;\n    };\n\n    botkit.on = function(event, cb) {\n        botkit.debug('Setting up a handler for', event);\n        var events = (typeof(event) == 'string') ? event.split(/\\,/g) : event;\n\n        for (var e in events) {\n            if (!this.events[events[e]]) {\n                this.events[events[e]] = [];\n            }\n            this.events[events[e]].push(cb);\n        }\n        return this;\n    };\n\n    botkit.trigger = function(event, data) {\n        if (this.events[event]) {\n            for (var e = 0; e < this.events[event].length; e++) {\n                var res = this.events[event][e].apply(this, data);\n                if (res === false) {\n                    return;\n                }\n            }\n        } else {\n            botkit.debug('No handler for', event);\n        }\n    };\n\n    botkit.startConversation = function(bot, message, cb) {\n        botkit.startTask(bot, message, function(task, convo) {\n            cb(null, convo);\n        });\n    };\n\n    botkit.defineBot = function(unit) {\n        if (typeof(unit) != 'function') {\n            throw new Error('Bot definition must be a constructor function');\n        }\n        this.worker = unit;\n    };\n\n    botkit.spawn = function(config, cb) {\n        var worker = new this.worker(this, config);\n        if (cb) { cb(worker); }\n        return worker;\n    };\n\n    botkit.startTicking = function() {\n        if (!botkit.tickInterval) {\n            // set up a once a second tick to process messages\n            botkit.tickInterval = setInterval(function() {\n                botkit.tick();\n            }, 1000);\n        }\n    };\n\n    botkit.shutdown = function() {\n        if (botkit.tickInterval) {\n            clearInterval(botkit.tickInterval);\n        }\n    };\n\n    botkit.startTask = function(bot, message, cb) {\n\n\n        var task = new Task(bot, message, this);\n\n        task.id = botkit.taskCount++;\n        botkit.log('[Start] ', task.id, ' Task for ', message.user, 'in', message.channel);\n\n        var convo = task.startConversation(message);\n\n        this.tasks.push(task);\n\n        if (cb) {\n            cb(task, convo);\n        } else {\n            return task;\n        }\n\n    };\n\n    botkit.receiveMessage = function(bot, message) {\n        botkit.debug('RECEIVED MESSAGE');\n        bot.findConversation(message, function(convo) {\n            if (convo) {\n                convo.handle(message);\n            } else {\n                botkit.trigger('message_received', [bot, message]);\n            }\n        });\n    };\n\n    botkit.tick = function() {\n        for (var t = 0; t < botkit.tasks.length; t++) {\n            botkit.tasks[t].tick();\n        }\n        for (var t = botkit.tasks.length - 1; t >= 0; t--) {\n            if (!botkit.tasks[t].isActive()) {\n                botkit.tasks.splice(t, 1);\n            }\n        }\n\n\n        this.trigger('tick', []);\n\n    };\n\n\n    /**\n     * Define a default worker bot. This function should be customized outside\n     * of Botkit and passed in as a parameter by the developer\n     **/\n    botkit.worker = function(botkit, config) {\n        this.botkit = botkit;\n        this.config = config;\n\n        this.say = function(message, cb) {\n            botkit.debug('SAY:', message);\n        };\n\n        this.replyWithQuestion = function(message, question, cb) {\n\n            botkit.startConversation(message, function(convo) {\n                convo.ask(question, cb);\n            });\n\n        };\n\n        this.reply = function(src, resp) {\n            botkit.debug('REPLY:', resp);\n        };\n\n\n        this.findConversation = function(message, cb) {\n            botkit.debug('DEFAULT FIND CONVO');\n            cb(null);\n        };\n    };\n\n    botkit.config = configuration;\n\n    if (!configuration.logLevel) {\n        if (configuration.debug) {\n            configuration.logLevel = 'debug';\n        } else if (configuration.log === false) {\n            configuration.logLevel = 'error';\n        } else {\n            configuration.logLevel = 'info';\n        }\n    }\n\n    if (configuration.logger) {\n        if (typeof configuration.logger.log === 'function') {\n            botkit.logger = configuration.logger;\n        } else {\n            throw new Error('Logger object does not have a `log` method!');\n        }\n    } else {\n        botkit.logger = ConsoleLogger(console, configuration.logLevel);\n    }\n\n    botkit.log = function() {\n        botkit.log.info.apply(botkit.log, arguments);\n    };\n    Object.keys(LogLevels).forEach(function(level) {\n        botkit.log[level] = botkit.logger.log.bind(botkit.logger, level);\n    });\n    botkit.debug = botkit.log.debug;\n\n    if (configuration.storage) {\n        if (\n            configuration.storage.teams &&\n            configuration.storage.teams.get &&\n            configuration.storage.teams.save &&\n\n            configuration.storage.users &&\n            configuration.storage.users.get &&\n            configuration.storage.users.save &&\n\n            configuration.storage.channels &&\n            configuration.storage.channels.get &&\n            configuration.storage.channels.save\n        ) {\n            botkit.log('** Using custom storage system.');\n            botkit.storage = configuration.storage;\n        } else {\n            throw new Error('Storage object does not have all required methods!');\n        }\n    } else if (configuration.json_file_store) {\n        botkit.log('** Using simple storage. Saving data to ' + configuration.json_file_store);\n        botkit.storage = simple_storage({path: configuration.json_file_store});\n    } else {\n        botkit.log('** No persistent storage method specified! Data may be lost when process shuts down.');\n    }\n\n    return botkit;\n}\n\nmodule.exports = Botkit;\n"
  },
  {
    "path": "lib/SlackBot.js",
    "content": "var Botkit = require(__dirname + '/CoreBot.js');\nvar request = require('request');\nvar express = require('express');\nvar bodyParser = require('body-parser');\n\nfunction Slackbot(configuration) {\n\n    // Create a core botkit bot\n    var slack_botkit = Botkit(configuration || {});\n\n    // customize the bot definition, which will be used when new connections\n    // spawn!\n    slack_botkit.defineBot(require(__dirname + '/Slackbot_worker.js'));\n\n    // set up configuration for oauth\n    // slack_app_config should contain\n    // { clientId, clientSecret, scopes}\n    // https://api.slack.com/docs/oauth-scopes\n    slack_botkit.configureSlackApp = function(slack_app_config, cb) {\n\n        slack_botkit.log('** Configuring app as a Slack App!');\n        if (!slack_app_config || !slack_app_config.clientId ||\n            !slack_app_config.clientSecret || !slack_app_config.scopes) {\n            throw new Error('Missing oauth config details', bot);\n        } else {\n            slack_botkit.config.clientId = slack_app_config.clientId;\n            slack_botkit.config.clientSecret = slack_app_config.clientSecret;\n            if (slack_app_config.redirectUri) slack_botkit.config.redirectUri = slack_app_config.redirectUri;\n            if (typeof(slack_app_config.scopes) == 'string') {\n                slack_botkit.config.scopes = slack_app_config.scopes.split(/\\,/);\n            } else {\n                slack_botkit.config.scopes = slack_app_config.scopes;\n            }\n            if (cb) cb(null, bot);\n        }\n\n        return slack_botkit;\n\n    };\n\n    // set up a web route that is a landing page\n    slack_botkit.createHomepageEndpoint = function(webserver) {\n\n        slack_botkit.log('** Serving app landing page at : http://MY_HOST:' + slack_botkit.config.port + '/');\n\n        // FIX THIS!!!\n        // this is obvs not right.\n        webserver.get('/', function(req, res) {\n\n            res.send('Howdy!');\n\n        });\n\n        return slack_botkit;\n\n    };\n\n    // set up a web route for receiving outgoing webhooks and/or slash commands\n    slack_botkit.createWebhookEndpoints = function(webserver) {\n\n        slack_botkit.log(\n            '** Serving webhook endpoints for Slash commands and outgoing ' +\n            'webhooks at: http://MY_HOST:' + slack_botkit.config.port + '/slack/receive');\n        webserver.post('/slack/receive', function(req, res) {\n\n            // this is a slash command\n            if (req.body.command) {\n                var message = {};\n\n                for (var key in req.body) {\n                    message[key] = req.body[key];\n                }\n\n                // let's normalize some of these fields to match the rtm message format\n                message.user = message.user_id;\n                message.channel = message.channel_id;\n\n                slack_botkit.findTeamById(message.team_id, function(err, team) {\n                    // FIX THIS\n                    // this won't work for single team bots because the team info\n                    // might not be in a db\n                    if (err || !team) {\n                        slack_botkit.log.error('Received slash command, but could not load team');\n                    } else {\n                        message.type = 'slash_command';\n                        // HEY THERE\n                        // Slash commands can actually just send back a response\n                        // and have it displayed privately. That means\n                        // the callback needs access to the res object\n                        // to send an optional response.\n\n                        res.status(200);\n\n                        var bot = slack_botkit.spawn(team);\n\n                        bot.team_info = team;\n                        bot.res = res;\n\n                        slack_botkit.receiveMessage(bot, message);\n\n                    }\n                });\n\n            } else if (req.body.trigger_word) {\n\n                var message = {};\n\n                for (var key in req.body) {\n                    message[key] = req.body[key];\n                }\n\n                // let's normalize some of these fields to match the rtm message format\n                message.user = message.user_id;\n                message.channel = message.channel_id;\n\n                slack_botkit.findTeamById(message.team_id, function(err, team) {\n\n                    // FIX THIS\n                    // this won't work for single team bots because the team info\n                    // might not be in a db\n                    if (err || !team) {\n                        slack_botkit.log.error('Received outgoing webhook but could not load team');\n                    } else {\n                        message.type = 'outgoing_webhook';\n\n\n                        res.status(200);\n\n                        var bot = slack_botkit.spawn(team);\n                        bot.res = res;\n                        bot.team_info = team;\n\n\n                        slack_botkit.receiveMessage(bot, message);\n\n                        // outgoing webhooks are also different. They can simply return\n                        // a response instead of using the API to reply.  Maybe this is\n                        // a different type of event!!\n\n                    }\n                });\n\n            }\n\n        });\n\n        return slack_botkit;\n    };\n\n    slack_botkit.saveTeam = function(team, cb) {\n        slack_botkit.storage.teams.save(team, cb);\n    };\n\n    // look up a team's memory and configuration and return it, or\n    // return an error!\n    slack_botkit.findTeamById = function(id, cb) {\n        slack_botkit.storage.teams.get(id, cb);\n    };\n\n    slack_botkit.setupWebserver = function(port, cb) {\n\n        if (!port) {\n            throw new Error('Cannot start webserver without a port');\n        }\n        if (isNaN(port)) {\n            throw new Error('Specified port is not a valid number');\n        }\n\n        slack_botkit.config.port = port;\n\n        slack_botkit.webserver = express();\n        slack_botkit.webserver.use(bodyParser.json());\n        slack_botkit.webserver.use(bodyParser.urlencoded({ extended: true }));\n        slack_botkit.webserver.use(express.static(__dirname + '/public'));\n\n        var server = slack_botkit.webserver.listen(\n            slack_botkit.config.port,\n            function() {\n                slack_botkit.log('** Starting webserver on port ' +\n                    slack_botkit.config.port);\n                if (cb) { cb(null, slack_botkit.webserver); }\n            });\n\n        return slack_botkit;\n\n    };\n\n    // get a team url to redirect the user through oauth process\n    slack_botkit.getAuthorizeURL = function(team_id) {\n\n        var url = 'https://slack.com/oauth/authorize';\n        var scopes = slack_botkit.config.scopes;\n        url = url + '?client_id=' + slack_botkit.config.clientId + '&scope=' +\n            scopes.join(',') + '&state=botkit';\n\n        if (team_id) {\n            url = url + '&team=' + team_id;\n        }\n        if (slack_botkit.config.redirectUri) {\n            url = url + '&redirect_uri=' + slack_botkit.config.redirectUri;\n        }\n\n        return url;\n\n    };\n\n    // set up a web route for redirecting users\n    // and collecting authentication details\n    // https://api.slack.com/docs/oauth\n    // https://api.slack.com/docs/oauth-scopes\n    slack_botkit.createOauthEndpoints = function(webserver, callback) {\n\n        slack_botkit.log('** Serving login URL: http://MY_HOST:' + slack_botkit.config.port + '/login');\n\n        if (!slack_botkit.config.clientId) {\n            throw new Error(\n                'Cannot create oauth endpoints without calling configureSlackApp() with a clientId first');\n        }\n        if (!slack_botkit.config.clientSecret) {\n            throw new Error(\n                'Cannot create oauth endpoints without calling configureSlackApp() with a clientSecret first');\n        }\n        if (!slack_botkit.config.scopes) {\n            throw new Error(\n                'Cannot create oauth endpoints without calling configureSlackApp() with a list of scopes first');\n        }\n\n        var call_api = function(command, options, cb) {\n            slack_botkit.log('** API CALL: ' + 'https://slack.com/api/' + command);\n            request.post('https://slack.com/api/' + command, function(error, response, body) {\n                slack_botkit.debug('Got response', error, body);\n                if (!error && response.statusCode == 200) {\n                    var json = JSON.parse(body);\n                    if (json.ok) {\n                        if (cb) cb(null, json);\n                    } else {\n                        if (cb) cb(json.error, json);\n                    }\n                } else {\n                    if (cb) cb(error);\n                }\n            }).form(options);\n        };\n\n        var oauth_access = function(options, cb) {\n            call_api('oauth.access', options, cb);\n        };\n\n        var auth_test = function(options, cb) {\n            call_api('auth.test', options, cb);\n        };\n\n        webserver.get('/login', function(req, res) {\n            res.redirect(slack_botkit.getAuthorizeURL());\n        });\n\n        slack_botkit.log('** Serving oauth return endpoint: http://MY_HOST:' + slack_botkit.config.port + '/oauth');\n\n        webserver.get('/oauth', function(req, res) {\n\n            var code = req.query.code;\n            var state = req.query.state;\n\n            var opts = {\n                client_id: slack_botkit.config.clientId,\n                client_secret: slack_botkit.config.clientSecret,\n                code: code\n            };\n\n            if (slack_botkit.config.redirectUri) opts.redirect_uri = slack_botkit.config.redirectUri;\n\n            oauth_access(opts, function(err, auth) {\n\n                if (err) {\n                    if (callback) {\n                        callback(err, req, res);\n                    } else {\n                        res.status(500).send(err);\n                    }\n                    slack_botkit.trigger('oauth_error', [err]);\n                } else {\n\n                    // auth contains at least:\n                    // { access_token, scope, team_name}\n                    // May also contain:\n                    // { team_id } (not in incoming_webhook scope)\n                    // info about incoming webhooks:\n                    // { incoming_webhook: { url, channel, configuration_url} }\n                    // might also include slash commands:\n                    // { commands: ??}\n\n                    // what scopes did we get approved for?\n                    var scopes = auth.scope.split(/\\,/);\n\n                    // temporarily use the token we got from the oauth\n                    // we need to call auth.test to make sure the token is valid\n                    // but also so that we reliably have the team_id field!\n                    //slack_botkit.config.token = auth.access_token;\n                    auth_test({token: auth.access_token}, function(err, identity) {\n\n                        if (err) {\n                            if (callback) {\n                                callback(err, req, res);\n                            } else {\n                                res.status(500).send(err);\n                            }\n\n                            slack_botkit.trigger('oauth_error', [err]);\n\n                        } else {\n\n                            // we need to deal with any team-level provisioning info\n                            // like incoming webhooks and bot users\n                            // and also with the personal access token from the user\n\n                            slack_botkit.findTeamById(identity.team_id, function(err, team) {\n\n                                var isnew = false;\n                                if (!team) {\n                                    isnew = true;\n                                    team = {\n                                        id: identity.team_id,\n                                        createdBy: identity.user_id,\n                                        url: identity.url,\n                                        name: identity.team,\n                                    };\n                                }\n\n                                var bot = slack_botkit.spawn(team);\n\n                                if (auth.incoming_webhook) {\n                                    auth.incoming_webhook.token = auth.access_token;\n                                    auth.incoming_webhook.createdBy = identity.user_id;\n                                    team.incoming_webhook = auth.incoming_webhook;\n                                    bot.configureIncomingWebhook(team.incoming_webhook);\n                                    slack_botkit.trigger('create_incoming_webhook', [bot, team.incoming_webhook]);\n                                }\n\n                                if (auth.bot) {\n                                    team.bot = {\n                                        token: auth.bot.bot_access_token,\n                                        user_id: auth.bot.bot_user_id,\n                                        createdBy: identity.user_id,\n                                    };\n                                    bot.configureRTM(team.bot);\n                                    slack_botkit.trigger('create_bot', [bot, team.bot]);\n                                }\n\n                                slack_botkit.saveTeam(team, function(err, id) {\n                                    if (err) {\n                                        slack_botkit.log.error('An error occurred while saving a team: ', err);\n                                        if (callback) {\n                                            callback(err, req, res);\n                                        } else {\n                                            res.status(500).send(err);\n                                        }\n                                        slack_botkit.trigger('error', [err]);\n                                    } else {\n                                        if (isnew) {\n                                            slack_botkit.trigger('create_team', [bot, team]);\n                                        } else {\n                                            slack_botkit.trigger('update_team', [bot, team]);\n                                        }\n\n                                        slack_botkit.storage.users.get(identity.user_id, function(err, user) {\n                                            isnew = false;\n                                            if (!user) {\n                                                isnew = true;\n                                                user = {\n                                                    id: identity.user_id,\n                                                    access_token: auth.access_token,\n                                                    scopes: scopes,\n                                                    team_id: identity.team_id,\n                                                    user: identity.user,\n                                                };\n                                            }\n                                            slack_botkit.storage.users.save(user, function(err, id) {\n\n                                                if (err) {\n                                                    slack_botkit.log.error(\n                                                        'An error occurred while saving a user: ', err);\n                                                    if (callback) {\n                                                        callback(err, req, res);\n                                                    } else {\n                                                        res.status(500).send(err);\n                                                    }\n                                                    slack_botkit.trigger('error', [err]);\n                                                } else {\n                                                    if (isnew) {\n                                                        slack_botkit.trigger('create_user', [bot, user]);\n                                                    } else {\n                                                        slack_botkit.trigger('update_user', [bot, user]);\n                                                    }\n                                                    if (callback) {\n                                                        callback(null, req, res);\n                                                    } else {\n                                                        res.redirect('/');\n                                                    }\n                                                }\n                                            });\n                                        });\n                                    }\n                                });\n                            });\n                        }\n                    });\n                }\n            });\n        });\n\n        return slack_botkit;\n\n    };\n\n    slack_botkit.handleSlackEvents = function() {\n\n        slack_botkit.log('** Setting up custom handlers for processing Slack messages');\n        slack_botkit.on('message_received', function(bot, message) {\n\n\n            if (message.ok != undefined) {\n                // this is a confirmation of something we sent.\n                return false;\n            }\n\n            slack_botkit.debug('DEFAULT SLACK MSG RECEIVED RESPONDER');\n            if ('message' == message.type) {\n\n                if (message.text) {\n                    message.text = message.text.trim();\n                }\n\n                // set up a couple of special cases based on subtype\n                if (message.subtype && message.subtype == 'channel_join') {\n                    // someone joined. maybe do something?\n                    if (message.user == bot.identity.id) {\n                        slack_botkit.trigger('bot_channel_join', [bot, message]);\n                        return false;\n                    } else {\n                        slack_botkit.trigger('user_channel_join', [bot, message]);\n                        return false;\n                    }\n                } else if (message.subtype && message.subtype == 'group_join') {\n                    // someone joined. maybe do something?\n                    if (message.user == bot.identity.id) {\n                        slack_botkit.trigger('bot_group_join', [bot, message]);\n                        return false;\n                    } else {\n                        slack_botkit.trigger('user_group_join', [bot, message]);\n                        return false;\n                    }\n                } else if (message.subtype) {\n                    slack_botkit.trigger(message.subtype, [bot, message]);\n                    return false;\n                } else if (message.channel.match(/^D/)) {\n                    // this is a direct message\n                    if (message.user == bot.identity.id) {\n                        return false;\n                    }\n                    if (!message.text) {\n                        // message without text is probably an edit\n                        return false;\n                    }\n\n                    // remove direct mention so the handler doesn't have to deal with it\n                    var direct_mention = new RegExp('^\\<\\@' + bot.identity.id + '\\>', 'i');\n                    message.text = message.text.replace(direct_mention, '')\n                    .replace(/^\\s+/, '').replace(/^\\:\\s+/, '').replace(/^\\s+/, '');\n\n                    message.event = 'direct_message';\n\n                    slack_botkit.trigger('direct_message', [bot, message]);\n                    return false;\n\n                } else {\n                    if (message.user == bot.identity.id) {\n                        return false;\n                    }\n                    if (!message.text) {\n                        // message without text is probably an edit\n                        return false;\n                    }\n\n                    var direct_mention = new RegExp('^\\<\\@' + bot.identity.id + '\\>', 'i');\n                    var mention = new RegExp('\\<\\@' + bot.identity.id + '\\>', 'i');\n\n                    if (message.text.match(direct_mention)) {\n                        // this is a direct mention\n                        message.text = message.text.replace(direct_mention, '')\n                        .replace(/^\\s+/, '').replace(/^\\:\\s+/, '').replace(/^\\s+/, '');\n                        message.event = 'direct_mention';\n\n                        slack_botkit.trigger('direct_mention', [bot, message]);\n                        return false;\n                    } else if (message.text.match(mention)) {\n                        message.event = 'mention';\n                        slack_botkit.trigger('mention', [bot, message]);\n                        return false;\n                    } else {\n                        message.event = 'ambient';\n                        slack_botkit.trigger('ambient', [bot, message]);\n                        return false;\n\n                    }\n                }\n            } else {\n                // this is a non-message object, so trigger a custom event based on the type\n                slack_botkit.trigger(message.type, [bot, message]);\n            }\n        });\n    };\n\n    // set up the RTM message handlers once\n    slack_botkit.handleSlackEvents();\n\n    return slack_botkit;\n};\n\nmodule.exports = Slackbot;\n"
  },
  {
    "path": "lib/Slack_web_api.js",
    "content": "var request = require('request');\n\nmodule.exports = function(bot, config) {\n\n    // create a nice wrapper for the Slack API\n    var slack_api = {\n        api_url: 'https://slack.com/api/',\n        // this is a simple function used to call the slack web API\n        callAPI: function(command, options, cb) {\n            bot.log('** API CALL: ' + slack_api.api_url + command);\n            if (!options.token) {\n                options.token = config.token;\n            }\n            bot.debug(command, options);\n            request.post(this.api_url + command, function(error, response, body) {\n                bot.debug('Got response', error, body);\n                if (!error && response.statusCode == 200) {\n                    var json = JSON.parse(body);\n                    if (json.ok) {\n                        if (cb) cb(null, json);\n                    } else {\n                        if (cb) cb(json.error, json);\n                    }\n                } else {\n                    if (cb) cb(error);\n                }\n            }).form(options);\n        },\n        auth: {\n            test: function(options, cb) {\n                slack_api.callAPI('auth.test', options, cb);\n            }\n        },\n        oauth: {\n            access: function(options, cb) {\n                slack_api.callAPIWithoutToken('oauth.access', options, cb);\n            }\n        },\n        channels: {\n            archive: function(options, cb) {\n                slack_api.callAPI('channels.archive', options, cb);\n            },\n            create: function(options, cb) {\n                slack_api.callAPI('channels.create', options, cb);\n            },\n            history: function(options, cb) {\n                slack_api.callAPI('channels.history', options, cb);\n            },\n            info: function(options, cb) {\n                slack_api.callAPI('channels.info', options, cb);\n            },\n            invite: function(options, cb) {\n                slack_api.callAPI('channels.invite', options, cb);\n            },\n            join: function(options, cb) {\n                slack_api.callAPI('channels.join', options, cb);\n            },\n            kick: function(options, cb) {\n                slack_api.callAPI('channels.kick', options, cb);\n            },\n            leave: function(options, cb) {\n                slack_api.callAPI('channels.leave', options, cb);\n            },\n            list: function(options, cb) {\n                slack_api.callAPI('channels.list', options, cb);\n            },\n            mark: function(options, cb) {\n                slack_api.callAPI('channels.mark', options, cb);\n            },\n            rename: function(options, cb) {\n                slack_api.callAPI('channels.rename', options, cb);\n            },\n            setPurpose: function(options, cb) {\n                slack_api.callAPI('channels.setPurpose', options, cb);\n            },\n            setTopic: function(options, cb) {\n                slack_api.callAPI('channels.setTopic', options, cb);\n            },\n            unarchive: function(options, cb) {\n                slack_api.callAPI('channels.unarchive', options, cb);\n            }\n        },\n        chat: {\n            delete: function(options, cb) {\n                slack_api.callAPI('chat.delete', options, cb);\n            },\n            postMessage: function(options, cb) {\n                if (options.attachments && typeof(options.attachments) != 'string') {\n                    options.attachments = JSON.stringify(options.attachments);\n                }\n                slack_api.callAPI('chat.postMessage', options, cb);\n            },\n            update: function(options, cb) {\n                slack_api.callAPI('chat.update', options, cb);\n            }\n        },\n        emoji: {\n            list: function(options, cb) {\n                slack_api.callAPI('emoji.list', options, cb);\n            }\n        },\n        files: {\n            delete: function(options, cb) {\n                slack_api.callAPI('files.delete', options, cb);\n            },\n            info: function(options, cb) {\n                slack_api.callAPI('files.info', options, cb);\n            },\n            list: function(options, cb) {\n                slack_api.callAPI('files.list', options, cb);\n            },\n            upload: function(options, cb) {\n                slack_api.callAPI('files.upload', options, cb);\n            },\n        },\n        groups: {\n            archive: function(options, cb) {\n                slack_api.callAPI('groups.archive', options, cb);\n            },\n            close: function(options, cb) {\n                slack_api.callAPI('groups.close', options, cb);\n            },\n            create: function(options, cb) {\n                slack_api.callAPI('groups.create', options, cb);\n            },\n            createChild: function(options, cb) {\n                slack_api.callAPI('groups.createChild', options, cb);\n            },\n            history: function(options, cb) {\n                slack_api.callAPI('groups.history', options, cb);\n            },\n            info: function(options, cb) {\n                slack_api.callAPI('groups.info', options, cb);\n            },\n            invite: function(options, cb) {\n                slack_api.callAPI('groups.invite', options, cb);\n            },\n            kick: function(options, cb) {\n                slack_api.callAPI('groups.kick', options, cb);\n            },\n            leave: function(options, cb) {\n                slack_api.callAPI('groups.leave', options, cb);\n            },\n            list: function(options, cb) {\n                slack_api.callAPI('groups.list', options, cb);\n            },\n            mark: function(options, cb) {\n                slack_api.callAPI('groups.mark', options, cb);\n            },\n            open: function(options, cb) {\n                slack_api.callAPI('groups.open', options, cb);\n            },\n            rename: function(options, cb) {\n                slack_api.callAPI('groups.rename', options, cb);\n            },\n            setPurpose: function(options, cb) {\n                slack_api.callAPI('groups.setPurpose', options, cb);\n            },\n            setTopic: function(options, cb) {\n                slack_api.callAPI('groups.setTopic', options, cb);\n            },\n            unarchive: function(options, cb) {\n                slack_api.callAPI('groups.unarchive', options, cb);\n            },\n        },\n        im: {\n            close: function(options, cb) {\n                slack_api.callAPI('im.close', options, cb);\n            },\n            history: function(options, cb) {\n                slack_api.callAPI('im.history', options, cb);\n            },\n            list: function(options, cb) {\n                slack_api.callAPI('im.list', options, cb);\n            },\n            mark: function(options, cb) {\n                slack_api.callAPI('im.mark', options, cb);\n            },\n            open: function(options, cb) {\n                slack_api.callAPI('im.open', options, cb);\n            }\n        },\n        mpim: {\n            close: function(options, cb) {\n                slack_api.callAPI('mpim.close', options, cb);\n            },\n            history: function(options, cb) {\n                slack_api.callAPI('mpim.history', options, cb);\n            },\n            list: function(options, cb) {\n                slack_api.callAPI('mpim.list', options, cb);\n            },\n            mark: function(options, cb) {\n                slack_api.callAPI('mpim.mark', options, cb);\n            },\n            open: function(options, cb) {\n                slack_api.callAPI('mpim.open', options, cb);\n            }\n        },\n        pins: {\n            add: function(options, cb) {\n                slack_api.callAPI('pins.add', options, cb);\n            },\n            list: function(options, cb) {\n                slack_api.callAPI('pins.list', options, cb);\n            },\n            remove: function(options, cb) {\n                slack_api.callAPI('pins.remove', options, cb);\n            }\n        },\n        reactions: {\n            add: function(options, cb) {\n                slack_api.callAPI('reactions.add', options, cb);\n            },\n            get: function(options, cb) {\n                slack_api.callAPI('reactions.get', options, cb);\n            },\n            list: function(options, cb) {\n                slack_api.callAPI('reactions.list', options, cb);\n            },\n            remove: function(options, cb) {\n                slack_api.callAPI('reactions.remove', options, cb);\n            },\n        },\n        rtm: {\n            start: function(options, cb) {\n                slack_api.callAPI('rtm.start', options, cb);\n            },\n        },\n        search: {\n            all: function(options, cb) {\n                slack_api.callAPI('search.all', options, cb);\n            },\n            files: function(options, cb) {\n                slack_api.callAPI('search.files', options, cb);\n            },\n            messages: function(options, cb) {\n                slack_api.callAPI('search.messages', options, cb);\n            },\n        },\n        stars: {\n            list: function(options, cb) {\n                slack_api.callAPI('stars.list', options, cb);\n            },\n        },\n        team: {\n            accessLogs: function(options, cb) {\n                slack_api.callAPI('team.accessLogs', options, cb);\n            },\n            info: function(options, cb) {\n                slack_api.callAPI('team.info', options, cb);\n            },\n        },\n        users: {\n            getPresence: function(options, cb) {\n                slack_api.callAPI('users.getPresence', options, cb);\n            },\n            info: function(options, cb) {\n                slack_api.callAPI('users.info', options, cb);\n            },\n            list: function(options, cb) {\n                slack_api.callAPI('users.list', options, cb);\n            },\n            setActive: function(options, cb) {\n                slack_api.callAPI('users.setActive', options, cb);\n            },\n            setPresence: function(options, cb) {\n                slack_api.callAPI('users.setPresence', options, cb);\n            },\n        }\n    };\n\n    return slack_api;\n\n};\n"
  },
  {
    "path": "lib/Slackbot_worker.js",
    "content": "var Ws = require('ws');\nvar request = require('request');\nvar slackWebApi = require(__dirname + '/Slack_web_api.js');\nvar HttpsProxyAgent = require('https-proxy-agent');\n\n\nmodule.exports = function(botkit, config) {\n    var bot = {\n        botkit: botkit,\n        config: config || {},\n        utterances: botkit.utterances,\n        api: slackWebApi(botkit, config || {})\n    };\n\n    /**\n     * Set up API to send incoming webhook\n     */\n    bot.configureIncomingWebhook = function(options) {\n        if (!options.url)\n            throw new Error('No incoming webhook URL specified!');\n\n        bot.config.incoming_webhook = options;\n\n        return bot;\n    };\n\n    bot.sendWebhook = function(options, cb) {\n        if (!bot.config.incoming_webhook || !bot.config.incoming_webhook.url) {\n            botkit.debug('CANNOT SEND WEBHOOK!!');\n\n            return cb && cb('No webhook url specified');\n        }\n\n        request.post(bot.config.incoming_webhook.url, function(err, res, body) {\n            if (err) {\n                botkit.debug('WEBHOOK ERROR', err);\n                return cb && cb(err);\n            }\n            botkit.debug('WEBHOOK SUCCESS', body);\n            cb && cb(null, body);\n        }).form({ payload: JSON.stringify(options) });\n    };\n\n    bot.configureRTM = function(config) {\n        bot.config.token = config.token;\n        return bot;\n    };\n\n    bot.closeRTM = function() {\n        if (bot.rtm)\n            bot.rtm.close();\n    };\n\n    bot.startRTM = function(cb) {\n        bot.api.rtm.start({\n            no_unreads: true,\n            simple_latest: true,\n        }, function(err, res) {\n            if (err) {\n                return cb && cb(err);\n            }\n\n            if (!res) {\n                return cb && cb('Invalid response from rtm.start');\n            }\n\n            bot.identity = res.self;\n            bot.team_info = res.team;\n\n            /**\n             * Also available:\n             * res.users, res.channels, res.groups, res.ims,\n             * res.bots\n             *\n             * Could be stored & cached for later use.\n             */\n\n            botkit.log.notice('** BOT ID:', bot.identity.name, '...attempting to connect to RTM!');\n\n            var agent = null;\n            var proxyUrl = process.env.https_proxy || process.env.http_proxy;\n            if (proxyUrl) {\n                agent = new HttpsProxyAgent(proxyUrl);\n            }\n\n            bot.rtm = new Ws(res.url, null, {agent: agent});\n            bot.msgcount = 1;\n\n            var pingIntervalId = null;\n            bot.rtm.on('open', function() {\n\n                pingIntervalId = setInterval(function() {\n                    bot.rtm.ping(null, null, true);\n                }, 5000);\n\n                botkit.trigger('rtm_open', [bot]);\n\n                bot.rtm.on('message', function(data, flags) {\n\n                    var message = null;\n                    try {\n                        message = JSON.parse(data);\n                    } catch (err) {\n                        console.log('** RECEIVED BAD JSON FROM SLACK');\n                    }\n                    /**\n                     * Lets construct a nice quasi-standard botkit message\n                     * it leaves the main slack message at the root\n                     * but adds in additional fields for internal use!\n                     * (including the teams api details)\n                     */\n                    if (message != null) {\n                        botkit.receiveMessage(bot, message);\n                    }\n                });\n\n                botkit.startTicking();\n\n                cb && cb(null, bot, res);\n            });\n\n            bot.rtm.on('error', function(err) {\n                botkit.log.error('RTM websocket error!', err);\n                botkit.trigger('rtm_close', [bot, err]);\n            });\n\n            bot.rtm.on('close', function() {\n                if (pingIntervalId) {\n                    clearInterval(pingIntervalId);\n                }\n                botkit.trigger('rtm_close', [bot]);\n            });\n        });\n\n        return bot;\n    };\n\n    bot.identifyBot = function(cb) {\n        if (bot.identity) {\n            bot.identifyTeam(function(err, team) {\n                cb(null, {\n                    name: bot.identity.name,\n                    id: bot.identity.id,\n                    team_id: team\n                });\n            });\n        } else {\n            /**\n             * Note: Are there scenarios other than the RTM\n             * where we might pull identity info, perhaps from\n             * bot.api.auth.test on a given token?\n             */\n            cb('Identity Unknown: Not using RTM api');\n        };\n    };\n\n    bot.identifyTeam = function(cb) {\n        if (bot.team_info)\n            return cb(null, bot.team_info.id);\n\n        /**\n         * Note: Are there scenarios other than the RTM\n         * where we might pull identity info, perhaps from\n         * bot.api.auth.test on a given token?\n         */\n        cb('Unknown Team!');\n    };\n\n    /**\n     * Convenience method for creating a DM convo.\n     */\n    bot.startPrivateConversation = function(message, cb) {\n        botkit.startTask(this, message, function(task, convo) {\n            bot._startDM(task, message.user, function(err, dm) {\n                convo.stop();\n                cb(err, dm);\n            });\n        });\n    };\n\n    bot.startConversation = function(message, cb) {\n        botkit.startConversation(this, message, cb);\n    };\n\n    /**\n     * Convenience method for creating a DM convo.\n     */\n    bot._startDM = function(task, user_id, cb) {\n        bot.api.im.open({ user: user_id }, function(err, channel) {\n            if (err) return cb(err);\n\n            cb(null, task.startConversation({\n                channel: channel.channel.id,\n                user: user_id\n            }));\n        });\n    };\n\n    bot.say = function(message, cb) {\n        botkit.debug('SAY', message);\n\n        /**\n         * Construct a valid slack message.\n         */\n        var slack_message = {\n            type: message.type || 'message',\n            channel: message.channel,\n            text: message.text || null,\n            username: message.username || null,\n            parse: message.parse || null,\n            link_names: message.link_names || null,\n            attachments: message.attachments ?\n                JSON.stringify(message.attachments) : null,\n            unfurl_links: typeof message.unfurl_links !== 'undefined' ? message.unfurl_links : null,\n            unfurl_media: typeof message.unfurl_media !== 'undefined' ? message.unfurl_media : null,\n            icon_url: message.icon_url || null,\n            icon_emoji: message.icon_emoji || null,\n        };\n        bot.msgcount++;\n\n        if (message.icon_url || message.icon_emoji || message.username) {\n            slack_message.as_user = false;\n        } else {\n            slack_message.as_user = message.as_user || true;\n        }\n\n        /**\n         * These options are not supported by the RTM\n         * so if they are specified, we use the web API to send messages.\n         */\n        if (message.attachments || message.icon_emoji ||\n            message.username || message.icon_url) {\n\n            if (!bot.config.token) {\n                throw new Error('Cannot use web API to send messages.');\n            }\n\n            bot.api.chat.postMessage(slack_message, function(err, res) {\n                if (err) {\n                    cb && cb(err);\n                } else {\n                    cb && cb(null, res);\n                }\n            });\n\n        } else {\n            if (!bot.rtm)\n                throw new Error('Cannot use the RTM API to send messages.');\n\n            slack_message.id = message.id || bot.msgcount;\n\n\n            try {\n                bot.rtm.send(JSON.stringify(slack_message), function(err) {\n                    if (err) {\n                        cb && cb(err);\n                    } else {\n                        cb && cb();\n                    }\n                });\n            } catch (err) {\n                /**\n                 * The RTM failed and for some reason it didn't get caught\n                 * elsewhere. This happens sometimes when the rtm has closed but\n                 * We are sending messages anyways.\n                 * Bot probably needs to reconnect!\n                 */\n                cb && cb(err);\n            }\n        }\n    };\n\n    bot.replyPublic = function(src, resp, cb) {\n        if (!bot.res) {\n            cb && cb('No web response object found');\n        } else {\n            var msg = {};\n\n            if (typeof(resp) == 'string') {\n                msg.text = resp;\n            } else {\n                msg = resp;\n            }\n\n            msg.channel = src.channel;\n\n            msg.response_type = 'in_channel';\n            bot.res.json(msg);\n            cb && cb();\n        }\n    };\n\n    bot.replyPublicDelayed = function(src, resp, cb) {\n        if (!src.response_url) {\n            cb && cb('No response_url found');\n        } else {\n            var msg = {};\n\n            if (typeof(resp) == 'string') {\n                msg.text = resp;\n            } else {\n                msg = resp;\n            }\n\n            msg.channel = src.channel;\n\n            msg.response_type = 'in_channel';\n            var requestOptions = {\n                uri: src.response_url,\n                method: 'POST',\n                json: msg\n            };\n            request(requestOptions, function(err, resp, body) {\n                /**\n                 * Do something?\n                 */\n                if (err) {\n                    botkit.log.error('Error sending slash command response:', err);\n                    cb && cb(err);\n                } else {\n                    cb && cb();\n                }\n            });\n        }\n    };\n\n    bot.replyPrivate = function(src, resp, cb) {\n        if (!bot.res) {\n            cb && cb('No web response object found');\n        } else {\n            var msg = {};\n\n            if (typeof(resp) == 'string') {\n                msg.text = resp;\n            } else {\n                msg = resp;\n            }\n\n            msg.channel = src.channel;\n\n            msg.response_type = 'ephemeral';\n            bot.res.json(msg);\n\n            cb && cb();\n        }\n    };\n\n    bot.replyPrivateDelayed = function(src, resp, cb) {\n        if (!src.response_url) {\n            cb && cb('No response_url found');\n        } else {\n            var msg = {};\n\n            if (typeof(resp) == 'string') {\n                msg.text = resp;\n            } else {\n                msg = resp;\n            }\n\n            msg.channel = src.channel;\n\n            msg.response_type = 'ephemeral';\n\n            var requestOptions = {\n                uri: src.response_url,\n                method: 'POST',\n                json: msg\n            };\n            request(requestOptions, function(err, resp, body) {\n                /**\n                 * Do something?\n                 */\n                if (err) {\n                    botkit.log.error('Error sending slash command response:', err);\n                    cb && cb(err);\n                } else {\n                    cb && cb();\n                }\n            });\n        }\n    };\n\n    bot.reply = function(src, resp, cb) {\n        var msg = {};\n\n        if (typeof(resp) == 'string') {\n            msg.text = resp;\n        } else {\n            msg = resp;\n        }\n\n        msg.channel = src.channel;\n\n        bot.say(msg, cb);\n    };\n\n    /**\n     * sends a typing message to the source channel\n     *\n     * @param {Object} src message source\n     */\n    bot.startTyping = function(src) {\n        bot.reply(src, { type: 'typing' });\n    };\n\n    /**\n     * replies with message after typing delay\n     *\n     * @param {Object} src message source\n     * @param {(string|Object)} resp string or object\n     * @param {function} cb optional request callback\n     */\n    bot.replyWithTyping = function(src, resp, cb) {\n        var text;\n\n        if (typeof(resp) == 'string') {\n            text = resp;\n        } else {\n            text = resp.text;\n        }\n\n        var typingLength = 1200 / 60 * text.length;\n        typingLength = typingLength > 2000 ? 2000 : typingLength;\n\n        bot.startTyping(src);\n\n        setTimeout(function() {\n            bot.reply(src, resp, cb);\n        }, typingLength);\n    };\n\n    /**\n     * This handles the particulars of finding an existing conversation or\n     * topic to fit the message into...\n     */\n    bot.findConversation = function(message, cb) {\n        botkit.debug('CUSTOM FIND CONVO', message.user, message.channel);\n        if (message.type == 'message' || message.type == 'slash_command' ||\n            message.type == 'outgoing_webhook') {\n            for (var t = 0; t < botkit.tasks.length; t++) {\n                for (var c = 0; c < botkit.tasks[t].convos.length; c++) {\n                    if (\n                        botkit.tasks[t].convos[c].isActive() &&\n                        botkit.tasks[t].convos[c].source_message.user == message.user &&\n                            botkit.tasks[t].convos[c].source_message.channel == message.channel\n                    ) {\n                        botkit.debug('FOUND EXISTING CONVO!');\n                        cb(botkit.tasks[t].convos[c]);\n                        return;\n                    }\n                }\n            }\n        }\n\n        cb();\n    };\n\n    if (bot.config.incoming_webhook)\n        bot.configureIncomingWebhook(config.incoming_webhook);\n\n    if (bot.config.bot)\n        bot.configureRTM(config.bot);\n\n    return bot;\n};\n"
  },
  {
    "path": "lib/console_logger.js",
    "content": "var slice = Array.prototype.slice;\n/**\n * RFC 5424 syslog severity levels, see\n * https://tools.ietf.org/html/rfc5424#section-6.2.1\n */\nvar levels = [\n    'emergency',\n    'alert',\n    'critical',\n    'error',\n    'warning',\n    'notice',\n    'info',\n    'debug'\n];\nvar levelsByName = levels.reduce(function(out, name, index) {\n    out[name] = index;\n    return out;\n}, {});\n\nfunction normalizeLogLevel(level) {\n    if (typeof level === 'string') {\n        level = levelsByName[level];\n    }\n    if (typeof level === 'number' && level >= 0 && level < levels.length) {\n        return level;\n    }\n    return false;\n}\n\nfunction ConsoleLogger(_console, maxLevel, defaultLevel) {\n    _console = _console || console;\n    maxLevel = normalizeLogLevel(maxLevel) || 6;\n    defaultLevel = normalizeLogLevel(defaultLevel) || 6;\n    return {\n        log: function(level, message) {\n            var normalizedLevel = normalizeLogLevel(level);\n            if (!normalizedLevel) {\n                message = level;\n                normalizedLevel = defaultLevel;\n            }\n            var levelName = levels[normalizedLevel];\n            if (normalizedLevel <= maxLevel) {\n                _console.log.apply(\n                    _console,\n                    [levelName + ': ' + message].concat(slice.call(arguments, 2))\n                );\n            }\n        }\n    };\n}\n\nConsoleLogger.LogLevels = levelsByName;\n\nmodule.exports = ConsoleLogger;\n"
  },
  {
    "path": "lib/storage/firebase_storage.js",
    "content": "/*\nFirebase storage module for bots.\n\nNote that this storage module does not specify how to authenticate to Firebase.\nThere are many methods of user authentication for Firebase.\nPlease read: https://www.firebase.com/docs/web/guide/user-auth.html\n\nSupports storage of data on a team-by-team, user-by-user, and chnnel-by-channel basis.\n\nsave can be used to store arbitrary object.\nThese objects must include an id by which they can be looked up.\nIt is recommended to use the team/user/channel id for this purpose.\nExample usage of save:\ncontroller.storage.teams.save({id: message.team, foo:\"bar\"}, function(err){\n  if (err)\n    console.log(err)`\n});\n\nget looks up an object by id.\nExample usage of get:\ncontroller.storage.teams.get(message.team, function(err, team_data){\n  if (err)\n    console.log(err)\n  else\n    console.log(team_data)\n});\n*/\n\nvar Firebase = require('firebase');\n\nmodule.exports = function(config) {\n\n    if (!config && !config.firebase_uri)\n        throw new Error('Need to provide firebase address. This should look something like ' +\n            '\"https://botkit-example.firebaseio.com/\"');\n\n    var rootRef = new Firebase(config.firebase_uri);\n    var teamsRef = rootRef.child('teams');\n    var usersRef = rootRef.child('users');\n    var channelsRef = rootRef.child('channels');\n\n    var get = function(firebaseRef) {\n        return function(id, cb) {\n            firebaseRef.child(id).once('value',\n                function(records) {\n                    cb(undefined, records.val());\n                },\n                function(err) {\n                    cb(err, undefined);\n                }\n            );\n        };\n    };\n\n    var save = function(firebaseRef) {\n        return function(data, cb) {\n            var firebase_update = {};\n            firebase_update[data.id] = data;\n            firebaseRef.update(firebase_update, cb);\n        };\n    };\n\n    var all = function(firebaseRef) {\n        return function(cb) {\n            firebaseRef.once('value',\n                function(records) {\n                    var list = [];\n                    for (key of Object.keys(records.val())) {\n                        list.push(records.val()[key]);\n                    }\n                    cb(undefined, list);\n                },\n                function(err) {\n                    cb(err, undefined);\n                }\n            );\n        };\n    };\n\n    var storage = {\n        teams: {\n            get: get(teamsRef),\n            save: save(teamsRef),\n            all: all(teamsRef)\n        },\n        channels: {\n            get: get(channelsRef),\n            save: save(channelsRef),\n            all: all(channelsRef)\n        },\n        users: {\n            get: get(usersRef),\n            save: save(usersRef),\n            all: all(usersRef)\n        }\n    };\n\n    return storage;\n\n};\n"
  },
  {
    "path": "lib/storage/redis_storage.js",
    "content": "var redis = require('redis'); //https://github.com/NodeRedis/node_redis\n\n/*\n * All optional\n *\n * config = {\n *  namespace: namespace,\n *  host: host,\n *  port: port\n * }\n * // see\n * https://github.com/NodeRedis/node_redis\n * #options-is-an-object-with-the-following-possible-properties for a full list of the valid options\n */\nmodule.exports = function(config) {\n    config = config || {};\n    config.namespace = config.namespace || 'botkit:store';\n\n    var storage = {},\n    client = redis.createClient(config), // could pass specific redis config here\n    methods = config.methods || ['teams', 'users', 'channels'];\n\n    // Implements required API methods\n    for (var i = 0; i < methods.length; i++) {\n        storage[methods[i]] = function(hash) {\n            return {\n                get: function(id, cb) {\n                    client.hget(config.namespace + ':' + hash, id, function(err, res) {\n                        cb(err, JSON.parse(res));\n                    });\n                },\n                save: function(object, cb) {\n                    if (!object.id) // Silently catch this error?\n                        return cb(new Error('The given object must have an id property'), {});\n                    client.hset(config.namespace + ':' + hash, object.id, JSON.stringify(object), cb);\n                },\n                all: function(cb, options) {\n                    client.hgetall(config.namespace + ':' + hash, function(err, res) {\n                        if (err)\n                        return cb(err, {});\n\n                        if (null === res)\n                        return cb(err, res);\n\n                        var parsed;\n                        var array = [];\n\n                        for (var i in res) {\n                            parsed = JSON.parse(res[i]);\n                            res[i] = parsed;\n                            array.push(parsed);\n                        }\n\n                        cb(err, options && options.type === 'object' ? res : array);\n                    });\n                },\n                allById: function(cb) {\n                    this.all(cb, {type: 'object'});\n                }\n            };\n        }(methods[i]);\n    }\n    return storage;\n};\n"
  },
  {
    "path": "lib/storage/simple_storage.js",
    "content": "/*\nStorage module for bots.\n\nSupports storage of data on a team-by-team, user-by-user, and chnnel-by-channel basis.\n\nsave can be used to store arbitrary object.\nThese objects must include an id by which they can be looked up.\nIt is recommended to use the team/user/channel id for this purpose.\nExample usage of save:\ncontroller.storage.teams.save({id: message.team, foo:\"bar\"}, function(err){\n  if (err)\n    console.log(err)\n});\n\nget looks up an object by id.\nExample usage of get:\ncontroller.storage.teams.get(message.team, function(err, team_data){\n  if (err)\n    console.log(err)\n  else\n    console.log(team_data)\n});\n*/\n\nvar Store = require('jfs');\n\nmodule.exports = function(config) {\n\n    if (!config) {\n        config = {\n            path: './',\n        };\n    }\n\n    var teams_db = new Store(config.path + '/teams', {saveId: 'id'});\n    var users_db = new Store(config.path + '/users', {saveId: 'id'});\n    var channels_db = new Store(config.path + '/channels', {saveId: 'id'});\n\n    var objectsToList = function(cb) {\n        return function(err, data) {\n            if (err) {\n                cb(err, data);\n            } else {\n                cb(err, Object.keys(data).map(function(key) {\n                    return data[key];\n                }));\n            }\n        };\n    };\n\n    var storage = {\n        teams: {\n            get: function(team_id, cb) {\n                teams_db.get(team_id, cb);\n            },\n            save: function(team_data, cb) {\n                teams_db.save(team_data.id, team_data, cb);\n            },\n            all: function(cb) {\n                teams_db.all(objectsToList(cb));\n            }\n        },\n        users: {\n            get: function(user_id, cb) {\n                users_db.get(user_id, cb);\n            },\n            save: function(user, cb) {\n                users_db.save(user.id, user, cb);\n            },\n            all: function(cb) {\n                users_db.all(objectsToList(cb));\n            }\n        },\n        channels: {\n            get: function(channel_id, cb) {\n                channels_db.get(channel_id, cb);\n            },\n            save: function(channel, cb) {\n                channels_db.save(channel.id, channel, cb);\n            },\n            all: function(cb) {\n                channels_db.all(objectsToList(cb));\n            }\n        }\n    };\n\n    return storage;\n};\n"
  },
  {
    "path": "lib/storage/storage_test.js",
    "content": "/*\nTests for storage modules.\nThis file currently test simple_storage.js, redis_storage, and firebase_storage.\n\nIf you build a new storage module,\nyou must add it to this test file before your PR will be considered.\nHow to add it to this test file:\n\nAdd the following to the bottom of this file:\n\n// Test <your_storage_module>\n<your_storage_module> = require('./<your_storage_module>.js')(<appropriate config object for your storage module>);\ncheck(<your_storage_module>.users);\ncheck(<your_storage_module>.channels);\ncheck(<your_storage_module>.teams);\n*/\n\nvar test = require('unit.js');\n\ntestObj0 = {id: 'TEST0', foo: 'bar0'};\ntestObj1 = {id: 'TEST1', foo: 'bar1'};\n\nvar testStorageMethod = function(storageMethod) {\n    storageMethod.save(testObj0, function(err) {\n        test.assert(!err);\n        storageMethod.save(testObj1, function(err) {\n            test.assert(!err);\n            storageMethod.get(testObj0.id, function(err, data) {\n                test.assert(!err);\n                console.log(data);\n                test.assert(data.foo === testObj0.foo);\n            });\n            storageMethod.all(function(err, data) {\n                test.assert(!err);\n                console.log(data);\n                test.assert(\n                    data[0].foo === testObj0.foo && data[1].foo === testObj1.foo ||\n                    data[0].foo === testObj1.foo && data[1].foo === testObj0.foo\n                );\n            });\n        });\n    });\n};\n\nconsole.log('If no asserts failed then the test has passed!');\n\n// Test simple_storage\nvar simple_storage = require('./simple_storage.js')();\ntestStorageMethod(simple_storage.users);\ntestStorageMethod(simple_storage.channels);\ntestStorageMethod(simple_storage.teams);\n\n// Test redis_storage\nvar redis_storage = require('./redis_storage.js')({\n    url: 'redis://redistogo:d175f29259bd73e442eefcaeff8e78aa@tarpon.redistogo.com:11895/'\n});\ntestStorageMethod(redis_storage.users);\ntestStorageMethod(redis_storage.channels);\ntestStorageMethod(redis_storage.teams);\n\n// Test firebase_storage\nvar firebase_storage = require('./firebase_storage.js')({\n    firebase_uri: 'https://botkit-example.firebaseio.com'\n});\ntestStorageMethod(firebase_storage.users);\ntestStorageMethod(firebase_storage.channels);\ntestStorageMethod(firebase_storage.teams);\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"slackshell\",\n  \"version\": \"1.0.0\",\n  \"description\": \"BASH in slack\",\n  \"main\": \"bot.js\",\n  \"dependencies\": {\n    \"body-parser\": \"^1.14.2\",\n    \"express\": \"^4.13.3\",\n    \"jfs\": \"^0.2.6\",\n    \"mustache\": \"^2.2.1\",\n    \"request\": \"^2.67.0\",\n    \"ws\": \"^1.0.1\",\n    \"https-proxy-agent\": \"^1.0.0\"\n  },\n  \"devDependencies\": {\n    \"jscs\": \"^2.7.0\",\n    \"mocha\": \"^2.4.5\",\n    \"should\": \"^8.0.2\",\n    \"winston\": \"^2.1.1\"\n  },\n  \"scripts\": {\n    \"pretest\": \"jscs ./lib/\",\n    \"test\": \"mocha tests/*.js\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/dtesler/slackshell.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/dtesler/slackshell/issues\"\n  },\n  \"homepage\": \"https://github.com/dtesler/slackshell\",\n  \"keywords\": [\n    \"bots\",\n    \"chatbots\",\n    \"slack\"\n  ],\n  \"license\": \"ISC\",\n  \"directories\": {\n    \"test\": \"tests\"\n  }\n}\n"
  },
  {
    "path": "tests/Slack_web_api.js",
    "content": "var should = require('should');\nvar Botkit = require('../');\nvar path = require('path');\nvar tmpdir = require('os').tmpdir();\nvar fs = require('fs');\nvar winston = require('winston');\n\nvar token = process.env.TOKEN;\n\ndescribe('Test', function() {\n    it('should have a token', function(done) {\n        should.exist(token);\n        done();\n    });\n\n    it('should have Botkit instance', function(done) {\n        should.exist(Botkit);\n        should.exist(Botkit.core);\n        should.exist(Botkit.slackbot);\n        done();\n    });\n});\n\ndescribe('Botkit', function() {\n    this.timeout(5000);\n\n    it('should start and then stop', function(done) {\n        var controller = Botkit.slackbot({debug: false});\n        var openIsCalled = false;\n\n        controller.on('rtm_open', function(bot) {\n            should.exist(bot);\n            openIsCalled = true;\n        });\n\n        controller.on('rtm_close', function(bot) {\n            should.exist(bot);\n            openIsCalled.should.be.true;\n            controller.shutdown();\n            done();\n        });\n\n        controller\n            .spawn({\n                token: token\n            })\n            .startRTM(function(err, bot, payload) {\n                (err === null).should.be.true;\n                should.exist(bot);\n                bot.closeRTM();\n            });\n    });\n\n    it('should have fail with false token', function(done) {\n        this.timeout(5000);\n\n        var controller = Botkit.slackbot({debug: false});\n\n        controller\n            .spawn({\n                token: '1234'\n            })\n            .startRTM(function(err, bot, payload) {\n                should.exist(err);\n\n                controller.shutdown();\n                done();\n            });\n    });\n});\n\ndescribe('Log', function() {\n    it('should use an external logging provider', function(done) {\n        var logFile = path.join(tmpdir, 'botkit.log');\n        var logger = new winston.Logger({\n            transports: [\n                new (winston.transports.File)({ filename: logFile })\n            ]\n        });\n\n        logger.cli();\n\n        var controller = Botkit.slackbot({\n                debug: true,\n                logger: logger\n            });\n\n        controller\n            .spawn({\n                token: '1234'\n            })\n            .startRTM(function(err, bot, payload) {\n                should.exist(err);\n\n                controller.shutdown();\n\n                fs.readFile(logFile, 'utf8', function(err, res) {\n                    (err === null).should.be.true;\n                    should.exist(res);\n                    done();\n                });\n            });\n    });\n});\n"
  }
]