<commit_before>// Node.js API.
var http = require('http')

// Control-flow utilities.
var cadence = require('cadence')
var delta = require('delta')

// Route Sencha Connect middleware based on request method and URL patterns.
var dispatch = require('dispatch')

// Exceptions that you can catch by type.
var interrupt = require('interrupt').createInterrupter('reactor')

// Contextualized callbacks and event handlers.
var Operation = require('operation')

// Evented work queue.
var Turnstile = require('turnstile')
Turnstile.Queue = require('turnstile/queue')

// Catch exceptions based on a regex match of an error message or property.
var rescue = require('rescue/redux')

// Return the first not null-like value.
var coalesce = require('extant')

// Do nothing.
var noop = require('nop')

// MIME type parser.
var typer = require('media-typer')

var arrayed = require('./arrayed')

function Constructor (object, dispatch) {
    this._object = object
    this._dispatch = dispatch
    this._use = []
    this._defaultUse = true
    this.logger = function (entry) {
        if (entry.error) {
            console.log(entry.error.stack)
        }
    }
}

Constructor.prototype.useDefault = function () {
    this.use(require('express-auth-parser'))
    this.use(require('body-parser').urlencoded({ extended: false, limit: '64mb' }))
    this.use(require('body-parser').json({ limit: '64mb' }))
}

Constructor.prototype.use = function () {
    var vargs = Array.prototype.slice.call(arguments)
    this._defaultUse = false
    while (vargs.length) {
        var varg = vargs.shift()
        if (Array.isArray(varg)) {
            this._use.push.apply(this._use, varg)
        } else {
            this._use.push(varg)
        }
    }
}

Constructor.prototype.dispatch = function () {
    var vargs = Array.prototype.slice.call(arguments)
    this._dispatch[vargs.shift()] = Operation(vargs, { object: this._object })
}

function handler (queue, before, operation) {
    return function (request, response, next) {
        var vargs = Array.prototype.slice.call(arguments, 3)
        request.entry = {
            when: {
                push: Date.now(),
                start: null,
                headers: null,
                finish: null
            },
            health: {
                push: JSON.parse(JSON.stringify(queue.turnstile.health)),
                start: null,
                finish: null
            },
            duration: {
                start: null,
                headers: null,
                finish: null,
            },
            request: {
                method: request.method,
                header: request.headers,
                url: request.url,
                vargs: vargs
            }
        }
        before(request, response, function (error) {
            queue.push({
                error: coalesce(error),
                operation: operation,
                request: request,
                response: response,
                vargs: vargs
            })
        })
    }
}

function Reactor (object, configurator) {
    var constructor = new Constructor(object, this._dispatch = {})
    configurator(constructor)
    this.turnstile = new Turnstile({
        Date: coalesce(constructor.Date, Date),
        turnstiles: coalesce(constructor.turnstiles, 24),
        timeout: coalesce(constructor.timeout)
    })
    this._queue = new Turnstile.Queue(this, '_respond', this.turnstile)
    this._logger = constructor.logger
    this._completed = coalesce(constructor.completed, noop)
    this._object = object
    if (constructor._defaultUse) {
        constructor.useDefault()
    }
    var before = require('connect')()
    constructor._use.forEach(function (middleware) {
        before.use(middleware)
    })
    var dispatcher = {}
    for (var pattern in this._dispatch) {
        dispatcher[pattern] = handler(this._queue, before, this._dispatch[pattern])
    }
    this.middleware = require('connect')().use(dispatch(dispatcher))
}

Reactor.prototype._timeout = cadence(function () { throw 503 })

function createProperties (properties) {
    return {
        statusCode: properties.statusCode,
        headers: coalesce(properties.headers, {}),
        description: coalesce(properties.description)
    }
}

Reactor.prototype._respond = cadence(function (async, envelope) {
    var work = envelope.body
    var next = work.next

    var entry = work.request.entry

    entry.when.start = Date.now()
    entry.health.start = JSON.parse(JSON.stringify(this.turnstile.health))

    work.request.entry = entry

    if (envelope.timedout) {
        work.operation = Operation([ this, '_timeout' ])
    }

    var finish

    async(function () {
        async(function () {
            async([function () {
                async(function () {
                    if (work.error) {
                        throw { cause: work.error, statusCode: coalesce(work.error.statusCode) }
                    }
                    work.operation.apply(null, [ work.request ].concat(work.vargs, async()))
                }, function () {
                    return arrayed(Array.prototype.slice.call(arguments))
                })
            }, function (caught) {
                for (;;) {
                    try {
                        return rescue(/^reactor#http$/m, function (rescued) {
                            var error = rescued.errors.shift()
                            var statusCode = error.statusCode
                            var description = coalesce(error.description, http.STATUS_CODES[statusCode])
                            var headers = coalesce(error.headers, {})
                            var body = coalesce(error.body, description)

                            entry.error = coalesce(error.cause)

                            interrupt.assert(description != null, 'unknown.http.status', { statusCode: statusCode })

                            return {
                                statusCode,
                                description: description,
                                headers: headers,
                                body: body
                            }
                        })(caught)
                    } catch (error) {
                        if (
                            typeof error == 'number' &&
                            !isNaN(error) &&
                            (error | 0) === error &&
                            Math.floor(error / 100) <= 5 &&
                            Math.floor(error / 100) >= 3
                        ) {
                            error = { statusCode: error }
                        } else if (typeof error == 'string') {
                            error = { statusCode: 307, location: error }
                        }
                        if (Array.isArray(error)) {
                            error = interrupt('http', arrayed(error.slice()))
                        } else if (
                            ! (error instanceof Error) &&
                            typeof error == 'object' &&
                            typeof error.statusCode == 'number' &&
                            !isNaN(error.statusCode) &&
                            (error.statusCode | 0) === error.statusCode &&
                            Math.floor(error.statusCode / 100) <= 5 &&
                            Math.floor(error.statusCode / 100) >= 2
                        ) {
                            var properties = createProperties(error)
                            if (error.location) {
                                properties.headers.location = error.location
                            }
                            error = interrupt('http', properties, { cause: coalesce(error.cause) })
                        } else {
                            error = { statusCode: 500, cause: error }
                        }
                        caught = error
                    }
                }
            }])
        }, function (responder) {
            var body, f
            if (typeof responder.body == 'function') {
                f = responder.body
            } else {
                if (!('content-type' in responder.headers)) {
                    responder.headers['content-type'] = 'application/json'
                }
                var type = typer.parse(responder.headers['content-type'])
                if (
                    !Buffer.isBuffer(responder.body) &&
                    type.type + '/' + type.subtype == 'application/json'
                ) {
                    responder.body = new Buffer(JSON.stringify(responder.body) + '\n')
                }
                f = function (response) {
                    response.end(responder.body)
                }
            }

            work.response.writeHead(responder.statusCode, responder.description, responder.headers)

            entry.when.headers = Date.now()

            entry.statusCode = responder.statusCode
            entry.description = responder.description
            entry.headers = responder.headers

            var finish = delta(async()).ee(work.response).on('finish')

            async([function () {
                async(function () {
                    if (f.length == 2) {
                        f.call(work.operation.object, work.response, async())
                    } else {
                        return [ f.call(work.operation.object, work.response) ]
                    }
                }, function () {
                    return []
                })
            }, function (error) {
                entry.error = error
                work.response.end()
                finish.cancel()
            }])
        })
    }, function () {
        entry.health.done = JSON.parse(JSON.stringify(this.turnstile.health))
        entry.when.finish = Date.now()
        entry.duration = {
            start: entry.when.start - entry.when.push,
            headers: entry.when.headers - entry.when.push,
            finish: entry.when.finish - entry.when.push
        }
        this._logger.call(null, entry)
        this._completed.call(null, entry)
    })
})

Reactor.resend = function (statusCode, headers, body) {
    return Reactor.send({ statusCode: statusCode, headers: headers }, body)
}

Reactor.send = function (properties, buffer) {
    var headers = JSON.parse(JSON.stringify(properties.headers))
    delete headers['content-length']
    delete headers['transfer-encoding']
    return [ properties.statusCode, properties.statusMessage, headers, buffer ]
}

Reactor.stream = function (properties, stream) {
    var vargs = Array.prototype.slice.call(arguments)
    var stream = vargs.pop()
    vargs.push(function (response) { stream.pipe(response) })
    var response = arrayed(vargs)
    response.headers['content-length']
    response.headers['transfer-encoding'] = 'chunked'
    return [ response.statusCode, response.description, response.headers, response.body ]
}

module.exports = Reactor
<commit_msg>Upgrade `interrupt` to 6.3.x.<commit_after>// Node.js API.
var http = require('http')

// Control-flow utilities.
var cadence = require('cadence')
var delta = require('delta')

// Route Sencha Connect middleware based on request method and URL patterns.
var dispatch = require('dispatch')

// Exceptions that you can catch by type.
var Interrupt = require('interrupt').createInterrupter('reactor')

// Contextualized callbacks and event handlers.
var Operation = require('operation')

// Evented work queue.
var Turnstile = require('turnstile')
Turnstile.Queue = require('turnstile/queue')

// Catch exceptions based on a regex match of an error message or property.
var rescue = require('rescue/redux')

// Return the first not null-like value.
var coalesce = require('extant')

// Do nothing.
var noop = require('nop')

// MIME type parser.
var typer = require('media-typer')

var arrayed = require('./arrayed')

function Constructor (object, dispatch) {
    this._object = object
    this._dispatch = dispatch
    this._use = []
    this._defaultUse = true
    this.logger = function (entry) {
        if (entry.error) {
            console.log(entry.error.stack)
        }
    }
}

Constructor.prototype.useDefault = function () {
    this.use(require('express-auth-parser'))
    this.use(require('body-parser').urlencoded({ extended: false, limit: '64mb' }))
    this.use(require('body-parser').json({ limit: '64mb' }))
}

Constructor.prototype.use = function () {
    var vargs = Array.prototype.slice.call(arguments)
    this._defaultUse = false
    while (vargs.length) {
        var varg = vargs.shift()
        if (Array.isArray(varg)) {
            this._use.push.apply(this._use, varg)
        } else {
            this._use.push(varg)
        }
    }
}

Constructor.prototype.dispatch = function () {
    var vargs = Array.prototype.slice.call(arguments)
    this._dispatch[vargs.shift()] = Operation(vargs, { object: this._object })
}

function handler (queue, before, operation) {
    return function (request, response, next) {
        var vargs = Array.prototype.slice.call(arguments, 3)
        request.entry = {
            when: {
                push: Date.now(),
                start: null,
                headers: null,
                finish: null
            },
            health: {
                push: JSON.parse(JSON.stringify(queue.turnstile.health)),
                start: null,
                finish: null
            },
            duration: {
                start: null,
                headers: null,
                finish: null,
            },
            request: {
                method: request.method,
                header: request.headers,
                url: request.url,
                vargs: vargs
            }
        }
        before(request, response, function (error) {
            queue.push({
                error: coalesce(error),
                operation: operation,
                request: request,
                response: response,
                vargs: vargs
            })
        })
    }
}

