<commit_before>/*

Note: At this time the plugin wrapper for this code is still under development.
It works with browsers that support foreignObject, but the logic for handling
tspans in Internet Explorer and other browsers that don't support foreignObject
has not yet been refactored for the D3 plugin format. That code will work if
carefully transplanted to other projects, and you're welcome to harvest it and
apply it in that manner, but it can't yet simply be applied to a selection by
calling d3.textwrap(). I know that's kind of the whole point! That is the
eventual goal here, of course; stay tuned.

*/

(function() {

    // exit immediately if this has already been defined
    // as a function; the plugin will defer to whatever
    // else you're doing in your code
    if(d3.selection.prototype.textwrap) {
        return false;
    }

    var force_wrap_method;
    // set this variable to a string value to always force a particular
    // wrap method for development purposes. set to 'tspan' to
    // use tspans and 'foreignobject' to use foreignobject
    // force_wrap_method = 'tspans'; // uncomment statement to use tspans
    // force_wrap_method = 'foreignobjects'; // uncomment statement to use foreignobjects
    force_wrap_method = false; // by default no wrap method is forced

    // create the plugin method twice, both for regular use
    // and again for use inside the enter() selection
    d3.selection.prototype.textwrap = d3.selection.enter.prototype.textwrap = function(bounds) {

        // save callee into a variable so we can continue to refer to it
        // as the function scope changes
        var selection = this;

        // extract wrap boundaries from any d3-selected rect and return them
        // in a format that matches the simpler object argument option
        var extract_bounds = function(bounds) {
            // discard the nested array wrappers added by d3
            var bounding_rect = bounds[0][0];
            // sanitize the svg element name so we can test against it
            var element_type = bounding_rect.tagName.toString();
            // if it's not a rect, exit
            if(element_type !== 'rect') {
                return false;
            // if it's a rect, proceed to extracting the position attributes
            } else {
                var bounds_extracted = {};
                bounds_extracted.x = d3.select(bounding_rect).attr('x') || 0;
                bounds_extracted.y = d3.select(bounding_rect).attr('y') || 0;
                bounds_extracted.width = d3.select(bounding_rect).attr('width') || 0;
                bounds_extracted.height = d3.select(bounding_rect).attr('height') || 0;
                // also pass along the getter function
                bounds_extracted.attr = bounds.attr;
                return bounds_extracted;
            }
        }

        // double check the input argument for the wrapping
        // boundaries to make sure it actually contains all
        // the information we'll need in order to wrap successfully
        var verify_bounds = function(bounds) {
            // quickly add a simple getter method so you can use either
            // bounds.x or bounds.attr('x') as your notation,
            // the latter being a common convention among D3
            // developers
            if(!bounds.attr) {
                bounds.attr = function(property) {
                    if(this[property]) {
                        return this[property];
                    }
                }
            }
            // if it's an associative array, make sure it has all the
            // necessary properties represented directly
            if(
                (typeof bounds == 'object') &&
                (bounds.x) &&
                (bounds.y) &&
                (bounds.width) &&
                (bounds.height)
                // if that's the case, then the bounds are fine
            ) {
                // return the lightly modified bounds
                return bounds;
            // if it's a numerically indexed array, assume it's a
            // d3-selected rect and try to extract the positions
            } else if(
                    // first try to make sure it's an array using Array.isArray
                    (
                        (typeof Array.isArray == 'function') &&
                        (Array.isArray(bounds))
                    ) ||
                    // but since Array.isArray isn't always supported, fall
                    // back to casting to the object to string when it's not
                    (Object.prototype.toString.call(bounds) === '[object Array]')
            ) {
                // once you're sure it's an array, extract the boundaries
                // from the rect
                var extracted_bounds = extract_bounds(bounds);
                return extracted_bounds;
            } else {
            // but if the bounds are neither an object nor a numerical
            // array, then the bounds argument is invalid and you'll
            // need to fix it
                return false;
            }
        }

        var verified_bounds = verify_bounds(bounds);

        // check that we have the necessary conditions for this function to operate properly
        if(
            // selection it's operating on cannot be not empty
            (selection.length == 0) ||
            // d3 must be available
            (!d3) ||
            // desired wrapping bounds must be provided as an input argument
            (!bounds) ||
            // input bounds must validate
            (!verified_bounds)
        ) {
            // try to return the calling selection if possible
            // so as not to interfere with methods downstream in the
            // chain
            if(selection) {
                return selection;
            // if all else fails, just return false. if you hit this point then you're
            // almost trying to call the textwrap() method on something that
            // doesn't make sense!
            } else {
                return false;
            }
        // if we've validated everything then we can finally proceed
        // to the meat of this operation
        } else {

            // reassign the verified bounds as the set we want
            // to work with from here on; this ensures that we're
            // using the same data structure for our bounds regardless
            // of whether the input argument was a simple object or
            // a d3 selection
            bounds = verified_bounds;

            // wrap using html and foreignObjects if they are supported
            var wrap_with_foreignobjects = function(item) {
                console.log('wrapping with foreign object');
                // establish variables to quickly reference target nodes later
                var parent = d3.select(item[0].parentNode);
                var text_node = parent.selectAll('text');
                // extract our desired content from the single text element
                var text_to_wrap = text_node.text();
                // remove the text node and replace with a foreign object
                text_node.remove();
                var foreign_object = parent.append('foreignObject');
                // add foreign object and set dimensions, position, etc
                foreign_object
                    .attr("requiredFeatures", "http://www.w3.org/TR/SVG11/feature#Extensibility")
                    .attr('x', bounds.x)
                    .attr('y', bounds.y)
                    .attr('width', bounds.width)
                    .attr('height', bounds.height)
                ;
                // insert an HTML div
                var wrap_div = foreign_object
                    .append('xhtml:div')
                    // this class is currently hardcoded
                    // probably not necessary but easy to
                    // override using .classed() and for now
                    // it's nice to avoid a litany of input
                    // arguments
                    .attr('class', 'wrapped')
                ;
                // set div to same dimensions as foreign object
                wrap_div
                    .style('height', bounds.height)
                    .style('width', bounds.width)
                    // insert text content
                    .html(text_to_wrap)
                ;
            }

            // wrap with tspans if foreignObject is undefined
            var wrap_with_tspans = function(text_node) {
                console.log('wrapping with tspans');
                // only fire the rest of this if the text content
                // overflows the desired dimensions
                if(this.getBBox().width > max_width) {
                    // store whatever is inside the text node
                    // in a variable and then zero out the
                    // initial content; we'll reinsert in a moment
                    // using tspan elements.
                    var text_node = d3.select(this);
                    var text_to_wrap = text_node.text();
                    text_node.text('');

                    if(text_to_wrap) {
                        // split at spaces to create an array of individual words
                        var text_to_wrap_array;
                        if(text_to_wrap.indexOf(' ') !== -1) {
                            text_to_wrap_array = text_to_wrap.split(' ');
                        } else {
                            // if there are no spaces, chop it in half.
                            // this is a hack! better to apply
                            // CSS word-break: break-all in order
                            // to handle long single-word strings that
                            // might overflow without spaces. will fix to
                            // dynamically compute the appropriate number
                            // of cuts later.
                            var string_length = text_to_wrap.length;
                            var midpoint = parseInt(string_length / 2);
                            var first_half = text_to_wrap.substr(0, midpoint);
                            var second_half = text_to_wrap.substr(midpoint, string_length);
                            text_to_wrap_array = [first_half, second_half];
                        }

                        // new array where we'll store the words re-assembled into
                        // substrings that have been tested against the desired
                        // maximum wrapping width
                        var substrings = [];
                        // computed text length is arguably incorrectly reported for
                        // all tspans after the first one, in that they will include
                        // the width of previous separate tspans. to compensate we need
                        // to manually track the computed text length of all those
                        // previous tspans and substrings, and then use that to offset
                        // the miscalculation. this then gives us the actual correct
                        // position we want to use in rendering the text in the SVG.
                        var offset = 0;
                        // loop through the words and test the computed text length
                        // of the string against the maximum desired wrapping width
                        for(var i = 0; i < text_to_wrap_array.length; i++) {
                            var word = text_to_wrap_array[i];
                            var previous_string = text_node.text();
                            var previous_width = this.getComputedTextLength();
                            // initialize the current word as the first word
                            // or append to the previous string if one exists
                            var new_string;
                            if(previous_string) {
                                new_string = previous_string + ' ' + word;
                            } else {
                                new_string = word;
                            }
                            // add the newest substring back to the text node and
                            // measure the length
                            text_node.text(new_string);
                            var new_width = this.getComputedTextLength();
                            // adjust the length by the offset we've tracked
                            // due to the misreported length discussed above
                            var test_width = new_width - offset;
                            // if our latest version of the string is too
                            // big for the bounds, use the previous
                            // version of the string (without the newest word
                            // added) and use the latest word to restart the
                            // process with a new tspan
                            if(test_width > max_width) {
                                if(previous_string !== '') {
                                    var temp = {string: previous_string, width: previous_width - offset};
                                    substrings.push(temp);
                                    offset = offset + previous_width;
                                    text_node.text('');
                                    text_node.text(word);
                                }
                            }
                            // if we're up to the last word in the array,
                            // get the computed length as is without
                            // appending anything
                            if(i == text_to_wrap_array.length - 1) {
                                var final_string = new_string.substr(previous_string.length);
                                // remove leading spaces if present
                                if(final_string.substr(0, 1) == ' ') {
                                    final_string = final_string.substr(1);
                                }
                                if(final_string !== '') {
                                    if((new_width - offset) > 0) {new_width = new_width - offset}
                                    var temp = {string: final_string, width: new_width};
                                    substrings.push(temp);
                                    text_node.text('');
                                }
                            }
                        }
                        // double check that there are no empty substrings
                        // because those would create blank tspans
                        var substrings_clean = [];
                        for(var i = 0; i < substrings.length; i++) {
                            if(substrings[i].string.length > 0) {
                                substrings_clean.push(substrings[i]);
                            }
                        }
                        // append each substring as a tspan
                        var current_tspan;
                        var tspan_count;
                        for(var i = 0; i < substrings_clean.length; i++) {
                            var substring = substrings_clean[i].string;
                            if(i > 0) {
                                var previous_substring = substrings_clean[i - 1];
                            }
                            current_tspan = text_node.append('tspan')
                                .text(substring)
                            ;
                            current_tspan
                                .attr('dy', function(d) {
                                    if(i > 0) {
                                        // line height shouldn't be hard coded
                                        // will fix this later
                                        return '1.5em';
                                    }
                                })
                            ;
                            // shift left from default position, which
                            // is probably based on the full length of the
                            // text string until we make this adjustment
                            current_tspan
                                .attr('dx', function() {
                                    var render_offset = 0;
                                    if(i > 0) {
                                        render_offset = 0;
                                        for(j = 0; j < i; j++) {
                                            render_offset += substrings[j].width;
                                        }
                                    }
                                    return render_offset * -1;
                                })
                            ;
                        }
                    }
                }
            }

            // variable used to hold the functions that let us
            // switch between the wrap methods
            var wrap_method;

            // if a wrap method if being forced, assign that
            // function
            if(force_wrap_method) {
                if(force_wrap_method == 'foreignobjects') {
                    wrap_method = wrap_with_foreignobjects;
                } else if (force_wrap_method == 'tspans') {
                    wrap_method = wrap_with_tspans;
                }
            }

            // if no wrap method is being forced, then instead
            // test for browser support of foreignobject and
            // use whichever wrap method makes sense accordingly
            if(!force_wrap_method) {
                if(typeof SVGForeignObjectElement !== 'undefined') {
                    wrap_method = wrap_with_foreignobjects;
                } else {
                    wrap_method = wrap_with_tspans;
                }
            }

            // run the desired wrap function for each item
            // in the d3 selection that called .textwrap()
            for(var i = 0; i < selection.length; i++) {
                var item = selection[i];
                wrap_method(item);
            }

        }

    }

})();
<commit_msg>comments<commit_after>/*

Note: At this time the plugin wrapper for this code is still under development.
It works with browsers that support foreignObject, but the logic for handling
tspans in Internet Explorer and other browsers that don't support foreignObject
has not yet been refactored for the D3 plugin format. That code will work if
carefully transplanted to other projects, and you're welcome to harvest it and
apply it in that manner, but it can't yet simply be applied to a selection by
calling d3.textwrap(). I know that's kind of the whole point! That is the
eventual goal here, of course; stay tuned.

*/

