<commit_before>// Liveplot.js -- a p5js scene by Tim Schäfer.
// This is free software, published under the GPLv3 license. No warranties.

// +++ Settings +++
var numDots = 50;   // number of rows of the field
var numPositionsPerDot = 20;      // number of target positions per line, or columns (vertical lines)
var numLines = 15;           // number of the moving, colorful plot lines
var numPositionsToDrawForLine = 18;

var doDrawPotentialTargetPoints = false;
var doDrawTargetPoints = false;
var doDrawCurrentPoints = false;
var doDrawVerticalGridLines = true;
var doDrawHorizontalGridLines = true;
var doDrawMaxDrawXLine = false;
var doDrawXLabels = true;
var doDrawYLabels = true;
var doDrawSumAtVerticalLineBottom = true;

var doUseMouseAttraction = true;

var backGroundColor = 80;
var verticalLinesColor = 160;
var horizontalLinesColor = 160;
var useTextSize = 8;
var textColor = 255;
// +++ End of settings ++++

var minDistToTrackedMouseYPos;
var dotPositions = [];
var linesYSpots = [];
var maxXDrawLine;
var interDotDistanceY;
var interDotPositionDistanceX;
var nextLineYSpot;
var someColors = ['#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#ffffff', '#000000' ];

function setup() {
	frameRate(30);
  createCanvas(600, 400);
  textSize(useTextSize);
	interDotDistanceY = (height/numDots);
  interDotPositionDistanceX = (width/numPositionsPerDot);

	for (var i = 0; i < numDots; i++) {
    var currentYPos = i * interDotDistanceY + 0.5 * interDotDistanceY;
    for (var j = 0; j <= numPositionsPerDot; j++) {
      var currentXPos = j * interDotPositionDistanceX + 0.5 * interDotPositionDistanceX;
		    dotPositions.push([currentXPos, currentYPos]);
    }
	}

  maxDrawLineX = dotPositions[numPositionsToDrawForLine-2][0] + 0.5 * interDotPositionDistanceX;

  nextLineYSpot = [];
  for (var k = 0; k < numLines; k++) {
    var currentLineYSpots = [];
    for (var l = 0; l < numPositionsToDrawForLine; l++) {
      currentLineYSpots.push(getRandomInt(0, numDots-1));
    }
    linesYSpots.push(currentLineYSpots);
    var nextRandomYSpot = getRandomYPositionPotentiallyWithMouseBonus(0, numDots-1);
    nextLineYSpot.push(nextRandomYSpot);
  }
}

function draw() {
  background(backGroundColor);
	// move Dots
  var jumpThisFrame = false;
	for (var i = 0; i < dotPositions.length; i++) {
    dotPositions[i][0] -= 2;
		if(dotPositions[i][0] < -interDotPositionDistanceX) {  // reset if it left screen
      var howMuchSmaller = -interDotPositionDistanceX -dotPositions[i][0];
		  dotPositions[i][0] = width - howMuchSmaller;
      jumpThisFrame = true;
		}
	}

  if(jumpThisFrame) {
    // draw new random next points for all lines
    for (var k = 0; k < numLines; k++) {
      linesYSpots[k].shift();
      linesYSpots[k].push(nextLineYSpot[k]);
      nextLineYSpot[k] = getRandomYPositionPotentiallyWithMouseBonus(0, numDots-1);
    }
  }

  if(doDrawYLabels || doDrawHorizontalGridLines) {
    fill(textColor);
    for (var i = 0; i < numDots; i++) {
      if(i > 0 && i % floor(numDots/10) == 0) {
        var currentYPos = i * interDotDistanceY + 0.5 * interDotDistanceY;
        text(""+i, width -15, currentYPos);
        stroke(horizontalLinesColor);
        line(0, currentYPos, width, currentYPos);
      }
    }
	}

  // draw maxDrawLineX
  if(doDrawMaxDrawXLine) {
    stroke(color(0, 255, 0));
    line(maxDrawLineX, 0, maxDrawLineX, height);
  }
  stroke(color(0));

	// draw dots
	fill(color(255, 0, 0));
  if (doDrawPotentialTargetPoints) {
	  for (var j = 0; j < dotPositions.length; j++) {
		  ellipse(dotPositions[j][0], dotPositions[j][1], 5);
	  }
  }

  // advance Y spots

  // draw lines
  //  - find the current X positions of all points
  var currentPointXPositions = [];
  stroke(verticalLinesColor);
  for (var l = 0; l <= numPositionsPerDot; l++) {
    currentPointXPositions[l] = dotPositions[l][0];
    if(doDrawXLabels) {
      fill(textColor);
      text(" "+l, currentPointXPositions[l], 10);
    }
    // draw vertical lines
    if(doDrawVerticalGridLines) {
      line(currentPointXPositions[l], 0, currentPointXPositions[l], height);
    }
  }
  stroke(color(0));
  // - sort by X values
  sortWithIndeces(currentPointXPositions);
  // - draw lines between first numPositionsToDrawForLine points

  // draw sum
  if(doDrawSumAtVerticalLineBottom) {
    fill(textColor);
    var numToSkipAtEnd = numPositionsPerDot - numPositionsToDrawForLine;
    var sumOfSums = 0;
    for(var m=0; m < currentPointXPositions.sortIndices.length; m++) {
      var idx=currentPointXPositions.sortIndices[m];
      var sumAtLinePosition = 0;
      for(var n=0; n < numLines; n++) {
        sumAtLinePosition += linesYSpots[n][idx];
      }
      var labelToPlot = " "+sumAtLinePosition;
      if(isNaN(sumAtLinePosition)) {
        sumAtLinePosition = 0;
        labelToPlot = "";
      }
      text(labelToPlot, currentPointXPositions[idx], height - 5);
      sumOfSums += sumAtLinePosition;
    }
    fill(textColor);
    text("m="+round(sumOfSums/numLines), width - 35, height - 5);
  }

// draw red marker in upper right corner to show user that mouse tracking is active
if(doUseMouseAttraction && mouseRoughlyWithFrame()) {
    fill(color(255, 0, 0));
    ellipse(width -20, 20, 10);
    stroke(color(255, 0, 0));
    line(0, mouseY, width, mouseY);
}

  for (var k = 0; k < numLines; k++) {
    var thisLineYSpots = linesYSpots[k]; // these are only the indices, i.e., row numbers
    //print("Line " + k + " last y spots: " + thisLineYSpots.join(",") + ". Next=" + nextLineYSpot[k]);

    var thisLineYPositionsOfSpots = []; // y coordinates
    for (var l = 0; l < numPositionsToDrawForLine; l++) {
      thisLineYPositionsOfSpots[l] = thisLineYSpots[l] * interDotDistanceY + 0.5 * interDotDistanceY;
    }
    //print("Line " + k + " y positions: " + thisLineYPositionsOfSpots.join(","));

    var positionShare = currentPointXPositions[0] / width;     // make alpha depend on screen position (less tansparent the more it gets to the right)
    var colorNoAlpha = color(someColors[k]);
    var newAlpha = 100 + positionShare * 155;
    var colorAlpha = color(red(colorNoAlpha), green(colorNoAlpha), blue(colorNoAlpha), newAlpha);
    stroke(colorAlpha);

    strokeWeight(4);
    line(0, thisLineYPositionsOfSpots[0], currentPointXPositions[0], thisLineYPositionsOfSpots[0]);
    for (var l = 0; l <= numPositionsToDrawForLine; l++) {
      positionShare = currentPointXPositions[l] / width;     // make alpha depend on screen position (less tansparent the more it gets to the right)
      colorNoAlpha = color(someColors[k]);
      newAlpha = 100 + positionShare * 155;
      colorAlpha = color(red(colorNoAlpha), green(colorNoAlpha), blue(colorNoAlpha), newAlpha);
      stroke(colorAlpha);

      line(currentPointXPositions[l], thisLineYPositionsOfSpots[l], currentPointXPositions[l+1], thisLineYPositionsOfSpots[l+1]);
    }
    // Draw line towards next point: compute y position at x position maxDrawLineX
    var nextY = nextLineYSpot[k] * interDotDistanceY + 0.5 * interDotDistanceY;
    var lastY = thisLineYPositionsOfSpots[numPositionsToDrawForLine-1];
    var lastX = currentPointXPositions[numPositionsToDrawForLine-1];
    var nextX = currentPointXPositions[numPositionsToDrawForLine];
    var yDiff = nextY - lastY;
    var xDiff = nextX - lastX;
    var ascent = yDiff / xDiff;
    var xDiffToMaxDrawLineX = maxDrawLineX - lastX;
    var yAtmaxDrawLineX = lastY + (ascent * xDiffToMaxDrawLineX);
    line(currentPointXPositions[numPositionsToDrawForLine-1], lastY, maxDrawLineX, yAtmaxDrawLineX);
    strokeWeight(1);
    //print("Line " + k + ": ascent=" + ascent +", nextY=" + nextY +", lastY=" +lastY + ". lastX=" + lastX + " ,nextX=" + nextX + ". xDiff=" + xDiff + " (interDotPositionDistanceX=" + interDotPositionDistanceX + "), yDiff=" + yDiff);

    if(doDrawCurrentPoints) {
      fill(color(255,127,80));    // draw current dot in orange (stroke with current line's color)
      ellipse(lastX, lastY, 10);
    }
    if(doDrawTargetPoints) {
      fill(color(255, 255, 0));   // draw next target in yellow (stroke with current line's color)
      ellipse(nextX, nextY, 10);
    }
  }
}

// function sortWithIndeces by Dave Aaron Smith, see https://stackoverflow.com/questions/3730510/javascript-sort-array-and-return-an-array-of-indicies-that-indicates-the-positi
function sortWithIndeces(toSort) {
  for (var i = 0; i < toSort.length; i++) {
    toSort[i] = [toSort[i], i];
  }
  toSort.sort(function(left, right) {
    return left[0] < right[0] ? -1 : 1;
  });
  toSort.sortIndices = [];
  for (var j = 0; j < toSort.length; j++) {
    toSort.sortIndices.push(toSort[j][1]);
    toSort[j] = toSort[j][0];
  }
  return toSort;
}

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function mouseRoughlyWithFrame() {
  var border = 10;
  return mouseX > border && mouseX < (width - border) && mouseY > border && mouseY < (height - border);
}

