<commit_before>var Q = require("q");
var _ = require("lodash");
var path = require("path");
var swig = require('swig');
var tmp = require('tmp');

var fs = require("./fs");
var parse = require("../parse");

var generators = {
    "site": require("./site"),
    "page": require("./page"),
    "pdf": require("./pdf"),
    "ebook": require("./ebook"),
    "json": require("./json")
};

var defaultDescription = "Book generated using GitBook";

/*
 *  Use a specific generator to convert a gitbook to a site/pdf/ebook/
 *  output is always a folder
 */
var generate = function(options) {
    var generator = null;
    var files;

    options = _.defaults(options || {}, {
        // Folders to use
        input: null,
        output: null,

        // Output generator
        generator: "site",

        // Book title, keyword, description
        title: null,
        description: null,

        // Origin github repository id
        github: null,
        githubHost: 'https://github.com/',

        // Theming
        theme: path.resolve(__dirname, '../../theme')
    });

    if (!options.input) {
        return Q.reject(new Error("Need option input (book input directory)"));
    }

    if (!generators[options.generator]) {
        return Q.reject(new Error("Invalid generator (availables are: "+_.keys(generators).join(", ")));
    }

    options.output = options.output || path.join(options.input, "_book");

    // Clean output folder
    return fs.remove(options.output)

    .then(function() {
        return fs.mkdirp(options.output);
    })

    // List all files in the repository
    .then(function() {
        return fs.list(options.input)
        .then(function(_files) {
            files = _files;
        })
    })

    // Create the generator
    .then(function() {
        generator = new generators[options.generator](options);
    })

    // Detect multi-languages book
    .then(function() {
        if (_.contains(files, "LANGS.md")) {
            // Multi-languages book
            return fs.readFile(path.join(options.input, "LANGS.md"), "utf-8")

            // Generate sub-books
            .then(function(_langsSummary) {
                options.langsSummary = parse.langs(_langsSummary);

                // Generated a book for each valid entry
                return Q.all(
                    _.map(options.langsSummary.list, function(entry) {
                        return generate(_.extend({}, options, {
                            input: path.join(options.input, entry.path),
                            output: path.join(options.output, entry.path)
                        }));
                    })
                );
            })

            // Generate languages index
            .then(function() {
                return generator.langsIndex(options.langsSummary);
            });
        } else if (!_.contains(files, "SUMMARY.md") || !_.contains(files, "README.md")) {
            // Invalid book
            return Q.reject(new Error("Invalid gitbook repository, need SUMMARY.md and README.md"));
        } else {
            // Generate the book
            return Q()

            // Read readme
            .then(function() {
                return fs.readFile(path.join(options.input, "README.md"), "utf-8")
                .then(function(_readme) {
                    _readme = parse.readme(_readme);

                    options.title = options.title || _readme.title;
                    options.description = options.description || _readme.description || defaultDescription;
                });
            })

            // Get summary
            .then(function() {
                return fs.readFile(path.join(options.input, "SUMMARY.md"), "utf-8")
                .then(function(_summary) {
                    options.summary = parse.summary(_summary);

                    // Parse navigation
                    options.navigation = parse.navigation(options.summary);
                });
            })

            // Copy file and replace markdown file
            .then(function() {
                return Q.all(
                    _.chain(files)
                    .map(function(file) {
                        if (!file) return;

                        if (file[file.length -1] == "/") {
                            return Q(generator.transferFolder(file));
                        } else if (path.extname(file) == ".md" && options.navigation[file] != null) {
                            return fs.readFile(path.join(options.input, file), "utf-8")
                            .then(function(content) {
                                return Q(generator.convertFile(content, file));
                            });
                        } else {
                            return Q(generator.transferFile(file));
                        }
                    })
                    .value()
                );
            })

            // Finish gneration
            .then(function() {
                return generator.finish();
            });
        }
    })

    // Return all options
    .then(function() {
        return options;
    });
};

/*
 *  Extract files from generate output in a temporary folder
 */
var generateFile = function(options) {
    options = _.defaults(options || {}, {
        input: null,
        output: null,
        extension: null
    });

    return Q.nfcall(tmp.dir)
    .then(function(tmpDir) {
        return generate(
            _.extend({},
                options,
                {
                    output: tmpDir
                })
        )
        .then(function(_options) {
            var ext = options.extension;
            var outputFile = options.output || path.resolve(options.input, "book."+ext);

            var copyFile = function(lang) {
                var _outputFile = outputFile;
                var _tmpDir = tmpDir;

                if (lang) {
                    _outputFile = _outputFile.slice(0, -path.extname(_outputFile).length)+"_"+lang+path.extname(_outputFile);
                    _tmpDir = path.join(_tmpDir, lang);
                }

                return fs.copy(
                    path.join(_tmpDir, "index."+ext),
                    _outputFile
                );
            };

            // Multi-langs book
            return Q()
            .then(function() {
                if (_options.langsSummary) {
                    return Q.all(
                        _.map(_options.langsSummary.list, function(lang) {
                            return copyFile(lang.lang);
                        })
                    );
                } else {
                    return copyFile();
                }
            })
            .then(function() {
                return fs.remove(tmpDir);
            });
        });
    });
};

module.exports = {
    generators: generators,
    folder: generate,
    file: generateFile
};
<commit_msg>Do not pass SUMMARY.md to generator, fixes #93<commit_after>var Q = require("q");
var _ = require("lodash");
var path = require("path");
var swig = require('swig');
var tmp = require('tmp');

var fs = require("./fs");
var parse = require("../parse");

var generators = {
    "site": require("./site"),
    "page": require("./page"),
    "pdf": require("./pdf"),
    "ebook": require("./ebook"),
    "json": require("./json")
};

var defaultDescription = "Book generated using GitBook";

/*
 *  Use a specific generator to convert a gitbook to a site/pdf/ebook/
 *  output is always a folder
 */
var generate = function(options) {
    var generator = null;
    var files;

    options = _.defaults(options || {}, {
        // Folders to use
        input: null,
        output: null,

        // Output generator
        generator: "site",

        // Book title, keyword, description
        title: null,
        description: null,

        // Origin github repository id
        github: null,
        githubHost: 'https://github.com/',

        // Theming
        theme: path.resolve(__dirname, '../../theme')
    });

    if (!options.input) {
        return Q.reject(new Error("Need option input (book input directory)"));
    }

    if (!generators[options.generator]) {
        return Q.reject(new Error("Invalid generator (availables are: "+_.keys(generators).join(", ")));
    }

    options.output = options.output || path.join(options.input, "_book");

    // Clean output folder
    return fs.remove(options.output)

    .then(function() {
        return fs.mkdirp(options.output);
    })

    // List all files in the repository
    .then(function() {
        return fs.list(options.input)
        .then(function(_files) {
            files = _files;
        })
    })

    // Create the generator
    .then(function() {
        generator = new generators[options.generator](options);
    })

    // Detect multi-languages book
    .then(function() {
        if (_.contains(files, "LANGS.md")) {
            // Multi-languages book
            return fs.readFile(path.join(options.input, "LANGS.md"), "utf-8")

            // Generate sub-books
            .then(function(_langsSummary) {
                options.langsSummary = parse.langs(_langsSummary);

                // Generated a book for each valid entry
                return Q.all(
                    _.map(options.langsSummary.list, function(entry) {
                        return generate(_.extend({}, options, {
                            input: path.join(options.input, entry.path),
                            output: path.join(options.output, entry.path)
                        }));
                    })
                );
            })

            // Generate languages index
            .then(function() {
                return generator.langsIndex(options.langsSummary);
            });
        } else if (!_.contains(files, "SUMMARY.md") || !_.contains(files, "README.md")) {
            // Invalid book
            return Q.reject(new Error("Invalid gitbook repository, need SUMMARY.md and README.md"));
        } else {
            // Generate the book
            return Q()

            // Read readme
            .then(function() {
                return fs.readFile(path.join(options.input, "README.md"), "utf-8")
                .then(function(_readme) {
                    _readme = parse.readme(_readme);

                    options.title = options.title || _readme.title;
                    options.description = options.description || _readme.description || defaultDescription;
                });
            })

            // Get summary
            .then(function() {
                return fs.readFile(path.join(options.input, "SUMMARY.md"), "utf-8")
                .then(function(_summary) {
                    options.summary = parse.summary(_summary);

                    // Parse navigation
                    options.navigation = parse.navigation(options.summary);
                });
            })

            // Skip processing some files
            .then(function() {
                files = _.filter(files, function (file) {
                    return !(
                        file === 'SUMMARY.md'
                    );
                });
            })

            // Copy file and replace markdown file
            .then(function() {
                return Q.all(
                    _.chain(files)
                    .map(function(file) {
                        if (!file) return;

                        if (file[file.length -1] == "/") {
                            return Q(generator.transferFolder(file));
                        } else if (path.extname(file) == ".md" && options.navigation[file] != null) {
                            return fs.readFile(path.join(options.input, file), "utf-8")
                            .then(function(content) {
                                return Q(generator.convertFile(content, file));
                            });
                        } else {
                            return Q(generator.transferFile(file));
                        }
                    })
                    .value()
                );
            })

            // Finish gneration
            .then(function() {
                return generator.finish();
            });
        }
    })

    // Return all options
    .then(function() {
        return options;
    });
};

/*
 *  Extract files from generate output in a temporary folder
 */
var generateFile = function(options) {
    options = _.defaults(options || {}, {
        input: null,
        output: null,
        extension: null
    });

    return Q.nfcall(tmp.dir)
    .then(function(tmpDir) {
        return generate(
            _.extend({},
                options,
                {
                    output: tmpDir
                })
        )
        .then(function(_options) {
            var ext = options.extension;
            var outputFile = options.output || path.resolve(options.input, "book."+ext);

            var copyFile = function(lang) {
                var _outputFile = outputFile;
                var _tmpDir = tmpDir;

                if (lang) {
                    _outputFile = _outputFile.slice(0, -path.extname(_outputFile).length)+"_"+lang+path.extname(_outputFile);
                    _tmpDir = path.join(_tmpDir, lang);
                }

                return fs.copy(
                    path.join(_tmpDir, "index."+ext),
                    _outputFile
                );
            };

            // Multi-langs book
            return Q()
            .then(function() {
                if (_options.langsSummary) {
                    return Q.all(
                        _.map(_options.langsSummary.list, function(lang) {
                            return copyFile(lang.lang);
                        })
                    );
                } else {
                    return copyFile();
                }
            })
            .then(function() {
                return fs.remove(tmpDir);
            });
        });
    });
};