(function() {

    // exit immediately if this has already been defined
    // as a function; the plugin will defer to whatever
    // else you're doing in your code
    if(d3.selection.prototype.textwrap) {
        return false;
    }

    var force_wrap_method;
    // set this variable to a string value to always force a particular
    // wrap method for development purposes, for example to check tspan
    // rendering using a foreignobject-enabled browser. set to 'tspan' to
    // use tspans and 'foreignobject' to use foreignobject
    // force_wrap_method = 'tspans'; // uncomment statement to use tspans
    // force_wrap_method = 'foreignobjects'; // uncomment statement to use foreignobjects
    force_wrap_method = false; // by default no wrap method is forced

    // create the plugin method twice, both for regular use
    // and again for use inside the enter() selection
    d3.selection.prototype.textwrap = d3.selection.enter.prototype.textwrap = function(bounds) {

        // save callee into a variable so we can continue to refer to it
        // as the function scope changes
        var selection = this;

        // extract wrap boundaries from any d3-selected rect and return them
        // in a format that matches the simpler object argument option
        var extract_bounds = function(bounds) {
            // discard the nested array wrappers added by d3
            var bounding_rect = bounds[0][0];
            // sanitize the svg element name so we can test against it
            var element_type = bounding_rect.tagName.toString();
            // if it's not a rect, exit
            if(element_type !== 'rect') {
                return false;
            // if it's a rect, proceed to extracting the position attributes
            } else {
                var bounds_extracted = {};
                bounds_extracted.x = d3.select(bounding_rect).attr('x') || 0;
                bounds_extracted.y = d3.select(bounding_rect).attr('y') || 0;
                bounds_extracted.width = d3.select(bounding_rect).attr('width') || 0;
                bounds_extracted.height = d3.select(bounding_rect).attr('height') || 0;
                // also pass along the getter function
                bounds_extracted.attr = bounds.attr;
                return bounds_extracted;
            }
        }

        // double check the input argument for the wrapping
        // boundaries to make sure it actually contains all
        // the information we'll need in order to wrap successfully
        var verify_bounds = function(bounds) {
            // quickly add a simple getter method so you can use either
            // bounds.x or bounds.attr('x') as your notation,
            // the latter being a common convention among D3
            // developers
            if(!bounds.attr) {
                bounds.attr = function(property) {
                    if(this[property]) {
                        return this[property];
                    }
                }
            }
            // if it's an associative array, make sure it has all the
            // necessary properties represented directly
            if(
                (typeof bounds == 'object') &&
                (bounds.x) &&
                (bounds.y) &&
                (bounds.width) &&
                (bounds.height)
                // if that's the case, then the bounds are fine
            ) {
                // return the lightly modified bounds
                return bounds;
            // if it's a numerically indexed array, assume it's a
            // d3-selected rect and try to extract the positions
            } else if(
                    // first try to make sure it's an array using Array.isArray
                    (
                        (typeof Array.isArray == 'function') &&
                        (Array.isArray(bounds))
                    ) ||
                    // but since Array.isArray isn't always supported, fall
                    // back to casting to the object to string when it's not
                    (Object.prototype.toString.call(bounds) === '[object Array]')
            ) {
                // once you're sure it's an array, extract the boundaries
                // from the rect
                var extracted_bounds = extract_bounds(bounds);
                return extracted_bounds;
            } else {
            // but if the bounds are neither an object nor a numerical
            // array, then the bounds argument is invalid and you'll
            // need to fix it
                return false;
            }
        }

        var verified_bounds = verify_bounds(bounds);

        // check that we have the necessary conditions for this function to operate properly
        if(
            // selection it's operating on cannot be not empty
            (selection.length == 0) ||
            // d3 must be available
            (!d3) ||
            // desired wrapping bounds must be provided as an input argument
            (!bounds) ||
            // input bounds must validate
            (!verified_bounds)
        ) {
            // try to return the calling selection if possible
            // so as not to interfere with methods downstream in the
            // chain
            if(selection) {
                return selection;
            // if all else fails, just return false. if you hit this point then you're
            // almost trying to call the textwrap() method on something that
            // doesn't make sense!
            } else {
                return false;
            }
        // if we've validated everything then we can finally proceed
        // to the meat of this operation
        } else {

            // reassign the verified bounds as the set we want
            // to work with from here on; this ensures that we're
            // using the same data structure for our bounds regardless
            // of whether the input argument was a simple object or
            // a d3 selection
            bounds = verified_bounds;

            // wrap using html and foreignObjects if they are supported
            var wrap_with_foreignobjects = function(item) {
                console.log('wrapping with foreign object');
                // establish variables to quickly reference target nodes later
                var parent = d3.select(item[0].parentNode);
                var text_node = parent.selectAll('text');
                // extract our desired content from the single text element
                var text_to_wrap = text_node.text();
                // remove the text node and replace with a foreign object
                text_node.remove();
                var foreign_object = parent.append('foreignObject');
                // add foreign object and set dimensions, position, etc
                foreign_object
                    .attr("requiredFeatures", "http://www.w3.org/TR/SVG11/feature#Extensibility")
                    .attr('x', bounds.x)
                    .attr('y', bounds.y)
                    .attr('width', bounds.width)
                    .attr('height', bounds.height)
                ;
                // insert an HTML div
                var wrap_div = foreign_object
                    .append('xhtml:div')
                    // this class is currently hardcoded
                    // probably not necessary but easy to
                    // override using .classed() and for now
                    // it's nice to avoid a litany of input
                    // arguments
                    .attr('class', 'wrapped')
                ;
                // set div to same dimensions as foreign object
                wrap_div
                    .style('height', bounds.height)
                    .style('width', bounds.width)
                    // insert text content
                    .html(text_to_wrap)
                ;
            }

            // wrap with tspans if foreignObject is undefined
            var wrap_with_tspans = function(text_node) {
                console.log('wrapping with tspans');
                // only fire the rest of this if the text content
                // overflows the desired dimensions
                if(this.getBBox().width > max_width) {
                    // store whatever is inside the text node
                    // in a variable and then zero out the
                    // initial content; we'll reinsert in a moment
                    // using tspan elements.
                    var text_node = d3.select(this);
                    var text_to_wrap = text_node.text();
                    text_node.text('');

                    if(text_to_wrap) {
                        // split at spaces to create an array of individual words
                        var text_to_wrap_array;
                        if(text_to_wrap.indexOf(' ') !== -1) {
                            text_to_wrap_array = text_to_wrap.split(' ');
                        } else {
                            // if there are no spaces, chop it in half.
                            // this is a hack! better to apply
                            // CSS word-break: break-all in order
                            // to handle long single-word strings that
                            // might overflow without spaces. will fix to
                            // dynamically compute the appropriate number
                            // of cuts later.
                            var string_length = text_to_wrap.length;
                            var midpoint = parseInt(string_length / 2);
                            var first_half = text_to_wrap.substr(0, midpoint);
                            var second_half = text_to_wrap.substr(midpoint, string_length);
                            text_to_wrap_array = [first_half, second_half];
                        }

                        // new array where we'll store the words re-assembled into
                        // substrings that have been tested against the desired
                        // maximum wrapping width
                        var substrings = [];
                        // computed text length is arguably incorrectly reported for
                        // all tspans after the first one, in that they will include
                        // the width of previous separate tspans. to compensate we need
                        // to manually track the computed text length of all those
                        // previous tspans and substrings, and then use that to offset
                        // the miscalculation. this then gives us the actual correct
                        // position we want to use in rendering the text in the SVG.
                        var offset = 0;
                        // loop through the words and test the computed text length
                        // of the string against the maximum desired wrapping width
                        for(var i = 0; i < text_to_wrap_array.length; i++) {
                            var word = text_to_wrap_array[i];
                            var previous_string = text_node.text();
                            var previous_width = this.getComputedTextLength();
                            // initialize the current word as the first word
                            // or append to the previous string if one exists
                            var new_string;
                            if(previous_string) {
                                new_string = previous_string + ' ' + word;
                            } else {
                                new_string = word;
                            }
                            // add the newest substring back to the text node and
                            // measure the length
                            text_node.text(new_string);
                            var new_width = this.getComputedTextLength();
                            // adjust the length by the offset we've tracked
                            // due to the misreported length discussed above
                            var test_width = new_width - offset;
                            // if our latest version of the string is too
                            // big for the bounds, use the previous
                            // version of the string (without the newest word
                            // added) and use the latest word to restart the
                            // process with a new tspan
                            if(test_width > max_width) {
                                if(previous_string !== '') {
                                    var temp = {string: previous_string, width: previous_width - offset};
                                    substrings.push(temp);
                                    offset = offset + previous_width;
                                    text_node.text('');
                                    text_node.text(word);
                                }
                            }
                            // if we're up to the last word in the array,
                            // get the computed length as is without
                            // appending anything
                            if(i == text_to_wrap_array.length - 1) {
                                var final_string = new_string.substr(previous_string.length);
                                // remove leading spaces if present
                                if(final_string.substr(0, 1) == ' ') {
                                    final_string = final_string.substr(1);
                                }
                                if(final_string !== '') {
                                    if((new_width - offset) > 0) {new_width = new_width - offset}
                                    var temp = {string: final_string, width: new_width};
                                    substrings.push(temp);
                                    text_node.text('');
                                }
                            }
                        }
                        // double check that there are no empty substrings
                        // because those would create blank tspans
                        var substrings_clean = [];
                        for(var i = 0; i < substrings.length; i++) {
                            if(substrings[i].string.length > 0) {
                                substrings_clean.push(substrings[i]);
                            }
                        }
                        // append each substring as a tspan
                        var current_tspan;
                        var tspan_count;
                        for(var i = 0; i < substrings_clean.length; i++) {
                            var substring = substrings_clean[i].string;
                            if(i > 0) {
                                var previous_substring = substrings_clean[i - 1];
                            }
                            current_tspan = text_node.append('tspan')
                                .text(substring)
                            ;
                            current_tspan
                                .attr('dy', function(d) {
                                    if(i > 0) {
                                        // line height shouldn't be hard coded
                                        // will fix this later
                                        return '1.5em';
                                    }
                                })
                            ;
                            // shift left from default position, which
                            // is probably based on the full length of the
                            // text string until we make this adjustment
                            current_tspan
                                .attr('dx', function() {
                                    var render_offset = 0;
                                    if(i > 0) {
                                        render_offset = 0;
                                        for(j = 0; j < i; j++) {
                                            render_offset += substrings[j].width;
                                        }
                                    }
                                    return render_offset * -1;
                                })
                            ;
                        }
                    }
                }
            }

            // variable used to hold the functions that let us
            // switch between the wrap methods
            var wrap_method;

            // if a wrap method if being forced, assign that
            // function
            if(force_wrap_method) {
                if(force_wrap_method == 'foreignobjects') {
                    wrap_method = wrap_with_foreignobjects;
                } else if (force_wrap_method == 'tspans') {
                    wrap_method = wrap_with_tspans;
                }
            }

            // if no wrap method is being forced, then instead
            // test for browser support of foreignobject and
            // use whichever wrap method makes sense accordingly
            if(!force_wrap_method) {
                if(typeof SVGForeignObjectElement !== 'undefined') {
                    wrap_method = wrap_with_foreignobjects;
                } else {
                    wrap_method = wrap_with_tspans;
                }
            }

            // run the desired wrap function for each item
            // in the d3 selection that called .textwrap()
            for(var i = 0; i < selection.length; i++) {
                var item = selection[i];
                wrap_method(item);
            }

        }

    }

})();
<|endoftext|>


