<commit_before>L.LatLngBounds.prototype.toOverpassBBoxString = function (){

	var a = this._southWest,
	b = this._northEast;

	return [a.lat, a.lng, b.lat, b.lng].join(',');
};

L.OverPassLayer = L.FeatureGroup.extend({

	options: {

		'minZoom': 15,
		'requestPerTile': true,
		'endPoint': 'http://overpass-api.de/api/',
		'query': '(node({{bbox}})[organic];node({{bbox}})[second_hand];);out qt;',
		'timeout': 30 * 1000, // Milliseconds
		'retryOnTimeout': false,
		'noInitialRequest': false,

		beforeRequest: function() {

		},

		afterRequest: function() {

		},

		onSuccess: function(data) {

			for(var i = 0; i < data.elements.length; i++) {

				var pos, popup, circle,
				e = data.elements[i];

				if (e.id in this.instance._ids) return;

				this.instance._ids[e.id] = true;

				if (e.type == 'node') {

					pos = new L.LatLng(e.lat, e.lon);
				} else {

					pos = new L.LatLng(e.center.lat, e.center.lon);
				}

				popup = this.instance._poiInfo(e.tags, e.id);
				circle = L.circle(pos, 50, {

					'color': 'green',
					'fillColor': '#3f0',
					'fillOpacity': 0.5,
				})
				.bindPopup(popup);

				this.instance.addLayer(circle);
			}
		},

		onError: function() {

		},

		onTimeout: function() {

		},

		minZoomIndicatorOptions: {

			'position': 'bottomleft',
			'minZoomMessageNoLayer': 'no layer assigned',
			'minZoomMessage': 'current Zoom-Level: CURRENTZOOM all data at Level: MINZOOMLEVEL',
		},
	},

	initialize: function (options) {

		L.Util.setOptions(this, options);

		this._layers = {};
		this._ids = {};
		this._requested = {};
	},

	_poiInfo: function(tags, id) {

		var row,
		link = document.createElement('a'),
		table = document.createElement('table'),
		div = document.createElement('div');

		link.href = 'http://www.openstreetmap.org/edit?editor=id&node=' + id;
		link.appendChild(document.createTextNode("Edit this entry in iD"));

		for (var key in tags){

			row = table.insertRow(0);
			row.insertCell(0).appendChild(document.createTextNode(key));
			row.insertCell(1).appendChild(document.createTextNode(tags[key]));
		}

		div.appendChild(link);
		div.appendChild(table);

		return div;
	},

	/**
	* splits the current view in uniform bboxes to allow caching
	*/
	long2tile: function (lon, zoom) {

		return ( Math.floor((lon + 180) / 360 * Math.pow(2, zoom)) );
	},

	lat2tile: function (lat, zoom)	{

		return ( Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI/180)) / Math.PI) / 2 * Math.pow(2, zoom)) );
	},

	tile2long: function (x, z) {

		return ( x / Math.pow(2, z) * 360 - 180 );
	},

	tile2lat: function (y, z) {

		var n = Math.PI - 2 * Math.PI * y / Math.pow(2, z);

		return ( 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))) );
	},

	_view2BBoxes: function(l, b, r, t) {

		var top, right, bottom, left,
		requestZoomLevel= 14,
		lidx = this.long2tile(l, requestZoomLevel),
		ridx = this.long2tile(r, requestZoomLevel),
		tidx = this.lat2tile(t, requestZoomLevel),
		bidx = this.lat2tile(b, requestZoomLevel),
		result = [];

		for (var x = lidx; x <= ridx; x++) {

			for (var y = tidx; y <= bidx; y++) {

				left = Math.round(this.tile2long(x, requestZoomLevel) * 1000000) / 1000000;
				right = Math.round(this.tile2long(x + 1, requestZoomLevel) * 1000000) / 1000000;
				top = Math.round(this.tile2lat(y, requestZoomLevel) * 1000000) / 1000000;
				bottom = Math.round(this.tile2lat(y + 1, requestZoomLevel) * 1000000) / 1000000;

				result.push(

					new L.LatLngBounds(new L.LatLng(bottom, left),
					new L.LatLng(top, right))
				);
			}
		}

		return result;
	},

	onMoveEnd: function () {

		if (this._map.getZoom() >= this.options.minZoom) {

			var x, y, bbox, bboxList, request, url, queryWithMapCoordinates,
			self = this,
			countdown = 0,
			beforeRequest = true,
			done = function () {

				if (--countdown === 0) {

					self.options.afterRequest.call(self);
				}
			};

			if (this.options.requestPerTile) {

				bboxList = this._view2BBoxes(
					this._map.getBounds()._southWest.lng,
					this._map.getBounds()._southWest.lat,
					this._map.getBounds()._northEast.lng,
					this._map.getBounds()._northEast.lat
				);
			} else {

				bboxList = new Array(this._map.getBounds());
			}

			countdown = bboxList.length;

			for (var i = 0; i < bboxList.length; i++) {

				bbox = bboxList[i];
				x = bbox._southWest.lng;
				y = bbox._northEast.lat;

				if ((x in this._requested) && (y in this._requested[x]) && (this._requested[x][y] === true)) {

					countdown--;
					continue;
				}

				if (!(x in this._requested)) {

					this._requested[x] = {};
				}

				this._requested[x][y] = true;

				queryWithMapCoordinates = this.options.query.replace(/(\{\{bbox\}\})/g, bbox.toOverpassBBoxString());
				url = this.options.endPoint + 'interpreter?data=[out:json];'+ queryWithMapCoordinates;

				if (beforeRequest) {

					var beforeRequestResult = this.options.beforeRequest.call(this);

					if ( beforeRequestResult === false ) {

						this.options.afterRequest.call(this);

						return;
					}

					beforeRequest = false;
				}

				this.sendRequest(url, done);
			}
		}
	},

	sendRequest: function(url, done) {

		var self = this,
		reference = { 'instance': this };

		request = new XMLHttpRequest();
		request.open('GET', url, true);
		request.timeout = this.options.timeout;

		request.ontimeout = function () {

			self.options.onTimeout.call(reference, this);

			if ( self.options.retryOnTimeout ) {

				self.sendRequest( url, done );
			}
			else {

				done();
			}
		};

		request.onload = function () {

			if (this.status >= 200 && this.status < 400) {

				self.options.onSuccess.call(reference, JSON.parse(this.response));
			}
			else {

				self.options.onError.call(reference, this);
			}

			done();
		};

		request.send();
	},

	onAdd: function (map) {

		this._map = map;

		if (map.zoomIndicator) {

			this._zoomControl = map.zoomIndicator;
			this._zoomControl._addLayer(this);
		} else {

			this._zoomControl = new L.Control.MinZoomIndicator(this.options.minZoomIndicatorOptions);

			map.addControl(this._zoomControl);

			this._zoomControl._addLayer(this);
		}

		if ( !this.options.noInitialRequest ) {

			this.onMoveEnd();
		}

		if (this.options.query.indexOf('({{bbox}})') !== -1) {

			map.on('moveend', this.onMoveEnd, this);
		}
	},

	onRemove: function (map) {

		L.LayerGroup.prototype.onRemove.call(this, map);

		this._ids = {};
		this._requested = {};
		this._zoomControl._removeLayer(this);

		map.off({

			'moveend': this.onMoveEnd
		}, this);

		this._map = null;
	},

	getData: function () {

		return this._data;
	},
});
<commit_msg>Fix on the default callback<commit_after>L.LatLngBounds.prototype.toOverpassBBoxString = function (){

	var a = this._southWest,
	b = this._northEast;

	return [a.lat, a.lng, b.lat, b.lng].join(',');
};

L.OverPassLayer = L.FeatureGroup.extend({

	options: {

		'minZoom': 15,
		'requestPerTile': true,
		'endPoint': 'http://overpass-api.de/api/',
		'query': '(node({{bbox}})[organic];node({{bbox}})[second_hand];);out qt;',
		'timeout': 30 * 1000, // Milliseconds
		'retryOnTimeout': false,
		'noInitialRequest': false,

		beforeRequest: function() {

		},

		afterRequest: function() {

		},

		onSuccess: function(data) {

			for(var i = 0; i < data.elements.length; i++) {

				var pos, popup, circle,
				e = data.elements[i];

				if (e.id in this.instance._ids) continue;

				this.instance._ids[e.id] = true;

				if (e.type == 'node') {

					pos = new L.LatLng(e.lat, e.lon);
				} else {

					pos = new L.LatLng(e.center.lat, e.center.lon);
				}

				popup = this.instance._poiInfo(e.tags, e.id);
				circle = L.circle(pos, 50, {

					'color': 'green',
					'fillColor': '#3f0',
					'fillOpacity': 0.5,
				})
				.bindPopup(popup);

				this.instance.addLayer(circle);
			}
		},

		onError: function() {

		},

		onTimeout: function() {

		},

		minZoomIndicatorOptions: {

			'position': 'bottomleft',
			'minZoomMessageNoLayer': 'no layer assigned',
			'minZoomMessage': 'current Zoom-Level: CURRENTZOOM all data at Level: MINZOOMLEVEL',
		},
	},

	initialize: function (options) {

		L.Util.setOptions(this, options);

		this._layers = {};
		this._ids = {};
		this._requested = {};
	},

	_poiInfo: function(tags, id) {

		var row,
		link = document.createElement('a'),
		table = document.createElement('table'),
		div = document.createElement('div');

		link.href = 'http://www.openstreetmap.org/edit?editor=id&node=' + id;
		link.appendChild(document.createTextNode("Edit this entry in iD"));

		for (var key in tags){

			row = table.insertRow(0);
			row.insertCell(0).appendChild(document.createTextNode(key));
			row.insertCell(1).appendChild(document.createTextNode(tags[key]));
		}

		div.appendChild(link);
		div.appendChild(table);

		return div;
	},

	/**
	* splits the current view in uniform bboxes to allow caching
	*/
	long2tile: function (lon, zoom) {

		return ( Math.floor((lon + 180) / 360 * Math.pow(2, zoom)) );
	},

	lat2tile: function (lat, zoom)	{

		return ( Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI/180)) / Math.PI) / 2 * Math.pow(2, zoom)) );
	},

	tile2long: function (x, z) {

		return ( x / Math.pow(2, z) * 360 - 180 );
	},

	tile2lat: function (y, z) {

		var n = Math.PI - 2 * Math.PI * y / Math.pow(2, z);

		return ( 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))) );
	},

	_view2BBoxes: function(l, b, r, t) {

		var top, right, bottom, left,
		requestZoomLevel= 14,
		lidx = this.long2tile(l, requestZoomLevel),
		ridx = this.long2tile(r, requestZoomLevel),
		tidx = this.lat2tile(t, requestZoomLevel),
		bidx = this.lat2tile(b, requestZoomLevel),
		result = [];

		for (var x = lidx; x <= ridx; x++) {

			for (var y = tidx; y <= bidx; y++) {

				left = Math.round(this.tile2long(x, requestZoomLevel) * 1000000) / 1000000;
				right = Math.round(this.tile2long(x + 1, requestZoomLevel) * 1000000) / 1000000;
				top = Math.round(this.tile2lat(y, requestZoomLevel) * 1000000) / 1000000;
				bottom = Math.round(this.tile2lat(y + 1, requestZoomLevel) * 1000000) / 1000000;

				result.push(

					new L.LatLngBounds(new L.LatLng(bottom, left),
					new L.LatLng(top, right))
				);
			}
		}

		return result;
	},

	onMoveEnd: function () {

		if (this._map.getZoom() >= this.options.minZoom) {

			var x, y, bbox, bboxList, request, url, queryWithMapCoordinates,
			self = this,
			countdown = 0,
			beforeRequest = true,
			done = function () {

				if (--countdown === 0) {

					self.options.afterRequest.call(self);
				}
			};

			if (this.options.requestPerTile) {

				bboxList = this._view2BBoxes(
					this._map.getBounds()._southWest.lng,
					this._map.getBounds()._southWest.lat,
					this._map.getBounds()._northEast.lng,
					this._map.getBounds()._northEast.lat
				);
			} else {

				bboxList = new Array(this._map.getBounds());
			}

			countdown = bboxList.length;

			for (var i = 0; i < bboxList.length; i++) {

				bbox = bboxList[i];
				x = bbox._southWest.lng;
				y = bbox._northEast.lat;

				if ((x in this._requested) && (y in this._requested[x]) && (this._requested[x][y] === true)) {

					countdown--;
					continue;
				}

				if (!(x in this._requested)) {

					this._requested[x] = {};
				}

				this._requested[x][y] = true;

				queryWithMapCoordinates = this.options.query.replace(/(\{\{bbox\}\})/g, bbox.toOverpassBBoxString());
				url = this.options.endPoint + 'interpreter?data=[out:json];'+ queryWithMapCoordinates;

				if (beforeRequest) {

					var beforeRequestResult = this.options.beforeRequest.call(this);

					if ( beforeRequestResult === false ) {

						this.options.afterRequest.call(this);

						return;
					}

					beforeRequest = false;
				}

				this.sendRequest(url, done);
			}
		}
	},

	sendRequest: function(url, done) {

		var self = this,
		reference = { 'instance': this };

		request = new XMLHttpRequest();
		request.open('GET', url, true);
		request.timeout = this.options.timeout;

		request.ontimeout = function () {

			self.options.onTimeout.call(reference, this);

			if ( self.options.retryOnTimeout ) {

				self.sendRequest( url, done );
			}
			else {

				done();
			}
		};

		request.onload = function () {

			if (this.status >= 200 && this.status < 400) {

				self.options.onSuccess.call(reference, JSON.parse(this.response));
			}
			else {

				self.options.onError.call(reference, this);
			}

			done();
		};

		request.send();
	},

	onAdd: function (map) {

		this._map = map;

		if (map.zoomIndicator) {

			this._zoomControl = map.zoomIndicator;
			this._zoomControl._addLayer(this);
		} else {

			this._zoomControl = new L.Control.MinZoomIndicator(this.options.minZoomIndicatorOptions);

			map.addControl(this._zoomControl);

			this._zoomControl._addLayer(this);
		}

		if ( !this.options.noInitialRequest ) {

			this.onMoveEnd();
		}

		if (this.options.query.indexOf('({{bbox}})') !== -1) {

			map.on('moveend', this.onMoveEnd, this);
		}
	},

	onRemove: function (map) {

		L.LayerGroup.prototype.onRemove.call(this, map);

		this._ids = {};
		this._requested = {};
		this._zoomControl._removeLayer(this);

		map.off({

			'moveend': this.onMoveEnd
		}, this);

		this._map = null;
	},

	getData: function () {

		return this._data;
	},
});
<|endoftext|>


