<commit_before>/**
 * Created by marija on 9.3.2016..
 */
var xpath = require('xpath-range').xpath;

//function that does replacement based on xpath and offsets
function Substitution(jsonAnnotation) {
    var obj = JSON.parse(jsonAnnotation);
    var xpathPositionStart = obj.ranges[0].start;
    var xpathPositionEnd = obj.ranges[0].end;
    var startOffset = obj.ranges[0].startOffset;
    var endOffset = obj.ranges[0].endOffset;

    if (document.implementation.hasFeature("Range", "2.0")) {
        var oRange = document.createRange();
       // oRange.setStart(xpathPositionStart, startOffset);
       // oRange.setEnd(xpathPositionEnd, endOffset);
        console.log(oRange);
             //range code here
    }

   // var proba = xpath.toRange('//div/p[1]', 8, '//div/p[1]', 37, "/");

    var startContainer = document.evaluate('/'+ xpathPositionStart, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    var endContainer = document.evaluate('/' + xpathPositionEnd, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

   // var p = xpath.toRange(startContainer, 2, endContainer, 8);
    //var tst = xpath.toRange('html/body/div[1]/div/p', 8, 'html/body/div[1]/div/p', 37);

    var substituteText = obj.text;
    var substitutionLength = substituteText.length;
    var annotationLength = endOffset - startOffset;

    //console.log(startContainer.childNodes[0].nodeValue);

    //commonAncestor = xpath.fromNode($(commonAncestor))[0]
    //console.log(commonAncestor);

    fun = (function () {
        var commonAncestor = startContainer;
        var child = commonAncestor;
        var prefOffset = 0; //sum of all predeccessor lengths
        var startNode = null;

        var findStartNodeToBeginSubstitution = function() {
            var childNodes = child.childNodes;
            var i = 0, num = childNodes.length;
            while (i < num && startNode == null) {
                child = childNodes[i];
                i++;
                if (child.nodeType == 3) {
                    var len;
                    if (child.nodeValue != "") {
                        len = child.nodeValue.length - 1;
                    } else {
                        len = 0;
                    }
                    if ((len + prefOffset) < startOffset) {
                        prefOffset += (child.nodeValue).length;
                        continue;
                    } else {
                        startNode = child;
                        return;
                    }
                } else {
                    findStartNodeToBeginSubstitution();
                }
            }
            return;
        }
        findStartNodeToBeginSubstitution();
        child = startNode;
        var substNodeLen = child.length;
        child.nodeValue = getSubstituteText(prefOffset, startOffset, endOffset, child.nodeValue, substituteText);

        var nextSibl;
        if(child.nextSibling) {
            nextSibl = child.nextSibling;
        } else if(child.parentNode) {
            nextSibl = child.parentNode.nextSibling;
        }
        var flag = true;
        while (prefOffset < endOffset && nextSibl && nextSibl != commonAncestor) {
            if (flag) {
                prefOffset += substNodeLen;
                flag = false;
            }
            prefOffset = removeHangingTextPerNodes(nextSibl, prefOffset, endOffset, true);
            nextSibl = nextSibl.parentNode.nextSibling;
        }
    })(startContainer, endContainer);

    fun(startContainer, endContainer);

    //var startNodeToBeginSubstitution = findStartNodeToBeginSubstitution(commonAncestor, startOffset, 0);
  // var commonAncestorXPath = xpath.fromNode($(commonAncestor))[0];

};

function removeHangingTextPerNodes(node, prefOffset, endOffset, callNextSibling) {
    if (node.nodeType == 3) {
        var remainedTxtToRemove = endOffset - prefOffset;
        var len = node.nodeValue.length;
        prefOffset += len;
        if (remainedTxtToRemove > len) {
            node.nodeValue = "";
        } else {
            node.nodeValue = (node.nodeValue).substring(remainedTxtToRemove, len);
        }
    } else if (node.childNodes) {
        var childNodes = node.childNodes;
        var i = 0, num = childNodes.length;
        while (i < num) {
            var child = childNodes[i];
            console.log(child);
            console.log(i);
            if (prefOffset < endOffset) {
                prefOffset = removeHangingTextPerNodes(child, prefOffset, endOffset, false);
            } else {
                break;
            }
            i++;
        }
    }
    if (prefOffset < endOffset && node.nextSibling != null && callNextSibling) {
        removeHangingTextPerNodes(node.nextSibling, prefOffset, endOffset, true);
    }
    return prefOffset;
};

function getSubstituteText(prefOffset, startOffset, endOffset, originalText, substituteText) {
    var leftTextToSubstitute;
    var origTextLen = originalText.length;
    var subsTextLen = substituteText.length;
    //there is a guarantee that first letter of a substituteText is within an originalText
    var prefText = "", sufText = "";
    startOffset = startOffset - prefOffset;
    endOffset = endOffset - prefOffset;
    if (startOffset != 0) {
        prefText = originalText.substring(0, startOffset);
    }
    if (origTextLen > endOffset) {
        sufText = originalText.substring(endOffset, origTextLen);
    }
    return prefText + substituteText + sufText;
};

/*
function findStartNodeToBeginSubstitution(commonAncestor, offset, prefOffset) {
    var childNodes = commonAncestor.childNodes;
    for(i = 0; i < childNodes.length; i++) {
        var child = childNodes[i];
        if (child.nodeType == 3) {
            var len = child.nodeValue.length - 1;
            if ((len + prefOffset) < offset) {
                prefOffset += (child.nodeValue).length;
                continue;
            } else {
                return [child, prefOffset];
            }
        } else {
            return findStartNodeToBeginSubstitution(child, offset, prefOffset);
        }
    }
};
*/

function contains(parent, child) {
    var node;
    node = child;
    while (node !== null) {
        if (node === parent) {
            return true;
        }
        node = node.parentNode;
    }
    return false;
};

exports.Substitution = Substitution;
<commit_msg>added function to handle multiple annotations per same tag<commit_after>/**
 * Created by marija on 9.3.2016..
 */
var xpath = require('xpath-range').xpath;

function MultipleSubstitution(annotationsToSub) {
    var prefOffset = 0, jsonObj, obj, xpathPositionStart, xpathPositionEnd, startOffset, endOffset, substituteText;
    for (i = 0; i < annotationsToSub.length; i++) {
       jsonObj = JSON.stringify(JSON.decycle(annotationsToSub[i]), null, "  ");
       obj = JSON.parse(jsonObj);
       xpathPositionStart = obj.ranges[0].start;
       xpathPositionEnd = obj.ranges[0].end;
       startOffset = obj.ranges[0].startOffset;
       endOffset = obj.ranges[0].endOffset;
       substituteText = obj.text;
       var annotationLen = endOffset - startOffset;
       var substituteTextLen = substituteText.length;
       if (i != 0) {
           startOffset += prefOffset;
           endOffset += prefOffset;
       }
       Substitution(xpathPositionStart, xpathPositionEnd, startOffset, endOffset, substituteText);
         if (annotationLen < substituteTextLen) {
           prefOffset += (substituteTextLen - annotationLen);
       } else {
           prefOffset -= (annotationLen - substituteTextLen);
       }
    }
}

//function that does replacement based on xpath and offsets
function Substitution(xpathPositionStart, xpathPositionEnd, startOffset, endOffset, substituteText) {
    /*
    if (document.implementation.hasFeature("Range", "2.0")) {
        var oRange = document.createRange();
       // oRange.setStart(xpathPositionStart, startOffset);
       // oRange.setEnd(xpathPositionEnd, endOffset);
        console.log(oRange);
             //range code here
    }*/

   // var test = xpath.toRange('//div/p[1]', 8, '//div/p[1]', 37, "/");

    var startContainer = document.evaluate('/'+ xpathPositionStart, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    var endContainer = document.evaluate('/' + xpathPositionEnd, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

   // var p = xpath.toRange(startContainer, 2, endContainer, 8);
    //var tst = xpath.toRange('html/body/div[1]/div/p', 8, 'html/body/div[1]/div/p', 37);

    var substitutionLength = substituteText.length;
    var annotationLength = endOffset - startOffset;

    //console.log(startContainer.childNodes[0].nodeValue);

    //commonAncestor = xpath.fromNode($(commonAncestor))[0]
    //console.log(commonAncestor);

    var fun = (function () {
        var commonAncestor = startContainer;
        var child = commonAncestor;
        var prefOffset = 0; //sum of all predeccessor lengths
        var startNode = null;

        var findStartNodeToBeginSubstitution = function() {
            var childNodes = child.childNodes;
            var i = 0, num = childNodes.length;
            while (i < num && startNode == null) {
                child = childNodes[i];
                i++;
                if (child.nodeType == 3) {
                    var len;
                    if (child.nodeValue != "") {
                        len = child.nodeValue.length - 1;
                    } else {
                        len = 0;
                    }
                    if ((len + prefOffset) < startOffset) {
                        prefOffset += (child.nodeValue).length;
                        continue;
                    } else {
                        startNode = child;
                        return;
                    }
                } else {
                    findStartNodeToBeginSubstitution();
                }
            }
            return;
        }
        findStartNodeToBeginSubstitution();
        child = startNode;
        var substNodeLen = child.length;
        child.nodeValue = getSubstituteText(prefOffset, startOffset, endOffset, child.nodeValue, substituteText);

        var nextSibl;
        if(child.nextSibling) {
            nextSibl = child.nextSibling;
        } else if(child.parentNode) {
            nextSibl = child.parentNode.nextSibling;
        }
        var flag = true;
        while (prefOffset < endOffset && nextSibl && nextSibl != commonAncestor) {
            if (flag) {
                prefOffset += substNodeLen;
                flag = false;
            }
            prefOffset = removeHangingTextPerNodes(nextSibl, prefOffset, endOffset, true);
            nextSibl = nextSibl.parentNode.nextSibling;
        }
    })(startContainer, endContainer);

    // fun(startContainer, endContainer);

    //var startNodeToBeginSubstitution = findStartNodeToBeginSubstitution(commonAncestor, startOffset, 0);
  // var commonAncestorXPath = xpath.fromNode($(commonAncestor))[0];

};

function removeHangingTextPerNodes(node, prefOffset, endOffset, callNextSibling) {
    if (node.nodeType == 3) {
        var remainedTxtToRemove = endOffset - prefOffset;
        var len = node.nodeValue.length;
        prefOffset += len;
        if (remainedTxtToRemove > len) {
            node.nodeValue = "";
        } else {
            node.nodeValue = (node.nodeValue).substring(remainedTxtToRemove, len);
        }
    } else if (node.childNodes) {
        var childNodes = node.childNodes;
        var i = 0, num = childNodes.length;
        while (i < num) {
            var child = childNodes[i];
            console.log(child);
            console.log(i);
            if (prefOffset < endOffset) {
                prefOffset = removeHangingTextPerNodes(child, prefOffset, endOffset, false);
            } else {
                break;
            }
            i++;
        }
    }
    if (prefOffset < endOffset && node.nextSibling != null && callNextSibling) {
        removeHangingTextPerNodes(node.nextSibling, prefOffset, endOffset, true);
    }
    return prefOffset;
};

function getSubstituteText(prefOffset, startOffset, endOffset, originalText, substituteText) {
    var leftTextToSubstitute;
    var origTextLen = originalText.length;
    var subsTextLen = substituteText.length;
    //there is a guarantee that first letter of a substituteText is within an originalText
    var prefText = "", sufText = "";
    startOffset = startOffset - prefOffset;
    endOffset = endOffset - prefOffset;
    if (startOffset != 0) {
        prefText = originalText.substring(0, startOffset);
    }
    if (origTextLen > endOffset) {
        sufText = originalText.substring(endOffset, origTextLen);
    }
    return prefText + substituteText + sufText;
};

/*
function findStartNodeToBeginSubstitution(commonAncestor, offset, prefOffset) {
    var childNodes = commonAncestor.childNodes;
    for(i = 0; i < childNodes.length; i++) {
        var child = childNodes[i];
        if (child.nodeType == 3) {
            var len = child.nodeValue.length - 1;
            if ((len + prefOffset) < offset) {
                prefOffset += (child.nodeValue).length;
                continue;
            } else {
                return [child, prefOffset];
            }
        } else {
            return findStartNodeToBeginSubstitution(child, offset, prefOffset);
        }
    }
};
*/

function contains(parent, child) {
    var node;
    node = child;
    while (node !== null) {
        if (node === parent) {
            return true;
        }
        node = node.parentNode;
    }
    return false;
};

exports.Substitution = Substitution;
exports.MultipleSubstitution = MultipleSubstitution;
<|endoftext|>


<commit_before>module.exports = function (grunt) {
  'use strict'

  require('load-grunt-tasks')(grunt)

  var config = {
    pkg: grunt.file.readJSON('package.json'),

    concat: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n',
        sourceMap: true
      },
      dist: {
        src: ['src/*.js'],
        dest: 'dist/<%= pkg.name %>.js'
      }
    },

    comments: {
      js: {
        options: {
          singleline: true,
          multiline: true
        },
        src: ['<%= concat.dist.dest %>']
      }
    },

    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n',
        sourceMap: true
      },
      dist: {
        files: {
          'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
        }
      }
    },

    eslint: {
      options: {
      },
      target: [
        'Gruntfile.js',
        'src/**/*.js',
        'test/**/*.js'
      ]
    },

    bump: {
      options: {
        files: [
          'bower.json',
          'package.json'
        ],
        commit: true,
        commitMessage: 'Release: v%VERSION%',
        commitFiles: [
          'CHANGELOG.md',
          'bower.json',
          'package.json',
          'dist/*.js',
          'dist/*.js.map'
        ],
        createTag: true,
        tagName: '%VERSION%',
        tagMessage: 'chore(release): version %VERSION%',
        push: false,
        pushTo: 'origin'
      }
    },

    conventionalChangelog: {
      options: {
        changelogOpts: {
          preset: 'angular'
        }
      },
      release: {
        src: 'CHANGELOG.md'
      }
    },

    // Server-side tests
    simplemocha: {
      test: {
        src: 'test/node.js',
        options: {
          globals: ['should'],
          timeout: 3000,
          ignoreLeaks: false,
          reporter: 'spec'
        }
      }
    },

    mocha_istanbul: {
      coverage: {
        src: 'test/node.js', // a folder works nicely
        options: {
          globals: ['should'],
          timeout: 3000,
          ignoreLeaks: false,
          reporter: 'spec'
        }
      }
    }
  }

  grunt.initConfig(config)

  grunt.registerTask('lint', ['eslint'])
  grunt.registerTask('test', ['lint', 'simplemocha'])
  grunt.registerTask('coverage', ['lint', 'mocha_istanbul'])
  grunt.registerTask('build', ['test', 'concat', 'comments', 'uglify'])
  grunt.registerTask('release', [
    'bump-only', 'build', 'conventionalChangelog', 'bump-commit'
  ])

  grunt.registerTask('default', [])
}
<commit_msg>chore(build): proper fix for release message<commit_after>module.exports = function (grunt) {
  'use strict'

  require('load-grunt-tasks')(grunt)

  var config = {
    pkg: grunt.file.readJSON('package.json'),

    concat: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n',
        sourceMap: true
      },
      dist: {
        src: ['src/*.js'],
        dest: 'dist/<%= pkg.name %>.js'
      }
    },

    comments: {
      js: {
        options: {
          singleline: true,
          multiline: true
        },
        src: ['<%= concat.dist.dest %>']
      }
    },

    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n',
        sourceMap: true
      },
      dist: {
        files: {
          'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
        }
      }
    },

    eslint: {
      options: {
      },
      target: [
        'Gruntfile.js',
        'src/**/*.js',
        'test/**/*.js'
      ]
    },

    bump: {
      options: {
        files: [
          'bower.json',
          'package.json'
        ],
        commit: true,
        commitMessage: 'chore(release): version %VERSION%',
        commitFiles: [
          'CHANGELOG.md',
          'bower.json',
          'package.json',
          'dist/*.js',
          'dist/*.js.map'
        ],
        createTag: true,
        tagName: '%VERSION%',
        tagMessage: 'Version %VERSION%',
        push: false,
        pushTo: 'origin'
      }
    },

    conventionalChangelog: {
      options: {
        changelogOpts: {
          preset: 'angular'
        }
      },
      release: {
        src: 'CHANGELOG.md'
      }
    },

    // Server-side tests
    simplemocha: {
      test: {
        src: 'test/node.js',
        options: {
          globals: ['should'],
          timeout: 3000,
          ignoreLeaks: false,
          reporter: 'spec'
        }
      }
    },

    mocha_istanbul: {
      coverage: {
        src: 'test/node.js', // a folder works nicely
        options: {
          globals: ['should'],
          timeout: 3000,
          ignoreLeaks: false,
          reporter: 'spec'
        }
      }
    }
  }

  grunt.initConfig(config)

  grunt.registerTask('lint', ['eslint'])
  grunt.registerTask('test', ['lint', 'simplemocha'])
  grunt.registerTask('coverage', ['lint', 'mocha_istanbul'])
  grunt.registerTask('build', ['test', 'concat', 'comments', 'uglify'])
  grunt.registerTask('release', [
    'bump-only', 'build', 'conventionalChangelog', 'bump-commit'
  ])

  grunt.registerTask('default', [])
}
<|endoftext|>


