<commit_before>var util = require('util');

var PRIMITIVE_TPL = '' +
    'window.__templates__ = window.__templates__ || {};\n' +
    'window.__templates__[\'%s\'] = \'%s\';\n';

var SINGLE_PRIMITIVE_TPL = '' +
    'window[\'%s\'] = window[\'%s\'] || {};\n' +
    'window[\'%s\'] = \'%s\';\n';

var TEMPLATE = 'angular.factory(\'%s\', function() {\n' +
    '    return \'%s\';\n' +
    '});\n';

var SINGLE_MODULE_TPL =
    '(function(module) {\n' +
    'try {\n' +
    '    module = angular.module(\'%s\');\n' +
    '} catch (e) {\n' +
    '    module = angular.module(\'%s\', []);\n' +
    '}\n' +
    'module.factory(\'%s\', function() {\n' +
    '    return \'%s\';\n' +
    '});\n' +
    '})();\n';

var escapeContent = function (content) {
    return content.replace(/\\/g, '\\\\').replace(/'/g, '\\\'').replace(/\r?\n/g, '\\n\' +\n    \'');
};

var text2JsPreprocessor = function (logger, basePath, config) {
    config = typeof config === 'object' ? config : {};

    var log = logger.create('preprocessor.text2js');
    var moduleName = config.moduleName;
    // angularMode default is true;
    var angularMode = config.angularMode || true;
    var stripPrefix = new RegExp('^' + (config.stripPrefix || ''));
    var prependPrefix = config.prependPrefix || '';
    var stripSufix = new RegExp((config.stripSufix || '') + '$');
    var cacheIdFromPath = config && config.cacheIdFromPath || function (filepath) {
            return prependPrefix + filepath.replace(stripPrefix, '').replace(stripSufix, '');
        };

    return function (content, file, done) {
        log.debug('Processing "%s".', file.originalPath);

        var textPath = cacheIdFromPath(file.originalPath.replace(basePath + '/', ''));

        if (!/\.js$/.test(file.path)) {
            file.path = file.path + '.js';
        }

        if (angularMode && moduleName) {
            done(util.format(SINGLE_MODULE_TPL, moduleName, moduleName, textPath, escapeContent(content)));
        } else if (angularMode && !moduleName) {
            done(util.format(TEMPLATE, textPath, textPath, escapeContent(content)));
        } else if (!angularMode && moduleName) {
            done(util.format(SINGLE_PRIMITIVE_TPL, textPath, textPath, textPath, escapeContent(content)));
        } else if (!angularMode && !moduleName) {
            done(util.format(PRIMITIVE_TPL, textPath, escapeContent(content)));
        }
    };
};

text2JsPreprocessor.$inject = ['logger', 'config.basePath', 'config.html2JsPreprocessor'];

module.exports = text2JsPreprocessor;
<commit_msg>fix config entry bug<commit_after>var util = require('util');

var PRIMITIVE_TPL = '' +
    'window.__templates__ = window.__templates__ || {};\n' +
    'window.__templates__[\'%s\'] = \'%s\';\n';

var SINGLE_PRIMITIVE_TPL = '' +
    'window[\'%s\'] = window[\'%s\'] || {};\n' +
    'window[\'%s\'] = \'%s\';\n';

var TEMPLATE = 'angular.factory(\'%s\', function() {\n' +
    '    return \'%s\';\n' +
    '});\n';

var SINGLE_MODULE_TPL =
    '(function(module) {\n' +
    'try {\n' +
    '    module = angular.module(\'%s\');\n' +
    '} catch (e) {\n' +
    '    module = angular.module(\'%s\', []);\n' +
    '}\n' +
    'module.factory(\'%s\', function() {\n' +
    '    return \'%s\';\n' +
    '});\n' +
    '})();\n';

var escapeContent = function (content) {
    return content.replace(/\\/g, '\\\\').replace(/'/g, '\\\'').replace(/\r?\n/g, '\\n\' +\n    \'');
};

var text2JsPreprocessor = function (logger, basePath, config) {
    config = typeof config === 'object' ? config : {};

    var log = logger.create('preprocessor.text2js');
    var moduleName = config.moduleName;
    // angularMode default is false;
    var angularMode = config.angularMode || false;
    var stripPrefix = new RegExp('^' + (config.stripPrefix || ''));
    var prependPrefix = config.prependPrefix || '';
    var stripSufix = new RegExp((config.stripSufix || '') + '$');
    var cacheIdFromPath = config && config.cacheIdFromPath || function (filepath) {
            return prependPrefix + filepath.replace(stripPrefix, '').replace(stripSufix, '');
        };

    return function (content, file, done) {
        log.debug('Processing "%s".', file.originalPath);

        var textPath = cacheIdFromPath(file.originalPath.replace(basePath + '/', ''));

        if (!/\.js$/.test(file.path)) {
            file.path = file.path + '.js';
        }

        var newContent;
        if (angularMode && moduleName) {
            console.log('angular mode ON, module name ' + moduleName);
            newContent = util.format(SINGLE_MODULE_TPL, moduleName, moduleName, textPath, escapeContent(content));
        } else if (angularMode && !moduleName) {
            console.log('angular mode ON, module name ' + moduleName);
            newContent = util.format(TEMPLATE, textPath, textPath, escapeContent(content));
        } else if (!angularMode && moduleName) {
            console.log('angular mode OFF, module name ' + moduleName);
            newContent = util.format(SINGLE_PRIMITIVE_TPL, textPath, textPath, textPath, escapeContent(content));
        } else {
            console.log('angular mode OFF, module name ' + moduleName);
            newContent = util.format(PRIMITIVE_TPL, textPath, escapeContent(content));
        }
        done(newContent);
    };
};

text2JsPreprocessor.$inject = ['logger', 'config.basePath', 'config.text2JsPreprocessor'];

module.exports = text2JsPreprocessor;
<|endoftext|>


<commit_before>var WebRTC = require('webrtc');
var WildEmitter = require('wildemitter');
var webrtcSupport = require('webrtcsupport');
var attachMediaStream = require('attachmediastream');
var getScreenMedia = require('getscreenmedia');
var mockconsole = require('mockconsole');
var io = require('socket.io-client');

function SimpleWebRTC(opts) {
    var self = this;
    var options = opts || {};
    var config = this.config = {
        url: 'http://signaling.simplewebrtc.com:8888',
        debug: false,
        localVideoEl: '',
        remoteVideosEl: '',
        enableDataChannels: true,
        autoRequestMedia: false,
        autoRemoveVideos: true,
        adjustPeerVolume: true,
        peerVolumeWhenSpeaking: 0.25
    };
    var item, connection;

    // We also allow a 'logger' option. It can be any object that implements
    // log, warn, and error methods.
    // We log nothing by default, following "the rule of silence":
    // http://www.linfo.org/rule_of_silence.html
    this.logger = function () {
        // we assume that if you're in debug mode and you didn't
        // pass in a logger, you actually want to log as much as
        // possible.
        if (opts.debug) {
            return opts.logger || console;
        } else {
        // or we'll use your logger which should have its own logic
        // for output. Or we'll return the no-op.
            return opts.logger || mockconsole;
        }
    }();

    // set our config from options
    for (item in options) {
        this.config[item] = options[item];
    }

    // attach detected support for convenience
    this.capabilities = webrtcSupport;

    // call WildEmitter constructor
    WildEmitter.call(this);

    // our socket.io connection
    connection = this.connection = io.connect(this.config.url);

    connection.on('connect', function () {
        self.onSignalConnect();
    });

    connection.on('message', function (message) {
        self.onSignalMessage(message);
    });

    connection.on('remove', function (room) {
        self.onSignalRemove(room);
    });

    // instantiate our main WebRTC helper
    // using same logger from logic here
    opts.logger = this.logger;
    opts.debug = false;
    this.webrtc = new WebRTC(opts);

    // attach a few methods from underlying lib to simple.
    ['mute', 'unmute', 'pause', 'resume'].forEach(function (method) {
        self[method] = self.webrtc[method].bind(self.webrtc);
    });

    // proxy events from WebRTC
    this.webrtc.on('*', function () {
       self.emit.apply(self, arguments);
    });

    // log all events in debug mode
    if (config.debug) {
        this.on('*', this.logger.log.bind(this.logger, 'SimpleWebRTC event:'));
    }

    // check for readiness
    this.webrtc.on('localStream', function () {
       this.onWebRTCLocalStream();
    });

    this.webrtc.on('message', function (payload) {
       this.onWebRTCMessage(payload);
    });

    this.webrtc.on('peerStreamAdded', this.handlePeerStreamAdded.bind(this));
    this.webrtc.on('peerStreamRemoved', this.handlePeerStreamRemoved.bind(this));

    // echo cancellation attempts
    if (this.config.adjustPeerVolume) {
        this.webrtc.on('speaking', this.setVolumeForAll.bind(this, this.config.peerVolumeWhenSpeaking));
        this.webrtc.on('stoppedSpeaking', this.setVolumeForAll.bind(this, 1));
    }

    if (this.config.autoRequestMedia) this.startLocalVideo();
}

SimpleWebRTC.prototype = Object.create(WildEmitter.prototype, {
    constructor: {
        value: SimpleWebRTC
    }
});

SimpleWebRTC.prototype.onWebRTCMessages = function() {
   this.emit.apply(this, arguments);
};

SimpleWebRTC.prototype.onWebRTCLocalStream = function() {
    this.testReadiness();
};

SimpleWebRTC.prototype.onWebRTCMessage = function(payload) {
   this.connection.emit('message', payload);
};