<commit_before>const Transport = require('winston-transport'),
      fetch     = require('node-fetch'),
      util      = require('util'),
      https     = require('https'),
      url       = require('url')

const defaultOptions = {
                          name:         'SlackTransport',
                          level:        'info',
                          iconEmoji:    ':wave:',
                          unfurlLinks:  false
                        }

module.exports.SlackTransport = class SlackTransport extends Transport {

  constructor(opts) {

    if(!opts.webhook){
      throw new Error("webhook is required")
    }

    const options = Object.assign({},defaultOptions,opts)

    super(options)
    this.options = options

  }

  async log(info, callback){
    let slackResponse,
        color,
        payload = {
                  text:       `*${info.level}*`,
                  channel:     this.options.channel,
                  username:    this.options.username,
                  icon_emoji:  this.options.iconEmoji,
                  unfurlLinks: this.options.unfurlLinks,
                  attachments: []
                }

    // set the appropriate color (courtesy of https://github.com/motomux/winston-slack-webhook)
    switch (info.level) {
      case 'error':
        color = "danger"
        break
      case 'warn':
        color = "warning"
        break
      default:
        color = "good"
    }

    // get all the info and make it act like an attachment
    Object.keys(info).forEach(key => {
      payload.attachments.push({
              fallback: util.inspect(info[key]),
              text: `${key}: ${util.inspect(info[key])}`,
              color: color,
          })
    })

    const body = JSON.stringify(payload),
          url = this.options.webhook,
          props = {
                    method:'post',
                    body: body,
                    headers: {
                                'Accept': 'application/json',
                                'Content-Type': 'application/json',
                                'Content-Length': Buffer.byteLength(body)
                              }
                  }

    try{
      slackResponse = await fetch(url, props)
    }catch(err){
      callback(err)
      return
    }

    if(slackResponse.status === 200){
      callback(null, slackResponse)
    }else{
      callback(slackResponse.statusText)
    }

    setImmediate( () => {
      this.emit('logged', info)
    })
  }
}
<commit_msg>update formatting<commit_after>const Transport = require('winston-transport'),
      fetch     = require('node-fetch'),
      util      = require('util'),
      https     = require('https'),
      url       = require('url')

const defaultOptions = {
                          name:         'SlackTransport',
                          level:        'info',
                          iconEmoji:    ':wave:',
                          unfurlLinks:  false
                        }

module.exports.SlackTransport = class SlackTransport extends Transport {

  constructor(opts) {

    if(!opts.webhook){
      throw new Error("webhook is required")
    }

    const options = Object.assign({},defaultOptions,opts)

    super(options)
    this.options = options

  }

  async log(info, callback){
    let slackResponse,
        color,
        payload = {
                  text:       `*${info.level}*`,
                  channel:     this.options.channel,
                  username:    this.options.username,
                  icon_emoji:  this.options.iconEmoji,
                  unfurlLinks: this.options.unfurlLinks,
                  attachments: []
                }

    // set the appropriate color (courtesy of https://github.com/motomux/winston-slack-webhook)
    switch (info.level) {
      case 'error':
        color = "danger"
        break
      case 'warn':
        color = "warning"
        break
      default:
        color = "good"
    }

    // get all the info and make it act like an attachment
    Object.keys(info).forEach(key => {
      payload.attachments.push({
              fallback: util.inspect(info[key]),
              text: `*${key}*: ${util.inspect(info[key])}`,
              color: color,
          })
    })

    const body = JSON.stringify(payload),
          url = this.options.webhook,
          props = {
                    method:'post',
                    body: body,
                    headers: {
                                'Accept': 'application/json',
                                'Content-Type': 'application/json',
                                'Content-Length': Buffer.byteLength(body)
                              }
                  }

    try{
      slackResponse = await fetch(url, props)
    }catch(err){
      callback(err)
      return
    }

    if(slackResponse.status === 200){
      callback(null, slackResponse)
    }else{
      callback(slackResponse.statusText)
    }

    setImmediate( () => {
      this.emit('logged', info)
    })
  }
}
<|endoftext|>


<commit_before>describe("Click events", function() {
	it("no event handlers", function() {
		// Parse circle
		var circle = parseShapeData({"type": "circle","data": {"id": "circle_a","x": 1,"y": 10,"radius": 10,"stroke": {"r": 255,"g": 255,"b": 255,"a": 1 },"strokeWidth": 2,"fill": {"r":255,"g":0,"b":0,"a":1},"scaleX": 1.5}});
    expect(circle.eventListeners).toBeDefined();
    expect(circle.eventListeners.click).toBeUndefined();
	});
	it("creating mouseclick event handler", function() {
		// Parse circle
		var message = {"type": "circle", "data": {"id": "circle_nr_1", "x": 20, "y": 20, "radius": 5, "stroke": {"r": 255,"g": 255,"b": 255,"a": 1 }, "strokeWidth": 2, "fill": {"r":255,"g":0,"b":0,"a":1}}, "eventData": {"listen" : ["mousedown","mouseclick","mouseup","mousedoubleclick","mousedrag","mouseover", "mouseout"]}};
		var circle = parseShapeData(message);
		enableEventHandlers(circle, message);
    expect(circle.eventListeners).toBeDefined();
    expect(circle.eventListeners.click).toBeDefined();
    expect(circle.eventListeners.click[0]).toBeDefined();
    expect(circle.eventListeners.click[0].handler).toBeDefined();
	});
  it("testing handling of mouseclick event", function() {
    // Parse circle
    var message = {"type": "circle", "data": {"id": "circle_nr_1", "x": 20, "y": 20, "radius": 5, "stroke": {"r": 255,"g": 255,"b": 255,"a": 1 }, "strokeWidth": 2, "fill": {"r":255,"g":0,"b":0,"a":1}}, "eventData": {"listen" : ["mousedown","mouseclick","mouseup","mousedoubleclick","mousedrag","mouseover", "mouseout"]}};
    var circle = parseShapeData(message);
    enableEventHandlers(circle, message);

    var clickSpy = jasmine.createSpy();
    // Create spy for mouseEvent method
    spyOn(window, 'mouseEvent');

    // Test if mouseEvent method is called after
    // click event on circle but not before.
    expect(window.mouseEvent.callCount).toBe(0);
    circle.fire('click');
    expect(window.mouseEvent).toHaveBeenCalled();
  });
});

