Full Code of dtesler/slackshell for AI

master 11e9862bcf06 cached
15 files
91.7 KB
18.6k tokens
5 symbols
1 requests
Download .txt
Repository: dtesler/slackshell
Branch: master
Commit: 11e9862bcf06
Files: 15
Total size: 91.7 KB

Directory structure:
gitextract_jrepep98/

├── .gitignore
├── README.md
├── bot.js
├── lib/
│   ├── Botkit.js
│   ├── CoreBot.js
│   ├── SlackBot.js
│   ├── Slack_web_api.js
│   ├── Slackbot_worker.js
│   ├── console_logger.js
│   └── storage/
│       ├── firebase_storage.js
│       ├── redis_storage.js
│       ├── simple_storage.js
│       └── storage_test.js
├── package.json
└── tests/
    └── Slack_web_api.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
node_modules/
start.sh
start_button.sh
db/
.DS_Store
*/.DS_Store
.env


================================================
FILE: README.md
================================================
## Slackshell
BASH terminal for your slack team


### Getting started

Clone the repository on to your desired server (Ubuntu 14.04 Recommended)

``` shell
git clone https://github.com/dtesler/slackshell.git
```

next, create a bot for your team at https://my.slack.com/services/new/bot

![Creating a bot](http://i.imgur.com/M7XqlcT.gif)

Once you have created the bot, copy your token

![Bot Token](http://i.imgur.com/cBmDAuV.png)

and then execute the following in your project directory

``` shell
token={token} node bot.js
```

where *{token}* is your token that you previously copied

You 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.

![Slackshell test](http://i.imgur.com/oqeX8KF.gif)

###### please contribute to my crap code


================================================
FILE: bot.js
================================================
var c = require('child_process');

if (!process.env.token) {
    console.log('Error: Specify token in environment');
    process.exit(1);
}

var Botkit = require('./lib/Botkit.js');
var os = require('os');

var controller = Botkit.slackbot({
    debug: false,
});

var bot = controller.spawn({
    token: process.env.token
}).startRTM();

function getUID(user, callback) {
    c.exec('id ' + user, function (err, response, stderr) {
        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
            callback(false); // Basically
        }
        else {
            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
        }
    });
}

controller.hears([''],'direct_message,direct_mention',function(bot, message) {

    bot.api.reactions.add({ // Add a heart cause why the hell not
        timestamp: message.ts,
        channel: message.channel,
        name: 'heart',
    },function(err, res) {
        if (err) {
            bot.botkit.log('Failed to add emoji reaction :(',err);
        }
    });

    getUID(message.user, function (uid) {
        if (!uid) {
            // Add user
            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) {
                if (err) {
                    bot.reply(message, err);
                }
                else {
                    console.log('shell user created:', message.user);
                    getUID(message.user, function (uid) {
                        c.exec(message.text, {uid:uid}, function (err, stdout, stderr) {
                            if (err) bot.reply(message, err);
                            else bot.reply(message, stdout);
                        });
                    });
                }
            });
        }
        else {
            c.exec(message.text, {uid:uid}, function (err, stdout, stderr) {
                if (err) bot.reply(message, err);
                else bot.reply(message, stdout);
            });
        }
    });
});


================================================
FILE: lib/Botkit.js
================================================
var CoreBot = require(__dirname + '/CoreBot.js');
var Slackbot = require(__dirname + '/SlackBot.js');

module.exports = {
    core: CoreBot,
    slackbot: Slackbot,
};


================================================
FILE: lib/CoreBot.js
================================================
/**
 * This is a module that makes a bot
 * It expects to receive messages via the botkit.receiveMessage function
 * These messages are expected to match Slack's message format.
 **/
var mustache = require('mustache');
var simple_storage = require(__dirname + '/storage/simple_storage.js');
var ConsoleLogger = require(__dirname + '/console_logger.js');
var LogLevels = ConsoleLogger.LogLevels;

