<commit_before>define(function() {

    'use strict';

    var defaults = {
        authFlow: false,
        maxRetries: 0,
        maxAuthRetries: 1,
        followLocation: false,
        fileUpload: false,
        extractHeaders: ['Mendeley-Count', 'Link', 'Location']
    };
    var noopNotifier = { notify: function() {}};

    function create(request, settings, notifier) {
        return new Request(request, $.extend({}, defaults, settings), notifier);
    }

    function Request(request, settings, notifier) {
        if (!settings.authFlow) {
            throw new Error('Please provide an authentication interface');
        }
        this.request = request;
        this.settings = settings;
        this.retries = 0;
        this.authRetries = 0;
        this.notifier = notifier || noopNotifier;

        this.notifier.notify('startInfo', [this.request.type, this.request.url], this.request);
    }

    function send(dfd, request) {

        dfd = dfd || $.Deferred();
        request = request || this.request;
        request.headers = request.headers || {};
        var token = this.settings.authFlow.getToken();
        // If no token at all (cookie deleted or expired) refresh token if possible or authenticate
        // because if you send 'Bearer ' you get a 400 rather than a 401 - is that a bug in the api?
        if (!token) {
            this.authRetries++;
            this.notifier.notify('authWarning', ['n.a.', this.authRetries, this.settings.maxAuthRetries], this.request);
            refreshToken.call(this, dfd);
            return dfd.promise();
        }

        request.headers.Authorization = 'Bearer ' + token;

        if (this.settings.fileUpload) {
            // Undocumented way to access XHR so we can add upload progress listeners
            var xhr = $.ajaxSettings.xhr();
            request.xhr = function() { return xhr; };

            // The response may have JSON Content-Type which makes jQuery invoke JSON.parse
            // on the reponse, but there isn't one so there's an error which causes the deferred
            // to be rejected. Specifying a dataType of 'text' prevents this.
            request.dataType = 'text';

            // Decorate the xhr with upload progress events
            ['loadstart', 'loadend', 'load', 'progress', 'abort', 'error', 'timeout']
                .forEach(function(uploadEvent) {
                    xhr.upload.addEventListener(uploadEvent, uploadProgressFun.call(this, dfd, request, xhr));
                }.bind(this));
        }

        $.ajax(request)
            .fail(onFail.bind(this, dfd))
            .done(onDone.bind(this, dfd));

        return dfd.promise();
    }

    function onFail(dfd, xhr) {
        switch (xhr.status) {
            case 0:
            case 504:
                // 504 Gateway timeout or communication error
                if (this.retries < this.settings.maxRetries) {
                    this.retries++;
                    this.notifier.notify('commWarning', [xhr.status, this.retries, this.settings.maxRetries], this.request, xhr);
                    this.send(dfd);
                } else {
                    this.notifier.notify('commError', [xhr.status, this.settings.maxRetries], this.request, xhr);
                    dfd.reject(this.request, xhr);
                }
                break;

            case 401:
                // 401 Unauthorized
                if (this.authRetries < this.settings.maxAuthRetries) {
                    this.authRetries++;
                    this.notifier.notify('authWarning', [xhr.status, this.authRetries, this.settings.maxAuthRetries], this.request, xhr);
                    refreshToken.call(this, dfd, xhr);
                } else {
                    this.notifier.notify('authError', [xhr.status, this.settings.maxAuthRetries], this.request, xhr);
                    dfd.reject(this.request, xhr);
                    this.settings.authFlow.authenticate(200);
                }
                break;

            default:
                this.notifier.notify('reqError', [xhr.status], this.request, xhr);
                dfd.reject(this.request, xhr);
        }
    }

    function onDone(dfd, response, success, xhr) {

        var locationHeader = getResponseHeader(xhr, 'Location'),
            headers;

        if (locationHeader && this.settings.followLocation && xhr.status === 201) {
            var redirect = {
                type: 'GET',
                url: locationHeader,
                dataType: 'json'
            };
            this.notifier.notify('redirectInfo', null, this.request, redirect);
            this.send(dfd, redirect);
        } else {
            if (this.settings.extractHeaders) {
                headers = extractHeaders.call(this, xhr);
            }

            // File uploads have type set to text, so if there is some JSON parse it manually
            if (this.settings.fileUpload) {
                if (response) {
                    try {
                        response = JSON.parse(response);
                    } catch (error) {
                        this.notifier.notify('parseError', null, this.request, xhr);
                        dfd.reject(error);
                        return;
                    }
                }
                this.notifier.notify('uploadSuccessInfo', null, this.request, response);
                dfd.resolve(response, xhr);
            } else {
                this.notifier.notify('successInfo', null, this.request, response);
                dfd.resolve(response, headers);
            }
        }
    }

    function refreshToken(dfd, xhr) {
        var refresh = this.settings.authFlow.refreshToken();
        if (refresh) {
            $.when(refresh)
                // If OK update the access token and re-send the request
                .done(function() {
                    this.send(dfd);
                }.bind(this))
                // If fails then we need to re-authenticate
                .fail(function(refreshxhr) {
                    this.notifier.notify('refreshError', [refreshxhr.status], this.request, refreshxhr);
                    dfd.reject(this.request, xhr);
                    this.settings.authFlow.authenticate(200);
                }.bind(this));
        } else {
            this.notifier.notify('refreshNotConfigured', []);
            dfd.reject(this.request, xhr);
            this.settings.authFlow.authenticate(200);
        }
    }

    /**
     * Get a function to monitor upload progress and emit notify events
     * on a deferred.
     *
     * @private
     * @param {object} dfd - Deferred
     * @param {object} request - The original request
     * @param {object} xhr The original XHR
     */
    function uploadProgressFun(dfd, request, xhr) {
        var progressPercent;
        var bytesTotal;
        var bytesSent;

        return function(progressEvent) {
            var eventType = progressEvent.type;

            if (progressEvent.lengthComputable) {
                bytesSent = progressEvent.loaded || progressEvent.position; // position is deprecated
                bytesTotal = progressEvent.total;
                progressPercent = Math.round(100 * bytesSent / bytesTotal);
                dfd.notify(progressEvent, progressPercent, bytesSent, bytesTotal);

                if (eventType === 'progress') {
                    this.notifier.notify('uploadProgressInfo', [progressPercent, bytesSent, bytesTotal], request, xhr);
                }
            }
            if (eventType === 'abort' || eventType === 'timeout' || eventType === 'error') {
                this.notifier.notify('uploadError', [eventType, progressPercent], request, xhr);
                dfd.reject(request, xhr, { event: progressEvent, percent: progressPercent });
            }
        }.bind(this);
    }

    function getResponseHeader(xhr, name) {
        if (!xhr || !xhr.getResponseHeader) {
            return '';
        }

        return xhr.getResponseHeader(name);
    }

    function getAllResponseHeaders(xhr) {
        if (!xhr || !xhr.getAllResponseHeaders) {
            return '';
        }

        return xhr.getAllResponseHeaders();
    }

    function extractHeaders(xhr) {
        var headers = {}, headerValue;

        this.settings.extractHeaders.forEach(function(headerName) {
            headerValue = headerName === 'Link' ?
                extractLinkHeaders.call(this, xhr) : getResponseHeader(xhr, headerName);

            if (headerValue) {
                headers[headerName] = headerValue;
            }
        });

        return headers;
    }

    function extractLinkHeaders(xhr) {

        // Safe way to get multiple headers of same type in IE
        var headerName = 'Link';
        var links = getAllResponseHeaders(xhr).split('\n')
            .filter(function(row) {
                return row.match(new RegExp('^' + headerName + ':.*'));
            })
            .map(function(row) {
                return row.trim().substr(headerName.length + 1);
            })
            .join(',');

        if (!links) {
            return false;
        }
        // Tidy into nice object like {next: 'http://example.com/?p=1'}
        var tokens, url, rel, linksArray = links.split(','), value = {};
        for (var i = 0, l = linksArray.length; i < l; i++) {
            tokens = linksArray[i].split(';');
            url = tokens[0].replace(/[<>]/g, '').trim();
            rel = tokens[1].trim().split('=')[1].replace(/"/g, '');
            value[rel] = url;
        }

        return value;
    }

    Request.prototype = {
        send: send
    };

    return { create: create };

});
<commit_msg>always set empty accept header, unless it was explicitly specified<commit_after>define(function() {

    'use strict';

    var defaults = {
        authFlow: false,
        maxRetries: 0,
        maxAuthRetries: 1,
        followLocation: false,
        fileUpload: false,
        extractHeaders: ['Mendeley-Count', 'Link', 'Location']
    };
    var noopNotifier = { notify: function() {}};

    function create(request, settings, notifier) {
        return new Request(request, $.extend({}, defaults, settings), notifier);
    }

    function Request(request, settings, notifier) {
        if (!settings.authFlow) {
            throw new Error('Please provide an authentication interface');
        }
        this.request = request;
        this.settings = settings;
        this.retries = 0;
        this.authRetries = 0;
        this.notifier = notifier || noopNotifier;

        this.notifier.notify('startInfo', [this.request.type, this.request.url], this.request);
    }

    function send(dfd, request) {

        dfd = dfd || $.Deferred();
        request = request || this.request;

        var token = this.settings.authFlow.getToken(),
            headers = $.extend({ Accept: '' }, request.headers);

        // If no token at all (cookie deleted or expired) refresh token if possible or authenticate
        // because if you send 'Bearer ' you get a 400 rather than a 401 - is that a bug in the api?
        if (!token) {
            this.authRetries++;
            this.notifier.notify('authWarning', ['n.a.', this.authRetries, this.settings.maxAuthRetries], this.request);
            refreshToken.call(this, dfd);
            return dfd.promise();
        }

        request.headers = headers;
        request.headers.Authorization = 'Bearer ' + token;

        if (this.settings.fileUpload) {
            // Undocumented way to access XHR so we can add upload progress listeners
            var xhr = $.ajaxSettings.xhr();
            request.xhr = function() { return xhr; };

            // The response may have JSON Content-Type which makes jQuery invoke JSON.parse
            // on the reponse, but there isn't one so there's an error which causes the deferred
            // to be rejected. Specifying a dataType of 'text' prevents this.
            request.dataType = 'text';

            // Decorate the xhr with upload progress events
            ['loadstart', 'loadend', 'load', 'progress', 'abort', 'error', 'timeout']
                .forEach(function(uploadEvent) {
                    xhr.upload.addEventListener(uploadEvent, uploadProgressFun.call(this, dfd, request, xhr));
                }.bind(this));
        }

        $.ajax(request)
            .fail(onFail.bind(this, dfd))
            .done(onDone.bind(this, dfd));

        return dfd.promise();
    }

    function onFail(dfd, xhr) {
        switch (xhr.status) {
            case 0:
            case 504:
                // 504 Gateway timeout or communication error
                if (this.retries < this.settings.maxRetries) {
                    this.retries++;
                    this.notifier.notify('commWarning', [xhr.status, this.retries, this.settings.maxRetries], this.request, xhr);
                    this.send(dfd);
                } else {
                    this.notifier.notify('commError', [xhr.status, this.settings.maxRetries], this.request, xhr);
                    dfd.reject(this.request, xhr);
                }
                break;

            case 401:
                // 401 Unauthorized
                if (this.authRetries < this.settings.maxAuthRetries) {
                    this.authRetries++;
                    this.notifier.notify('authWarning', [xhr.status, this.authRetries, this.settings.maxAuthRetries], this.request, xhr);
                    refreshToken.call(this, dfd, xhr);
                } else {
                    this.notifier.notify('authError', [xhr.status, this.settings.maxAuthRetries], this.request, xhr);
                    dfd.reject(this.request, xhr);
                    this.settings.authFlow.authenticate(200);
                }
                break;

            default:
                this.notifier.notify('reqError', [xhr.status], this.request, xhr);
                dfd.reject(this.request, xhr);
        }
    }

    function onDone(dfd, response, success, xhr) {

        var locationHeader = getResponseHeader(xhr, 'Location'),
            headers;

        if (locationHeader && this.settings.followLocation && xhr.status === 201) {
            var redirect = {
                type: 'GET',
                url: locationHeader,
                dataType: 'json'
            };
            this.notifier.notify('redirectInfo', null, this.request, redirect);
            this.send(dfd, redirect);
        } else {
            if (this.settings.extractHeaders) {
                headers = extractHeaders.call(this, xhr);
            }

            // File uploads have type set to text, so if there is some JSON parse it manually
            if (this.settings.fileUpload) {
                if (response) {
                    try {
                        response = JSON.parse(response);
                    } catch (error) {
                        this.notifier.notify('parseError', null, this.request, xhr);
                        dfd.reject(error);
                        return;
                    }
                }
                this.notifier.notify('uploadSuccessInfo', null, this.request, response);
                dfd.resolve(response, xhr);
            } else {
                this.notifier.notify('successInfo', null, this.request, response);
                dfd.resolve(response, headers);
            }
        }
    }

    function refreshToken(dfd, xhr) {
        var refresh = this.settings.authFlow.refreshToken();
        if (refresh) {
            $.when(refresh)
                // If OK update the access token and re-send the request
                .done(function() {
                    this.send(dfd);
                }.bind(this))
                // If fails then we need to re-authenticate
                .fail(function(refreshxhr) {
                    this.notifier.notify('refreshError', [refreshxhr.status], this.request, refreshxhr);
                    dfd.reject(this.request, xhr);
                    this.settings.authFlow.authenticate(200);
                }.bind(this));
        } else {
            this.notifier.notify('refreshNotConfigured', []);
            dfd.reject(this.request, xhr);
            this.settings.authFlow.authenticate(200);
        }
    }

    /**
     * Get a function to monitor upload progress and emit notify events
     * on a deferred.
     *
     * @private
     * @param {object} dfd - Deferred
     * @param {object} request - The original request
     * @param {object} xhr The original XHR
     */
    function uploadProgressFun(dfd, request, xhr) {
        var progressPercent;
        var bytesTotal;
        var bytesSent;

        return function(progressEvent) {
            var eventType = progressEvent.type;

            if (progressEvent.lengthComputable) {
                bytesSent = progressEvent.loaded || progressEvent.position; // position is deprecated
                bytesTotal = progressEvent.total;
                progressPercent = Math.round(100 * bytesSent / bytesTotal);
                dfd.notify(progressEvent, progressPercent, bytesSent, bytesTotal);

                if (eventType === 'progress') {
                    this.notifier.notify('uploadProgressInfo', [progressPercent, bytesSent, bytesTotal], request, xhr);
                }
            }
            if (eventType === 'abort' || eventType === 'timeout' || eventType === 'error') {
                this.notifier.notify('uploadError', [eventType, progressPercent], request, xhr);
                dfd.reject(request, xhr, { event: progressEvent, percent: progressPercent });
            }
        }.bind(this);
    }

    function getResponseHeader(xhr, name) {
        if (!xhr || !xhr.getResponseHeader) {
            return '';
        }

        return xhr.getResponseHeader(name);
    }

    function getAllResponseHeaders(xhr) {
        if (!xhr || !xhr.getAllResponseHeaders) {
            return '';
        }

        return xhr.getAllResponseHeaders();
    }

    function extractHeaders(xhr) {
        var headers = {}, headerValue;

        this.settings.extractHeaders.forEach(function(headerName) {
            headerValue = headerName === 'Link' ?
                extractLinkHeaders.call(this, xhr) : getResponseHeader(xhr, headerName);

            if (headerValue) {
                headers[headerName] = headerValue;
            }
        });

        return headers;
    }

    function extractLinkHeaders(xhr) {

        // Safe way to get multiple headers of same type in IE
        var headerName = 'Link';
        var links = getAllResponseHeaders(xhr).split('\n')
            .filter(function(row) {
                return row.match(new RegExp('^' + headerName + ':.*'));
            })
            .map(function(row) {
                return row.trim().substr(headerName.length + 1);
            })
            .join(',');

        if (!links) {
            return false;
        }
        // Tidy into nice object like {next: 'http://example.com/?p=1'}
        var tokens, url, rel, linksArray = links.split(','), value = {};
        for (var i = 0, l = linksArray.length; i < l; i++) {
            tokens = linksArray[i].split(';');
            url = tokens[0].replace(/[<>]/g, '').trim();
            rel = tokens[1].trim().split('=')[1].replace(/"/g, '');
            value[rel] = url;
        }

        return value;
    }

    Request.prototype = {
        send: send
    };

    return { create: create };

});
<|endoftext|>


<commit_before>const https = require('https');
const { URL } = require('url');
const pkg = require('../package.json');

class Request {
  constructor(options) {
    this.baseUrl = options.baseUrl || 'https://mailosaur.com/';
    const encodedKey = Buffer.from(`${options.apiKey}:`).toString('base64');
    this.headers = {
      Accept: 'application/json',
      Authorization: `Basic ${encodedKey}`,
      'User-Agent': options.agent || `mailosaur-node/${pkg.version}`
    };
  }

  buildOptions(method, path, options = {}) {
    const result = {
      method,
      url: `${this.baseUrl}${path}`,
      headers: {
        Accept: this.headers.Accept,
        Authorization: this.headers.Authorization,
        'User-Agent': this.headers['User-Agent']
      }
    };

    if (options.buffer) {
      result.buffer = true;
    }

    if (options.qs) {
      Object.keys(options.qs).forEach((key, index) => {
        let value = options.qs[key];
        if (!value) {
          return;
        }
        if (value instanceof Date) {
          value = value.toISOString();
        }
        const prefix = !index ? '?' : '&';
        result.url += `${prefix}${key}=${value}`;
      });
    }

    if (options.body) {
      result.data = JSON.stringify(options.body);
      result.headers['Content-Type'] = 'application/json';
      result.headers['Content-Length'] = result.data.length;
    }
    return result;
  }

  invoke(options, callback) {
    const url = new URL(options.url);

    const req = https.request({
      ...options,
      protocol: url.protocol,
      hostname: url.hostname,
      path: url.pathname + url.search
    }, (res) => {
      const data = [];
      res.on('data', (chunk) => data.push(chunk));
      res.on('end', () => {
        try {
          if (data) {
            res.body = options.buffer ?
              Buffer.concat(data) :
              JSON.parse(Buffer.concat(data).toString());
          }
        } catch (e) {
          res.body = data;
        }

        callback(null, res, res.body);
      });
    });

    req.on('error', (e) => callback(e));

    if (options.data) {
      req.write(options.data);
    }

    req.end();
  }

  request(method, path, options, callback) {
    /* eslint-disable no-param-reassign */
    if (typeof options === 'function') {
      callback = options;
      options = undefined;
    }
    /* eslint-enable no-param-reassign */

    const requestOptions = this.buildOptions(method, path, options);
    this.invoke(requestOptions, callback);
  }

  get(path, options, callback) {
    this.request('GET', path, options, callback);
  }

  put(path, options, callback) {
    this.request('PUT', path, options, callback);
  }

  post(path, options, callback) {
    this.request('POST', path, options, callback);
  }

  del(path, options, callback) {
    this.request('DELETE', path, options, callback);
  }
}

module.exports = Request;
<commit_msg>Remove incompatible spread<commit_after>const https = require('https');
const { URL } = require('url');
const pkg = require('../package.json');

class Request {
  constructor(options) {
    this.baseUrl = options.baseUrl || 'https://mailosaur.com/';
    const encodedKey = Buffer.from(`${options.apiKey}:`).toString('base64');
    this.headers = {
      Accept: 'application/json',
      Authorization: `Basic ${encodedKey}`,
      'User-Agent': options.agent || `mailosaur-node/${pkg.version}`
    };
  }

  buildOptions(method, path, options = {}) {
    const result = {
      method,
      url: `${this.baseUrl}${path}`,
      headers: {
        Accept: this.headers.Accept,
        Authorization: this.headers.Authorization,
        'User-Agent': this.headers['User-Agent']
      }
    };

    if (options.buffer) {
      result.buffer = true;
    }

    if (options.qs) {
      Object.keys(options.qs).forEach((key, index) => {
        let value = options.qs[key];
        if (!value) {
          return;
        }
        if (value instanceof Date) {
          value = value.toISOString();
        }
        const prefix = !index ? '?' : '&';
        result.url += `${prefix}${key}=${value}`;
      });
    }

    if (options.body) {
      result.data = JSON.stringify(options.body);
      result.headers['Content-Type'] = 'application/json';
      result.headers['Content-Length'] = result.data.length;
    }
    return result;
  }

  invoke(options, callback) {
    const url = new URL(options.url);

    // Ignoring as spread is not compatible with Node 6
    // eslint-disable-next-line prefer-object-spread
    const spread = Object.assign({}, options, {
      protocol: url.protocol,
      hostname: url.hostname,
      path: url.pathname + url.search
    });
    const req = https.request(spread, (res) => {
      const data = [];
      res.on('data', (chunk) => data.push(chunk));
      res.on('end', () => {
        try {
          if (data) {
            res.body = options.buffer ?
              Buffer.concat(data) :
              JSON.parse(Buffer.concat(data).toString());
          }
        } catch (e) {
          res.body = data;
        }

        callback(null, res, res.body);
      });
    });

    req.on('error', (e) => callback(e));

    if (options.data) {
      req.write(options.data);
    }

    req.end();
  }

  request(method, path, options, callback) {
    /* eslint-disable no-param-reassign */
    if (typeof options === 'function') {
      callback = options;
      options = undefined;
    }
    /* eslint-enable no-param-reassign */

    const requestOptions = this.buildOptions(method, path, options);
    this.invoke(requestOptions, callback);
  }

  get(path, options, callback) {
    this.request('GET', path, options, callback);
  }

  put(path, options, callback) {
    this.request('PUT', path, options, callback);
  }

  post(path, options, callback) {
    this.request('POST', path, options, callback);
  }

  del(path, options, callback) {
    this.request('DELETE', path, options, callback);
  }
}

module.exports = Request;
<|endoftext|>


<commit_before>'use strict';

const groups = require.main.require('./src/groups');
const user = require.main.require('./src/user');
const db = require.main.require('./src/database');

const winston = require.main.require('winston');
const validator = require.main.require('validator');

const rewards = module.exports;

rewards.get = function (rewards, callback) {
	getGroupList(function (err, groups) {
		if (err) {
			return err;
		}

		rewards = rewards.concat([
			{
				rid: 'essentials/add-to-group',
				name: 'Add to Group',
				inputs: [
					{
						type: 'select',
						name: 'groupname',
						label: 'Group Name:',
						values: groups,
					},
				],
			},
			{
				rid: 'essentials/remove-from-group',
				name: 'Remove from Group',
				inputs: [
					{
						type: 'select',
						name: 'groupname',
						label: 'Group Name:',
						values: groups,
					},
				],
			},
			{
				rid: 'essentials/alert-user',
				name: 'Send alert message',
				inputs: [
					{
						type: 'text',
						name: 'title',
						label: 'Title:',
					},
					{
						type: 'text',
						name: 'message',
						label: 'Message:',
					},
				],
			},
			{
				rid: 'essentials/award-reputation',
				name: 'Award Reputation',
				inputs: [
					{
						type: 'text',
						name: 'reputation',
						label: 'Amount of reputation:',
					},
				],
			},
		]);

		callback(null, rewards);
	});
};

rewards.addToGroup = async function (data) {
	await groups.join(data.reward.groupname, data.uid);
};

rewards.removeFromGroup = async function (data) {
	await groups.leave(data.reward.groupname, data.uid);
};

rewards.alertUser = function (data) {
	var websockets = require.main.require('./src/socket.io');
	websockets.in('uid_' + data.uid).emit('event:alert', data.reward);
};

rewards.awardReputation = function (data) {
	user.incrementUserReputationBy(data.uid, data.reward.reputation, function (err) {
		if (err) {
			return winston.error(err.stack);
		}
	});
};

function getGroupList(callback) {
	var list = [];

	groups.getGroups('groups:createtime', 0, -1, function (err, groups) {
		if (err) {
			return callback(err);
		}
		groups.forEach(function (group) {
			var name = group;
			var matchesCatPrivsGroup = group.match(/cid:([0-9]*):privileges:groups:([\s\S]*)/);

			if (matchesCatPrivsGroup !== null) {
				name = 'Category ' + matchesCatPrivsGroup[1] + ' group with privilege ' + matchesCatPrivsGroup[2];
			}

			list.push({
				name: validator.escape(String(name || group)),
				value: validator.escape(String(group)),
			});
		});
		callback(null, list);
	});
}
<commit_msg>fix: remove category 0 group from rewards<commit_after>'use strict';

const groups = require.main.require('./src/groups');
const user = require.main.require('./src/user');
const db = require.main.require('./src/database');

const winston = require.main.require('winston');
const validator = require.main.require('validator');

const rewards = module.exports;

rewards.get = function (rewards, callback) {
	getGroupList(function (err, groups) {
		if (err) {
			return err;
		}

		rewards = rewards.concat([
			{
				rid: 'essentials/add-to-group',
				name: 'Add to Group',
				inputs: [
					{
						type: 'select',
						name: 'groupname',
						label: 'Group Name:',
						values: groups,
					},
				],
			},
			{
				rid: 'essentials/remove-from-group',
				name: 'Remove from Group',
				inputs: [
					{
						type: 'select',
						name: 'groupname',
						label: 'Group Name:',
						values: groups,
					},
				],
			},
			{
				rid: 'essentials/alert-user',
				name: 'Send alert message',
				inputs: [
					{
						type: 'text',
						name: 'title',
						label: 'Title:',
					},
					{
						type: 'text',
						name: 'message',
						label: 'Message:',
					},
				],
			},
			{
				rid: 'essentials/award-reputation',
				name: 'Award Reputation',
				inputs: [
					{
						type: 'text',
						name: 'reputation',
						label: 'Amount of reputation:',
					},
				],
			},
		]);

		callback(null, rewards);
	});
};

rewards.addToGroup = async function (data) {
	await groups.join(data.reward.groupname, data.uid);
};

rewards.removeFromGroup = async function (data) {
	await groups.leave(data.reward.groupname, data.uid);
};

rewards.alertUser = function (data) {
	var websockets = require.main.require('./src/socket.io');
	websockets.in('uid_' + data.uid).emit('event:alert', data.reward);
};

rewards.awardReputation = function (data) {
	user.incrementUserReputationBy(data.uid, data.reward.reputation, function (err) {
		if (err) {
			return winston.error(err.stack);
		}
	});
};

function getGroupList(callback) {
	var list = [];

	groups.getGroups('groups:createtime', 0, -1, function (err, groups) {
		if (err) {
			return callback(err);
		}
		groups.forEach(function (group) {
			var name = group;
			var matchesCatPrivsGroup = group.match(/cid:([0-9]*):privileges:groups:([\s\S]*)/);

			if (matchesCatPrivsGroup !== null) {
				if (matchesCatPrivsGroup[1] === '0') {
					return;
				}
				name = 'Category ' + matchesCatPrivsGroup[1] + ' group with privilege ' + matchesCatPrivsGroup[2];
			}

			list.push({
				name: validator.escape(String(name || group)),
				value: validator.escape(String(group)),
			});
		});
		callback(null, list);
	});
}
<|endoftext|>


<commit_before>var fe = require('mitro/mitro_fe');
var mitro = require('mitro/mitro_lib');

// ---

var availableCommands = {
  browse: 'browse',
  ls: 'list-all',
  list: 'list-all',
  lg: 'list-groups',
  'list-groups': 'list-groups',
  listGroups: 'list-groups',
  lss: 'list-secrets',
  'list-secrets': 'list-secrets',
  listSecrets: 'list-secrets',
  show: 'get-secret',
  see: 'get-secret',
  'get-secret': 'get-secret',
  get: 'get-secret'
};

// ---

module.exports = function init(argv) {

  var command = argv._[0];
  var runCommand = availableCommands[command];

  fe.setDeviceId(argv.deviceId);

  if (command && runCommand) {
    require('../lib/commands/' + runCommand)(mitro);
  }

};

<commit_msg>Start interactive mode if no command was provided<commit_after>var fe = require('mitro/mitro_fe');
var mitro = require('mitro/mitro_lib');

// ---

var availableCommands = {
  browse: 'browse',
  ls: 'list-all',
  list: 'list-all',
  lg: 'list-groups',
  'list-groups': 'list-groups',
  listGroups: 'list-groups',
  lss: 'list-secrets',
  'list-secrets': 'list-secrets',
  listSecrets: 'list-secrets',
  show: 'get-secret',
  see: 'get-secret',
  'get-secret': 'get-secret',
  get: 'get-secret'
};

// ---

module.exports = function init(argv) {

  var command = argv._[0];
  var runCommand = availableCommands[command];

  fe.setDeviceId(argv.deviceId);

  if (command && runCommand) {
    require('../lib/commands/' + runCommand)(mitro);
  } else {
    // Start interactive mode if no command was provided
    require('../lib/commands/browse')(mitro);
  }

};

<|endoftext|>


<commit_before>/**
 * Copyright 2013-2017  Zaid Abdulla
 *
 * This file is part of GenieACS.
 *
 * GenieACS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * GenieACS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with GenieACS.  If not, see <http://www.gnu.org/licenses/>.
 */
"use strict";

const vm = require("vm");

const seedrandom = require("seedrandom");

const common = require("./common");
const device = require("./device");
const extensions = require("./extensions");
const logger = require("./logger");
const scheduling = require("./scheduling");

// Used for throwing to exit user script and commit
const COMMIT = Symbol();

// Used to execute extensions and restart
const EXT = Symbol();

const context = vm.createContext();

let state;

class SandboxDate {
  constructor() {
    if (arguments.length)
      return new Date(...arguments);

    return new Date(state.sessionContext.timestamp);
  }

  static now(intervalOrCron, variance) {
    let t = state.sessionContext.timestamp;

    if (typeof intervalOrCron === "number") {

      if (variance == null)
        variance = intervalOrCron;

      let offset = 0;
      if (variance)
        offset = scheduling.variance(state.sessionContext.deviceId, variance);

      t = scheduling.interval(t, intervalOrCron, offset);
    }
    else if (typeof intervalOrCron === "string") {
      let offset = 0;
      if (variance)
        offset = scheduling.variance(state.sessionContext.deviceId, variance);
      let cron = scheduling.parseCron(intervalOrCron);
      t = scheduling.cron(t, cron, offset)[0];
    }
    else if (intervalOrCron) {
      throw new Error("Invalid Date.now() argument");
    }

    return t;
  }
};

function random() {
  if (!state.rng)
    state.rng = seedrandom(state.sessionContext.deviceId);

  return state.rng();
}

random.seed = function(s) {
  state.rng = seedrandom(s);
}

class ParameterWrapper {
  constructor(path, attributes, unpacked, unpackedRevision) {
    for (let attrName of attributes) {
      Object.defineProperty(this, attrName, {get: function() {
        if (state.uncommitted)
          commit();

        if (state.revision !== unpackedRevision) {
          unpackedRevision = state.revision;
          unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision);
        }

        if (!unpacked.length)
          return undefined;

        let attr = state.sessionContext.deviceData.attributes.get(unpacked[0], state.revision)[attrName];

        if (!attr)
          return undefined;

        return attr[1];
      }});
    }

    Object.defineProperty(this, "path", {get: function() {
      if (state.uncommitted)
        commit();

      if (state.revision !== unpackedRevision) {
        unpackedRevision = state.revision;
        unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision);
      }

      if (!unpacked.length)
        return undefined;

      return unpacked[0].join(".");
    }});

    Object.defineProperty(this, "size", {get: function() {
      if (state.uncommitted)
        commit();

      if (state.revision !== unpackedRevision) {
        unpackedRevision = state.revision;
        unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision);
      }

      if (!unpacked.length)
        return undefined;

      return unpacked.length;
    }});

    this[Symbol.iterator] = function*() {
      if (state.uncommitted)
        commit();

      if (state.revision !== unpackedRevision) {
        unpackedRevision = state.revision;
        unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision);
      }

      for (let p of unpacked)
        yield new ParameterWrapper(p, attributes, [p], state.revision);
    }
  }
}