<commit_before>var config = {
  sauceSeleniumAddress: 'localhost:4445/wd/hub',
  sauceUser: process.env.SAUCE_USERNAME,
  sauceKey: process.env.SAUCE_ACCESS_KEY,

  browsers: [
    {browserName: 'chrome'},
    {browserName: 'firefox'},
    {browserName: 'safari', version: 7, platform: 'OS X 10.9'},
    {browserName: 'safari', version: 6, platform: 'OS X 10.8'},
    {browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'},
    {browserName: 'internet explorer', version: 10, platform: 'Windows 8'},
    {browserName: 'internet explorer', version: 9, platform: 'Windows 7'},
    {browserName: 'iphone', version:'7.1', platform: 'OS X 10.9'},
    {browserName: 'iphone', version:'7.0', platform: 'OS X 10.9'},
    {browserName: 'iphone', version:'6.1', platform: 'OS X 10.8'},
    {browserName: 'ipad', version:'7.1', platform: 'OS X 10.9'},
    {browserName: 'ipad', version:'7.0', platform: 'OS X 10.9'},
    {browserName: 'ipad', version:'6.1', platform: 'OS X 10.8'}
  ],

  specs: ['spec.js'],

  // This enable testing on none angular pages without test code change.
  onPrepare: function () {
    element = browser.element;
    browser.ignoreSynchronization = true;
  },

  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 30000
  }
};