describe("Draw elements to test event handling", function() {
  var flag;
  var canvas;
  beforeEach(function(){
    // Setup canvas of Canvas.Hs
    canvas_wrapper = document.createElement("div");
    canvas_wrapper.id='canvas';
    $(document.body).append(canvas_wrapper);
    initCanvas($(canvas_wrapper), 900, 600); // Init canvas
    canvas = $(canvas_wrapper); // Get canvas element
    var layer = new Kinetic.Layer();
  });
    it("testing message after mouseclick event", function() {
      // Draw in the Canvas.hs canvas
      connectionDataReceived({"data":'{"shape":{"type": "circle", "data": {"x": 20, "y": 20, "radius": 5, "stroke": {"r": 255,"g": 255,"b": 255,"a": 1 }, "strokeWidth": 2, "fill": {"r":255,"g":0,"b":0,"a":1}}, "eventData": {"eventId": "circle_nr_2", "listen" : ["mousedown","mouseclick","mouseup","mousedoubleclick","mousedrag","mouseover", "mouseout"]}}}'});
      stage.batchDraw();
      stage.draw();

      var clickSpy = jasmine.createSpy();
      // Create spy for mouseEvent method
      spyOn(connection, 'send');
      // Test if mouseEvent method is called after
      // click event on circle but not before.
      expect(connection.send.callCount).toBe(0);
      stage.find('Circle')[0].fire('click');
      expect(connection.send).toHaveBeenCalledWith('{"event":"mouseclick","data":{"id":"circle_nr_2","x":null,"y":null}}');
    });
  afterEach(function(){
    // Remove the canvas elements used for testing and reset variables
    $(canvas_wrapper).remove();
    canvas = null;
    canvas_wrapper = null;
    // Reset variables of Canvas.hs
    var topLayerIdx = 0;
    var layerList = new Array();
    var stage = undefined;
    var connection = new WebSocket('ws://localhost:8080');
    var open = false;
  });
});<commit_msg>js_tests All mouse events apart from mousedrag have tests now<commit_after>describe("Click events", function() {
	it("no event handlers", function() {
		// Parse circle
		var circle = parseShapeData({"type": "circle","data": {"id": "circle_a","x": 1,"y": 10,"radius": 10,"stroke": {"r": 255,"g": 255,"b": 255,"a": 1 },"strokeWidth": 2,"fill": {"r":255,"g":0,"b":0,"a":1},"scaleX": 1.5}});
    expect(circle.eventListeners).toBeDefined();
    expect(circle.eventListeners.mousedown).toBeUndefined();
    expect(circle.eventListeners.click).toBeUndefined();
    expect(circle.eventListeners.mouseup).toBeUndefined();
    expect(circle.eventListeners.dblclick).toBeUndefined();
    expect(circle.eventListeners.mouseover).toBeUndefined();
    expect(circle.eventListeners.mouseout).toBeUndefined();
	});
	it("creating mouse event handlers", function() {
		// Parse circle
		var message = {"type": "circle", "data": {"id": "circle_nr_1", "x": 20, "y": 20, "radius": 5, "stroke": {"r": 255,"g": 255,"b": 255,"a": 1 }, "strokeWidth": 2, "fill": {"r":255,"g":0,"b":0,"a":1}}, "eventData": {"listen" : ["mousedown","mouseclick","mouseup","mousedoubleclick","mouseover", "mouseout"]}};
		var circle = parseShapeData(message);
		enableEventHandlers(circle, message);
    expect(circle.eventListeners).toBeDefined();
    // mousedown
    expect(circle.eventListeners.mousedown).toBeDefined();
    expect(circle.eventListeners.mousedown[0]).toBeDefined();
    expect(circle.eventListeners.mousedown[0].handler).toBeDefined();
    // mouseclick
    expect(circle.eventListeners.click).toBeDefined();
    expect(circle.eventListeners.click[0]).toBeDefined();
    expect(circle.eventListeners.click[0].handler).toBeDefined();
    // mouseup
    expect(circle.eventListeners.mouseup).toBeDefined();
    expect(circle.eventListeners.mouseup[0]).toBeDefined();
    expect(circle.eventListeners.mouseup[0].handler).toBeDefined();
    // mousedoubleclick
    expect(circle.eventListeners.dblclick).toBeDefined();
    expect(circle.eventListeners.dblclick[0]).toBeDefined();
    expect(circle.eventListeners.dblclick[0].handler).toBeDefined();
    // mouseover
    expect(circle.eventListeners.mouseover).toBeDefined();
    expect(circle.eventListeners.mouseover[0]).toBeDefined();
    expect(circle.eventListeners.mouseover[0].handler).toBeDefined();
    // mouseout
    expect(circle.eventListeners.mouseout).toBeDefined();
    expect(circle.eventListeners.mouseout[0]).toBeDefined();
    expect(circle.eventListeners.mouseout[0].handler).toBeDefined();
	});
  it("creating mousedrag event handler (special case compared to other mouse event handlers)", function() {
    // Parse circle
    var message = {"type": "circle", "data": {"id": "circle_nr_1", "x": 20, "y": 20, "radius": 5, "stroke": {"r": 255,"g": 255,"b": 255,"a": 1 }, "strokeWidth": 2, "fill": {"r":255,"g":0,"b":0,"a":1}}, "eventData": {"listen" : ["mousedrag"]}};
    var circle = parseShapeData(message);
    enableEventHandlers(circle, message);
    expect(circle.eventListeners).toBeDefined();
    // mousedown made because of mousedrag
    expect(circle.eventListeners.mousedown).toBeDefined();
    expect(circle.eventListeners.mousedown[0]).toBeDefined();
    expect(circle.eventListeners.mousedown[0].handler).toBeDefined();
  });
});

describe("Draw elements to test event handling", function() {
  var flag;
  var canvas;
  beforeEach(function(){
    // Setup canvas of Canvas.Hs
    canvas_wrapper = document.createElement("div");
    canvas_wrapper.id='canvas';
    $(document.body).append(canvas_wrapper);
    initCanvas($(canvas_wrapper), 900, 600); // Init canvas
    canvas = $(canvas_wrapper); // Get canvas element
    var layer = new Kinetic.Layer();
  });
    it("testing message after mousedown event", function() {
      // Draw in the Canvas.hs canvas
      connectionDataReceived({"data":'{"shape":{"type": "circle", "data": {"x": 20, "y": 20, "radius": 5, "stroke": {"r": 255,"g": 255,"b": 255,"a": 1 }, "strokeWidth": 2, "fill": {"r":255,"g":0,"b":0,"a":1}}, "eventData": {"eventId": "circle_nr_2", "listen" : ["mousedown"]}}}'});
      stage.batchDraw();
      stage.draw();

      // Create spy for mouseEvent method
      spyOn(connection, 'send');
      // Test if mouseEvent method is called after
      // mouse event on circle but not before.
      expect(connection.send.callCount).toBe(0);
      var event = new Event("mousedown");
      event.pageX = 20;
      event.pageY = 126;
      stage.find('Circle')[0].fire('mousedown', event);
      expect(connection.send).toHaveBeenCalledWith('{"event":"mousedown","data":{"id":"circle_nr_2","x":20,"y":20}}');
    });
    it("testing message after mouseclick event", function() {
      // Draw in the Canvas.hs canvas
      connectionDataReceived({"data":'{"shape":{"type": "circle", "data": {"x": 20, "y": 20, "radius": 5, "stroke": {"r": 255,"g": 255,"b": 255,"a": 1 }, "strokeWidth": 2, "fill": {"r":255,"g":0,"b":0,"a":1}}, "eventData": {"eventId": "circle_nr_2", "listen" : ["mouseclick"]}}}'});
      stage.batchDraw();
      stage.draw();

      // Create spy for mouseEvent method
      spyOn(connection, 'send');
      // Test if mouseEvent method is called after
      // mouse event on circle but not before.
      expect(connection.send.callCount).toBe(0);
      var event = new Event("click");
      event.pageX = 20;
      event.pageY = 126;
      stage.find('Circle')[0].fire('click', event);
      expect(connection.send).toHaveBeenCalledWith('{"event":"mouseclick","data":{"id":"circle_nr_2","x":20,"y":20}}');
    });
    it("testing message after mouseup event", function() {
      // Draw in the Canvas.hs canvas
      connectionDataReceived({"data":'{"shape":{"type": "circle", "data": {"x": 20, "y": 20, "radius": 5, "stroke": {"r": 255,"g": 255,"b": 255,"a": 1 }, "strokeWidth": 2, "fill": {"r":255,"g":0,"b":0,"a":1}}, "eventData": {"eventId": "circle_nr_2", "listen" : ["mouseup"]}}}'});
      stage.batchDraw();
      stage.draw();

      // Create spy for mouseEvent method
      spyOn(connection, 'send');
      // Test if mouseEvent method is called after
      // mouse event on circle but not before.
      expect(connection.send.callCount).toBe(0);
      var event = new Event("mouseup");
      event.pageX = 20;
      event.pageY = 126;
      stage.find('Circle')[0].fire('mouseup', event);
      expect(connection.send).toHaveBeenCalledWith('{"event":"mouseup","data":{"id":"circle_nr_2","x":20,"y":20}}');
    });
    it("testing message after mousedoubleclick event", function() {
      // Draw in the Canvas.hs canvas
      connectionDataReceived({"data":'{"shape":{"type": "circle", "data": {"x": 20, "y": 20, "radius": 5, "stroke": {"r": 255,"g": 255,"b": 255,"a": 1 }, "strokeWidth": 2, "fill": {"r":255,"g":0,"b":0,"a":1}}, "eventData": {"eventId": "circle_nr_2", "listen" : ["mousedoubleclick"]}}}'});
      stage.batchDraw();
      stage.draw();

      // Create spy for mouseEvent method
      spyOn(connection, 'send');
      // Test if mouseEvent method is called after
      // mouse event on circle but not before.
      expect(connection.send.callCount).toBe(0);
      var event = new Event("dblclick");
      event.pageX = 20;
      event.pageY = 126;
      stage.find('Circle')[0].fire('dblclick', event);
      expect(connection.send).toHaveBeenCalledWith('{"event":"mousedoubleclick","data":{"id":"circle_nr_2","x":20,"y":20}}');
    });
    it("testing message after mouseover event", function() {
      // Draw in the Canvas.hs canvas
      connectionDataReceived({"data":'{"shape":{"type": "circle", "data": {"x": 20, "y": 20, "radius": 5, "stroke": {"r": 255,"g": 255,"b": 255,"a": 1 }, "strokeWidth": 2, "fill": {"r":255,"g":0,"b":0,"a":1}}, "eventData": {"eventId": "circle_nr_2", "listen" : ["mouseover"]}}}'});
      stage.batchDraw();
      stage.draw();

      // Create spy for mouseEvent method
      spyOn(connection, 'send');
      // Test if mouseEvent method is called after
      // mouse event on circle but not before.
      expect(connection.send.callCount).toBe(0);
      var event = new Event("mouseover");
      event.pageX = 20;
      event.pageY = 126;
      stage.find('Circle')[0].fire('mouseover', event);
      expect(connection.send).toHaveBeenCalledWith('{"event":"mouseover","data":{"id":"circle_nr_2","x":20,"y":20}}');
    });
    it("testing message after mouseout event", function() {
      // Draw in the Canvas.hs canvas
      connectionDataReceived({"data":'{"shape":{"type": "circle", "data": {"x": 20, "y": 20, "radius": 5, "stroke": {"r": 255,"g": 255,"b": 255,"a": 1 }, "strokeWidth": 2, "fill": {"r":255,"g":0,"b":0,"a":1}}, "eventData": {"eventId": "circle_nr_2", "listen" : ["mouseout"]}}}'});
      stage.batchDraw();
      stage.draw();

      // Create spy for mouseEvent method
      spyOn(connection, 'send');
      // Test if mouseEvent method is called after
      // mouse event on circle but not before.
      expect(connection.send.callCount).toBe(0);
      var event = new Event("mouseout");
      event.pageX = 20;
      event.pageY = 126;
      event.targetNode = stage.find('Circle')[0];
      stage.find('Circle')[0].fire('mouseout', event);
      expect(connection.send).toHaveBeenCalledWith('{"event":"mouseout","data":{"id":"circle_nr_2","x":20,"y":20}}');
    });
    // it("testing message after mousedrag event", function() {
    //   // Draw in the Canvas.hs canvas
    //   connectionDataReceived({"data":'{"shape":{"type": "circle", "data": {"x": 20, "y": 20, "radius": 5, "stroke": {"r": 255,"g": 255,"b": 255,"a": 1 }, "strokeWidth": 2, "fill": {"r":255,"g":0,"b":0,"a":1}}, "eventData": {"eventId": "circle_nr_2", "listen" : ["mousedrag"]}}}'});
    //   stage.batchDraw();
    //   stage.draw();

    //   var clickSpy = jasmine.createSpy();
    //   // Create spy for mouseEvent method
    //   spyOn(connection, 'send');
    //   // Test if mouseEvent method is called after
    //   // click event on circle but not before.
    //   expect(connection.send.callCount).toBe(0);
    //   stage.find('Circle')[0].fire('mousedown');

    //   var event = jQuery.Event( "mousedrag" );
    //   event.pageX = 100;
    //   event.pageY = 200;
    //   canvas.trigger(event);
    //   expect(connection.send).toHaveBeenCalled();

      // expect(canvas.eventListeners).toBeDefined();
      // // mouseout made because of mousedrag
      // expect(canvas.eventListeners.mouseout).toBeDefined();
      // expect(canvas.eventListeners.mouseout[0]).toBeDefined();
      // expect(canvas.eventListeners.mouseout[0].handler).toBeDefined();
      // // mouseup made because of mousedrag
      // expect(canvas.eventListeners.mouseup).toBeDefined();
      // expect(canvas.eventListeners.mouseup[0]).toBeDefined();
      // expect(canvas.eventListeners.mouseup[0].handler).toBeDefined();
      // // mousemove made because of mousedrag
      // expect(canvas.eventListeners.mousemove).toBeDefined();
      // expect(canvas.eventListeners.mousemove[0]).toBeDefined();
      // expect(canvas.eventListeners.mousemove[0].handler).toBeDefined();
    // });
  afterEach(function(){
    // Remove the canvas elements used for testing and reset variables
    $(canvas_wrapper).remove();
    canvas = null;
    canvas_wrapper = null;
    // Reset variables of Canvas.hs
    var topLayerIdx = 0;
    var layerList = new Array();
    var stage = undefined;
    var connection = new WebSocket('ws://localhost:8080');
    var open = false;
  });
});<|endoftext|>


