<commit_before>/**
 * @file wow command
 * @author James Naylor (a.k.a euronay)
 * @license GPL-3.0
 */

const request = require('request-promise-native');

const wowConstants = {
  faction: [
    'Alliance',
    'Horde'
  ],
  gender: [
    'None',
    'Male',
    'Female'
  ],
  race: [
    'None',
    'Human',
    'Orc',
    'Dwarf',
    'Night Elf',
    'Undead',
    'Tauren',
    'Gnome',
    'Troll',
    'Goblin',
    'Blood Elf',
    'Draenei',
    'Fel Orc',
    'Naga',
    'Broken',
    'Skeleton',
    'Vrykul',
    'Tuskarr',
    'Forest Troll',
    'Taunka',
    'Northrend Skeleton',
    'Ice Troll',
    'Worgen',
    'Gilnean',
    'Pandaren',
    'Pandaren',
    'Pandaren',
    'Nightborn',
    'Highmountain Tauren',
    'Void Elf',
    'Lightforged Draenei',
    'Zandalari Troll',
    'Kul Tiran',
    'Human',
    'Dark Iron Dwarf',
    'Vulpera',
    'Mag\'har Orc'
  ],
  class: [
    'None',
    'Warrior',
    'Paladin',
    'Hunter',
    'Rogue',
    'Priest',
    'Death Knight',
    'Shaman',
    'Mage',
    'Warlock',
    'Druid',
    'Demon Hunter'
  ]
};

exports.exec = async (Bastion, message, args) => {
  try {
    if (!args.character || !args.realm) {
      /**
       * The command was ran with invalid parameters.
       * @fires commandUsage
       */
      return Bastion.emit('commandUsage', message, this.help);
    }

    args.region = args.region.toLowerCase();
    let locale = args.region === 'us' ? 'en-us' : 'en-gb';

    let options = {
      url: `https://${args.region}.api.battle.net/wow/character/${args.realm}/${args.character}`,
      qs: {
        fields: 'guild,items',
        locale: locale,
        apikey: Bastion.credentials.battleNetApiKey
      },
      json: true
    };

    let response = await request(options);

    let factionIcon = `https://worldofwarcraft.akamaized.net/static/components/Logo/Logo-${response.faction === 0 ? 'alliance' : 'horde'}.png`;
    let avatar = `https://render-${args.region}.worldofwarcraft.com/character/${response.thumbnail}`;

    let stats = [
      {
        name: 'Character',
        value: `${response.level} - ${wowConstants.race[response.race]} - ${wowConstants.class[response.class]}`
      },
      {
        name: 'Achievement Points',
        value: response.achievementPoints,
        inline: true
      },
      {
        name: 'Item Level',
        value: response.items.averageItemLevel,
        inline: true
      }
    ];

    if (response.guild) {
      stats.push(
        {
          name: 'Guild',
          value: response.guild.name,
          inline: true
        }
      );
    }

    message.channel.send({
      embed: {
        color: Bastion.colors.BLUE,
        author: {
          name: response.name,
          url: `https://worldofwarcraft.com/${locale}/character/${args.realm}/${args.character}`,
          icon_url: factionIcon
        },
        fields: stats,
        thumbnail: {
          url: avatar
        },
        footer: {
          text: 'Powered by Blizzard Battle.net'
        }
      }
    }).catch(e => {
      Bastion.log.error(e);
    });

  }
  catch (e) {
    if (e.name === 'StatusCodeError') {
      if (e.statusCode === 404) {
         return Bastion.emit('error', '', Bastion.strings.error(message.guild.language, 'notFound', true, 'player'), message.channel);
      }
      return Bastion.emit('error', e.statusCode, e.error.message, message.channel);
    }
    Bastion.log.error(e);
  }
};

exports.config = {
  aliases: [],
  enabled: true,
  argsDefinitions: [
    { name: 'character', type: String, defaultOption: true },
    { name: 'realm', type: String, alias: 'r' },
    { name: 'region', type: String, defaultValue: 'us' }
  ]
};

exports.help = {
  name: 'wow',
  description: 'Get stats of any World of Warcraft character from the Armory.',
  botPermission: '',
  userTextPermission: '',
  userVoicePermission: '',
  usage: 'wow <CHARACTER> <--realm REALM> [-region <REGION>]',
  example: [
    'wow Fallken --realm Burning-Blade --region EU'
  ]
};
<commit_msg>Fixed spacing issue<commit_after>/**
 * @file wow command
 * @author James Naylor (a.k.a euronay)
 * @license GPL-3.0
 */

const request = require('request-promise-native');

const wowConstants = {
  faction: [
    'Alliance',
    'Horde'
  ],
  gender: [
    'None',
    'Male',
    'Female'
  ],
  race: [
    'None',
    'Human',
    'Orc',
    'Dwarf',
    'Night Elf',
    'Undead',
    'Tauren',
    'Gnome',
    'Troll',
    'Goblin',
    'Blood Elf',
    'Draenei',
    'Fel Orc',
    'Naga',
    'Broken',
    'Skeleton',
    'Vrykul',
    'Tuskarr',
    'Forest Troll',
    'Taunka',
    'Northrend Skeleton',
    'Ice Troll',
    'Worgen',
    'Gilnean',
    'Pandaren',
    'Pandaren',
    'Pandaren',
    'Nightborn',
    'Highmountain Tauren',
    'Void Elf',
    'Lightforged Draenei',
    'Zandalari Troll',
    'Kul Tiran',
    'Human',
    'Dark Iron Dwarf',
    'Vulpera',
    'Mag\'har Orc'
  ],
  class: [
    'None',
    'Warrior',
    'Paladin',
    'Hunter',
    'Rogue',
    'Priest',
    'Death Knight',
    'Shaman',
    'Mage',
    'Warlock',
    'Druid',
    'Demon Hunter'
  ]
};

exports.exec = async (Bastion, message, args) => {
  try {
    if (!args.character || !args.realm) {
      /**
       * The command was ran with invalid parameters.
       * @fires commandUsage
       */
      return Bastion.emit('commandUsage', message, this.help);
    }

    args.region = args.region.toLowerCase();
    let locale = args.region === 'us' ? 'en-us' : 'en-gb';

    let options = {
      url: `https://${args.region}.api.battle.net/wow/character/${args.realm}/${args.character}`,
      qs: {
        fields: 'guild,items',
        locale: locale,
        apikey: Bastion.credentials.battleNetApiKey
      },
      json: true
    };

    let response = await request(options);

    let factionIcon = `https://worldofwarcraft.akamaized.net/static/components/Logo/Logo-${response.faction === 0 ? 'alliance' : 'horde'}.png`;
    let avatar = `https://render-${args.region}.worldofwarcraft.com/character/${response.thumbnail}`;

    let stats = [
      {
        name: 'Character',
        value: `${response.level} - ${wowConstants.race[response.race]} - ${wowConstants.class[response.class]}`
      },
      {
        name: 'Achievement Points',
        value: response.achievementPoints,
        inline: true
      },
      {
        name: 'Item Level',
        value: response.items.averageItemLevel,
        inline: true
      }
    ];

    if (response.guild) {
      stats.push(
        {
          name: 'Guild',
          value: response.guild.name,
          inline: true
        }
      );
    }

    message.channel.send({
      embed: {
        color: Bastion.colors.BLUE,
        author: {
          name: response.name,
          url: `https://worldofwarcraft.com/${locale}/character/${args.realm}/${args.character}`,
          icon_url: factionIcon
        },
        fields: stats,
        thumbnail: {
          url: avatar
        },
        footer: {
          text: 'Powered by Blizzard Battle.net'
        }
      }
    }).catch(e => {
      Bastion.log.error(e);
    });

  }
  catch (e) {
    if (e.name === 'StatusCodeError') {
      if (e.statusCode === 404) {
        return Bastion.emit('error', '', Bastion.strings.error(message.guild.language, 'notFound', true, 'player'), message.channel);
      }
      return Bastion.emit('error', e.statusCode, e.error.message, message.channel);
    }
    Bastion.log.error(e);
  }
};

exports.config = {
  aliases: [],
  enabled: true,
  argsDefinitions: [
    { name: 'character', type: String, defaultOption: true },
    { name: 'realm', type: String, alias: 'r' },
    { name: 'region', type: String, defaultValue: 'us' }
  ]
};

exports.help = {
  name: 'wow',
  description: 'Get stats of any World of Warcraft character from the Armory.',
  botPermission: '',
  userTextPermission: '',
  userVoicePermission: '',
  usage: 'wow <CHARACTER> <--realm REALM> [-region <REGION>]',
  example: [
    'wow Fallken --realm Burning-Blade --region EU'
  ]
};
<|endoftext|>


<commit_before>hqDefine('registration/js/login', [
    'jquery',
    'blazy/blazy',
    'analytix/js/kissmetrix',
    'hqwebapp/js/captcha', // shows captcha
], function (
    $,
    blazy,
    kissmetrics
) {
    $(function () {
        // Blazy for loading images asynchronously
        // Usage: specify the b-lazy class on an element and adding the path
        // to the image in data-src="{% static 'path/to/image.jpg' %}"
        new blazy({
            container: 'body',
        });

        // populate username field if set in the query string
        const urlParams = new URLSearchParams(window.location.search);
        const username = urlParams.get('username');
        if (username) {
            var usernameElt = document.getElementById('id_auth-username');
            if (usernameElt) {
                usernameElt.value = username;
            }
        }

        kissmetrics.whenReadyAlways(function () {

            $('#cta-form-get-demo-button-body').click(function () {
                kissmetrics.track.event("Demo Workflow - Body Button Clicked");
            });

            $('#cta-form-get-demo-button-header').click(function () {
                kissmetrics.track.event("Demo Workflow - Header Button Clicked");
            });
        });
    });

});
<commit_msg>initialize the login controller only when enforcing SSO is enabled<commit_after>hqDefine('registration/js/login', [
    'jquery',
    'blazy/blazy',
    'analytix/js/kissmetrix',
    'registration/js/user_login_form',
    'hqwebapp/js/initial_page_data',
    'hqwebapp/js/captcha', // shows captcha
], function (
    $,
    blazy,
    kissmetrics,
    userLoginForm,
    initialPageData
) {
    $(function () {
        // Blazy for loading images asynchronously
        // Usage: specify the b-lazy class on an element and adding the path
        // to the image in data-src="{% static 'path/to/image.jpg' %}"
        new blazy({
            container: 'body',
        });

        // populate username field if set in the query string
        const urlParams = new URLSearchParams(window.location.search);
        const username = urlParams.get('username');
        if (username) {
            var usernameElt = document.getElementById('id_auth-username');
            if (usernameElt) {
                usernameElt.value = username;
            }
        }

        if (initialPageData.get('enforce_sso_login')) {
            let $passwordField = $('#id_auth-password');
            let loginController = userLoginForm.loginController({
                initialUsername: $('#id_auth-username').val(),
                passwordField: $passwordField,
                passwordFormGroup: $passwordField.closest('.form-group'),
            });
            $('#user-login-form').koApplyBindings(loginController);
            loginController.init();
        }

        kissmetrics.whenReadyAlways(function () {

            $('#cta-form-get-demo-button-body').click(function () {
                kissmetrics.track.event("Demo Workflow - Body Button Clicked");
            });

            $('#cta-form-get-demo-button-header').click(function () {
                kissmetrics.track.event("Demo Workflow - Header Button Clicked");
            });
        });
    });

});
<|endoftext|>


