<commit_before>'use strict';

const Utils = require('./../lib/Utils');
const MAX_UINT_16 = 0xFFFF;

/**
 * The Serializer is responsible for serializing the game world and its
 * objects on the server, before they are sent to each client.  On the client side the
 * Serializer deserializes these objects.
 *
 * The Serializer defines the data types which can be serialized.
 */
class Serializer {

    constructor() {
        this.registeredClasses = {};
        this.customTypes = {};
        this.netSchemeSizeCache = {}; // used to cache calculated netSchemes sizes
        this.registerClass(require('./TwoVector'));
        this.registerClass(require('./ThreeVector'));
        this.registerClass(require('./Quaternion'));
    }

    /**
     * Adds a custom primitive to the serializer instance.
     * This will enable you to use it in an object's netScheme
     * @param customType
     */
    // TODO: the function below is not used, and it is not clear what that
    // first argument is supposed to be
    addCustomType(customType) {
        this.customTypes[customType.type] = customType;
    }

    /**
     * Checks if type can be assigned by value.
     * @param {String} type Type to Checks
     * @return {Boolean} True if type can be assigned
     */
    static typeCanAssign(type) {
        return type !== Serializer.TYPES.CLASSINSTANCE && type !== Serializer.TYPES.LIST;
    }

    /**
     * Registers a new class with the serializer, so it may be deserialized later
     * @param {Function} classObj reference to the class (not an instance!)
     * @param {String} classId Unit specifying a class ID
     */
    registerClass(classObj, classId) {
        // if no classId is specified, hash one from the class name
        classId = classId ? classId : Utils.hashStr(classObj.name);
        if (this.registeredClasses[classId]) {
            console.error(`Serializer: accidental override of classId ${classId} when registering class`, classObj);
        }

        this.registeredClasses[classId] = classObj;
    }

    deserialize(dataBuffer, byteOffset) {
        byteOffset = byteOffset ? byteOffset : 0;
        let localByteOffset = 0;

        let dataView = new DataView(dataBuffer);

        let objectClassId = dataView.getUint8(byteOffset + localByteOffset);

        // todo if classId is 0 - take care of dynamic serialization.
        let objectClass = this.registeredClasses[objectClassId];
        if (objectClass == null) {
            console.error(`Serializer: unable to find class with objectClassId ${objectClassId}`);
        }

        localByteOffset += Uint8Array.BYTES_PER_ELEMENT; // advance the byteOffset after the classId

        let obj = new objectClass();
        for (let property of Object.keys(objectClass.netScheme).sort()) {
            let read = this.readDataView(dataView, byteOffset + localByteOffset, objectClass.netScheme[property]);
            obj[property] = read.data;
            localByteOffset += read.bufferSize;
        }

        return { obj, byteOffset: localByteOffset };
    }

    writeDataView(dataView, value, bufferOffset, netSchemProp) {
        if (netSchemProp.type == Serializer.TYPES.FLOAT32) {
            dataView.setFloat32(bufferOffset, value);
        } else if (netSchemProp.type == Serializer.TYPES.INT32) {
            dataView.setInt32(bufferOffset, value);
        } else if (netSchemProp.type == Serializer.TYPES.INT16) {
            dataView.setInt16(bufferOffset, value);
        } else if (netSchemProp.type == Serializer.TYPES.INT8) {
            dataView.setInt8(bufferOffset, value);
        } else if (netSchemProp.type == Serializer.TYPES.UINT8) {
            dataView.setUint8(bufferOffset, value);
        } else if (netSchemProp.type == Serializer.TYPES.STRING) {

            //   MAX_UINT_16 is a reserved (length) value which indicates string hasn't changed
            if (value === null) {
                dataView.setUint16(bufferOffset, MAX_UINT_16);
            } else {
                let strLen = value.length;
                dataView.setUint16(bufferOffset, strLen);
                let localBufferOffset = 2;
                for (let i = 0; i < strLen; i++)
                    dataView.setUint16(bufferOffset + localBufferOffset + i*2, value.charCodeAt(i));
            }
        } else if (netSchemProp.type == Serializer.TYPES.CLASSINSTANCE) {
            value.serialize(this, {
                dataBuffer: dataView.buffer,
                bufferOffset: bufferOffset
            });
        } else if (netSchemProp.type == Serializer.TYPES.LIST) {
            let localBufferOffset = 0;

            // a list is comprised of the number of items followed by the items
            dataView.setUint16(bufferOffset + localBufferOffset, value.length);
            localBufferOffset += Uint16Array.BYTES_PER_ELEMENT;

            for (let item of value) {
                // TODO: inelegant, currently doesn't support list of lists
                if (netSchemProp.itemType == Serializer.TYPES.CLASSINSTANCE) {
                    let serializedObj = item.serialize(this, {
                        dataBuffer: dataView.buffer,
                        bufferOffset: bufferOffset + localBufferOffset
                    });
                    localBufferOffset += serializedObj.bufferOffset;
                } else {
                    this.writeDataView(dataView, item, bufferOffset + localBufferOffset, { type: netSchemProp.itemType });
                    localBufferOffset += this.getTypeByteSize(netSchemProp.itemType);
                }
            }
        } else if (this.customTypes[netSchemProp.type]) {
            // this is a custom data property which needs to define its own write method
            this.customTypes[netSchemProp.type].writeDataView(dataView, value, bufferOffset);
        } else {
            console.error(`No custom property ${netSchemProp.type} found!`);
        }

    }

    readDataView(dataView, bufferOffset, netSchemProp) {
        let data, bufferSize;

        if (netSchemProp.type == Serializer.TYPES.FLOAT32) {
            data = dataView.getFloat32(bufferOffset);
            bufferSize = this.getTypeByteSize(netSchemProp.type);
        } else if (netSchemProp.type == Serializer.TYPES.INT32) {
            data = dataView.getInt32(bufferOffset);
            bufferSize = this.getTypeByteSize(netSchemProp.type);
        } else if (netSchemProp.type == Serializer.TYPES.INT16) {
            data = dataView.getInt16(bufferOffset);
            bufferSize = this.getTypeByteSize(netSchemProp.type);
        } else if (netSchemProp.type == Serializer.TYPES.INT8) {
            data = dataView.getInt8(bufferOffset);
            bufferSize = this.getTypeByteSize(netSchemProp.type);
        } else if (netSchemProp.type == Serializer.TYPES.UINT8) {
            data = dataView.getUint8(bufferOffset);
            bufferSize = this.getTypeByteSize(netSchemProp.type);
        } else if (netSchemProp.type == Serializer.TYPES.STRING) {
            let length = dataView.getUint16(bufferOffset);
            let localBufferOffset = Uint16Array.BYTES_PER_ELEMENT;
            bufferSize = localBufferOffset;
            if (length === MAX_UINT_16) {
                data = null;
            } else {
                data = String.fromCharCode.apply(null, dataView.buffer.slice(bufferOffset + localBufferOffset, length));
                bufferSize += length * Uint16Array.BYTES_PER_ELEMENT;
            }
        } else if (netSchemProp.type == Serializer.TYPES.CLASSINSTANCE) {
            var deserializeData = this.deserialize(dataView.buffer, bufferOffset);
            data = deserializeData.obj;
            bufferSize = deserializeData.byteOffset;
        } else if (netSchemProp.type == Serializer.TYPES.LIST) {
            let localBufferOffset = 0;

            let items = [];
            let itemCount = dataView.getUint16(bufferOffset + localBufferOffset);
            localBufferOffset += Uint16Array.BYTES_PER_ELEMENT;

            for (let x = 0; x < itemCount; x++) {
                let read = this.readDataView(dataView, bufferOffset + localBufferOffset, { type: netSchemProp.itemType });
                items.push(read.data);
                localBufferOffset += read.bufferSize;
            }

            data = items;
            bufferSize = localBufferOffset;
        }
        // this is a custom data property which needs to define its own read method
        else if (this.customTypes[netSchemProp.type] != null) {
            data = this.customTypes[netSchemProp.type].readDataView(dataView, bufferOffset);
        } else {
            console.error(`No custom property ${netSchemProp.type} found!`);
        }

        return { data: data, bufferSize: bufferSize };
    }

    getTypeByteSize(type) {

        switch (type) {
        case Serializer.TYPES.FLOAT32: {
            return Float32Array.BYTES_PER_ELEMENT;
        }
        case Serializer.TYPES.INT32: {
            return Int32Array.BYTES_PER_ELEMENT;
        }
        case Serializer.TYPES.INT16: {
            return Int16Array.BYTES_PER_ELEMENT;
        }
        case Serializer.TYPES.INT8: {
            return Int8Array.BYTES_PER_ELEMENT;
        }
        case Serializer.TYPES.UINT8: {
            return Uint8Array.BYTES_PER_ELEMENT;
        }

            // not one of the basic properties
        default: {
            if (this.customTypes[type] == null) {
                console.error(`netScheme property ${type} undefined! Did you forget to add it to the serializer?`);
                break;
            } else {
                return this.customTypes[type].BYTES_PER_ELEMENT;
            }
        }

        }

    }
}

/**
* The TYPES object defines the supported serialization types
* @constant
*/
Serializer.TYPES = {
    FLOAT32: 'FLOAT32',
    INT32: 'INT32',
    INT16: 'INT16',
    INT8: 'INT8',
    UINT8: 'UINT8',
    STRING: 'STRING',
    CLASSINSTANCE: 'CLASSINSTANCE',
    LIST: 'LIST'
};

module.exports = Serializer;
<commit_msg>parse string one character at a time<commit_after>'use strict';

const Utils = require('./../lib/Utils');
const MAX_UINT_16 = 0xFFFF;

/**
 * The Serializer is responsible for serializing the game world and its
 * objects on the server, before they are sent to each client.  On the client side the
 * Serializer deserializes these objects.
 *
 * The Serializer defines the data types which can be serialized.
 */
class Serializer {

    constructor() {
        this.registeredClasses = {};
        this.customTypes = {};
        this.netSchemeSizeCache = {}; // used to cache calculated netSchemes sizes
        this.registerClass(require('./TwoVector'));
        this.registerClass(require('./ThreeVector'));
        this.registerClass(require('./Quaternion'));
    }

    /**
     * Adds a custom primitive to the serializer instance.
     * This will enable you to use it in an object's netScheme
     * @param customType
     */
    // TODO: the function below is not used, and it is not clear what that
    // first argument is supposed to be
    addCustomType(customType) {
        this.customTypes[customType.type] = customType;
    }

    /**
     * Checks if type can be assigned by value.
     * @param {String} type Type to Checks
     * @return {Boolean} True if type can be assigned
     */
    static typeCanAssign(type) {
        return type !== Serializer.TYPES.CLASSINSTANCE && type !== Serializer.TYPES.LIST;
    }

    /**
     * Registers a new class with the serializer, so it may be deserialized later
     * @param {Function} classObj reference to the class (not an instance!)
     * @param {String} classId Unit specifying a class ID
     */
    registerClass(classObj, classId) {
        // if no classId is specified, hash one from the class name
        classId = classId ? classId : Utils.hashStr(classObj.name);
        if (this.registeredClasses[classId]) {
            console.error(`Serializer: accidental override of classId ${classId} when registering class`, classObj);
        }

        this.registeredClasses[classId] = classObj;
    }