function Reactor (object, configurator) {
    var constructor = new Constructor(object, this._dispatch = {})
    configurator(constructor)
    this.turnstile = new Turnstile({
        Date: coalesce(constructor.Date, Date),
        turnstiles: coalesce(constructor.turnstiles, 24),
        timeout: coalesce(constructor.timeout)
    })
    this._queue = new Turnstile.Queue(this, '_respond', this.turnstile)
    this._logger = constructor.logger
    this._completed = coalesce(constructor.completed, noop)
    this._object = object
    if (constructor._defaultUse) {
        constructor.useDefault()
    }
    var before = require('connect')()
    constructor._use.forEach(function (middleware) {
        before.use(middleware)
    })
    var dispatcher = {}
    for (var pattern in this._dispatch) {
        dispatcher[pattern] = handler(this._queue, before, this._dispatch[pattern])
    }
    this.middleware = require('connect')().use(dispatch(dispatcher))
}

Reactor.prototype._timeout = cadence(function () { throw 503 })

function createProperties (properties) {
    return {
        statusCode: properties.statusCode,
        headers: coalesce(properties.headers, {}),
        description: coalesce(properties.description)
    }
}

Reactor.prototype._respond = cadence(function (async, envelope) {
    var work = envelope.body
    var next = work.next

    var entry = work.request.entry

    entry.when.start = Date.now()
    entry.health.start = JSON.parse(JSON.stringify(this.turnstile.health))

    work.request.entry = entry

    if (envelope.timedout) {
        work.operation = Operation([ this, '_timeout' ])
    }

    var finish

    async(function () {
        async(function () {
            async([function () {
                async(function () {
                    if (work.error) {
                        throw { cause: work.error, statusCode: coalesce(work.error.statusCode) }
                    }
                    work.operation.apply(null, [ work.request ].concat(work.vargs, async()))
                }, function () {
                    return arrayed(Array.prototype.slice.call(arguments))
                })
            }, function (caught) {
                for (;;) {
                    try {
                        return rescue(/^reactor#http$/m, function (rescued) {
                            var error = rescued.errors.shift()
                            var statusCode = error.statusCode
                            var description = coalesce(error.description, http.STATUS_CODES[statusCode])
                            var headers = coalesce(error.headers, {})
                            var body = coalesce(error.body, description)

                            entry.error = coalesce(error.cause)

                            Interrupt.assert(description != null, 'unknown.http.status', { statusCode: statusCode })

                            return {
                                statusCode,
                                description: description,
                                headers: headers,
                                body: body
                            }
                        })(caught)
                    } catch (error) {
                        if (
                            typeof error == 'number' &&
                            !isNaN(error) &&
                            (error | 0) === error &&
                            Math.floor(error / 100) <= 5 &&
                            Math.floor(error / 100) >= 3
                        ) {
                            error = { statusCode: error }
                        } else if (typeof error == 'string') {
                            error = { statusCode: 307, location: error }
                        }
                        if (Array.isArray(error)) {
                            error = new Interrupt('http', arrayed(error.slice()))
                        } else if (
                            ! (error instanceof Error) &&
                            typeof error == 'object' &&
                            typeof error.statusCode == 'number' &&
                            !isNaN(error.statusCode) &&
                            (error.statusCode | 0) === error.statusCode &&
                            Math.floor(error.statusCode / 100) <= 5 &&
                            Math.floor(error.statusCode / 100) >= 2
                        ) {
                            var properties = createProperties(error)
                            if (error.location) {
                                properties.headers.location = error.location
                            }
                            properties.cause = coalesce(error.cause)
                            error = new Interrupt('http', properties)
                        } else {
                            error = { statusCode: 500, cause: error }
                        }
                        caught = error
                    }
                }
            }])
        }, function (responder) {
            var body, f
            if (typeof responder.body == 'function') {
                f = responder.body
            } else {
                if (!('content-type' in responder.headers)) {
                    responder.headers['content-type'] = 'application/json'
                }
                var type = typer.parse(responder.headers['content-type'])
                if (
                    !Buffer.isBuffer(responder.body) &&
                    type.type + '/' + type.subtype == 'application/json'
                ) {
                    responder.body = new Buffer(JSON.stringify(responder.body) + '\n')
                }
                f = function (response) {
                    response.end(responder.body)
                }
            }

            work.response.writeHead(responder.statusCode, responder.description, responder.headers)

            entry.when.headers = Date.now()

            entry.statusCode = responder.statusCode
            entry.description = responder.description
            entry.headers = responder.headers

            var finish = delta(async()).ee(work.response).on('finish')

            async([function () {
                async(function () {
                    if (f.length == 2) {
                        f.call(work.operation.object, work.response, async())
                    } else {
                        return [ f.call(work.operation.object, work.response) ]
                    }
                }, function () {
                    return []
                })
            }, function (error) {
                entry.error = error
                work.response.end()
                finish.cancel()
            }])
        })
    }, function () {
        entry.health.done = JSON.parse(JSON.stringify(this.turnstile.health))
        entry.when.finish = Date.now()
        entry.duration = {
            start: entry.when.start - entry.when.push,
            headers: entry.when.headers - entry.when.push,
            finish: entry.when.finish - entry.when.push
        }
        this._logger.call(null, entry)
        this._completed.call(null, entry)
    })
})

Reactor.resend = function (statusCode, headers, body) {
    return Reactor.send({ statusCode: statusCode, headers: headers }, body)
}

Reactor.send = function (properties, buffer) {
    var headers = JSON.parse(JSON.stringify(properties.headers))
    delete headers['content-length']
    delete headers['transfer-encoding']
    return [ properties.statusCode, properties.statusMessage, headers, buffer ]
}

Reactor.stream = function (properties, stream) {
    var vargs = Array.prototype.slice.call(arguments)
    var stream = vargs.pop()
    vargs.push(function (response) { stream.pipe(response) })
    var response = arrayed(vargs)
    response.headers['content-length']
    response.headers['transfer-encoding'] = 'chunked'
    return [ response.statusCode, response.description, response.headers, response.body ]
}

module.exports = Reactor
<|endoftext|>


<commit_before>// Thin GL program wrapp to cache uniform locations/values, do compile-time pre-processing
// (injecting #defines and #pragma transforms into shaders), etc.

var GL = require('./gl.js');
var Utils = require('../utils.js');
var Queue = require('queue-async');

GLProgram.id = 0; // assign each program a unique id
GLProgram.programs = {}; // programs, by id

function GLProgram (gl, vertex_shader, fragment_shader, options)
{
    options = options || {};

    this.gl = gl;
    this.program = null;
    this.compiled = false;
    this.defines = options.defines || {}; // key/values inserted as #defines into shaders at compile-time
    this.transforms = options.transforms; // key/values for URLs of blocks that can be injected into shaders at compile-time
    this.uniforms = {}; // program locations of uniforms, set/updated at compile-time
    this.attribs = {}; // program locations of vertex attributes

    this.vertex_shader = vertex_shader;
    this.fragment_shader = fragment_shader;

    // Save pre-modified versions of shaders that will act as "templates" for #defines, code injection, etc.
    this.template_vertex_shader = this.vertex_shader;
    this.template_fragment_shader = this.fragment_shader;

    this.id = GLProgram.id++;
    GLProgram.programs[this.id] = this;
    this.name = options.name; // can provide a program name (useful for debugging)

    this.compile(options.callback);
};

// Creates a program that will refresh from source URLs each time it is compiled
GLProgram.createProgramFromURLs = function (gl, vertex_shader_url, fragment_shader_url, options)
{
    var program = Object.create(GLProgram.prototype);

    program.vertex_shader_url = vertex_shader_url;
    program.fragment_shader_url = fragment_shader_url;

    program.updateVertexShaderSource = function () {
        var source;
        var req = new XMLHttpRequest();
        req.onload = function () { source = req.response; };
        req.open('GET', Utils.urlForPath(this.vertex_shader_url) + '?' + (+new Date()), false /* async flag */);
        req.responseType = 'text';
        req.send();
        return source;
    };

    program.updateFragmentShaderSource = function () {
        var source;
        var req = new XMLHttpRequest();
        req.onload = function () { source = req.response; };
        req.open('GET', Utils.urlForPath(this.fragment_shader_url) + '?' + (+new Date()), false /* async flag */);
        req.responseType = 'text';
        req.send();
        return source;
    };

    GLProgram.call(program, gl, null, null, options);
    return program;
};

// Use program wrapper with simple state cache
GLProgram.prototype.use = function ()
{
    if (!this.compiled) {
        return;
    }

    if (GLProgram.current != this) {
        this.gl.useProgram(this.program);
    }
    GLProgram.current = this;
};
GLProgram.current = null;

// Global defines applied to all programs (duplicate properties for a specific program will take precedence)
GLProgram.defines = {};