<commit_before>var _ = require('underscore');

module.exports = function(robot){

	robot.welcome = {

		welcomeMessage: "*Hello {{user}}!*\nWelcome to BOC's Slack!  I'm *bocbot*.  Pleased to make your acquaintance!\nYou can type `bocbot help` to learn what I can do.",

		// Sets all the users that bocbot currently knows about as 'KnownUsers'
		setKnownUsers: function(){
			var users = _.map(robot.brain.users(), function(user){
				return user.id;
			});
			robot.brain.setObject('KnownUsers', users);
		},

		// Gets all of bocbot's 'KnownUsers'
		getKnownUsers: function(){
			return robot.brain.getObject('KnownUsers');
		},

		// Checks to see if a user is new
		isNewUser: function(user){
			var users = robot.brain.getObject('KnownUsers');
			if (!_.contains(users, user.id))
				return true;
			else
				return false;
		},

		// Handles a user entering a room
		roomEntrance: function(user, room){
			var isNewUser = this.isNewUser(user);
			if (isNewUser){
				var message = this.welcomeMessage.replace(/{{user}}/g, robot.util.capitalize(user.name));
				robot.messageRoom(room, message);
				robot.welcome.setKnownUsers();
			}
		}
	};

	robot.enter(function(res){
		robot.welcome.roomEntrance(res.message.user, res.message.room);
	});

	robot.respond(/set known users/i, function(res){
		robot.welcome.setKnownUsers();
	});
}
<commit_msg>Update welcome message<commit_after>var _ = require('underscore');

