<commit_before>const request = require('request');
var _ = require( 'underscore' );
var results = []
var survey_result = {}

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

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

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

var controller = Botkit.facebookbot({
    debug: false,
    access_token: process.env.page_token,
    verify_token: process.env.verify_token,
});

var bot = controller.spawn({
});

controller.setupWebserver(process.env.port || 3000, function(err, webserver) {
    controller.createWebhookEndpoints(webserver, bot, function() {
        console.log('ONLINE!');
    });
});

controller.hears(['hi','Hi'], 'message_received', function(bot, message) {
    getProfile(message.user, function(err, profile) {
        var found_result = _.findWhere(results, {id: message.user});
        if (found_result == undefined){
            bot.reply(message, `Hello ${profile.first_name}`);
            askSurvey(bot, message);
        } else {
            bot.reply(message, `Hello ${profile.first_name}, you have already done the survey`);
            redoSurvey(bot, message)
        }
    });

});

// POSTBACK HANLDER
controller.on('facebook_postback', function(bot, message) {
    if (message.payload == 'yes(start)') {
        bot.reply(message, `Excellent! Lets get started.`);
        survey_result = {}
        getProfile(message.user, function(err, profile) {
            survey_result.id = message.user
            survey_result.user = `${profile.first_name} ${profile.last_name}`
            survey_result.gender = `${profile.gender}`
            survey_result.locale = `${profile.locale}`
            survey_result.timezone = `${profile.timezone}`
        });
        askRelationship(bot, message)
    } else if (message.payload == 'I love it' || message.payload == 'I hate it' || message.payload == 'Guilty pleasure') {

            if (survey_result.relationship == null) {
                survey_result.relationship = message.payload
                askDetail(bot, message)
            } else {
                bot.reply(message, `You've already answered that question.`);
            }

    } else if (message.payload == 'I make it myself' || message.payload == 'KFC is my go to' || message.payload == 'Any way is good' || message.payload == 'Fried food is gross' || message.payload == `I don't eat animals` || message.payload == `It's a secret` || message.payload == `reward` ||message.payload == `cures hangover`) {

            if (survey_result.detail == null) {
                survey_result.detail = message.payload
                askMood(bot, message)
            } else {
                bot.reply(message, `You've already answered that question.`);
            }

    } else if (message.payload == '(•‿•)' || message.payload == '(•︵•)' || message.payload == '(•_•)') {

            if (survey_result.mood == null) {
                survey_result.mood = message.payload
                askPreference(bot, message)
            } else {
                bot.reply(message, `You've already answered that question.`);
            }

    } else if (message.payload == 'Chicken Parmesan' || message.payload == 'Double Down' || message.payload == 'Fried Drumsticks' || message.payload == 'Chicken Nuggets' || message.payload == 'Veggies') {

            if (survey_result.preference == null) {
                survey_result.preference = message.payload
                askHungry(bot, message)
            } else {
                bot.reply(message, `You've already answered that question.`);
            }

    } else if (message.payload == 'yes' || message.payload == 'no' ) {

            if (survey_result.hungry == null) {
                survey_result.hungry = message.payload
                saveResults(bot, message)
            } else if (survey_result == {}){
            bot.reply(message, `You've already answered that question.`);
            }
    } else if (message.payload == 'no(survey)') {
        sayThanks(bot, message)
    } else if (message.payload == 'View results') {
        viewResults(bot, message)
    } else if (message.payload == 'Re-do survey') {
        var found_result = _.findWhere(results, {id: message.user});
        bot.reply(message, `Excellent! Lets get started.`);
        found_result = {}
        survey_result = {}
        getProfile(message.user, function(err, profile) {
            survey_result.id = message.user
            survey_result.user = `${profile.first_name} ${profile.last_name}`
            survey_result.gender = `${profile.gender}`
            survey_result.locale = `${profile.locale}`
            survey_result.timezone = `${profile.timezone}`
        });
        askRelationship(bot, message)
    }
});
// QUESTIONS
viewResults = function(bot, message) {
    var found_result = _.findWhere(results, {id: message.user});
    var text = JSON.stringify(found_result)
    bot.reply(message, `${text}`);

}

askSurvey = function(bot, message) {
    var attachment = {
        'type':'template',
        'payload':{
            'template_type':'button',
            'text': 'Do you have a moment to answer some questions about fried chicken ?',
            'buttons':[
                {
                'type':'postback',
                'title':`Yes`,
                'payload':`yes(start)`
                },
                {
                'type':'postback',
                'title':`No`,
                'payload':`no(survey)`
                }
            ]
        }
    };

    bot.reply(message, {
        attachment: attachment,
    });
}

askRelationship = function(bot, message) {
    var attachment = {
        'type':'template',
        'payload':{
            'template_type':'button',
            'text':  'What would you say your relationship is with fried chicken ?',
            'buttons':[
                {
                'type':'postback',
                'title':'I love it',
                'payload':'I love it'
                },
                {
                'type':'postback',
                'title':'I hate it',
                'payload':'I hate it'
                },
                {
                'type':'postback',
                'title':'Guilty pleasure',
                'payload':'Guilty pleasure'
                }
            ]
        }
    };

    bot.reply(message, {
        attachment: attachment,
    });
}

askDetail = function(bot, message) {
    if (message.payload == 'I love it') {
        var attachment = {
            'type':'template',
            'payload':{
                'template_type':'button',
                'text': 'What is your favourite way to eat fried chicken ?',
                'buttons':[
                    {
                    'type':'postback',
                    'title':'I make it myself',
                    'payload':'I make it myself'
                    },
                    {
                    'type':'postback',
                    'title':'KFC is my go to',
                    'payload':'KFC is my go to'
                    },
                    {
                    'type':'postback',
                    'title':'Any way is good',
                    'payload':'Any way is good'
                    }
                ]
            }
        };

        bot.reply(message, {
            attachment: attachment,
        });

    } else if (message.payload == 'I hate it') {
        var attachment = {
            'type':'template',
            'payload':{
                'template_type':'button',
                'text': 'Not a fan ? tell me more.',
                'buttons':[
                    {
                    'type':'postback',
                    'title':'Fried food is gross',
                    'payload':'Fried food is gross'
                    },
                    {
                    'type':'postback',
                    'title':`I don't eat animals`,
                    'payload':`I don't eat animals`
                    },
                    {
                    'type':'postback',
                    'title':`It's a secret`,
                    'payload':`It's a secret`
                    }
                ]
            }
        };

        bot.reply(message, {
            attachment: attachment,
        });

    } else if (message.payload == 'Guilty pleasure') {
        var attachment = {
            'type':'template',
            'payload':{
                'template_type':'button',
                'text':  'Guilty pleasure you say, tell me more.',
                'buttons':[
                    {
                    'type':'postback',
                    'title':'When Hungover',
                    'payload':'cures hangover'
                    },
                    {
                    'type':'postback',
                    'title':'Reward for myself',
                    'payload':'reward'
                    },
                    {
                    'type':'postback',
                    'title':`It's a secret`,
                    'payload':`It's a secret`
                    }
                ]
            }
        };

        bot.reply(message, {
            attachment: attachment,
        });

    } else  {
        bot.reply(message, 'oops')
    }

}

askMood = function(bot, message) {
    var attachment = {
        'type':'template',
        'payload':{
            'template_type':'button',
            'text': 'What is your current mood ?',
            'buttons':[
                {
                'type':'postback',
                'title':`(•‿•)`,
                'payload':`(•‿•)`
                },
                {
                'type':'postback',
                'title':`(•︵•)`,
                'payload':`(•︵•)`
                },
                {
                'type':'postback',
                'title':`(•_•)`,
                'payload':`(•_•)`
                }
            ]
        }
    };

    bot.reply(message, {
        attachment: attachment,
    });

}