function Botkit(configuration) {
    var botkit = {
        events: {}, // this will hold event handlers
        config: {}, // this will hold the configuration
        tasks: [],
        taskCount: 0,
        convoCount: 0,
        memory_store: {
            users: {},
            channels: {},
            teams: {},
        }
    };

    botkit.utterances = {
        yes: new RegExp(/^(yes|yea|yup|yep|ya|sure|ok|y|yeah|yah)/i),
        no: new RegExp(/^(no|nah|nope|n)/i),
    };

    function Conversation(task, message) {

        this.messages = [];
        this.sent = [];
        this.transcript = [];

        this.events = {};

        this.vars = {};

        this.topics = {};
        this.topic = null;

        this.status = 'new';
        this.task = task;
        this.source_message = message;
        this.handler = null;
        this.responses = {};
        this.capture_options = {};
        this.startTime = new Date();
        this.lastActive = new Date();

        this.capture = function(response) {
            var capture_key = this.sent[this.sent.length - 1].text;

            response.text = response.text.trim();

            if (this.capture_options.key) {
                capture_key = this.capture_options.key;
            }

            // capture the question that was asked
            // if text is an array, get 1st
            if (typeof(this.sent[this.sent.length - 1].text) == 'string') {
                response.question = this.sent[this.sent.length - 1].text;
            } else {
                response.question = this.sent[this.sent.length - 1].text[0];
            }

            if (this.capture_options.multiple) {
                if (!this.responses[capture_key]) {
                    this.responses[capture_key] = [];
                }
                this.responses[capture_key].push(response);
            } else {
                this.responses[capture_key] = response;
            }

        };

        this.handle = function(message) {

            this.lastActive = new Date();
            this.transcript.push(message);
            botkit.debug('HANDLING MESSAGE IN CONVO', message);
            // do other stuff like call custom callbacks
            if (this.handler) {
                this.capture(message);

                // if the handler is a normal function, just execute it!
                // NOTE: anyone who passes in their own handler has to call
                // convo.next() to continue after completing whatever it is they want to do.
                if (typeof(this.handler) == 'function') {
                    this.handler(message, this);
                } else {
                    // handle might be a mapping of keyword to callback.
                    // lets see if the message matches any of the keywords
                    var match, patterns = this.handler;
                    for (var p = 0; p < patterns.length; p++) {
                        if (patterns[p].pattern && (match = message.text.match(patterns[p].pattern))) {
                            message.match = match;
                            patterns[p].callback(message, this);
                            return;
                        }
                    }

                    // none of the messages matched! What do we do?
                    // if a default exists, fire it!
                    for (var p = 0; p < patterns.length; p++) {
                        if (patterns[p].default) {
                            patterns[p].callback(message, this);
                            return;
                        }
                    }

                }
            } else {
                // do nothing
            }
        };

        this.activate = function() {
            this.status = 'active';
        };

        /**
         * active includes both ACTIVE and ENDING
         * in order to allow the timeout end scripts to play out
         **/
        this.isActive = function() {
            return (this.status == 'active' || this.status == 'ending');
        };

        this.deactivate = function() {
            this.status = 'inactive';
        };

        this.say = function(message) {
            this.addMessage(message);
        };

        this.sayFirst = function(message) {
            if (typeof(message) == 'string') {
                message = {
                    text: message,
                    channel: this.source_message.channel,
                };
            } else {
                message.channel = this.source_message.channel;
            }
            this.messages.unshift(message);
        };


        this.on = function(event, cb) {
            botkit.debug('Setting up a handler for', event);
            var events = event.split(/\,/g);
            for (var e in events) {
                if (!this.events[events[e]]) {
                    this.events[events[e]] = [];
                }
                this.events[events[e]].push(cb);
            }
            return this;
        };

        this.trigger = function(event, data) {
            if (this.events[event]) {
                for (var e = 0; e < this.events[event].length; e++) {
                    var res = this.events[event][e].apply(this, data);
                    if (res === false) {
                        return;
                    }
                }
            } else {
                botkit.debug('No handler for', event);
            }
        };

        // proceed to the next message after waiting for an answer
        this.next = function() {
            this.handler = null;
        };

        this.repeat = function() {
            if (this.sent.length) {
                this.messages.push(this.sent[this.sent.length - 1]);
            } else {
                // console.log('TRIED TO REPEAT, NOTHING TO SAY');
            }
        };

        this.silentRepeat = function() {
            return;
        };

        this.addQuestion = function(message, cb, capture_options, topic) {
            if (typeof(message) == 'string') {
                message = {
                    text: message,
                    channel: this.source_message.channel
                };
            } else {
                message.channel = this.source_message.channel;
            }

            if (capture_options) {
                message.capture_options = capture_options;
            }

            message.handler = cb;
            this.addMessage(message, topic);
        };


        this.ask = function(message, cb, capture_options) {
            this.addQuestion(message, cb, capture_options, this.topic || 'default');
        };

        this.addMessage = function(message, topic) {
            if (!topic) {
                topic = this.topic;
            }
            if (typeof(message) == 'string') {
                message = {
                    text: message,
                    channel: this.source_message.channel,
                };
            } else {
                message.channel = this.source_message.channel;
            }

            if (!this.topics[topic]) {
                this.topics[topic] = [];
            }
            this.topics[topic].push(message);

            // this is the current topic, so add it here as well
            if (this.topic == topic) {
                this.messages.push(message);
            }
        };

        this.changeTopic = function(topic) {
            this.topic = topic;

            if (!this.topics[topic]) {
                this.topics[topic] = [];
            }
            this.messages = this.topics[topic].slice();

            this.handler = null;
        };

        this.combineMessages = function(messages) {
            if (!messages) {
                return '';
            };
            if (messages.length > 1) {
                var txt = [];
                var last_user = null;
                var multi_users = false;
                last_user = messages[0].user;
                for (var x = 0; x < messages.length; x++) {
                    if (messages[x].user != last_user) {
                        multi_users = true;
                    }
                }
                last_user = '';
                for (var x = 0; x < messages.length; x++) {
                    if (multi_users && messages[x].user != last_user) {
                        last_user = messages[x].user;
                        if (txt.length) {
                            txt.push('');
                        }
                        txt.push('<@' + messages[x].user + '>:');
                    }
                    txt.push(messages[x].text);
                }
                return txt.join('\n');
            } else {
                if (messages.length) {
                    return messages[0].text;
                } else {
                    return messages.text;
                }
            }
        };

        this.getResponses = function() {

            var res = {};
            for (var key in this.responses) {

                res[key] = {
                    question: this.responses[key].length ?
                     this.responses[key][0].question : this.responses[key].question,
                    key: key,
                    answer: this.extractResponse(key),
                };
            }
            return res;
        };

        this.getResponsesAsArray = function() {

            var res = [];
            for (var key in this.responses) {

                res.push({
                    question: this.responses[key].length ?
                     this.responses[key][0].question : this.responses[key].question,
                    key: key,
                    answer: this.extractResponse(key),
                });
            }
            return res;
        };


        this.extractResponses = function() {

            var res = {};
            for (var key in this.responses) {
                res[key] = this.extractResponse(key);
            }
            return res;
        };

        this.extractResponse = function(key) {
            return this.combineMessages(this.responses[key]);
        };

        this.replaceTokens = function(text) {

            var vars = {
                identity: this.task.bot.identity,
                responses: this.extractResponses(),
                origin: this.task.source_message,
                vars: this.vars,
            };
            return mustache.render(text, vars);
        };

        this.stop = function(status) {
            this.handler = null;
            this.messages = [];
            this.status = status || 'stopped';
            botkit.debug('Conversation is over!');
            this.task.conversationEnded(this);
        };

        this.tick = function() {
            var now = new Date();

            if (this.isActive()) {
                if (this.handler) {
                    // check timeout!
                    // how long since task started?
                    var duration = (now.getTime() - this.task.startTime.getTime());
                    // how long since last active?
                    var lastActive = (now.getTime() - this.lastActive.getTime());

                    if (this.task.timeLimit && // has a timelimit
                        (duration > this.task.timeLimit) && // timelimit is up
                        (lastActive > this.task.timeLimit) // nobody has typed for 60 seconds at least
                    ) {

                        if (this.topics.timeout) {
                            this.status = 'ending';
                            this.changeTopic('timeout');
                        } else {
                            this.stop('timeout');
                        }
                    }
                    // otherwise do nothing
                } else {
                    if (this.messages.length) {
                        if (typeof(this.messages[0].timestamp) == 'undefined' ||
                            this.messages[0].timestamp <= now.getTime()) {
                            var message = this.messages.shift();
                            //console.log('HANDLING NEW MESSAGE',message);
                            // make sure next message is delayed appropriately
                            if (this.messages.length && this.messages[0].delay) {
                                this.messages[0].timestamp = now.getTime() + this.messages[0].delay;
                            }
                            if (message.handler) {
                                //console.log(">>>>>> SET HANDLER IN TICK");
                                this.handler = message.handler;
                            } else {
                                this.handler = null;
                                //console.log(">>>>>>> CLEARING HANDLER BECAUSE NO HANDLER NEEDED");
                            }
                            if (message.capture_options) {
                                this.capture_options = message.capture_options;
                            } else {
                                this.capture_options = {};
                            }

                            this.sent.push(message);
                            this.transcript.push(message);
                            this.lastActive = new Date();

                            if (message.text || message.attachments) {

                                // clone this object so as not to modify source
                                var outbound = JSON.parse(JSON.stringify(message));

                                if (typeof(message.text) == 'string') {
                                    outbound.text = this.replaceTokens(message.text);
                                } else {
                                    outbound.text = this.replaceTokens(
                                        message.text[Math.floor(Math.random() * message.text.length)]
                                    );
                                }

                                if (this.messages.length && !message.handler) {
                                    outbound.continue_typing = true;
                                }

                                if (typeof(message.attachments) == 'function') {
                                    outbound.attachments = message.attachments(this);
                                }

                                this.task.bot.say(outbound, function(err) {
                                    if (err) {
                                        botkit.log('An error occurred while sending a message: ', err);
                                    }
                                });
                            }
                            if (message.action) {
                                if (typeof(message.action) == 'function') {
                                    message.action(this);
                                } else if (message.action == 'repeat') {
                                    this.repeat();
                                } else if (message.action == 'wait') {
                                    this.silentRepeat();
                                } else if (message.action == 'stop') {
                                    this.stop();
                                } else if (message.action == 'timeout') {
                                    this.stop('timeout');
                                } else if (this.topics[message.action]) {
                                    this.changeTopic(message.action);
                                }
                            }
                        } else {
                            //console.log('Waiting to send next message...');
                        }

                        // end immediately instad of waiting til next tick.
                        // if it hasn't already been ended by a message action!
                        if (this.isActive() && !this.messages.length && !this.handler) {
                            this.status = 'completed';
                            botkit.debug('Conversation is over!');
                            this.task.conversationEnded(this);
                        }

                    } else if (this.sent.length) { // sent at least 1 message
                        this.status = 'completed';
                        botkit.debug('Conversation is over!');
                        this.task.conversationEnded(this);
                    }
                }
            }
        };

        botkit.debug('CREATED A CONVO FOR', this.source_message.user, this.source_message.channel);
        this.changeTopic('default');
    };

    function Task(bot, message, botkit) {

        this.convos = [];
        this.botkit = botkit;
        this.bot = bot;

        this.events = {};
        this.source_message = message;
        this.status = 'active';
        this.startTime = new Date();

        this.isActive = function() {
            return this.status == 'active';
        };

        this.startConversation = function(message) {
            var convo = new Conversation(this, message);
            convo.id = botkit.convoCount++;

            botkit.log('>   [Start] ', convo.id, ' Conversation with ', message.user, 'in', message.channel);

            convo.activate();
            this.convos.push(convo);
            this.trigger('conversationStarted', [convo]);
            return convo;
        };

        this.conversationEnded = function(convo) {
            botkit.log('>   [End] ', convo.id, ' Conversation with ',
                       convo.source_message.user, 'in', convo.source_message.channel);
            this.trigger('conversationEnded', [convo]);
            convo.trigger('end', [convo]);
            var actives = 0;
            for (var c = 0; c < this.convos.length; c++) {
                if (this.convos[c].isActive()) {
                    actives++;
                }
            }
            if (actives == 0) {
                this.taskEnded();
            }

        };

        this.endImmediately = function(reason) {

            for (var c = 0; c < this.convos.length; c++) {
                if (this.convos[c].isActive()) {
                    this.convos[c].stop(reason || 'stopped');
                }
            }

        };

        this.taskEnded = function() {
            botkit.log('[End] ', this.id, ' Task for ',
                this.source_message.user, 'in', this.source_message.channel);

            this.status = 'completed';
            this.trigger('end', [this]);

        };

        this.on = function(event, cb) {
            botkit.debug('Setting up a handler for', event);
            var events = event.split(/\,/g);
            for (var e in events) {
                if (!this.events[events[e]]) {
                    this.events[events[e]] = [];
                }
                this.events[events[e]].push(cb);
            }
            return this;
        };

        this.trigger = function(event, data) {
            if (this.events[event]) {
                for (var e = 0; e < this.events[event].length; e++) {
                    var res = this.events[event][e].apply(this, data);
                    if (res === false) {
                        return;
                    }
                }
            } else {
                botkit.debug('No handler for', event);
            }
        };


        this.getResponsesByUser = function() {

            var users = {};

            // go through all conversations
            // extract normalized answers
            for (var c = 0; c < this.convos.length; c++) {

                var user = this.convos[c].source_message.user;
                users[this.convos[c].source_message.user] = {};
                var convo = this.convos[c];
                users[user] = convo.extractResponses();
            }

            return users;

        };

        this.getResponsesBySubject = function() {
            var answers = {};

            // go through all conversations
            // extract normalized answers
            for (var c = 0; c < this.convos.length; c++) {
                var convo = this.convos[c];

                for (var key in convo.responses) {
                    if (!answers[key]) {
                        answers[key] = {};
                    }
                    answers[key][convo.source_message.user] = convo.extractResponse(key);
                }
            }

            return answers;
        };

        this.tick = function() {

            for (var c = 0; c < this.convos.length; c++) {
                if (this.convos[c].isActive()) {
                    this.convos[c].tick();
                }
            }
        };
    };

    botkit.storage = {
        teams: {
            get: function(team_id, cb) {
                cb(null, botkit.memory_store.teams[team_id]);
            },
            save: function(team, cb) {
                botkit.log('Warning: using temporary storage. Data will be lost when process restarts.');
                if (team.id) {
                    botkit.memory_store.teams[team.id] = team;
                    cb(null, team.id);
                } else {
                    cb('No ID specified');
                }
            },
            all: function(cb) {
                cb(null, botkit.memory_store.teams);
            }
        },
        users: {
            get: function(user_id, cb) {
                cb(null, botkit.memory_store.users[user_id]);
            },
            save: function(user, cb) {
                botkit.log('Warning: using temporary storage. Data will be lost when process restarts.');
                if (user.id) {
                    botkit.memory_store.users[user.id] = user;
                    cb(null, user.id);
                } else {
                    cb('No ID specified');
                }
            },
            all: function(cb) {
                cb(null, botkit.memory_store.users);
            }
        },
        channels: {
            get: function(channel_id, cb) {
                cb(null, botkit.memory_store.channels[channel_id]);
            },
            save: function(channel, cb) {
                botkit.log('Warning: using temporary storage. Data will be lost when process restarts.');
                if (channel.id) {
                    botkit.memory_store.channels[channel.id] = channel;
                    cb(null, channel.id);
                } else {
                    cb('No ID specified');
                }
            },
            all: function(cb) {
                cb(null, botkit.memory_store.channels);
            }
        }
    };

    botkit.debug = function() {
        if (configuration.debug) {
            var args = [];
            for (var k = 0; k < arguments.length; k++) {
                args.push(arguments[k]);
            }
            console.log.apply(null, args);
        }
    };

    botkit.log = function() {
        if (configuration.log || configuration.log === undefined) { //default to true
            var args = [];
            for (var k = 0; k < arguments.length; k++) {
                args.push(arguments[k]);
            }
            console.log.apply(null, args);
        }
    };

    botkit.hears = function(keywords, events, cb) {
        if (typeof(keywords) == 'string') {
            keywords = [keywords];
        }
        if (typeof(events) == 'string') {
            events = events.split(/\,/g);
        }

        var match;
        for (var k = 0; k < keywords.length; k++) {
            var keyword = keywords[k];
            for (var e = 0; e < events.length; e++) {
                (function(keyword) {
                    botkit.on(events[e], function(bot, message) {
                        if (message.text) {
                            if (match = message.text.match(new RegExp(keyword, 'i'))) {
                                botkit.debug('I HEARD', keyword);
                                message.match = match;
                                cb.apply(this, [bot, message]);
                                return false;
                            }
                        }
                    });
                })(keyword);
            }
        }
        return this;
    };

    botkit.on = function(event, cb) {
        botkit.debug('Setting up a handler for', event);
        var events = (typeof(event) == 'string') ? event.split(/\,/g) : event;

        for (var e in events) {
            if (!this.events[events[e]]) {
                this.events[events[e]] = [];
            }
            this.events[events[e]].push(cb);
        }
        return this;
    };

    botkit.trigger = function(event, data) {
        if (this.events[event]) {
            for (var e = 0; e < this.events[event].length; e++) {
                var res = this.events[event][e].apply(this, data);
                if (res === false) {
                    return;
                }
            }
        } else {
            botkit.debug('No handler for', event);
        }
    };

    botkit.startConversation = function(bot, message, cb) {
        botkit.startTask(bot, message, function(task, convo) {
            cb(null, convo);
        });
    };

    botkit.defineBot = function(unit) {
        if (typeof(unit) != 'function') {
            throw new Error('Bot definition must be a constructor function');
        }
        this.worker = unit;
    };

    botkit.spawn = function(config, cb) {
        var worker = new this.worker(this, config);
        if (cb) { cb(worker); }
        return worker;
    };

    botkit.startTicking = function() {
        if (!botkit.tickInterval) {
            // set up a once a second tick to process messages
            botkit.tickInterval = setInterval(function() {
                botkit.tick();
            }, 1000);
        }
    };

    botkit.shutdown = function() {
        if (botkit.tickInterval) {
            clearInterval(botkit.tickInterval);
        }
    };

    botkit.startTask = function(bot, message, cb) {


        var task = new Task(bot, message, this);

        task.id = botkit.taskCount++;
        botkit.log('[Start] ', task.id, ' Task for ', message.user, 'in', message.channel);

        var convo = task.startConversation(message);

        this.tasks.push(task);

        if (cb) {
            cb(task, convo);
        } else {
            return task;
        }

    };

    botkit.receiveMessage = function(bot, message) {
        botkit.debug('RECEIVED MESSAGE');
        bot.findConversation(message, function(convo) {
            if (convo) {
                convo.handle(message);
            } else {
                botkit.trigger('message_received', [bot, message]);
            }
        });
    };

    botkit.tick = function() {
        for (var t = 0; t < botkit.tasks.length; t++) {
            botkit.tasks[t].tick();
        }
        for (var t = botkit.tasks.length - 1; t >= 0; t--) {
            if (!botkit.tasks[t].isActive()) {
                botkit.tasks.splice(t, 1);
            }
        }


        this.trigger('tick', []);

    };


    /**
     * Define a default worker bot. This function should be customized outside
     * of Botkit and passed in as a parameter by the developer
     **/
    botkit.worker = function(botkit, config) {
        this.botkit = botkit;
        this.config = config;

        this.say = function(message, cb) {
            botkit.debug('SAY:', message);
        };

        this.replyWithQuestion = function(message, question, cb) {

            botkit.startConversation(message, function(convo) {
                convo.ask(question, cb);
            });

        };

        this.reply = function(src, resp) {
            botkit.debug('REPLY:', resp);
        };


        this.findConversation = function(message, cb) {
            botkit.debug('DEFAULT FIND CONVO');
            cb(null);
        };
    };

    botkit.config = configuration;

    if (!configuration.logLevel) {
        if (configuration.debug) {
            configuration.logLevel = 'debug';
        } else if (configuration.log === false) {
            configuration.logLevel = 'error';
        } else {
            configuration.logLevel = 'info';
        }
    }

    if (configuration.logger) {
        if (typeof configuration.logger.log === 'function') {
            botkit.logger = configuration.logger;
        } else {
            throw new Error('Logger object does not have a `log` method!');
        }
    } else {
        botkit.logger = ConsoleLogger(console, configuration.logLevel);
    }

    botkit.log = function() {
        botkit.log.info.apply(botkit.log, arguments);
    };
    Object.keys(LogLevels).forEach(function(level) {
        botkit.log[level] = botkit.logger.log.bind(botkit.logger, level);
    });
    botkit.debug = botkit.log.debug;

    if (configuration.storage) {
        if (
            configuration.storage.teams &&
            configuration.storage.teams.get &&
            configuration.storage.teams.save &&

            configuration.storage.users &&
            configuration.storage.users.get &&
            configuration.storage.users.save &&

            configuration.storage.channels &&
            configuration.storage.channels.get &&
            configuration.storage.channels.save
        ) {
            botkit.log('** Using custom storage system.');
            botkit.storage = configuration.storage;
        } else {
            throw new Error('Storage object does not have all required methods!');
        }
    } else if (configuration.json_file_store) {
        botkit.log('** Using simple storage. Saving data to ' + configuration.json_file_store);
        botkit.storage = simple_storage({path: configuration.json_file_store});
    } else {
        botkit.log('** No persistent storage method specified! Data may be lost when process shuts down.');
    }

    return botkit;
}