function declare(path, timestamps, values) {
  state.uncommitted = true;
  if (!timestamps)
    timestamps = {};

  if (!values)
    values = {};

  let parsedPath = common.parsePath(path);

  let declaration = [parsedPath, 1];
  let attrs = {};

  for (let attrName in values) {
    if (!timestamps[attrName])
      timestamps[attrName] = 1;

    if (attrName === "path")
      declaration[3] = values.path;
    else {
      attrs[attrName] = 1;
      declaration[4] = declaration[4] || {};
      if (attrName === "value" && !Array.isArray(values.value))
        declaration[4].value = [values.value];
      else
        declaration[4][attrName] = values[attrName];
    }
  }

  for (let attrName in timestamps) {
    if (attrName === "path")
      declaration[1] = timestamps.path;
    else {
      attrs[attrName] = 1;
      declaration[2] = declaration[2] || {};
      declaration[2][attrName] = timestamps[attrName];
    }
  }

  state.declarations.push(declaration);

  return new ParameterWrapper(parsedPath, Object.keys(attrs));
}

function clear(path, timestamp, attributes) {
  state.uncommitted = true;

  if (state.revision === state.maxRevision)
    state.clear.push([common.parsePath(path), timestamp, attributes]);
}

function commit() {
  ++ state.revision;
  state.uncommitted = false;

  if (state.revision === state.maxRevision + 1)
    throw COMMIT;
  else if (state.revision > state.maxRevision + 1)
    throw new Error("Declare function should not be called from within a try/catch block");
}