function getRandomYPositionPotentiallyWithMouseBonus(min, max) {
  if(doUseMouseAttraction && mouseRoughlyWithFrame()) {
    // find y spot closest to mouse position
    var minDistToMouse = Number.POSITIVE_INFINITY;
    var minDistIndex = 0;
    for(var p=0; p < numDots; p++) {
      var currentYPosDot = p * interDotDistanceY + 0.5 * interDotDistanceY;
      if(abs(currentYPosDot - mouseY) < minDistToMouse) {
        minDistToMouse = abs(currentYPosDot - mouseY);
        minDistIndex = p;
        minDistToTrackedMouseYPos = currentYPosDot;
      }
    }
    // with a certain probability, draw the line closest to the mouse
    if(getRandomInt(1, 10) < 4) {
      return minDistIndex;
    }
  }
  return getRandomInt(min, max);
}
<commit_msg>add author info<commit_after>// Liveplot.js -- a p5js scene by Tim Schäfer. Just for fun.
// See http://rcmd.org/ts or http://github.com/dfsp-spirit/
// This is free software, published under the GPLv3 license. No warranties.

// +++ Settings +++
var numDots = 50;   // number of rows of the field
var numPositionsPerDot = 20;      // number of target positions per line, or columns (vertical lines)
var numLines = 15;           // number of the moving, colorful plot lines. If you want many, you may need to add more colors to the 'someColors' var below.
var numPositionsToDrawForLine = 18;

var doDrawPotentialTargetPoints = false;
var doDrawTargetPoints = false;
var doDrawCurrentPoints = false;
var doDrawVerticalGridLines = true;
var doDrawHorizontalGridLines = true;
var doDrawMaxDrawXLine = false;
var doDrawXLabels = true;
var doDrawYLabels = true;
var doDrawSumAtVerticalLineBottom = true;

var doUseMouseAttraction = true;

var backGroundColor = 80;
var verticalLinesColor = 160;
var horizontalLinesColor = 160;
var useTextSize = 8;
var textColor = 255;
// +++ End of settings ++++

var minDistToTrackedMouseYPos;
var dotPositions = [];
var linesYSpots = [];
var maxXDrawLine;
var interDotDistanceY;
var interDotPositionDistanceX;
var nextLineYSpot;
var someColors = ['#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#ffffff', '#000000' ];

function setup() {
	frameRate(30);
  createCanvas(600, 400);
  textSize(useTextSize);
	interDotDistanceY = (height/numDots);
  interDotPositionDistanceX = (width/numPositionsPerDot);

	for (var i = 0; i < numDots; i++) {
    var currentYPos = i * interDotDistanceY + 0.5 * interDotDistanceY;
    for (var j = 0; j <= numPositionsPerDot; j++) {
      var currentXPos = j * interDotPositionDistanceX + 0.5 * interDotPositionDistanceX;
		    dotPositions.push([currentXPos, currentYPos]);
    }
	}

  maxDrawLineX = dotPositions[numPositionsToDrawForLine-2][0] + 0.5 * interDotPositionDistanceX;

  nextLineYSpot = [];
  for (var k = 0; k < numLines; k++) {
    var currentLineYSpots = [];
    for (var l = 0; l < numPositionsToDrawForLine; l++) {
      currentLineYSpots.push(getRandomInt(0, numDots-1));
    }
    linesYSpots.push(currentLineYSpots);
    var nextRandomYSpot = getRandomYPositionPotentiallyWithMouseBonus(0, numDots-1);
    nextLineYSpot.push(nextRandomYSpot);
  }
}

function draw() {
  background(backGroundColor);
	// move Dots
  var jumpThisFrame = false;
	for (var i = 0; i < dotPositions.length; i++) {
    dotPositions[i][0] -= 2;
		if(dotPositions[i][0] < -interDotPositionDistanceX) {  // reset if it left screen
      var howMuchSmaller = -interDotPositionDistanceX -dotPositions[i][0];
		  dotPositions[i][0] = width - howMuchSmaller;
      jumpThisFrame = true;
		}
	}

  if(jumpThisFrame) {
    // draw new random next points for all lines
    for (var k = 0; k < numLines; k++) {
      linesYSpots[k].shift();
      linesYSpots[k].push(nextLineYSpot[k]);
      nextLineYSpot[k] = getRandomYPositionPotentiallyWithMouseBonus(0, numDots-1);
    }
  }

  if(doDrawYLabels || doDrawHorizontalGridLines) {
    fill(textColor);
    for (var i = 0; i < numDots; i++) {
      if(i > 0 && i % floor(numDots/10) == 0) {
        var currentYPos = i * interDotDistanceY + 0.5 * interDotDistanceY;
        text(""+i, width -15, currentYPos);
        stroke(horizontalLinesColor);
        line(0, currentYPos, width, currentYPos);
      }
    }
	}

  // draw maxDrawLineX
  if(doDrawMaxDrawXLine) {
    stroke(color(0, 255, 0));
    line(maxDrawLineX, 0, maxDrawLineX, height);
  }
  stroke(color(0));

	// draw dots
	fill(color(255, 0, 0));
  if (doDrawPotentialTargetPoints) {
	  for (var j = 0; j < dotPositions.length; j++) {
		  ellipse(dotPositions[j][0], dotPositions[j][1], 5);
	  }
  }

  // advance Y spots

  // draw lines
  //  - find the current X positions of all points
  var currentPointXPositions = [];
  stroke(verticalLinesColor);
  for (var l = 0; l <= numPositionsPerDot; l++) {
    currentPointXPositions[l] = dotPositions[l][0];
    if(doDrawXLabels) {
      fill(textColor);
      text(" "+l, currentPointXPositions[l], 10);
    }
    // draw vertical lines
    if(doDrawVerticalGridLines) {
      line(currentPointXPositions[l], 0, currentPointXPositions[l], height);
    }
  }
  stroke(color(0));
  // - sort by X values
  sortWithIndeces(currentPointXPositions);
  // - draw lines between first numPositionsToDrawForLine points

  // draw sum
  if(doDrawSumAtVerticalLineBottom) {
    fill(textColor);
    var numToSkipAtEnd = numPositionsPerDot - numPositionsToDrawForLine;
    var sumOfSums = 0;
    for(var m=0; m < currentPointXPositions.sortIndices.length; m++) {
      var idx=currentPointXPositions.sortIndices[m];
      var sumAtLinePosition = 0;
      for(var n=0; n < numLines; n++) {
        sumAtLinePosition += linesYSpots[n][idx];
      }
      var labelToPlot = " "+sumAtLinePosition;
      if(isNaN(sumAtLinePosition)) {
        sumAtLinePosition = 0;
        labelToPlot = "";
      }
      text(labelToPlot, currentPointXPositions[idx], height - 5);
      sumOfSums += sumAtLinePosition;
    }
    fill(textColor);
    text("m="+round(sumOfSums/numLines), width - 35, height - 5);
  }

// draw red marker in upper right corner to show user that mouse tracking is active
if(doUseMouseAttraction && mouseRoughlyWithFrame()) {
    fill(color(255, 0, 0));
    ellipse(width -20, 20, 10);
    stroke(color(255, 0, 0));
    line(0, mouseY, width, mouseY);
}

  for (var k = 0; k < numLines; k++) {
    var thisLineYSpots = linesYSpots[k]; // these are only the indices, i.e., row numbers
    //print("Line " + k + " last y spots: " + thisLineYSpots.join(",") + ". Next=" + nextLineYSpot[k]);

    var thisLineYPositionsOfSpots = []; // y coordinates
    for (var l = 0; l < numPositionsToDrawForLine; l++) {
      thisLineYPositionsOfSpots[l] = thisLineYSpots[l] * interDotDistanceY + 0.5 * interDotDistanceY;
    }
    //print("Line " + k + " y positions: " + thisLineYPositionsOfSpots.join(","));

    var positionShare = currentPointXPositions[0] / width;     // make alpha depend on screen position (less tansparent the more it gets to the right)
    var colorNoAlpha = color(someColors[k]);
    var newAlpha = 100 + positionShare * 155;
    var colorAlpha = color(red(colorNoAlpha), green(colorNoAlpha), blue(colorNoAlpha), newAlpha);
    stroke(colorAlpha);

    strokeWeight(4);
    line(0, thisLineYPositionsOfSpots[0], currentPointXPositions[0], thisLineYPositionsOfSpots[0]);
    for (var l = 0; l <= numPositionsToDrawForLine; l++) {
      positionShare = currentPointXPositions[l] / width;     // make alpha depend on screen position (less tansparent the more it gets to the right)
      colorNoAlpha = color(someColors[k]);
      newAlpha = 100 + positionShare * 155;
      colorAlpha = color(red(colorNoAlpha), green(colorNoAlpha), blue(colorNoAlpha), newAlpha);
      stroke(colorAlpha);

      line(currentPointXPositions[l], thisLineYPositionsOfSpots[l], currentPointXPositions[l+1], thisLineYPositionsOfSpots[l+1]);
    }
    // Draw line towards next point: compute y position at x position maxDrawLineX
    var nextY = nextLineYSpot[k] * interDotDistanceY + 0.5 * interDotDistanceY;
    var lastY = thisLineYPositionsOfSpots[numPositionsToDrawForLine-1];
    var lastX = currentPointXPositions[numPositionsToDrawForLine-1];
    var nextX = currentPointXPositions[numPositionsToDrawForLine];
    var yDiff = nextY - lastY;
    var xDiff = nextX - lastX;
    var ascent = yDiff / xDiff;
    var xDiffToMaxDrawLineX = maxDrawLineX - lastX;
    var yAtmaxDrawLineX = lastY + (ascent * xDiffToMaxDrawLineX);
    line(currentPointXPositions[numPositionsToDrawForLine-1], lastY, maxDrawLineX, yAtmaxDrawLineX);
    strokeWeight(1);
    //print("Line " + k + ": ascent=" + ascent +", nextY=" + nextY +", lastY=" +lastY + ". lastX=" + lastX + " ,nextX=" + nextX + ". xDiff=" + xDiff + " (interDotPositionDistanceX=" + interDotPositionDistanceX + "), yDiff=" + yDiff);

    if(doDrawCurrentPoints) {
      fill(color(255,127,80));    // draw current dot in orange (stroke with current line's color)
      ellipse(lastX, lastY, 10);
    }
    if(doDrawTargetPoints) {
      fill(color(255, 255, 0));   // draw next target in yellow (stroke with current line's color)
      ellipse(nextX, nextY, 10);
    }
  }
}