module.exports = Botkit;


================================================
FILE: lib/SlackBot.js
================================================
var Botkit = require(__dirname + '/CoreBot.js');
var request = require('request');
var express = require('express');
var bodyParser = require('body-parser');

function Slackbot(configuration) {

    // Create a core botkit bot
    var slack_botkit = Botkit(configuration || {});

    // customize the bot definition, which will be used when new connections
    // spawn!
    slack_botkit.defineBot(require(__dirname + '/Slackbot_worker.js'));

    // set up configuration for oauth
    // slack_app_config should contain
    // { clientId, clientSecret, scopes}
    // https://api.slack.com/docs/oauth-scopes
    slack_botkit.configureSlackApp = function(slack_app_config, cb) {

        slack_botkit.log('** Configuring app as a Slack App!');
        if (!slack_app_config || !slack_app_config.clientId ||
            !slack_app_config.clientSecret || !slack_app_config.scopes) {
            throw new Error('Missing oauth config details', bot);
        } else {
            slack_botkit.config.clientId = slack_app_config.clientId;
            slack_botkit.config.clientSecret = slack_app_config.clientSecret;
            if (slack_app_config.redirectUri) slack_botkit.config.redirectUri = slack_app_config.redirectUri;
            if (typeof(slack_app_config.scopes) == 'string') {
                slack_botkit.config.scopes = slack_app_config.scopes.split(/\,/);
            } else {
                slack_botkit.config.scopes = slack_app_config.scopes;
            }
            if (cb) cb(null, bot);
        }

        return slack_botkit;

    };

    // set up a web route that is a landing page
    slack_botkit.createHomepageEndpoint = function(webserver) {

        slack_botkit.log('** Serving app landing page at : http://MY_HOST:' + slack_botkit.config.port + '/');

        // FIX THIS!!!
        // this is obvs not right.
        webserver.get('/', function(req, res) {

            res.send('Howdy!');

        });

        return slack_botkit;

    };

    // set up a web route for receiving outgoing webhooks and/or slash commands
    slack_botkit.createWebhookEndpoints = function(webserver) {

        slack_botkit.log(
            '** Serving webhook endpoints for Slash commands and outgoing ' +
            'webhooks at: http://MY_HOST:' + slack_botkit.config.port + '/slack/receive');
        webserver.post('/slack/receive', function(req, res) {

            // this is a slash command
            if (req.body.command) {
                var message = {};

                for (var key in req.body) {
                    message[key] = req.body[key];
                }

                // let's normalize some of these fields to match the rtm message format
                message.user = message.user_id;
                message.channel = message.channel_id;

                slack_botkit.findTeamById(message.team_id, function(err, team) {
                    // FIX THIS
                    // this won't work for single team bots because the team info
                    // might not be in a db
                    if (err || !team) {
                        slack_botkit.log.error('Received slash command, but could not load team');
                    } else {
                        message.type = 'slash_command';
                        // HEY THERE
                        // Slash commands can actually just send back a response
                        // and have it displayed privately. That means
                        // the callback needs access to the res object
                        // to send an optional response.

                        res.status(200);

                        var bot = slack_botkit.spawn(team);

                        bot.team_info = team;
                        bot.res = res;

                        slack_botkit.receiveMessage(bot, message);

                    }
                });

            } else if (req.body.trigger_word) {

                var message = {};

                for (var key in req.body) {
                    message[key] = req.body[key];
                }

                // let's normalize some of these fields to match the rtm message format
                message.user = message.user_id;
                message.channel = message.channel_id;

                slack_botkit.findTeamById(message.team_id, function(err, team) {

                    // FIX THIS
                    // this won't work for single team bots because the team info
                    // might not be in a db
                    if (err || !team) {
                        slack_botkit.log.error('Received outgoing webhook but could not load team');
                    } else {
                        message.type = 'outgoing_webhook';


                        res.status(200);

                        var bot = slack_botkit.spawn(team);
                        bot.res = res;
                        bot.team_info = team;


                        slack_botkit.receiveMessage(bot, message);

                        // outgoing webhooks are also different. They can simply return
                        // a response instead of using the API to reply.  Maybe this is
                        // a different type of event!!

                    }
                });

            }

        });

        return slack_botkit;
    };

    slack_botkit.saveTeam = function(team, cb) {
        slack_botkit.storage.teams.save(team, cb);
    };

    // look up a team's memory and configuration and return it, or
    // return an error!
    slack_botkit.findTeamById = function(id, cb) {
        slack_botkit.storage.teams.get(id, cb);
    };

    slack_botkit.setupWebserver = function(port, cb) {

        if (!port) {
            throw new Error('Cannot start webserver without a port');
        }
        if (isNaN(port)) {
            throw new Error('Specified port is not a valid number');
        }

        slack_botkit.config.port = port;

        slack_botkit.webserver = express();
        slack_botkit.webserver.use(bodyParser.json());
        slack_botkit.webserver.use(bodyParser.urlencoded({ extended: true }));
        slack_botkit.webserver.use(express.static(__dirname + '/public'));

        var server = slack_botkit.webserver.listen(
            slack_botkit.config.port,
            function() {
                slack_botkit.log('** Starting webserver on port ' +
                    slack_botkit.config.port);
                if (cb) { cb(null, slack_botkit.webserver); }
            });

        return slack_botkit;

    };

    // get a team url to redirect the user through oauth process
    slack_botkit.getAuthorizeURL = function(team_id) {

        var url = 'https://slack.com/oauth/authorize';
        var scopes = slack_botkit.config.scopes;
        url = url + '?client_id=' + slack_botkit.config.clientId + '&scope=' +
            scopes.join(',') + '&state=botkit';

        if (team_id) {
            url = url + '&team=' + team_id;
        }
        if (slack_botkit.config.redirectUri) {
            url = url + '&redirect_uri=' + slack_botkit.config.redirectUri;
        }

        return url;

    };

    // set up a web route for redirecting users
    // and collecting authentication details
    // https://api.slack.com/docs/oauth
    // https://api.slack.com/docs/oauth-scopes
    slack_botkit.createOauthEndpoints = function(webserver, callback) {

        slack_botkit.log('** Serving login URL: http://MY_HOST:' + slack_botkit.config.port + '/login');

        if (!slack_botkit.config.clientId) {
            throw new Error(
                'Cannot create oauth endpoints without calling configureSlackApp() with a clientId first');
        }
        if (!slack_botkit.config.clientSecret) {
            throw new Error(
                'Cannot create oauth endpoints without calling configureSlackApp() with a clientSecret first');
        }
        if (!slack_botkit.config.scopes) {
            throw new Error(
                'Cannot create oauth endpoints without calling configureSlackApp() with a list of scopes first');
        }

        var call_api = function(command, options, cb) {
            slack_botkit.log('** API CALL: ' + 'https://slack.com/api/' + command);
            request.post('https://slack.com/api/' + command, function(error, response, body) {
                slack_botkit.debug('Got response', error, body);
                if (!error && response.statusCode == 200) {
                    var json = JSON.parse(body);
                    if (json.ok) {
                        if (cb) cb(null, json);
                    } else {
                        if (cb) cb(json.error, json);
                    }
                } else {
                    if (cb) cb(error);
                }
            }).form(options);
        };

        var oauth_access = function(options, cb) {
            call_api('oauth.access', options, cb);
        };

        var auth_test = function(options, cb) {
            call_api('auth.test', options, cb);
        };

        webserver.get('/login', function(req, res) {
            res.redirect(slack_botkit.getAuthorizeURL());
        });

        slack_botkit.log('** Serving oauth return endpoint: http://MY_HOST:' + slack_botkit.config.port + '/oauth');

        webserver.get('/oauth', function(req, res) {

            var code = req.query.code;
            var state = req.query.state;

            var opts = {
                client_id: slack_botkit.config.clientId,
                client_secret: slack_botkit.config.clientSecret,
                code: code
            };

            if (slack_botkit.config.redirectUri) opts.redirect_uri = slack_botkit.config.redirectUri;

            oauth_access(opts, function(err, auth) {

                if (err) {
                    if (callback) {
                        callback(err, req, res);
                    } else {
                        res.status(500).send(err);
                    }
                    slack_botkit.trigger('oauth_error', [err]);
                } else {

                    // auth contains at least:
                    // { access_token, scope, team_name}
                    // May also contain:
                    // { team_id } (not in incoming_webhook scope)
                    // info about incoming webhooks:
                    // { incoming_webhook: { url, channel, configuration_url} }
                    // might also include slash commands:
                    // { commands: ??}

                    // what scopes did we get approved for?
                    var scopes = auth.scope.split(/\,/);

                    // temporarily use the token we got from the oauth
                    // we need to call auth.test to make sure the token is valid
                    // but also so that we reliably have the team_id field!
                    //slack_botkit.config.token = auth.access_token;
                    auth_test({token: auth.access_token}, function(err, identity) {

                        if (err) {
                            if (callback) {
                                callback(err, req, res);
                            } else {
                                res.status(500).send(err);
                            }

                            slack_botkit.trigger('oauth_error', [err]);

                        } else {

                            // we need to deal with any team-level provisioning info
                            // like incoming webhooks and bot users
                            // and also with the personal access token from the user

                            slack_botkit.findTeamById(identity.team_id, function(err, team) {

                                var isnew = false;
                                if (!team) {
                                    isnew = true;
                                    team = {
                                        id: identity.team_id,
                                        createdBy: identity.user_id,
                                        url: identity.url,
                                        name: identity.team,
                                    };
                                }

                                var bot = slack_botkit.spawn(team);

                                if (auth.incoming_webhook) {
                                    auth.incoming_webhook.token = auth.access_token;
                                    auth.incoming_webhook.createdBy = identity.user_id;
                                    team.incoming_webhook = auth.incoming_webhook;
                                    bot.configureIncomingWebhook(team.incoming_webhook);
                                    slack_botkit.trigger('create_incoming_webhook', [bot, team.incoming_webhook]);
                                }

                                if (auth.bot) {
                                    team.bot = {
                                        token: auth.bot.bot_access_token,
                                        user_id: auth.bot.bot_user_id,
                                        createdBy: identity.user_id,
                                    };
                                    bot.configureRTM(team.bot);
                                    slack_botkit.trigger('create_bot', [bot, team.bot]);
                                }

                                slack_botkit.saveTeam(team, function(err, id) {
                                    if (err) {
                                        slack_botkit.log.error('An error occurred while saving a team: ', err);
                                        if (callback) {
                                            callback(err, req, res);
                                        } else {
                                            res.status(500).send(err);
                                        }
                                        slack_botkit.trigger('error', [err]);
                                    } else {
                                        if (isnew) {
                                            slack_botkit.trigger('create_team', [bot, team]);
                                        } else {
                                            slack_botkit.trigger('update_team', [bot, team]);
                                        }

                                        slack_botkit.storage.users.get(identity.user_id, function(err, user) {
                                            isnew = false;
                                            if (!user) {
                                                isnew = true;
                                                user = {
                                                    id: identity.user_id,
                                                    access_token: auth.access_token,
                                                    scopes: scopes,
                                                    team_id: identity.team_id,
                                                    user: identity.user,
                                                };
                                            }
                                            slack_botkit.storage.users.save(user, function(err, id) {

                                                if (err) {
                                                    slack_botkit.log.error(
                                                        'An error occurred while saving a user: ', err);
                                                    if (callback) {
                                                        callback(err, req, res);
                                                    } else {
                                                        res.status(500).send(err);
                                                    }
                                                    slack_botkit.trigger('error', [err]);
                                                } else {
                                                    if (isnew) {
                                                        slack_botkit.trigger('create_user', [bot, user]);
                                                    } else {
                                                        slack_botkit.trigger('update_user', [bot, user]);
                                                    }
                                                    if (callback) {
                                                        callback(null, req, res);
                                                    } else {
                                                        res.redirect('/');
                                                    }
                                                }
                                            });
                                        });
                                    }
                                });
                            });
                        }
                    });
                }
            });
        });

        return slack_botkit;

    };

    slack_botkit.handleSlackEvents = function() {

        slack_botkit.log('** Setting up custom handlers for processing Slack messages');
        slack_botkit.on('message_received', function(bot, message) {


            if (message.ok != undefined) {
                // this is a confirmation of something we sent.
                return false;
            }

            slack_botkit.debug('DEFAULT SLACK MSG RECEIVED RESPONDER');
            if ('message' == message.type) {

                if (message.text) {
                    message.text = message.text.trim();
                }

                // set up a couple of special cases based on subtype
                if (message.subtype && message.subtype == 'channel_join') {
                    // someone joined. maybe do something?
                    if (message.user == bot.identity.id) {
                        slack_botkit.trigger('bot_channel_join', [bot, message]);
                        return false;
                    } else {
                        slack_botkit.trigger('user_channel_join', [bot, message]);
                        return false;
                    }
                } else if (message.subtype && message.subtype == 'group_join') {
                    // someone joined. maybe do something?
                    if (message.user == bot.identity.id) {
                        slack_botkit.trigger('bot_group_join', [bot, message]);
                        return false;
                    } else {
                        slack_botkit.trigger('user_group_join', [bot, message]);
                        return false;
                    }
                } else if (message.subtype) {
                    slack_botkit.trigger(message.subtype, [bot, message]);
                    return false;
                } else if (message.channel.match(/^D/)) {
                    // this is a direct message
                    if (message.user == bot.identity.id) {
                        return false;
                    }
                    if (!message.text) {
                        // message without text is probably an edit
                        return false;
                    }

                    // remove direct mention so the handler doesn't have to deal with it
                    var direct_mention = new RegExp('^\<\@' + bot.identity.id + '\>', 'i');
                    message.text = message.text.replace(direct_mention, '')
                    .replace(/^\s+/, '').replace(/^\:\s+/, '').replace(/^\s+/, '');

                    message.event = 'direct_message';

                    slack_botkit.trigger('direct_message', [bot, message]);
                    return false;

                } else {
                    if (message.user == bot.identity.id) {
                        return false;
                    }
                    if (!message.text) {
                        // message without text is probably an edit
                        return false;
                    }

                    var direct_mention = new RegExp('^\<\@' + bot.identity.id + '\>', 'i');
                    var mention = new RegExp('\<\@' + bot.identity.id + '\>', 'i');

                    if (message.text.match(direct_mention)) {
                        // this is a direct mention
                        message.text = message.text.replace(direct_mention, '')
                        .replace(/^\s+/, '').replace(/^\:\s+/, '').replace(/^\s+/, '');
                        message.event = 'direct_mention';

                        slack_botkit.trigger('direct_mention', [bot, message]);
                        return false;
                    } else if (message.text.match(mention)) {
                        message.event = 'mention';
                        slack_botkit.trigger('mention', [bot, message]);
                        return false;
                    } else {
                        message.event = 'ambient';
                        slack_botkit.trigger('ambient', [bot, message]);
                        return false;

                    }
                }
            } else {
                // this is a non-message object, so trigger a custom event based on the type
                slack_botkit.trigger(message.type, [bot, message]);
            }
        });
    };

    // set up the RTM message handlers once
    slack_botkit.handleSlackEvents();

    return slack_botkit;
};