module.exports = function(robot){

	robot.welcome = {

		welcomeMessage: "*Hello {{user}}!*\n" +
						"Welcome to BOC's Slack!  I'm *bocbot*.  Pleased to make your acquaintance!\n" +
						"We have lots of channels so come on in and make yourself at home." +
						"If you have any questions about me or what I can do, you can type `bocbot help` or just DM me `help`.",

		// Sets all the users that bocbot currently knows about as 'KnownUsers'
		setKnownUsers: function(){
			var users = _.map(robot.brain.users(), function(user){
				return user.id;
			});
			robot.brain.setObject('KnownUsers', users);
		},

		// Gets all of bocbot's 'KnownUsers'
		getKnownUsers: function(){
			return robot.brain.getObject('KnownUsers');
		},

		// Checks to see if a user is new
		isNewUser: function(user){
			var users = robot.brain.getObject('KnownUsers');
			if (!_.contains(users, user.id))
				return true;
			else
				return false;
		},

		// Handles a user entering a room
		roomEntrance: function(user, room){
			var isNewUser = this.isNewUser(user);
			if (isNewUser){
				var message = this.welcomeMessage.replace(/{{user}}/g, robot.util.capitalize(user.name));
				robot.messageRoom(room, message);
				robot.welcome.setKnownUsers();
			}
		}
	};

	robot.enter(function(res){
		robot.welcome.roomEntrance(res.message.user, res.message.room);
	});

	robot.respond(/set known users/i, function(res){
		robot.welcome.setKnownUsers();
	});
}
<|endoftext|>


<commit_before>import { vec3, mat4 } from 'gl-matrix';

export class Camera {
  constructor(position, lookAt, up) {
    this.forward = vec3.create();
    this.up = vec3.create();
    this.right = vec3.create();

    this.position = position;

    vec3.subtract(this.forward, lookAt, this.position);
    vec3.cross(this.right, this.forward, up);
    vec3.cross(this.up, this.right, this.forward);

    vec3.normalize(this.forward, this.forward);
    vec3.normalize(this.right, this.right);
    vec3.normalize(this.up, this.up);
  }

  getViewMatrix (out) {
    const lookAt = vec3.create();
    vec3.add(lookAt, this.position, this.forward);
    mat4.lookAt(out, this.position, lookAt, this.up);
    return out;
  }

  rotateRight(rad) {
  const rightMatrix = mat4.create();
    mat4.rotate(rightMatrix, rightMatrix, rad, vec3.fromValues(0, 0, 1));
    vec3.transformMat4(this.forward, this.forward, rightMatrix);
    this.realign();
  }

  realign() {
    vec3.cross(this.right, this.forward, this.up);
    vec3.cross(this.up, this.right, this.forward);

    vec3.normalize(this.forward, this.forward);
    vec3.normalize(this.right, this.right);
    vec3.normalize(this.up, this.up);
  };

  moveForward(dist) {
    vec3.scaleAndAdd(this.position, this.position, this.forward, dist);
  }

  moveRight(dist) {
    vec3.scaleAndAdd(this.position, this.position, this.right, dist);
  }

  moveUp(dist) {
    vec3.scaleAndAdd(this.position, this.position, this.up, dist);
  }
}
<commit_msg>:beginner: camera fixes kurwaaaaa<commit_after>import { vec3, mat4 } from 'gl-matrix';

export class Camera {
  constructor(position, lookAt, up) {
    this.forward = vec3.create();
    this.up = vec3.create();
    this.right = vec3.create();

    this.position = position;

    vec3.subtract(this.forward, lookAt, this.position);
    vec3.cross(this.right, this.forward, up);
    vec3.cross(this.up, this.right, this.forward);

    vec3.normalize(this.forward, this.forward);
    vec3.normalize(this.right, this.right);
    vec3.normalize(this.up, this.up);
  }

  getViewMatrix (out) {
    const lookAt = vec3.create();
    vec3.add(lookAt, this.position, this.forward);
    mat4.lookAt(out, this.position, lookAt, this.up);
    return out;
  }

  rotateRight(rad) {
  const rightMatrix = mat4.create();
    mat4.rotate(rightMatrix, rightMatrix, rad, vec3.fromValues(0, 0, 1));
    vec3.transformMat4(this.forward, this.forward, rightMatrix);
    this.realign();
  }

  realign() {
    vec3.cross(this.right, this.forward, this.up);
    vec3.cross(this.up, this.right, this.forward);

    vec3.normalize(this.forward, this.forward);
    vec3.normalize(this.right, this.right);
    vec3.normalize(this.up, this.up);
  }

  moveForward(dist) {
    vec3.scaleAndAdd(this.position, this.position, this.forward, dist);
  }

  moveRight(dist) {
    vec3.scaleAndAdd(this.position, this.position, this.right, dist);
  }

  moveUp(dist) {
    vec3.scaleAndAdd(this.position, this.position, this.up, dist);
  }
}
<|endoftext|>


<commit_before>//////////////////////////////////////////////////////////////////////////////
/**
 * Create a new instance of class object
 *
 * @class
 * @extends vgl.object
 * @returns {geo.object}
 */
//////////////////////////////////////////////////////////////////////////////
geo.object = function () {
  "use strict";
  if (!(this instanceof geo.object)) {
    return new geo.object();
  }

  var m_this = this,
      m_eventHandlers = {},
      m_idleHandlers = [],
      m_deferredCount = 0;

  //////////////////////////////////////////////////////////////////////////////
  /**
   *  Bind a handler that will be called once when all deferreds are resolved.
   *
   *  @param {function} handler A function taking no arguments
   *  @returns {geo.object[]|geo.object} this
   */
  //////////////////////////////////////////////////////////////////////////////
  this.onIdle = function (handler) {
    if (m_deferredCount) {
      m_idleHandlers.push(handler);
    } else {
      handler();
    }
    return m_this;
  };

  //////////////////////////////////////////////////////////////////////////////
  /**
   *  Add a new deferred object preventing idle event handlers from being called.
   *
   *  @param {$.defer} defer A jquery defered object
   */
  //////////////////////////////////////////////////////////////////////////////
  this.addDeferred = function (defer) {
    m_deferredCount += 1;
    defer.done(function () {
      m_deferredCount -= 1;
      if (!m_deferredCount) {
        m_idleHandlers.splice(0, m_idleHandlers.length)
          .forEach(function (handler) {
            handler();
          });
      }
    });
    return m_this;
  };

  //////////////////////////////////////////////////////////////////////////////
  /**
   *  Bind an event handler to this object
   *
   *  @param {String} event
   *    An event from {geo.events}
   *  @param {function} handler
   *    A function that will be called when ``event`` is triggered.  The
   *    function will be given an event object as a first parameter and
   *    optionally a second argument provided by the triggerer.
   */
  //////////////////////////////////////////////////////////////////////////////
  this.geoOn = function (event, handler) {
    if (Array.isArray(event)) {
      event.forEach(function (e) {
        m_this.geoOn(e, handler);
      });
      return m_this;
    }
    if (!m_eventHandlers.hasOwnProperty(event)) {
      m_eventHandlers[event] = [];
    }
    m_eventHandlers[event].push(handler);
    return m_this;
  };

  //////////////////////////////////////////////////////////////////////////////
  /**
   *  Trigger an event (or events) on this object and call all handlers
   *
   *  @param {String} event An event from {geo.events}
   *  @param {Object} args An optional argument to pass to handlers
   */
  //////////////////////////////////////////////////////////////////////////////
  this.geoTrigger = function (event, args) {

    // if we have an array of events, recall with single events
    if (Array.isArray(event)) {
      event.forEach(function (e) {
        m_this.geoTrigger(e, args);
      });
      return m_this;
    }

    if (m_eventHandlers.hasOwnProperty(event)) {
      m_eventHandlers[event].forEach(function (handler) {
        handler.call(m_this, args);
      });
    }

    return m_this;
  };

  //////////////////////////////////////////////////////////////////////////////
  /**
   *  Remove handlers from an event (or an array of events).  If no event is
   *  provided all hanlders will be removed.
   *
   *  @param {string?} event An event from {geo.events}
   *  @param {object?} arg A function or array of functions to remove from the events
   *                      or if falsey remove all handlers from the events
   */
  //////////////////////////////////////////////////////////////////////////////
  this.geoOff = function (event, arg) {
    if (event === undefined) {
      m_eventHandlers = {};
      m_idleHandlers = [];
      m_deferredCount = 0;
    }
    if (Array.isArray(event)) {
      event.forEach(function (e) {
        m_this.geoOff(e, arg);
      });
      return m_this;
    }
    if (!arg) {
      m_eventHandlers[event] = [];
    } else if (Array.isArray(arg)) {
      arg.forEach(function (handler) {
        m_this.geoOff(event, handler);
      });
      return m_this;
    }
    // What do we do if the handler is not already bound?
    //   ignoring for now...
    if (m_eventHandlers.hasOwnProperty(event)) {
      m_eventHandlers[event] = m_eventHandlers[event].filter(function (f) {
          return f !== arg;
        }
      );
    }
    return m_this;
  };

  //////////////////////////////////////////////////////////////////////////////
  /**
   * Free all resources and destroy the object.
   */
  //////////////////////////////////////////////////////////////////////////////
  this._exit = function () {
    m_this.geoOff();
  };

  vgl.object.call(this);

  return this;
};