SimpleWebRTC.prototype.onSignalMessage = function(message) {
    var peers = this.webrtc.getPeers(message.from, message.roomType);
    var peer;

    if (message.type === 'offer') {
        if (peers.length) {
            peer = peers[0];
        } else {
            console.log("Creating peer.");
            peer = this.webrtc.createPeer({
                id: message.from,
                type: message.roomType,
                sharemyscreen: message.roomType === 'screen' && !message.broadcaster
            });
        }
        peer.handleMessage(message);
    } else if (peers.length) {
        peers.forEach(function (peer) {
            peer.handleMessage(message);
        });
    }
};

SimpleWebRTC.prototype.onSignalRemove = function(room) {
    if (room.id !== this.connection.socket.sessionid) {
        this.webrtc.removePeers(room.id, room.type);
    }
};

SimpleWebRTC.prototype.onSignalConnect = function() {
    this.emit('connectionReady', this.connection.socket.sessionid);
    this.sessionReady = true;
    this.testReadiness();
};

SimpleWebRTC.prototype.leaveRoom = function () {
    if (this.roomName) {
        this.connection.emit('leave', this.roomName);
        this.webrtc.peers.forEach(function (peer) {
            peer.end();
        });
        if (this.getLocalScreen()) {
            this.stopScreenShare();
        }
        this.emit('leftRoom', this.roomName);
    }
};

SimpleWebRTC.prototype.handlePeerStreamAdded = function (peer) {
    var container = this.getRemoteVideoContainer();
    var video = attachMediaStream(peer.stream);

    // store video element as part of peer for easy removal
    peer.videoEl = video;
    video.id = this.getDomId(peer);

    if (container) container.appendChild(video);

    this.emit('videoAdded', video, peer);
};

SimpleWebRTC.prototype.handlePeerStreamRemoved = function (peer) {
    var container = this.getRemoteVideoContainer();
    var videoEl = peer.videoEl;
    if (this.config.autoRemoveVideos && container && videoEl) {
        container.removeChild(videoEl);
    }
    if (videoEl) this.emit('videoRemoved', videoEl, peer);
};

SimpleWebRTC.prototype.getDomId = function (peer) {
    return [peer.id, peer.type, peer.broadcaster ? 'broadcasting' : 'incoming'].join('_');
};

// set volume on video tag for all peers takse a value between 0 and 1
SimpleWebRTC.prototype.setVolumeForAll = function (volume) {
    this.webrtc.peers.forEach(function (peer) {
        if (peer.videoEl) peer.videoEl.volume = volume;
    });
};

SimpleWebRTC.prototype.joinRoom = function (name, cb) {
    var self = this;
    this.roomName = name;
    this.connection.emit('join', name, function (err, roomDescription) {
        if (err) {
            self.emit('error', err);
        } else {
            var id,
                client,
                type,
                peer;
            for (id in roomDescription.clients) {
                client = roomDescription.clients[id];
                for (type in client) {
                    if (client[type]) {
                        console.log("Creating peer.");
                        peer = self.webrtc.createPeer({
                            id: id,
                            type: type
                        });
                        peer.start();
                    }
                }
            }
        }

        if (cb) cb(err, roomDescription);
        self.emit('joinedRoom', name);
    });
};

SimpleWebRTC.prototype.getEl = function (idOrEl) {
    if (typeof idOrEl === 'string') {
        return document.getElementById(idOrEl);
    } else {
        return idOrEl;
    }
};

SimpleWebRTC.prototype.startLocalVideo = function () {
    var self = this;
    this.webrtc.startLocalMedia(null, function (err, stream) {
        if (err) {
            self.emit(err);
        } else {
            attachMediaStream(stream, self.getLocalVideoContainer(), {muted: true, mirror: true});
        }
    });
};

SimpleWebRTC.prototype.stopLocalVideo = function () {
    this.webrtc.stopLocalMedia();
};

// this accepts either element ID or element
// and either the video tag itself or a container
// that will be used to put the video tag into.
SimpleWebRTC.prototype.getLocalVideoContainer = function () {
    var el = this.getEl(this.config.localVideoEl);
    if (el && el.tagName === 'VIDEO') {
        return el;
    } else if (el) {
        var video = document.createElement('video');
        el.appendChild(video);
        return video;
    } else {
        return;
    }
};

SimpleWebRTC.prototype.getRemoteVideoContainer = function () {
    return this.getEl(this.config.remoteVideosEl);
};

SimpleWebRTC.prototype.shareScreen = function (cb) {
    var self = this,
        peer;
    getScreenMedia(function (err, stream) {
        var item,
            el = document.createElement('video'),
            container = self.getRemoteVideoContainer();

        if (!err) {
            self.webrtc.localScreen = stream;
            el.id = 'localScreen';
            attachMediaStream(stream, el);
            if (container) {
                container.appendChild(el);
            }

            // TODO: Once this chrome bug is fixed:
            // https://code.google.com/p/chromium/issues/detail?id=227485
            // we need to listen for the screenshare stream ending and call
            // the "stopScreenShare" method to clean things up.

            self.emit('localScreenAdded', el);
            self.connection.emit('shareScreen');
            self.webrtc.peers.forEach(function (existingPeer) {
                var peer;
                if (existingPeer.type === 'video') {
                    console.log("Creating peer.");
                    peer = self.webrtc.createPeer({
                        id: existingPeer.id,
                        type: 'screen',
                        sharemyscreen: true,
                        broadcaster: self.connection.socket.sessionid
                    });
                    peer.start();
                }
            });
        } else {
            self.emit(err);
        }

        // enable the callback
        if (cb) cb(err, stream);
    });
};

SimpleWebRTC.prototype.getLocalScreen = function () {
    return this.webrtc.localScreen;
};

SimpleWebRTC.prototype.stopScreenShare = function () {
    this.connection.emit('unshareScreen');
    var videoEl = document.getElementById('localScreen');
    var container = this.getRemoteVideoContainer();
    var stream = this.getLocalScreen();

    if (this.config.autoRemoveVideos && container && videoEl) {
        container.removeChild(videoEl);
    }

    // a hack to emit the event the removes the video
    // element that we want
    if (videoEl) this.emit('videoRemoved', videoEl);
    if (stream) stream.stop();
    this.webrtc.peers.forEach(function (peer) {
        if (peer.broadcaster) {
            peer.end();
        }
    });
    delete this.webrtc.localScreen;
};

SimpleWebRTC.prototype.testReadiness = function () {
    var self = this;
    if (this.webrtc.localStream && this.sessionReady) {
        // This timeout is a workaround for the strange no-audio bug
        // as described here: https://code.google.com/p/webrtc/issues/detail?id=1525
        // remove timeout when this is fixed.
        setTimeout(function () {
            self.emit('readyToCall', self.connection.socket.sessionid);
        }, 1000);
    }
};

SimpleWebRTC.prototype.createRoom = function (name, cb) {
    if (arguments.length === 2) {
        this.connection.emit('create', name, cb);
    } else {
        this.connection.emit('create', name);
    }
};

SimpleWebRTC.prototype.sendFile = function () {
    if (!webrtcSupport.dataChannel) {
        return this.emit('error', new Error('DataChannelNotSupported'));
    }

};

module.exports = SimpleWebRTC;
<commit_msg>Using binding rather than anonymous functions<commit_after>var WebRTC = require('webrtc');
var WildEmitter = require('wildemitter');
var webrtcSupport = require('webrtcsupport');
var attachMediaStream = require('attachmediastream');
var getScreenMedia = require('getscreenmedia');
var mockconsole = require('mockconsole');
var io = require('socket.io-client');

function SimpleWebRTC(opts) {
    var self = this;
    var options = opts || {};
    var config = this.config = {
        url: 'http://signaling.simplewebrtc.com:8888',
        debug: false,
        localVideoEl: '',
        remoteVideosEl: '',
        enableDataChannels: true,
        autoRequestMedia: false,
        autoRemoveVideos: true,
        adjustPeerVolume: true,
        peerVolumeWhenSpeaking: 0.25
    };
    var item, connection;

    // We also allow a 'logger' option. It can be any object that implements
    // log, warn, and error methods.
    // We log nothing by default, following "the rule of silence":
    // http://www.linfo.org/rule_of_silence.html
    this.logger = function () {
        // we assume that if you're in debug mode and you didn't
        // pass in a logger, you actually want to log as much as
        // possible.
        if (opts.debug) {
            return opts.logger || console;
        } else {
        // or we'll use your logger which should have its own logic
        // for output. Or we'll return the no-op.
            return opts.logger || mockconsole;
        }
    }();

    // set our config from options
    for (item in options) {
        this.config[item] = options[item];
    }

    // attach detected support for convenience
    this.capabilities = webrtcSupport;

    // call WildEmitter constructor
    WildEmitter.call(this);

    // our socket.io connection
    connection = this.connection = io.connect(this.config.url);

    connection.on('connect', function () {
        self.onSignalConnect();
    });

    connection.on('message', function (message) {
        self.onSignalMessage(message);
    });

    connection.on('remove', function (room) {
        self.onSignalRemove(room);
    });

    // instantiate our main WebRTC helper
    // using same logger from logic here
    opts.logger = this.logger;
    opts.debug = false;
    this.webrtc = new WebRTC(opts);

    // attach a few methods from underlying lib to simple.
    ['mute', 'unmute', 'pause', 'resume'].forEach(function (method) {
        self[method] = self.webrtc[method].bind(self.webrtc);
    });

    // proxy events from WebRTC
    this.webrtc.on('*', this.onWebRTCLocalStream.bind(this));

    // log all events in debug mode
    if (config.debug) {
        this.on('*', this.logger.log.bind(this.logger, 'SimpleWebRTC event:'));
    }

    // check for readiness
    this.webrtc.on('localStream', this.onWebRTCLocalStream.bind(this));

    this.webrtc.on('message', this.onWebRTCMessage.bind(this));

    this.webrtc.on('peerStreamAdded', this.handlePeerStreamAdded.bind(this));
    this.webrtc.on('peerStreamRemoved', this.handlePeerStreamRemoved.bind(this));

    // echo cancellation attempts
    if (this.config.adjustPeerVolume) {
        this.webrtc.on('speaking', this.setVolumeForAll.bind(this, this.config.peerVolumeWhenSpeaking));
        this.webrtc.on('stoppedSpeaking', this.setVolumeForAll.bind(this, 1));
    }

    if (this.config.autoRequestMedia) this.startLocalVideo();
}