// function sortWithIndeces by Dave Aaron Smith, see https://stackoverflow.com/questions/3730510/javascript-sort-array-and-return-an-array-of-indicies-that-indicates-the-positi
function sortWithIndeces(toSort) {
  for (var i = 0; i < toSort.length; i++) {
    toSort[i] = [toSort[i], i];
  }
  toSort.sort(function(left, right) {
    return left[0] < right[0] ? -1 : 1;
  });
  toSort.sortIndices = [];
  for (var j = 0; j < toSort.length; j++) {
    toSort.sortIndices.push(toSort[j][1]);
    toSort[j] = toSort[j][0];
  }
  return toSort;
}

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function mouseRoughlyWithFrame() {
  var border = 10;
  return mouseX > border && mouseX < (width - border) && mouseY > border && mouseY < (height - border);
}

function getRandomYPositionPotentiallyWithMouseBonus(min, max) {
  if(doUseMouseAttraction && mouseRoughlyWithFrame()) {
    // find y spot closest to mouse position
    var minDistToMouse = Number.POSITIVE_INFINITY;
    var minDistIndex = 0;
    for(var p=0; p < numDots; p++) {
      var currentYPosDot = p * interDotDistanceY + 0.5 * interDotDistanceY;
      if(abs(currentYPosDot - mouseY) < minDistToMouse) {
        minDistToMouse = abs(currentYPosDot - mouseY);
        minDistIndex = p;
        minDistToTrackedMouseYPos = currentYPosDot;
      }
    }
    // with a certain probability, draw the line closest to the mouse
    if(getRandomInt(1, 10) < 4) {
      return minDistIndex;
    }
  }
  return getRandomInt(min, max);
}
<|endoftext|>


<commit_before>'use strict'
/* eslint-disable no-unused-vars */
var getOwnPropertySymbols = Object.getOwnPropertySymbols
var hasOwnProperty = Object.prototype.hasOwnProperty
var propIsEnumerable = Object.prototype.propertyIsEnumerable

function toObject(val) {
  if (val === null || val === undefined) {
    throw new TypeError('Object.assign cannot be called with null or undefined')
  }

  return Object(val)
}

export function assign(target, source) {
  var from
  var to = toObject(target)
  var symbols

  for (var s = 1; s < arguments.length; s++) {
    from = Object(arguments[s])

    for (var key in from) {
      if (hasOwnProperty.call(from, key)) {
        to[key] = from[key]
      }
    }

    if (getOwnPropertySymbols) {
      symbols = getOwnPropertySymbols(from)
      for (var i = 0; i < symbols.length; i++) {
        if (propIsEnumerable.call(from, symbols[i])) {
          to[symbols[i]] = from[symbols[i]]
        }
      }
    }
  }

  return to
}

!window.addEventListener &&
  (function(
    WindowPrototype,
    DocumentPrototype,
    ElementPrototype,
    addEventListener,
    removeEventListener,
    dispatchEvent,
    registry
  ) {
    WindowPrototype[addEventListener] = DocumentPrototype[addEventListener] = ElementPrototype[addEventListener] = function (type, listener) {
      var target = this

      registry.unshift([target, type, listener, function (event) {
          event.currentTarget = target
        event.preventDefault = function () { event.returnValue = false }
        event.stopPropagation = function () { event.cancelBubble = true }
          event.target = event.srcElement || target

          listener.call(target, event)
      }])

      this.attachEvent("on" + type, registry[0][3])
    };

    WindowPrototype[removeEventListener] = DocumentPrototype[removeEventListener] = ElementPrototype[removeEventListener] = function (type, listener) {
      for (var index = 0, register; register = registry[index]; ++index) {
        if (register[0] == this && register[1] == type && register[2] == listener) {
          return this.detachEvent("on" + type, registry.splice(index, 1)[0][3])
        }
      }
    };

    WindowPrototype[dispatchEvent] = DocumentPrototype[dispatchEvent] = ElementPrototype[dispatchEvent] = function (eventObject) {
      return this.fireEvent("on" + eventObject.type, eventObject)
    };
  })(
    Window.prototype,
    HTMLDocument.prototype,
    Element.prototype,
    'addEventListener',
    'removeEventListener',
    'dispatchEvent',
    []
  )