module.exports = Slackbot;


================================================
FILE: lib/Slack_web_api.js
================================================
var request = require('request');

module.exports = function(bot, config) {

    // create a nice wrapper for the Slack API
    var slack_api = {
        api_url: 'https://slack.com/api/',
        // this is a simple function used to call the slack web API
        callAPI: function(command, options, cb) {
            bot.log('** API CALL: ' + slack_api.api_url + command);
            if (!options.token) {
                options.token = config.token;
            }
            bot.debug(command, options);
            request.post(this.api_url + command, function(error, response, body) {
                bot.debug('Got response', error, body);
                if (!error && response.statusCode == 200) {
                    var json = JSON.parse(body);
                    if (json.ok) {
                        if (cb) cb(null, json);
                    } else {
                        if (cb) cb(json.error, json);
                    }
                } else {
                    if (cb) cb(error);
                }
            }).form(options);
        },
        auth: {
            test: function(options, cb) {
                slack_api.callAPI('auth.test', options, cb);
            }
        },
        oauth: {
            access: function(options, cb) {
                slack_api.callAPIWithoutToken('oauth.access', options, cb);
            }
        },
        channels: {
            archive: function(options, cb) {
                slack_api.callAPI('channels.archive', options, cb);
            },
            create: function(options, cb) {
                slack_api.callAPI('channels.create', options, cb);
            },
            history: function(options, cb) {
                slack_api.callAPI('channels.history', options, cb);
            },
            info: function(options, cb) {
                slack_api.callAPI('channels.info', options, cb);
            },
            invite: function(options, cb) {
                slack_api.callAPI('channels.invite', options, cb);
            },
            join: function(options, cb) {
                slack_api.callAPI('channels.join', options, cb);
            },
            kick: function(options, cb) {
                slack_api.callAPI('channels.kick', options, cb);
            },
            leave: function(options, cb) {
                slack_api.callAPI('channels.leave', options, cb);
            },
            list: function(options, cb) {
                slack_api.callAPI('channels.list', options, cb);
            },
            mark: function(options, cb) {
                slack_api.callAPI('channels.mark', options, cb);
            },
            rename: function(options, cb) {
                slack_api.callAPI('channels.rename', options, cb);
            },
            setPurpose: function(options, cb) {
                slack_api.callAPI('channels.setPurpose', options, cb);
            },
            setTopic: function(options, cb) {
                slack_api.callAPI('channels.setTopic', options, cb);
            },
            unarchive: function(options, cb) {
                slack_api.callAPI('channels.unarchive', options, cb);
            }
        },
        chat: {
            delete: function(options, cb) {
                slack_api.callAPI('chat.delete', options, cb);
            },
            postMessage: function(options, cb) {
                if (options.attachments && typeof(options.attachments) != 'string') {
                    options.attachments = JSON.stringify(options.attachments);
                }
                slack_api.callAPI('chat.postMessage', options, cb);
            },
            update: function(options, cb) {
                slack_api.callAPI('chat.update', options, cb);
            }
        },
        emoji: {
            list: function(options, cb) {
                slack_api.callAPI('emoji.list', options, cb);
            }
        },
        files: {
            delete: function(options, cb) {
                slack_api.callAPI('files.delete', options, cb);
            },
            info: function(options, cb) {
                slack_api.callAPI('files.info', options, cb);
            },
            list: function(options, cb) {
                slack_api.callAPI('files.list', options, cb);
            },
            upload: function(options, cb) {
                slack_api.callAPI('files.upload', options, cb);
            },
        },
        groups: {
            archive: function(options, cb) {
                slack_api.callAPI('groups.archive', options, cb);
            },
            close: function(options, cb) {
                slack_api.callAPI('groups.close', options, cb);
            },
            create: function(options, cb) {
                slack_api.callAPI('groups.create', options, cb);
            },
            createChild: function(options, cb) {
                slack_api.callAPI('groups.createChild', options, cb);
            },
            history: function(options, cb) {
                slack_api.callAPI('groups.history', options, cb);
            },
            info: function(options, cb) {
                slack_api.callAPI('groups.info', options, cb);
            },
            invite: function(options, cb) {
                slack_api.callAPI('groups.invite', options, cb);
            },
            kick: function(options, cb) {
                slack_api.callAPI('groups.kick', options, cb);
            },
            leave: function(options, cb) {
                slack_api.callAPI('groups.leave', options, cb);
            },
            list: function(options, cb) {
                slack_api.callAPI('groups.list', options, cb);
            },
            mark: function(options, cb) {
                slack_api.callAPI('groups.mark', options, cb);
            },
            open: function(options, cb) {
                slack_api.callAPI('groups.open', options, cb);
            },
            rename: function(options, cb) {
                slack_api.callAPI('groups.rename', options, cb);
            },
            setPurpose: function(options, cb) {
                slack_api.callAPI('groups.setPurpose', options, cb);
            },
            setTopic: function(options, cb) {
                slack_api.callAPI('groups.setTopic', options, cb);
            },
            unarchive: function(options, cb) {
                slack_api.callAPI('groups.unarchive', options, cb);
            },
        },
        im: {
            close: function(options, cb) {
                slack_api.callAPI('im.close', options, cb);
            },
            history: function(options, cb) {
                slack_api.callAPI('im.history', options, cb);
            },
            list: function(options, cb) {
                slack_api.callAPI('im.list', options, cb);
            },
            mark: function(options, cb) {
                slack_api.callAPI('im.mark', options, cb);
            },
            open: function(options, cb) {
                slack_api.callAPI('im.open', options, cb);
            }
        },
        mpim: {
            close: function(options, cb) {
                slack_api.callAPI('mpim.close', options, cb);
            },
            history: function(options, cb) {
                slack_api.callAPI('mpim.history', options, cb);
            },
            list: function(options, cb) {
                slack_api.callAPI('mpim.list', options, cb);
            },
            mark: function(options, cb) {
                slack_api.callAPI('mpim.mark', options, cb);
            },
            open: function(options, cb) {
                slack_api.callAPI('mpim.open', options, cb);
            }
        },
        pins: {
            add: function(options, cb) {
                slack_api.callAPI('pins.add', options, cb);
            },
            list: function(options, cb) {
                slack_api.callAPI('pins.list', options, cb);
            },
            remove: function(options, cb) {
                slack_api.callAPI('pins.remove', options, cb);
            }
        },
        reactions: {
            add: function(options, cb) {
                slack_api.callAPI('reactions.add', options, cb);
            },
            get: function(options, cb) {
                slack_api.callAPI('reactions.get', options, cb);
            },
            list: function(options, cb) {
                slack_api.callAPI('reactions.list', options, cb);
            },
            remove: function(options, cb) {
                slack_api.callAPI('reactions.remove', options, cb);
            },
        },
        rtm: {
            start: function(options, cb) {
                slack_api.callAPI('rtm.start', options, cb);
            },
        },
        search: {
            all: function(options, cb) {
                slack_api.callAPI('search.all', options, cb);
            },
            files: function(options, cb) {
                slack_api.callAPI('search.files', options, cb);
            },
            messages: function(options, cb) {
                slack_api.callAPI('search.messages', options, cb);
            },
        },
        stars: {
            list: function(options, cb) {
                slack_api.callAPI('stars.list', options, cb);
            },
        },
        team: {
            accessLogs: function(options, cb) {
                slack_api.callAPI('team.accessLogs', options, cb);
            },
            info: function(options, cb) {
                slack_api.callAPI('team.info', options, cb);
            },
        },
        users: {
            getPresence: function(options, cb) {
                slack_api.callAPI('users.getPresence', options, cb);
            },
            info: function(options, cb) {
                slack_api.callAPI('users.info', options, cb);
            },
            list: function(options, cb) {
                slack_api.callAPI('users.list', options, cb);
            },
            setActive: function(options, cb) {
                slack_api.callAPI('users.setActive', options, cb);
            },
            setPresence: function(options, cb) {
                slack_api.callAPI('users.setPresence', options, cb);
            },
        }
    };

    return slack_api;

};