config.multiCapabilities = config.browsers.map(function (cfg) {
  cfg.build = process.env.TRAVIS_BUILD_NUMBER;
  cfg['tunnel-identifier'] = process.env.TRAVIS_BUILD_NUMBER;
  cfg.name = 'Browser Test for FluxEx';
  cfg.public = 'public';
  cfg.tags = [process.env.TRAVIS_JOB_ID, process.env.TRAVIS_COMMIT, 'fluxex'];
  return cfg;
});

module.exports.config = config;<commit_msg>fix tunnel id<commit_after>var config = {
  sauceSeleniumAddress: 'localhost:4445/wd/hub',
  sauceUser: process.env.SAUCE_USERNAME,
  sauceKey: process.env.SAUCE_ACCESS_KEY,

  browsers: [
    {browserName: 'chrome'},
    {browserName: 'firefox'},
    {browserName: 'safari', version: 7, platform: 'OS X 10.9'},
    {browserName: 'safari', version: 6, platform: 'OS X 10.8'},
    {browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'},
    {browserName: 'internet explorer', version: 10, platform: 'Windows 8'},
    {browserName: 'internet explorer', version: 9, platform: 'Windows 7'},
    {browserName: 'iphone', version:'7.1', platform: 'OS X 10.9'},
    {browserName: 'iphone', version:'7.0', platform: 'OS X 10.9'},
    {browserName: 'iphone', version:'6.1', platform: 'OS X 10.8'},
    {browserName: 'ipad', version:'7.1', platform: 'OS X 10.9'},
    {browserName: 'ipad', version:'7.0', platform: 'OS X 10.9'},
    {browserName: 'ipad', version:'6.1', platform: 'OS X 10.8'}
  ],

  specs: ['spec.js'],

  // This enable testing on none angular pages without test code change.
  onPrepare: function () {
    element = browser.element;
    browser.ignoreSynchronization = true;
  },

  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 30000
  }
};

config.multiCapabilities = config.browsers.map(function (cfg) {
  cfg.build = process.env.TRAVIS_BUILD_NUMBER;
  cfg['tunnel-identifier'] = process.env.TRAVIS_JOB_NUMBER;
  cfg.name = 'Browser Test for FluxEx';
  cfg.public = 'public';
  cfg.tags = [process.env.TRAVIS_JOB_ID, process.env.TRAVIS_COMMIT, 'fluxex'];
  return cfg;
});

module.exports.config = config;<|endoftext|>


<commit_before>var gulp = require('gulp');
var del = require('del');
var shell = require('shelljs');
var bump = require('gulp-bump');
var util = require('util');

var config = {
    wowAddonDir: 'C:/Program Files (x86)/World of Warcraft/Interface/AddOns/StatWeightScore',
    src: 'src/**/*.{lua,xml,toc}'
};

gulp.task('clean-local', function(cb){
    del([util.format('%s/**/*', config.wowAddonDir)], { force: true }, cb);
})

gulp.task('publish-local', ['clean-local'], function(){
    return gulp
        .src(config.src)
        .pipe(gulp.dest(config.wowAddonDir));
});

gulp.task('watch', ['publish-local'], function(){
    gulp.watch(config.src, ['publish-local'])
});

gulp.task('_bump', function(){
    var argv = require('yargs')
        .alias('v', 'version')
        .alias('t', 'type')
        .check(function(argv){
            if((!argv.version && !argv.type) || (argv.version && argv.type)){
                throw 'Exactly one of version or type arguments is required';
            }

            return true;
        })
        .argv;

    var bumpOptions = {
        preid: 'rc'
    };

    if(argv.version){
        bumpOptions.version = argv.version;
    } else if(argv.type){
        bumpOptions.type = argv.type;
    }

    return gulp
        .src('package.json')
        .pipe(bump(bumpOptions))
        .pipe(gulp.dest('.'));
});

gulp.task('release', ['_bump'], function(){
    var version = require('./package.json').version;

    shell.exec(util.format('git commit -a -m "Release v%s"', version));
    shell.exec(util.format('git tag %s', version));
    shell.exec('git push origin master');
    shell.exec('git checkout curse');
    shell.exec('git pull curse master');
    shell.exec('git merge master');
    shell.exec('git push curse master');
    shell.exec('git push --tags');

    console.log(util.format('Release %s completed', version));
});<commit_msg>Simplify release task further<commit_after>var gulp = require('gulp');
var del = require('del');
var shell = require('shelljs');
var bump = require('gulp-bump');
var util = require('util');

var config = {
    wowAddonDir: 'C:/Program Files (x86)/World of Warcraft/Interface/AddOns/StatWeightScore',
    src: 'src/**/*.{lua,xml,toc}'
};

gulp.task('clean-local', function(cb){
    del([util.format('%s/**/*', config.wowAddonDir)], { force: true }, cb);
})

gulp.task('publish-local', ['clean-local'], function(){
    return gulp
        .src(config.src)
        .pipe(gulp.dest(config.wowAddonDir));
});

gulp.task('watch', ['publish-local'], function(){
    gulp.watch(config.src, ['publish-local'])
});

gulp.task('_bump', function(){
    var argv = require('yargs')
        .alias('v', 'version')
        .alias('t', 'type')
        .check(function(argv){
            if((!argv.version && !argv.type) || (argv.version && argv.type)){
                throw 'Exactly one of version or type arguments is required';
            }

            return true;
        })
        .argv;

    var bumpOptions = {
        preid: 'rc'
    };

    if(argv.version){
        bumpOptions.version = argv.version;
    } else if(argv.type){
        bumpOptions.type = argv.type;
    }

    return gulp
        .src('package.json')
        .pipe(bump(bumpOptions))
        .pipe(gulp.dest('.'));
});

gulp.task('release', ['_bump'], function(){
    var version = require('./package.json').version;

    shell.exec(util.format('git commit -a -m "Release v%s"', version));
    shell.exec(util.format('git tag %s', version));
    shell.exec('git push origin master');

    console.log(util.format('Release %s completed', version));
});<|endoftext|>


<commit_before>var $media = null;