<commit_before>var later  = require('later');
var moment = require('moment');
var sugar  = require('sugar');
var hash = require('json-hash');

require("moment-duration-format");

var bot;

var minimumCreationDelay = 0;
var minimumInterval = 60;
var noCommands = false;
var digestLength = 8;

// digest -> schedule
var schedules = {};
// { 'id': <digest of data before adding timer and stuff>,
//   'created': <created timestamp>,
//   'schedule:' <later schedule>,
//   'blame': <owner>,
//   'calls': <number of calls left>,
//   'channel': <channel to print in>,
//   'command': <what to say / execute>,
//   'timer': <our timer instance if any>
//

// m1 - m2 so pass in backwards for positive
function getDifference(m1,m2) {
  return moment.duration(moment(m1).diff(moment(m2), "milliseconds"));
}

function getAverageInterval(s) {
  var numSamples = 5;
  var next = later.schedule(s).next(numSamples+2); // skip NEXT occurrence
  var totalSeconds = 0;

  next.forEach(function(e,i,d) {
    e = moment(e);
  });

  for(i=1; i<=numSamples; i++)
  {
    totalSeconds += parseInt(getDifference(next[i+1], next[i]).format("s"));
  }

  return totalSeconds/numSamples;
}

function writeSchedule(data) {
  bot.writeDataFile("later.json",
      JSON.stringify(schedules), function(err) {
        if(err) console.log("Error writing command file: " + err);
  });
}

function newSchedule(data) {
  var s;

  // reminders use cron
  if(data['calls'] == -1)
  {
    try {
      s = later.parse.text(data.schedule);
    } catch (ex) {
      return ex;
    }

    if(typeof(s) == "number" || s.error >= 0 )
    {
      var m = "Provided schedule query doesn't parse:\n";
      var offset = 0;

      if( typeof(s) == "number")
        offset = s;
      else
        offset = s.error;

      m += "    " + data.schedule.slice(0,offset) + '\u032D'
           + data.schedule.slice(offset,data.schedule.length);
      return m;
    }

    data.schedule = s;
  }
  else
    s = data.schedule = later.parse.cron(data.schedule);

  // check that command is valid
  //   - check if commands are disabled
  //   - doesn't call schedule
  //   - doesn't call a dumbcommand which calls schedule
  //     * checked via perceived caller
  //   - isn't too close to last call
  // check execution frequency is below some minimum (config)

  if(noCommands && data.command.substring(0, bot.config.commandPrefix.length)
      == bot.config.commandPrefix)
    return "Commands are disabled";

  if(data.command.match(/^!schedule/))
    return data.blame + ": fuck you";

  if(data.blame == bot.client.nick)
    return "Cannot call schedule recursively.";

  if(minimumCreationDelay > 0 && schedules.length > 0
      && moment().diff(schedules.slice(-1)[0].created, 'seconds') <= minimumCreationDelay)
    return "Schedule creation is rate limited. Please wait at least " + minimumCreationDelay
           + " seconds between schedule creation.";

  if(data.calls >= 5 || data.calls == -1)
  {
    //s.range should be the range, in seconds, between schedule calls...
    //but it's undefined.
    var interval = getAverageInterval(s);
    if(minimumInterval > 0 && interval < minimumInterval)
      return "Parsed average frequency of " + moment.duration(interval, "seconds").format() + " is below the minimum "
             + "interval of " + minimumInterval + " seconds";
  }

  // Handle private message schedules. These show as channel being us--- it should
  // be them.
  if(data.channel == bot.client.nick)
    data.channel = data.blame;

  // get digest for our data
  var digest = hash.digest(data).substr(0,digestLength);
  data.id = digest;
  data.timer = null;
  schedules[digest] = data;

  registerCommand(data);
  writeSchedule(schedules);

  var next = getDifference(later.schedule(s).next(1), moment()).format();
  if(next.match(/^\d+$/))
    next = next + " seconds"; //FIXME: vv breaks

  if(next == "0") //FIXME: this usually isn't true. It may say now, but it never makes it.
    return "Created " + digest.substr(0,8) + ", first execution is now.";
  else
    return "Created " + digest.substr(0,8) + ", the first execution is in " + next + ".";
}

function registerCommand(data) {
  var command;

  command = function() {
    var s = schedules[data.id];

    // deactivate and delete command
    if(data.calls == 0)
    {
      if(s.timer != null)
        s.timer.clear();

      delete schedules[data.id];
      writeSchedule(schedules);
      return; // command expired
    }

    if(data.target != undefined)
      bot.sayTo(data.channel, data.target + ': ' + data.command);
    else
      bot.sayTo(data.channel, data.command);
    bot.client.emit('message', bot.client.nick, data.channel,
      data.command, data.command);

    if(s.calls != -1)
    {
      s.calls -= 1;

      // write changes
      writeSchedule(schedules);
    }
  };

  schedules[data.id].timer = later.setInterval(command, data.schedule);
}

module.exports.init = function(b) {
  bot = b;
  bot.readDataFile("later.json", function(err, data) {
    console.log("Read data file w/ error '" + err + "'");
    if(err) {
      console.log("Initializing later.json");
      schedules = {};
      writeSchedule(schedules);
    } else {
      try {
        console.log("Parsing later.json...");
        schedules = JSON.parse(data);

        // process schedules
        Object.keys(schedules).forEach(function(i) {
          var e = schedules[i];

          e.created = moment(e.created);
          e.timer = null;
          registerCommand(e);
        });
      } catch(ex) {
        console.log("Error parsing: " + ex);
        console.log("Corrupted later.json for schedule! Resetting file...");
        schedules = {};
        writeSchedule(schedules);
      }
    }
  });

  bot.getConfig("schedule.json", function(err, conf) {
    if(!err) {
      minimumInterval      = conf.minimum_interval;
      minimumCreationDelay = conf.minimum_creation_delay;
      noCommands           = conf.no_commands;
      digestLength         = conf.digest_length;
    }
  });
};