askPreference = function(bot, message) {
    var attachment = {
        'type':'template',
        'payload': {
                'template_type': 'generic',
                'elements': [
                    {
                        'title': 'Chicken Parmesan',
                        'image_url': 'http://fiber-international.com/wp-content/uploads/2015/04/800x600-chicken.jpg',
                        'buttons': [
                            {
                                'type': 'postback',
                                'title': 'Choose',
                                'payload': 'Chicken Parmesan'
                            }
                        ]
                    },
                    {
                        'title': 'Double Down',
                        'image_url': 'http://assets.bwbx.io/images/ieMg5BCeWkWU/v1/-1x-1.jpg',
                        'buttons': [
                            {
                                'type': 'postback',
                                'title': 'Choose',
                                'payload': 'Double Down'
                            }
                        ]
                    },
                    {
                        'title': 'Fried Drumsticks',
                        'image_url': 'https://i.ytimg.com/vi/G8hbFO-r2nQ/maxresdefault.jpg',
                        'buttons': [
                            {
                                'type': 'postback',
                                'title': 'Choose',
                                'payload': 'Fried Drumsticks'
                            }
                        ]
                    },
                    {
                        'title': 'Chicken Nuggets',
                        'image_url': 'http://www.urbanmommies.com/wp-content/uploads/McDonalds-Chicken-Nuggets.jpg',
                        'buttons': [
                            {
                                'type': 'postback',
                                'title': 'Choose',
                                'payload': 'Chicken Nuggets'
                            }
                        ]
                    },
                    {
                        'title': 'Veggies',
                        'image_url': 'http://www.stevensonfitness.com/wp-content/uploads/2014/10/veggies.jpg',
                        'buttons': [
                            {
                                'type': 'postback',
                                'title': 'Choose',
                                'payload': 'Veggies'
                            }
                        ]
                    }
                ]
            }
    };

    bot.reply(message, {
        attachment: attachment,
    });

    bot.reply(message, 'Which of these meals your you like to be eating right now ?');
}

askHungry = function(bot, message) {
    var attachment = {
        'type':'template',
        'payload':{
            'template_type':'button',
            'text': 'Have I made you hungry ?',
            'buttons':[
                {
                'type':'postback',
                'title':`yes`,
                'payload':`yes`
                },
                {
                'type':'postback',
                'title':`no`,
                'payload':`no`
                }
            ]
        }
    };

    bot.reply(message, {
        attachment: attachment,
    });

}
// OTHER RESPONSES
sayThanks = function(bot, message) {
  bot.reply(message, 'OK! thanks for your time');
}

saveResults = function(bot, message) {
    results.push(survey_result)
    console.log(results)
    redoSurvey(bot, message)
}

redoSurvey = function(bot, message) {
    var attachment = {
        'type':'template',
        'payload':{
            'template_type':'button',
            'text': 'OK! thanks for your time',
            'buttons':[
                {
                'type':'postback',
                'title':`view results`,
                'payload':`View results`
                },
                {
                'type':'postback',
                'title':`re-do survey`,
                'payload':`Re-do survey`
                }
            ]
        }
    };

    bot.reply(message, {
        attachment: attachment,
    });

}
// GET USER INFO !!!
getProfile = function (id, cb) {
    if (!cb) cb = Function.prototype

    request({
      method: 'GET',
      uri: `https://graph.facebook.com/v2.6/${id}`,
      qs: {
        fields: 'first_name,last_name,profile_pic,gender,locale,timezone',
        access_token: process.env.page_token
      },
      json: true
    }, function(err, res, body) {
      if (err) return cb(err)
      if (body.error) return cb(body.error)

      cb(null, body)
    })
}
<commit_msg>one result per unique user<commit_after>const request = require('request');
var _ = require( 'underscore' );
var results = []
var survey_result = {}

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

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

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

var controller = Botkit.facebookbot({
    debug: false,
    access_token: process.env.page_token,
    verify_token: process.env.verify_token,
});

var bot = controller.spawn({
});

controller.setupWebserver(process.env.port || 3000, function(err, webserver) {
    controller.createWebhookEndpoints(webserver, bot, function() {
        console.log('ONLINE!');
    });
});

controller.hears(['hi','Hi'], 'message_received', function(bot, message) {
    getProfile(message.user, function(err, profile) {
        var found_result = _.findWhere(results, {id: message.user});
        if (found_result == undefined){
            bot.reply(message, `Hello ${profile.first_name}`);
            askSurvey(bot, message);
        } else {
            bot.reply(message, `Hello ${profile.first_name}, you have already done the survey`);
            redoSurvey(bot, message)
        }
    });

});

// POSTBACK HANLDER
controller.on('facebook_postback', function(bot, message) {
    if (message.payload == 'yes(start)') {
        bot.reply(message, `Excellent! Lets get started.`);
        survey_result = {}
        getProfile(message.user, function(err, profile) {
            survey_result.id = message.user
            survey_result.user = `${profile.first_name} ${profile.last_name}`
            survey_result.gender = `${profile.gender}`
            survey_result.locale = `${profile.locale}`
            survey_result.timezone = `${profile.timezone}`
        });
        askRelationship(bot, message)
    } else if (message.payload == 'I love it' || message.payload == 'I hate it' || message.payload == 'Guilty pleasure') {

            if (survey_result.relationship == null) {
                survey_result.relationship = message.payload
                askDetail(bot, message)
            } else {
                bot.reply(message, `You've already answered that question.`);
            }

    } else if (message.payload == 'I make it myself' || message.payload == 'KFC is my go to' || message.payload == 'Any way is good' || message.payload == 'Fried food is gross' || message.payload == `I don't eat animals` || message.payload == `It's a secret` || message.payload == `reward` ||message.payload == `cures hangover`) {

            if (survey_result.detail == null) {
                survey_result.detail = message.payload
                askMood(bot, message)
            } else {
                bot.reply(message, `You've already answered that question.`);
            }

    } else if (message.payload == '(•‿•)' || message.payload == '(•︵•)' || message.payload == '(•_•)') {

            if (survey_result.mood == null) {
                survey_result.mood = message.payload
                askPreference(bot, message)
            } else {
                bot.reply(message, `You've already answered that question.`);
            }

    } else if (message.payload == 'Chicken Parmesan' || message.payload == 'Double Down' || message.payload == 'Fried Drumsticks' || message.payload == 'Chicken Nuggets' || message.payload == 'Veggies') {

            if (survey_result.preference == null) {
                survey_result.preference = message.payload
                askHungry(bot, message)
            } else {
                bot.reply(message, `You've already answered that question.`);
            }

    } else if (message.payload == 'yes' || message.payload == 'no' ) {

            if (survey_result.hungry == null) {
                survey_result.hungry = message.payload
                saveResults(bot, message)
            } else if (survey_result == {}){
            bot.reply(message, `You've already answered that question.`);
            }
    } else if (message.payload == 'no(survey)') {
        sayThanks(bot, message)
    } else if (message.payload == 'View results') {
        viewResults(bot, message)
    } else if (message.payload == 'Re-do survey') {
        bot.reply(message, `Excellent! Lets get started.`);
        survey_result = {}
        getProfile(message.user, function(err, profile) {
            survey_result.id = message.user
            survey_result.user = `${profile.first_name} ${profile.last_name}`
            survey_result.gender = `${profile.gender}`
            survey_result.locale = `${profile.locale}`
            survey_result.timezone = `${profile.timezone}`

        });
        askRelationship(bot, message)
    } else if (message.payload == `I'm done`) {
        saveResults(bot, message)
        sayThanks(bot, message)
    }
});
// View Results
viewResults = function(bot, message) {
    var found_result = _.findWhere(results, {id: message.user});
    var text = JSON.stringify(found_result)
    bot.reply(message, `${text}`);
}
// QUESTIONS
askSurvey = function(bot, message) {
    var attachment = {
        'type':'template',
        'payload':{
            'template_type':'button',
            'text': 'Do you have a moment to answer some questions about fried chicken ?',
            'buttons':[
                {
                'type':'postback',
                'title':`Yes`,
                'payload':`yes(start)`
                },
                {
                'type':'postback',
                'title':`No`,
                'payload':`no(survey)`
                }
            ]
        }
    };

    bot.reply(message, {
        attachment: attachment,
    });
}

askRelationship = function(bot, message) {
    var attachment = {
        'type':'template',
        'payload':{
            'template_type':'button',
            'text':  'What would you say your relationship is with fried chicken ?',
            'buttons':[
                {
                'type':'postback',
                'title':'I love it',
                'payload':'I love it'
                },
                {
                'type':'postback',
                'title':'I hate it',
                'payload':'I hate it'
                },
                {
                'type':'postback',
                'title':'Guilty pleasure',
                'payload':'Guilty pleasure'
                }
            ]
        }
    };

    bot.reply(message, {
        attachment: attachment,
    });
}