$(document).ready(function() {

	// initialize Foundation
	$(document).foundation();

	var $prefix = $("meta[property='slides:api']").attr("content");
	var $base = document.baseURI;
	$media = $("meta[property='slides:media']").attr("content");

	/***
	 * viewModel: an our bound object for KnockoutJS
	 */
	var viewModel = function() {
		var self = this;

		// utility function for loading data
		self.loadData = function(url, params, next) {
			$.get($prefix + url, params, function(data, textStatus, jqXHR) {
				next(data);
			}).fail(function (data) {
				console.log("Failed to load data.");
				console.log(data);
			});
		};

		self.presentations = ko.observableArray();
		self.assets = ko.observableArray();
		self.currentSlide = ko.observable();
		self.slideshowRunning = ko.observable(null);
		self.windowHeight = ko.observable($(window).height() + "px");
		self.timeout = null;

		/***
		 * loadPresentation: fired when a presentation is selected, loads the
		 * 	image assets into the visible array, and starts the slideshow.
		 *
		 * data - the current presentation
		 * event - the event (click) that caused it to load
		 *
		 * returns nothing
		 */
		self.loadPresentation = function(data, event) {
			// set visibility on each slide
			data.assets[0].visible = ko.observable(true);
			for (var i=1; i<data.assets.length; i++) {
				data.assets[i].visible = ko.observable(false);
			}
			self.assets(data.assets);

			// set the current slide
			self.currentSlide(0);

			// if there's a slideshow running, stop it
			self.stopPresentation();

			// start the next slideshow
			if (self.timeout || self.slideshowRunning()) return;
			self.slideshowRunning(true);
			$('body').css('cursor', 'none'); // hide cursor
			self.timeout = setTimeout(function() {
				self.advanceSlide();
			}, self.assets()[self.currentSlide()].time * 1000);
		};

		/***
		 * stopPresentation: stops the currently running presentation
		 *
		 * returns nothing
		 */
		self.stopPresentation = function() {
			if (self.slideshowRunning()) {
				self.slideshowRunning(false);
				$('body').css('cursor', 'auto'); // show cursor again
				self.assets([]);
			}
			clearTimeout(self.timeout);
			self.timeout = null;
		};

		/***
		 * advanceSlide: advances the currently running slideshow to the next slide
		 *
		 * returns nothing
		 */
		self.advanceSlide = function() {
			var length = self.assets().length;
			var nextSlide = self.currentSlide() + 1;
			if (nextSlide >= length) nextSlide = 0;
			self.assets()[self.currentSlide()].visible(false);
			self.assets()[nextSlide].visible(true);
			self.currentSlide(nextSlide);
			self.timeout = setTimeout(function() {
				if (self.slideshowRunning()) self.advanceSlide();
			}, self.assets()[self.currentSlide()].time * 1000);
		};
	}; /* end viewModel */

	// create a binding handler for a fade transition
	ko.bindingHandlers.visibleFade = {
		init: function(element, valueAccessor) {
			var value = valueAccessor();
			$(element).toggle(value());
		},
		update: function(element, valueAccessor) {
			var value = valueAccessor();
			value() ? $(element).fadeIn(1000) : $(element).fadeOut(1000);
		}
	}

	// apply our view bindings
	var view = new viewModel();
	ko.applyBindings(view);

	// load the list of possible presentations
	view.loadData("/presentation/", {}, function(data){
		view.presentations(data.objects);
	});

	// bind our listener for setting the image height
	$(window).resize(function() {
		view.windowHeight($(window).height() + "px");
	});

	// bind our keypress handler for stopping the slideshow
	$(window).keypress(function(event) {
		if (event.which === 0) {
			view.stopPresentation();
		}
	});
});

<commit_msg>Better comments, and simplify presentation start<commit_after>// money variables in the global scope, since we use them in our KO data-binds
var $media = null,
		$prefix = null,
		$base = null;

$(document).ready(function() {

	// initialize Zurb Foundation
	$(document).foundation();

	// we store some variable properties in the HTML, get them.
	$media = $("meta[property='slides:media']").attr("content");
	$prefix = $("meta[property='slides:api']").attr("content");
	$base = document.baseURI;

	/***
	 * viewModel: an our bound object for KnockoutJS
	 */
	var viewModel = function() {
		var self = this;

		// utility function for loading data
		self.loadData = function(url, params, next) {
			$.get($prefix + url, params, function(data, textStatus, jqXHR) {
				next(data);
			}).fail(function (data) {
				// TODO handle failures better
				console.log("Failed to load data.");
				console.log(data);
			});
		};

		// list of presentations that were loaded from the server
		self.presentations = ko.observableArray();
		// list of assets (slides) in the currently running presentation
		self.assets = ko.observableArray([]);
		// the currently displayed slide
		self.currentSlide = ko.observable();
		// whether or not the slideshow is running
		self.slideshowRunning = ko.observable(null);
		// the current window height in pixels
		self.windowHeight = ko.observable($(window).height() + "px");
		// the timer that advances to the next slide
		self.timeout = null;

		/***
		 * loadPresentation: fired when a presentation is selected, loads the
		 * 	image assets into the visible array, and starts the slideshow.
		 *
		 * data - the current presentation
		 * event - the event (click) that caused it to load
		 *
		 * returns nothing
		 */
		self.loadPresentation = function(data, event) {
			// put a visibility marker on each slide
			for (var i=0; i<data.assets.length; i++) {
				data.assets[i].visible = ko.observable(false);
			}
			self.assets(data.assets);

			// set the current slide to -1, so we fade in the first slide
			self.currentSlide(-1);

			// if there's a slideshow running, stop it
			self.stopPresentation();

			// mark the slideshow as running. bail if there's one running already
			if (self.timeout || self.slideshowRunning()) return;
			self.slideshowRunning(true);

			// hide the cursor
			$('body').css('cursor', 'none');

			// show the first slide
			self.advanceSlide();
		};

		/***
		 * stopPresentation: stops the currently running presentation
		 *
		 * returns nothing
		 */
		self.stopPresentation = function() {
			// clear the timeout
			clearTimeout(self.timeout);
			self.timeout = null;

			// if the slideshow is running, we reset things to non-running state
			if (self.slideshowRunning()) {
				self.slideshowRunning(false);
				// show the cursor again
				$('body').css('cursor', 'auto');
				// blank out the asset list
				self.assets([]);
			}
		};

		/***
		 * advanceSlide: advances the currently running slideshow to the next slide
		 *
		 * returns nothing
		 */
		self.advanceSlide = function() {
			// the next slide should be one after the current
			var nextSlide = self.currentSlide() + 1;
			// if we've overrun the end of the assets though, go back to the beginning
			if (nextSlide >= self.assets().length) nextSlide = 0;

			// special case for the first slide, since we set -1 above
			if (self.currentSlide() >= 0)
				self.assets()[self.currentSlide()].visible(false);

			// make the next slide visible, and set it as current slide
			self.assets()[nextSlide].visible(true);
			self.currentSlide(nextSlide);

			// set timeout for the next advance
			self.timeout = setTimeout(function() {
				if (self.slideshowRunning()) self.advanceSlide();
			}, self.assets()[self.currentSlide()].time * 1000);
		};
	}; /* end viewModel */

	// create a binding handler for a fade transition
	ko.bindingHandlers.visibleFade = {
		init: function(element, valueAccessor) {
			var value = valueAccessor();
			$(element).toggle(value());
		},
		update: function(element, valueAccessor) {
			var value = valueAccessor();
			value() ? $(element).fadeIn(1000) : $(element).fadeOut(1000);
		}
	}

	// apply our view bindings
	var view = new viewModel();
	ko.applyBindings(view);

	// load the list of possible presentations
	view.loadData("/presentation/", {}, function(data){
		view.presentations(data.objects);
	});

	// bind our listener for setting the image height
	$(window).resize(function() {
		view.windowHeight($(window).height() + "px");
	});

	// bind our keypress handler for stopping the slideshow
	$(window).keypress(function(event) {
		if (event.which === 0) {
			view.stopPresentation();
		}
	});
});

<|endoftext|>


<commit_before>/*global module, process, require*/

"use strict";

var request = require("request");

module.exports = function(grunt) {
    var browsers = [
        {platform: "linux", browserName: "chrome", version: "36"},
        {platform: "linux", browserName: "chrome", version: "35"},
        {platform: "linux", browserName: "firefox", version: "31"},
        {platform: "linux", browserName: "firefox", version: "30"},
        {platform: "linux", browserName: "firefox", version: "29"},
        {platform: "WIN8.1", browserName: "firefox", version: "31"},
        {platform: "WIN8.1", browserName: "chrome", version: "36"},
        {platform: "WIN8.1", browserName: "internet explorer", version: "11"},
        {platform: "WIN8", browserName: "internet explorer", version: "10"},
        // {platform: "WIN7", browserName: "internet explorer", version: "9"},
        {platform: "WIN7", browserName: "internet explorer", version: "10"},
        // {platform: "VISTA", browserName: "internet explorer", version: "9"},
        {platform: "OS X 10.8", browserName: "safari", version: "6"},
        {platform: "OS X 10.8", browserName: "chrome", version: "35"},
        {platform: "OS X 10.9", browserName: "chrome", version: "35"},
        {platform: "OS X 10.9", browserName: "safari", version: "7"},
        {platform: "OS X 10.9", browserName: "firefox", version: "28"},
        {platform: "OS X 10.9", browserName: "firefox", version: "29"},
        {platform: "OS X 10.9", browserName: "firefox", version: "30"},
        {platform: "OS X 10.9", browserName: "iphone", version: "7.1"},
        {platform: "linux", browserName: "android", version: "4.3"},
        {platform: "linux", browserName: "android", version: "4.4"}
        ];

        grunt.initConfig({
            connect: {
                server: {
                    options: {
                        base: "",
                        port: 9999
                    }
                }
            },
            "saucelabs-jasmine": {
                all: {
                    options: {
                        urls: ["http://127.0.0.1:9999/jasmine/SpecRunner.html"],
                        tunnelTimeout: 15,
                        build: process.env.TRAVIS_JOB_ID,
                        concurrency: 3,
                        browsers: browsers,
                        capabilities: {
                            browserName: "chrome",
                            "chromeOptions":{
                                args: ["--test-type"]
                            }
                        },
                        testname: "iSat Unit tests",
                        tags: ["master"],
                        sauceConfig: {
                            "video-upload-on-pass": false //,
                            // "idle-timeout": 600
                        },
                        onTestComplete: function(result, cb){
                         // returning undefined doesn't alter the test result
                         //   but returning true or false does.
                         //   See: https://github.com/axemclion/grunt-saucelabs

                        var user = process.env.SAUCE_USERNAME;
                        var pass = process.env.SAUCE_ACCESS_KEY;
                        request.put({
                            url: ["https://saucelabs.com/rest/v1", user, "jobs", result.job_id].join("/"),
                            auth: { user: user, pass: pass },
                            json: { passed: !result.passed }
                        }, function (error, response, body) {
                          if (error) {
                            cb(error);
                        } else if (response.statusCode !== 200) {
                            cb(new Error("Unexpected response status:" + response.statusCode));
                        } else {
                            cb(null, !result.passed);
                        }
                        body=body; // Shuts up JSHint
                    });
                        // if (result.passed === false) {
                        //     cb(null, true);
                        // } else {
                        //    cb(null,undefined);
                        // }
                    }
                }
            }
        },
        watch: {}
    });

    // Loading dependencies
    for (var key in grunt.file.readJSON("package.json").devDependencies) {
        if (key !== "grunt" && key.indexOf("grunt") === 0) grunt.loadNpmTasks(key);
    }

    grunt.registerTask("dev", ["connect", "watch"]);
    grunt.registerTask("test", ["connect", "saucelabs-jasmine"]);
};
<commit_msg>Bump both timeouts to 15 minutes. #162<commit_after>/*global module, process, require*/