GLProgram.prototype.compile = function (callback)
{
    var queue = Queue();

    // Optionally update sources
    if (typeof this.updateVertexShaderSource == 'function') {
        this.template_vertex_shader = this.updateVertexShaderSource();
    }
    if (typeof this.updateFragmentShaderSource == 'function') {
        this.template_fragment_shader = this.updateFragmentShaderSource();
    }

    // Copy sources from pre-modified template
    this.vertex_shader = this.template_vertex_shader;
    this.fragment_shader = this.template_fragment_shader;

    // Make list of defines to be injected later
    var defines = this.buildDefineList();

    // Inject user-defined transforms (arbitrary code points matching named #pragmas)
    // Replace according to this pattern:
    // #pragma tangram: [key]
    // e.g. #pragma tangram: globals

    // TODO: flag to avoid re-retrieving transform URLs over network when rebuilding?
    // TODO: support glslify #pragma export names for better compatibility? (e.g. rename main() functions)
    // TODO: auto-insert uniforms referenced in mode definition, but not in shader base or transforms? (problem: don't have access to uniform list/type here)

    // Gather all transform code snippets (can be either inline in the style file, or over the network via URL)
    // This is an async process, since code may be retrieved remotely
    var regexp;
    var loaded_transforms = {}; // master list of transforms, with an ordered list for each (since we want to guarantee order of transforms)
    if (this.transforms != null) {

        for (var key in this.transforms) {
            var transform = this.transforms[key];
            if (transform == null) {
                continue;
            }

            // Each code point can be a single item (string or hash object) or a list (array object with non-zero length)
            if (typeof transform == 'string' || (typeof transform == 'object' && transform.length == null)) {
                transform = [transform];
            }

            // First find code replace points in shaders
            var regexp = new RegExp('^\\s*#pragma\\s+tangram:\\s+' + key + '\\s*$', 'm');
            var inject_vertex = this.vertex_shader.match(regexp);
            var inject_fragment = this.fragment_shader.match(regexp);

            // Avoid network request if nothing to replace
            if (inject_vertex == null && inject_fragment == null) {
                continue;
            }

            // Collect all transforms for this type
            loaded_transforms[key] = {};
            loaded_transforms[key].regexp = new RegExp(regexp); // save regexp so we can inject later without having to recreate it
            loaded_transforms[key].inject_vertex = (inject_vertex != null); // save regexp code point matches so we don't have to do them again
            loaded_transforms[key].inject_fragment = (inject_fragment != null);
            loaded_transforms[key].list = [];

            // Get the code (possibly over the network, so needs to be async)
            for (var u=0; u < transform.length; u++) {
                queue.defer(GLProgram.loadTransform, loaded_transforms, transform[u], key, u);
            }

            // Add a #define for this injection point
            defines['TANGRAM_TRANSFORM_' + key.replace(' ', '_').toUpperCase()] = true;
        }
    }

    // When all transform code snippets are collected, combine and inject them
    queue.awaitAll(function(error) {
        if (error) {
            console.log("error loading transforms: " + error);
            return;
        }

        // Do the code injection with the collected sources
        for (var t in loaded_transforms) {
            // Concatenate
            var combined_source = "";
            for (var s=0; s < loaded_transforms[t].list.length; s++) {
                combined_source += loaded_transforms[t].list[s] + '\n';
            }

            // Inject
            if (loaded_transforms[t].inject_vertex != null) {
                this.vertex_shader = this.vertex_shader.replace(loaded_transforms[t].regexp, combined_source);
            }
            if (loaded_transforms[t].inject_fragment != null) {
                this.fragment_shader = this.fragment_shader.replace(loaded_transforms[t].regexp, combined_source);
            }
        }

        // Clean-up any #pragmas that weren't replaced (to prevent compiler warnings)
        var regexp = new RegExp('^\\s*#pragma\\s+tangram:\\s+\\w+\\s*$', 'gm');
        this.vertex_shader = this.vertex_shader.replace(regexp, '');
        this.fragment_shader = this.fragment_shader.replace(regexp, '');

        // Build & inject defines
        // This is done *after* code injection so that we can add defines for which code points were injected
        var define_str = GLProgram.buildDefineString(defines);
        this.vertex_shader = define_str + this.vertex_shader;
        this.fragment_shader = define_str + this.fragment_shader;

        // Include program info useful for debugging
        var info = (this.name ? (this.name + ' / id ' + this.id) : ('id ' + this.id));
        this.vertex_shader = '// Program: ' + info + '\n' + this.vertex_shader;
        this.fragment_shader = '// Program: ' + info + '\n' + this.fragment_shader;

        // Compile & set uniforms to cached values
        try {
            this.program = GL.updateProgram(this.gl, this.program, this.vertex_shader, this.fragment_shader);
            this.compiled = true;
        }
        catch (e) {
            this.program = null;
            this.compiled = false;
        }

        this.use();
        this.refreshUniforms();
        this.refreshAttributes();

        // Notify caller
        if (typeof callback == 'function') {
            callback(this);
        }
    }.bind(this));
};

// Retrieve a single transform, for a given injection point, at a certain index (to preserve original order)
// Can be async, calls 'complete' callback when done
GLProgram.loadTransform = function (transforms, block, key, index, complete) {
    // Can be an inline block of GLSL, or a URL to retrieve GLSL block from
    var type, value, source;

    // Inline code
    if (typeof block == 'string') {
        transforms[key].list[index] = block;
        complete();
    }
    // Remote code
    else if (typeof block == 'object' && block.url) {
        var req = new XMLHttpRequest();

        req.onload = function () {
            source = req.response;
            transforms[key].list[index] = source;
            complete();
        };
        req.open('GET', Utils.urlForPath(block.url) + '?' + (+new Date()), true /* async flag */);
        req.responseType = 'text';
        req.send();
    }
};

// Make list of defines (global, then program-specific)
GLProgram.prototype.buildDefineList = function () {
    var defines = {};
    for (var d in GLProgram.defines) {
        defines[d] = GLProgram.defines[d];
    }
    for (var d in this.defines) {
        defines[d] = this.defines[d];
    }
    return defines;
};

// Turn #defines into a combined string
GLProgram.buildDefineString = function (defines) {
    var define_str = "";
    for (var d in defines) {
        if (defines[d] == false) {
            continue;
        }
        else if (typeof defines[d] == 'boolean' && defines[d] == true) { // booleans are simple defines with no value
            define_str += "#define " + d + "\n";
        }
        else if (typeof defines[d] == 'number' && Math.floor(defines[d]) == defines[d]) { // int to float conversion to satisfy GLSL floats
            define_str += "#define " + d + " " + defines[d].toFixed(1) + "\n";
        }
        else { // any other float or string value
            define_str += "#define " + d + " " + defines[d] + "\n";
        }
    }
    return define_str;
};

// ex: program.uniform('3f', 'position', x, y, z);
// TODO: only update uniforms when changed
GLProgram.prototype.uniform = function (method, name) // method-appropriate arguments follow
{
    if (!this.compiled) {
        return;
    }

    var uniform = (this.uniforms[name] = this.uniforms[name] || {});
    uniform.name = name;
    uniform.location = uniform.location || this.gl.getUniformLocation(this.program, name);
    uniform.method = 'uniform' + method;
    uniform.values = Array.prototype.slice.call(arguments, 2);
    this.updateUniform(name);
};

// Set a single uniform
GLProgram.prototype.updateUniform = function (name)
{
    if (!this.compiled) {
        return;
    }

    var uniform = this.uniforms[name];
    if (uniform == null || uniform.location == null) {
        return;
    }

    this.use();
    this.gl[uniform.method].apply(this.gl, [uniform.location].concat(uniform.values)); // call appropriate GL uniform method and pass through arguments
};

// Refresh uniform locations and set to last cached values
GLProgram.prototype.refreshUniforms = function ()
{
    if (!this.compiled) {
        return;
    }

    for (var u in this.uniforms) {
        this.uniforms[u].location = this.gl.getUniformLocation(this.program, u);
        this.updateUniform(u);
    }
};

GLProgram.prototype.refreshAttributes = function ()
{
    // var len = this.gl.getProgramParameter(this.program, this.gl.ACTIVE_ATTRIBUTES);
    // for (var i=0; i < len; i++) {
    //     var a = this.gl.getActiveAttrib(this.program, i);
    //     console.log(a);
    // }
    this.attribs = {};
};

// Get the location of a vertex attribute
GLProgram.prototype.attribute = function (name)
{
    if (!this.compiled) {
        return;
    }

    var attrib = (this.attribs[name] = this.attribs[name] || {});
    if (attrib.location != null) {
        return attrib;
    }

    attrib.name = name;
    attrib.location = this.gl.getAttribLocation(this.program, name);

    // var info = this.gl.getActiveAttrib(this.program, attrib.location);
    // attrib.type = info.type;
    // attrib.size = info.size;

    return attrib;
};

if (module !== undefined) {
    module.exports = GLProgram;
}
<commit_msg>don't pass GL program to the compilation callback unecessary (for now anyway) and wreaking havoc on the queue callbacks (being interpreted as errors, but reluctant to switch to node-style error-first pattern)<commit_after>// Thin GL program wrapp to cache uniform locations/values, do compile-time pre-processing
// (injecting #defines and #pragma transforms into shaders), etc.

var GL = require('./gl.js');
var Utils = require('../utils.js');
var Queue = require('queue-async');

GLProgram.id = 0; // assign each program a unique id
GLProgram.programs = {}; // programs, by id

function GLProgram (gl, vertex_shader, fragment_shader, options)
{
    options = options || {};

    this.gl = gl;
    this.program = null;
    this.compiled = false;
    this.defines = options.defines || {}; // key/values inserted as #defines into shaders at compile-time
    this.transforms = options.transforms; // key/values for URLs of blocks that can be injected into shaders at compile-time
    this.uniforms = {}; // program locations of uniforms, set/updated at compile-time
    this.attribs = {}; // program locations of vertex attributes

    this.vertex_shader = vertex_shader;
    this.fragment_shader = fragment_shader;

    // Save pre-modified versions of shaders that will act as "templates" for #defines, code injection, etc.
    this.template_vertex_shader = this.vertex_shader;
    this.template_fragment_shader = this.fragment_shader;

    this.id = GLProgram.id++;
    GLProgram.programs[this.id] = this;
    this.name = options.name; // can provide a program name (useful for debugging)

    this.compile(options.callback);
};

// Creates a program that will refresh from source URLs each time it is compiled
GLProgram.createProgramFromURLs = function (gl, vertex_shader_url, fragment_shader_url, options)
{
    var program = Object.create(GLProgram.prototype);

    program.vertex_shader_url = vertex_shader_url;
    program.fragment_shader_url = fragment_shader_url;

    program.updateVertexShaderSource = function () {
        var source;
        var req = new XMLHttpRequest();
        req.onload = function () { source = req.response; };
        req.open('GET', Utils.urlForPath(this.vertex_shader_url) + '?' + (+new Date()), false /* async flag */);
        req.responseType = 'text';
        req.send();
        return source;
    };

    program.updateFragmentShaderSource = function () {
        var source;
        var req = new XMLHttpRequest();
        req.onload = function () { source = req.response; };
        req.open('GET', Utils.urlForPath(this.fragment_shader_url) + '?' + (+new Date()), false /* async flag */);
        req.responseType = 'text';
        req.send();
        return source;
    };

    GLProgram.call(program, gl, null, null, options);
    return program;
};

// Use program wrapper with simple state cache
GLProgram.prototype.use = function ()
{
    if (!this.compiled) {
        return;
    }

    if (GLProgram.current != this) {
        this.gl.useProgram(this.program);
    }
    GLProgram.current = this;
};
GLProgram.current = null;

// Global defines applied to all programs (duplicate properties for a specific program will take precedence)
GLProgram.defines = {};