    deserialize(dataBuffer, byteOffset) {
        byteOffset = byteOffset ? byteOffset : 0;
        let localByteOffset = 0;

        let dataView = new DataView(dataBuffer);

        let objectClassId = dataView.getUint8(byteOffset + localByteOffset);

        // todo if classId is 0 - take care of dynamic serialization.
        let objectClass = this.registeredClasses[objectClassId];
        if (objectClass == null) {
            console.error(`Serializer: unable to find class with objectClassId ${objectClassId}`);
        }

        localByteOffset += Uint8Array.BYTES_PER_ELEMENT; // advance the byteOffset after the classId

        let obj = new objectClass();
        for (let property of Object.keys(objectClass.netScheme).sort()) {
            let read = this.readDataView(dataView, byteOffset + localByteOffset, objectClass.netScheme[property]);
            obj[property] = read.data;
            localByteOffset += read.bufferSize;
        }

        return { obj, byteOffset: localByteOffset };
    }

    writeDataView(dataView, value, bufferOffset, netSchemProp) {
        if (netSchemProp.type == Serializer.TYPES.FLOAT32) {
            dataView.setFloat32(bufferOffset, value);
        } else if (netSchemProp.type == Serializer.TYPES.INT32) {
            dataView.setInt32(bufferOffset, value);
        } else if (netSchemProp.type == Serializer.TYPES.INT16) {
            dataView.setInt16(bufferOffset, value);
        } else if (netSchemProp.type == Serializer.TYPES.INT8) {
            dataView.setInt8(bufferOffset, value);
        } else if (netSchemProp.type == Serializer.TYPES.UINT8) {
            dataView.setUint8(bufferOffset, value);
        } else if (netSchemProp.type == Serializer.TYPES.STRING) {

            //   MAX_UINT_16 is a reserved (length) value which indicates string hasn't changed
            if (value === null) {
                dataView.setUint16(bufferOffset, MAX_UINT_16);
            } else {
                let strLen = value.length;
                dataView.setUint16(bufferOffset, strLen);
                let localBufferOffset = 2;
                for (let i = 0; i < strLen; i++)
                    dataView.setUint16(bufferOffset + localBufferOffset + i*2, value.charCodeAt(i));
            }
        } else if (netSchemProp.type == Serializer.TYPES.CLASSINSTANCE) {
            value.serialize(this, {
                dataBuffer: dataView.buffer,
                bufferOffset: bufferOffset
            });
        } else if (netSchemProp.type == Serializer.TYPES.LIST) {
            let localBufferOffset = 0;

            // a list is comprised of the number of items followed by the items
            dataView.setUint16(bufferOffset + localBufferOffset, value.length);
            localBufferOffset += Uint16Array.BYTES_PER_ELEMENT;

            for (let item of value) {
                // TODO: inelegant, currently doesn't support list of lists
                if (netSchemProp.itemType == Serializer.TYPES.CLASSINSTANCE) {
                    let serializedObj = item.serialize(this, {
                        dataBuffer: dataView.buffer,
                        bufferOffset: bufferOffset + localBufferOffset
                    });
                    localBufferOffset += serializedObj.bufferOffset;
                } else {
                    this.writeDataView(dataView, item, bufferOffset + localBufferOffset, { type: netSchemProp.itemType });
                    localBufferOffset += this.getTypeByteSize(netSchemProp.itemType);
                }
            }
        } else if (this.customTypes[netSchemProp.type]) {
            // this is a custom data property which needs to define its own write method
            this.customTypes[netSchemProp.type].writeDataView(dataView, value, bufferOffset);
        } else {
            console.error(`No custom property ${netSchemProp.type} found!`);
        }

    }

    readDataView(dataView, bufferOffset, netSchemProp) {
        let data, bufferSize;

        if (netSchemProp.type == Serializer.TYPES.FLOAT32) {
            data = dataView.getFloat32(bufferOffset);
            bufferSize = this.getTypeByteSize(netSchemProp.type);
        } else if (netSchemProp.type == Serializer.TYPES.INT32) {
            data = dataView.getInt32(bufferOffset);
            bufferSize = this.getTypeByteSize(netSchemProp.type);
        } else if (netSchemProp.type == Serializer.TYPES.INT16) {
            data = dataView.getInt16(bufferOffset);
            bufferSize = this.getTypeByteSize(netSchemProp.type);
        } else if (netSchemProp.type == Serializer.TYPES.INT8) {
            data = dataView.getInt8(bufferOffset);
            bufferSize = this.getTypeByteSize(netSchemProp.type);
        } else if (netSchemProp.type == Serializer.TYPES.UINT8) {
            data = dataView.getUint8(bufferOffset);
            bufferSize = this.getTypeByteSize(netSchemProp.type);
        } else if (netSchemProp.type == Serializer.TYPES.STRING) {
            let length = dataView.getUint16(bufferOffset);
            let localBufferOffset = Uint16Array.BYTES_PER_ELEMENT;
            bufferSize = localBufferOffset;
            if (length === MAX_UINT_16) {
                data = null;
            } else {
                let a = [];
                for (let i = 0; i < length; i++)
                    a[i] = dataView.getUint16(bufferOffset + localBufferOffset + i * 2);
                data = String.fromCharCode.apply(null, a);
                bufferSize += length * Uint16Array.BYTES_PER_ELEMENT;
            }
        } else if (netSchemProp.type == Serializer.TYPES.CLASSINSTANCE) {
            var deserializeData = this.deserialize(dataView.buffer, bufferOffset);
            data = deserializeData.obj;
            bufferSize = deserializeData.byteOffset;
        } else if (netSchemProp.type == Serializer.TYPES.LIST) {
            let localBufferOffset = 0;

            let items = [];
            let itemCount = dataView.getUint16(bufferOffset + localBufferOffset);
            localBufferOffset += Uint16Array.BYTES_PER_ELEMENT;

            for (let x = 0; x < itemCount; x++) {
                let read = this.readDataView(dataView, bufferOffset + localBufferOffset, { type: netSchemProp.itemType });
                items.push(read.data);
                localBufferOffset += read.bufferSize;
            }

            data = items;
            bufferSize = localBufferOffset;
        }
        // this is a custom data property which needs to define its own read method
        else if (this.customTypes[netSchemProp.type] != null) {
            data = this.customTypes[netSchemProp.type].readDataView(dataView, bufferOffset);
        } else {
            console.error(`No custom property ${netSchemProp.type} found!`);
        }

        return { data: data, bufferSize: bufferSize };
    }

    getTypeByteSize(type) {

        switch (type) {
        case Serializer.TYPES.FLOAT32: {
            return Float32Array.BYTES_PER_ELEMENT;
        }
        case Serializer.TYPES.INT32: {
            return Int32Array.BYTES_PER_ELEMENT;
        }
        case Serializer.TYPES.INT16: {
            return Int16Array.BYTES_PER_ELEMENT;
        }
        case Serializer.TYPES.INT8: {
            return Int8Array.BYTES_PER_ELEMENT;
        }
        case Serializer.TYPES.UINT8: {
            return Uint8Array.BYTES_PER_ELEMENT;
        }

            // not one of the basic properties
        default: {
            if (this.customTypes[type] == null) {
                console.error(`netScheme property ${type} undefined! Did you forget to add it to the serializer?`);
                break;
            } else {
                return this.customTypes[type].BYTES_PER_ELEMENT;
            }
        }

        }

    }
}

/**
* The TYPES object defines the supported serialization types
* @constant
*/
Serializer.TYPES = {
    FLOAT32: 'FLOAT32',
    INT32: 'INT32',
    INT16: 'INT16',
    INT8: 'INT8',
    UINT8: 'UINT8',
    STRING: 'STRING',
    CLASSINSTANCE: 'CLASSINSTANCE',
    LIST: 'LIST'
};

module.exports = Serializer;
<|endoftext|>


<commit_before>/**
 * Core dependencies.
 */

var basename = require('path').basename;

/**
 * Researcher.
 *
 * @param {Stats} stats
 * @constructor
 */

function Researcher(stats) {
  this.stats = stats;
}

/**
 * Analyze a node.
 *
 * @param {Object} node or token
 * @api public
 */

Researcher.prototype.node = function(node) {
  switch (node.type) {
    case 'AssignmentExpression':
    case 'ArrayExpression':
    case 'ConditionalExpression':
    case 'DoWhileStatement':
    case 'ForStatement':
    case 'ForInStatement':
    case 'FunctionDeclaration':
    case 'FunctionExpression':
    case 'IfStatement':
    case 'ObjectExpression':
    case 'SwitchStatement':
    case 'ReturnStatement':
    case 'ThrowStatement':
    case 'TryStatement':
    case 'VariableDeclaration':
    case 'VariableDeclarator':
    case 'WhileStatement':
    case 'Boolean':
    case 'Identifier':
    case 'Null':
    case 'Numeric':
    case 'RegularExpression':
    case 'String':
      return this.inc(node);

    case 'Line':
    case 'Block':
      return this.inc('Comment');

    case 'CallExpression':
      return this.fncall(node);
    case 'Punctuator':
      return this.punctuator(node);
  }
};

/**
 * Analyze a file.
 *
 * @param {String} filename
 * @param {String} file contents
 * @api public
 */

Researcher.prototype.file = function(filename, data) {
  this.inc('FilenameLength', filenameLen(filename));
  this.inc('Lines', linesLen(data));
};

/**
 * Increment a counter.
 *
 * @param {String|Object} type / node
 * @api private
 */

Researcher.prototype.inc = function(node) {
  this.stats.inc(Object(node) === node ? node.type : node);
};

/**
 * Handle semicolons.
 *
 * @param {Object} token
 * @api private
 */

Researcher.prototype.punctuator = function(token) {
  if (token.value !== ';') return;
  this.stats.inc('Semicolon');
};

/**
 * Handle function calls.
 *
 * @param {Object} node
 * @api private
 */

Researcher.prototype.fncall = function(node) {
  var callee = node.callee;

  if (callee.type === 'Identifier' && callee.name === 'require') {
    this.inc('Require');
  }

  this.inc(node.type);
};

/**
 * Split `str` into lines and return its size.
 *
 * @param {String} str
 * @returns {Number}
 * @api private
 */

function linesLen(str) {
  return str.split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/).length - 1;
}

/**
 * Return filename length.
 *
 * @param {String} path
 * @returns {Number}
 * @api private
 */

function filenameLen(file) {
  return basename(file).replace(/\.js$/, '').length;
}

/**
 * Primary export.
 */

module.exports = Researcher;
<commit_msg>No need to care about who increments the counters<commit_after>/**
 * Core dependencies.
 */

var basename = require('path').basename;

/**
 * Researcher.
 *
 * @param {Stats} stats
 * @constructor
 */

function Researcher(stats) {
  this.stats = stats;
}

/**
 * Analyze a node.
 *
 * @param {Object} node or token
 * @api public
 */

Researcher.prototype.node = function(node) {
  switch (node.type) {
    case 'AssignmentExpression':
    case 'ArrayExpression':
    case 'ConditionalExpression':
    case 'DoWhileStatement':
    case 'ForStatement':
    case 'ForInStatement':
    case 'FunctionDeclaration':
    case 'FunctionExpression':
    case 'IfStatement':
    case 'ObjectExpression':
    case 'SwitchStatement':
    case 'ReturnStatement':
    case 'ThrowStatement':
    case 'TryStatement':
    case 'VariableDeclaration':
    case 'VariableDeclarator':
    case 'WhileStatement':
    case 'Boolean':
    case 'Identifier':
    case 'Null':
    case 'Numeric':
    case 'RegularExpression':
    case 'String':
      return this.inc(node);

    case 'Line':
    case 'Block':
      return this.inc('Comment');

    case 'CallExpression':
      return this.fncall(node);
    case 'Punctuator':
      return this.punctuator(node);
  }
};

/**
 * Analyze a file.
 *
 * @param {String} filename
 * @param {String} file contents
 * @api public
 */

Researcher.prototype.file = function(filename, data) {
  this.inc('FilenameLength', filenameLen(filename));
  this.inc('Lines', linesLen(data));
};