module.exports = {
    generators: generators,
    folder: generate,
    file: generateFile
};
<|endoftext|>


<commit_before>//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

'use strict';

// Status: Partial Functionality
// This module wraps a table service client to provide encryption and decryption trying
// to reuse existing helper code. It only wraps what is needed for this library to work.

// ----------------------------------------------------------------------------
// This is a Node.js implementation of client-side table entity encryption,
// compatible with the official Azure storage .NET library.
// ----------------------------------------------------------------------------

const async = require('async');
const encryption = require('./encryption');

function retrieveEntity() {
  const args = Array.prototype.slice.call(arguments);
  const tableClient = args.shift();
  const encryptionOptions = args.shift();
  // args[0] is the tableName;
  const partitionKey = args[1];
  const rowKey = args[2];
  const callback = args.pop();
  args.push((error, result, response) => {
    if (error) {
      return callback(error);
    }
    const reducedEntity = encryptionOptions.tableDehydrator(result);
    encryption.decryptEntity(partitionKey, rowKey, reducedEntity, encryptionOptions, (decryptError, entity) => {
      if (decryptError) {
        return callback(decryptError);
      }
      const hydrated = encryptionOptions.tableRehydrator(partitionKey, rowKey, entity);
      return callback(null, hydrated, response);
    });
  });
  tableClient.retrieveEntity.apply(tableClient, args);
}

function queryEntitiesCallback(encryptionOptions, callback, error, results, headers) {
  if (error) {
    error.headers = headers;
    return callback(error);
  }
  if (!(results && results.entries && results.entries.length > 0)) {
    return callback(null, results, headers);
  }
  const entities = results.entries;
  async.map(entities, (row, next) => {
    if (row === undefined || row.PartitionKey === undefined || row.PartitionKey._ === undefined) {
      return next(null, row);
      // return next(new Error('Entity does not have a PartitionKey.'));
    }
    if (row === undefined || row.RowKey === undefined || row.RowKey._ === undefined) {
      return next(null, row);
      // return next(new Error('Entity does not have a RowKey.'));
    }
    const partitionKey = row.PartitionKey._;
    const rowKey = row.RowKey._;
    let reducedEntity = null;
    try {
      reducedEntity = encryptionOptions.tableDehydrator(row);
    } catch (rex) {
      return next(rex);
    }
    encryption.decryptEntity(partitionKey, rowKey, reducedEntity, encryptionOptions, (decryptError, entity) => {
      if (decryptError) {
        return next(decryptError);
      }
      const hydrated = encryptionOptions.tableRehydrator(partitionKey, rowKey, entity);
      return next(null, hydrated);
    });
  }, (asyncError, decryptedRows) => {
    if (asyncError) {
      return callback(asyncError);
    }
    results.entries = decryptedRows;
    return callback(null, results);
  });
}

function queryEntities() {
  const args = Array.prototype.slice.call(arguments);
  const tableClient = args.shift();
  const encryptionOptions = args.shift();
  const callback = args.pop();
  args.push(queryEntitiesCallback.bind(undefined, encryptionOptions, callback));
  tableClient.queryEntities.apply(tableClient, args);
}

function insertEntity() {
  const args = Array.prototype.slice.call(arguments);
  const tableClient = args.shift();
  const encryptionOptions = args.shift();
  const entity = args[1];
  const partitionKey = entity.PartitionKey._;
  const rowKey = entity.RowKey._;
  const reducedEntity = encryptionOptions.tableDehydrator(entity);
  const callback = args[args.length - 1];
  encryption.encryptEntity(partitionKey, rowKey, reducedEntity, encryptionOptions, (encryptError, encryptedEntity) => {
    if (encryptError) {
      return callback(encryptError);
    }
    args[1] /* entity */ = encryptionOptions.tableRehydrator(partitionKey, rowKey, encryptedEntity);
    tableClient.insertEntity.apply(tableClient, args);
  });
}

function replaceEntity() {
  const args = Array.prototype.slice.call(arguments);
  const tableClient = args.shift();
  const encryptionOptions = args.shift();
  const entity = args[1];
  const partitionKey = entity.PartitionKey._;
  const rowKey = entity.RowKey._;
  const reducedEntity = encryptionOptions.tableDehydrator(entity);
  const callback = args[args.length - 1];
  encryption.encryptEntity(partitionKey, rowKey, reducedEntity, encryptionOptions, (encryptError, encryptedEntity) => {
    if (encryptError) {
      return callback(encryptError);
    }
    args[1] /* entity */ = encryptionOptions.tableRehydrator(partitionKey, rowKey, encryptedEntity);
    tableClient.replaceEntity.apply(tableClient, args);
  });
}

function mergeEntity() {
  const args = Array.prototype.slice.call(arguments);
  const callback = args.pop();
  return callback(new Error('Entity merge operations are not supported when using table encryption.'));
}

module.exports = function wrapTableClient(tableClient, encryptionOptions) {
  const wrapped = {
    insertEntity: insertEntity.bind(undefined, tableClient, encryptionOptions),
    mergeEntity: mergeEntity.bind(undefined, tableClient, encryptionOptions),
    queryEntities: queryEntities.bind(undefined, tableClient, encryptionOptions),
    replaceEntity: replaceEntity.bind(undefined, tableClient, encryptionOptions),
    retrieveEntity: retrieveEntity.bind(undefined, tableClient, encryptionOptions),
  };
  const passthru = [
    'createTableIfNotExists',
    'deleteEntity',
  ];
  for (let i = 0; i < passthru.length; i++) {
    const name = passthru[i];
    wrapped[name] = tableClient[name].bind(tableClient);
  }
  return wrapped;
};
<commit_msg>Revert "Returning objects without going through encryption when no RowKey and no PartitionKey"<commit_after>//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

'use strict';

// Status: Partial Functionality
// This module wraps a table service client to provide encryption and decryption trying
// to reuse existing helper code. It only wraps what is needed for this library to work.

// ----------------------------------------------------------------------------
// This is a Node.js implementation of client-side table entity encryption,
// compatible with the official Azure storage .NET library.
// ----------------------------------------------------------------------------

const async = require('async');
const encryption = require('./encryption');

function retrieveEntity() {
  const args = Array.prototype.slice.call(arguments);
  const tableClient = args.shift();
  const encryptionOptions = args.shift();
  // args[0] is the tableName;
  const partitionKey = args[1];
  const rowKey = args[2];
  const callback = args.pop();
  args.push((error, result, response) => {
    if (error) {
      return callback(error);
    }
    const reducedEntity = encryptionOptions.tableDehydrator(result);
    encryption.decryptEntity(partitionKey, rowKey, reducedEntity, encryptionOptions, (decryptError, entity) => {
      if (decryptError) {
        return callback(decryptError);
      }
      const hydrated = encryptionOptions.tableRehydrator(partitionKey, rowKey, entity);
      return callback(null, hydrated, response);
    });
  });
  tableClient.retrieveEntity.apply(tableClient, args);
}

function queryEntitiesCallback(encryptionOptions, callback, error, results, headers) {
  if (error) {
    error.headers = headers;
    return callback(error);
  }
  if (!(results && results.entries && results.entries.length > 0)) {
    return callback(null, results, headers);
  }
  const entities = results.entries;
  async.map(entities, (row, next) => {
    if (row === undefined || row.PartitionKey === undefined || row.PartitionKey._ === undefined) {
      return next(new Error('Entity does not have a PartitionKey.'));
    }
    if (row === undefined || row.RowKey === undefined || row.RowKey._ === undefined) {
      return next(new Error('Entity does not have a RowKey.'));
    }
    const partitionKey = row.PartitionKey._;
    const rowKey = row.RowKey._;
    let reducedEntity = null;
    try {
      reducedEntity = encryptionOptions.tableDehydrator(row);
    } catch (rex) {
      return next(rex);
    }
    encryption.decryptEntity(partitionKey, rowKey, reducedEntity, encryptionOptions, (decryptError, entity) => {
      if (decryptError) {
        return next(decryptError);
      }
      const hydrated = encryptionOptions.tableRehydrator(partitionKey, rowKey, entity);
      return next(null, hydrated);
    });
  }, (asyncError, decryptedRows) => {
    if (asyncError) {
      return callback(asyncError);
    }
    results.entries = decryptedRows;
    return callback(null, results);
  });
}

function queryEntities() {
  const args = Array.prototype.slice.call(arguments);
  const tableClient = args.shift();
  const encryptionOptions = args.shift();
  const callback = args.pop();
  args.push(queryEntitiesCallback.bind(undefined, encryptionOptions, callback));
  tableClient.queryEntities.apply(tableClient, args);
}

function insertEntity() {
  const args = Array.prototype.slice.call(arguments);
  const tableClient = args.shift();
  const encryptionOptions = args.shift();
  const entity = args[1];
  const partitionKey = entity.PartitionKey._;
  const rowKey = entity.RowKey._;
  const reducedEntity = encryptionOptions.tableDehydrator(entity);
  const callback = args[args.length - 1];
  encryption.encryptEntity(partitionKey, rowKey, reducedEntity, encryptionOptions, (encryptError, encryptedEntity) => {
    if (encryptError) {
      return callback(encryptError);
    }
    args[1] /* entity */ = encryptionOptions.tableRehydrator(partitionKey, rowKey, encryptedEntity);
    tableClient.insertEntity.apply(tableClient, args);
  });
}

function replaceEntity() {
  const args = Array.prototype.slice.call(arguments);
  const tableClient = args.shift();
  const encryptionOptions = args.shift();
  const entity = args[1];
  const partitionKey = entity.PartitionKey._;
  const rowKey = entity.RowKey._;
  const reducedEntity = encryptionOptions.tableDehydrator(entity);
  const callback = args[args.length - 1];
  encryption.encryptEntity(partitionKey, rowKey, reducedEntity, encryptionOptions, (encryptError, encryptedEntity) => {
    if (encryptError) {
      return callback(encryptError);
    }
    args[1] /* entity */ = encryptionOptions.tableRehydrator(partitionKey, rowKey, encryptedEntity);
    tableClient.replaceEntity.apply(tableClient, args);
  });
}

function mergeEntity() {
  const args = Array.prototype.slice.call(arguments);
  const callback = args.pop();
  return callback(new Error('Entity merge operations are not supported when using table encryption.'));
}

