<commit_before>/* jshint: */
var createHAR = require('../../createHAR.js');

var version = require('../../../package.json').version;

/*
    request all external contents

    collect sizes, width and without GZIP

    "entries": [
          {
            "startedDateTime": "2013-12-02T09:34:02.260Z",
            "time": null,
            "request": {
              "method": "GET",
              "url": "http://helios.finn.no/addyn%7C3.0%7C989.1%7C4489198%7C0%7C16%7CADTECH;cfp=1;rndc=1385976829;cookie=info;loc=100;target=_blank;alias=car%2Fused%2Fsearch%2Flist_mm;grp=154176158;kvuserid=854203993;misc=414222659203370",
              "httpVersion": "HTTP/1.1",
              "cookies": [],
              "headers": [],
              "queryString": [],
              "headersSize": -1,
              "bodySize": -1
            },
            "response": {
              "status": 200,
              "statusText": "OK",
              "httpVersion": "HTTP/1.1",
              "cookies": [],
              "headers": [],
              "redirectURL": "",
              "headersSize": -1,
              "bodySize": 7508,
              "content": {
                "size": 7508,
                "mimeType": "application/x-javascript"
              }
            },
            "cache": {},
            "timings": {
              "blocked": 0,
              "dns": -1,
              "connect": -1,
              "send": 0,
              "wait": 76,
              "receive": 0,
              "ssl": -1
            },
            "pageref": "pasties-validator-phantomjs"
          }

    =>

    bodySize: 33,
    content: {
        "size": 33,
        "compression": 0,
        "mimeType": "text/html; charset=utf-8",
        "text": "\n",
        "encoding": "base64"
    }

*/
var async = require('async');
var request = require('request');
var zlib = require('zlib');
var hoek = require('hoek');

