<commit_before>/**
 * Module dependencies.
 */
var passport = require('passport-strategy')
  , url = require('url')
  , util = require('util')
  , utils = require('./utils')
  , OAuth = require('oauth').OAuth
  , SessionRequestTokenStore = require('./requesttoken/session')
  , InternalOAuthError = require('./errors/internaloautherror');

/**
 * Creates an instance of `OAuthStrategy`.
 *
 * The OAuth authentication strategy authenticates requests using the OAuth
 * protocol.
 *
 * OAuth provides a facility for delegated authentication, whereby users can
 * authenticate using a third-party service such as Twitter.  Delegating in this
 * manner involves a sequence of events, including redirecting the user to the
 * third-party service for authorization.  Once authorization has been obtained,
 * the user is redirected back to the application and a token can be used to
 * obtain credentials.
 *
 * Applications must supply a `verify` callback, for which the function
 * signature is:
 *
 *     function(token, tokenSecret, profile, done) { ... }
 *
 * The verify callback is responsible for finding or creating the user, and
 * invoking `done` with the following arguments:
 *
 *     done(err, user, info);
 *
 * `user` should be set to `false` to indicate an authentication failure.
 * Additional `info` can optionally be passed as a third argument, typically
 * used to display informational messages.  If an exception occured, `err`
 * should be set.
 *
 * Options:
 *
 *   - `requestTokenURL`       URL used to obtain an unauthorized request token
 *   - `accessTokenURL`        URL used to exchange a user-authorized request token for an access token
 *   - `userAuthorizationURL`  URL used to obtain user authorization
 *   - `consumerKey`           identifies client to service provider
 *   - `consumerSecret`        secret used to establish ownership of the consumer key
 *   - 'signatureMethod'       signature method used to sign the request (default: 'HMAC-SHA1')
 *   - `callbackURL`           URL to which the service provider will redirect the user after obtaining authorization
 *   - `passReqToCallback`     when `true`, `req` is the first argument to the verify callback (default: `false`)
 *
 * Examples:
 *
 *     passport.use(new OAuthStrategy({
 *         requestTokenURL: 'https://www.example.com/oauth/request_token',
 *         accessTokenURL: 'https://www.example.com/oauth/access_token',
 *         userAuthorizationURL: 'https://www.example.com/oauth/authorize',
 *         consumerKey: '123-456-789',
 *         consumerSecret: 'shhh-its-a-secret'
 *         callbackURL: 'https://www.example.net/auth/example/callback'
 *       },
 *       function(token, tokenSecret, profile, done) {
 *         User.findOrCreate(..., function (err, user) {
 *           done(err, user);
 *         });
 *       }
 *     ));
 *
 * @constructor
 * @param {Object} options
 * @param {Function} verify
 * @api public
 */
function OAuthStrategy(options, verify) {
  if (typeof options == 'function') {
    verify = options;
    options = undefined;
  }
  options = options || {};

  if (!verify) { throw new TypeError('OAuthStrategy requires a verify callback'); }
  if (!options.requestTokenURL) { throw new TypeError('OAuthStrategy requires a requestTokenURL option'); }
  if (!options.accessTokenURL) { throw new TypeError('OAuthStrategy requires a accessTokenURL option'); }
  if (!options.userAuthorizationURL) { throw new TypeError('OAuthStrategy requires a userAuthorizationURL option'); }
  if (!options.consumerKey) { throw new TypeError('OAuthStrategy requires a consumerKey option'); }
  if (options.consumerSecret === undefined) { throw new TypeError('OAuthStrategy requires a consumerSecret option'); }

  passport.Strategy.call(this);
  this.name = 'oauth';
  this._verify = verify;

  // NOTE: The _oauth property is considered "protected".  Subclasses are
  //       allowed to use it when making protected resource requests to retrieve
  //       the user profile.
  this._oauth = new OAuth(options.requestTokenURL, options.accessTokenURL,
                          options.consumerKey,  options.consumerSecret,
                          '1.0', null, options.signatureMethod || 'HMAC-SHA1',
                          null, options.customHeaders);

  this._userAuthorizationURL = options.userAuthorizationURL;
  this._callbackURL = options.callbackURL;
  this._key = options.sessionKey || 'oauth';
  this._requestTokenStore = options.requestTokenStore || new SessionRequestTokenStore({ key: this._key });
  this._trustProxy = options.proxy;
  this._passReqToCallback = options.passReqToCallback;
  this._skipUserProfile = (options.skipUserProfile === undefined) ? false : options.skipUserProfile;
}

/**
 * Inherit from `passport.Strategy`.
 */
util.inherits(OAuthStrategy, passport.Strategy);

/**
 * Authenticate request by delegating to a service provider using OAuth.
 *
 * @param {Object} req
 * @api protected
 */
OAuthStrategy.prototype.authenticate = function(req, options) {
  options = options || {};

  var self = this;

  if (req.query && req.query.oauth_token) {
    // The request being authenticated contains an oauth_token parameter in the
    // query portion of the URL.  This indicates that the service provider has
    // redirected the user back to the application, after authenticating the
    // user and obtaining their authorization.
    //
    // The value of the oauth_token parameter is the request token.  Together
    // with knowledge of the token secret (stored in the session), the request
    // token can be exchanged for an access token and token secret.
    //
    // This access token and token secret, along with the optional ability to
    // fetch profile information from the service provider, is sufficient to
    // establish the identity of the user.
    var oauthToken = req.query.oauth_token;
    this._requestTokenStore.get(req, oauthToken, function(err, oauthTokenSecret) {
      if (err) { return self.error(err); }

      // NOTE: The oauth_verifier parameter will be supplied in the query portion
      //       of the redirect URL, if the server supports OAuth 1.0a.
      var oauthVerifier = req.query.oauth_verifier || null;

      self._oauth.getOAuthAccessToken(oauthToken, oauthTokenSecret, oauthVerifier, function(err, token, tokenSecret, params) {
        if (err) { return self.error(self._createOAuthError('Failed to obtain access token', err)); }

        // The request token has been exchanged for an access token.  Since the
        // request token is a single-use token, that data can be removed from the
        // store.
        self._requestTokenStore.destroy(req, oauthToken, function(err) {
          if (err) { return self.error(err); }

          self._loadUserProfile(token, tokenSecret, params, function(err, profile) {
            if (err) { return self.error(err); }

            function verified(err, user, info) {
              if (err) { return self.error(err); }
              if (!user) { return self.fail(info); }
              self.success(user, info);
            }

            try {
              if (self._passReqToCallback) {
                var arity = self._verify.length;
                if (arity == 6) {
                  self._verify(req, token, tokenSecret, params, profile, verified);
                } else { // arity == 5
                  self._verify(req, token, tokenSecret, profile, verified);
                }
              } else {
                var arity = self._verify.length;
                if (arity == 5) {
                  self._verify(token, tokenSecret, params, profile, verified);
                } else { // arity == 4
                  self._verify(token, tokenSecret, profile, verified);
                }
              }
            } catch (ex) {
              return self.error(ex);
            }
          });
        });
      });
    });
  } else {
    // In order to authenticate via OAuth, the application must obtain a request
    // token from the service provider and redirect the user to the service
    // provider to obtain their authorization.  After authorization has been
    // approved the user will be redirected back the application, at which point
    // the application can exchange the request token for an access token.
    //
    // In order to successfully exchange the request token, its corresponding
    // token secret needs to be known.  The token secret will be temporarily
    // stored in the session, so that it can be retrieved upon the user being
    // redirected back to the application.

    var params = this.requestTokenParams(options);
    var callbackURL = options.callbackURL || this._callbackURL;
    if (callbackURL) {
      var parsed = url.parse(callbackURL);
      if (!parsed.protocol) {
        // The callback URL is relative, resolve a fully qualified URL from the
        // URL of the originating request.
        callbackURL = url.resolve(utils.originalURL(req, { proxy: this._trustProxy }), callbackURL);
      }
    }
    params.oauth_callback = callbackURL;

    this._oauth.getOAuthRequestToken(params, function(err, token, tokenSecret, params) {
      if (err) { return self.error(self._createOAuthError('Failed to obtain request token', err)); }

      // NOTE: params will contain an oauth_callback_confirmed property set to
      //       true, if the server supports OAuth 1.0a.
      //       { oauth_callback_confirmed: 'true' }

      self._requestTokenStore.set(req, token, tokenSecret, function(err) {
        if (err) { return self.error(err); }

        var parsed = url.parse(self._userAuthorizationURL, true);
        parsed.query.oauth_token = token;
        if (!params.oauth_callback_confirmed && callbackURL) {
          // NOTE: If oauth_callback_confirmed=true is not present when issuing a
          //       request token, the server does not support OAuth 1.0a.  In this
          //       circumstance, `oauth_callback` is passed when redirecting the
          //       user to the service provider.
          parsed.query.oauth_callback = callbackURL;
        }
        utils.merge(parsed.query, self.userAuthorizationParams(options));
        delete parsed.search;
        var location = url.format(parsed);
        self.redirect(location);
      });
    });
  }
};

/**
 * Retrieve user profile from service provider.
 *
 * OAuth-based authentication strategies can overrride this function in order to
 * load the user's profile from the service provider.  This assists applications
 * (and users of those applications) in the initial registration process by
 * automatically submitting required information.
 *
 * @param {String} token
 * @param {String} tokenSecret
 * @param {Object} params
 * @param {Function} done
 * @api protected
 */
OAuthStrategy.prototype.userProfile = function(token, tokenSecret, params, done) {
  return done(null, {});
};

/**
 * Return extra parameters to be included in the request token request.
 *
 * Some OAuth providers require additional parameters to be included when
 * issuing a request token.  Since these parameters are not standardized by the
 * OAuth specification, OAuth-based authentication strategies can overrride this
 * function in order to populate these parameters as required by the provider.
 *
 * @param {Object} options
 * @return {Object}
 * @api protected
 */
OAuthStrategy.prototype.requestTokenParams = function(options) {
  return {};
};

/**
 * Return extra parameters to be included in the user authorization request.
 *
 * Some OAuth providers allow additional, non-standard parameters to be included
 * when requesting authorization.  Since these parameters are not standardized
 * by the OAuth specification, OAuth-based authentication strategies can
 * overrride this function in order to populate these parameters as required by
 * the provider.
 *
 * @param {Object} options
 * @return {Object}
 * @api protected
 */
OAuthStrategy.prototype.userAuthorizationParams = function(options) {
  return {};
};

/**
 * Parse error response from OAuth endpoint.
 *
 * OAuth-based authentication strategies can overrride this function in order to
 * parse error responses received from the request token and access token
 * endpoints, allowing the most informative message to be displayed.
 *
 * If this function is not overridden, a generic error will be thrown.
 *
 * @param {String} body
 * @param {Number} status
 * @return {Error}
 * @api protected
 */