SimpleWebRTC.prototype = Object.create(WildEmitter.prototype, {
    constructor: {
        value: SimpleWebRTC
    }
});

SimpleWebRTC.prototype.onWebRTCMessages = function() {
   this.emit.apply(this, arguments);
};

SimpleWebRTC.prototype.onWebRTCLocalStream = function() {
    this.testReadiness();
};

SimpleWebRTC.prototype.onWebRTCMessage = function(payload) {
   this.connection.emit('message', payload);
};

SimpleWebRTC.prototype.onSignalMessage = function(message) {
    var peers = this.webrtc.getPeers(message.from, message.roomType);
    var peer;

    if (message.type === 'offer') {
        if (peers.length) {
            peer = peers[0];
        } else {
            console.log("Creating peer.");
            peer = this.webrtc.createPeer({
                id: message.from,
                type: message.roomType,
                sharemyscreen: message.roomType === 'screen' && !message.broadcaster
            });
        }
        peer.handleMessage(message);
    } else if (peers.length) {
        peers.forEach(function (peer) {
            peer.handleMessage(message);
        });
    }
};

SimpleWebRTC.prototype.onSignalRemove = function(room) {
    if (room.id !== this.connection.socket.sessionid) {
        this.webrtc.removePeers(room.id, room.type);
    }
};

SimpleWebRTC.prototype.onSignalConnect = function() {
    this.emit('connectionReady', this.connection.socket.sessionid);
    this.sessionReady = true;
    this.testReadiness();
};

SimpleWebRTC.prototype.leaveRoom = function () {
    if (this.roomName) {
        this.connection.emit('leave', this.roomName);
        this.webrtc.peers.forEach(function (peer) {
            peer.end();
        });
        if (this.getLocalScreen()) {
            this.stopScreenShare();
        }
        this.emit('leftRoom', this.roomName);
    }
};

SimpleWebRTC.prototype.handlePeerStreamAdded = function (peer) {
    var container = this.getRemoteVideoContainer();
    var video = attachMediaStream(peer.stream);

    // store video element as part of peer for easy removal
    peer.videoEl = video;
    video.id = this.getDomId(peer);

    if (container) container.appendChild(video);

    this.emit('videoAdded', video, peer);
};

SimpleWebRTC.prototype.handlePeerStreamRemoved = function (peer) {
    var container = this.getRemoteVideoContainer();
    var videoEl = peer.videoEl;
    if (this.config.autoRemoveVideos && container && videoEl) {
        container.removeChild(videoEl);
    }
    if (videoEl) this.emit('videoRemoved', videoEl, peer);
};

SimpleWebRTC.prototype.getDomId = function (peer) {
    return [peer.id, peer.type, peer.broadcaster ? 'broadcasting' : 'incoming'].join('_');
};

// set volume on video tag for all peers takse a value between 0 and 1
SimpleWebRTC.prototype.setVolumeForAll = function (volume) {
    this.webrtc.peers.forEach(function (peer) {
        if (peer.videoEl) peer.videoEl.volume = volume;
    });
};

SimpleWebRTC.prototype.joinRoom = function (name, cb) {
    var self = this;
    this.roomName = name;
    this.connection.emit('join', name, function (err, roomDescription) {
        if (err) {
            self.emit('error', err);
        } else {
            var id,
                client,
                type,
                peer;
            for (id in roomDescription.clients) {
                client = roomDescription.clients[id];
                for (type in client) {
                    if (client[type]) {
                        console.log("Creating peer.");
                        peer = self.webrtc.createPeer({
                            id: id,
                            type: type
                        });
                        peer.start();
                    }
                }
            }
        }

        if (cb) cb(err, roomDescription);
        self.emit('joinedRoom', name);
    });
};

SimpleWebRTC.prototype.getEl = function (idOrEl) {
    if (typeof idOrEl === 'string') {
        return document.getElementById(idOrEl);
    } else {
        return idOrEl;
    }
};

SimpleWebRTC.prototype.startLocalVideo = function () {
    var self = this;
    this.webrtc.startLocalMedia(null, function (err, stream) {
        if (err) {
            self.emit(err);
        } else {
            attachMediaStream(stream, self.getLocalVideoContainer(), {muted: true, mirror: true});
        }
    });
};

SimpleWebRTC.prototype.stopLocalVideo = function () {
    this.webrtc.stopLocalMedia();
};

// this accepts either element ID or element
// and either the video tag itself or a container
// that will be used to put the video tag into.
SimpleWebRTC.prototype.getLocalVideoContainer = function () {
    var el = this.getEl(this.config.localVideoEl);
    if (el && el.tagName === 'VIDEO') {
        return el;
    } else if (el) {
        var video = document.createElement('video');
        el.appendChild(video);
        return video;
    } else {
        return;
    }
};

SimpleWebRTC.prototype.getRemoteVideoContainer = function () {
    return this.getEl(this.config.remoteVideosEl);
};

SimpleWebRTC.prototype.shareScreen = function (cb) {
    var self = this,
        peer;
    getScreenMedia(function (err, stream) {
        var item,
            el = document.createElement('video'),
            container = self.getRemoteVideoContainer();

        if (!err) {
            self.webrtc.localScreen = stream;
            el.id = 'localScreen';
            attachMediaStream(stream, el);
            if (container) {
                container.appendChild(el);
            }

            // TODO: Once this chrome bug is fixed:
            // https://code.google.com/p/chromium/issues/detail?id=227485
            // we need to listen for the screenshare stream ending and call
            // the "stopScreenShare" method to clean things up.

            self.emit('localScreenAdded', el);
            self.connection.emit('shareScreen');
            self.webrtc.peers.forEach(function (existingPeer) {
                var peer;
                if (existingPeer.type === 'video') {
                    console.log("Creating peer.");
                    peer = self.webrtc.createPeer({
                        id: existingPeer.id,
                        type: 'screen',
                        sharemyscreen: true,
                        broadcaster: self.connection.socket.sessionid
                    });
                    peer.start();
                }
            });
        } else {
            self.emit(err);
        }

        // enable the callback
        if (cb) cb(err, stream);
    });
};

SimpleWebRTC.prototype.getLocalScreen = function () {
    return this.webrtc.localScreen;
};

SimpleWebRTC.prototype.stopScreenShare = function () {
    this.connection.emit('unshareScreen');
    var videoEl = document.getElementById('localScreen');
    var container = this.getRemoteVideoContainer();
    var stream = this.getLocalScreen();

    if (this.config.autoRemoveVideos && container && videoEl) {
        container.removeChild(videoEl);
    }

    // a hack to emit the event the removes the video
    // element that we want
    if (videoEl) this.emit('videoRemoved', videoEl);
    if (stream) stream.stop();
    this.webrtc.peers.forEach(function (peer) {
        if (peer.broadcaster) {
            peer.end();
        }
    });
    delete this.webrtc.localScreen;
};

SimpleWebRTC.prototype.testReadiness = function () {
    var self = this;
    if (this.webrtc.localStream && this.sessionReady) {
        // This timeout is a workaround for the strange no-audio bug
        // as described here: https://code.google.com/p/webrtc/issues/detail?id=1525
        // remove timeout when this is fixed.
        setTimeout(function () {
            self.emit('readyToCall', self.connection.socket.sessionid);
        }, 1000);
    }
};

SimpleWebRTC.prototype.createRoom = function (name, cb) {
    if (arguments.length === 2) {
        this.connection.emit('create', name, cb);
    } else {
        this.connection.emit('create', name);
    }
};

SimpleWebRTC.prototype.sendFile = function () {
    if (!webrtcSupport.dataChannel) {
        return this.emit('error', new Error('DataChannelNotSupported'));
    }

};

module.exports = SimpleWebRTC;
<|endoftext|>


<commit_before>import getDatabase from './database';

const escapeRegex = text => text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');

const getVideo = async (type, messageText, page) => {
  const text = escapeRegex(messageText);
  const query = {};
  query[type] = {
    $regex: text,
    $options: 'gi',
  };

  const db = await getDatabase();
  const results = await db
    .collection('videos')
    .aggregate([
      { $match: query },
      {
        $group: {
          _id: '$code',
          title: { $first: '$title' },
          models: { $first: '$models' },
          img_url: { $first: '$img_url' },
          code: { $first: '$code' },
          tags: { $first: '$tags' },
          duration: { $first: '$duration' },
          total_count: { $sum: 1 },
          total_view_count: { $sum: 'view_count' },
          videos: {
            $push: {
              source: '$source',
              url: '$url',
              view_count: '$view_count',
            },
          },
        },
      },
      { $sort: { total_view_count: -1 } },
      { $skip: page },
      { $limit: 1 },
    ])
    .toArray();

  return {
    searchValue: text,
    type,
    results: results[0],
  };
};

