<commit_before>define([
	'goo/loaders/handlers/ConfigHandler',
	'goo/renderer/Texture',
	'goo/loaders/dds/DdsLoader',
	'goo/loaders/crunch/CrunchLoader',
	'goo/loaders/tga/TgaLoader',
	'goo/util/rsvp',
	'goo/util/PromiseUtil',
	'goo/renderer/Util',
	'goo/util/ObjectUtil',
	'goo/util/CanvasUtils',
	'goo/util/StringUtil'
],
/** @lends */
function (
	ConfigHandler,
	Texture,
	DdsLoader,
	CrunchLoader,
	TgaLoader,
	RSVP,
	PromiseUtil,
	Util,
	_,
	CanvasUtils,
	StringUtil
) {
	'use strict';

	/*jshint eqeqeq: false, -W041 */
	/**
	 * @class Handler for loading materials into engine
	 * @extends ConfigHandler
	 * @param {World} world
	 * @param {Function} getConfig
	 * @param {Function} updateObject
	 * @private
	 */
	function TextureHandler() {
		ConfigHandler.apply(this, arguments);
	}

	TextureHandler.prototype = Object.create(ConfigHandler.prototype);
	TextureHandler.prototype.constructor = TextureHandler;
	ConfigHandler._registerClass('texture', TextureHandler);

	TextureHandler.minFilters = [
		'NearestNeighborNoMipMaps',
		'NearestNeighborNearestMipMap',
		'NearestNeighborLinearMipMap',
		'BilinearNoMipMaps',
		'BilinearNearestMipMap',
		'Trilinear'
	];
	TextureHandler.magFilters = [
		'NearestNeighbor',
		'Bilinear'
	];

	TextureHandler.loaders = {
		dds: DdsLoader,
		crn: CrunchLoader, // TODO: not working atm.
		tga: TgaLoader
	};

	// Dummy textures to use while loading image
	TextureHandler.WHITE = new Uint8Array([255, 255, 255, 255]);
	TextureHandler.BLACK = new Uint8Array([0, 0, 0, 255]);

	/**
	 * Preparing texture config by populating it with defaults.
	 * @param {object} config
	 * @private
	 */
	TextureHandler.prototype._prepare = function (config) {
		_.defaults(config, {
			wrapS: 'Repeat',
			wrapT: 'Repeat',
			magFilter: 'Bilinear',
			minFilter: 'Trilinear',
			anisotropy: 1,
			offset: [0, 0],
			repeat: [1, 1],
			flipY: true,
			lodBias: 0.0,
			loop: true
		});
	};

	/**
	 * Removes a texture
	 * @param {ref}
	 * @private
	 */
	TextureHandler.prototype._remove = function (ref) {
		if (this._objects[ref] && this._objects[ref].destroy && this.world.gooRunner) {
			this._objects[ref].destroy(this.world.gooRunner.renderer.context);
		}
		delete this._objects[ref];
	};

	/**
	 * Creates an empty Texture.
	 * @param {string} ref will be the entity's id
	 * @returns {Texture}
	 * @private
	 */
	TextureHandler.prototype._create = function () {
		return new Texture();
	};

	/**
	 * Adds/updates/removes a texture
	 * @param {string} ref
	 * @param {object|null} config
	 * @param {object} options
	 * @returns {RSVP.Promise} Resolves with the updated texture or null if removed
	 */
	TextureHandler.prototype._update = function (ref, config, options) {
		var that = this;
		return ConfigHandler.prototype._update.call(this, ref, config, options).then(function (texture) {
			if (!texture) { return; }
			var ret;

			// Texture settings
			texture.wrapS = config.wrapS;
			texture.wrapT = config.wrapT;

			if (TextureHandler.magFilters.indexOf(config.magFilter) !== -1) {
				texture.magFilter = config.magFilter;
			}
			if (TextureHandler.minFilters.indexOf(config.minFilter) !== -1) {
				texture.minFilter = config.minFilter;
			}

			texture.anisotropy = Math.max(config.anisotropy, 1);

			texture.offset.set(config.offset);
			texture.repeat.set(config.repeat);
			texture.lodBias = config.lodBias;

			if (texture.flipY !== config.flipY) {
				texture.flipY = config.flipY;
				texture.setNeedsUpdate();
			}

			if (texture.generateMipmaps !== config.generateMipmaps) {
				texture.generateMipmaps = config.generateMipmaps;
				texture.setNeedsUpdate();
			}

			texture.updateCallback = null;

			var imageRef = config.imageRef;
			if (imageRef) {
				var path = StringUtil.parseURL(imageRef).path;
				var type = path.substr(path.lastIndexOf('.') + 1).toLowerCase();
				var Loader = TextureHandler.loaders[type];
				if (Loader) {
					// Special (dds, tga, crn)
					texture.a = imageRef;
					ret = that.loadObject(imageRef).then(function (data) {
						if (data && data.preloaded) {
							_.extend(texture.image, data.image);
							texture.format = data.format;
							texture.setNeedsUpdate();
							return texture;
						}
						var loader = new Loader();
						loader.load(data, texture, config.flipY, 0, data.byteLength);
						return texture;
					});
				} else if (['jpg', 'jpeg', 'png', 'gif'].indexOf(type) !== -1) {
					// Images
					// Beware of image caching but should be handled by Ajax
					ret = that.loadObject(imageRef, options).then(function (image) {
						if (texture.image !== image) {
							texture.setImage(image);
						}
						return texture;
					});
				} else if (['mp4', 'ogv', 'webm'].indexOf(type) !== -1) {
					// Video
					ret = that.loadObject(imageRef, options).then(function (video) {
						video.width = video.videoWidth;
						video.height = video.videoHeight;
						video.loop = config.loop !== undefined ? config.loop : true;
						if (Util.isPowerOfTwo(video.width) === false || Util.isPowerOfTwo(video.height) === false) {
							texture.generateMipmaps = false;
							texture.minFilter = 'BilinearNoMipMaps';
						}
						texture.setImage(video);
						texture.updateCallback = function () {
							return !video.paused;
						};
						if (config.autoPlay === undefined || config.autoPlay) {
							video.play();
						}
						return texture;
					});
				} else {
					throw new Error('Unknown texture type');
				}
			} else if (config.svgData) {
				// Load SVG data
				ret = PromiseUtil.createPromise(function (resolve, reject) {
					CanvasUtils.renderSvgToCanvas(config.svgData, {}, function (canvas) {
						if (canvas) {
							texture.setImage(canvas);
							resolve(texture);
						} else {
							reject('could not render svg to canvas');
						}
					});
				});
			} else {
				// Blank
				// console.warn('Texture ' + ref + ' has no imageRef');
				// texture.setImage(TextureHandler.WHITE, 1, 1);
				ret = texture;
			}
			if (options && options.texture && options.texture.dontwait) {
				return texture;
			} else {
				return ret;
			}
		});
	};

	TextureHandler.prototype._remove = function (ref) {
		if (this._objects[ref] && this._objects[ref].destroy && this.world.gooRunner) {
			this._objects[ref].destroy(this.world.gooRunner.renderer.context);
		}
		delete this._objects[ref];
	};

	return TextureHandler;
});
<commit_msg>Post-reviewing https://trello.com/c/mrkd6dyi/1864-set-generatemipmap-property-for-textures<commit_after>define([
	'goo/loaders/handlers/ConfigHandler',
	'goo/renderer/Texture',
	'goo/loaders/dds/DdsLoader',
	'goo/loaders/crunch/CrunchLoader',
	'goo/loaders/tga/TgaLoader',
	'goo/util/rsvp',
	'goo/util/PromiseUtil',
	'goo/renderer/Util',
	'goo/util/ObjectUtil',
	'goo/util/CanvasUtils',
	'goo/util/StringUtil'
],
/** @lends */
function (
	ConfigHandler,
	Texture,
	DdsLoader,
	CrunchLoader,
	TgaLoader,
	RSVP,
	PromiseUtil,
	Util,
	_,
	CanvasUtils,
	StringUtil
) {
	'use strict';

	/*jshint eqeqeq: false, -W041 */
	/**
	 * @class Handler for loading materials into engine
	 * @extends ConfigHandler
	 * @param {World} world
	 * @param {Function} getConfig
	 * @param {Function} updateObject
	 * @private
	 */
	function TextureHandler() {
		ConfigHandler.apply(this, arguments);
	}

	TextureHandler.prototype = Object.create(ConfigHandler.prototype);
	TextureHandler.prototype.constructor = TextureHandler;
	ConfigHandler._registerClass('texture', TextureHandler);

	TextureHandler.minFilters = [
		'NearestNeighborNoMipMaps',
		'NearestNeighborNearestMipMap',
		'NearestNeighborLinearMipMap',
		'BilinearNoMipMaps',
		'BilinearNearestMipMap',
		'Trilinear'
	];
	TextureHandler.magFilters = [
		'NearestNeighbor',
		'Bilinear'
	];

	TextureHandler.loaders = {
		dds: DdsLoader,
		crn: CrunchLoader, // TODO: not working atm.
		tga: TgaLoader
	};

	// Dummy textures to use while loading image
	TextureHandler.WHITE = new Uint8Array([255, 255, 255, 255]);
	TextureHandler.BLACK = new Uint8Array([0, 0, 0, 255]);

	/**
	 * Preparing texture config by populating it with defaults.
	 * @param {object} config
	 * @private
	 */
	TextureHandler.prototype._prepare = function (config) {
		_.defaults(config, {
			wrapS: 'Repeat',
			wrapT: 'Repeat',
			magFilter: 'Bilinear',
			minFilter: 'Trilinear',
			anisotropy: 1,
			offset: [0, 0],
			repeat: [1, 1],
			flipY: true,
			lodBias: 0.0,
			loop: true
		});
	};

	/**
	 * Removes a texture
	 * @param {ref}
	 * @private
	 */
	TextureHandler.prototype._remove = function (ref) {
		if (this._objects[ref] && this._objects[ref].destroy && this.world.gooRunner) {
			this._objects[ref].destroy(this.world.gooRunner.renderer.context);
		}
		delete this._objects[ref];
	};

	/**
	 * Creates an empty Texture.
	 * @param {string} ref will be the entity's id
	 * @returns {Texture}
	 * @private
	 */
	TextureHandler.prototype._create = function () {
		return new Texture();
	};

	/**
	 * Adds/updates/removes a texture
	 * @param {string} ref
	 * @param {object|null} config
	 * @param {object} options
	 * @returns {RSVP.Promise} Resolves with the updated texture or null if removed
	 */
	TextureHandler.prototype._update = function (ref, config, options) {
		var that = this;
		return ConfigHandler.prototype._update.call(this, ref, config, options).then(function (texture) {
			if (!texture) { return; }
			var ret;

			// Texture settings
			texture.wrapS = config.wrapS;
			texture.wrapT = config.wrapT;

			if (TextureHandler.magFilters.indexOf(config.magFilter) !== -1) {
				texture.magFilter = config.magFilter;
			}
			if (TextureHandler.minFilters.indexOf(config.minFilter) !== -1) {
				texture.minFilter = config.minFilter;
			}

			texture.anisotropy = Math.max(config.anisotropy, 1);

			texture.offset.set(config.offset);
			texture.repeat.set(config.repeat);
			texture.lodBias = config.lodBias;

			if (texture.flipY !== config.flipY) {
				texture.flipY = config.flipY;
				texture.setNeedsUpdate();
			}

			if (texture.generateMipmaps !== config.generateMipmaps) {
				texture.generateMipmaps = config.generateMipmaps !== false;
				texture.setNeedsUpdate();
			}

			texture.updateCallback = null;

			var imageRef = config.imageRef;
			if (imageRef) {
				var path = StringUtil.parseURL(imageRef).path;
				var type = path.substr(path.lastIndexOf('.') + 1).toLowerCase();
				var Loader = TextureHandler.loaders[type];
				if (Loader) {
					// Special (dds, tga, crn)
					texture.a = imageRef;
					ret = that.loadObject(imageRef).then(function (data) {
						if (data && data.preloaded) {
							_.extend(texture.image, data.image);
							texture.format = data.format;
							texture.setNeedsUpdate();
							return texture;
						}
						var loader = new Loader();
						loader.load(data, texture, config.flipY, 0, data.byteLength);
						return texture;
					});
				} else if (['jpg', 'jpeg', 'png', 'gif'].indexOf(type) !== -1) {
					// Images
					// Beware of image caching but should be handled by Ajax
					ret = that.loadObject(imageRef, options).then(function (image) {
						if (texture.image !== image) {
							texture.setImage(image);
						}
						return texture;
					});
				} else if (['mp4', 'ogv', 'webm'].indexOf(type) !== -1) {
					// Video
					ret = that.loadObject(imageRef, options).then(function (video) {
						video.width = video.videoWidth;
						video.height = video.videoHeight;
						video.loop = config.loop !== undefined ? config.loop : true;
						if (Util.isPowerOfTwo(video.width) === false || Util.isPowerOfTwo(video.height) === false) {
							texture.generateMipmaps = false;
							texture.minFilter = 'BilinearNoMipMaps';
						}
						texture.setImage(video);
						texture.updateCallback = function () {
							return !video.paused;
						};
						if (config.autoPlay === undefined || config.autoPlay) {
							video.play();
						}
						return texture;
					});
				} else {
					throw new Error('Unknown texture type');
				}
			} else if (config.svgData) {
				// Load SVG data
				ret = PromiseUtil.createPromise(function (resolve, reject) {
					CanvasUtils.renderSvgToCanvas(config.svgData, {}, function (canvas) {
						if (canvas) {
							texture.setImage(canvas);
							resolve(texture);
						} else {
							reject('could not render svg to canvas');
						}
					});
				});
			} else {
				// Blank
				// console.warn('Texture ' + ref + ' has no imageRef');
				// texture.setImage(TextureHandler.WHITE, 1, 1);
				ret = texture;
			}
			if (options && options.texture && options.texture.dontwait) {
				return texture;
			} else {
				return ret;
			}
		});
	};

	TextureHandler.prototype._remove = function (ref) {
		if (this._objects[ref] && this._objects[ref].destroy && this.world.gooRunner) {
			this._objects[ref].destroy(this.world.gooRunner.renderer.context);
		}
		delete this._objects[ref];
	};

	return TextureHandler;
});
<|endoftext|>