inherit(geo.object, vgl.object);
<commit_msg>Adding event type to event objects<commit_after>//////////////////////////////////////////////////////////////////////////////
/**
 * Create a new instance of class object
 *
 * @class
 * @extends vgl.object
 * @returns {geo.object}
 */
//////////////////////////////////////////////////////////////////////////////
geo.object = function () {
  "use strict";
  if (!(this instanceof geo.object)) {
    return new geo.object();
  }

  var m_this = this,
      m_eventHandlers = {},
      m_idleHandlers = [],
      m_deferredCount = 0;

  //////////////////////////////////////////////////////////////////////////////
  /**
   *  Bind a handler that will be called once when all deferreds are resolved.
   *
   *  @param {function} handler A function taking no arguments
   *  @returns {geo.object[]|geo.object} this
   */
  //////////////////////////////////////////////////////////////////////////////
  this.onIdle = function (handler) {
    if (m_deferredCount) {
      m_idleHandlers.push(handler);
    } else {
      handler();
    }
    return m_this;
  };

  //////////////////////////////////////////////////////////////////////////////
  /**
   *  Add a new deferred object preventing idle event handlers from being called.
   *
   *  @param {$.defer} defer A jquery defered object
   */
  //////////////////////////////////////////////////////////////////////////////
  this.addDeferred = function (defer) {
    m_deferredCount += 1;
    defer.done(function () {
      m_deferredCount -= 1;
      if (!m_deferredCount) {
        m_idleHandlers.splice(0, m_idleHandlers.length)
          .forEach(function (handler) {
            handler();
          });
      }
    });
    return m_this;
  };

  //////////////////////////////////////////////////////////////////////////////
  /**
   *  Bind an event handler to this object
   *
   *  @param {String} event
   *    An event from {geo.events}
   *  @param {function} handler
   *    A function that will be called when ``event`` is triggered.  The
   *    function will be given an event object as a first parameter and
   *    optionally a second argument provided by the triggerer.
   */
  //////////////////////////////////////////////////////////////////////////////
  this.geoOn = function (event, handler) {
    if (Array.isArray(event)) {
      event.forEach(function (e) {
        m_this.geoOn(e, handler);
      });
      return m_this;
    }
    if (!m_eventHandlers.hasOwnProperty(event)) {
      m_eventHandlers[event] = [];
    }
    m_eventHandlers[event].push(handler);
    return m_this;
  };

  //////////////////////////////////////////////////////////////////////////////
  /**
   *  Trigger an event (or events) on this object and call all handlers
   *
   *  @param {String} event An event from {geo.event}
   *  @param {Object} args An optional argument to pass to handlers
   */
  //////////////////////////////////////////////////////////////////////////////
  this.geoTrigger = function (event, args) {

    // if we have an array of events, recall with single events
    if (Array.isArray(event)) {
      event.forEach(function (e) {
        m_this.geoTrigger(e, args);
      });
      return m_this;
    }

    // append the event type to the argument object
    args = args || {};
    args.event = event;

    if (m_eventHandlers.hasOwnProperty(event)) {
      m_eventHandlers[event].forEach(function (handler) {
        handler.call(m_this, args);
      });
    }

    return m_this;
  };

  //////////////////////////////////////////////////////////////////////////////
  /**
   *  Remove handlers from an event (or an array of events).  If no event is
   *  provided all hanlders will be removed.
   *
   *  @param {string?} event An event from {geo.events}
   *  @param {object?} arg A function or array of functions to remove from the events
   *                      or if falsey remove all handlers from the events
   */
  //////////////////////////////////////////////////////////////////////////////
  this.geoOff = function (event, arg) {
    if (event === undefined) {
      m_eventHandlers = {};
      m_idleHandlers = [];
      m_deferredCount = 0;
    }
    if (Array.isArray(event)) {
      event.forEach(function (e) {
        m_this.geoOff(e, arg);
      });
      return m_this;
    }
    if (!arg) {
      m_eventHandlers[event] = [];
    } else if (Array.isArray(arg)) {
      arg.forEach(function (handler) {
        m_this.geoOff(event, handler);
      });
      return m_this;
    }
    // What do we do if the handler is not already bound?
    //   ignoring for now...
    if (m_eventHandlers.hasOwnProperty(event)) {
      m_eventHandlers[event] = m_eventHandlers[event].filter(function (f) {
          return f !== arg;
        }
      );
    }
    return m_this;
  };

  //////////////////////////////////////////////////////////////////////////////
  /**
   * Free all resources and destroy the object.
   */
  //////////////////////////////////////////////////////////////////////////////
  this._exit = function () {
    m_this.geoOff();
  };

  vgl.object.call(this);

  return this;
};

inherit(geo.object, vgl.object);
<|endoftext|>


<commit_before>exports.up = function (knex, Promise) {

}

exports.down = function (knex, Promise) {

}
<commit_msg>FK constraint<commit_after>let tableName = 'messages'

exports.up = function (knex, Promise) {
  return knex
    .schema
    .createTable(tableName, (tbl) => {
      tbl.increments()
      tbl
        .string('message', 512)
        .notNullable()
      tbl
        .integer('user_id')
        .references('id')
        .inTable('users')
        .notNullable()
    })
}

exports.down = function (knex, Promise) {
  return knex.schema.dropTableIfExists(tableName)
}
<|endoftext|>


<commit_before>/*6*/
// var requireInfo = [];
function GetInfo(id, from, unsubscribe,senderName){
  this.id = id;
  this.from = from;
  this.unsubscribe = unsubscribe;
  this.senderName = senderName;
};

var lookUpTable = {};
function noSubscribeHeader(currMessage) {
  var raw;
  if (currMessage.payload.parts) {
    if (currMessage.payload.parts.length > 1) {
      if (currMessage.payload.parts[1].body.data) {
        try {
          raw = currMessage.payload.parts[1].body.data.split(/[-_]/);
        }
        catch(e) {
        }
      }
    } else {
      if (currMessage.payload.parts[0].parts) {
        try {
          raw = currMessage.payload.parts[0].parts[1].body.data.split(/[-_]/);
        }
        catch(e) {
        }
      }
    }
  } else {
    try {
      raw = currMessage.payload.body.data.split(/[-_]/);
    }
    catch(e) {
    }
  }
  if (raw) {
    var newString = raw.reduce(function(acc, next) {
      try {
        return acc + (atob(next));
      }
      catch(e) {
      }
    }, '');
    var unsubscribePosition = newString.search('unsubscribe');
    if (unsubscribePosition === -1) {
      unsubscribePosition = newString.search('opt out');
    }
    var cutTo;
    if (unsubscribePosition - 1200 < 0) {
      cutTo = 0;
    } else {
      cutTo = unsubscribePosition - 1200;
    }
    var linkString = newString.slice(cutTo, unsubscribePosition);
    var allHrefs = linkString.split('href="');
    var link;
    if (allHrefs.length === 1) {
      if (unsubscribePosition + 1200 > newString.length) {
        cutTo = newString.length - 1;
      } else {
        cutTo = unsubscribePosition + 1200;
      }
      linkString = newString.slice(unsubscribePosition, cutTo);
      allHrefs = linkString.split('href="');
      if (allHrefs.length !== 1) {
        link = allHrefs[1].split('"')[0];
      }
    } else {
      link = allHrefs[allHrefs.length - 1].split('"')[0];
    }
  }
  return link;
};

