<commit_before>const chalk      = require('chalk'),
      _          = require('lodash'),
      moment     = require('moment'),
      prettyjson = require('prettyjson');

const MAX_MESSAGE_LENGTH = 2000,
      MAX_MESSAGE_COUNT  = 10;

class AbstractCommand {
    static get name() {
        throw Error("Commands must override get name()");
    }

    static get description() {
        throw Error("Commands must override get description()");
    }

    static get adminCommand() {
        return false;
    }

    static get help() {
        return "None";
    }

    static get noHelp() {
        return false;
    }

    get server() {
        return this.message.channel.server === undefined ? 'pm' : this.message.channel.server;
    }

    get channel() {
        return this.message.channel === undefined ? 'pm' : this.message.channel;
    }

    get time() {
        return moment(this.message.timestamp).format("HH:mm:ss");
    }

    get author() {
        return this.message.author;
    }

    get botMention() {
        return this.rawContent.indexOf(this.prefix) === 0 || this.message.isMentioned(this.client.user) || this.pm;
    }

    get content() {
        let content = this.rawContent;

        let regex = new RegExp(`^(${this.client.user.mention()})|(\\${this.prefix})`);
        content   = _.trim(content.replace(regex, ''));

        return content;
    }

    get rawContent() {
        return this.message.content;
    }

    get pm() {
        return this.message.channel.server === undefined;
    }

    get prefix() {
        return !this.isPm() ? this.dbServer.prefix : undefined;
    }

    get mentions() {
        let users = [];

        for (let index in this.message.mentions) {
            if (this.message.mentions.hasOwnProperty(index)) {
                let mention = this.message.mentions[index];
                if (mention.id !== undefined && mention.username !== undefined) {
                    users.push({id: mention.id, name: mention.username, mention: mention.mention});
                }
            }
        }

        return users;
    }

    isPm() {
        return this.pm;
    }

    constructor(container, message, dbServer) {
        if (this.constructor === AbstractCommand) {
            throw new Error("Can't instantiate abstract class!");
        }

        this.container = container;
        this.message   = message;
        this.dbServer  = dbServer;

        this.logger   = container.get('logger');
        this.throttle = container.get('helper.throttle');
        this.client   = container.get('client');

        this.initialize();
    }

    initialize() {
    }

    isThrottled(key, length) {
        return this.throttle.isThrottled(key, this.message, length);
    }

    isAdmin() {
        return this.author.id === this.client.admin.id;
    }

    isOwner(pm) {
        pm = pm === undefined ? false : pm;

        if (this.isPm()) {
            return pm;
        }

        return this.author.id === this.server.owner.id;
    }

    reply(content, delay, deleteDelay, mention) {
        if (mention) {
            content = this.author.mention() + ', ' + content;
        }

        return this.sendMessage(this.message, content, delay, deleteDelay);
    }

    sendMessage(location, message, delay, deleteDelay) {
        if (message.length > MAX_MESSAGE_LENGTH * MAX_MESSAGE_COUNT) {
            return this.logger.error("Message is too long. Will spam API.");
        }

            delay = delay === undefined ? 0 : delay;
        if (delay) {
            return setTimeout(() => this.sendMessage(location, message, 0, deleteDelay), delay);
        }

        let messageRemainder = '';
        if (message.length > 2000) {
            let chunk = this.chunkString(message, 2000);
            message = chunk.shift();
            messageRemainder = message.join("");
        }

        return new Promise((resolve, reject) => {
            this.client.sendMessage(location, message, (error, message) => {
                if (error) {
                    return reject(error);
                }

                if (deleteDelay) {
                    this.client.deleteMessage(message, {wait: deleteDelay}, error => {
                        if (error) {
                            return reject(error);
                        }

                        if (!messageRemainder) {
                            resolve();
                        }
                    });

                    if (!messageRemainder) {
                        return;
                    }
                }

                if (messageRemainder) {
                    return this.sendMessage(location, messageRemainder, delay, deleteDelay).then(m => {
                        return resolve(Array,isArray(m) ? m.concat(message) : [message]);
                    }).catch(reject);
                }

                resolve(message);
            });
        });
    }

    chunkString(str, length) {
        return str.match(new RegExp('.{1,' + length + '}', 'g'));
    }

    handle() {
        throw Error("Commands must override handleMessage");
    }

    getMatches(content, regex, callback, noPrint) {
        let matches = regex.exec(content);

        if (matches === null) {
            return false;
        }

        let result = callback(matches);

        if (!noPrint && result !== false) {
            let array = {
                Command: {
                    time:       this.time,
                    author:     this.author.username,
                    server:     this.server !== 'pm' ? this.server.name : 'pm',
                    channel:    this.channel.name,
                    content:    this.content,
                    botMention: this.botMention,
                    pm:         this.pm,
                    regex:      regex ? regex.toString() : 'uh.....',
                    matches:    matches,
                    mentions:   this.mentions
                }
            };

            this.logger.info("\n" + prettyjson.render(array));
        }
    }

    hears(regex, callback, noPrint) {
        if (noPrint === undefined) {
            noPrint = false;
        }

        return this.getMatches(this.rawContent, regex, callback, noPrint);
    }

    responds(regex, callback, noPrint) {
        if (!this.botMention) {
            return;
        }

        if (noPrint === undefined) {
            noPrint = false;
        }

        return this.getMatches(this.content, regex, callback, noPrint)
    }
}

module.exports = AbstractCommand;
<commit_msg>Fixing remainder shit<commit_after>const chalk      = require('chalk'),
      _          = require('lodash'),
      moment     = require('moment'),
      prettyjson = require('prettyjson');

const MAX_MESSAGE_LENGTH = 2000,
      MAX_MESSAGE_COUNT  = 10;

class AbstractCommand {
    static get name() {
        throw Error("Commands must override get name()");
    }

    static get description() {
        throw Error("Commands must override get description()");
    }

    static get adminCommand() {
        return false;
    }

    static get help() {
        return "None";
    }

    static get noHelp() {
        return false;
    }

    get server() {
        return this.message.channel.server === undefined ? 'pm' : this.message.channel.server;
    }

    get channel() {
        return this.message.channel === undefined ? 'pm' : this.message.channel;
    }

    get time() {
        return moment(this.message.timestamp).format("HH:mm:ss");
    }

    get author() {
        return this.message.author;
    }

    get botMention() {
        return this.rawContent.indexOf(this.prefix) === 0 || this.message.isMentioned(this.client.user) || this.pm;
    }

    get content() {
        let content = this.rawContent;

        let regex = new RegExp(`^(${this.client.user.mention()})|(\\${this.prefix})`);
        content   = _.trim(content.replace(regex, ''));

        return content;
    }

    get rawContent() {
        return this.message.content;
    }

    get pm() {
        return this.message.channel.server === undefined;
    }

    get prefix() {
        return !this.isPm() ? this.dbServer.prefix : undefined;
    }

    get mentions() {
        let users = [];

        for (let index in this.message.mentions) {
            if (this.message.mentions.hasOwnProperty(index)) {
                let mention = this.message.mentions[index];
                if (mention.id !== undefined && mention.username !== undefined) {
                    users.push({id: mention.id, name: mention.username, mention: mention.mention});
                }
            }
        }

        return users;
    }

    isPm() {
        return this.pm;
    }

    constructor(container, message, dbServer) {
        if (this.constructor === AbstractCommand) {
            throw new Error("Can't instantiate abstract class!");
        }

        this.container = container;
        this.message   = message;
        this.dbServer  = dbServer;

        this.logger   = container.get('logger');
        this.throttle = container.get('helper.throttle');
        this.client   = container.get('client');

        this.initialize();
    }

    initialize() {
    }

    isThrottled(key, length) {
        return this.throttle.isThrottled(key, this.message, length);
    }

    isAdmin() {
        return this.author.id === this.client.admin.id;
    }

    isOwner(pm) {
        pm = pm === undefined ? false : pm;

        if (this.isPm()) {
            return pm;
        }

        return this.author.id === this.server.owner.id;
    }

    reply(content, delay, deleteDelay, mention) {
        if (mention) {
            content = this.author.mention() + ', ' + content;
        }

        return this.sendMessage(this.message, content, delay, deleteDelay);
    }

    sendMessage(location, message, delay, deleteDelay) {
        if (message.length > MAX_MESSAGE_LENGTH * MAX_MESSAGE_COUNT) {
            return this.logger.error("Message is too long. Will spam API.");
        }

            delay = delay === undefined ? 0 : delay;
        if (delay) {
            return setTimeout(() => this.sendMessage(location, message, 0, deleteDelay), delay);
        }

        let messageRemainder = '';
        if (message.length > 2000) {
            let chunk = this.chunkString(message, 2000);
            message = chunk.shift();
            messageRemainder = chunk.join("");
        }

        return new Promise((resolve, reject) => {
            this.client.sendMessage(location, message, (error, message) => {
                if (error) {
                    return reject(error);
                }

                if (deleteDelay) {
                    this.client.deleteMessage(message, {wait: deleteDelay}, error => {
                        if (error) {
                            return reject(error);
                        }

                        if (!messageRemainder) {
                            resolve();
                        }
                    });

                    if (!messageRemainder) {
                        return;
                    }
                }

                if (messageRemainder) {
                    return this.sendMessage(location, messageRemainder, delay, deleteDelay).then(m => {
                        return resolve(Array,isArray(m) ? m.concat(message) : [message]);
                    }).catch(reject);
                }

                resolve(message);
            });
        });
    }

    chunkString(str, length) {
        return str.match(new RegExp('.{1,' + length + '}', 'g'));
    }

    handle() {
        throw Error("Commands must override handleMessage");
    }

    getMatches(content, regex, callback, noPrint) {
        let matches = regex.exec(content);

        if (matches === null) {
            return false;
        }

        let result = callback(matches);

        if (!noPrint && result !== false) {
            let array = {
                Command: {
                    time:       this.time,
                    author:     this.author.username,
                    server:     this.server !== 'pm' ? this.server.name : 'pm',
                    channel:    this.channel.name,
                    content:    this.content,
                    botMention: this.botMention,
                    pm:         this.pm,
                    regex:      regex ? regex.toString() : 'uh.....',
                    matches:    matches,
                    mentions:   this.mentions
                }
            };

            this.logger.info("\n" + prettyjson.render(array));
        }
    }

    hears(regex, callback, noPrint) {
        if (noPrint === undefined) {
            noPrint = false;
        }

        return this.getMatches(this.rawContent, regex, callback, noPrint);
    }

    responds(regex, callback, noPrint) {
        if (!this.botMention) {
            return;
        }

        if (noPrint === undefined) {
            noPrint = false;
        }

        return this.getMatches(this.content, regex, callback, noPrint)
    }
}