OAuthStrategy.prototype.parseErrorResponse = function(body, status) {
  return null;
};

/**
 * Load user profile, contingent upon options.
 *
 * @param {String} accessToken
 * @param {Function} done
 * @api private
 */
OAuthStrategy.prototype._loadUserProfile = function(token, tokenSecret, params, done) {
  var self = this;

  function loadIt() {
    return self.userProfile(token, tokenSecret, params, done);
  }
  function skipIt() {
    return done(null);
  }

  if (typeof this._skipUserProfile == 'function' && this._skipUserProfile.length > 1) {
    // async
    this._skipUserProfile(token, tokenSecret, function(err, skip) {
      if (err) { return done(err); }
      if (!skip) { return loadIt(); }
      return skipIt();
    });
  } else {
    var skip = (typeof this._skipUserProfile == 'function') ? this._skipUserProfile() : this._skipUserProfile;
    if (!skip) { return loadIt(); }
    return skipIt();
  }
};

/**
 * Create an OAuth error.
 *
 * @param {String} message
 * @param {Object|Error} err
 * @api private
 */
OAuthStrategy.prototype._createOAuthError = function(message, err) {
  var e;
  if (err.statusCode && err.data) {
    try {
      e = this.parseErrorResponse(err.data, err.statusCode);
    } catch (_) {}
  }
  if (!e) { e = new InternalOAuthError(message, err); }
  return e;
};

/**
 * Expose `OAuthStrategy`.
 */
module.exports = OAuthStrategy;
<commit_msg>Pass metadata to request token store on get.<commit_after>/**
 * Module dependencies.
 */
var passport = require('passport-strategy')
  , url = require('url')
  , util = require('util')
  , utils = require('./utils')
  , OAuth = require('oauth').OAuth
  , SessionRequestTokenStore = require('./requesttoken/session')
  , InternalOAuthError = require('./errors/internaloautherror');

/**
 * Creates an instance of `OAuthStrategy`.
 *
 * The OAuth authentication strategy authenticates requests using the OAuth
 * protocol.
 *
 * OAuth provides a facility for delegated authentication, whereby users can
 * authenticate using a third-party service such as Twitter.  Delegating in this
 * manner involves a sequence of events, including redirecting the user to the
 * third-party service for authorization.  Once authorization has been obtained,
 * the user is redirected back to the application and a token can be used to
 * obtain credentials.
 *
 * Applications must supply a `verify` callback, for which the function
 * signature is:
 *
 *     function(token, tokenSecret, profile, done) { ... }
 *
 * The verify callback is responsible for finding or creating the user, and
 * invoking `done` with the following arguments:
 *
 *     done(err, user, info);
 *
 * `user` should be set to `false` to indicate an authentication failure.
 * Additional `info` can optionally be passed as a third argument, typically
 * used to display informational messages.  If an exception occured, `err`
 * should be set.
 *
 * Options:
 *
 *   - `requestTokenURL`       URL used to obtain an unauthorized request token
 *   - `accessTokenURL`        URL used to exchange a user-authorized request token for an access token
 *   - `userAuthorizationURL`  URL used to obtain user authorization
 *   - `consumerKey`           identifies client to service provider
 *   - `consumerSecret`        secret used to establish ownership of the consumer key
 *   - 'signatureMethod'       signature method used to sign the request (default: 'HMAC-SHA1')
 *   - `callbackURL`           URL to which the service provider will redirect the user after obtaining authorization
 *   - `passReqToCallback`     when `true`, `req` is the first argument to the verify callback (default: `false`)
 *
 * Examples:
 *
 *     passport.use(new OAuthStrategy({
 *         requestTokenURL: 'https://www.example.com/oauth/request_token',
 *         accessTokenURL: 'https://www.example.com/oauth/access_token',
 *         userAuthorizationURL: 'https://www.example.com/oauth/authorize',
 *         consumerKey: '123-456-789',
 *         consumerSecret: 'shhh-its-a-secret'
 *         callbackURL: 'https://www.example.net/auth/example/callback'
 *       },
 *       function(token, tokenSecret, profile, done) {
 *         User.findOrCreate(..., function (err, user) {
 *           done(err, user);
 *         });
 *       }
 *     ));
 *
 * @constructor
 * @param {Object} options
 * @param {Function} verify
 * @api public
 */
function OAuthStrategy(options, verify) {
  if (typeof options == 'function') {
    verify = options;
    options = undefined;
  }
  options = options || {};

  if (!verify) { throw new TypeError('OAuthStrategy requires a verify callback'); }
  if (!options.requestTokenURL) { throw new TypeError('OAuthStrategy requires a requestTokenURL option'); }
  if (!options.accessTokenURL) { throw new TypeError('OAuthStrategy requires a accessTokenURL option'); }
  if (!options.userAuthorizationURL) { throw new TypeError('OAuthStrategy requires a userAuthorizationURL option'); }
  if (!options.consumerKey) { throw new TypeError('OAuthStrategy requires a consumerKey option'); }
  if (options.consumerSecret === undefined) { throw new TypeError('OAuthStrategy requires a consumerSecret option'); }

  passport.Strategy.call(this);
  this.name = 'oauth';
  this._verify = verify;

  // NOTE: The _oauth property is considered "protected".  Subclasses are
  //       allowed to use it when making protected resource requests to retrieve
  //       the user profile.
  this._oauth = new OAuth(options.requestTokenURL, options.accessTokenURL,
                          options.consumerKey,  options.consumerSecret,
                          '1.0', null, options.signatureMethod || 'HMAC-SHA1',
                          null, options.customHeaders);

  this._userAuthorizationURL = options.userAuthorizationURL;
  this._callbackURL = options.callbackURL;
  this._key = options.sessionKey || 'oauth';
  this._requestTokenStore = options.requestTokenStore || new SessionRequestTokenStore({ key: this._key });
  this._trustProxy = options.proxy;
  this._passReqToCallback = options.passReqToCallback;
  this._skipUserProfile = (options.skipUserProfile === undefined) ? false : options.skipUserProfile;
}

/**
 * Inherit from `passport.Strategy`.
 */
util.inherits(OAuthStrategy, passport.Strategy);

/**
 * Authenticate request by delegating to a service provider using OAuth.
 *
 * @param {Object} req
 * @api protected
 */
OAuthStrategy.prototype.authenticate = function(req, options) {
  options = options || {};

  var self = this;
  var meta = {
    requestTokenURL: this._oauth._requestUrl,
    accessTokenURL: this._oauth._accessUrl,
    userAuthorizationURL:  this._userAuthorizationURL,
    consumerKey: this._oauth._consumerKey
  }

  if (req.query && req.query.oauth_token) {
    // The request being authenticated contains an oauth_token parameter in the
    // query portion of the URL.  This indicates that the service provider has
    // redirected the user back to the application, after authenticating the
    // user and obtaining their authorization.
    //
    // The value of the oauth_token parameter is the request token.  Together
    // with knowledge of the token secret (stored in the session), the request
    // token can be exchanged for an access token and token secret.
    //
    // This access token and token secret, along with the optional ability to
    // fetch profile information from the service provider, is sufficient to
    // establish the identity of the user.
    var oauthToken = req.query.oauth_token;
    this._requestTokenStore.get(req, oauthToken, function(err, oauthTokenSecret) {
      if (err) { return self.error(err); }

      // NOTE: The oauth_verifier parameter will be supplied in the query portion
      //       of the redirect URL, if the server supports OAuth 1.0a.
      var oauthVerifier = req.query.oauth_verifier || null;

      self._oauth.getOAuthAccessToken(oauthToken, oauthTokenSecret, oauthVerifier, function(err, token, tokenSecret, params) {
        if (err) { return self.error(self._createOAuthError('Failed to obtain access token', err)); }

        // The request token has been exchanged for an access token.  Since the
        // request token is a single-use token, that data can be removed from the
        // store.
        self._requestTokenStore.destroy(req, oauthToken, function(err) {
          if (err) { return self.error(err); }

          self._loadUserProfile(token, tokenSecret, params, function(err, profile) {
            if (err) { return self.error(err); }

            function verified(err, user, info) {
              if (err) { return self.error(err); }
              if (!user) { return self.fail(info); }
              self.success(user, info);
            }

            try {
              if (self._passReqToCallback) {
                var arity = self._verify.length;
                if (arity == 6) {
                  self._verify(req, token, tokenSecret, params, profile, verified);
                } else { // arity == 5
                  self._verify(req, token, tokenSecret, profile, verified);
                }
              } else {
                var arity = self._verify.length;
                if (arity == 5) {
                  self._verify(token, tokenSecret, params, profile, verified);
                } else { // arity == 4
                  self._verify(token, tokenSecret, profile, verified);
                }
              }
            } catch (ex) {
              return self.error(ex);
            }
          });
        });
      });
    });
  } else {
    // In order to authenticate via OAuth, the application must obtain a request
    // token from the service provider and redirect the user to the service
    // provider to obtain their authorization.  After authorization has been
    // approved the user will be redirected back the application, at which point
    // the application can exchange the request token for an access token.
    //
    // In order to successfully exchange the request token, its corresponding
    // token secret needs to be known.  The token secret will be temporarily
    // stored in the session, so that it can be retrieved upon the user being
    // redirected back to the application.

    var params = this.requestTokenParams(options);
    var callbackURL = options.callbackURL || this._callbackURL;
    if (callbackURL) {
      var parsed = url.parse(callbackURL);
      if (!parsed.protocol) {
        // The callback URL is relative, resolve a fully qualified URL from the
        // URL of the originating request.
        callbackURL = url.resolve(utils.originalURL(req, { proxy: this._trustProxy }), callbackURL);
      }
    }
    params.oauth_callback = callbackURL;

    this._oauth.getOAuthRequestToken(params, function(err, token, tokenSecret, params) {
      if (err) { return self.error(self._createOAuthError('Failed to obtain request token', err)); }

      // NOTE: params will contain an oauth_callback_confirmed property set to
      //       true, if the server supports OAuth 1.0a.
      //       { oauth_callback_confirmed: 'true' }

      function stored(err) {
        if (err) { return self.error(err); }

        var parsed = url.parse(self._userAuthorizationURL, true);
        parsed.query.oauth_token = token;
        if (!params.oauth_callback_confirmed && callbackURL) {
          // NOTE: If oauth_callback_confirmed=true is not present when issuing a
          //       request token, the server does not support OAuth 1.0a.  In this
          //       circumstance, `oauth_callback` is passed when redirecting the
          //       user to the service provider.
          parsed.query.oauth_callback = callbackURL;
        }
        utils.merge(parsed.query, self.userAuthorizationParams(options));
        delete parsed.search;
        var location = url.format(parsed);
        self.redirect(location);
      }

      try {
        var arity = self._requestTokenStore.set.length;
        if (arity == 5) {
          self._requestTokenStore.set(req, token, tokenSecret, meta, stored);
        } else { // arity == 4
          self._requestTokenStore.set(req, token, tokenSecret, stored);
        }
      } catch (ex) {
        return self.error(ex);
      }
    });
  }
};