"use strict";

var request = require("request");

module.exports = function(grunt) {
    var browsers = [
        {platform: "linux", browserName: "chrome", version: "36"},
        {platform: "linux", browserName: "chrome", version: "35"},
        {platform: "linux", browserName: "firefox", version: "31"},
        {platform: "linux", browserName: "firefox", version: "30"},
        {platform: "linux", browserName: "firefox", version: "29"},
        {platform: "WIN8.1", browserName: "firefox", version: "31"},
        {platform: "WIN8.1", browserName: "chrome", version: "36"},
        {platform: "WIN8.1", browserName: "internet explorer", version: "11"},
        {platform: "WIN8", browserName: "internet explorer", version: "10"},
        // {platform: "WIN7", browserName: "internet explorer", version: "9"},
        {platform: "WIN7", browserName: "internet explorer", version: "10"},
        // {platform: "VISTA", browserName: "internet explorer", version: "9"},
        {platform: "OS X 10.8", browserName: "safari", version: "6"},
        {platform: "OS X 10.8", browserName: "chrome", version: "35"},
        {platform: "OS X 10.9", browserName: "chrome", version: "35"},
        {platform: "OS X 10.9", browserName: "safari", version: "7"},
        {platform: "OS X 10.9", browserName: "firefox", version: "28"},
        {platform: "OS X 10.9", browserName: "firefox", version: "29"},
        {platform: "OS X 10.9", browserName: "firefox", version: "30"},
        {platform: "OS X 10.9", browserName: "iphone", version: "7.1"},
        {platform: "linux", browserName: "android", version: "4.3"},
        {platform: "linux", browserName: "android", version: "4.4"}
        ];

        grunt.initConfig({
            connect: {
                server: {
                    options: {
                        base: "",
                        port: 9999
                    }
                }
            },
            "saucelabs-jasmine": {
                all: {
                    options: {
                        urls: ["http://127.0.0.1:9999/jasmine/SpecRunner.html"],
                        tunnelTimeout: 15,
                        build: process.env.TRAVIS_JOB_ID,
                        concurrency: 3,
                        browsers: browsers,
                        capabilities: {
                            browserName: "chrome",
                            "chromeOptions":{
                                args: ["--test-type"]
                            }
                        },
                        testname: "iSat Unit tests",
                        tags: ["master"],
                        sauceConfig: {
                            "video-upload-on-pass": false,
                            "idle-timeout": 600
                        },
                        onTestComplete: function(result, cb){
                         // returning undefined doesn't alter the test result
                         //   but returning true or false does.
                         //   See: https://github.com/axemclion/grunt-saucelabs

                        var user = process.env.SAUCE_USERNAME;
                        var pass = process.env.SAUCE_ACCESS_KEY;
                        request.put({
                            url: ["https://saucelabs.com/rest/v1", user, "jobs", result.job_id].join("/"),
                            auth: { user: user, pass: pass },
                            json: { passed: !result.passed }
                        }, function (error, response, body) {
                          if (error) {
                            cb(error);
                        } else if (response.statusCode !== 200) {
                            cb(new Error("Unexpected response status:" + response.statusCode));
                        } else {
                            cb(null, !result.passed);
                        }
                        body=body; // Shuts up JSHint
                    });
                        // if (result.passed === false) {
                        //     cb(null, true);
                        // } else {
                        //    cb(null,undefined);
                        // }
                    }
                }
            }
        },
        watch: {}
    });

    // Loading dependencies
    for (var key in grunt.file.readJSON("package.json").devDependencies) {
        if (key !== "grunt" && key.indexOf("grunt") === 0) grunt.loadNpmTasks(key);
    }

    grunt.registerTask("dev", ["connect", "watch"]);
    grunt.registerTask("test", ["connect", "saucelabs-jasmine"]);
};
<|endoftext|>


<commit_before>'use strict';

const apiai = require('apiai');
const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('uuid');
const request = require('request');
const JSONbig = require('json-bigint');
const async = require('async');

const REST_PORT = (process.env.PORT || 5000);
const APIAI_ACCESS_TOKEN = process.env.APIAI_ACCESS_TOKEN;
const APIAI_LANG = process.env.APIAI_LANG || 'en';
const FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN;
const FB_PAGE_ACCESS_TOKEN = process.env.FB_PAGE_ACCESS_TOKEN;
const FB_TEXT_LIMIT = 640;

const FACEBOOK_LOCATION = "FACEBOOK_LOCATION";
const FACEBOOK_WELCOME = "FACEBOOK_WELCOME";
var flagNombre = false;

class FacebookBot {
    constructor() {
        this.apiAiService = apiai(APIAI_ACCESS_TOKEN, {language: APIAI_LANG, requestSource: "fb"});
        this.sessionIds = new Map();
        this.messagesDelay = 200;
    }

    doDataResponse(sender, facebookResponseData) {
        if (!Array.isArray(facebookResponseData)) {
            console.log('Response as formatted message');
            this.sendFBMessage(sender, facebookResponseData)
                .catch(err => console.error(err));
        } else {
            async.eachSeries(facebookResponseData, (facebookMessage, callback) => {
                if (facebookMessage.sender_action) {
                    console.log('Response as sender action');
                    this.sendFBSenderAction(sender, facebookMessage.sender_action)
                        .then(() => callback())
                        .catch(err => callback(err));
                }
                else {
                    console.log('Response as formatted message');
                    this.sendFBMessage(sender, facebookMessage)
                        .then(() => callback())
                        .catch(err => callback(err));
                }
            }, (err) => {
                if (err) {
                    console.error(err);
                } else {
                    console.log('Data response completed');
                }
            });
        }
    }