askDetail = function(bot, message) {
    if (message.payload == 'I love it') {
        var attachment = {
            'type':'template',
            'payload':{
                'template_type':'button',
                'text': 'What is your favourite way to eat fried chicken ?',
                'buttons':[
                    {
                    'type':'postback',
                    'title':'I make it myself',
                    'payload':'I make it myself'
                    },
                    {
                    'type':'postback',
                    'title':'KFC is my go to',
                    'payload':'KFC is my go to'
                    },
                    {
                    'type':'postback',
                    'title':'Any way is good',
                    'payload':'Any way is good'
                    }
                ]
            }
        };

        bot.reply(message, {
            attachment: attachment,
        });

    } else if (message.payload == 'I hate it') {
        var attachment = {
            'type':'template',
            'payload':{
                'template_type':'button',
                'text': 'Not a fan ? tell me more.',
                'buttons':[
                    {
                    'type':'postback',
                    'title':'Fried food is gross',
                    'payload':'Fried food is gross'
                    },
                    {
                    'type':'postback',
                    'title':`I don't eat animals`,
                    'payload':`I don't eat animals`
                    },
                    {
                    'type':'postback',
                    'title':`It's a secret`,
                    'payload':`It's a secret`
                    }
                ]
            }
        };

        bot.reply(message, {
            attachment: attachment,
        });

    } else if (message.payload == 'Guilty pleasure') {
        var attachment = {
            'type':'template',
            'payload':{
                'template_type':'button',
                'text':  'Guilty pleasure you say, tell me more.',
                'buttons':[
                    {
                    'type':'postback',
                    'title':'When Hungover',
                    'payload':'cures hangover'
                    },
                    {
                    'type':'postback',
                    'title':'Reward for myself',
                    'payload':'reward'
                    },
                    {
                    'type':'postback',
                    'title':`It's a secret`,
                    'payload':`It's a secret`
                    }
                ]
            }
        };

        bot.reply(message, {
            attachment: attachment,
        });

    } else  {
        bot.reply(message, 'oops')
    }

}

askMood = function(bot, message) {
    var attachment = {
        'type':'template',
        'payload':{
            'template_type':'button',
            'text': 'What is your current mood ?',
            'buttons':[
                {
                'type':'postback',
                'title':`(•‿•)`,
                'payload':`(•‿•)`
                },
                {
                'type':'postback',
                'title':`(•︵•)`,
                'payload':`(•︵•)`
                },
                {
                'type':'postback',
                'title':`(•_•)`,
                'payload':`(•_•)`
                }
            ]
        }
    };

    bot.reply(message, {
        attachment: attachment,
    });

}

askPreference = function(bot, message) {
    var attachment = {
        'type':'template',
        'payload': {
                'template_type': 'generic',
                'elements': [
                    {
                        'title': 'Chicken Parmesan',
                        'image_url': 'http://fiber-international.com/wp-content/uploads/2015/04/800x600-chicken.jpg',
                        'buttons': [
                            {
                                'type': 'postback',
                                'title': 'Choose',
                                'payload': 'Chicken Parmesan'
                            }
                        ]
                    },
                    {
                        'title': 'Double Down',
                        'image_url': 'http://assets.bwbx.io/images/ieMg5BCeWkWU/v1/-1x-1.jpg',
                        'buttons': [
                            {
                                'type': 'postback',
                                'title': 'Choose',
                                'payload': 'Double Down'
                            }
                        ]
                    },
                    {
                        'title': 'Fried Drumsticks',
                        'image_url': 'https://i.ytimg.com/vi/G8hbFO-r2nQ/maxresdefault.jpg',
                        'buttons': [
                            {
                                'type': 'postback',
                                'title': 'Choose',
                                'payload': 'Fried Drumsticks'
                            }
                        ]
                    },
                    {
                        'title': 'Chicken Nuggets',
                        'image_url': 'http://www.urbanmommies.com/wp-content/uploads/McDonalds-Chicken-Nuggets.jpg',
                        'buttons': [
                            {
                                'type': 'postback',
                                'title': 'Choose',
                                'payload': 'Chicken Nuggets'
                            }
                        ]
                    },
                    {
                        'title': 'Veggies',
                        'image_url': 'http://www.stevensonfitness.com/wp-content/uploads/2014/10/veggies.jpg',
                        'buttons': [
                            {
                                'type': 'postback',
                                'title': 'Choose',
                                'payload': 'Veggies'
                            }
                        ]
                    }
                ]
            }
    };

    bot.reply(message, {
        attachment: attachment,
    });

    bot.reply(message, 'Which of these meals your you like to be eating right now ?');
}

askHungry = function(bot, message) {
    var attachment = {
        'type':'template',
        'payload':{
            'template_type':'button',
            'text': 'Have I made you hungry ?',
            'buttons':[
                {
                'type':'postback',
                'title':`yes`,
                'payload':`yes`
                },
                {
                'type':'postback',
                'title':`no`,
                'payload':`no`
                }
            ]
        }
    };

    bot.reply(message, {
        attachment: attachment,
    });

}
// OTHER RESPONSES
sayThanks = function(bot, message) {
  bot.reply(message, 'OK! thanks for your time');
}
//saving
saveResults = function(bot, message) {
    results.push(survey_result)
    console.log(results)
}

redoSurvey = function(bot, message) {
    var attachment = {
        'type':'template',
        'payload':{
            'template_type':'button',
            'text': 'OK! thanks for your time',
            'buttons':[
                {
                'type':'postback',
                'title':`view results`,
                'payload':`View results`
                },
                {
                'type':'postback',
                'title':`re-do survey`,
                'payload':`Re-do survey`
                },
                {
                'type':'postback',
                'title':`I'm done`,
                'payload':`I'm done`
                }
            ]
        }
    };

    bot.reply(message, {
        attachment: attachment,
    });

}
// GET USER INFO !!!
getProfile = function (id, cb) {
    if (!cb) cb = Function.prototype

    request({
      method: 'GET',
      uri: `https://graph.facebook.com/v2.6/${id}`,
      qs: {
        fields: 'first_name,last_name,profile_pic,gender,locale,timezone',
        access_token: process.env.page_token
      },
      json: true
    }, function(err, res, body) {
      if (err) return cb(err)
      if (body.error) return cb(body.error)

      cb(null, body)
    })
}
<|endoftext|>


<commit_before>const express = require('express')
const expressWs = require('express-ws')
const pty = require('node-pty')

const {interval, Subject} = require('rxjs')
const {map, filter, bufferTime} = require('rxjs/operators')

const terminals = {}
const subscriptions = []
const logs = {}

const connect = (req, res) => {
    const cols = parseInt(req.query.cols)
    const rows = parseInt(req.query.rows)
    const username = JSON.parse(req.headers['sepal-user']).username
    const keyFile = `/sepalUsers/${username}/.ssh/id_rsa`
    const sshGateway = process.env.SSH_GATEWAY_HOST
    const term = pty.spawn('sudo', ['ssh', '-q', '-o', 'StrictHostKeyChecking=no', '-i', keyFile, `${username}@${sshGateway}`], {
        name: 'xterm-color',
        cols: cols || 80,
        rows: rows || 24,
        cwd: process.env.PWD,
        env: process.env
    })

    console.log('Created terminal with PID: ' + term.pid)
    terminals[term.pid] = term
    logs[term.pid] = ''
    term.on('data', data => logs[term.pid] += data)
    res.send(term.pid.toString())
    res.end()
}

const resize = (req, res) => {
    const pid = parseInt(req.params.pid)
    const cols = parseInt(req.query.cols)
    const rows = parseInt(req.query.rows)
    const term = terminals[pid]

    term.resize(cols, rows)
    console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.')
    res.end()
}

const terminal = (ws, req) => {
    var term = terminals[parseInt(req.params.pid)]
    console.log('Connected to terminal ' + term.pid)
    try {
        ws.send(logs[term.pid])
    } catch (ex) {}

    const downStream$ = new Subject()
    subscriptions.push(
        downStream$
            .pipe(
                bufferTime(10),
                map(buffered => buffered.join('')),
                filter(data => data)
            )
            .subscribe(
                data => {
                    try {
                        ws.send(data)
                    } catch (ex) {}
                }
            )
    )

    subscriptions.push(
        interval(3000)
            .subscribe(() => ws.send(''))
    )

    term.on('data', data => downStream$.next(data))

    ws.on('message', msg => term.write(msg))

    ws.on('close', () => {
        term.kill()
        subscriptions.forEach(subscription => subscription.unsubscribe())
        console.log('Closed terminal ' + term.pid)
        delete terminals[term.pid]
        delete logs[term.pid]
    })
}

const startServer = () => {

    const app = express()
    expressWs(app)

    app.post('/', connect)
    app.post('/:pid/size', resize)
    app.ws('/:pid', terminal)

    const port = process.env.PORT || 3000
    const host = process.env.IP || '127.0.0.1'

    console.log('App listening to http://' + host + ':' + port)
    app.listen(port, host)
}

startServer()
<commit_msg>Protected websocket write against socket closed.<commit_after>const express = require('express')
const expressWs = require('express-ws')
const pty = require('node-pty')

const {interval, Subject} = require('rxjs')
const {map, filter, bufferTime} = require('rxjs/operators')