const getRandomThreeVideos = async () => {
  const db = await getDatabase();
  const results = await db
    .collection('videos')
    .aggregate([
      { $sort: { count: -1 } },
      { $limit: 50 },
      { $sample: { size: 3 } },
    ])
    .toArray();
  return {
    searchValue: 'PPAV',
    results,
  };
};

export { getVideo, getRandomThreeVideos };
<commit_msg>fix aggregate<commit_after>import getDatabase from './database';

const escapeRegex = text => text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');

const getVideo = async (type, messageText, page) => {
  const text = escapeRegex(messageText);
  const query = {};
  query[type] = {
    $regex: text,
    $options: 'gi',
  };

  const db = await getDatabase();
  const results = await db
    .collection('videos')
    .aggregate([
      { $match: query },
      {
        $group: {
          _id: '$code',
          title: { $first: '$title' },
          models: { $first: '$models' },
          img_url: { $first: '$img_url' },
          code: { $first: '$code' },
          tags: { $first: '$tags' },
          duration: { $first: '$duration' },
          total_view_count: { $sum: 'view_count' },
          videos: {
            $push: {
              source: '$source',
              url: '$url',
              view_count: '$view_count',
            },
          },
        },
      },
      { $sort: { total_view_count: -1 } },
    ])
    .toArray();

  return {
    searchValue: text,
    type,
    results: results[page],
    total_count: results.length,
  };
};

const getRandomThreeVideos = async () => {
  const db = await getDatabase();
  const results = await db
    .collection('videos')
    .aggregate([
      { $sort: { count: -1 } },
      { $limit: 50 },
      { $sample: { size: 3 } },
    ])
    .toArray();
  return {
    searchValue: 'PPAV',
    results,
  };
};

export { getVideo, getRandomThreeVideos };
<|endoftext|>


<commit_before>/**
 * A* path-finder.
 * @constructor
 * @extends PF.BaseFinder
 * @requires PF.Heap
 * @param {boolean} allowDiagonal - Whether diagonal movement is allowed.
 * @param {function(number, number): number} [heuristic] - Heuristic function
 *     being used to estimate the distance(defaults to manhattan).
 * @example
 * var finder, path;
 * finder = PF.AStarFinder(true);
 * path = finder.findPath(...);
 *
 * // Available heuristics:
 * //     PF.AStarFinder.manhattan
 * //     PF.AStarFinder.euclidean
 * //     PF.AStarFinder.chebyshev
 * //
 * // Sample Custom heuristic:
 * function(dx, dy) {
 *     return Math.min(dx, dy);
 * }
 */
PF.AStarFinder = function(allowDiagonal, heuristic) {
    PF.BaseFinder.call(this, allowDiagonal);

    if (allowDiagonal) {
        this._inspectSurround = this._inspectSurroundDiagonal;
    }
    this.heuristic = heuristic || PF.AStarFinder.manhattan;
};

/**
 * Extends the BaseFinder
 */
PF.AStarFinder.prototype = new PF.BaseFinder();

/**
 * The constructor of the instance.
 */
PF.AStarFinder.prototype.constructor = PF.AStarFinder;

/**
 * Find and return the the path.
 * @protected
 * @return {Array.<[number, number]>} The path, including both start and
 *     end positions.
 */
PF.AStarFinder.prototype._find = function() {
    var x, y,    // current x, y
        sx = this.startX,
        sy = this.startY,
        ex = this.endX,
        ey = this.endY,
        grid = this.grid,

        openList = new PF.Heap(function(pos_a, pos_b) {
            var fa = grid.getAttributeAt(pos_a[0], pos_a[1], 'f'),
                fb = grid.getAttributeAt(pos_b[0], pos_b[1], 'f');
            if (fa != fb) {
                return fa < fb;
            } else {
                return grid.getAttributeAt(pos_a[0], pos_a[1], 'h') <
                       grid.getAttributeAt(pos_b[0], pos_b[1], 'h');
            }
        }),
        node;

    this.openList = openList;

    // set the `g` and `f` value of the start node to be 0
    node = grid.getNodeAt(sx, sy);
    node.set('g', 0);
    node.set('f', 0);

    // push the start node into the open list
    openList.push([sx, sy]);
    node.set('opened', true);

    // while the open list is not empty
    while (!openList.isEmpty()) {
        // pop the position of node which has the minimum `f` value.
        pos = openList.pop();
        x = pos[0];
        y = pos[1];
        grid.setAttributeAt(x, y, 'closed', true);

        // if reached the end position, construct the path and return it
        if (x == ex && y == ey) {
            return this._constructPath();
        }

        // inspect the surrounding positions
        this._inspectSurround(x, y);

    }

    // fail to find the path
    return [];
};

/**
 * Push the position into the open list if this position is not in the list.
 * Otherwise, if the position can be accessed with a lower cost from the given
 * parent position, then update its parent and cost
 * @protected
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 * @param {number} px - The x coordinate of the parent position.
 * @param {number} py - The y coordinate of the parent position.
 * @param {boolean} isDiagonal - Whether [x, y] and [px, py] is diagonal
 */
PF.AStarFinder.prototype._inspectNodeAt = function(x, y, px, py, isDiagonal) {
    var grid = this.grid,
        openList = this.openList,
        node = grid.getNodeAt(x, y);

    if (node.get('closed')) {
        return;
    }

    if (node.get('opened')) {
        if (this._tryUpdate(x, y, px, py, isDiagonal)) {
            openList.heapify();
        }
    } else {
        node.set('opened', true);
        this._tryUpdate(x, y, px, py, isDiagonal);
        openList.push([x, y]);
    }
};

/**
 * Try to update the position's info with the given parent.
 * If this position can be accessed from the given parent with lower
 * `g` cost, then this position's parent, `g` and `f` values will be updated.
 * @protected
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 * @param {number} px - The x coordinate of the parent position.
 * @param {number} py - The y coordinate of the parent position.
 * @param {boolean} isDiagonal - Whether [x, y] and [px, py] is diagonal
 * @return {boolean} Whether this position's info has been updated.
 */
PF.AStarFinder.prototype._tryUpdate = function(x, y, px, py, isDiagonal) {
    var grid = this.grid,
        pNode = grid.getNodeAt(px, py), // parent node
        ng = pNode.get('g') + (isDiagonal ? 1.4142 : 1); // next `g` value
        node = grid.getNodeAt(x, y);

    if (node.get('g') === undefined || ng < node.get('g')) {
        node.set('parent', [px, py]);
        node.set('g', ng);
        node.set('h', this._calculateH(x, y));
        node.set('f', node.get('g') + node.get('h'));
        return true;
    }
    return false;
};

/**
 * Calculate the `h` value of a given position.
 * @protected
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 * @return {number}
 */
PF.AStarFinder.prototype._calculateH = function(x, y) {
    var dx = Math.abs(x - this.endX),
        dy = Math.abs(y - this.endY);
    return this.heuristic(dx, dy);
};

/**
 * Manhattan distance.
 * @description dx + dy
 */
PF.AStarFinder.manhattan = function(dx, dy) {
    return dx + dy;
};

/**
 * Euclidean distance.
 * @description sqrt(dx * dx, dy * dy)
 */
PF.AStarFinder.euclidean = function(dx, dy) {
    return Math.sqrt(dx * dx + dy * dy);
};

/**
 * Chebyshev distance.
 * @description max(dx, dy)
 */
PF.AStarFinder.chebyshev = function(dx, dy) {
    return Math.max(dx, dy);
};

/**
 * Inspect the surrounding nodes of the given position
 * @protected
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 */
PF.AStarFinder.prototype._inspectSurround = function(x, y) {
    var xOffsets = PF.BaseFinder.xOffsets,
        yOffsets = PF.BaseFinder.yOffsets,
        grid = this.grid,
        i, nx, ny;

    for (i = 0; i < xOffsets.length; ++i) {
        nx = x + xOffsets[i];
        ny = y + yOffsets[i];

        if (grid.isInside(nx, ny) && grid.isWalkableAt(nx, ny)) {
            this._inspectNodeAt(nx, ny, x, y, false);
        }
    }
};

/**
 * Inspect the surrounding nodes of the given position
 * (including the diagonal ones).
 * @protected
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 */
PF.AStarFinder.prototype._inspectSurroundDiagonal = function(x, y) {
    var xOffsets = PF.BaseFinder.xOffsets,
        yOffsets = PF.BaseFinder.yOffsets,
        xDiagonalOffsets = PF.BaseFinder.xDiagonalOffsets,
        yDiagonalOffsets = PF.BaseFinder.yDiagonalOffsets,
        grid = this.grid,
        i, nx, ny, diagonalCan = [];

    for (i = 0; i < xOffsets.length; ++i) {
        nx = x + xOffsets[i];
        ny = y + yOffsets[i];

        if (grid.isInside(nx, ny) && grid.isWalkableAt(nx, ny)) {
            this._inspectNodeAt(nx, ny, x, y, false);

            diagonalCan.push(i);
        }
    }

    // further inspect diagonal nodes
    for (i = 0; i < diagonalCan.length; ++i) {
        nx = x + xDiagonalOffsets[diagonalCan[i]];
        ny = y + yDiagonalOffsets[diagonalCan[i]];
        if (grid.isInside(nx, ny) && grid.isWalkableAt(nx, ny)) {
            this._inspectNodeAt(nx, ny, x, y, true);
        }
    }
};
<commit_msg>mod: variable names<commit_after>/**
 * A* path-finder.
 * @constructor
 * @extends PF.BaseFinder
 * @requires PF.Heap
 * @param {boolean} allowDiagonal - Whether diagonal movement is allowed.
 * @param {function(number, number): number} [heuristic] - Heuristic function
 *     being used to estimate the distance(defaults to manhattan).
 * @example
 * var finder, path;
 * finder = PF.AStarFinder(true);
 * path = finder.findPath(...);
 *
 * // Available heuristics:
 * //     PF.AStarFinder.manhattan
 * //     PF.AStarFinder.euclidean
 * //     PF.AStarFinder.chebyshev
 * //
 * // Sample Custom heuristic:
 * function(dx, dy) {
 *     return Math.min(dx, dy);
 * }
 */