function ext() {
  ++ state.extCounter;
  let extCall = Array.from(arguments).map(String);
  let key = `${state.revision}: ${JSON.stringify(extCall)}`;

  if (key in state.sessionContext.extensionsCache)
    return state.sessionContext.extensionsCache[key];

  state.extensions[key] = extCall;
  throw EXT;
}

function log(msg, meta) {
  if (state.revision === state.maxRevision && state.extCounter >= 0) {
    const details = Object.assign({}, meta, {
      sessionContext: state.sessionContext,
      message: `Script: ${msg}`
    });

    delete details.hostname;
    delete details.pid;
    delete details.name;
    delete details.version;
    delete details.deviceId;
    delete details.remoteAddress;

    logger.accessInfo(details);
  }
}

Object.defineProperty(context, "Date", {value: SandboxDate});
Object.defineProperty(context, "declare", {value: declare});
Object.defineProperty(context, "clear", {value: clear});
Object.defineProperty(context, "commit", {value: commit});
Object.defineProperty(context, "ext", {value: ext});
Object.defineProperty(context, "log", {value: log});

// Monkey-patch Math.random() to make it deterministic
context.random = random;
vm.runInContext("Math.random = random;", context);
delete context.random;

function errorToFault(err) {
  if (!err)
    return null;

  if (!err.name)
    return {code: "script", message: `${err}`};

  let fault = {
    code: `script.${err.name}`,
    message: err.message,
    detail: {
      name: err.name,
      message: err.message
    }
  };

  if (err.stack) {
    // Trim the stack trace at the self-executing anonymous wrapper function
    let stackTrimIndex = err.stack.match(/\s+at\s[^\s]+\s+at\s[^\s]+\s\(vm\.js.+\)/).index;
    fault.detail.stack = err.stack.slice(0, stackTrimIndex);
  }

  return fault;
}