/**
 * Increment a counter.
 *
 * @param {String|Object} type / node
 * @api private
 */

Researcher.prototype.inc = function(node) {
  this.stats.inc(Object(node) === node ? node.type : node);
};

/**
 * Handle semicolons.
 *
 * @param {Object} token
 * @api private
 */

Researcher.prototype.punctuator = function(token) {
  if (token.value !== ';') return;
  this.inc('Semicolon');
};

/**
 * Handle function calls.
 *
 * @param {Object} node
 * @api private
 */

Researcher.prototype.fncall = function(node) {
  var callee = node.callee;

  if (callee.type === 'Identifier' && callee.name === 'require') {
    this.inc('Require');
  }

  this.inc(node.type);
};

/**
 * Split `str` into lines and return its size.
 *
 * @param {String} str
 * @returns {Number}
 * @api private
 */

function linesLen(str) {
  return str.split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/).length - 1;
}

/**
 * Return filename length.
 *
 * @param {String} path
 * @returns {Number}
 * @api private
 */

function filenameLen(file) {
  return basename(file).replace(/\.js$/, '').length;
}

/**
 * Primary export.
 */

module.exports = Researcher;
<|endoftext|>


<commit_before>
// Tyler Close
// Ported by Kris Kowal
// Variation to illustrated ideas for improvements on the API.
// * Deferred, Rejection, Reference instead of defer, reject, ref, and promise.
// * Promise constructor that takes a descriptor and fallback.
// * near has been changed to valueOf, and uses a valueOf operator instead
//   an undefined operator, to reduce special cases.
// * variadic arguments are used internally where applicable (POST arguments
//   have not yet been altered.

/*
 * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found
 * at http://www.opensource.org/licenses/mit-license.html
 *
 * ref_send.js version: 2009-05-11
 */

/*whatsupdoc*/

// - the enclosure ensures that this module will function properly both as a
// CommonJS module and as a script in the browser.  In CommonJS, this module
// exports the "Q" API.  In the browser, this script creates a "Q" object in
// global scope.
// - the use of "undefined" on the enclosure is a micro-optmization for
// compression systems, permitting every occurrence of the "undefined" keyword
// bo be replaced with a single-character.
(function (exports, undefined) {
"use strict";

// this provides an enqueue method in browsers, Narwhal, and NodeJS
var enqueue;
try {
    enqueue = require("event-loop").enqueue;
} catch(e) {
    enqueue = function (task) {
        setTimeout(task, 0);
    };
}

var print;
if (typeof require !== "undefined") {
    try {
        print = require("system").print; // CommonJS
    } catch (exception) {
        print = require("sys").puts; // Node
    }
} else if (typeof console !== "undefined") {
    print = function (message) {
        console.log(message);
    };
} else {
    print = function () {}
}

/**
 * Performs a task in a future turn of the event loop.
 * @param {Function} task
 */
exports.enqueue = enqueue;

/**
 * Constructs a {promise, resolve} object.
 *
 * The resolver is a callback to invoke with a more resolved value for the
 * promise. To fulfill the promise, invoke the resolver with any value that is
 * not a function. To reject the promise, invoke the resolver with a rejection
 * object. To put the promise in the same state as another promise, invoke the
 * resolver with that other promise.
 */
exports.defer = defer;

function defer() {
    // if "pending" is an "Array", that indicates that the promise has not yet
    // been resolved.  If it is "undefined", it has been resolved.  Each
    // element of the pending array is itself an array of complete arguments to
    // forward to the resolved promise.  We coerce the resolution value to a
    // promise using the ref promise because it handles both fully
    // resolved values and other promises gracefully.
    var pending = [], value;

    var promise = Object.create(Promise.prototype);
    promise.emit = function () {
        var args = Array.prototype.slice.call(arguments);
        if (pending) {
            pending.push(args);
        } else {
            forward.apply(undefined, [value].concat(args));
        }
    };

    var resolve = function (resolvedValue) {
        var i, ii, task;
        if (!pending)
            return;
        value = ref(resolvedValue);
        for (i = 0, ii = pending.length; i < ii; ++i) {
            forward.apply(undefined, [value].concat(pending[i]));
        }
        pending = undefined;
    };

    return {
        "promise": promise,
        "resolve": resolve,
        "reject": function (reason) {
            resolve(reject(reason));
        }
    };
}

/**
 * Constructs a Promise with a promise descriptor object and optional fallback
 * function.  The descriptor contains methods like when(rejected), get(name),
 * put(name, value), post(name, args), delete(name), and valueOf(), which all
 * return either a value, a promise for a value, or a rejection.  The fallback
 * accepts the operation name, a resolver, and any further arguments that would
 * have been forwarded to the appropriate method above had a method been
 * provided with the proper name.  The API makes no guarantees about the nature
 * of the returned object, apart from that it is usable whereever promises are
 * bought and sold.
 */
exports.Promise = Promise;

function Promise(descriptor, fallback) {

    if (fallback === undefined) {
        fallback = function (op) {
            return reject("Promise does not support operation: " + op);
        };
    }

    var promise = Object.create(Promise.prototype);

    promise.emit = function (op, resolved /* ...args */) {
        var args = Array.prototype.slice.call(arguments, 2);
        var result;
        if (descriptor[op])
            result = descriptor[op].apply(descriptor, args);
        else
            result = fallback.apply(descriptor, arguments);
        if (resolved)
            return resolved(result);
        return result;
    };

    return promise;
};

Promise.prototype.toSource = function () {
    return this.toString();
};

Promise.prototype.toString = function () {
    return '[object Promise]';
};

Promise.prototype.valueOf = function () {
    return this.emit("valueOf");
};

/**
 * @returns whether the given object is a promise.
 * Otherwise it is a resolved value.
 */
exports.isPromise = isPromise;
function isPromise(object) {
    return object instanceof Promise;
};

/**
 * Constructs a rejected promise.
 * @param reason value describing the failure
 */
exports.reject = reject;

function reject(reason) {
    return Promise({
        "when": function (rejected) {
            return rejected ? rejected(reason) : reject(reason);
        }
    }, function fallback(op, resolved) {
        var rejection = reject(reason);
        return resolved ? resolved(rejection) : rejection;
    });
}

/**
 * Constructs a promise for an immediate reference.
 * @param value immediate reference
 */
exports.ref = ref;

function ref(object) {
    // If the object is already a Promise, return it directly.  This enables
    // the ref function to both be used to created references from
    // objects, but to tolerably coerce non-promises to refs if they are
    // not already Promises.
    if (isPromise(object))
        return object;
    return Promise({
        "when": function (rejected) {
            return object;
        },
        "get": function (name) {
            return object[name];
        },
        "put": function (name, value) {
            object[name] = value;
        },
        "delete": function (name) {
            delete object[name];
        },
        "post": function (name, args) {
            return object[name].apply(object, args);
        },
        "valueOf": function () {
            return object;
        }
    });
}

/**
 * Constructs a promise method that can be used to safely observe resolution of
 * a promise for an arbitrarily named method like "propfind" in a future turn.
 *
 * "Method" constructs methods like "get(promise, name)" and "put(promise)".
 */
exports.Method = Method;
function Method (methodName) {
    return function (object) {
        var deferred = defer();
        var args = Array.prototype.slice.call(arguments, 1);
        forward.apply(undefined, [
            ref(object),
            methodName,
            deferred.resolve
        ].concat(args));
        return deferred.promise;
    };
}

/**
 * Registers an observer on a promise.
 *
 * Guarantees:
 *
 * 1. that resolved and rejected will be called only once.
 * 2. that either the resolved callback or the rejected callback will be
 *    called, but not both.
 * 3. that resolved and rejected will not be called in this turn.
 *
 * @param value     promise or immediate reference to observe
 * @param resolve function to be called with the resolved value
 * @param rejected  function to be called with the rejection reason
 * @return promise for the return value from the invoked callback
 */
exports.when = function (value, resolved, rejected) {
    var deferred = defer();
    var done = false;   // ensure the untrusted promise makes at most a
                        // single call to one of the callbacks
    forward(ref(value), "when", function (value) {
        if (done)
            return;
        done = true;
        deferred.resolve(ref(value).emit("when", resolved, rejected));
    }, function (reason) {
        if (done)
            return;
        done = true;
        deferred.resolve(rejected ? rejected(reason) : reject(reason));
    });
    return deferred.promise;
};

/**
 */
exports.asap = function (value, resolved, rejected) {
    if (exports.isPromise(value)) {
        return exports.when(value, resolved, rejected);
    } else {
        return resolved(value);
    }
};

/**
 * Gets the value of a property in a future turn.
 * @param object    promise or immediate reference for target object
 * @param name      name of property to get
 * @return promise for the property value
 */
exports.get = Method("get");

/**
 * Sets the value of a property in a future turn.
 * @param object    promise or immediate reference for object object
 * @param name      name of property to set
 * @param value     new value of property
 * @return promise for the return value
 */
exports.put = Method("put");

/**
 * Deletes a property in a future turn.
 * @param object    promise or immediate reference for target object
 * @param name      name of property to delete
 * @return promise for the return value
 */
exports.del = Method("del");

/**
 * Invokes a method in a future turn.
 * @param object    promise or immediate reference for target object
 * @param name      name of method to invoke
 * @param argv      array of invocation arguments
 * @return promise for the return value
 */
exports.post = Method("post");

/**
 * Guarantees that the give promise resolves to a defined, non-null value.
 */
exports.defined = function (value) {
    return exports.when(value, function (value) {
        if (value === undefined || value === null)
            return reject("Resolved undefined value: " + value);
        return value;
    });
};

/**
 * Throws an error with the given reason.
 */
exports.error = function (reason) {
    if (!(reason instanceof Error))
        reason = new Error(reason);
    throw reason;
};

/*
 * Enqueues a promise operation for a future turn.
 */
function forward(promise /*, op, resolved, ... */) {
    var args = Array.prototype.slice.call(arguments, 1);
    enqueue(function () {
        try {
            promise.emit.apply(promise, args);
        } catch (exception) {
            print(exception.stack || exception);
        }
    });
}

// Complete the closure: use either CommonJS exports or browser global Q object
// for the exports internally.
})(
    typeof exports !== "undefined" ?
    exports :
    this["/q"] = {}
);

<commit_msg>Module name is actually 'event-queue' not a 'event-loop'.<commit_after>
// Tyler Close
// Ported by Kris Kowal
// Variation to illustrated ideas for improvements on the API.
// * Deferred, Rejection, Reference instead of defer, reject, ref, and promise.
// * Promise constructor that takes a descriptor and fallback.
// * near has been changed to valueOf, and uses a valueOf operator instead
//   an undefined operator, to reduce special cases.
// * variadic arguments are used internally where applicable (POST arguments
//   have not yet been altered.

/*
 * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found
 * at http://www.opensource.org/licenses/mit-license.html
 *
 * ref_send.js version: 2009-05-11
 */

/*whatsupdoc*/