================================================
FILE: lib/Slackbot_worker.js
================================================
var Ws = require('ws');
var request = require('request');
var slackWebApi = require(__dirname + '/Slack_web_api.js');
var HttpsProxyAgent = require('https-proxy-agent');


module.exports = function(botkit, config) {
    var bot = {
        botkit: botkit,
        config: config || {},
        utterances: botkit.utterances,
        api: slackWebApi(botkit, config || {})
    };

    /**
     * Set up API to send incoming webhook
     */
    bot.configureIncomingWebhook = function(options) {
        if (!options.url)
            throw new Error('No incoming webhook URL specified!');

        bot.config.incoming_webhook = options;

        return bot;
    };

    bot.sendWebhook = function(options, cb) {
        if (!bot.config.incoming_webhook || !bot.config.incoming_webhook.url) {
            botkit.debug('CANNOT SEND WEBHOOK!!');

            return cb && cb('No webhook url specified');
        }

        request.post(bot.config.incoming_webhook.url, function(err, res, body) {
            if (err) {
                botkit.debug('WEBHOOK ERROR', err);
                return cb && cb(err);
            }
            botkit.debug('WEBHOOK SUCCESS', body);
            cb && cb(null, body);
        }).form({ payload: JSON.stringify(options) });
    };

    bot.configureRTM = function(config) {
        bot.config.token = config.token;
        return bot;
    };

    bot.closeRTM = function() {
        if (bot.rtm)
            bot.rtm.close();
    };

    bot.startRTM = function(cb) {
        bot.api.rtm.start({
            no_unreads: true,
            simple_latest: true,
        }, function(err, res) {
            if (err) {
                return cb && cb(err);
            }

            if (!res) {
                return cb && cb('Invalid response from rtm.start');
            }

            bot.identity = res.self;
            bot.team_info = res.team;

            /**
             * Also available:
             * res.users, res.channels, res.groups, res.ims,
             * res.bots
             *
             * Could be stored & cached for later use.
             */

            botkit.log.notice('** BOT ID:', bot.identity.name, '...attempting to connect to RTM!');

            var agent = null;
            var proxyUrl = process.env.https_proxy || process.env.http_proxy;
            if (proxyUrl) {
                agent = new HttpsProxyAgent(proxyUrl);
            }

            bot.rtm = new Ws(res.url, null, {agent: agent});
            bot.msgcount = 1;

            var pingIntervalId = null;
            bot.rtm.on('open', function() {

                pingIntervalId = setInterval(function() {
                    bot.rtm.ping(null, null, true);
                }, 5000);

                botkit.trigger('rtm_open', [bot]);

                bot.rtm.on('message', function(data, flags) {

                    var message = null;
                    try {
                        message = JSON.parse(data);
                    } catch (err) {
                        console.log('** RECEIVED BAD JSON FROM SLACK');
                    }
                    /**
                     * Lets construct a nice quasi-standard botkit message
                     * it leaves the main slack message at the root
                     * but adds in additional fields for internal use!
                     * (including the teams api details)
                     */
                    if (message != null) {
                        botkit.receiveMessage(bot, message);
                    }
                });

                botkit.startTicking();

                cb && cb(null, bot, res);
            });

            bot.rtm.on('error', function(err) {
                botkit.log.error('RTM websocket error!', err);
                botkit.trigger('rtm_close', [bot, err]);
            });

            bot.rtm.on('close', function() {
                if (pingIntervalId) {
                    clearInterval(pingIntervalId);
                }
                botkit.trigger('rtm_close', [bot]);
            });
        });

        return bot;
    };

    bot.identifyBot = function(cb) {
        if (bot.identity) {
            bot.identifyTeam(function(err, team) {
                cb(null, {
                    name: bot.identity.name,
                    id: bot.identity.id,
                    team_id: team
                });
            });
        } else {
            /**
             * Note: Are there scenarios other than the RTM
             * where we might pull identity info, perhaps from
             * bot.api.auth.test on a given token?
             */
            cb('Identity Unknown: Not using RTM api');
        };
    };

    bot.identifyTeam = function(cb) {
        if (bot.team_info)
            return cb(null, bot.team_info.id);

        /**
         * Note: Are there scenarios other than the RTM
         * where we might pull identity info, perhaps from
         * bot.api.auth.test on a given token?
         */
        cb('Unknown Team!');
    };

    /**
     * Convenience method for creating a DM convo.
     */
    bot.startPrivateConversation = function(message, cb) {
        botkit.startTask(this, message, function(task, convo) {
            bot._startDM(task, message.user, function(err, dm) {
                convo.stop();
                cb(err, dm);
            });
        });
    };

    bot.startConversation = function(message, cb) {
        botkit.startConversation(this, message, cb);
    };

    /**
     * Convenience method for creating a DM convo.
     */
    bot._startDM = function(task, user_id, cb) {
        bot.api.im.open({ user: user_id }, function(err, channel) {
            if (err) return cb(err);

            cb(null, task.startConversation({
                channel: channel.channel.id,
                user: user_id
            }));
        });
    };

    bot.say = function(message, cb) {
        botkit.debug('SAY', message);

        /**
         * Construct a valid slack message.
         */
        var slack_message = {
            type: message.type || 'message',
            channel: message.channel,
            text: message.text || null,
            username: message.username || null,
            parse: message.parse || null,
            link_names: message.link_names || null,
            attachments: message.attachments ?
                JSON.stringify(message.attachments) : null,
            unfurl_links: typeof message.unfurl_links !== 'undefined' ? message.unfurl_links : null,
            unfurl_media: typeof message.unfurl_media !== 'undefined' ? message.unfurl_media : null,
            icon_url: message.icon_url || null,
            icon_emoji: message.icon_emoji || null,
        };
        bot.msgcount++;

        if (message.icon_url || message.icon_emoji || message.username) {
            slack_message.as_user = false;
        } else {
            slack_message.as_user = message.as_user || true;
        }

        /**
         * These options are not supported by the RTM
         * so if they are specified, we use the web API to send messages.
         */
        if (message.attachments || message.icon_emoji ||
            message.username || message.icon_url) {

            if (!bot.config.token) {
                throw new Error('Cannot use web API to send messages.');
            }

            bot.api.chat.postMessage(slack_message, function(err, res) {
                if (err) {
                    cb && cb(err);
                } else {
                    cb && cb(null, res);
                }
            });

        } else {
            if (!bot.rtm)
                throw new Error('Cannot use the RTM API to send messages.');

            slack_message.id = message.id || bot.msgcount;


            try {
                bot.rtm.send(JSON.stringify(slack_message), function(err) {
                    if (err) {
                        cb && cb(err);
                    } else {
                        cb && cb();
                    }
                });
            } catch (err) {
                /**
                 * The RTM failed and for some reason it didn't get caught
                 * elsewhere. This happens sometimes when the rtm has closed but
                 * We are sending messages anyways.
                 * Bot probably needs to reconnect!
                 */
                cb && cb(err);
            }
        }
    };

    bot.replyPublic = function(src, resp, cb) {
        if (!bot.res) {
            cb && cb('No web response object found');
        } else {
            var msg = {};

            if (typeof(resp) == 'string') {
                msg.text = resp;
            } else {
                msg = resp;
            }

            msg.channel = src.channel;

            msg.response_type = 'in_channel';
            bot.res.json(msg);
            cb && cb();
        }
    };

    bot.replyPublicDelayed = function(src, resp, cb) {
        if (!src.response_url) {
            cb && cb('No response_url found');
        } else {
            var msg = {};

            if (typeof(resp) == 'string') {
                msg.text = resp;
            } else {
                msg = resp;
            }

            msg.channel = src.channel;

            msg.response_type = 'in_channel';
            var requestOptions = {
                uri: src.response_url,
                method: 'POST',
                json: msg
            };
            request(requestOptions, function(err, resp, body) {
                /**
                 * Do something?
                 */
                if (err) {
                    botkit.log.error('Error sending slash command response:', err);
                    cb && cb(err);
                } else {
                    cb && cb();
                }
            });
        }
    };

    bot.replyPrivate = function(src, resp, cb) {
        if (!bot.res) {
            cb && cb('No web response object found');
        } else {
            var msg = {};

            if (typeof(resp) == 'string') {
                msg.text = resp;
            } else {
                msg = resp;
            }

            msg.channel = src.channel;

            msg.response_type = 'ephemeral';
            bot.res.json(msg);

            cb && cb();
        }
    };

    bot.replyPrivateDelayed = function(src, resp, cb) {
        if (!src.response_url) {
            cb && cb('No response_url found');
        } else {
            var msg = {};

            if (typeof(resp) == 'string') {
                msg.text = resp;
            } else {
                msg = resp;
            }

            msg.channel = src.channel;

            msg.response_type = 'ephemeral';

            var requestOptions = {
                uri: src.response_url,
                method: 'POST',
                json: msg
            };
            request(requestOptions, function(err, resp, body) {
                /**
                 * Do something?
                 */
                if (err) {
                    botkit.log.error('Error sending slash command response:', err);
                    cb && cb(err);
                } else {
                    cb && cb();
                }
            });
        }
    };

    bot.reply = function(src, resp, cb) {
        var msg = {};

        if (typeof(resp) == 'string') {
            msg.text = resp;
        } else {
            msg = resp;
        }

        msg.channel = src.channel;

        bot.say(msg, cb);
    };

    /**
     * sends a typing message to the source channel
     *
     * @param {Object} src message source
     */
    bot.startTyping = function(src) {
        bot.reply(src, { type: 'typing' });
    };

    /**
     * replies with message after typing delay
     *
     * @param {Object} src message source
     * @param {(string|Object)} resp string or object
     * @param {function} cb optional request callback
     */
    bot.replyWithTyping = function(src, resp, cb) {
        var text;

        if (typeof(resp) == 'string') {
            text = resp;
        } else {
            text = resp.text;
        }

        var typingLength = 1200 / 60 * text.length;
        typingLength = typingLength > 2000 ? 2000 : typingLength;

        bot.startTyping(src);

        setTimeout(function() {
            bot.reply(src, resp, cb);
        }, typingLength);
    };

    /**
     * This handles the particulars of finding an existing conversation or
     * topic to fit the message into...
     */
    bot.findConversation = function(message, cb) {
        botkit.debug('CUSTOM FIND CONVO', message.user, message.channel);
        if (message.type == 'message' || message.type == 'slash_command' ||
            message.type == 'outgoing_webhook') {
            for (var t = 0; t < botkit.tasks.length; t++) {
                for (var c = 0; c < botkit.tasks[t].convos.length; c++) {
                    if (
                        botkit.tasks[t].convos[c].isActive() &&
                        botkit.tasks[t].convos[c].source_message.user == message.user &&
                            botkit.tasks[t].convos[c].source_message.channel == message.channel
                    ) {
                        botkit.debug('FOUND EXISTING CONVO!');
                        cb(botkit.tasks[t].convos[c]);
                        return;
                    }
                }
            }
        }

        cb();
    };

    if (bot.config.incoming_webhook)
        bot.configureIncomingWebhook(config.incoming_webhook);

    if (bot.config.bot)
        bot.configureRTM(config.bot);

    return bot;
};