function run(script, globals, sessionContext, startRevision, maxRevision, callback, extCounter = 0) {
  state = {
    sessionContext: sessionContext,
    revision: startRevision,
    maxRevision: maxRevision,
    uncommitted: false,
    declarations: [],
    extensions: {},
    clear: [],
    rng: null,
    extCounter: extCounter
  };

  for (let n of Object.keys(context))
    delete context[n];

  Object.assign(context, globals);

  let ret, status;

  try {
    ret = script.runInContext(context, {displayErrors: false});
    status = 0;
  }
  catch (err) {
    if (err === COMMIT)
      status = 1;
    else if (err === EXT)
      status = 2;
    else
      return callback(null, errorToFault(err));
  }

  let _state = state;
  let args = Array.from(arguments);

  // Need to maintain a counter of ext() calls to avoid calling certain
  // functions (e.g. log()) multiple times as calling ext() results in
  // re-execution of script without revision being incremented.
  args[6] = 0 - (state.extCounter - extCounter);

  let counter = 3;
  for (let key of Object.keys(_state.extensions)) {
    counter += 2;
    extensions.run(_state.extensions[key], function(err, fault, res) {
      if (err || fault) {
        if (counter & 1)
          callback(err, fault);
        return counter = 0;
      }
      _state.sessionContext.extensionsCache[key] = res;
      if ((counter -= 2) === 1)
        if (status === 2)
          return run.apply(null, args);
        else
          return callback(null, null, _state.clear, _state.declarations, status === 0, ret);
    });
  }

  if ((counter -= 2) === 1)
    if (status === 2)
      return run.apply(null, args);
    else
      return callback(null, null, _state.clear, _state.declarations, status === 0, ret);
}

exports.run = run;
<commit_msg>Fix error trying to trim a partial stack trace<commit_after>/**
 * Copyright 2013-2017  Zaid Abdulla
 *
 * This file is part of GenieACS.
 *
 * GenieACS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * GenieACS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with GenieACS.  If not, see <http://www.gnu.org/licenses/>.
 */
"use strict";

const vm = require("vm");

const seedrandom = require("seedrandom");

const common = require("./common");
const device = require("./device");
const extensions = require("./extensions");
const logger = require("./logger");
const scheduling = require("./scheduling");

// Used for throwing to exit user script and commit
const COMMIT = Symbol();

// Used to execute extensions and restart
const EXT = Symbol();

const context = vm.createContext();

let state;

class SandboxDate {
  constructor() {
    if (arguments.length)
      return new Date(...arguments);

    return new Date(state.sessionContext.timestamp);
  }

  static now(intervalOrCron, variance) {
    let t = state.sessionContext.timestamp;

    if (typeof intervalOrCron === "number") {

      if (variance == null)
        variance = intervalOrCron;

      let offset = 0;
      if (variance)
        offset = scheduling.variance(state.sessionContext.deviceId, variance);

      t = scheduling.interval(t, intervalOrCron, offset);
    }
    else if (typeof intervalOrCron === "string") {
      let offset = 0;
      if (variance)
        offset = scheduling.variance(state.sessionContext.deviceId, variance);
      let cron = scheduling.parseCron(intervalOrCron);
      t = scheduling.cron(t, cron, offset)[0];
    }
    else if (intervalOrCron) {
      throw new Error("Invalid Date.now() argument");
    }

    return t;
  }
};

function random() {
  if (!state.rng)
    state.rng = seedrandom(state.sessionContext.deviceId);

  return state.rng();
}

random.seed = function(s) {
  state.rng = seedrandom(s);
}

class ParameterWrapper {
  constructor(path, attributes, unpacked, unpackedRevision) {
    for (let attrName of attributes) {
      Object.defineProperty(this, attrName, {get: function() {
        if (state.uncommitted)
          commit();

        if (state.revision !== unpackedRevision) {
          unpackedRevision = state.revision;
          unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision);
        }

        if (!unpacked.length)
          return undefined;

        let attr = state.sessionContext.deviceData.attributes.get(unpacked[0], state.revision)[attrName];

        if (!attr)
          return undefined;

        return attr[1];
      }});
    }

    Object.defineProperty(this, "path", {get: function() {
      if (state.uncommitted)
        commit();

      if (state.revision !== unpackedRevision) {
        unpackedRevision = state.revision;
        unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision);
      }

      if (!unpacked.length)
        return undefined;

      return unpacked[0].join(".");
    }});

    Object.defineProperty(this, "size", {get: function() {
      if (state.uncommitted)
        commit();

      if (state.revision !== unpackedRevision) {
        unpackedRevision = state.revision;
        unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision);
      }

      if (!unpacked.length)
        return undefined;

      return unpacked.length;
    }});

    this[Symbol.iterator] = function*() {
      if (state.uncommitted)
        commit();

      if (state.revision !== unpackedRevision) {
        unpackedRevision = state.revision;
        unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision);
      }

      for (let p of unpacked)
        yield new ParameterWrapper(p, attributes, [p], state.revision);
    }
  }
}

function declare(path, timestamps, values) {
  state.uncommitted = true;
  if (!timestamps)
    timestamps = {};

  if (!values)
    values = {};

  let parsedPath = common.parsePath(path);

  let declaration = [parsedPath, 1];
  let attrs = {};

  for (let attrName in values) {
    if (!timestamps[attrName])
      timestamps[attrName] = 1;

    if (attrName === "path")
      declaration[3] = values.path;
    else {
      attrs[attrName] = 1;
      declaration[4] = declaration[4] || {};
      if (attrName === "value" && !Array.isArray(values.value))
        declaration[4].value = [values.value];
      else
        declaration[4][attrName] = values[attrName];
    }
  }

  for (let attrName in timestamps) {
    if (attrName === "path")
      declaration[1] = timestamps.path;
    else {
      attrs[attrName] = 1;
      declaration[2] = declaration[2] || {};
      declaration[2][attrName] = timestamps[attrName];
    }
  }

  state.declarations.push(declaration);

  return new ParameterWrapper(parsedPath, Object.keys(attrs));
}