// - the enclosure ensures that this module will function properly both as a
// CommonJS module and as a script in the browser.  In CommonJS, this module
// exports the "Q" API.  In the browser, this script creates a "Q" object in
// global scope.
// - the use of "undefined" on the enclosure is a micro-optmization for
// compression systems, permitting every occurrence of the "undefined" keyword
// bo be replaced with a single-character.
(function (exports, undefined) {
"use strict";

// this provides an enqueue method in browsers, Narwhal, and NodeJS
var enqueue;
try {
    enqueue = require("event-queue").enqueue;
} catch(e) {
    enqueue = function (task) {
        setTimeout(task, 0);
    };
}

var print;
if (typeof require !== "undefined") {
    try {
        print = require("system").print; // CommonJS
    } catch (exception) {
        print = require("sys").puts; // Node
    }
} else if (typeof console !== "undefined") {
    print = function (message) {
        console.log(message);
    };
} else {
    print = function () {}
}

/**
 * Performs a task in a future turn of the event loop.
 * @param {Function} task
 */
exports.enqueue = enqueue;

/**
 * Constructs a {promise, resolve} object.
 *
 * The resolver is a callback to invoke with a more resolved value for the
 * promise. To fulfill the promise, invoke the resolver with any value that is
 * not a function. To reject the promise, invoke the resolver with a rejection
 * object. To put the promise in the same state as another promise, invoke the
 * resolver with that other promise.
 */
exports.defer = defer;

function defer() {
    // if "pending" is an "Array", that indicates that the promise has not yet
    // been resolved.  If it is "undefined", it has been resolved.  Each
    // element of the pending array is itself an array of complete arguments to
    // forward to the resolved promise.  We coerce the resolution value to a
    // promise using the ref promise because it handles both fully
    // resolved values and other promises gracefully.
    var pending = [], value;

    var promise = Object.create(Promise.prototype);
    promise.emit = function () {
        var args = Array.prototype.slice.call(arguments);
        if (pending) {
            pending.push(args);
        } else {
            forward.apply(undefined, [value].concat(args));
        }
    };

    var resolve = function (resolvedValue) {
        var i, ii, task;
        if (!pending)
            return;
        value = ref(resolvedValue);
        for (i = 0, ii = pending.length; i < ii; ++i) {
            forward.apply(undefined, [value].concat(pending[i]));
        }
        pending = undefined;
    };

    return {
        "promise": promise,
        "resolve": resolve,
        "reject": function (reason) {
            resolve(reject(reason));
        }
    };
}

/**
 * Constructs a Promise with a promise descriptor object and optional fallback
 * function.  The descriptor contains methods like when(rejected), get(name),
 * put(name, value), post(name, args), delete(name), and valueOf(), which all
 * return either a value, a promise for a value, or a rejection.  The fallback
 * accepts the operation name, a resolver, and any further arguments that would
 * have been forwarded to the appropriate method above had a method been
 * provided with the proper name.  The API makes no guarantees about the nature
 * of the returned object, apart from that it is usable whereever promises are
 * bought and sold.
 */
exports.Promise = Promise;

function Promise(descriptor, fallback) {

    if (fallback === undefined) {
        fallback = function (op) {
            return reject("Promise does not support operation: " + op);
        };
    }

    var promise = Object.create(Promise.prototype);

    promise.emit = function (op, resolved /* ...args */) {
        var args = Array.prototype.slice.call(arguments, 2);
        var result;
        if (descriptor[op])
            result = descriptor[op].apply(descriptor, args);
        else
            result = fallback.apply(descriptor, arguments);
        if (resolved)
            return resolved(result);
        return result;
    };

    return promise;
};

Promise.prototype.toSource = function () {
    return this.toString();
};

Promise.prototype.toString = function () {
    return '[object Promise]';
};

Promise.prototype.valueOf = function () {
    return this.emit("valueOf");
};

/**
 * @returns whether the given object is a promise.
 * Otherwise it is a resolved value.
 */
exports.isPromise = isPromise;
function isPromise(object) {
    return object instanceof Promise;
};

/**
 * Constructs a rejected promise.
 * @param reason value describing the failure
 */
exports.reject = reject;

function reject(reason) {
    return Promise({
        "when": function (rejected) {
            return rejected ? rejected(reason) : reject(reason);
        }
    }, function fallback(op, resolved) {
        var rejection = reject(reason);
        return resolved ? resolved(rejection) : rejection;
    });
}

/**
 * Constructs a promise for an immediate reference.
 * @param value immediate reference
 */
exports.ref = ref;

function ref(object) {
    // If the object is already a Promise, return it directly.  This enables
    // the ref function to both be used to created references from
    // objects, but to tolerably coerce non-promises to refs if they are
    // not already Promises.
    if (isPromise(object))
        return object;
    return Promise({
        "when": function (rejected) {
            return object;
        },
        "get": function (name) {
            return object[name];
        },
        "put": function (name, value) {
            object[name] = value;
        },
        "delete": function (name) {
            delete object[name];
        },
        "post": function (name, args) {
            return object[name].apply(object, args);
        },
        "valueOf": function () {
            return object;
        }
    });
}

/**
 * Constructs a promise method that can be used to safely observe resolution of
 * a promise for an arbitrarily named method like "propfind" in a future turn.
 *
 * "Method" constructs methods like "get(promise, name)" and "put(promise)".
 */
exports.Method = Method;
function Method (methodName) {
    return function (object) {
        var deferred = defer();
        var args = Array.prototype.slice.call(arguments, 1);
        forward.apply(undefined, [
            ref(object),
            methodName,
            deferred.resolve
        ].concat(args));
        return deferred.promise;
    };
}

/**
 * Registers an observer on a promise.
 *
 * Guarantees:
 *
 * 1. that resolved and rejected will be called only once.
 * 2. that either the resolved callback or the rejected callback will be
 *    called, but not both.
 * 3. that resolved and rejected will not be called in this turn.
 *
 * @param value     promise or immediate reference to observe
 * @param resolve function to be called with the resolved value
 * @param rejected  function to be called with the rejection reason
 * @return promise for the return value from the invoked callback
 */
exports.when = function (value, resolved, rejected) {
    var deferred = defer();
    var done = false;   // ensure the untrusted promise makes at most a
                        // single call to one of the callbacks
    forward(ref(value), "when", function (value) {
        if (done)
            return;
        done = true;
        deferred.resolve(ref(value).emit("when", resolved, rejected));
    }, function (reason) {
        if (done)
            return;
        done = true;
        deferred.resolve(rejected ? rejected(reason) : reject(reason));
    });
    return deferred.promise;
};

/**
 */
exports.asap = function (value, resolved, rejected) {
    if (exports.isPromise(value)) {
        return exports.when(value, resolved, rejected);
    } else {
        return resolved(value);
    }
};

/**
 * Gets the value of a property in a future turn.
 * @param object    promise or immediate reference for target object
 * @param name      name of property to get
 * @return promise for the property value
 */
exports.get = Method("get");

/**
 * Sets the value of a property in a future turn.
 * @param object    promise or immediate reference for object object
 * @param name      name of property to set
 * @param value     new value of property
 * @return promise for the return value
 */
exports.put = Method("put");

/**
 * Deletes a property in a future turn.
 * @param object    promise or immediate reference for target object
 * @param name      name of property to delete
 * @return promise for the return value
 */
exports.del = Method("del");

/**
 * Invokes a method in a future turn.
 * @param object    promise or immediate reference for target object
 * @param name      name of method to invoke
 * @param argv      array of invocation arguments
 * @return promise for the return value
 */
exports.post = Method("post");

/**
 * Guarantees that the give promise resolves to a defined, non-null value.
 */
exports.defined = function (value) {
    return exports.when(value, function (value) {
        if (value === undefined || value === null)
            return reject("Resolved undefined value: " + value);
        return value;
    });
};

/**
 * Throws an error with the given reason.
 */
exports.error = function (reason) {
    if (!(reason instanceof Error))
        reason = new Error(reason);
    throw reason;
};

/*
 * Enqueues a promise operation for a future turn.
 */
function forward(promise /*, op, resolved, ... */) {
    var args = Array.prototype.slice.call(arguments, 1);
    enqueue(function () {
        try {
            promise.emit.apply(promise, args);
        } catch (exception) {
            print(exception.stack || exception);
        }
    });
}

// Complete the closure: use either CommonJS exports or browser global Q object
// for the exports internally.
})(
    typeof exports !== "undefined" ?
    exports :
    this["/q"] = {}
);

<|endoftext|>


<commit_before>/**
 * This is the file where commands get parsed
 *
 * Some parts of this code are taken from the Pokémon Showdown server code, so
 * credits also go to Guangcong Luo and other Pokémon Showdown contributors.
 * https://github.com/Zarel/Pokemon-Showdown
 *
 * @license MIT license
 */

var sys = require('sys');
var https = require('https');
var url = require('url');

exports.parse = {
	actionUrl: url.parse('https://play.pokemonshowdown.com/~~' + config.serverid + '/action.php'),
	room: 'lobby',

	data: function(data, connection) {
		if (data.substr(0, 1) === 'a') {
			this.room = 'lobby';

			data = JSON.parse(data.substr(1));
			if (data instanceof Array) {
				for (var i = 0; i < data.length; i++) {
					this.message(data[i], connection);
				}
			} else {
				this.message(data, connection);
			}
		}
	},
	message: function(message, connection) {
		if (!message) return;

		if (message.indexOf('\n') > -1) {
			var spl = message.split('\n');
			for (var i = 0; i < spl.length; i++) {
				this.message(spl[i], connection);
			}
		}

		var spl = message.split('|');
		if (!spl[1]) {
			spl = message.split('>');
			if (!spl[1])
				return;
			this.channel = spl[1];
		}

		switch (spl[1]) {
			case 'challstr':
				info('received challstr, logging in...');
				var id = spl[2];
				var str = spl[3];

				var requestOptions = {
					hostname: this.actionUrl.hostname,
					port: this.actionUrl.port,
					path: this.actionUrl.pathname,
					agent: false
				};

				if (!config.pass) {
					requestOptions.method = 'GET';
					requestOptions.path += '?act=getassertion&userid=' + toId(config.nick) + '&challengekeyid=' + id + '&challenge=' + str;
				} else {
					requestOptions.method = 'POST';
					var data = 'act=login&name=' + config.nick + '&pass=' + config.pass + '&challengekeyid=' + id + '&challenge=' + str;
					requestOptions.headers = {
						'Content-Type': 'application/x-www-form-urlencoded',
						'Content-Length': data.length
					};
				}

				var req = https.request(requestOptions, function(res) {
					res.setEncoding('utf8');
					var data = '';
					res.on('data', function(chunk) {
						data += chunk;
					});
					res.on('end', function() {
						if (data === ';') {
							error('failed to log in; nick is registered - invalid or no password given');
							process.exit(-1);
						}
						if (data.length < 50) {
							error('failed to log in: ' + data);
							process.exit(-1);
						}

						if (data.indexOf('heavy load') !== -1) {
							error('the login server is under heavy load; trying again in one minute');
							setTimeout(function() {
								this.message(message);
							}.bind(this), 60000);
							return;
						}

						try {
							data = JSON.parse(data.substr(1));
							if (data.actionsuccess) {
								data = data.assertion;
							} else {
								error('could not log in; action was not successful: ' + JSON.stringify(data));
								process.exit(-1);
							}
						} catch (e) {}
						send(connection, '|/trn ' + config.nick + ',0,' + data);
					}.bind(this));
				}.bind(this));

				req.on('error', function(err) {
					error('login error: ' + sys.inspect(err));
				});

				if (data) {
					req.write(data);
				}
				req.end();
				break;
			case 'updateuser':
				if (spl[2] !== config.nick) {
					return;
				}

				if (spl[3] !== '1') {
					error('failed to log in, still guest');
					process.exit(-1);
				}

				ok('logged in as ' + spl[2]);

				// Now join the rooms
				var cmds = [];
				for (var i in config.rooms) {
					var room = config.rooms[i].toLowerCase();
					if (room === 'lobby' && config.serverid === 'showdown') {
						continue;
					}
					cmds.push('|/join ' + room);
				}
				send(connection, cmds);
				break;
			case 'title':
				ok('joined ' + spl[2]);
				break;
			case 'c':
				var by = spl[2];
				spl.splice(0, 3);
				this.chatMessage(spl.join('|'), by, this.room, connection);
				break;
			case 'pm':
				var by = spl[2].substr(1);
				if (by.substr(1) === config.nick) return;
				spl.splice(0, 4);
				this.chatMessage(spl.join('|'), by, ',' + by, connection);
				break;
		}
	},
	chatMessage: function(message, by, room, connection) {
		message = message.trim();
		if (message.substr(0, config.commandcharacter.length) !== config.commandcharacter) {
			return;
		}

		message = message.substr(config.commandcharacter.length);
		var index = message.indexOf(' ');
		var arg = '';
		if (index > -1) {
			var cmd = message.substr(0, index);
			arg = message.substr(index + 1);
		} else {
			var cmd = message;
		}

		if (Commands[cmd]) {
			var failsafe = 0;
			while (typeof Commands[cmd] !== "function" && failsafe++ < 10) {
				cmd = Commands[cmd];
			}
			if (typeof Commands[cmd] === "function") {
				Commands[cmd].call(this, arg, by, room, connection);
			} else {
				error("invalid command type for " + cmd + ": " + (typeof Commands[cmd]));
			}
		}
	},
	say: function(connection, room, text) {
		if (room.substr(0, 1) !== ',') {
			var str = (room !== 'lobby' ? room : '') + '|' + text;
			send(connection, str);
		} else {
			room = room.substr(1);
			var str = '|/pm ' + room + ', ' + text;
			send(connection, str);
		}
	},
	hasRank: function(user, rank) {
		var ranks = rank.split('');
		for (var i = 0; i < ranks.length; i++) {
			if (ranks[i] === user.substr(0, 1)) return true;
		}

		return false;
	},
	uncacheTree: function(root) {
		var uncache = [require.resolve(root)];
		do {
			var newuncache = [];
			for (var i = 0; i < uncache.length; ++i) {
				if (require.cache[uncache[i]]) {
					newuncache.push.apply(newuncache,
						require.cache[uncache[i]].children.map(function(module) {
							return module.filename;
						})
					);
					delete require.cache[uncache[i]];
				}
			}
			uncache = newuncache;
		} while (uncache.length > 0);
	}
};<commit_msg>Fix rank check for PMs<commit_after>/**
 * This is the file where commands get parsed
 *
 * Some parts of this code are taken from the Pokémon Showdown server code, so
 * credits also go to Guangcong Luo and other Pokémon Showdown contributors.
 * https://github.com/Zarel/Pokemon-Showdown
 *
 * @license MIT license
 */