PF.AStarFinder = function(allowDiagonal, heuristic) {
    PF.BaseFinder.call(this, allowDiagonal);

    if (allowDiagonal) {
        this._inspectSurround = this._inspectSurroundDiagonal;
    }
    this.heuristic = heuristic || PF.AStarFinder.manhattan;
};

/**
 * Extends the BaseFinder
 */
PF.AStarFinder.prototype = new PF.BaseFinder();

/**
 * The constructor of the instance.
 */
PF.AStarFinder.prototype.constructor = PF.AStarFinder;

/**
 * Find and return the the path.
 * @protected
 * @return {Array.<[number, number]>} The path, including both start and
 *     end positions.
 */
PF.AStarFinder.prototype._find = function() {
    var x, y,    // current x, y
        sx = this.startX,
        sy = this.startY,
        ex = this.endX,
        ey = this.endY,
        grid = this.grid,

        openList = new PF.Heap(function(posA, posB) {
            var fa = grid.getAttributeAt(posA[0], posA[1], 'f'),
                fb = grid.getAttributeAt(posB[0], posB[1], 'f');
            if (fa != fb) {
                return fa < fb;
            } else {
                return grid.getAttributeAt(posA[0], posA[1], 'h') <
                       grid.getAttributeAt(posB[0], posB[1], 'h');
            }
        }),
        node;

    this.openList = openList;

    // set the `g` and `f` value of the start node to be 0
    node = grid.getNodeAt(sx, sy);
    node.set('g', 0);
    node.set('f', 0);

    // push the start node into the open list
    openList.push([sx, sy]);
    node.set('opened', true);

    // while the open list is not empty
    while (!openList.isEmpty()) {
        // pop the position of node which has the minimum `f` value.
        pos = openList.pop();
        x = pos[0];
        y = pos[1];
        grid.setAttributeAt(x, y, 'closed', true);

        // if reached the end position, construct the path and return it
        if (x == ex && y == ey) {
            return this._constructPath();
        }

        // inspect the surrounding positions
        this._inspectSurround(x, y);

    }

    // fail to find the path
    return [];
};

/**
 * Push the position into the open list if this position is not in the list.
 * Otherwise, if the position can be accessed with a lower cost from the given
 * parent position, then update its parent and cost
 * @protected
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 * @param {number} px - The x coordinate of the parent position.
 * @param {number} py - The y coordinate of the parent position.
 * @param {boolean} isDiagonal - Whether [x, y] and [px, py] is diagonal
 */
PF.AStarFinder.prototype._inspectNodeAt = function(x, y, px, py, isDiagonal) {
    var grid = this.grid,
        openList = this.openList,
        node = grid.getNodeAt(x, y);

    if (node.get('closed')) {
        return;
    }

    if (node.get('opened')) {
        if (this._tryUpdate(x, y, px, py, isDiagonal)) {
            openList.heapify();
        }
    } else {
        node.set('opened', true);
        this._tryUpdate(x, y, px, py, isDiagonal);
        openList.push([x, y]);
    }
};

/**
 * Try to update the position's info with the given parent.
 * If this position can be accessed from the given parent with lower
 * `g` cost, then this position's parent, `g` and `f` values will be updated.
 * @protected
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 * @param {number} px - The x coordinate of the parent position.
 * @param {number} py - The y coordinate of the parent position.
 * @param {boolean} isDiagonal - Whether [x, y] and [px, py] is diagonal
 * @return {boolean} Whether this position's info has been updated.
 */
PF.AStarFinder.prototype._tryUpdate = function(x, y, px, py, isDiagonal) {
    var grid = this.grid,
        pNode = grid.getNodeAt(px, py), // parent node
        ng = pNode.get('g') + (isDiagonal ? 1.4142 : 1); // next `g` value
        node = grid.getNodeAt(x, y);

    if (node.get('g') === undefined || ng < node.get('g')) {
        node.set('parent', [px, py]);
        node.set('g', ng);
        node.set('h', this._calculateH(x, y));
        node.set('f', node.get('g') + node.get('h'));
        return true;
    }
    return false;
};

/**
 * Calculate the `h` value of a given position.
 * @protected
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 * @return {number}
 */
PF.AStarFinder.prototype._calculateH = function(x, y) {
    var dx = Math.abs(x - this.endX),
        dy = Math.abs(y - this.endY);
    return this.heuristic(dx, dy);
};

/**
 * Manhattan distance.
 * @description dx + dy
 */
PF.AStarFinder.manhattan = function(dx, dy) {
    return dx + dy;
};

/**
 * Euclidean distance.
 * @description sqrt(dx * dx, dy * dy)
 */
PF.AStarFinder.euclidean = function(dx, dy) {
    return Math.sqrt(dx * dx + dy * dy);
};

/**
 * Chebyshev distance.
 * @description max(dx, dy)
 */
PF.AStarFinder.chebyshev = function(dx, dy) {
    return Math.max(dx, dy);
};

/**
 * Inspect the surrounding nodes of the given position
 * @protected
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 */
PF.AStarFinder.prototype._inspectSurround = function(x, y) {
    var xOffsets = PF.BaseFinder.xOffsets,
        yOffsets = PF.BaseFinder.yOffsets,
        grid = this.grid,
        i, nx, ny;

    for (i = 0; i < xOffsets.length; ++i) {
        nx = x + xOffsets[i];
        ny = y + yOffsets[i];

        if (grid.isInside(nx, ny) && grid.isWalkableAt(nx, ny)) {
            this._inspectNodeAt(nx, ny, x, y, false);
        }
    }
};

/**
 * Inspect the surrounding nodes of the given position
 * (including the diagonal ones).
 * @protected
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 */
PF.AStarFinder.prototype._inspectSurroundDiagonal = function(x, y) {
    var xOffsets = PF.BaseFinder.xOffsets,
        yOffsets = PF.BaseFinder.yOffsets,
        xDiagonalOffsets = PF.BaseFinder.xDiagonalOffsets,
        yDiagonalOffsets = PF.BaseFinder.yDiagonalOffsets,
        grid = this.grid,
        i, nx, ny, diagonalCan = [];

    for (i = 0; i < xOffsets.length; ++i) {
        nx = x + xOffsets[i];
        ny = y + yOffsets[i];

        if (grid.isInside(nx, ny) && grid.isWalkableAt(nx, ny)) {
            this._inspectNodeAt(nx, ny, x, y, false);

            diagonalCan.push(i);
        }
    }

    // further inspect diagonal nodes
    for (i = 0; i < diagonalCan.length; ++i) {
        nx = x + xDiagonalOffsets[diagonalCan[i]];
        ny = y + yDiagonalOffsets[diagonalCan[i]];
        if (grid.isInside(nx, ny) && grid.isWalkableAt(nx, ny)) {
            this._inspectNodeAt(nx, ny, x, y, true);
        }
    }
};
<|endoftext|>