if (typeof Object.create !== 'function') {
  Object.create = function(proto, propertiesObject) {
    if (typeof proto !== 'object' && typeof proto !== 'function') {
      throw new TypeError('Object prototype may only be an Object: ' + proto)
    } else if (proto === null) {
      throw new Error(
        "This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument."
    }

    // if (typeof propertiesObject != 'undefined') {
    //     throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument.");
    // }

    function F() {}
    F.prototype = proto

    return new F()
  }
}

/**
 *  Copy all properties from `props` onto `obj`.
 *  @param {Object} obj		Object onto which properties should be copied.
 *  @param {Object} props	Object from which to copy properties.
 *  @returns obj
 *  @private
 */
export function extend(obj, props) {
  for (let i in props) obj[i] = props[i]
  return obj
}

/**
 * Call a function asynchronously, as soon as possible. Makes
 * use of HTML Promise to schedule the callback if available,
 * otherwise falling back to `setTimeout` (mainly for IE<11).
 *
 * @param {Function} callback
 */

let usePromise = typeof Promise == 'function'

// for native
if (
  typeof document !== 'object' &&
  typeof global !== 'undefined' &&
  global.__config__
) {
  if (global.__config__.platform === 'android') {
    usePromise = true
  } else {
    let systemVersion =
      (global.__config__.systemVersion &&
        global.__config__.systemVersion.split('.')[0]) ||
      0
    if (systemVersion > 8) {
      usePromise = true
    }
  }
}

export const defer = usePromise
  ? Promise.resolve().then.bind(Promise.resolve())
  : setTimeout

export function isArray(obj) {
  return Object.prototype.toString.call(obj) === '[object Array]'
}

export function nProps(props) {
  if (!props || isArray(props)) return {}
  const result = {}
  Object.keys(props).forEach(key => {
    result[key] = props[key].value
  })
  return result
}
<commit_msg>omio - fix lint error<commit_after>'use strict'
/* eslint-disable no-unused-vars */
var getOwnPropertySymbols = Object.getOwnPropertySymbols
var hasOwnProperty = Object.prototype.hasOwnProperty
var propIsEnumerable = Object.prototype.propertyIsEnumerable

function toObject(val) {
  if (val === null || val === undefined) {
    throw new TypeError('Object.assign cannot be called with null or undefined')
  }

  return Object(val)
}

export function assign(target, source) {
  var from
  var to = toObject(target)
  var symbols

  for (var s = 1; s < arguments.length; s++) {
    from = Object(arguments[s])

    for (var key in from) {
      if (hasOwnProperty.call(from, key)) {
        to[key] = from[key]
      }
    }

    if (getOwnPropertySymbols) {
      symbols = getOwnPropertySymbols(from)
      for (var i = 0; i < symbols.length; i++) {
        if (propIsEnumerable.call(from, symbols[i])) {
          to[symbols[i]] = from[symbols[i]]
        }
      }
    }
  }

  return to
}

!window.addEventListener &&
  (function(
    WindowPrototype,
    DocumentPrototype,
    ElementPrototype,
    addEventListener,
    removeEventListener,
    dispatchEvent,
    registry
  ) {
    WindowPrototype[addEventListener] = DocumentPrototype[addEventListener] = ElementPrototype[addEventListener] = function (type, listener) {
      var target = this

      registry.unshift([target, type, listener, function (event) {
          event.currentTarget = target
        event.preventDefault = function () { event.returnValue = false }
        event.stopPropagation = function () { event.cancelBubble = true }
          event.target = event.srcElement || target

          listener.call(target, event)
      }])

      this.attachEvent("on" + type, registry[0][3])
    };

    WindowPrototype[removeEventListener] = DocumentPrototype[removeEventListener] = ElementPrototype[removeEventListener] = function (type, listener) {
      for (var index = 0, register; register = registry[index]; ++index) {
        if (register[0] == this && register[1] == type && register[2] == listener) {
          return this.detachEvent("on" + type, registry.splice(index, 1)[0][3])
        }
      }
    };

    WindowPrototype[dispatchEvent] = DocumentPrototype[dispatchEvent] = ElementPrototype[dispatchEvent] = function (eventObject) {
      return this.fireEvent("on" + eventObject.type, eventObject)
    };
  })(
    Window.prototype,
    HTMLDocument.prototype,
    Element.prototype,
    'addEventListener',
    'removeEventListener',
    'dispatchEvent',
    []
  )

if (typeof Object.create !== 'function') {
  Object.create = function(proto, propertiesObject) {
    if (typeof proto !== 'object' && typeof proto !== 'function') {
      throw new TypeError('Object prototype may only be an Object: ' + proto)
    } else if (proto === null) {
      throw new Error(
        "This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument."
      )
    }

    // if (typeof propertiesObject != 'undefined') {
    //     throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument.");
    // }

    function F() {}
    F.prototype = proto

    return new F()
  }
}

/**
 *  Copy all properties from `props` onto `obj`.
 *  @param {Object} obj		Object onto which properties should be copied.
 *  @param {Object} props	Object from which to copy properties.
 *  @returns obj
 *  @private
 */
export function extend(obj, props) {
  for (let i in props) obj[i] = props[i]
  return obj
}

/**
 * Call a function asynchronously, as soon as possible. Makes
 * use of HTML Promise to schedule the callback if available,
 * otherwise falling back to `setTimeout` (mainly for IE<11).
 *
 * @param {Function} callback
 */

let usePromise = typeof Promise == 'function'

// for native
if (
  typeof document !== 'object' &&
  typeof global !== 'undefined' &&
  global.__config__
) {
  if (global.__config__.platform === 'android') {
    usePromise = true
  } else {
    let systemVersion =
      (global.__config__.systemVersion &&
        global.__config__.systemVersion.split('.')[0]) ||
      0
    if (systemVersion > 8) {
      usePromise = true
    }
  }
}

export const defer = usePromise
  ? Promise.resolve().then.bind(Promise.resolve())
  : setTimeout

export function isArray(obj) {
  return Object.prototype.toString.call(obj) === '[object Array]'
}

export function nProps(props) {
  if (!props || isArray(props)) return {}
  const result = {}
  Object.keys(props).forEach(key => {
    result[key] = props[key].value
  })
  return result
}
<|endoftext|>


<commit_before>// ==UserScript==
// @name         Recursive Twitter Block
// @namespace    https://enopoletus.github.io
// @version      0.16
// @description  Blocks everyone who liked a certain tweet
// @author       E. Harding
// @include      https://twitter.com/*
// @updateURL    https://enopoletus.github.io/recursiveblock.user.js
// @grant        none
// @run-at       document-start
// ==/UserScript==

const timeso=[];
timeso.push(0);
const timeso2=[];
timeso2.push(0);
const truefals=[];
window.addEventListener("load", buttoncreate);
window.addEventListener("load", buttonsactivate);
setInterval(buttoncreate, 1200);
setInterval(buttonsactivate, 1201);
function buttoncreate(){
  const blockbuttons=document.getElementsByClassName("block-link js-actionBlock");
  for (let i=0; i<blockbuttons.length; i++){
    if (blockbuttons[i].parentElement.getElementsByClassName("blockAllLikers").length == 0){
      const blockbutton=blockbuttons[i];
      const newnode=document.createElement("li");
      newnode.setAttribute("class", "blockAllLikers");
      newnode.setAttribute("role", "presentation");
      const newblockb=document.createElement("button");
      newblockb.setAttribute("type", "button");
      newblockb.setAttribute("class", "dropdown-link");
      const nbbtxt=document.createTextNode("Block all liking Tweet");
      newblockb.appendChild(nbbtxt);
      newnode.append(newblockb);
      blockbutton.after(newnode);
    };
  };
};
function buttonsactivate(){
  const buttons=document.getElementsByClassName("blockAllLikers")
  for (let i=0; i<buttons.length; i++){
    const tweetidproto=buttons[i].parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement;
    const tweetid=tweetidproto.getAttribute("data-tweet-id");
    buttons[i].addEventListener("click", function(){dowork(tweetid)})
  };
};
function dowork(tweetid){
  const ourtime = new Date().getTime();
  if (ourtime>(timeso2.slice(-1)[0])+700 || truefals.slice(-1)[0] == "t"){
    const thetext=[];
    const currentuser=document.getElementById("current-user-id").value;
    const xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function() {
      if (xmlhttp.readyState == XMLHttpRequest.DONE) {
      if (xmlhttp.status == 200) {
        const usernums=xmlhttp.responseText.match(/data-item-id=\\.*?\\/g).map(el => el.replace(/\D/g,"")).slice(1).filter(function(e) {return e !== currentuser});
        thetext.push(usernums);
        processing(thetext[0], tweetid);
      }
      else if (xmlhttp.status == 400) {console.log('There was an error 400');}
      else {console.log('something else other than 200 was returned');};
      };
    };
  xmlhttp.open("GET", "https://twitter.com/i/activity/favorited_popup?id=".concat(tweetid), true);
  xmlhttp.send();
  timeso2.push(ourtime);
  }
}
function processing(thetext0, tweetid){
  const autok= document.getElementsByName("authenticity_token")[0].value;
  for (let i=0; i<thetext0.length; i++){
    const xmlhttp = new XMLHttpRequest();
    xmlhttp.open("POST", "https://twitter.com/i/user/block", true);
    xmlhttp.setRequestHeader("accept", "application/json, text/javascript, */*; q=0.01");
    xmlhttp.setRequestHeader("x-requested-with", "XMLHttpRequest");
    xmlhttp.setRequestHeader("x-twitter-active-user", "yes");
    xmlhttp.setRequestHeader("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
    const formdat= "authenticity_token="+autok+"&challenges_passed=false&handles_challenges=1&user_id="+thetext0[i];
    xmlhttp.send(formdat);
  }
  const ourtime = new Date().getTime();
  if(thetext0.length==0){truefals.push("f")}else{truefals.push("t")};
  if(thetext0.length>0 && ourtime>(timeso.slice(-1)[0])+200){dowork(tweetid); timeso.push(ourtime);};
};
<commit_msg>no more need for autok<commit_after>// ==UserScript==
// @name         Recursive Twitter Block
// @namespace    https://enopoletus.github.io
// @version      0.17
// @description  Blocks everyone who liked a certain tweet
// @author       E. Harding
// @include      https://twitter.com/*
// @updateURL    https://enopoletus.github.io/recursiveblock.user.js
// @grant        none
// @run-at       document-start
// ==/UserScript==

const timeso=[];
timeso.push(0);
const timeso2=[];
timeso2.push(0);
const truefals=[];
window.addEventListener("load", buttoncreate);
window.addEventListener("load", buttonsactivate);
setInterval(buttoncreate, 1200);
setInterval(buttonsactivate, 1201);
function buttoncreate(){
  const blockbuttons=document.getElementsByClassName("block-link js-actionBlock");
  for (let i=0; i<blockbuttons.length; i++){
    if (blockbuttons[i].parentElement.getElementsByClassName("blockAllLikers").length == 0){
      const blockbutton=blockbuttons[i];
      const newnode=document.createElement("li");
      newnode.setAttribute("class", "blockAllLikers");
      newnode.setAttribute("role", "presentation");
      const newblockb=document.createElement("button");
      newblockb.setAttribute("type", "button");
      newblockb.setAttribute("class", "dropdown-link");
      const nbbtxt=document.createTextNode("Block all liking Tweet");
      newblockb.appendChild(nbbtxt);
      newnode.append(newblockb);
      blockbutton.after(newnode);
    };
  };
};
function buttonsactivate(){
  const buttons=document.getElementsByClassName("blockAllLikers")
  for (let i=0; i<buttons.length; i++){
    const tweetidproto=buttons[i].parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement;
    const tweetid=tweetidproto.getAttribute("data-tweet-id");
    buttons[i].addEventListener("click", function(){dowork(tweetid)})
  };
};
function dowork(tweetid){
  const ourtime = new Date().getTime();
  if (ourtime>(timeso2.slice(-1)[0])+700 || truefals.slice(-1)[0] == "t"){
    const thetext=[];
    const currentuser=document.getElementById("current-user-id").value;
    const xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function() {
      if (xmlhttp.readyState == XMLHttpRequest.DONE) {
      if (xmlhttp.status == 200) {
        const usernums=xmlhttp.responseText.match(/data-item-id=\\.*?\\/g).map(el => el.replace(/\D/g,"")).slice(1).filter(function(e) {return e !== currentuser});
        thetext.push(usernums);
        processing(thetext[0], tweetid);
      }
      else if (xmlhttp.status == 400) {console.log('There was an error 400');}
      else {console.log('something else other than 200 was returned');};
      };
    };
  xmlhttp.open("GET", "https://twitter.com/i/activity/favorited_popup?id=".concat(tweetid), true);
  xmlhttp.send();
  timeso2.push(ourtime);
  }
}
function processing(thetext0, tweetid){
  for (let i=0; i<thetext0.length; i++){
    const xmlhttp = new XMLHttpRequest();
    xmlhttp.open("POST", "https://twitter.com/i/user/block", true);
    xmlhttp.setRequestHeader("accept", "application/json, text/javascript, */*; q=0.01");
    xmlhttp.setRequestHeader("x-requested-with", "XMLHttpRequest");
    xmlhttp.setRequestHeader("x-twitter-active-user", "yes");
    xmlhttp.setRequestHeader("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
    const formdat= "authenticity_token=&challenges_passed=false&handles_challenges=1&user_id="+thetext0[i];
    xmlhttp.send(formdat);
  }
  const ourtime = new Date().getTime();
  if(thetext0.length==0){truefals.push("f")}else{truefals.push("t")};
  if(thetext0.length>0 && ourtime>(timeso.slice(-1)[0])+200){dowork(tweetid); timeso.push(ourtime);};
};
<|endoftext|>


<commit_before>import _ from 'lodash';
import moment from 'moment';
import Promise from 'bluebird';
import debugLib from 'debug';
import models, { sequelize, Op } from '../server/models';
import emailLib from '../server/lib/email';
import config from 'config';
import { exportToPDF } from '../server/lib/utils';
import { getTransactions } from '../server/lib/transactions';
import { getHostedCollectives, getBackersStats, sumTransactions } from '../server/lib/hostlib';

const debug = debugLib('hostreport');

if (process.env.SKIP_PLATFORM_STATS) {
  console.log('Skipping computing platform stats');
}

const summary = {
  totalHosts: 0,
  totalActiveHosts: 0,
  totalCollectives: 0,
  totalActiveCollectives: 0,
  numberTransactions: 0,
  numberDonations: 0,
  numberPaidExpenses: 0,
  hosts: [],
};

async function HostReport(year, month, hostId) {
  let startDate, endDate;

  const d = new Date();
  d.setFullYear(year);
  let yearlyReport = false;
  if (typeof month === 'number') {
    d.setMonth(month);
    startDate = new Date(d.getFullYear(), d.getMonth(), 1);
    endDate = new Date(d.getFullYear(), d.getMonth() + 1, 1);
  } else {
    // yearly report
    yearlyReport = true;
    startDate = new Date(d.getFullYear(), 0, 1);
    endDate = new Date(d.getFullYear() + 1, 0, 1);
  }

  const endDateIncluded = moment(endDate)
    .subtract(1, 'days')
    .toDate();

  const dateRange = {
    createdAt: { [Op.gte]: startDate, [Op.lt]: endDate },
  };

  const emailTemplate = yearlyReport ? 'host.yearlyreport' : 'host.monthlyreport';
  const reportName = yearlyReport ? `${year} Yearly Host Report` : `${year}/${month + 1} Monthly Host Report`;
  const dateFormat = yearlyReport ? 'YYYY' : 'YYYYMM';
  const csv_filename = `${moment(d).format(dateFormat)}-transactions.csv`;
  const pdf_filename = `${moment(d).format(dateFormat)}-expenses.pdf`;
  console.log('startDate', startDate, 'endDate', endDate);

  year = year || startDate.getFullYear();

  let previewCondition = '';
  if (process.env.DEBUG && process.env.DEBUG.match(/preview/))
    previewCondition = 'AND c.id IN (11004, 9804, 9802, 9801)'; // open source collective host, wwcode host, brusselstogether, changex
  // previewCondition = "AND c.id IN (9802)"; // brusselstogether

  if (hostId) {
    previewCondition = `AND c.id = ${hostId}`;
  }

  if (process.env.SLUGS) {
    const slugs = process.env.SLUGS.split(',');
    previewCondition = `AND c.slug IN ('${slugs.join("','")}')`;
    process.env.SKIP_PLATFORM_STATS = true;
  }

  const getHostStats = (host, collectiveids) => {
    const where = { CollectiveId: { [Op.in]: collectiveids } };
    const whereWithDateRange = { ...where, ...dateRange };

    const payoutProcessorFeesQuery = service => {
      // When using Sequelize query.include[].where it performs the condition on the join and it doesn't work
      // that's why have to do the query this this way.
      if (service === 'paypal') {
        where['$PaymentMethod.service$'] = 'paypal';
      } else {
        where['$PaymentMethod.service$'] = {
          [Op.or]: { [Op.ne]: 'paypal', [Op.is]: null },
        };
      }

      return {
        where: { ...where, ...dateRange, type: 'DEBIT' },
        raw: true,
        include: [
          {
            model: models.PaymentMethod,
            attributes: [[sequelize.fn('MAX', sequelize.col('service')), 'service']],
          },
        ],
      };
    };

    return Promise.props({
      balance: sumTransactions(
        'netAmountInCollectiveCurrency',
        { where: { ...where, createdAt: { [Op.lt]: endDate } } },
        host.currency,
      ), // total host balance
      delta: sumTransactions('netAmountInCollectiveCurrency', { where: whereWithDateRange }, host.currency), // delta host balance last month
      totalAmountDonations: sumTransactions(
        'amount',
        { where: { ...whereWithDateRange, type: 'CREDIT' } },
        host.currency,
      ), // total donations last month
      totalNetAmountReceivedForCollectives: sumTransactions(
        'netAmountInCollectiveCurrency',
        { where: { ...whereWithDateRange, type: 'CREDIT' } },
        host.currency,
      ), // total net amount received last month
      totalAmountPaidExpenses: sumTransactions(
        'netAmountInCollectiveCurrency',
        { where: { ...whereWithDateRange, type: 'DEBIT' } },
        host.currency,
      ), // total net amount paid out last month
      totalTaxAmountCollected: sumTransactions(
        'taxAmount',
        { where: whereWithDateRange, type: 'CREDIT' },
        host.currency,
      ), // total tax collected
      totalTaxAmountPaid: sumTransactions('taxAmount', { where: whereWithDateRange, type: 'DEBIT' }, host.currency), // total tax paid
      // We only count HostFees on CREDIT transactions with an Order and on DEBIT transactions with an Expense
      // (because the host fee of the destination host is recorded in the DEBIT transaction when making a donation to another host)
      totalHostFees: sumTransactions(
        'hostFeeInHostCurrency',
        {
          where: {
            ...whereWithDateRange,
            [Op.or]: [{ type: 'CREDIT', OrderId: { [Op.ne]: null } }, { type: 'DEBIT', ExpenseId: { [Op.ne]: null } }],
          },
        },
        host.currency,
      ),
      backers: getBackersStats(startDate, endDate, collectiveids),
      platformFees: sumTransactions('platformFeeInHostCurrency', { where: whereWithDateRange }, host.currency),
      paymentProcessorFees: sumTransactions(
        'paymentProcessorFeeInHostCurrency',
        { where: { ...whereWithDateRange, type: 'CREDIT' } },
        host.currency,
      ), // total stripe fees
      payoutProcessorFeesPaypal: sumTransactions(
        'paymentProcessorFeeInHostCurrency',
        payoutProcessorFeesQuery('paypal'),
        host.currency,
      ), // total paypal fees
      payoutProcessorFeesOther: sumTransactions(
        'paymentProcessorFeeInHostCurrency',
        payoutProcessorFeesQuery('other'),
        host.currency,
      ), // total other payout processor fees (manually recorded)
    });
  };

  const processHost = host => {
    summary.totalHosts++;
    console.log('>>> Processing host', host.slug);
    const data = {},
      attachments = [];
    const note = 'using fxrate of the day of the transaction as provided by the ECB. Your effective fxrate may vary.';
    const expensesPerPage = 30; // number of expenses per page of the Table Of Content (for PDF export)

    let collectivesById = {};
    let page = 1;
    let currentPage = 0;

    data.host = host;
    data.collective = host;
    data.reportDate = endDate;
    data.month = !yearlyReport && moment(startDate).format('MMMM');
    data.year = year;
    data.startDate = startDate;
    data.endDate = endDate;
    data.endDateIncluded = endDateIncluded;
    data.config = _.pick(config, 'host');
    data.maxSlugSize = 0;
    data.notes = null;
    data.expensesPerPage = [[]];
    data.stats = {
      numberPaidExpenses: 0,
    };

    const getHostAdminsEmails = host => {
      if (host.type === 'USER') {
        return models.User.findAll({ where: { CollectiveId: host.id } }).map(u => u.email);
      }
      return models.Member.findAll({
        where: {
          CollectiveId: host.id,
          role: 'ADMIN',
        },
      }).map(
        admin => {
          return models.User.findOne({
            attributes: ['email'],
            where: { CollectiveId: admin.MemberCollectiveId },
          }).then(user => user.email);
        },
        { concurrency: 1 },
      );
    };

    const processTransaction = transaction => {
      const t = transaction;
      t.collective = collectivesById[t.CollectiveId].dataValues;
      t.collective.shortSlug = t.collective.slug.replace(/^wwcode-?(.)/, '$1');
      t.notes = t.Expense && t.Expense.privateMessage;
      if (t.data && t.data.fxrateSource) {
        t.notes = t.notes ? `${t.notes} (${note})` : note;
        data.notes = note;
      }

      // We prepare expenses for the PDF export
      if (t.type === 'DEBIT' && t.ExpenseId) {
        t.page = page++;
        data.stats.numberPaidExpenses++;
        if ((page - 1) % expensesPerPage === 0) {
          currentPage++;
          data.expensesPerPage[currentPage] = [];
        }
        data.expensesPerPage[currentPage].push(t);
      }

      data.maxSlugSize = Math.max(data.maxSlugSize, t.collective.shortSlug.length + 1);
      if (!t.description) {
        return transaction.getSource().then(source => {
          if (!source) {
            console.log('>>> no source found for ', transaction);
            return t;
          }
          t.description = source.description;
          return t;
        });
      } else {
        return Promise.resolve(t);
      }
    };

    return getHostedCollectives(host.id, endDate)
      .tap(collectives => {
        collectivesById = _.keyBy(collectives, 'id');
        data.stats.totalCollectives = collectives.filter(c => c.type === 'COLLECTIVE').length;
        summary.totalCollectives += data.stats.totalCollectives;
        console.log(`>>> processing ${data.stats.totalCollectives} collectives`);
      })
      .then(() =>
        getTransactions(Object.keys(collectivesById), startDate, endDate, {
          include: [{ model: models.Expense }, { model: models.User, as: 'createdByUser' }],
        }),
      )
      .tap(transactions => {
        if (!transactions || transactions.length == 0) {
          throw new Error('No transaction found');
        }
        console.log(`>>> processing ${transactions.length} transactions`);
      })
      .map(processTransaction)
      .tap(transactions => {
        const csv = models.Transaction.exportCSV(transactions, collectivesById);
        attachments.push({
          filename: csv_filename,
          content: csv,
        });
      })
      .then(transactions => (data.transactions = transactions))
      .then(() =>
        exportToPDF('expenses', data, {
          paper: host.currency === 'USD' ? 'Letter' : 'A4',
        }),
      )
      .then(pdf => {
        attachments.push({
          filename: pdf_filename,
          content: pdf,
        });
      })
      .then(() => getHostStats(host, Object.keys(collectivesById)))
      .then(stats => {
        stats.totalAmountPaid = {
          totalInHostCurrency:
            stats.totalAmountPaidExpenses.totalInHostCurrency +
            stats.paymentProcessorFees.totalInHostCurrency +
            stats.platformFees.totalInHostCurrency,
        };
        stats.totalAmountSpent = {
          totalInHostCurrency:
            stats.totalAmountPaidExpenses.totalInHostCurrency +
            stats.payoutProcessorFeesPaypal.totalInHostCurrency +
            stats.payoutProcessorFeesOther.totalInHostCurrency,
        };
        stats.totalHostRevenue = {
          totalInHostCurrency: stats.totalHostFees.totalInHostCurrency,
        };
        // Total net amount received on the host's bank account = total amount - payment processor fees and platform fees
        stats.totalNetAmountReceived = {
          totalInHostCurrency:
            stats.totalAmountDonations.totalInHostCurrency +
            stats.paymentProcessorFees.totalInHostCurrency +
            stats.platformFees.totalInHostCurrency,
        };

        data.stats = {
          ...data.stats,
          ...stats,
          totalActiveCollectives: Object.keys(_.keyBy(data.transactions, 'CollectiveId')).length,
          numberTransactions: data.transactions.length,
          numberDonations: data.transactions.length - data.stats.numberPaidExpenses,
        };
        summary.hosts.push({
          host: { name: host.name, slug: host.slug, currency: host.currency },
          stats: data.stats,
        });
        summary.totalActiveHosts++;
        summary.totalActiveCollectives += data.stats.totalActiveCollectives;
        summary.numberTransactions += data.stats.numberTransactions;
        summary.numberDonations += data.stats.numberDonations;
        summary.numberPaidExpenses += data.stats.numberPaidExpenses;
        summary.totalAmountPaidExpenses += data.stats.totalAmountPaidExpenses;
      })
      .then(() => getHostAdminsEmails(host))
      .then(admins => sendEmail(admins, data, attachments))
      .catch(e => {
        console.error(`Error in processing host ${host.slug}:`, e.message, e);
        debug(e);
      });
  };

  const sendEmail = (recipients, data, attachments) => {
    debug('Sending email to ', recipients);
    if (!recipients || recipients.length === 0) {
      console.error('Unable to send host report for ', data.host.slug, 'No recipient to send to');
      return;
    }
    // debug("email data transactions", data.transactions);
    debug('email data stats', data.stats);
    debug('email data stats.backers', data.stats.backers);
    const options = { attachments };
    return emailLib.send(emailTemplate, recipients, data, options);
  };

  const query = `
  with "hosts" as (SELECT DISTINCT "HostCollectiveId" AS id FROM "Collectives" WHERE "deletedAt" IS NULL AND "isActive" IS TRUE AND "HostCollectiveId" IS NOT NULL)
  SELECT c.* FROM "Collectives" c WHERE c.id IN (SELECT h.id FROM hosts h) ${previewCondition}
  `;

  const hosts = await sequelize.query(query, {
    model: models.Collective,
    type: sequelize.QueryTypes.SELECT,
  });
  console.log(`Preparing the ${reportName} for ${hosts.length} hosts`);

  return Promise.map(hosts, processHost, { concurrency: 1 }).then(() => {
    console.log('>>> All done. Exiting.');
    process.exit(0);
  });
}

export default HostReport;
<commit_msg>fix(hostReport): take into account host at the time of transaction<commit_after>import _ from 'lodash';
import moment from 'moment';
import Promise from 'bluebird';
import debugLib from 'debug';
import models, { sequelize, Op } from '../server/models';
import emailLib from '../server/lib/email';
import config from 'config';
import { exportToPDF } from '../server/lib/utils';
import { getTransactions } from '../server/lib/transactions';
import { getHostedCollectives, getBackersStats, sumTransactions } from '../server/lib/hostlib';

const debug = debugLib('hostreport');

if (process.env.SKIP_PLATFORM_STATS) {
  console.log('Skipping computing platform stats');
}

const summary = {
  totalHosts: 0,
  totalActiveHosts: 0,
  totalCollectives: 0,
  totalActiveCollectives: 0,
  numberTransactions: 0,
  numberDonations: 0,
  numberPaidExpenses: 0,
  hosts: [],
};

async function HostReport(year, month, hostId) {
  let startDate, endDate;

  const d = new Date();
  d.setFullYear(year);
  let yearlyReport = false;
  if (typeof month === 'number') {
    d.setMonth(month);
    startDate = new Date(d.getFullYear(), d.getMonth(), 1);
    endDate = new Date(d.getFullYear(), d.getMonth() + 1, 1);
  } else {
    // yearly report
    yearlyReport = true;
    startDate = new Date(d.getFullYear(), 0, 1);
    endDate = new Date(d.getFullYear() + 1, 0, 1);
  }

  const endDateIncluded = moment(endDate)
    .subtract(1, 'days')
    .toDate();

  const dateRange = {
    createdAt: { [Op.gte]: startDate, [Op.lt]: endDate },
  };

  const emailTemplate = yearlyReport ? 'host.yearlyreport' : 'host.monthlyreport';
  const reportName = yearlyReport ? `${year} Yearly Host Report` : `${year}/${month + 1} Monthly Host Report`;
  const dateFormat = yearlyReport ? 'YYYY' : 'YYYYMM';
  const csv_filename = `${moment(d).format(dateFormat)}-transactions.csv`;
  const pdf_filename = `${moment(d).format(dateFormat)}-expenses.pdf`;
  console.log('startDate', startDate, 'endDate', endDate);

  year = year || startDate.getFullYear();

  let previewCondition = '';
  if (process.env.DEBUG && process.env.DEBUG.match(/preview/))
    previewCondition = 'AND c.id IN (11004, 9804, 9802, 9801)'; // open source collective host, wwcode host, brusselstogether, changex
  // previewCondition = "AND c.id IN (9802)"; // brusselstogether

  if (hostId) {
    previewCondition = `AND c.id = ${hostId}`;
  }

  if (process.env.SLUGS) {
    const slugs = process.env.SLUGS.split(',');
    previewCondition = `AND c.slug IN ('${slugs.join("','")}')`;
    process.env.SKIP_PLATFORM_STATS = true;
  }

  const getHostStats = (host, collectiveids) => {
    // Since collectives can change host,
    // we don't fetch transactions based on the CollectiveId but based on the HostCollectiveId
    // at the time of the transaction
    const where = { HostCollectiveId: host.id };
    const whereWithDateRange = { ...where, ...dateRange };

    const payoutProcessorFeesQuery = service => {
      // When using Sequelize query.include[].where it performs the condition on the join and it doesn't work
      // that's why have to do the query this this way.
      if (service === 'paypal') {
        where['$PaymentMethod.service$'] = 'paypal';
      } else {
        where['$PaymentMethod.service$'] = {
          [Op.or]: { [Op.ne]: 'paypal', [Op.is]: null },
        };
      }

      return {
        where: { ...where, ...dateRange, type: 'DEBIT' },
        raw: true,
        include: [
          {
            model: models.PaymentMethod,
            attributes: [[sequelize.fn('MAX', sequelize.col('service')), 'service']],
          },
        ],
      };
    };

    return Promise.props({
      balance: sumTransactions(
        'netAmountInCollectiveCurrency',
        { where: { ...where, createdAt: { [Op.lt]: endDate } } },
        host.currency,
      ), // total host balance
      delta: sumTransactions('netAmountInCollectiveCurrency', { where: whereWithDateRange }, host.currency), // delta host balance last month
      totalAmountDonations: sumTransactions(
        'amount',
        { where: { ...whereWithDateRange, type: 'CREDIT' } },
        host.currency,
      ), // total donations last month
      totalNetAmountReceivedForCollectives: sumTransactions(
        'netAmountInCollectiveCurrency',
        { where: { ...whereWithDateRange, type: 'CREDIT' } },
        host.currency,
      ), // total net amount received last month
      totalAmountPaidExpenses: sumTransactions(
        'netAmountInCollectiveCurrency',
        { where: { ...whereWithDateRange, type: 'DEBIT' } },
        host.currency,
      ), // total net amount paid out last month
      totalTaxAmountCollected: sumTransactions(
        'taxAmount',
        { where: whereWithDateRange, type: 'CREDIT' },
        host.currency,
      ), // total tax collected
      totalTaxAmountPaid: sumTransactions('taxAmount', { where: whereWithDateRange, type: 'DEBIT' }, host.currency), // total tax paid
      // We only count HostFees on CREDIT transactions with an Order and on DEBIT transactions with an Expense
      // (because the host fee of the destination host is recorded in the DEBIT transaction when making a donation to another host)
      totalHostFees: sumTransactions(
        'hostFeeInHostCurrency',
        {
          where: {
            ...whereWithDateRange,
            [Op.or]: [{ type: 'CREDIT', OrderId: { [Op.ne]: null } }, { type: 'DEBIT', ExpenseId: { [Op.ne]: null } }],
          },
        },
        host.currency,
      ),
      backers: getBackersStats(startDate, endDate, collectiveids),
      platformFees: sumTransactions('platformFeeInHostCurrency', { where: whereWithDateRange }, host.currency),
      paymentProcessorFees: sumTransactions(
        'paymentProcessorFeeInHostCurrency',
        { where: { ...whereWithDateRange, type: 'CREDIT' } },
        host.currency,
      ), // total stripe fees
      payoutProcessorFeesPaypal: sumTransactions(
        'paymentProcessorFeeInHostCurrency',
        payoutProcessorFeesQuery('paypal'),
        host.currency,
      ), // total paypal fees
      payoutProcessorFeesOther: sumTransactions(
        'paymentProcessorFeeInHostCurrency',
        payoutProcessorFeesQuery('other'),
        host.currency,
      ), // total other payout processor fees (manually recorded)
    });
  };

  const processHost = host => {
    summary.totalHosts++;
    console.log('>>> Processing host', host.slug);
    const data = {},
      attachments = [];
    const note = 'using fxrate of the day of the transaction as provided by the ECB. Your effective fxrate may vary.';
    const expensesPerPage = 30; // number of expenses per page of the Table Of Content (for PDF export)

    let collectivesById = {};
    let page = 1;
    let currentPage = 0;

    data.host = host;
    data.collective = host;
    data.reportDate = endDate;
    data.month = !yearlyReport && moment(startDate).format('MMMM');
    data.year = year;
    data.startDate = startDate;
    data.endDate = endDate;
    data.endDateIncluded = endDateIncluded;
    data.config = _.pick(config, 'host');
    data.maxSlugSize = 0;
    data.notes = null;
    data.expensesPerPage = [[]];
    data.stats = {
      numberPaidExpenses: 0,
    };

    const getHostAdminsEmails = host => {
      if (host.type === 'USER') {
        return models.User.findAll({ where: { CollectiveId: host.id } }).map(u => u.email);
      }
      return models.Member.findAll({
        where: {
          CollectiveId: host.id,
          role: 'ADMIN',
        },
      }).map(
        admin => {
          return models.User.findOne({
            attributes: ['email'],
            where: { CollectiveId: admin.MemberCollectiveId },
          }).then(user => user.email);
        },
        { concurrency: 1 },
      );
    };

    const processTransaction = transaction => {
      const t = transaction;
      t.collective = collectivesById[t.CollectiveId].dataValues;
      t.collective.shortSlug = t.collective.slug.replace(/^wwcode-?(.)/, '$1');
      t.notes = t.Expense && t.Expense.privateMessage;
      if (t.data && t.data.fxrateSource) {
        t.notes = t.notes ? `${t.notes} (${note})` : note;
        data.notes = note;
      }

      // We prepare expenses for the PDF export
      if (t.type === 'DEBIT' && t.ExpenseId) {
        t.page = page++;
        data.stats.numberPaidExpenses++;
        if ((page - 1) % expensesPerPage === 0) {
          currentPage++;
          data.expensesPerPage[currentPage] = [];
        }
        data.expensesPerPage[currentPage].push(t);
      }

      data.maxSlugSize = Math.max(data.maxSlugSize, t.collective.shortSlug.length + 1);
      if (!t.description) {
        return transaction.getSource().then(source => {
          if (!source) {
            console.log('>>> no source found for ', transaction);
            return t;
          }
          t.description = source.description;
          return t;
        });
      } else {
        return Promise.resolve(t);
      }
    };

    return getHostedCollectives(host.id, endDate)
      .tap(collectives => {
        collectivesById = _.keyBy(collectives, 'id');
        data.stats.totalCollectives = collectives.filter(c => c.type === 'COLLECTIVE').length;
        summary.totalCollectives += data.stats.totalCollectives;
        console.log(`>>> processing ${data.stats.totalCollectives} collectives`);
      })
      .then(() =>
        getTransactions(Object.keys(collectivesById), startDate, endDate, {
          include: [{ model: models.Expense }, { model: models.User, as: 'createdByUser' }],
        }),
      )
      .tap(transactions => {
        if (!transactions || transactions.length == 0) {
          throw new Error('No transaction found');
        }
        console.log(`>>> processing ${transactions.length} transactions`);
      })
      .map(processTransaction)
      .tap(transactions => {
        const csv = models.Transaction.exportCSV(transactions, collectivesById);
        attachments.push({
          filename: csv_filename,
          content: csv,
        });
      })
      .then(transactions => (data.transactions = transactions))
      .then(() =>
        exportToPDF('expenses', data, {
          paper: host.currency === 'USD' ? 'Letter' : 'A4',
        }),
      )
      .then(pdf => {
        attachments.push({
          filename: pdf_filename,
          content: pdf,
        });
      })
      .then(() => getHostStats(host, Object.keys(collectivesById)))
      .then(stats => {
        stats.totalAmountPaid = {
          totalInHostCurrency:
            stats.totalAmountPaidExpenses.totalInHostCurrency +
            stats.paymentProcessorFees.totalInHostCurrency +
            stats.platformFees.totalInHostCurrency,
        };
        stats.totalAmountSpent = {
          totalInHostCurrency:
            stats.totalAmountPaidExpenses.totalInHostCurrency +
            stats.payoutProcessorFeesPaypal.totalInHostCurrency +
            stats.payoutProcessorFeesOther.totalInHostCurrency,
        };
        stats.totalHostRevenue = {
          totalInHostCurrency: stats.totalHostFees.totalInHostCurrency,
        };
        // Total net amount received on the host's bank account = total amount - payment processor fees and platform fees
        stats.totalNetAmountReceived = {
          totalInHostCurrency:
            stats.totalAmountDonations.totalInHostCurrency +
            stats.paymentProcessorFees.totalInHostCurrency +
            stats.platformFees.totalInHostCurrency,
        };

        data.stats = {
          ...data.stats,
          ...stats,
          totalActiveCollectives: Object.keys(_.keyBy(data.transactions, 'CollectiveId')).length,
          numberTransactions: data.transactions.length,
          numberDonations: data.transactions.length - data.stats.numberPaidExpenses,
        };
        summary.hosts.push({
          host: { name: host.name, slug: host.slug, currency: host.currency },
          stats: data.stats,
        });
        summary.totalActiveHosts++;
        summary.totalActiveCollectives += data.stats.totalActiveCollectives;
        summary.numberTransactions += data.stats.numberTransactions;
        summary.numberDonations += data.stats.numberDonations;
        summary.numberPaidExpenses += data.stats.numberPaidExpenses;
        summary.totalAmountPaidExpenses += data.stats.totalAmountPaidExpenses;
      })
      .then(() => getHostAdminsEmails(host))
      .then(admins => sendEmail(admins, data, attachments))
      .catch(e => {
        console.error(`Error in processing host ${host.slug}:`, e.message, e);
        debug(e);
      });
  };

  const sendEmail = (recipients, data, attachments) => {
    debug('Sending email to ', recipients);
    if (!recipients || recipients.length === 0) {
      console.error('Unable to send host report for ', data.host.slug, 'No recipient to send to');
      return;
    }
    // debug("email data transactions", data.transactions);
    debug('email data stats', data.stats);
    debug('email data stats.backers', data.stats.backers);
    const options = { attachments };
    return emailLib.send(emailTemplate, recipients, data, options);
  };

  const query = `
  with "hosts" as (SELECT DISTINCT "HostCollectiveId" AS id FROM "Collectives" WHERE "deletedAt" IS NULL AND "isActive" IS TRUE AND "HostCollectiveId" IS NOT NULL)
  SELECT c.* FROM "Collectives" c WHERE c.id IN (SELECT h.id FROM hosts h) ${previewCondition}
  `;

  const hosts = await sequelize.query(query, {
    model: models.Collective,
    type: sequelize.QueryTypes.SELECT,
  });
  console.log(`Preparing the ${reportName} for ${hosts.length} hosts`);

  return Promise.map(hosts, processHost, { concurrency: 1 }).then(() => {
    console.log('>>> All done. Exiting.');
    process.exit(0);
  });
}

export default HostReport;
<|endoftext|>


<commit_before>//
//  ____  _                           _
// / ___|| |_ __ _ _ __   ___  ___   (_)___
// \___ \| __/ _` | '_ \ / _ \/ __|  | / __|
//  ___) | || (_| | |_) |  __/\__ \_ | \__ \
// |____/ \__\__,_| .__/ \___||___(_)/ |___/
//              |_|              |__/
//
// (c) Hay Kranen < hay@bykr.org >
// Version 0.2 - Released under the terms of the MIT license
// < http://en.wikipedia.org/wiki/MIT_License >
//
// Stapes.js : http://projects.haykranen.nl/stapes

(function() {
    /** Utility functions
     *
     *  Note that these functions are only used inside Stapes, and therefore
     *  aren't that failsafe as the options in libraries
     *  such as Underscore.js, so that's why they're not usable outside
     *  the private scope.
     */
    var util = {
        bind : function(fn, ctx) {
            if (Function.prototype.bind) {
                // Native
                return fn.bind(ctx);
            } else {
                // Non-native
                return function() {
                    return fn.apply(ctx, arguments);
                };
            }
        },

        create : function(context) {
            if (typeof Object.create === "function") {
                // Native
                var instance = Object.create(context);
            } else {
                // Non-native
                var F = function(){};
                F.prototype = context;
                var instance = new F();
            }

            instance._guid = guid++;
            Stapes._attributes[instance._guid] = {};
            Stapes._eventHandlers[instance._guid] = {};

            return instance;
        },

        each : function(list, fn) {
            if (util.isArray(list)) {
                if (Array.prototype.forEach) {
                    // Native forEach
                    list.forEach( fn );
                } else {
                    for (var i = 0, l = list.length; i < l; i++) {
                        fn( list[i], i);
                    }
                }
            } else {
                for (var key in list) {
                    fn( list[key], key );
                }
            }
        },

        isArray : function(val) {
            return Object.prototype.toString.call( val ) === "[object Array]";
        },

        isObject : function(val) {
            return (typeof val === "object") && (!util.isArray(val) && val !== null);
        },

        // from http://stackoverflow.com/a/2117523/152809
        makeUuid : function() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
                return v.toString(16);
            });
        },

        toArray : function(val) {
            if (util.isObject(val)) {
                return util.values(val);
            } else {
                return Array.prototype.slice.call(val, 0);
            }
        },

        values : function(obj) {
            var values = [];

            util.each(obj, function(value, key) {
                values.push(value);
            });

            return values;
        }
    };

    /** Private helper functions */
    function addEvent(event) {
        // If we don't have any handlers for this type of event, add a new
        // array we can use to push new handlers
        if (!Stapes._eventHandlers[event.guid][event.type]) {
            Stapes._eventHandlers[event.guid][event.type] = [];
        }

        // Push an event object
        Stapes._eventHandlers[event.guid][event.type].push({
            "guid" : event.guid,
            "handler" : event.handler,
            "scope" : event.scope,
            "type" : event.type
        });
    }

    function addEventHandler(argTypeOrMap, argHandlerOrScope, argScope) {
        var eventMap = {},
            scope;

        if (typeof argTypeOrMap === "string") {
            scope = argScope || false;
            eventMap[ argTypeOrMap ] = argHandlerOrScope;
        } else {
            scope = argHandlerOrScope || false;
            eventMap = argTypeOrMap;
        }

        util.each(eventMap, util.bind(function(handler, eventString) {
            var events = eventString.split(" ");

            util.each(events, util.bind(function(eventType) {
                addEvent.call(this, {
                    "guid" : this._guid,
                    "handler" : handler,
                    "scope" : scope,
                    "type" : eventType
                });
            }, this));
        }, this));
    }

    function emitEvents(type, data, explicitType, explicitGuid) {
        explicitType = explicitType || false;
        explicitGuid = explicitGuid || this._guid;

        util.each(Stapes._eventHandlers[explicitGuid][type], util.bind(function(event) {
            var scope = (event.scope) ? event.scope : this;
            if (explicitType) {
                event.type = explicitType;
            }
            event.scope = scope;
            event.handler.call(event.scope, data, event);
        }, this));
    }

    function setAttribute(key, value) {
        // We need to do this before we actually add the item :)
        var itemExists = this.has(key);

        // Actually add the item to the attributes
        Stapes._attributes[this._guid][key] = value;

        // Throw a generic event
        this.emit('change', key);

        // And a namespaced event as well, NOTE that we pass value instead of
        // key here!
        this.emit('change:' + key, value);

        // Also throw a specific event for this type of set
        var specificEvent = itemExists ? 'update' : 'create';

        this.emit(specificEvent, key);

        // And a namespaced event as well, NOTE that we pass value instead of key
        this.emit(specificEvent + ':' + key, value);
    }

    var guid = 0;

    var Module = {
        create : function() {
            return util.create(this);
        },

        emit : function(types, data) {
            data = data || null;

            util.each(types.split(" "), util.bind(function(type) {
                // First 'all' type events: is there an 'all' handler in the
                // global stack?
                if (Stapes._eventHandlers[-1].all) {
                    emitEvents.call(this, "all", data, type, -1);
                }

                // Catch all events for this type?
                if (Stapes._eventHandlers[-1][type]) {
                    emitEvents.call(this, type, data, type, -1);
                }

                // 'all' event for this specific module?
                if (Stapes._eventHandlers[this._guid]["all"]) {
                    emitEvents.call(this, "all", data, type);
                }

                // Finally, normal events :)
                if (Stapes._eventHandlers[this._guid][type]) {
                    emitEvents.call(this, type, data);
                }
            }, this));
        },

        extend : function(objectOrValues, valuesIfObject) {
            var object = (valuesIfObject) ? objectOrValues : this,
                values = (valuesIfObject) ? valuesIfObject : objectOrValues;

            util.each(values, function(value, key) {
                object[key] = value;
            });

            return this;
        },

        filter : function(fn) {
            var items = [];

            util.each(Stapes._attributes[this._guid], function(item) {
                if (fn(item)) {
                    items.push(item);
                }
            });

            return items;
        },

        get : function(input) {
            if (typeof input === "string") {
                return this.has(input) ? Stapes._attributes[this._guid][input] : null;
            } else if (typeof input === "function") {
                var items = this.filter(input);
                if (items.length) {
                    return items.length[0];
                }
            }
        },

        getAll : function() {
            return Stapes._attributes[this._guid];
        },

        getAllAsArray : function() {
            var arr = [];

            util.each(Stapes._attributes[this._guid], function(value, key) {
                if (util.isObject(value)) {
                    value.id = key;
                }

                arr.push(value);
            });

            return arr;

        },

        has : function(key) {
            return (typeof Stapes._attributes[this._guid][key] !== "undefined");
        },

        init : function() {
            this.emit('ready');
            return this;
        },

        on : function() {
            addEventHandler.apply(this, arguments);
        },

        // Akin to set(), but makes a unique id
        push : function(input) {
            if (util.isArray(input)) {
                util.each(input, util.bind(function(value) {
                    setAttribute.call(this, util.makeUuid(), value);
                }, this));

                this.emit('changemany createmany', util.toArray(input).length);
            } else {
                setAttribute.call(this, util.makeUuid(), input);
            }
        },

        remove : function(input) {
            if (typeof input === "function") {
                util.each(Stapes._attributes[this._guid], util.bind(function(item, key) {
                    if (input(item)) {
                        delete Stapes._attributes[this._guid][key];
                        this.emit('remove change');
                    }
                }, this));
            } else {
                if (typeof input === "string") {
                    input = [input];
                }

                util.each(util.toArray(input), util.bind(function(id) {
                    if (this.has(id)) {
                        delete Stapes._attributes[this._guid][id];
                        this.emit('remove change');
                    }
                }, this));
            }
        },

        set : function(objOrKey, value) {
            if (util.isObject(objOrKey)) {
                util.each(objOrKey, util.bind(function(value, key) {
                    setAttribute.call(this, key, value);
                }, this));

                this.emit('changemany', objOrKey.length);
            } else {
                setAttribute.call(this, objOrKey, value);
            }
        },

        update : function(key, fn) {
            var item = this.get(key);
            setAttribute.call(this, key, fn( item ));
        }
    };

    var Stapes = {
        "_attributes" : {},

        "_eventHandlers" : {
            "-1" : {} // '-1' is used for the global event handling
        },

        "_guid" : -1,

        "create" : function() {
            return util.create(Module);
        },

        "extend" : function(obj) {
            util.each(obj, function(value, key) {
                Module[key] = value;
            });
        },

        "on" : function() {
            addEventHandler.apply(this, arguments);
        }
    };

    // This library can be used as an AMD module, a Node.js module, or an
    // old fashioned global
    if (typeof exports !== "undefined") {
        // Server
        if (typeof module !== "undefined" && module.exports) {
            exports = module.exports = initalizer;
        }
        exports.Stapes = Stapes;
    } else if (typeof define === "function" && define.amd) {
        // AMD
        define(function() {
            return Stapes;
        });
    } else {
        // Global scope
        window.Stapes = Stapes;
    }
})();<commit_msg>fixed bug with get() and node.js module<commit_after>//
//  ____  _                           _
// / ___|| |_ __ _ _ __   ___  ___   (_)___
// \___ \| __/ _` | '_ \ / _ \/ __|  | / __|
//  ___) | || (_| | |_) |  __/\__ \_ | \__ \
// |____/ \__\__,_| .__/ \___||___(_)/ |___/
//              |_|              |__/
//
// (c) Hay Kranen < hay@bykr.org >
// Version 0.2 - Released under the terms of the MIT license
// < http://en.wikipedia.org/wiki/MIT_License >
//
// Stapes.js : http://projects.haykranen.nl/stapes