var sys = require('sys');
var https = require('https');
var url = require('url');

exports.parse = {
	actionUrl: url.parse('https://play.pokemonshowdown.com/~~' + config.serverid + '/action.php'),
	room: 'lobby',

	data: function(data, connection) {
		if (data.substr(0, 1) === 'a') {
			this.room = 'lobby';

			data = JSON.parse(data.substr(1));
			if (data instanceof Array) {
				for (var i = 0; i < data.length; i++) {
					this.message(data[i], connection);
				}
			} else {
				this.message(data, connection);
			}
		}
	},
	message: function(message, connection) {
		if (!message) return;

		if (message.indexOf('\n') > -1) {
			var spl = message.split('\n');
			for (var i = 0; i < spl.length; i++) {
				this.message(spl[i], connection);
			}
		}

		var spl = message.split('|');
		if (!spl[1]) {
			spl = message.split('>');
			if (!spl[1])
				return;
			this.channel = spl[1];
		}

		switch (spl[1]) {
			case 'challstr':
				info('received challstr, logging in...');
				var id = spl[2];
				var str = spl[3];

				var requestOptions = {
					hostname: this.actionUrl.hostname,
					port: this.actionUrl.port,
					path: this.actionUrl.pathname,
					agent: false
				};

				if (!config.pass) {
					requestOptions.method = 'GET';
					requestOptions.path += '?act=getassertion&userid=' + toId(config.nick) + '&challengekeyid=' + id + '&challenge=' + str;
				} else {
					requestOptions.method = 'POST';
					var data = 'act=login&name=' + config.nick + '&pass=' + config.pass + '&challengekeyid=' + id + '&challenge=' + str;
					requestOptions.headers = {
						'Content-Type': 'application/x-www-form-urlencoded',
						'Content-Length': data.length
					};
				}

				var req = https.request(requestOptions, function(res) {
					res.setEncoding('utf8');
					var data = '';
					res.on('data', function(chunk) {
						data += chunk;
					});
					res.on('end', function() {
						if (data === ';') {
							error('failed to log in; nick is registered - invalid or no password given');
							process.exit(-1);
						}
						if (data.length < 50) {
							error('failed to log in: ' + data);
							process.exit(-1);
						}

						if (data.indexOf('heavy load') !== -1) {
							error('the login server is under heavy load; trying again in one minute');
							setTimeout(function() {
								this.message(message);
							}.bind(this), 60000);
							return;
						}

						try {
							data = JSON.parse(data.substr(1));
							if (data.actionsuccess) {
								data = data.assertion;
							} else {
								error('could not log in; action was not successful: ' + JSON.stringify(data));
								process.exit(-1);
							}
						} catch (e) {}
						send(connection, '|/trn ' + config.nick + ',0,' + data);
					}.bind(this));
				}.bind(this));

				req.on('error', function(err) {
					error('login error: ' + sys.inspect(err));
				});

				if (data) {
					req.write(data);
				}
				req.end();
				break;
			case 'updateuser':
				if (spl[2] !== config.nick) {
					return;
				}

				if (spl[3] !== '1') {
					error('failed to log in, still guest');
					process.exit(-1);
				}

				ok('logged in as ' + spl[2]);

				// Now join the rooms
				var cmds = [];
				for (var i in config.rooms) {
					var room = config.rooms[i].toLowerCase();
					if (room === 'lobby' && config.serverid === 'showdown') {
						continue;
					}
					cmds.push('|/join ' + room);
				}
				send(connection, cmds);
				break;
			case 'title':
				ok('joined ' + spl[2]);
				break;
			case 'c':
				var by = spl[2];
				spl.splice(0, 3);
				this.chatMessage(spl.join('|'), by, this.room, connection);
				break;
			case 'pm':
				var by = spl[2];
				if (by.substr(1) === config.nick) return;
				spl.splice(0, 4);
				this.chatMessage(spl.join('|'), by, ',' + by, connection);
				break;
		}
	},
	chatMessage: function(message, by, room, connection) {
		message = message.trim();
		if (message.substr(0, config.commandcharacter.length) !== config.commandcharacter) {
			return;
		}

		message = message.substr(config.commandcharacter.length);
		var index = message.indexOf(' ');
		var arg = '';
		if (index > -1) {
			var cmd = message.substr(0, index);
			arg = message.substr(index + 1);
		} else {
			var cmd = message;
		}

		if (Commands[cmd]) {
			var failsafe = 0;
			while (typeof Commands[cmd] !== "function" && failsafe++ < 10) {
				cmd = Commands[cmd];
			}
			if (typeof Commands[cmd] === "function") {
				Commands[cmd].call(this, arg, by, room, connection);
			} else {
				error("invalid command type for " + cmd + ": " + (typeof Commands[cmd]));
			}
		}
	},
	say: function(connection, room, text) {
		if (room.substr(0, 1) !== ',') {
			var str = (room !== 'lobby' ? room : '') + '|' + text;
			send(connection, str);
		} else {
			room = room.substr(1);
			var str = '|/pm ' + room + ', ' + text;
			send(connection, str);
		}
	},
	hasRank: function(user, rank) {
		var ranks = rank.split('');
		for (var i = 0; i < ranks.length; i++) {
			if (ranks[i] === user.substr(0, 1)) return true;
		}

		return false;
	},
	uncacheTree: function(root) {
		var uncache = [require.resolve(root)];
		do {
			var newuncache = [];
			for (var i = 0; i < uncache.length; ++i) {
				if (require.cache[uncache[i]]) {
					newuncache.push.apply(newuncache,
						require.cache[uncache[i]].children.map(function(module) {
							return module.filename;
						})
					);
					delete require.cache[uncache[i]];
				}
			}
			uncache = newuncache;
		} while (uncache.length > 0);
	}
};
<|endoftext|>


<commit_before>"use strict";

var request  = require("request");
var cheerio  = require("cheerio");
var moment   = require("moment");
var mongoose = require("mongoose");
var async    = require("async");
var crypto   = require("crypto");

// just a static test url, later the client will send a week and a year
var url = "https://lsf.hft-stuttgart.de/qisserver/rds?state=wplan&k_abstgv.abstgvnr=262&week=12_2015&act=stg&pool=stg&show=plan&P.vx=lang&P.Print=";

var parse = function(html) {
  var table = null;
  var days = [];
  var timeTableGrid = [];
  var lectures = [];
  var $ = cheerio.load(html);

  // load days before we cut of td.plan_rahmen stuff
  days = getDaysInThisWeek($);

  // remove td.plan rahmen from the table, this removes a lot of trouble
  table = $("th.plan_rahmen").closest("table");
  table.find(".plan_rahmen").remove();

  // lets parse
  // get all the rows of the timetable (except the header (days) row)
  var rows = table.children("tr:not(:first-child)");

  // create and prefill the grid, which holds our whole timetable
  for (var i = 0; i < rows.length; i++) {     // rows
    timeTableGrid[i] = new Array(days.length);
    for (var j = 0; j < days.length; j++) {   // columns
      timeTableGrid[i][j] = ".";
    }
  }

  // iterate over each row and each cell
  rows.each(function(i) {
    var cells = $(this).children("td:not(:first-child)");

    // it seems like cheerio doesnt support collection.get(), so we just
    // bottle that cheerio collection into a native array.
    var cellsArray = [];
    cells.each(function(j) {
      cellsArray.push($(this));
    });

    for (var j = 0; j < days.length; j++) {
      if (timeTableGrid[i][j] === ".") { // here must be a td-(lecture)-element

        var currCell = cellsArray[0];
        cellsArray.shift(); // removes the first element, its a pseudo queue

        if (currCell.attr("class").indexOf("plan1") > -1) {
          // no lecture here
        } else if (currCell.attr("class").indexOf("plan2") > -1) { // here we have a lecture
          // now mark the whole rowspan down as the same lecture
          var rowspan = currCell.attr("rowspan");
          for (var k = 0; k < rowspan; k++) {
            timeTableGrid[i+k][j] = j;
          }

          // parse the lectures td element
          var moreLectures = parseGroupsInLecture(currCell, days, j);
          lectures = lectures.concat(moreLectures);
        }
      }
    }
  });
  return lectures;
};

var insertInDatabase = function(lectures) {
  // connect to mongodb if not already
  if(mongoose.connection.readyState === 0) {
    mongoose.connect("mongodb://localhost:27017/wuw");
  }

  // create models from our schemas
  var Lecture = require("./model_lecture");
  //var Deadline = require("./model_deadline");

  // drop current lecture collection to get a fresh result
  mongoose.connection.collections.lectures.drop(function(err) {
    if(err) { console.log(err); }

    // push every lecture to our db
    async.each(lectures, function(lecture, cb) {
      // create Lecture from our Model
      var Lec = new Lecture();
      // set attributes
      Lec.date = lecture.start;
      Lec.fullLectureName = lecture.lsfName;
      Lec.shortLectureName = lecture.shortName;
      Lec.room = lecture.lsfRoom;
      Lec.startTime = lecture.start;
      Lec.endTime = lecture.end;
      Lec.group = lecture.group;
      Lec.hashCode = lecture.hashCode;
      // save lecture to db
      Lec.save(cb);
    }, function(err) {
      if (err) { console.log(err); }
      mongoose.disconnect();
      console.log("Success! The parser inserted " + lectures.length + " lectures in the database");
    });
  });
};