<commit_before>(function (window, document, $) {
  'use strict';
  var $window = $(window);

  var $header = $('header');

  // Set header's background image to hat.png when it's day time.
  // Day time: 7:00 -- 18:00
  var hour = new Date();
  hour = hour.getHours();
  if (7 <= hour && hour <= 17) {
    var bgColor = '#D2E374';
    $header.css('background-color', bgColor)
      .css('background-image', 'url("/img/hat.png")');
  }

  // Set header's height to window's innerHeight.
  // Also, applies to sections
  var $sections = $('section');
  var setHeight = function setHeight ($els, $target) {
    $els.forEach(function ($el) {
      $el.css('height', $target.innerHeight() + 'px');
    });
  };
  setHeight([$header, $sections], $window);
  $window.on('resize', setHeight.bind(this, [$header, $sections], $window));

  // Magic bg section.
  var $greeting = $('section#greetings');
  var $bgLeft = $greeting.find('#magic-bg-left');
  var $bgRight = $greeting.find('#magic-bg-right');
  var $avatarLeft = $greeting.find('#avatar-left');
  var $avatarRight = $greeting.find('#avatar-right');

  $bgLeft.css('width', $window.innerWidth() / 2 + 'px');
  $bgRight.css('width', $window.innerWidth() + 'px');

  var offset = 143 / 2;
  $avatarLeft.css('left', $window.innerWidth() / 2 - offset + 'px');
  $avatarRight.css('left', $window.innerWidth() / 2 - offset + 'px');

  var $content = $greeting.find('#section-content');
  var padding = 100;
  var setMagicHeight = function setMagicHeight () {
    var height = $content.innerHeight() + padding;
    $greeting.css('height', height + 'px');
    $bgLeft.css('height', height + 'px');
    $bgRight.css('height', height + 'px')
            .css('width', $window.innerWidth() + 'px');

    $avatarLeft.css('left', $window.innerWidth() / 2 - offset + 'px');
    $avatarRight.css('left', $window.innerWidth() / 2 - offset + 'px');
  };
  setMagicHeight();
  $window.on('resize', setMagicHeight);

  // Register magic bg event.
  $greeting.on('mousemove', function (evt) {
    $bgLeft.css('width', evt.clientX + 'px');
  });

  // Set external links.
  var $externals = $('a[rel*=external]');
  $externals.each(function () {
    var $this = $(this);
    $this.attr('target', '_blank');
  });

  // Set scrolling anchors.
  var $body = $('html, body');
  var $scrollings = $('a[data-type*=scroll]');
  $scrollings.each(function () {
    var $this = $(this);
    var $target = $this.attr('href');
    $target = $($target);
    $this.on('click', function (evt) {
      evt.preventDefault();
      $body.animate({
        scrollTop: $target.offset().top
      }, 500);
    });
  });
})(window, document, jQuery);<commit_msg>vim motion keys<commit_after>(function (window, document, $) {
  'use strict';
  var $window = $(window);

  var $header = $('header');

  // Set header's background image to hat.png when it's day time.
  // Day time: 7:00 -- 18:00
  var hour = new Date();
  hour = hour.getHours();
  if (7 <= hour && hour <= 17) {
    var bgColor = '#D2E374';
    $header.css('background-color', bgColor)
      .css('background-image', 'url("/img/hat.png")');
  }

  // Set header's height to window's innerHeight.
  // Also, applies to sections
  var $sections = $('section');
  var setHeight = function setHeight ($els, $target) {
    $els.forEach(function ($el) {
      $el.css('height', $target.innerHeight() + 'px');
    });
  };
  setHeight([$header, $sections], $window);
  $window.on('resize', setHeight.bind(this, [$header, $sections], $window));

  // Magic bg section.
  var $greeting = $('section#greetings');
  var $bgLeft = $greeting.find('#magic-bg-left');
  var $bgRight = $greeting.find('#magic-bg-right');
  var $avatarLeft = $greeting.find('#avatar-left');
  var $avatarRight = $greeting.find('#avatar-right');

  $bgLeft.css('width', $window.innerWidth() / 2 + 'px');
  $bgRight.css('width', $window.innerWidth() + 'px');

  var offset = 143 / 2;
  $avatarLeft.css('left', $window.innerWidth() / 2 - offset + 'px');
  $avatarRight.css('left', $window.innerWidth() / 2 - offset + 'px');

  var $content = $greeting.find('#section-content');
  var padding = 100;
  var setMagicHeight = function setMagicHeight () {
    var height = $content.innerHeight() + padding;
    $greeting.css('height', height + 'px');
    $bgLeft.css('height', height + 'px');
    $bgRight.css('height', height + 'px')
            .css('width', $window.innerWidth() + 'px');

    $avatarLeft.css('left', $window.innerWidth() / 2 - offset + 'px');
    $avatarRight.css('left', $window.innerWidth() / 2 - offset + 'px');
  };
  setMagicHeight();
  $window.on('resize', setMagicHeight);

  // Register magic bg event.
  $greeting.on('mousemove', function (evt) {
    $bgLeft.css('width', evt.clientX + 'px');
  });

  // Set external links.
  var $externals = $('a[rel*=external]');
  $externals.each(function () {
    var $this = $(this);
    $this.attr('target', '_blank');
  });

  // Set scrolling anchors.
  var $body = $('html, body');
  var $scrollings = $('a[data-type*=scroll]');
  $scrollings.each(function () {
    var $this = $(this);
    var $target = $this.attr('href');
    $target = $($target);
    $this.on('click', function (evt) {
      evt.preventDefault();
      $body.animate({
        scrollTop: $target.offset().top
      }, 500);
    });
  });

  // Register Vim motion keys (i.e. j, k, gg, G)
  var step = 50;
  var preKey;
  $window.on('keypress', function (evt) {
    var key = evt.keyCode;

    if (key === 103) { // key: g
      if (preKey === true) {
        $window.scrollTop(0);
        preKey = false;
      } else {
        preKey = true;
      }
      return;
    }

    preKey = false;

    if (key === 106) { // key: j
      $window.scrollTop($window.scrollTop() + step);
      return;
    }
    else if (key === 107) { // key: k
      $window.scrollTop($window.scrollTop() - step);
      return;
    }
    else if (key === 71) { // key: G
      $window.scrollTop($body.innerHeight());
      return;
    }
  });
})(window, document, jQuery);<|endoftext|>


<commit_before>var irc = require('irc');
var fs = require('fs');
var async = require('async');

var TreslekBot = function(conf) {
  this.conf = conf;
  this.plugins = {};
  this.registeredCommands = {};
  this.registeredHooks = {};
};

TreslekBot.prototype.start = function() {
  var that = this;

  this.irc = new irc.Client(this.conf.host, this.conf.nick, this.conf.ircOptions);
  this.irc.connect(1, function() {
    console.log("CONNECTED");
  });

  // Add error listener
  this.irc.addListener('error', function(err) {
    console.log('Error: ' + err);
  });

  // Add say listener
  this.irc.addListener('message#', function(from, to, text, msg) {
    // Emit message to plugins
    console.log('pubmsg to ' + to + ' from ' + from);
    that.executeCommands(from, to, text, msg);
    that.executeHooks(from, to, text, msg);
  });

  this.irc.addListener('pm', function(from, text, msg) {
    // Emit pm to plugins
    console.log('privmsg from ' + from);
    that.executeCommands(from, that.conf.nick, text, msg);
  });

  this.loadPlugins();
};

TreslekBot.prototype.executeCommands = function(from, to, text, msg) {
  var command, plugin;

  if (from === this.conf.nick || text.indexOf('!') !== 0) {
    return;
  }

  text = text.split(' ');

  command = text.shift().slice(1);

  if (!this.registeredCommands.hasOwnProperty(command)) {
    return;
  }

  plugin = new this.plugins[this.registeredCommands[command]]();
  plugin[command](this.irc, to, from, text.join(' '), function(err) {
    if (err) {
      console.log('Error!', err);
      return;
    }
    console.log('Successfully executed ' + command);
  });
};

TreslekBot.prototype.executeHooks = function(from, to, text, msg) {
  var that = this;

  async.forEach(Object.keys(this.registeredHooks), function(hook, callback) {
    var plugin = new that.plugins[that.registeredHooks[hook]]();
    plugin[hook](that.irc, to, from, text, function(err) {
      if (err) {
        console.log('Error!', err);
        callback();
      }

      console.log('Successfully executed ' + hook);
      callback();
    });
  });
};

TreslekBot.prototype.loadPlugin = function(pluginFile) {
  var plugin,
      that = this;

  try {
    if (require.cache.hasOwnProperty(pluginFile)) {
      delete require.cache[pluginFile];
    }
    var pluginModule = require(pluginFile).Plugin;
    this.plugins[pluginFile] = pluginModule;

    plugin = new pluginModule;
    plugin.commands.forEach(function(command) {
      that.registeredCommands[command] = pluginFile;
    });
    plugin.hooks.forEach(function(hook) {
      that.registeredHooks[hook] = pluginFile;
    });
  } catch (err) {
    console.log('Error loading plugin: ', {plugin: plugin, err: err});
  }
};

TreslekBot.prototype.loadPlugins = function() {
  var that = this;

  fs.readdir(this.conf.plugins_dir, function(err, files) {
    if (err) {
      return;
    }

    files.forEach(function(file) {
      // Only load js files
      if (file.slice(-3) === '.js') {
        that.loadPlugin(that.conf.plugins_dir + file.slice(0, -3));
      }
    });
  });
};

exports.TreslekBot = TreslekBot;
<commit_msg>add some comments<commit_after>var irc = require('irc');
var fs = require('fs');
var async = require('async');

/*
 * TreslekBot. Where the magic happens.
 * {Obj} Config object.
 */
var TreslekBot = function(conf) {
  this.conf = conf;
  this.plugins = {};
  this.registeredCommands = {};
  this.registeredHooks = {};
};

/*
 * Start the bot. Connect to irc, connect listeners, and load plugins.
 */
TreslekBot.prototype.start = function() {
  var that = this;

  this.irc = new irc.Client(this.conf.host, this.conf.nick, this.conf.ircOptions);
  this.irc.connect(1, function() {
    console.log("CONNECTED");
  });

  this.irc.addListener('error', function(err) {
    console.log('Error: ' + err);
  });

  this.irc.addListener('message#', function(from, to, text, msg) {
    // Emit message to plugins
    console.log('pubmsg to ' + to + ' from ' + from);
    that.executeCommands(from, to, text, msg);
    that.executeHooks(from, to, text, msg);
  });

  this.irc.addListener('pm', function(from, text, msg) {
    // Emit pm to plugins
    console.log('privmsg from ' + from);
    that.executeCommands(from, that.conf.nick, text, msg);
  });

  this.loadPlugins();
};

/*
 * Given a message, see if we have any loaded plugins that register
 * a command for the message. Commands start with !.
 */
TreslekBot.prototype.executeCommands = function(from, to, text, msg) {
  var command, plugin;

  if (from === this.conf.nick || text.indexOf('!') !== 0) {
    return;
  }

  text = text.split(' ');

  command = text.shift().slice(1);

  if (!this.registeredCommands.hasOwnProperty(command)) {
    return;
  }

  plugin = new this.plugins[this.registeredCommands[command]]();
  plugin[command](this.irc, to, from, text.join(' '), function(err) {
    if (err) {
      console.log('Error!', err);
      return;
    }
    console.log('Successfully executed ' + command);
  });
};