const terminals = {}
const subscriptions = []
const logs = {}

const connect = (req, res) => {
    const cols = parseInt(req.query.cols)
    const rows = parseInt(req.query.rows)
    const username = JSON.parse(req.headers['sepal-user']).username
    const keyFile = `/sepalUsers/${username}/.ssh/id_rsa`
    const sshGateway = process.env.SSH_GATEWAY_HOST
    const term = pty.spawn('sudo', ['ssh', '-q', '-o', 'StrictHostKeyChecking=no', '-i', keyFile, `${username}@${sshGateway}`], {
        name: 'xterm-color',
        cols: cols || 80,
        rows: rows || 24,
        cwd: process.env.PWD,
        env: process.env
    })

    console.log('Created terminal with PID: ' + term.pid)
    terminals[term.pid] = term
    logs[term.pid] = ''
    term.on('data', data => logs[term.pid] += data)
    res.send(term.pid.toString())
    res.end()
}

const resize = (req, res) => {
    const pid = parseInt(req.params.pid)
    const cols = parseInt(req.query.cols)
    const rows = parseInt(req.query.rows)
    const term = terminals[pid]

    term.resize(cols, rows)
    console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.')
    res.end()
}

const terminal = (ws, req) => {
    var term = terminals[parseInt(req.params.pid)]
    console.log('Connected to terminal ' + term.pid)

    const webSocketSend = data => {
        try {
            ws.send(data)
        } catch (ex) {}
    }

    webSocketSend(logs[term.pid])

    const downStream$ = new Subject()
    subscriptions.push(
        downStream$
            .pipe(
                bufferTime(10),
                map(buffered => buffered.join('')),
                filter(data => data)
            )
            .subscribe(
                data => webSocketSend(data)
            )
    )

    subscriptions.push(
        interval(3000)
            .subscribe(() => webSocketSend(''))
    )

    term.on('data', data => downStream$.next(data))

    ws.on('message', msg => term.write(msg))

    ws.on('close', () => {
        term.kill()
        subscriptions.forEach(subscription => subscription.unsubscribe())
        console.log('Closed terminal ' + term.pid)
        delete terminals[term.pid]
        delete logs[term.pid]
    })
}

const startServer = () => {

    const app = express()
    expressWs(app)

    app.post('/', connect)
    app.post('/:pid/size', resize)
    app.ws('/:pid', terminal)

    const port = process.env.PORT || 3000
    const host = process.env.IP || '127.0.0.1'

    console.log('App listening to http://' + host + ':' + port)
    app.listen(port, host)
}

startServer()
<|endoftext|>


<commit_before>const mailgun = require('mailgun-js');
const mailcomposer = require('mailcomposer');
const Mustache = require('mustache');
const co = require('co');
const fs = require('fs');
const path = require('path');
const MailAdapter = require('./MailAdapter');

const ERRORS = {
    missing_configuration: 'MailgunAdapter requires configuration.',
    missing_mailgun_settings: 'MailgunAdapter requires valid API Key, domain and fromAddress.',
    bad_template_config: 'MailgunAdapter templates are not properly configured.',
    invalid_callback: 'MailgunAdapter template callback is not a function.',
    invalid_template_name: 'Invalid options object: missing templateName'
};

/**
 * MailAdapter implementation used by the Parse Server to send
 * password reset and email verification emails though Mailgun
 * @classnpm install --save-dev babel-preset-es2015-node
 */
class MailgunAdapter extends MailAdapter {
    constructor(options) {
        if (!options) {
            throw new Error(ERRORS.missing_configuration);
        }

        super(options);

        const { apiKey, domain, fromAddress } = options;
        if (!apiKey || !domain || !fromAddress) {
            throw new Error(ERRORS.missing_mailgun_settings);
        }

        const { templates } = options;
        if (!templates || Object.keys(templates).length === 0) {
            throw new Error(ERRORS.bad_template_config);
        }

        for (let name in templates) {
            const { subject, pathPlainText, callback } = templates[name];

            if (typeof subject !== 'string' || typeof pathPlainText !== 'string') {
                throw new Error(ERRORS.bad_template_config);
            }

            if (callback && typeof callback !== 'function') {
                throw new Error(ERRORS.invalid_callback);
            }
        }

        this.mailcomposer = mailcomposer;
        this.mailgun = mailgun({ apiKey, domain });
        this.fromAddress = fromAddress;
        this.templates = templates;
        this.cache = {};
    }

    /**
     * Method to send MIME emails via Mailgun
     * @param {Object} options
     * @returns {Promise}
     */
    _sendMail(options) {
        let templateVars, message, selectedTemplate = {};

        let templateName = selectedTemplate.name = options.templateName;
        if (!templateName) {
            throw new Error(ERRORS.invalid_template_name);
        }

        let template = selectedTemplate.config = this.templates[templateName];
        if (!template) {
            throw new Error(`Could not find template with name ${templateName}`);
        }

        // The adapter is used directly by the user's code instead via Parse Server
        if (options.direct) {
            const { subject, fromAddress, recipient, variables } = options;

            if (!recipient) {
                throw new Error(`Cannot send email with template ${templateName} without a recipient`);
            }

            templateVars = variables || {};
            message = {
                from: fromAddress || this.fromAddress,
                to: recipient,
                subject: subject || template.subject
            };
        } else {
            const { link, appName, user } = options;
            const { callback } = template;

            let userVars;
            if (callback && typeof callback === 'function') {
                userVars = callback(user);
                userVars = this._validateUserVars(userVars);
            }

            templateVars = Object.assign({
                link,
                appName,
                username: user.get('username'),
                email: user.get('email')
            }, userVars);

            message = {
                from: this.fromAddress,
                to: user.get('email'),
                subject: template.subject
            };
        }

        const args = { templateVars, message, selectedTemplate };
        return co(this._mailGenerator.bind(this, args)).catch(e => console.error(e));
    }

    /**
     * Generator function that handles that handles all the async operations:
     * template loading, MIME string building and email sending.
     */
    *_mailGenerator(args) {
        let compiled;
        let { config: template, name: templateName } = args.selectedTemplate;
        let { message, templateVars } = args;
        let pathPlainText = template.pathPlainText;
        let pathHtml = template.pathHtml;
        let cachedTemplate = this.cache[templateName] = this.cache[templateName] || {};

        // Load plain-text version
        if (!cachedTemplate['text']) {
            let plainTextEmail = yield this._loadEmailTemplate(pathPlainText);
            plainTextEmail = plainTextEmail.toString('utf8');
            cachedTemplate['text'] = plainTextEmail;
        }

        // Compile plain-text template
        message.text = Mustache.render(cachedTemplate['text'], templateVars);

        // Load html version if available
        if (pathHtml) {
            if (!cachedTemplate['html']) {
                let htmlEmail = yield this._loadEmailTemplate(pathHtml);
                cachedTemplate['html'] = htmlEmail.toString('utf8');
            }
            // Add processed HTML to the message object
            message.html = Mustache.render(cachedTemplate['html'], templateVars);;
        }

        // Initialize mailcomposer with message
        const composer = this.mailcomposer(message);

        // Create MIME string
        const mimeString = yield new Promise((resolve, reject) => {
            composer.build((error, message) => {
                if (error) reject(error);
                resolve(message);
            });
        });

        // Assemble payload object for Mailgun
        const payload = {
            to: message.to,
            message: mimeString.toString('utf8')
        };

        return new Promise((resolve, reject) => {
            this.mailgun.messages().sendMime(payload, (error, body) => {
                if (error) reject(error);
                resolve(body);
            });
        });
    }

    /**
     * sendMail wrapper to send an email with password reset link
     * The options object would have the parameters link, appName, user
     * @param {Object} options
     * @returns {Promise}
     */
    sendPasswordResetEmail({ link, appName, user }) {
        return this._sendMail({ templateName: 'passwordResetEmail', link, appName, user });
    }

    /**
     * sendMail wrapper to send an email with an account verification link
     * The options object would have the parameters link, appName, user
     * @param {Object} options
     * @returns {Promise}
     */
    sendVerificationEmail({ link, appName, user }) {
        return this._sendMail({ templateName: 'verificationEmail', link, appName, user });
    }

    /**
     * sendMail wrapper to send general purpose emails
     * The options object would have the parameters:
     * - templateName: name of template to be used
     * - subject: overrides the default value
     * - fromAddress: overrides the default from address
     * - recipient: email's recipient
     * - variables: An object whose property names represent template variables,
     *              and whose values will replace the template variable placeholders
     * @param {Object} options
     * @returns {Promise}
     */
    send({ templateName, subject, fromAddress, recipient, variables }) {
        return this._sendMail({ templateName, subject, fromAddress, recipient, variables, direct: true });
    }