/**
 * Retrieve user profile from service provider.
 *
 * OAuth-based authentication strategies can overrride this function in order to
 * load the user's profile from the service provider.  This assists applications
 * (and users of those applications) in the initial registration process by
 * automatically submitting required information.
 *
 * @param {String} token
 * @param {String} tokenSecret
 * @param {Object} params
 * @param {Function} done
 * @api protected
 */
OAuthStrategy.prototype.userProfile = function(token, tokenSecret, params, done) {
  return done(null, {});
};

/**
 * Return extra parameters to be included in the request token request.
 *
 * Some OAuth providers require additional parameters to be included when
 * issuing a request token.  Since these parameters are not standardized by the
 * OAuth specification, OAuth-based authentication strategies can overrride this
 * function in order to populate these parameters as required by the provider.
 *
 * @param {Object} options
 * @return {Object}
 * @api protected
 */
OAuthStrategy.prototype.requestTokenParams = function(options) {
  return {};
};

/**
 * Return extra parameters to be included in the user authorization request.
 *
 * Some OAuth providers allow additional, non-standard parameters to be included
 * when requesting authorization.  Since these parameters are not standardized
 * by the OAuth specification, OAuth-based authentication strategies can
 * overrride this function in order to populate these parameters as required by
 * the provider.
 *
 * @param {Object} options
 * @return {Object}
 * @api protected
 */
OAuthStrategy.prototype.userAuthorizationParams = function(options) {
  return {};
};

/**
 * Parse error response from OAuth endpoint.
 *
 * OAuth-based authentication strategies can overrride this function in order to
 * parse error responses received from the request token and access token
 * endpoints, allowing the most informative message to be displayed.
 *
 * If this function is not overridden, a generic error will be thrown.
 *
 * @param {String} body
 * @param {Number} status
 * @return {Error}
 * @api protected
 */
OAuthStrategy.prototype.parseErrorResponse = function(body, status) {
  return null;
};

/**
 * Load user profile, contingent upon options.
 *
 * @param {String} accessToken
 * @param {Function} done
 * @api private
 */
OAuthStrategy.prototype._loadUserProfile = function(token, tokenSecret, params, done) {
  var self = this;

  function loadIt() {
    return self.userProfile(token, tokenSecret, params, done);
  }
  function skipIt() {
    return done(null);
  }

  if (typeof this._skipUserProfile == 'function' && this._skipUserProfile.length > 1) {
    // async
    this._skipUserProfile(token, tokenSecret, function(err, skip) {
      if (err) { return done(err); }
      if (!skip) { return loadIt(); }
      return skipIt();
    });
  } else {
    var skip = (typeof this._skipUserProfile == 'function') ? this._skipUserProfile() : this._skipUserProfile;
    if (!skip) { return loadIt(); }
    return skipIt();
  }
};

/**
 * Create an OAuth error.
 *
 * @param {String} message
 * @param {Object|Error} err
 * @api private
 */
OAuthStrategy.prototype._createOAuthError = function(message, err) {
  var e;
  if (err.statusCode && err.data) {
    try {
      e = this.parseErrorResponse(err.data, err.statusCode);
    } catch (_) {}
  }
  if (!e) { e = new InternalOAuthError(message, err); }
  return e;
};

/**
 * Expose `OAuthStrategy`.
 */
module.exports = OAuthStrategy;
<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */
var util = require('util')
  , OAuth2Strategy = require('passport-oauth-no-web-server').OAuth2Strategy
  , https = require('https');

/**
 * `Strategy` constructor.
 *
 * The Mercadolibre authentication strategy authenticates requests by delegating
 * to Mercadolibre using the OAuth 2.0 protocol.
 *
 * Applications must supply a `verify` callback which accepts an `accessToken`,
 * `refreshToken` and service-specific `profile`, and then calls the `done`
 * callback supplying a `user`, which should be set to `false` if the
 * credentials are not valid.  If an exception occured, `err` should be set.
 *
 * Options:
 *   - `clientID`      your Mercadolibre application's client id
 *   - `clientSecret`  your Mercadolibre application's client secret
 *   - `callbackURL`   URL to which Mercadolibre will redirect the user after granting authorization
 *
 * Examples:
 *
 *     passport.use(new MercadolibreStrategy({
 *         clientID: '2288989987133514',
 *         clientSecret: 'shhh-its-a-secret'
 *         callbackURL: 'https://www.example.net/auth/mercadolibre/callback'
 *       },
 *       function(accessToken, refreshToken, profile, done) {
 *         User.findOrCreate(..., function (err, user) {
 *           done(err, user);
 *         });
 *       }
 *     ));
 *
 * @param {Object} options
 * @param {Function} verify
 * @api public
 */
function Strategy(options, verify) {
  options = options || {};
  options.authorizationURL = options.authorizationURL || 'https://auth.mercadolibre.com.ar/authorization';
  options.tokenURL = options.tokenURL || 'https://api.mercadolibre.com/oauth/token';
  this.profileUrl = options.profileUrl || "https://api.mercadolibre.com/users/me";

  OAuth2Strategy.call(this, options, verify);
  this.name = 'mercadolibre';
}

/**
 * Inherit from `OAuth2Strategy`.
 */
util.inherits(Strategy, OAuth2Strategy);

/**
 * Retrieve user profile from Mercadolibre.
 *
 * This function constructs a normalized profile.
 *
 * @param {String} accessToken
 * @param {Function} done
 * @api protected
 */
Strategy.prototype.userProfile = function(accessToken, done) {

  this._oauth2.get(this.profileUrl, accessToken, function (err, body, res) {

    if (err) { return done(err); };

    try {
      var json = JSON.parse(body);
      console.log(json);
      var profile = { provider: 'MercadoLibre' };
      profile.id = json.id;
      profile.nickname = json.nickname;
      profile.first_name = json.first_name;
      profile.last_name = json.last_name;
      profile.email = json.email;
      profile.accessToken = accessToken;
      profile._raw = body;
      profile._json = json;

      done(null, profile);
    } catch(e) {
      done(e);
    };
  });
}

/**
 * Setup SSLv3 because of MercadoLibre API requirement
 */
https.globalAgent.options.secureProtocol = 'SSLv3_method';

/**
 * Expose `Strategy`.
 */
module.exports = Strategy;
<commit_msg>Chau SSL3<commit_after>/**
 * Module dependencies.
 */
var util = require('util')
  , OAuth2Strategy = require('passport-oauth-no-web-server').OAuth2Strategy
  , https = require('https');

/**
 * `Strategy` constructor.
 *
 * The Mercadolibre authentication strategy authenticates requests by delegating
 * to Mercadolibre using the OAuth 2.0 protocol.
 *
 * Applications must supply a `verify` callback which accepts an `accessToken`,
 * `refreshToken` and service-specific `profile`, and then calls the `done`
 * callback supplying a `user`, which should be set to `false` if the
 * credentials are not valid.  If an exception occured, `err` should be set.
 *
 * Options:
 *   - `clientID`      your Mercadolibre application's client id
 *   - `clientSecret`  your Mercadolibre application's client secret
 *   - `callbackURL`   URL to which Mercadolibre will redirect the user after granting authorization
 *
 * Examples:
 *
 *     passport.use(new MercadolibreStrategy({
 *         clientID: '2288989987133514',
 *         clientSecret: 'shhh-its-a-secret'
 *         callbackURL: 'https://www.example.net/auth/mercadolibre/callback'
 *       },
 *       function(accessToken, refreshToken, profile, done) {
 *         User.findOrCreate(..., function (err, user) {
 *           done(err, user);
 *         });
 *       }
 *     ));
 *
 * @param {Object} options
 * @param {Function} verify
 * @api public
 */
function Strategy(options, verify) {
  options = options || {};
  options.authorizationURL = options.authorizationURL || 'https://auth.mercadolibre.com.ar/authorization';
  options.tokenURL = options.tokenURL || 'https://api.mercadolibre.com/oauth/token';
  this.profileUrl = options.profileUrl || "https://api.mercadolibre.com/users/me";

  OAuth2Strategy.call(this, options, verify);
  this.name = 'mercadolibre';
}

/**
 * Inherit from `OAuth2Strategy`.
 */
util.inherits(Strategy, OAuth2Strategy);

/**
 * Retrieve user profile from Mercadolibre.
 *
 * This function constructs a normalized profile.
 *
 * @param {String} accessToken
 * @param {Function} done
 * @api protected
 */
Strategy.prototype.userProfile = function(accessToken, done) {

  this._oauth2.get(this.profileUrl, accessToken, function (err, body, res) {

    if (err) { return done(err); };

    try {
      var json = JSON.parse(body);
      console.log(json);
      var profile = { provider: 'MercadoLibre' };
      profile.id = json.id;
      profile.nickname = json.nickname;
      profile.first_name = json.first_name;
      profile.last_name = json.last_name;
      profile.email = json.email;
      profile.accessToken = accessToken;
      profile._raw = body;
      profile._json = json;

      done(null, profile);
    } catch(e) {
      done(e);
    };
  });
}

/**
 * Expose `Strategy`.
 */
module.exports = Strategy;
<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */
var parse = require('./profile').parse
  , util = require('util')
  , url = require('url')
  , OAuth2Strategy = require('passport-oauth2')
  , InternalOAuthError = require('passport-oauth2').InternalOAuthError
  , VkontakteAuthorizationError = require('./errors/vkontakteauthorizationerror')
  , VkontakteTokenError = require('./errors/vkontaktetokenerror')
  , VkontakteAPIError = require('./errors/vkontakteapierror');

/**
 * `Strategy` constructor.
 *
 * The VK.com authentication strategy authenticates requests by delegating to
 * VK.com using the OAuth 2.0 protocol.
 *
 * Applications must supply a `verify` callback which accepts an `accessToken`,
 * `refreshToken` and service-specific `profile`, and then calls the `done`
 * callback supplying a `user`, which should be set to `false` if the
 * credentials are not valid.  If an exception occured, `err` should be set.
 *
 * Options:
 *   - `clientID`      your VK.com application's App ID
 *   - `clientSecret`  your VK.com application's App Secret
 *   - `callbackURL`   URL to which VK.com will redirect the user after granting authorization
 *   - `profileFields` array of fields to retrieve from VK.com
 *   - `apiVersion`    version of VK API to use
 *
 * Examples:
 *
 *     passport.use(new VKontakteStrategy({
 *         clientID: '123-456-789',
 *         clientSecret: 'shhh-its-a-secret'
 *         callbackURL: 'https://www.example.net/auth/facebook/callback'
 *       },
 *       function(accessToken, refreshToken, profile, done) {
 *         User.findOrCreate(..., function (err, user) {
 *           done(err, user);
 *         });
 *       }
 *     ));
 *
 * @param {Object} options
 * @param {Function} verify
 * @api public
 */