function clear(path, timestamp, attributes) {
  state.uncommitted = true;

  if (state.revision === state.maxRevision)
    state.clear.push([common.parsePath(path), timestamp, attributes]);
}

function commit() {
  ++ state.revision;
  state.uncommitted = false;

  if (state.revision === state.maxRevision + 1)
    throw COMMIT;
  else if (state.revision > state.maxRevision + 1)
    throw new Error("Declare function should not be called from within a try/catch block");
}

function ext() {
  ++ state.extCounter;
  let extCall = Array.from(arguments).map(String);
  let key = `${state.revision}: ${JSON.stringify(extCall)}`;

  if (key in state.sessionContext.extensionsCache)
    return state.sessionContext.extensionsCache[key];

  state.extensions[key] = extCall;
  throw EXT;
}

function log(msg, meta) {
  if (state.revision === state.maxRevision && state.extCounter >= 0) {
    const details = Object.assign({}, meta, {
      sessionContext: state.sessionContext,
      message: `Script: ${msg}`
    });

    delete details.hostname;
    delete details.pid;
    delete details.name;
    delete details.version;
    delete details.deviceId;
    delete details.remoteAddress;

    logger.accessInfo(details);
  }
}

Object.defineProperty(context, "Date", {value: SandboxDate});
Object.defineProperty(context, "declare", {value: declare});
Object.defineProperty(context, "clear", {value: clear});
Object.defineProperty(context, "commit", {value: commit});
Object.defineProperty(context, "ext", {value: ext});
Object.defineProperty(context, "log", {value: log});

// Monkey-patch Math.random() to make it deterministic
context.random = random;
vm.runInContext("Math.random = random;", context);
delete context.random;

function errorToFault(err) {
  if (!err)
    return null;

  if (!err.name)
    return {code: "script", message: `${err}`};

  let fault = {
    code: `script.${err.name}`,
    message: err.message,
    detail: {
      name: err.name,
      message: err.message
    }
  };

  if (err.stack) {
    fault.detail.stack = err.stack;
    // Trim the stack trace at the self-executing anonymous wrapper function
    let stackTrimIndex = fault.detail.stack.match(/\s+at\s[^\s]+\s+at\s[^\s]+\s\(vm\.js.+\)/);
    if (stackTrimIndex)
      fault.detail.stack = fault.detail.stack.slice(0, stackTrimIndex.index);
  }

  return fault;
}

function run(script, globals, sessionContext, startRevision, maxRevision, callback, extCounter = 0) {
  state = {
    sessionContext: sessionContext,
    revision: startRevision,
    maxRevision: maxRevision,
    uncommitted: false,
    declarations: [],
    extensions: {},
    clear: [],
    rng: null,
    extCounter: extCounter
  };

  for (let n of Object.keys(context))
    delete context[n];

  Object.assign(context, globals);

  let ret, status;

  try {
    ret = script.runInContext(context, {displayErrors: false});
    status = 0;
  }
  catch (err) {
    if (err === COMMIT)
      status = 1;
    else if (err === EXT)
      status = 2;
    else
      return callback(null, errorToFault(err));
  }

  let _state = state;
  let args = Array.from(arguments);

  // Need to maintain a counter of ext() calls to avoid calling certain
  // functions (e.g. log()) multiple times as calling ext() results in
  // re-execution of script without revision being incremented.
  args[6] = 0 - (state.extCounter - extCounter);

  let counter = 3;
  for (let key of Object.keys(_state.extensions)) {
    counter += 2;
    extensions.run(_state.extensions[key], function(err, fault, res) {
      if (err || fault) {
        if (counter & 1)
          callback(err, fault);
        return counter = 0;
      }
      _state.sessionContext.extensionsCache[key] = res;
      if ((counter -= 2) === 1)
        if (status === 2)
          return run.apply(null, args);
        else
          return callback(null, null, _state.clear, _state.declarations, status === 0, ret);
    });
  }

  if ((counter -= 2) === 1)
    if (status === 2)
      return run.apply(null, args);
    else
      return callback(null, null, _state.clear, _state.declarations, status === 0, ret);
}

exports.run = run;
<|endoftext|>


<commit_before>var sugar = require('sugar');
var irc = require('irc');
var webSocket = require('ws');

var channel = '#atptest';
var webAddress = 'http://www.caseyliss.com/showbot'

var titles = [];
var connections = [];
var links = [];

function sendToAll(packet) {
    connections.forEach(function (connection) {
        connection.send(JSON.stringify(packet));
    });
}

function saveBackup() {
    // TODO: Figure out what to do here.
}

function handleNewSuggestion(from, message) {
    var title = '';
    if (message.startsWith('!suggest')) {
        title = message.substring(9);
    } else if (message.startsWith('!s')) {
        title = message.substring(3);
    }

    if (title.length > 75) {
        client.say(from, 'That title is too long; please try again.');
        title = '';
    }
    if (title.length > 0) {
        // Make sure this isn't a duplicate.
        if (titles.findAll({titleLower: title.toLowerCase()}).length === 0) {
            var title = {
                id: titles.length,
                author: from,
                title: title,
                titleLower: title.toLowerCase(),
                votes: 0,
                votesBy: [],
                time: new Date()
            };
            titles.push(title);

            sendToAll({operation: 'NEW', title: title});
        } else {
            //client.say(channel, 'Sorry, ' + from + ', your title is a duplicate. Please try another!');
            client.say(from, 'Sorry, your title is a duplicate. Please try another!');
        }
    }
}

function handleSendVotes(from, message) {
    var titlesByVote = titles.sortBy(function (t) {
        return t.votes;
    }, true).to(3);

    client.say(from, 'Three most popular titles:');
    for (var i = 0; i < titlesByVote.length; ++i) {
        var votes = titlesByVote[i]['votes'];
        client.say(from, titlesByVote[i]['votes'] + ' vote' + (votes != 1 ? 's' : '') +  ': " ' + titlesByVote[i].title + '"');
    }
}

function handleNewLink(from, message) {
    if (message.startsWith('!link')) {
        message = message.substring(6);
    } else if (message.startsWith('!l')) {
        message = message.substring(3);
    }

    if (message.startsWith('http')) {
        var link = {
            id: links.length,
            author: from,
            link: message,
            time: new Date()
        };
        links.push(link);

        sendToAll({operation: 'NEWLINK', link: link});
    } else {
        client.say(from, "That doesn't look like a link to me.");
    }
}

function handleHelp(from) {
    client.say(from, 'Options:');
    client.say(from, '!s {title} - suggest a title.');
    client.say(from, '!votes - get the three most highly voted titles.')
    client.say(from, '!link {URL} - suggest a link.');
    client.say(from, '!help - see this message.');
    client.say(from, 'To see titles/links, go to: ' + webAddress);
}

var client = new irc.Client('irc.freenode.net', 'accidentalbot', {
    channels: [channel]
});

client.addListener('join', function (channel, nick, message) {
    console.log('Joined channel ' + channel);
    setInterval(saveBackup, 300000);
});

client.addListener('message', function (from, to, message) {
    if (message.startsWith('!s')) {
        handleNewSuggestion(from, message);
    } else if (message.startsWith("!votes")) {
        handleSendVotes(from, message);
    } else if (message.startsWith('!l')) {
        handleNewLink(from, message);
    } else if (message.startsWith('!help')) {
        handleHelp(from);
    }
});

client.addListener('error', function (message) {
    console.log('error: ', message)
});

/***************************************************
 * WEB SOCKETS                                     *
 ***************************************************/

var port = Number(process.env.PORT || 5001);
var socketServer = new webSocket.Server({port: port});

socketServer.on('connection', function(socket) {
    connections.push(socket);
    // This is valid for direct deployments, without routing/load balancing.
    // var address = socket.upgradeReq.connection.remoteAddress;
    // This is valid for Heroku's routing. Your mileage may vary.
    var address = socket.upgradeReq.headers['x-forwarded-for'];
    console.log('Client connected: ' + address);
    var titlesWithVotes = titles.map(function (title) {
        var isVoted = title.votesBy.some(function (testAddress) {
            return testAddress === address;
        });
        var newTitle = Object.clone(title, true);
        newTitle.voted = isVoted;
        return newTitle;
    });
    socket.send(JSON.stringify({operation: 'REFRESH', titles: titlesWithVotes, links: links}));

    socket.on('close', function () {
        console.log('Client disconnected: ' + address);
        connections.splice(connections.indexOf(socket), 1);
    });

    socket.on('message', function (data, flags) {
        if (flags.binary) {
            console.log("ignoring binary message from "  + address);
            return;
        }
        var packet = JSON.parse(data);
        if (packet.operation === 'VOTE') {
            var matches = titles.findAll({id: packet['id']});

            if (matches.length > 0) {
                var upvoted = matches[0];
                if (upvoted['votesBy'].any(address) == false) {
                    upvoted['votes'] = Number(upvoted['votes']) + 1;
                    upvoted['votesBy'].push(address);
                    sendToAll({operation: 'VOTE', votes: upvoted['votes'], id: upvoted['id']});
                    console.log('+1 for ' + upvoted['title'] + ' by ' + address);
                } else {
                    console.log('ignoring duplicate vote by ' + address + ' for ' + upvoted['title']);
                }
            } else {
                console.log('no matches for id: ' + packet['id']);
            }
        } else if (packet.operation === 'PING') {
            socket.send(JSON.stringify({operation: 'PONG'}));
        } else {
            console.log("Don't know what to do with " + packet['operation']);
        }
    });
});
<commit_msg>Initial basic probably-inadequate DOS protection.<commit_after>var sugar = require('sugar');
var irc = require('irc');
var webSocket = require('ws');

var channel = '#atptest';
var webAddress = 'http://www.caseyliss.com/showbot'

var titles = [];
var connections = [];
var links = [];

function sendToAll(packet) {
    connections.forEach(function (connection) {
        connection.send(JSON.stringify(packet));
    });
}

function saveBackup() {
    // TODO: Figure out what to do here.
}