var parseGroupsInLecture = function(html, days, dayPos) {
  var lectures = [];
  var $ = cheerio.load(html);

  // there could be more then one groups in this lecture,
  // we create a lecture for every group
  html.find("table").each(function(i) {
    var time = trimProperty($(this).find("td.notiz").first().text());
    var date = days[dayPos];

    var lecture = {};
    lecture.lsfName = $(this).find("a").first().attr("title");
    lecture.start = parseStartEnd(date, time).start;
    lecture.end = parseStartEnd(date, time).end;
    lecture.lsfDate = date;
    lecture.lsfTime = time;
    lecture.lsfRoom = $(this).find("td.notiz a").first().text();
    lecture.group = parseGroup(lecture.lsfName);
    lecture.shortName = parseShortName(lecture.lsfName);
    lecture.hashCode = hashCode(lecture.lsfName+lecture.lsfDate+lecture.lsfTime+lecture.lsfRoom);

    lectures.push(lecture);
  });
  return lectures;
};

var parseGroup = function(s) {
    return s.split(" ")[0];
};

var parseShortName = function(s) {
    var parts = s.split(" ");
    parts.shift();
    var shortName = parts.join(" ");
    return shortName;
};

var parseStartEnd = function(date, time) {
    var start = null;
    var end = null;
    var timeString = time.split(" ")[0];
    var starTimeString = timeString.split("-")[0];
    var endTimeString = timeString.split("-")[1];

    var dateString = date.split(" ")[1];
    var startString = dateString + " " + starTimeString;
    var endString  = dateString + " " + endTimeString;

    start = moment(startString, "DD-MM-YYYY HH:mm");
    end = moment(endString, "DD-MM-YYYY HH:mm");

    return { start: start, end: end };
};

var trimProperty = function(s) {
  s = s.replace(/ /g, ""); // remove that whitespace
  s = s.replace(/\n/g, "");
  s = s.replace(/\r/g, "");
  s = s.trim();
  return s;
};

var outputAsTable = function(timeTableGrid, iteration) {
  for (var i = 0; i < timeTableGrid.length; i++) {
    var row = timeTableGrid[i];
    process.stdout.write(iteration + ". ROW: " + i + ":\t");

    for (var j = 0; j < row.length; j++) {
      process.stdout.write(row[j] + "  ");
    }

    process.stdout.write("\n");
  }
};

var getDaysInThisWeek = function($) {
  var days = [];
  var headerRow = $("th.plan_rahmen").closest("table").children("tr").first();
  headerRow.find("th").each(function(index) {
    var day = $(this).text();
    day = day.replace(/ /g, "");
    day = day.replace(/\n/g, " ");
    day = day.trim();
    days.push(day);
  });
  return days;
};

var hashCode = function(s){
	var hash = crypto.createHash('md5').update(s).digest('hex');
    return hash;
};

var startParser = function() {
  request(url, function(error, response, html) {
    if(!error) {
      var lectures = parse(html);
      insertInDatabase(lectures);
    }
  });
};

startParser();

module.exports = { parse: parse, startParser: startParser };<commit_msg>remove empty if block<commit_after>"use strict";

var request  = require("request");
var cheerio  = require("cheerio");
var moment   = require("moment");
var mongoose = require("mongoose");
var async    = require("async");
var crypto   = require("crypto");

// just a static test url, later the client will send a week and a year
var url = "https://lsf.hft-stuttgart.de/qisserver/rds?state=wplan&k_abstgv.abstgvnr=262&week=12_2015&act=stg&pool=stg&show=plan&P.vx=lang&P.Print=";

var parse = function(html) {
  var table = null;
  var days = [];
  var timeTableGrid = [];
  var lectures = [];
  var $ = cheerio.load(html);

  // load days before we cut of td.plan_rahmen stuff
  days = getDaysInThisWeek($);

  // remove td.plan rahmen from the table, this removes a lot of trouble
  table = $("th.plan_rahmen").closest("table");
  table.find(".plan_rahmen").remove();

  // lets parse
  // get all the rows of the timetable (except the header (days) row)
  var rows = table.children("tr:not(:first-child)");

  // create and prefill the grid, which holds our whole timetable
  for (var i = 0; i < rows.length; i++) {     // rows
    timeTableGrid[i] = new Array(days.length);
    for (var j = 0; j < days.length; j++) {   // columns
      timeTableGrid[i][j] = ".";
    }
  }

  // iterate over each row and each cell
  rows.each(function(i) {
    var cells = $(this).children("td:not(:first-child)");

    // it seems like cheerio doesnt support collection.get(), so we just
    // bottle that cheerio collection into a native array.
    var cellsArray = [];
    cells.each(function(j) {
      cellsArray.push($(this));
    });

    for (var j = 0; j < days.length; j++) {
      if (timeTableGrid[i][j] === ".") { // here must be a td-(lecture)-element

        var currCell = cellsArray[0];
        cellsArray.shift(); // removes the first element, its a pseudo queue

        if (currCell.attr("class").indexOf("plan2") > -1) { // here we have a lecture
          // now mark the whole rowspan down as the same lecture
          var rowspan = currCell.attr("rowspan");
          for (var k = 0; k < rowspan; k++) {
            timeTableGrid[i+k][j] = j;
          }

          // parse the lectures td element
          var moreLectures = parseGroupsInLecture(currCell, days, j);
          lectures = lectures.concat(moreLectures);
        }
      }
    }
  });
  return lectures;
};

var insertInDatabase = function(lectures) {
  // connect to mongodb if not already
  if(mongoose.connection.readyState === 0) {
    mongoose.connect("mongodb://localhost:27017/wuw");
  }

  // create models from our schemas
  var Lecture = require("./model_lecture");
  //var Deadline = require("./model_deadline");

  // drop current lecture collection to get a fresh result
  mongoose.connection.collections.lectures.drop(function(err) {
    if(err) { console.log(err); }

    // push every lecture to our db
    async.each(lectures, function(lecture, cb) {
      // create Lecture from our Model
      var Lec = new Lecture();
      // set attributes
      Lec.date = lecture.start;
      Lec.fullLectureName = lecture.lsfName;
      Lec.shortLectureName = lecture.shortName;
      Lec.room = lecture.lsfRoom;
      Lec.startTime = lecture.start;
      Lec.endTime = lecture.end;
      Lec.group = lecture.group;
      Lec.hashCode = lecture.hashCode;
      // save lecture to db
      Lec.save(cb);
    }, function(err) {
      if (err) { console.log(err); }
      mongoose.disconnect();
      console.log("Success! The parser inserted " + lectures.length + " lectures in the database");
    });
  });
};

var parseGroupsInLecture = function(html, days, dayPos) {
  var lectures = [];
  var $ = cheerio.load(html);

  // there could be more then one groups in this lecture,
  // we create a lecture for every group
  html.find("table").each(function(i) {
    var time = trimProperty($(this).find("td.notiz").first().text());
    var date = days[dayPos];

    var lecture = {};
    lecture.lsfName = $(this).find("a").first().attr("title");
    lecture.start = parseStartEnd(date, time).start;
    lecture.end = parseStartEnd(date, time).end;
    lecture.lsfDate = date;
    lecture.lsfTime = time;
    lecture.lsfRoom = $(this).find("td.notiz a").first().text();
    lecture.group = parseGroup(lecture.lsfName);
    lecture.shortName = parseShortName(lecture.lsfName);
    lecture.hashCode = hashCode(lecture.lsfName+lecture.lsfDate+lecture.lsfTime+lecture.lsfRoom);

    lectures.push(lecture);
  });
  return lectures;
};

var parseGroup = function(s) {
    return s.split(" ")[0];
};

var parseShortName = function(s) {
    var parts = s.split(" ");
    parts.shift();
    var shortName = parts.join(" ");
    return shortName;
};

var parseStartEnd = function(date, time) {
    var start = null;
    var end = null;
    var timeString = time.split(" ")[0];
    var starTimeString = timeString.split("-")[0];
    var endTimeString = timeString.split("-")[1];

    var dateString = date.split(" ")[1];
    var startString = dateString + " " + starTimeString;
    var endString  = dateString + " " + endTimeString;

    start = moment(startString, "DD-MM-YYYY HH:mm");
    end = moment(endString, "DD-MM-YYYY HH:mm");

    return { start: start, end: end };
};

var trimProperty = function(s) {
  s = s.replace(/ /g, ""); // remove that whitespace
  s = s.replace(/\n/g, "");
  s = s.replace(/\r/g, "");
  s = s.trim();
  return s;
};

var outputAsTable = function(timeTableGrid, iteration) {
  for (var i = 0; i < timeTableGrid.length; i++) {
    var row = timeTableGrid[i];
    process.stdout.write(iteration + ". ROW: " + i + ":\t");

    for (var j = 0; j < row.length; j++) {
      process.stdout.write(row[j] + "  ");
    }

    process.stdout.write("\n");
  }
};

var getDaysInThisWeek = function($) {
  var days = [];
  var headerRow = $("th.plan_rahmen").closest("table").children("tr").first();
  headerRow.find("th").each(function(index) {
    var day = $(this).text();
    day = day.replace(/ /g, "");
    day = day.replace(/\n/g, " ");
    day = day.trim();
    days.push(day);
  });
  return days;
};

var hashCode = function(s){
	var hash = crypto.createHash('md5').update(s).digest('hex');
    return hash;
};

var startParser = function() {
  request(url, function(error, response, html) {
    if(!error) {
      var lectures = parse(html);
      insertInDatabase(lectures);
    }
  });
};

startParser();

module.exports = { parse: parse, startParser: startParser };<|endoftext|>


<commit_before>var http = require("http");
var cheerio = require("cheerio");
var esprima = require("esprima")
var async = require("async")

function start(param, response) {
	//console.log(param);
	param = param.substring(1);
	var regex = /x\=22\.[0-9]*\&y\=11[3-4]\.[0-9]*/;

	if(regex.test(param)) {
		var params = param.toString().split("&");
		params[0] = params[0].toString().split("=");
		params[1] = params[1].toString().split("=");

		var base = "/english/restaurant/sr1.htm?"
		var suffix = "&z=10&wxh=5x5&currentlocation=1"
		var queryurl = base + "x=" + params[0][1] + "&y=" + params[1][1] + suffix;
		var options = {
			host: "www.openrice.com",
			headers: {'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13' },
			path: queryurl
		}

		http.get(options, function(result) {
			var data = "";
			result.on("data", function(chunk){
				data += chunk;
			});

			result.on("end", function() {
				var $ = cheerio.load(data);

				var res_arr = [];

				$("a.sr1_title").each(function() {
					var link = $(this)
					var text = link.text();
					var href = link.attr("href");

					res_arr.push({ 'name': text, 'link': href })
				});

				var counter = 0
				async.each(res_arr, function(restaurant, callback) {
					href = restaurant.link;
					var option = {
						host: "www.openrice.com",
						headers: {'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13' },
						path: href
					}

					http.get(option, function(result) {
						var data = ""
						result.on("data", function(chunk){
							data += chunk;
						});

						result.on("end", function() {
							var $$ = cheerio.load(data);
							var coords = esprima.parse($$(".pg_main > script").first().text());
							restaurant.x = coords.body[0].expression.right.value;
							restaurant.y = coords.body[1].expression.right.value;
							//console.log(restaurant.x + "," + restaurant.y);
							counter++;
							callback();
						});
					});
				}, function (err) {
					if(err) return next(err);
					//console.log(counter)

					//console.log(res_arr);

					response.writeHead(200, {"Content-Type": "text/plain"});
					response.write(JSON.stringify(res_arr));
					response.end();
				});
			});
		});
	}
}

function parse(response, data) {

}