GLProgram.prototype.compile = function (callback)
{
    var queue = Queue();

    // Optionally update sources
    if (typeof this.updateVertexShaderSource == 'function') {
        this.template_vertex_shader = this.updateVertexShaderSource();
    }
    if (typeof this.updateFragmentShaderSource == 'function') {
        this.template_fragment_shader = this.updateFragmentShaderSource();
    }

    // Copy sources from pre-modified template
    this.vertex_shader = this.template_vertex_shader;
    this.fragment_shader = this.template_fragment_shader;

    // Make list of defines to be injected later
    var defines = this.buildDefineList();

    // Inject user-defined transforms (arbitrary code points matching named #pragmas)
    // Replace according to this pattern:
    // #pragma tangram: [key]
    // e.g. #pragma tangram: globals

    // TODO: flag to avoid re-retrieving transform URLs over network when rebuilding?
    // TODO: support glslify #pragma export names for better compatibility? (e.g. rename main() functions)
    // TODO: auto-insert uniforms referenced in mode definition, but not in shader base or transforms? (problem: don't have access to uniform list/type here)

    // Gather all transform code snippets (can be either inline in the style file, or over the network via URL)
    // This is an async process, since code may be retrieved remotely
    var regexp;
    var loaded_transforms = {}; // master list of transforms, with an ordered list for each (since we want to guarantee order of transforms)
    if (this.transforms != null) {

        for (var key in this.transforms) {
            var transform = this.transforms[key];
            if (transform == null) {
                continue;
            }

            // Each code point can be a single item (string or hash object) or a list (array object with non-zero length)
            if (typeof transform == 'string' || (typeof transform == 'object' && transform.length == null)) {
                transform = [transform];
            }

            // First find code replace points in shaders
            var regexp = new RegExp('^\\s*#pragma\\s+tangram:\\s+' + key + '\\s*$', 'm');
            var inject_vertex = this.vertex_shader.match(regexp);
            var inject_fragment = this.fragment_shader.match(regexp);

            // Avoid network request if nothing to replace
            if (inject_vertex == null && inject_fragment == null) {
                continue;
            }

            // Collect all transforms for this type
            loaded_transforms[key] = {};
            loaded_transforms[key].regexp = new RegExp(regexp); // save regexp so we can inject later without having to recreate it
            loaded_transforms[key].inject_vertex = (inject_vertex != null); // save regexp code point matches so we don't have to do them again
            loaded_transforms[key].inject_fragment = (inject_fragment != null);
            loaded_transforms[key].list = [];

            // Get the code (possibly over the network, so needs to be async)
            for (var u=0; u < transform.length; u++) {
                queue.defer(GLProgram.loadTransform, loaded_transforms, transform[u], key, u);
            }

            // Add a #define for this injection point
            defines['TANGRAM_TRANSFORM_' + key.replace(' ', '_').toUpperCase()] = true;
        }
    }

    // When all transform code snippets are collected, combine and inject them
    queue.await(function(error) {
        if (error) {
            console.log("error loading transforms: " + error);
            return;
        }

        // Do the code injection with the collected sources
        for (var t in loaded_transforms) {
            // Concatenate
            var combined_source = "";
            for (var s=0; s < loaded_transforms[t].list.length; s++) {
                combined_source += loaded_transforms[t].list[s] + '\n';
            }

            // Inject
            if (loaded_transforms[t].inject_vertex != null) {
                this.vertex_shader = this.vertex_shader.replace(loaded_transforms[t].regexp, combined_source);
            }
            if (loaded_transforms[t].inject_fragment != null) {
                this.fragment_shader = this.fragment_shader.replace(loaded_transforms[t].regexp, combined_source);
            }
        }

        // Clean-up any #pragmas that weren't replaced (to prevent compiler warnings)
        var regexp = new RegExp('^\\s*#pragma\\s+tangram:\\s+\\w+\\s*$', 'gm');
        this.vertex_shader = this.vertex_shader.replace(regexp, '');
        this.fragment_shader = this.fragment_shader.replace(regexp, '');

        // Build & inject defines
        // This is done *after* code injection so that we can add defines for which code points were injected
        var define_str = GLProgram.buildDefineString(defines);
        this.vertex_shader = define_str + this.vertex_shader;
        this.fragment_shader = define_str + this.fragment_shader;

        // Include program info useful for debugging
        var info = (this.name ? (this.name + ' / id ' + this.id) : ('id ' + this.id));
        this.vertex_shader = '// Program: ' + info + '\n' + this.vertex_shader;
        this.fragment_shader = '// Program: ' + info + '\n' + this.fragment_shader;

        // Compile & set uniforms to cached values
        try {
            this.program = GL.updateProgram(this.gl, this.program, this.vertex_shader, this.fragment_shader);
            this.compiled = true;
        }
        catch (e) {
            this.program = null;
            this.compiled = false;
        }

        this.use();
        this.refreshUniforms();
        this.refreshAttributes();

        // Notify caller
        if (typeof callback == 'function') {
            callback();
        }
    }.bind(this));
};

// Retrieve a single transform, for a given injection point, at a certain index (to preserve original order)
// Can be async, calls 'complete' callback when done
GLProgram.loadTransform = function (transforms, block, key, index, complete) {
    // Can be an inline block of GLSL, or a URL to retrieve GLSL block from
    var type, value, source;

    // Inline code
    if (typeof block == 'string') {
        transforms[key].list[index] = block;
        complete();
    }
    // Remote code
    else if (typeof block == 'object' && block.url) {
        var req = new XMLHttpRequest();

        req.onload = function () {
            source = req.response;
            transforms[key].list[index] = source;
            complete();
        };
        req.open('GET', Utils.urlForPath(block.url) + '?' + (+new Date()), true /* async flag */);
        req.responseType = 'text';
        req.send();
    }
};

// Make list of defines (global, then program-specific)
GLProgram.prototype.buildDefineList = function () {
    var defines = {};
    for (var d in GLProgram.defines) {
        defines[d] = GLProgram.defines[d];
    }
    for (var d in this.defines) {
        defines[d] = this.defines[d];
    }
    return defines;
};

// Turn #defines into a combined string
GLProgram.buildDefineString = function (defines) {
    var define_str = "";
    for (var d in defines) {
        if (defines[d] == false) {
            continue;
        }
        else if (typeof defines[d] == 'boolean' && defines[d] == true) { // booleans are simple defines with no value
            define_str += "#define " + d + "\n";
        }
        else if (typeof defines[d] == 'number' && Math.floor(defines[d]) == defines[d]) { // int to float conversion to satisfy GLSL floats
            define_str += "#define " + d + " " + defines[d].toFixed(1) + "\n";
        }
        else { // any other float or string value
            define_str += "#define " + d + " " + defines[d] + "\n";
        }
    }
    return define_str;
};

// ex: program.uniform('3f', 'position', x, y, z);
// TODO: only update uniforms when changed
GLProgram.prototype.uniform = function (method, name) // method-appropriate arguments follow
{
    if (!this.compiled) {
        return;
    }

    var uniform = (this.uniforms[name] = this.uniforms[name] || {});
    uniform.name = name;
    uniform.location = uniform.location || this.gl.getUniformLocation(this.program, name);
    uniform.method = 'uniform' + method;
    uniform.values = Array.prototype.slice.call(arguments, 2);
    this.updateUniform(name);
};

// Set a single uniform
GLProgram.prototype.updateUniform = function (name)
{
    if (!this.compiled) {
        return;
    }

    var uniform = this.uniforms[name];
    if (uniform == null || uniform.location == null) {
        return;
    }

    this.use();
    this.gl[uniform.method].apply(this.gl, [uniform.location].concat(uniform.values)); // call appropriate GL uniform method and pass through arguments
};

// Refresh uniform locations and set to last cached values
GLProgram.prototype.refreshUniforms = function ()
{
    if (!this.compiled) {
        return;
    }

    for (var u in this.uniforms) {
        this.uniforms[u].location = this.gl.getUniformLocation(this.program, u);
        this.updateUniform(u);
    }
};

GLProgram.prototype.refreshAttributes = function ()
{
    // var len = this.gl.getProgramParameter(this.program, this.gl.ACTIVE_ATTRIBUTES);
    // for (var i=0; i < len; i++) {
    //     var a = this.gl.getActiveAttrib(this.program, i);
    //     console.log(a);
    // }
    this.attribs = {};
};

// Get the location of a vertex attribute
GLProgram.prototype.attribute = function (name)
{
    if (!this.compiled) {
        return;
    }

    var attrib = (this.attribs[name] = this.attribs[name] || {});
    if (attrib.location != null) {
        return attrib;
    }

    attrib.name = name;
    attrib.location = this.gl.getAttribLocation(this.program, name);

    // var info = this.gl.getActiveAttrib(this.program, attrib.location);
    // attrib.type = info.type;
    // attrib.size = info.size;

    return attrib;
};

if (module !== undefined) {
    module.exports = GLProgram;
}
<|endoftext|>


<commit_before>// prepare global variables
var volume = 0; // master volume (0 by default)
var trackVolume = 1.0; // percentage of master volume to use in this track

// modify prototypes to add and unify functionality
function modifyMediaElementPrototypes() {
    HTMLMediaElement.prototype.hide = function() {
        this.setAttribute("opacity", 0);
    };
    HTMLVideoElement.prototype.show = function() {
        playerStarted();
        this.setAttribute("opacity", 1);
    };
    HTMLAudioElement.prototype.show = function() {
        // does nothing to prevent audio player becomming visible
        return;
    };
    HTMLVideoElement.prototype.hasStarted = function() {
        this.show();
    };
    HTMLAudioElement.prototype.hasStarted = function() {
        // audio doesn't need to tell parent that it's playing
    };
    HTMLMediaElement.prototype.hasEnded = function() {
        if (this.didEnd == false){
            this.didEnd = true;
            if (!this.soloLoop){
                this.hide();
                playerEnded();
                this.destroy();
            }
        }
    };
    HTMLMediaElement.prototype.hasErrored = function(event) {
        submessage = "";
        type = this.nodeName.toLowerCase();
        switch (this.error.code) {
            case this.error.MEDIA_ERR_ABORTED:
                err = " file playback has been aborted";
                break;
            case this.error.MEDIA_ERR_NETWORK:
                err = " file download halted due to network error";
                break;
            case this.error.MEDIA_ERR_DECODE:
                err = " file could not be decoded";
                break;
            case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
                if (this.networkState == HTMLMediaElement.NETWORK_NO_SOURCE) {
                    err = " file could not be found";
                } else {
                    err = " file format is not supported";
                }
                break;
        }
        submessage = type + err
        reportError(submessage);
        this.hasEnded()
    };
    HTMLMediaElement.prototype.setVolume = function(volumeLevel) {
        this.volume = volumeLevel / 100.0;
    };
    HTMLMediaElement.prototype.timeUpdated = function(event) {
        if (this.currentTime >= this.endTime &&
                this.duration != "Inf" &&
                !this.didEnd){
            if (this.soloLoop){
                this.currentTime = 0;
                this.play();
            } else {
                this.hide();
                this.pause();
                this.hasEnded();
            }
        }
    };
    HTMLMediaElement.prototype.loadMediaObj = function() {
        var e = this.mediaObj.end;
        if (e.length > 0) {
            this.endTime = e;
        }
        this.muted = this.mediaObj.muted;
        this.src = this.mediaObj.src;
    };
    HTMLMediaElement.prototype.seekToEnd = function() {
        this.currentTime = this.endTime;
    };
    HTMLMediaElement.prototype.destroy = function() {
        this.parentNode.removeChild(this);
    };
    HTMLMediaElement.prototype.spawn = function(mediaObj) {
        this.ondurationchange = function() {
            this.endTime = this.endTime || this.duration;
            this.hasStarted();
        };
        this.onplay = function() {
            this.show();
            this.didEnd = false;
        };
        this.ontimeupdate = function(event) {this.timeUpdated(event);};
        this.onerror = function(event) {this.hasErrored(event);};
        this.onended =  function() {this.hasEnded();};
        //this.onpause = function() {this.hasEnded();};
        this.mediaObj = mediaObj;
        this.soloLoop = mediaObj.loop;
        this.endTime = false;
        this.didEnd = false;
        this.setVolume(volume * trackVolume);
        this.loadMediaObj();
        this.setAttribute("class", "media");
        this.setAttribute("opacity", 0);
        this.setAttribute("preload", "auto");
        document.body.appendChild(this);
        this.play();
        return;
    };
}