function handleNewSuggestion(from, message) {
    var title = '';
    if (message.startsWith('!suggest')) {
        title = message.substring(9);
    } else if (message.startsWith('!s')) {
        title = message.substring(3);
    }

    if (title.length > 75) {
        client.say(from, 'That title is too long; please try again.');
        title = '';
    }
    if (title.length > 0) {
        // Make sure this isn't a duplicate.
        if (titles.findAll({titleLower: title.toLowerCase()}).length === 0) {
            var title = {
                id: titles.length,
                author: from,
                title: title,
                titleLower: title.toLowerCase(),
                votes: 0,
                votesBy: [],
                time: new Date()
            };
            titles.push(title);

            sendToAll({operation: 'NEW', title: title});
        } else {
            //client.say(channel, 'Sorry, ' + from + ', your title is a duplicate. Please try another!');
            client.say(from, 'Sorry, your title is a duplicate. Please try another!');
        }
    }
}

function handleSendVotes(from, message) {
    var titlesByVote = titles.sortBy(function (t) {
        return t.votes;
    }, true).to(3);

    client.say(from, 'Three most popular titles:');
    for (var i = 0; i < titlesByVote.length; ++i) {
        var votes = titlesByVote[i]['votes'];
        client.say(from, titlesByVote[i]['votes'] + ' vote' + (votes != 1 ? 's' : '') +  ': " ' + titlesByVote[i].title + '"');
    }
}

function handleNewLink(from, message) {
    if (message.startsWith('!link')) {
        message = message.substring(6);
    } else if (message.startsWith('!l')) {
        message = message.substring(3);
    }

    if (message.startsWith('http')) {
        var link = {
            id: links.length,
            author: from,
            link: message,
            time: new Date()
        };
        links.push(link);

        sendToAll({operation: 'NEWLINK', link: link});
    } else {
        client.say(from, "That doesn't look like a link to me.");
    }
}

function handleHelp(from) {
    client.say(from, 'Options:');
    client.say(from, '!s {title} - suggest a title.');
    client.say(from, '!votes - get the three most highly voted titles.')
    client.say(from, '!link {URL} - suggest a link.');
    client.say(from, '!help - see this message.');
    client.say(from, 'To see titles/links, go to: ' + webAddress);
}

var client = new irc.Client('irc.freenode.net', 'accidentalbot', {
    channels: [channel]
});

client.addListener('join', function (channel, nick, message) {
    console.log('Joined channel ' + channel);
    setInterval(saveBackup, 300000);
});

client.addListener('message', function (from, to, message) {
    if (message.startsWith('!s')) {
        handleNewSuggestion(from, message);
    } else if (message.startsWith("!votes")) {
        handleSendVotes(from, message);
    } else if (message.startsWith('!l')) {
        handleNewLink(from, message);
    } else if (message.startsWith('!help')) {
        handleHelp(from);
    }
});

client.addListener('error', function (message) {
    console.log('error: ', message)
});

/***************************************************
 * WEB SOCKETS                                     *
 ***************************************************/

var port = Number(process.env.PORT || 5001);
var proxied = process.env.PROXIED !== 'false';
var socketServer = new webSocket.Server({port: port});

// DOS protection - we disconnect any address which sends more than windowLimit
// messages in a window of windowSize milliseconds.
var windowLimit = 50;
var windowSize = 5000;
var currentWindow = 0;
var recentMessages = {};
function floodedBy(socket) {
    // To be called each time we get a message or connection attempt.
    //
    // If that address has been flooding us, we disconnect all open connections
    // from that address and return `true` to indicate that it should be
    // ignored. (They will not be prevented from re-connecting after waiting
    // for the next window.)
    if (socket.readyState == socket.CLOSED) {
        return true;
    }

    var address = getRequestAddress(socket.upgradeReq);

    var updatedWindow = 0 | ((new Date) / windowSize);
    if (currentWindow !== updatedWindow) {
        currentWindow = updatedWindow;
        recentMessages = {};
    }

    if (address in recentMessages) {
        recentMessages[address]++;
    } else {
        recentMessages[address] = 1;
    }

    if (recentMessages[address] > windowLimit) {
        console.warn("Disconnecting flooding address: " + address);
        socket.terminate();

        for (var i = 0, l = connections.length; i < l; i++) {
            if (getRequestAddress(connections[i].upgradeReq) === address &&
                connections[i] != socket) {
                console.log("Disconnecting additional connection.");
                connections[i].terminate();
            }
        }

        return true;
    } else {
        return false;
    }
}

function getRequestAddress(request) {
    if (proxied) {
        // This assumes that the X-Forwarded-For header is generated by a
        // trusted proxy such as Heroku. If not, a malicious user could take
        // advantage of this logic and use it to to spoof their IP.
        var forwardedForAddresses = request.headers['x-forwarded-for'].split(',');
        return forwardedForAddresses[forwardedForAddresses.length - 1].trim();
    } else {
        // This is valid for direct deployments, without routing/load balancing.
        return request.connection.remoteAddress;
    }
}

socketServer.on('connection', function(socket) {
    if (floodedBy(socket)) return;

    connections.push(socket);
    var address = getRequestAddress(socket.upgradeReq);
    console.log('Client connected: ' + address);
    var titlesWithVotes = titles.map(function (title) {
        var isVoted = title.votesBy.some(function (testAddress) {
            return testAddress === address;
        });
        var newTitle = Object.clone(title, true);
        newTitle.voted = isVoted;
        return newTitle;
    });
    socket.send(JSON.stringify({operation: 'REFRESH', titles: titlesWithVotes, links: links}));

    socket.on('close', function () {
        console.log('Client disconnected: ' + address);
        connections.splice(connections.indexOf(socket), 1);
    });

    socket.on('message', function (data, flags) {
        if (floodedBy(socket)) return;

        if (flags.binary) {
            console.log("ignoring binary message from "  + address);
            return;
        }

        var packet = JSON.parse(data);
        if (packet.operation === 'VOTE') {
            var matches = titles.findAll({id: packet['id']});

            if (matches.length > 0) {
                var upvoted = matches[0];
                if (upvoted['votesBy'].any(address) == false) {
                    upvoted['votes'] = Number(upvoted['votes']) + 1;
                    upvoted['votesBy'].push(address);
                    sendToAll({operation: 'VOTE', votes: upvoted['votes'], id: upvoted['id']});
                    console.log('+1 for ' + upvoted['title'] + ' by ' + address);
                } else {
                    console.log('ignoring duplicate vote by ' + address + ' for ' + upvoted['title']);
                }
            } else {
                console.log('no matches for id: ' + packet['id']);
            }
        } else if (packet.operation === 'PING') {
            socket.send(JSON.stringify({operation: 'PONG'}));
        } else {
            console.log("Don't know what to do with " + packet['operation']);
        }
    });
});
<|endoftext|>


<commit_before>var split = require('split');
var through = require('through');
var combine = require('stream-combiner');
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;
var Duplex = require('readable-stream').Duplex;
var indexOf = require('indexof');
var json = typeof JSON !== 'undefined' ? JSON : require('jsonify');
var objectKeys = require('object-keys');
var generateId = require('./id.js');
var semver = require('semver');

module.exports = Seaport;
inherits(Seaport, EventEmitter);

function Seaport () {
    if (!(this instanceof Seaport)) return new Seaport;
    this.endpoints = [];
    this.streams = [];
    this.services = {};
    this.known = {};
}

Seaport.prototype.createStream = function () {
    var self = this;
    var sp = split(json.parse);
    var p = new Protocol;
    this.endpoints.push(p);

    p.on('end', function () {
        var ix = indexOf(self.endpoints, p);
        self.endpoints.splice(ix, 1);
    });
    p.on('register', function (id, meta) {
        self.known[id] = meta;
    });
    p.on('unregister', function (id) {
        delete self.known[id];
    });

    this.emit('endpoint', p);
    var stream = combine(sp, p, unsplit());
    stream.helo = function (addr) {
        p.push([ 'helo', addr ]);
    };
    registerServices(this.services, p);
    return stream;
};

Seaport.prototype.register = function (meta) {
    var self = this;
    var id = generateId();
    meta = fixMeta(meta);

    if (!meta.port) {
        meta.port = 10000 + Math.floor(Math.random() * 55000);
    }
    this.services[id] = meta;

    var mserv = {};
    mserv[id] = meta;
    for (var i = 0; i < this.endpoints.length; i++) {
        registerServices(mserv, this.endpoints[i]);
    }

    return meta.port;
};

Seaport.prototype.query = function (meta) {
    meta = fixMeta(meta);
    var mkeys = objectKeys(meta);
    var keys = objectKeys(this.known);

    var rows = [];
    for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        var row = this.known[key];

        for (var j = 0; j < mkeys.length; j++) {
            var mkey = mkeys[j];
            if (mkey === 'version') {
                if (!semver.satisfies(row.version, meta.version)) break;
            }
            else if (row[mkey] !== meta[mkey]) break;
        }
        if (j === mkeys.length) rows.push(row);
    }
    return rows;
};

inherits(Protocol, Duplex);
function Protocol () {
    Duplex.call(this, { objectMode: true });
}

Protocol.prototype._write = function (row, enc, next) {
    if (row[0] === 'register') {
        this.emit('register', row[1], row[2]);
    }
    next();
};

Protocol.prototype._read = function (size) {};

function unsplit () {
    return through(function (row) {
        this.queue(json.stringify(row) + '\n')
    });
}

function registerServices (services, p) {
    var keys = objectKeys(services);
    for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        p.push([ 'register', key, services[key] ]);
    }
}

function fixMeta (meta) {
    if (!meta) return {};
    if (typeof meta === 'string') {
        meta = { role: meta };
    }
    if (/@/.test(meta.role)) {
        meta.version = meta.role.split('@')[1];
        meta.role = meta.role.split('@')[0];
    }
    return meta;
}
<commit_msg>unregister services when the origin goes away<commit_after>var split = require('split');
var through = require('through');
var combine = require('stream-combiner');
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;
var Duplex = require('readable-stream').Duplex;
var indexOf = require('indexof');
var json = typeof JSON !== 'undefined' ? JSON : require('jsonify');
var objectKeys = require('object-keys');
var generateId = require('./id.js');
var semver = require('semver');

module.exports = Seaport;
inherits(Seaport, EventEmitter);

function Seaport () {
    if (!(this instanceof Seaport)) return new Seaport;
    this.endpoints = [];
    this.streams = [];
    this.services = {};
    this.known = {};
}