================================================
FILE: lib/console_logger.js
================================================
var slice = Array.prototype.slice;
/**
 * RFC 5424 syslog severity levels, see
 * https://tools.ietf.org/html/rfc5424#section-6.2.1
 */
var levels = [
    'emergency',
    'alert',
    'critical',
    'error',
    'warning',
    'notice',
    'info',
    'debug'
];
var levelsByName = levels.reduce(function(out, name, index) {
    out[name] = index;
    return out;
}, {});

function normalizeLogLevel(level) {
    if (typeof level === 'string') {
        level = levelsByName[level];
    }
    if (typeof level === 'number' && level >= 0 && level < levels.length) {
        return level;
    }
    return false;
}

function ConsoleLogger(_console, maxLevel, defaultLevel) {
    _console = _console || console;
    maxLevel = normalizeLogLevel(maxLevel) || 6;
    defaultLevel = normalizeLogLevel(defaultLevel) || 6;
    return {
        log: function(level, message) {
            var normalizedLevel = normalizeLogLevel(level);
            if (!normalizedLevel) {
                message = level;
                normalizedLevel = defaultLevel;
            }
            var levelName = levels[normalizedLevel];
            if (normalizedLevel <= maxLevel) {
                _console.log.apply(
                    _console,
                    [levelName + ': ' + message].concat(slice.call(arguments, 2))
                );
            }
        }
    };
}