var generateInfo = function(resp){ //eslint-disable-line
  var id = resp.id;
  var from = resp.payload.headers.reduce(function(curr, next) {
    if (next.name === 'From' || next.name === 'sender') {
      curr.push(next.value);
    }
    return curr;
  },[])[0];
  var unsubscribe = resp.payload.headers.filter(function(itemH) {
    return itemH.name === 'List-Unsubscribe';
  })[0];
  if (!unsubscribe) {
    unsubscribe = noSubscribeHeader(resp);
  } else {
    unsubscribe = unsubscribe.value;
    if(unsubscribe.includes('<')){
      unsubscribe = (unsubscribe.split('<')[1]).split('>')[0];
    };
  };
  var possibleSenderName = from.split('@')[1].split('.');
  senderName = (from.split('@')[1].split('.'))[possibleSenderName.length - 2];
  if(senderName.length < 3){
    senderName = (from.split('@')[1].split('.'))[possibleSenderName.length - 3];
  };
  if(from.includes('<')){
    from = (from.split('<')[1]).split('>')[0];
  };
  if(unsubscribe){
    if(lookUpTable.hasOwnProperty(senderName)){}
    else{
      lookUpTable[senderName] = true;
      var template = Handlebars.compile($('#unsubscribe-template').html());
      var uniqueObj = new GetInfo(id, from, unsubscribe, senderName);
      $('#unsubscribe-page').append(template(uniqueObj));
    };
  };
};
<commit_msg>Changed senderName to be the sender's name instead of domain.<commit_after>/*6*/
// var requireInfo = [];
function GetInfo(id, from, unsubscribe,senderName){
  this.id = id;
  this.from = from;
  this.unsubscribe = unsubscribe;
  this.senderName = senderName;
};

var lookUpTable = {};
function noSubscribeHeader(currMessage) {
  var raw;
  if (currMessage.payload.parts) {
    if (currMessage.payload.parts.length > 1) {
      if (currMessage.payload.parts[1].body.data) {
        try {
          raw = currMessage.payload.parts[1].body.data.split(/[-_]/);
        }
        catch(e) {
        }
      }
    } else {
      if (currMessage.payload.parts[0].parts) {
        try {
          raw = currMessage.payload.parts[0].parts[1].body.data.split(/[-_]/);
        }
        catch(e) {
        }
      }
    }
  } else {
    try {
      raw = currMessage.payload.body.data.split(/[-_]/);
    }
    catch(e) {
    }
  }
  if (raw) {
    var newString = raw.reduce(function(acc, next) {
      try {
        return acc + (atob(next));
      }
      catch(e) {
      }
    }, '');
    var unsubscribePosition = newString.search('unsubscribe');
    if (unsubscribePosition === -1) {
      unsubscribePosition = newString.search('opt out');
    }
    var cutTo;
    if (unsubscribePosition - 1200 < 0) {
      cutTo = 0;
    } else {
      cutTo = unsubscribePosition - 1200;
    }
    var linkString = newString.slice(cutTo, unsubscribePosition);
    var allHrefs = linkString.split('href="');
    var link;
    if (allHrefs.length === 1) {
      if (unsubscribePosition + 1200 > newString.length) {
        cutTo = newString.length - 1;
      } else {
        cutTo = unsubscribePosition + 1200;
      }
      linkString = newString.slice(unsubscribePosition, cutTo);
      allHrefs = linkString.split('href="');
      if (allHrefs.length !== 1) {
        link = allHrefs[1].split('"')[0];
      }
    } else {
      link = allHrefs[allHrefs.length - 1].split('"')[0];
    }
  }
  return link;
};

var generateInfo = function(resp){ //eslint-disable-line
  var id = resp.id;
  var from = resp.payload.headers.reduce(function(curr, next) {
    if (next.name === 'From' || next.name === 'sender') {
      curr.push(next.value);
    }
    return curr;
  },[])[0];
  var unsubscribe = resp.payload.headers.filter(function(itemH) {
    return itemH.name === 'List-Unsubscribe';
  })[0];
  if (!unsubscribe) {
    unsubscribe = noSubscribeHeader(resp);
  } else {
    unsubscribe = unsubscribe.value;
    if(unsubscribe.includes('<')){
      unsubscribe = (unsubscribe.split('<')[1]).split('>')[0];
    };
  };
  senderName = from.split('<')[0];
  if(from.includes('<')){
    from = (from.split('<')[1]).split('>')[0];
  };
  if(unsubscribe){
    if(lookUpTable.hasOwnProperty(senderName)){}
    else{
      lookUpTable[senderName] = true;
      var template = Handlebars.compile($('#unsubscribe-template').html());
      var uniqueObj = new GetInfo(id, from, unsubscribe, senderName);
      $('#unsubscribe-page').append(template(uniqueObj));
    };
  };
};
<|endoftext|>


<commit_before>/**
 * jQuery Steam - TF2Items plugin
 *
 * Licensed under MIT LICENSE:
 *  http://www.opensource.org/licenses/mit-license.php
 *
 * Depencies:
 *  jQuery [of course]
 *  WebAPI plugin
 *
 * With the methods provided by this plugin, you will be
 * able to have an always-updated player backpack. Since
 * the data is REALLY big, it will take some time to load
 * the data [about 5 seconds on a good connection].
 *
 * Some stuff on this plugin are really ugly, I'll try to
 * update it ASAP.
 */