Seaport.prototype.createStream = function () {
    var self = this;
    var sp = split(json.parse);

    var p = new Protocol;
    this.endpoints.push(p);

    p.on('end', onend);
    p.on('register', function (id, meta) {
        self.known[id] = meta;
        self.emit('register', id, meta);
    });
    p.on('unregister', function (id) {
        var meta = self.known[id];
        delete self.known[id];
        self.emit('unregister', id, meta);
    });

    this.emit('endpoint', p);
    var stream = combine(sp, p, unsplit());
    stream.on('error', onend);

    stream.helo = function (addr) {
        p.push([ 'helo', addr ]);
    };
    registerServices(this.services, p);
    return stream;

    function onend () {
        var ix = indexOf(self.endpoints, p);
        self.endpoints.splice(ix, 1);
        var keys = objectKeys(p.known);
        for (var i = 0; i < keys.length; i++) {
            var key = keys[i];
            delete self.known[key];
            self.emit('unregister', self.known[key]);
        }
    }
};

Seaport.prototype.register = function (meta) {
    var self = this;
    var id = generateId();
    meta = fixMeta(meta);

    if (!meta.port) {
        meta.port = 10000 + Math.floor(Math.random() * 55000);
    }
    this.services[id] = meta;

    var mserv = {};
    mserv[id] = meta;
    for (var i = 0; i < this.endpoints.length; i++) {
        registerServices(mserv, this.endpoints[i]);
    }

    return meta.port;
};

Seaport.prototype.query = function (meta) {
    meta = fixMeta(meta);
    var mkeys = objectKeys(meta);
    var keys = objectKeys(this.known);

    var rows = [];
    for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        var row = this.known[key];

        for (var j = 0; j < mkeys.length; j++) {
            var mkey = mkeys[j];
            if (mkey === 'version') {
                if (!semver.satisfies(row.version, meta.version)) break;
            }
            else if (row[mkey] !== meta[mkey]) break;
        }
        if (j === mkeys.length) rows.push(row);
    }
    return rows;
};

inherits(Protocol, Duplex);
function Protocol () {
    Duplex.call(this, { objectMode: true });
    this.known = {};
}

Protocol.prototype._write = function (row, enc, next) {
    if (row[0] === 'register') {
        this.known[row[1]] = row[2];
        this.emit('register', row[1], row[2]);
    }
    next();
};

Protocol.prototype._read = function (size) {};

function unsplit () {
    return through(function (row) {
        this.queue(json.stringify(row) + '\n')
    });
}

function registerServices (services, p) {
    var keys = objectKeys(services);
    for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        p.push([ 'register', key, services[key] ]);
    }
}

function fixMeta (meta) {
    if (!meta) return {};
    if (typeof meta === 'string') {
        meta = { role: meta };
    }
    if (/@/.test(meta.role)) {
        meta.version = meta.role.split('@')[1];
        meta.role = meta.role.split('@')[0];
    }
    return meta;
}
<|endoftext|>


<commit_before>define(function(require, exports, module) {

"use strict";

var url = require("url");
var Types = require("./types").Types;
var Route = require("./route");
var flatten = require("./utils").flatten;

module.exports = function Section(name, description, types) {

    types = types || new Types();
    var routes = [];
    var sections = {};
    var self = this;

    this.middlewares = [];
    this.globals = [];
    this.errorHandlers = [];
    this.name = name;

    var methods = ["head", "get", "put", "post", "delete", "patch", "upgrade", "options"];
    methods.forEach(function(method) {
        routes[method] = [];

        self[method] = (function(method, route, options, handler) {
            // options is optional
            if (typeof options == "function" || Array.isArray(options)) {
                handler = options;
                options = {};
            }
            options.method = method;
            return self._route(route, options, handler);
        }).bind(self, method);
    });
    this.del = this["delete"];

    this.registerType = types.register.bind(types);

    this.all = function(method, route, options, handler) {
        var self = this;
        var args = arguments;

        methods.forEach(function(method) {
            self[method].apply(self, args);
        });
    };

    /**
     * Insert global middleware(s) for this section: The parent section is
     * always an empty container that delegates to it's sub-sections, and
     * executes all the middelwares for that section.
     *
     * This method allows us to inject a "global" middleware that is inserted
     * *after* decodeParams and *before* all other middelwares.
     */
    this.global = function( middleware ){
        this.globals.push.apply(this.globals, flatten(middleware));
    };

    this.use = function(middleware) {
        this.middlewares.push.apply(this.middlewares, flatten(middleware));
    };

    this.error = function(middleware) {
        this.errorHandlers.push.apply(this.errorHandlers, flatten(middleware));
    };

    this._route = function(route, options, handler) {
        route = new Route(route, options, handler, types);
        route.parent = this;
        routes[route.method].push(route);
        return this;
    };

    this.section = function(name, description) {
        var section = new Section(name, description, types);
        section.parent = this;
        if (!sections[name])
            sections[name] = [];

        sections[name].push(section);
        return section;
    };

    this.mount = function( name, section ){
        if (!sections[name])
            sections[name] = [];

        sections[name].push(section);
    };

    this._rootHandler = function(req, res) {
        this.handle(req, res, function(err) {
            if (err) {
                res.statusCode = err.code || err.statusCode || 500;
                res.end(err.message || err.toString);
            } else {
                res.statusCode = 404;
                res.end("cannot " + req.method);
            }
        });
    };

    this.handle = (function(path, req, res, next) {
        var that = this;

        if (arguments.length == 2) {
            return this._rootHandler.apply(this, arguments);
        }

        if (arguments.length === 3) {
            req = arguments[0];
            res = arguments[1];
            next = arguments[2];

            if (!req.parsedUrl)
                req.parsedUrl = url.parse(req.url, true);

            path = req.parsedUrl.pathname;
        }

        req.params = req.params || {};

        var method = req.method.toLowerCase();
        if (methods.indexOf(method) == -1)
            return next();

        var handler = this.match(req, path, method);
        if (!handler)
            return next();

        var middleware = [];
        var errorHandlers = [];
        while (handler) {
            middleware.unshift.apply(middleware, handler.middlewares || []);
            errorHandlers.unshift.apply(errorHandlers, handler.errorHandlers || []);
            handler = handler.parent;
        }

        if (this.globals.length)
            middleware.splice.apply( middleware, [1, 0].concat( this.globals ) );

        var i = 0;
        function processNext() {
            handler = middleware[i++];
            if (!handler || i > middleware.length)
                return next();

            handler(req, res, function(err) {
                if (err)
                    return that.handleError(errorHandlers, err, req, res, next);

                processNext();
            });
        }

        processNext();
    }).bind(this);

    this.handleError = function (errorHandlers, err, req, res, next) {
        var i = 0;
        function processNext(err) {
            if (!err)
                return next();

            var handler = errorHandlers[i++];
            if (!handler || i > errorHandlers.length)
                return next(err);

            handler(err, req, res, processNext);
        }

        processNext(err);
    };

    this.match = function(req, path, method) {
        var splitPath = path.split("/");
        if (!splitPath[0])
            splitPath.shift();
        if (splitPath.length) {
            var section = sections[splitPath[0]];
            if (section && section.length) {
                var subPath = "/" + splitPath.slice(1).join("/");
                for (var i = 0; i < section.length; i++) {
                    var handler = section[i].match(req, subPath, method);
                    if (handler)
                        return handler;
                }
            }
        }

        var methodRoutes = routes[method];
        for (var i = 0; i < methodRoutes.length; i++) {
            var route = methodRoutes[i];
            if (route.match(req, path))
                return route;
        }
    };

    this.describe = function() {
        // sections and routes
        var api = {};

        if (name)
            api.name = name;

        if (description)
            api.description = description;
        api.sections = [];
        for (var key in sections) {
            for (var i=0; i < sections[key].length; i++) {
                api.sections.push(sections[key][i].describe());
            }
        }
        if (!api.sections.length)
            delete api.sections;

        api.routes = [];
        for (var method in routes) {
            for (var i=0; i < routes[method].length; i++) {
                api.routes.push(routes[method][i].describe());
            }
        }
        if (!api.routes.length)
            delete api.routes;

        return api;
    };
};

});<commit_msg>tidied<commit_after>define(function(require, exports, module) {

"use strict";

var url = require("url");
var Types = require("./types").Types;
var Route = require("./route");
var flatten = require("./utils").flatten;

module.exports = function Section(name, description, types) {

    types = types || new Types();
    var routes = [];
    var sections = {};
    var self = this;

    this.middlewares = [];
    this.globals = [];
    this.errorHandlers = [];
    this.name = name;

    var methods = ["head", "get", "put", "post", "delete", "patch", "upgrade", "options"];
    methods.forEach(function(method) {
        routes[method] = [];

        self[method] = (function(method, route, options, handler) {
            // options is optional
            if (typeof options == "function" || Array.isArray(options)) {
                handler = options;
                options = {};
            }
            options.method = method;
            return self._route(route, options, handler);
        }).bind(self, method);
    });
    this.del = this["delete"];

    this.registerType = types.register.bind(types);

    this.all = function(method, route, options, handler) {
        var self = this;
        var args = arguments;

        methods.forEach(function(method) {
            self[method].apply(self, args);
        });
    };

    /**
     * Insert global middleware(s) for this section: The parent section is
     * always an empty container that delegates to it's sub-sections, and
     * executes all the middelwares for that section.
     *
     * This method allows us to inject a "global" middleware that is inserted
     * *after* decodeParams and *before* all other middelwares.
     */
    this.global = function( middleware ){
        this.globals.push.apply(this.globals, flatten(middleware));
    };

    this.use = function(middleware) {
        this.middlewares.push.apply(this.middlewares, flatten(middleware));
    };

    this.error = function(middleware) {
        this.errorHandlers.push.apply(this.errorHandlers, flatten(middleware));
    };

    this._route = function(route, options, handler) {
        route = new Route(route, options, handler, types);
        route.parent = this;
        routes[route.method].push(route);
        return this;
    };

    this.section = function(name, description) {
        var section = new Section(name, description, types);
        section.parent = this;
        if (!sections[name])
            sections[name] = [];

        sections[name].push(section);
        return section;
    };

    this.mount = function(name, section) {
        if (!sections[name])
            sections[name] = [];

        sections[name].push(section);
    };

    this._rootHandler = function(req, res) {
        this.handle(req, res, function(err) {
            if (err) {
                res.statusCode = err.code || err.statusCode || 500;
                res.end(err.message || err.toString);
            } else {
                res.statusCode = 404;
                res.end("cannot " + req.method);
            }
        });
    };

    this.handle = (function(path, req, res, next) {
        var that = this;

        if (arguments.length == 2) {
            return this._rootHandler.apply(this, arguments);
        }

        if (arguments.length === 3) {
            req = arguments[0];
            res = arguments[1];
            next = arguments[2];

            if (!req.parsedUrl)
                req.parsedUrl = url.parse(req.url, true);

            path = req.parsedUrl.pathname;
        }

        req.params = req.params || {};

        var method = req.method.toLowerCase();
        if (methods.indexOf(method) == -1)
            return next();

        var handler = this.match(req, path, method);
        if (!handler)
            return next();

        var middleware = [];
        var errorHandlers = [];
        while (handler) {
            middleware.unshift.apply(middleware, handler.middlewares || []);
            errorHandlers.unshift.apply(errorHandlers, handler.errorHandlers || []);
            handler = handler.parent;
        }

        if (this.globals.length)
            middleware.splice.apply( middleware, [1, 0].concat( this.globals ) );

        var i = 0;
        function processNext() {
            handler = middleware[i++];
            if (!handler || i > middleware.length)
                return next();

            handler(req, res, function(err) {
                if (err)
                    return that.handleError(errorHandlers, err, req, res, next);

                processNext();
            });
        }

        processNext();
    }).bind(this);

    this.handleError = function (errorHandlers, err, req, res, next) {
        var i = 0;
        function processNext(err) {
            if (!err)
                return next();

            var handler = errorHandlers[i++];
            if (!handler || i > errorHandlers.length)
                return next(err);

            handler(err, req, res, processNext);
        }

        processNext(err);
    };

    this.match = function(req, path, method) {
        var splitPath = path.split("/");
        if (!splitPath[0])
            splitPath.shift();
        if (splitPath.length) {
            var section = sections[splitPath[0]];
            if (section && section.length) {
                var subPath = "/" + splitPath.slice(1).join("/");
                for (var i = 0; i < section.length; i++) {
                    var handler = section[i].match(req, subPath, method);
                    if (handler)
                        return handler;
                }
            }
        }

        var methodRoutes = routes[method];
        for (var i = 0; i < methodRoutes.length; i++) {
            var route = methodRoutes[i];
            if (route.match(req, path))
                return route;
        }
    };

    this.describe = function() {
        // sections and routes
        var api = {};

        if (name)
            api.name = name;

        if (description)
            api.description = description;
        api.sections = [];
        for (var key in sections) {
            for (var i=0; i < sections[key].length; i++) {
                api.sections.push(sections[key][i].describe());
            }
        }
        if (!api.sections.length)
            delete api.sections;

        api.routes = [];
        for (var method in routes) {
            for (var i=0; i < routes[method].length; i++) {
                api.routes.push(routes[method][i].describe());
            }
        }
        if (!api.routes.length)
            delete api.routes;

        return api;
    };
};

});<|endoftext|>