module.exports = AbstractCommand;
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var levels = require('./levels');

function init ( ) {

  var settings = {
    units: 'mg/dL'
    , timeFormat: '12'
    , nightMode: false
    , showRawbg: 'never'
    , customTitle: 'Nightscout'
    , theme: 'default'
    , alarmUrgentHigh: true
    , alarmUrgentHighMins: [30, 60, 90, 120]
    , alarmHigh: true
    , alarmHighMins: [30, 60, 90, 120]
    , alarmLow: true
    , alarmLowMins: [15, 30, 45, 60]
    , alarmUrgentLow: true
    , alarmUrgentLowMins: [15, 30, 45]
    , alarmUrgentMins: [30, 60, 90, 120]
    , alarmWarnMins: [30, 60, 90, 120]
    , alarmTimeagoWarn: true
    , alarmTimeagoWarnMins: 15
    , alarmTimeagoUrgent: true
    , alarmTimeagoUrgentMins: 30
    , language: 'en'
    , showPlugins: ''
    , thresholds: {
      bgHigh: 260
      , bgTargetTop: 180
      , bgTargetBottom: 80
      , bgLow: 55
    }
  };

  var valueMappers = {
    alarmUrgentHighMins: mapNumberArray
    , alarmHighMins: mapNumberArray
    , alarmLowMins: mapNumberArray
    , alarmUrgentLowMins: mapNumberArray
    , alarmUrgentMins: mapNumberArray
    , alarmWarnMins: mapNumberArray
  };

  function mapNumberArray (value) {
    if (!value || _.isArray(value)) {
      return value;
    }

    if (isNaN(value)) {
      var rawValues = value && value.split(' ') || [];
      return _.map(rawValues, function (num) {
        return isNaN(num) ? null : Number(num);
      });
    } else {
      return value;
    }
  }

  //TODO: getting sent in status.json, shouldn't be
  settings.DEFAULT_FEATURES = ['delta', 'direction', 'upbat', 'errorcodes'];

  var wasSet = [];

  function isSimple (value) {
    return _.isArray(value) || (typeof value !== 'function' && typeof value !== 'object');
  }

  function nameFromKey (key, nameType) {
    return nameType === 'env' ? _.snakeCase(key).toUpperCase() : key;
  }

  function eachSettingAs (nameType) {

    function mapKeys (accessor, keys) {
      _.forIn(keys, function each (value, key) {
        if (isSimple(value)) {
          var newValue = accessor(nameFromKey(key, nameType));
          if (newValue !== undefined) {
            var mapper = valueMappers[key];
            wasSet.push(key);
            keys[key] = mapper ? mapper(newValue) : newValue;
          }
        }
      });
    }

    return function allKeys (accessor) {
      mapKeys(accessor, settings);
      mapKeys(accessor, settings.thresholds);
      enableAndDisableFeatures(accessor, nameType);
    };
  }

  function enableAndDisableFeatures (accessor, nameType) {

    function getAndPrepare (key) {
      var raw = accessor(nameFromKey(key, nameType)) || '';
      var cleaned = decodeURIComponent(raw).toLowerCase();
      return cleaned ? cleaned.split(' ') : [];
    }

    function enableIf (feature, condition) {
      if (condition) {
        enable.push(feature);
      }
    }

    function anyEnabled (features) {
      return _.findIndex(features, function (feature) {
        return enable.indexOf(feature) > -1;
      }) > -1;
    }

    function prepareAlarmTypes ( ) {
      var alarmTypes = _.filter(getAndPrepare('alarmTypes'), function onlyKnownTypes (type) {
        return type === 'predict' || type === 'simple';
      });

      if (alarmTypes.length === 0) {
        var thresholdWasSet = _.findIndex(wasSet, function (name) {
          return name.indexOf('bg') === 0;
        }) > -1;
        alarmTypes = thresholdWasSet ? ['simple'] : ['predict'];
      }

      return alarmTypes;
    }

    var enable = getAndPrepare('enable');
    var disable = getAndPrepare('disable');

    settings.alarmTypes = prepareAlarmTypes();

    //don't require pushover to be enabled to preserve backwards compatibility if there are extendedSettings for it
    enableIf('pushover', accessor(nameFromKey('pushoverApiToken', nameType)));

    enableIf('treatmentnotify', anyEnabled(['careportal', 'pushover', 'maker']));

    _.each(settings.DEFAULT_FEATURES, function eachDefault (feature) {
      enableIf(feature, enable.indexOf(feature) < 0);
    });

    //TODO: maybe get rid of ALARM_TYPES and only use enable?
    enableIf('simplealarms', settings.alarmTypes.indexOf('simple') > -1);
    enableIf('ar2', settings.alarmTypes.indexOf('predict') > -1);

    if (disable.length > 0) {
      console.info('disabling', disable);
    }

    //all enabled feature, without any that have been disabled
    settings.enable = _.difference(enable, disable);

    var thresholds = settings.thresholds;

    thresholds.bgHigh = Number(thresholds.bgHigh);
    thresholds.bgTargetTop = Number(thresholds.bgTargetTop);
    thresholds.bgTargetBottom = Number(thresholds.bgTargetBottom);
    thresholds.bgLow = Number(thresholds.bgLow);

    verifyThresholds();
    adjustShownPlugins();
  }

  function verifyThresholds() {
    var thresholds = settings.thresholds;

    if (thresholds.bgTargetBottom >= thresholds.bgTargetTop) {
      console.warn('BG_TARGET_BOTTOM(' + thresholds.bgTargetBottom + ') was >= BG_TARGET_TOP(' + thresholds.bgTargetTop + ')');
      thresholds.bgTargetBottom = thresholds.bgTargetTop - 1;
      console.warn('BG_TARGET_BOTTOM is now ' + thresholds.bgTargetBottom);
    }
    if (thresholds.bgTargetTop <= thresholds.bgTargetBottom) {
      console.warn('BG_TARGET_TOP(' + thresholds.bgTargetTop + ') was <= BG_TARGET_BOTTOM(' + thresholds.bgTargetBottom + ')');
      thresholds.bgTargetTop = thresholds.bgTargetBottom + 1;
      console.warn('BG_TARGET_TOP is now ' + thresholds.bgTargetTop);
    }
    if (thresholds.bgLow >= thresholds.bgTargetBottom) {
      console.warn('BG_LOW(' + thresholds.bgLow + ') was >= BG_TARGET_BOTTOM(' + thresholds.bgTargetBottom + ')');
      thresholds.bgLow = thresholds.bgTargetBottom - 1;
      console.warn('BG_LOW is now ' + thresholds.bgLow);
    }
    if (thresholds.bgHigh <= thresholds.bgTargetTop) {
      console.warn('BG_HIGH(' + thresholds.bgHigh + ') was <= BG_TARGET_TOP(' + thresholds.bgTargetTop + ')');
      thresholds.bgHigh = thresholds.bgTargetTop + 1;
      console.warn('BG_HIGH is now ' + thresholds.bgHigh);
    }
  }

  function adjustShownPlugins ( ) {
    //TODO: figure out something for some plugins to have them shown by default
    if (settings.showPlugins !== '') {
      settings.showPlugins += ' delta direction upbat';
      if (settings.showRawbg === 'always' || settings.showRawbg === 'noise') {
        settings.showPlugins += ' rawbg';
      }
    }
  }

  function isEnabled (feature) {
    var enabled = false;

    if (settings.enable && typeof feature === 'object' && feature.length !== undefined) {
      enabled = _.find(feature, function eachFeature (f) {
        return settings.enable.indexOf(f) > -1;
      }) !== undefined;
    } else {
      enabled = settings.enable && settings.enable.indexOf(feature) > -1;
    }

    return enabled;
  }

  function isAlarmEventEnabled (notify) {
    var enabled = false;

    if (!notify.eventName) {
      enabled = true;
    } else if (notify.eventName === 'high' && notify.level === levels.URGENT && settings.alarmUrgentHigh) {
      enabled = true;
    } else if (notify.eventName === 'high' && settings.alarmHigh) {
      enabled = true;
    } else if (notify.eventName === 'low' && notify.level === levels.URGENT && settings.alarmUrgentLow) {
      enabled = true;
    } else if (notify.eventName === 'low' && settings.alarmLow) {
      enabled = true;
    }

    return enabled;
  }

  function snoozeMinsForAlarmEvent (notify) {
    var snoozeTime = [30];

    if (notify.eventName === 'high' && notify.level === levels.URGENT && settings.alarmUrgentHigh) {
     snoozeTime = settings.alarmUrgentHighMins || snoozeTime;
    } else if (notify.eventName === 'high' && settings.alarmHigh) {
      snoozeTime = settings.alarmHighMins || snoozeTime;
    } else if (notify.eventName === 'low' && notify.level === levels.URGENT && settings.alarmUrgentLow) {
      snoozeTime = settings.alarmUrgentLowMins || snoozeTime;
    } else if (notify.eventName === 'low' && settings.alarmLow) {
      snoozeTime = settings.alarmLowMins || snoozeTime;
    }

    return snoozeTime;
  }

  function snoozeFirstMinsForAlarmEvent (notify) {
    return _.first(snoozeMinsForAlarmEvent(notify));
  }

  settings.eachSetting = eachSettingAs();
  settings.eachSettingAsEnv = eachSettingAs('env');
  settings.isEnabled = isEnabled;
  settings.isAlarmEventEnabled = isAlarmEventEnabled;
  settings.snoozeMinsForAlarmEvent = snoozeMinsForAlarmEvent;
  settings.snoozeFirstMinsForAlarmEvent = snoozeFirstMinsForAlarmEvent;

  return settings;

}

module.exports = init;<commit_msg>also look for alarmUrgentMins and alarmWarnMins if not high/low<commit_after>'use strict';

var _ = require('lodash');
var levels = require('./levels');