(function($){
  var tf2items = (function(){
    // Game defines
    var classes = ['scout', 'sniper', 'soldier', 'demoman', 'medic', 'heavy', 'pyro', 'spy', 'engineer'];
    var particles = ['particle0', 'particle1', 'particle2', 'particle3', 'particle4', 'particle5',
                     'Green Confetti',  'Purple Confetti', 'Haunted Ghosts', 'Green Energy', 'Purple Energy',
                     'Circling TF Logo', 'Massed Flies', 'Burning Flames', 'Scorching Flames', 'Searing Plasma',
                     'Vivid Plasma', 'Sunbeams', 'Circling Peace Sign', 'Circling Heart'];

    // Attributes that shouldn't be added to items
    var attBlackList = [25, 37, 116, 142, 195, 196];
    // Items that have limited uses number
    var limitedItems = [241, 280, 281, 282, 283, 284, 286, 287, 288, 289, 290, 291,
                        5020, 5021, 5023, 5027, 5028, 5029, 5030, 5031, 5032, 5033,
                        5034, 5035, 5036, 5037, 5038, 5039, 5040, 5042, 5043, 5044];
    // Strings that come unstralated from the server
    var unstraslated = {
      TF_Wearable_Hat: 'Hat',
      TF_LockedCrate: 'Crate'
    };

    var schema = null;
    var language = null;

    // Parse the inventory token to its real values
    // http://wiki.teamfortress.com/wiki/WebAPI#Inventory_Token
    var parseInventory = function(inv) {
      var val = Number(inv);
      var equipped = [];
      for(var n = 0; n < 9; n++) {
        if((val & 1 << (n + 16))) {
          equipped.push(classes[n]);
        }
      }

      return {
          position: val & 0xFFFF,
          equipped: equipped
      };
    };

    // Load the item data, parsing the Schema value
    var loadSchema = function(callback) {
      if(schema) {
        callback();
      }
      else {
        $.steam.webapi.tf2.schema((language || 'en'), function(data) {
          var items = data.result.items.item;
          var qualities = data.result.qualities;
          var qualityNames = data.result.qualityNames;
          var attributes = data.result.attributes.attribute;

          schema = {
            qualities: [],
            items: [],
            attributes: {}
          };

          for(var q in qualities) {
            schema.qualities[qualities[q]] = {
              code: q,
              name: qualityNames[q]
            };
          }

          for(var i in items) {
            var itm = items[i];
            schema.items[itm.defindex] = itm;
          }

          // Store the attributes indexed by their names. This optimizes the search
          for(var a in attributes) {
            var att = attributes[a];
            schema.attributes[att.name] = att;
          }

          callback();
        });
      }
    };

    // Build the item name
    var buildName = function(defindex, quality) {
      var ret = '';

      if(schema) {
        var name = schema.items[defindex].item_name;

        // This will allow us to get default item data
        if(quality) {
          var title = schema.qualities[quality].name;
          // If we the quality has a title [not unique], use it
          if(title !== 'Unique') {
            // If the name is like "The Cloack and Dagger" we insert the title after "The "
            // We could use schema.items[itm.defindex].proper_name to check this, but it's buggy
            if(name.indexOf("The ") === 0) {
              ret += "The ";
              name = name.substr(4);
            }

            ret += title + ' ';
          }
        }

        ret +=  name;
      }

      return ret;
    };

    // Get the list of item attributes
    var parseAttributes = function(defindex, extra) {
      var ret = [];

      if(schema) {
        // If the item has default attributes, parse them
        if(schema.items[defindex].attributes) {
          var attributes = schema.items[defindex].attributes.attribute;
          for(var a in attributes) {
            var att = attributes[a];
            var data = schema.attributes[att.name];
            if(validAttribute(data.defindex, att)) {
              ret.push({
                name: buildAttribute({
                  string: data.description_string || null,
                  format: data.description_format || null,
                  value: att.value,
                  float_value: att.float_value
                }),
                type: data.effect_type
              });
            }
          }
        }
        // Read extras attributes, if any
        if(extra) {
          var n = 0;
          for(var e in extra) {
            var att = extra[e];
            // Bypass the crate series from the item
            if(validAttribute(att.defindex, att) && att.defindex != 187) {
              var data = searchAttribute(att.defindex);
              ret.push({
                name: buildAttribute({
                  string: data.description_string || null,
                  format: data.description_format || null,
                  value: att.value,
                  float_value: att.float_value
                }),
                type: data.effect_type
              });
            }
          }
        }
      }
      return ret;
    };

    // Check if the attribute info matches with any bad one
    var validAttribute = function(defindex, data) {
      if(!(data.value || data.float_value)) {
        return false;
      }
      for(var i in attBlackList) {
        if(attBlackList[i] == defindex) {
          return false;
        }
      }
      return true;
    }

    // Get the attribute by its defindex
    var searchAttribute = function(defindex) {
      for(var a in schema.attributes) {
        if(schema.attributes[a].defindex == defindex){
          return schema.attributes[a];
        }
      }
    };

    // Build the attribute value string
    var buildAttribute = function(data) {
      var ret = '';

      if(data.format) {
        var value;

        switch(data.format) {
          case 'value_is_percentage':
            value = Math.round((data.value*100) - 100);
          break;
          case 'value_is_inverted_percentage':
            value = Math.round((100 - (data.value*100)));
          break;
          case 'value_is_additive':
            value = data.value;
          break;
          case 'value_is_additive_percentage':
            value = Math.round(data.value*100);
          break;
          case 'value_is_date':
            var date = new Date(data.value*1000);
            value = date.toLocaleDateString();
          break;
          case 'value_is_account_id':
            value = 'STEAM_0:'+(data.value%2)+':'+(data.value/2);
          break;
          case 'value_is_particle_index':
            value = particles[data.float_value];
          break;
          default:
            value = "V:"+data.value+"F:"+data.format;
        }

        ret = data.string.replace("%s1", value);
      }
      else {
        ret = data.string;
      }

      return ret;
    };

    // Check and get the color of a painted item
    var itemPainted = function(itm) {
      var data = schema.items[itm.defindex];
      var attributes = data.attributes ? data.attributes.attribute : null;
      for(var a in attributes) {
        var att = attributes[a];
        if(att.name == 'set item tint RGB') {
          return parseInt(att.value).toString(16);
        }
      }
      var extra = itm.attributes ? itm.attributes.attribute : null;
      for(var e in extra) {
        var att = extra[e];
        if(att.defindex == 142) {
          return parseInt(att.float_value).toString(16);
        }
      }

      return false;
    };

    // Check and get who gifted the item
    var itemGifted = function(itm) {
      var attributes = itm.attributes ? itm.attributes.attribute : null;
      for(var a in attributes) {
        var att = attributes[a];
        if(att.defindex == 186) {
          return att.value + 76561197960265728;
        }
      }

      return false;
    };

    var itemLimited = function(defindex) {
      for(var i in limitedItems) {
        if(limitedItems[i] == defindex) {
          return true;
        }
      }
      return false;
    };

    var fixString = function(str) {
      if(unstraslated[str]) {
        return unstraslated[str];
      }

      return str;
    }

    return {
      // Get and parse the items for a specific backpack
      getItems: function(id, callback) {
        $.steam.webapi.tf2.playerItems(id, function(data){
          var items = data.result.items.item;
          // Ensure that our schema is loaded
          loadSchema(function() {
            var ret = [];
            // Parse the item data, to be readable
            var a = 0;
            for(var i in items) {
              var itm = items[i];
              var inv = parseInventory(itm.inventory);
              var data = schema.items[itm.defindex];
              var att = itm.attributes ? itm.attributes.attribute : null;
              var cur = {
                position: inv.position,
                name: buildName(itm.defindex, itm.quality),
                custom_name: itm.custom_name,
                type: fixString(data.item_type_name),
                level: itm.level,
                quality: schema.qualities[itm.quality].code,
                quantity: itemLimited(itm.defindex) ? itm.quantity : null,
                equipped: inv.equipped,
                // Itens for all classes dont have 'used_by_classes', so use the classes array
                used_by: data.used_by_classes || classes,
                // Check and negate if something block the item from be traded
                tradable: !(itm.flag_cannot_trade || (typeof data.tradable === 'undefined' ? false : !data.tradable)),
                // Hardcoded: get the name of the image
                image: data.image_url.substr(46),
                // Parse some usefull data from the attributes
                color: itemPainted(itm),
                gifted: itemGifted(itm),
                attributes: parseAttributes(itm.defindex, att)
              };

              ret.push(cur);
            }

            callback(ret);
          });
        });
      },
      getItemList: function(callback) {
        // Get the schema
        loadSchema(function() {
          var items = schema.items;
          var ret = [];
          // Parse the item data, to be readable
          var a = 0;
          for(var i in items) {
            var itm = items[i];
            var att = itm.attributes;
            var cur = {
              name: buildName(itm.defindex),
              type: fixString(itm.item_type_name),
              // Itens for all classes dont have 'used_by_classes', so use the classes array
              used_by: itm.used_by_classes || classes,
              // Check and negate if something block the item from be traded
              tradable: !(itm.flag_cannot_trade),
              // Hardcoded: get the name of the image
              image: itm.image_url.substr(46),
              // Parse some usefull data from the attributes
              attributes: parseAttributes(itm.defindex)
            };

            ret.push(cur);
          }

          callback(ret);
        });
      },
      // Allow us to use multiple languages
      setLanguage: function(_language){
        if(_language === null || (_language && typeof _language === 'string')) {
          language = _language;
          // Clear the itemData cache, since it has language specific stuff
          if(schema !== null) {
            schema = null;
          }
        }
      }
    }
  })();

  // Create the steam object on jQuery and expose the tf2items
  if(!$.steam || typeof $.steam !== 'object') {
    $.steam = {};
  }
  $.steam.tf2items = tf2items;
})(jQuery);<commit_msg>fixed the particle attribute added Attrib_ElevateQuality attribute to blacklist<commit_after>/**
 * jQuery Steam - TF2Items plugin
 *
 * Licensed under MIT LICENSE:
 *  http://www.opensource.org/licenses/mit-license.php
 *
 * Depencies:
 *  jQuery [of course]
 *  WebAPI plugin
 *
 * With the methods provided by this plugin, you will be
 * able to have an always-updated player backpack. Since
 * the data is REALLY big, it will take some time to load
 * the data [about 5 seconds on a good connection].
 *
 * Some stuff on this plugin are really ugly, I'll try to
 * update it ASAP.
 */