	doRichContentResponse(sender, messages) {
        let facebookMessages = []; // array with result messages

        for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
            let message = messages[messageIndex];
			console.log('message.type: '+message.type);
            switch (message.type) {
                //message.type 0 means text message
                case 0:
                    // speech: ["hi"]
                    // we have to get value from fulfillment.speech, because of here is raw speech
                    if (message.speech) {

                        let splittedText = this.splitResponse(message.speech);
						console.log('message.speech: '+message.speech);
						if (message.speech=='Me indicas tú nombre completo, por favor'){
						flagNombre=true;
						//initfirebase();
						}

                        splittedText.forEach(s => {
                            facebookMessages.push({text: s});
                        });
                    }

                    break;
                //message.type 1 means card message
                case 1: {
                    let carousel = [message];

                    for (messageIndex++; messageIndex < messages.length; messageIndex++) {
                        if (messages[messageIndex].type == 1) {
                            carousel.push(messages[messageIndex]);
                        } else {
                            messageIndex--;
                            break;
                        }
                    }

                    let facebookMessage = {};
                    carousel.forEach((c) => {
                        // buttons: [ {text: "hi", postback: "postback"} ], imageUrl: "", title: "", subtitle: ""

                        let card = {};

                        card.title = c.title;
                        card.image_url = c.imageUrl;
                        if (this.isDefined(c.subtitle)) {
                            card.subtitle = c.subtitle;
                        }
                        //If button is involved in.
                        if (c.buttons.length > 0) {
                            let buttons = [];
                            for (let buttonIndex = 0; buttonIndex < c.buttons.length; buttonIndex++) {
                                let button = c.buttons[buttonIndex];

                                if (button.text) {
                                    let postback = button.postback;
                                    if (!postback) {
                                        postback = button.text;
                                    }

                                    let buttonDescription = {
                                        title: button.text
                                    };

                                    if (postback.startsWith("http")) {
                                        buttonDescription.type = "web_url";
                                        buttonDescription.url = postback;
                                    } else {
                                        buttonDescription.type = "postback";
                                        buttonDescription.payload = postback;
                                    }

                                    buttons.push(buttonDescription);
                                }
                            }

                            if (buttons.length > 0) {
                                card.buttons = buttons;
                            }
                        }

                        if (!facebookMessage.attachment) {
                            facebookMessage.attachment = {type: "template"};
                        }

                        if (!facebookMessage.attachment.payload) {
                            facebookMessage.attachment.payload = {template_type: "generic", elements: []};
                        }

                        facebookMessage.attachment.payload.elements.push(card);
                    });

                    facebookMessages.push(facebookMessage);
                }

                    break;
                //message.type 2 means quick replies message
                case 2: {
                    if (message.replies && message.replies.length > 0) {
                        let facebookMessage = {};

                        facebookMessage.text = message.title ? message.title : 'Choose an item';
						console.log('facebookMessage.text: '+facebookMessage.text);
                        facebookMessage.quick_replies = [];

                        message.replies.forEach((r) => {
                            facebookMessage.quick_replies.push({
                                content_type: "text",
                                title: r,
                                payload: r
                            });
                        });

                        facebookMessages.push(facebookMessage);
                    }
                }

                    break;
                //message.type 3 means image message
                case 3:

                    if (message.imageUrl) {
                        let facebookMessage = {};

                        // "imageUrl": "http://example.com/image.jpg"
                        facebookMessage.attachment = {type: "image"};
                        facebookMessage.attachment.payload = {url: message.imageUrl};

                        facebookMessages.push(facebookMessage);
                    }

                    break;
                //message.type 4 means custom payload message
                case 4:
                    if (message.payload && message.payload.facebook) {
                        facebookMessages.push(message.payload.facebook);
                    }
                    break;

                default:
                    break;
            }
        }

        return new Promise((resolve, reject) => {
            async.eachSeries(facebookMessages, (msg, callback) => {
                    this.sendFBSenderAction(sender, "typing_on")
                        .then(() => this.sleep(this.messagesDelay))
                        .then(() => this.sendFBMessage(sender, msg))
                        .then(() => callback())
                        .catch(callback);
                },
                (err) => {
                    if (err) {
                        console.error(err);
                        reject(err);
                    } else {
                        console.log('Messages sent');
                        resolve();
                    }
                });
        });

    }

    doTextResponse(sender, responseText) {
        console.log('Response as text message');
        // facebook API limit for text length is 640,
        // so we must split message if needed
        let splittedText = this.splitResponse(responseText);

        async.eachSeries(splittedText, (textPart, callback) => {
            this.sendFBMessage(sender, {text: textPart})
                .then(() => callback())
                .catch(err => callback(err));
        });
    }

    //which webhook event
    getEventText(event) {
		console.log('flagNombre ='+flagNombre);
        if (event.message) {
            if (event.message.quick_reply && event.message.quick_reply.payload) {
                console.log('event.message = true');
				return event.message.quick_reply.payload;
            }

            if (event.message.text) {
                console.log('event.message.text = true');
				console.log("event.sender.id.toString()"+event.sender.id.toString());
					if(event.message.text=='Registrarse'){
					return 'Alta';
				}
				if(flagNombre==true){
					console.log('nombre a registrar en DB: '+event.message.text);
					flagNombre=false;
				}
				return event.message.text;
            }
        }

        if (event.postback && event.postback.payload) {
			console.log('event.postback && event.postback.payload = true');
            return event.postback.payload;
        }

		console.log('return null');
        return null;

    }

    getFacebookEvent(event) {
        if (event.postback && event.postback.payload) {

            let payload = event.postback.payload;

            switch (payload) {
                case FACEBOOK_WELCOME:
                    return {name: FACEBOOK_WELCOME};

                case FACEBOOK_LOCATION:
                    return {name: FACEBOOK_LOCATION, data: event.postback.data}
            }
        }

        return null;
    }

    processFacebookEvent(event) {
        const sender = event.sender.id.toString();
        const eventObject = this.getFacebookEvent(event);

        if (eventObject) {

            // Handle a text message from this sender
            if (!this.sessionIds.has(sender)) {
                this.sessionIds.set(sender, uuid.v4());
            }

            let apiaiRequest = this.apiAiService.eventRequest(eventObject,
                {
                    sessionId: this.sessionIds.get(sender),
                    originalRequest: {
                        data: event,
                        source: "facebook"
                    }
                });
            this.doApiAiRequest(apiaiRequest, sender);
        }
    }

    processMessageEvent(event) {
        const sender = event.sender.id.toString();
        const text = this.getEventText(event);

        if (text) {

            // Handle a text message from this sender
            if (!this.sessionIds.has(sender)) {
                this.sessionIds.set(sender, uuid.v4());
            }

            console.log("Text", text);
            //send user's text to api.ai service
            let apiaiRequest = this.apiAiService.textRequest(text,
                {
                    sessionId: this.sessionIds.get(sender),
                    originalRequest: {
                        data: event,
                        source: "facebook"
                    }
                });

            this.doApiAiRequest(apiaiRequest, sender);
        }
    }

    doApiAiRequest(apiaiRequest, sender) {
        apiaiRequest.on('response', (response) => {
            if (this.isDefined(response.result) && this.isDefined(response.result.fulfillment)) {
                let responseText = response.result.fulfillment.speech;
                let responseData = response.result.fulfillment.data;
                let responseMessages = response.result.fulfillment.messages;

                if (this.isDefined(responseData) && this.isDefined(responseData.facebook)) {
                    let facebookResponseData = responseData.facebook;
                    this.doDataResponse(sender, facebookResponseData);
                } else if (this.isDefined(responseMessages) && responseMessages.length > 0) {
                    this.doRichContentResponse(sender, responseMessages);
                }
                else if (this.isDefined(responseText)) {
                    this.doTextResponse(sender, responseText);
                }

            }
        });

        apiaiRequest.on('error', (error) => console.error(error));
        apiaiRequest.end();
    }

    splitResponse(str) {
        if (str.length <= FB_TEXT_LIMIT) {
            return [str];
        }

        return this.chunkString(str, FB_TEXT_LIMIT);
    }

    chunkString(s, len) {
        let curr = len, prev = 0;

        let output = [];

        while (s[curr]) {
            if (s[curr++] == ' ') {
                output.push(s.substring(prev, curr));
                prev = curr;
                curr += len;
            }
            else {
                let currReverse = curr;
                do {
                    if (s.substring(currReverse - 1, currReverse) == ' ') {
                        output.push(s.substring(prev, currReverse));
                        prev = currReverse;
                        curr = currReverse + len;
                        break;
                    }
                    currReverse--;
                } while (currReverse > prev)
            }
        }
        output.push(s.substr(prev));
        return output;
    }

    sendFBMessage(sender, messageData) {
        return new Promise((resolve, reject) => {
            request({
                url: 'https://graph.facebook.com/v2.6/me/messages',
                qs: {access_token: FB_PAGE_ACCESS_TOKEN},
                method: 'POST',
                json: {
                    recipient: {id: sender},
                    message: messageData
                }
            }, (error, response) => {
                if (error) {
                    console.log('Error sending message: ', error);
                    reject(error);
                } else if (response.body.error) {
                    console.log('Error: ', response.body.error);
                    reject(new Error(response.body.error));
                }

                resolve();
            });
        });
    }

    sendFBSenderAction(sender, action) {
        return new Promise((resolve, reject) => {
            request({
                url: 'https://graph.facebook.com/v2.6/me/messages',
                qs: {access_token: FB_PAGE_ACCESS_TOKEN},
                method: 'POST',
                json: {
                    recipient: {id: sender},
                    sender_action: action
                }
            }, (error, response) => {
                if (error) {
                    console.error('Error sending action: ', error);
                    reject(error);
                } else if (response.body.error) {
                    console.error('Error: ', response.body.error);
                    reject(new Error(response.body.error));
                }

                resolve();
            });
        });
    }

    doSubscribeRequest() {
        request({
                method: 'POST',
                uri: `https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=${FB_PAGE_ACCESS_TOKEN}`
            },
            (error, response, body) => {
                if (error) {
                    console.error('Error while subscription: ', error);
                } else {
                    console.log('Subscription result: ', response.body);
                }
            });
    }

    configureGetStartedEvent() {
        request({
                method: 'POST',
                uri: `https://graph.facebook.com/v2.6/me/thread_settings?access_token=${FB_PAGE_ACCESS_TOKEN}`,
                json: {
                    setting_type: "call_to_actions",
                    thread_state: "new_thread",
                    call_to_actions: [
                        {
                            payload: FACEBOOK_WELCOME
                        }
                    ]
                }
            },
            (error, response, body) => {
                if (error) {
                    console.error('Error while subscription', error);
                } else {
                    console.log('Subscription result', response.body);
                }
            });
    }

    isDefined(obj) {
        if (typeof obj == 'undefined') {
            return false;
        }

        if (!obj) {
            return false;
        }

        return obj != null;
    }

    sleep(delay) {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve(), delay);
        });
    }

}