ConsoleLogger.LogLevels = levelsByName;

module.exports = ConsoleLogger;


================================================
FILE: lib/storage/firebase_storage.js
================================================
/*
Firebase storage module for bots.

Note that this storage module does not specify how to authenticate to Firebase.
There are many methods of user authentication for Firebase.
Please read: https://www.firebase.com/docs/web/guide/user-auth.html

Supports storage of data on a team-by-team, user-by-user, and chnnel-by-channel basis.

save can be used to store arbitrary object.
These objects must include an id by which they can be looked up.
It is recommended to use the team/user/channel id for this purpose.
Example usage of save:
controller.storage.teams.save({id: message.team, foo:"bar"}, function(err){
  if (err)
    console.log(err)`
});

get looks up an object by id.
Example usage of get:
controller.storage.teams.get(message.team, function(err, team_data){
  if (err)
    console.log(err)
  else
    console.log(team_data)
});
*/

var Firebase = require('firebase');

module.exports = function(config) {

    if (!config && !config.firebase_uri)
        throw new Error('Need to provide firebase address. This should look something like ' +
            '"https://botkit-example.firebaseio.com/"');

    var rootRef = new Firebase(config.firebase_uri);
    var teamsRef = rootRef.child('teams');
    var usersRef = rootRef.child('users');
    var channelsRef = rootRef.child('channels');

    var get = function(firebaseRef) {
        return function(id, cb) {
            firebaseRef.child(id).once('value',
                function(records) {
                    cb(undefined, records.val());
                },
                function(err) {
                    cb(err, undefined);
                }
            );
        };
    };

    var save = function(firebaseRef) {
        return function(data, cb) {
            var firebase_update = {};
            firebase_update[data.id] = data;
            firebaseRef.update(firebase_update, cb);
        };
    };

    var all = function(firebaseRef) {
        return function(cb) {
            firebaseRef.once('value',
                function(records) {
                    var list = [];
                    for (key of Object.keys(records.val())) {
                        list.push(records.val()[key]);
                    }
                    cb(undefined, list);
                },
                function(err) {
                    cb(err, undefined);
                }
            );
        };
    };

    var storage = {
        teams: {
            get: get(teamsRef),
            save: save(teamsRef),
            all: all(teamsRef)
        },
        channels: {
            get: get(channelsRef),
            save: save(channelsRef),
            all: all(channelsRef)
        },
        users: {
            get: get(usersRef),
            save: save(usersRef),
            all: all(usersRef)
        }
    };

    return storage;

};


================================================
FILE: lib/storage/redis_storage.js
================================================
var redis = require('redis'); //https://github.com/NodeRedis/node_redis

/*
 * All optional
 *
 * config = {
 *  namespace: namespace,
 *  host: host,
 *  port: port
 * }
 * // see
 * https://github.com/NodeRedis/node_redis
 * #options-is-an-object-with-the-following-possible-properties for a full list of the valid options
 */
module.exports = function(config) {
    config = config || {};
    config.namespace = config.namespace || 'botkit:store';

    var storage = {},
    client = redis.createClient(config), // could pass specific redis config here
    methods = config.methods || ['teams', 'users', 'channels'];

    // Implements required API methods
    for (var i = 0; i < methods.length; i++) {
        storage[methods[i]] = function(hash) {
            return {
                get: function(id, cb) {
                    client.hget(config.namespace + ':' + hash, id, function(err, res) {
                        cb(err, JSON.parse(res));
                    });
                },
                save: function(object, cb) {
                    if (!object.id) // Silently catch this error?
                        return cb(new Error('The given object must have an id property'), {});
                    client.hset(config.namespace + ':' + hash, object.id, JSON.stringify(object), cb);
                },
                all: function(cb, options) {
                    client.hgetall(config.namespace + ':' + hash, function(err, res) {
                        if (err)
                        return cb(err, {});

                        if (null === res)
                        return cb(err, res);

                        var parsed;
                        var array = [];

                        for (var i in res) {
                            parsed = JSON.parse(res[i]);
                            res[i] = parsed;
                            array.push(parsed);
                        }

                        cb(err, options && options.type === 'object' ? res : array);
                    });
                },
                allById: function(cb) {
                    this.all(cb, {type: 'object'});
                }
            };
        }(methods[i]);
    }
    return storage;
};


================================================
FILE: lib/storage/simple_storage.js
================================================
/*
Storage module for bots.

Supports storage of data on a team-by-team, user-by-user, and chnnel-by-channel basis.

save can be used to store arbitrary object.
These objects must include an id by which they can be looked up.
It is recommended to use the team/user/channel id for this purpose.
Example usage of save:
controller.storage.teams.save({id: message.team, foo:"bar"}, function(err){
  if (err)
    console.log(err)
});

get looks up an object by id.
Example usage of get:
controller.storage.teams.get(message.team, function(err, team_data){
  if (err)
    console.log(err)
  else
    console.log(team_data)
});
*/

var Store = require('jfs');

module.exports = function(config) {

    if (!config) {
        config = {
            path: './',
        };
    }

    var teams_db = new Store(config.path + '/teams', {saveId: 'id'});
    var users_db = new Store(config.path + '/users', {saveId: 'id'});
    var channels_db = new Store(config.path + '/channels', {saveId: 'id'});

    var objectsToList = function(cb) {
        return function(err, data) {
            if (err) {
                cb(err, data);
            } else {
                cb(err, Object.keys(data).map(function(key) {
                    return data[key];
                }));
            }
        };
    };

    var storage = {
        teams: {
            get: function(team_id, cb) {
                teams_db.get(team_id, cb);
            },
            save: function(team_data, cb) {
                teams_db.save(team_data.id, team_data, cb);
            },
            all: function(cb) {
                teams_db.all(objectsToList(cb));
            }
        },
        users: {
            get: function(user_id, cb) {
                users_db.get(user_id, cb);
            },
            save: function(user, cb) {
                users_db.save(user.id, user, cb);
            },
            all: function(cb) {
                users_db.all(objectsToList(cb));
            }
        },
        channels: {
            get: function(channel_id, cb) {
                channels_db.get(channel_id, cb);
            },
            save: function(channel, cb) {
                channels_db.save(channel.id, channel, cb);
            },
            all: function(cb) {
                channels_db.all(objectsToList(cb));
            }
        }
    };

    return storage;
};


================================================
FILE: lib/storage/storage_test.js
================================================
/*
Tests for storage modules.
This file currently test simple_storage.js, redis_storage, and firebase_storage.

If you build a new storage module,
you must add it to this test file before your PR will be considered.
How to add it to this test file:

Add the following to the bottom of this file:

// Test <your_storage_module>
<your_storage_module> = require('./<your_storage_module>.js')(<appropriate config object for your storage module>);
check(<your_storage_module>.users);
check(<your_storage_module>.channels);
check(<your_storage_module>.teams);
*/