    /**
     * Simple Promise wrapper to asynchronously fetch the contents of a template.
     * @param {String} path
     * @returns {Promise}
     */
    _loadEmailTemplate(path) {
        return new Promise((resolve, reject) => {
            fs.readFile(path, (err, data) => {
                if (err) reject(err);
                resolve(data);
            });
        });
    }

    /**
     * Validator for user provided template variables
     * @param {Object} userVars
     * @returns {Object}
     */
    _validateUserVars(userVars) {
        const validUserVars = userVars && userVars.constructor === Object;
        // Fall back to an empty object if the callback did not return an Object instance
        return validUserVars ? userVars : {};
    }
}

module.exports = MailgunAdapter;<commit_msg>Add extra option to extend message<commit_after>const mailgun = require('mailgun-js');
const mailcomposer = require('mailcomposer');
const Mustache = require('mustache');
const co = require('co');
const fs = require('fs');
const path = require('path');
const MailAdapter = require('./MailAdapter');

const ERRORS = {
    missing_configuration: 'MailgunAdapter requires configuration.',
    missing_mailgun_settings: 'MailgunAdapter requires valid API Key, domain and fromAddress.',
    bad_template_config: 'MailgunAdapter templates are not properly configured.',
    invalid_callback: 'MailgunAdapter template callback is not a function.',
    invalid_template_name: 'Invalid options object: missing templateName'
};

/**
 * MailAdapter implementation used by the Parse Server to send
 * password reset and email verification emails though Mailgun
 * @classnpm install --save-dev babel-preset-es2015-node
 */
class MailgunAdapter extends MailAdapter {
    constructor(options) {
        if (!options) {
            throw new Error(ERRORS.missing_configuration);
        }

        super(options);

        const { apiKey, domain, fromAddress } = options;
        if (!apiKey || !domain || !fromAddress) {
            throw new Error(ERRORS.missing_mailgun_settings);
        }

        const { templates } = options;
        if (!templates || Object.keys(templates).length === 0) {
            throw new Error(ERRORS.bad_template_config);
        }

        for (let name in templates) {
            const { subject, pathPlainText, callback } = templates[name];

            if (typeof subject !== 'string' || typeof pathPlainText !== 'string') {
                throw new Error(ERRORS.bad_template_config);
            }

            if (callback && typeof callback !== 'function') {
                throw new Error(ERRORS.invalid_callback);
            }
        }

        this.mailcomposer = mailcomposer;
        this.mailgun = mailgun({ apiKey, domain });
        this.fromAddress = fromAddress;
        this.templates = templates;
        this.cache = {};
    }

    /**
     * Method to send MIME emails via Mailgun
     * @param {Object} options
     * @returns {Promise}
     */
    _sendMail(options) {
        let templateVars, message, selectedTemplate = {};

        let templateName = selectedTemplate.name = options.templateName;
        if (!templateName) {
            throw new Error(ERRORS.invalid_template_name);
        }

        let template = selectedTemplate.config = this.templates[templateName];
        if (!template) {
            throw new Error(`Could not find template with name ${templateName}`);
        }

        // The adapter is used directly by the user's code instead via Parse Server
        if (options.direct) {
            const { subject, fromAddress, recipient, variables, extra } = options;

            if (!recipient) {
                throw new Error(`Cannot send email with template ${templateName} without a recipient`);
            }

            templateVars = variables || {};
            message = Object.assign({
                from: fromAddress || this.fromAddress,
                to: recipient,
                subject: subject || template.subject
            }, extra || {});
        } else {
            const { link, appName, user } = options;
            const { callback } = template;

            let userVars;
            if (callback && typeof callback === 'function') {
                userVars = callback(user);
                userVars = this._validateUserVars(userVars);
            }

            templateVars = Object.assign({
                link,
                appName,
                username: user.get('username'),
                email: user.get('email')
            }, userVars);

            message = {
                from: this.fromAddress,
                to: user.get('email'),
                subject: template.subject
            };
        }

        const args = { templateVars, message, selectedTemplate };
        return co(this._mailGenerator.bind(this, args)).catch(e => console.error(e));
    }

    /**
     * Generator function that handles that handles all the async operations:
     * template loading, MIME string building and email sending.
     */
    *_mailGenerator(args) {
        let compiled;
        let { config: template, name: templateName } = args.selectedTemplate;
        let { message, templateVars } = args;
        let pathPlainText = template.pathPlainText;
        let pathHtml = template.pathHtml;
        let cachedTemplate = this.cache[templateName] = this.cache[templateName] || {};

        // Load plain-text version
        if (!cachedTemplate['text']) {
            let plainTextEmail = yield this._loadEmailTemplate(pathPlainText);
            plainTextEmail = plainTextEmail.toString('utf8');
            cachedTemplate['text'] = plainTextEmail;
        }

        // Compile plain-text template
        message.text = Mustache.render(cachedTemplate['text'], templateVars);

        // Load html version if available
        if (pathHtml) {
            if (!cachedTemplate['html']) {
                let htmlEmail = yield this._loadEmailTemplate(pathHtml);
                cachedTemplate['html'] = htmlEmail.toString('utf8');
            }
            // Add processed HTML to the message object
            message.html = Mustache.render(cachedTemplate['html'], templateVars);;
        }

        // Initialize mailcomposer with message
        const composer = this.mailcomposer(message);

        // Create MIME string
        const mimeString = yield new Promise((resolve, reject) => {
            composer.build((error, message) => {
                if (error) reject(error);
                resolve(message);
            });
        });

        // Assemble payload object for Mailgun
        const payload = {
            to: message.to,
            message: mimeString.toString('utf8')
        };

        return new Promise((resolve, reject) => {
            this.mailgun.messages().sendMime(payload, (error, body) => {
                if (error) reject(error);
                resolve(body);
            });
        });
    }

    /**
     * sendMail wrapper to send an email with password reset link
     * The options object would have the parameters link, appName, user
     * @param {Object} options
     * @returns {Promise}
     */
    sendPasswordResetEmail({ link, appName, user }) {
        return this._sendMail({ templateName: 'passwordResetEmail', link, appName, user });
    }

    /**
     * sendMail wrapper to send an email with an account verification link
     * The options object would have the parameters link, appName, user
     * @param {Object} options
     * @returns {Promise}
     */
    sendVerificationEmail({ link, appName, user }) {
        return this._sendMail({ templateName: 'verificationEmail', link, appName, user });
    }

    /**
     * sendMail wrapper to send general purpose emails
     * The options object would have the parameters:
     * - templateName: name of template to be used
     * - subject: overrides the default value
     * - fromAddress: overrides the default from address
     * - recipient: email's recipient
     * - variables: An object whose property names represent template variables,
     *              and whose values will replace the template variable placeholders
     * @param {Object} options
     * @returns {Promise}
     */
    send({ templateName, subject, fromAddress, recipient, variables }) {
        return this._sendMail({ templateName, subject, fromAddress, recipient, variables, direct: true });
    }

    /**
     * Simple Promise wrapper to asynchronously fetch the contents of a template.
     * @param {String} path
     * @returns {Promise}
     */
    _loadEmailTemplate(path) {
        return new Promise((resolve, reject) => {
            fs.readFile(path, (err, data) => {
                if (err) reject(err);
                resolve(data);
            });
        });
    }

    /**
     * Validator for user provided template variables
     * @param {Object} userVars
     * @returns {Object}
     */
    _validateUserVars(userVars) {
        const validUserVars = userVars && userVars.constructor === Object;
        // Fall back to an empty object if the callback did not return an Object instance
        return validUserVars ? userVars : {};
    }
}

module.exports = MailgunAdapter;
<|endoftext|>


<commit_before>(function () {
    var cabrito = cabrito || {};
    window.cabrito = cabrito;
    return window.cabrito;
})();

(function (namespace) {
    if (!namespace) {
        return;
    }

    var Class = function () {
        this.constructor = function Class () {};
        this.create = function () {
            var instance = this;
            instance.constructor.apply(instance, arguments);
            return instance;
        };
        this.extends = function (body) {
            var constructor = function () {};
            var prototype = constructor.prototype = this;
            body.call(this);
            return constructor.prototype;
        };
    };

    namespace.Class = namespace.Class || new Class();

    return namespace.Class;
})((window.cabrito) ? window.cabrito : undefined);