function Strategy(options, verify) {
  options = options || {};
  options.authorizationURL = options.authorizationURL || 'https://oauth.vk.com/authorize';
  options.tokenURL = options.tokenURL || 'https://oauth.vk.com/access_token';
  options.scopeSeparator = options.scopeSeparator || ',';
  options.passReqToCallback = false;
  this.lang = options.lang || 'en';
  this.photoSize = options.photoSize || 200;

  // since options.lang have nothing to do with OAuth2Strategy
  delete options.lang;
  delete options.photoSize;

  OAuth2Strategy.call(this, options, verifyWrapper(options, verify));
  this.name = 'vkontakte';
  this._profileURL = options.profileURL || 'https://api.vk.com/method/users.get';
  this._profileFields = options.profileFields || [];
  this._apiVersion = options.apiVersion || '5.110'; // last API version at this moment
}

/**
 * VK doesn't allow getting user's email using its API method.
 * But, if the app requests `email` scope, it can be requested during
 * token exchange. See https://new.vk.com/dev/auth_sites
 *
 * We wrap the `verify` function supplied by the user of this library
 * in order to transparently merge the result of calling `users.get` API
 * method and this email we got.
 */
function verifyWrapper(options, verify) {

  return function passportVerify(accessToken, refreshToken, params, profile, verified) {

    if (params && params.email) {
      profile.emails = [{value: params.email}];
    }

    var arity = verify.length;
    if (arity == 5) {
      verify(accessToken, refreshToken, params, profile, verified);
    }
    else if (arity == 4) {
      verify(accessToken, refreshToken, profile, verified);
    }
    else {
      this.error(new Error('VKontakteStrategy: verify callback must take 4 or 5 parameters'));
    }
  }
}

/**
 * Inherit from `OAuth2Strategy`.
 */
util.inherits(Strategy, OAuth2Strategy);

/**
 * Return extra parameters to be included in the authorization request.
 *
 * Options:
 *  - `display`  Display mode to render dialog, { `page`, `popup`, `mobile` }.
 *
 * @param {Object} options
 * @return {Object}
 * @api protected
 */
Strategy.prototype.authorizationParams = function (options) {
  var params = {};

  // http://vk.com/dev/auth_mobile
  if (options.display) {
    params.display = options.display;
  }

  return params;
};

/**
 * Retrieve user profile from Vkontakte.
 *
 * This function constructs a normalized profile, with the following properties:
 *
 *   - `provider`         always set to `vkontakte`
 *   - `id`               the user's VK.com ID
 *   - `displayName`      the user's full name
 *   - `name.familyName`  the user's last name
 *   - `name.givenName`   the user's first name
 *   - `gender`           the user's gender: `male` or `female`
 *   - `photos`           array of `{ value: 'url' }`
 *   - `city`             the user's city (if requested by specifying it in profileFields setting)
 *
 * @param {String} accessToken
 * @param {Function} done
 * @api protected
 */
Strategy.prototype.userProfile = function(accessToken, done) {
  var url = this._profileURL;

  var fields = [
    'uid'
  , 'first_name'
  , 'last_name'
  , 'screen_name'
  , 'sex'
  , 'photo_' + this.photoSize,
  ];

  this._profileFields.forEach(function(f) {
    if (fields.indexOf(f) < 0) fields.push(f);
  });

  url += '?fields=' + fields.join(',') + '&v='+this._apiVersion + '&https=1';

  if (this.lang) url += '&lang=' + this.lang;

  this._oauth2.getProtectedResource(url, accessToken, function (err, body, res) {
    if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }

    try {
      var json = JSON.parse(body);
      if (json.error) throw new VkontakteAPIError(json.error.error_msg, json.error.error_code);
      json = json.response[0];

      var profile = parse(json);
      profile.provider = 'vkontakte';
      profile._raw = body;
      profile._json = json;

      done(null, profile);
    } catch(e) {
      done(e);
    }
  });
}

/**
 * Parse error response from Vkontakte OAuth 2.0 token endpoint.
 *
 * @param {String} body
 * @param {Number} status
 * @return {Error}
 * @api protected
 */
Strategy.prototype.parseErrorResponse = function(body, status) {
  var json = JSON.parse(body);
  if (json.error && typeof json.error == 'object') {
    return new VkontakteTokenError(json.error.error_msg, json.error.error_code);
  }
  return OAuth2Strategy.prototype.parseErrorResponse.call(this, body, status);
};

/**
 * Expose `Strategy`.
 */
module.exports = Strategy;
<commit_msg>feat: pass req on 6 arguments<commit_after>/**
 * Module dependencies.
 */
var parse = require('./profile').parse
  , util = require('util')
  , url = require('url')
  , OAuth2Strategy = require('passport-oauth2')
  , InternalOAuthError = require('passport-oauth2').InternalOAuthError
  , VkontakteAuthorizationError = require('./errors/vkontakteauthorizationerror')
  , VkontakteTokenError = require('./errors/vkontaktetokenerror')
  , VkontakteAPIError = require('./errors/vkontakteapierror');

/**
 * `Strategy` constructor.
 *
 * The VK.com authentication strategy authenticates requests by delegating to
 * VK.com using the OAuth 2.0 protocol.
 *
 * Applications must supply a `verify` callback which accepts an `accessToken`,
 * `refreshToken` and service-specific `profile`, and then calls the `done`
 * callback supplying a `user`, which should be set to `false` if the
 * credentials are not valid.  If an exception occured, `err` should be set.
 *
 * Options:
 *   - `clientID`      your VK.com application's App ID
 *   - `clientSecret`  your VK.com application's App Secret
 *   - `callbackURL`   URL to which VK.com will redirect the user after granting authorization
 *   - `profileFields` array of fields to retrieve from VK.com
 *   - `apiVersion`    version of VK API to use
 *
 * Examples:
 *
 *     passport.use(new VKontakteStrategy({
 *         clientID: '123-456-789',
 *         clientSecret: 'shhh-its-a-secret'
 *         callbackURL: 'https://www.example.net/auth/facebook/callback'
 *       },
 *       function(accessToken, refreshToken, profile, done) {
 *         User.findOrCreate(..., function (err, user) {
 *           done(err, user);
 *         });
 *       }
 *     ));
 *
 * @param {Object} options
 * @param {Function} verify
 * @api public
 */
function Strategy(options, verify) {
  options = options || {};
  options.authorizationURL = options.authorizationURL || 'https://oauth.vk.com/authorize';
  options.tokenURL = options.tokenURL || 'https://oauth.vk.com/access_token';
  options.scopeSeparator = options.scopeSeparator || ',';
  options.passReqToCallback = true;
  this.lang = options.lang || 'en';
  this.photoSize = options.photoSize || 200;

  // since options.lang have nothing to do with OAuth2Strategy
  delete options.lang;
  delete options.photoSize;

  OAuth2Strategy.call(this, options, verifyWrapper(options, verify));
  this.name = 'vkontakte';
  this._profileURL = options.profileURL || 'https://api.vk.com/method/users.get';
  this._profileFields = options.profileFields || [];
  this._apiVersion = options.apiVersion || '5.110'; // last API version at this moment
}

/**
 * VK doesn't allow getting user's email using its API method.
 * But, if the app requests `email` scope, it can be requested during
 * token exchange. See https://new.vk.com/dev/auth_sites
 *
 * We wrap the `verify` function supplied by the user of this library
 * in order to transparently merge the result of calling `users.get` API
 * method and this email we got.
 */
function verifyWrapper(options, verify) {

  return function passportVerify(req, accessToken, refreshToken, params, profile, verified) {

    if (params && params.email) {
      profile.emails = [{value: params.email}];
    }

    var arity = verify.length;
    if (arity == 6) {
      verify(req, accessToken, refreshToken, params, profile, verified);
    } if (arity == 5) {
      verify(accessToken, refreshToken, params, profile, verified);
    }
    else if (arity == 4) {
      verify(accessToken, refreshToken, profile, verified);
    }
    else {
      this.error(new Error('VKontakteStrategy: verify callback must take 4 or 5 parameters'));
    }
  }
}

/**
 * Inherit from `OAuth2Strategy`.
 */
util.inherits(Strategy, OAuth2Strategy);

/**
 * Return extra parameters to be included in the authorization request.
 *
 * Options:
 *  - `display`  Display mode to render dialog, { `page`, `popup`, `mobile` }.
 *
 * @param {Object} options
 * @return {Object}
 * @api protected
 */
Strategy.prototype.authorizationParams = function (options) {
  var params = {};

  // http://vk.com/dev/auth_mobile
  if (options.display) {
    params.display = options.display;
  }

  return params;
};

/**
 * Retrieve user profile from Vkontakte.
 *
 * This function constructs a normalized profile, with the following properties:
 *
 *   - `provider`         always set to `vkontakte`
 *   - `id`               the user's VK.com ID
 *   - `displayName`      the user's full name
 *   - `name.familyName`  the user's last name
 *   - `name.givenName`   the user's first name
 *   - `gender`           the user's gender: `male` or `female`
 *   - `photos`           array of `{ value: 'url' }`
 *   - `city`             the user's city (if requested by specifying it in profileFields setting)
 *
 * @param {String} accessToken
 * @param {Function} done
 * @api protected
 */
Strategy.prototype.userProfile = function(accessToken, done) {
  var url = this._profileURL;

  var fields = [
    'uid'
  , 'first_name'
  , 'last_name'
  , 'screen_name'
  , 'sex'
  , 'photo_' + this.photoSize,
  ];

  this._profileFields.forEach(function(f) {
    if (fields.indexOf(f) < 0) fields.push(f);
  });

  url += '?fields=' + fields.join(',') + '&v='+this._apiVersion + '&https=1';

  if (this.lang) url += '&lang=' + this.lang;

  this._oauth2.getProtectedResource(url, accessToken, function (err, body, res) {
    if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }

    try {
      var json = JSON.parse(body);
      if (json.error) throw new VkontakteAPIError(json.error.error_msg, json.error.error_code);
      json = json.response[0];

      var profile = parse(json);
      profile.provider = 'vkontakte';
      profile._raw = body;
      profile._json = json;

      done(null, profile);
    } catch(e) {
      done(e);
    }
  });
}