var test = require('unit.js');

testObj0 = {id: 'TEST0', foo: 'bar0'};
testObj1 = {id: 'TEST1', foo: 'bar1'};

var testStorageMethod = function(storageMethod) {
    storageMethod.save(testObj0, function(err) {
        test.assert(!err);
        storageMethod.save(testObj1, function(err) {
            test.assert(!err);
            storageMethod.get(testObj0.id, function(err, data) {
                test.assert(!err);
                console.log(data);
                test.assert(data.foo === testObj0.foo);
            });
            storageMethod.all(function(err, data) {
                test.assert(!err);
                console.log(data);
                test.assert(
                    data[0].foo === testObj0.foo && data[1].foo === testObj1.foo ||
                    data[0].foo === testObj1.foo && data[1].foo === testObj0.foo
                );
            });
        });
    });
};

console.log('If no asserts failed then the test has passed!');

// Test simple_storage
var simple_storage = require('./simple_storage.js')();
testStorageMethod(simple_storage.users);
testStorageMethod(simple_storage.channels);
testStorageMethod(simple_storage.teams);

// Test redis_storage
var redis_storage = require('./redis_storage.js')({
    url: 'redis://redistogo:d175f29259bd73e442eefcaeff8e78aa@tarpon.redistogo.com:11895/'
});
testStorageMethod(redis_storage.users);
testStorageMethod(redis_storage.channels);
testStorageMethod(redis_storage.teams);

// Test firebase_storage
var firebase_storage = require('./firebase_storage.js')({
    firebase_uri: 'https://botkit-example.firebaseio.com'
});
testStorageMethod(firebase_storage.users);
testStorageMethod(firebase_storage.channels);
testStorageMethod(firebase_storage.teams);


================================================
FILE: package.json
================================================
{
  "name": "slackshell",
  "version": "1.0.0",
  "description": "BASH in slack",
  "main": "bot.js",
  "dependencies": {
    "body-parser": "^1.14.2",
    "express": "^4.13.3",
    "jfs": "^0.2.6",
    "mustache": "^2.2.1",
    "request": "^2.67.0",
    "ws": "^1.0.1",
    "https-proxy-agent": "^1.0.0"
  },
  "devDependencies": {
    "jscs": "^2.7.0",
    "mocha": "^2.4.5",
    "should": "^8.0.2",
    "winston": "^2.1.1"
  },
  "scripts": {
    "pretest": "jscs ./lib/",
    "test": "mocha tests/*.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/dtesler/slackshell.git"
  },
  "bugs": {
    "url": "https://github.com/dtesler/slackshell/issues"
  },
  "homepage": "https://github.com/dtesler/slackshell",
  "keywords": [
    "bots",
    "chatbots",
    "slack"
  ],
  "license": "ISC",
  "directories": {
    "test": "tests"
  }
}


================================================
FILE: tests/Slack_web_api.js
================================================
var should = require('should');
var Botkit = require('../');
var path = require('path');
var tmpdir = require('os').tmpdir();
var fs = require('fs');
var winston = require('winston');

var token = process.env.TOKEN;

describe('Test', function() {
    it('should have a token', function(done) {
        should.exist(token);
        done();
    });

    it('should have Botkit instance', function(done) {
        should.exist(Botkit);
        should.exist(Botkit.core);
        should.exist(Botkit.slackbot);
        done();
    });
});

describe('Botkit', function() {
    this.timeout(5000);

    it('should start and then stop', function(done) {
        var controller = Botkit.slackbot({debug: false});
        var openIsCalled = false;

        controller.on('rtm_open', function(bot) {
            should.exist(bot);
            openIsCalled = true;
        });

        controller.on('rtm_close', function(bot) {
            should.exist(bot);
            openIsCalled.should.be.true;
            controller.shutdown();
            done();
        });

        controller
            .spawn({
                token: token
            })
            .startRTM(function(err, bot, payload) {
                (err === null).should.be.true;
                should.exist(bot);
                bot.closeRTM();
            });
    });

    it('should have fail with false token', function(done) {
        this.timeout(5000);

        var controller = Botkit.slackbot({debug: false});

        controller
            .spawn({
                token: '1234'
            })
            .startRTM(function(err, bot, payload) {
                should.exist(err);

                controller.shutdown();
                done();
            });
    });
});

describe('Log', function() {
    it('should use an external logging provider', function(done) {
        var logFile = path.join(tmpdir, 'botkit.log');
        var logger = new winston.Logger({
            transports: [
                new (winston.transports.File)({ filename: logFile })
            ]
        });

        logger.cli();

        var controller = Botkit.slackbot({
                debug: true,
                logger: logger
            });

        controller
            .spawn({
                token: '1234'
            })
            .startRTM(function(err, bot, payload) {
                should.exist(err);

                controller.shutdown();

                fs.readFile(logFile, 'utf8', function(err, res) {
                    (err === null).should.be.true;
                    should.exist(res);
                    done();
                });
            });
    });
});
Download .txt
gitextract_jrepep98/

├── .gitignore
├── README.md
├── bot.js
├── lib/
│   ├── Botkit.js
│   ├── CoreBot.js
│   ├── SlackBot.js
│   ├── Slack_web_api.js
│   ├── Slackbot_worker.js
│   ├── console_logger.js
│   └── storage/
│       ├── firebase_storage.js
│       ├── redis_storage.js
│       ├── simple_storage.js
│       └── storage_test.js
├── package.json
└── tests/
    └── Slack_web_api.js
Download .txt
SYMBOL INDEX (5 symbols across 4 files)

FILE: bot.js
  function getUID (line 19) | function getUID(user, callback) {

FILE: lib/CoreBot.js
  function Botkit (line 11) | function Botkit(configuration) {

FILE: lib/SlackBot.js
  function Slackbot (line 6) | function Slackbot(configuration) {

FILE: lib/console_logger.js
  function normalizeLogLevel (line 21) | function normalizeLogLevel(level) {
  function ConsoleLogger (line 31) | function ConsoleLogger(_console, maxLevel, defaultLevel) {
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (98K chars).
[
  {
    "path": ".gitignore",
    "chars": 70,
    "preview": "node_modules/\nstart.sh\nstart_button.sh\ndb/\n.DS_Store\n*/.DS_Store\n.env\n"
  },
  {
    "path": "README.md",
    "chars": 862,
    "preview": "## Slackshell\nBASH terminal for your slack team\n\n\n### Getting started\n\nClone the repository on to your desired server (U"
  },
  {
    "path": "bot.js",
    "chars": 2286,
    "preview": "var c = require('child_process');\n\nif (!process.env.token) {\n    console.log('Error: Specify token in environment');\n   "
  },
  {
    "path": "lib/Botkit.js",
    "chars": 168,
    "preview": "var CoreBot = require(__dirname + '/CoreBot.js');\nvar Slackbot = require(__dirname + '/SlackBot.js');\n\nmodule.exports = "
  },
  {
    "path": "lib/CoreBot.js",
    "chars": 30363,
    "preview": "/**\n * This is a module that makes a bot\n * It expects to receive messages via the botkit.receiveMessage function\n * The"
  },
  {
    "path": "lib/SlackBot.js",
    "chars": 21587,
    "preview": "var Botkit = require(__dirname + '/CoreBot.js');\nvar request = require('request');\nvar express = require('express');\nvar"
  },
  {
    "path": "lib/Slack_web_api.js",
    "chars": 10233,
    "preview": "var request = require('request');\n\nmodule.exports = function(bot, config) {\n\n    // create a nice wrapper for the Slack "
  },
  {
    "path": "lib/Slackbot_worker.js",
    "chars": 13626,
    "preview": "var Ws = require('ws');\nvar request = require('request');\nvar slackWebApi = require(__dirname + '/Slack_web_api.js');\nva"
  },
  {
    "path": "lib/console_logger.js",
    "chars": 1436,
    "preview": "var slice = Array.prototype.slice;\n/**\n * RFC 5424 syslog severity levels, see\n * https://tools.ietf.org/html/rfc5424#se"
  },
  {
    "path": "lib/storage/firebase_storage.js",
    "chars": 2818,
    "preview": "/*\nFirebase storage module for bots.\n\nNote that this storage module does not specify how to authenticate to Firebase.\nTh"
  },
  {
    "path": "lib/storage/redis_storage.js",
    "chars": 2226,
    "preview": "var redis = require('redis'); //https://github.com/NodeRedis/node_redis\n\n/*\n * All optional\n *\n * config = {\n *  namespa"
  },
  {
    "path": "lib/storage/simple_storage.js",
    "chars": 2363,
    "preview": "/*\nStorage module for bots.\n\nSupports storage of data on a team-by-team, user-by-user, and chnnel-by-channel basis.\n\nsav"
  },
  {
    "path": "lib/storage/storage_test.js",
    "chars": 2299,
    "preview": "/*\nTests for storage modules.\nThis file currently test simple_storage.js, redis_storage, and firebase_storage.\n\nIf you b"
  },
  {
    "path": "package.json",
    "chars": 865,
    "preview": "{\n  \"name\": \"slackshell\",\n  \"version\": \"1.0.0\",\n  \"description\": \"BASH in slack\",\n  \"main\": \"bot.js\",\n  \"dependencies\": "
  },
  {
    "path": "tests/Slack_web_api.js",
    "chars": 2650,
    "preview": "var should = require('should');\nvar Botkit = require('../');\nvar path = require('path');\nvar tmpdir = require('os').tmpd"
  }
]

About this extraction

This page contains the full source code of the dtesler/slackshell GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (91.7 KB), approximately 18.6k tokens, and a symbol index with 5 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!