exports.start = start;<commit_msg>Added images<commit_after>var http = require("http");
var cheerio = require("cheerio");
var esprima = require("esprima")
var async = require("async")

function start(param, response) {
	//console.log(param);
	param = param.substring(1);
	var regex = /x\=22\.[0-9]*\&y\=11[3-4]\.[0-9]*/;

	if(regex.test(param)) {
		var params = param.toString().split("&");
		params[0] = params[0].toString().split("=");
		params[1] = params[1].toString().split("=");

		var base = "/english/restaurant/sr1.htm?"
		var suffix = "&z=10&wxh=5x5&currentlocation=1"
		var queryurl = base + "x=" + params[0][1] + "&y=" + params[1][1] + suffix;
		var options = {
			host: "www.openrice.com",
			headers: {'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13' },
			path: queryurl
		}

		http.get(options, function(result) {
			var data = "";
			result.on("data", function(chunk){
				data += chunk;
			});

			result.on("end", function() {
				var $ = cheerio.load(data);

				var res_arr = [];

				$("div.poiblock").each(function() {
					var link = $(this).children(".ib").children(".sr1_poi_title").children(".sr1_title");
					var text = link.text();
					var href = link.attr("href");

					var img = $(this).children(".sr1_content").children(".PR10").children(".rel_pos").children("a").children(".sr1_doorphoto");
					var src = img.attr("style");
					console.log(src);
					src = src.substring(15, src.length-2);

					res_arr.push({ 'name': text, 'link': href, 'img': src });
				});

				var counter = 0
				async.each(res_arr, function(restaurant, callback) {
					href = restaurant.link;
					var option = {
						host: "www.openrice.com",
						headers: {'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13' },
						path: href
					}

					http.get(option, function(result) {
						var data = ""
						result.on("data", function(chunk){
							data += chunk;
						});

						result.on("end", function() {
							var $$ = cheerio.load(data);
							var coords = esprima.parse($$(".pg_main > script").first().text());
							restaurant.x = coords.body[0].expression.right.value;
							restaurant.y = coords.body[1].expression.right.value;
							//console.log(restaurant.x + "," + restaurant.y);
							counter++;
							callback();
						});
					});
				}, function (err) {
					if(err) return next(err);
					//console.log(counter)

					console.log(res_arr);

					response.writeHead(200, {"Content-Type": "text/plain"});
					response.write(JSON.stringify(res_arr));
					response.end();
				});
			});
		});
	}
}

function parse(response, data) {

}

exports.start = start;<|endoftext|>


<commit_before>/**
 * This is the file where commands get parsed
 *
 * Some parts of this code are taken from the Pokémon Showdown server code, so
 * credits also go to Guangcong Luo and other Pokémon Showdown contributors.
 * https://github.com/Zarel/Pokemon-Showdown
 *
 * @license MIT license
 */

var sys = require('sys');
var https = require('https');
var url = require('url');

exports.parse = {
	actionUrl: url.parse('https://play.pokemonshowdown.com/~~' + config.serverid + '/action.php'),
	room: 'lobby',
	chatData: {},
	ranks: {},

	data: function(data, connection) {
		if (data.substr(0, 1) === 'a') {
			data = JSON.parse(data.substr(1));
			if (data instanceof Array) {
				for (var i = 0; i < data.length; i++) {
					this.message(data[i], connection);
				}
			} else {
				this.message(data, connection);
			}
		}
	},
	message: function(message, connection) {
		if (!message) return;

		if (message.indexOf('\n') > -1) {
			var spl = message.split('\n');
			for (var i = 0; i < spl.length; i++) {
				if (spl[i].split('|')[1] && (spl[i].split('|')[1] === 'init' || spl[i].split('|')[1] === 'tournament')) {
					this.room = '';
					break;
				}
				this.message(spl[i], connection);
			}
			return;
		}

		var spl = message.split('|');
		if (!spl[1]) {
			spl = message.split('>');
			if (!spl[1])
				return;
			this.room = spl[1];
		}

		switch (spl[1]) {
			case 'challstr':
				info('received challstr, logging in...');
				var id = spl[2];
				var str = spl[3];

				var requestOptions = {
					hostname: this.actionUrl.hostname,
					port: this.actionUrl.port,
					path: this.actionUrl.pathname,
					agent: false
				};

				if (!config.pass) {
					requestOptions.method = 'GET';
					requestOptions.path += '?act=getassertion&userid=' + toId(config.nick) + '&challengekeyid=' + id + '&challenge=' + str;
				} else {
					requestOptions.method = 'POST';
					var data = 'act=login&name=' + config.nick + '&pass=' + config.pass + '&challengekeyid=' + id + '&challenge=' + str;
					requestOptions.headers = {
						'Content-Type': 'application/x-www-form-urlencoded',
						'Content-Length': data.length
					};
				}

				var req = https.request(requestOptions, function(res) {
					res.setEncoding('utf8');
					var data = '';
					res.on('data', function(chunk) {
						data += chunk;
					});
					res.on('end', function() {
						if (data === ';') {
							error('failed to log in; nick is registered - invalid or no password given');
							process.exit(-1);
						}
						if (data.length < 50) {
							error('failed to log in: ' + data);
							process.exit(-1);
						}

						if (data.indexOf('heavy load') !== -1) {
							error('the login server is under heavy load; trying again in one minute');
							setTimeout(function() {
								this.message(message);
							}.bind(this), 60000);
							return;
						}

						try {
							data = JSON.parse(data.substr(1));
							if (data.actionsuccess) {
								data = data.assertion;
							} else {
								error('could not log in; action was not successful: ' + JSON.stringify(data));
								process.exit(-1);
							}
						} catch (e) {}
						send(connection, '|/trn ' + config.nick + ',0,' + data);
					}.bind(this));
				}.bind(this));

				req.on('error', function(err) {
					error('login error: ' + sys.inspect(err));
				});

				if (data) {
					req.write(data);
				}
				req.end();
				break;
			case 'updateuser':
				if (spl[2] !== config.nick) {
					return;
				}

				if (spl[3] !== '1') {
					error('failed to log in, still guest');
					process.exit(-1);
				}

				ok('logged in as ' + spl[2]);

				// Now join the rooms
				var cmds = ['/idle'];
				for (var i in config.rooms) {
					var room = config.rooms[i].toLowerCase();
					if (room === 'lobby' && config.serverid === 'showdown') {
						continue;
					}
					cmds.push('|/join ' + room);
				}

				var self = this;
				if (cmds.length > 6) {
					self.nextJoin = 0;
					self.joinSpacer = setInterval(function(con, cmds) {
						if (cmds.length > self.nextJoin + 5) {
							send(con, cmds.slice(self.nextJoin, self.nextJoin + 5));
							self.nextJoin += 5;
						} else {
							send(con, cmds.slice(self.nextJoin));
							delete self.nextJoin;
							clearInterval(self.joinSpacer);
						}
					}, 8*1000, connection, cmds);
				} else {
					send(connection, cmds);
				}

				this.chatDataTimer = setInterval(
					function() {self.chatData = cleanChatData(self.chatData);},
					30*60*1000
				);
				this.room = '';
				break;
			case 'title':
				ok('joined ' + spl[2]);
				this.room = '';
				break;
			case 'c':
				var by = spl[2];
				spl.splice(0, 3);
				this.chatMessage(spl.join('|'), by, this.room || 'lobby', connection);
				this.processChatData(by, this.room || 'lobby', connection, spl.join('|'));
				this.room = '';
				break;
			case 'pm':
				var by = spl[2];
				if (by.substr(1) === config.nick) return;
				spl.splice(0, 4);
				this.chatMessage(spl.join('|'), by, ',' + by, connection);
				this.room = '';
				break;
			case 'N': case 'J': case 'j':
				var by = spl[2];
				if (by.substr(1) !== config.nick || ' +%@&#~'.indexOf(by.charAt(0)) === -1) return;
				this.ranks[(this.room === ''?'lobby':this.room)] = by.charAt(0);
				this.room = '';
				break;
		}
	},
	chatMessage: function(message, by, room, connection) {
		message = message.trim();
		if (message.substr(0, config.commandcharacter.length) !== config.commandcharacter || toId(by) === toId(config.nick)) {
			return;
		}

		message = message.substr(config.commandcharacter.length);
		var index = message.indexOf(' ');
		var arg = '';
		if (index > -1) {
			var cmd = message.substr(0, index);
			arg = message.substr(index + 1).trim();
		} else {
			var cmd = message;
		}

		if (Commands[cmd]) {
			var failsafe = 0;
			while (typeof Commands[cmd] !== "function" && failsafe++ < 10) {
				cmd = Commands[cmd];
			}
			if (typeof Commands[cmd] === "function") {
				Commands[cmd].call(this, arg, by, room, connection);
			} else {
				error("invalid command type for " + cmd + ": " + (typeof Commands[cmd]));
			}
		}
	},
	say: function(connection, room, text) {
		if (room.substr(0, 1) !== ',') {
			var str = (room !== 'lobby' ? room : '') + '|' + text;
			send(connection, str);
		} else {
			room = room.substr(1);
			var str = '|/pm ' + room + ', ' + text;
			send(connection, str);
		}
	},
	hasRank: function(user, rank) {
		var hasRank = (rank.split('').indexOf(user.charAt(0)) !== -1) || (config.excepts.indexOf(toId(user.substr(1))) !== -1);
		return hasRank;
	},
	processChatData: function(user, room, connection, msg) {
		// NOTE: this is still in early stages
		user = toId(user);
		if (room.charAt(0) === ',' || user === toId(config.nick)) return;
		room = toId(room);
		msg = msg.trim().replace(/ +/g, " ");
		if (!this.chatData[user]) this.chatData[user] = {zeroTol:0};
		if (!this.chatData[user][room]) this.chatData[user][room] = {times:[], points:0, lastAction:0};

		this.chatData[user][room].times.push(Date.now());

		// this deals with punishing rulebreakers, but note that the bot can't think, so it might make mistakes
		if (config.allowmute && this.hasRank(this.ranks[room] || ' ', '%@&#~')) {
			var pointVal = 0;
			var muteMessage = '';

			if (msg.match(/snen/g) && msg.match(/snen/g).length > 6) {
				if (pointVal < 4) {
					muteMessage = ', Automated response: possible "snen" spammer';
					pointVal = (room === 'lobby') ? 5 : 4;
				}
			}
			if (this.chatData[user][room].times.length >= 5 && (Date.now() - this.chatData[user][room].times[this.chatData[user][room].times.length - 5]) < 6*1000) {
				if (pointVal < 2) {
					pointVal = 2;
					muteMessage = ', Automated response: flooding';
				}
			}
			var capsMatch = msg.replace(/[^A-Za-z]/g, '').match(/[A-Z]/g);
			if (capsMatch && toId(msg).length > 18 && (capsMatch.length >= Math.floor(toId(msg).length * 0.8))) {
				if (pointVal < 1) {
					pointVal = 1;
					muteMessage = ', Automated response: caps';
				}
			}
			var stretchMatch = msg.toLowerCase().match(/(.)\1{7,}/g); // matches the same character 8 or more times in a row
			if (stretchMatch) {
				if (pointVal < 1) {
					pointVal = 1;
					muteMessage = ', Automated response: stretching';
				}
			}

			if (pointVal > 0 && !(Date.now() - this.chatData[user][room].lastAction < 3*1000)) {
				var cmd = 'mute';
				if (this.chatData[user][room].points >= pointVal && pointVal < 4) {
					this.chatData[user][room].points++;
					cmd = config.punishvals[this.chatData[user][room].points] || cmd;
				} else {
					cmd = config.punishvals[pointVal] || cmd;
					this.chatData[user][room].points = pointVal;
				}
				if (this.chatData[user][room].points >= 4 && !this.hasRank(this.ranks[room] || ' ', '@&#~')) cmd = 'hourmute';
				if (this.chatData[user].zeroTol > 4) {
					muteMessage = ', Automated response: zero tolerance user';
					cmd = this.hasRank(this.ranks[room] || ' ', '@&#~') ? 'roomban' : 'hourmute';
				}
				if (this.chatData[user][room].points >= 2) this.chatData[user].zeroTol++;
				this.chatData[user][room].lastAction = Date.now();
				this.say(connection, room, '/' + cmd + ' ' + user + muteMessage);
			}
		}
	},
	uncacheTree: function(root) {
		var uncache = [require.resolve(root)];
		do {
			var newuncache = [];
			for (var i = 0; i < uncache.length; ++i) {
				if (require.cache[uncache[i]]) {
					newuncache.push.apply(newuncache,
						require.cache[uncache[i]].children.map(function(module) {
							return module.filename;
						})
					);
					delete require.cache[uncache[i]];
				}
			}
			uncache = newuncache;
		} while (uncache.length > 0);
	}
};
<commit_msg>Accept invitations to join rooms<commit_after>/**
 * This is the file where commands get parsed
 *
 * Some parts of this code are taken from the Pokémon Showdown server code, so
 * credits also go to Guangcong Luo and other Pokémon Showdown contributors.
 * https://github.com/Zarel/Pokemon-Showdown
 *
 * @license MIT license
 */