/**
 * Parse error response from Vkontakte OAuth 2.0 token endpoint.
 *
 * @param {String} body
 * @param {Number} status
 * @return {Error}
 * @api protected
 */
Strategy.prototype.parseErrorResponse = function(body, status) {
  var json = JSON.parse(body);
  if (json.error && typeof json.error == 'object') {
    return new VkontakteTokenError(json.error.error_msg, json.error.error_code);
  }
  return OAuth2Strategy.prototype.parseErrorResponse.call(this, body, status);
};

/**
 * Expose `Strategy`.
 */
module.exports = Strategy;
<|endoftext|>


<commit_before>'use strict';
module.exports = () => {
  return {
    prefix: 'subg-',
    checkVoiceUpdate(client, oldMember, newMember) {
      // Only in Armory and Test Discord
      if (newMember.guild.id !== '84764735832068096' && newMember.guild.id !== '140025699867164673') return;
      const oldVoice = oldMember.voiceChannel;
      const newVoice = newMember.voiceChannel;

      // Left a sub voice channel and is not in a new voice channel
      if (oldVoice && !newVoice && oldVoice.name.includes(this.prefix)) return this.moveMembers(client, false);
      // Joined a sub voice channel and was not in one prior
      if (!oldVoice && newVoice && newVoice.name.includes(this.prefix)) return this.moveMembers(client, false);
      // Moved voice channels
      if (oldVoice && newVoice) {
        // If coming from other voice channel to subg- voice
        if (!oldVoice.name.includes(this.prefix) &&
          newVoice.name.includes(this.prefix)) return this.moveMembers(client, false);
        // If leaving from subg- voice to channel other than streamer lounge
        if (oldVoice.name.includes(this.prefix) &&
          newVoice.name !== 'Streamer Lounge' &&
          newVoice.position > oldVoice.position) return this.moveMembers(client, false);
      }
    },
    moveMembers(client, sendToStreamerLounge) {
      return new Promise((resolve, reject) => {
        // Get Guild
        const guild = client.guilds.get('140025699867164673');
        if (!guild) return reject('No Guild');
        // Get subg- voice channels
        const voiceChannels = guild.channels
          .filter(x => x.type === 'voice' && x.name.includes(this.prefix))
          .sort((a, b) => {
            const posA = parseInt(a.name.split(this.prefix)[1]);
            const posB = parseInt(b.name.split(this.prefix)[1]);
            return posA - posB;
          });
        if (!voiceChannels || voiceChannels.length === 0) return reject('No Voice Channels');
        // Get streamer lounge voice channel
        const streamerLounge = guild.channels.find('name', 'Streamer Lounge');
        if (!streamerLounge) return reject('No Streamer Lounge');
        const membersInVoiceChannels = [];
        voiceChannels.forEach(x => {
          if (x.members.first()) {
            membersInVoiceChannels.push(x.members.first());
          }
        });
        const voiceChannelIds = voiceChannels.map(x => x.id);
        if (sendToStreamerLounge) {
          voiceChannelIds.unshift(streamerLounge.id);
        }
        const setVoiceArray = [];
        for (let i = 0; i < membersInVoiceChannels.length; i++) {
          // Only set channel if channel is different
          // Reduces api calls
          if (membersInVoiceChannels[i].voiceChannel.id !== voiceChannelIds[i]) {
            setVoiceArray.push(membersInVoiceChannels[i].setVoiceChannel(voiceChannelIds[i]));
          }
        }
        Promise.all(setVoiceArray)
          .then(() => {
            const memberToNotify = sendToStreamerLounge ? membersInVoiceChannels[1] : membersInVoiceChannels[0];
            if (memberToNotify) {
              const anneMLG = client.emojis.get('471888236927516703') || client.emojis.get('471888150281715712');
              memberToNotify.user.send('Hello!\nYou are in Position **#1** in queue for ' + // eslint-disable-line prefer-template, max-len
                'AnneMunition\'s Sub Games.\nIf you are using a microphone, please make sure it is connected and ' +
                'working.\nHave a fun time! ' + (anneMLG || ''))
                .catch(() => {
                  // Do Nothing
                });
            }
            resolve();
          })
          .catch(reject);
      });
    },
  };
};
<commit_msg>Get armory or test server<commit_after>'use strict';
module.exports = () => {
  return {
    prefix: 'subg-',
    checkVoiceUpdate(client, oldMember, newMember) {
      // Only in Armory and Test Discord
      if (newMember.guild.id !== '84764735832068096' && newMember.guild.id !== '140025699867164673') return;
      const oldVoice = oldMember.voiceChannel;
      const newVoice = newMember.voiceChannel;

      // Left a sub voice channel and is not in a new voice channel
      if (oldVoice && !newVoice && oldVoice.name.includes(this.prefix)) return this.moveMembers(client, false);
      // Joined a sub voice channel and was not in one prior
      if (!oldVoice && newVoice && newVoice.name.includes(this.prefix)) return this.moveMembers(client, false);
      // Moved voice channels
      if (oldVoice && newVoice) {
        // If coming from other voice channel to subg- voice
        if (!oldVoice.name.includes(this.prefix) &&
          newVoice.name.includes(this.prefix)) return this.moveMembers(client, false);
        // If leaving from subg- voice to channel other than streamer lounge
        if (oldVoice.name.includes(this.prefix) &&
          newVoice.name !== 'Streamer Lounge' &&
          newVoice.position > oldVoice.position) return this.moveMembers(client, false);
      }
    },
    moveMembers(client, sendToStreamerLounge) {
      return new Promise((resolve, reject) => {
        // Get Guild
        const guild = client.guilds.get('84764735832068096') || client.guilds.get('140025699867164673');
        if (!guild) return reject('No Guild');
        // Get subg- voice channels
        const voiceChannels = guild.channels
          .filter(x => x.type === 'voice' && x.name.includes(this.prefix))
          .sort((a, b) => {
            const posA = parseInt(a.name.split(this.prefix)[1]);
            const posB = parseInt(b.name.split(this.prefix)[1]);
            return posA - posB;
          });
        if (!voiceChannels || voiceChannels.length === 0) return reject('No Voice Channels');
        // Get streamer lounge voice channel
        const streamerLounge = guild.channels.find('name', 'Streamer Lounge');
        if (!streamerLounge) return reject('No Streamer Lounge');
        const membersInVoiceChannels = [];
        voiceChannels.forEach(x => {
          if (x.members.first()) {
            membersInVoiceChannels.push(x.members.first());
          }
        });
        const voiceChannelIds = voiceChannels.map(x => x.id);
        if (sendToStreamerLounge) {
          voiceChannelIds.unshift(streamerLounge.id);
        }
        const setVoiceArray = [];
        for (let i = 0; i < membersInVoiceChannels.length; i++) {
          // Only set channel if channel is different
          // Reduces api calls
          if (membersInVoiceChannels[i].voiceChannel.id !== voiceChannelIds[i]) {
            setVoiceArray.push(membersInVoiceChannels[i].setVoiceChannel(voiceChannelIds[i]));
          }
        }
        Promise.all(setVoiceArray)
          .then(() => {
            const memberToNotify = sendToStreamerLounge ? membersInVoiceChannels[1] : membersInVoiceChannels[0];
            if (memberToNotify) {
              const anneMLG = client.emojis.get('471888236927516703') || client.emojis.get('471888150281715712');
              memberToNotify.user.send('Hello!\nYou are in Position **#1** in queue for ' + // eslint-disable-line prefer-template, max-len
                'AnneMunition\'s Sub Games.\nIf you are using a microphone, please make sure it is connected and ' +
                'working.\nHave a fun time! ' + (anneMLG || ''))
                .catch(() => {
                  // Do Nothing
                });
            }
            resolve();
          })
          .catch(reject);
      });
    },
  };
};
<|endoftext|>


<commit_before>/**  @author Gilles Coomans <gilles.coomans@gmail.com> */
"use strict";

var utils = require('./utils'),
	interpolable = require('./interpolable').interpolable,
	Context = require('./context'),
	api = require('./api'),
	listenerParser = require('./parsers/listener-call');

function argToArr(arg, start) {
	return Array.prototype.slice.call(arg, start);
}

function parseMap(map, methodName) {
	var t = y();
	for (var i in map)
		t[methodName](i, map[i]);
	return t;
}

function parseAttrMap(attrMap) {
	var t = y(),
		keys = Object.keys(attrMap);
	for (var i = 0, l = keys.length; i < l; i++) {
		var prop = keys[i],
			value = attrMap[prop];
		switch (prop) {
			case 'style':
				t.use(parseMap(value, 'css'));
				break;
			case 'classes':
				t.use(parseMap(value, 'cl'));
				break;
			case 'value':
				t.val(value);
				break;
			case 'disabled':
				t.disabled(value);
				break;
			case 'visible':
				t.visible(value);
				break;
			default: // attr
				t.attr(prop, value);
		}
	}
	return t;
};
utils.parseAttrMap = parseAttrMap;

function execEngineBlock(templ, block, args, suspendAfter) {
	templ._queue.push({
		engineBlock: block,
		args: args,
		suspendAfter: suspendAfter
	});
	return templ;
}

function Template(t) {
	this.__yTemplate__ = true;
	if (t) {
		if (t.forEach)
			this._queue = t;
		else
			this._queue = t._queue.slice();
	} else
		this._queue = [];
}

function y() {
	return new Template();
}

function enqueue(templ, type, handler, args, suspendAfter) {
	templ._queue.push({
		type: type, // means engine related
		handler: handler,
		args: args,
		suspendAfter: suspendAfter
	});
	return templ;
}