function init ( ) {

  var settings = {
    units: 'mg/dL'
    , timeFormat: '12'
    , nightMode: false
    , showRawbg: 'never'
    , customTitle: 'Nightscout'
    , theme: 'default'
    , alarmUrgentHigh: true
    , alarmUrgentHighMins: [30, 60, 90, 120]
    , alarmHigh: true
    , alarmHighMins: [30, 60, 90, 120]
    , alarmLow: true
    , alarmLowMins: [15, 30, 45, 60]
    , alarmUrgentLow: true
    , alarmUrgentLowMins: [15, 30, 45]
    , alarmUrgentMins: [30, 60, 90, 120]
    , alarmWarnMins: [30, 60, 90, 120]
    , alarmTimeagoWarn: true
    , alarmTimeagoWarnMins: 15
    , alarmTimeagoUrgent: true
    , alarmTimeagoUrgentMins: 30
    , language: 'en'
    , showPlugins: ''
    , thresholds: {
      bgHigh: 260
      , bgTargetTop: 180
      , bgTargetBottom: 80
      , bgLow: 55
    }
  };

  var valueMappers = {
    alarmUrgentHighMins: mapNumberArray
    , alarmHighMins: mapNumberArray
    , alarmLowMins: mapNumberArray
    , alarmUrgentLowMins: mapNumberArray
    , alarmUrgentMins: mapNumberArray
    , alarmWarnMins: mapNumberArray
  };

  function mapNumberArray (value) {
    if (!value || _.isArray(value)) {
      return value;
    }

    if (isNaN(value)) {
      var rawValues = value && value.split(' ') || [];
      return _.map(rawValues, function (num) {
        return isNaN(num) ? null : Number(num);
      });
    } else {
      return value;
    }
  }

  //TODO: getting sent in status.json, shouldn't be
  settings.DEFAULT_FEATURES = ['delta', 'direction', 'upbat', 'errorcodes'];

  var wasSet = [];

  function isSimple (value) {
    return _.isArray(value) || (typeof value !== 'function' && typeof value !== 'object');
  }

  function nameFromKey (key, nameType) {
    return nameType === 'env' ? _.snakeCase(key).toUpperCase() : key;
  }

  function eachSettingAs (nameType) {

    function mapKeys (accessor, keys) {
      _.forIn(keys, function each (value, key) {
        if (isSimple(value)) {
          var newValue = accessor(nameFromKey(key, nameType));
          if (newValue !== undefined) {
            var mapper = valueMappers[key];
            wasSet.push(key);
            keys[key] = mapper ? mapper(newValue) : newValue;
          }
        }
      });
    }

    return function allKeys (accessor) {
      mapKeys(accessor, settings);
      mapKeys(accessor, settings.thresholds);
      enableAndDisableFeatures(accessor, nameType);
    };
  }

  function enableAndDisableFeatures (accessor, nameType) {

    function getAndPrepare (key) {
      var raw = accessor(nameFromKey(key, nameType)) || '';
      var cleaned = decodeURIComponent(raw).toLowerCase();
      return cleaned ? cleaned.split(' ') : [];
    }

    function enableIf (feature, condition) {
      if (condition) {
        enable.push(feature);
      }
    }

    function anyEnabled (features) {
      return _.findIndex(features, function (feature) {
        return enable.indexOf(feature) > -1;
      }) > -1;
    }

    function prepareAlarmTypes ( ) {
      var alarmTypes = _.filter(getAndPrepare('alarmTypes'), function onlyKnownTypes (type) {
        return type === 'predict' || type === 'simple';
      });

      if (alarmTypes.length === 0) {
        var thresholdWasSet = _.findIndex(wasSet, function (name) {
          return name.indexOf('bg') === 0;
        }) > -1;
        alarmTypes = thresholdWasSet ? ['simple'] : ['predict'];
      }

      return alarmTypes;
    }

    var enable = getAndPrepare('enable');
    var disable = getAndPrepare('disable');

    settings.alarmTypes = prepareAlarmTypes();

    //don't require pushover to be enabled to preserve backwards compatibility if there are extendedSettings for it
    enableIf('pushover', accessor(nameFromKey('pushoverApiToken', nameType)));

    enableIf('treatmentnotify', anyEnabled(['careportal', 'pushover', 'maker']));

    _.each(settings.DEFAULT_FEATURES, function eachDefault (feature) {
      enableIf(feature, enable.indexOf(feature) < 0);
    });

    //TODO: maybe get rid of ALARM_TYPES and only use enable?
    enableIf('simplealarms', settings.alarmTypes.indexOf('simple') > -1);
    enableIf('ar2', settings.alarmTypes.indexOf('predict') > -1);

    if (disable.length > 0) {
      console.info('disabling', disable);
    }

    //all enabled feature, without any that have been disabled
    settings.enable = _.difference(enable, disable);

    var thresholds = settings.thresholds;

    thresholds.bgHigh = Number(thresholds.bgHigh);
    thresholds.bgTargetTop = Number(thresholds.bgTargetTop);
    thresholds.bgTargetBottom = Number(thresholds.bgTargetBottom);
    thresholds.bgLow = Number(thresholds.bgLow);

    verifyThresholds();
    adjustShownPlugins();
  }

  function verifyThresholds() {
    var thresholds = settings.thresholds;

    if (thresholds.bgTargetBottom >= thresholds.bgTargetTop) {
      console.warn('BG_TARGET_BOTTOM(' + thresholds.bgTargetBottom + ') was >= BG_TARGET_TOP(' + thresholds.bgTargetTop + ')');
      thresholds.bgTargetBottom = thresholds.bgTargetTop - 1;
      console.warn('BG_TARGET_BOTTOM is now ' + thresholds.bgTargetBottom);
    }
    if (thresholds.bgTargetTop <= thresholds.bgTargetBottom) {
      console.warn('BG_TARGET_TOP(' + thresholds.bgTargetTop + ') was <= BG_TARGET_BOTTOM(' + thresholds.bgTargetBottom + ')');
      thresholds.bgTargetTop = thresholds.bgTargetBottom + 1;
      console.warn('BG_TARGET_TOP is now ' + thresholds.bgTargetTop);
    }
    if (thresholds.bgLow >= thresholds.bgTargetBottom) {
      console.warn('BG_LOW(' + thresholds.bgLow + ') was >= BG_TARGET_BOTTOM(' + thresholds.bgTargetBottom + ')');
      thresholds.bgLow = thresholds.bgTargetBottom - 1;
      console.warn('BG_LOW is now ' + thresholds.bgLow);
    }
    if (thresholds.bgHigh <= thresholds.bgTargetTop) {
      console.warn('BG_HIGH(' + thresholds.bgHigh + ') was <= BG_TARGET_TOP(' + thresholds.bgTargetTop + ')');
      thresholds.bgHigh = thresholds.bgTargetTop + 1;
      console.warn('BG_HIGH is now ' + thresholds.bgHigh);
    }
  }

  function adjustShownPlugins ( ) {
    //TODO: figure out something for some plugins to have them shown by default
    if (settings.showPlugins !== '') {
      settings.showPlugins += ' delta direction upbat';
      if (settings.showRawbg === 'always' || settings.showRawbg === 'noise') {
        settings.showPlugins += ' rawbg';
      }
    }
  }

  function isEnabled (feature) {
    var enabled = false;

    if (settings.enable && typeof feature === 'object' && feature.length !== undefined) {
      enabled = _.find(feature, function eachFeature (f) {
        return settings.enable.indexOf(f) > -1;
      }) !== undefined;
    } else {
      enabled = settings.enable && settings.enable.indexOf(feature) > -1;
    }

    return enabled;
  }

  function isAlarmEventEnabled (notify) {
    var enabled = false;

    if (!notify.eventName) {
      enabled = true;
    } else if (notify.eventName === 'high' && notify.level === levels.URGENT && settings.alarmUrgentHigh) {
      enabled = true;
    } else if (notify.eventName === 'high' && settings.alarmHigh) {
      enabled = true;
    } else if (notify.eventName === 'low' && notify.level === levels.URGENT && settings.alarmUrgentLow) {
      enabled = true;
    } else if (notify.eventName === 'low' && settings.alarmLow) {
      enabled = true;
    }

    return enabled;
  }

  function snoozeMinsForAlarmEvent (notify) {
    var snoozeTime = [30];

    if (notify.eventName === 'high' && notify.level === levels.URGENT && settings.alarmUrgentHigh) {
     snoozeTime = settings.alarmUrgentHighMins || snoozeTime;
    } else if (notify.eventName === 'high' && settings.alarmHigh) {
      snoozeTime = settings.alarmHighMins || snoozeTime;
    } else if (notify.eventName === 'low' && notify.level === levels.URGENT && settings.alarmUrgentLow) {
      snoozeTime = settings.alarmUrgentLowMins || snoozeTime;
    } else if (notify.eventName === 'low' && settings.alarmLow) {
      snoozeTime = settings.alarmLowMins || snoozeTime;
    } else if (notify.level === levels.URGENT) {
      snoozeTime = settings.alarmUrgentMins || snoozeTime;
    } else if (notify.level === levels.WARN) {
      snoozeTime = settings.alarmWarnMins || snoozeTime;
    }

    return snoozeTime;
  }

  function snoozeFirstMinsForAlarmEvent (notify) {
    return _.first(snoozeMinsForAlarmEvent(notify));
  }

  settings.eachSetting = eachSettingAs();
  settings.eachSettingAsEnv = eachSettingAs('env');
  settings.isEnabled = isEnabled;
  settings.isAlarmEventEnabled = isAlarmEventEnabled;
  settings.snoozeMinsForAlarmEvent = snoozeMinsForAlarmEvent;
  settings.snoozeFirstMinsForAlarmEvent = snoozeFirstMinsForAlarmEvent;

  return settings;

}

module.exports = init;<|endoftext|>


<commit_before>const scramjet = require('./');

const SPLIT_LINE = /\r\n?|\n/g;

/**
 * A stream of string objects for further transformation on top of DataStream.
 *
 * @extends DataStream
 */
class StringStream extends scramjet.DataStream {

    /**
     * Constructs the stream with the given encoding
     *
     * @param  {String} encoding the encoding to use
     * @return {StringStream}  the created data stream
     *
     * @example {@link ../samples/string-stream-constructor.js}
     */
    constructor(encoding, options) {

        super(typeof encoding === "string" ? options : encoding);
        this.buffer = "";
        this.constructed = new Error().stack;
        this.encoding = typeof encoding === "string" ? encoding : "utf-8";
    }

    /**
     * @callback ShiftCallback
     * @param {String} shifted Pooped chars
     */

    /**
     * Shifts given length of chars from the original stream
     *
     * Works the same way as {@see DataStream.shift}, but in this case extracts
     * the given number of characters.
     *
     * @param {Number} bytes The number of characters to shift.
     * @param {ShiftCallback} func Function that receives a string of shifted
     *                                 chars.
     * @return {StringStream}  substream.
     *
     * @example {@link ../samples/string-stream-shift.js}
     */
    shift(bytes, func) {
        const ret = "";
        const str = this.tap()._selfInstance({
            referrer: this
        });
        let offs = 0;

        const chunkHandler = (chunk) => {
            const length = Math.min(bytes - offs, chunk.length);
            chunk.substr(0, length);
            offs += length;
            if (length >= bytes) {
                unHook()
                    .then(
                        () => {
                            str.write(chunk.slice(length));
                            this.pipe(str);
                        }
                    );
            }
        };

        const endHandler = (...args) => {
            if (ret.length < bytes) {
                unHook();
            }
            str.end(...args);
        };

        const errorHandler = str.emit.bind(str, "error");

        const unHook = () => {  // jshint ignore:line
            this.removeListener("data", chunkHandler);
            this.removeListener("end", endHandler);
            this.removeListener("error", errorHandler);
            return Promise.resolve(ret)
                .then(func);
        };

        this.on("data", chunkHandler);
        this.on("end", endHandler);
        this.on("error", errorHandler);

        return str;
    }