/*
 * Given a message, send the message to any hooks that have been registered
 * by loaded plugins.
 */
TreslekBot.prototype.executeHooks = function(from, to, text, msg) {
  var that = this;

  async.forEach(Object.keys(this.registeredHooks), function(hook, callback) {
    var plugin = new that.plugins[that.registeredHooks[hook]]();
    plugin[hook](that.irc, to, from, text, function(err) {
      if (err) {
        console.log('Error!', err);
        callback();
      }

      console.log('Successfully executed ' + hook);
      callback();
    });
  });
};

/*
 * Load a plugin, and register its commands and hooks.
 */
TreslekBot.prototype.loadPlugin = function(pluginFile) {
  var plugin,
      that = this;

  try {
    if (require.cache.hasOwnProperty(pluginFile)) {
      delete require.cache[pluginFile];
    }
    var pluginModule = require(pluginFile).Plugin;
    this.plugins[pluginFile] = pluginModule;

    plugin = new pluginModule;
    plugin.commands.forEach(function(command) {
      that.registeredCommands[command] = pluginFile;
    });
    plugin.hooks.forEach(function(hook) {
      that.registeredHooks[hook] = pluginFile;
    });
  } catch (err) {
    console.log('Error loading plugin: ', {plugin: plugin, err: err});
  }
};

/*
 * Check for the plugins_dir specified in the config, and load all js
 * files located there as plugins.
 */
TreslekBot.prototype.loadPlugins = function() {
  var that = this;

  fs.readdir(this.conf.plugins_dir, function(err, files) {
    if (err) {
      return;
    }

    files.forEach(function(file) {
      // Only load js files
      if (file.slice(-3) === '.js') {
        that.loadPlugin(that.conf.plugins_dir + file.slice(0, -3));
      }
    });
  });
};

exports.TreslekBot = TreslekBot;
<|endoftext|>


<commit_before>var Bacon = require('baconjs');
var BaconModel = require('bacon.model');
var React = require('react');
var ReactDOM = require('react-dom');
var ReactGridLayout = require('react-grid-layout');
var WidthProvider = require('react-grid-layout').WidthProvider;

var InstrumentPanel = require('./instrumentpanel');
var IpNavBar = require('./navbar');
var SettingsPanel = require('./settings/settingspanel');
var StreamBundle = require('../streambundle');

var streamBundle = new StreamBundle();
var instrumentPanel = new InstrumentPanel(streamBundle);

var model = Bacon.Model({
  isLocked: Bacon.Model(true),
  settingsVisible: Bacon.Model(false),
  gridSettings: instrumentPanel.gridSettingsModel
});

ReactGridLayout = WidthProvider(ReactGridLayout);

var App = React.createClass({
  mixins: [require('../util/baconmodelmixin')],

  getInitialState: function() {
    return this.props.model.get();
  },

  render: function() {
    var wrapInDiv = function(widget, i) {
      return React.createElement('div', {key: widget.key}, widget);
    };

    var width = window.innerWidth
      || document.documentElement.clientWidth
      || document.body.clientWidth;

    var height = window.innerHeight
      || document.documentElement.clientHeight
      || document.body.clientHeight;

    return (

        <IpNavBar model={this.props.model} instrumentPanel={instrumentPanel}></IpNavBar>
        {this.state.settingsVisible ?
          <SettingsPanel instrumentPanel={instrumentPanel}></SettingsPanel> :
          <ReactGridLayout
            className="layout"
            layout={this.state.gridSettings.layout}
            children={this.state.gridSettings.activeWidgets.map(wrapInDiv)}
            cols={Math.round(width/320*2)}
            rowHeight={50}
            isDraggable={!this.state.isLocked}
            isResizable={!this.state.isLocked}
            onLayoutChange={instrumentPanel.onLayoutChange.bind(instrumentPanel)} />}

    );
  }
});

var app = ReactDOM.render(<App model={model}/>, document.getElementById('content'));
<commit_msg>Fix bacon model combining<commit_after>var Bacon = require('baconjs');
var BaconModel = require('bacon.model');
var React = require('react');
var ReactDOM = require('react-dom');
var ReactGridLayout = require('react-grid-layout');
var WidthProvider = require('react-grid-layout').WidthProvider;

var InstrumentPanel = require('./instrumentpanel');
var IpNavBar = require('./navbar');
var SettingsPanel = require('./settings/settingspanel');
var StreamBundle = require('../streambundle');

var streamBundle = new StreamBundle();
var instrumentPanel = new InstrumentPanel(streamBundle);

var model = Bacon.Model.combine({
  isLocked: Bacon.Model(true),
  settingsVisible: Bacon.Model(false),
  gridSettings: instrumentPanel.gridSettingsModel
});

ReactGridLayout = WidthProvider(ReactGridLayout);

var App = React.createClass({
  mixins: [require('../util/baconmodelmixin')],

  getInitialState: function() {
    return this.props.model.get();
  },

  render: function() {
    var wrapInDiv = function(widget, i) {
      return React.createElement('div', {key: widget.key}, widget);
    };

    var width = window.innerWidth
      || document.documentElement.clientWidth
      || document.body.clientWidth;

    var height = window.innerHeight
      || document.documentElement.clientHeight
      || document.body.clientHeight;

    return (

        <IpNavBar model={this.props.model} instrumentPanel={instrumentPanel}></IpNavBar>
        {this.state.settingsVisible ?
          <SettingsPanel instrumentPanel={instrumentPanel}></SettingsPanel> :
          <ReactGridLayout
            className="layout"
            layout={this.state.gridSettings.layout}
            children={this.state.gridSettings.activeWidgets.map(wrapInDiv)}
            cols={Math.round(width/320*2)}
            rowHeight={50}
            isDraggable={!this.state.isLocked}
            isResizable={!this.state.isLocked}
            onLayoutChange={instrumentPanel.onLayoutChange.bind(instrumentPanel)} />}

    );
  }
});

var app = ReactDOM.render(<App model={model}/>, document.getElementById('content'));
<|endoftext|>


<commit_before>'use strict';

const botkit = require('botkit');
const _S = require('underscore.string');
const moment = require('moment');
const bluebird = require('bluebird');

function setupApp(slackapp, config, businessUnitProvider, trustpilot) {

  /*
    Startup
  */

  slackapp.configureSlackApp({
    clientId: config.SLACK_CLIENT_ID,
    clientSecret: config.SLACK_SECRET,
    scopes: ['bot', 'channels:history', 'incoming-webhook', 'commands']
  });

  slackapp.on('tick', () => {});

  // just a simple way to make sure we don't
  // connect to the RTM twice for the same team
  var _bots = {};

  function trackBot(bot) {
    _bots[bot.config.token] = bot;
  }

  slackapp.on('create_bot', (bot, config) => {
    if (_bots[bot.config.token]) {
      // already online! do nothing.
      return;
    }
    bluebird.promisifyAll(bot);

    bot.startRTMAsync().then(() => {
      trackBot(bot);
      console.info(`Use /incoming-webhooks/${bot.team_info.id} to receive new reviews.`);

      bot.startPrivateConversationAsync({
        user: config.createdBy
      }).then((convo) => {
        convo.say('I am a bot that has just joined your team');
        convo.say('You must now /invite me to a channel so that I can be of use!');
      });
    });
  });

  /*
    Internal workings
  */

  function formatReview(review) {
    var stars = _S.repeat('★', review.stars) + _S.repeat('✩', 5 - review.stars);
    var reviewMoment = moment(review.createdAt);
    var color = (review.stars >= 4) ? 'good' : (review.stars <= 2) ? 'danger' : 'warning';

    return {
      'text': '',
      'attachments': [{
        'callback_id': review.id,
        'attachment_type': 'default',
        'fallback': '',
        'author_name': review.consumer.displayName,
        'title': review.title,
        'text': review.text,
        'color': color,
        'footer': stars,
        'ts': reviewMoment.format('X'),
        'actions': [{
          'name': 'step_1_write_reply',
          'text': ':writing_hand: Reply',
          'value': 'step_1_write_reply',
          'type': 'button'
        }]
      }]
    };
  }

  var _replyTrackers = {};

  function trackReply(reviewId, message) {
    _replyTrackers[reviewId] = {
      reviewMessageTs: message.original_message.ts,
      start: message.action_ts
    };
  }

  function getReplyTracker(reviewId) {
    return _replyTrackers[reviewId];
  }

  function askForReply(bot, message) {
    var reviewId = message.original_message.attachments[0].callback_id;
    trackReply(reviewId, message);

    var replyStepMessage = message.original_message;
    replyStepMessage.text = 'You are replying to';
    replyStepMessage.attachments[0].actions = null;
    bot.replyInteractive(message, replyStepMessage);

    bot.replyInThread(replyStepMessage, {
      'text': `Please write your reply in this thread, in as many lines as you need. Hit the "Send reply" button
 when you're done.`,
      'attachments': [{
        'callback_id': reviewId,
        'attachment_type': 'default',
        'text': '',
        'actions': [{
          'name': 'step_2_send_reply',
          'text': ':postal_horn: Send reply',
          'value': 'step_2_send_reply',
          'type': 'button'
        }]
      }]
    });
  }

  function collectUserMessages(bot, user, channel, threadTs) {
    bluebird.promisifyAll(bot.api.channels);

    return bot.api.channels.repliesAsync({
      'token': bot.config.incoming_webhook.token,
      'channel': channel,
      'thread_ts': threadTs
    }).then((data) => {
      if (data && data.hasOwnProperty('messages')) {
        var fullText = data.messages.filter((message) => {
          return message.user === user;
        })
        .map((message) => {
          return message.text;
        })
        .join('\n');
        return fullText;
      }
      return null;
    });
  }

  function handleReply(bot, message) {
    var currentChannel = message.channel;
    var ts = message.original_message.ts;
    var reviewId = message.original_message.attachments[0].callback_id;
    var tracker = getReplyTracker(reviewId);
    if (!tracker) {
      return;
    }

    collectUserMessages(bot, message.user, currentChannel, tracker.reviewMessageTs).then((fullText) => {
      if (fullText) {
        trustpilot.replyToReview(reviewId, fullText).then(() => {
          bot.api.chat.update({
            ts: tracker.reviewMessageTs,
            channel: currentChannel,
            text: 'You have replied to this review.'
          });
          bot.api.chat.delete({
            ts: ts,
            channel: currentChannel
          });
          delete _replyTrackers[reviewId];
        });
      }
    });
  }

  /*
    Entry points
  */

  slackapp.hears(['.*'], ['direct_mention'], (bot, message) => {
    bot.reply(message, 'I can take Slash commands now! Just use /trustpilot in a public channel where I\'m invited.');
  });

  slackapp.on('slash_command', (bot, message) => {
    if (message.token !== config.VERIFICATION_TOKEN) {
      return;
    }
    if (/^[1-5] stars?$/i.test(message.text) || /^la(te)?st$/i.test(message.text)) {
      var nbStars = Number(message.text.split(' ')[0]);
      nbStars = isNaN(nbStars) ? null : nbStars;
      var slackTeamId = bot.team_info.id;

      businessUnitProvider.getTeamBusinessUnitId(slackTeamId).then(function (businessUnitId) {
        trustpilot.getLastUnansweredReview(nbStars, businessUnitId).then(function (lastReview) {
          if (lastReview) {
            bot.reply(message, formatReview(lastReview));
          }
        });
      });
    }
  });

  slackapp.on('interactive_message_callback', (bot, message) => {
    if (message.token !== config.VERIFICATION_TOKEN) {
      return;
    }
    switch (message.actions[0].value) {
      case 'step_1_write_reply':
        askForReply(bot, message);
        break;
      case 'step_2_send_reply':
        handleReply(bot, message);
        break;
    }
  });

  /*
    Incoming webhook plumbing
  */

  slackapp.postNewReview = function (review, teamId) {
    slackapp.findTeamById(teamId, (err, team) => {
      if (!err && team) {
        var bot = slackapp.spawn(team);
        var message = formatReview(review);
        message.username = bot.config.bot.name; // Confusing, but such is life
        message.channel = bot.config.incoming_webhook.channel;
        bot.send(message);
      }
    });
  };
}