//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImNhYnJpdG8uanMiLCJjbGFzcy5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNMQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJjYWJyaXRvLmpzIiwic291cmNlc0NvbnRlbnQiOlsiKGZ1bmN0aW9uICgpIHtcbiAgICB2YXIgY2Ficml0byA9IGNhYnJpdG8gfHwge307XG4gICAgd2luZG93LmNhYnJpdG8gPSBjYWJyaXRvO1xuICAgIHJldHVybiB3aW5kb3cuY2Ficml0bztcbn0pKCk7XG4iLCIoZnVuY3Rpb24gKG5hbWVzcGFjZSkge1xuICAgIGlmICghbmFtZXNwYWNlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB2YXIgQ2xhc3MgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHRoaXMuY29uc3RydWN0b3IgPSBmdW5jdGlvbiBDbGFzcyAoKSB7fTtcbiAgICAgICAgdGhpcy5jcmVhdGUgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICB2YXIgaW5zdGFuY2UgPSB0aGlzO1xuICAgICAgICAgICAgaW5zdGFuY2UuY29uc3RydWN0b3IuYXBwbHkoaW5zdGFuY2UsIGFyZ3VtZW50cyk7XG4gICAgICAgICAgICByZXR1cm4gaW5zdGFuY2U7XG4gICAgICAgIH07XG4gICAgICAgIHRoaXMuZXh0ZW5kcyA9IGZ1bmN0aW9uIChib2R5KSB7XG4gICAgICAgICAgICB2YXIgY29uc3RydWN0b3IgPSBmdW5jdGlvbiAoKSB7fTtcbiAgICAgICAgICAgIHZhciBwcm90b3R5cGUgPSBjb25zdHJ1Y3Rvci5wcm90b3R5cGUgPSB0aGlzO1xuICAgICAgICAgICAgYm9keS5jYWxsKHRoaXMpO1xuICAgICAgICAgICAgcmV0dXJuIGNvbnN0cnVjdG9yLnByb3RvdHlwZTtcbiAgICAgICAgfTtcbiAgICB9O1xuXG4gICAgbmFtZXNwYWNlLkNsYXNzID0gbmFtZXNwYWNlLkNsYXNzIHx8IG5ldyBDbGFzcygpO1xuXG4gICAgcmV0dXJuIG5hbWVzcGFjZS5DbGFzcztcbn0pKCh3aW5kb3cuY2Ficml0bykgPyB3aW5kb3cuY2Ficml0byA6IHVuZGVmaW5lZCk7XG4iXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0=<commit_msg>Atualizando Cabrito.js<commit_after>(function () {
    var cabrito = cabrito || {};
    window.cabrito = cabrito;
    return window.cabrito;
})();

(function (namespace) {
    if (!namespace) {
        return;
    }

    var Class = function () {
        this.constructor = function Class () {};
        this.create = function () {
            var instance = this;
            instance.constructor.apply(instance, arguments);
            return instance;
        };
        this.extends = function (body) {
            var constructor = function () {};
            var prototype = constructor.prototype = this;
            body.call(this);
            return constructor.prototype;
        };
    };

    namespace.Class = namespace.Class || new Class();

    return namespace.Class;
})((window.cabrito) ? window.cabrito : undefined);

(function (namespace) {
    if (!namespace) {
        return;
    }

    var PageLoad = function () {
        var $public = this;
        this.ready = function (fn, timeout) {
            var readyState = document.readyState;
            timeout = (timeout ? timeout + 10 : 0);
            if (typeof fn != "function") {
                return false;
            }
            if (readyState && readyState == "complete") {
                fn();
            } else {
                setTimeout(function() {
                    $public.ready(fn, timeout);
                }, timeout);
            }
        };
    };

    namespace.PageLoad = namespace.PageLoad || new PageLoad();

    return namespace.PageLoad;
})((window.cabrito) ? window.cabrito : undefined);

//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImNhYnJpdG8uanMiLCJjbGFzcy5qcyIsInBhZ2Vsb2FkLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ0xBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDeEJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImNhYnJpdG8uanMiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gKCkge1xuICAgIHZhciBjYWJyaXRvID0gY2Ficml0byB8fCB7fTtcbiAgICB3aW5kb3cuY2Ficml0byA9IGNhYnJpdG87XG4gICAgcmV0dXJuIHdpbmRvdy5jYWJyaXRvO1xufSkoKTtcbiIsIihmdW5jdGlvbiAobmFtZXNwYWNlKSB7XG4gICAgaWYgKCFuYW1lc3BhY2UpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIHZhciBDbGFzcyA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdGhpcy5jb25zdHJ1Y3RvciA9IGZ1bmN0aW9uIENsYXNzICgpIHt9O1xuICAgICAgICB0aGlzLmNyZWF0ZSA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHZhciBpbnN0YW5jZSA9IHRoaXM7XG4gICAgICAgICAgICBpbnN0YW5jZS5jb25zdHJ1Y3Rvci5hcHBseShpbnN0YW5jZSwgYXJndW1lbnRzKTtcbiAgICAgICAgICAgIHJldHVybiBpbnN0YW5jZTtcbiAgICAgICAgfTtcbiAgICAgICAgdGhpcy5leHRlbmRzID0gZnVuY3Rpb24gKGJvZHkpIHtcbiAgICAgICAgICAgIHZhciBjb25zdHJ1Y3RvciA9IGZ1bmN0aW9uICgpIHt9O1xuICAgICAgICAgICAgdmFyIHByb3RvdHlwZSA9IGNvbnN0cnVjdG9yLnByb3RvdHlwZSA9IHRoaXM7XG4gICAgICAgICAgICBib2R5LmNhbGwodGhpcyk7XG4gICAgICAgICAgICByZXR1cm4gY29uc3RydWN0b3IucHJvdG90eXBlO1xuICAgICAgICB9O1xuICAgIH07XG5cbiAgICBuYW1lc3BhY2UuQ2xhc3MgPSBuYW1lc3BhY2UuQ2xhc3MgfHwgbmV3IENsYXNzKCk7XG5cbiAgICByZXR1cm4gbmFtZXNwYWNlLkNsYXNzO1xufSkoKHdpbmRvdy5jYWJyaXRvKSA/IHdpbmRvdy5jYWJyaXRvIDogdW5kZWZpbmVkKTtcbiIsIihmdW5jdGlvbiAobmFtZXNwYWNlKSB7XHJcbiAgICBpZiAoIW5hbWVzcGFjZSkge1xyXG4gICAgICAgIHJldHVybjtcclxuICAgIH1cclxuXHJcbiAgICB2YXIgUGFnZUxvYWQgPSBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgdmFyICRwdWJsaWMgPSB0aGlzO1xyXG4gICAgICAgIHRoaXMucmVhZHkgPSBmdW5jdGlvbiAoZm4sIHRpbWVvdXQpIHtcclxuICAgICAgICAgICAgdmFyIHJlYWR5U3RhdGUgPSBkb2N1bWVudC5yZWFkeVN0YXRlO1xyXG4gICAgICAgICAgICB0aW1lb3V0ID0gKHRpbWVvdXQgPyB0aW1lb3V0ICsgMTAgOiAwKTtcclxuICAgICAgICAgICAgaWYgKHR5cGVvZiBmbiAhPSBcImZ1bmN0aW9uXCIpIHtcclxuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICBpZiAocmVhZHlTdGF0ZSAmJiByZWFkeVN0YXRlID09IFwiY29tcGxldGVcIikge1xyXG4gICAgICAgICAgICAgICAgZm4oKTtcclxuICAgICAgICAgICAgfSBlbHNlIHtcclxuICAgICAgICAgICAgICAgIHNldFRpbWVvdXQoZnVuY3Rpb24oKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgJHB1YmxpYy5yZWFkeShmbiwgdGltZW91dCk7XHJcbiAgICAgICAgICAgICAgICB9LCB0aW1lb3V0KTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgIH07XHJcbiAgICB9O1xyXG5cclxuICAgIG5hbWVzcGFjZS5QYWdlTG9hZCA9IG5hbWVzcGFjZS5QYWdlTG9hZCB8fCBuZXcgUGFnZUxvYWQoKTtcclxuXHJcbiAgICByZXR1cm4gbmFtZXNwYWNlLlBhZ2VMb2FkO1xyXG59KSgod2luZG93LmNhYnJpdG8pID8gd2luZG93LmNhYnJpdG8gOiB1bmRlZmluZWQpO1xyXG4iXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0=
<|endoftext|>


<commit_before>import Ember from 'ember';
import layout from '../templates/components/ripple-effect';