let facebookBot = new FacebookBot();

const app = express();

app.use(bodyParser.text({type: 'application/json'}));

app.get('/webhook/', (req, res) => {
    if (req.query['hub.verify_token'] === FB_VERIFY_TOKEN) {
        res.send(req.query['hub.challenge']);

        setTimeout(() => {
            facebookBot.doSubscribeRequest();
        }, 3000);
    } else {
        res.send('Error, wrong validation token');
    }
});

app.post('/webhook/', (req, res) => {
    try {
        const data = JSONbig.parse(req.body);

        if (data.entry) {
            let entries = data.entry;
            entries.forEach((entry) => {
                let messaging_events = entry.messaging;
                if (messaging_events) {
                    messaging_events.forEach((event) => {
                        if (event.message && !event.message.is_echo) {

                            if (event.message.attachments) {
                                let locations = event.message.attachments.filter(a => a.type === "location");

                                // delete all locations from original message
                                event.message.attachments = event.message.attachments.filter(a => a.type !== "location");

                                if (locations.length > 0) {
                                    locations.forEach(l => {
                                        let locationEvent = {
                                            sender: event.sender,
                                            postback: {
                                                payload: "FACEBOOK_LOCATION",
                                                data: l.payload.coordinates
                                            }
                                        };

                                        facebookBot.processFacebookEvent(locationEvent);
                                    });
                                }
                            }

                            facebookBot.processMessageEvent(event);
                        } else if (event.postback && event.postback.payload) {
                            if (event.postback.payload === "FACEBOOK_WELCOME") {
                                facebookBot.processFacebookEvent(event);
                            } else {
                                facebookBot.processMessageEvent(event);
                            }
                        }
                    });
                }
            });
        }

        return res.status(200).json({
            status: "ok"
        });
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }

});

app.listen(REST_PORT, () => {
    console.log('Rest service ready on port ' + REST_PORT);
});

facebookBot.doSubscribeRequest();
<commit_msg>234324<commit_after>'use strict';

const apiai = require('apiai');
const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('uuid');
const request = require('request');
const JSONbig = require('json-bigint');
const async = require('async');

const REST_PORT = (process.env.PORT || 5000);
const APIAI_ACCESS_TOKEN = process.env.APIAI_ACCESS_TOKEN;
const APIAI_LANG = process.env.APIAI_LANG || 'en';
const FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN;
const FB_PAGE_ACCESS_TOKEN = process.env.FB_PAGE_ACCESS_TOKEN;
const FB_TEXT_LIMIT = 640;

const FACEBOOK_LOCATION = "FACEBOOK_LOCATION";
const FACEBOOK_WELCOME = "FACEBOOK_WELCOME";
var flagNombre = false;
var config = {
  apiKey: "<API_KEY>",
  authDomain: "<PROJECT_ID>.firebaseapp.com",
  databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
  storageBucket: "<BUCKET>.appspot.com",
};

class FacebookBot {
    constructor() {
        this.apiAiService = apiai(APIAI_ACCESS_TOKEN, {language: APIAI_LANG, requestSource: "fb"});
        this.sessionIds = new Map();
        this.messagesDelay = 200;
    }

    doDataResponse(sender, facebookResponseData) {
        if (!Array.isArray(facebookResponseData)) {
            console.log('Response as formatted message');
            this.sendFBMessage(sender, facebookResponseData)
                .catch(err => console.error(err));
        } else {
            async.eachSeries(facebookResponseData, (facebookMessage, callback) => {
                if (facebookMessage.sender_action) {
                    console.log('Response as sender action');
                    this.sendFBSenderAction(sender, facebookMessage.sender_action)
                        .then(() => callback())
                        .catch(err => callback(err));
                }
                else {
                    console.log('Response as formatted message');
                    this.sendFBMessage(sender, facebookMessage)
                        .then(() => callback())
                        .catch(err => callback(err));
                }
            }, (err) => {
                if (err) {
                    console.error(err);
                } else {
                    console.log('Data response completed');
                }
            });
        }
    }

	doRichContentResponse(sender, messages) {
        let facebookMessages = []; // array with result messages

        for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
            let message = messages[messageIndex];
			console.log('message.type: '+message.type);
            switch (message.type) {
                //message.type 0 means text message
                case 0:
                    // speech: ["hi"]
                    // we have to get value from fulfillment.speech, because of here is raw speech
                    if (message.speech) {

                        let splittedText = this.splitResponse(message.speech);
						console.log('message.speech: '+message.speech);
						if (message.speech=='Me indicas tú nombre completo, por favor'){
						flagNombre=true;
						//initfirebase();
						}

                        splittedText.forEach(s => {
                            facebookMessages.push({text: s});
                        });
                    }

                    break;
                //message.type 1 means card message
                case 1: {
                    let carousel = [message];

                    for (messageIndex++; messageIndex < messages.length; messageIndex++) {
                        if (messages[messageIndex].type == 1) {
                            carousel.push(messages[messageIndex]);
                        } else {
                            messageIndex--;
                            break;
                        }
                    }

                    let facebookMessage = {};
                    carousel.forEach((c) => {
                        // buttons: [ {text: "hi", postback: "postback"} ], imageUrl: "", title: "", subtitle: ""

                        let card = {};

                        card.title = c.title;
                        card.image_url = c.imageUrl;
                        if (this.isDefined(c.subtitle)) {
                            card.subtitle = c.subtitle;
                        }
                        //If button is involved in.
                        if (c.buttons.length > 0) {
                            let buttons = [];
                            for (let buttonIndex = 0; buttonIndex < c.buttons.length; buttonIndex++) {
                                let button = c.buttons[buttonIndex];

                                if (button.text) {
                                    let postback = button.postback;
                                    if (!postback) {
                                        postback = button.text;
                                    }

                                    let buttonDescription = {
                                        title: button.text
                                    };

                                    if (postback.startsWith("http")) {
                                        buttonDescription.type = "web_url";
                                        buttonDescription.url = postback;
                                    } else {
                                        buttonDescription.type = "postback";
                                        buttonDescription.payload = postback;
                                    }

                                    buttons.push(buttonDescription);
                                }
                            }

                            if (buttons.length > 0) {
                                card.buttons = buttons;
                            }
                        }

                        if (!facebookMessage.attachment) {
                            facebookMessage.attachment = {type: "template"};
                        }

                        if (!facebookMessage.attachment.payload) {
                            facebookMessage.attachment.payload = {template_type: "generic", elements: []};
                        }

                        facebookMessage.attachment.payload.elements.push(card);
                    });

                    facebookMessages.push(facebookMessage);
                }

                    break;
                //message.type 2 means quick replies message
                case 2: {
                    if (message.replies && message.replies.length > 0) {
                        let facebookMessage = {};

                        facebookMessage.text = message.title ? message.title : 'Choose an item';
						console.log('facebookMessage.text: '+facebookMessage.text);
                        facebookMessage.quick_replies = [];

                        message.replies.forEach((r) => {
                            facebookMessage.quick_replies.push({
                                content_type: "text",
                                title: r,
                                payload: r
                            });
                        });

                        facebookMessages.push(facebookMessage);
                    }
                }

                    break;
                //message.type 3 means image message
                case 3:

                    if (message.imageUrl) {
                        let facebookMessage = {};

                        // "imageUrl": "http://example.com/image.jpg"
                        facebookMessage.attachment = {type: "image"};
                        facebookMessage.attachment.payload = {url: message.imageUrl};

                        facebookMessages.push(facebookMessage);
                    }

                    break;
                //message.type 4 means custom payload message
                case 4:
                    if (message.payload && message.payload.facebook) {
                        facebookMessages.push(message.payload.facebook);
                    }
                    break;

                default:
                    break;
            }
        }