(function() {
    /** Utility functions
     *
     *  Note that these functions are only used inside Stapes, and therefore
     *  aren't that failsafe as the options in libraries
     *  such as Underscore.js, so that's why they're not usable outside
     *  the private scope.
     */
    var util = {
        bind : function(fn, ctx) {
            if (Function.prototype.bind) {
                // Native
                return fn.bind(ctx);
            } else {
                // Non-native
                return function() {
                    return fn.apply(ctx, arguments);
                };
            }
        },

        create : function(context) {
            if (typeof Object.create === "function") {
                // Native
                var instance = Object.create(context);
            } else {
                // Non-native
                var F = function(){};
                F.prototype = context;
                var instance = new F();
            }

            instance._guid = guid++;
            Stapes._attributes[instance._guid] = {};
            Stapes._eventHandlers[instance._guid] = {};

            return instance;
        },

        each : function(list, fn) {
            if (util.isArray(list)) {
                if (Array.prototype.forEach) {
                    // Native forEach
                    list.forEach( fn );
                } else {
                    for (var i = 0, l = list.length; i < l; i++) {
                        fn( list[i], i);
                    }
                }
            } else {
                for (var key in list) {
                    fn( list[key], key );
                }
            }
        },

        isArray : function(val) {
            return Object.prototype.toString.call( val ) === "[object Array]";
        },

        isObject : function(val) {
            return (typeof val === "object") && (!util.isArray(val) && val !== null);
        },

        // from http://stackoverflow.com/a/2117523/152809
        makeUuid : function() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
                return v.toString(16);
            });
        },

        toArray : function(val) {
            if (util.isObject(val)) {
                return util.values(val);
            } else {
                return Array.prototype.slice.call(val, 0);
            }
        },

        values : function(obj) {
            var values = [];

            util.each(obj, function(value, key) {
                values.push(value);
            });

            return values;
        }
    };

    /** Private helper functions */
    function addEvent(event) {
        // If we don't have any handlers for this type of event, add a new
        // array we can use to push new handlers
        if (!Stapes._eventHandlers[event.guid][event.type]) {
            Stapes._eventHandlers[event.guid][event.type] = [];
        }

        // Push an event object
        Stapes._eventHandlers[event.guid][event.type].push({
            "guid" : event.guid,
            "handler" : event.handler,
            "scope" : event.scope,
            "type" : event.type
        });
    }

    function addEventHandler(argTypeOrMap, argHandlerOrScope, argScope) {
        var eventMap = {},
            scope;

        if (typeof argTypeOrMap === "string") {
            scope = argScope || false;
            eventMap[ argTypeOrMap ] = argHandlerOrScope;
        } else {
            scope = argHandlerOrScope || false;
            eventMap = argTypeOrMap;
        }

        util.each(eventMap, util.bind(function(handler, eventString) {
            var events = eventString.split(" ");

            util.each(events, util.bind(function(eventType) {
                addEvent.call(this, {
                    "guid" : this._guid,
                    "handler" : handler,
                    "scope" : scope,
                    "type" : eventType
                });
            }, this));
        }, this));
    }

    function emitEvents(type, data, explicitType, explicitGuid) {
        explicitType = explicitType || false;
        explicitGuid = explicitGuid || this._guid;

        util.each(Stapes._eventHandlers[explicitGuid][type], util.bind(function(event) {
            var scope = (event.scope) ? event.scope : this;
            if (explicitType) {
                event.type = explicitType;
            }
            event.scope = scope;
            event.handler.call(event.scope, data, event);
        }, this));
    }

    function setAttribute(key, value) {
        // We need to do this before we actually add the item :)
        var itemExists = this.has(key);

        // Actually add the item to the attributes
        Stapes._attributes[this._guid][key] = value;

        // Throw a generic event
        this.emit('change', key);

        // And a namespaced event as well, NOTE that we pass value instead of
        // key here!
        this.emit('change:' + key, value);

        // Also throw a specific event for this type of set
        var specificEvent = itemExists ? 'update' : 'create';

        this.emit(specificEvent, key);

        // And a namespaced event as well, NOTE that we pass value instead of key
        this.emit(specificEvent + ':' + key, value);
    }

    var guid = 0;

    var Module = {
        create : function() {
            return util.create(this);
        },

        emit : function(types, data) {
            data = data || null;

            util.each(types.split(" "), util.bind(function(type) {
                // First 'all' type events: is there an 'all' handler in the
                // global stack?
                if (Stapes._eventHandlers[-1].all) {
                    emitEvents.call(this, "all", data, type, -1);
                }

                // Catch all events for this type?
                if (Stapes._eventHandlers[-1][type]) {
                    emitEvents.call(this, type, data, type, -1);
                }

                // 'all' event for this specific module?
                if (Stapes._eventHandlers[this._guid]["all"]) {
                    emitEvents.call(this, "all", data, type);
                }

                // Finally, normal events :)
                if (Stapes._eventHandlers[this._guid][type]) {
                    emitEvents.call(this, type, data);
                }
            }, this));
        },

        extend : function(objectOrValues, valuesIfObject) {
            var object = (valuesIfObject) ? objectOrValues : this,
                values = (valuesIfObject) ? valuesIfObject : objectOrValues;

            util.each(values, function(value, key) {
                object[key] = value;
            });

            return this;
        },

        filter : function(fn) {
            var items = [];

            util.each(Stapes._attributes[this._guid], function(item) {
                if (fn(item)) {
                    items.push(item);
                }
            });

            return items;
        },

        get : function(input) {
            if (typeof input === "string") {
                return this.has(input) ? Stapes._attributes[this._guid][input] : null;
            } else if (typeof input === "function") {
                var items = this.filter(input);
                return (items.length) ? items.length[0] : false;
            }
        },

        getAll : function() {
            return Stapes._attributes[this._guid];
        },

        getAllAsArray : function() {
            var arr = [];

            util.each(Stapes._attributes[this._guid], function(value, key) {
                if (util.isObject(value)) {
                    value.id = key;
                }

                arr.push(value);
            });

            return arr;

        },

        has : function(key) {
            return (typeof Stapes._attributes[this._guid][key] !== "undefined");
        },

        init : function() {
            this.emit('ready');
            return this;
        },

        on : function() {
            addEventHandler.apply(this, arguments);
        },

        // Akin to set(), but makes a unique id
        push : function(input) {
            if (util.isArray(input)) {
                util.each(input, util.bind(function(value) {
                    setAttribute.call(this, util.makeUuid(), value);
                }, this));

                this.emit('changemany createmany', util.toArray(input).length);
            } else {
                setAttribute.call(this, util.makeUuid(), input);
            }
        },

        remove : function(input) {
            if (typeof input === "function") {
                util.each(Stapes._attributes[this._guid], util.bind(function(item, key) {
                    if (input(item)) {
                        delete Stapes._attributes[this._guid][key];
                        this.emit('remove change');
                    }
                }, this));
            } else {
                if (typeof input === "string") {
                    input = [input];
                }

                util.each(util.toArray(input), util.bind(function(id) {
                    if (this.has(id)) {
                        delete Stapes._attributes[this._guid][id];
                        this.emit('remove change');
                    }
                }, this));
            }
        },

        set : function(objOrKey, value) {
            if (util.isObject(objOrKey)) {
                util.each(objOrKey, util.bind(function(value, key) {
                    setAttribute.call(this, key, value);
                }, this));

                this.emit('changemany', objOrKey.length);
            } else {
                setAttribute.call(this, objOrKey, value);
            }
        },

        update : function(key, fn) {
            var item = this.get(key);
            setAttribute.call(this, key, fn( item ));
        }
    };

    var Stapes = {
        "_attributes" : {},

        "_eventHandlers" : {
            "-1" : {} // '-1' is used for the global event handling
        },

        "_guid" : -1,

        "create" : function() {
            return util.create(Module);
        },

        "extend" : function(obj) {
            util.each(obj, function(value, key) {
                Module[key] = value;
            });
        },

        "on" : function() {
            addEventHandler.apply(this, arguments);
        }
    };

    // This library can be used as an AMD module, a Node.js module, or an
    // old fashioned global
    if (typeof exports !== "undefined") {
        // Server
        if (typeof module !== "undefined" && module.exports) {
            exports = module.exports = Stapes;
        }
        exports.Stapes = Stapes;
    } else if (typeof define === "function" && define.amd) {
        // AMD
        define(function() {
            return Stapes;
        });
    } else {
        // Global scope
        window.Stapes = Stapes;
    }
})();<|endoftext|>