module.exports.commands = {
  remindme: null, // alias for remind with target from
  remind: function(r, parts, reply, command, from, to, text, raw) {
            if(parts.length < 2)
            {
              reply("Usage: !remind ([target]) \"timeframe\" \"text\"");
              reply("       Optional target defaults no one (channel wide).");
              return;
            }

            var target, schedule, command;

            if(parts.length == 2)
            {
              schedule = parts[0];
              command  = parts[1];
            }
            else
            {
              target   = parts[0];
              schedule = parts[1];
              command  = parts[2];
            }

            if(schedule.match(/in|at|after|tomorrow|next|from/i))
            {
              // use sugar to handle relative dates--- moment/later sucks at this
              var s = Date.create(schedule);
              if(!s.isValid())
                return reply("Invalid relative date provided.");
              if(!s.isFuture())
                return reply("Cannot make reminders for past dates/times.");

              // massage into a cron format
              schedule = s.format('{mm} {hh} {dd} {mm} *');
            }

            reply(newSchedule({
              'blame': from,
              'created': new moment(),
              'schedule': schedule,
              'command': command,
              'channel': to,
              'calls': 1,
              'target': target
            }));
          },
  schedule: {
    _default: function(x,y,reply) {
      reply("Usage: !schedule [<add>|<remove>|<list>|<help>] [arguments]");
      reply("       Also see !schedule help <subcommand> for more details");
    },
    help: {
      _default: null, // to be aliased later to ^
      add: function(x,y,reply) {
        reply("Usage: !schedule add \"timeframe\" \"command or text\"");
        reply("       Timeframe syntax is here: http://bunkat.github.io/later/parsers.html#text");
        reply("       If a valid command isn't specified, it is treated as text to print.");
      },
      remove: function(x,y,reply) {
        reply("Usage: !schedule remove [digest]");
        reply("       Removes the schedule specified by the digest.");
      },
      list: function(x,y,reply) {
        reply("Usage: !schedule list [offset=0]");
        reply("       Provides a list of schedules, in pages, from the specified offset of 0.");
      },
    },
    add: function(r, parts, reply, command, from, to, text, raw) {
      if(parts.length !== 2) return reply("add must have *exactly* two arguments");

      reply(newSchedule({
        'blame': from,
        'created': new moment(),
        'schedule': parts[0],
        'command': parts[1],
        'channel': to,
        'calls': -1
      }));
    },
    list: function(x, parts, reply, command, from, to) {
      var offset = 0;
      var count  = 5;
      var ci     = count;
      var oi     = 0;
      var size = Object.keys(schedules).length;

      if(parts.length >= 1)
        oi = offset = parseInt(parts[0]);

      if(offset >= size)
        return bot.sayTo(from, "There are only " + size + " schedules");

      if(size == 0)
        return bot.sayTo(from, "There are no schedules.");

      if(count+offset > size)
        count = size-offset;

      // count and pick
      Object.keys(schedules).forEach(function(digest) {
        if(oi > 0)
        {
          oi -= 1;
          return;
        }

        if(ci == 0)
          return;

        var e = schedules[digest];
        var channel = e.channel;

        if(!channel.match(/^(#|&)/))
          channel = "@"+channel;

        bot.sayTo(from, digest + "     " + e.blame + "     "
                + e.created.format("ddd MM/DD/YY HH:mm:ss Z")
                + "     " + channel);

        var message =  "     ";
        if( e.command.match(/^!/) )
          message += "command: " + e.command;
        else
          message += "say: " + e.command;
        bot.sayTo(from, message);

        if(ci > 0)
          ci -= 1;
      });

      var message = "Displayed schedules " + (offset+1) + "-";
      message += (offset+count) + " of " + size;

      bot.sayTo(from, message);
    },

    remove: function(r, parts, reply) {
      if(parts.length !== 1) return reply("remove must have *exactly* one argument");

      var digest = parts[0];

      if(schedules.length == 0)
        return reply("There are no schedules to delete.");

      if(schedules[digest] == undefined)
        return reply("There is no schedule for " + digest);

      var s = schedules[digest];

      if(s.timer != null)
        s.timer.clear();

      reply("Removed schedule by \"" + s.blame + "\" which runs \"" + s.command + "\"" );

      delete schedules[digest];
      writeSchedule(schedules);
    },

  }
};
// alias default for help to default
module.exports.commands.schedule.help._default
  = module.exports.commands.schedule._default;

// remindme is just remind with our name in the first part
module.exports.commands.remindme
  = function(r, parts, reply, command, from, to, text, raw) {
    parts.unshift(from);
    module.exports.commands.remind(r, parts, reply, command, from, to, text, raw);
  };
<commit_msg>!schedule add filters fixed/message cleanup<commit_after>var later  = require('later');
var moment = require('moment');
var sugar  = require('sugar');
var hash = require('json-hash');

require("moment-duration-format");

var bot;

var minimumCreationDelay = 0;
var minimumInterval = 60;
var noCommands = false;
var digestLength = 8;

// digest -> schedule
var schedules = {};
var lastSchedule = null;
// { 'id': <digest of data before adding timer and stuff>,
//   'created': <created timestamp>,
//   'schedule:' <later schedule>,
//   'blame': <owner>,
//   'calls': <number of calls left>,
//   'channel': <channel to print in>,
//   'command': <what to say / execute>,
//   'timer': <our timer instance if any>
//

// m1 - m2 so pass in backwards for positive
function getDifference(m1,m2) {
  return moment.duration(moment(m1).diff(moment(m2), "milliseconds"));
}

function getAverageInterval(s) {
  var numSamples = 5;
  var next = later.schedule(s).next(numSamples+2); // skip NEXT occurrence
  var totalSeconds = 0;

  next.forEach(function(e,i,d) {
    e = moment(e);
  });

  for(i=1; i<=numSamples; i++)
  {
    totalSeconds += parseInt(getDifference(next[i+1], next[i]).format("s"));
  }

  return totalSeconds/numSamples;
}

function writeSchedule(data) {
  bot.writeDataFile("later.json",
      JSON.stringify(schedules), function(err) {
        if(err) console.log("Error writing command file: " + err);
  });
}

function newSchedule(data) {
  var s;

  // reminders use cron
  if(data['calls'] == -1)
  {
    try {
      s = later.parse.text(data.schedule);
    } catch (ex) {
      return ex;
    }

    if(typeof(s) == "number" || s.error >= 0 )
    {
      var m = "Provided schedule query doesn't parse:\n";
      var offset = 0;

      if( typeof(s) == "number")
        offset = s;
      else
        offset = s.error;

      m += "    " + data.schedule.slice(0,offset) + '\u032D'
           + data.schedule.slice(offset,data.schedule.length);
      return m;
    }

    data.schedule = s;
  }
  else
    s = data.schedule = later.parse.cron(data.schedule);

  // check that command is valid
  //   - check if commands are disabled
  //   - doesn't call schedule
  //   - doesn't call a dumbcommand which calls schedule
  //     * checked via perceived caller
  //   - isn't too close to last call
  // check execution frequency is below some minimum (config)

  if(noCommands && data.command.substring(0, bot.config.commandPrefix.length)
      == bot.config.commandPrefix)
    return "Commands are disabled";

  if(data.command.match(/^!schedule/))
    return data.blame + ": fuck you";

  if(data.blame == bot.client.nick)
    return "Cannot call schedule recursively.";

  if(minimumCreationDelay > 0 && schedules.length > 0 && lastSchedule != 0
      && moment().diff(schedules[lastSchedule].created, 'seconds') <= minimumCreationDelay)
    return "Schedule creation is rate limited. Please wait at least " + minimumCreationDelay
           + " seconds between schedule creation.";

  if(data.calls >= 5 || data.calls == -1)
  {
    //s.range should be the range, in seconds, between schedule calls...
    //but it's undefined.
    var interval = getAverageInterval(s);
    if(minimumInterval > 0 && interval < minimumInterval)
      return "Parsed average frequency of " + moment.duration(interval, "seconds").format() + " is below the minimum "
             + "interval of " + minimumInterval + " seconds";
  }

  // Handle private message schedules. These show as channel being us--- it should
  // be them.
  if(data.channel == bot.client.nick)
    data.channel = data.blame;

  // get digest for our data
  var digest = hash.digest(data).substr(0,digestLength);
  data.id = digest;
  data.timer = null;
  schedules[digest] = data;

  lasSchedule = digest;

  registerCommand(data);
  writeSchedule(schedules);

  var next = getDifference(later.schedule(s).next(1), moment()).format();
  if(next == "0")
    next = getDifference(later.schedule(s).next(2)[1], moment()).format();

  if(next.match(/^\d+$/))
    next = next + " seconds";

  return "Created " + digest + ", the first execution is in " + next + ".";
}

function registerCommand(data) {
  var command;

  command = function() {
    var s = schedules[data.id];

    // deactivate and delete command
    if(data.calls == 0)
    {
      if(s.timer != null)
        s.timer.clear();

      delete schedules[data.id];
      writeSchedule(schedules);
      return; // command expired
    }

    if(data.target != undefined)
      bot.sayTo(data.channel, data.target + ': ' + data.command);
    else
      bot.sayTo(data.channel, data.command);
    bot.client.emit('message', bot.client.nick, data.channel,
      data.command, data.command);

    if(s.calls != -1)
    {
      s.calls -= 1;

      // write changes
      writeSchedule(schedules);
    }
  };

  schedules[data.id].timer = later.setInterval(command, data.schedule);
}

module.exports.init = function(b) {
  bot = b;
  bot.readDataFile("later.json", function(err, data) {
    console.log("Read data file w/ error '" + err + "'");
    if(err) {
      console.log("Initializing later.json");
      schedules = {};
      writeSchedule(schedules);
    } else {
      try {
        console.log("Parsing later.json...");
        schedules = JSON.parse(data);

        // process schedules
        Object.keys(schedules).forEach(function(i) {
          var e = schedules[i];

          e.created = moment(e.created);
          e.timer = null;
          registerCommand(e);
        });
      } catch(ex) {
        console.log("Error parsing: " + ex);
        console.log("Corrupted later.json for schedule! Resetting file...");
        schedules = {};
        writeSchedule(schedules);
      }
    }
  });

  bot.getConfig("schedule.json", function(err, conf) {
    if(!err) {
      minimumInterval      = conf.minimum_interval;
      minimumCreationDelay = conf.minimum_creation_delay;
      noCommands           = conf.no_commands;
      digestLength         = conf.digest_length;
    }
  });
};

module.exports.commands = {
  remindme: null, // alias for remind with target from
  remind: function(r, parts, reply, command, from, to, text, raw) {
            if(parts.length < 2)
            {
              reply("Usage: !remind ([target]) \"timeframe\" \"text\"");
              reply("       Optional target defaults no one (channel wide).");
              return;
            }

            var target, schedule, command;

            if(parts.length == 2)
            {
              schedule = parts[0];
              command  = parts[1];
            }
            else
            {
              target   = parts[0];
              schedule = parts[1];
              command  = parts[2];
            }

            if(schedule.match(/in|at|after|tomorrow|next|from/i))
            {
              // use sugar to handle relative dates--- moment/later sucks at this
              var s = Date.create(schedule);
              if(!s.isValid())
                return reply("Invalid relative date provided.");
              if(!s.isFuture())
                return reply("Cannot make reminders for past dates/times.");

              // massage into a cron format
              schedule = s.format('{mm} {hh} {dd} {mm} *');
            }

            reply(newSchedule({
              'blame': from,
              'created': new moment(),
              'schedule': schedule,
              'command': command,
              'channel': to,
              'calls': 1,
              'target': target
            }));
          },
  schedule: {
    _default: function(x,y,reply) {
      reply("Usage: !schedule [<add>|<remove>|<list>|<help>] [arguments]");
      reply("       Also see !schedule help <subcommand> for more details");
    },
    help: {
      _default: null, // to be aliased later to ^
      add: function(x,y,reply) {
        reply("Usage: !schedule add \"timeframe\" \"command or text\"");
        reply("       Timeframe syntax is here: http://bunkat.github.io/later/parsers.html#text");
        reply("       If a valid command isn't specified, it is treated as text to print.");
      },
      remove: function(x,y,reply) {
        reply("Usage: !schedule remove [digest]");
        reply("       Removes the schedule specified by the digest.");
      },
      list: function(x,y,reply) {
        reply("Usage: !schedule list [offset=0]");
        reply("       Provides a list of schedules, in pages, from the specified offset of 0.");
      },
    },
    add: function(r, parts, reply, command, from, to, text, raw) {
      if(parts.length !== 2) return reply("add must have *exactly* two arguments");

      reply(newSchedule({
        'blame': from,
        'created': new moment(),
        'schedule': parts[0],
        'command': parts[1],
        'channel': to,
        'calls': -1
      }));
    },
    list: function(x, parts, reply, command, from, to) {
      var offset = 0;
      var count  = 5;
      var ci     = count;
      var oi     = 0;
      var size = Object.keys(schedules).length;

      if(parts.length >= 1)
        oi = offset = parseInt(parts[0]);

      if(offset >= size)
        return bot.sayTo(from, "There are only " + size + " schedules");

      if(size == 0)
        return bot.sayTo(from, "There are no schedules.");

      if(count+offset > size)
        count = size-offset;

      // count and pick
      Object.keys(schedules).forEach(function(digest) {
        if(oi > 0)
        {
          oi -= 1;
          return;
        }

        if(ci == 0)
          return;

        var e = schedules[digest];
        var channel = e.channel;

        if(!channel.match(/^(#|&)/))
          channel = "@"+channel;

        bot.sayTo(from, digest + "     " + e.blame + "     "
                + e.created.format("ddd MM/DD/YY HH:mm:ss Z")
                + "     " + channel);

        var message =  "     ";
        if( e.command.match(/^!/) )
          message += "command: " + e.command;
        else
          message += "say: " + e.command;
        bot.sayTo(from, message);

        if(ci > 0)
          ci -= 1;
      });

      var message = "Displayed schedules " + (offset+1) + "-";
      message += (offset+count) + " of " + size;

      bot.sayTo(from, message);
    },

    remove: function(r, parts, reply) {
      if(parts.length !== 1) return reply("remove must have *exactly* one argument");

      var digest = parts[0];

      if(schedules.length == 0)
        return reply("There are no schedules to delete.");

      if(schedules[digest] == undefined)
        return reply("There is no schedule for " + digest);

      var s = schedules[digest];

      if(s.timer != null)
        s.timer.clear();

      reply("Removed schedule by \"" + s.blame + "\" which runs \"" + s.command + "\"" );

      delete schedules[digest];
      writeSchedule(schedules);
    },

  }
};
// alias default for help to default
module.exports.commands.schedule.help._default
  = module.exports.commands.schedule._default;

// remindme is just remind with our name in the first part
module.exports.commands.remindme
  = function(r, parts, reply, command, from, to, text, raw) {
    parts.unshift(from);
    module.exports.commands.remind(r, parts, reply, command, from, to, text, raw);
  };
<|endoftext|>


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

    //// JavaScript Code ////

    //// Angular Code ////
    angular.module('codeine')
        .constant('HelpConstants', {
            "howToRunHelp" : "Select a command execution strategy.

'Single' - allows command only on a single node at a time.

" +
                "'Immediately' - run command on nodes without delay.

" +
                "'Progressive' - run command on nodes gradually at configured rate.

",
            "monitorHelp" : "Select which nodes to show.

'Any Alert' - show nodes with alerts (no matter which monitor).

" +
                "'All Nodes' - also show nodes without alerts.

" +
                "Specific Monitor - show only nodes with alerts from that monitor.",
            "commandNodesFilterHelp" : "Select the nodes on which the command will be executed on nodes of checked versiosns.

'All Selected' - All the nodes in the selected versions.

" +
                "'Only Failing' - Only on nodes that have failing monitors.

" +
                "'Limited Number' - the command will be executed on this number of nodes from all selected versions (total).

",
            "ratioHelp" : "The rate for command execution.

'Linear' - Spread nodes evenly in the specified time period.

" +
                "'Exponential' - execute on one node first, then increase number of nodes exponentially (1, 2, 4, ...).

",
            "discardOldCommandsEnabledHelp" : "Controls the disk consumption of Codeine by managing how long you'd like to keep records of the commands (such as console output)",
            "discardMaxCommandsHelp" : "You can have Codeine make sure that it only maintains up to N command records. If a new command is started, the oldest record will be simply removed.",
            "discardMaxDaysHelp" : "You can have Codeine delete a record if it reaches a certain age (for example, 7 days old).",
            "projectEnvVarsHelp" : "Set environment variables that will be available for monitors and commands execution environment.",
            "command_patameter_description_Help" : "A description that will be displayed as a tooltip to the user executing the command.",
            "command_patameter_values_Help" : "Comma-delimited list of parameters for user selection. For example: 1,2,3.",
            "command_patameter_name_Help" : "The name of the parameter, will also be used as environment variable in the executing command.",
            "command_patameter_default_val_Help" : "The default value that will be suggested to the user when executing the command.",
            "command_patameter_validation_expression_Help" : "A regular expression that will validate the parameter in the UI.

" +
                "For example: \\S* - a word, \\d - one digit number, .* - not empty (like required). add ^ and $ to force the entire expression.",
            "concurrencyHelp" : "On how many nodes to execute the command in parallel.",
            "stopOnErrorHelp" : "Check for errors during command execution, and stop commanding more nodes if the percent of nodes with failing monitors is greater than the configured value.",
            "durationHelp" : "The time period for the command execution. Codeine will spread execution on nodes across that time based on the ratio.",
            "preventOverrideHelp" : "If checked, when executing the command, the user will not be able to change the configuration below.",
            "commandsConfigureHelp" : "Configure commands to execute on node. Such command can upgrade to new version or restart the process.",
            "commandHistoryHelp" : "All the currently running and finished commands of the selected project",
            "commandExecutorHelp" : "All the currently running commands on the Codeine server from all projects",
            "nodeTagsHelp" : "Tags on nodes.
Press a tag to filter only nodes with that tag (blue). You can select the logic operator between tags in the toogle button (OR, AND)",
            "emailHelp" : "Codeine will notify on failing monitors to configured users. " +
                "

'Immediately' - send mail immediately after a monitor has failed." +
                "

'Hourly'/'Daily' - send mail after a monitor has failed, Codeine will aggregate the notifications to send them at the specified frequency.",
            "nodesHelp" : "Configure how Codeine detects nodes for the project." +
                "

'Configuration' - nodes are statically configured." +
                "

'Reporter' - nodes are dynamically created when they report to Codeine via the Codeine API." +
                "

'Script' - nodes are dynamically discovered by a script.",
            "discoveryScriptHelp" : "Type a shell script to discover nodes for the project. The script runs on each individual Codeine peer.

" +
                "The script output should be written to a file in a path specified in the environment variable CODEINE_OUTPUT_FILE in the following json format:
[{name:\"<name>\", alias:\"<alias>\"}, ...]

" +
                "The script can use the following environment variables in execution: CODEINE_PROJECT_NAME",
            "tagsdiscoveryScriptHelp" : "Type a shell script to discover tags for the project's nodes. The script runs on each individual Codeine peer.

" +
                "The script output should be written to a file in a path specified in the environment variable CODEINE_OUTPUT_FILE in the following json format:
[\"<tag-string>\", ...]

" +
                "The script can use the following environment variables in execution: CODEINE_PROJECT_NAME, CODEINE_NODE_NAME, CODEINE_NODE_ALIAS, CODEINE_HOST, CODEINE_PORT",
            "commandScriptHelp" : "Type a shell script to execute on nodes.

" +
                "Parameters will be provided to the script as Environment Variables.
" +
                "In addition, the script can use the following environment variables in execution: CODEINE_PROJECT_NAME, CODEINE_NODE_NAME, CODEINE_NODE_ALIAS, CODEINE_NODE_TAGS, CODEINE_HOST, CODEINE_PORT",
            "monitorsConfigureHelp" : "Monitors used to detect malfunctions and errors on the running nodes.

" +
                "Codeine runs the monitors periodically on all nodes, and notifies when a node fails.",
            "collectorsConfigureHelp" : "Collectors used to detect malfunctions and errors on the running nodes, and also to provide information about nodes status.

" +
                "Codeine runs the collectors periodically on all nodes, and notifies when a node fails.",
            "versionHelp" : "A version will be reported by each node.",
            "minIntervalHelp" : "The minimal interval that Codeine will run the collector.

" +
                "This option is good when the collector execution itself is time consuming or may cause load on the node itself and hence should not executed at high frequency.

" +
                "Specify an integer in minutes. Default is less than a minute.",
            "commandTimeoutHelp" : "The maximum time in minutes command will be allowed to run on node, before termination",
            "credentialsHelp" : "The user that will be used to execute the configured script.

 This feature does not work for most cases right now.",
            "notificationsEnabledHelp" : "If checked, emails will be sent to the configured users, when this collector starts to fail.",
            "monitorScriptHelp" : "Type a shell script to monitor the desired functionality of the node.

By convention, monitors are marked as failed if their exit status is non-zero.

" +
                "The script can use the following environment variables in execution: CODEINE_PROJECT_NAME, CODEINE_NODE_NAME, CODEINE_NODE_ALIAS, CODEINE_NODE_TAGS, CODEINE_HOST, CODEINE_PORT.",
            "collectorScriptHelp" : "Type a shell script to collector the desired functionality of the node.

By convention, collectors are marked as failed if their exit status is non-zero.

" +
                "The script can use the following environment variables in execution: CODEINE_PROJECT_NAME, CODEINE_NODE_NAME, CODEINE_NODE_ALIAS, CODEINE_NODE_TAGS, CODEINE_HOST, CODEINE_PORT.
"+
                "The collector output is expected in a file, that its name is in CODEINE_OUTPUT_FILE environment variable.",
            "versionScriptHelp" : "Type a shell script to specify the node's version.

" +
                "The script output should be written to a file in a path specified in the environment variable CODEINE_OUTPUT_FILE and should contain the version number (e.g.: 1.0.3).

" +
                "The script can use the following environment variables in execution: CODEINE_PROJECT_NAME, CODEINE_NODE_NAME, CODEINE_NODE_ALIAS, CODEINE_NODE_TAGS, CODEINE_HOST, CODEINE_PORT.",
            "tabsConfigureHelp" : "Enables a separate view for a group of projects in the main page of Codeine.",
            "serverNameHelp" : "A name to be displayed when accessing UI or in mail messages",
            "webServerHostHelp" : "Full host name of the web server",
            "webServerPortHelp" : "Port of the web server (requires restart)",
            "directory_hostHelp" : "Full host name of the directory server",
            "directory_portHelp" : "Port of the directory server",
            "mysql_hostHelp" : "Full host name of the MySQL server",
            "MysqlPortConfigureHelp" : "Port of the MySQL server",
            "MysqlDirConfigureHelp" : "A directory for MySQL data",
            "MysqlBinDirConfigureHelp" : "The MySQL binaries directory (mysqld, ...)",
            "AdminMailConfigureHelp" : "The Codeine admin's email",
            "AuthenticationMethodConfigureHelp" : "The way Codeine will authenticate users.
Disabled - No authentication
Builtin - Codeine internal database will store users
WindowsCredentials - Using spnego to authenticate users
",
            "rolesConfigureHelp" : "Internal domains for Windows credentials",
            "permissionsConfigureHelp" : "Configure access privileges for the users of Codeine.",
            "tabExpressionHelp" : "Comma-separated list of project names or regular expressions",
            "adminPermissionsConfigureHelp" : "Is administrator of Codeine",
            "viewProjectPermissionsConfigureHelp" : "List of projects (or regular expression) that the user can view only",
            "commandProjectPermissionsConfigureHelp" : "List of projects (or regular expression) for which the user can view and execute commands",
            "configProjectPermissionsConfigureHelp" : "List of projects (or regular expression) for which the user has full privileges",
            "ProjectPermissionsConfigureSection" : "Configure the users/groups and their permissions on this project",
            "ProjectPermissionsConfigureSection_read" : "Allow to view project nodes",
            "ProjectPermissionsConfigureSection_command" : "List of nodes (or regular expression) for which the user can execute commands",
            "ProjectPermissionsConfigureSection_configure" : "Allow to configure project (this page)",
            "MysqlUserConfigureHelp" : "Username for MySQL connections",
            "MysqlPasswordConfigureHelp" : "Password for MySQL connections",
            "MysqlManagedByCodeineConfigureHelp" : "Codeine is responsible to start/stop MySQL server instance.",
            "codeineConfigureHelp" : "Here the admin can configure all the Codeine global settings.
Some changes might require a restart.",
            "codeineAdminHelp" : "Here the admin can perform operations on codeine.",
            "projectDescriptionHelp" : "Description is a convenient way to let users know what this project is about.",
            "commandDescriptionHelp" : "Description is a convenient way to let users know what this command is doing and for what purpose it should be used.",
            "projectIncludeCommandsFromHelp" : "List here projects to include their configured commands in this project",
            "typeHelp" : "Select a type for the parameter for command execution",
            "collectorDescriptionHelp" : "Description will be displayed to explain what the collector is doing",
            "safeGuardHelp" : "Will take extra measures to validate the correct command executed (to prevent human errors)",
            "blockAfterExecutionHelp" : "This option is appropriate for asynchronous commands that runs in background.
Entering number here will cause the command to wait the specified time in minutes after execution, and before proceeding to next node.",
            "nodesPerMinuteHelp" : "An estimation of the execution frequency",
            "collectorTypeHelp" : "Select a type for collector. types are:
"+
                "
String - text value that will be displayed to user"+
                "
Number - numerical value that will be displayed to user and can be a base for more calculations"+
                "
Monitor - indicates failures or success by the script exit status (zero is success).",
            "operatingSystemHelp" : "Type of operating system for this project, will define the way scripts run in this project (shell/cmd)",
            "commandTags" : "Will filter commands menu based on the tags of"
                + " the selected nodes.
Only commands with nodes that apply to selected nodes tags will be displayed",
            "collectorsTags" : "The collector will run only on nodes that"
                + " has all specified tags"
        });

})(angular);
<commit_msg>fixed build<commit_after>(function (angular) {
    'use strict';

    //// JavaScript Code ////

    //// Angular Code ////
    angular.module('codeine')
        .constant('HelpConstants', {
            "howToRunHelp" : "Select a command execution strategy.

'Single' - allows command only on a single node at a time.

" +
                "'Immediately' - run command on nodes without delay.

" +
                "'Progressive' - run command on nodes gradually at configured rate.

",
            "monitorHelp" : "Select which nodes to show.

'Any Alert' - show nodes with alerts (no matter which monitor).

" +
                "'All Nodes' - also show nodes without alerts.

" +
                "Specific Monitor - show only nodes with alerts from that monitor.",
            "commandNodesFilterHelp" : "Select the nodes on which the command will be executed on nodes of checked versiosns.

'All Selected' - All the nodes in the selected versions.

" +
                "'Only Failing' - Only on nodes that have failing monitors.

" +
                "'Limited Number' - the command will be executed on this number of nodes from all selected versions (total).

",
            "ratioHelp" : "The rate for command execution.

'Linear' - Spread nodes evenly in the specified time period.

" +
                "'Exponential' - execute on one node first, then increase number of nodes exponentially (1, 2, 4, ...).

",
            "discardOldCommandsEnabledHelp" : "Controls the disk consumption of Codeine by managing how long you'd like to keep records of the commands (such as console output)",
            "discardMaxCommandsHelp" : "You can have Codeine make sure that it only maintains up to N command records. If a new command is started, the oldest record will be simply removed.",
            "discardMaxDaysHelp" : "You can have Codeine delete a record if it reaches a certain age (for example, 7 days old).",
            "projectEnvVarsHelp" : "Set environment variables that will be available for monitors and commands execution environment.",
            "command_patameter_description_Help" : "A description that will be displayed as a tooltip to the user executing the command.",
            "command_patameter_values_Help" : "Comma-delimited list of parameters for user selection. For example: 1,2,3.",
            "command_patameter_name_Help" : "The name of the parameter, will also be used as environment variable in the executing command.",
            "command_patameter_default_val_Help" : "The default value that will be suggested to the user when executing the command.",
            "command_patameter_validation_expression_Help" : "A regular expression that will validate the parameter in the UI.

" +
                "For example: \\S* - a word, \\d - one digit number, .* - not empty (like required). add ^ and $ to force the entire expression.",
            "concurrencyHelp" : "On how many nodes to execute the command in parallel.",
            "stopOnErrorHelp" : "Check for errors during command execution, and stop commanding more nodes if the percent of nodes with failing monitors is greater than the configured value.",
            "durationHelp" : "The time period for the command execution. Codeine will spread execution on nodes across that time based on the ratio.",
            "preventOverrideHelp" : "If checked, when executing the command, the user will not be able to change the configuration below.",
            "commandsConfigureHelp" : "Configure commands to execute on node. Such command can upgrade to new version or restart the process.",
            "commandHistoryHelp" : "All the currently running and finished commands of the selected project",
            "commandExecutorHelp" : "All the currently running commands on the Codeine server from all projects",
            "nodeTagsHelp" : "Tags on nodes.
Press a tag to filter only nodes with that tag (blue). You can select the logic operator between tags in the toogle button (OR, AND)",
            "emailHelp" : "Codeine will notify on failing monitors to configured users. " +
                "

'Immediately' - send mail immediately after a monitor has failed." +
                "

'Hourly'/'Daily' - send mail after a monitor has failed, Codeine will aggregate the notifications to send them at the specified frequency.",
            "nodesHelp" : "Configure how Codeine detects nodes for the project." +
                "

'Configuration' - nodes are statically configured." +
                "

'Reporter' - nodes are dynamically created when they report to Codeine via the Codeine API." +
                "

'Script' - nodes are dynamically discovered by a script.",
            "discoveryScriptHelp" : "Type a shell script to discover nodes for the project. The script runs on each individual Codeine peer.

" +
                "The script output should be written to a file in a path specified in the environment variable CODEINE_OUTPUT_FILE in the following json format:
[{name:\"<name>\", alias:\"<alias>\"}, ...]

" +
                "The script can use the following environment variables in execution: CODEINE_PROJECT_NAME",
            "tagsdiscoveryScriptHelp" : "Type a shell script to discover tags for the project's nodes. The script runs on each individual Codeine peer.

" +
                "The script output should be written to a file in a path specified in the environment variable CODEINE_OUTPUT_FILE in the following json format:
[\"<tag-string>\", ...]

" +
                "The script can use the following environment variables in execution: CODEINE_PROJECT_NAME, CODEINE_NODE_NAME, CODEINE_NODE_ALIAS, CODEINE_HOST, CODEINE_PORT",
            "commandScriptHelp" : "Type a shell script to execute on nodes.

" +
                "Parameters will be provided to the script as Environment Variables.
" +
                "In addition, the script can use the following environment variables in execution: CODEINE_PROJECT_NAME, CODEINE_NODE_NAME, CODEINE_NODE_ALIAS, CODEINE_NODE_TAGS, CODEINE_HOST, CODEINE_PORT",
            "monitorsConfigureHelp" : "Monitors used to detect malfunctions and errors on the running nodes.

" +
                "Codeine runs the monitors periodically on all nodes, and notifies when a node fails.",
            "collectorsConfigureHelp" : "Collectors used to detect malfunctions and errors on the running nodes, and also to provide information about nodes status.

" +
                "Codeine runs the collectors periodically on all nodes, and notifies when a node fails.",
            "versionHelp" : "A version will be reported by each node.",
            "minIntervalHelp" : "The minimal interval that Codeine will run the collector.

" +
                "This option is good when the collector execution itself is time consuming or may cause load on the node itself and hence should not executed at high frequency.

" +
                "Specify an integer in minutes. Default is less than a minute.",
            "commandTimeoutHelp" : "The maximum time in minutes command will be allowed to run on node, before termination",
            "credentialsHelp" : "The user that will be used to execute the configured script.

 This feature does not work for most cases right now.",
            "notificationsEnabledHelp" : "If checked, emails will be sent to the configured users, when this collector starts to fail.",
            "monitorScriptHelp" : "Type a shell script to monitor the desired functionality of the node.

By convention, monitors are marked as failed if their exit status is non-zero.

" +
                "The script can use the following environment variables in execution: CODEINE_PROJECT_NAME, CODEINE_NODE_NAME, CODEINE_NODE_ALIAS, CODEINE_NODE_TAGS, CODEINE_HOST, CODEINE_PORT.",
            "collectorScriptHelp" : "Type a shell script to collector the desired functionality of the node.

By convention, collectors are marked as failed if their exit status is non-zero.

" +
                "The script can use the following environment variables in execution: CODEINE_PROJECT_NAME, CODEINE_NODE_NAME, CODEINE_NODE_ALIAS, CODEINE_NODE_TAGS, CODEINE_HOST, CODEINE_PORT.
"+
                "The collector output is expected in a file, that its name is in CODEINE_OUTPUT_FILE environment variable.",
            "versionScriptHelp" : "Type a shell script to specify the node's version.

" +
                "The script output should be written to a file in a path specified in the environment variable CODEINE_OUTPUT_FILE and should contain the version number (e.g.: 1.0.3).

" +
                "The script can use the following environment variables in execution: CODEINE_PROJECT_NAME, CODEINE_NODE_NAME, CODEINE_NODE_ALIAS, CODEINE_NODE_TAGS, CODEINE_HOST, CODEINE_PORT.",
            "tabsConfigureHelp" : "Enables a separate view for a group of projects in the main page of Codeine.",
            "serverNameHelp" : "A name to be displayed when accessing UI or in mail messages",
            "webServerHostHelp" : "Full host name of the web server",
            "webServerPortHelp" : "Port of the web server (requires restart)",
            "directory_hostHelp" : "Full host name of the directory server",
            "directory_portHelp" : "Port of the directory server",
            "mysql_hostHelp" : "Full host name of the MySQL server",
            "MysqlPortConfigureHelp" : "Port of the MySQL server",
            "MysqlDirConfigureHelp" : "A directory for MySQL data",
            "MysqlBinDirConfigureHelp" : "The MySQL binaries directory (mysqld, ...)",
            "AdminMailConfigureHelp" : "The Codeine admin's email",
            "AuthenticationMethodConfigureHelp" : "The way Codeine will authenticate users.
Disabled - No authentication
Builtin - Codeine internal database will store users
WindowsCredentials - Using spnego to authenticate users
",
            "rolesConfigureHelp" : "Internal domains for Windows credentials",
            "permissionsConfigureHelp" : "Configure access privileges for the users of Codeine.",
            "tabExpressionHelp" : "Comma-separated list of project names or regular expressions",
            "adminPermissionsConfigureHelp" : "Is administrator of Codeine",
            "viewProjectPermissionsConfigureHelp" : "List of projects (or regular expression) that the user can view only",
            "commandProjectPermissionsConfigureHelp" : "List of projects (or regular expression) for which the user can view and execute commands",
            "configProjectPermissionsConfigureHelp" : "List of projects (or regular expression) for which the user has full privileges",
            "ProjectPermissionsConfigureSection" : "Configure the users/groups and their permissions on this project",
            "ProjectPermissionsConfigureSection_read" : "Allow to view project nodes",
            "ProjectPermissionsConfigureSection_command" : "List of nodes (or regular expression) for which the user can execute commands",
            "ProjectPermissionsConfigureSection_configure" : "Allow to configure project (this page)",
            "MysqlUserConfigureHelp" : "Username for MySQL connections",
            "MysqlPasswordConfigureHelp" : "Password for MySQL connections",
            "MysqlManagedByCodeineConfigureHelp" : "Codeine is responsible to start/stop MySQL server instance.",
            "codeineConfigureHelp" : "Here the admin can configure all the Codeine global settings.
Some changes might require a restart.",
            "codeineAdminHelp" : "Here the admin can perform operations on codeine.",
            "projectDescriptionHelp" : "Description is a convenient way to let users know what this project is about.",
            "commandDescriptionHelp" : "Description is a convenient way to let users know what this command is doing and for what purpose it should be used.",
            "projectIncludeCommandsFromHelp" : "List here projects to include their configured commands in this project",
            "typeHelp" : "Select a type for the parameter for command execution",
            "collectorDescriptionHelp" : "Description will be displayed to explain what the collector is doing",
            "safeGuardHelp" : "Will take extra measures to validate the correct command executed (to prevent human errors)",
            "blockAfterExecutionHelp" : "This option is appropriate for asynchronous commands that runs in background.
Entering number here will cause the command to wait the specified time in minutes after execution, and before proceeding to next node.",
            "nodesPerMinuteHelp" : "An estimation of the execution frequency",
            "collectorTypeHelp" : "Select a type for collector. types are:
"+
                "
String - text value that will be displayed to user"+
                "
Number - numerical value that will be displayed to user and can be a base for more calculations"+
                "
Monitor - indicates failures or success by the script exit status (zero is success).",
            "operatingSystemHelp" : "Type of operating system for this project, will define the way scripts run in this project (shell/cmd)",
            "commandTags" : "Will filter commands menu based on the tags of the selected nodes.
Only commands with nodes that apply to selected nodes tags will be displayed",
            "collectorsTags" : "The collector will run only on nodes that has all specified tags"
        });

})(angular);
<|endoftext|>


<commit_before>/* global $, $iq, Promise, Strophe */

var logger = require("jitsi-meet-logger").getLogger(__filename);
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var AuthenticationEvents
    = require("../../service/authentication/AuthenticationEvents");
var GlobalOnErrorHandler = require("../util/GlobalOnErrorHandler");
import Settings from "../settings/Settings";

function createExpBackoffTimer(step) {
    var count = 1;
    return function (reset) {
        // Reset call
        if (reset) {
            count = 1;
            return;
        }
        // Calculate next timeout
        var timeout = Math.pow(2, count - 1);
        count += 1;
        return timeout * step;
    };
}

function Moderator(roomName, xmpp, emitter, options) {
    this.roomName = roomName;
    this.xmppService = xmpp;
    this.getNextTimeout = createExpBackoffTimer(1000);
    this.getNextErrorTimeout = createExpBackoffTimer(1000);
    // External authentication stuff
    this.externalAuthEnabled = false;
    this.options = options;

    // Sip gateway can be enabled by configuring Jigasi host in config.js or
    // it will be enabled automatically if focus detects the component through
    // service discovery.
    this.sipGatewayEnabled = this.options.connection.hosts &&
        this.options.connection.hosts.call_control !== undefined;

    this.eventEmitter = emitter;

    this.connection = this.xmppService.connection;
    this.focusUserJid;
    //FIXME:
    // Message listener that talks to POPUP window
    function listener(event) {
        if (event.data && event.data.sessionId) {
            if (event.origin !== window.location.origin) {
                logger.warn("Ignoring sessionId from different origin: " +
                    event.origin);
                return;
            }
            Settings.setSessionId(event.data.sessionId);
            // After popup is closed we will authenticate
        }
    }
    // Register
    if (window.addEventListener) {
        window.addEventListener("message", listener, false);
    } else {
        window.attachEvent("onmessage", listener);
    }
}

Moderator.prototype.isExternalAuthEnabled =  function () {
    return this.externalAuthEnabled;
};

Moderator.prototype.isSipGatewayEnabled =  function () {
    return this.sipGatewayEnabled;
};

Moderator.prototype.onMucMemberLeft =  function (jid) {
    logger.info("Someone left is it focus ? " + jid);
    var resource = Strophe.getResourceFromJid(jid);
    if (resource === 'focus') {
        logger.info(
            "Focus has left the room - leaving conference");
        this.eventEmitter.emit(XMPPEvents.FOCUS_LEFT);
    }
};

Moderator.prototype.setFocusUserJid =  function (focusJid) {
    if (!this.focusUserJid) {
        this.focusUserJid = focusJid;
        logger.info("Focus jid set to:  " + this.focusUserJid);
    }
};

Moderator.prototype.getFocusUserJid =  function () {
    return this.focusUserJid;
};

Moderator.prototype.getFocusComponent =  function () {
    // Get focus component address
    var focusComponent = this.options.connection.hosts.focus;
    // If not specified use default:  'focus.domain'
    if (!focusComponent) {
        focusComponent = 'focus.' + this.options.connection.hosts.domain;
    }
    return focusComponent;
};

Moderator.prototype.createConferenceIq =  function () {
    // Generate create conference IQ
    var elem = $iq({to: this.getFocusComponent(), type: 'set'});

    // Session Id used for authentication
    var sessionId = Settings.getSessionId();
    var machineUID = Settings.getMachineId();

    logger.info(
            "Session ID: " + sessionId + " machine UID: " + machineUID);

    elem.c('conference', {
        xmlns: 'http://jitsi.org/protocol/focus',
        room: this.roomName,
        'machine-uid': machineUID
    });

    if (sessionId) {
        elem.attrs({ 'session-id': sessionId});
    }
    if (this.options.connection.enforcedBridge !== undefined) {
        elem.c(
            'property', {
                name: 'enforcedBridge',
                value: this.options.connection.enforcedBridge
            }).up();
    }
    // Tell the focus we have Jigasi configured
    if (this.options.connection.hosts !== undefined &&
        this.options.connection.hosts.call_control !== undefined) {
        elem.c(
            'property', {
                name: 'call_control',
                value: this.options.connection.hosts.call_control
            }).up();
    }
    if (this.options.conference.channelLastN !== undefined) {
        elem.c(
            'property', {
                name: 'channelLastN',
                value: this.options.conference.channelLastN
            }).up();
    }
    if (this.options.conference.disableAdaptiveSimulcast !== undefined ||
        this.options.conference.disableSimulcast) {
        // disableSimulcast implies disableAdaptiveSimulcast.
        var value = this.options.conference.disableSimulcast ? true :
            this.options.conference.disableAdaptiveSimulcast;
        elem.c(
            'property', {
                name: 'disableAdaptiveSimulcast',
                value: value
            }).up();
    }
    if (this.options.conference.disableRtx !== undefined) {
        elem.c(
            'property', {
                name: 'disableRtx',
                value: this.options.conference.disableRtx
            }).up();
    }
    elem.c(
        'property', {
            name: 'enableLipSync',
            value: false !== this.options.connection.enableLipSync
        }).up();
    if (this.options.conference.audioPacketDelay !== undefined) {
        elem.c(
            'property', {
                name: 'audioPacketDelay',
                value: this.options.conference.audioPacketDelay
            }).up();
    }
    if (this.options.conference.startBitrate) {
        elem.c(
            'property', {
                name: 'startBitrate',
                value: this.options.conference.startBitrate
            }).up();
    }
    if (this.options.conference.minBitrate) {
        elem.c(
            'property', {
                name: 'minBitrate',
                value: this.options.conference.minBitrate
            }).up();
    }
    if (this.options.conference.openSctp !== undefined) {
        elem.c(
            'property', {
                name: 'openSctp',
                value: this.options.conference.openSctp
            }).up();
    }
    if (this.options.conference.startAudioMuted !== undefined) {
        elem.c(
            'property', {
                name: 'startAudioMuted',
                value: this.options.conference.startAudioMuted
            }).up();
    }
    if (this.options.conference.startVideoMuted !== undefined) {
        elem.c(
            'property', {
                name: 'startVideoMuted',
                value: this.options.conference.startVideoMuted
            }).up();
    }
    if (this.options.conference.stereo !== undefined) {
        elem.c(
            'property', {
                name: 'stereo',
                value: this.options.conference.stereo
            }).up();
    }

    elem.c(
        'property', {
            name: 'simulcastMode',
            value: 'rewriting'
        }).up();

    if (this.options.conference.useRoomAsSharedDocumentName !== undefined) {
        elem.c(
            'property', {
                name: 'useRoomAsSharedDocumentName',
                value: this.options.conference.useRoomAsSharedDocumentName
            }).up();
    }
    elem.up();
    return elem;
};

Moderator.prototype.parseSessionId =  function (resultIq) {
    var sessionId = $(resultIq).find('conference').attr('session-id');
    if (sessionId) {
        logger.info('Received sessionId:  ' + sessionId);
        Settings.setSessionId(sessionId);
    }
};

Moderator.prototype.parseConfigOptions =  function (resultIq) {

    this.setFocusUserJid(
        $(resultIq).find('conference').attr('focusjid'));

    var authenticationEnabled
        = $(resultIq).find(
            '>conference>property' +
            '[name=\'authentication\'][value=\'true\']').length > 0;

    logger.info("Authentication enabled: " + authenticationEnabled);

    this.externalAuthEnabled = $(resultIq).find(
            '>conference>property' +
            '[name=\'externalAuth\'][value=\'true\']').length > 0;

    logger.info(
        'External authentication enabled: ' + this.externalAuthEnabled);

    if (!this.externalAuthEnabled) {
        // We expect to receive sessionId in 'internal' authentication mode
        this.parseSessionId(resultIq);
    }

    var authIdentity = $(resultIq).find('>conference').attr('identity');

    this.eventEmitter.emit(AuthenticationEvents.IDENTITY_UPDATED,
        authenticationEnabled, authIdentity);

    // Check if focus has auto-detected Jigasi component(this will be also
    // included if we have passed our host from the config)
    if ($(resultIq).find(
        '>conference>property' +
        '[name=\'sipGatewayEnabled\'][value=\'true\']').length) {
        this.sipGatewayEnabled = true;
    }

    logger.info("Sip gateway enabled:  " + this.sipGatewayEnabled);
};

// FIXME We need to show the fact that we're waiting for the focus to the user
// (or that the focus is not available)
/**
 * Allocates the conference focus.
 *
 * @param {Function} callback - the function to be called back upon the
 * successful allocation of the conference focus
 */
Moderator.prototype.allocateConferenceFocus =  function (callback) {
    // Try to use focus user JID from the config
    this.setFocusUserJid(this.options.connection.focusUserJid);
    // Send create conference IQ
    this.connection.sendIQ(
        this.createConferenceIq(),
        result => this._allocateConferenceFocusSuccess(result, callback),
        error => this._allocateConferenceFocusError(error, callback));
    // XXX We're pressed for time here because we're beginning a complex and/or
    // lengthy conference-establishment process which supposedly involves
    // multiple RTTs. We don't have the time to wait for Strophe to decide to
    // send our IQ.
    this.connection.flush();
};

/**
 * Invoked by {@link #allocateConferenceFocus} upon its request receiving an
 * error result.
 *
 * @param error - the error result of the request that
 * {@link #allocateConferenceFocus} sent
 * @param {Function} callback - the function to be called back upon the
 * successful allocation of the conference focus
 */
Moderator.prototype._allocateConferenceFocusError = function (error, callback) {
    // If the session is invalid, remove and try again without session ID to get
    // a new one
    var invalidSession = $(error).find('>error>session-invalid').length;
    if (invalidSession) {
        logger.info("Session expired! - removing");
        Settings.clearSessionId();
    }
    if ($(error).find('>error>graceful-shutdown').length) {
        this.eventEmitter.emit(XMPPEvents.GRACEFUL_SHUTDOWN);
        return;
    }
    // Check for error returned by the reservation system
    var reservationErr = $(error).find('>error>reservation-error');
    if (reservationErr.length) {
        // Trigger error event
        var errorCode = reservationErr.attr('error-code');
        var errorTextNode = $(error).find('>error>text');
        var errorMsg;
        if (errorTextNode) {
            errorMsg = errorTextNode.text();
        }
        this.eventEmitter.emit(
                XMPPEvents.RESERVATION_ERROR, errorCode, errorMsg);
        return;
    }
    // Not authorized to create new room
    if ($(error).find('>error>not-authorized').length) {
        logger.warn("Unauthorized to start the conference", error);
        var toDomain = Strophe.getDomainFromJid(error.getAttribute('to'));
        if (toDomain !== this.options.connection.hosts.anonymousdomain) {
            //FIXME "is external" should come either from the focus or config.js
            this.externalAuthEnabled = true;
        }
        this.eventEmitter.emit(XMPPEvents.AUTHENTICATION_REQUIRED);
        return;
    }
    var waitMs = this.getNextErrorTimeout();
    var errmsg = "Focus error, retry after "+ waitMs;
    GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
    logger.error(errmsg, error);
    // Show message
    var focusComponent = this.getFocusComponent();
    var retrySec = waitMs / 1000;
    //FIXME: message is duplicated ? Do not show in case of session invalid
    // which means just a retry
    if (!invalidSession) {
        this.eventEmitter.emit(
                XMPPEvents.FOCUS_DISCONNECTED, focusComponent, retrySec);
    }
    // Reset response timeout
    this.getNextTimeout(true);
    window.setTimeout( () => this.allocateConferenceFocus(callback), waitMs);
};

/**
 * Invoked by {@link #allocateConferenceFocus} upon its request receiving a
 * success (i.e. non-error) result.
 *
 * @param result - the success (i.e. non-error) result of the request that
 * {@link #allocateConferenceFocus} sent
 * @param {Function} callback - the function to be called back upon the
 * successful allocation of the conference focus
 */
Moderator.prototype._allocateConferenceFocusSuccess = function (
        result,
        callback) {
    // Setup config options
    this.parseConfigOptions(result);

    // Reset the error timeout (because we haven't failed here).
    this.getNextErrorTimeout(true);
    if ('true' === $(result).find('conference').attr('ready')) {
        // Reset the non-error timeout (because we've succeeded here).
        this.getNextTimeout(true);
        // Exec callback
        callback();
    } else {
        var waitMs = this.getNextTimeout();
        logger.info("Waiting for the focus... " + waitMs);
        window.setTimeout(() => this.allocateConferenceFocus(callback),
            waitMs);
    }
};

Moderator.prototype.authenticate = function () {
    return new Promise((resolve, reject) => {
        this.connection.sendIQ(
            this.createConferenceIq(),
            result => {
                this.parseSessionId(result);
                resolve();
            }, error => {
                var code = $(error).find('>error').attr('code');
                reject(error, code);
            }
        );
    });
};

Moderator.prototype.getLoginUrl = function (urlCallback, failureCallback) {
    this._getLoginUrl(/* popup */ false, urlCallback, failureCallback);
};

/**
 *
 * @param {boolean} popup false for {@link Moderator#getLoginUrl} or true for
 * {@link Moderator#getPopupLoginUrl}
 * @param urlCb
 * @param failureCb
 */
Moderator.prototype._getLoginUrl = function (popup, urlCb, failureCb) {
    var iq = $iq({to: this.getFocusComponent(), type: 'get'});
    var attrs = {
        xmlns: 'http://jitsi.org/protocol/focus',
        room: this.roomName,
        'machine-uid': Settings.getMachineId()
    };
    var str = 'auth url'; // for logger
    if (popup) {
       attrs.popup = true;
       str = 'POPUP ' + str;
    }
    iq.c('login-url', attrs);
    /**
     * Implements a failure callback which reports an error message and an error
     * through (1) GlobalOnErrorHandler, (2) logger, and (3) failureCb.
     *
     * @param {string} errmsg the error messsage to report
     * @param {*} error the error to report (in addition to errmsg)
     */
    function reportError(errmsg, err) {
        GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
        logger.error(errmsg, err);
        failureCb(err);
    }
    this.connection.sendIQ(
        iq,
        function (result) {
            var url = $(result).find('login-url').attr('url');
            url = decodeURIComponent(url);
            if (url) {
                logger.info('Got ' + str + ': ' + url);
                urlCb(url);
            } else {
                reportError('Failed to get ' + str + ' from the focus', result);
            }
        },
        reportError.bind(undefined, 'Get ' + str + ' error')
    );
};

Moderator.prototype.getPopupLoginUrl = function (urlCallback, failureCallback) {
    this._getLoginUrl(/* popup */ true, urlCallback, failureCallback);
};

Moderator.prototype.logout =  function (callback) {
    var iq = $iq({to: this.getFocusComponent(), type: 'set'});
    var sessionId = Settings.getSessionId();
    if (!sessionId) {
        callback();
        return;
    }
    iq.c('logout', {
        xmlns: 'http://jitsi.org/protocol/focus',
        'session-id': sessionId
    });
    this.connection.sendIQ(
        iq,
        function (result) {
            var logoutUrl = $(result).find('logout').attr('logout-url');
            if (logoutUrl) {
                logoutUrl = decodeURIComponent(logoutUrl);
            }
            logger.info("Log out OK, url: " + logoutUrl, result);
            Settings.clearSessionId();
            callback(logoutUrl);
        }.bind(this),
        function (error) {
            var errmsg = "Logout error";
            GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
            logger.error(errmsg, error);
        }
    );
};

module.exports = Moderator;
<commit_msg>cleanup: remove code handling simulcast mode<commit_after>/* global $, $iq, Promise, Strophe */

var logger = require("jitsi-meet-logger").getLogger(__filename);
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var AuthenticationEvents
    = require("../../service/authentication/AuthenticationEvents");
var GlobalOnErrorHandler = require("../util/GlobalOnErrorHandler");
import Settings from "../settings/Settings";

function createExpBackoffTimer(step) {
    var count = 1;
    return function (reset) {
        // Reset call
        if (reset) {
            count = 1;
            return;
        }
        // Calculate next timeout
        var timeout = Math.pow(2, count - 1);
        count += 1;
        return timeout * step;
    };
}

function Moderator(roomName, xmpp, emitter, options) {
    this.roomName = roomName;
    this.xmppService = xmpp;
    this.getNextTimeout = createExpBackoffTimer(1000);
    this.getNextErrorTimeout = createExpBackoffTimer(1000);
    // External authentication stuff
    this.externalAuthEnabled = false;
    this.options = options;

    // Sip gateway can be enabled by configuring Jigasi host in config.js or
    // it will be enabled automatically if focus detects the component through
    // service discovery.
    this.sipGatewayEnabled = this.options.connection.hosts &&
        this.options.connection.hosts.call_control !== undefined;

    this.eventEmitter = emitter;

    this.connection = this.xmppService.connection;
    this.focusUserJid;
    //FIXME:
    // Message listener that talks to POPUP window
    function listener(event) {
        if (event.data && event.data.sessionId) {
            if (event.origin !== window.location.origin) {
                logger.warn("Ignoring sessionId from different origin: " +
                    event.origin);
                return;
            }
            Settings.setSessionId(event.data.sessionId);
            // After popup is closed we will authenticate
        }
    }
    // Register
    if (window.addEventListener) {
        window.addEventListener("message", listener, false);
    } else {
        window.attachEvent("onmessage", listener);
    }
}

Moderator.prototype.isExternalAuthEnabled =  function () {
    return this.externalAuthEnabled;
};

Moderator.prototype.isSipGatewayEnabled =  function () {
    return this.sipGatewayEnabled;
};

Moderator.prototype.onMucMemberLeft =  function (jid) {
    logger.info("Someone left is it focus ? " + jid);
    var resource = Strophe.getResourceFromJid(jid);
    if (resource === 'focus') {
        logger.info(
            "Focus has left the room - leaving conference");
        this.eventEmitter.emit(XMPPEvents.FOCUS_LEFT);
    }
};

Moderator.prototype.setFocusUserJid =  function (focusJid) {
    if (!this.focusUserJid) {
        this.focusUserJid = focusJid;
        logger.info("Focus jid set to:  " + this.focusUserJid);
    }
};

Moderator.prototype.getFocusUserJid =  function () {
    return this.focusUserJid;
};

Moderator.prototype.getFocusComponent =  function () {
    // Get focus component address
    var focusComponent = this.options.connection.hosts.focus;
    // If not specified use default:  'focus.domain'
    if (!focusComponent) {
        focusComponent = 'focus.' + this.options.connection.hosts.domain;
    }
    return focusComponent;
};

Moderator.prototype.createConferenceIq =  function () {
    // Generate create conference IQ
    var elem = $iq({to: this.getFocusComponent(), type: 'set'});

    // Session Id used for authentication
    var sessionId = Settings.getSessionId();
    var machineUID = Settings.getMachineId();

    logger.info(
            "Session ID: " + sessionId + " machine UID: " + machineUID);

    elem.c('conference', {
        xmlns: 'http://jitsi.org/protocol/focus',
        room: this.roomName,
        'machine-uid': machineUID
    });

    if (sessionId) {
        elem.attrs({ 'session-id': sessionId});
    }
    if (this.options.connection.enforcedBridge !== undefined) {
        elem.c(
            'property', {
                name: 'enforcedBridge',
                value: this.options.connection.enforcedBridge
            }).up();
    }
    // Tell the focus we have Jigasi configured
    if (this.options.connection.hosts !== undefined &&
        this.options.connection.hosts.call_control !== undefined) {
        elem.c(
            'property', {
                name: 'call_control',
                value: this.options.connection.hosts.call_control
            }).up();
    }
    if (this.options.conference.channelLastN !== undefined) {
        elem.c(
            'property', {
                name: 'channelLastN',
                value: this.options.conference.channelLastN
            }).up();
    }
    if (this.options.conference.disableAdaptiveSimulcast !== undefined ||
        this.options.conference.disableSimulcast) {
        // disableSimulcast implies disableAdaptiveSimulcast.
        var value = this.options.conference.disableSimulcast ? true :
            this.options.conference.disableAdaptiveSimulcast;
        elem.c(
            'property', {
                name: 'disableAdaptiveSimulcast',
                value: value
            }).up();
    }
    if (this.options.conference.disableRtx !== undefined) {
        elem.c(
            'property', {
                name: 'disableRtx',
                value: this.options.conference.disableRtx
            }).up();
    }
    elem.c(
        'property', {
            name: 'enableLipSync',
            value: false !== this.options.connection.enableLipSync
        }).up();
    if (this.options.conference.audioPacketDelay !== undefined) {
        elem.c(
            'property', {
                name: 'audioPacketDelay',
                value: this.options.conference.audioPacketDelay
            }).up();
    }
    if (this.options.conference.startBitrate) {
        elem.c(
            'property', {
                name: 'startBitrate',
                value: this.options.conference.startBitrate
            }).up();
    }
    if (this.options.conference.minBitrate) {
        elem.c(
            'property', {
                name: 'minBitrate',
                value: this.options.conference.minBitrate
            }).up();
    }
    if (this.options.conference.openSctp !== undefined) {
        elem.c(
            'property', {
                name: 'openSctp',
                value: this.options.conference.openSctp
            }).up();
    }
    if (this.options.conference.startAudioMuted !== undefined) {
        elem.c(
            'property', {
                name: 'startAudioMuted',
                value: this.options.conference.startAudioMuted
            }).up();
    }
    if (this.options.conference.startVideoMuted !== undefined) {
        elem.c(
            'property', {
                name: 'startVideoMuted',
                value: this.options.conference.startVideoMuted
            }).up();
    }
    if (this.options.conference.stereo !== undefined) {
        elem.c(
            'property', {
                name: 'stereo',
                value: this.options.conference.stereo
            }).up();
    }
    if (this.options.conference.useRoomAsSharedDocumentName !== undefined) {
        elem.c(
            'property', {
                name: 'useRoomAsSharedDocumentName',
                value: this.options.conference.useRoomAsSharedDocumentName
            }).up();
    }
    elem.up();
    return elem;
};

Moderator.prototype.parseSessionId =  function (resultIq) {
    var sessionId = $(resultIq).find('conference').attr('session-id');
    if (sessionId) {
        logger.info('Received sessionId:  ' + sessionId);
        Settings.setSessionId(sessionId);
    }
};

Moderator.prototype.parseConfigOptions =  function (resultIq) {

    this.setFocusUserJid(
        $(resultIq).find('conference').attr('focusjid'));

    var authenticationEnabled
        = $(resultIq).find(
            '>conference>property' +
            '[name=\'authentication\'][value=\'true\']').length > 0;

    logger.info("Authentication enabled: " + authenticationEnabled);

    this.externalAuthEnabled = $(resultIq).find(
            '>conference>property' +
            '[name=\'externalAuth\'][value=\'true\']').length > 0;

    logger.info(
        'External authentication enabled: ' + this.externalAuthEnabled);

    if (!this.externalAuthEnabled) {
        // We expect to receive sessionId in 'internal' authentication mode
        this.parseSessionId(resultIq);
    }

    var authIdentity = $(resultIq).find('>conference').attr('identity');

    this.eventEmitter.emit(AuthenticationEvents.IDENTITY_UPDATED,
        authenticationEnabled, authIdentity);

    // Check if focus has auto-detected Jigasi component(this will be also
    // included if we have passed our host from the config)
    if ($(resultIq).find(
        '>conference>property' +
        '[name=\'sipGatewayEnabled\'][value=\'true\']').length) {
        this.sipGatewayEnabled = true;
    }

    logger.info("Sip gateway enabled:  " + this.sipGatewayEnabled);
};

// FIXME We need to show the fact that we're waiting for the focus to the user
// (or that the focus is not available)
/**
 * Allocates the conference focus.
 *
 * @param {Function} callback - the function to be called back upon the
 * successful allocation of the conference focus
 */
Moderator.prototype.allocateConferenceFocus =  function (callback) {
    // Try to use focus user JID from the config
    this.setFocusUserJid(this.options.connection.focusUserJid);
    // Send create conference IQ
    this.connection.sendIQ(
        this.createConferenceIq(),
        result => this._allocateConferenceFocusSuccess(result, callback),
        error => this._allocateConferenceFocusError(error, callback));
    // XXX We're pressed for time here because we're beginning a complex and/or
    // lengthy conference-establishment process which supposedly involves
    // multiple RTTs. We don't have the time to wait for Strophe to decide to
    // send our IQ.
    this.connection.flush();
};

/**
 * Invoked by {@link #allocateConferenceFocus} upon its request receiving an
 * error result.
 *
 * @param error - the error result of the request that
 * {@link #allocateConferenceFocus} sent
 * @param {Function} callback - the function to be called back upon the
 * successful allocation of the conference focus
 */
Moderator.prototype._allocateConferenceFocusError = function (error, callback) {
    // If the session is invalid, remove and try again without session ID to get
    // a new one
    var invalidSession = $(error).find('>error>session-invalid').length;
    if (invalidSession) {
        logger.info("Session expired! - removing");
        Settings.clearSessionId();
    }
    if ($(error).find('>error>graceful-shutdown').length) {
        this.eventEmitter.emit(XMPPEvents.GRACEFUL_SHUTDOWN);
        return;
    }
    // Check for error returned by the reservation system
    var reservationErr = $(error).find('>error>reservation-error');
    if (reservationErr.length) {
        // Trigger error event
        var errorCode = reservationErr.attr('error-code');
        var errorTextNode = $(error).find('>error>text');
        var errorMsg;
        if (errorTextNode) {
            errorMsg = errorTextNode.text();
        }
        this.eventEmitter.emit(
                XMPPEvents.RESERVATION_ERROR, errorCode, errorMsg);
        return;
    }
    // Not authorized to create new room
    if ($(error).find('>error>not-authorized').length) {
        logger.warn("Unauthorized to start the conference", error);
        var toDomain = Strophe.getDomainFromJid(error.getAttribute('to'));
        if (toDomain !== this.options.connection.hosts.anonymousdomain) {
            //FIXME "is external" should come either from the focus or config.js
            this.externalAuthEnabled = true;
        }
        this.eventEmitter.emit(XMPPEvents.AUTHENTICATION_REQUIRED);
        return;
    }
    var waitMs = this.getNextErrorTimeout();
    var errmsg = "Focus error, retry after "+ waitMs;
    GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
    logger.error(errmsg, error);
    // Show message
    var focusComponent = this.getFocusComponent();
    var retrySec = waitMs / 1000;
    //FIXME: message is duplicated ? Do not show in case of session invalid
    // which means just a retry
    if (!invalidSession) {
        this.eventEmitter.emit(
                XMPPEvents.FOCUS_DISCONNECTED, focusComponent, retrySec);
    }
    // Reset response timeout
    this.getNextTimeout(true);
    window.setTimeout( () => this.allocateConferenceFocus(callback), waitMs);
};

/**
 * Invoked by {@link #allocateConferenceFocus} upon its request receiving a
 * success (i.e. non-error) result.
 *
 * @param result - the success (i.e. non-error) result of the request that
 * {@link #allocateConferenceFocus} sent
 * @param {Function} callback - the function to be called back upon the
 * successful allocation of the conference focus
 */
Moderator.prototype._allocateConferenceFocusSuccess = function (
        result,
        callback) {
    // Setup config options
    this.parseConfigOptions(result);

    // Reset the error timeout (because we haven't failed here).
    this.getNextErrorTimeout(true);
    if ('true' === $(result).find('conference').attr('ready')) {
        // Reset the non-error timeout (because we've succeeded here).
        this.getNextTimeout(true);
        // Exec callback
        callback();
    } else {
        var waitMs = this.getNextTimeout();
        logger.info("Waiting for the focus... " + waitMs);
        window.setTimeout(() => this.allocateConferenceFocus(callback),
            waitMs);
    }
};

Moderator.prototype.authenticate = function () {
    return new Promise((resolve, reject) => {
        this.connection.sendIQ(
            this.createConferenceIq(),
            result => {
                this.parseSessionId(result);
                resolve();
            }, error => {
                var code = $(error).find('>error').attr('code');
                reject(error, code);
            }
        );
    });
};

Moderator.prototype.getLoginUrl = function (urlCallback, failureCallback) {
    this._getLoginUrl(/* popup */ false, urlCallback, failureCallback);
};

/**
 *
 * @param {boolean} popup false for {@link Moderator#getLoginUrl} or true for
 * {@link Moderator#getPopupLoginUrl}
 * @param urlCb
 * @param failureCb
 */
Moderator.prototype._getLoginUrl = function (popup, urlCb, failureCb) {
    var iq = $iq({to: this.getFocusComponent(), type: 'get'});
    var attrs = {
        xmlns: 'http://jitsi.org/protocol/focus',
        room: this.roomName,
        'machine-uid': Settings.getMachineId()
    };
    var str = 'auth url'; // for logger
    if (popup) {
       attrs.popup = true;
       str = 'POPUP ' + str;
    }
    iq.c('login-url', attrs);
    /**
     * Implements a failure callback which reports an error message and an error
     * through (1) GlobalOnErrorHandler, (2) logger, and (3) failureCb.
     *
     * @param {string} errmsg the error messsage to report
     * @param {*} error the error to report (in addition to errmsg)
     */
    function reportError(errmsg, err) {
        GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
        logger.error(errmsg, err);
        failureCb(err);
    }
    this.connection.sendIQ(
        iq,
        function (result) {
            var url = $(result).find('login-url').attr('url');
            url = decodeURIComponent(url);
            if (url) {
                logger.info('Got ' + str + ': ' + url);
                urlCb(url);
            } else {
                reportError('Failed to get ' + str + ' from the focus', result);
            }
        },
        reportError.bind(undefined, 'Get ' + str + ' error')
    );
};

Moderator.prototype.getPopupLoginUrl = function (urlCallback, failureCallback) {
    this._getLoginUrl(/* popup */ true, urlCallback, failureCallback);
};

Moderator.prototype.logout =  function (callback) {
    var iq = $iq({to: this.getFocusComponent(), type: 'set'});
    var sessionId = Settings.getSessionId();
    if (!sessionId) {
        callback();
        return;
    }
    iq.c('logout', {
        xmlns: 'http://jitsi.org/protocol/focus',
        'session-id': sessionId
    });
    this.connection.sendIQ(
        iq,
        function (result) {
            var logoutUrl = $(result).find('logout').attr('logout-url');
            if (logoutUrl) {
                logoutUrl = decodeURIComponent(logoutUrl);
            }
            logger.info("Log out OK, url: " + logoutUrl, result);
            Settings.clearSessionId();
            callback(logoutUrl);
        }.bind(this),
        function (error) {
            var errmsg = "Logout error";
            GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
            logger.error(errmsg, error);
        }
    );
};

module.exports = Moderator;
<|endoftext|>


<commit_before>/*global define, $*/

define(function () {
    'use strict';
    return {
        main: function () {
            this.bindTooltip();
        },
        bindTooltip: function () {
            $('#504-tooltip').tooltip();
        }
    };
});<commit_msg>Changed selector for tooltip binding.<commit_after>/*global define, $*/

define(function () {
    'use strict';
    return {
        main: function () {
            this.bindTooltip();
        },
        bindTooltip: function () {
            $('.bind-tooltip').tooltip();
        }
    };
});<|endoftext|>