<commit_before>import * as utils from 'base/utils';
import Component from 'base/component';

var precision = 1;

//constants
var class_playing = "vzb-playing";
var class_loading = "vzb-ts-loading";
var class_hide_play = "vzb-ts-hide-play-button";
var class_dragging = "vzb-ts-dragging";
var class_axis_aligned = "vzb-ts-axis-aligned";
var class_show_value = "vzb-ts-show-value";
var class_show_value_when_drag_play = "vzb-ts-show-value-when-drag-play";

//margins for slider
var profiles = {
  small: {
    margin: {
      top: 7,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 8,
    label_spacing: 10
  },
  medium: {
    margin: {
      top: 16,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 9,
    label_spacing: 12
  },
  large: {
    margin: {
      top: 14,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 11,
    label_spacing: 14
  }
};

var presentationProfileChanges = {
  "medium": {
    margin: {
      top: 9
    }
  },
  "large": {
    margin: {
    }
  }
}

var TimeSlider = Component.extend({
  /**
   * Initializes the timeslider.
   * Executed once before any template is rendered.
   * @param model The model passed to the component
   * @param context The component's parent
   */
  init: function(model, context) {

    this.name = "gapminder-timeslider";
    this.template = this.template || "timeslider.html";
    this.prevPosition = null;
    //define expected models/hooks for this component
    this.model_expects = [{
      name: "time",
      type: "time"
    }];

    var _this = this;

    //starts as splash if this is the option
    this._splash = model.ui.splash;

    //binds methods to this model
    this.model_binds = {
      'change:time': function(evt, path) {

        //TODO: readyOnce CANNOT be run twice
        if(_this._splash !== _this.model.time.splash) {
          _this._splash = _this.model.time.splash;
          _this.readyOnce();
          _this.ready();
        }

        if(!_this._splash) {

          if((['time.start', 'time.end']).indexOf(path) !== -1) {
            _this.changeLimits();
          }
          _this._optionClasses();
        }
      },
      'change:time.value': function(evt, path) {
        if(!_this._splash) {
          //only set handle position if change is external
          if(!_this.model.time.dragging) _this._setHandle(_this.model.time.playing);
        }
      }
    };

    this.ui = utils.extend({
      show_limits: false,
      show_value: false,
      show_value_when_drag_play: true,
      show_button: true,
      class_axis_aligned: false
    }, model.ui, this.ui);

    // Same constructor as the superclass
    this._super(model, context);

    //defaults
    this.width = 0;
    this.height = 0;

    this.getValueWidth = utils.memoize(this.getValueWidth);
    this._setTime = utils.throttle(this._setTime, 50);
  },

  //template is ready
  readyOnce: function () {

    if(this._splash) return;

    var _this = this;

    //DOM to d3
    //TODO: remove this ugly hack
    this.element = utils.isArray(this.element) ? this.element : d3.select(this.element);
    this.element.classed(class_loading, false);

    //html elements
    this.slider_outer = this.element.select(".vzb-ts-slider");
    this.slider = this.slider_outer.select("g");
    this.axis = this.element.select(".vzb-ts-slider-axis");
    this.slide = this.element.select(".vzb-ts-slider-slide");
    this.handle = this.slide.select(".vzb-ts-slider-handle");
    this.valueText = this.slide.select('.vzb-ts-slider-value');
    //Scale
    this.xScale = d3.time.scale.utc()
      .clamp(true);

    //Axis
    this.xAxis = d3.svg.axis()
      .orient("bottom")
      .tickSize(0);
    //Value
    this.valueText.attr("text-anchor", "middle").attr("dy", "-0.7em");

    var brushed = _this._getBrushed(),
      brushedEnd = _this._getBrushedEnd();

    //Brush for dragging
    this.brush = d3.svg.brush()
      .x(this.xScale)
      .extent([0, 0])
      .on("brush", function () {
        brushed.call(this);
      })
      .on("brushend", function () {
        brushedEnd.call(this);
      });

    //Slide
    this.slide.call(this.brush);
    this.slide.selectAll(".extent,.resize")
      .remove();

    this.parent.on('myEvent', function (evt, arg) {
      var layoutProfile = _this.getLayoutProfile();

      if (arg.profile && arg.profile.margin) {
        profiles[layoutProfile].margin = arg.profile.margin;
      }

      // set the right margin that depends on longest label width
      _this.element.select(".vzb-ts-slider-wrapper")
        .style("right", (arg.mRight - profiles[layoutProfile].margin.right) + "px");

      _this.xScale.range([0, arg.rangeMax]);
      _this.resize();
    });
  },

  //template and model are ready
  ready: function () {
    if(this._splash) return;

    var play = this.element.select(".vzb-ts-btn-play");
    var pause = this.element.select(".vzb-ts-btn-pause");
    var _this = this;
    var time = this.model.time;

    play.on('click', function () {

      _this.model.time.play();
    });

    pause.on('click', function () {
      _this.model.time.pause("soft");
    });

    this.changeLimits();
    this.changeTime();
    this.resize();

  },

  changeLimits: function() {
    var minValue = this.model.time.start;
    var maxValue = this.model.time.end;
    //scale
    this.xScale.domain([minValue, maxValue]);
    //axis
    this.xAxis.tickValues([minValue, maxValue])
      .tickFormat(this.model.time.timeFormat);
  },

  changeTime: function() {
    this.ui.format = this.model.time.unit;
    //time slider should always receive a time model
    var time = this.model.time.value;
    //special classes
    this._optionClasses();
  },

  /**
   * Executes everytime the container or vizabi is resized
   * Ideally,it contains only operations related to size
   */
  resize: function () {

    this.model.time.pause();

    this.profile = this.getActiveProfile(profiles, presentationProfileChanges);

    var slider_w = parseInt(this.slider_outer.style("width"), 10);
    var slider_h = parseInt(this.slider_outer.style("height"), 10);
    this.width = slider_w - this.profile.margin.left - this.profile.margin.right;
    this.height = slider_h - this.profile.margin.bottom - this.profile.margin.top;
    var _this = this;

    //translate according to margins
    this.slider.attr("transform", "translate(" + this.profile.margin.left + "," + this.profile.margin.top + ")");

    //adjust scale width if it was not set manually before
    if (this.xScale.range()[1] = 1) this.xScale.range([0, this.width]);

    //adjust axis with scale
    this.xAxis = this.xAxis.scale(this.xScale)
      .tickPadding(this.profile.label_spacing);

    this.axis.attr("transform", "translate(0," + this.height / 2 + ")")
      .call(this.xAxis);

    this.slide.select(".background")
      .attr("height", this.height);

    //size of handle
    this.handle.attr("transform", "translate(0," + this.height / 2 + ")")
      .attr("r", this.profile.radius);

    this.sliderWidth = _this.slider.node().getBoundingClientRect().width;

    this._setHandle();

  },

  /**
   * Returns width of slider text value.
   * Parameters in this function needed for memoize function, so they are not redundant.
   */
  getValueWidth: function(layout, value) {
    return this.valueText.node().getBoundingClientRect().width;
  },

  /**
   * Gets brushed function to be executed when dragging
   * @returns {Function} brushed function
   */
  _getBrushed: function() {
    var _this = this;
    return function() {

      if (_this.model.time.playing)
        _this.model.time.pause();

      _this._optionClasses();
      _this.element.classed(class_dragging, true);

      var value = _this.brush.extent()[0];

      //set brushed properties

      if(d3.event.sourceEvent) {
        _this.model.time.dragStart();
        var posX = utils.roundStep(Math.round(d3.mouse(this)[0]), precision);
        value = _this.xScale.invert(posX);
        var maxPosX = _this.width;

        if(posX > maxPosX) {
          posX = maxPosX;
        } else if(posX < 0) {
          posX = 0;
        }

        //set handle position
        _this.handle.attr("cx", posX);
        _this.valueText.attr("transform", "translate(" + posX + "," + (_this.height / 2) + ")");
        _this.valueText.text(_this.model.time.timeFormat(value));
      }

      //set time according to dragged position
      if(value - _this.model.time.value !== 0) {
        _this._setTime(value);
      }
    };
  },

  /**
   * Gets brushedEnd function to be executed when dragging ends
   * @returns {Function} brushedEnd function
   */
  _getBrushedEnd: function() {
    var _this = this;
    return function() {
      _this._setTime.recallLast();
      _this.element.classed(class_dragging, false);
      _this.model.time.dragStop();
      _this.model.time.snap();
    };
  },

  /**
   * Sets the handle to the correct position
   * @param {Boolean} transition whether to use transition or not
   */
  _setHandle: function(transition) {
    var _this = this;
    var value = this.model.time.value;
    this.slide.call(this.brush.extent([value, value]));
    this.valueText.text(this.model.time.timeFormat(value));

//    var old_pos = this.handle.attr("cx");
    var new_pos = this.xScale(value);
    if(_this.prevPosition == null) _this.prevPosition = new_pos;
    var delayAnimations = new_pos > _this.prevPosition ? this.model.time.delayAnimations : 0;
    if(transition) {
      this.handle.attr("cx", _this.prevPosition)
        .transition()
        .duration(delayAnimations)
        .ease("linear")
        .attr("cx", new_pos);

      this.valueText.attr("transform", "translate(" + _this.prevPosition + "," + (this.height / 2) + ")")
        .transition()
        .duration(delayAnimations)
        .ease("linear")
        .attr("transform", "translate(" + new_pos + "," + (this.height / 2) + ")");
    } else {
      this.handle
        //cancel active transition
        .interrupt()
        .attr("cx", new_pos);

      this.valueText
        //cancel active transition
        .interrupt()
        .attr("transform", "translate(" + new_pos + "," + (this.height / 2) + ")");
    }
    _this.prevPosition = new_pos;

  },

  /**
   * Sets the current time model to time
   * @param {number} time The time
   */
  _setTime: function(time) {
    //update state
    var _this = this;
    //  frameRate = 50;

    //avoid updating more than once in "frameRate"
    //var now = new Date();
    //if (this._updTime != null && now - this._updTime < frameRate) return;
    //this._updTime = now;
    var persistent = !this.model.time.dragging && !this.model.time.playing;
    _this.model.time.getModelObject('value').set(time, false, persistent); // non persistent
  },

  /**
   * Applies some classes to the element according to options
   */
  _optionClasses: function() {
    //show/hide classes

    var show_limits = this.ui.show_limits;
    var show_value = this.ui.show_value;
    var show_value_when_drag_play = this.ui.show_value_when_drag_play;
    var axis_aligned = this.ui.axis_aligned;
    var show_play = (this.ui.show_button) && (this.model.time.playable);

    if(!show_limits) {
      this.xAxis.tickValues([]).ticks(0);
    }

    this.element.classed(class_hide_play, !show_play);
    this.element.classed(class_playing, this.model.time.playing);
    this.element.classed(class_show_value, show_value);
    this.element.classed(class_show_value_when_drag_play, show_value_when_drag_play);
    this.element.classed(class_axis_aligned, axis_aligned);
  }
});

export default TimeSlider;
<commit_msg>Attempt to block page scrolling while dragging time slider #1340<commit_after>import * as utils from 'base/utils';
import Component from 'base/component';

var precision = 1;

//constants
var class_playing = "vzb-playing";
var class_loading = "vzb-ts-loading";
var class_hide_play = "vzb-ts-hide-play-button";
var class_dragging = "vzb-ts-dragging";
var class_axis_aligned = "vzb-ts-axis-aligned";
var class_show_value = "vzb-ts-show-value";
var class_show_value_when_drag_play = "vzb-ts-show-value-when-drag-play";

//margins for slider
var profiles = {
  small: {
    margin: {
      top: 7,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 8,
    label_spacing: 10
  },
  medium: {
    margin: {
      top: 16,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 9,
    label_spacing: 12
  },
  large: {
    margin: {
      top: 14,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 11,
    label_spacing: 14
  }
};

var presentationProfileChanges = {
  "medium": {
    margin: {
      top: 9
    }
  },
  "large": {
    margin: {
    }
  }
}

var TimeSlider = Component.extend({
  /**
   * Initializes the timeslider.
   * Executed once before any template is rendered.
   * @param model The model passed to the component
   * @param context The component's parent
   */
  init: function(model, context) {

    this.name = "gapminder-timeslider";
    this.template = this.template || "timeslider.html";
    this.prevPosition = null;
    //define expected models/hooks for this component
    this.model_expects = [{
      name: "time",
      type: "time"
    }];

    var _this = this;

    //starts as splash if this is the option
    this._splash = model.ui.splash;

    //binds methods to this model
    this.model_binds = {
      'change:time': function(evt, path) {

        //TODO: readyOnce CANNOT be run twice
        if(_this._splash !== _this.model.time.splash) {
          _this._splash = _this.model.time.splash;
          _this.readyOnce();
          _this.ready();
        }

        if(!_this._splash) {

          if((['time.start', 'time.end']).indexOf(path) !== -1) {
            _this.changeLimits();
          }
          _this._optionClasses();
        }
      },
      'change:time.value': function(evt, path) {
        if(!_this._splash) {
          //only set handle position if change is external
          if(!_this.model.time.dragging) _this._setHandle(_this.model.time.playing);
        }
      }
    };

    this.ui = utils.extend({
      show_limits: false,
      show_value: false,
      show_value_when_drag_play: true,
      show_button: true,
      class_axis_aligned: false
    }, model.ui, this.ui);

    // Same constructor as the superclass
    this._super(model, context);

    //defaults
    this.width = 0;
    this.height = 0;

    this.getValueWidth = utils.memoize(this.getValueWidth);
    this._setTime = utils.throttle(this._setTime, 50);
  },

  //template is ready
  readyOnce: function () {

    if(this._splash) return;

    var _this = this;

    //DOM to d3
    //TODO: remove this ugly hack
    this.element = utils.isArray(this.element) ? this.element : d3.select(this.element);
    this.element.classed(class_loading, false);

    //html elements
    this.slider_outer = this.element.select(".vzb-ts-slider");
    this.slider = this.slider_outer.select("g");
    this.axis = this.element.select(".vzb-ts-slider-axis");
    this.slide = this.element.select(".vzb-ts-slider-slide");
    this.handle = this.slide.select(".vzb-ts-slider-handle");
    this.valueText = this.slide.select('.vzb-ts-slider-value');
    //Scale
    this.xScale = d3.time.scale.utc()
      .clamp(true);

    //Axis
    this.xAxis = d3.svg.axis()
      .orient("bottom")
      .tickSize(0);
    //Value
    this.valueText.attr("text-anchor", "middle").attr("dy", "-0.7em");

    var brushed = _this._getBrushed(),
      brushedEnd = _this._getBrushedEnd();

    //Brush for dragging
    this.brush = d3.svg.brush()
      .x(this.xScale)
      .extent([0, 0])
      .on("brush", function () {
        brushed.call(this);
      })
      .on("brushend", function () {
        brushedEnd.call(this);
      });

    //Slide
    this.slide.call(this.brush);

    this.slider_outer.on("mousewheel", function () {
        console.log("scroll")
        //do nothing and dont pass the event on
        d3.event.preventDefault();
        d3.event.stopPropagation();
    });

    this.slide.selectAll(".extent,.resize")
      .remove();

    this.parent.on('myEvent', function (evt, arg) {
      var layoutProfile = _this.getLayoutProfile();

      if (arg.profile && arg.profile.margin) {
        profiles[layoutProfile].margin = arg.profile.margin;
      }

      // set the right margin that depends on longest label width
      _this.element.select(".vzb-ts-slider-wrapper")
        .style("right", (arg.mRight - profiles[layoutProfile].margin.right) + "px");

      _this.xScale.range([0, arg.rangeMax]);
      _this.resize();
    });
  },

  //template and model are ready
  ready: function () {
    if(this._splash) return;

    var play = this.element.select(".vzb-ts-btn-play");
    var pause = this.element.select(".vzb-ts-btn-pause");
    var _this = this;
    var time = this.model.time;

    play.on('click', function () {

      _this.model.time.play();
    });

    pause.on('click', function () {
      _this.model.time.pause("soft");
    });

    this.changeLimits();
    this.changeTime();
    this.resize();

  },

  changeLimits: function() {
    var minValue = this.model.time.start;
    var maxValue = this.model.time.end;
    //scale
    this.xScale.domain([minValue, maxValue]);
    //axis
    this.xAxis.tickValues([minValue, maxValue])
      .tickFormat(this.model.time.timeFormat);
  },

  changeTime: function() {
    this.ui.format = this.model.time.unit;
    //time slider should always receive a time model
    var time = this.model.time.value;
    //special classes
    this._optionClasses();
  },

  /**
   * Executes everytime the container or vizabi is resized
   * Ideally,it contains only operations related to size
   */
  resize: function () {

    this.model.time.pause();

    this.profile = this.getActiveProfile(profiles, presentationProfileChanges);

    var slider_w = parseInt(this.slider_outer.style("width"), 10);
    var slider_h = parseInt(this.slider_outer.style("height"), 10);
    this.width = slider_w - this.profile.margin.left - this.profile.margin.right;
    this.height = slider_h - this.profile.margin.bottom - this.profile.margin.top;
    var _this = this;

    //translate according to margins
    this.slider.attr("transform", "translate(" + this.profile.margin.left + "," + this.profile.margin.top + ")");

    //adjust scale width if it was not set manually before
    if (this.xScale.range()[1] = 1) this.xScale.range([0, this.width]);

    //adjust axis with scale
    this.xAxis = this.xAxis.scale(this.xScale)
      .tickPadding(this.profile.label_spacing);

    this.axis.attr("transform", "translate(0," + this.height / 2 + ")")
      .call(this.xAxis);

    this.slide.select(".background")
      .attr("height", this.height);

    //size of handle
    this.handle.attr("transform", "translate(0," + this.height / 2 + ")")
      .attr("r", this.profile.radius);

    this.sliderWidth = _this.slider.node().getBoundingClientRect().width;

    this._setHandle();

  },

  /**
   * Returns width of slider text value.
   * Parameters in this function needed for memoize function, so they are not redundant.
   */
  getValueWidth: function(layout, value) {
    return this.valueText.node().getBoundingClientRect().width;
  },

  /**
   * Gets brushed function to be executed when dragging
   * @returns {Function} brushed function
   */
  _getBrushed: function() {
    var _this = this;
    return function() {

      if (_this.model.time.playing)
        _this.model.time.pause();

      _this._optionClasses();
      _this.element.classed(class_dragging, true);

      var value = _this.brush.extent()[0];

      //set brushed properties

      if(d3.event.sourceEvent) {
        _this.model.time.dragStart();
        var posX = utils.roundStep(Math.round(d3.mouse(this)[0]), precision);
        value = _this.xScale.invert(posX);
        var maxPosX = _this.width;

        if(posX > maxPosX) {
          posX = maxPosX;
        } else if(posX < 0) {
          posX = 0;
        }

        //set handle position
        _this.handle.attr("cx", posX);
        _this.valueText.attr("transform", "translate(" + posX + "," + (_this.height / 2) + ")");
        _this.valueText.text(_this.model.time.timeFormat(value));
      }

      //set time according to dragged position
      if(value - _this.model.time.value !== 0) {
        _this._setTime(value);
      }
    };
  },

  /**
   * Gets brushedEnd function to be executed when dragging ends
   * @returns {Function} brushedEnd function
   */
  _getBrushedEnd: function() {
    var _this = this;
    return function() {
      _this._setTime.recallLast();
      _this.element.classed(class_dragging, false);
      _this.model.time.dragStop();
      _this.model.time.snap();
    };
  },

  /**
   * Sets the handle to the correct position
   * @param {Boolean} transition whether to use transition or not
   */
  _setHandle: function(transition) {
    var _this = this;
    var value = this.model.time.value;
    this.slide.call(this.brush.extent([value, value]));
    this.valueText.text(this.model.time.timeFormat(value));

//    var old_pos = this.handle.attr("cx");
    var new_pos = this.xScale(value);
    if(_this.prevPosition == null) _this.prevPosition = new_pos;
    var delayAnimations = new_pos > _this.prevPosition ? this.model.time.delayAnimations : 0;
    if(transition) {
      this.handle.attr("cx", _this.prevPosition)
        .transition()
        .duration(delayAnimations)
        .ease("linear")
        .attr("cx", new_pos);

      this.valueText.attr("transform", "translate(" + _this.prevPosition + "," + (this.height / 2) + ")")
        .transition()
        .duration(delayAnimations)
        .ease("linear")
        .attr("transform", "translate(" + new_pos + "," + (this.height / 2) + ")");
    } else {
      this.handle
        //cancel active transition
        .interrupt()
        .attr("cx", new_pos);

      this.valueText
        //cancel active transition
        .interrupt()
        .attr("transform", "translate(" + new_pos + "," + (this.height / 2) + ")");
    }
    _this.prevPosition = new_pos;

  },

  /**
   * Sets the current time model to time
   * @param {number} time The time
   */
  _setTime: function(time) {
    //update state
    var _this = this;
    //  frameRate = 50;

    //avoid updating more than once in "frameRate"
    //var now = new Date();
    //if (this._updTime != null && now - this._updTime < frameRate) return;
    //this._updTime = now;
    var persistent = !this.model.time.dragging && !this.model.time.playing;
    _this.model.time.getModelObject('value').set(time, false, persistent); // non persistent
  },

  /**
   * Applies some classes to the element according to options
   */
  _optionClasses: function() {
    //show/hide classes

    var show_limits = this.ui.show_limits;
    var show_value = this.ui.show_value;
    var show_value_when_drag_play = this.ui.show_value_when_drag_play;
    var axis_aligned = this.ui.axis_aligned;
    var show_play = (this.ui.show_button) && (this.model.time.playable);

    if(!show_limits) {
      this.xAxis.tickValues([]).ticks(0);
    }

    this.element.classed(class_hide_play, !show_play);
    this.element.classed(class_playing, this.model.time.playing);
    this.element.classed(class_show_value, show_value);
    this.element.classed(class_show_value_when_drag_play, show_value_when_drag_play);
    this.element.classed(class_axis_aligned, axis_aligned);
  }
});

export default TimeSlider;
<|endoftext|>


<commit_before>const {Router} = require('express');
const body = require('body-parser');
const MagicLink = require('@tryghost/magic-link');
const StripePaymentProcessor = require('./lib/stripe');

const Tokens = require('./lib/tokens');
const Users = require('./lib/users');
const common = require('./lib/common');

module.exports = function MembersApi({
    tokenConfig: {
        issuer,
        privateKey,
        publicKey
    },
    auth: {
        getSigninURL
    },
    paymentConfig,
    mail: {
        transporter,
        getText,
        getHTML
    },
    setMemberMetadata,
    getMemberMetadata,
    createMember,
    getMember,
    updateMember,
    deleteMember,
    listMembers,
    logger
}) {
    if (logger) {
        common.logging.setLogger(logger);
    }

    const {encodeIdentityToken, decodeToken} = Tokens({privateKey, publicKey, issuer});

    const stripeStorage = {
        async get(member) {
            return getMemberMetadata(member, 'stripe');
        },
        async set(member, metadata) {
            return setMemberMetadata(member, 'stripe', metadata);
        }
    };
    const stripe = paymentConfig.stripe ? new StripePaymentProcessor(paymentConfig.stripe, stripeStorage, common.logging) : null;

    async function ensureStripe(_req, res, next) {
        if (!stripe) {
            res.writeHead(400);
            return res.end('Stripe not configured');
        }
        try {
            await stripe.ready();
            next();
        } catch (err) {
            res.writeHead(500);
            return res.end('There was an error configuring stripe');
        }
    }

    const magicLinkService = new MagicLink({
        transporter,
        publicKey,
        privateKey,
        getSigninURL,
        getText,
        getHTML
    });

    async function sendEmailWithMagicLink(email, requestedType, options = {forceEmailType: false}){
        if (options.forceEmailType) {
            return magicLinkService.sendMagicLink({email, user: {email}, type: requestedType});
        }
        const member = await users.get({email});
        if (member) {
            return magicLinkService.sendMagicLink({email, user: {email}, type: 'signin'});
        } else {
            const type = requestedType === 'subscribe' ? 'subscribe' : 'signup';
            return magicLinkService.sendMagicLink({email, user: {email}, type});
        }
    }

    const users = Users({
        sendEmailWithMagicLink,
        stripe,
        createMember,
        getMember,
        updateMember,
        deleteMember,
        listMembers
    });

    async function getMemberDataFromMagicLinkToken(token){
        const user = await magicLinkService.getUserFromToken(token);
        const email = user && user.email;
        if (!email) {
            return null;
        }
        const member = await getMemberIdentityData(email);
        if (member) {
            return member;
        }
        await users.create({email});
        return getMemberIdentityData(email);
    }
    async function getMemberIdentityData(email){
        return users.get({email});
    }
    async function getMemberIdentityToken(email){
        const member = await getMemberIdentityData(email);
        if (!member) {
            return null;
        }
        return encodeIdentityToken({sub: member.email});
    }

    const middleware = {
        sendMagicLink: Router(),
        createCheckoutSession: Router(),
        handleStripeWebhook: Router()
    };

    middleware.sendMagicLink.use(body.json(), async function (req, res) {
        const email = req.body.email;
        if (!email) {
            res.writeHead(400);
            return res.end('Bad Request.');
        }
        const emailType = req.body.emailType;
        try {
            await sendEmailWithMagicLink(email, emailType);
            res.writeHead(201);
            return res.end('Created.');
        } catch (err) {
            common.logging.error(err);
            res.writeHead(500);
            return res.end('Internal Server Error.');
        }
    });

    middleware.createCheckoutSession.use(ensureStripe, body.json(), async function (req, res) {
        const plan = req.body.plan;
        const identity = req.body.identity;

        if (!plan) {
            res.writeHead(400);
            return res.end('Bad Request.');
        }

        let email;
        try {
            if (!identity) {
                email = null;
            } else {
                const claims = await decodeToken(identity);
                email = claims.sub;
            }
        } catch (err) {
            res.writeHead(401);
            return res.end('Unauthorized');
        }

        const member = email ? await users.get({email}) : null;

        // Do not allow members already with a subscription to initiate a new checkout session
        if (member && member.stripe.subscriptions.length > 0) {
            res.writeHead(403);
            return res.end('No permission');
        }

        const sessionInfo = await stripe.createCheckoutSession(member, plan, {
            successUrl: req.body.successUrl,
            cancelUrl: req.body.cancelUrl
        });

        res.writeHead(200, {
            'Content-Type': 'application/json'
        });

        res.end(JSON.stringify(sessionInfo));
    });

    middleware.handleStripeWebhook.use(ensureStripe, body.raw({type: 'application/json'}), async function (req, res) {
        try {
            const event = await stripe.parseWebhook(req.body, req.headers['stripe-signature']);
            if (event.type !== 'checkout.session.completed') {
                res.writeHead(200);
                return res.end();
            }

            const customer = await stripe.getCustomer(event.data.object.customer);
            const member = await users.get({email: customer.email}) || await users.create({email: customer.email});

            await stripe.addCustomerToMember(member, customer);

            const emailType = 'signup';
            await sendEmailWithMagicLink(customer.email, emailType, {forceEmailType: true});
            res.writeHead(200);
            res.end();
        } catch (err) {
            common.logging.error(err);
            res.writeHead(400);
            res.end();
        }
    });

    const getPublicConfig = function () {
        return Promise.resolve({
            publicKey,
            issuer
        });
    };

    const bus = new (require('events').EventEmitter)();

    if (stripe) {
        stripe.ready().then(() => {
            bus.emit('ready');
        }).catch((err) => {
            bus.emit('error', err);
        });
    } else {
        process.nextTick(() => bus.emit('ready'));
    }

    return {
        middleware,
        getMemberDataFromMagicLinkToken,
        getMemberIdentityToken,
        getMemberIdentityData,
        getPublicConfig,
        bus,
        members: users
    };
};
<commit_msg>Added allowSelfSignup options to auth config<commit_after>const {Router} = require('express');
const body = require('body-parser');
const MagicLink = require('@tryghost/magic-link');
const StripePaymentProcessor = require('./lib/stripe');

const Tokens = require('./lib/tokens');
const Users = require('./lib/users');
const common = require('./lib/common');

module.exports = function MembersApi({
    tokenConfig: {
        issuer,
        privateKey,
        publicKey
    },
    auth: {
        allowSelfSignup,
        getSigninURL
    },
    paymentConfig,
    mail: {
        transporter,
        getText,
        getHTML
    },
    setMemberMetadata,
    getMemberMetadata,
    createMember,
    getMember,
    updateMember,
    deleteMember,
    listMembers,
    logger
}) {
    if (logger) {
        common.logging.setLogger(logger);
    }

    const {encodeIdentityToken, decodeToken} = Tokens({privateKey, publicKey, issuer});

    const stripeStorage = {
        async get(member) {
            return getMemberMetadata(member, 'stripe');
        },
        async set(member, metadata) {
            return setMemberMetadata(member, 'stripe', metadata);
        }
    };
    const stripe = paymentConfig.stripe ? new StripePaymentProcessor(paymentConfig.stripe, stripeStorage, common.logging) : null;

    async function ensureStripe(_req, res, next) {
        if (!stripe) {
            res.writeHead(400);
            return res.end('Stripe not configured');
        }
        try {
            await stripe.ready();
            next();
        } catch (err) {
            res.writeHead(500);
            return res.end('There was an error configuring stripe');
        }
    }

    const magicLinkService = new MagicLink({
        transporter,
        publicKey,
        privateKey,
        getSigninURL,
        getText,
        getHTML
    });

    async function sendEmailWithMagicLink(email, requestedType, options = {forceEmailType: false}){
        if (options.forceEmailType) {
            return magicLinkService.sendMagicLink({email, user: {email}, type: requestedType});
        }
        const member = await users.get({email});
        if (member) {
            return magicLinkService.sendMagicLink({email, user: {email}, type: 'signin'});
        } else {
            const type = requestedType === 'subscribe' ? 'subscribe' : 'signup';
            return magicLinkService.sendMagicLink({email, user: {email}, type});
        }
    }

    const users = Users({
        sendEmailWithMagicLink,
        stripe,
        createMember,
        getMember,
        updateMember,
        deleteMember,
        listMembers
    });

    async function getMemberDataFromMagicLinkToken(token){
        const user = await magicLinkService.getUserFromToken(token);
        const email = user && user.email;
        if (!email) {
            return null;
        }
        const member = await getMemberIdentityData(email);
        if (member) {
            return member;
        }
        await users.create({email});
        return getMemberIdentityData(email);
    }
    async function getMemberIdentityData(email){
        return users.get({email});
    }
    async function getMemberIdentityToken(email){
        const member = await getMemberIdentityData(email);
        if (!member) {
            return null;
        }
        return encodeIdentityToken({sub: member.email});
    }

    const middleware = {
        sendMagicLink: Router(),
        createCheckoutSession: Router(),
        handleStripeWebhook: Router()
    };

    middleware.sendMagicLink.use(body.json(), async function (req, res) {
        const email = req.body.email;
        if (!email) {
            res.writeHead(400);
            return res.end('Bad Request.');
        }
        const emailType = req.body.emailType;
        try {
            if (!allowSelfSignup) {
                const member = await users.get({email});
                if (member) {
                    await sendEmailWithMagicLink(email, emailType);
                }
            } else {
                await sendEmailWithMagicLink(email, emailType);
            }
            res.writeHead(201);
            return res.end('Created.');
        } catch (err) {
            common.logging.error(err);
            res.writeHead(500);
            return res.end('Internal Server Error.');
        }
    });

    middleware.createCheckoutSession.use(ensureStripe, body.json(), async function (req, res) {
        const plan = req.body.plan;
        const identity = req.body.identity;

        if (!plan) {
            res.writeHead(400);
            return res.end('Bad Request.');
        }

        let email;
        try {
            if (!identity) {
                email = null;
            } else {
                const claims = await decodeToken(identity);
                email = claims.sub;
            }
        } catch (err) {
            res.writeHead(401);
            return res.end('Unauthorized');
        }

        const member = email ? await users.get({email}) : null;

        // Do not allow members already with a subscription to initiate a new checkout session
        if (member && member.stripe.subscriptions.length > 0) {
            res.writeHead(403);
            return res.end('No permission');
        }

        const sessionInfo = await stripe.createCheckoutSession(member, plan, {
            successUrl: req.body.successUrl,
            cancelUrl: req.body.cancelUrl
        });

        res.writeHead(200, {
            'Content-Type': 'application/json'
        });

        res.end(JSON.stringify(sessionInfo));
    });

    middleware.handleStripeWebhook.use(ensureStripe, body.raw({type: 'application/json'}), async function (req, res) {
        try {
            const event = await stripe.parseWebhook(req.body, req.headers['stripe-signature']);
            if (event.type !== 'checkout.session.completed') {
                res.writeHead(200);
                return res.end();
            }

            const customer = await stripe.getCustomer(event.data.object.customer);
            const member = await users.get({email: customer.email}) || await users.create({email: customer.email});

            await stripe.addCustomerToMember(member, customer);

            const emailType = 'signup';
            await sendEmailWithMagicLink(customer.email, emailType, {forceEmailType: true});
            res.writeHead(200);
            res.end();
        } catch (err) {
            common.logging.error(err);
            res.writeHead(400);
            res.end();
        }
    });

    const getPublicConfig = function () {
        return Promise.resolve({
            publicKey,
            issuer
        });
    };

    const bus = new (require('events').EventEmitter)();

    if (stripe) {
        stripe.ready().then(() => {
            bus.emit('ready');
        }).catch((err) => {
            bus.emit('error', err);
        });
    } else {
        process.nextTick(() => bus.emit('ready'));
    }

    return {
        middleware,
        getMemberDataFromMagicLinkToken,
        getMemberIdentityToken,
        getMemberIdentityData,
        getPublicConfig,
        bus,
        members: users
    };
};
<|endoftext|>


<commit_before>const Command = require("../base/Command");

class Versus extends Command {
    constructor(Bot) {
        super(Bot, {
            name: "versus",
            category: "Dev",
            enabled: true,
            aliases: ["vs"],
            permissions: ["EMBED_LINKS"],
            subArgs: {}
        });
    }

    async run(Bot, message, [user1str, user2str, ...character], options) { // eslint-disable-line no-unused-vars
        const statList = [
            {stat: "Health",                   short: "Health"},
            {stat: "Protection",               short: "Prot"  },
            {stat: "Speed",                    short: "Speed" },
            {stat: "Armor",                    short: "Armor" },
            {stat: "Physical Damage",          short: "P Dmg" },
            {stat: "Physical Critical Chance", short: "P Crit"},
            {stat: "Special Damage",           short: "S Dmg" },
            {stat: "Special Critical Chance",  short: "S Crit"},
            {stat: "Critical Damage",          short: "Crit D"},
            {stat: "Potency",                  short: "Pot"   },
            {stat: "Tenacity",                 short: "Ten"   }
        ];
        /*
         * If user1str AND user2str are both valid users, use both
         *
         * If only user2str is valid, spit out an error
         *
         * If only user1str is valid, set it up for user2 and grab the primary code if available
         *  -- If there is no valid primary/ linked allycode for author, spit out an error
         *  -- In this case, tack whatever user2str is onto the beginning of the character array
         *
         * Once both users are validated in some way, check against the character, get a valid baseId and go from there.
         *  -- If character is invalid, spit out an error
         */

        // If it can't find a valid character, but can find a ship, set this to true so it knows how to format the data later
        // let ship = false;

        const msg = await message.channel.send("Please wait while I process your data.");

        let user1 = await super.getUser(message, user1str, false);
        let user2 = await super.getUser(message, user2str, false);

        if (user1 && user2) {
            // Everyting is fine, they entered 2 valid users
            if (character.length) {
                character = character.join(" ");
            } else {
                // Spit out an error because of no character
                // TODO return error here
            }
        } else if (user1) {
            // Just user1 is valid, grab author's linked code and attach user2str to character
            user2 = user1;
            user1 = await super.getUser(message, message.author.id, false);
            if (!user1) {
                // Spit out an error because the message author is not registered
                // TODO return error here
            }
            character = [user2str].concat(character).join(" ");
        } else if (user2) {
            // Something went wrong with user1, let's spit out an error
            // TODO return error here
        } else {
            // Something really went wrong, and neither user is valid. Spit out a bigger error
            // TODO return error here
        }

        // If it got this far, it has 2 users and a character that needs checking.
        let char = Bot.findChar(character, Bot.characters);
        if (!char.length) {
            char = Bot.findChar(character, Bot.ships, true);
            // ship = true;
        }
        if (!char.length) {
            //TODO return super.error(message, message.language.get("COMMAND_GRANDARENA_INVALID_CHAR", character));
        } else if (char.length > 1) {
            // If found more than 1 match
            // TODO return error here about more than 1 match
        } else {
            // It only found one match
            if (Array.isArray(char)) char = char[0];
        }

        const cooldown = await Bot.getPlayerCooldown(message.author.id);
        try {
            user1 = await Bot.swgohAPI.unitStats(user1, cooldown);
            if (Array.isArray(user1)) user1 = user1[0];
        } catch (e) {
            return super.error(message, "Something broke when getting user 1");
        }
        try {
            user2 = await Bot.swgohAPI.unitStats(user2, cooldown);
            if (Array.isArray(user2)) user2 = user2[0];
        } catch (e) {
            return super.error(message, "Something broke when getting user 2");
        }
        const errArr = [];
        if (!user1?.roster?.length) {
            errArr.push("User 1 is missing pieces, please try again later.");
        }
        if (!user2?.roster?.length) {
            errArr.push("User 2 is missing pieces, please try again later.");
        }
        if (errArr.length) {
            return super.error(message, errArr.join("\n"));
        }

        // From here, we should have both user's rosters, so grab the characters and make the table
        const char1 = user1.roster.find(c => c.defId === char.uniqueName);
        const char2 = user2.roster.find(c => c.defId === char.uniqueName);

        const isShip = char1.combatType === 1 ? false : true;

        const genOut = [];

        // Stick the level in
        genOut.push({
            stat: "Lvl",
            user1: char1?.level,
            user2: char2?.level
        });

        // Add in the Star level / rarity
        genOut.push({
            stat: "Rarity",
            user1: char1?.rarity,
            user2: char2?.rarity
        });

        if (!isShip) {
            // Add in the zeta count
            const user1Zetas = char1?.skills.filter(sk => sk.isZeta && sk.tiers === sk.tier).length;
            const user2Zetas = char2?.skills.filter(sk => sk.isZeta && sk.tiers === sk.tier).length;
            if (user1Zetas || user2Zetas) {
                genOut.push({
                    stat: "Zetas",
                    user1: user1Zetas,
                    user2: user2Zetas
                });
            }

            // Add in the gear level
            genOut.push({
                stat: "Gear",
                user1: char1?.gear,
                user2: char2?.gear
            });

            const user1Relic = char1?.relic?.currentTier - 2;
            const user2Relic = char2?.relic?.currentTier - 2;
            if (user1Relic || user2Relic) {
                genOut.push({
                    stat: "Relic",
                    user1: user1Relic,
                    user2: user2Relic
                });
            }
        }

        const generalTable = Bot.makeTable({
            stat:  {value: "Stat", align: "right", endWith: "::"},
            user1: {value: user1.name, align: "right", endWith: "vs"},
            user2: {value: user2.name, align: "left"}
        }, genOut, {boldHeader: false, useHeader: false});

        // For each stat in a list, add onto the statOut array
        const statOut = [];
        statList.forEach(stat => {
            const s1 = char1.stats.final[stat.stat];
            const s2 = char2.stats.final[stat.stat];
            statOut.push({
                stat: stat.short,
                user1: s1 % 1 === 0 ? s1.toLocaleString() : (s1 * 100).toFixed(2)+"%",
                user2: s2 % 1 === 0 ? s2.toLocaleString() : (s2 * 100).toFixed(2)+"%",
            });
        });

        const langChar = await Bot.swgohAPI.langChar({defId: char1.defId});
        const charName = langChar.nameKey;
        const statTable = Bot.makeTable({
            stat:  {value: "Stat", align: "right", endWith: "::"},
            user1: {value: user1.name, align: "right", endWith: "vs"},
            user2: {value: user2.name, align: "left"}
        }, statOut, {boldHeader: false, useHeader: false});

        const footer = Bot.updatedFooter(Math.min(user1.updated, user2.updated), message, "player", cooldown);
        return msg.edit("", {embed: {
            title: `${user1.name} vs. ${user2.name} (${charName})`,
            fields: [
                {
                    name: "General Info",
                    value: Bot.codeBlock(generalTable.join("\n"), "asciidoc")
                },
                {
                    name: "Stats",
                    value: Bot.codeBlock(statTable.join("\n"), "asciidoc")
                }
            ],
            footer: footer
        }});
    }
}

module.exports = Versus;

<commit_msg>Updated ;versus to account for one or both account not having a character<commit_after>const Command = require("../base/Command");

class Versus extends Command {
    constructor(Bot) {
        super(Bot, {
            name: "versus",
            category: "SWGoH",
            enabled: true,
            aliases: ["vs"],
            permissions: ["EMBED_LINKS"],
            subArgs: {}
        });
    }

    async run(Bot, message, [user1str, user2str, ...character], options) { // eslint-disable-line no-unused-vars
        const statList = [
            {stat: "Health",                   short: "Health"},
            {stat: "Protection",               short: "Prot"  },
            {stat: "Speed",                    short: "Speed" },
            {stat: "Armor",                    short: "Armor" },
            {stat: "Physical Damage",          short: "P Dmg" },
            {stat: "Physical Critical Chance", short: "P Crit"},
            {stat: "Special Damage",           short: "S Dmg" },
            {stat: "Special Critical Chance",  short: "S Crit"},
            {stat: "Critical Damage",          short: "Crit D"},
            {stat: "Potency",                  short: "Pot"   },
            {stat: "Tenacity",                 short: "Ten"   }
        ];
        /*
         * If user1str AND user2str are both valid users, use both
         *
         * If only user2str is valid, spit out an error
         *
         * If only user1str is valid, set it up for user2 and grab the primary code if available
         *  -- If there is no valid primary/ linked allycode for author, spit out an error
         *  -- In this case, tack whatever user2str is onto the beginning of the character array
         *
         * Once both users are validated in some way, check against the character, get a valid baseId and go from there.
         *  -- If character is invalid, spit out an error
         */

        // If it can't find a valid character, but can find a ship, set this to true so it knows how to format the data later
        // let ship = false;

        const msg = await message.channel.send("Please wait while I process your data.");

        let user1 = await super.getUser(message, user1str, false);
        let user2 = await super.getUser(message, user2str, false);

        if (user1 && user2) {
            // Everyting is fine, they entered 2 valid users
            if (character.length) {
                character = character.join(" ");
            } else {
                // Spit out an error because of no character
                // TODO return error here
            }
        } else if (user1) {
            // Just user1 is valid, grab author's linked code and attach user2str to character
            user2 = user1;
            user1 = await super.getUser(message, message.author.id, false);
            if (!user1) {
                // Spit out an error because the message author is not registered
                // TODO return error here
            }
            character = [user2str].concat(character).join(" ");
        } else if (user2) {
            // Something went wrong with user1, let's spit out an error
            // TODO return error here
        } else {
            // Something really went wrong, and neither user is valid. Spit out a bigger error
            // TODO return error here
        }

        // If it got this far, it has 2 users and a character that needs checking.
        let char = Bot.findChar(character, Bot.characters);
        if (!char.length) {
            char = Bot.findChar(character, Bot.ships, true);
            // ship = true;
        }
        if (!char.length) {
            //TODO return super.error(message, message.language.get("COMMAND_GRANDARENA_INVALID_CHAR", character));
        } else if (char.length > 1) {
            // If found more than 1 match
            // TODO return error here about more than 1 match
        } else {
            // It only found one match
            if (Array.isArray(char)) char = char[0];
        }

        const cooldown = await Bot.getPlayerCooldown(message.author.id);
        try {
            user1 = await Bot.swgohAPI.unitStats(user1, cooldown);
            if (Array.isArray(user1)) user1 = user1[0];
        } catch (e) {
            return super.error(message, "Something broke when getting user 1");
        }
        try {
            user2 = await Bot.swgohAPI.unitStats(user2, cooldown);
            if (Array.isArray(user2)) user2 = user2[0];
        } catch (e) {
            return super.error(message, "Something broke when getting user 2");
        }
        const errArr = [];
        if (!user1?.roster?.length) {
            errArr.push("User 1 is missing pieces, please try again later.");
        }
        if (!user2?.roster?.length) {
            errArr.push("User 2 is missing pieces, please try again later.");
        }
        if (errArr.length) {
            return super.error(message, errArr.join("\n"));
        }

        // From here, we should have both user's rosters, so grab the characters and make the table
        const char1 = user1.roster.find(c => c.defId === char.uniqueName);
        const char2 = user2.roster.find(c => c.defId === char.uniqueName);

        if (!char1 && !char2) {
            return super.error(message, "Neither user seems to have that character unlocked!") ;
        }

        const isShip = (char1 ? char1.combatType : char2.combatType) === 1 ? false : true;

        const genOut = [];

        // Stick the level in
        genOut.push({
            stat: "Lvl",
            user1: char1?.level,
            user2: char2?.level
        });

        // Add in the Star level / rarity
        genOut.push({
            stat: "Rarity",
            user1: char1?.rarity,
            user2: char2?.rarity
        });

        if (!isShip) {
            // Add in the zeta count
            const user1Zetas = char1?.skills.filter(sk => sk.isZeta && sk.tiers === sk.tier).length;
            const user2Zetas = char2?.skills.filter(sk => sk.isZeta && sk.tiers === sk.tier).length;
            if (user1Zetas || user2Zetas) {
                genOut.push({
                    stat: "Zetas",
                    user1: user1Zetas,
                    user2: user2Zetas
                });
            }

            // Add in the gear level
            genOut.push({
                stat: "Gear",
                user1: char1?.gear,
                user2: char2?.gear
            });

            const user1Relic = char1?.relic?.currentTier - 2;
            const user2Relic = char2?.relic?.currentTier - 2;
            if (user1Relic || user2Relic) {
                genOut.push({
                    stat: "Relic",
                    user1: user1Relic,
                    user2: user2Relic
                });
            }
        }

        const generalTable = Bot.makeTable({
            stat:  {value: "Stat", align: "right", endWith: "::"},
            user1: {value: user1.name, align: "right", endWith: "vs"},
            user2: {value: user2.name, align: "left"}
        }, genOut, {boldHeader: false, useHeader: false});

        // For each stat in a list, add onto the statOut array
        const statOut = [];
        statList.forEach(stat => {
            const s1 = char1?.stats?.final[stat.stat];
            const s2 = char2?.stats?.final[stat.stat];
            statOut.push({
                stat: stat.short,
                user1: s1 ? (s1 % 1 === 0 ? s1.toLocaleString() : (s1 * 100).toFixed(2)+"%") : "N/A",
                user2: s2 ? (s2 % 1 === 0 ? s2.toLocaleString() : (s2 * 100).toFixed(2)+"%") : "N/A",
            });
        });

        const langChar = await Bot.swgohAPI.langChar({defId: char1.defId});
        const charName = langChar.nameKey;
        const statTable = Bot.makeTable({
            stat:  {value: "Stat", align: "right", endWith: "::"},
            user1: {value: user1.name, align: "right", endWith: "vs"},
            user2: {value: user2.name, align: "left"}
        }, statOut, {boldHeader: false, useHeader: false});

        const footer = Bot.updatedFooter(Math.min(user1.updated, user2.updated), message, "player", cooldown);
        return msg.edit("", {embed: {
            title: `${user1.name} vs. ${user2.name} (${charName})`,
            fields: [
                {
                    name: "General Info",
                    value: Bot.codeBlock(generalTable.join("\n"), "asciidoc")
                },
                {
                    name: "Stats",
                    value: Bot.codeBlock(statTable.join("\n"), "asciidoc")
                }
            ],
            footer: footer
        }});
    }
}

module.exports = Versus;

<|endoftext|>


<commit_before>/*
 *	Initial setup
 */

"use strict" // Otherwise we're stuck with ES5

const LOCAL_HOST = 3000;

// Variables required for Markov Chain generation
var input = [];
var firstWords = [];
var currentWord = "";

// Variables required for bottom
var currentTopicId;
var currentToken;

// Set up modules
var express = require("express");
var request = require("request");
var bodyParser = require("body-parser");
var cheerio = require('cheerio');
var client = require('redis').createClient(process.env.REDIS_URL);

// Set up app
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.cookieJar = request.jar(); // Global cookie store
app.cachedData = { "post": "" }; // Basic format for JSON response
app.loggedIn = false; // Indicates whether app has login cookie for ETI

// Set up routing for API
var routes = require("./routes/routes.js")(app);

// Set up server
var server = app.listen(process.env.PORT || LOCAL_HOST, () => {
	console.log(process.env.USERNAME + "-bot is sentient and ready to shitpost");
});

/**
	*		Redis db handler
	*/

client.on("connect", () => {
		// Attempt to get "quotes" array from Redis
		client.lrange("quotes", 0, -1, (error, items) => {
			if (error) {
				// TODO: Store emergency vesper.txt somewhere in repo and use that instead
				throw error;
			}
			else {
				input = items;
				app.generateMarkovChain();
			}
		});
});

/**
  * Markov chain methods
	*/

app.generateMarkovChain = function(shouldReturn) {
	var output = [];

	app.createArrays();

	// Pick random first word to start with
	currentWord = firstWords[Math.floor((Math.random() * firstWords.length))];

	for (let i = 0, len = app.getPostLength(); i < len; i++) {
		output.push(currentWord);

		var nextWord = app.generateNextWord();

		if (nextWord) {
			currentWord = nextWord;
		}
		else {
			currentWord = "";
		}
	}

	app.cachedData.post = output.join(" ").trim();

	if (shouldReturn) {
		return app.cachedData.post;
	}
};

app.getPostLength = function() {
	return input[Math.floor((Math.random() * input.length))].length;
};

app.createArrays = function() {
	// Create array containing first word of each sentence
	for (let i = 0, len = input.length; i < len; i++) {
		let sentence = input[i];
		let firstWord = sentence.split(" ")[0];
		firstWords.push(firstWord);
	}
};

app.generateNextWord = function() {
	var tempArray = [];
	// Iterate over input array to find instances of current word
	for (let i = 0, len = input.length - 1; i < len; i++) {
		let sentenceToCheck = input[i].split(" ");

		for (let j = 0, len = sentenceToCheck.length; j < len; j++) {
			let wordToCheck = sentenceToCheck[j];
			// create array of words that are likely to follow current word
			if (wordToCheck === currentWord) {
				// Make sure that item at index i + 1 exists
				if (j < len - 1) {
					let nextWord = sentenceToCheck[j + 1];
					tempArray.push(nextWord);
				}
				else {
					// reached end of array - do nothing
				}
			}
		}
	}

	let rnd = Math.floor((Math.random() * tempArray.length));

	if (tempArray[rnd]) {
		return tempArray[rnd];
	}

	else {
		return;
	}
};

/**
  *		Method which allows us to add new quotes from pastebin raw links
  */

app.addNewQuotes = function(url) {
	request.get(url, ((error, response, body) => {
		if (!error && response.statusCode == 200) {
			// Ignore blank lines,
			var quotes = body.split("\r\n").filter((line) => line && line.indexOf("<") == -1 && line.charAt(0) !== "<");

			client.lrange("quotes", 0, -1, (error, items) => {
				if (error) {
					throw error;
				}
				else {
					// Update local & disk cache
					input = input.concat(quotes);
					client.rpush.apply(client, ["quotes"].concat(quotes).concat(() => { console.log("Quotes stored to redis") }));
				}
			});

		}

	}));
};

/**
  *		Log into ETI using environment vars as credentials
  */

app.loginToBlueSite = function() {
	const LOGIN_URL = "https://endoftheinter.net/";
	const formData = { b: process.env.USERNAME, p: process.env.PASSWORD };

	request.post({

		headers: {"content-type": "application/x-www-form-urlencoded"},
		url: LOGIN_URL,
		form: formData,
		jar: app.cookieJar

	}, (error, response, body) => {

			// After successful login, ETI will attempt to redirect you to homepage
			if (!error && response.statusCode === 302) {
					app.getTopicList();
			}

			else {
				console.log("Login failed.");
				throw error;
			}
	});
};

app.getTopicList = function() {
	var LUE_TOPICS = "https://boards.endoftheinter.net/topics/LUE";

	request({

		url: LUE_TOPICS,
		jar: app.cookieJar

	}
	, (error, response, body) => {

		if (!error && response.statusCode === 200) {
			// Find random topic to pester
			var $ = cheerio.load(body);
			var randomTopic = Math.floor(Math.random() * 50) + 1;
			var href;

			// I don't understand the cheerio library. This is ridiculous. I don't care.
			$('td.oh div.fl a').each((index, element) => {

				if (index === randomTopic) {
					href = "https:" + $(element).attr('href');
					var topicNumberRegex = href.match(/(topic=)([0-9]+)/);

					if (href !== "https:" && topicNumberRegex) {
						currentTopicId = topicNumberRegex[2];
						console.log('current topic id:', currentTopicId);
						return false;
					}

					else {
						console.log("Couldn't find href. Index:", index);
						console.log("Random topic:", randomTopic);
						// Avoid potential for infinite loop here
						return false;
					}
				}

			});

			app.getMessageList(href);
		}

		else {
			console.log("Topic list GET failed.");
			throw error;
		}

	});
};

app.getMessageList = function(url) {

	request({

		url: url,
		jar: app.cookieJar

	}, (error, response, body) => {

		if (!error && response.statusCode === 200) {

			var $ = cheerio.load(body);
			// Can't make POST requests without the value of this token, scraped from quickpost area
			currentToken = $('input[name="h"]').attr('value');
			console.log(currentToken);
			app.contributeToDiscussion();
		}

		else {
			console.log("Topic list GET failed.");
			throw error;
		}

	});
};

app.contributeToDiscussion = function() {
	const QUICKPOST_URL = "https://boards.endoftheinter.net/async-post.php";

	var formData = {};
			formData.topic = currentTopicId;
			formData.h = currentToken;
			formData.message = app.generateMarkovChain(true); // Pass true to get return value immediately

	request.post({

		uri: QUICKPOST_URL,
		form: formData,
		jar: app.cookieJar

	}, (error, response, body) => {

			if (!error && response.statusCode === 200) {
				console.log("Post successful");
				response.statusCode = 302;
				response.setHeader("Location", "http://boards.endoftheinter.net/showmessages.php?topic=" + currentTopicId);
				response.end();
			}

			else {
				console.log("Post unsuccessful: code ", response.statusCode);
				throw error;
			}

	});
};<commit_msg>Wanna see the headers<commit_after>/*
 *	Initial setup
 */

"use strict" // Otherwise we're stuck with ES5

const LOCAL_HOST = 3000;

// Variables required for Markov Chain generation
var input = [];
var firstWords = [];
var currentWord = "";

// Variables required for bottom
var currentTopicId;
var currentToken;

// Set up modules
var express = require("express");
var request = require("request");
var bodyParser = require("body-parser");
var cheerio = require('cheerio');
var client = require('redis').createClient(process.env.REDIS_URL);

// Set up app
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.cookieJar = request.jar(); // Global cookie store
app.cachedData = { "post": "" }; // Basic format for JSON response
app.loggedIn = false; // Indicates whether app has login cookie for ETI

// Set up routing for API
var routes = require("./routes/routes.js")(app);

// Set up server
var server = app.listen(process.env.PORT || LOCAL_HOST, () => {
	console.log(process.env.USERNAME + "-bot is sentient and ready to shitpost");
});

/**
	*		Redis db handler
	*/

client.on("connect", () => {
		// Attempt to get "quotes" array from Redis
		client.lrange("quotes", 0, -1, (error, items) => {
			if (error) {
				// TODO: Store emergency vesper.txt somewhere in repo and use that instead
				throw error;
			}
			else {
				input = items;
				app.generateMarkovChain();
			}
		});
});

/**
  * Markov chain methods
	*/

app.generateMarkovChain = function(shouldReturn) {
	var output = [];

	app.createArrays();

	// Pick random first word to start with
	currentWord = firstWords[Math.floor((Math.random() * firstWords.length))];

	for (let i = 0, len = app.getPostLength(); i < len; i++) {
		output.push(currentWord);

		var nextWord = app.generateNextWord();

		if (nextWord) {
			currentWord = nextWord;
		}
		else {
			currentWord = "";
		}
	}

	app.cachedData.post = output.join(" ").trim();

	if (shouldReturn) {
		return app.cachedData.post;
	}
};

app.getPostLength = function() {
	return input[Math.floor((Math.random() * input.length))].length;
};

app.createArrays = function() {
	// Create array containing first word of each sentence
	for (let i = 0, len = input.length; i < len; i++) {
		let sentence = input[i];
		let firstWord = sentence.split(" ")[0];
		firstWords.push(firstWord);
	}
};

app.generateNextWord = function() {
	var tempArray = [];
	// Iterate over input array to find instances of current word
	for (let i = 0, len = input.length - 1; i < len; i++) {
		let sentenceToCheck = input[i].split(" ");

		for (let j = 0, len = sentenceToCheck.length; j < len; j++) {
			let wordToCheck = sentenceToCheck[j];
			// create array of words that are likely to follow current word
			if (wordToCheck === currentWord) {
				// Make sure that item at index i + 1 exists
				if (j < len - 1) {
					let nextWord = sentenceToCheck[j + 1];
					tempArray.push(nextWord);
				}
				else {
					// reached end of array - do nothing
				}
			}
		}
	}

	let rnd = Math.floor((Math.random() * tempArray.length));

	if (tempArray[rnd]) {
		return tempArray[rnd];
	}

	else {
		return;
	}
};

/**
  *		Method which allows us to add new quotes from pastebin raw links
  */

app.addNewQuotes = function(url) {
	request.get(url, ((error, response, body) => {
		if (!error && response.statusCode == 200) {
			// Ignore blank lines,
			var quotes = body.split("\r\n").filter((line) => line && line.indexOf("<") == -1 && line.charAt(0) !== "<");

			client.lrange("quotes", 0, -1, (error, items) => {
				if (error) {
					throw error;
				}
				else {
					// Update local & disk cache
					input = input.concat(quotes);
					client.rpush.apply(client, ["quotes"].concat(quotes).concat(() => { console.log("Quotes stored to redis") }));
				}
			});

		}

	}));
};

/**
  *		Log into ETI using environment vars as credentials
  */

app.loginToBlueSite = function() {
	const LOGIN_URL = "https://endoftheinter.net/";
	const formData = { b: process.env.USERNAME, p: process.env.PASSWORD };

	request.post({

		headers: {"content-type": "application/x-www-form-urlencoded"},
		url: LOGIN_URL,
		form: formData,
		jar: app.cookieJar

	}, (error, response, body) => {

			// After successful login, ETI will attempt to redirect you to homepage
			if (!error && response.statusCode === 302) {
					app.getTopicList();
			}

			else {
				console.log("Login failed.");
				throw error;
			}
	});
};

app.getTopicList = function() {
	var LUE_TOPICS = "https://boards.endoftheinter.net/topics/LUE";

	request({

		url: LUE_TOPICS,
		jar: app.cookieJar

	}
	, (error, response, body) => {

		if (!error && response.statusCode === 200) {
			// Find random topic to pester
			var $ = cheerio.load(body);
			var randomTopic = Math.floor(Math.random() * 50) + 1;
			var href;

			// I don't understand the cheerio library. This is ridiculous. I don't care.
			$('td.oh div.fl a').each((index, element) => {

				if (index === randomTopic) {
					href = "https:" + $(element).attr('href');
					var topicNumberRegex = href.match(/(topic=)([0-9]+)/);

					if (href !== "https:" && topicNumberRegex) {
						currentTopicId = topicNumberRegex[2];
						console.log('current topic id:', currentTopicId);
						return false;
					}

					else {
						console.log("Couldn't find href. Index:", index);
						console.log("Random topic:", randomTopic);
						// Avoid potential for infinite loop here
						return false;
					}
				}

			});

			app.getMessageList(href);
		}

		else {
			console.log("Topic list GET failed.");
			throw error;
		}

	});
};

app.getMessageList = function(url) {

	request({

		url: url,
		jar: app.cookieJar

	}, (error, response, body) => {

		if (!error && response.statusCode === 200) {

			var $ = cheerio.load(body);
			// Can't make POST requests without the value of this token, scraped from quickpost area
			currentToken = $('input[name="h"]').attr('value');
			console.log(currentToken);
			app.contributeToDiscussion();
		}

		else {
			console.log("Topic list GET failed.");
			throw error;
		}

	});
};

app.contributeToDiscussion = function() {
	const QUICKPOST_URL = "https://boards.endoftheinter.net/async-post.php";

	var formData = {};
			formData.topic = currentTopicId;
			formData.h = currentToken;
			formData.message = app.generateMarkovChain(true); // Pass true to get return value immediately

	request.post({

		uri: QUICKPOST_URL,
		form: formData,
		jar: app.cookieJar

	}, (error, response, body) => {

			if (!error && response.statusCode === 200) {
				console.log("Post successful");
				console.log(response.headers);
			}

			else {
				console.log("Post unsuccessful: code ", response.statusCode);
				throw error;
			}

	});
};<|endoftext|>


<commit_before>const log = require('../util/logger.js')
const FeedSelector = require('../structs/FeedSelector.js')
const MenuUtils = require('../structs/MenuUtils.js')
const FeedFetcher = require('../util/FeedFetcher.js')
const ArticleMessageQueue = require('../structs/ArticleMessageQueue.js')
const Translator = require('../structs/Translator.js')
const GuildProfile = require('../structs/db/GuildProfile.js')
const FailCounter = require('../structs/db/FailCounter.js')
const Feed = require('../structs/db/Feed.js')
const Format = require('../structs/db/Format.js')
const Subscriber = require('../structs/db/Subscriber.js')
const Supporter = require('../structs/db/Supporter.js')
const FilteredFormat = require('../structs/db/FilteredFormat.js')

module.exports = async (bot, message, command) => {
  const simple = MenuUtils.extractArgsAfterCommand(message.content).includes('simple')
  try {
    const profile = await GuildProfile.get(message.guild.id)
    const feeds = await Feed.getManyBy('guild', message.guild.id)

    const guildLocale = profile ? profile.locale : undefined
    const translate = Translator.createLocaleTranslator(guildLocale)
    const feedSelector = new FeedSelector(message, null, { command: command }, feeds)
    const data = await new MenuUtils.MenuSeries(message, [feedSelector], { locale: guildLocale }).start()
    if (!data) {
      return
    }
    const { feed } = data
    if (await FailCounter.hasFailed(feed.url)) {
      return await message.channel.send(translate('commands.rsstest.failed'))
    }
    const [ format, subscribers, filteredFormats ] = await Promise.all([
      Format.getBy('feed', feed._id),
      Subscriber.getManyBy('feed', feed._id),
      FilteredFormat.getManyBy('feed', feed._id)
    ])
    const grabMsg = await message.channel.send(translate('commands.rsstest.grabbingRandom'))
    const article = await FeedFetcher.fetchRandomArticle(feed.url)
    if (!article) {
      return await message.channel.send(translate('commands.rsstest.noArticles'))
    }
    article._delivery = {
      rssName: feed._id,
      source: {
        ...feed.toJSON(),
        format: format ? format.toJSON() : undefined,
        filteredFormats: filteredFormats.map(f => f.toJSON()),
        subscribers: subscribers.map(s => s.toJSON()),
        dateSettings: profile
          ? {
            timezone: profile.timezone,
            format: profile.dateFormat,
            language: profile.dateLanguage
          }
          : {}
      }
    }
    if (Supporter.enabled && profile.webhook && !(await Supporter.hasValidGuild(message.guild.id))) {
      log.command.warning('Illegal webhook detected for non-vip user', message.guild, message.author)
      profile.webhook = undefined
      await profile.save()
    }

    const queue = new ArticleMessageQueue()
    await queue.enqueue(article, !simple, true)
    await queue.send(bot)
    await grabMsg.delete()
  } catch (err) {
    log.command.warning(`rsstest`, message.guild, err)
    if (err.code !== 50013) message.channel.send(err.message).catch(err => log.command.warning('rsstest 1', message.guild, err))
  }
}
<commit_msg>Fix lint<commit_after>const log = require('../util/logger.js')
const FeedSelector = require('../structs/FeedSelector.js')
const MenuUtils = require('../structs/MenuUtils.js')
const FeedFetcher = require('../util/FeedFetcher.js')
const ArticleMessageQueue = require('../structs/ArticleMessageQueue.js')
const Translator = require('../structs/Translator.js')
const GuildProfile = require('../structs/db/GuildProfile.js')
const FailCounter = require('../structs/db/FailCounter.js')
const Feed = require('../structs/db/Feed.js')
const Format = require('../structs/db/Format.js')
const Subscriber = require('../structs/db/Subscriber.js')
const Supporter = require('../structs/db/Supporter.js')
const FilteredFormat = require('../structs/db/FilteredFormat.js')

module.exports = async (bot, message, command) => {
  const simple = MenuUtils.extractArgsAfterCommand(message.content).includes('simple')
  try {
    const profile = await GuildProfile.get(message.guild.id)
    const feeds = await Feed.getManyBy('guild', message.guild.id)

    const guildLocale = profile ? profile.locale : undefined
    const translate = Translator.createLocaleTranslator(guildLocale)
    const feedSelector = new FeedSelector(message, null, { command: command }, feeds)
    const data = await new MenuUtils.MenuSeries(message, [feedSelector], { locale: guildLocale }).start()
    if (!data) {
      return
    }
    const { feed } = data
    if (await FailCounter.hasFailed(feed.url)) {
      return await message.channel.send(translate('commands.rsstest.failed'))
    }
    const [ format, subscribers, filteredFormats ] = await Promise.all([
      Format.getBy('feed', feed._id),
      Subscriber.getManyBy('feed', feed._id),
      FilteredFormat.getManyBy('feed', feed._id)
    ])
    const grabMsg = await message.channel.send(translate('commands.rsstest.grabbingRandom'))
    const article = await FeedFetcher.fetchRandomArticle(feed.url)
    if (!article) {
      return await message.channel.send(translate('commands.rsstest.noArticles'))
    }
    article._delivery = {
      rssName: feed._id,
      source: {
        ...feed.toJSON(),
        format: format ? format.toJSON() : undefined,
        filteredFormats: filteredFormats.map(f => f.toJSON()),
        subscribers: subscribers.map(s => s.toJSON()),
        dateSettings: profile
          ? {
            timezone: profile.timezone,
            format: profile.dateFormat,
            language: profile.dateLanguage
          }
          : {}
      }
    }
    if (Supporter.enabled && profile.webhook && !(await Supporter.hasValidGuild(message.guild.id))) {
      log.command.warning('Illegal webhook detected for non-vip user', message.guild, message.author)
      profile.webhook = undefined
      await profile.save()
    }

    const queue = new ArticleMessageQueue()
    await queue.enqueue(article, !simple, true)
    await queue.send(bot)
    await grabMsg.delete()
  } catch (err) {
    log.command.warning(`rsstest`, message.guild, err)
    if (err.code !== 50013) message.channel.send(err.message).catch(err => log.command.warning('rsstest 1', message.guild, err))
  }
}
<|endoftext|>


<commit_before>var fs = require('fs');
var posix = require('posix');
var Stratum = require('stratum-pool');
var PoolLogger = require('./libs/logutils.js');

JSON.minify = JSON.minify || require("node-json-minify");

try{
    posix.setrlimit('nofile', { soft: 100000 });
}
catch(e){
    console.error(e);
}

var loggerInstance = new PoolLogger({
    'default': true,
    'keys': {
        //'client'      : 'warning',
        'system'      : true,
        'submitblock' : true
    }
});

var logDebug   = loggerInstance.logDebug;
var logWarning = loggerInstance.logWarning;
var logError   = loggerInstance.logError;

var config = JSON.parse(JSON.minify(fs.readFileSync("config.json", {encoding: 'utf8'})));

var stratum = new Stratum(config);
stratum.on('log', function(logText){
    logDebug(logText);
});

fs.readdirSync('coins').forEach(function(file){

    var coinOptions = JSON.parse(JSON.minify(fs.readFileSync('coins/' + file, {encoding: 'utf8'})));

    var authorizeFN = function (ip, workerName, password, callback) {
        // Default implementation just returns true
        logDebug(coinOptions.name, 'client', "Authorize ["+ip+"] "+workerName+":"+password);
        callback({
            error: null,
            authorized: true,
            disconnect: false
        });
    };

    var pool = stratum.createPool(coinOptions, authorizeFN);
    pool.on('share', function(isValidShare, isValidBlock, data){

        var shareData = JSON.stringify(data);

        if (isValidBlock)
            logDebug(coinOptions.name, 'client', 'Block found, share data: ' + shareData);
        else if (isValidShare)
            logDebug(coinOptions.name, 'client', 'Valid share submitted, share data: ' + shareData);
        else if (data.solution)
            logDebug(coinOptions.name, 'client', 'We thought a block solution was found but it was rejected by the daemon, share data: ' + shareData);
        else
            logDebug(coinOptions.name, 'client', 'Invalid share submitted, share data: ' + shareData)

    }).on('log', function(severity, logKey, logText) {
        if (severity == 'debug') {
            logDebug(coinOptions.name, logKey, logText);
        } else if (severity == 'warning') {
            logWarning(coinOptions.name, logKey, logText);
        } else if (severity == 'error') {
            logError(coinOptions.name, logKey, logText);
        }
    });
    pool.start();

});

<commit_msg>Added posix setrlimit / ulimit<commit_after>var fs = require('fs');
var posix = require('posix');
var Stratum = require('stratum-pool');
var PoolLogger = require('./libs/logutils.js');

JSON.minify = JSON.minify || require("node-json-minify");

try{
    posix.setrlimit('nofile', { soft: 100000, hard: 100000 });
}
catch(e){
    console.error(e);
}

var loggerInstance = new PoolLogger({
    'default': true,
    'keys': {
        //'client'      : 'warning',
        'system'      : true,
        'submitblock' : true
    }
});

var logDebug   = loggerInstance.logDebug;
var logWarning = loggerInstance.logWarning;
var logError   = loggerInstance.logError;

var config = JSON.parse(JSON.minify(fs.readFileSync("config.json", {encoding: 'utf8'})));

var stratum = new Stratum(config);
stratum.on('log', function(logText){
    logDebug(logText);
});

fs.readdirSync('coins').forEach(function(file){

    var coinOptions = JSON.parse(JSON.minify(fs.readFileSync('coins/' + file, {encoding: 'utf8'})));

    var authorizeFN = function (ip, workerName, password, callback) {
        // Default implementation just returns true
        logDebug(coinOptions.name, 'client', "Authorize ["+ip+"] "+workerName+":"+password);
        callback({
            error: null,
            authorized: true,
            disconnect: false
        });
    };

    var pool = stratum.createPool(coinOptions, authorizeFN);
    pool.on('share', function(isValidShare, isValidBlock, data){

        var shareData = JSON.stringify(data);

        if (isValidBlock)
            logDebug(coinOptions.name, 'client', 'Block found, share data: ' + shareData);
        else if (isValidShare)
            logDebug(coinOptions.name, 'client', 'Valid share submitted, share data: ' + shareData);
        else if (data.solution)
            logDebug(coinOptions.name, 'client', 'We thought a block solution was found but it was rejected by the daemon, share data: ' + shareData);
        else
            logDebug(coinOptions.name, 'client', 'Invalid share submitted, share data: ' + shareData)

    }).on('log', function(severity, logKey, logText) {
        if (severity == 'debug') {
            logDebug(coinOptions.name, logKey, logText);
        } else if (severity == 'warning') {
            logWarning(coinOptions.name, logKey, logText);
        } else if (severity == 'error') {
            logError(coinOptions.name, logKey, logText);
        }
    });
    pool.start();

});

<|endoftext|>