    /**
     * A handly split by line regex to quickly get a line-by-line stream
     */
    static get SPLIT_LINE() {
        return SPLIT_LINE;
    }

    /**
     * Splits the string stream by the specified regexp or string
     *
     * @todo implement splitting by buffer or string
     * @param  {RegExp|String} splitter What to split by
     * @return {StringStream}  the re-splitted string stream.
     *
     * @example {@link ../samples/string-stream-split.js}
     */
    split(splitter) {
        if (splitter instanceof RegExp || typeof splitter === "string") {
            return this.tap().pipe(new StringStream({
                transform(chunk, encoding, callback) {
                    this.buffer += chunk.toString(this.encoding);
                    const newChunks = this.buffer.split(splitter);
                    while(newChunks.length > 1) {
                        this.push(newChunks.shift());
                    }
                    this.buffer = newChunks[0];
                    callback();
                },
                flush(callback) {
                    this.push(this.buffer);
                    this.buffer = "";
                    callback();
                },
                referrer: this
            }));
        } else if (splitter instanceof Function) {
            return this.tap().pipe(new StringStream({
                transform: splitter,
                referrer: this
            }));
        }
    }

    /**
     * Appends given argument to all the items.
     *
     * @param {Function|String} arg the argument to append. If function passed then it will be called and resolved
     *                              and the resolution will be appended.
     * @return {StringStream}  the resulting stream
     *
     * @example {@link ../samples/string-stream-append.js}
     */
    append(arg) {
        return typeof arg === "function" ? this.map(item => Promise.resolve(item).then(arg).then((result) => item + result)) : this.map(item => item + arg);
    }

    /**
     * Prepends given argument to all the items.
     *
     * @param {Function|String} arg the argument to prepend. If function passed then it will be called and resolved
     *                              and the resolution will be prepended.
     * @return {StringStream}  the resulting stream
     *
     * @example {@link ../samples/string-stream-prepend.js}
     */
    prepend(arg) {
        return typeof arg === "function" ? this.map(item => Promise.resolve(item).then(arg).then((result) => result + item)) : this.map(item => arg + item);
    }

    /**
     * Finds matches in the string stream and streams the match results
     *
     * @todo implement splitting by buffer or string
     * @param  {RegExp} splitter A function that will be called for every
     *                             stream chunk.
     * @return {StringStream}  string stream of matches.
     *
     * @example {@link ../samples/string-stream-match.js}
     */
    match(matcher) {
        if (matcher instanceof RegExp) {
            const replaceRegex = (matcher.source.search(/\((?!\?)/g) > -1) ?
                new RegExp("[\\s\\S]*?" + matcher.source, (matcher.ignoreCase ? 'i' : '') + (matcher.multiline ? 'm' : '') + (matcher.unicode ? 'u' : '') + 'g') :
                new RegExp("[\\s\\S]*?(" + matcher.source + ")", (matcher.ignoreCase ? 'i' : '') + (matcher.multiline ? 'm' : '') + (matcher.unicode ? 'u' : '') + 'g')
                ;

            return this.tap().pipe(new scramjet.DataStream({
                transform(chunk, encoding, callback) {
                    this.buffer = (this.buffer || "") + chunk.toString("utf-8");
                    this.buffer = this.buffer.replace(replaceRegex, (...match) => {
                        this.push(match.slice(1, match.length - 2).join(""));
                        return '';
                    });

                    callback();
                },
                referrer: this
            }));

        }
        throw new Error("Mathcher must be a RegExp!");
    }

    /**
     * Transforms the StringStream to BufferStream
     *
     * Creates a buffer stream from the given string stream. Still it returns a
     * DataStream derivative and isn't the typical node.js stream so you can do
     * all your transforms when you like.
     *
     * @return {StringStream}  The converted stream.
     *
     * @example {@link ../samples/string-stream-tobufferstream.js}
     */
    toBufferStream() {
        return this.tap().map(
            (str) => Buffer.from(str, this.encoding),
            new scramjet.BufferStream({
                referrer: this
            })
        );
    }

    toStringStream(encoding) {
        if (encoding)
            return this.tap().pipe(this._selfInstance(encoding, {
                referrer: this
            }));
        else
            return this;
    }

    /**
     * @callback ParseCallback
     * @param {String} chunk the transformed chunk
     * @return {Promise}  the promise should be resolved with the parsed object
     */

     /**
      * Parses every string to object
      *
      * The method MUST parse EVERY string into a single object, so the string
      * stream here should already be splitted.
      *
      * @param  {ParseCallback} parser The transform function
      * @return {DataStream}  The parsed objects stream.
      *
      * @example {@link ../samples/string-stream-parse.js}
      */
    parse(parser) {
        return this.tap().map(parser, scramjet.DataStream);
    }

    _transform(chunk, encoding, callback) {
        this.push(chunk.toString(this.encoding));
        return callback();
    }

    /**
     * Creates a StringStream and writes a specific string.
     *
     * @param  {String} str      the string to push the your stream
     * @param  {String} encoding optional encoding
     * @return {StringStream}          new StringStream.
     */
    static fromString(str, encoding) {
        const st =  new StringStream(encoding || 'utf-8');
        st.end(str);
        return st;
    }

}

StringStream.prototype.pop = StringStream.prototype.shift;

module.exports = StringStream;
<commit_msg>fix default string encoding<commit_after>const scramjet = require('./');

const SPLIT_LINE = /\r\n?|\n/g;

/**
 * A stream of string objects for further transformation on top of DataStream.
 *
 * @extends DataStream
 */
class StringStream extends scramjet.DataStream {

    /**
     * Constructs the stream with the given encoding
     *
     * @param  {String} encoding the encoding to use
     * @return {StringStream}  the created data stream
     *
     * @example {@link ../samples/string-stream-constructor.js}
     */
    constructor(encoding, options) {

        super(typeof encoding === "string" ? options : encoding);
        this.buffer = "";
        this.constructed = new Error().stack;
        this.encoding = typeof encoding === "string" ? encoding : "utf8";
    }

    /**
     * @callback ShiftCallback
     * @param {String} shifted Pooped chars
     */

    /**
     * Shifts given length of chars from the original stream
     *
     * Works the same way as {@see DataStream.shift}, but in this case extracts
     * the given number of characters.
     *
     * @param {Number} bytes The number of characters to shift.
     * @param {ShiftCallback} func Function that receives a string of shifted
     *                                 chars.
     * @return {StringStream}  substream.
     *
     * @example {@link ../samples/string-stream-shift.js}
     */
    shift(bytes, func) {
        const ret = "";
        const str = this.tap()._selfInstance({
            referrer: this
        });
        let offs = 0;

        const chunkHandler = (chunk) => {
            const length = Math.min(bytes - offs, chunk.length);
            chunk.substr(0, length);
            offs += length;
            if (length >= bytes) {
                unHook()
                    .then(
                        () => {
                            str.write(chunk.slice(length));
                            this.pipe(str);
                        }
                    );
            }
        };

        const endHandler = (...args) => {
            if (ret.length < bytes) {
                unHook();
            }
            str.end(...args);
        };

        const errorHandler = str.emit.bind(str, "error");

        const unHook = () => {  // jshint ignore:line
            this.removeListener("data", chunkHandler);
            this.removeListener("end", endHandler);
            this.removeListener("error", errorHandler);
            return Promise.resolve(ret)
                .then(func);
        };

        this.on("data", chunkHandler);
        this.on("end", endHandler);
        this.on("error", errorHandler);

        return str;
    }

    /**
     * A handly split by line regex to quickly get a line-by-line stream
     */
    static get SPLIT_LINE() {
        return SPLIT_LINE;
    }

    /**
     * Splits the string stream by the specified regexp or string
     *
     * @todo implement splitting by buffer or string
     * @param  {RegExp|String} splitter What to split by
     * @return {StringStream}  the re-splitted string stream.
     *
     * @example {@link ../samples/string-stream-split.js}
     */
    split(splitter) {
        if (splitter instanceof RegExp || typeof splitter === "string") {
            return this.tap().pipe(new StringStream({
                transform(chunk, encoding, callback) {
                    this.buffer += chunk.toString(this.encoding);
                    const newChunks = this.buffer.split(splitter);
                    while(newChunks.length > 1) {
                        this.push(newChunks.shift());
                    }
                    this.buffer = newChunks[0];
                    callback();
                },
                flush(callback) {
                    this.push(this.buffer);
                    this.buffer = "";
                    callback();
                },
                referrer: this
            }));
        } else if (splitter instanceof Function) {
            return this.tap().pipe(new StringStream({
                transform: splitter,
                referrer: this
            }));
        }
    }

    /**
     * Appends given argument to all the items.
     *
     * @param {Function|String} arg the argument to append. If function passed then it will be called and resolved
     *                              and the resolution will be appended.
     * @return {StringStream}  the resulting stream
     *
     * @example {@link ../samples/string-stream-append.js}
     */
    append(arg) {
        return typeof arg === "function" ? this.map(item => Promise.resolve(item).then(arg).then((result) => item + result)) : this.map(item => item + arg);
    }

    /**
     * Prepends given argument to all the items.
     *
     * @param {Function|String} arg the argument to prepend. If function passed then it will be called and resolved
     *                              and the resolution will be prepended.
     * @return {StringStream}  the resulting stream
     *
     * @example {@link ../samples/string-stream-prepend.js}
     */
    prepend(arg) {
        return typeof arg === "function" ? this.map(item => Promise.resolve(item).then(arg).then((result) => result + item)) : this.map(item => arg + item);
    }