export default Ember.Component.extend({
  layout: layout,

  onInsert: function() {
    var overlay = this.$("div.ripple-effect-container div.ripple-effect-overlay");
    overlay.click((event) => {
      this.rippleOut(evt, overlay);
    });
  }.on('didInsertElement');

  rippleOut: function(evt, targetElement) {
    // Create SVG circle
    var svgImg = this.$("<svg><g id=\"two\"> <circle fill=\"rgba(255,255,255,0.3)\" " +
        "cx=\"" + evt.offsetX + "\" cy=\"" + evt.offsetY +  "\" r=\"20\"/> </g></svg>");
    svgImg.css("position", "absolute");
    svgImg.css("float", "left");
    svgImg.css("left", "0px");
    svgImg.css("top", "0px");
    svgImg.prop("background","red");
    svgImg.prop("z-index", 100);
    targetElement.append(svgImg);

    //Animate the circle growing
    var c = this.$(svgImg).find("circle");
    c.animate({
      "r" : this.$(svgImg).outerWidth()
    }, {
      duration: 400,
      step : function(val){
        c.attr("r", val);
      },
      complete:  function() {
        // remove circle after animation complete.
        svgImg.remove();
      }
    });
  }
});
<commit_msg>fix comma instead of semicolon<commit_after>import Ember from 'ember';
import layout from '../templates/components/ripple-effect';

export default Ember.Component.extend({
  layout: layout,

  onInsert: function() {
    var overlay = this.$("div.ripple-effect-container div.ripple-effect-overlay");
    overlay.click((event) => {
      this.rippleOut(evt, overlay);
    });
  }.on('didInsertElement'),

  rippleOut: function(evt, targetElement) {
    // Create SVG circle
    var svgImg = this.$("<svg><g id=\"two\"> <circle fill=\"rgba(255,255,255,0.3)\" " +
        "cx=\"" + evt.offsetX + "\" cy=\"" + evt.offsetY +  "\" r=\"20\"/> </g></svg>");
    svgImg.css("position", "absolute");
    svgImg.css("float", "left");
    svgImg.css("left", "0px");
    svgImg.css("top", "0px");
    svgImg.prop("background","red");
    svgImg.prop("z-index", 100);
    targetElement.append(svgImg);

    //Animate the circle growing
    var c = this.$(svgImg).find("circle");
    c.animate({
      "r" : this.$(svgImg).outerWidth()
    }, {
      duration: 400,
      step : function(val){
        c.attr("r", val);
      },
      complete:  function() {
        // remove circle after animation complete.
        svgImg.remove();
      }
    });
  }
});
<|endoftext|>


<commit_before>// @flow

import * as React from 'react'
import {
	StyleSheet,
	ScrollView,
	View,
	Text,
	RefreshControl,
	Alert,
} from 'react-native'
import {TabBarIcon} from '../components/tabbar-icon'
import {connect} from 'react-redux'
import {Cell, TableView, Section} from 'react-native-tableview-simple'
import {
	hasSeenAcknowledgement,
	type LoginStateType,
} from '../../flux/parts/settings'
import {updateBalances} from '../../flux/parts/balances'
import {type ReduxState} from '../../flux'
import delay from 'delay'
import * as c from '../components/colors'
import type {TopLevelViewPropsType} from '../types'

const DISCLAIMER = 'This data may be outdated or otherwise inaccurate.'
const LONG_DISCLAIMER =
	'This data may be inaccurate.\nBon Appétit is always right.\nThis app is unofficial.'

type ReactProps = TopLevelViewPropsType

type ReduxStateProps = {
	dining: ?string,
	schillers: ?string,
	print: ?string,
	weeklyMeals: ?string,
	dailyMeals: ?string,
	guestSwipes: ?string,
	loginState: LoginStateType,
	message: ?string,
	alertSeen: boolean,
}

type ReduxDispatchProps = {
	hasSeenAcknowledgement: () => any,
	updateBalances: boolean => any,
}

type Props = ReactProps & ReduxStateProps & ReduxDispatchProps

type State = {
	loading: boolean,
}

class BalancesView extends React.PureComponent<Props, State> {
	static navigationOptions = {
		tabBarLabel: 'Balances',
		tabBarIcon: TabBarIcon('card'),
	}

	state = {
		loading: false,
	}

	componentDidMount() {
		// calling "refresh" here, to make clear to the user
		// that the data is being updated
		this.refresh()

		if (!this.props.alertSeen) {
			Alert.alert('', LONG_DISCLAIMER, [
				{text: 'I Disagree', onPress: this.goBack, style: 'cancel'},
				{text: 'Okay', onPress: this.props.hasSeenAcknowledgement},
			])
		}
	}

	refresh = async () => {
		let start = Date.now()
		this.setState(() => ({loading: true}))

		await this.fetchData()

		// wait 0.5 seconds – if we let it go at normal speed, it feels broken.
		let elapsed = Date.now() - start
		await delay(500 - elapsed)

		this.setState(() => ({loading: false}))
	}

	fetchData = async () => {
		await this.props.updateBalances(true)
	}

	openSettings = () => {
		this.props.navigation.navigate('SettingsView')
	}

	goBack = () => {
		this.props.navigation.goBack(null)
	}

	render() {
		let {
			dining,
			schillers,
			dailyMeals,
			weeklyMeals,
			guestSwipes,
		} = this.props
		let {loading} = this.state

		return (
			<ScrollView
				contentContainerStyle={styles.stage}
				refreshControl={
					<RefreshControl
						onRefresh={this.refresh}
						refreshing={this.state.loading}
					/>
				}
			>
				<TableView>

						<View style={styles.balancesRow}>
							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Schillers"
								value={schillers}
							/>

							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Dining"
								value={dining}
							/>
						</View>

						<View style={styles.balancesRow}>
							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Daily Meals"
								value={dailyMeals}
							/>

							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Weekly Meals"
								value={weeklyMeals}
							/>

							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Guest Swipes"
								style={styles.finalCell}
								value={guestSwipes}
							/>
						</View>

					{(this.props.loginState !== 'checking' &&
						this.props.loginState !== 'logged-in') ||
					this.props.message ? (

							{this.props.loginState !== 'logged-in' ? (
								<Cell
									accessory="DisclosureIndicator"
									cellStyle="Basic"
									onPress={this.openSettings}
									title="Log in with Carleton"
								/>
							) : null}

							{this.props.message ? (
								<Cell cellStyle="Basic" title={this.props.message} />
							) : null}

					) : null}
				</TableView>
			</ScrollView>
		)
	}
}

function mapState(state: ReduxState): ReduxStateProps {
	return {
		schillers: state.balances ? state.balances.schillersBalance : null,
		dining: state.balances ? state.balances.diningBalance : null,
		print: state.balances ? state.balances.printBalance : null,
		weeklyMeals: state.balances ? state.balances.mealsRemainingThisWeek : null,
		dailyMeals: state.balances ? state.balances.mealsRemainingToday : null,
		guestSwipes: state.balances ? state.balances.guestSwipesCount : null,
		message: state.balances ? state.balances.balancesErrorMessage : null,
		alertSeen: state.settings ? state.settings.unofficiallyAcknowledged : false,

		loginState: state.settings ? state.settings.loginState : 'logged-out',
	}
}

function mapDispatch(dispatch): ReduxDispatchProps {
	return {
		updateBalances: force => dispatch(updateBalances(force)),
		hasSeenAcknowledgement: () => dispatch(hasSeenAcknowledgement()),
	}
}

const ConnectedBalancesView = connect(mapState, mapDispatch)(BalancesView)

export default ConnectedBalancesView

let cellMargin = 10
let cellSidePadding = 10
let cellEdgePadding = 10

let styles = StyleSheet.create({
	stage: {
		backgroundColor: c.iosLightBackground,
		paddingTop: 20,
		paddingBottom: 20,
	},

	common: {
		backgroundColor: c.white,
	},

	balances: {
		borderRightWidth: StyleSheet.hairlineWidth,
		borderRightColor: c.iosSeparator,
	},

	finalCell: {
		borderRightWidth: 0,
	},

	balancesRow: {
		flexDirection: 'row',
		marginTop: 0,
		marginBottom: -10,
	},

	rectangle: {
		height: 88,
		flex: 1,
		alignItems: 'center',
		paddingTop: cellSidePadding,
		paddingBottom: cellSidePadding,
		paddingRight: cellEdgePadding,
		paddingLeft: cellEdgePadding,
		marginBottom: cellMargin,
	},

	// Text styling
	financialText: {
		paddingTop: 8,
		color: c.iosDisabledText,
		textAlign: 'center',
		fontWeight: '200',
		fontSize: 23,
	},
	rectangleButtonText: {
		paddingTop: 15,
		color: c.black,
		textAlign: 'center',
		fontSize: 16,
	},
})

export function BigBalancesView(props: TopLevelViewPropsType) {
	return <ConnectedBalancesView navigation={props.navigation} />
}
BigBalancesView.navigationOptions = {
	title: 'Balances',
}