function modifyImgElementPrototype() {
    HTMLImageElement.prototype.hide = function() {
        this.setAttribute("opacity", 0);
    };
    HTMLImageElement.prototype.show = function() {
        playerStarted();
        this.setAttribute("opacity", 1);
    };
    HTMLImageElement.prototype.loadMediaObj = function() {
        this.src = this.mediaObj.src;
    };
    HTMLImageElement.prototype.destroy = function() {
        this.parentNode.removeChild(this);
    };
    HTMLImageElement.prototype.setVolume = function(volumeLevel) {
        // should do nothing
        return;
    };
    HTMLImageElement.prototype.seekToEnd = function(volumeLevel) {
        return;
    };
    HTMLImageElement.prototype.spawn = function(mediaObj) {
        this.mediaObj = mediaObj;
        this.setAttribute("class", "media");
        this.loadMediaObj();
        document.body.appendChild(this);
        return;
    }
    return;
}

function receiveMessage(event) {
    if (event.origin !== "http://localhost:8181" &&
        event.origin !== "https://truveris.com"){
        return;
    }
    var message = JSON.parse(event.data);
    if (message.status == "media") {
        mediaObj = message.mediaObj;
        spawnMediaObj(mediaObj);
    }
}

function spawnMediaObj(mediaObj) {
    switch (mediaObj.format){
        case "video":
            spawnStandardPlayer(mediaObj);
            break;
        case "audio":
            spawnStandardPlayer(mediaObj);
            break;
        case "img":
            spawnStandardPlayer(mediaObj);
            break;
        case "web":
            spawnWeb(mediaObj);
            break;
        default:
            reportError("unrecognized format: " + mediaObj.format)
    }
}

// spawns <video>, <audio>, or
function spawnStandardPlayer(mediaObj) {
    var player = document.createElement(mediaObj.format);
    player.spawn(mediaObj);
    return player;
}

// spawns iframe to show webpage
function spawnWeb(mediaObj) {
    var web = document.createElement("iframe");
    web.spawn(mediaObj);
    return web;
}

function playerStarted() {
    sendMessage("PLAYING");
    return;
}

function playerEnded() {
    if (getVisibleCount() == 0) {
        sendMessage("ENDED");
    }
    return;
}

function getVisibleCount() {
    count = 0
    for(el of getAllPlayers()) {
        if(el.getAttribute("opacity") != 0) {
            count++;
        }
    }
    return count
}

function getAllPlayers() {
    return document.querySelectorAll("body > *");
}

function reportError(submessage) {
    submessage = submessage || "";
    sendMessage("ERRORED", submessage);
    return;
}

function sendMessage(state, submessage) {
    message = {
        playerState: state,
        submessage: submessage,
    }
    parent.postMessage(JSON.stringify(message), "*");
    return;
}

function setVolume(newVolume) {
    if (newVolume !== undefined && newVolume !== null){
        volume = newVolume;
    }
    for (player of getAllPlayers()) {
        if (player.setVolume) {
            player.setVolume(volume * trackVolume);
        }
    }
    return;
}

function setTrackVolume(newVolume) {
    trackVolume = newVolume;
    setVolume();
    return;
}

function shutup() {
    // kills all the players
    while (getAllPlayers().length > 0){
        getAllPlayers()[0].destroy();
    }
    return;
}

window.onload=function(){
    // handle messages from parent and children
    window.addEventListener("message", receiveMessage, false);
    // set the volume variable to parent window's volume variable
    volume = parent.volume;

    modifyMediaElementPrototypes();
    modifyImgElementPrototype();

    return;
}
<commit_msg>iframe elements should have common functions<commit_after>// prepare global variables
var volume = 0; // master volume (0 by default)
var trackVolume = 1.0; // percentage of master volume to use in this track

// modify prototypes to add and unify functionality
function modifyMediaElementPrototypes() {
    HTMLMediaElement.prototype.hide = function() {
        this.setAttribute("opacity", 0);
    };
    HTMLVideoElement.prototype.show = function() {
        playerStarted();
        this.setAttribute("opacity", 1);
    };
    HTMLAudioElement.prototype.show = function() {
        // does nothing to prevent audio player becomming visible
        return;
    };
    HTMLVideoElement.prototype.hasStarted = function() {
        this.show();
    };
    HTMLAudioElement.prototype.hasStarted = function() {
        // audio doesn't need to tell parent that it's playing
    };
    HTMLMediaElement.prototype.hasEnded = function() {
        if (this.didEnd == false){
            this.didEnd = true;
            if (!this.soloLoop){
                this.hide();
                playerEnded();
                this.destroy();
            }
        }
    };
    HTMLMediaElement.prototype.hasErrored = function(event) {
        submessage = "";
        type = this.nodeName.toLowerCase();
        switch (this.error.code) {
            case this.error.MEDIA_ERR_ABORTED:
                err = " file playback has been aborted";
                break;
            case this.error.MEDIA_ERR_NETWORK:
                err = " file download halted due to network error";
                break;
            case this.error.MEDIA_ERR_DECODE:
                err = " file could not be decoded";
                break;
            case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
                if (this.networkState == HTMLMediaElement.NETWORK_NO_SOURCE) {
                    err = " file could not be found";
                } else {
                    err = " file format is not supported";
                }
                break;
        }
        submessage = type + err
        reportError(submessage);
        this.hasEnded()
    };
    HTMLMediaElement.prototype.setVolume = function(volumeLevel) {
        this.volume = volumeLevel / 100.0;
    };
    HTMLMediaElement.prototype.timeUpdated = function(event) {
        if (this.currentTime >= this.endTime &&
                this.duration != "Inf" &&
                !this.didEnd){
            if (this.soloLoop){
                this.currentTime = 0;
                this.play();
            } else {
                this.hide();
                this.pause();
                this.hasEnded();
            }
        }
    };
    HTMLMediaElement.prototype.loadMediaObj = function() {
        var e = this.mediaObj.end;
        if (e.length > 0) {
            this.endTime = e;
        }
        this.muted = this.mediaObj.muted;
        this.src = this.mediaObj.src;
    };
    HTMLMediaElement.prototype.seekToEnd = function() {
        this.currentTime = this.endTime;
    };
    HTMLMediaElement.prototype.destroy = function() {
        this.parentNode.removeChild(this);
    };
    HTMLMediaElement.prototype.spawn = function(mediaObj) {
        this.ondurationchange = function() {
            this.endTime = this.endTime || this.duration;
            this.hasStarted();
        };
        this.onplay = function() {
            this.show();
            this.didEnd = false;
        };
        this.ontimeupdate = function(event) {this.timeUpdated(event);};
        this.onerror = function(event) {this.hasErrored(event);};
        this.onended =  function() {this.hasEnded();};
        //this.onpause = function() {this.hasEnded();};
        this.mediaObj = mediaObj;
        this.soloLoop = mediaObj.loop;
        this.endTime = false;
        this.didEnd = false;
        this.setVolume(volume * trackVolume);
        this.loadMediaObj();
        this.setAttribute("class", "media");
        this.setAttribute("opacity", 0);
        this.setAttribute("preload", "auto");
        document.body.appendChild(this);
        this.play();
        return;
    };
}

function modifyImgElementPrototype() {
    HTMLImageElement.prototype.hide = function() {
        this.setAttribute("opacity", 0);
    };
    HTMLImageElement.prototype.show = function() {
        playerStarted();
        this.setAttribute("opacity", 1);
    };
    HTMLImageElement.prototype.loadMediaObj = function() {
        this.src = this.mediaObj.src;
    };
    HTMLImageElement.prototype.destroy = function() {
        this.parentNode.removeChild(this);
    };
    HTMLImageElement.prototype.setVolume = function(volumeLevel) {
        // should do nothing
        return;
    };
    HTMLImageElement.prototype.seekToEnd = function(volumeLevel) {
        return;
    };
    HTMLImageElement.prototype.spawn = function(mediaObj) {
        this.mediaObj = mediaObj;
        this.setAttribute("class", "media");
        this.loadMediaObj();
        document.body.appendChild(this);
        return;
    }
    return;
}

function modifyIframeElementPrototype() {
    HTMLIFrameElement.prototype.hide = function() {
        this.setAttribute("opacity", 0);
    };
    HTMLIFrameElement.prototype.show = function() {
        playerStarted();
        this.setAttribute("opacity", 1);
    };
    HTMLIFrameElement.prototype.loadMediaObj = function() {
        this.src = this.mediaObj.src;
    };
    HTMLIFrameElement.prototype.destroy = function() {
        this.parentNode.removeChild(this);
    };
    HTMLIFrameElement.prototype.setVolume = function(volumeLevel) {
        // should do nothing
        return;
    };
    HTMLIFrameElement.prototype.seekToEnd = function(volumeLevel) {
        return;
    };
    HTMLIFrameElement.prototype.spawn = function(mediaObj) {
        this.mediaObj = mediaObj;
        this.setAttribute("class", "media");
        this.loadMediaObj();
        document.body.appendChild(this);
        return;
    }
    return;
}

function receiveMessage(event) {
    if (event.origin !== "http://localhost:8181" &&
        event.origin !== "https://truveris.com"){
        return;
    }
    var message = JSON.parse(event.data);
    if (message.status == "media") {
        mediaObj = message.mediaObj;
        spawnMediaObj(mediaObj);
    }
}

function spawnMediaObj(mediaObj) {
    switch (mediaObj.format){
        case "video":
            spawnStandardPlayer(mediaObj);
            break;
        case "audio":
            spawnStandardPlayer(mediaObj);
            break;
        case "img":
            spawnStandardPlayer(mediaObj);
            break;
        case "web":
            spawnWeb(mediaObj);
            break;
        default:
            reportError("unrecognized format: " + mediaObj.format)
    }
}

// spawns <video>, <audio>, or
function spawnStandardPlayer(mediaObj) {
    var player = document.createElement(mediaObj.format);
    player.spawn(mediaObj);
    return player;
}