module.exports = function (config, businessUnitProvider, trustpilot, storage) {
  var slackapp = botkit.slackbot({
    'debug': false,
    'storage': storage,
    'json_file_store': './storage/', // Fallback to jfs when no storage middleware provided
    'retry': 2
  });
  setupApp(slackapp, config, businessUnitProvider, trustpilot);
  return slackapp;
};
<commit_msg>Use thread timestamp to collect messages instead of in-memory tracker<commit_after>'use strict';

const botkit = require('botkit');
const _S = require('underscore.string');
const moment = require('moment');
const bluebird = require('bluebird');

function setupApp(slackapp, config, businessUnitProvider, trustpilot) {

  /*
    Startup
  */

  slackapp.configureSlackApp({
    clientId: config.SLACK_CLIENT_ID,
    clientSecret: config.SLACK_SECRET,
    scopes: ['bot', 'channels:history', 'incoming-webhook', 'commands']
  });

  slackapp.on('tick', () => {});

  // just a simple way to make sure we don't
  // connect to the RTM twice for the same team
  var _bots = {};

  function trackBot(bot) {
    _bots[bot.config.token] = bot;
  }

  slackapp.on('create_bot', (bot, config) => {
    if (_bots[bot.config.token]) {
      // already online! do nothing.
      return;
    }
    bluebird.promisifyAll(bot);

    bot.startRTMAsync().then(() => {
      trackBot(bot);
      console.info(`Use /incoming-webhooks/${bot.team_info.id} to receive new reviews.`);

      bot.startPrivateConversationAsync({
        user: config.createdBy
      }).then((convo) => {
        convo.say('I am a bot that has just joined your team');
        convo.say('You must now /invite me to a channel so that I can be of use!');
      });
    });
  });

  /*
    Internal workings
  */

  function formatReview(review) {
    var stars = _S.repeat('★', review.stars) + _S.repeat('✩', 5 - review.stars);
    var reviewMoment = moment(review.createdAt);
    var color = (review.stars >= 4) ? 'good' : (review.stars <= 2) ? 'danger' : 'warning';

    return {
      'text': '',
      'attachments': [{
        'callback_id': review.id,
        'attachment_type': 'default',
        'fallback': '',
        'author_name': review.consumer.displayName,
        'title': review.title,
        'text': review.text,
        'color': color,
        'footer': stars,
        'ts': reviewMoment.format('X'),
        'actions': [{
          'name': 'step_1_write_reply',
          'text': ':writing_hand: Reply',
          'value': 'step_1_write_reply',
          'type': 'button'
        }]
      }]
    };
  }

  function askForReply(bot, message) {
    var reviewId = message.original_message.attachments[0].callback_id;
    var replyStepMessage = message.original_message;
    replyStepMessage.text = 'You are replying to';
    replyStepMessage.attachments[0].actions = null;
    bot.replyInteractive(message, replyStepMessage);

    bot.replyInThread(replyStepMessage, {
      'text': `Please write your reply in this thread, in as many lines as you need. Hit the "Send reply" button
 when you're done.`,
      'attachments': [{
        'callback_id': reviewId,
        'attachment_type': 'default',
        'text': '',
        'actions': [{
          'name': 'step_2_send_reply',
          'text': ':postal_horn: Send reply',
          'value': 'step_2_send_reply',
          'type': 'button'
        }]
      }]
    });
  }

  function collectUserMessages(bot, user, channel, threadTs) {
    bluebird.promisifyAll(bot.api.channels);

    return bot.api.channels.repliesAsync({
      'token': bot.config.incoming_webhook.token,
      'channel': channel,
      'thread_ts': threadTs
    }).then((data) => {
      if (data && data.hasOwnProperty('messages')) {
        var fullText = data.messages.filter((message) => {
          return message.user === user;
        })
        .map((message) => {
          return message.text;
        })
        .join('\n');
        return fullText;
      }
      return null;
    });
  }

  function handleReply(bot, message) {
    var currentChannel = message.channel;
    var ts = message.original_message.ts;
    var threadTs = message.original_message.thread_ts;
    var reviewId = message.original_message.attachments[0].callback_id;

    collectUserMessages(bot, message.user, currentChannel, threadTs).then((fullText) => {
      if (fullText) {
        trustpilot.replyToReview(reviewId, fullText).then(() => {
          bot.api.chat.update({
            ts: threadTs,
            channel: currentChannel,
            text: 'You have replied to this review.'
          });
          bot.api.chat.delete({
            ts: ts,
            channel: currentChannel
          });
        });
      }
    });
  }

  /*
    Entry points
  */

  slackapp.hears(['.*'], ['direct_mention'], (bot, message) => {
    bot.reply(message, 'I can take Slash commands now! Just use /trustpilot in a public channel where I\'m invited.');
  });

  slackapp.on('slash_command', (bot, message) => {
    if (message.token !== config.VERIFICATION_TOKEN) {
      return;
    }
    if (/^[1-5] stars?$/i.test(message.text) || /^la(te)?st$/i.test(message.text)) {
      var nbStars = Number(message.text.split(' ')[0]);
      nbStars = isNaN(nbStars) ? null : nbStars;
      var slackTeamId = bot.team_info.id;

      businessUnitProvider.getTeamBusinessUnitId(slackTeamId).then(function (businessUnitId) {
        trustpilot.getLastUnansweredReview(nbStars, businessUnitId).then(function (lastReview) {
          if (lastReview) {
            bot.reply(message, formatReview(lastReview));
          }
        });
      });
    }
  });

  slackapp.on('interactive_message_callback', (bot, message) => {
    if (message.token !== config.VERIFICATION_TOKEN) {
      return;
    }
    switch (message.actions[0].value) {
      case 'step_1_write_reply':
        askForReply(bot, message);
        break;
      case 'step_2_send_reply':
        handleReply(bot, message);
        break;
    }
  });

  /*
    Incoming webhook plumbing
  */

  slackapp.postNewReview = function (review, teamId) {
    slackapp.findTeamById(teamId, (err, team) => {
      if (!err && team) {
        var bot = slackapp.spawn(team);
        var message = formatReview(review);
        message.username = bot.config.bot.name; // Confusing, but such is life
        message.channel = bot.config.incoming_webhook.channel;
        bot.send(message);
      }
    });
  };
}

module.exports = function (config, businessUnitProvider, trustpilot, storage) {
  var slackapp = botkit.slackbot({
    'debug': false,
    'storage': storage,
    'json_file_store': './storage/', // Fallback to jfs when no storage middleware provided
    'retry': 2
  });
  setupApp(slackapp, config, businessUnitProvider, trustpilot);
  return slackapp;
};
<|endoftext|>