var sys = require('sys');
var https = require('https');
var url = require('url');

exports.parse = {
	actionUrl: url.parse('https://play.pokemonshowdown.com/~~' + config.serverid + '/action.php'),
	room: 'lobby',
	chatData: {},
	ranks: {},

	data: function(data, connection) {
		if (data.substr(0, 1) === 'a') {
			data = JSON.parse(data.substr(1));
			if (data instanceof Array) {
				for (var i = 0; i < data.length; i++) {
					this.message(data[i], connection);
				}
			} else {
				this.message(data, connection);
			}
		}
	},
	message: function(message, connection) {
		if (!message) return;

		if (message.indexOf('\n') > -1) {
			var spl = message.split('\n');
			for (var i = 0; i < spl.length; i++) {
				if (spl[i].split('|')[1] && (spl[i].split('|')[1] === 'init' || spl[i].split('|')[1] === 'tournament')) {
					this.room = '';
					break;
				}
				this.message(spl[i], connection);
			}
			return;
		}

		var spl = message.split('|');
		if (!spl[1]) {
			spl = message.split('>');
			if (!spl[1])
				return;
			this.room = spl[1];
		}

		switch (spl[1]) {
			case 'challstr':
				info('received challstr, logging in...');
				var id = spl[2];
				var str = spl[3];

				var requestOptions = {
					hostname: this.actionUrl.hostname,
					port: this.actionUrl.port,
					path: this.actionUrl.pathname,
					agent: false
				};

				if (!config.pass) {
					requestOptions.method = 'GET';
					requestOptions.path += '?act=getassertion&userid=' + toId(config.nick) + '&challengekeyid=' + id + '&challenge=' + str;
				} else {
					requestOptions.method = 'POST';
					var data = 'act=login&name=' + config.nick + '&pass=' + config.pass + '&challengekeyid=' + id + '&challenge=' + str;
					requestOptions.headers = {
						'Content-Type': 'application/x-www-form-urlencoded',
						'Content-Length': data.length
					};
				}

				var req = https.request(requestOptions, function(res) {
					res.setEncoding('utf8');
					var data = '';
					res.on('data', function(chunk) {
						data += chunk;
					});
					res.on('end', function() {
						if (data === ';') {
							error('failed to log in; nick is registered - invalid or no password given');
							process.exit(-1);
						}
						if (data.length < 50) {
							error('failed to log in: ' + data);
							process.exit(-1);
						}

						if (data.indexOf('heavy load') !== -1) {
							error('the login server is under heavy load; trying again in one minute');
							setTimeout(function() {
								this.message(message);
							}.bind(this), 60000);
							return;
						}

						try {
							data = JSON.parse(data.substr(1));
							if (data.actionsuccess) {
								data = data.assertion;
							} else {
								error('could not log in; action was not successful: ' + JSON.stringify(data));
								process.exit(-1);
							}
						} catch (e) {}
						send(connection, '|/trn ' + config.nick + ',0,' + data);
					}.bind(this));
				}.bind(this));

				req.on('error', function(err) {
					error('login error: ' + sys.inspect(err));
				});

				if (data) {
					req.write(data);
				}
				req.end();
				break;
			case 'updateuser':
				if (spl[2] !== config.nick) {
					return;
				}

				if (spl[3] !== '1') {
					error('failed to log in, still guest');
					process.exit(-1);
				}

				ok('logged in as ' + spl[2]);

				// Now join the rooms
				var cmds = ['/idle'];
				for (var i in config.rooms) {
					var room = config.rooms[i].toLowerCase();
					if (room === 'lobby' && config.serverid === 'showdown') {
						continue;
					}
					cmds.push('|/join ' + room);
				}

				var self = this;
				if (cmds.length > 6) {
					self.nextJoin = 0;
					self.joinSpacer = setInterval(function(con, cmds) {
						if (cmds.length > self.nextJoin + 5) {
							send(con, cmds.slice(self.nextJoin, self.nextJoin + 5));
							self.nextJoin += 5;
						} else {
							send(con, cmds.slice(self.nextJoin));
							delete self.nextJoin;
							clearInterval(self.joinSpacer);
						}
					}, 8*1000, connection, cmds);
				} else {
					send(connection, cmds);
				}

				this.chatDataTimer = setInterval(
					function() {self.chatData = cleanChatData(self.chatData);},
					30*60*1000
				);
				this.room = '';
				break;
			case 'title':
				ok('joined ' + spl[2]);
				this.room = '';
				break;
			case 'c':
				var by = spl[2];
				spl.splice(0, 3);
				this.chatMessage(spl.join('|'), by, this.room || 'lobby', connection);
				this.processChatData(by, this.room || 'lobby', connection, spl.join('|'));
				this.room = '';
				break;
			case 'pm':
				var by = spl[2];
				if (by.substr(1) === config.nick) return;
				spl.splice(0, 4);
				this.chatMessage(spl.join('|'), by, ',' + by, connection);
				this.room = '';
				break;
			case 'N': case 'J': case 'j':
				var by = spl[2];
				if (by.substr(1) !== config.nick || ' +%@&#~'.indexOf(by.charAt(0)) === -1) return;
				this.ranks[(this.room === ''?'lobby':this.room)] = by.charAt(0);
				this.room = '';
				break;
		}
	},
	chatMessage: function(message, by, room, connection) {
		message = message.trim();
		if (message.substr(0, config.commandcharacter.length) !== config.commandcharacter || toId(by) === toId(config.nick)) {
			return;
		}

		message = message.substr(config.commandcharacter.length);
		var index = message.indexOf(' ');
		var arg = '';
		if (index > -1) {
			var cmd = message.substr(0, index);
			arg = message.substr(index + 1).trim();
		} else {
			var cmd = message;
		}

		if (Commands[cmd]) {
			var failsafe = 0;
			while (typeof Commands[cmd] !== "function" && failsafe++ < 10) {
				cmd = Commands[cmd];
			}
			if (typeof Commands[cmd] === "function") {
				Commands[cmd].call(this, arg, by, room, connection);
			} else {
				error("invalid command type for " + cmd + ": " + (typeof Commands[cmd]));
			}
		}

		// auto accept invitations to rooms
		if (room.charAt(0) === ',' && message.substr(0,8) === '/invite ') {
			this.say(connection, '', '/join ' + message.substr(8));
		}
	},
	say: function(connection, room, text) {
		if (room.substr(0, 1) !== ',') {
			var str = (room !== 'lobby' ? room : '') + '|' + text;
			send(connection, str);
		} else {
			room = room.substr(1);
			var str = '|/pm ' + room + ', ' + text;
			send(connection, str);
		}
	},
	hasRank: function(user, rank) {
		var hasRank = (rank.split('').indexOf(user.charAt(0)) !== -1) || (config.excepts.indexOf(toId(user.substr(1))) !== -1);
		return hasRank;
	},
	processChatData: function(user, room, connection, msg) {
		// NOTE: this is still in early stages
		user = toId(user);
		if (room.charAt(0) === ',' || user === toId(config.nick)) return;
		room = toId(room);
		msg = msg.trim().replace(/ +/g, " ");
		if (!this.chatData[user]) this.chatData[user] = {zeroTol:0};
		if (!this.chatData[user][room]) this.chatData[user][room] = {times:[], points:0, lastAction:0};

		this.chatData[user][room].times.push(Date.now());

		// this deals with punishing rulebreakers, but note that the bot can't think, so it might make mistakes
		if (config.allowmute && this.hasRank(this.ranks[room] || ' ', '%@&#~')) {
			var pointVal = 0;
			var muteMessage = '';

			if (msg.match(/snen/g) && msg.match(/snen/g).length > 6) {
				if (pointVal < 4) {
					muteMessage = ', Automated response: possible "snen" spammer';
					pointVal = (room === 'lobby') ? 5 : 4;
				}
			}
			if (this.chatData[user][room].times.length >= 5 && (Date.now() - this.chatData[user][room].times[this.chatData[user][room].times.length - 5]) < 6*1000) {
				if (pointVal < 2) {
					pointVal = 2;
					muteMessage = ', Automated response: flooding';
				}
			}
			var capsMatch = msg.replace(/[^A-Za-z]/g, '').match(/[A-Z]/g);
			if (capsMatch && toId(msg).length > 18 && (capsMatch.length >= Math.floor(toId(msg).length * 0.8))) {
				if (pointVal < 1) {
					pointVal = 1;
					muteMessage = ', Automated response: caps';
				}
			}
			var stretchMatch = msg.toLowerCase().match(/(.)\1{7,}/g); // matches the same character 8 or more times in a row
			if (stretchMatch) {
				if (pointVal < 1) {
					pointVal = 1;
					muteMessage = ', Automated response: stretching';
				}
			}

			if (pointVal > 0 && !(Date.now() - this.chatData[user][room].lastAction < 3*1000)) {
				var cmd = 'mute';
				if (this.chatData[user][room].points >= pointVal && pointVal < 4) {
					this.chatData[user][room].points++;
					cmd = config.punishvals[this.chatData[user][room].points] || cmd;
				} else {
					cmd = config.punishvals[pointVal] || cmd;
					this.chatData[user][room].points = pointVal;
				}
				if (this.chatData[user][room].points >= 4 && !this.hasRank(this.ranks[room] || ' ', '@&#~')) cmd = 'hourmute';
				if (this.chatData[user].zeroTol > 4) {
					muteMessage = ', Automated response: zero tolerance user';
					cmd = this.hasRank(this.ranks[room] || ' ', '@&#~') ? 'roomban' : 'hourmute';
				}
				if (this.chatData[user][room].points >= 2) this.chatData[user].zeroTol++;
				this.chatData[user][room].lastAction = Date.now();
				this.say(connection, room, '/' + cmd + ' ' + user + muteMessage);
			}
		}
	},
	uncacheTree: function(root) {
		var uncache = [require.resolve(root)];
		do {
			var newuncache = [];
			for (var i = 0; i < uncache.length; ++i) {
				if (require.cache[uncache[i]]) {
					newuncache.push.apply(newuncache,
						require.cache[uncache[i]].children.map(function(module) {
							return module.filename;
						})
					);
					delete require.cache[uncache[i]];
				}
			}
			uncache = newuncache;
		} while (uncache.length > 0);
	}
};
<|endoftext|>