// spawns iframe to show webpage
function spawnWeb(mediaObj) {
    var web = document.createElement("iframe");
    web.spawn(mediaObj);
    return web;
}

function playerStarted() {
    sendMessage("PLAYING");
    return;
}

function playerEnded() {
    if (getVisibleCount() == 0) {
        sendMessage("ENDED");
    }
    return;
}

function getVisibleCount() {
    count = 0
    for(el of getAllPlayers()) {
        if(el.getAttribute("opacity") != 0) {
            count++;
        }
    }
    return count
}

function getAllPlayers() {
    return document.querySelectorAll("body > *");
}

function reportError(submessage) {
    submessage = submessage || "";
    sendMessage("ERRORED", submessage);
    return;
}

function sendMessage(state, submessage) {
    message = {
        playerState: state,
        submessage: submessage,
    }
    parent.postMessage(JSON.stringify(message), "*");
    return;
}

function setVolume(newVolume) {
    if (newVolume !== undefined && newVolume !== null){
        volume = newVolume;
    }
    for (player of getAllPlayers()) {
        if (player.setVolume) {
            player.setVolume(volume * trackVolume);
        }
    }
    return;
}

function setTrackVolume(newVolume) {
    trackVolume = newVolume;
    setVolume();
    return;
}

function shutup() {
    // kills all the players
    while (getAllPlayers().length > 0){
        getAllPlayers()[0].destroy();
    }
    return;
}