function getRealResources(harvested, report, nextValidator) {

    if (!harvested.HARFile.log){
        return nextValidator();
    }

    async.forEachSeries(harvested.HARFile.log.entries, iterator, doneHandler);

    harvested.rawFileData = {};

    function iterator(entry, next){
        /*
            1 Collect body size.
            2 Calculate gzip.
            3 Find out many redirects.
        */

        var options = {
            headers: {
                'Accept-Encoding': 'gzip,deflate',
                'User-Agent': 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26     (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25'
            },
            'encoding': null

        };
        request(entry.request.url, options, function(err, response, body){

            if (err){
                console.log(err);
            }

            // phantom sizes are buggy at best.
            entry.response.bodySize = body.length;
            entry.response.content.size = body.length;

            var output = harvested.rawFileData[entry.request.url] = {
                redirects: response.request.redirects,
                contentType: response.headers['content-type'],
                contentLength: response.headers['content-length'],
                bodyLength: body.length,
                body: body
            };

            var gzipped = response.headers['content-encoding']||response.headers['Content-Encoding']
            output.compressed = !!gzipped;

            // find compressions
            zlib.gzip(body, function(err, buffer){
                var compressionPossible = body.length - buffer.length;

                if (gzipped){
                    entry.response.content.compression = compressionPossible;
                }

                output.compressionPossible = compressionPossible;
                output.compressedSize = buffer.length;

                next();
            });

        });

    }

    function doneHandler(err, result){

        var keys = Object.keys(harvested.rawFileData);
        var iterList = keys.map(function(key){
            return {key: key, value: harvested.rawFileData[key]};
        });

        function list(type, fn){
            return iterList[type](function(entry){
                return fn(entry.key, entry.value);
            });
        }

        function getRedirects(){
            var _list = list('filter', function(key, value){
                return value.redirects && value.redirects.length > 0;
            }).map(function(entry){
                return entry.value.redirects;
            });
            return hoek.flatten.apply(hoek.flatten, _list);
        }

        function getBodySize(){
            return list('map', function(key, value){
                return value.bodyLength;
            }).reduce(function(current, b){
                return current + b;
            }, 0);
        }

        function getCompressedBodySize(){
            return list('map', function(key, value){
                return value.compressed ? value.compressedSize : value.bodyLength;
            }).reduce(function(current, b){
                return current + b;
            }, 0);
        }

        function getTotalIfCompressed(){
            return list('map', function(key, value){
                return value.compressedSize;
            }).reduce(function(current, b){
                return current + b;
            }, 0);
        }

        var redirects = getRedirects();
        var bodySize = getBodySize();
        var compressedBodySize = getCompressedBodySize();
        var totalIfCompressed = getTotalIfCompressed();

        // summary
        harvested.rawFileDataSummary = {
            total: {
                redirects: redirects.length,
                rawRequests: keys.length,
                requests: keys.length + redirects.length,
                bodySize: bodySize,
                compressedBodySize: compressedBodySize,
            },
            tips: {
                possibleCompressTarget: totalIfCompressed,
                possibleCompressImprovement: compressedBodySize - totalIfCompressed
            }
        };

        nextValidator();
    }

}

module.exports = {
    getRealResources: getRealResources,
    validate: function (harvested, report, next) {

        function filterHttp(entry) {
            return entry.request.url.indexOf('http') === 0;
        }

        var input = harvested.harInput;

        var raw = {
            'resources': input.resources.filter(filterHttp),
            'startTime': input.startTime,
            'endTime':  input.endTime
        };

        harvested.HARFile = createHAR({
            title: 'phantom',
            address: 'pasties-validator-phantomjs',
            creator: {
                name: 'pasties-validator-phantomjs',
                version: version
            }
        }, raw);

        getRealResources(harvested, report, next);

    }
};
<commit_msg>dont flatten if there is nothing to flatten<commit_after>/* jshint: */
var createHAR = require('../../createHAR.js');

var version = require('../../../package.json').version;

/*
    request all external contents

    collect sizes, width and without GZIP

    "entries": [
          {
            "startedDateTime": "2013-12-02T09:34:02.260Z",
            "time": null,
            "request": {
              "method": "GET",
              "url": "http://helios.finn.no/addyn%7C3.0%7C989.1%7C4489198%7C0%7C16%7CADTECH;cfp=1;rndc=1385976829;cookie=info;loc=100;target=_blank;alias=car%2Fused%2Fsearch%2Flist_mm;grp=154176158;kvuserid=854203993;misc=414222659203370",
              "httpVersion": "HTTP/1.1",
              "cookies": [],
              "headers": [],
              "queryString": [],
              "headersSize": -1,
              "bodySize": -1
            },
            "response": {
              "status": 200,
              "statusText": "OK",
              "httpVersion": "HTTP/1.1",
              "cookies": [],
              "headers": [],
              "redirectURL": "",
              "headersSize": -1,
              "bodySize": 7508,
              "content": {
                "size": 7508,
                "mimeType": "application/x-javascript"
              }
            },
            "cache": {},
            "timings": {
              "blocked": 0,
              "dns": -1,
              "connect": -1,
              "send": 0,
              "wait": 76,
              "receive": 0,
              "ssl": -1
            },
            "pageref": "pasties-validator-phantomjs"
          }

    =>

    bodySize: 33,
    content: {
        "size": 33,
        "compression": 0,
        "mimeType": "text/html; charset=utf-8",
        "text": "\n",
        "encoding": "base64"
    }

*/
var async = require('async');
var request = require('request');
var zlib = require('zlib');
var hoek = require('hoek');

function getRealResources(harvested, report, nextValidator) {

    if (!harvested.HARFile.log){
        return nextValidator();
    }

    async.forEachSeries(harvested.HARFile.log.entries, iterator, doneHandler);

    harvested.rawFileData = {};

    function iterator(entry, next){
        /*
            1 Collect body size.
            2 Calculate gzip.
            3 Find out many redirects.
        */

        var options = {
            headers: {
                'Accept-Encoding': 'gzip,deflate',
                'User-Agent': 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26     (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25'
            },
            'encoding': null

        };
        request(entry.request.url, options, function(err, response, body){

            if (err){
                console.log(err);
            }

            // phantom sizes are buggy at best.
            entry.response.bodySize = body.length;
            entry.response.content.size = body.length;

            var output = harvested.rawFileData[entry.request.url] = {
                redirects: response.request.redirects,
                contentType: response.headers['content-type'],
                contentLength: response.headers['content-length'],
                bodyLength: body.length,
                body: body.toString('base64')
            };

            var gzipped = response.headers['content-encoding']||response.headers['Content-Encoding'];
            output.compressed = !!gzipped;

            // find compressions
            zlib.gzip(body, function(err, buffer){
                var compressionPossible = body.length - buffer.length;

                if (gzipped){
                    entry.response.content.compression = compressionPossible;
                }

                output.compressionPossible = compressionPossible;
                output.compressedSize = buffer.length;

                next();
            });

        });

    }

    function doneHandler(err, result){

        var keys = Object.keys(harvested.rawFileData);
        var iterList = keys.map(function(key){
            return {key: key, value: harvested.rawFileData[key]};
        });

        function list(type, fn){
            return iterList[type](function(entry){
                return fn(entry.key, entry.value);
            });
        }

        function getRedirects(){
            var _list = list('filter', function(key, value){
                return value.redirects && value.redirects.length > 0;
            }).map(function(entry){
                return entry.value.redirects;
            });
            return _list.length > 0 ? hoek.flatten.apply(hoek.flatten, _list) : _list;
        }

        function getBodySize(){
            return list('map', function(key, value){
                return value.bodyLength;
            }).reduce(function(current, b){
                return current + b;
            }, 0);
        }

        function getCompressedBodySize(){
            return list('map', function(key, value){
                return value.compressed ? value.compressedSize : value.bodyLength;
            }).reduce(function(current, b){
                return current + b;
            }, 0);
        }

        function getTotalIfCompressed(){
            return list('map', function(key, value){
                return value.compressedSize;
            }).reduce(function(current, b){
                return current + b;
            }, 0);
        }

        var redirects = getRedirects();
        var bodySize = getBodySize();
        var compressedBodySize = getCompressedBodySize();
        var totalIfCompressed = getTotalIfCompressed();

        // summary
        harvested.rawFileDataSummary = {
            total: {
                redirects: redirects.length,
                rawRequests: keys.length,
                requests: keys.length + redirects.length,
                bodySize: bodySize,
                compressedBodySize: compressedBodySize,
            },
            tips: {
                possibleCompressTarget: totalIfCompressed,
                possibleCompressImprovement: compressedBodySize - totalIfCompressed
            }
        };

        nextValidator();
    }

}

module.exports = {
    getRealResources: getRealResources,
    validate: function (harvested, report, next) {

        function filterHttp(entry) {
            return entry.request.url.indexOf('http') === 0;
        }

        var input = harvested.harInput;

        var raw = {
            'resources': input.resources.filter(filterHttp),
            'startTime': input.startTime,
            'endTime':  input.endTime
        };

        harvested.HARFile = createHAR({
            title: 'phantom',
            address: 'pasties-validator-phantomjs',
            creator: {
                name: 'pasties-validator-phantomjs',
                version: version
            }
        }, raw);

        getRealResources(harvested, report, next);

    }
};
<|endoftext|>


<commit_before>var _ = require("./utils"),
    $Element = require("./element"),
    DOM = require("./dom"),
    features = require("./features"),
    visibleFn = function(el) { return el.get("aria-hidden") !== "true" },
    makeVisibilityMethod = function(name) {
        var createCallback = function(el) {
                return function() { el.set("aria-hidden", name === "hide") };
            };

        return function(delay) {
            if (delay && (typeof delay !== "number" || delay < 0)) _.makeError(name, this);

            if (delay) {
                setTimeout(createCallback(this), delay);

                return this;
            }

            return this.set("aria-hidden", name === "hide");
        };

    };

/**
 * Show element
 * @param {Number} [delay=0] time in miliseconds to wait
 * @return {$Element}
 * @function
 */
$Element.prototype.show = makeVisibilityMethod("show");

/**
 * Hide element
 * @param {Number} [delay=0] time in miliseconds to wait
 * @return {$Element}
 * @function
 */
$Element.prototype.hide = makeVisibilityMethod("hide");

/**
 * Toggle element visibility
 * @param {Boolean|Function} [visible] true if the element should be visible and false otherwise
 * @return {$Element}
 */
$Element.prototype.toggle = function(visible) {
    var visibleType = typeof visible,
        value = visibleFn;

    if (visibleType === "boolean") {
        value = !visible;
    } else if (visibleType === "function") {
        value = function(el, index) { return !visible(el, index) };
    }

    return this.set("aria-hidden", value);
};

// [aria-hidden=true] could be overriden only if browser supports animations
// pointer-events:none helps to solve accidental clicks on a hidden element
DOM.importStyles("[aria-hidden=true]", "pointer-events:none; display:none" + (features.CSS3_ANIMATIONS ? "" : " !important"));
<commit_msg>fix typos<commit_after>var _ = require("./utils"),
    $Element = require("./element"),
    DOM = require("./dom"),
    features = require("./features"),
    toggleFn = function(el) { return el.get("aria-hidden") !== "true" },
    makeVisibilityMethod = function(name) {
        var createCallback = function(el) {
                return function() { el.set("aria-hidden", name === "hide") };
            };

        return function(delay) {
            if (delay && (typeof delay !== "number" || delay < 0)) {
                throw _.makeError(name, this);
            }

            if (delay) {
                setTimeout(createCallback(this), delay);

                return this;
            }

            return this.set("aria-hidden", name === "hide");
        };

    };

/**
 * Show element
 * @param {Number} [delay=0] time in miliseconds to wait
 * @return {$Element}
 * @function
 */
$Element.prototype.show = makeVisibilityMethod("show");

/**
 * Hide element
 * @param {Number} [delay=0] time in miliseconds to wait
 * @return {$Element}
 * @function
 */
$Element.prototype.hide = makeVisibilityMethod("hide");

/**
 * Toggle element visibility
 * @param {Boolean|Function} [visible] true if the element should be visible and false otherwise
 * @return {$Element}
 */
$Element.prototype.toggle = function(visible) {
    var visibleType = typeof visible,
        value = toggleFn;

    if (visibleType === "boolean") {
        value = !visible;
    } else if (visibleType === "function") {
        value = function(el, index) { return !visible(el, index) };
    } else if (visible) {
        throw _.makeError("toggle", this);
    }

    return this.set("aria-hidden", value);
};

// [aria-hidden=true] could be overriden only if browser supports animations
// pointer-events:none helps to solve accidental clicks on a hidden element
DOM.importStyles("[aria-hidden=true]", "pointer-events:none; display:none" + (features.CSS3_ANIMATIONS ? "" : " !important"));
<|endoftext|>


<commit_before>var _ = require("underscore")._,
    l = require("locale"),
    express = require("express"),
    Parser = require("./parser/Parser"),
    MongoTranslationStore = require('./MongoTranslationStore'),
    Translator = require('../shared/translator.umd.js'),
    HandlebarsHelpers = require('../shared/handlebars-helpers.umd.js'),
    root = "/translate.me",
    apiRoot = root + "/api/:format";

/**
 * Creates a new TranslateMe object.
 *
 * @param {String} mongoURL an URL of a mongo db to create the translations in
 * @param {String} defaultLocale a default country code, which language the translation statements represent
 * @param {String[]} supportedLocales a list of locales, which shall be supported by the system
 * @constructor
 */
TranslateMe = function (mongoURL, defaultLocale, supportedLocales) {
    if(!mongoURL) {
        throw "Please pass a mongo URL to connect to.";
    }
    if(!defaultLocale) {
        throw "Please pass a locale.";
    }
    if(!supportedLocales) {
        throw "Please pass at least one other locale which should be supported by the system.";
    }

    l.Locale['default'] = new l.Locale(defaultLocale);
    supportedLocales.push(defaultLocale);
    this.supportedLocales = new l.Locales(_.uniq(supportedLocales));
    this.translationStore = new MongoTranslationStore(mongoURL);
    this.parser = new Parser(this.translationStore);
};

/**
 * Determines the best locale, based on the passed acceptLanguage string.
 *
 * @param {string} acceptLanguage value of the acceptLanguage http header property
 * @returns {string} best locale for the passed acceptLanguage string
 */
TranslateMe.prototype.bestLocale = function(acceptLanguage) {
    var locales = new l.Locales(acceptLanguage);
    return locales.best(this.supportedLocales).language;
};

/**
 * Returns an instance of a Translator. Can be used to translate on the server-side.
 *
 * @param {function(Translator)} ready which will be called when the Translator is ready to translate
 * @returns {Translator} a singleton of the translator
 */
TranslateMe.prototype.getTranslator = function(ready) {
    var that = this,
        translations = {};

    if(this.translator === undefined) {
        // Cache translations
        this.translationStore.getTranslations({}, function(err, docs) {
            if(!err) {
                // Create an array which groups the translations in the following structure: [local][namespace][key]
                _.each(docs, function(t) {
                    if(t.value) {
                        if (!translations[t.locale]) {
                            translations[t.locale] = {};
                        }
                        if (!translations[t.locale][t.namespace]) {
                            translations[t.locale][t.namespace] = {};
                        }
                        translations[t.locale][t.namespace][t.key] = t.value;
                    }
                });
                if(_.isFunction(ready)) {
                    ready(this.translator);
                }
            } else {
                throw new Error("Could not cache translations. Here's the error: " + JSON.stringify(err));
            }
        });

        this.translator = new Translator({
            get: function(locale, namespace, key) {
                if(!_.isEmpty(translations)) {
                    if(translations[locale]
                        && translations[locale][namespace]
                        && translations[locale][namespace][key]) {
                        return translations[locale][namespace][key];
                    }
                } else {
                    throw new Error("Could not get translations. Invalid state. No translations are present.");
                }
            },

            createMaster: function(namespace, key) {
                that.translationStore.createMasterTranslation({
                    namespace: namespace,
                    key: key
                }, 'dynamic');
            }
        }, l.Locale['default'].toString());
    }

    return this.translator;
}

/**
 * Provides block helpers to support namespace and translation blocks in templates.
 *
 * @returns {{namespace: *, translation: *}}
 */
TranslateMe.prototype.HandlebarsBlockHelpers = function() {
    var that = this;
    return {
        namespace: HandlebarsHelpers.createNamespaceBlockHelper(),
        translation: HandlebarsHelpers.createTranslationBlockHelper(that.getTranslator())
    }
}

/**
 * Generates translations entries in a mongo-db, based on the translation statements in the files of the passed
 * directories. Currently supported are javascript and mustache files. The translation statements are as following:
 *
 * - .mustache: {{translation key="key" namespace="namespace"}}
 * - .js: _.translate("key", "ns:namespace", parameters);
 *
 * @public
 * @param {String|String[]} directories a directory or an array of directories where to search for translation statements
 * @param {Function} [callback] a function which will be called, when the operation finished
 */
TranslateMe.prototype.generateDefaultTranslations = function(directories, callback) {
    if(!directories) {
        throw "Please pass a directory or list of directories to check for translation statements.";
    }
    this.parser.parse(directories, l.Locale['default'].toString(), callback);
}

/**
 * Registers all necessary middleware to the passed express app to enable translation.
 *
 * @param app express app to register the translation ui to
 * @param {Boolean} [enableAdminUI] true to enable the admin-ui, otherwise false or undefined
 */
TranslateMe.prototype.registerRoutes = function(app, enableAdminUI) {
    if(!app) {
        throw "Please pass an express app object.";
    }
    var self = this,
        masterLocale = l.Locale['default'].toString();

    if(enableAdminUI) {
        app.use(root + '/admin', express.static(__dirname + "/../../public"));
    }
    app.use(root, express.static(__dirname + "/../browser"));
    app.use(root, express.static(__dirname + "/../shared"));
    app.use(l(this.supportedLocales));

    app.all(apiRoot + "/*", function(req, res, next) {
        res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
        switch(req.param('format')) {
            case 'require':
                res.sendData = function(object) {
                    res.type(express.mime.types.js).end("define(function() {return " + JSON.stringify(object) + "});");
                };
                break;

            case 'json':
                res.sendData = function(object) {
                    res.type(express.mime.types.json).end(JSON.stringify(object));
                };
                break;

            case 'script':
                var variable = (req.param(0) === 'translations') ? 'translateMeTranslations' : 'translateMeStats';

                res.sendData = function (object) {
                    res.type(express.mime.types.js).end('var ' + variable + ' = ' + JSON.stringify(object) + ';');
                };
                break;

            default:
                res.send(500, "Sorry. Couldn't do anything. Output format: \"" + req.param('format') + "\" is unknown.");
                return;
                break;
        }

        next();
    });

    app.get(apiRoot + '/translations/:id', function(req, res) {
        self.translationStore.getTranslationById(req.param('id'), function(err, translation) {
            if(!err) {
                res.sendData(translation);
            } else {
                res.send(404, "Sorry. Couldn't find any translation with the id: \"" + id + "\".");
            }
        });
    });

    // Get translations
    app.get(apiRoot + '/translations', function(req, res) {
        var locales = req.param('locale'),
            namespaces = req.param('namespace'),
            emulateMissingTranslations = req.param('emulateMissingTranslations'),
            conditions = {
                namespace: namespaces,
                locale: locales
            };

        if(emulateMissingTranslations) {
            delete(conditions.locale);
        }

        self.translationStore.getTranslations(conditions,
            function(err, translations) {
                if(!err) {
                    if(emulateMissingTranslations) {
                        translations = emulateTranslation(translations, locales, masterLocale, self.supportedLocales);
                    }
                    res.sendData(translations);
                } else {
                    res.send(500, "Sorry. Couldn't retrieve the translations. Here's the error: " + JSON.stringify(err));
                }
            }
        );
    });

    // Create or update translation
    app.post(apiRoot + '/translations', function(req, res) {
        var translation = {
            locale: req.param('locale'),
            namespace: req.param('namespace'),
            key: req.param('key')
        };

        if(translation.namespace === undefined) {
            res.send(400, "Sorry. Couldn't create translation. Parameter 'namespace' is missing.");
            return;
        }
        if(!translation.key) {
            res.send(400, "Sorry. Couldn't create translation. Parameter 'key' is missing.");
            return;
        }
        if(!translation.locale) {
            translation.locale = masterLocale;
        }
        if(req.param('value')) {
            translation.value = req.param('value');
        }
        if(req.param('origin')) {
            translation.origin = req.param('origin');
        } else {
            translation.origin = 'static';
        }

        self.translationStore.createOrUpdateTranslation(translation, function(err, newTranslation) {
            if(!err) {
                res.sendData(newTranslation);
            } else {
                res.send(500, "Sorry. Couldn't create translation. Here's the error: " + JSON.stringify(err));
            }
        });
    });

    // Update existing translation
    app.put(apiRoot + '/translations/:id', function(req, res) {
        self.translationStore.updateTranslationById(req.param('id'), {value: req.body.value},  function(err, updatedTranslation) {
            if(!err) {
                res.sendData(updatedTranslation);
            } else {
                res.send(500, "Sorry. Couldn't update the translation. Here's the error: " + JSON.stringify(err));
            }
        });
    });

    app.get(apiRoot + '/locales', function(req, res) {
        var locales = [];
        _.each(self.supportedLocales, function(locale) {
            locales.push(locale.toString());
        })
        res.sendData(locales);
    });

    app.get(apiRoot + '/namespaces', function(req, res) {
        self.translationStore.getNamespaces(function (err, namespaces) {
            if(!err) {
                res.sendData(namespaces);
            } else {
                res.send(500, "Sorry. Couldn't retrieve namespaces. Here's the error: " + JSON.stringify(err));
            }
        });
    });

    app.get(apiRoot + "/stats", function(req, res) {
        self.translationStore.getStats(function(err, stats) {
            var result;
            if(!err) {
                result = {};
                result.masterLocale = masterLocale;
                result.numberOfTranslations = _.find(stats, function(stat) { return stat.locale === result.masterLocale }).numberOfTranslations;
                result.translatedLocales = {};
                _.each(stats, function(stat) {
                    var translatedInPercent = stat.numberOfTranslationsWithValue / result.numberOfTranslations;
                    if(stat.locale === result.masterLocale) {
                        translatedInPercent = 1;
                    }
                    result.translatedLocales[stat.locale] = {
                        translatedInPercent: translatedInPercent
                    };
                });
                res.sendData(result);
            } else {
                res.send(500, "Sorry. Couldn't create stats. Here's the error: " + JSON.stringify(err));
            }
        });
    });
}

function emulateTranslation(translations, locales, masterLocale, supportedLocales) {
    var emulatedTranslations = [],
        masterTranslations = _.where(translations, {locale: masterLocale});

    if(!locales) {
        locales = [];
        _.each(supportedLocales, function(supportedLocale) {
            locales.push(supportedLocale.toString());
        });
    } else if(_.isString(locales)) {
        locales = [locales];
    }

    _.each(locales, function(locale) {
        _.each(masterTranslations, function(mt) {
            var translation = _.find(translations, function(t) {
                return locale === t.locale && t.namespace === mt.namespace && t.key === mt.key;
            });
            if(!translation) {
                translation = {
                    key: mt.key,
                    namespace: mt.namespace,
                    locale: locale,
                    origin: mt.origin
                };
            }
            emulatedTranslations.push(translation);
        });
    });

    return emulatedTranslations;
}

module.exports = TranslateMe;
<commit_msg>Fixed bug with translation service<commit_after>var _ = require("underscore")._,
    l = require("locale"),
    express = require("express"),
    Parser = require("./parser/Parser"),
    MongoTranslationStore = require('./MongoTranslationStore'),
    Translator = require('../shared/translator.umd.js'),
    HandlebarsHelpers = require('../shared/handlebars-helpers.umd.js'),
    root = "/translate.me",
    apiRoot = root + "/api/:format";

/**
 * Creates a new TranslateMe object.
 *
 * @param {String} mongoURL an URL of a mongo db to create the translations in
 * @param {String} defaultLocale a default country code, which language the translation statements represent
 * @param {String[]} supportedLocales a list of locales, which shall be supported by the system
 * @constructor
 */
TranslateMe = function (mongoURL, defaultLocale, supportedLocales) {
    if(!mongoURL) {
        throw "Please pass a mongo URL to connect to.";
    }
    if(!defaultLocale) {
        throw "Please pass a locale.";
    }
    if(!supportedLocales) {
        throw "Please pass at least one other locale which should be supported by the system.";
    }

    l.Locale['default'] = new l.Locale(defaultLocale);
    supportedLocales.push(defaultLocale);
    this.supportedLocales = new l.Locales(_.uniq(supportedLocales));
    this.translationStore = new MongoTranslationStore(mongoURL);
    this.parser = new Parser(this.translationStore);
};

/**
 * Determines the best locale, based on the passed acceptLanguage string.
 *
 * @param {string} acceptLanguage value of the acceptLanguage http header property
 * @returns {string} best locale for the passed acceptLanguage string
 */
TranslateMe.prototype.bestLocale = function(acceptLanguage) {
    var locales = new l.Locales(acceptLanguage);
    return locales.best(this.supportedLocales).language;
};

/**
 * Returns an instance of a Translator. Can be used to translate on the server-side.
 *
 * @param {function(Translator)} ready which will be called when the Translator is ready to translate
 * @returns {Translator} a singleton of the translator
 */
TranslateMe.prototype.getTranslator = function(ready) {
    var that = this,
        translations = {};

    if(this.translator === undefined) {
        // Cache translations
        this.translationStore.getTranslations({}, function(err, docs) {
            if(!err) {
                // Create an array which groups the translations in the following structure: [local][namespace][key]
                _.each(docs, function(t) {
                    if(t.value) {
                        if (!translations[t.locale]) {
                            translations[t.locale] = {};
                        }
                        if (!translations[t.locale][t.namespace]) {
                            translations[t.locale][t.namespace] = {};
                        }
                        translations[t.locale][t.namespace][t.key] = t.value;
                    }
                });
                if(_.isFunction(ready)) {
                    ready(this.translator);
                }
            } else {
                throw new Error("Could not cache translations. Here's the error: " + JSON.stringify(err));
            }
        });

        this.translator = new Translator({
            get: function(locale, namespace, key) {
                if(!_.isEmpty(translations)) {
                    if(translations[locale]
                        && translations[locale][namespace]
                        && translations[locale][namespace][key]) {
                        return translations[locale][namespace][key];
                    }
                } else {
                    throw new Error("Could not get translations. Invalid state. No translations are present.");
                }
            },

            createMaster: function(namespace, key) {
                that.translationStore.createMasterTranslation({
                    namespace: namespace,
                    key: key
                }, 'dynamic');
            }
        }, l.Locale['default'].toString());
    }

    return this.translator;
}

/**
 * Provides block helpers to support namespace and translation blocks in templates.
 *
 * @returns {{namespace: *, translation: *}}
 */
TranslateMe.prototype.HandlebarsBlockHelpers = function() {
    var that = this;
    return {
        namespace: HandlebarsHelpers.createNamespaceBlockHelper(),
        translation: HandlebarsHelpers.createTranslationBlockHelper(that.getTranslator())
    }
}

/**
 * Generates translations entries in a mongo-db, based on the translation statements in the files of the passed
 * directories. Currently supported are javascript and mustache files. The translation statements are as following:
 *
 * - .mustache: {{translation key="key" namespace="namespace"}}
 * - .js: _.translate("key", "ns:namespace", parameters);
 *
 * @public
 * @param {String|String[]} directories a directory or an array of directories where to search for translation statements
 * @param {Function} [callback] a function which will be called, when the operation finished
 */
TranslateMe.prototype.generateDefaultTranslations = function(directories, callback) {
    if(!directories) {
        throw "Please pass a directory or list of directories to check for translation statements.";
    }
    this.parser.parse(directories, l.Locale['default'].toString(), callback);
}

/**
 * Registers all necessary middleware to the passed express app to enable translation.
 *
 * @param app express app to register the translation ui to
 * @param {Boolean} [enableAdminUI] true to enable the admin-ui, otherwise false or undefined
 */
TranslateMe.prototype.registerRoutes = function(app, enableAdminUI) {
    if(!app) {
        throw "Please pass an express app object.";
    }
    var self = this,
        masterLocale = l.Locale['default'].toString();

    if(enableAdminUI) {
        app.use(root + '/admin', express.static(__dirname + "/../../public"));
    }
    app.use(root, express.static(__dirname + "/../browser"));
    app.use(root, express.static(__dirname + "/../shared"));
    app.use(l(this.supportedLocales));

    app.all(apiRoot + "/*", function(req, res, next) {
        res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
        switch(req.param('format')) {
            case 'require':
                res.sendData = function(object) {
                    res.type(express.mime.types.js).end("define(function() {return " + JSON.stringify(object) + "});");
                };
                break;

            case 'json':
                res.sendData = function(object) {
                    res.type(express.mime.types.json).end(JSON.stringify(object));
                };
                break;

            case 'script':
                var variable = (req.param(0) === 'translations') ? 'translateMeTranslations' : 'translateMeStats';

                res.sendData = function (object) {
                    res.type(express.mime.types.js).end('var ' + variable + ' = ' + JSON.stringify(object) + ';');
                };
                break;

            default:
                res.send(500, "Sorry. Couldn't do anything. Output format: \"" + req.param('format') + "\" is unknown.");
                return;
                break;
        }

        next();
    });

    app.get(apiRoot + '/translations/:id', function(req, res) {
        self.translationStore.getTranslationById(req.param('id'), function(err, translation) {
            if(!err) {
                res.sendData(translation);
            } else {
                res.send(404, "Sorry. Couldn't find any translation with the id: \"" + id + "\".");
            }
        });
    });

    // Get translations
    app.get(apiRoot + '/translations', function(req, res) {
        var locales = req.param('locale'),
            namespaces = req.param('namespace'),
            emulateMissingTranslations = req.param('emulateMissingTranslations'),
            conditions = {
                namespaces: namespaces,
                locales: locales
            };

        if(emulateMissingTranslations) {
            delete(conditions.locales);
        }

        self.translationStore.getTranslations(conditions,
            function(err, translations) {
                if(!err) {
                    if(emulateMissingTranslations) {
                        translations = emulateTranslation(translations, locales, masterLocale, self.supportedLocales);
                    }
                    res.sendData(translations);
                } else {
                    res.send(500, "Sorry. Couldn't retrieve the translations. Here's the error: " + JSON.stringify(err));
                }
            }
        );
    });

    // Create or update translation
    app.post(apiRoot + '/translations', function(req, res) {
        var translation = {
            locale: req.param('locale'),
            namespace: req.param('namespace'),
            key: req.param('key')
        };

        if(translation.namespace === undefined) {
            res.send(400, "Sorry. Couldn't create translation. Parameter 'namespace' is missing.");
            return;
        }
        if(!translation.key) {
            res.send(400, "Sorry. Couldn't create translation. Parameter 'key' is missing.");
            return;
        }
        if(!translation.locale) {
            translation.locale = masterLocale;
        }
        if(req.param('value')) {
            translation.value = req.param('value');
        }
        if(req.param('origin')) {
            translation.origin = req.param('origin');
        } else {
            translation.origin = 'static';
        }

        self.translationStore.createOrUpdateTranslation(translation, function(err, newTranslation) {
            if(!err) {
                res.sendData(newTranslation);
            } else {
                res.send(500, "Sorry. Couldn't create translation. Here's the error: " + JSON.stringify(err));
            }
        });
    });

    // Update existing translation
    app.put(apiRoot + '/translations/:id', function(req, res) {
        self.translationStore.updateTranslationById(req.param('id'), {value: req.body.value},  function(err, updatedTranslation) {
            if(!err) {
                res.sendData(updatedTranslation);
            } else {
                res.send(500, "Sorry. Couldn't update the translation. Here's the error: " + JSON.stringify(err));
            }
        });
    });

    app.get(apiRoot + '/locales', function(req, res) {
        var locales = [];
        _.each(self.supportedLocales, function(locale) {
            locales.push(locale.toString());
        })
        res.sendData(locales);
    });

    app.get(apiRoot + '/namespaces', function(req, res) {
        self.translationStore.getNamespaces(function (err, namespaces) {
            if(!err) {
                res.sendData(namespaces);
            } else {
                res.send(500, "Sorry. Couldn't retrieve namespaces. Here's the error: " + JSON.stringify(err));
            }
        });
    });

    app.get(apiRoot + "/stats", function(req, res) {
        self.translationStore.getStats(function(err, stats) {
            var result;
            if(!err) {
                result = {};
                result.masterLocale = masterLocale;
                result.numberOfTranslations = _.find(stats, function(stat) { return stat.locale === result.masterLocale }).numberOfTranslations;
                result.translatedLocales = {};
                _.each(stats, function(stat) {
                    var translatedInPercent = stat.numberOfTranslationsWithValue / result.numberOfTranslations;
                    if(stat.locale === result.masterLocale) {
                        translatedInPercent = 1;
                    }
                    result.translatedLocales[stat.locale] = {
                        translatedInPercent: translatedInPercent
                    };
                });
                res.sendData(result);
            } else {
                res.send(500, "Sorry. Couldn't create stats. Here's the error: " + JSON.stringify(err));
            }
        });
    });
}

function emulateTranslation(translations, locales, masterLocale, supportedLocales) {
    var emulatedTranslations = [],
        masterTranslations = _.where(translations, {locale: masterLocale});

    if(!locales) {
        locales = [];
        _.each(supportedLocales, function(supportedLocale) {
            locales.push(supportedLocale.toString());
        });
    } else if(_.isString(locales)) {
        locales = [locales];
    }

    _.each(locales, function(locale) {
        _.each(masterTranslations, function(mt) {
            var translation = _.find(translations, function(t) {
                return locale === t.locale && t.namespace === mt.namespace && t.key === mt.key;
            });
            if(!translation) {
                translation = {
                    key: mt.key,
                    namespace: mt.namespace,
                    locale: locale,
                    origin: mt.origin
                };
            }
            emulatedTranslations.push(translation);
        });
    });

    return emulatedTranslations;
}

module.exports = TranslateMe;
<|endoftext|>


<commit_before>(function(){
    'use strict';

    var main        = global.cloudcmd.main,
        Util        = main.util,
        pipe        = main.pipe,
        path        = require('path'),
        fs          = require('fs'),
        diff        = main.diff,
        CloudFunc   = main.cloudfunc,
        fse         = main.require('fs-extra') || {
            mkdirs  : fs.mkdir.bind(fs),
        };

    exports.onPut = onPut;

    function onPut(name, query, readStream, callback) {
        var func        = Util.retExec(callback),
            baseName    = path.basename(name);

        switch(query) {
        case 'dir':
            fse.mkdirs(name, function(error) {
                var msg;

                if (!error)
                    msg = CloudFunc.formatMsg('make dir', name);

                func(error, msg);
            });
            break;

        default:
            pipe.create({
                read        : readStream,
                to          : name,
                callback    : function(error) {
                    var msg;

                    if (!error)
                        msg     = CloudFunc.formatMsg('save', baseName);

                    func(error, msg);
                }
            });
            break;

        case 'patch':
            pipe.getBody(readStream, function(patch) {
                fs.readFile(name, 'utf8', read);

                function read(error, data) {
                    var diffResult;

                    if (error)
                        func(error);
                    else {
                        error         = Util.tryCatchLog(function() {
                            diffResult = diff.applyPatch(data, patch);
                        });

                        if (diffResult && !error)
                            fs.writeFile(name, diffResult, write);
                        else {
                            msg     = CloudFunc.formatMsg('patch', baseName, 'fail');
                            func(null, msg);
                        }
                    }
                }

                function write(name, error) {
                    var msg;

                    if (!error)
                        msg = CloudFunc.formatMsg('patch', baseName);

                    func(error, msg);
                }
            });
            break;
        }
    }
})();
<commit_msg>feature(put) pathch: add size check<commit_after>(function() {
//    'use strict';

    var main        = global.cloudcmd.main,
        Util        = main.util,
        pipe        = main.pipe,
        path        = require('path'),
        fs          = require('fs'),
        dir         = main.srvrequire('dir'),
        diff        = main.diff,
        CloudFunc   = main.cloudfunc,
        fse         = main.require('fs-extra') || {
            mkdirs  : fs.mkdir.bind(fs),
        };

    exports.onPut = onPut;

    function onPut(name, query, readStream, callback) {
        var func        = Util.retExec(callback),
            baseName    = path.basename(name);

        switch(query) {
        case 'dir':
            fse.mkdirs(name, function(error) {
                var msg;

                if (!error)
                    msg = CloudFunc.formatMsg('make dir', name);

                func(error, msg);
            });
            break;

        default:
            pipe.create({
                read        : readStream,
                to          : name,
                callback    : function(error) {
                    var msg;

                    if (!error)
                        msg     = CloudFunc.formatMsg('save', baseName);

                    func(error, msg);
                }
            });
            break;

        case 'patch':
            dir.getSize(name, function(error, size) {
                var MAX_SIZE = 100 * 1024;

                if (!error)
                    if (size < MAX_SIZE)
                        pipe.getBody(readStream, function(patch) {
                            patchFile(patch);
                        });
                    else
                        error = 'File is to big.';

                if (error)
                    func(error);
            });

             function patchFile(patch) {
                fs.readFile(name, 'utf8', read);

                function read(error, data) {
                    var diffResult;

                    if (error)
                        func(error);
                    else {
                        error         = Util.tryCatchLog(function() {
                            diffResult = diff.applyPatch(data, patch);
                        });

                        if (diffResult && !error)
                            fs.writeFile(name, diffResult, write);
                        else {
                            msg     = CloudFunc.formatMsg('patch', baseName, 'fail');
                            func(null, msg);
                        }
                    }
                }

                function write(name, error) {
                    var msg;

                    if (!error)
                        msg = CloudFunc.formatMsg('patch', baseName);

                    func(error, msg);
                }
            }
            break;
        }
    }
})();
<|endoftext|>


<commit_before>'use strict';
'browser-only';

/**
 * @license
 * (c) 2014 Cluster Labs, Inc. https://cluster.co/
 * License: MIT
 */

var ElementWrapper = skit.browser.ElementWrapper;

var Event = function(evt) {
  this.evt_ = evt;

  this.target = new ElementWrapper(evt.srcElement || evt.target);
  this.currentTarget = null;

  this.type = evt.type;

  // mouseX, mouseY, keyCode, etc.
};

Event.prototype.stopPropagation = function() {
  if (this.evt_.stopPropagation) {
    this.evt_.stopPropagation();
  } else {
    this.evt_.cancelBubble = true;
  }
};

Event.prototype.preventDefault = function() {
  if (this.evt_.preventDefault) {
    this.evt_.preventDefault();
  } else {
    this.evt_.returnValue = false;
  }
};

return Event;
<commit_msg>Include keyCode/shiftKey.<commit_after>'use strict';
'browser-only';

/**
 * @license
 * (c) 2014 Cluster Labs, Inc. https://cluster.co/
 * License: MIT
 */

var ElementWrapper = skit.browser.ElementWrapper;

var Event = function(evt) {
  this.evt_ = evt;

  this.target = new ElementWrapper(evt.srcElement || evt.target);
  this.currentTarget = null;

  this.type = evt.type;

  this.keyCode = evt.keyCode || null;
  this.shiftKey = evt.shiftKey || false;

  // mouseX, mouseY, keyCode, etc.
};

Event.prototype.stopPropagation = function() {
  if (this.evt_.stopPropagation) {
    this.evt_.stopPropagation();
  } else {
    this.evt_.cancelBubble = true;
  }
};

Event.prototype.preventDefault = function() {
  if (this.evt_.preventDefault) {
    this.evt_.preventDefault();
  } else {
    this.evt_.returnValue = false;
  }
};

return Event;
<|endoftext|>


<commit_before>//Lets require/import the HTTP module
var http = require('http');

//Lets define a port we want to listen to
const PORT=8080;

//We need a function which handles requests and send response
function handleRequest(request, response){
    response.end('It Works!! Path Hit: ' + request.url);
}

//Create a server
var server = http.createServer(handleRequest);

//Lets start our server
server.listen(PORT, function(){
    //Callback triggered when server is successfully listening. Hurray!
    console.log('Server listening on: http://localhost:%s', PORT);
});<commit_msg>Update SimpleHttpServer.js<commit_after>//Lets require/import the HTTP module
var http = require('http');

//Lets define a port we want to listen to
const PORT=8080;

//We need a function which handles requests and send response
function handleRequest(request, response){
  response.end('It Works!! Path Hit: ' + request.url);
}

//Create a server
var server = http.createServer(handleRequest);

//Lets start our server
server.listen(PORT, function(){
  //Callback triggered when server is successfully listening. Hurray!
  console.log('Server listening on: http://localhost:%s', PORT);
});
<|endoftext|>


<commit_before>const eyes = require('eyes'),
      omit = require('lodash').omit,
      defaults = require('lodash').defaults,
      request = require('request'),
      async = require('async');

module.exports = (state, opts, cb) => {
  const url = state.conf('api:url'),
        feeds = state.feeds.filter(feed => (state.posts[feed.id] || []).length > 0);

  async.eachSeries(feeds, (feedData, feedNext) => {
    const feedOpts = {
      url: `${url}/feeds`,
      method: 'POST',
      body: omit(feedData, 'id'),
      json: true
    };

    request(feedOpts, (feedErr, response, feed) => {
      if (feedErr) { return feedNext(feedErr); }

      eyes.inspect(feed, 'feed');
      return async.eachSeries(state.posts[feedData.id] || [], (postData, postNext) => {
        const postOpts = {
          url: `${url}/posts`,
          method: 'POST',
          body: defaults({ feedId: feed.id }, omit(postData, 'feedUri', 'feedTitle', 'feedLink')),
          json: true
        };

        request(postOpts, (postErr, res, post) => {
          postNext(postErr);
          eyes.inspect(post, 'post');
        });
      }, feedNext);
    });
  }, cb);
};
<commit_msg>Minor fix<commit_after>const eyes = require('eyes'),
      omit = require('lodash').omit,
      defaults = require('lodash').defaults,
      request = require('request'),
      async = require('async');

module.exports = (state, opts, cb) => {
  const url = state.conf('api:url'),
        feeds = state.feeds.filter(feed => (state.posts[feed.id] || []).length > 0);

  async.eachSeries(feeds, (feedData, feedNext) => {
    const feedOpts = {
      url: `${url}/feeds`,
      method: 'POST',
      body: omit(feedData, 'id'),
      json: true
    };

    request(feedOpts, (feedErr, response, feed) => {
      if (feedErr) { return feedNext(feedErr); }

      eyes.inspect(feed, 'feed');
      return async.eachSeries(state.posts[feedData.id], (postData, postNext) => {
        const postOpts = {
          url: `${url}/posts`,
          method: 'POST',
          body: defaults({ feedId: feed.id }, omit(postData, 'feedUri', 'feedTitle', 'feedLink')),
          json: true
        };

        request(postOpts, (postErr, res, post) => {
          postNext(postErr);
          eyes.inspect(post, 'post');
        });
      }, feedNext);
    });
  }, cb);
};
<|endoftext|>


<commit_before>/*!
 * patron - The cleanest command framework for discord.js and eris.
 * Copyright (C) 2020  LJ Talbot
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
"use strict";
const Registry = require("./Registry.js");
const ArgumentDefault = require("../enums/ArgumentDefault.js");
const ResultType = require("../enums/ResultType.js");
const CommandResult = require("../results/CommandResult.js");
const CooldownResult = require("../results/CooldownResult.js");
const ErrorResult = require("../results/ErrorResult.js");
const ExecutionResult = require("../results/ExecutionResult.js");
const Result = require("../results/Result.js");
const TypeReaderResult = require("../results/TypeReaderResult.js");
const Command = require("../structures/Command.js");
const {regexes} = require("../utils/constants.js");
const {checkPrefix, invalidOptions} = require("../utils/structUtil.js");

class Handler {
  constructor(options) {
    if(invalidOptions(options))
      throw ReferenceError("The Handler constructor requires an options object.");

    this.defaultRegex = options.argumentRegex == null;
    this.argumentRegex = options.argumentRegex ?? regexes.argument;
    this.separator = options.separator ?? " ";
    this.registry = options.registry;
    Handler.validate(this);
  }

  parsePrefix(message) {
    const lib = Registry.getLibrary();
    const client = lib.getClient(message);
    const content = this.registry.toLowerCase(message.content);
    let prefixes;
    let hasPrefix = false;

    if(message.channel.guild != null) {
      prefixes = this.registry.prefixes.get(message.channel.guild.id);

      if(prefixes != null) {
        hasPrefix = true;

        const prefix = prefixes.find(p => checkPrefix(content, client.user.id, this.registry.toLowerCase(p)));

        if(prefix != null)
          return prefix === "@mention" ? `<@!${client.user.id}>` : prefix;
        else
          return;
      }
    }

    prefixes = this.registry.prefixes.get("global");

    if(prefixes == null) {
      if(hasPrefix)
        return;

      throw Error("No prefixes have been registered to Patron.");
    }

    const prefix = prefixes.find(p => checkPrefix(content, client.user.id, this.registry.toLowerCase(p)));

    if(prefix == null)
      return;

    return prefix === "@mention" ? `<@!${client.user.id}>` : prefix;
  }

  parseCommand(message, prefix) {
    const prefixLength = prefix?.length ?? this.parsePrefix(message)?.length;

    if(prefixLength == null)
      return CommandResult.fromUnknown("");

    let args = message.content.slice(prefixLength).match(this.argumentRegex);

    if(args == null)
      return CommandResult.fromUnknown("");

    const command = this.registry.getCommand(args[0]);

    if(command == null)
      return CommandResult.fromUnknown(args[0]);
    else if(this.defaultRegex)
      args = args.slice(1).map(arg => arg.replace(regexes.quotes, ""));
    else
      args = args.slice(1);

    return {args, command};
  }

  validateCommand(message, command) {
    const lib = Registry.getLibrary();
    const result = lib.validateContext(message, command);

    if(result != null || message.channel.guild == null)
      return result;

    return lib.validatePermissions(message, command);
  }

  async useCooldowns(message, command) {
    let active = await command.useCooldowns(message);
    let group;

    if(active.every(a => !a)) {
      if(command.group == null)
        return;

      group = this.registry.getGroup(command.group);
      active = await group.useCooldowns(message);

      if(active.every(a => !a))
        return;
    }

    const cooldowns = (group ?? command).cooldowns.filter((v, i) => active[i]);
    const info = (await (group ?? command).getCooldowns(message)).filter((v, i) => active[i]);

    return CooldownResult.fromFailure(command, cooldowns, info, group != null);
  }

  async revertCooldowns(message, command) {
    await command.revertCooldowns(message);

    if(command.group != null)
      await this.registry.getGroup(command.group).revertCooldowns(message);
  }

  async runPreconditions(message, command) {
    if(!(command instanceof Command))
      command = this.registry.getCommand(command);

    if(command.group != null) {
      const group = this.registry.getGroup(command.group);

      for(let i = 0; i < group.preconditions.length; i++) {
        const result = await this.registry.getPrecondition(group.preconditions[i])
          .run(command, message, group.preconditionOptions[i]);

        if(result.type !== ResultType.Success)
          return result;
      }
    }

    for(let i = 0; i < command.preconditions.length; i++) {
      const result = await this.registry.getPrecondition(command.preconditions[i])
        .run(command, message, command.preconditionOptions[i]);

      if(result.type !== ResultType.Success)
        return result;
    }
  }

  async validateArgument(message, command, info, parsed, input) {
    let result = await this.registry.getTypeReader(info.type).read(input, command, message, info, parsed, info.typeOptions);

    if(result.type !== ResultType.Success)
      return result;

    const {value} = result;

    for(let i = 0; i < info.preconditions.length; i++) {
      result = await this.registry.getArgumentPrecondition(info.preconditions[i])
        .run(value, command, message, info, parsed, info.preconditionOptions[i]);

      if(result.type !== ResultType.Success)
        return result;
    }

    return value;
  }

  async parseArguments(message, command, args) {
    const parsed = {};
    let next = 0;

    for(let i = 0; i < command.arguments.length; i++) {
      const info = command.arguments[i];

      if(info.infinite) {
        parsed[info.key] = [];

        if(next >= args.length) {
          if(!info.optional)
            return CommandResult.fromArgumentCount(command, i);

          parsed[info.key].push(this.default(message, info.defaultValue));

          break;
        }

        for(let j = next; j < args.length; j++) {
          const result = await this.validateArgument(message, command, info, parsed, args[j]);

          if(result instanceof Result)
            return result;

          parsed[info.key].push(result);
        }

        continue;
      }else if(next >= args.length) {
        if(!info.optional)
          return CommandResult.fromArgumentCount(command, i);

        parsed[info.key] = this.default(message, info.defaultValue);

        continue;
      }else if(i > command.arguments.length - 1 && info.optional && command.arguments[i + 1].optional
          && !command.arguments[i + 1].infinite) {
        const list = command.arguments;
        const defaulted = [];
        let options = 2;

        for(let j = i + options; j < list.length; j++) {
          if(list[j].optional && !list[j].infinite)
            ++options;
          else
            break;
        }

        const size = i + options;

        for(; i < size; i++) {
          const input = list[i].remainder ? args.slice(next).join(this.separator) : args[next];
          const result = await this.validateArgument(message, command, list[i], parsed, input);

          if(result instanceof TypeReaderResult) {
            defaulted.push(i);
            parsed[list[i].key] = this.default(message, list[i].defaultValue);
          }else if(result instanceof Result) {
            return result;
          }else{
            parsed[list[i].key] = result;
            ++next;
          }
        }

        if(next < i && args.length > next && !list[i - 1].remainder) {
          for(let k = 0; k < defaulted.length; k++) {
            for(let l = next; l < args.length; l++) {
              const result = await this.validateArgument(message, command, list[defaulted[k]], parsed, args[l]);

              if(!(result instanceof TypeReaderResult))
                return CommandResult.fromUnorderedArgument(command, list[defaulted[k]]);
            }
          }
        }

        --i;

        continue;
      }

      const input = info.remainder ? args.slice(next).join(this.separator) : args[next];
      const result = await this.validateArgument(message, command, info, parsed, input);

      if(result instanceof TypeReaderResult && info.optional && i !== command.arguments.length - 1) {
        parsed[info.key] = this.default(message, info.defaultValue);
      }else if(result instanceof Result) {
        return result;
      }else{
        parsed[info.key] = result;
        ++next;
      }
    }

    return parsed;
  }

  async runPostconditions(message, command, value) {
    for(let i = 0; i < command.postconditions.length; i++) {
      await this.registry.getPostcondition(command.postconditions[i])
        .run(command, message, value, command.postconditionOptions[i]);
    }

    if(command.group == null)
      return;

    const group = this.registry.getGroup(command.group);

    for(let i = 0; i < group.postconditions.length; i++) {
      await this.registry.getPostcondition(group.postconditions[i])
        .run(command, message, value, group.postconditionOptions[i]);
    }
  }

  async executeCommand(message, command, args) {
    if(!(command instanceof Command))
      command = this.registry.getCommand(command);

    let result = this.validateCommand(message, command);

    if(result instanceof Result)
      return result;

    result = await this.useCooldowns(message, command);

    if(result instanceof Result)
      return result;

    let cooldown = true;

    try {
      result = await this.runPreconditions(message, command);

      if(result instanceof Result) {
        await this.revertCooldowns(message, command);

        return result;
      }

      result = await this.parseArguments(message, command, args);

      if(result instanceof Result) {
        await this.revertCooldowns(message, command);

        return result;
      }

      result = await command.run(message, result);

      if(result instanceof ExecutionResult) {
        if(result.type !== ResultType.Success) {
          await this.revertCooldowns(message, command);
          cooldown = false;
        }

        this.runPostconditions(message, command, result.value);
      }else{
        this.runPostconditions(message, command);
      }
    }catch(err) {
      if(cooldown)
        await this.revertCooldowns(message, command);

      return ErrorResult.fromFailure(command, err);
    }

    return Result.fromSuccess(command);
  }

  async run(message, prefix) {
    const result = this.parseCommand(message, prefix);

    if(result instanceof Result)
      return result;

    const {args, command} = result;

    return this.executeCommand(message, command, args);
  }

  default(message, value) {
    const lib = Registry.getLibrary();

    switch(value) {
      case ArgumentDefault.Author:
        return message.author;
      case ArgumentDefault.Channel:
        return message.channel;
      case ArgumentDefault.Guild:
        return message.channel.guild;
      case ArgumentDefault.HighestRole:
        return lib.highestRole(message);
      case ArgumentDefault.Member:
        return message.member;
      case ArgumentDefault.Message:
        return message;
      default:
        return value;
    }
  }

  static validate(handler) {
    if(!(handler.argumentRegex instanceof RegExp))
      throw TypeError("Handler#argumentRegex must be a regular expression if defined.");
    else if(typeof handler.separator !== "string")
      throw TypeError("Handler#separator must be a string if defined.");
    else if(!(handler.registry instanceof Registry))
      throw TypeError("Handler#registry must be a Registry.");
  }
}

module.exports = Handler;
<commit_msg>Run argument conditions on argument defaults<commit_after>/*!
 * patron - The cleanest command framework for discord.js and eris.
 * Copyright (C) 2020  LJ Talbot
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
"use strict";
const Registry = require("./Registry.js");
const ArgumentDefault = require("../enums/ArgumentDefault.js");
const ResultType = require("../enums/ResultType.js");
const CommandResult = require("../results/CommandResult.js");
const CooldownResult = require("../results/CooldownResult.js");
const ErrorResult = require("../results/ErrorResult.js");
const ExecutionResult = require("../results/ExecutionResult.js");
const Result = require("../results/Result.js");
const TypeReaderResult = require("../results/TypeReaderResult.js");
const Command = require("../structures/Command.js");
const {regexes} = require("../utils/constants.js");
const {checkPrefix, partOf, invalidOptions} = require("../utils/structUtil.js");

class Handler {
  constructor(options) {
    if(invalidOptions(options))
      throw ReferenceError("The Handler constructor requires an options object.");

    this.defaultRegex = options.argumentRegex == null;
    this.argumentRegex = options.argumentRegex ?? regexes.argument;
    this.separator = options.separator ?? " ";
    this.registry = options.registry;
    Handler.validate(this);
  }

  parsePrefix(message) {
    const lib = Registry.getLibrary();
    const client = lib.getClient(message);
    const content = this.registry.toLowerCase(message.content);
    let prefixes;
    let hasPrefix = false;

    if(message.channel.guild != null) {
      prefixes = this.registry.prefixes.get(message.channel.guild.id);

      if(prefixes != null) {
        hasPrefix = true;

        const prefix = prefixes.find(p => checkPrefix(content, client.user.id, this.registry.toLowerCase(p)));

        if(prefix != null)
          return prefix === "@mention" ? `<@!${client.user.id}>` : prefix;
        else
          return;
      }
    }

    prefixes = this.registry.prefixes.get("global");

    if(prefixes == null) {
      if(hasPrefix)
        return;

      throw Error("No prefixes have been registered to Patron.");
    }

    const prefix = prefixes.find(p => checkPrefix(content, client.user.id, this.registry.toLowerCase(p)));

    if(prefix == null)
      return;

    return prefix === "@mention" ? `<@!${client.user.id}>` : prefix;
  }

  parseCommand(message, prefix) {
    const prefixLength = prefix?.length ?? this.parsePrefix(message)?.length;

    if(prefixLength == null)
      return CommandResult.fromUnknown("");

    let args = message.content.slice(prefixLength).match(this.argumentRegex);

    if(args == null)
      return CommandResult.fromUnknown("");

    const command = this.registry.getCommand(args[0]);

    if(command == null)
      return CommandResult.fromUnknown(args[0]);
    else if(this.defaultRegex)
      args = args.slice(1).map(arg => arg.replace(regexes.quotes, ""));
    else
      args = args.slice(1);

    return {args, command};
  }

  validateCommand(message, command) {
    const lib = Registry.getLibrary();
    const result = lib.validateContext(message, command);

    if(result != null || message.channel.guild == null)
      return result;

    return lib.validatePermissions(message, command);
  }

  async useCooldowns(message, command) {
    let active = await command.useCooldowns(message);
    let group;

    if(active.every(a => !a)) {
      if(command.group == null)
        return;

      group = this.registry.getGroup(command.group);
      active = await group.useCooldowns(message);

      if(active.every(a => !a))
        return;
    }

    const cooldowns = (group ?? command).cooldowns.filter((v, i) => active[i]);
    const info = (await (group ?? command).getCooldowns(message)).filter((v, i) => active[i]);

    return CooldownResult.fromFailure(command, cooldowns, info, group != null);
  }

  async revertCooldowns(message, command) {
    await command.revertCooldowns(message);

    if(command.group != null)
      await this.registry.getGroup(command.group).revertCooldowns(message);
  }

  async runPreconditions(message, command) {
    if(!(command instanceof Command))
      command = this.registry.getCommand(command);

    if(command.group != null) {
      const group = this.registry.getGroup(command.group);

      for(let i = 0; i < group.preconditions.length; i++) {
        const result = await this.registry.getPrecondition(group.preconditions[i])
          .run(command, message, group.preconditionOptions[i]);

        if(result.type !== ResultType.Success)
          return result;
      }
    }

    for(let i = 0; i < command.preconditions.length; i++) {
      const result = await this.registry.getPrecondition(command.preconditions[i])
        .run(command, message, command.preconditionOptions[i]);

      if(result.type !== ResultType.Success)
        return result;
    }
  }

  async runArgumentPreconditions(message, command, info, parsed, value) {
    for(let i = 0; i < info.preconditions.length; i++) {
      result = await this.registry.getArgumentPrecondition(info.preconditions[i])
        .run(value, command, message, info, parsed, info.preconditionOptions[i]);

      if(result.type !== ResultType.Success)
        return result;
    }

    return value;
  }

  async validateArgument(message, command, info, parsed, input) {
    let result = await this.registry.getTypeReader(info.type).read(input, command, message, info, parsed, info.typeOptions);

    if(result.type !== ResultType.Success)
      return result;

    const {value} = result;

    result = await this.runArgumentPreconditions(message, command, info, parsed, value);

    if(result instanceof Result)
      return result;

    return value;
  }

  async parseArguments(message, command, args) {
    const parsed = {};
    let next = 0;

    for(let i = 0; i < command.arguments.length; i++) {
      const info = command.arguments[i];

      if(info.infinite) {
        parsed[info.key] = [];

        if(next >= args.length) {
          if(!info.optional)
            return CommandResult.fromArgumentCount(command, i);

          let result = this.default(message, info.defaultValue);

          if(partOf(ArgumentDefault, info.defaultValue)) {
            result = await this.runArgumentPreconditions(message, command, info, parsed, result);

            if(result instanceof Result)
              return result;
          }

          parsed[info.key].push(result);

          break;
        }

        for(let j = next; j < args.length; j++) {
          const result = await this.validateArgument(message, command, info, parsed, args[j]);

          if(result instanceof Result)
            return result;

          parsed[info.key].push(result);
        }

        continue;
      }else if(next >= args.length) {
        if(!info.optional)
          return CommandResult.fromArgumentCount(command, i);

        let result = this.default(message, info.defaultValue);

        if(partOf(ArgumentDefault, info.defaultValue)) {
          result = await this.runArgumentPreconditions(message, command, info, parsed, result);

          if(result instanceof Result)
            return result;
        }

        parsed[info.key] = result;

        continue;
      }else if(i > command.arguments.length - 1 && info.optional && command.arguments[i + 1].optional
          && !command.arguments[i + 1].infinite) {
        const list = command.arguments;
        const defaulted = [];
        let options = 2;

        for(let j = i + options; j < list.length; j++) {
          if(list[j].optional && !list[j].infinite)
            ++options;
          else
            break;
        }

        const size = i + options;

        for(; i < size; i++) {
          const input = list[i].remainder ? args.slice(next).join(this.separator) : args[next];
          let result = await this.validateArgument(message, command, list[i], parsed, input);

          if(result instanceof TypeReaderResult) {
            defaulted.push(i);
            result = this.default(message, info.defaultValue);

            if(partOf(ArgumentDefault, info.defaultValue)) {
              result = await this.runArgumentPreconditions(message, command, info, parsed, result);

              if(result instanceof Result)
                return result;
            }

            parsed[list[i].key] = result;
          }else if(result instanceof Result) {
            return result;
          }else{
            parsed[list[i].key] = result;
            ++next;
          }
        }

        if(next < i && args.length > next && !list[i - 1].remainder) {
          for(let k = 0; k < defaulted.length; k++) {
            for(let l = next; l < args.length; l++) {
              const result = await this.validateArgument(message, command, list[defaulted[k]], parsed, args[l]);

              if(!(result instanceof TypeReaderResult))
                return CommandResult.fromUnorderedArgument(command, list[defaulted[k]]);
            }
          }
        }

        --i;

        continue;
      }

      const input = info.remainder ? args.slice(next).join(this.separator) : args[next];
      let result = await this.validateArgument(message, command, info, parsed, input);

      if(result instanceof TypeReaderResult && info.optional && i !== command.arguments.length - 1) {
        result = this.default(message, info.defaultValue);

        if(partOf(ArgumentDefault, info.defaultValue)) {
          result = await this.runArgumentPreconditions(message, command, info, parsed, result);

          if(result instanceof Result)
            return result;
        }

        parsed[info.key] = result;
      }else if(result instanceof Result) {
        return result;
      }else{
        parsed[info.key] = result;
        ++next;
      }
    }

    return parsed;
  }

  async runPostconditions(message, command, value) {
    for(let i = 0; i < command.postconditions.length; i++) {
      await this.registry.getPostcondition(command.postconditions[i])
        .run(command, message, value, command.postconditionOptions[i]);
    }

    if(command.group == null)
      return;

    const group = this.registry.getGroup(command.group);

    for(let i = 0; i < group.postconditions.length; i++) {
      await this.registry.getPostcondition(group.postconditions[i])
        .run(command, message, value, group.postconditionOptions[i]);
    }
  }

  async executeCommand(message, command, args) {
    if(!(command instanceof Command))
      command = this.registry.getCommand(command);

    let result = this.validateCommand(message, command);

    if(result instanceof Result)
      return result;

    result = await this.useCooldowns(message, command);

    if(result instanceof Result)
      return result;

    let cooldown = true;

    try {
      result = await this.runPreconditions(message, command);

      if(result instanceof Result) {
        await this.revertCooldowns(message, command);

        return result;
      }

      result = await this.parseArguments(message, command, args);

      if(result instanceof Result) {
        await this.revertCooldowns(message, command);

        return result;
      }

      result = await command.run(message, result);

      if(result instanceof ExecutionResult) {
        if(result.type !== ResultType.Success) {
          await this.revertCooldowns(message, command);
          cooldown = false;
        }

        this.runPostconditions(message, command, result.value);
      }else{
        this.runPostconditions(message, command);
      }
    }catch(err) {
      if(cooldown)
        await this.revertCooldowns(message, command);

      return ErrorResult.fromFailure(command, err);
    }

    return Result.fromSuccess(command);
  }

  async run(message, prefix) {
    const result = this.parseCommand(message, prefix);

    if(result instanceof Result)
      return result;

    const {args, command} = result;

    return this.executeCommand(message, command, args);
  }

  default(message, value) {
    const lib = Registry.getLibrary();

    switch(value) {
      case ArgumentDefault.Author:
        return message.author;
      case ArgumentDefault.Channel:
        return message.channel;
      case ArgumentDefault.Guild:
        return message.channel.guild;
      case ArgumentDefault.HighestRole:
        return lib.highestRole(message);
      case ArgumentDefault.Member:
        return message.member;
      case ArgumentDefault.Message:
        return message;
      default:
        return value;
    }
  }

  static validate(handler) {
    if(!(handler.argumentRegex instanceof RegExp))
      throw TypeError("Handler#argumentRegex must be a regular expression if defined.");
    else if(typeof handler.separator !== "string")
      throw TypeError("Handler#separator must be a string if defined.");
    else if(!(handler.registry instanceof Registry))
      throw TypeError("Handler#registry must be a Registry.");
  }
}

module.exports = Handler;
<|endoftext|>


<commit_before>"use strict";

const Call = require("./Call");
const User = require("./User");

/**
* Represents a message
* @prop {String} id The ID of the message
* @prop {Channel} channel The channel the message is in
* @prop {Guild?} guild The guild the message channel is in. Alias to channel.guild
* @prop {Number} timestamp Timestamp of message creation
* @prop {User} author The message author
* @prop {Member?} member The message author with server-specific data
* @prop {User[]} mentions Array of mentioned users
* @prop {String} content Message content
* @prop {String?} cleanContent Message content with mentions replaced by names, and @everyone/@here escaped
* @prop {String[]} roleMentions Array of mentioned roles' ids
* @prop {String[]?} channelMentions Array of mentions channels' ids
* @prop {Number?} editedTimestamp Timestamp of latest message edit
* @prop {Boolean} tts Whether to play the message using TTS or not
* @prop {Boolean} mentionEveryone Whether the message mentions everyone/here or not
* @prop {Object[]} attachments Array of attachments
* @prop {Object[]} embeds Array of embeds
* @prop {Boolean} command True if message is a command, false if not (CommandClient only)
*/
class Message {
    constructor(data, client) {
        this.id = data.id;
        this._client = client;
        this.type = data.type;
        this.timestamp = Date.parse(data.timestamp);
        this.channel = client.getChannel(data.channel_id) || {
            id: data.channel_id
        };
        if(data.author) {
            if(data.author.discriminator !== "0000") {
                client.users.update(data.author);
            }
            this.author = new User(data.author, client);
            if(this.channel.guild) {
                this.member = this.channel.guild.members.get(this.author.id);
            }
        }
        if(this.type === 0 || this.type === undefined);
        else if(this.type === 1) {
            data.content = `${this.author.mention} added <@${data.mentions[0].id}>.`;
        } else if(this.type === 2) {
            if(this.author.id === data.mentions[0].id) {
                data.content = `@${this.author.username} left the group.`;
            } else {
                data.content = `${this.author.mention} removed @${data.mentions[0].username}.`;
            }
        } else if(this.type === 3) { // (╯°□°）╯︵ ┻━┻
            if(data.call.ended_timestamp) {
                if((!this.channel.lastCall || this.channel.lastCall.endedTimestamp < Date.parse(data.call.ended_timestamp))) {
                    data.call.id = this.id;
                    this.channel.lastCall = new Call(data.call, this.channel);
                }
                if(~data.call.participants.indexOf(client.user.id)) {
                    data.content = `You missed a call from ${this.author.mention}.`;
                } else {
                    data.content = `${this.author.mention} started a call.`;
                }
            } else {
                if(!this.channel.call) {
                    data.call.id = this.id;
                    this.channel.call = new Call(data.call, this.channel);
                }
                data.content = `${this.author.mention} started a call. — Join the call.`;
            }
        } else if(this.type === 4) {
            data.content = `${this.author.mention} changed the channel name: ${data.content}`;
        } else if(this.type === 5) {
            data.content = `${this.author.mention} changed the channel icon.`;
        } else if(this.type === 6) {
            data.content = `${this.author.mention} pinned a message to this channel. See all the pins.`;
        } else {
            throw new Error("Unhandled MESSAGE_CREATE type: " + JSON.stringify(data, null, 2));
        }
        this.update(data, client);
    }

    update(data, client) {
        if(this.type === 3) { // (╯°□°）╯︵ ┻━┻
            (this.channel.call || this.channel.lastCall).update(data.call);
        }

        if(data.content !== undefined) {
            this.content = data.content || "";
            this._cleanContent = null;
            this.mentionEveryone = !!data.mention_everyone;

            this.mentions = data.mentions.map((mention) => client.users.add(mention, client));

            this.roleMentions = data.mention_roles;
        }

        this.pinned = data.pinned !== undefined ? !!data.pinned : this.pinned;
        this.editedTimestamp = data.edited_timestamp !== undefined ? Date.parse(data.edited_timestamp) : this.editedTimestamp;
        this.tts = data.tts !== undefined ? data.tts : this.tts;
        this.attachments = data.attachments !== undefined ? data.attachments : this.attachments; // TODO parse attachments
        this.embeds = data.embeds !== undefined ? data.embeds : this.embeds; // TODO parse embeds
    }

    get cleanContent() {
        if(this._cleanContent) {
            return this._cleanContent;
        }

        this._cleanContent = this.content;

        this.mentions.map((mention) => {
            if(this.channel.guild) {
                var member = this.channel.guild.members.get(mention.id);
                if(member) {
                    this._cleanContent = this._cleanContent.replace(new RegExp(`<@\!${mention.id}>`, "g"), "@" + member.nick || mention.username);
                }
            }
            this._cleanContent = this._cleanContent.replace(new RegExp(`<@\!?${mention.id}>`, "g"), "@" + mention.username);
        });

        if(this.channel.guild) {
            for(var roleID of this.roleMentions) {
                this._cleanContent = this._cleanContent.replace(new RegExp(`<@&${roleID}>`, "g"), "@" + this.channel.guild.roles.get(roleID).name);
            }
        }

        this.channelMentions.forEach((id) => {
            var channel = this._client.getChannel(id);
            if(channel && channel.name && channel.mention) {
                this._cleanContent = this._cleanContent.replace(channel.mention, "#" + channel.name);
            }
        });

        return (this._cleanContent = this._cleanContent.replace(/@everyone/g, "@\u200beveryone").replace(/@here/g, "@\u200bhere"));
    }

    get guild() {
        return this.channel.guild;
    }

    get channelMentions() {
        if(this._channelMentions) {
            return this._channelMentions;
        }

        return (this._channelMentions = (this.content.match(/<#[0-9]+>/g) || []).map((mention) => mention.substring(2, mention.length - 1)));
    }

    /**
    * Edit the message
    * @arg {String} content The updated message content
    * @arg {Boolean} [disableEveryone] Whether to filter @everyone/@here or not (overrides default)
    * @returns {Promise<Message>}
    */
    edit(content, disableEveryone) {
        return this._client.editMessage.call(this._client, this.channel.id, this.id, content, disableEveryone);
    }

    /**
    * Pin the message
    * @returns {Promise}
    */
    pin() {
        return this._client.pinMessage.call(this._client, this.channel.id, this.id);
    }

    /**
    * Unpin the message
    * @returns {Promise}
    */
    unpin() {
        return this._client.unpinMessage.call(this._client, this.channel.id, this.id);
    }

    /**
    * Delete the message
    * @returns {Promise}
    */
    delete() {
        return this._client.deleteMessage.call(this._client, this.channel.id, this.id);
    }
}

module.exports = Message;
<commit_msg>Move Message.member to getter<commit_after>"use strict";

const Call = require("./Call");
const User = require("./User");

/**
* Represents a message
* @prop {String} id The ID of the message
* @prop {Channel} channel The channel the message is in
* @prop {Guild?} guild The guild the message channel is in. Alias to channel.guild
* @prop {Number} timestamp Timestamp of message creation
* @prop {User} author The message author
* @prop {Member?} member The message author with server-specific data
* @prop {User[]} mentions Array of mentioned users
* @prop {String} content Message content
* @prop {String?} cleanContent Message content with mentions replaced by names, and @everyone/@here escaped
* @prop {String[]} roleMentions Array of mentioned roles' ids
* @prop {String[]?} channelMentions Array of mentions channels' ids
* @prop {Number?} editedTimestamp Timestamp of latest message edit
* @prop {Boolean} tts Whether to play the message using TTS or not
* @prop {Boolean} mentionEveryone Whether the message mentions everyone/here or not
* @prop {Object[]} attachments Array of attachments
* @prop {Object[]} embeds Array of embeds
* @prop {Boolean} command True if message is a command, false if not (CommandClient only)
*/
class Message {
    constructor(data, client) {
        this.id = data.id;
        this._client = client;
        this.type = data.type;
        this.timestamp = Date.parse(data.timestamp);
        this.channel = client.getChannel(data.channel_id) || {
            id: data.channel_id
        };
        if(data.author) {
            if(data.author.discriminator !== "0000") {
                client.users.update(data.author);
            }
            this.author = new User(data.author, client);
        }
        if(this.type === 0 || this.type === undefined);
        else if(this.type === 1) {
            data.content = `${this.author.mention} added <@${data.mentions[0].id}>.`;
        } else if(this.type === 2) {
            if(this.author.id === data.mentions[0].id) {
                data.content = `@${this.author.username} left the group.`;
            } else {
                data.content = `${this.author.mention} removed @${data.mentions[0].username}.`;
            }
        } else if(this.type === 3) { // (╯°□°）╯︵ ┻━┻
            if(data.call.ended_timestamp) {
                if((!this.channel.lastCall || this.channel.lastCall.endedTimestamp < Date.parse(data.call.ended_timestamp))) {
                    data.call.id = this.id;
                    this.channel.lastCall = new Call(data.call, this.channel);
                }
                if(~data.call.participants.indexOf(client.user.id)) {
                    data.content = `You missed a call from ${this.author.mention}.`;
                } else {
                    data.content = `${this.author.mention} started a call.`;
                }
            } else {
                if(!this.channel.call) {
                    data.call.id = this.id;
                    this.channel.call = new Call(data.call, this.channel);
                }
                data.content = `${this.author.mention} started a call. — Join the call.`;
            }
        } else if(this.type === 4) {
            data.content = `${this.author.mention} changed the channel name: ${data.content}`;
        } else if(this.type === 5) {
            data.content = `${this.author.mention} changed the channel icon.`;
        } else if(this.type === 6) {
            data.content = `${this.author.mention} pinned a message to this channel. See all the pins.`;
        } else {
            throw new Error("Unhandled MESSAGE_CREATE type: " + JSON.stringify(data, null, 2));
        }
        this.update(data, client);
    }

    update(data, client) {
        if(this.type === 3) { // (╯°□°）╯︵ ┻━┻
            (this.channel.call || this.channel.lastCall).update(data.call);
        }

        if(data.content !== undefined) {
            this.content = data.content || "";
            this._cleanContent = null;
            this.mentionEveryone = !!data.mention_everyone;

            this.mentions = data.mentions.map((mention) => client.users.add(mention, client));

            this.roleMentions = data.mention_roles;
        }

        this.pinned = data.pinned !== undefined ? !!data.pinned : this.pinned;
        this.editedTimestamp = data.edited_timestamp !== undefined ? Date.parse(data.edited_timestamp) : this.editedTimestamp;
        this.tts = data.tts !== undefined ? data.tts : this.tts;
        this.attachments = data.attachments !== undefined ? data.attachments : this.attachments; // TODO parse attachments
        this.embeds = data.embeds !== undefined ? data.embeds : this.embeds; // TODO parse embeds
    }

    get cleanContent() {
        if(this._cleanContent) {
            return this._cleanContent;
        }

        this._cleanContent = this.content;

        this.mentions.map((mention) => {
            if(this.channel.guild) {
                var member = this.channel.guild.members.get(mention.id);
                if(member) {
                    this._cleanContent = this._cleanContent.replace(new RegExp(`<@\!${mention.id}>`, "g"), "@" + member.nick || mention.username);
                }
            }
            this._cleanContent = this._cleanContent.replace(new RegExp(`<@\!?${mention.id}>`, "g"), "@" + mention.username);
        });

        if(this.channel.guild) {
            for(var roleID of this.roleMentions) {
                this._cleanContent = this._cleanContent.replace(new RegExp(`<@&${roleID}>`, "g"), "@" + this.channel.guild.roles.get(roleID).name);
            }
        }

        this.channelMentions.forEach((id) => {
            var channel = this._client.getChannel(id);
            if(channel && channel.name && channel.mention) {
                this._cleanContent = this._cleanContent.replace(channel.mention, "#" + channel.name);
            }
        });

        return (this._cleanContent = this._cleanContent.replace(/@everyone/g, "@\u200beveryone").replace(/@here/g, "@\u200bhere"));
    }

    get guild() {
        return this.channel.guild;
    }

    get channelMentions() {
        if(this._channelMentions) {
            return this._channelMentions;
        }

        return (this._channelMentions = (this.content.match(/<#[0-9]+>/g) || []).map((mention) => mention.substring(2, mention.length - 1)));
    }

    get member() {
        return this.channel.guild && this.author && this.channel.guild.members.get(this.author.id) || null;
    }

    /**
    * Edit the message
    * @arg {String} content The updated message content
    * @arg {Boolean} [disableEveryone] Whether to filter @everyone/@here or not (overrides default)
    * @returns {Promise<Message>}
    */
    edit(content, disableEveryone) {
        return this._client.editMessage.call(this._client, this.channel.id, this.id, content, disableEveryone);
    }

    /**
    * Pin the message
    * @returns {Promise}
    */
    pin() {
        return this._client.pinMessage.call(this._client, this.channel.id, this.id);
    }

    /**
    * Unpin the message
    * @returns {Promise}
    */
    unpin() {
        return this._client.unpinMessage.call(this._client, this.channel.id, this.id);
    }

    /**
    * Delete the message
    * @returns {Promise}
    */
    delete() {
        return this._client.deleteMessage.call(this._client, this.channel.id, this.id);
    }
}

module.exports = Message;
<|endoftext|>