<commit_before>"use strict";

const util = require('util');

class Base {
    constructor(id) {
        if(id) {
            this.id = id;
        }
    }

    get createdAt() {
        return ~~(this.id / 4194304) + 1420070400000;
    }

    toString() {
        let ID = "";
        if(this.id) {
            ID = " " + this.id;
        } else if(this.user) {
            ID = " " + this.user.id;
        } else if(this.baseObject) {
            ID = "<" + this.baseObject.name + ">";
        } else if(this.label) {
            ID = " " + this.label;
        } else if(this.code) {
            ID = " " + this.code;
        }
        return "[" + this.constructor.name + ID + "]";
    }

    toJSON(simple) {
        if(simple) {
            return {
                id: this.id
            };
        }
        const base = {};
        for(const key in this) {
            if(!base.hasOwnProperty(key) && this.hasOwnProperty(key) && !key.startsWith("_")) {
                if(!this[key]) {
                    base[key] = this[key];
                } else if(this[key] instanceof Set) {
                    base[key] = Array.from(this[key]);
                } else if(this[key] instanceof Map) {
                    base[key] = Array.from(this[key].values());
                } else if(typeof this[key].toJSON === "function") {
                    base[key] = this[key].toJSON();
                } else {
                    base[key] = this[key];
                }
            }
        }
        return base;
    }

    [util.inspect.custom]() {
        // http://stackoverflow.com/questions/5905492/dynamic-function-name-in-javascript
        const copy = new {[this.constructor.name]: class {}}[this.constructor.name]();
        for(const key in this) {
            if(this.hasOwnProperty(key) && !key.startsWith("_") && this[key] !== undefined) {
                copy[key] = this[key];
            }
        }
        return copy;
    }
}