window.onload=function(){
    // handle messages from parent and children
    window.addEventListener("message", receiveMessage, false);
    // set the volume variable to parent window's volume variable
    volume = parent.volume;

    modifyMediaElementPrototypes();
    modifyImgElementPrototype();
    modifyIframeElementPrototype();

    return;
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
 */
YUI.add('TestsLayout', function(Y, NAME) {

/**
 * The MyLayout module.
 *
 * @module MyLayout
 */

    /**
     * Constructor for the Controller class.
     *
     * @class Controller
     * @constructor
     */
    Y.namespace('mojito.controllers')[NAME] = {

        init: function(mojitSpec) {
            this.spec = mojitSpec;
        },

        /**
         * Method corresponding to the 'index' action.
         *
         * @param ac {Object} The action context that provides access
         *        to the Mojito API.
         */
	     index: function(ac) {
            Y.log('index()', 'debug', NAME);
            var parentConfig = ac.config.get();
                myId = parentConfig.id;
            var childrenConfig = parentConfig.children;
            var childInfo = [];

            Y.Object.each(childrenConfig, function(childSpec, childName) {
            	var serverUrl, urlHTML;

            	if (childSpec.config)
                {
            		serverUrl = childSpec.config.myUrls;
            		Y.Array.each(serverUrl, function(eachUrl){
            			if (urlHTML)
            			{
            				urlHTML += "" + eachUrl + "
";
            			}
            			else
            			{
            				urlHTML = "" + eachUrl + "
";
            			}
            		});
                }
            	//console.log("childName******* "+ childName );
            	//console.log(childSpec);

                var info = {
                    name: childName,
                    urls: urlHTML
                };

                childInfo.push(info);
            });

			ac.composite.execute(parentConfig, function(childContent, meta){
				Y.Array.each(childInfo, function(info){
					info.html = childContent[info.name];
				});
				ac.done({
					title: myId,
					childData: childInfo
				}, meta);
			});
		}
    };

}, '0.0.1', {requires: ['mojito']});
<commit_msg>fixing functional test that was missing the mojito-config-addon requirement<commit_after>/*
 * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
 */
YUI.add('TestsLayout', function(Y, NAME) {

/**
 * The MyLayout module.
 *
 * @module MyLayout
 */

    /**
     * Constructor for the Controller class.
     *
     * @class Controller
     * @constructor
     */
    Y.namespace('mojito.controllers')[NAME] = {

        init: function(mojitSpec) {
            this.spec = mojitSpec;
        },

        /**
         * Method corresponding to the 'index' action.
         *
         * @param ac {Object} The action context that provides access
         *        to the Mojito API.
         */
	     index: function(ac) {
            Y.log('index()', 'debug', NAME);
            var parentConfig = ac.config.get();
                myId = parentConfig.id;
            var childrenConfig = parentConfig.children;
            var childInfo = [];

            Y.Object.each(childrenConfig, function(childSpec, childName) {
            	var serverUrl, urlHTML;

            	if (childSpec.config)
                {
            		serverUrl = childSpec.config.myUrls;
            		Y.Array.each(serverUrl, function(eachUrl){
            			if (urlHTML)
            			{
            				urlHTML += "" + eachUrl + "
";
            			}
            			else
            			{
            				urlHTML = "" + eachUrl + "
";
            			}
            		});
                }
            	//console.log("childName******* "+ childName );
            	//console.log(childSpec);

                var info = {
                    name: childName,
                    urls: urlHTML
                };

                childInfo.push(info);
            });

			ac.composite.execute(parentConfig, function(childContent, meta){
				Y.Array.each(childInfo, function(info){
					info.html = childContent[info.name];
				});
				ac.done({
					title: myId,
					childData: childInfo
				}, meta);
			});
		}
    };

}, '0.0.1', {requires: ['mojito', 'mojito-config-addon']});
<|endoftext|>


<commit_before>let Discord = require('discord.js'),
    config = require('./config/config'),
    roles = require('./config/roles'),
    Twitter = require('./modules/twitter').Twitter,
    fs = require('fs'),
    unirest = require('unirest'),
    token = config.TOKEN,
    bot = new Discord.Client(),
    server,
    version,
    exec,
    sbBusy = false;

/* VERSION */
function getVersion(callback) {
    exec = exec || require('child_process').exec;

    exec('git rev-parse --short=4 HEAD', function (error, version) {
        if (error) {
            console.log('Error getting version', error);
            return callback('unknown');
        }

        callback(version.trim());
    });
}

/* BOT EVENTS */
bot.on('ready', function () {
    online();
    console.log(getDateTime() + 'I am ready!');
    getVersion(function (v) {
        version = v;
        bot.user.setGame('version ' + version);

        if (config.DEBUG) bot.channels.find('id', config.BOT_CH).sendMessage('I am ready, running version `' + version + '`! 👌');
    });

    if (!bot.guilds.exists('id', config.SERVER_ID)) {
        console.log('Bot is not connected to the selected server!');
        process.exit();
    }

    server = bot.guilds.find('id', config.SERVER_ID);

    const twitter = new Twitter(config.TWITTER_API, server);

    setInterval(function () {
        twitter.postNewTweets();
    }, 60000 * 5);
});

bot.on('guildMemberAdd', function (member) {
    //bot.channels.find('id', config.DEFAULT_CH).sendMessage(member + ' Willkommen auf dem offiziellen Discord Server von Bronies.de ... Wirf doch für den Anfang einen Blick in den #info Bereich. :lyra_1:');
});

function onMessage(message) {
    if (message.author.id == bot.user.id) {
        return;
    }

    if (message.channel.type == 'group') {
        return;
    }

    function handleCommand() {
        let match = /^[\/!]([a-zA-Z]+).*$/.exec(message.content);

        if (message.channel.type == 'dm') {
            match = /^[\/!]?([a-zA-Z]+).*$/.exec(message.content);
        }

        if (match) {
            const args = message.content.split(' ').splice(1);
            const cmd = match[1].toLowerCase();

            if (!(cmd in commands)) {
                return;
            }

            const cmdObj = commands[cmd];

            if ('ignore' in cmdObj) {
                if (cmdObj.ignore == true) {
                    return;
                }
            }

            if ('server' in cmdObj) {
                if (cmdObj.server == true) {
                    if (message.guild != server) {
                        return respondPm(message, 'Dieser Befehl kann nur auf dem Bronies.de Discord Server ausgeführt werden!');
                    }
                }
            }

            if ('role' in cmdObj) {
                if (!checkPermissions(cmdObj.role, message.author)) {
                    return respond(message, 'Du besitzt nicht genügend Rechte um diesen Befehl auszuführen!', true);
                }
            }

            processCommand(message, cmd, cmdObj, args);
        }
    }

    if (server.channels.exists('id', message.channel.id)) {
        handleCommand();
    } else {
        if (server.members.exists('id', message.author.id)) {
            handleCommand();
        } else {
            return message.channel.sendMessage('You have to be member of ' + server.name + '!');
        }
    }
}

bot.on('message', onMessage);

bot.on('messageUpdate', function (oldMessage, newMessage) {
    if (typeof newMessage.author === 'undefined')
        return;

    onMessage(newMessage);
});

/* PERMISSIONS */
function checkPermissions(role, user) {
    const member = server.members.find('id', user.id);

    if (server.owner == member) {
        return true;
    }

    if (!server.roles.exists('name', role)) {
        return false;
    }

    return member.highestRole.comparePositionTo(server.roles.find('name', role)) >= 0;
}

function getGuildMember(user) {
    return server.members.find('id', user.id);
}

function respond(message, response, mention) {
    if (typeof mention === 'undefined') {
        mention = false;
    }

    if (mention) {
        message.reply(response);
    } else {
        message.channel.sendMessage(response);
    }
}

function respondPm(message, response) {
    message.author.sendMessage(response);
}

function getEmoji(name) {
    if (server.emojis.exists('name', name)) {
        return server.emojis.find('name', name).toString();
    } else {
        return ':robot:';
    }
}

/* COMMAND PROCESSING */
function processCommand(message, cmd, cmdObj, args) {
    switch (cmd) {
        case 'help':
            (function () {
                let text = '\n\nBefehle müssen `/` oder `!` vorangestellt haben. Groß- und Kleinschreibung wird nicht beachtet.\nIn PMs wird kein Präfix benötigt.\n\n';

                text += 'Liste aller Befehle, die **du** nutzen kannst:\n\n';

                Object.keys(commands).forEach(function (command) {
                    command = commands[command];

                    if ('role' in command) {
                        if (!checkPermissions(command.role, message.author)) {
                            return;
                        }
                    }

                    text += '**`' + command.name + '`**';
                    if ('aliases' in command) {
                        text += ' (alternativ: ';
                        text += '`' + command.aliases.join('`, `');
                        text += '`)';
                    }

                    text += '\n```' + command.help + '```\n\n';
                });

                respondPm(message, text);

                if (message.channel.type == 'text') {
                    message.delete();
                }
            })();
            break;
        case 'ver':
        case 'version':
            (function () {
                respond(message, "Bronies.de DSB version `" + version + "`.\nAktuellster Commit: https://github.com/DerAtrox/Bronies.de-DSB/commit/" + version);
            })();
            break;
        case 'nsfw':
            (function () {
                const msg = message;
                message.delete();

                if (args.length != 1) {
                    return respondPm(msg, 'Nutze `!nsfw <join|leave>` um den NSFW Bereich zu betreten bzw. zu verlassen. Beispiel: `!nsfw join`');
                }

                const arg = args[0].toLowerCase();

                const nsfwRole = server.roles.find('name', 'NSFW');
                const member = getGuildMember(msg.author);

                if (arg == 'join') {
                    if (!member.roles.exists('name', 'NSFW')) {
                        member.addRole(nsfwRole);
                        return respondPm(msg, 'Bronies.de NSFW Bereich beigetreten. :smirk:');
                    }
                } else if (arg == 'leave') {
                    if (member.roles.exists('name', 'NSFW')) {
                        member.removeRole(nsfwRole);
                        return respondPm(msg, 'Bronies.de NSFW Bereich verlassen.');
                    }
                } else {
                    return respondPm(msg, 'Nutze `!nsfw <join|leave>` um den NSFW Bereich zu betreten bzw. zu verlassen. Beispiel: `!nsfw join`');
                }
            })();
            break;
        case 'soundboard':
        case 'sb':
            (function () {
                if (args.length != 1) {
                    respondPm(message, 'Spiele Pony Sounds in deinem aktuellen Voicechannel ab. Nutze `!sb help` um alle Sounds anzuzeigen.\nBeispiel: `!sb lunafun`');
                    return message.delete();
                }

                const arg = args[0].toLowerCase();

                if (arg == 'help') {
                    respondPm(message, 'Folgende Sounds können abgespielt werden:\n```' + Object.keys(sounds).join(' ') + '```');
                    return message.delete();
                } else {
                    if (sbBusy) {
                        return;
                    }

                    const member = getGuildMember(message.author);

                    if (typeof member.voiceChannel == 'undefined') {
                        return;
                    }

                    if (!(arg in sounds)) {
                        return;
                    }

                    const soundPath = './sounds/' + sounds[arg];

                    fs.access(soundPath, fs.constants.R_OK, function (err) {
                        if (!err) {
                            sbBusy = true;
                            member.voiceChannel.join().then(function (connection) {
                                const dispatcher = connection.playFile('./sounds/' + sounds[arg]);

                                dispatcher.on('end', function () {
                                    sbBusy = false;
                                    connection.disconnect();

                                });

                                dispatcher.on('error', function (message) {
                                    console.log(message);
                                });
                            })
                                .catch(console.error);
                        } else {
                            console.log('Soundfile not found: ' + arg + ' file ' + sounds[arg]);
                        }
                    });
                }

            })();
            break;
        case 'derpi':
        case 'db':
            (function () {
                if (args.length < 1) {
                    return respond(message, 'Dieser Befehl benötigt zusätzliche Parameter. Mehr unter `!help`');
                }

                let regexOrder = /\bo:(desc|asc)\b/i,
                    regexSort = /\bby:(score|relevance|width|height|comments|random)\b/i,
                    parameters = '',
                    query = args.join(' ');

                if (message.channel == server.channels.find('name', 'nsfw')) {
                    parameters += '&filter_id=134141';
                }

                if (regexOrder.test(query)) {
                    parameters += '&sd=' + query.match(regexOrder)[1];
                    query = query.replace(regexOrder, '');
                }

                if (regexSort.test(query)) {
                    parameters += '&sf=' + query.match(regexSort)[1];
                    query = query.replace(regexSort, '');
                } else {
                    parameters += '&sf=random';
                }

                query = query.replace(/,{2,}/g, ',').replace(/(^,|,$)/, '');
                const url = 'https://derpibooru.org/search.json?q=' + encodeURIComponent(query) + parameters;
                console.log(message.author.username + '#' + message.author.discriminator + ' - Derpibooru search: ' + url);

                unirest.get(url)
                    .header("Accept", "application/json")
                    .end(function (result) {
                        if (result.error || typeof result.body !== 'object') {
                            console.log(result.error, result.body);
                            return respond(message, 'Derpibooru Anfrage fehlgeschlagen (HTTP ' + result.status + ')');
                        }

                        const data = result.body;
                        if (typeof data.search === 'undefined' || typeof data.search[0] === 'undefined')
                            return respond(message, 'Keine Suchergebnisse gefunden.');

                        const img = data.search[0];

                        if (!img.is_rendered) {
                            return respond(message, 'Dieses Bild wurde noch nicht von Derpibooru verarbeitet. Bitte versuche es später erneut.');
                        }

                        respond(message, '<http://derpibooru.org/' + img.id + '>\nhttps:' + (img.image.replace(/__[^.]+(.\w+)$/, '$1')));
                    });

            })();
            break;
    }
}

const commands = {
    help: {
        name: 'help',
        help: 'Zeigt alle verfügbaren Befehle an.'
    },
    version: {
        name: 'version',
        help: 'Zeigt die verwendete Bronies.de DSB Version an.',
        aliases: ['ver']
    },
    nsfw: {
        name: 'nsfw <join|leave>',
        help: '#nsfw betreten oder verlassen.',
        server: true,
        role: roles.community
    },
    soundboard: {
        name: 'soundboard <sound>',
        help: 'Sound in aktuellem Sprachchannel abspielen. Liste aller Sounds mit !sb help',
        aliases: ['sb'],
        server: true,
        role: roles.community
    },
    derpi: {
        name: 'derpi <search>',
        help: 'Gibt das erste Bild einer Derpibooru Suche zurück.\n\n' +
        'Optionen:\n' +
        ' - Reihenfolge  o:<desc|asc>\n' +
        ' - Sortierung   by:<score|relevance|width|height|comments|random>',
        aliases: ['db'],
        server: true,
        role: roles.community
    },
    nowplaying: {
        name: 'nowplaying',
        help: 'Zeigt den aktuell gespielten Track des BRG-Musikbots an.',
        aliases: ['np'],
        ignore: true
    },
    playradio: {
        name: 'playradio',
        help: 'Startet den Radiostream neu.',
        aliases: ['pr'],
        role: roles.brgteam,
        ignore: true
    }
};

const sounds = {
    'lunafun': 'Princess Luna/the fun has been doubled.mp3',
    'eyyup': 'Big Macintosh/eyup.mp3',
    'nope': 'Big Macintosh/nnope.mp3',
    'yeehaw': 'Applejack/yeehaw.mp3',
    'laugh': 'Rainbow Dash/laughing.mp3',
    'catchy': 'Twilight Sparkle/wow catchy.mp3',
    'crazy': 'Twilight Sparkle/are you crazy.mp3',
    'grin': 'Fluttershy/(grin).mp3',
    'choochoo': 'Fluttershy/choo choo train.mp3',
    'yay': 'Fluttershy/yay.mp3',
    'boring': 'Pinkie Pie/boring.mp3',
    'giggle': 'Pinkie Pie/giggle.mp3',
    'oki': 'Pinkie Pie/oki doki loki.mp3',
    'rimshot': 'Pinkie Pie/rimshot.mp3',
    'yeah': 'Snowflake/yeah2.mp3',
    'fanfare': 'Trixie/fanfare.mp3',
    'youmad': 'Zecora/have you gone mad.mp3',
    '10seconds': 'Rainbow Dash/10 seconds flat.mp3',
    'style': 'Pinkie Pie/pinkie pie style.mp3',
    'notcool': 'Rainbow Dash/not cool.mp3',
    'wrong': 'Derpy/i just dont know what went wrong.mp3'
};

/* GENERAL APPLICATION STUFF */
process.on('exit', idle);

process.on('SIGINT', function () {
    idle();
    process.exit();

});

function getDateTime() {
    return "[" + new Date().toLocaleString() + "] ";
}

function idle() {
    bot.user.setStatus('idle');
}

function online() {
    bot.user.setStatus('online');
}

/* LOGIN */
bot.login(token);<commit_msg>fixed aliases<commit_after>let Discord = require('discord.js'),
    config = require('./config/config'),
    roles = require('./config/roles'),
    Twitter = require('./modules/twitter').Twitter,
    fs = require('fs'),
    unirest = require('unirest'),
    token = config.TOKEN,
    bot = new Discord.Client(),
    server,
    version,
    exec,
    sbBusy = false;

/* VERSION */
function getVersion(callback) {
    exec = exec || require('child_process').exec;

    exec('git rev-parse --short=4 HEAD', function (error, version) {
        if (error) {
            console.log('Error getting version', error);
            return callback('unknown');
        }

        callback(version.trim());
    });
}

/* BOT EVENTS */
bot.on('ready', function () {
    online();
    console.log(getDateTime() + 'I am ready!');
    getVersion(function (v) {
        version = v;
        bot.user.setGame('version ' + version);

        if (config.DEBUG) bot.channels.find('id', config.BOT_CH).sendMessage('I am ready, running version `' + version + '`! 👌');
    });

    if (!bot.guilds.exists('id', config.SERVER_ID)) {
        console.log('Bot is not connected to the selected server!');
        process.exit();
    }

    server = bot.guilds.find('id', config.SERVER_ID);

    const twitter = new Twitter(config.TWITTER_API, server);

    setInterval(function () {
        twitter.postNewTweets();
    }, 60000 * 5);
});

bot.on('guildMemberAdd', function (member) {
    //bot.channels.find('id', config.DEFAULT_CH).sendMessage(member + ' Willkommen auf dem offiziellen Discord Server von Bronies.de ... Wirf doch für den Anfang einen Blick in den #info Bereich. :lyra_1:');
});

function onMessage(message) {
    if (message.author.id == bot.user.id) {
        return;
    }

    if (message.channel.type == 'group') {
        return;
    }

    function handleCommand() {
        let match = /^[\/!]([a-zA-Z]+).*$/.exec(message.content);

        if (message.channel.type == 'dm') {
            match = /^[\/!]?([a-zA-Z]+).*$/.exec(message.content);
        }

        if (match) {
            const args = message.content.split(' ').splice(1);
            let cmd = match[1].toLowerCase();

            if (!(cmd in commands)) {
                const resolved = resolveAlias(cmd)

                if (!resolved) {
                    return
                } else {
                    cmd = resolved;
                }
            }

            const cmdObj = commands[cmd];

            if ('ignore' in cmdObj) {
                if (cmdObj.ignore == true) {
                    return;
                }
            }

            if ('server' in cmdObj) {
                if (cmdObj.server == true) {
                    if (message.guild != server) {
                        return respondPm(message, 'Dieser Befehl kann nur auf dem Bronies.de Discord Server ausgeführt werden!');
                    }
                }
            }

            if ('role' in cmdObj) {
                if (!checkPermissions(cmdObj.role, message.author)) {
                    return respond(message, 'Du besitzt nicht genügend Rechte um diesen Befehl auszuführen!', true);
                }
            }

            processCommand(message, cmd, cmdObj, args);
        }
    }

    if (server.channels.exists('id', message.channel.id)) {
        handleCommand();
    } else {
        if (server.members.exists('id', message.author.id)) {
            handleCommand();
        } else {
            return message.channel.sendMessage('You have to be member of ' + server.name + '!');
        }
    }
}

bot.on('message', onMessage);

bot.on('messageUpdate', function (oldMessage, newMessage) {
    if (typeof newMessage.author === 'undefined')
        return;

    onMessage(newMessage);
});

/* PERMISSIONS */
function checkPermissions(role, user) {
    const member = server.members.find('id', user.id);

    if (server.owner == member) {
        return true;
    }

    if (!server.roles.exists('name', role)) {
        return false;
    }

    return member.highestRole.comparePositionTo(server.roles.find('name', role)) >= 0;
}

function getGuildMember(user) {
    return server.members.find('id', user.id);
}

function respond(message, response, mention) {
    if (typeof mention === 'undefined') {
        mention = false;
    }

    if (mention) {
        message.reply(response);
    } else {
        message.channel.sendMessage(response);
    }
}

function respondPm(message, response) {
    message.author.sendMessage(response);
}

function getEmoji(name) {
    if (server.emojis.exists('name', name)) {
        return server.emojis.find('name', name).toString();
    } else {
        return ':robot:';
    }
}

function resolveAlias(resolvable) {
    let result = false;

    Object.keys(commands).some(function (cmd) {
        var cmdObj = commands[cmd];

        if (!('aliases' in cmdObj)) {
            return false;
        }

        let resolved = false;

        cmdObj.aliases.some(function (alias) {
            if (resolvable == alias) {
                resolved = true;
                return true;
            } else {
                return false;
            }
        });

        if (resolved) {
            result = cmd;
            return true;
        } else {
            return false;
        }
    });

    return result;
}

/* COMMAND PROCESSING */
function processCommand(message, cmd, cmdObj, args) {
    switch (cmd) {
        case 'help':
            (function () {
                let text = '\n\nBefehle müssen `/` oder `!` vorangestellt haben. Groß- und Kleinschreibung wird nicht beachtet.\nIn PMs wird kein Präfix benötigt.\n\n';

                text += 'Liste aller Befehle, die **du** nutzen kannst:\n\n';

                Object.keys(commands).forEach(function (command) {
                    command = commands[command];

                    if ('role' in command) {
                        if (!checkPermissions(command.role, message.author)) {
                            return;
                        }
                    }

                    text += '**`' + command.name + '`**';
                    if ('aliases' in command) {
                        text += ' (alternativ: ';
                        text += '`' + command.aliases.join('`, `');
                        text += '`)';
                    }

                    text += '\n```' + command.help + '```\n\n';
                });

                respondPm(message, text);

                if (message.channel.type == 'text') {
                    message.delete();
                }
            })();
            break;
        case 'version':
            (function () {
                respond(message, "Bronies.de DSB version `" + version + "`.\nAktuellster Commit: https://github.com/DerAtrox/Bronies.de-DSB/commit/" + version);
            })();
            break;
        case 'nsfw':
            (function () {
                const msg = message;
                message.delete();

                if (args.length != 1) {
                    return respondPm(msg, 'Nutze `!nsfw <join|leave>` um den NSFW Bereich zu betreten bzw. zu verlassen. Beispiel: `!nsfw join`');
                }

                const arg = args[0].toLowerCase();

                const nsfwRole = server.roles.find('name', 'NSFW');
                const member = getGuildMember(msg.author);

                if (arg == 'join') {
                    if (!member.roles.exists('name', 'NSFW')) {
                        member.addRole(nsfwRole);
                        return respondPm(msg, 'Bronies.de NSFW Bereich beigetreten. :smirk:');
                    }
                } else if (arg == 'leave') {
                    if (member.roles.exists('name', 'NSFW')) {
                        member.removeRole(nsfwRole);
                        return respondPm(msg, 'Bronies.de NSFW Bereich verlassen.');
                    }
                } else {
                    return respondPm(msg, 'Nutze `!nsfw <join|leave>` um den NSFW Bereich zu betreten bzw. zu verlassen. Beispiel: `!nsfw join`');
                }
            })();
            break;
        case 'soundboard':
            (function () {
                if (args.length != 1) {
                    respondPm(message, 'Spiele Pony Sounds in deinem aktuellen Voicechannel ab. Nutze `!sb help` um alle Sounds anzuzeigen.\nBeispiel: `!sb lunafun`');
                    return message.delete();
                }

                const arg = args[0].toLowerCase();

                if (arg == 'help') {
                    respondPm(message, 'Folgende Sounds können abgespielt werden:\n```' + Object.keys(sounds).join(' ') + '```');
                    return message.delete();
                } else {
                    if (sbBusy) {
                        return;
                    }

                    const member = getGuildMember(message.author);

                    if (typeof member.voiceChannel == 'undefined') {
                        return;
                    }

                    if (!(arg in sounds)) {
                        return;
                    }

                    const soundPath = './sounds/' + sounds[arg];

                    fs.access(soundPath, fs.constants.R_OK, function (err) {
                        if (!err) {
                            sbBusy = true;
                            member.voiceChannel.join().then(function (connection) {
                                const dispatcher = connection.playFile('./sounds/' + sounds[arg]);

                                dispatcher.on('end', function () {
                                    sbBusy = false;
                                    connection.disconnect();

                                });

                                dispatcher.on('error', function (message) {
                                    console.log(message);
                                });
                            })
                                .catch(console.error);
                        } else {
                            console.log('Soundfile not found: ' + arg + ' file ' + sounds[arg]);
                        }
                    });
                }

            })();
            break;
        case 'derpi':
            (function () {
                if (args.length < 1) {
                    return respond(message, 'Dieser Befehl benötigt zusätzliche Parameter. Mehr unter `!help`');
                }

                let regexOrder = /\bo:(desc|asc)\b/i,
                    regexSort = /\bby:(score|relevance|width|height|comments|random)\b/i,
                    parameters = '',
                    query = args.join(' ');

                if (message.channel == server.channels.find('name', 'nsfw')) {
                    parameters += '&filter_id=134141';
                }

                if (regexOrder.test(query)) {
                    parameters += '&sd=' + query.match(regexOrder)[1];
                    query = query.replace(regexOrder, '');
                }

                if (regexSort.test(query)) {
                    parameters += '&sf=' + query.match(regexSort)[1];
                    query = query.replace(regexSort, '');
                } else {
                    parameters += '&sf=random';
                }

                query = query.replace(/,{2,}/g, ',').replace(/(^,|,$)/, '');
                const url = 'https://derpibooru.org/search.json?q=' + encodeURIComponent(query) + parameters;
                console.log(message.author.username + '#' + message.author.discriminator + ' - Derpibooru search: ' + url);

                unirest.get(url)
                    .header("Accept", "application/json")
                    .end(function (result) {
                        if (result.error || typeof result.body !== 'object') {
                            console.log(result.error, result.body);
                            return respond(message, 'Derpibooru Anfrage fehlgeschlagen (HTTP ' + result.status + ')');
                        }

                        const data = result.body;
                        if (typeof data.search === 'undefined' || typeof data.search[0] === 'undefined')
                            return respond(message, 'Keine Suchergebnisse gefunden.');

                        const img = data.search[0];

                        if (!img.is_rendered) {
                            return respond(message, 'Dieses Bild wurde noch nicht von Derpibooru verarbeitet. Bitte versuche es später erneut.');
                        }

                        respond(message, '<http://derpibooru.org/' + img.id + '>\nhttps:' + (img.image.replace(/__[^.]+(.\w+)$/, '$1')));
                    });

            })();
            break;
    }
}

const commands = {
    help: {
        name: 'help',
        help: 'Zeigt alle verfügbaren Befehle an.'
    },
    version: {
        name: 'version',
        help: 'Zeigt die verwendete Bronies.de DSB Version an.',
        aliases: ['ver']
    },
    nsfw: {
        name: 'nsfw <join|leave>',
        help: '#nsfw betreten oder verlassen.',
        server: true,
        role: roles.community
    },
    soundboard: {
        name: 'soundboard <sound>',
        help: 'Sound in aktuellem Sprachchannel abspielen. Liste aller Sounds mit !sb help',
        aliases: ['sb'],
        server: true,
        role: roles.community
    },
    derpi: {
        name: 'derpi <search>',
        help: 'Gibt das erste Bild einer Derpibooru Suche zurück.\n\n' +
        'Optionen:\n' +
        ' - Reihenfolge  o:<desc|asc>\n' +
        ' - Sortierung   by:<score|relevance|width|height|comments|random>',
        aliases: ['db'],
        server: true,
        role: roles.community
    },
    nowplaying: {
        name: 'nowplaying',
        help: 'Zeigt den aktuell gespielten Track des BRG-Musikbots an.',
        aliases: ['np'],
        ignore: true
    },
    playradio: {
        name: 'playradio',
        help: 'Startet den Radiostream neu.',
        aliases: ['pr'],
        role: roles.brgteam,
        ignore: true
    }
};

const sounds = {
    'lunafun': 'Princess Luna/the fun has been doubled.mp3',
    'eyyup': 'Big Macintosh/eyup.mp3',
    'nope': 'Big Macintosh/nnope.mp3',
    'yeehaw': 'Applejack/yeehaw.mp3',
    'laugh': 'Rainbow Dash/laughing.mp3',
    'catchy': 'Twilight Sparkle/wow catchy.mp3',
    'crazy': 'Twilight Sparkle/are you crazy.mp3',
    'grin': 'Fluttershy/(grin).mp3',
    'choochoo': 'Fluttershy/choo choo train.mp3',
    'yay': 'Fluttershy/yay.mp3',
    'boring': 'Pinkie Pie/boring.mp3',
    'giggle': 'Pinkie Pie/giggle.mp3',
    'oki': 'Pinkie Pie/oki doki loki.mp3',
    'rimshot': 'Pinkie Pie/rimshot.mp3',
    'yeah': 'Snowflake/yeah2.mp3',
    'fanfare': 'Trixie/fanfare.mp3',
    'youmad': 'Zecora/have you gone mad.mp3',
    '10seconds': 'Rainbow Dash/10 seconds flat.mp3',
    'style': 'Pinkie Pie/pinkie pie style.mp3',
    'notcool': 'Rainbow Dash/not cool.mp3',
    'wrong': 'Derpy/i just dont know what went wrong.mp3'
};

/* GENERAL APPLICATION STUFF */
process.on('exit', idle);

process.on('SIGINT', function () {
    idle();
    process.exit();

});

function getDateTime() {
    return "[" + new Date().toLocaleString() + "] ";
}

function idle() {
    bot.user.setStatus('idle');
}

function online() {
    bot.user.setStatus('online');
}

/* LOGIN */
bot.login(token);<|endoftext|>


<commit_before><commit_msg>new build<commit_after><|endoftext|>


<commit_before><commit_msg>updated request comp<commit_after><|endoftext|>