module.exports = function wrapTableClient(tableClient, encryptionOptions) {
  const wrapped = {
    insertEntity: insertEntity.bind(undefined, tableClient, encryptionOptions),
    mergeEntity: mergeEntity.bind(undefined, tableClient, encryptionOptions),
    queryEntities: queryEntities.bind(undefined, tableClient, encryptionOptions),
    replaceEntity: replaceEntity.bind(undefined, tableClient, encryptionOptions),
    retrieveEntity: retrieveEntity.bind(undefined, tableClient, encryptionOptions),
  };
  const passthru = [
    'createTableIfNotExists',
    'deleteEntity',
  ];
  for (let i = 0; i < passthru.length; i++) {
    const name = passthru[i];
    wrapped[name] = tableClient[name].bind(tableClient);
  }
  return wrapped;
};
<|endoftext|>


<commit_before>/*
* MultiSelect v0.6
* Copyright (c) 2012 Louis Cuny
*
* Dual licensed under the MIT and GPL licenses:
*    http://www.opensource.org/licenses/mit-license.php
*    http://www.gnu.org/licenses/gpl.html
*
*/

(function($){
  var msMethods = {
    'init' : function(options){
      this.settings = {
        disabledClass : 'disabled',
        callbackOnInit: false,
        keepOrder : false
      };
      if(options) {
        this.settings = $.extend(this.settings, options);
      }
      var multiSelects = this;
      multiSelects.css('position', 'absolute').css('left', '-9999px');
      multiSelects.each(function(){
        var ms = $(this);

        if (ms.next('.ms-container').length == 0){
          ms.attr('id', ms.attr('id') ? ms.attr('id') : 'ms-'+Math.ceil(Math.random()*1000));
          var container = $('

'),
              selectableContainer = $('

'),
              selectedContainer = $('

'),
              selectableUl = $('

'),
              selectedUl = $('

');

          ms.data('settings', multiSelects.settings);

          var optgroupLabel = null,
              optgroupId = null,
              optgroupCpt = 0,
              scroll = 0;
          ms.find('optgroup,option').each(function(){
            if ($(this).is('optgroup')){
              optgroupLabel = $(this).attr('label');
              optgroupId = 'ms-'+ms.attr('id')+'-optgroup-'+optgroupCpt;
              selectableUl.append($('

'+
                                  optgroupLabel+'

'));
              optgroupCpt++;
            }
            if ($(this).is("option:not(option[value=''])")){
              var klass = $(this).attr('class') ? ' '+$(this).attr('class') : '';
              var selectableLi = $('
'+$(this).text()+'
');

              if ($(this).attr('title'))
                selectableLi.attr('title', $(this).attr('title'));
              if ($(this).attr('disabled') || ms.attr('disabled')){
                selectableLi.attr('disabled', 'disabled');
                selectableLi.addClass(multiSelects.settings.disabledClass);
              }
              selectableLi.click(function(){
                ms.multiSelect('select', $(this).attr('ms-value'));
              });
              var container = optgroupId ? selectableUl.children('#'+optgroupId).find('ul').first() : selectableUl;
              container.append(selectableLi);
            }
          });
          if (multiSelects.settings.selectableHeader){
            selectableContainer.append(multiSelects.settings.selectableHeader);
          }
          selectableContainer.append(selectableUl);
          if (multiSelects.settings.selectedHeader){
            selectedContainer.append(multiSelects.settings.selectedHeader);
          }
          selectedContainer.append(selectedUl);
          container.append(selectableContainer);
          container.append(selectedContainer);
          ms.after(container);
          ms.find('option:selected').each(function(){
            ms.multiSelect('select', $(this).val(), 'init');
          });

          $('.ms-elem-selectable', container).on('mouseenter', function(){
            $('li', container).removeClass('ms-hover');
            $(this).addClass('ms-hover');
          }).on('mouseout', function(){
            $('li', container).removeClass('ms-hover');
          });

          selectableUl.on('focusin', function(){
            $(this).addClass('ms-focus');
          }).on('focusout', function(){
            $(this).removeClass('ms-focus');
            $('li', container).removeClass('ms-hover');
          });

          selectedUl.on('focusin', function(){
            $(this).addClass('ms-focus');
          }).on('focusout', function(){
            $(this).removeClass('ms-focus');
            $('li', container).removeClass('ms-hover');
          });

          ms.on('focusin', function(){
            selectableUl.focus();
          }).on('focusout', function(){
            selectableUl.removeClass('ms-focus');
            selectedUl.removeClass('ms-focus');
          });

          ms.onKeyDown = function(e, keyContainer){
            var selectables = $('.'+keyContainer+' li:visible:not(.ms-optgroup-label, .ms-optgroup-container)', container),
                selectablesLength = selectables.length,
                selectableFocused = $('.'+keyContainer+' li.ms-hover', container),
                selectableFocusedIndex = $('.'+keyContainer+' li:visible:not(.ms-optgroup-label, .ms-optgroup-container)', container).index(selectableFocused),
                liHeight = selectables.first().outerHeight(),
                numberOfItemsDisplayed = Math.ceil(container.outerHeight()/liHeight),
                scrollStart = Math.ceil(numberOfItemsDisplayed/4);

            selectables.removeClass('ms-hover');
            if (e.keyCode == 32){ // space
              var method = keyContainer == 'ms-selectable' ? 'select' : 'deselect';
              ms.multiSelect(method, selectableFocused.first().attr('ms-value'));

            } else if (e.keyCode == 40){ // Down
              var nextIndex = (selectableFocusedIndex+1 != selectablesLength) ? selectableFocusedIndex+1 : 0,
                  nextSelectableLi = selectables.eq(nextIndex);

              nextSelectableLi.addClass('ms-hover');
              if (nextIndex > scrollStart){
                scroll += liHeight;
              } else if (nextIndex == 0){
                scroll = 0;
              }
              $('.'+keyContainer+' ul', container).scrollTop(scroll);
            } else if (e.keyCode == 38){ // Up
              var prevIndex = (selectableFocusedIndex-1 >= 0) ? selectableFocusedIndex-1 : selectablesLength-1,
                  prevSelectableLi = selectables.eq(prevIndex);
              selectables.removeClass('ms-hover');
              prevSelectableLi.addClass('ms-hover');
              if (selectablesLength-prevIndex+1 < scrollStart){
                scroll = liHeight*(selectablesLength-scrollStart);
              } else {
                scroll -= liHeight;
              }
              $('.'+keyContainer+' ul').scrollTop(scroll);
            } else if (e.keyCode == 37 || e.keyCode == 39){ // Right and Left
              if (selectableUl.hasClass('ms-focus')){
                selectableUl.focusout();
                selectedUl.focusin();
              } else {
                selectableUl.focusin();
                selectedUl.focusout();
              }
            }
          }

          ms.on('keydown', function(e){
            if (ms.is(':focus')){
              var keyContainer = selectableUl.hasClass('ms-focus') ? 'ms-selectable' : 'ms-selection';
              ms.onKeyDown(e, keyContainer);
            }
          });
        }
      });
    },
    'refresh' : function() {
      $("#ms-"+$(this).attr("id")).remove();
      $(this).multiSelect("init", $(this).data("settings"));
    },
    'select' : function(value, method){
      var ms = this,
          selectedOption = ms.find('option[value="'+value +'"]'),
          text = selectedOption.text(),
          klass = selectedOption.attr('class'),
          titleAttr = selectedOption.attr('title');

      var selectedLi = $('
'+text+'
'),
          selectableUl = $('#ms-'+ms.attr('id')+' .ms-selectable ul'),
          selectedUl = $('#ms-'+ms.attr('id')+' .ms-selection ul'),
          selectableLi = selectableUl.children('li[ms-value="'+value+'"]'),
          haveToSelect = null;

      if (method == 'init'){
        haveToSelect = !selectableLi.hasClass(ms.data('settings').disabledClass) && selectedOption.attr('selected');
      } else {
        haveToSelect = !selectableLi.hasClass(ms.data('settings').disabledClass);
      }
      if (haveToSelect && value && value != '' && selectedUl.children('li[ms-value="'+value+'"]').length == 0){
        ms.focus();
        var parentOptgroup = selectableLi.parent('.ms-optgroup');
        if (parentOptgroup.length > 0)
          if (parentOptgroup.children('.ms-elem-selectable:not(:hidden)').length == 1)
            parentOptgroup.children('.ms-optgroup-label').hide();
        selectableLi.addClass('ms-selected');
        selectableLi.hide();
        selectedOption.attr('selected', 'selected');
        if(titleAttr){
          selectedLi.attr('title', titleAttr)
        }
        if (selectableLi.hasClass(ms.data('settings').disabledClass)){
          selectedLi.addClass(ms.data('settings').disabledClass);
        } else {
          selectedLi.click(function(){
            ms.multiSelect('deselect', $(this).attr('ms-value'));
          });
        }

        var selectedUlLis = selectedUl.children('.ms-elem-selected');
        if (method != 'init' && ms.data('settings').keepOrder && selectedUlLis.length > 0) {

          var getIndexOf = function(value) {
            elems = selectableUl.children('.ms-elem-selectable');
            return(elems.index(elems.closest('[ms-value="'+value+'"]')));
          }

          var index = getIndexOf(selectedLi.attr('ms-value'));
          if (index == 0)
            selectedUl.prepend(selectedLi);
          else {
            for (i = index - 1; i >= 0; i--){
              if (selectedUlLis[i] && getIndexOf($(selectedUlLis[i]).attr('ms-value')) < index) {
                $(selectedUlLis[i]).after(selectedLi);
                break;
              } else if (i == 0) {
                $(selectedUlLis[i]).before(selectedLi);
              }
            }
          }
        } else {
          selectedUl.append(selectedLi);
        }
        selectedLi.on('mouseenter', function(){
          $('li', selectedUl).removeClass('ms-hover');
          $(this).addClass('ms-hover');
        }).on('mouseout', function(){
          $('li', selectedUl).removeClass('ms-hover');
        });
        if (ms.find("option[value='']")){
          ms.find("option[value='']").removeAttr('selected');
        }
        if (method == "select_all" && parentOptgroup.children('.ms-elem-selectable').length > 0){
          parentOptgroup.children('.ms-optgroup-label').hide();
        }
        if(method != 'init' || ms.data('settings').callbackOnInit){
          ms.trigger('change');
          selectedUl.trigger('change');
          selectableUl.trigger('change');
          if (typeof ms.data('settings').afterSelect == 'function' &&
              (method != 'init' || ms.data('settings').callbackOnInit)) {
            ms.data('settings').afterSelect.call(this, value, text);
          }
        }
      }
    },
    'deselect' : function(value){
      var ms = this,
          selectedUl = $('#ms-'+ms.attr('id')+' .ms-selection ul'),
          selectedOption = ms.find('option[value="'+value +'"]'),
          selectedLi = selectedUl.children('li[ms-value="'+value+'"]');

      if(selectedLi){
        selectedUl.focusin();
        var selectableUl = $('#ms-'+ms.attr('id')+' .ms-selectable ul'),
            selectedUl = $('#ms-'+ms.attr('id')+' .ms-selection ul'),
            selectableLi = selectableUl.children('li[ms-value="'+value+'"]'),
            selectedLi = selectedUl.children('li[ms-value="'+value+'"]');

        var parentOptgroup = selectableLi.parent('.ms-optgroup');
        if (parentOptgroup.length > 0){
          parentOptgroup.children('.ms-optgroup-label').addClass('ms-collapse').show();
          parentOptgroup.children('.ms-elem-selectable:not(.ms-selected)').show();
        }
        selectedOption.removeAttr('selected');
        selectableLi.show();
        selectableLi.removeClass('ms-selected');
        selectedLi.remove();
        selectedUl.trigger('change');
        selectableUl.trigger('change');
        ms.trigger('change');
        if (typeof ms.data('settings').afterDeselect == 'function') {
          ms.data('settings').afterDeselect.call(this, value, selectedLi.text());
        }
      }
    },
    'select_all' : function(visible){
      var ms = this,
          selectableUl = $('#ms-'+ms.attr('id')+' .ms-selectable ul');

      ms.find("option:not(option[value=''])").each(function(){
        var value = $(this).val();
        if (visible){
          var selectableLi = selectableUl.children('li[ms-value="'+value+'"]');
          if (selectableLi.filter(':visible').length > 0){
            ms.multiSelect('select', value, 'select_all');
          }
        } else {
          ms.multiSelect('select', value, 'select_all');
        }
      });
    },
    'deselect_all' : function(){
      var ms = this;
      ms.find("option:not(option[value=''])").each(function(){
        ms.multiSelect('deselect', $(this).val(), 'deselect_all');
      });
    }
  };

  $.fn.multiSelect = function(method){
    if ( msMethods[method] ) {
      return msMethods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'object' || ! method ) {
      return msMethods.init.apply( this, arguments );
    } else {
      if(console.log) console.log( 'Method ' +  method + ' does not exist on jquery.multiSelect' );
    }
    return false;
  };
})(jQuery);
<commit_msg>rename callbackOnInit on selectCallbackOnInit option<commit_after>/*
* MultiSelect v0.6
* Copyright (c) 2012 Louis Cuny
*
* Dual licensed under the MIT and GPL licenses:
*    http://www.opensource.org/licenses/mit-license.php
*    http://www.gnu.org/licenses/gpl.html
*
*/

(function($){
  var msMethods = {
    'init' : function(options){
      this.settings = {
        disabledClass : 'disabled',
        selectCallbackOnInit: false,
        keepOrder : false
      };
      if(options) {
        this.settings = $.extend(this.settings, options);
      }
      var multiSelects = this;
      multiSelects.css('position', 'absolute').css('left', '-9999px');
      multiSelects.each(function(){
        var ms = $(this);

        if (ms.next('.ms-container').length == 0){
          ms.attr('id', ms.attr('id') ? ms.attr('id') : 'ms-'+Math.ceil(Math.random()*1000));
          var container = $('

'),
              selectableContainer = $('

'),
              selectedContainer = $('

'),
              selectableUl = $('

'),
              selectedUl = $('

');

          ms.data('settings', multiSelects.settings);

          var optgroupLabel = null,
              optgroupId = null,
              optgroupCpt = 0,
              scroll = 0;
          ms.find('optgroup,option').each(function(){
            if ($(this).is('optgroup')){
              optgroupLabel = $(this).attr('label');
              optgroupId = 'ms-'+ms.attr('id')+'-optgroup-'+optgroupCpt;
              selectableUl.append($('

'+
                                  optgroupLabel+'

'));
              optgroupCpt++;
            }
            if ($(this).is("option:not(option[value=''])")){
              var klass = $(this).attr('class') ? ' '+$(this).attr('class') : '';
              var selectableLi = $('
'+$(this).text()+'
');

              if ($(this).attr('title'))
                selectableLi.attr('title', $(this).attr('title'));
              if ($(this).attr('disabled') || ms.attr('disabled')){
                selectableLi.attr('disabled', 'disabled');
                selectableLi.addClass(multiSelects.settings.disabledClass);
              }
              selectableLi.click(function(){
                ms.multiSelect('select', $(this).attr('ms-value'));
              });
              var container = optgroupId ? selectableUl.children('#'+optgroupId).find('ul').first() : selectableUl;
              container.append(selectableLi);
            }
          });
          if (multiSelects.settings.selectableHeader){
            selectableContainer.append(multiSelects.settings.selectableHeader);
          }
          selectableContainer.append(selectableUl);
          if (multiSelects.settings.selectedHeader){
            selectedContainer.append(multiSelects.settings.selectedHeader);
          }
          selectedContainer.append(selectedUl);
          container.append(selectableContainer);
          container.append(selectedContainer);
          ms.after(container);
          ms.find('option:selected').each(function(){
            ms.multiSelect('select', $(this).val(), 'init');
          });

          $('.ms-elem-selectable', container).on('mouseenter', function(){
            $('li', container).removeClass('ms-hover');
            $(this).addClass('ms-hover');
          }).on('mouseout', function(){
            $('li', container).removeClass('ms-hover');
          });

          selectableUl.on('focusin', function(){
            $(this).addClass('ms-focus');
          }).on('focusout', function(){
            $(this).removeClass('ms-focus');
            $('li', container).removeClass('ms-hover');
          });

          selectedUl.on('focusin', function(){
            $(this).addClass('ms-focus');
          }).on('focusout', function(){
            $(this).removeClass('ms-focus');
            $('li', container).removeClass('ms-hover');
          });

          ms.on('focusin', function(){
            selectableUl.focus();
          }).on('focusout', function(){
            selectableUl.removeClass('ms-focus');
            selectedUl.removeClass('ms-focus');
          });

          ms.onKeyDown = function(e, keyContainer){
            var selectables = $('.'+keyContainer+' li:visible:not(.ms-optgroup-label, .ms-optgroup-container)', container),
                selectablesLength = selectables.length,
                selectableFocused = $('.'+keyContainer+' li.ms-hover', container),
                selectableFocusedIndex = $('.'+keyContainer+' li:visible:not(.ms-optgroup-label, .ms-optgroup-container)', container).index(selectableFocused),
                liHeight = selectables.first().outerHeight(),
                numberOfItemsDisplayed = Math.ceil(container.outerHeight()/liHeight),
                scrollStart = Math.ceil(numberOfItemsDisplayed/4);

            selectables.removeClass('ms-hover');
            if (e.keyCode == 32){ // space
              var method = keyContainer == 'ms-selectable' ? 'select' : 'deselect';
              ms.multiSelect(method, selectableFocused.first().attr('ms-value'));

            } else if (e.keyCode == 40){ // Down
              var nextIndex = (selectableFocusedIndex+1 != selectablesLength) ? selectableFocusedIndex+1 : 0,
                  nextSelectableLi = selectables.eq(nextIndex);

              nextSelectableLi.addClass('ms-hover');
              if (nextIndex > scrollStart){
                scroll += liHeight;
              } else if (nextIndex == 0){
                scroll = 0;
              }
              $('.'+keyContainer+' ul', container).scrollTop(scroll);
            } else if (e.keyCode == 38){ // Up
              var prevIndex = (selectableFocusedIndex-1 >= 0) ? selectableFocusedIndex-1 : selectablesLength-1,
                  prevSelectableLi = selectables.eq(prevIndex);
              selectables.removeClass('ms-hover');
              prevSelectableLi.addClass('ms-hover');
              if (selectablesLength-prevIndex+1 < scrollStart){
                scroll = liHeight*(selectablesLength-scrollStart);
              } else {
                scroll -= liHeight;
              }
              $('.'+keyContainer+' ul').scrollTop(scroll);
            } else if (e.keyCode == 37 || e.keyCode == 39){ // Right and Left
              if (selectableUl.hasClass('ms-focus')){
                selectableUl.focusout();
                selectedUl.focusin();
              } else {
                selectableUl.focusin();
                selectedUl.focusout();
              }
            }
          }

          ms.on('keydown', function(e){
            if (ms.is(':focus')){
              var keyContainer = selectableUl.hasClass('ms-focus') ? 'ms-selectable' : 'ms-selection';
              ms.onKeyDown(e, keyContainer);
            }
          });
        }
      });
    },
    'refresh' : function() {
      $("#ms-"+$(this).attr("id")).remove();
      $(this).multiSelect("init", $(this).data("settings"));
    },
    'select' : function(value, method){
      var ms = this,
          selectedOption = ms.find('option[value="'+value +'"]'),
          text = selectedOption.text(),
          klass = selectedOption.attr('class'),
          titleAttr = selectedOption.attr('title');

      var selectedLi = $('
'+text+'
'),
          selectableUl = $('#ms-'+ms.attr('id')+' .ms-selectable ul'),
          selectedUl = $('#ms-'+ms.attr('id')+' .ms-selection ul'),
          selectableLi = selectableUl.children('li[ms-value="'+value+'"]'),
          haveToSelect = null;

      if (method == 'init'){
        haveToSelect = !selectableLi.hasClass(ms.data('settings').disabledClass) && selectedOption.attr('selected');
      } else {
        haveToSelect = !selectableLi.hasClass(ms.data('settings').disabledClass);
      }
      if (haveToSelect && value && value != '' && selectedUl.children('li[ms-value="'+value+'"]').length == 0){
        ms.focus();
        var parentOptgroup = selectableLi.parent('.ms-optgroup');
        if (parentOptgroup.length > 0)
          if (parentOptgroup.children('.ms-elem-selectable:not(:hidden)').length == 1)
            parentOptgroup.children('.ms-optgroup-label').hide();
        selectableLi.addClass('ms-selected');
        selectableLi.hide();
        selectedOption.attr('selected', 'selected');
        if(titleAttr){
          selectedLi.attr('title', titleAttr)
        }
        if (selectableLi.hasClass(ms.data('settings').disabledClass)){
          selectedLi.addClass(ms.data('settings').disabledClass);
        } else {
          selectedLi.click(function(){
            ms.multiSelect('deselect', $(this).attr('ms-value'));
          });
        }

        var selectedUlLis = selectedUl.children('.ms-elem-selected');
        if (method != 'init' && ms.data('settings').keepOrder && selectedUlLis.length > 0) {

          var getIndexOf = function(value) {
            elems = selectableUl.children('.ms-elem-selectable');
            return(elems.index(elems.closest('[ms-value="'+value+'"]')));
          }

          var index = getIndexOf(selectedLi.attr('ms-value'));
          if (index == 0)
            selectedUl.prepend(selectedLi);
          else {
            for (i = index - 1; i >= 0; i--){
              if (selectedUlLis[i] && getIndexOf($(selectedUlLis[i]).attr('ms-value')) < index) {
                $(selectedUlLis[i]).after(selectedLi);
                break;
              } else if (i == 0) {
                $(selectedUlLis[i]).before(selectedLi);
              }
            }
          }
        } else {
          selectedUl.append(selectedLi);
        }
        selectedLi.on('mouseenter', function(){
          $('li', selectedUl).removeClass('ms-hover');
          $(this).addClass('ms-hover');
        }).on('mouseout', function(){
          $('li', selectedUl).removeClass('ms-hover');
        });
        if (ms.find("option[value='']")){
          ms.find("option[value='']").removeAttr('selected');
        }
        if (method == "select_all" && parentOptgroup.children('.ms-elem-selectable').length > 0){
          parentOptgroup.children('.ms-optgroup-label').hide();
        }
        if(method != 'init' || ms.data('settings').selectCallbackOnInit){
          ms.trigger('change');
          selectedUl.trigger('change');
          selectableUl.trigger('change');
          if (typeof ms.data('settings').afterSelect == 'function' &&
              (method != 'init' || ms.data('settings').selectCallbackOnInit)) {
            ms.data('settings').afterSelect.call(this, value, text);
          }
        }
      }
    },
    'deselect' : function(value){
      var ms = this,
          selectedUl = $('#ms-'+ms.attr('id')+' .ms-selection ul'),
          selectedOption = ms.find('option[value="'+value +'"]'),
          selectedLi = selectedUl.children('li[ms-value="'+value+'"]');

      if(selectedLi){
        selectedUl.focusin();
        var selectableUl = $('#ms-'+ms.attr('id')+' .ms-selectable ul'),
            selectedUl = $('#ms-'+ms.attr('id')+' .ms-selection ul'),
            selectableLi = selectableUl.children('li[ms-value="'+value+'"]'),
            selectedLi = selectedUl.children('li[ms-value="'+value+'"]');

        var parentOptgroup = selectableLi.parent('.ms-optgroup');
        if (parentOptgroup.length > 0){
          parentOptgroup.children('.ms-optgroup-label').addClass('ms-collapse').show();
          parentOptgroup.children('.ms-elem-selectable:not(.ms-selected)').show();
        }
        selectedOption.removeAttr('selected');
        selectableLi.show();
        selectableLi.removeClass('ms-selected');
        selectedLi.remove();
        selectedUl.trigger('change');
        selectableUl.trigger('change');
        ms.trigger('change');
        if (typeof ms.data('settings').afterDeselect == 'function') {
          ms.data('settings').afterDeselect.call(this, value, selectedLi.text());
        }
      }
    },
    'select_all' : function(visible){
      var ms = this,
          selectableUl = $('#ms-'+ms.attr('id')+' .ms-selectable ul');

      ms.find("option:not(option[value=''])").each(function(){
        var value = $(this).val();
        if (visible){
          var selectableLi = selectableUl.children('li[ms-value="'+value+'"]');
          if (selectableLi.filter(':visible').length > 0){
            ms.multiSelect('select', value, 'select_all');
          }
        } else {
          ms.multiSelect('select', value, 'select_all');
        }
      });
    },
    'deselect_all' : function(){
      var ms = this;
      ms.find("option:not(option[value=''])").each(function(){
        ms.multiSelect('deselect', $(this).val(), 'deselect_all');
      });
    }
  };

  $.fn.multiSelect = function(method){
    if ( msMethods[method] ) {
      return msMethods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'object' || ! method ) {
      return msMethods.init.apply( this, arguments );
    } else {
      if(console.log) console.log( 'Method ' +  method + ' does not exist on jquery.multiSelect' );
    }
    return false;
  };
})(jQuery);
<|endoftext|>


<commit_before>/**
 * telapi
 * Copyright (c) 2012 TelTech Systems Inc. <info@telapi.com>
 * MIT Licensed
 */

var util  =
    require('util'),
    telapi_schema = require("./schemas/telapi.json"),
    S = require('string'),
    querystring = require('querystring'),
    https = require('https');

module.exports = function(account_sid, auth_token) {

    /**
     * TelAPI Account Sid is unique to the account. AccountSid can be
     * found at: http://www.telapi.com/dashboard
     *
     * @param {String} account_sid: (Required)
     */
    this.account_sid = account_sid;

    /**
     * TelAPI Auth Token is unique to the account. AuthToken can be
     * found at: http://www.telapi.com/dashboard
     *
     * @param {String} auth_token: (Required)
     */
    this.auth_token  = auth_token;

    /**
     * TelAPI API host
     *
     * @param {String} host: (Required)
     */
    this.host        = 'api.telapi.com';

    /**
     * TelAPI API port (SSL)
     *
     * @param {Int} port: (Required)
     */
    this.port        = 443;

    /**
     * TelAPI base URI. This is used for internal purposes of building the URL
     *
     * @param {String} base_uri: (Required)
     */
    this.base_uri    = '/%s/Accounts/%s';

    /**
     * TelAPI API active version.
     *
     * @param {String} api_version: (Required)
     */
    this.api_version = '2011-07-01';

    /**
     * TelAPI contant type. Currently the Node.JS helper supports only JSON, so please do not
     * change the variable to anything else.
     *
     * @param {String} content_type: (Required)
     */
    this.content_type = 'json';

    /**
     * TelAPI INTERNAL: Built Request URL will be stored and used from here
     *
     * @param {String} request_url: (Required)
     */
    this.request_url  = '';

    /**
     * TelAPI INTERNAL: Request method. Can be GET, POST, DELETE
     *
     * @param {String} request_method: (Required)
     */
    this.request_method       = 'GET';

    /**
     * TelAPI INTERNAL: Prepared query string (GET)
     *
     * @param {String} request_querystring: (Required)
     */
    this.request_querystring  = {};

    /**
     * TelAPI INTERNAL: Prepared post data that will be sent with POST
     *
     * @param {String} request_postdata: (Required)
     */
    this.request_postdata     = {};

     /**
     * TelAPI INTERNAL: Used as a schema validation helper
     *
     * @param {Object} resource_details: (Required)
     */
    this.resource_details     = {};

    /** --------------------------------------------------------------------------- **/
    /** MASTER CRUD API METHODS. EVERTHING YOU NEED TO HAVE **/
    /** --------------------------------------------------------------------------- **/

    /**
     * Method used to fetch details about some particular resource instance or to list
     * resource items based on pagination rules
     *
     * @param {String}   resource:   (Required) Valid resources: see this.getAvailableResources()
     * @param {Object}   parameters: (Optional) Parameters {} used for pagination/filter purposes
     * @param {Function} success:    (Optional) Success callback
     * @param {Function} error:      (Optional) Error callback
     */
    this.get         = function(resource, parameters, success, error) {
        this._buildUrl(resource, parameters)
        this._run(success, error);
    };

    /**
     * Method used to create new instance of some resource
     *
     * @param {String}   resource:   (Required) Valid resources: see this.getAvailableResources()
     * @param {Object}   post_data:  (Required) Post data {} that you want to send with instance creation
     * @param {Function} success:    (Optional) Success callback
     * @param {Function} error:      (Optional) Error callback
     */
    this.create      = function(resource, post_data, success, error) {
        this.request_method = 'POST';
        this._buildUrl(resource, {});
        this._assertPostData(post_data);
        this._run(success, error);
    };

    /**
     * Method used to update existing instance of some resource
     *
     * @param {String}   resource:   (Required) Valid resources: see this.getAvailableResources()
     * @param {Object}   post_data:  (Required) Post data {} that you want to send with instance update
     * @param {Function} success:    (Optional) Success callback
     * @param {Function} error:      (Optional) Error callback
     */
    this.update      = function(resource, post_data, success, error) {
        this.request_method = 'POST';
        this._buildUrl(resource, {});
        this._assertPostData(post_data);
        this._run(success, error);
    };

    /**
     * Method used to delete existing instance of some resource
     *
     * @param {String}   resource:   (Required) Valid resource and sid: see this.getAvailableResources()
     * @param {Function} success:    (Optional) Success callback
     * @param {Function} error:      (Optional) Error callback
     */
    this.delete      = function(resource, success, error) {
        this._buildUrl(resource, {});
        this.request_method = 'DELETE';
        this._run(success, error);
    };

    /**
     * Method used to get details about resource
     *
     * @param {String}   resource:   (Required) Valid resources: see this.getAvailableResources()
     *
     * @returns {Object} or {null}
     */
    this.getResource = function(resource) {
        for(var component in telapi_schema.rest_api.components) {
            if(component == resource.toLowerCase()) {
                return telapi_schema.rest_api.components[component];
            }
        }
        return null;
    };

    /**
     * Method used to check whether resource exists or not
     *
     * @param {String}   resource:   (Required) Valid resource name
     *
     * @returns {Boolean}
     */
    this.resourceExists = function(resource) {
        for(var component in telapi_schema.rest_api.components) {
            if(component == resource.toLowerCase()) return true;
        }
        return false;
    };

    /**
     * Method used to retreive all available REST resources
     *
     * @returns {Object}
     */
    this.getAvailableResources = function() {
        var available_resources = [];
        for(var component in telapi_schema.rest_api.components) {
            available_resources.push(component);
        }
        return available_resources;
    };

    /** --------------------------------------------------------------------------- **/
    /** "PRIVATE" MEMBERS USED FOR INTERNAL PURPOSES ONLY **/
    /** --------------------------------------------------------------------------- **/

    /**
     * Method used to build TelAPI request URL
     *
     * @param {Mixed}    uri_parts:   (Required) Valid resource and (if) instance: see this.getAvailableResources()
     * @param {Object}   parameters:  (Optional) Parameters {} used for pagination/filter purposes
     */
    this._buildUrl   = function(uri_parts, parameters) {

        // Restart request url to nothing
        this.request_url = '';

        // Now let's build the base url which includes the account
        this.request_url = util.format(this.base_uri, this.api_version, this.account_sid);

        var resource_details = {};
        var resource         = '';
        var resource_extras  = '';

        // In case that user wants to fetch account or accounts resource we are breaking url built
        // because we already have it built
        if( typeof uri_parts == 'string' ) {
            resource = uri_parts;
        }
        else if (typeof uri_parts == 'object') {
            resource = uri_parts[0];
            delete uri_parts[0];
            for(var index in uri_parts) resource_extras += "/" + uri_parts[index];
        }

        if (resource != 'account' && resource != 'accounts') {

            if(!this.resourceExists(resource)) {
                throw new Error(
                    "Resource [" + resource + "] does not exist and therefore is not a valid "+
                    "resource. Available resources are: [" + this.getAvailableResources() + "]"
                );
            }

            resource_details = this.getResource(resource);
            this.request_url += "/" + resource_details.url + resource_extras;
        }

        this.resource_details = resource_details;

        this.request_url += '.' + this.content_type;
        this._assertGetParams(parameters);

        return this.request_url;

    }

    /**
     * Internal method used to assert Request URI GET parameters
     *
     * @param {Object}   parameters: (Optional) Parameters {} used for pagination/filter purposes
     */
    this._assertGetParams = function(parameters) {
        this.request_querystring = querystring.stringify(parameters);
        if( this.request_querystring.length > 1) this.request_url += "?" + this.request_querystring;
    };

    /**
     * Internal method used to assert request POST data
     *
     * @param {Object}   post_data: (Required) Data that will be sent with request
     */
    this._assertPostData = function(post_data) {
        this._validatePostData(post_data);
        this.request_postdata = querystring.stringify(post_data);
    };

    /**
     * Internal method used to validate given POST data for specific resource. If
     * one of the required resource parameters are missing, error will be thrown
     *
     * @param {Object}   post_data: (Required) Data that will be sent with request
     */
    this._validatePostData = function(post_data) {
        for(var param in this.resource_details.create_params) {
            if(!(this.resource_details.create_params[param] in post_data)) {
                throw new Error(
                    "Invalid POST data. Missing required attribute '" +
                    this.resource_details.create_params[param] +
                    "'. "
                );
            }
        }
    };

    /**
     * Internal method used to send requests to TelAPI
     *
     * @param {Function} success:    (Optional) Success callback
     * @param {Function} error:      (Optional) Error callback
     */
    this._run        = function(succ, err) {

        var request_options = {
            host   : this.host,
            port   : this.port,
            path   : this.request_url,
            method : this.request_method,
        };

        request_options.headers = {}
        request_options.headers.Host = this.host;
        request_options.headers.Authorization = "Basic " + (new Buffer(this.account_sid + ':' + this.auth_token)).toString('base64');

        if(this.request_method == 'POST') {
            request_options.headers['Content-Length'] = this.request_postdata.length;
            request_options.headers['Content-Type']   = 'application/x-www-form-urlencoded';
        }

        if ( this.request_method == 'DELETE' ) request_options.headers['Content-Length'] = 0;

        var request = https.request(request_options, function(response) {
            var responseChunks = [];
            response.setEncoding('utf8');

            response.on('data', function(chunk) { responseChunks.push(chunk); });

            response.on('end', function() {

                try {
                    body = JSON.parse(responseChunks.join(''));
                }
                catch(err) {
                    throw new Error(
                        "Error occured: 'Cannot parse JSON'. TelAPI returned: " + responseChunks.join('')
                    );
                }

                if ( response.statusCode != 200 ) {
                    if(body.status != 200) {
                        throw new Error("TelAPI Error: '" + body.message + "'. More info URL: '" + body.more_info + "'");
                    }
                }

                if(typeof succ == 'function') succ(body);

            });

            if(typeof err == 'function') response.on('error', err);

        });

        if(this.request_method == 'POST') request.write(this.request_postdata);
        request.end();

    }

    /** --------------------------------------------------------------------------- **/
    /** CHECK SOME INTERNAL STUFF ONCE CLASS IS BEING INITIALIZED **/
    /** --------------------------------------------------------------------------- **/

    if(this.account_sid.length != 34 || !S(this.account_sid).startsWith('AC')) throw new Error("AccountSid is not valid!");
    if(this.auth_token.length != 32) throw new Error("AuthToken is not valid!");

};<commit_msg>Add ability to pass a failed sms back to err function<commit_after>/**
 * telapi
 * Copyright (c) 2012 TelTech Systems Inc. <info@telapi.com>
 * MIT Licensed
 */

var util  =
    require('util'),
    telapi_schema = require("./schemas/telapi.json"),
    S = require('string'),
    querystring = require('querystring'),
    https = require('https');

module.exports = function(account_sid, auth_token) {

    /**
     * TelAPI Account Sid is unique to the account. AccountSid can be
     * found at: http://www.telapi.com/dashboard
     *
     * @param {String} account_sid: (Required)
     */
    this.account_sid = account_sid;

    /**
     * TelAPI Auth Token is unique to the account. AuthToken can be
     * found at: http://www.telapi.com/dashboard
     *
     * @param {String} auth_token: (Required)
     */
    this.auth_token  = auth_token;

    /**
     * TelAPI API host
     *
     * @param {String} host: (Required)
     */
    this.host        = 'api.telapi.com';

    /**
     * TelAPI API port (SSL)
     *
     * @param {Int} port: (Required)
     */
    this.port        = 443;

    /**
     * TelAPI base URI. This is used for internal purposes of building the URL
     *
     * @param {String} base_uri: (Required)
     */
    this.base_uri    = '/%s/Accounts/%s';

    /**
     * TelAPI API active version.
     *
     * @param {String} api_version: (Required)
     */
    this.api_version = '2011-07-01';

    /**
     * TelAPI contant type. Currently the Node.JS helper supports only JSON, so please do not
     * change the variable to anything else.
     *
     * @param {String} content_type: (Required)
     */
    this.content_type = 'json';

    /**
     * TelAPI INTERNAL: Built Request URL will be stored and used from here
     *
     * @param {String} request_url: (Required)
     */
    this.request_url  = '';

    /**
     * TelAPI INTERNAL: Request method. Can be GET, POST, DELETE
     *
     * @param {String} request_method: (Required)
     */
    this.request_method       = 'GET';

    /**
     * TelAPI INTERNAL: Prepared query string (GET)
     *
     * @param {String} request_querystring: (Required)
     */
    this.request_querystring  = {};

    /**
     * TelAPI INTERNAL: Prepared post data that will be sent with POST
     *
     * @param {String} request_postdata: (Required)
     */
    this.request_postdata     = {};

     /**
     * TelAPI INTERNAL: Used as a schema validation helper
     *
     * @param {Object} resource_details: (Required)
     */
    this.resource_details     = {};

    /** --------------------------------------------------------------------------- **/
    /** MASTER CRUD API METHODS. EVERTHING YOU NEED TO HAVE **/
    /** --------------------------------------------------------------------------- **/

    /**
     * Method used to fetch details about some particular resource instance or to list
     * resource items based on pagination rules
     *
     * @param {String}   resource:   (Required) Valid resources: see this.getAvailableResources()
     * @param {Object}   parameters: (Optional) Parameters {} used for pagination/filter purposes
     * @param {Function} success:    (Optional) Success callback
     * @param {Function} error:      (Optional) Error callback
     */
    this.get         = function(resource, parameters, success, error) {
        this._buildUrl(resource, parameters)
        this._run(success, error);
    };

    /**
     * Method used to create new instance of some resource
     *
     * @param {String}   resource:   (Required) Valid resources: see this.getAvailableResources()
     * @param {Object}   post_data:  (Required) Post data {} that you want to send with instance creation
     * @param {Function} success:    (Optional) Success callback
     * @param {Function} error:      (Optional) Error callback
     */
    this.create      = function(resource, post_data, success, error) {
        this.request_method = 'POST';
        this._buildUrl(resource, {});
        this._assertPostData(post_data);
        this._run(success, error);
    };

    /**
     * Method used to update existing instance of some resource
     *
     * @param {String}   resource:   (Required) Valid resources: see this.getAvailableResources()
     * @param {Object}   post_data:  (Required) Post data {} that you want to send with instance update
     * @param {Function} success:    (Optional) Success callback
     * @param {Function} error:      (Optional) Error callback
     */
    this.update      = function(resource, post_data, success, error) {
        this.request_method = 'POST';
        this._buildUrl(resource, {});
        this._assertPostData(post_data);
        this._run(success, error);
    };

    /**
     * Method used to delete existing instance of some resource
     *
     * @param {String}   resource:   (Required) Valid resource and sid: see this.getAvailableResources()
     * @param {Function} success:    (Optional) Success callback
     * @param {Function} error:      (Optional) Error callback
     */
    this.delete      = function(resource, success, error) {
        this._buildUrl(resource, {});
        this.request_method = 'DELETE';
        this._run(success, error);
    };

    /**
     * Method used to get details about resource
     *
     * @param {String}   resource:   (Required) Valid resources: see this.getAvailableResources()
     *
     * @returns {Object} or {null}
     */
    this.getResource = function(resource) {
        for(var component in telapi_schema.rest_api.components) {
            if(component == resource.toLowerCase()) {
                return telapi_schema.rest_api.components[component];
            }
        }
        return null;
    };

    /**
     * Method used to check whether resource exists or not
     *
     * @param {String}   resource:   (Required) Valid resource name
     *
     * @returns {Boolean}
     */
    this.resourceExists = function(resource) {
        for(var component in telapi_schema.rest_api.components) {
            if(component == resource.toLowerCase()) return true;
        }
        return false;
    };

    /**
     * Method used to retreive all available REST resources
     *
     * @returns {Object}
     */
    this.getAvailableResources = function() {
        var available_resources = [];
        for(var component in telapi_schema.rest_api.components) {
            available_resources.push(component);
        }
        return available_resources;
    };

    /** --------------------------------------------------------------------------- **/
    /** "PRIVATE" MEMBERS USED FOR INTERNAL PURPOSES ONLY **/
    /** --------------------------------------------------------------------------- **/

    /**
     * Method used to build TelAPI request URL
     *
     * @param {Mixed}    uri_parts:   (Required) Valid resource and (if) instance: see this.getAvailableResources()
     * @param {Object}   parameters:  (Optional) Parameters {} used for pagination/filter purposes
     */
    this._buildUrl   = function(uri_parts, parameters) {

        // Restart request url to nothing
        this.request_url = '';

        // Now let's build the base url which includes the account
        this.request_url = util.format(this.base_uri, this.api_version, this.account_sid);

        var resource_details = {};
        var resource         = '';
        var resource_extras  = '';

        // In case that user wants to fetch account or accounts resource we are breaking url built
        // because we already have it built
        if( typeof uri_parts == 'string' ) {
            resource = uri_parts;
        }
        else if (typeof uri_parts == 'object') {
            resource = uri_parts[0];
            delete uri_parts[0];
            for(var index in uri_parts) resource_extras += "/" + uri_parts[index];
        }

        if (resource != 'account' && resource != 'accounts') {

            if(!this.resourceExists(resource)) {
                throw new Error(
                    "Resource [" + resource + "] does not exist and therefore is not a valid "+
                    "resource. Available resources are: [" + this.getAvailableResources() + "]"
                );
            }

            resource_details = this.getResource(resource);
            this.request_url += "/" + resource_details.url + resource_extras;
        }

        this.resource_details = resource_details;

        this.request_url += '.' + this.content_type;
        this._assertGetParams(parameters);

        return this.request_url;

    }

    /**
     * Internal method used to assert Request URI GET parameters
     *
     * @param {Object}   parameters: (Optional) Parameters {} used for pagination/filter purposes
     */
    this._assertGetParams = function(parameters) {
        this.request_querystring = querystring.stringify(parameters);
        if( this.request_querystring.length > 1) this.request_url += "?" + this.request_querystring;
    };

    /**
     * Internal method used to assert request POST data
     *
     * @param {Object}   post_data: (Required) Data that will be sent with request
     */
    this._assertPostData = function(post_data) {
        this._validatePostData(post_data);
        this.request_postdata = querystring.stringify(post_data);
    };

    /**
     * Internal method used to validate given POST data for specific resource. If
     * one of the required resource parameters are missing, error will be thrown
     *
     * @param {Object}   post_data: (Required) Data that will be sent with request
     */
    this._validatePostData = function(post_data) {
        for(var param in this.resource_details.create_params) {
            if(!(this.resource_details.create_params[param] in post_data)) {
                throw new Error(
                    "Invalid POST data. Missing required attribute '" +
                    this.resource_details.create_params[param] +
                    "'. "
                );
            }
        }
    };

    /**
     * Internal method used to send requests to TelAPI
     *
     * @param {Function} success:    (Optional) Success callback
     * @param {Function} error:      (Optional) Error callback
     */
    this._run        = function(succ, err) {

        var request_options = {
            host   : this.host,
            port   : this.port,
            path   : this.request_url,
            method : this.request_method,
        };

        request_options.headers = {}
        request_options.headers.Host = this.host;
        request_options.headers.Authorization = "Basic " + (new Buffer(this.account_sid + ':' + this.auth_token)).toString('base64');

        if(this.request_method == 'POST') {
            request_options.headers['Content-Length'] = this.request_postdata.length;
            request_options.headers['Content-Type']   = 'application/x-www-form-urlencoded';
        }

        if ( this.request_method == 'DELETE' ) request_options.headers['Content-Length'] = 0;

        var request = https.request(request_options, function(response) {
            var responseChunks = [];
            response.setEncoding('utf8');

            response.on('data', function(chunk) { responseChunks.push(chunk); });

            response.on('end', function() {

                try {
                    body = JSON.parse(responseChunks.join(''));
                }
                catch(err) {
                    throw new Error(
                        "Error occured: 'Cannot parse JSON'. TelAPI returned: " + responseChunks.join('')
                    );
                }

                if ( response.statusCode != 200 ) {
                    if(body.status != 200) {
                        if(typeof err == 'function') err(body);
                        else throw new Error("TelAPI Error: '" + body.message + "'. More info URL: '" + body.more_info + "'");
                    }
                }

                if(typeof succ == 'function') succ(body);

            });

            if(typeof err == 'function') response.on('error', err);

        });

        if(this.request_method == 'POST') request.write(this.request_postdata);
        request.end();

    }

    /** --------------------------------------------------------------------------- **/
    /** CHECK SOME INTERNAL STUFF ONCE CLASS IS BEING INITIALIZED **/
    /** --------------------------------------------------------------------------- **/

    if(this.account_sid.length != 34 || !S(this.account_sid).startsWith('AC')) throw new Error("AccountSid is not valid!");
    if(this.auth_token.length != 32) throw new Error("AuthToken is not valid!");

};
<|endoftext|>


<commit_before>const Promise = require('bluebird')
const xmlBuilder = require('xmlbuilder')
const TurndownService = require('turndown')
const turndownService = new TurndownService()
const calculateScore = require('../calculateScore')
const calculateHintCost = require('../calculateHintCost')
const hmacSha1 = require('../hmac')
const options = require('../options')
const { readFileSync } = require('fs')
const path = require('path')
let rtbTemplate = {}

function createRtbExport (challenges, { insertHints, insertHintUrls, ctfKey, juiceShopUrl }) {

  function checkHints() {
    return (challenge.hint && insertHints !== options.noTextHints)
  }

  function checkHintsURL() {
    return (challenge.hintUrl && insertHintUrls !== options.noHintUrls)
  }

  function getDescription(category) {
    if (rtbTemplate["categories"][category] == undefined) {
      return category
    }
    return rtbTemplate["categories"][category].description
  }

  function getImage(category) {
    if (rtbTemplate["categories"][category] == undefined) {
      category = "Miscellaneous"
    }
    return rtbTemplate["categories"][category].image
  }

  function insertHint(challenge, flag) {
    if (checkHints || checkHintsURL) {
      const hints = flag.ele("hints")
      let count = 0
      if (checkHints) {
        const hint = hints.ele("hint")
        hint.ele("description", turndownService.turndown(challenge.hint))
        hint.ele("price", calculateHintCost(challenge, insertHints))
        count += 1
      }
      if (checkHintsURL) {
        const hint = hints.ele("hint")
        hint.ele("description", "[This page should help](" + challenge.hintUrl + ")")
        hint.ele("price", calculateHintCost(challenge, insertHintUrls))
        count += 1
      }
      hints.att({'count': count})
    }
  }

  function insertBoxes(challenges, boxes, category) {
    box = boxes.ele("box", {'gamelevel': "0"})
    box.ele("name", category)
    box.ele("category", category)
    box.ele("description", getDescription(category) +
      "\n\n**Target:** [OWASP Juice Shop](" + juiceShopUrl + ")")
    box.ele("avatar", getImage(category))
    flags = box.ele("flags")
    let i = 0
    for (const key in challenges) {
      if (Object.prototype.hasOwnProperty.call(challenges, key)) {
        const challenge = challenges[key]
        if (category === challenge.category) {
            i += 1
            insertFlag(challenge, flags, i)
        }
      }
    }
    flags.att({"count": i.toString()})
  }

  function difficultyText(value) {
    let difficulty = ""
    for (let i = 0; i < value; i++) {
      difficulty += "⭐"
    }
    return difficulty
  }

  function insertFlag(challenge, flags, order) {
    flag = flags.ele("flag", {"type": "static"})
    flag.ele("name", challenge.name)
    flag.ele("description", turndownService.turndown(challenge.description) +
      " **Difficulty** " + difficultyText(challenge.difficulty))
    flag.ele("token", hmacSha1(ctfKey, challenge.name))
    flag.ele("value", calculateScore(challenge.difficulty))
    flag.ele("order", order.toString())
    insertHint(challenge, flag)
  }

  function insertCategories(challenges, xmlRTB) {
    const categories = new Set()
    for (const key in challenges) {
      categories.add(challenges[key].category)
    }
    if (categories.size > 0) {
      categories_xml = xmlRTB.ele('categories', {'count': categories.size})
      for (let category of categories) {
        categories_xml.ele("category").ele("category", category)
      }
      categories_xml.up()
    }
    return categories
  }

  function insertCorporation(categories, xmlRTB) {
    let corporation = xmlRTB.ele('corporations', { 'count': '1'}).ele('corporation')
    corporation.ele("name")
    let boxes = corporation.ele('boxes', {'count': categories.size.toString()})
    return boxes
  }

  function insertConfiguration(xmlRTB) {
    const config = xmlRTB.ele('configuration')
    config.ele('game_name', rtbTemplate['configuration'].game_name)
    config.ele('org_footer', rtbTemplate['configuration'].org_footer)
    config.ele('ctf_tagline', rtbTemplate['configuration'].ctf_tagline)
    config.ele('ctf_logo', rtbTemplate['configuration'].ctf_logo)
    config.ele('scoreboard_right_image', rtbTemplate['configuration'].scoreboard_right_image)
  }

  function sortByDifficulty(dict){
    const sorted = []
    let maxdifficulty = 5
    for (let i = 0; i <= maxdifficulty; i++) {
        for (const key in challenges) {
            if (Object.prototype.hasOwnProperty.call(challenges, key)) {
                let challenge = challenges[key]
                if (challenge.difficulty === i) {
                    sorted[sorted.length] = challenge
                }
                maxdifficulty = Math.max(challenge.difficulty, maxdifficulty)
            }
        }
    }
    return sorted
  }

  function loadTemplate () {
    let template = readFileSync(path.join(__dirname, '../../data/rtbImportTemplate.json'));
    return JSON.parse(template);
  }

  return new Promise((resolve, reject) => {
    try {
      rtbTemplate = loadTemplate()
      const xmlRTB = xmlBuilder.create("rootthebox", {version: '1.0', encoding: 'UTF-8'}).att('api', '1')
      insertConfiguration(xmlRTB)
      xmlRTB.ele('gamelevels', { 'count': '1'}).up()
      const categories = insertCategories(challenges, xmlRTB)
      const boxes = insertCorporation(categories, xmlRTB)
      const sorted_challenges = sortByDifficulty(challenges)
      for (let category of categories) {
        insertBoxes(sorted_challenges, boxes, category)
      }
      resolve(xmlRTB.end({ pretty: true }))
    } catch (error) {
      reject(new Error('Failed to generate challenge data! ' + error.message))
    }
  })
}

module.exports = createRtbExport
<commit_msg>Hint URL description<commit_after>const Promise = require('bluebird')
const xmlBuilder = require('xmlbuilder')
const TurndownService = require('turndown')
const turndownService = new TurndownService()
const calculateScore = require('../calculateScore')
const calculateHintCost = require('../calculateHintCost')
const hmacSha1 = require('../hmac')
const options = require('../options')
const { readFileSync } = require('fs')
const path = require('path')
let rtbTemplate = {}

function createRtbExport (challenges, { insertHints, insertHintUrls, ctfKey, juiceShopUrl }) {

  function checkHints() {
    return (challenge.hint && insertHints !== options.noTextHints)
  }

  function checkHintsURL() {
    return (challenge.hintUrl && insertHintUrls !== options.noHintUrls)
  }

  function getDescription(category) {
    if (rtbTemplate["categories"][category] == undefined) {
      return category
    }
    return rtbTemplate["categories"][category].description
  }

  function getImage(category) {
    if (rtbTemplate["categories"][category] == undefined) {
      category = "Miscellaneous"
    }
    return rtbTemplate["categories"][category].image
  }

  function insertHint(challenge, flag) {
    if (checkHints || checkHintsURL) {
      const hints = flag.ele("hints")
      let count = 0
      if (checkHints) {
        const hint = hints.ele("hint")
        hint.ele("description", turndownService.turndown(challenge.hint))
        hint.ele("price", calculateHintCost(challenge, insertHints))
        count += 1
      }
      if (checkHintsURL) {
        const hint = hints.ele("hint")
        hint.ele("description", "[" + formatHintURL(challenge.hintUrl) +
          "](" + challenge.hintUrl + ")")
        hint.ele("price", calculateHintCost(challenge, insertHintUrls))
        count += 1
      }
      hints.att({'count': count})
    }
  }

  function formatHintURL(url) {
    url = url.substring(url.lastIndexOf("#")+1, url.length).replace(/-/g, " ")
    url = url.replace(/\b[a-z]|['_][a-z]|\B[A-Z]/g, function(x){
      return x[0]==="'"||x[0]==="_"?x:String.fromCharCode(x.charCodeAt(0)^32)
    })
    return url
  }

  function insertBoxes(challenges, boxes, category) {
    box = boxes.ele("box", {'gamelevel': "0"})
    box.ele("name", category)
    box.ele("category", category)
    box.ele("description", getDescription(category) +
      "\n\n**Target:** [OWASP Juice Shop](" + juiceShopUrl + ")")
    box.ele("avatar", getImage(category))
    flags = box.ele("flags")
    let i = 0
    for (const key in challenges) {
      if (Object.prototype.hasOwnProperty.call(challenges, key)) {
        const challenge = challenges[key]
        if (category === challenge.category) {
            i += 1
            insertFlag(challenge, flags, i)
        }
      }
    }
    flags.att({"count": i.toString()})
  }

  function difficultyText(value) {
    let difficulty = ""
    for (let i = 0; i < value; i++) {
      difficulty += "⭐"
    }
    return difficulty
  }

  function insertFlag(challenge, flags, order) {
    flag = flags.ele("flag", {"type": "static"})
    flag.ele("name", challenge.name)
    flag.ele("description", turndownService.turndown(challenge.description) +
      " **Difficulty** " + difficultyText(challenge.difficulty))
    flag.ele("token", hmacSha1(ctfKey, challenge.name))
    flag.ele("value", calculateScore(challenge.difficulty))
    flag.ele("order", order.toString())
    insertHint(challenge, flag)
  }

  function insertCategories(challenges, xmlRTB) {
    const categories = new Set()
    for (const key in challenges) {
      categories.add(challenges[key].category)
    }
    if (categories.size > 0) {
      categories_xml = xmlRTB.ele('categories', {'count': categories.size})
      for (let category of categories) {
        categories_xml.ele("category").ele("category", category)
      }
      categories_xml.up()
    }
    return categories
  }

  function insertCorporation(categories, xmlRTB) {
    let corporation = xmlRTB.ele('corporations', { 'count': '1'}).ele('corporation')
    corporation.ele("name")
    let boxes = corporation.ele('boxes', {'count': categories.size.toString()})
    return boxes
  }

  function insertConfiguration(xmlRTB) {
    const config = xmlRTB.ele('configuration')
    config.ele('game_name', rtbTemplate['configuration'].game_name)
    config.ele('org_footer', rtbTemplate['configuration'].org_footer)
    config.ele('ctf_tagline', rtbTemplate['configuration'].ctf_tagline)
    config.ele('ctf_logo', rtbTemplate['configuration'].ctf_logo)
    config.ele('scoreboard_right_image', rtbTemplate['configuration'].scoreboard_right_image)
  }

  function sortByDifficulty(dict){
    const sorted = []
    let maxdifficulty = 5
    for (let i = 0; i <= maxdifficulty; i++) {
        for (const key in challenges) {
            if (Object.prototype.hasOwnProperty.call(challenges, key)) {
                let challenge = challenges[key]
                if (challenge.difficulty === i) {
                    sorted[sorted.length] = challenge
                }
                maxdifficulty = Math.max(challenge.difficulty, maxdifficulty)
            }
        }
    }
    return sorted
  }

  function loadTemplate () {
    let template = readFileSync(path.join(__dirname, '../../data/rtbImportTemplate.json'));
    return JSON.parse(template);
  }

  return new Promise((resolve, reject) => {
    try {
      rtbTemplate = loadTemplate()
      const xmlRTB = xmlBuilder.create("rootthebox", {version: '1.0', encoding: 'UTF-8'}).att('api', '1')
      insertConfiguration(xmlRTB)
      xmlRTB.ele('gamelevels', { 'count': '1'}).up()
      const categories = insertCategories(challenges, xmlRTB)
      const boxes = insertCorporation(categories, xmlRTB)
      const sorted_challenges = sortByDifficulty(challenges)
      for (let category of categories) {
        insertBoxes(sorted_challenges, boxes, category)
      }
      resolve(xmlRTB.end({ pretty: true }))
    } catch (error) {
      reject(new Error('Failed to generate challenge data! ' + error.message))
    }
  })
}

module.exports = createRtbExport
<|endoftext|>


<commit_before>function Bullet(resources, overrides) {
  Entity.call(this, resources, overrides);
  this.width = this.width || 32;
  this.height = this.height || 16;
  this.sprite = this.sprite || this.sprites.bullet;
  this.damage = this.damage || 1;
  this.speed = this.speed || 30;
  this.alignment = this.source ? this.source.alignment : 'none';

  // Calculate on creation and not per tick
  this.deltaX = -Math.cos(this.direction) * this.speed;
  this.deltaY = -Math.sin(this.direction) * this.speed;

  this.shadow = {
    on: false, // True set in tick: sometimes on to 'flicker'
    offset: { x: 0, y: 0 },
    color: 'rgba(255, 244, 91,' + Math.random() * 0.15 + ')',
    size: { x: 28, y: 48 },
    shape: 'circle',
    todScale: 0
  };

  this.additionalPierceChance = this.additionalPierceChance || 0; // Piercing modifier from upgrades
  this.lifespan = this.lifespan || Number.MAX_VALUE;
}

Bullet.prototype = new Entity();

Bullet.prototype.constructor = Bullet;

Bullet.prototype.tick = function () {
  this.x += this.deltaX;
  this.y += this.deltaY;

  var list = this.game.spatialHash.query(this.x, this.y);

  for (var i = 0; i < list.length; i++) {
    var ent = list[i];
    if (this.collidesWith(ent, Math.max(this.collisionRadius, this.speed * 0.75))) {
      var pierceChance = 1;

      if (this.alignment !== 'none') {
        if ((this.alignment === 'friendly' && ent.alignment === 'enemy') ||
            (this.alignment === 'enemy' && ent.alignment === 'friendly')) {
          pierceChance = ent.enemyPierceChance;
          ent.damage(this.damage, this);
          this.game.audio.play('pierce', 0.7);

          if (typeof this.onhit === 'function') {
            this.onhit();
          }
        } else if (this.alignment === ent.alignment) {
          pierceChance = ent.friendlyPierceChance;
          if (Math.random() > ent.friendlyPierceChance && typeof this.onhit === 'function') {
            this.onhit();
          }
        }
      }

      if (Math.random() - this.additionalPierceChance > pierceChance) {
        this.die();

        this.game.addEntity(
          new BulletPing(this.resources, {
            x: ent.x,
            y: ent.y,
            rotation: ent.rotation
          })
        );
      }
    }
  }

  if (this.age % 10 === 0) {
    this.checkOutOfBounds();
  }

  if (this.age > this.lifespan) {
    this.die();
  }
};

Bullet.prototype.checkOutOfBounds = function () {
  if (this.x < -100 || this.x > this.game.canvas.width + 100 ||
      this.y < -100 || this.y > this.game.canvas.height + 100) {
    this.markedForDeletion = true;
  }
};
<commit_msg>Fix pierce sound playing on every hit<commit_after>function Bullet(resources, overrides) {
  Entity.call(this, resources, overrides);
  this.width = this.width || 32;
  this.height = this.height || 16;
  this.sprite = this.sprite || this.sprites.bullet;
  this.damage = this.damage || 1;
  this.speed = this.speed || 30;
  this.alignment = this.source ? this.source.alignment : 'none';

  // Calculate on creation and not per tick
  this.deltaX = -Math.cos(this.direction) * this.speed;
  this.deltaY = -Math.sin(this.direction) * this.speed;

  this.shadow = {
    on: false, // True set in tick: sometimes on to 'flicker'
    offset: { x: 0, y: 0 },
    color: 'rgba(255, 244, 91,' + Math.random() * 0.15 + ')',
    size: { x: 28, y: 48 },
    shape: 'circle',
    todScale: 0
  };

  this.additionalPierceChance = this.additionalPierceChance || 0; // Piercing modifier from upgrades
  this.lifespan = this.lifespan || Number.MAX_VALUE;
}

Bullet.prototype = new Entity();

Bullet.prototype.constructor = Bullet;

Bullet.prototype.tick = function () {
  this.x += this.deltaX;
  this.y += this.deltaY;

  var list = this.game.spatialHash.query(this.x, this.y);

  for (var i = 0; i < list.length; i++) {
    var ent = list[i];
    if (this.collidesWith(ent, Math.max(this.collisionRadius, this.speed * 0.75))) {
      var pierceChance = 1;

      if (this.alignment !== 'none') {
        if ((this.alignment === 'friendly' && ent.alignment === 'enemy') ||
            (this.alignment === 'enemy' && ent.alignment === 'friendly')) {
          pierceChance = ent.enemyPierceChance;
          ent.damage(this.damage, this);

          if (typeof this.onhit === 'function') {
            this.onhit();
          }
        } else if (this.alignment === ent.alignment) {
          pierceChance = ent.friendlyPierceChance;
          if (Math.random() > ent.friendlyPierceChance && typeof this.onhit === 'function') {
            this.onhit();
          }
        }
      }

      if (Math.random() - this.additionalPierceChance > pierceChance) {
        this.die();

        this.game.addEntity(
          new BulletPing(this.resources, {
            x: ent.x,
            y: ent.y,
            rotation: ent.rotation
          })
        );
      } else if (ent.alignment === 'enemy') {
        this.game.audio.play('pierce', 1.0);
      }
    }
  }

  if (this.age % 10 === 0) {
    this.checkOutOfBounds();
  }

  if (this.age > this.lifespan) {
    this.die();
  }
};

Bullet.prototype.checkOutOfBounds = function () {
  if (this.x < -100 || this.x > this.game.canvas.width + 100 ||
      this.y < -100 || this.y > this.game.canvas.height + 100) {
    this.markedForDeletion = true;
  }
};
<|endoftext|>