Template.prototype = {
	//___________________________________________ EXECUTIONS HANDLERS
	dom: function(func, args, suspendAfter) {
		return enqueue(this, 'dom', func, args, suspendAfter);
	},
	string: function(func, args, suspendAfter) {
		return enqueue(this, 'string', func, args, suspendAfter);
	},
	context: function(func, args, suspendAfter) {
		return enqueue(this, 'context', func, args, suspendAfter);
	},
	// normally you should never call this one (only for internal usage)
	exec: function(name, args, suspendAfter) {
		if (typeof name === 'object')
			return enqueue(this, 'custom', name, args, suspendAfter);
		return enqueue(this, '*', name, args, suspendAfter);
	},
	// normally you should never call this one (only for internal context tricks)
	firstPass: function(func, args, suspendAfter) {
		return enqueue(this, 'firstPass', func, args, suspendAfter);
	},
	// normally you should never call this one (only for internal context tricks)
	secondPass: function(func, args, suspendAfter) {
		return enqueue(this, 'secondPass', func, args, suspendAfter);
	},
	//________________________________ CONTEXT and Assignation
	set: function(path, value) {
		return this.context(function(context) {
			context.set(path, value);
		});
	},
	setAsync: function(path, value) {
		return this.context(function(context) {
			context.setAsync(path, value);
		});
	},
	dependent: function(path, args, func) {
		return this.context(function(context) {
			context.dependent(path, args, func);
		});
	},
	push: function(path, value) {
		return this.context(function(context) {
			context.push(path, value);
		});
	},
	pushAsync: function(path, value) {
		return this.context(function(context) {
			context.pushAsync(path, value);
		});
	},
	toggle: function(path) {
		return this.context(function(context) {
			context.toggle(path);
		});
	},
	toggleInArray: function(path) {
		return this.context(function(context) {
			context.toggleInArray(path);
		});
	},
	del: function(path) {
		return this.context(function(context) {
			context.del(path);
		});
	},
	newContext: function(data, parent, path) {
		var path;
		if (typeof data === 'string') {
			path = data;
			data = null;
		}
		return this.exec('newContext', [path ? undefined : data, parent, path]);
	},
	with: function(path, template) {
		return this.exec('with', [path, template]);
	},
	subscribe: function(path, handler, upstream) {
		return this.context(function(context) {
			context.subscribe(path, handler, upstream);
		});
	},
	unsubscribe: function(path, handler, upstream) {
		return this.context(function(context) {
			context.unsubscribe(path, handler, upstream);
		});
	},
	//__________________________________ Agora
	onAgora: function(name, handler) {
		return this.context(function(context) {
			context.onAgora(name, handler);
		});
	},
	offAgora: function(name, handler) {
		return this.context(function(context) {
			context.offAgora(name, handler);
		});
	},
	toAgora: function(name, msg) {
		return this.context(function(context) {
			context.toAgora(name, msg);
		});
	},
	//__________________________________ Attributes
	attr: function(name, value) {
		return this.exec('attr', [name, typeof value !== 'undefined' ? interpolable(value) : undefined]);
	},
	id: function(value) {
		return this.attr('id', value);
	},
	disabled: function(xpr) {
		return this.exec('disabled', [interpolable(xpr)]);
	},
	val: function(value) {
		var varPath;
		if (typeof value === 'string') {
			value = interpolable(value);
			if (value.__interpolable__) {
				if (value.dependenciesCount !== 1)
					throw new Error("template.val expression could only depend to one variable.");
				varPath = value.parts[1].dep[0];
			}
		}
		return this.exec('val', [varPath, value]);
	},
	setClass: function(name, flag) {
		if (typeof flag === 'undefined')
			flag = true;
		else
			flag = interpolable(flag);
		return this.exec('setClass', [interpolable(name), flag]);
	},
	css: function(prop, value) {
		return this.exec('css', [prop, interpolable(value)]);
	},
	visible: function(flag) {
		return this.exec('visible', [interpolable(flag)]);
	},
	//_______________________________________ HTML TAGS

	text: function(value) {
		return this.exec('text', [interpolable(value)]);
	},
	tag: function(name, attrMap) { // arguments : name, ?(attrMap|template1), ?t2, ...
		var t,
			hasAttrMap = (attrMap && typeof attrMap === 'object' && !attrMap.__yTemplate__) ? attrMap : null;
		if (hasAttrMap)
			t = parseAttrMap(attrMap);
		else
			t = y();
		// still to convert string arguments to .text(...)
		for (var i = (hasAttrMap ? 2 : 1), len = arguments.length; i < len; ++i) {
			if (!arguments[i])
				continue;
			if (!arguments[i].__yTemplate__)
				t.text(arguments[i]);
			else
				t.use(arguments[i]);
		}
		return this.exec('tag', [name, t]);
	},
	br: function() {
		return this.exec('br');
	},
	a: function(href) {
		var args = argToArr(arguments, 1);
		args.unshift('a', typeof href === 'string' ? y().attr('href', href) : href);
		return this.tag.apply(this, args);
	},
	img: function(src) {
		var args = argToArr(arguments, 1);
		args.unshift('img', typeof src === 'string' ? y().attr('src', src) : src);
		return this.tag.apply(this, args);
	},
	/**
	 * add input tag.    .input(type, value, t1, t2, ...)   or .input(attrMap, t1, t2, ...)
	 * @param  {String} type  'text' or 'password' or ...
	 * @param  {*} value the value to pass to .val(...). Could be a string, an interpolable string, or a direct value (aka an int, a bool, ...)
	 * @return {Template}     current template handler
	 */
	input: function(type, value) {
		var args;
		if (typeof type === 'string') {
			args = argToArr(arguments, 2);
			var template = y().attr('type', type);
			if (value)
				template.val(value);
			args.unshift('input', template);
		} else
			args = argToArr(arguments);
		return this.tag.apply(this, args);
	},
	h: function(level) {
		var args = argToArr(arguments, 1);
		args.unshift('h' + level);
		return this.tag.apply(this, args);
	},
	space: function() {
		return this.exec('text', [' ']);
	},
	nbsp: function() {
		return this.exec('text', ['\u00A0']);
	},
	//___________________________________ EVENTS LISTENER
	on: function(name, handler) {
		if (typeof handler === 'string')
			handler = listenerParser.parseListener(handler);
		return this.exec('on', [name, handler]);
	},
	once: function(name, handler) {
		if (typeof handler === 'string')
			handler = listenerParser.parseListener(handler);
		return this.exec('once', [name, handler]);
	},
	off: function(name, handler) {
		if (typeof handler === 'string')
			handler = listenerParser.parseListener(handler);
		return this.exec('off', [name, handler]);
	},
	//___________________________________________ Collection
	each: function(path, templ, emptyTempl) {
		if (!templ)
			throw new Error('yamvish each methods needs a template. (path : ' + path + ')');
		templ = (typeof templ === 'string') ? y().use(templ) : templ;
		if (emptyTempl)
			emptyTempl = (typeof emptyTempl === 'string') ? y().use(emptyTempl) : emptyTempl;
		return this.exec('each', [path, templ, emptyTempl]);
	},
	//_____________________________ Conditional node rendering
	if: function(condition, successTempl, failTempl) {
		successTempl = (typeof successTempl === 'string') ? y().use(successTempl) : successTempl;
		if (failTempl)
			failTempl = (typeof failTempl === 'string') ? y().use(failTempl) : failTempl;
		return this.exec('if', [interpolable(condition), successTempl, failTempl]);
	},
	switch: function(xpr, map) {
		for (var i in map)
			if (typeof map[i] === 'string')
				map[i] = y().text(map[i]);
		return this.exec('switch', [interpolable(xpr), map]);
	},
	//____________________________________________ API mngt
	use: function(name) {
		if (!name)
			return this;
		var args = argToArr(arguments);
		args.shift();
		if (typeof name === 'string')
			name = name.split(':');
		var method = (name.forEach ? utils.getApiMethod(api, name) : name);
		if (method.__yView__)
			return this.exec('mountHere', [method]);
		else if (method.__yTemplate__)
			this._queue = this._queue.concat(method._queue);
		else
			method.apply(this, args);
		return this;
	},
	// for lazzy people :
	// add all methods from given api to current template instance (will only affect current chain)
	addApi: function(name) {
		var Api = (typeof name === 'string') ? api[name] : name;
		if (!Api)
			throw new Error('no template api found with : ' + name);
		for (var i in Api)
			this[i] = Api[i];
		return this;
	},
	// mount template as container here
	mountHere: function(templ) {
		return this.exec('mountHere', [templ]);
	},
	// conditionnal client/server template execution
	client: function(templ) {
		templ = (!templ.__yTemplate__) ? y().use(templ) : templ;
		return this.exec('client', [templ]);
	},
	server: function(templ) {
		templ = (!templ.__yTemplate__) ? y().use(templ) : templ;
		return this.exec('server', [templ]);
	},
	// suspend render until xpr could be evaluated to true
	suspendUntil: function(xpr) {
		return this.exec('suspendUntil', [interpolable(xpr), this._queue.length + 1, this], true);
	}
};

Template.addAPI = function(api) {
	for (var i in api)
		Template.prototype[i] = api[i];
	return Template;
};

Template.prototype.cl = Template.prototype.setClass;

// Complete tag list
['div', 'span', 'ul', 'li', 'button', 'p', 'form', 'table', 'tr', 'td', 'th', 'section', 'code', 'pre', 'q', 'blockquote', 'style', 'nav', 'article', 'header', 'footer', 'aside', 'label', 'select', 'option']
.forEach(function(tag) {
	Template.prototype[tag] = function() {
		var args = argToArr(arguments);
		args.unshift(tag);
		return this.tag.apply(this, args);
	};
});
// Complete events list
['click', 'blur', 'focus', 'submit']
.forEach(function(eventName) {
	Template.prototype[eventName] = function(handler) {
		return this.on(eventName, handler);
	};
});

module.exports = Template;
<commit_msg>click-blur-focus-... for pure Dom Nodes<commit_after>/**  @author Gilles Coomans <gilles.coomans@gmail.com> */
"use strict";

var utils = require('./utils'),
	interpolable = require('./interpolable').interpolable,
	Context = require('./context'),
	api = require('./api'),
	listenerParser = require('./parsers/listener-call');

function argToArr(arg, start) {
	return Array.prototype.slice.call(arg, start);
}

function parseMap(map, methodName) {
	var t = y();
	for (var i in map)
		t[methodName](i, map[i]);
	return t;
}

function parseAttrMap(attrMap) {
	var t = y(),
		keys = Object.keys(attrMap);
	for (var i = 0, l = keys.length; i < l; i++) {
		var prop = keys[i],
			value = attrMap[prop];
		switch (prop) {
			case 'style':
				t.use(parseMap(value, 'css'));
				break;
			case 'classes':
				t.use(parseMap(value, 'cl'));
				break;
			case 'value':
				t.val(value);
				break;
			case 'disabled':
				t.disabled(value);
				break;
			case 'visible':
				t.visible(value);
				break;
			default: // attr
				t.attr(prop, value);
		}
	}
	return t;
};
utils.parseAttrMap = parseAttrMap;

function execEngineBlock(templ, block, args, suspendAfter) {
	templ._queue.push({
		engineBlock: block,
		args: args,
		suspendAfter: suspendAfter
	});
	return templ;
}

function Template(t) {
	this.__yTemplate__ = true;
	if (t) {
		if (t.forEach)
			this._queue = t;
		else
			this._queue = t._queue.slice();
	} else
		this._queue = [];
}

function y() {
	return new Template();
}

function enqueue(templ, type, handler, args, suspendAfter) {
	templ._queue.push({
		type: type, // means engine related
		handler: handler,
		args: args,
		suspendAfter: suspendAfter
	});
	return templ;
}