(function($){
  var tf2items = (function(){
    // Game defines
    var classes = ['scout', 'sniper', 'soldier', 'demoman', 'medic', 'heavy', 'pyro', 'spy', 'engineer'];
    var particles = ['particle0', 'particle1', 'particle2', 'particle3', 'particle4', 'particle5',
                     'Green Confetti',  'Purple Confetti', 'Haunted Ghosts', 'Green Energy', 'Purple Energy',
                     'Circling TF Logo', 'Massed Flies', 'Burning Flames', 'Scorching Flames', 'Searing Plasma',
                     'Vivid Plasma', 'Sunbeams', 'Circling Peace Sign', 'Circling Heart'];

    // Attributes that shouldn't be added to items
    var attBlackList = [25, 37, 116, 142, 189, 195, 196];
    // Items that have limited uses number
    var limitedItems = [241, 280, 281, 282, 283, 284, 286, 287, 288, 289, 290, 291,
                        5020, 5021, 5023, 5027, 5028, 5029, 5030, 5031, 5032, 5033,
                        5034, 5035, 5036, 5037, 5038, 5039, 5040, 5042, 5043, 5044];
    // Strings that come unstralated from the server
    var unstraslated = {
      TF_Wearable_Hat: 'Hat',
      TF_LockedCrate: 'Crate'
    };

    var schema = null;
    var language = null;

    // Parse the inventory token to its real values
    // http://wiki.teamfortress.com/wiki/WebAPI#Inventory_Token
    var parseInventory = function(inv) {
      var val = Number(inv);
      var equipped = [];
      for(var n = 0; n < 9; n++) {
        if((val & 1 << (n + 16))) {
          equipped.push(classes[n]);
        }
      }

      return {
          position: val & 0xFFFF,
          equipped: equipped
      };
    };

    // Load the item data, parsing the Schema value
    var loadSchema = function(callback) {
      if(schema) {
        callback();
      }
      else {
        $.steam.webapi.tf2.schema((language || 'en'), function(data) {
          var items = data.result.items.item;
          var qualities = data.result.qualities;
          var qualityNames = data.result.qualityNames;
          var attributes = data.result.attributes.attribute;

          schema = {
            qualities: [],
            items: [],
            attributes: {}
          };

          for(var q in qualities) {
            schema.qualities[qualities[q]] = {
              code: q,
              name: qualityNames[q]
            };
          }

          for(var i in items) {
            var itm = items[i];
            schema.items[itm.defindex] = itm;
          }

          // Store the attributes indexed by their names. This optimizes the search
          for(var a in attributes) {
            var att = attributes[a];
            schema.attributes[att.name] = att;
          }

          callback();
        });
      }
    };

    // Build the item name
    var buildName = function(defindex, quality) {
      var ret = '';

      if(schema) {
        var name = schema.items[defindex].item_name;

        // This will allow us to get default item data
        if(quality) {
          var title = schema.qualities[quality].name;
          // If we the quality has a title [not unique], use it
          if(title !== 'Unique') {
            // If the name is like "The Cloack and Dagger" we insert the title after "The "
            // We could use schema.items[itm.defindex].proper_name to check this, but it's buggy
            if(name.indexOf("The ") === 0) {
              ret += "The ";
              name = name.substr(4);
            }

            ret += title + ' ';
          }
        }

        ret +=  name;
      }

      return ret;
    };

    // Get the list of item attributes
    var parseAttributes = function(defindex, extra) {
      var ret = [];

      if(schema) {
        // If the item has default attributes, parse them
        if(schema.items[defindex].attributes) {
          var attributes = schema.items[defindex].attributes.attribute;
          for(var a in attributes) {
            var att = attributes[a];
            var data = schema.attributes[att.name];
            if(validAttribute(data.defindex, att)) {
              ret.push({
                name: buildAttribute({
                  string: data.description_string || null,
                  format: data.description_format || null,
                  value: att.value,
                  float_value: att.float_value
                }),
                type: data.effect_type
              });
            }
          }
        }
        // Read extras attributes, if any
        if(extra) {
          var n = 0;
          for(var e in extra) {
            var att = extra[e];
            // Bypass the crate series from the item
            if(validAttribute(att.defindex, att) && att.defindex != 187) {
              var data = searchAttribute(att.defindex);
              ret.push({
                name: buildAttribute({
                  string: data.description_string || null,
                  format: data.description_format || null,
                  value: att.value,
                  float_value: att.float_value
                }),
                type: data.effect_type
              });
            }
          }
        }
      }
      return ret;
    };

    // Check if the attribute info matches with any bad one
    var validAttribute = function(defindex, data) {
      if(!(data.value || data.float_value)) {
        return false;
      }
      for(var i in attBlackList) {
        if(attBlackList[i] == defindex) {
          return false;
        }
      }
      return true;
    }

    // Get the attribute by its defindex
    var searchAttribute = function(defindex) {
      for(var a in schema.attributes) {
        if(schema.attributes[a].defindex == defindex){
          return schema.attributes[a];
        }
      }
    };

    // Build the attribute value string
    var buildAttribute = function(data) {
      var ret = '';

      if(data.format) {
        var value;

        switch(data.format) {
          case 'value_is_percentage':
            value = Math.round((data.value*100) - 100);
          break;
          case 'value_is_inverted_percentage':
            value = Math.round((100 - (data.value*100)));
          break;
          case 'value_is_additive':
            value = data.value;
          break;
          case 'value_is_additive_percentage':
            value = Math.round(data.value*100);
          break;
          case 'value_is_date':
            var date = new Date(data.value*1000);
            value = date.toLocaleDateString();
          break;
          case 'value_is_account_id':
            value = 'STEAM_0:'+(data.value%2)+':'+(data.value/2);
          break;
          case 'value_is_particle_index':
            value = particles[(data.value || data.float_value)] + dump(data);
          break;
          default:
            value = "V:"+data.value+"F:"+data.format;
        }

        ret = data.string.replace("%s1", value);
      }
      else {
        ret = data.string;
      }

      return ret;
    };

    // Check and get the color of a painted item
    var itemPainted = function(itm) {
      var data = schema.items[itm.defindex];
      var attributes = data.attributes ? data.attributes.attribute : null;
      for(var a in attributes) {
        var att = attributes[a];
        if(att.name == 'set item tint RGB') {
          return parseInt(att.value).toString(16);
        }
      }
      var extra = itm.attributes ? itm.attributes.attribute : null;
      for(var e in extra) {
        var att = extra[e];
        if(att.defindex == 142) {
          return parseInt(att.float_value).toString(16);
        }
      }

      return false;
    };

    // Check and get who gifted the item
    var itemGifted = function(itm) {
      var attributes = itm.attributes ? itm.attributes.attribute : null;
      for(var a in attributes) {
        var att = attributes[a];
        if(att.defindex == 186) {
          return att.value + 76561197960265728;
        }
      }

      return false;
    };

    var itemLimited = function(defindex) {
      for(var i in limitedItems) {
        if(limitedItems[i] == defindex) {
          return true;
        }
      }
      return false;
    };

    var fixString = function(str) {
      if(unstraslated[str]) {
        return unstraslated[str];
      }

      return str;
    }

    return {
      // Get and parse the items for a specific backpack
      getItems: function(id, callback) {
        $.steam.webapi.tf2.playerItems(id, function(data){
          var items = data.result.items.item;
          // Ensure that our schema is loaded
          loadSchema(function() {
            var ret = [];
            // Parse the item data, to be readable
            var a = 0;
            for(var i in items) {
              var itm = items[i];
              var inv = parseInventory(itm.inventory);
              var data = schema.items[itm.defindex];
              var att = itm.attributes ? itm.attributes.attribute : null;
              var cur = {
                position: inv.position,
                name: buildName(itm.defindex, itm.quality),
                custom_name: itm.custom_name,
                type: fixString(data.item_type_name),
                level: itm.level,
                quality: schema.qualities[itm.quality].code,
                quantity: itemLimited(itm.defindex) ? itm.quantity : null,
                equipped: inv.equipped,
                // Itens for all classes dont have 'used_by_classes', so use the classes array
                used_by: data.used_by_classes || classes,
                // Check and negate if something block the item from be traded
                tradable: !(itm.flag_cannot_trade || (typeof data.tradable === 'undefined' ? false : !data.tradable)),
                // Hardcoded: get the name of the image
                image: data.image_url.substr(46),
                // Parse some usefull data from the attributes
                color: itemPainted(itm),
                gifted: itemGifted(itm),
                attributes: parseAttributes(itm.defindex, att)
              };

              ret.push(cur);
            }

            callback(ret);
          });
        });
      },
      getItemList: function(callback) {
        // Get the schema
        loadSchema(function() {
          var items = schema.items;
          var ret = [];
          // Parse the item data, to be readable
          var a = 0;
          for(var i in items) {
            var itm = items[i];
            var att = itm.attributes;
            var cur = {
              name: buildName(itm.defindex),
              type: fixString(itm.item_type_name),
              // Itens for all classes dont have 'used_by_classes', so use the classes array
              used_by: itm.used_by_classes || classes,
              // Check and negate if something block the item from be traded
              tradable: !(itm.flag_cannot_trade),
              // Hardcoded: get the name of the image
              image: itm.image_url.substr(46),
              // Parse some usefull data from the attributes
              attributes: parseAttributes(itm.defindex)
            };

            ret.push(cur);
          }

          callback(ret);
        });
      },
      // Allow us to use multiple languages
      setLanguage: function(_language){
        if(_language === null || (_language && typeof _language === 'string')) {
          language = _language;
          // Clear the itemData cache, since it has language specific stuff
          if(schema !== null) {
            schema = null;
          }
        }
      }
    }
  })();

  // Create the steam object on jQuery and expose the tf2items
  if(!$.steam || typeof $.steam !== 'object') {
    $.steam = {};
  }
  $.steam.tf2items = tf2items;
})(jQuery);<|endoftext|>