<commit_before>// selleck.js —- javascript templating

var selleck = (function() {
  var cache = {};
  // A forgiving object[key]
  var getValue = function(obj, key) {
    var gv = function(obj, keys) {
      if (keys.length === 0) {
        return obj;
      }
      first = keys.shift();
      // empty return on missing key
      if (!obj.hasOwnProperty(first)) return '';
      return gv(obj[first], keys);
    };
    var keys = key.split('.');
    return gv(obj, keys);
  };
  var getArray = function(obj, key) {
    // XXX more checks on whether gotten value is array?
    return getValue(obj, key) || [];
  };
  // expand all partials
  var expand = function(template, partials) {
    template = template.replace(/{>([\w.]+)}/g, function(str, partialKey) {
      // get rid of extra whitespace at the end of the partial
      return getValue(partials, partialKey).replace(/\s+$/, '');
    });
    return template;
  };
  var compile = function(template, partials) {
    var expanded = template = expand(template, partials);
    // first check cache and return if cached
    if (cache[expanded]) {
      return cache[expanded];
    }
    // compile if not cached
    var replacement;
    var levels = {'for':0, 'if': 0};
    var nearby = function(offset, s) {
      return s.slice(offset-30, offset+10);
    };
    // replace simple key tags (like {foo})
    template = template.replace(/{([\w.]+)}/g, function(str, key) {
      return '\'+getValue(context,\''+key+'\')+\'';
    });
    // replace logic tags (like {for foo in bar} and {if foo})
    template = template.replace(/((?:\n|\r|\r\n)[ \t]*|){(\/?)(for|if|)( +|)(not +|)((\w+) +in +|)([\w.]+|)}(?:([ \t]*(?=\n|\r|\r\n))|)/g, function(str, newlineBefore, close, statement, statementSpace, ifNot, forIn, iterVar, key, newlineAfter, offset, s) {
      // if a logic tag is on a line by itself remove preceding newline and
      // whitespace
      if (newlineBefore && newlineAfter !== undefined) {
        newlineBefore = '';
      }
      // {for foo in bar}
      if (statement === 'for' && statementSpace && iterVar && key) {
        levels['for']++;
        replacement = newlineBefore+'\';var '+iterVar+'A=getArray(context,\''+key+'\');for(var '+iterVar+'I=0;'+iterVar+'I<'+iterVar+'A.length;'+iterVar+'I++){context[\''+iterVar+'\']='+iterVar+'A['+iterVar+'I];buffer+=\'';
      // {if foo} or {if not foo}
      } else if (statement === 'if' && statementSpace && key) {
        levels['if']++;
        replacement = newlineBefore+'\';if('+(ifNot?'!':'')+'getValue(context,\''+key+'\')){buffer+=\'';
      // {/for} or {/if}
      } else if (close && statement) {
        if (levels[statement] === 0) {
          console.warn('extra {/'+statement+'} ignored at "'+nearby(offset, s)+'"');
          replacement = '';
        } else {
          levels[statement]--;
          replacement = newlineBefore+'\';}buffer+=\'';
        }
      // not a tag, don't replace
      } else {
        replacement = str;
      }
      return replacement;
    });
    // close extra fors and ifs
    for (statement in levels) {
      for (var i=0; i<levels[statement]; i++) {
        console.warn('extra "'+statement+'" closed at end of template');
        template = template+'\';}buffer+=\'';
      }
    }
    // ready newlines for eval
    template = template.replace(/\n/g, '\\n');
    // wrap in function
    template = '(function(){return function(context){var buffer=\''+template+'\';return buffer}})()';
    // cache template
    cache[expanded] = eval(template);
    return cache[expanded];
  };
  var render = function(template, context, partials) {
    var compiled = compile(template, partials);
    return compiled(context);
  };
  return {
    compile: compile,
    render: render
  };
})();

// populate exports for commonJS
for (attribute in selleck) {
  if (selleck.hasOwnProperty(attribute)) {
    exports[attribute] = selleck[attribute];
  }
}
<commit_msg>Handle caching in render<commit_after>// selleck.js —- javascript templating

var selleck = (function() {
  var cache = {};
  // A forgiving object[key]
  var getValue = function(obj, key) {
    var gv = function(obj, keys) {
      if (keys.length === 0) {
        return obj;
      }
      first = keys.shift();
      // return empty string on missing key
      if (!obj.hasOwnProperty(first)) return '';
      return gv(obj[first], keys);
    };
    var keys = key.split('.');
    return gv(obj, keys);
  };
  var getArray = function(obj, key) {
    // XXX check on whether retrieved value is array?
    return getValue(obj, key) || [];
  };
  // expand all partials
  var expand = function(template, partials) {
    template = template.replace(/{>([\w.]+)}/g, function(str, partialKey) {
      // get rid of extra whitespace at the end of the partial
      return getValue(partials, partialKey).replace(/\s+$/, '');
    });
    return template;
  };
  var compile = function(template) {
    var replacement;
    var levels = {'for':0, 'if': 0};
    var nearby = function(offset, s) {
      return s.slice(offset-30, offset+10);
    };
    // replace simple key tags (like {foo})
    template = template.replace(/{([\w.]+)}/g, function(str, key) {
      return '\'+getValue(context,\''+key+'\')+\'';
    });
    // replace logic tags (like {for foo in bar} and {if foo})
    template = template.replace(/((?:\n|\r|\r\n)[ \t]*|){(\/?)(for|if|)( +|)(not +|)((\w+) +in +|)([\w.]+|)}(?:([ \t]*(?=\n|\r|\r\n))|)/g, function(str, newlineBefore, close, statement, statementSpace, ifNot, forIn, iterVar, key, newlineAfter, offset, s) {
      // if a logic tag is on a line by itself remove preceding newline and
      // whitespace
      if (newlineBefore && newlineAfter !== undefined) {
        newlineBefore = '';
      }
      // {for foo in bar}
      if (statement === 'for' && statementSpace && iterVar && key) {
        levels['for']++;
        replacement = newlineBefore+'\';var '+iterVar+'A=getArray(context,\''+key+'\');for(var '+iterVar+'I=0;'+iterVar+'I<'+iterVar+'A.length;'+iterVar+'I++){context[\''+iterVar+'\']='+iterVar+'A['+iterVar+'I];buffer+=\'';
      // {if foo} or {if not foo}
      } else if (statement === 'if' && statementSpace && key) {
        levels['if']++;
        replacement = newlineBefore+'\';if('+(ifNot?'!':'')+'getValue(context,\''+key+'\')){buffer+=\'';
      // {/for} or {/if}
      } else if (close && statement) {
        if (levels[statement] === 0) {
          console.warn('extra {/'+statement+'} ignored at "'+nearby(offset, s)+'"');
          replacement = '';
        } else {
          levels[statement]--;
          replacement = newlineBefore+'\';}buffer+=\'';
        }
      // not a tag, don't replace
      } else {
        replacement = str;
      }
      return replacement;
    });
    // close extra fors and ifs
    for (statement in levels) {
      for (var i=0; i<levels[statement]; i++) {
        console.warn('extra "'+statement+'" closed at end of template');
        template = template+'\';}buffer+=\'';
      }
    }
    // ready newlines for eval
    template = template.replace(/\n/g, '\\n');
    // wrap in function
    template = '(function(){return function(context){var buffer=\''+template+'\';return buffer}})()';
    return template;
  };
  var render = function(template, context, partials) {
    template = expand(template, partials);
    // use cached if it exists
    if (cache[template]) return cache[template](context);
    // compile and cache
    cache[template] = eval(compile(template));
    return cache[template](context);
  };
  return {
    compile: compile,
    render: render
  };
})();

// populate exports for commonJS
for (attribute in selleck) {
  if (selleck.hasOwnProperty(attribute)) {
    exports[attribute] = selleck[attribute];
  }
}
<|endoftext|>