    /**
     * Finds matches in the string stream and streams the match results
     *
     * @todo implement splitting by buffer or string
     * @param  {RegExp} splitter A function that will be called for every
     *                             stream chunk.
     * @return {StringStream}  string stream of matches.
     *
     * @example {@link ../samples/string-stream-match.js}
     */
    match(matcher) {
        if (matcher instanceof RegExp) {
            const replaceRegex = (matcher.source.search(/\((?!\?)/g) > -1) ?
                new RegExp("[\\s\\S]*?" + matcher.source, (matcher.ignoreCase ? 'i' : '') + (matcher.multiline ? 'm' : '') + (matcher.unicode ? 'u' : '') + 'g') :
                new RegExp("[\\s\\S]*?(" + matcher.source + ")", (matcher.ignoreCase ? 'i' : '') + (matcher.multiline ? 'm' : '') + (matcher.unicode ? 'u' : '') + 'g')
                ;

            return this.tap().pipe(new scramjet.DataStream({
                transform(chunk, encoding, callback) {
                    this.buffer = (this.buffer || "") + chunk.toString("utf-8");
                    this.buffer = this.buffer.replace(replaceRegex, (...match) => {
                        this.push(match.slice(1, match.length - 2).join(""));
                        return '';
                    });

                    callback();
                },
                referrer: this
            }));

        }
        throw new Error("Mathcher must be a RegExp!");
    }

    /**
     * Transforms the StringStream to BufferStream
     *
     * Creates a buffer stream from the given string stream. Still it returns a
     * DataStream derivative and isn't the typical node.js stream so you can do
     * all your transforms when you like.
     *
     * @return {StringStream}  The converted stream.
     *
     * @example {@link ../samples/string-stream-tobufferstream.js}
     */
    toBufferStream() {
        return this.tap().map(
            (str) => Buffer.from(str, this.encoding),
            new scramjet.BufferStream({
                referrer: this
            })
        );
    }

    toStringStream(encoding) {
        if (encoding)
            return this.tap().pipe(this._selfInstance(encoding, {
                referrer: this
            }));
        else
            return this;
    }

    /**
     * @callback ParseCallback
     * @param {String} chunk the transformed chunk
     * @return {Promise}  the promise should be resolved with the parsed object
     */

     /**
      * Parses every string to object
      *
      * The method MUST parse EVERY string into a single object, so the string
      * stream here should already be splitted.
      *
      * @param  {ParseCallback} parser The transform function
      * @return {DataStream}  The parsed objects stream.
      *
      * @example {@link ../samples/string-stream-parse.js}
      */
    parse(parser) {
        return this.tap().map(parser, scramjet.DataStream);
    }

    _transform(chunk, encoding, callback) {
        this.push(chunk.toString(this.encoding));
        return callback();
    }

    /**
     * Creates a StringStream and writes a specific string.
     *
     * @param  {String} str      the string to push the your stream
     * @param  {String} encoding optional encoding
     * @return {StringStream}          new StringStream.
     */
    static fromString(str, encoding) {
        const st =  new StringStream(encoding || 'utf-8');
        st.end(str);
        return st;
    }

}

StringStream.prototype.pop = StringStream.prototype.shift;

module.exports = StringStream;
<|endoftext|>


<commit_before>"use babel";

export default {
  formatOnSave: {
    title: "Format on save",
    description: "Automatically format files on save.",
    type: "boolean",
    default: true,
    order: 1
  },
  showErrorNotifications: {
    title: "Show error notifications",
    description: "Show an error notification when formatting fails.",
    type: "boolean",
    default: true,
    order: 2
  },
  externalExfmtDirectory: {
    title: "External exfmt directory",
    description:
      "By default, `exfmt-atom` requires the presence a local `mix.exs` file \
      with `exfmt` included as a dependency. To use an external `exfmt` \
      installation, specify the directory here.",
    type: "string",
    default: "",
    order: 3
  }
};
<commit_msg>Default formatOnSave setting to false while exfmt is in alpha<commit_after>"use babel";

export default {
  formatOnSave: {
    title: "Format on save",
    description: "Automatically format files on save.",
    type: "boolean",
    default: false,
    order: 1
  },
  showErrorNotifications: {
    title: "Show error notifications",
    description: "Show an error notification when formatting fails.",
    type: "boolean",
    default: true,
    order: 2
  },
  externalExfmtDirectory: {
    title: "External exfmt directory",
    description:
      "By default, `exfmt-atom` requires the presence a local `mix.exs` file \
      with `exfmt` included as a dependency. To use an external `exfmt` \
      installation, specify the directory here.",
    type: "string",
    default: "",
    order: 3
  }
};
<|endoftext|>


<commit_before>"use strict";

var fs = require("fs");
const log = require("matrix-appservice-bridge").Logging.get("substitutions");

// Ordered
var SUBSTITUTION_PAIRS = [];

function add(slack, matrix) {
    SUBSTITUTION_PAIRS.push({
        slack: slack,
        matrix: matrix
    });
}

// slack -> matrix substitutions are performed top -> bottom
// matrix -> slack substitutions are performed bottom -> top
//
// The ordering here matters because some characters are present in both the
// "old" and "new" patterns, and we may end up over- or under-escaping things,
// or in an escaping loop, if things aren't properly ordered.
add("<", "<");
add(">", ">");
add("&", "&"); // & must be after all replacements involving &s.

var emojiIndex = (function () {
    var emojiJson = fs.readFileSync("./external/emoji-data/emoji.json");
    var emoji = JSON.parse(emojiJson);
    var index = {};
    for (var i = 0; i < emoji.length; ++i) {
        var e = emoji[i];
        for (var j = 0; j < e.short_names.length; ++j) {
            if (e["unified"].indexOf("-") < 0) {
                var char = String.fromCodePoint(parseInt(e["unified"], 16));
                index[":" + e.short_names[j] + ":"] = char;
            }
        }
    }
    index[":simple_smile:"] = index[":smile:"];
    index[":+1:"] = index[":thumbsup:"];
    index[":squirrel:"] = index[":chipmunk:"];
    index[":shipit:"] = index[":chipmunk:"]; // https://www.quora.com/GitHub-What-is-the-significance-of-the-Ship-It-squirrel
    return index;
})();

function replaceAll(string, old, replacement) {
    return string.split(old).join(replacement);
}

/**
 * Performs any escaping, unescaping, or substituting required to make the text
 * of a Slack message appear like the text of a Matrix message.
 *
 * @param {string} string the text, in Slack's format.
 * @param {Object} file options slack file object
 */
var slackToMatrix = function(body, file) {
    log.info("running substitutions on: " + body);

    if (undefined != body) {
        for (var i = 0; i < SUBSTITUTION_PAIRS.length; ++i) {
            var pair = SUBSTITUTION_PAIRS[i];
            body = replaceAll(body, pair.slack, pair.matrix);
        }
    }

    // convert @channel to @room
    body = body.replace("<!channel>", "@room");

    // escape slack-links e.g:
    // "hello @oddvar:<http://oddvar.org|oddvar.org> how are ya"
    // becomes
    // hello @oddvar:http://oddvar.org how are ya
    body = body.replace(/<(https?:\/\/[^\|]+?)\|[^>]+?>/g, '$1');

    // remove <> from links. if you post http://oddvar.org in slack,
    // this becomes <http://oddvar.org> in the event sent to Matrix
    // so we need to strip <> from the link
    body = body.replace(/<(https?:\/\/[^>]+?)>/g, '$1');

    // if we have a file, attempt to get the direct link to the file
    if (file && file.public_url_shared) {
        var url = getSlackFileUrl(file);
        body = body.replace(file.permalink, url);
    }

    // attempt to match any text inside colons to emoji, e.g. :smiley:
    // we use replace to run a function on each match, replacing matches in buf
    // (returning the match to body doesn't work, hence this approach)
    var buf = body;
    body.replace(/(?=(:[^: ]+?:))/g, function(fullMatch, match1) {
        if (undefined == emojiIndex[match1]) {
            log.warn("Failed to recognise emoji sequence: " + match1);
        }
        else {
            buf = buf.replace(match1, emojiIndex[match1]);
        }
        return;
    });
    return buf;
};

var getSlackFileUrl = function(file) {
    var pub_secret = file.permalink_public.match(/https?:\/\/slack-files.com\/[^-]*-[^-]*-(.*)/);
    // try to get direct link to the file
    if (pub_secret !== undefined && pub_secret.length > 0) {
        return file.url_private + "?pub_secret=" + pub_secret[1];
    }
};

/**
 * Performs any escaping, unescaping, or substituting required to make the text
 * of a Matrix message appear like the text of a Slack message.
 *
 * @param {MatrixEvent} event the Matrix event.
 * @param {Main} main the toplevel main instance
 * @return An object which can be posted as JSON to the Slack API.
 */
var matrixToSlack = function(event, main) {
    log.debug("Event from matrix: " + JSON.stringify(event));

    var string = event.content.body;

    // remove <> from links, e.g:
    string = string.replace(/<((https?:\/\/)?[^>]+?)>/g, '$1');

    if (undefined != string) {
        for (var i = SUBSTITUTION_PAIRS.length - 1; i >= 0; --i) {
            var pair = SUBSTITUTION_PAIRS[i];
            string = replaceAll(string, pair.matrix, pair.slack);
        }
        // emotes in slack are just italicised
        if (event.content.msgtype == "m.emote") {
            string = "_" + string + "_";
        }

        // replace riot "pill" behavior to "@" mention for slack users
        const html_string = event.content.formatted_body;
        if (undefined != html_string) {
            const regex = new RegExp('([^<]+)', "g");

            let match = regex.exec(html_string);
            while (match != null) {
                // Extract the slack ID from the matrix id
                const userid = match[1].split("_")[1].split(":")[0];
                // Construct the new slack mention
                string = string.replace(match[2], "<@" + userid + ">");
                match = regex.exec(html_string);
            }
        }
    }

    // convert @room to @channel
    string = string.replace("@room", "@channel");

    // Strip out any language specifier on the code tags, as they are not supported by slack.
    string = string.replace(/```[\w*]+\n/g, "```\n");

    return plainTextSlackMentions(main, string, event.room_id).then(string => {
        var ret = {
            username: event.user_id,
            text: string,
        }
        if (event.content.msgtype == "m.image" && event.content.url.indexOf("mxc://") === 0) {
            var url = main.getUrlForMxc(event.content.url);
            delete ret.text;
            ret.attachments = [{
                fallback: string,
                image_url: url
            }];
        } else if (event.content.msgtype == "m.file" && event.content.url.indexOf("mxc://") === 0) {
            var url = main.getUrlForMxc(event.content.url);
            ret.text = "<" + url + "|" + string + ">";
        }

        return ret;
    });
}

var htmlEscape = function(s) {
    return s.replace(/&/g, "&")
        .replace(/</g, "<")
        .replace(/>/g, ">");
};

/**
 * Get a mapping of the nicknames of all Slack Ghosts in the room to their slack user ids.
 *
 * @param {String} room_id The room id to make the maps for.
 * @return {Promise} A mapping of display name to slack user id.
 */
function getDisplayMap(main, room_id) {
    var store = main.getUserStore();
    return main.listGhostUsers(room_id)
        .then(users => Promise.all(users.map(user => store.select({id: user}))))
        .then(users => {
            var displaymap = {};
            users.forEach(user => {
                if (user && user[0]) {
                    displaymap[user[0].display_name] = user[0].id.split("_")[2].split(":")[0];
                }
            });
            return displaymap;
        });
}

/**
 * Construct a mapping of the first words of all the display names to
 * an array of objects which map full display names to their slack
 * user ids.
 * i.e. {"bob": [{"bob": "U123123"}, {"bob smith": "U2891283"}]}
 *
 * @param {Object} displaymap A mapping of display names to slack user ids.
 * @return {Object} A mapping of first words of display names.
 */
function makeFirstWordMap(displaymap) {
    var displaynames = Object.keys(displaymap);
    var firstwords = {};

    for (const dispname of displaynames) {
        var firstword = dispname.split(" ")[0];
        var amap = {};
        amap[dispname] = displaymap[dispname];
        if (firstwords.hasOwnProperty(firstword)) {
            firstwords[firstword].push(amap);
        }
        else {
            firstwords[firstword] = [amap];
        }
    }
    return firstwords;
}

/**
 * Do string replacement on a message given the display map.
 *
 * @param {String} string The string to perform replacements on.
 * @param {Object} displaymap A mapping of display names to slack user ids.
 * @return {String} The string with replacements performed.
 */
function replacementFromDisplayMap(string, displaymap) {
    var firstwords = makeFirstWordMap(displaymap);

    // Now parse the message and extract the first word after the @
    var matches = string.match(/(@\w+)/g);
    if (matches && matches.length) {
        for (const match of matches) {
            var firstword = match.replace("@", "");
            if (firstwords.hasOwnProperty(firstword)) {
                var nicks = firstwords[firstword];
                if (nicks.length === 1) {
                    var userid = nicks[0][firstword];
                    string = string.replace(match, "<@" + userid + ">");
                }
                else {
                    // Do something to match the longest nick.
                    var displaynames = nicks.map(x => Object.keys(x)[0]);
                    displaynames.sort((x, y) => y.length - x.length);
                    for (var displayname of displaynames) {
                        var replacement = "@" + displayname
                        if (string.includes(replacement)) {
                            string = string.replace(replacement, "<@" + displaymap[displayname] + ">");
                        }
                    }
                }
            }
        }
    }
    return string;
}

/**
 * Replace plain text form of @displayname mentions with the slack mention syntax.
 *
 * @param {Main} main the toplevel main instance
 * @param {String} string The string to perform replacements on.
 * @param {String} room_id The room the message was sent in.
 * @return {String} The string with replacements performed.
 */
function plainTextSlackMentions(main, string, room_id) {
    return getDisplayMap(main, room_id).then(displaymap => replacementFromDisplayMap(string, displaymap));
}

// These functions are copied and modified from the Gitter AS
// idx counts backwards from the end of the string; 0 is final character
function rcharAt(s,idx) {
    return s.charAt(s.length-1 - idx);
}

function firstWord(s) {
    var groups = s.match(/^\s*\S+/);
    return groups ? groups[0] : "";
}

function finalWord(s) {
    var groups = s.match(/\S+\s*$/);
    return groups ? groups[0] : "";
}

function makeDiff(prev, curr) {
    var i;
    for (i = 0; i < curr.length && i < prev.length; i++) {
        if (curr.charAt(i) != prev.charAt(i)) break;
    }
    // retreat to the start of a word
    while(i > 0 && /\S/.test(curr.charAt(i-1))) i--;

    var prefixLen = i;

    for(i = 0; i < curr.length && i < prev.length; i++) {
        if (rcharAt(curr, i) != rcharAt(prev, i)) break;
    }
    // advance to the end of a word
    while(i > 0 && /\S/.test(rcharAt(curr, i-1))) i--;

    var suffixLen = i;

    // Extract the common prefix and suffix strings themselves and
    //   mutate the prev/curr strings to only contain the differing
    //   middle region
    var prefix = curr.slice(0, prefixLen);
    curr = curr.slice(prefixLen);
    prev = prev.slice(prefixLen);

    var suffix = "";
    if (suffixLen > 0) {
        suffix = curr.slice(-suffixLen);
        curr = curr.slice(0, -suffixLen);
        prev = prev.slice(0, -suffixLen);
    }

    // At this point, we have four strings; the common prefix and
    //   suffix, and the edited middle part. To display it nicely as a
    //   matrix message we'll use the final word of the prefix and the
    //   first word of the suffix as "context" for a customly-formatted
    //   message.

    var before = finalWord(prefix);
    if (before != prefix) { before = "... " + before; }

    var after = firstWord(suffix);
    if (after != suffix) { after = after + " ..."; }

    // return {prev: prev,
    //         curr: curr,
    //         before: before,
    //         after: after};
    return {prev, curr, before, after};
}

module.exports = {
    matrixToSlack: matrixToSlack,
    slackToMatrix: slackToMatrix,
    getSlackFileUrl: getSlackFileUrl,
    htmlEscape: htmlEscape,
    replacementFromDisplayMap: replacementFromDisplayMap,
    makeFirstWordMap: makeFirstWordMap,
    makeDiff: makeDiff
};
<commit_msg>Make everything const, because I dont want @Half-Shot to loose sleep<commit_after>"use strict";

var fs = require("fs");
const log = require("matrix-appservice-bridge").Logging.get("substitutions");

// Ordered
var SUBSTITUTION_PAIRS = [];

function add(slack, matrix) {
    SUBSTITUTION_PAIRS.push({
        slack: slack,
        matrix: matrix
    });
}

// slack -> matrix substitutions are performed top -> bottom
// matrix -> slack substitutions are performed bottom -> top
//
// The ordering here matters because some characters are present in both the
// "old" and "new" patterns, and we may end up over- or under-escaping things,
// or in an escaping loop, if things aren't properly ordered.
add("<", "<");
add(">", ">");
add("&", "&"); // & must be after all replacements involving &s.

var emojiIndex = (function () {
    var emojiJson = fs.readFileSync("./external/emoji-data/emoji.json");
    var emoji = JSON.parse(emojiJson);
    var index = {};
    for (var i = 0; i < emoji.length; ++i) {
        var e = emoji[i];
        for (var j = 0; j < e.short_names.length; ++j) {
            if (e["unified"].indexOf("-") < 0) {
                var char = String.fromCodePoint(parseInt(e["unified"], 16));
                index[":" + e.short_names[j] + ":"] = char;
            }
        }
    }
    index[":simple_smile:"] = index[":smile:"];
    index[":+1:"] = index[":thumbsup:"];
    index[":squirrel:"] = index[":chipmunk:"];
    index[":shipit:"] = index[":chipmunk:"]; // https://www.quora.com/GitHub-What-is-the-significance-of-the-Ship-It-squirrel
    return index;
})();

function replaceAll(string, old, replacement) {
    return string.split(old).join(replacement);
}

/**
 * Performs any escaping, unescaping, or substituting required to make the text
 * of a Slack message appear like the text of a Matrix message.
 *
 * @param {string} string the text, in Slack's format.
 * @param {Object} file options slack file object
 */
var slackToMatrix = function(body, file) {
    log.info("running substitutions on: " + body);

    if (undefined != body) {
        for (var i = 0; i < SUBSTITUTION_PAIRS.length; ++i) {
            var pair = SUBSTITUTION_PAIRS[i];
            body = replaceAll(body, pair.slack, pair.matrix);
        }
    }

    // convert @channel to @room
    body = body.replace("<!channel>", "@room");

    // escape slack-links e.g:
    // "hello @oddvar:<http://oddvar.org|oddvar.org> how are ya"
    // becomes
    // hello @oddvar:http://oddvar.org how are ya
    body = body.replace(/<(https?:\/\/[^\|]+?)\|[^>]+?>/g, '$1');

    // remove <> from links. if you post http://oddvar.org in slack,
    // this becomes <http://oddvar.org> in the event sent to Matrix
    // so we need to strip <> from the link
    body = body.replace(/<(https?:\/\/[^>]+?)>/g, '$1');

    // if we have a file, attempt to get the direct link to the file
    if (file && file.public_url_shared) {
        var url = getSlackFileUrl(file);
        body = body.replace(file.permalink, url);
    }

    // attempt to match any text inside colons to emoji, e.g. :smiley:
    // we use replace to run a function on each match, replacing matches in buf
    // (returning the match to body doesn't work, hence this approach)
    var buf = body;
    body.replace(/(?=(:[^: ]+?:))/g, function(fullMatch, match1) {
        if (undefined == emojiIndex[match1]) {
            log.warn("Failed to recognise emoji sequence: " + match1);
        }
        else {
            buf = buf.replace(match1, emojiIndex[match1]);
        }
        return;
    });
    return buf;
};

var getSlackFileUrl = function(file) {
    var pub_secret = file.permalink_public.match(/https?:\/\/slack-files.com\/[^-]*-[^-]*-(.*)/);
    // try to get direct link to the file
    if (pub_secret !== undefined && pub_secret.length > 0) {
        return file.url_private + "?pub_secret=" + pub_secret[1];
    }
};

/**
 * Performs any escaping, unescaping, or substituting required to make the text
 * of a Matrix message appear like the text of a Slack message.
 *
 * @param {MatrixEvent} event the Matrix event.
 * @param {Main} main the toplevel main instance
 * @return An object which can be posted as JSON to the Slack API.
 */
var matrixToSlack = function(event, main) {
    log.debug("Event from matrix: " + JSON.stringify(event));

    var string = event.content.body;

    // remove <> from links, e.g:
    string = string.replace(/<((https?:\/\/)?[^>]+?)>/g, '$1');

    if (undefined != string) {
        for (var i = SUBSTITUTION_PAIRS.length - 1; i >= 0; --i) {
            var pair = SUBSTITUTION_PAIRS[i];
            string = replaceAll(string, pair.matrix, pair.slack);
        }
        // emotes in slack are just italicised
        if (event.content.msgtype == "m.emote") {
            string = "_" + string + "_";
        }

        // replace riot "pill" behavior to "@" mention for slack users
        const html_string = event.content.formatted_body;
        if (undefined != html_string) {
            const regex = new RegExp('([^<]+)', "g");

            let match = regex.exec(html_string);
            while (match != null) {
                // Extract the slack ID from the matrix id
                const userid = match[1].split("_")[1].split(":")[0];
                // Construct the new slack mention
                string = string.replace(match[2], "<@" + userid + ">");
                match = regex.exec(html_string);
            }
        }
    }

    // convert @room to @channel
    string = string.replace("@room", "@channel");

    // Strip out any language specifier on the code tags, as they are not supported by slack.
    string = string.replace(/```[\w*]+\n/g, "```\n");

    return plainTextSlackMentions(main, string, event.room_id).then(string => {
        var ret = {
            username: event.user_id,
            text: string,
        }
        if (event.content.msgtype == "m.image" && event.content.url.indexOf("mxc://") === 0) {
            var url = main.getUrlForMxc(event.content.url);
            delete ret.text;
            ret.attachments = [{
                fallback: string,
                image_url: url
            }];
        } else if (event.content.msgtype == "m.file" && event.content.url.indexOf("mxc://") === 0) {
            var url = main.getUrlForMxc(event.content.url);
            ret.text = "<" + url + "|" + string + ">";
        }

        return ret;
    });
}

var htmlEscape = function(s) {
    return s.replace(/&/g, "&")
        .replace(/</g, "<")
        .replace(/>/g, ">");
};

/**
 * Get a mapping of the nicknames of all Slack Ghosts in the room to their slack user ids.
 *
 * @param {String} room_id The room id to make the maps for.
 * @return {Promise} A mapping of display name to slack user id.
 */
function getDisplayMap(main, room_id) {
    const store = main.getUserStore();
    return main.listGhostUsers(room_id)
        .then(users => Promise.all(users.map(user => store.select({id: user}))))
        .then(users => {
            const displaymap = {};
            users.forEach(user => {
                if (user && user[0]) {
                    displaymap[user[0].display_name] = user[0].id.split("_")[2].split(":")[0];
                }
            });
            return displaymap;
        });
}

/**
 * Construct a mapping of the first words of all the display names to
 * an array of objects which map full display names to their slack
 * user ids.
 * i.e. {"bob": [{"bob": "U123123"}, {"bob smith": "U2891283"}]}
 *
 * @param {Object} displaymap A mapping of display names to slack user ids.
 * @return {Object} A mapping of first words of display names.
 */
function makeFirstWordMap(displaymap) {
    const displaynames = Object.keys(displaymap);
    const firstwords = {};

    for (const dispname of displaynames) {
        const firstword = dispname.split(" ")[0];
        const amap = {};
        amap[dispname] = displaymap[dispname];
        if (firstwords.hasOwnProperty(firstword)) {
            firstwords[firstword].push(amap);
        }
        else {
            firstwords[firstword] = [amap];
        }
    }
    return firstwords;
}

/**
 * Do string replacement on a message given the display map.
 *
 * @param {String} string The string to perform replacements on.
 * @param {Object} displaymap A mapping of display names to slack user ids.
 * @return {String} The string with replacements performed.
 */
function replacementFromDisplayMap(string, displaymap) {
    const firstwords = makeFirstWordMap(displaymap);

    // Now parse the message and extract the first word after the @
    const matches = string.match(/(@\w+)/g);
    if (matches && matches.length) {
        for (const match of matches) {
            const firstword = match.replace("@", "");
            if (firstwords.hasOwnProperty(firstword)) {
                const nicks = firstwords[firstword];
                if (nicks.length === 1) {
                    string = string.replace(match, "<@" + nicks[0][firstword] + ">");
                }
                else {
                    // Do something to match the longest nick.
                    const displaynames = nicks.map(x => Object.keys(x)[0]);
                    displaynames.sort((x, y) => y.length - x.length);
                    for (const displayname of displaynames) {
                        const replacement = "@" + displayname
                        if (string.includes(replacement)) {
                            string = string.replace(replacement, "<@" + displaymap[displayname] + ">");
                        }
                    }
                }
            }
        }
    }
    return string;
}

/**
 * Replace plain text form of @displayname mentions with the slack mention syntax.
 *
 * @param {Main} main the toplevel main instance
 * @param {String} string The string to perform replacements on.
 * @param {String} room_id The room the message was sent in.
 * @return {String} The string with replacements performed.
 */
function plainTextSlackMentions(main, string, room_id) {
    return getDisplayMap(main, room_id).then(displaymap => replacementFromDisplayMap(string, displaymap));
}

// These functions are copied and modified from the Gitter AS
// idx counts backwards from the end of the string; 0 is final character
function rcharAt(s,idx) {
    return s.charAt(s.length-1 - idx);
}

function firstWord(s) {
    var groups = s.match(/^\s*\S+/);
    return groups ? groups[0] : "";
}

function finalWord(s) {
    var groups = s.match(/\S+\s*$/);
    return groups ? groups[0] : "";
}

function makeDiff(prev, curr) {
    var i;
    for (i = 0; i < curr.length && i < prev.length; i++) {
        if (curr.charAt(i) != prev.charAt(i)) break;
    }
    // retreat to the start of a word
    while(i > 0 && /\S/.test(curr.charAt(i-1))) i--;

    var prefixLen = i;

    for(i = 0; i < curr.length && i < prev.length; i++) {
        if (rcharAt(curr, i) != rcharAt(prev, i)) break;
    }
    // advance to the end of a word
    while(i > 0 && /\S/.test(rcharAt(curr, i-1))) i--;

    var suffixLen = i;

    // Extract the common prefix and suffix strings themselves and
    //   mutate the prev/curr strings to only contain the differing
    //   middle region
    var prefix = curr.slice(0, prefixLen);
    curr = curr.slice(prefixLen);
    prev = prev.slice(prefixLen);

    var suffix = "";
    if (suffixLen > 0) {
        suffix = curr.slice(-suffixLen);
        curr = curr.slice(0, -suffixLen);
        prev = prev.slice(0, -suffixLen);
    }

    // At this point, we have four strings; the common prefix and
    //   suffix, and the edited middle part. To display it nicely as a
    //   matrix message we'll use the final word of the prefix and the
    //   first word of the suffix as "context" for a customly-formatted
    //   message.

    var before = finalWord(prefix);
    if (before != prefix) { before = "... " + before; }

    var after = firstWord(suffix);
    if (after != suffix) { after = after + " ..."; }

    // return {prev: prev,
    //         curr: curr,
    //         before: before,
    //         after: after};
    return {prev, curr, before, after};
}

module.exports = {
    matrixToSlack: matrixToSlack,
    slackToMatrix: slackToMatrix,
    getSlackFileUrl: getSlackFileUrl,
    htmlEscape: htmlEscape,
    replacementFromDisplayMap: replacementFromDisplayMap,
    makeFirstWordMap: makeFirstWordMap,
    makeDiff: makeDiff
};
<|endoftext|>


<commit_before>// @flow weak
/* eslint max-len: "off" */

import React, { Component } from 'react';
import { interval } from 'd3-timer';
import { transition, stop } from '../core/transition';

type Props = {
  /**
   * Boolean value that determines if the child should be mounted or not.
   */
  show: bool,
  /**
  * The starting state.
  */
  start: {},
  /**
   * An object or array of objects describing how the state should transform on enter.
   */
  enter?: {} | Array<{}>,
  /**
   * An object or array of objects describing how the state should transform on update. ***Note:*** although not required, in most cases it make sense to specify an update prop to handle interrupted enter and leave transitions.
   */
  update?: {} | Array<{}>,
  /**
   * An object or array of objects describing how the state should transform on leave.
   */
  leave?: {} | Array<{}>,
  /**
   * A function that renders the node.  The function is passed the data and state.
   */
  children: (state: {}) => {},
};

class Animate extends Component {
  static defaultProps = {
    show: true,
  }

  state = this.props.start

  componentWillMount() {
    if (this.props.show === true) {
      this.renderNull = false;
    }
  }

  componentDidMount() {
    const { enter, show } = this.props;

    if (enter && show === true) {
      transition.call(this, enter);
    }
  }

  componentWillReceiveProps(next) {
    const { show, start, enter, update, leave } = next;

    if (this.props.show === false && this.renderNull === true && show === true) {
      this.renderNull = false;

      this.setState(() => start, () => {
        if (enter) {
          transition.call(this, enter);
        }
      });
    } else if (this.props.show === true && show === false) {
      if (leave) {
        transition.call(this, leave);
        this.interval = interval(this.checkTransitionStatus);
      } else {
        this.renderNull = true;
        this.setState((prevState) => prevState); // force render as null
      }
    } else if (show === true && update && update !== this.props.update) {
      if (this.interval) {
        this.interval.stop();
      }

      transition.call(this, update);
    }
  }

  componentWillUnmount() {
    if (this.interval) {
      this.interval.stop();
    }

    stop.call(this);
  }

  checkTransitionStatus = () => {
    if (!this.TRANSITION_SCHEDULES) {
      this.interval.stop();

      if (this.props.show === false) {
        this.renderNull = true;
        this.setState((prevState) => prevState); // force render as null
      }
    }
  }

  props: Props;

  interval = null;
  renderNull = true;

  render() {
    if (this.renderNull === true) {
      return null;
    }

    const renderedChildren = this.props.children(this.state);
    return renderedChildren && React.Children.only(renderedChildren);
  }
}

export default Animate;
<commit_msg>remove equality check - not going to work<commit_after>// @flow weak
/* eslint max-len: "off" */

import React, { Component } from 'react';
import { interval } from 'd3-timer';
import { transition, stop } from '../core/transition';

type Props = {
  /**
   * Boolean value that determines if the child should be mounted or not.
   */
  show: bool,
  /**
  * The starting state.
  */
  start: {},
  /**
   * An object or array of objects describing how the state should transform on enter.
   */
  enter?: {} | Array<{}>,
  /**
   * An object or array of objects describing how the state should transform on update. ***Note:*** although not required, in most cases it make sense to specify an update prop to handle interrupted enter and leave transitions.
   */
  update?: {} | Array<{}>,
  /**
   * An object or array of objects describing how the state should transform on leave.
   */
  leave?: {} | Array<{}>,
  /**
   * A function that renders the node.  The function is passed the data and state.
   */
  children: (state: {}) => {},
};

class Animate extends Component {
  static defaultProps = {
    show: true,
  }

  state = this.props.start

  componentWillMount() {
    if (this.props.show === true) {
      this.renderNull = false;
    }
  }

  componentDidMount() {
    const { enter, show } = this.props;

    if (enter && show === true) {
      transition.call(this, enter);
    }
  }

  componentWillReceiveProps(next) {
    const { show, start, enter, update, leave } = next;

    if (this.props.show === false && this.renderNull === true && show === true) {
      this.renderNull = false;

      this.setState(() => start, () => {
        if (enter) {
          transition.call(this, enter);
        }
      });
    } else if (this.props.show === true && show === false) {
      if (leave) {
        transition.call(this, leave);
        this.interval = interval(this.checkTransitionStatus);
      } else {
        this.renderNull = true;
        this.setState((prevState) => prevState); // force render as null
      }
    } else if (show === true && update) {
      if (this.interval) {
        this.interval.stop();
      }

      transition.call(this, update);
    }
  }

  componentWillUnmount() {
    if (this.interval) {
      this.interval.stop();
    }

    stop.call(this);
  }

  checkTransitionStatus = () => {
    if (!this.TRANSITION_SCHEDULES) {
      this.interval.stop();

      if (this.props.show === false) {
        this.renderNull = true;
        this.setState((prevState) => prevState); // force render as null
      }
    }
  }

  props: Props;

  interval = null;
  renderNull = true;

  render() {
    if (this.renderNull === true) {
      return null;
    }

    const renderedChildren = this.props.children(this.state);
    return renderedChildren && React.Children.only(renderedChildren);
  }
}

export default Animate;
<|endoftext|>


<commit_before>// This module exports one function: start()
//
// start() binds the changes listeners for a few different functionalities:
//
// * On the users db, when we see a user has a control database, we trigger the listener on that user's control database
// * On the handshake database, we listen for requests for new sessions, and connect them to the correct per-user control channel. Currently we only bind one listener to the handshake database (the Facebook listner) but we plan to add more (Twitter, email, enterprise SSO, etc).

var sessionFromFacebook = require('./sessionFromFacebook'),
    userControlSetup = require('./userControlSetup'),
    coux = require('coux').coux,
    docstate = require("docstate"),
    async = require("async"),
    e = require('errLog').e;

function Syncpoint(config) {
    function bindHandshakeDb() {
        var control = docstate.control();
        sessionFromFacebook.bind(control, config);
        //todo sessionFromEmail.bind(control, config);
        // todo make sessionFrom* into a plugin pattern
        control.start();
        console.log("listening for session handshakes");
        coux.subscribeDb(config.host + "/" + config.handshake_db, function(change) {
            control.handle(change.doc);
        });
    };

    function bindUsersDb() {
        var control = docstate.control();
        userControlSetup.bind(control, config);
        control.start();
        console.log("listening for new user control databases");
        coux.subscribeDb(config.host + "/" + config.users_db, function(change) {
            control.handle(change.doc);
        });
    };

    // ensure the users database exists
    function setupUserDb(cb) {
        coux.put([config.host, config.users_db], function() {
            cb(false)
        });
    }

    // ensure the handshake database exists
    function setupHandshakeDb(cb) {
        coux.put([config.host, config.handshake_db], function() {
            cb(false)
        });
    }

    function createConfigDDoc(cb) {
        var ddoc = require('./config_ddoc').ddoc;
        coux.put([config.host, config.config_db, ddoc._id], ddoc, function(err, ok) {
            if (err && err.code != 409) {
                cb(err)
            } else {
                cb(false)
            }
        })
    }

    // ensure the config database exists
    function setupConfigDb(cb) {
        coux.put([config.host, config.config_db], function() {
            createConfigDDoc(cb)
        })
    };

    this.start = function(startedCb) {
        console.log("Syncpoint API listeners starting");
        setupConfigDb(function() {
            setupUserDb(function() {
                setupHandshakeDb(function() {
                    bindHandshakeDb();
                    bindUsersDb();
                    startedCb();
                });
            });
        });
    };
};

module.exports = Syncpoint;
<commit_msg>remove unused require<commit_after>// This module exports one function: start()
//
// start() binds the changes listeners for a few different functionalities:
//
// * On the users db, when we see a user has a control database, we trigger the listener on that user's control database
// * On the handshake database, we listen for requests for new sessions, and connect them to the correct per-user control channel. Currently we only bind one listener to the handshake database (the Facebook listner) but we plan to add more (Twitter, email, enterprise SSO, etc).

var sessionFromFacebook = require('./sessionFromFacebook'),
    userControlSetup = require('./userControlSetup'),
    coux = require('coux').coux,
    docstate = require("docstate"),
    e = require('errLog').e;

function Syncpoint(config) {
    function bindHandshakeDb() {
        var control = docstate.control();
        sessionFromFacebook.bind(control, config);
        //todo sessionFromEmail.bind(control, config);
        // todo make sessionFrom* into a plugin pattern
        control.start();
        console.log("listening for session handshakes");
        coux.subscribeDb(config.host + "/" + config.handshake_db, function(change) {
            control.handle(change.doc);
        });
    };

    function bindUsersDb() {
        var control = docstate.control();
        userControlSetup.bind(control, config);
        control.start();
        console.log("listening for new user control databases");
        coux.subscribeDb(config.host + "/" + config.users_db, function(change) {
            control.handle(change.doc);
        });
    };

    // ensure the users database exists
    function setupUserDb(cb) {
        coux.put([config.host, config.users_db], function() {
            cb(false)
        });
    }

    // ensure the handshake database exists
    function setupHandshakeDb(cb) {
        coux.put([config.host, config.handshake_db], function() {
            cb(false)
        });
    }

    function createConfigDDoc(cb) {
        var ddoc = require('./config_ddoc').ddoc;
        coux.put([config.host, config.config_db, ddoc._id], ddoc, function(err, ok) {
            if (err && err.code != 409) {
                cb(err)
            } else {
                cb(false)
            }
        })
    }

    // ensure the config database exists
    function setupConfigDb(cb) {
        coux.put([config.host, config.config_db], function() {
            createConfigDDoc(cb)
        })
    };

    this.start = function(startedCb) {
        console.log("Syncpoint API listeners starting");
        setupConfigDb(function() {
            setupUserDb(function() {
                setupHandshakeDb(function() {
                    bindHandshakeDb();
                    bindUsersDb();
                    startedCb();
                });
            });
        });
    };
};

module.exports = Syncpoint;
<|endoftext|>


<commit_before>    var registry = {},
        values = {};

    var set_addon = function(name, target) {
        registry[name] = target;
    };

    var set_value = function(name, target) {
        values[name] = target;
    };

    var get_value = function(name) {
        return values[name];
    };

exports.get_value = get_value;
exports.set_value = set_value;
exports.set_addon = set_addon;
<commit_msg>add an expository getAllValues method to wilson settings<commit_after>    var registry = {},
        values = {};

    var set_addon = function(name, target) {
        registry[name] = target;
    };

    var set_value = function(name, target) {
        values[name] = target;
    };

    var get_value = function(name) {
        return values[name];
    };

exports.get_value = get_value;
exports.set_value = set_value;
exports.set_addon = set_addon;

exports.getAllValues = function() {
    var output = {};
    for(var name in values) {
        output[name] = values[name];
    }
    return output;
};
<|endoftext|>


<commit_before>// Extend Pointer class to add querystring parsing and formatting
//

'use strict';

const _           = require('lodash');
const Pointer     = require('pointer');
const querystring = require('querystring');
const util        = require('util');

function Router(...args) {
  if (!(this instanceof Router)) return new Router(...args);

  Pointer.apply(this, args);
}

util.inherits(Router, Pointer);

Router.prototype.match = function (url) {
  let [ , urlpath, query, anchor ] = url.match(/^([^?#]*)(?:\?([^#]*))?(?:#(.*))?$/);
  let result = Pointer.prototype.match.call(this, urlpath);

  if (query) {
    if (result && result.params && !result.params.$query) {
      result.params.$query = querystring.parse(query);
    }
  }

  if (anchor) {
    if (result && result.params && !result.params.$anchor) {
      result.params.$anchor = anchor;
    }
  }

  return result;
};

Router.prototype.matchAll = function (url) {
  let [ , urlpath, query, anchor ] = url.match(/^([^?#]*)(?:\?([^#]*))?(?:#(.*))?$/);
  let results = Pointer.prototype.matchAll.call(this, urlpath);

  if (query) {
    results.forEach(result => {
      if (result && result.params && !result.params.$query) {
        result.params.$query = querystring.parse(query);
      }
    });
  }

  if (anchor) {
    results.forEach(result => {
      if (result && result.params && !result.params.$anchor) {
        result.params.$anchor = anchor;
      }
    });
  }

  return results;
};

Router.prototype.linkTo = function (apiPath, params, linkDefaults) {
  let query, anchor;

  if (params && (params.$query || params.$anchor)) {
    query  = params.$query;
    anchor = params.$anchor;
    params = _.omit(params, [ '$query', '$anchor' ]);
  }

  let result = Pointer.prototype.linkTo.call(this, apiPath, params, linkDefaults);

  if (!_.isEmpty(query)) {
    result += '?' + querystring.stringify(query);
  }

  if (anchor) {
    result += '#' + anchor;
  }

  return result;
};

module.exports = Router;
<commit_msg>Ignore undefined $query arguments in N.router.linkTo<commit_after>// Extend Pointer class to add querystring parsing and formatting
//

'use strict';

const _           = require('lodash');
const Pointer     = require('pointer');
const querystring = require('querystring');
const util        = require('util');

function Router(...args) {
  if (!(this instanceof Router)) return new Router(...args);

  Pointer.apply(this, args);
}

util.inherits(Router, Pointer);

Router.prototype.match = function (url) {
  let [ , urlpath, query, anchor ] = url.match(/^([^?#]*)(?:\?([^#]*))?(?:#(.*))?$/);
  let result = Pointer.prototype.match.call(this, urlpath);

  if (query) {
    if (result && result.params && !result.params.$query) {
      result.params.$query = querystring.parse(query);
    }
  }

  if (anchor) {
    if (result && result.params && !result.params.$anchor) {
      result.params.$anchor = anchor;
    }
  }

  return result;
};

Router.prototype.matchAll = function (url) {
  let [ , urlpath, query, anchor ] = url.match(/^([^?#]*)(?:\?([^#]*))?(?:#(.*))?$/);
  let results = Pointer.prototype.matchAll.call(this, urlpath);

  if (query) {
    results.forEach(result => {
      if (result && result.params && !result.params.$query) {
        result.params.$query = querystring.parse(query);
      }
    });
  }

  if (anchor) {
    results.forEach(result => {
      if (result && result.params && !result.params.$anchor) {
        result.params.$anchor = anchor;
      }
    });
  }

  return results;
};

Router.prototype.linkTo = function (apiPath, params, linkDefaults) {
  let query, anchor;

  if (params && (params.$query || params.$anchor)) {
    query  = _.omitBy(params.$query || {}, _.isNil);
    anchor = params.$anchor;
    params = _.omit(params, [ '$query', '$anchor' ]);
  }

  let result = Pointer.prototype.linkTo.call(this, apiPath, params, linkDefaults);

  if (!_.isEmpty(query)) {
    result += '?' + querystring.stringify(query);
  }

  if (anchor) {
    result += '#' + anchor;
  }

  return result;
};

module.exports = Router;
<|endoftext|>