module.exports = Base;
<commit_msg>Fix createdAt flooring with Math.floor() (#437)<commit_after>"use strict";

const util = require('util');

class Base {
    constructor(id) {
        if(id) {
            this.id = id;
        }
    }

    get createdAt() {
        return Math.floor(this.id / 4194304) + 1420070400000;
    }

    toString() {
        let ID = "";
        if(this.id) {
            ID = " " + this.id;
        } else if(this.user) {
            ID = " " + this.user.id;
        } else if(this.baseObject) {
            ID = "<" + this.baseObject.name + ">";
        } else if(this.label) {
            ID = " " + this.label;
        } else if(this.code) {
            ID = " " + this.code;
        }
        return "[" + this.constructor.name + ID + "]";
    }

    toJSON(simple) {
        if(simple) {
            return {
                id: this.id
            };
        }
        const base = {};
        for(const key in this) {
            if(!base.hasOwnProperty(key) && this.hasOwnProperty(key) && !key.startsWith("_")) {
                if(!this[key]) {
                    base[key] = this[key];
                } else if(this[key] instanceof Set) {
                    base[key] = Array.from(this[key]);
                } else if(this[key] instanceof Map) {
                    base[key] = Array.from(this[key].values());
                } else if(typeof this[key].toJSON === "function") {
                    base[key] = this[key].toJSON();
                } else {
                    base[key] = this[key];
                }
            }
        }
        return base;
    }

    [util.inspect.custom]() {
        // http://stackoverflow.com/questions/5905492/dynamic-function-name-in-javascript
        const copy = new {[this.constructor.name]: class {}}[this.constructor.name]();
        for(const key in this) {
            if(this.hasOwnProperty(key) && !key.startsWith("_") && this[key] !== undefined) {
                copy[key] = this[key];
            }
        }
        return copy;
    }
}

module.exports = Base;
<|endoftext|>


<commit_before>define([
    "jquery",
    "qvangular",
    "qlik",
    "angular",
    "text!./template.html",
    "text!./AppLinker.css",
    "./properties/definition",
    "./services/linkerService",
    "./services/qlikService",
    "./lib/external/jquery.popupoverlay"
],

// aliasing array elements as params:
function($, qva, qlik, angular, template, css, definition, linkerService) {

    // advised:
    'use strict';

    $("<style>").html(css).appendTo("head");

    $(document).on("mouseenter", ".img", function() {
        $(this).addClass("hover");
    });
    $(document).on("mouseleave", ".img", function() {
        $(this).removeClass("hover");
    });

    return {
        initialProperties: {
            version: 1.0
        },
        definition: definition,
        snapshot: {
            canTakeSnapshot: false
        },
        template: template,
        priority: 0,
        // resize: function($element) {
        //     console.log($element);
        // },
        controller: ['$scope', '$timeout', 'linkerService',
            function($scope, $timeout, linkerService) {

                $("#qv-toolbar-container").on("click", function() {
                    $(".applinker-stage").hide();
                });

                $(".close-applinker-stage").on("click", function() {
                    $(this).closest(".applinker-stage").hide();
                });

                function update() {
                    $scope.isLoading = true;
                    $scope.appItems = linkerService.getAppItems($scope.layout);

                    if ($scope.appItems.length > 0) {

                        $timeout(function() {

                            linkerService.getApps($scope.layout, $scope.appItems).then(function(apps) {
                                return linkerService.addFieldsToApps(apps);
                            }).then(function(apps){
                                $scope.linkedApps = apps;

                                return linkerService.getSelectedItemKeys();
                            }).then(function(currentSelections){
                                $scope.linkedApps = linkerService.addTransferableSelectionsToApps(currentSelections, $scope.linkedApps);

                                $scope.isLoading = false;
                                $scope.selectedItemCount = currentSelections.length;
                            });
                        }, 1500);
                    } else {
                        // no configured apps:
                        $scope.linkedApps = [];
                        $scope.isLoading = false;
                    }
                };

                $scope.isInEditMode = function(){
                    return qlik.navigation.getMode() === 'edit';
                }

                $scope.isConfiguredApps = function() {
                    return $scope.appItems.length > 0;
                }

                $scope.openLinkedApp = function(application, e) {

                    var transferringId = "#applinker-transferring-" + $scope.$parent.options.id;

                    // has it already been moved?
                    if ($("body > " + transferringId).length === 0) {
                        // transfer:
                        $(transferringId).appendTo("body");
                    }

                    // show it!
                    $(transferringId).fadeIn();

                    // stop!
                    e.preventDefault();

                    //
                    //$('#fadeandscale' + $scope.$parent.options.id).popup('hide');

                    linkerService.getSelections().then(function(dict) {

                            // no selections, but open app nonetheless:
                            if (void 0 === dict) {
                                $(transferringId).fadeOut(500, function() {

                                    linkerService.openApp(document.URL, application.qDocId, application.sheet);
                                });
                                return;
                            };

                            // current app's selections have been made, so make them in the new app:
                            linkerService.makeSelections(application.qDocId, dict).then(function() {

                                var stage = "#applinker-stage-" + $scope.$parent.options.id;
                                $(stage).hide();

                                $(transferringId).fadeOut(500, function() {
                                    linkerService.openApp(document.URL, application.qDocId, application.sheet);
                                });
                            });

                        },
                        function(b) {
                            console.log("Something went wrong whilst retrieving selection information");
                        }
                    );
                }

                $scope.openStage = function(e) {
                    // stop (prevent page jump to # named anchor - relevant for mobile, scrollPos > 0)
                    e.preventDefault();
                    if($scope.isInEditMode()) return;

                    linkerService.getSelectedItemKeys().then(function(currentSelections) {
                        $scope.linkedApps = linkerService.addTransferableSelectionsToApps(currentSelections, $scope.linkedApps);

                        // show the stage
                        var stage = $("#applinker-stage-" + $scope.$parent.options.id);
                        $(stage).appendTo(".qv-panel-stage");
                        $(stage).fadeIn();
                    });
                }

                // TODO: update only if the extension properties have changed
                $scope.$watch('layout', function(){
                    update();
                });
            }
        ]
    };
});
<commit_msg>Changing the wat the linked app is opened<commit_after>define([
    "jquery",
    "qvangular",
    "qlik",
    "angular",
    "text!./template.html",
    "text!./AppLinker.css",
    "./properties/definition",
    "./services/linkerService",
    "./services/qlikService",
    "./lib/external/jquery.popupoverlay"
],

// aliasing array elements as params:
function($, qva, qlik, angular, template, css, definition, linkerService) {

    // advised:
    'use strict';

    $("<style>").html(css).appendTo("head");

    $(document).on("mouseenter", ".img", function() {
        $(this).addClass("hover");
    });
    $(document).on("mouseleave", ".img", function() {
        $(this).removeClass("hover");
    });

    return {
        initialProperties: {
            version: 1.0
        },
        definition: definition,
        snapshot: {
            canTakeSnapshot: false
        },
        template: template,
        priority: 0,
        // resize: function($element) {
        //     console.log($element);
        // },
        controller: ['$scope', '$timeout', 'linkerService',
            function($scope, $timeout, linkerService) {

                $("#qv-toolbar-container").on("click", function() {
                    $(".applinker-stage").hide();
                });

                $(".close-applinker-stage").on("click", function() {
                    $(this).closest(".applinker-stage").hide();
                });

                function update() {
                    $scope.isLoading = true;
                    $scope.appItems = linkerService.getAppItems($scope.layout);

                    if ($scope.appItems.length > 0) {

                        $timeout(function() {

                            linkerService.getApps($scope.layout, $scope.appItems).then(function(apps) {
                                return linkerService.addFieldsToApps(apps);
                            }).then(function(apps){
                                $scope.linkedApps = apps;

                                return linkerService.getSelectedItemKeys();
                            }).then(function(currentSelections){
                                $scope.linkedApps = linkerService.addTransferableSelectionsToApps(currentSelections, $scope.linkedApps);

                                $scope.isLoading = false;
                                $scope.selectedItemCount = currentSelections.length;
                            });
                        }, 1500);
                    } else {
                        // no configured apps:
                        $scope.linkedApps = [];
                        $scope.isLoading = false;
                    }
                };

                $scope.isInEditMode = function(){
                    return qlik.navigation.getMode() === 'edit';
                }

                $scope.isConfiguredApps = function() {
                    return $scope.appItems.length > 0;
                }

                $scope.openLinkedApp = function(application, e) {
                    var transferringId = "#applinker-transferring-" + $scope.$parent.options.id;

                    // has it already been moved?
                    if ($("body > " + transferringId).length === 0) {
                        $(transferringId).appendTo("body");
                    }

                    $(transferringId).fadeIn();
                    e.preventDefault();

                    linkerService.getSelections().then(function(dict) {
                        var stage = "#applinker-stage-" + $scope.$parent.options.id;
                        $(stage).hide();

                        $(transferringId).fadeOut(500, function() {
                            linkerService.openApp(document.URL, application.qDocId, application.sheet);
                            linkerService.makeSelections(application.qDocId, dict);
                        });
                    });
                }

                $scope.openStage = function(e) {
                    // stop (prevent page jump to # named anchor - relevant for mobile, scrollPos > 0)
                    e.preventDefault();
                    if($scope.isInEditMode()) return;

                    linkerService.getSelectedItemKeys().then(function(currentSelections) {
                        $scope.linkedApps = linkerService.addTransferableSelectionsToApps(currentSelections, $scope.linkedApps);

                        // show the stage
                        var stage = $("#applinker-stage-" + $scope.$parent.options.id);
                        $(stage).appendTo(".qv-panel-stage");
                        $(stage).fadeIn();
                    });
                }

                // TODO: update only if the extension properties have changed
                $scope.$watch('layout', function(){
                    update();
                });
            }
        ]
    };
});
<|endoftext|>


<commit_before>var Packet = require('./packet');
var Vec2 = require('./modules/Vec2');
var BinaryWriter = require("./packet/BinaryWriter");

function PlayerTracker(gameServer, socket) {
    this.gameServer = gameServer;
    this.socket = socket;
    this.pID = -1;
    this.userAuth = null;
    this.isRemoved = false;
    this.isCloseRequested = false;
    this._name = "";
    this._skin = "";
    this._nameUtf8 = null;
    this._skinUtf8protocol11 = null;
    this._nameUnicode = null;
    this._skinUtf8 = null;
    this.color = { r: 0, g: 0, b: 0 };
    this.viewNodes = [];
    this.clientNodes = [];
    this.cells = [];
    this.mergeOverride = false; // Triggered by console command
    this._score = 0; // Needed for leaderboard
    this._scale = 1;
    this.borderCounter = 0;
    this.connectedTime = new Date();

    this.tickLeaderboard = 0;
    this.team = 0;
    this.spectate = false;
    this.freeRoam = false;      // Free-roam mode enables player to move in spectate mode
    this.spectateTarget = null; // Spectate target, null for largest player
    this.lastKeypressTick = 0;

    this.centerPos = new Vec2(0, 0);
    this.mouse = new Vec2(0, 0);
    this.viewBox = {
        minx: 0,
        miny: 0,
        maxx: 0,
        maxy: 0
    };

    // Scramble the coordinate system for anti-raga
    this.scrambleX = 0;
    this.scrambleY = 0;
    this.scrambleId = 0;
    this.isMinion = false;
    this.isMuted = false;

    // Custom commands
    this.spawnmass = 0;
    this.frozen = false;
    this.customspeed = 0;
    this.rec = false;

    // Minions
    this.miQ = 0;
    this.isMi = false;
    this.minionSplit = false;
    this.minionEject = false;
    this.minionFrozen = false;
    this.minionControl = false;
    this.collectPellets = false;

    // Gamemode function
    if (gameServer) {
        // Player id
        this.pID = gameServer.lastPlayerId++ >> 0;
        // Gamemode function
        gameServer.gameMode.onPlayerInit(this);
        // Only scramble if enabled in config
        this.scramble();
    }
    var UserRoleEnum = require("./enum/UserRoleEnum");
    this.userRole = UserRoleEnum.GUEST;
}

module.exports = PlayerTracker;

// Setters/Getters

PlayerTracker.prototype.scramble = function() {
    if (!this.gameServer.config.serverScrambleLevel) {
        this.scrambleId = 0;
        this.scrambleX = 0;
        this.scrambleY = 0;
    } else {
        this.scrambleId = (Math.random() * 0xFFFFFFFF) >>> 0;
        // avoid mouse packet limitations
        var maxx = Math.max(0, 31767 - this.gameServer.border.width);
        var maxy = Math.max(0, 31767 - this.gameServer.border.height);
        var x = maxx * Math.random();
        var y = maxy * Math.random();
        if (Math.random() >= 0.5) x = -x;
        if (Math.random() >= 0.5) y = -y;
        this.scrambleX = x;
        this.scrambleY = y;
    }
    this.borderCounter = 0;
};

PlayerTracker.prototype.setName = function(name) {
    this._name = name;
    var writer = new BinaryWriter()
    writer.writeStringZeroUnicode(name);
    this._nameUnicode = writer.toBuffer();
    writer = new BinaryWriter();
    writer.writeStringZeroUtf8(name);
    this._nameUtf8 = writer.toBuffer();
};

PlayerTracker.prototype.setSkin = function(skin) {
    this._skin = skin;
    var writer = new BinaryWriter();
    writer.writeStringZeroUtf8(skin);
    this._skinUtf8 = writer.toBuffer();
    var writer1 = new BinaryWriter();
    writer1.writeStringZeroUtf8("%" + skin);
    this._skinUtf8protocol11 = writer1.toBuffer();
};

PlayerTracker.prototype.setColor = function(color) {
    this.color.r = color.r;
    this.color.g = color.g;
    this.color.b = color.b;
};

PlayerTracker.prototype.getScale = function() {
    this._score = 0; // reset to not cause bugs with leaderboard
    var scale = 0; // reset to not cause bugs with viewbox
    for (var i = 0; i < this.cells.length; i++) {
        scale += this.cells[i]._size;
        this._score += this.cells[i]._mass;
    }
    if (!scale) return scale = this._score = 0; // reset
    else return this._scale = Math.pow(Math.min(64 / scale, 1), 0.4);
};

PlayerTracker.prototype.joinGame = function(name, skin) {
    if (this.cells.length) return;

    if (skin) this.setSkin(skin);
    if (!name) name = "An unnamed cell";
    this.setName(name);
    this.spectate = false;
    this.freeRoam = false;
    this.spectateTarget = null;
    var packetHandler = this.socket.packetHandler;

    if (!this.isMi && this.socket.isConnected != null) {
        // some old clients don't understand ClearAll message
        // so we will send update for them
        if (packetHandler.protocol < 6) {
            packetHandler.sendPacket(new Packet.UpdateNodes(this, [], [], [], this.clientNodes));
        }
        packetHandler.sendPacket(new Packet.ClearAll());
        this.clientNodes = [];
        this.scramble();
        if (this.gameServer.config.serverScrambleLevel < 2) {
            // no scramble / lightweight scramble
            packetHandler.sendPacket(new Packet.SetBorder(this, this.gameServer.border));
        }
        else if (this.gameServer.config.serverScrambleLevel == 3) {
            var ran = 10065536 * Math.random();
            // Ruins most known minimaps (no border)
            var border = {
                minx: this.gameServer.border.minx - ran,
                miny: this.gameServer.border.miny - ran,
                maxx: this.gameServer.border.maxx + ran,
                maxy: this.gameServer.border.maxy + ran
            };
            packetHandler.sendPacket(new Packet.SetBorder(this, border));
        }
    }
    this.gameServer.gameMode.onPlayerSpawn(this.gameServer, this);
};

PlayerTracker.prototype.checkConnection = function() {
    // Handle disconnection
    if (!this.socket.isConnected) {
        // Wait for playerDisconnectTime
        var pt = this.gameServer.config.playerDisconnectTime;
        var dt = (this.gameServer.stepDateTime - this.socket.closeTime) / 1e3;
        if (pt && (!this.cells.length || dt >= pt)) {
            // Remove all client cells
            while (this.cells.length) this.gameServer.removeNode(this.cells[0]);
        }
        this.cells = [];
        this.isRemoved = true;
        this.mouse = null;
        this.socket.packetHandler.pressSpace = false;
        this.socket.packetHandler.pressQ = false;
        this.socket.packetHandler.pressW = false;
        return;
    }

    // Check timeout
    if (!this.isCloseRequested && this.gameServer.config.serverTimeout) {
        dt = (this.gameServer.stepDateTime - this.socket.lastAliveTime) / 1000;
        if (dt >= this.gameServer.config.serverTimeout) {
            this.socket.close(1000, "Connection timeout");
            this.isCloseRequested = true;
        }
    }
};

PlayerTracker.prototype.updateTick = function() {
    if (this.isRemoved || this.isMinion)
        return; // do not update
    this.socket.packetHandler.process();
    if (this.isMi) return;

    // update viewbox
    this.updateSpecView(this.cells.length);
    var scale = Math.max(this.getScale(), this.gameServer.config.serverMinScale);
    var halfWidth = (this.gameServer.config.serverViewBaseX + 100) / scale / 2;
    var halfHeight = (this.gameServer.config.serverViewBaseY + 100) / scale / 2;
    this.viewBox = {
        minx: this.centerPos.x - halfWidth,
        miny: this.centerPos.y - halfHeight,
        maxx: this.centerPos.x + halfWidth,
        maxy: this.centerPos.y + halfHeight
    };

    // update visible nodes
    this.viewNodes = [];
    var self = this;
    this.gameServer.quadTree.find(this.viewBox, function(check) {
        if (check.owner != self)
            self.viewNodes.push(check);
    });
    this.viewNodes = this.viewNodes.concat(this.cells);
    this.viewNodes.sort(function(a, b) { return a.nodeId - b.nodeId; });
};

PlayerTracker.prototype.sendUpdate = function() {
    if (this.isRemoved || !this.socket.packetHandler.protocol ||
        !this.socket.isConnected || this.isMi || this.isMinion ||
        (this.socket._socket.writable != null && !this.socket._socket.writable) ||
        this.socket.readyState != this.socket.OPEN) {
        // do not send update for disconnected clients
        // also do not send if initialization is not complete yet
        return;
    }

    var packetHandler = this.socket.packetHandler;
    if (this.gameServer.config.serverScrambleLevel == 2) {
        // scramble (moving border)
        if (!this.borderCounter) {
            var b = this.gameServer.border, v = this.viewBox;
            var bound = {
                minx: Math.max(b.minx, v.minx - v.halfWidth),
                miny: Math.max(b.miny, v.miny - v.halfHeight),
                maxx: Math.min(b.maxx, v.maxx + v.halfWidth),
                maxy: Math.min(b.maxy, v.maxy + v.halfHeight)
            };
            packetHandler.sendPacket(new Packet.SetBorder(this, bound));
        }
        if (++this.borderCounter >= 20) this.borderCounter = 0;
    }

    var delNodes = [];
    var eatNodes = [];
    var addNodes = [];
    var updNodes = [];
    var oldIndex = 0;
    var newIndex = 0;
    for (; newIndex < this.viewNodes.length && oldIndex < this.clientNodes.length;) {
        if (this.viewNodes[newIndex].nodeId < this.clientNodes[oldIndex].nodeId) {
            if (this.viewNodes[newIndex].isRemoved) continue;
            addNodes.push(this.viewNodes[newIndex]);
            newIndex++;
            continue;
        }
        if (this.viewNodes[newIndex].nodeId > this.clientNodes[oldIndex].nodeId) {
            var node = this.clientNodes[oldIndex];
            if (node.isRemoved) eatNodes.push(node);
            else delNodes.push(node);
            oldIndex++;
            continue;
        }
        var node = this.viewNodes[newIndex];
        if (node.isRemoved) continue;
        // only send update for moving or player nodes
        if (node.isMoving || node.cellType == 0)
            updNodes.push(node);
        addNodes.push(node);
        newIndex++;
        oldIndex++;
    }
    for (; oldIndex < this.clientNodes.length; oldIndex++) {
        var node = this.clientNodes[oldIndex];
        if (node.isRemoved) eatNodes.push(node);
        else delNodes.push(node);
    }
    this.clientNodes = this.viewNodes;

    // Send update packet
    packetHandler.sendPacket(new Packet.UpdateNodes(this, addNodes, updNodes, eatNodes, delNodes));

    // Update leaderboard
    if (++this.tickLeaderboard > 25) {
        // 1 / 0.040 = 25 (once per second)
        this.tickLeaderboard = 0;
        if (this.gameServer.leaderboardType >= 0)
            packetHandler.sendPacket(new Packet.UpdateLeaderboard(this, this.gameServer.leaderboard, this.gameServer.leaderboardType));
    }
};

PlayerTracker.prototype.updateSpecView = function(len) {
    if (!this.spectate || len) {
        // in game
        var cx = 0, cy = 0;
        for (var i = 0; i < len; i++) {
            cx += this.cells[i].position.x / len;
            cy += this.cells[i].position.y / len;
            this.centerPos = new Vec2(cx, cy);
        }
    } else {
        if (this.freeRoam || this.getSpecTarget() == null) {
            // free roam
            var d = this.mouse.clone().sub(this.centerPos);
            var scale = this.gameServer.config.serverSpectatorScale;
            this.setCenterPos(this.centerPos.add(d, 32 / d.sqDist()));
        } else {
            // spectate target
            var player = this.getSpecTarget();
            if (player) {
                this.setCenterPos(player.centerPos);
                var scale = player.getScale();
                this.place = player.place;
                this.viewBox = player.viewBox;
                this.viewNodes = player.viewNodes;
            }
        }
        // sends camera packet
        this.socket.packetHandler.sendPacket(new Packet.UpdatePosition(
            this, this.centerPos.x, this.centerPos.y, scale
        ));
    }
}

PlayerTracker.prototype.pressSpace = function() {
    if (this.spectate) {
        // Check for spam first (to prevent too many add/del updates)
        if (this.gameServer.tickCounter - this.lastKeypressTick < 40)
            return;
        this.lastKeypressTick = this.gameServer.tickCounter;

        // Space doesn't work for freeRoam mode
        if (this.freeRoam || this.gameServer.largestClient == null)
            return;
    } else if (this.gameServer.run) {
        // Disable mergeOverride on the last merging cell
        if (this.cells.length <= 2)
            this.mergeOverride = false;
        // Cant split if merging or frozen
        if (this.mergeOverride || this.frozen)
            return;
        this.gameServer.splitCells(this);
    }
};

PlayerTracker.prototype.pressW = function() {
    if (this.spectate || !this.gameServer.run) return;
    this.gameServer.ejectMass(this);
};

PlayerTracker.prototype.pressQ = function() {
    if (this.spectate) {
        // Check for spam first (to prevent too many add/del updates)
        if (this.gameServer.tickCounter - this.lastKeypressTick < 40)
            return;

        this.lastKeypressTick = this.gameServer.tickCounter;
        if (this.spectateTarget == null)
            this.freeRoam = !this.freeRoam;
        this.spectateTarget = null;
    }
};

PlayerTracker.prototype.getSpecTarget = function() {
    if (this.spectateTarget == null || this.spectateTarget.isRemoved) {
        this.spectateTarget = null;
        return this.gameServer.largestClient;
    }
    return this.spectateTarget;
};

PlayerTracker.prototype.setCenterPos = function(p) {
    p.x = Math.max(p.x, this.gameServer.border.minx);
    p.y = Math.max(p.y, this.gameServer.border.miny);
    p.x = Math.min(p.x, this.gameServer.border.maxx);
    p.y = Math.min(p.y, this.gameServer.border.maxy);
    this.centerPos = p;
};
<commit_msg>Fix ejected mass shooting too far from PlayerCell<commit_after>var Packet = require('./packet');
var Vec2 = require('./modules/Vec2');
var BinaryWriter = require("./packet/BinaryWriter");

function PlayerTracker(gameServer, socket) {
    this.gameServer = gameServer;
    this.socket = socket;
    this.pID = -1;
    this.userAuth = null;
    this.isRemoved = false;
    this.isCloseRequested = false;
    this._name = "";
    this._skin = "";
    this._nameUtf8 = null;
    this._skinUtf8protocol11 = null;
    this._nameUnicode = null;
    this._skinUtf8 = null;
    this.color = { r: 0, g: 0, b: 0 };
    this.viewNodes = [];
    this.clientNodes = [];
    this.cells = [];
    this.mergeOverride = false; // Triggered by console command
    this._score = 0; // Needed for leaderboard
    this._scale = 1;
    this.borderCounter = 0;
    this.connectedTime = new Date();

    this.tickLeaderboard = 0;
    this.team = 0;
    this.spectate = false;
    this.freeRoam = false;      // Free-roam mode enables player to move in spectate mode
    this.spectateTarget = null; // Spectate target, null for largest player
    this.lastKeypressTick = 0;

    this.centerPos = new Vec2(0, 0);
    this.mouse = new Vec2(0, 0);
    this.viewBox = {
        minx: 0,
        miny: 0,
        maxx: 0,
        maxy: 0
    };

    // Scramble the coordinate system for anti-raga
    this.scrambleX = 0;
    this.scrambleY = 0;
    this.scrambleId = 0;
    this.isMinion = false;
    this.isMuted = false;

    // Custom commands
    this.spawnmass = 0;
    this.frozen = false;
    this.customspeed = 0;
    this.rec = false;

    // Minions
    this.miQ = 0;
    this.isMi = false;
    this.minionSplit = false;
    this.minionEject = false;
    this.minionFrozen = false;
    this.minionControl = false;
    this.collectPellets = false;

    // Gamemode function
    if (gameServer) {
        // Player id
        this.pID = gameServer.lastPlayerId++ >> 0;
        // Gamemode function
        gameServer.gameMode.onPlayerInit(this);
        // Only scramble if enabled in config
        this.scramble();
    }
    var UserRoleEnum = require("./enum/UserRoleEnum");
    this.userRole = UserRoleEnum.GUEST;
}

module.exports = PlayerTracker;

// Setters/Getters

PlayerTracker.prototype.scramble = function() {
    if (!this.gameServer.config.serverScrambleLevel) {
        this.scrambleId = 0;
        this.scrambleX = 0;
        this.scrambleY = 0;
    } else {
        this.scrambleId = (Math.random() * 0xFFFFFFFF) >>> 0;
        // avoid mouse packet limitations
        var maxx = Math.max(0, 31767 - this.gameServer.border.width);
        var maxy = Math.max(0, 31767 - this.gameServer.border.height);
        var x = maxx * Math.random();
        var y = maxy * Math.random();
        if (Math.random() >= 0.5) x = -x;
        if (Math.random() >= 0.5) y = -y;
        this.scrambleX = x;
        this.scrambleY = y;
    }
    this.borderCounter = 0;
};

PlayerTracker.prototype.setName = function(name) {
    this._name = name;
    var writer = new BinaryWriter()
    writer.writeStringZeroUnicode(name);
    this._nameUnicode = writer.toBuffer();
    writer = new BinaryWriter();
    writer.writeStringZeroUtf8(name);
    this._nameUtf8 = writer.toBuffer();
};

PlayerTracker.prototype.setSkin = function(skin) {
    this._skin = skin;
    var writer = new BinaryWriter();
    writer.writeStringZeroUtf8(skin);
    this._skinUtf8 = writer.toBuffer();
    var writer1 = new BinaryWriter();
    writer1.writeStringZeroUtf8("%" + skin);
    this._skinUtf8protocol11 = writer1.toBuffer();
};

PlayerTracker.prototype.getScale = function() {
    this._score = 0; // reset to not cause bugs with leaderboard
    var scale = 0; // reset to not cause bugs with viewbox
    for (var i = 0; i < this.cells.length; i++) {
        scale += this.cells[i]._size;
        this._score += this.cells[i]._mass;
    }
    if (!scale) return scale = this._score = 0; // reset
    else return this._scale = Math.pow(Math.min(64 / scale, 1), 0.4);
};

PlayerTracker.prototype.joinGame = function(name, skin) {
    if (this.cells.length) return;

    if (skin) this.setSkin(skin);
    if (!name) name = "An unnamed cell";
    this.setName(name);
    this.spectate = false;
    this.freeRoam = false;
    this.spectateTarget = null;
    var packetHandler = this.socket.packetHandler;

    if (!this.isMi && this.socket.isConnected != null) {
        // some old clients don't understand ClearAll message
        // so we will send update for them
        if (packetHandler.protocol < 6) {
            packetHandler.sendPacket(new Packet.UpdateNodes(this, [], [], [], this.clientNodes));
        }
        packetHandler.sendPacket(new Packet.ClearAll());
        this.clientNodes = [];
        this.scramble();
        if (this.gameServer.config.serverScrambleLevel < 2) {
            // no scramble / lightweight scramble
            packetHandler.sendPacket(new Packet.SetBorder(this, this.gameServer.border));
        }
        else if (this.gameServer.config.serverScrambleLevel == 3) {
            var ran = 10065536 * Math.random();
            // Ruins most known minimaps (no border)
            var border = {
                minx: this.gameServer.border.minx - ran,
                miny: this.gameServer.border.miny - ran,
                maxx: this.gameServer.border.maxx + ran,
                maxy: this.gameServer.border.maxy + ran
            };
            packetHandler.sendPacket(new Packet.SetBorder(this, border));
        }
    }
    this.gameServer.gameMode.onPlayerSpawn(this.gameServer, this);
};

PlayerTracker.prototype.checkConnection = function() {
    // Handle disconnection
    if (!this.socket.isConnected) {
        // Wait for playerDisconnectTime
        var pt = this.gameServer.config.playerDisconnectTime;
        var dt = (this.gameServer.stepDateTime - this.socket.closeTime) / 1e3;
        if (pt && (!this.cells.length || dt >= pt)) {
            // Remove all client cells
            while (this.cells.length) this.gameServer.removeNode(this.cells[0]);
        }
        this.cells = [];
        this.isRemoved = true;
        this.mouse = null;
        this.socket.packetHandler.pressSpace = false;
        this.socket.packetHandler.pressQ = false;
        this.socket.packetHandler.pressW = false;
        return;
    }

    // Check timeout
    if (!this.isCloseRequested && this.gameServer.config.serverTimeout) {
        dt = (this.gameServer.stepDateTime - this.socket.lastAliveTime) / 1000;
        if (dt >= this.gameServer.config.serverTimeout) {
            this.socket.close(1000, "Connection timeout");
            this.isCloseRequested = true;
        }
    }
};

PlayerTracker.prototype.updateTick = function() {
    if (this.isRemoved || this.isMinion)
        return; // do not update
    this.socket.packetHandler.process();
    if (this.isMi) return;

    // update viewbox
    this.updateSpecView(this.cells.length);
    var scale = Math.max(this.getScale(), this.gameServer.config.serverMinScale);
    var halfWidth = (this.gameServer.config.serverViewBaseX + 100) / scale / 2;
    var halfHeight = (this.gameServer.config.serverViewBaseY + 100) / scale / 2;
    this.viewBox = {
        minx: this.centerPos.x - halfWidth,
        miny: this.centerPos.y - halfHeight,
        maxx: this.centerPos.x + halfWidth,
        maxy: this.centerPos.y + halfHeight
    };

    // update visible nodes
    this.viewNodes = [];
    var self = this;
    this.gameServer.quadTree.find(this.viewBox, function(check) {
        self.viewNodes.push(check);
    });
    this.viewNodes.sort(function(a, b) { return a.nodeId - b.nodeId; });
};

PlayerTracker.prototype.sendUpdate = function() {
    if (this.isRemoved || !this.socket.packetHandler.protocol ||
        !this.socket.isConnected || this.isMi || this.isMinion ||
        (this.socket._socket.writable != null && !this.socket._socket.writable) ||
        this.socket.readyState != this.socket.OPEN) {
        // do not send update for disconnected clients
        // also do not send if initialization is not complete yet
        return;
    }

    var packetHandler = this.socket.packetHandler;
    if (this.gameServer.config.serverScrambleLevel == 2) {
        // scramble (moving border)
        if (!this.borderCounter) {
            var b = this.gameServer.border, v = this.viewBox;
            var bound = {
                minx: Math.max(b.minx, v.minx - v.halfWidth),
                miny: Math.max(b.miny, v.miny - v.halfHeight),
                maxx: Math.min(b.maxx, v.maxx + v.halfWidth),
                maxy: Math.min(b.maxy, v.maxy + v.halfHeight)
            };
            packetHandler.sendPacket(new Packet.SetBorder(this, bound));
        }
        if (++this.borderCounter >= 20) this.borderCounter = 0;
    }

    var delNodes = [];
    var eatNodes = [];
    var addNodes = [];
    var updNodes = [];
    var oldIndex = 0;
    var newIndex = 0;
    for (; newIndex < this.viewNodes.length && oldIndex < this.clientNodes.length;) {
        if (this.viewNodes[newIndex].nodeId < this.clientNodes[oldIndex].nodeId) {
            if (this.viewNodes[newIndex].isRemoved) continue;
            addNodes.push(this.viewNodes[newIndex]);
            newIndex++;
            continue;
        }
        if (this.viewNodes[newIndex].nodeId > this.clientNodes[oldIndex].nodeId) {
            var node = this.clientNodes[oldIndex];
            if (node.isRemoved) eatNodes.push(node);
            else delNodes.push(node);
            oldIndex++;
            continue;
        }
        var node = this.viewNodes[newIndex];
        if (node.isRemoved) continue;
        // only send update for moving or player nodes
        if (node.isMoving || node.cellType == 0 || node.cellType == 2)
            updNodes.push(node);
        newIndex++;
        oldIndex++;
    }
    for (; newIndex < this.viewNodes.length; newIndex++) {
        addNodes.push(this.viewNodes[newIndex]);
    }
    for (; oldIndex < this.clientNodes.length; oldIndex++) {
        var node = this.clientNodes[oldIndex];
        if (node.isRemoved) eatNodes.push(node);
        else delNodes.push(node);
    }
    this.clientNodes = this.viewNodes;

    // Send update packet
    packetHandler.sendPacket(new Packet.UpdateNodes(this, addNodes, updNodes, eatNodes, delNodes));

    // Update leaderboard
    if (++this.tickLeaderboard > 25) {
        // 1 / 0.040 = 25 (once per second)
        this.tickLeaderboard = 0;
        if (this.gameServer.leaderboardType >= 0)
            packetHandler.sendPacket(new Packet.UpdateLeaderboard(this, this.gameServer.leaderboard, this.gameServer.leaderboardType));
    }
};

PlayerTracker.prototype.updateSpecView = function(len) {
    if (!this.spectate || len) {
        // in game
        var cx = 0, cy = 0;
        for (var i = 0; i < len; i++) {
            cx += this.cells[i].position.x / len;
            cy += this.cells[i].position.y / len;
            this.centerPos = new Vec2(cx, cy);
        }
    } else {
        if (this.freeRoam || this.getSpecTarget() == null) {
            // free roam
            var d = this.mouse.clone().sub(this.centerPos);
            var scale = this.gameServer.config.serverSpectatorScale;
            this.setCenterPos(this.centerPos.add(d, 32 / d.sqDist()));
        } else {
            // spectate target
            var player = this.getSpecTarget();
            if (player) {
                this.setCenterPos(player.centerPos);
                var scale = player.getScale();
                this.place = player.place;
                this.viewBox = player.viewBox;
                this.viewNodes = player.viewNodes;
            }
        }
        // sends camera packet
        this.socket.packetHandler.sendPacket(new Packet.UpdatePosition(
            this, this.centerPos.x, this.centerPos.y, scale
        ));
    }
}

PlayerTracker.prototype.pressSpace = function() {
    if (this.spectate) {
        // Check for spam first (to prevent too many add/del updates)
        if (this.gameServer.tickCounter - this.lastKeypressTick < 40)
            return;
        this.lastKeypressTick = this.gameServer.tickCounter;

        // Space doesn't work for freeRoam mode
        if (this.freeRoam || this.gameServer.largestClient == null)
            return;
    } else if (this.gameServer.run) {
        // Disable mergeOverride on the last merging cell
        if (this.cells.length <= 2)
            this.mergeOverride = false;
        // Cant split if merging or frozen
        if (this.mergeOverride || this.frozen)
            return;
        this.gameServer.splitCells(this);
    }
};

PlayerTracker.prototype.pressW = function() {
    if (this.spectate || !this.gameServer.run) return;
    this.gameServer.ejectMass(this);
};

PlayerTracker.prototype.pressQ = function() {
    if (this.spectate) {
        // Check for spam first (to prevent too many add/del updates)
        if (this.gameServer.tickCounter - this.lastKeypressTick < 40)
            return;

        this.lastKeypressTick = this.gameServer.tickCounter;
        if (this.spectateTarget == null)
            this.freeRoam = !this.freeRoam;
        this.spectateTarget = null;
    }
};

PlayerTracker.prototype.getSpecTarget = function() {
    if (this.spectateTarget == null || this.spectateTarget.isRemoved) {
        this.spectateTarget = null;
        return this.gameServer.largestClient;
    }
    return this.spectateTarget;
};

PlayerTracker.prototype.setCenterPos = function(p) {
    p.x = Math.max(p.x, this.gameServer.border.minx);
    p.y = Math.max(p.y, this.gameServer.border.miny);
    p.x = Math.min(p.x, this.gameServer.border.maxx);
    p.y = Math.min(p.y, this.gameServer.border.maxy);
    this.centerPos = p;
};
<|endoftext|>


<commit_before>const fs = require('fs');
const path = require('path');
const Log = require("./Log");
const MasterPlugin = require("./MasterPlugin");
const Plugin = require("./Plugin");
const Util = require("./Util");
const {EventEmitter} = require("events");

// A small utility functor to find a plugin with a given name
const nameMatches = targetName => pl => pl.plugin.name.toLowerCase() === targetName.toLowerCase();

const SYNC_INTERVAL = 5000;

module.exports = class PluginManager {

    constructor(bot, config, auth) {
        this.bot = bot;
        this.log = Log.get("PluginManager", config);
        this.auth = auth;
        this.plugins = [];
        this.emitter = new EventEmitter();
        this.emitter.setMaxListeners(Infinity);

        this.masterPlugin = new MasterPlugin(this.emitter, this, config);
        this.masterPlugin.sendMessage = this.bot.sendMessage.bind(this.bot); // Dirty patch
        this.addPlugin(this.masterPlugin);

        this.config = config;

        const events = Object.keys(Plugin.handlerNames)
            // We handle the message event by ourselves.
            .filter(prop => prop !== "message")
            // Events beginning with an underscore (eg. _command) are internal.
            .filter(prop => prop[0] !== "_");

        // Registers a handler for every Telegram event.
        // It runs the message through the proxy and forwards it to the plugin manager.
        for (const eventName of events) {
            bot.on(
                eventName,
                message => {
                    this.parseEnableDisable(message);
                    Promise.all(
                        this.plugins
                            .filter(plugin => (plugin.plugin.type & Plugin.Type.PROXY) === Plugin.Type.PROXY)
                            .map(plugin => plugin.proxy(eventName, message))
                        )
                       .then(() => this.emit(
                            "message",
                            message
                        ))
                       .then(() => this.emit(
                            eventName,
                            message
                        ))
                        .catch(err => {
                            if (err) this.log.error("Message rejected with error", err);
                        });
                }
            );
        }
    }

    parseEnableDisable(message) {
        // Hardcoded hot-swap plugin
        if (!message.text) return;
        const parts = Util.parseCommand(
            message.text,
            [
                "enable",
                "disable"
            ],
            {
                overrideDeprecation: true
            }
        );
        if (!parts) return;
        if (!this.auth.isAdmin(message.from.id)) return;
        // Syntax: /("enable"|"disable") pluginName [targetChat|"chat"]
        // The string "chat" will enable the plugin in the current chat.
        let [command, pluginName, targetChat] = parts;
        if (targetChat === "chat") targetChat = message.chat.id;
        targetChat = Number(targetChat);
        // Checks if it is already in this.plugins
        const isGloballyEnabled = this.plugins.some(nameMatches(pluginName));
        let response;
        switch (command) {
        case "enable":
            if (targetChat) {
                // Enable it if necessary
                let status = true;
                if (!isGloballyEnabled)
                    status = this.loadAndAdd(pluginName);
                if (status) {
                    const plugin = this.plugins.find(nameMatches(pluginName));
                    plugin.blacklist.delete(targetChat);
                    response = `Plugin enabled successfully for chat ${targetChat}.`;
                } else {
                    response = "Couldn't load plugin.";
                }
            } else if (isGloballyEnabled) {
                response = "Plugin already enabled.";
            } else {
                this.log.info(`Enabling ${pluginName} from message interface`);
                const status = this.loadAndAdd(pluginName);
                response = status ? "Plugin enabled successfully." : "An error occurred.";
            }
            break;
        case "disable":
            if (targetChat) {
                if (isGloballyEnabled) {
                    const plugin = this.plugins.find(nameMatches(pluginName));
                    plugin.blacklist.add(targetChat);
                    response = `Plugin disabled successfully for chat ${targetChat}.`;
                } else {
                    response = "Plugin isn't enabled.";
                }
            } else if (isGloballyEnabled) {
                const outcome = this.removePlugin(pluginName);
                response = outcome ? "Plugin disabled successfully." : "An error occurred.";
            } else {
                response = "Plugin already disabled.";
            }
            break;
        default:
            break;
        }
        this.bot.sendMessage(message.chat.id, response);
    }

    // Instantiates the plugin.
    // Returns the plugin itself.
    loadPlugin(pluginName) {
        const pluginPath = path.join(__dirname, 'plugins', pluginName);
        /* Invalidates the require() cache.
         * This allows for "hot fixes" to plugins: just /disable it, make the
         * required changes, and /enable it again.
         * If the cache wasn't invalidated, the plugin would be loaded from
         * cache rather than from disk, meaning that your changes wouldn't apply.
         * Method: https://stackoverflow.com/a/16060619
         */
        delete require.cache[require.resolve(pluginPath)];
        const ThisPlugin = require(pluginPath);

        this.log.debug(`Required ${pluginName}`);

        const loadedPlugin = new ThisPlugin(this.emitter, this.bot);

        for (const method of Object.getOwnPropertyNames(Object.getPrototypeOf(this.bot))) {
            if (typeof this.bot[method] !== "function") continue;
            if (method === "constructor" || method === "on") continue;
            if (/^_/.test(method)) continue; // Do not expose internal methods
            this.log.debug(`Binding ${method}`);
            loadedPlugin[method] = this.bot[method].bind(this.bot);
        }

        this.log.debug(`Created ${pluginName}.`);
        loadedPlugin.start(this.config, this.auth);

        return loadedPlugin;
    }

    // Adds the plugin to the list of active plugins
    addPlugin(loadedPlugin) {
        this.plugins.push(loadedPlugin);
        this.log.verbose(`Added ${loadedPlugin.plugin.name}.`);
    }

    // Returns true if the plugin was added successfully, false otherwise.
    loadAndAdd(pluginName) {
        try {
            const plugin = this.loadPlugin(pluginName);
            this.log.debug(pluginName + " loaded correctly.");
            this.addPlugin(plugin);
            return true;
        } catch (e) {
            this.log.warn(e);
            this.log.warn(`Failed to initialize plugin ${pluginName}.`);
            return false;
        }
    }

    // Load and add every plugin in the list.
    loadPlugins(pluginNames) {
        this.log.verbose(`Loading and adding ${pluginNames.length} plugins...`);
        Error.stackTraceLimit = 5; // Avoid printing useless data in stack traces

        const log = pluginNames.map(name => this.loadAndAdd(name));
        if (log.some(result => result !== true)) {
            this.log.warn("Some plugins couldn't be loaded.");
        }

        Error.stackTraceLimit = 10; // Reset to default value
    }

    // Returns true if at least one plugin was removed
    removePlugin(pluginName) {
        this.log.verbose(`Removing plugin ${pluginName}`);
        const prevPluginNum = this.plugins.length;
        const isCurrentPlugin = nameMatches(pluginName);
        this.plugins.filter(isCurrentPlugin).forEach(pl => pl.stop());
        this.plugins = this.plugins.filter(pl => !isCurrentPlugin(pl));
        const curPluginNum = this.plugins.length;
        return (prevPluginNum - curPluginNum) > 0;
    }

    stopPlugins() {
        return Promise.all(this.plugins.map(pl => pl.stop()));
    }

    getDatabasePath(plugin) {
        return path.join(__dirname, '..', 'db', 'plugin_' + plugin.plugin.name + '.json');
    }

    startSynchronization() {
        this.plugins.forEach(plugin => {
            const databasePath = this.getDatabasePath(plugin);

            if (fs.existsSync(databasePath)) {
                const {db, blacklist} = JSON.parse(fs.readFileSync(databasePath, "utf8"));

                plugin.blacklist = new Set(blacklist);
                plugin.db = db;
            }
        });

        this.synchronizationInterval = setInterval(() => {
            this.plugins.forEach(plugin => (
                fs.writeFile(
                    this.getDatabasePath(plugin),
                    JSON.stringify({
                        db: plugin.db,
                        blacklist: Array.from(plugin.blacklist)
                    })
                )
            ));
        }, SYNC_INTERVAL);
    }

    stopSynchronization() {
        if (this.synchronizationInterval) {
            clearInterval(this.synchronizationInterval);
        }
    }

    emit(event, message) {
        this.log.debug(`Triggered event ${event}`);

        if (event !== "message") {
            // Command emitter
            if (message.text !== undefined && message.entities && message.entities[0].type === "bot_command") {
                const entity = message.entities[0];

                const rawCommand = message.text.slice(entity.offset + 1, entity.offset + entity.length);
                const [command] = rawCommand.replace(/\//, "").split("@");

                let args = [];
                if (entity.offset + entity.length < message.text.length) {
                    args = message.text.slice(entity.offset + entity.length + 1).split(" ");
                }

                this.emitter.emit("_command", {message, command, args});
            } else if (message.query !== undefined) {
                const parts = message.query.split(" ");
                const command = parts[0].toLowerCase();
                const args = parts.length > 1 ? parts.slice(1) : [];
                this.emitter.emit("_inline_command", {message, command, args});
            }
        }

        this.emitter.emit(event, {message});
    }
};
<commit_msg>Add callback for error logging<commit_after>const fs = require('fs');
const path = require('path');
const Log = require("./Log");
const MasterPlugin = require("./MasterPlugin");
const Plugin = require("./Plugin");
const Util = require("./Util");
const {EventEmitter} = require("events");

// A small utility functor to find a plugin with a given name
const nameMatches = targetName => pl => pl.plugin.name.toLowerCase() === targetName.toLowerCase();

const SYNC_INTERVAL = 5000;

module.exports = class PluginManager {

    constructor(bot, config, auth) {
        this.bot = bot;
        this.log = Log.get("PluginManager", config);
        this.auth = auth;
        this.plugins = [];
        this.emitter = new EventEmitter();
        this.emitter.setMaxListeners(Infinity);

        this.masterPlugin = new MasterPlugin(this.emitter, this, config);
        this.masterPlugin.sendMessage = this.bot.sendMessage.bind(this.bot); // Dirty patch
        this.addPlugin(this.masterPlugin);

        this.config = config;

        const events = Object.keys(Plugin.handlerNames)
            // We handle the message event by ourselves.
            .filter(prop => prop !== "message")
            // Events beginning with an underscore (eg. _command) are internal.
            .filter(prop => prop[0] !== "_");

        // Registers a handler for every Telegram event.
        // It runs the message through the proxy and forwards it to the plugin manager.
        for (const eventName of events) {
            bot.on(
                eventName,
                message => {
                    this.parseEnableDisable(message);
                    Promise.all(
                        this.plugins
                            .filter(plugin => (plugin.plugin.type & Plugin.Type.PROXY) === Plugin.Type.PROXY)
                            .map(plugin => plugin.proxy(eventName, message))
                        )
                       .then(() => this.emit(
                            "message",
                            message
                        ))
                       .then(() => this.emit(
                            eventName,
                            message
                        ))
                        .catch(err => {
                            if (err) this.log.error("Message rejected with error", err);
                        });
                }
            );
        }
    }

    parseEnableDisable(message) {
        // Hardcoded hot-swap plugin
        if (!message.text) return;
        const parts = Util.parseCommand(
            message.text,
            [
                "enable",
                "disable"
            ],
            {
                overrideDeprecation: true
            }
        );
        if (!parts) return;
        if (!this.auth.isAdmin(message.from.id)) return;
        // Syntax: /("enable"|"disable") pluginName [targetChat|"chat"]
        // The string "chat" will enable the plugin in the current chat.
        let [command, pluginName, targetChat] = parts;
        if (targetChat === "chat") targetChat = message.chat.id;
        targetChat = Number(targetChat);
        // Checks if it is already in this.plugins
        const isGloballyEnabled = this.plugins.some(nameMatches(pluginName));
        let response;
        switch (command) {
        case "enable":
            if (targetChat) {
                // Enable it if necessary
                let status = true;
                if (!isGloballyEnabled)
                    status = this.loadAndAdd(pluginName);
                if (status) {
                    const plugin = this.plugins.find(nameMatches(pluginName));
                    plugin.blacklist.delete(targetChat);
                    response = `Plugin enabled successfully for chat ${targetChat}.`;
                } else {
                    response = "Couldn't load plugin.";
                }
            } else if (isGloballyEnabled) {
                response = "Plugin already enabled.";
            } else {
                this.log.info(`Enabling ${pluginName} from message interface`);
                const status = this.loadAndAdd(pluginName);
                response = status ? "Plugin enabled successfully." : "An error occurred.";
            }
            break;
        case "disable":
            if (targetChat) {
                if (isGloballyEnabled) {
                    const plugin = this.plugins.find(nameMatches(pluginName));
                    plugin.blacklist.add(targetChat);
                    response = `Plugin disabled successfully for chat ${targetChat}.`;
                } else {
                    response = "Plugin isn't enabled.";
                }
            } else if (isGloballyEnabled) {
                const outcome = this.removePlugin(pluginName);
                response = outcome ? "Plugin disabled successfully." : "An error occurred.";
            } else {
                response = "Plugin already disabled.";
            }
            break;
        default:
            break;
        }
        this.bot.sendMessage(message.chat.id, response);
    }

    // Instantiates the plugin.
    // Returns the plugin itself.
    loadPlugin(pluginName) {
        const pluginPath = path.join(__dirname, 'plugins', pluginName);
        /* Invalidates the require() cache.
         * This allows for "hot fixes" to plugins: just /disable it, make the
         * required changes, and /enable it again.
         * If the cache wasn't invalidated, the plugin would be loaded from
         * cache rather than from disk, meaning that your changes wouldn't apply.
         * Method: https://stackoverflow.com/a/16060619
         */
        delete require.cache[require.resolve(pluginPath)];
        const ThisPlugin = require(pluginPath);

        this.log.debug(`Required ${pluginName}`);

        const loadedPlugin = new ThisPlugin(this.emitter, this.bot);

        for (const method of Object.getOwnPropertyNames(Object.getPrototypeOf(this.bot))) {
            if (typeof this.bot[method] !== "function") continue;
            if (method === "constructor" || method === "on") continue;
            if (/^_/.test(method)) continue; // Do not expose internal methods
            this.log.debug(`Binding ${method}`);
            loadedPlugin[method] = this.bot[method].bind(this.bot);
        }

        this.log.debug(`Created ${pluginName}.`);
        loadedPlugin.start(this.config, this.auth);

        return loadedPlugin;
    }

    // Adds the plugin to the list of active plugins
    addPlugin(loadedPlugin) {
        this.plugins.push(loadedPlugin);
        this.log.verbose(`Added ${loadedPlugin.plugin.name}.`);
    }

    // Returns true if the plugin was added successfully, false otherwise.
    loadAndAdd(pluginName) {
        try {
            const plugin = this.loadPlugin(pluginName);
            this.log.debug(pluginName + " loaded correctly.");
            this.addPlugin(plugin);
            return true;
        } catch (e) {
            this.log.warn(e);
            this.log.warn(`Failed to initialize plugin ${pluginName}.`);
            return false;
        }
    }

    // Load and add every plugin in the list.
    loadPlugins(pluginNames) {
        this.log.verbose(`Loading and adding ${pluginNames.length} plugins...`);
        Error.stackTraceLimit = 5; // Avoid printing useless data in stack traces

        const log = pluginNames.map(name => this.loadAndAdd(name));
        if (log.some(result => result !== true)) {
            this.log.warn("Some plugins couldn't be loaded.");
        }

        Error.stackTraceLimit = 10; // Reset to default value
    }

    // Returns true if at least one plugin was removed
    removePlugin(pluginName) {
        this.log.verbose(`Removing plugin ${pluginName}`);
        const prevPluginNum = this.plugins.length;
        const isCurrentPlugin = nameMatches(pluginName);
        this.plugins.filter(isCurrentPlugin).forEach(pl => pl.stop());
        this.plugins = this.plugins.filter(pl => !isCurrentPlugin(pl));
        const curPluginNum = this.plugins.length;
        return (prevPluginNum - curPluginNum) > 0;
    }

    stopPlugins() {
        return Promise.all(this.plugins.map(pl => pl.stop()));
    }

    getDatabasePath(plugin) {
        return path.join(__dirname, '..', 'db', 'plugin_' + plugin.plugin.name + '.json');
    }

    startSynchronization() {
        this.plugins.forEach(plugin => {
            const databasePath = this.getDatabasePath(plugin);

            if (fs.existsSync(databasePath)) {
                const {db, blacklist} = JSON.parse(fs.readFileSync(databasePath, "utf8"));

                plugin.blacklist = new Set(blacklist);
                plugin.db = db;
            }
        });

        this.synchronizationInterval = setInterval(() => {
            this.plugins.forEach(plugin => (
                fs.writeFile(
                    this.getDatabasePath(plugin),
                    JSON.stringify({
                        db: plugin.db,
                        blacklist: Array.from(plugin.blacklist)
                    })
                , err => {
                    if (err) {
                        this.log.error("Error database sync", err);
                    }
                })
            ));
        }, SYNC_INTERVAL);
    }

    stopSynchronization() {
        if (this.synchronizationInterval) {
            clearInterval(this.synchronizationInterval);
        }
    }

    emit(event, message) {
        this.log.debug(`Triggered event ${event}`);

        if (event !== "message") {
            // Command emitter
            if (message.text !== undefined && message.entities && message.entities[0].type === "bot_command") {
                const entity = message.entities[0];

                const rawCommand = message.text.slice(entity.offset + 1, entity.offset + entity.length);
                const [command] = rawCommand.replace(/\//, "").split("@");

                let args = [];
                if (entity.offset + entity.length < message.text.length) {
                    args = message.text.slice(entity.offset + entity.length + 1).split(" ");
                }

                this.emitter.emit("_command", {message, command, args});
            } else if (message.query !== undefined) {
                const parts = message.query.split(" ");
                const command = parts[0].toLowerCase();
                const args = parts.length > 1 ? parts.slice(1) : [];
                this.emitter.emit("_inline_command", {message, command, args});
            }
        }

        this.emitter.emit(event, {message});
    }
};
<|endoftext|>