        return new Promise((resolve, reject) => {
            async.eachSeries(facebookMessages, (msg, callback) => {
                    this.sendFBSenderAction(sender, "typing_on")
                        .then(() => this.sleep(this.messagesDelay))
                        .then(() => this.sendFBMessage(sender, msg))
                        .then(() => callback())
                        .catch(callback);
                },
                (err) => {
                    if (err) {
                        console.error(err);
                        reject(err);
                    } else {
                        console.log('Messages sent');
                        resolve();
                    }
                });
        });

    }

    doTextResponse(sender, responseText) {
        console.log('Response as text message');
        // facebook API limit for text length is 640,
        // so we must split message if needed
        let splittedText = this.splitResponse(responseText);

        async.eachSeries(splittedText, (textPart, callback) => {
            this.sendFBMessage(sender, {text: textPart})
                .then(() => callback())
                .catch(err => callback(err));
        });
    }

    //which webhook event
    getEventText(event) {
		console.log('flagNombre ='+flagNombre);
        if (event.message) {
            if (event.message.quick_reply && event.message.quick_reply.payload) {
                console.log('event.message = true');
				return event.message.quick_reply.payload;
            }

            if (event.message.text) {
                console.log('event.message.text = true');
				console.log("event.sender.id.toString()"+event.sender.id.toString());
					if(event.message.text=='Registrarse'){
					return 'Alta';
				}
				if(flagNombre==true){
					console.log('nombre a registrar en DB: '+event.message.text);
					flagNombre=false;
				}
				return event.message.text;
            }
        }

        if (event.postback && event.postback.payload) {
			console.log('event.postback && event.postback.payload = true');
            return event.postback.payload;
        }

		console.log('return null');
        return null;

    }

    getFacebookEvent(event) {
        if (event.postback && event.postback.payload) {

            let payload = event.postback.payload;

            switch (payload) {
                case FACEBOOK_WELCOME:
                    return {name: FACEBOOK_WELCOME};

                case FACEBOOK_LOCATION:
                    return {name: FACEBOOK_LOCATION, data: event.postback.data}
            }
        }

        return null;
    }

    processFacebookEvent(event) {
        const sender = event.sender.id.toString();
        const eventObject = this.getFacebookEvent(event);

        if (eventObject) {

            // Handle a text message from this sender
            if (!this.sessionIds.has(sender)) {
                this.sessionIds.set(sender, uuid.v4());
            }

            let apiaiRequest = this.apiAiService.eventRequest(eventObject,
                {
                    sessionId: this.sessionIds.get(sender),
                    originalRequest: {
                        data: event,
                        source: "facebook"
                    }
                });
            this.doApiAiRequest(apiaiRequest, sender);
        }
    }

    processMessageEvent(event) {
        const sender = event.sender.id.toString();
        const text = this.getEventText(event);

        if (text) {

            // Handle a text message from this sender
            if (!this.sessionIds.has(sender)) {
                this.sessionIds.set(sender, uuid.v4());
            }

            console.log("Text", text);
            //send user's text to api.ai service
            let apiaiRequest = this.apiAiService.textRequest(text,
                {
                    sessionId: this.sessionIds.get(sender),
                    originalRequest: {
                        data: event,
                        source: "facebook"
                    }
                });

            this.doApiAiRequest(apiaiRequest, sender);
        }
    }

    doApiAiRequest(apiaiRequest, sender) {
        apiaiRequest.on('response', (response) => {
            if (this.isDefined(response.result) && this.isDefined(response.result.fulfillment)) {
                let responseText = response.result.fulfillment.speech;
                let responseData = response.result.fulfillment.data;
                let responseMessages = response.result.fulfillment.messages;

                if (this.isDefined(responseData) && this.isDefined(responseData.facebook)) {
                    let facebookResponseData = responseData.facebook;
                    this.doDataResponse(sender, facebookResponseData);
                } else if (this.isDefined(responseMessages) && responseMessages.length > 0) {
                    this.doRichContentResponse(sender, responseMessages);
                }
                else if (this.isDefined(responseText)) {
                    this.doTextResponse(sender, responseText);
                }

            }
        });

        apiaiRequest.on('error', (error) => console.error(error));
        apiaiRequest.end();
    }

    splitResponse(str) {
        if (str.length <= FB_TEXT_LIMIT) {
            return [str];
        }

        return this.chunkString(str, FB_TEXT_LIMIT);
    }

    chunkString(s, len) {
        let curr = len, prev = 0;

        let output = [];

        while (s[curr]) {
            if (s[curr++] == ' ') {
                output.push(s.substring(prev, curr));
                prev = curr;
                curr += len;
            }
            else {
                let currReverse = curr;
                do {
                    if (s.substring(currReverse - 1, currReverse) == ' ') {
                        output.push(s.substring(prev, currReverse));
                        prev = currReverse;
                        curr = currReverse + len;
                        break;
                    }
                    currReverse--;
                } while (currReverse > prev)
            }
        }
        output.push(s.substr(prev));
        return output;
    }

    sendFBMessage(sender, messageData) {
        return new Promise((resolve, reject) => {
            request({
                url: 'https://graph.facebook.com/v2.6/me/messages',
                qs: {access_token: FB_PAGE_ACCESS_TOKEN},
                method: 'POST',
                json: {
                    recipient: {id: sender},
                    message: messageData
                }
            }, (error, response) => {
                if (error) {
                    console.log('Error sending message: ', error);
                    reject(error);
                } else if (response.body.error) {
                    console.log('Error: ', response.body.error);
                    reject(new Error(response.body.error));
                }

                resolve();
            });
        });
    }

    sendFBSenderAction(sender, action) {
        return new Promise((resolve, reject) => {
            request({
                url: 'https://graph.facebook.com/v2.6/me/messages',
                qs: {access_token: FB_PAGE_ACCESS_TOKEN},
                method: 'POST',
                json: {
                    recipient: {id: sender},
                    sender_action: action
                }
            }, (error, response) => {
                if (error) {
                    console.error('Error sending action: ', error);
                    reject(error);
                } else if (response.body.error) {
                    console.error('Error: ', response.body.error);
                    reject(new Error(response.body.error));
                }

                resolve();
            });
        });
    }

    doSubscribeRequest() {
        request({
                method: 'POST',
                uri: `https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=${FB_PAGE_ACCESS_TOKEN}`
            },
            (error, response, body) => {
                if (error) {
                    console.error('Error while subscription: ', error);
                } else {
                    console.log('Subscription result: ', response.body);
                }
            });
    }

    configureGetStartedEvent() {
        request({
                method: 'POST',
                uri: `https://graph.facebook.com/v2.6/me/thread_settings?access_token=${FB_PAGE_ACCESS_TOKEN}`,
                json: {
                    setting_type: "call_to_actions",
                    thread_state: "new_thread",
                    call_to_actions: [
                        {
                            payload: FACEBOOK_WELCOME
                        }
                    ]
                }
            },
            (error, response, body) => {
                if (error) {
                    console.error('Error while subscription', error);
                } else {
                    console.log('Subscription result', response.body);
                }
            });
    }

    isDefined(obj) {
        if (typeof obj == 'undefined') {
            return false;
        }

        if (!obj) {
            return false;
        }

        return obj != null;
    }

    sleep(delay) {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve(), delay);
        });
    }

}

let facebookBot = new FacebookBot();

const app = express();

app.use(bodyParser.text({type: 'application/json'}));

app.get('/webhook/', (req, res) => {
    if (req.query['hub.verify_token'] === FB_VERIFY_TOKEN) {
        res.send(req.query['hub.challenge']);

        setTimeout(() => {
            facebookBot.doSubscribeRequest();
        }, 3000);
    } else {
        res.send('Error, wrong validation token');
    }
});

app.post('/webhook/', (req, res) => {
    try {
        const data = JSONbig.parse(req.body);

        if (data.entry) {
            let entries = data.entry;
            entries.forEach((entry) => {
                let messaging_events = entry.messaging;
                if (messaging_events) {
                    messaging_events.forEach((event) => {
                        if (event.message && !event.message.is_echo) {

                            if (event.message.attachments) {
                                let locations = event.message.attachments.filter(a => a.type === "location");

                                // delete all locations from original message
                                event.message.attachments = event.message.attachments.filter(a => a.type !== "location");

                                if (locations.length > 0) {
                                    locations.forEach(l => {
                                        let locationEvent = {
                                            sender: event.sender,
                                            postback: {
                                                payload: "FACEBOOK_LOCATION",
                                                data: l.payload.coordinates
                                            }
                                        };

                                        facebookBot.processFacebookEvent(locationEvent);
                                    });
                                }
                            }

                            facebookBot.processMessageEvent(event);
                        } else if (event.postback && event.postback.payload) {
                            if (event.postback.payload === "FACEBOOK_WELCOME") {
                                facebookBot.processFacebookEvent(event);
                            } else {
                                facebookBot.processMessageEvent(event);
                            }
                        }
                    });
                }
            });
        }

        return res.status(200).json({
            status: "ok"
        });
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }

});

app.listen(REST_PORT, () => {
    console.log('Rest service ready on port ' + REST_PORT);
});

facebookBot.doSubscribeRequest();
<|endoftext|>