Template.prototype = {
	//___________________________________________ EXECUTIONS HANDLERS
	dom: function(func, args, suspendAfter) {
		return enqueue(this, 'dom', func, args, suspendAfter);
	},
	string: function(func, args, suspendAfter) {
		return enqueue(this, 'string', func, args, suspendAfter);
	},
	context: function(func, args, suspendAfter) {
		return enqueue(this, 'context', func, args, suspendAfter);
	},
	// normally you should never call this one (only for internal usage)
	exec: function(name, args, suspendAfter) {
		if (typeof name === 'object')
			return enqueue(this, 'custom', name, args, suspendAfter);
		return enqueue(this, '*', name, args, suspendAfter);
	},
	// normally you should never call this one (only for internal context tricks)
	firstPass: function(func, args, suspendAfter) {
		return enqueue(this, 'firstPass', func, args, suspendAfter);
	},
	// normally you should never call this one (only for internal context tricks)
	secondPass: function(func, args, suspendAfter) {
		return enqueue(this, 'secondPass', func, args, suspendAfter);
	},
	//________________________________ CONTEXT and Assignation
	set: function(path, value) {
		return this.context(function(context) {
			context.set(path, value);
		});
	},
	setAsync: function(path, value) {
		return this.context(function(context) {
			context.setAsync(path, value);
		});
	},
	dependent: function(path, args, func) {
		return this.context(function(context) {
			context.dependent(path, args, func);
		});
	},
	push: function(path, value) {
		return this.context(function(context) {
			context.push(path, value);
		});
	},
	pushAsync: function(path, value) {
		return this.context(function(context) {
			context.pushAsync(path, value);
		});
	},
	toggle: function(path) {
		return this.context(function(context) {
			context.toggle(path);
		});
	},
	toggleInArray: function(path) {
		return this.context(function(context) {
			context.toggleInArray(path);
		});
	},
	del: function(path) {
		return this.context(function(context) {
			context.del(path);
		});
	},
	newContext: function(data, parent, path) {
		var path;
		if (typeof data === 'string') {
			path = data;
			data = null;
		}
		return this.exec('newContext', [path ? undefined : data, parent, path]);
	},
	with: function(path, template) {
		return this.exec('with', [path, template]);
	},
	subscribe: function(path, handler, upstream) {
		return this.context(function(context) {
			context.subscribe(path, handler, upstream);
		});
	},
	unsubscribe: function(path, handler, upstream) {
		return this.context(function(context) {
			context.unsubscribe(path, handler, upstream);
		});
	},
	//__________________________________ Agora
	onAgora: function(name, handler) {
		return this.context(function(context) {
			context.onAgora(name, handler);
		});
	},
	offAgora: function(name, handler) {
		return this.context(function(context) {
			context.offAgora(name, handler);
		});
	},
	toAgora: function(name, msg) {
		return this.context(function(context) {
			context.toAgora(name, msg);
		});
	},
	//__________________________________ Attributes
	attr: function(name, value) {
		return this.exec('attr', [name, typeof value !== 'undefined' ? interpolable(value) : undefined]);
	},
	id: function(value) {
		return this.attr('id', value);
	},
	disabled: function(xpr) {
		return this.exec('disabled', [interpolable(xpr)]);
	},
	val: function(value) {
		var varPath;
		if (typeof value === 'string') {
			value = interpolable(value);
			if (value.__interpolable__) {
				if (value.dependenciesCount !== 1)
					throw new Error("template.val expression could only depend to one variable.");
				varPath = value.parts[1].dep[0];
			}
		}
		return this.exec('val', [varPath, value]);
	},
	setClass: function(name, flag) {
		if (typeof flag === 'undefined')
			flag = true;
		else
			flag = interpolable(flag);
		return this.exec('setClass', [interpolable(name), flag]);
	},
	css: function(prop, value) {
		return this.exec('css', [prop, interpolable(value)]);
	},
	visible: function(flag) {
		return this.exec('visible', [interpolable(flag)]);
	},
	//_______________________________________ HTML TAGS

	text: function(value) {
		return this.exec('text', [interpolable(value)]);
	},
	tag: function(name, attrMap) { // arguments : name, ?(attrMap|template1), ?t2, ...
		var t,
			hasAttrMap = (attrMap && typeof attrMap === 'object' && !attrMap.__yTemplate__) ? attrMap : null;
		if (hasAttrMap)
			t = parseAttrMap(attrMap);
		else
			t = y();
		// still to convert string arguments to .text(...)
		for (var i = (hasAttrMap ? 2 : 1), len = arguments.length; i < len; ++i) {
			if (!arguments[i])
				continue;
			if (!arguments[i].__yTemplate__)
				t.text(arguments[i]);
			else
				t.use(arguments[i]);
		}
		return this.exec('tag', [name, t]);
	},
	br: function() {
		return this.exec('br');
	},
	a: function(href) {
		var args = argToArr(arguments, 1);
		args.unshift('a', typeof href === 'string' ? y().attr('href', href) : href);
		return this.tag.apply(this, args);
	},
	img: function(src) {
		var args = argToArr(arguments, 1);
		args.unshift('img', typeof src === 'string' ? y().attr('src', src) : src);
		return this.tag.apply(this, args);
	},
	/**
	 * add input tag.    .input(type, value, t1, t2, ...)   or .input(attrMap, t1, t2, ...)
	 * @param  {String} type  'text' or 'password' or ...
	 * @param  {*} value the value to pass to .val(...). Could be a string, an interpolable string, or a direct value (aka an int, a bool, ...)
	 * @return {Template}     current template handler
	 */
	input: function(type, value) {
		var args;
		if (typeof type === 'string') {
			args = argToArr(arguments, 2);
			var template = y().attr('type', type);
			if (value)
				template.val(value);
			args.unshift('input', template);
		} else
			args = argToArr(arguments);
		return this.tag.apply(this, args);
	},
	h: function(level) {
		var args = argToArr(arguments, 1);
		args.unshift('h' + level);
		return this.tag.apply(this, args);
	},
	space: function() {
		return this.exec('text', [' ']);
	},
	nbsp: function() {
		return this.exec('text', ['\u00A0']);
	},
	//___________________________________ EVENTS LISTENER
	on: function(name, handler) {
		if (typeof handler === 'string')
			handler = listenerParser.parseListener(handler);
		return this.exec('on', [name, handler]);
	},
	once: function(name, handler) {
		if (typeof handler === 'string')
			handler = listenerParser.parseListener(handler);
		return this.exec('once', [name, handler]);
	},
	off: function(name, handler) {
		if (typeof handler === 'string')
			handler = listenerParser.parseListener(handler);
		return this.exec('off', [name, handler]);
	},
	//___________________________________________ Collection
	each: function(path, templ, emptyTempl) {
		if (!templ)
			throw new Error('yamvish each methods needs a template. (path : ' + path + ')');
		templ = (typeof templ === 'string') ? y().use(templ) : templ;
		if (emptyTempl)
			emptyTempl = (typeof emptyTempl === 'string') ? y().use(emptyTempl) : emptyTempl;
		return this.exec('each', [path, templ, emptyTempl]);
	},
	//_____________________________ Conditional node rendering
	if: function(condition, successTempl, failTempl) {
		successTempl = (typeof successTempl === 'string') ? y().use(successTempl) : successTempl;
		if (failTempl)
			failTempl = (typeof failTempl === 'string') ? y().use(failTempl) : failTempl;
		return this.exec('if', [interpolable(condition), successTempl, failTempl]);
	},
	switch: function(xpr, map) {
		for (var i in map)
			if (typeof map[i] === 'string')
				map[i] = y().text(map[i]);
		return this.exec('switch', [interpolable(xpr), map]);
	},
	//____________________________________________ API mngt
	use: function(name) {
		if (!name)
			return this;
		var args = argToArr(arguments);
		args.shift();
		if (typeof name === 'string')
			name = name.split(':');
		var method = (name.forEach ? utils.getApiMethod(api, name) : name);
		if (method.__yView__)
			return this.exec('mountHere', [method]);
		else if (method.__yTemplate__)
			this._queue = this._queue.concat(method._queue);
		else
			method.apply(this, args);
		return this;
	},
	// for lazzy people :
	// add all methods from given api to current template instance (will only affect current chain)
	addApi: function(name) {
		var Api = (typeof name === 'string') ? api[name] : name;
		if (!Api)
			throw new Error('no template api found with : ' + name);
		for (var i in Api)
			this[i] = Api[i];
		return this;
	},
	// mount template as container here
	mountHere: function(templ) {
		return this.exec('mountHere', [templ]);
	},
	// conditionnal client/server template execution
	client: function(templ) {
		templ = (!templ.__yTemplate__) ? y().use(templ) : templ;
		return this.exec('client', [templ]);
	},
	server: function(templ) {
		templ = (!templ.__yTemplate__) ? y().use(templ) : templ;
		return this.exec('server', [templ]);
	},
	// suspend render until xpr could be evaluated to true
	suspendUntil: function(xpr) {
		return this.exec('suspendUntil', [interpolable(xpr), this._queue.length + 1, this], true);
	}
};

Template.addAPI = function(api) {
	for (var i in api)
		Template.prototype[i] = api[i];
	return Template;
};

Template.prototype.cl = Template.prototype.setClass;

// Complete tag list
['div', 'span', 'ul', 'li', 'button', 'p', 'form', 'table', 'tr', 'td', 'th', 'section', 'code', 'pre', 'q', 'blockquote', 'style', 'nav', 'article', 'header', 'footer', 'aside', 'label', 'select', 'option']
.forEach(function(tag) {
	Template.prototype[tag] = function() {
		var args = argToArr(arguments);
		args.unshift(tag);
		return this.tag.apply(this, args);
	};
});

// Complete events list
['click', 'blur', 'focus', 'submit']
.forEach(function(eventName) {
	Template.prototype[eventName] = function(handler, useCapture) {
		if (typeof handler === 'string')
			handler = listenerParser.parseListener(handler);
		return this.dom(function(context, node) {
			node.addEventListener(eventName, function(e) {
				handler.call(context, e);
			}, useCapture);
		});
	};
});

module.exports = Template;
<|endoftext|>


<commit_before>// eslint-disable-next-line no-unused-vars
import React from 'react';

import { Yajinni, blazyb, fel1ne, Qbz, Zerotorescue, Abelito75, Anatta336, JeremyDwayne } from 'CONTRIBUTORS';
import { change, date } from 'common/changelog';