function getValueOrNa(value: ?string): string {
	// eslint-disable-next-line no-eq-null
	if (value == null) {
		return 'N/A'
	}
	return value
}

function FormattedValueCell(props: {
	indeterminate: boolean,
	label: string,
	value: ?string,
	style?: any,
	formatter: (?string) => string,
}) {
	let {indeterminate, label, value, style, formatter} = props
	return (
		<View style={[styles.rectangle, styles.common, styles.balances, style]}>
			<Text
				autoAdjustsFontSize={true}
				selectable={true}
				style={styles.financialText}
			>
				{indeterminate ? '…' : formatter(value)}
			</Text>
			<Text autoAdjustsFontSize={true} style={styles.rectangleButtonText}>
				{label}
			</Text>
		</View>
	)
}
<commit_msg>prettify<commit_after>// @flow

import * as React from 'react'
import {
	StyleSheet,
	ScrollView,
	View,
	Text,
	RefreshControl,
	Alert,
} from 'react-native'
import {TabBarIcon} from '../components/tabbar-icon'
import {connect} from 'react-redux'
import {Cell, TableView, Section} from 'react-native-tableview-simple'
import {
	hasSeenAcknowledgement,
	type LoginStateType,
} from '../../flux/parts/settings'
import {updateBalances} from '../../flux/parts/balances'
import {type ReduxState} from '../../flux'
import delay from 'delay'
import * as c from '../components/colors'
import type {TopLevelViewPropsType} from '../types'

const DISCLAIMER = 'This data may be outdated or otherwise inaccurate.'
const LONG_DISCLAIMER =
	'This data may be inaccurate.\nBon Appétit is always right.\nThis app is unofficial.'

type ReactProps = TopLevelViewPropsType

type ReduxStateProps = {
	dining: ?string,
	schillers: ?string,
	print: ?string,
	weeklyMeals: ?string,
	dailyMeals: ?string,
	guestSwipes: ?string,
	loginState: LoginStateType,
	message: ?string,
	alertSeen: boolean,
}

type ReduxDispatchProps = {
	hasSeenAcknowledgement: () => any,
	updateBalances: boolean => any,
}

type Props = ReactProps & ReduxStateProps & ReduxDispatchProps

type State = {
	loading: boolean,
}

class BalancesView extends React.PureComponent<Props, State> {
	static navigationOptions = {
		tabBarLabel: 'Balances',
		tabBarIcon: TabBarIcon('card'),
	}

	state = {
		loading: false,
	}

	componentDidMount() {
		// calling "refresh" here, to make clear to the user
		// that the data is being updated
		this.refresh()

		if (!this.props.alertSeen) {
			Alert.alert('', LONG_DISCLAIMER, [
				{text: 'I Disagree', onPress: this.goBack, style: 'cancel'},
				{text: 'Okay', onPress: this.props.hasSeenAcknowledgement},
			])
		}
	}

	refresh = async () => {
		let start = Date.now()
		this.setState(() => ({loading: true}))

		await this.fetchData()

		// wait 0.5 seconds – if we let it go at normal speed, it feels broken.
		let elapsed = Date.now() - start
		await delay(500 - elapsed)

		this.setState(() => ({loading: false}))
	}

	fetchData = async () => {
		await this.props.updateBalances(true)
	}

	openSettings = () => {
		this.props.navigation.navigate('SettingsView')
	}

	goBack = () => {
		this.props.navigation.goBack(null)
	}

	render() {
		let {dining, schillers, dailyMeals, weeklyMeals, guestSwipes} = this.props
		let {loading} = this.state

		return (
			<ScrollView
				contentContainerStyle={styles.stage}
				refreshControl={
					<RefreshControl
						onRefresh={this.refresh}
						refreshing={this.state.loading}
					/>
				}
			>
				<TableView>

						<View style={styles.balancesRow}>
							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Schillers"
								value={schillers}
							/>

							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Dining"
								value={dining}
							/>
						</View>

						<View style={styles.balancesRow}>
							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Daily Meals"
								value={dailyMeals}
							/>

							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Weekly Meals"
								value={weeklyMeals}
							/>

							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Guest Swipes"
								style={styles.finalCell}
								value={guestSwipes}
							/>
						</View>

					{(this.props.loginState !== 'checking' &&
						this.props.loginState !== 'logged-in') ||
					this.props.message ? (

							{this.props.loginState !== 'logged-in' ? (
								<Cell
									accessory="DisclosureIndicator"
									cellStyle="Basic"
									onPress={this.openSettings}
									title="Log in with Carleton"
								/>
							) : null}

							{this.props.message ? (
								<Cell cellStyle="Basic" title={this.props.message} />
							) : null}

					) : null}
				</TableView>
			</ScrollView>
		)
	}
}

function mapState(state: ReduxState): ReduxStateProps {
	return {
		schillers: state.balances ? state.balances.schillersBalance : null,
		dining: state.balances ? state.balances.diningBalance : null,
		print: state.balances ? state.balances.printBalance : null,
		weeklyMeals: state.balances ? state.balances.mealsRemainingThisWeek : null,
		dailyMeals: state.balances ? state.balances.mealsRemainingToday : null,
		guestSwipes: state.balances ? state.balances.guestSwipesCount : null,
		message: state.balances ? state.balances.balancesErrorMessage : null,
		alertSeen: state.settings ? state.settings.unofficiallyAcknowledged : false,

		loginState: state.settings ? state.settings.loginState : 'logged-out',
	}
}

function mapDispatch(dispatch): ReduxDispatchProps {
	return {
		updateBalances: force => dispatch(updateBalances(force)),
		hasSeenAcknowledgement: () => dispatch(hasSeenAcknowledgement()),
	}
}

const ConnectedBalancesView = connect(mapState, mapDispatch)(BalancesView)

export default ConnectedBalancesView

let cellMargin = 10
let cellSidePadding = 10
let cellEdgePadding = 10

let styles = StyleSheet.create({
	stage: {
		backgroundColor: c.iosLightBackground,
		paddingTop: 20,
		paddingBottom: 20,
	},

	common: {
		backgroundColor: c.white,
	},

	balances: {
		borderRightWidth: StyleSheet.hairlineWidth,
		borderRightColor: c.iosSeparator,
	},

	finalCell: {
		borderRightWidth: 0,
	},

	balancesRow: {
		flexDirection: 'row',
		marginTop: 0,
		marginBottom: -10,
	},

	rectangle: {
		height: 88,
		flex: 1,
		alignItems: 'center',
		paddingTop: cellSidePadding,
		paddingBottom: cellSidePadding,
		paddingRight: cellEdgePadding,
		paddingLeft: cellEdgePadding,
		marginBottom: cellMargin,
	},

	// Text styling
	financialText: {
		paddingTop: 8,
		color: c.iosDisabledText,
		textAlign: 'center',
		fontWeight: '200',
		fontSize: 23,
	},
	rectangleButtonText: {
		paddingTop: 15,
		color: c.black,
		textAlign: 'center',
		fontSize: 16,
	},
})

export function BigBalancesView(props: TopLevelViewPropsType) {
	return <ConnectedBalancesView navigation={props.navigation} />
}
BigBalancesView.navigationOptions = {
	title: 'Balances',
}

function getValueOrNa(value: ?string): string {
	// eslint-disable-next-line no-eq-null
	if (value == null) {
		return 'N/A'
	}
	return value
}

function FormattedValueCell(props: {
	indeterminate: boolean,
	label: string,
	value: ?string,
	style?: any,
	formatter: (?string) => string,
}) {
	let {indeterminate, label, value, style, formatter} = props
	return (
		<View style={[styles.rectangle, styles.common, styles.balances, style]}>
			<Text
				autoAdjustsFontSize={true}
				selectable={true}
				style={styles.financialText}
			>
				{indeterminate ? '…' : formatter(value)}
			</Text>
			<Text autoAdjustsFontSize={true} style={styles.rectangleButtonText}>
				{label}
			</Text>
		</View>
	)
}
<|endoftext|>


<commit_before>exports.bannedIPs = ['101.98.152.51','10.209.142.246','10.65.17.122'];

exports.ban = function(req, res, next){
    if (exports.bannedIPs.indexOf(req.ip) > -1){
        res.end('Fuck Off');
    } else {
        next();
    }
};
<commit_msg>i just banned the heroku proxies... nothing good happens after 2 am<commit_after>exports.bannedIPs = [];

exports.ban = function(req, res, next){
    if (exports.bannedIPs.indexOf(req.ip) > -1){
        res.end('Fuck Off');
    } else {
        next();
    }
};
<|endoftext|>