export default [
  change(date(2020, 3, 17), 'Added Visions of Perfection Statistic', [Abelito75]),
  change(date(2020, 1, 29), 'Bumped Updated for patch to 8.3', [blazyb]),
  change(date(2020, 1, 28), 'Updated mastery coefficient and thus making spec compatible with patch 8.3.', [blazyb]),
  change(date(2019, 10, 4), 'No spec changes have occurred, restoration druid module is compatible with patch 8.2.5', [JeremyDwayne]),
  change(date(2019, 8, 18), 'Corrected some global cooldown values related to catweaving.', [Anatta336]),
  change(date(2019, 8, 12), 'Added essence Lucid Dreams.', [blazyb]),
  change(date(2019, 6, 2), 'Enabled the QElive auto import link for stat values.', [Abelito75]),
  change(date(2019, 5, 26), 'Fixed a bug where Lively Spirit increased int gain on tab switch.', [Qbz]),
  change(date(2019, 4, 27), 'Fixed a bug where regrowths during innervate were used in clearcasting calculation.', [Qbz]),
  change(date(2019, 2, 20), 'Fixed a bug where Lifebloom showed an incorrect uptime value in fights with multiple resto druids.', [Zerotorescue]),
  change(date(2019, 2, 8), 'Grove Tending now has mastery healing included.', [blazyb]),
  change(date(2019, 2, 2), 'Int gain from extra rejuvenation duration from Autumn Leaves is added.', [blazyb]),
  change(date(2019, 2, 2), 'Fixed a minor bug with rampanth growth module.', [blazyb]),
  change(date(2019, 1, 14), 'Updated Lively spirit module to include the extra mana gained', [blazyb]),
  change(date(2019, 1, 14), 'Updated Autumn Leaves module to include the extra second on rejuvenation', [blazyb]),
  change(date(2018, 12, 16), 'Added support for Azerite trait Early Harvest', [Qbz]),
  change(date(2018, 12, 7), 'Improved regrowth values in the mana efficiency tab', [blazyb]),
  change(date(2018, 11, 26), 'Replace Mana usage statistics with new Mana effiency tab', [blazyb]),
  change(date(2018, 11, 15), 'Improved flourish module to discount overhealing, resulting in slightly lower values.', [blazyb]),
  change(date(2018, 11, 14), 'Added support for Azerite trait Rampant Growth', [blazyb]),
  change(date(2018, 11, 9), 'Added checklist and suggestions for WG with high initial overhealing.', [blazyb]),
  change(date(2018, 11, 7), 'Added support for random LB blooms in Photosynthesis.', [blazyb]),
  change(date(2018, 11, 6), 'Fixed a minor bug with Flourish module and ordering of event being skewed.', [blazyb]),
  change(date(2018, 10, 19), 'Updated bad Regrowth usage suggestions - using a regrowth with sufficent abundance stacks is not considered bad', [Qbz]),
  change(date(2018, 10, 17), 'Reintroduced Wild Growth average hit tracker.', [blazyb]),
  change(date(2018, 10, 17), 'Improved Tree of Life throughput calculation (Don\'t count rejuv mana savings if player has innervate).', [blazyb]),
  change(date(2018, 10, 17), 'Fixed some minor errors for the innervate module.', [blazyb]),
  change(date(2018, 10, 16), 'Added first version of module for premature rejuvenations with statistics and suggestions', [blazyb]),
  change(date(2018, 9, 24), 'Including Grove Tending in Flourish and Photosynthesis module.', [blazyb]),
  change(date(2018, 9, 24), 'Updated Photosynthesis module to use correct calculations.', [blazyb]),
  change(date(2018, 9, 22), 'Added module for tracking Abundance.', [blazyb]),
  change(date(2018, 9, 21), 'Added tracking of healing gained from Stonebark.', [blazyb]),
  change(date(2018, 9, 21), 'Added implementation of Azerite trait Lively spirit.', [blazyb]),
  change(date(2018, 9, 20), 'Added implementation of Azerite trait Synergistic Growth.', [blazyb]),
  change(date(2018, 9, 20), 'Added mana used doughnut chart (code taken from Hunter focus used module) to resto druid & set icons for Uldir bosses (not headshots)', [fel1ne]),
  change(date(2018, 9, 20), 'Updated bad Regrowth usage suggestions - using a non clearcasting regrowth on low health target is not considered bad.', [blazyb]),
  change(date(2018, 9, 18), 'Added implementation of Azerite trait Waking Dream.', [blazyb]),
  change(date(2018, 9, 17), 'Added HoT part of tranquillity to all relevant modules. This will change the values in mastery stack, flourish module and stat tracker.', [blazyb]),
  change(date(2018, 9, 12), 'Added implementation of Azerite trait Grove Tending.', [blazyb]),
  change(date(2018, 9, 12), 'Added implementation of Azerite trait Autumn Leaves.', [blazyb]),
  change(date(2018, 9, 12), 'Added implementation of Azerite trait Fungal Essence.', [blazyb]),
  change(date(2018, 9, 12), 'Updated changes on rejuvenation mana cost from hotfix september 10th.', [blazyb]),
  change(date(2018, 9, 10), 'Cleanup of old references from Legion and new updates from BFA.', [blazyb]),
  change(date(2018, 7, 27), 'Spell, talent, and GCD update to spells for BFA.', [Yajinni]),
];
<commit_msg>loggy<commit_after>// eslint-disable-next-line no-unused-vars
import React from 'react';

import { Yajinni, blazyb, fel1ne, Qbz, Zerotorescue, Abelito75, Anatta336, JeremyDwayne } from 'CONTRIBUTORS';
import { change, date } from 'common/changelog';

export default [
  change(date(2020, 5, 26), 'Added HPCT to stat weights.', [Abelito75]),
  change(date(2020, 3, 17), 'Added Visions of Perfection Statistic', [Abelito75]),
  change(date(2020, 1, 29), 'Bumped Updated for patch to 8.3', [blazyb]),
  change(date(2020, 1, 28), 'Updated mastery coefficient and thus making spec compatible with patch 8.3.', [blazyb]),
  change(date(2019, 10, 4), 'No spec changes have occurred, restoration druid module is compatible with patch 8.2.5', [JeremyDwayne]),
  change(date(2019, 8, 18), 'Corrected some global cooldown values related to catweaving.', [Anatta336]),
  change(date(2019, 8, 12), 'Added essence Lucid Dreams.', [blazyb]),
  change(date(2019, 6, 2), 'Enabled the QElive auto import link for stat values.', [Abelito75]),
  change(date(2019, 5, 26), 'Fixed a bug where Lively Spirit increased int gain on tab switch.', [Qbz]),
  change(date(2019, 4, 27), 'Fixed a bug where regrowths during innervate were used in clearcasting calculation.', [Qbz]),
  change(date(2019, 2, 20), 'Fixed a bug where Lifebloom showed an incorrect uptime value in fights with multiple resto druids.', [Zerotorescue]),
  change(date(2019, 2, 8), 'Grove Tending now has mastery healing included.', [blazyb]),
  change(date(2019, 2, 2), 'Int gain from extra rejuvenation duration from Autumn Leaves is added.', [blazyb]),
  change(date(2019, 2, 2), 'Fixed a minor bug with rampanth growth module.', [blazyb]),
  change(date(2019, 1, 14), 'Updated Lively spirit module to include the extra mana gained', [blazyb]),
  change(date(2019, 1, 14), 'Updated Autumn Leaves module to include the extra second on rejuvenation', [blazyb]),
  change(date(2018, 12, 16), 'Added support for Azerite trait Early Harvest', [Qbz]),
  change(date(2018, 12, 7), 'Improved regrowth values in the mana efficiency tab', [blazyb]),
  change(date(2018, 11, 26), 'Replace Mana usage statistics with new Mana effiency tab', [blazyb]),
  change(date(2018, 11, 15), 'Improved flourish module to discount overhealing, resulting in slightly lower values.', [blazyb]),
  change(date(2018, 11, 14), 'Added support for Azerite trait Rampant Growth', [blazyb]),
  change(date(2018, 11, 9), 'Added checklist and suggestions for WG with high initial overhealing.', [blazyb]),
  change(date(2018, 11, 7), 'Added support for random LB blooms in Photosynthesis.', [blazyb]),
  change(date(2018, 11, 6), 'Fixed a minor bug with Flourish module and ordering of event being skewed.', [blazyb]),
  change(date(2018, 10, 19), 'Updated bad Regrowth usage suggestions - using a regrowth with sufficent abundance stacks is not considered bad', [Qbz]),
  change(date(2018, 10, 17), 'Reintroduced Wild Growth average hit tracker.', [blazyb]),
  change(date(2018, 10, 17), 'Improved Tree of Life throughput calculation (Don\'t count rejuv mana savings if player has innervate).', [blazyb]),
  change(date(2018, 10, 17), 'Fixed some minor errors for the innervate module.', [blazyb]),
  change(date(2018, 10, 16), 'Added first version of module for premature rejuvenations with statistics and suggestions', [blazyb]),
  change(date(2018, 9, 24), 'Including Grove Tending in Flourish and Photosynthesis module.', [blazyb]),
  change(date(2018, 9, 24), 'Updated Photosynthesis module to use correct calculations.', [blazyb]),
  change(date(2018, 9, 22), 'Added module for tracking Abundance.', [blazyb]),
  change(date(2018, 9, 21), 'Added tracking of healing gained from Stonebark.', [blazyb]),
  change(date(2018, 9, 21), 'Added implementation of Azerite trait Lively spirit.', [blazyb]),
  change(date(2018, 9, 20), 'Added implementation of Azerite trait Synergistic Growth.', [blazyb]),
  change(date(2018, 9, 20), 'Added mana used doughnut chart (code taken from Hunter focus used module) to resto druid & set icons for Uldir bosses (not headshots)', [fel1ne]),
  change(date(2018, 9, 20), 'Updated bad Regrowth usage suggestions - using a non clearcasting regrowth on low health target is not considered bad.', [blazyb]),
  change(date(2018, 9, 18), 'Added implementation of Azerite trait Waking Dream.', [blazyb]),
  change(date(2018, 9, 17), 'Added HoT part of tranquillity to all relevant modules. This will change the values in mastery stack, flourish module and stat tracker.', [blazyb]),
  change(date(2018, 9, 12), 'Added implementation of Azerite trait Grove Tending.', [blazyb]),
  change(date(2018, 9, 12), 'Added implementation of Azerite trait Autumn Leaves.', [blazyb]),
  change(date(2018, 9, 12), 'Added implementation of Azerite trait Fungal Essence.', [blazyb]),
  change(date(2018, 9, 12), 'Updated changes on rejuvenation mana cost from hotfix september 10th.', [blazyb]),
  change(date(2018, 9, 10), 'Cleanup of old references from Legion and new updates from BFA.', [blazyb]),
  change(date(2018, 7, 27), 'Spell, talent, and GCD update to spells for BFA.', [Yajinni]),
];
<|endoftext|>