<commit_before>'use strict';

/**
 * Persistence layer, manages loading and saving game objects.
 * A lean persistence back-end module takes care of the actual
 * interaction with a specific storage facility (e.g. files on disk or
 * a database). It must implement the following API:
 * ```
 *     init(config, callback)
 *     close(callback)
 *     read(tsid) -> gameObjectData
 *     write(obj, callback)
 *     del(obj, callback)
 * ```
 * Where `callback` follows the usual Node conventions (`Error` object
 * or `null` as first parameter, function call results second). The
 * `init` and `close` functions are optional. `read` is expected to
 * return data synchronously; this is due to the fact that the GSJS
 * code was not designed with an asynchronous data store in mind. See
 * e.g. the {@link module:data/pbe/rethink|rethink} module for how to
 * work around this limitation using {@link
 * https://github.com/luciotato/waitfor|wait.for/fibers}.
 *
 * Once loaded, game objects are kept in a cache data structure here,
 * to avoid having to reload them from the back-end for each access.
 * Game logic functions do not need to take care of saving modified
 * objects explicitly; this happens automatically at the end of each
 * request processed through {@link RequestContext#run}, with help of
 * the {@link module:data/persProxy|persProxy} wrapper.
 *
 * @module
 */

// public interface
module.exports = {
	init: init,
	get: get,
	create: create,
	postRequestProc: postRequestProc,
};

var assert = require('assert');
var async = require('async');
var gsjsBridge = require('model/gsjsBridge');
var orProxy = require('data/objrefProxy');
var persProxy = require('data/persProxy');
var rpc = require('data/rpc');
var RC = require('data/RequestContext');

// live game object cache
var cache = {};

// persistence back-end
var pbe = null;

/**
 * (Re-)initializes the persistence layer by clearing the live object
 * cache and setting the supplied persistence back-end.
 *
 * @param {object} backEnd persistence back-end module; must implement
 *        the API shown in the above module docs.
 * @param {object} [config] configuration options for back-end module
 * @param {function} [callback] called when persistence layer is ready,
 *        or an error occurred during initialization
 */
function init(backEnd, config, callback) {
	cache = {};
	pbe = backEnd;
	if (pbe && typeof pbe.init === 'function') {
		return pbe.init(config, callback);
	}
	else if (callback) {
		return callback(null);
	}
}

/**
 * Loads the game object with the given TSID from persistence.
 * Depending on whether the GS is "responsible" for this object, it
 * will be wrapped either in a {@link module:data/persProxy|persProxy}
 * or {@link module:data/rpcProxy|rpcProxy}.
 *
 * @param {string} tsid TSID of the object to load
 * @returns {GameObject} the requested object
 * @throws {AssertionError} if no object with the given TSID was found
 */
function load(tsid) {
	assert(pbe, 'persistence back-end not set');
	log.debug('pers.load: %s', tsid);
	var data;
	data = pbe.read(tsid);
	assert(typeof data === 'object', 'no or invalid data for ' + tsid);
	orProxy.proxify(data);
	var obj = gsjsBridge.create(data);
	if (!rpc.isLocal(obj)) {
		// wrap object in RPC proxy and add it to request cache
		obj = rpc.makeProxy(obj);
		RC.getContext().cache[tsid] = obj;
	}
	else {
		// make sure any changes to the object are persisted
		obj = persProxy.makeProxy(obj);
		// send onLoad event if there is a handler
		if (obj.onLoad) {
			obj.onLoad();
		}
		cache[tsid] = obj;
	}
	return obj;
}

/**
 * Retrieves the game object with the given TSID, either from the live
 * object cache or request cache if available there, or from the
 * persistence back-end.
 *
 * @param {string} tsid TSID of the object to retrieve
 * @returns {GameObject} the requested object
 * @throws {AssertionError} if no object with the given TSID was found
 */
function get(tsid) {
	assert(gsjsBridge.isTsid(tsid), 'not a valid TSID: "' + tsid + '"');
	// get "live" objects from server memory
	if (tsid in cache) {
		return cache[tsid];
	}
	// otherwise, see if we already have it in the request cache
	var rc = RC.getContext();
	if (tsid in rc.cache) {
		return rc.cache[tsid];
	}
	// if not, actually load the object
	return load(tsid);
}

/**
 * Creates a new game object of the given type and adds it to
 * persistence. The returned object is wrapped in a ({@link
 * module:data/persProxy|persProxy}) to make sure all future changes
 * to the object are automatically persisted.
 * Also calls the object's GSJS `onCreate` handler, if there is one.
 *
 * @param {function} modelType the desired game object model type (i.e.
 *        a constructor like `Player` or `Geo`)
 * @param {object} [data] additional properties for the object
 * @returns {object} the new object, wrapped in a persistence proxy
 */
function create(modelType, data) {
	log.debug('pers.create: %s%s', modelType.name,
		(typeof data === 'object' && data.tsid) ? ('#' + data.tsid) : '');
	data = data || {};
	var obj = gsjsBridge.create(data, modelType);
	assert(!(obj.tsid in cache), 'object already exists: ' + obj.tsid);
	obj = persProxy.makeProxy(obj);
	cache[obj.tsid] = obj;
	RC.getContext().setDirty(obj);
	if (typeof obj.onCreate === 'function') {
		obj.onCreate();
	}
	return obj;
}

/**
 * Called by {@link RequestContext#run} after processing a request has
 * finished, writes all resulting game object changes to persistence.
 *
 * @param {object} dlist hash containing the modified game objects
 *        (TSIDs as keys, objects as values)
 * @param {object} ulist hash containing game objects to release from
 *        the live object cache
 * @param {string} logmsg optional information for log messages
 * @param {function} [callback] function to be called after persistence
 *        operations have finished
 */
function postRequestProc(dlist, ulist, logmsg, callback) {
	async.each(Object.keys(dlist),
		function iterate(k, iterCallback) {
			var obj = dlist[k];
			//TODO: stop game object timers/intervals
			/*
			if (obj.deleted || k in ulist) {
				// stop timers/intervals for deleted objects and objects about
				// to be unloaded from cache
			}
			*/
			// perform write or del operation; we're not inside the fiber
			// anymore (due to async), so handle errors carefully here
			var op = obj.deleted ? del : write;
			try {
				op(obj, logmsg, function cb(err, res) {
					// silently ignore errors (we're not interested in them here,
					// but we want to call callback when *all* ops have finished)
					iterCallback(null);
				});
			}
			catch (e) {
				log.error(e, 'failed to %s %s', op.name, obj);
				iterCallback(null);
			}
		},
		function cb() {
			for (var k in ulist) {
				unload(ulist[k]);
			}
			if (callback) callback();
		}
	);
}

/**
 * Writes a game object to persistent storage.
 *
 * @param {GameObject} obj game object to write
 * @param {string} logmsg short additional info for log messages
 * @param {function} callback called when write operation has finished,
 *        or in case of errors
 * @private
 */
function write(obj, logmsg, callback) {
	log.debug('pers.write: %s%s', obj.tsid, logmsg ? ' (' + logmsg + ')' : '');
	pbe.write(orProxy.refify(obj.serialize()), function cb(err, res) {
		if (err) log.error(err, 'could not write: %s', obj.tsid);
		if (callback) return callback(err, res);
	});
}

/**
 * Permanently deletes a game object from persistent storage. Also
 * removes the object from the live object cache.
 *
 * @param {GameObject} obj game object to remove
 * @param {string} logmsg short additional info for log messages
 * @param {function} callback called when delete operation has
 *        finished, or in case of errors
 * @private
 */
function del(obj, logmsg, callback) {
	log.debug('pers.del: %s%s', obj.tsid, logmsg ? ' (' + logmsg + ')' : '');
	delete cache[obj.tsid];
	pbe.del(obj.tsid, function db(err, res) {
		if (err) log.error(err, 'could not delete: %s', obj.tsid);
		if (callback) return callback(err, res);
	});
}

/**
 * Removes a game object from the live object cache. This can not check
 * whether there are still references to the object elsewhere (e.g.
 * pending timers), i.e. it cannot guarantee that memory is eventually
 * freed through garbage collection.
 *
 * @param {GameObject} obj game object to unload
 * @param {string} logmsg short additional info for log messages
 * @private
 */
function unload(obj, logmsg) {
	log.debug('pers.unload: %s%s', obj.tsid, logmsg ? ' (' + logmsg + ')' : '');
	if (obj.tsid in cache) {
		//TODO: stop game object timers/intervals
		delete cache[obj.tsid];
	}
}
<commit_msg>bugfix: game object deletion from persistence<commit_after>'use strict';

/**
 * Persistence layer, manages loading and saving game objects.
 * A lean persistence back-end module takes care of the actual
 * interaction with a specific storage facility (e.g. files on disk or
 * a database). It must implement the following API:
 * ```
 *     init(config, callback)
 *     close(callback)
 *     read(tsid) -> gameObjectData
 *     write(obj, callback)
 *     del(obj, callback)
 * ```
 * Where `callback` follows the usual Node conventions (`Error` object
 * or `null` as first parameter, function call results second). The
 * `init` and `close` functions are optional. `read` is expected to
 * return data synchronously; this is due to the fact that the GSJS
 * code was not designed with an asynchronous data store in mind. See
 * e.g. the {@link module:data/pbe/rethink|rethink} module for how to
 * work around this limitation using {@link
 * https://github.com/luciotato/waitfor|wait.for/fibers}.
 *
 * Once loaded, game objects are kept in a cache data structure here,
 * to avoid having to reload them from the back-end for each access.
 * Game logic functions do not need to take care of saving modified
 * objects explicitly; this happens automatically at the end of each
 * request processed through {@link RequestContext#run}, with help of
 * the {@link module:data/persProxy|persProxy} wrapper.
 *
 * @module
 */

// public interface
module.exports = {
	init: init,
	get: get,
	create: create,
	postRequestProc: postRequestProc,
};

var assert = require('assert');
var async = require('async');
var gsjsBridge = require('model/gsjsBridge');
var orProxy = require('data/objrefProxy');
var persProxy = require('data/persProxy');
var rpc = require('data/rpc');
var RC = require('data/RequestContext');

// live game object cache
var cache = {};

// persistence back-end
var pbe = null;

/**
 * (Re-)initializes the persistence layer by clearing the live object
 * cache and setting the supplied persistence back-end.
 *
 * @param {object} backEnd persistence back-end module; must implement
 *        the API shown in the above module docs.
 * @param {object} [config] configuration options for back-end module
 * @param {function} [callback] called when persistence layer is ready,
 *        or an error occurred during initialization
 */
function init(backEnd, config, callback) {
	cache = {};
	pbe = backEnd;
	if (pbe && typeof pbe.init === 'function') {
		return pbe.init(config, callback);
	}
	else if (callback) {
		return callback(null);
	}
}

/**
 * Loads the game object with the given TSID from persistence.
 * Depending on whether the GS is "responsible" for this object, it
 * will be wrapped either in a {@link module:data/persProxy|persProxy}
 * or {@link module:data/rpcProxy|rpcProxy}.
 *
 * @param {string} tsid TSID of the object to load
 * @returns {GameObject} the requested object
 * @throws {AssertionError} if no object with the given TSID was found
 */
function load(tsid) {
	assert(pbe, 'persistence back-end not set');
	log.debug('pers.load: %s', tsid);
	var data;
	data = pbe.read(tsid);
	assert(typeof data === 'object', 'no or invalid data for ' + tsid);
	orProxy.proxify(data);
	var obj = gsjsBridge.create(data);
	if (!rpc.isLocal(obj)) {
		// wrap object in RPC proxy and add it to request cache
		obj = rpc.makeProxy(obj);
		RC.getContext().cache[tsid] = obj;
	}
	else {
		// make sure any changes to the object are persisted
		obj = persProxy.makeProxy(obj);
		// send onLoad event if there is a handler
		if (obj.onLoad) {
			obj.onLoad();
		}
		cache[tsid] = obj;
	}
	return obj;
}

/**
 * Retrieves the game object with the given TSID, either from the live
 * object cache or request cache if available there, or from the
 * persistence back-end.
 *
 * @param {string} tsid TSID of the object to retrieve
 * @returns {GameObject} the requested object
 * @throws {AssertionError} if no object with the given TSID was found
 */
function get(tsid) {
	assert(gsjsBridge.isTsid(tsid), 'not a valid TSID: "' + tsid + '"');
	// get "live" objects from server memory
	if (tsid in cache) {
		return cache[tsid];
	}
	// otherwise, see if we already have it in the request cache
	var rc = RC.getContext();
	if (tsid in rc.cache) {
		return rc.cache[tsid];
	}
	// if not, actually load the object
	return load(tsid);
}

/**
 * Creates a new game object of the given type and adds it to
 * persistence. The returned object is wrapped in a ({@link
 * module:data/persProxy|persProxy}) to make sure all future changes
 * to the object are automatically persisted.
 * Also calls the object's GSJS `onCreate` handler, if there is one.
 *
 * @param {function} modelType the desired game object model type (i.e.
 *        a constructor like `Player` or `Geo`)
 * @param {object} [data] additional properties for the object
 * @returns {object} the new object, wrapped in a persistence proxy
 */
function create(modelType, data) {
	log.debug('pers.create: %s%s', modelType.name,
		(typeof data === 'object' && data.tsid) ? ('#' + data.tsid) : '');
	data = data || {};
	var obj = gsjsBridge.create(data, modelType);
	assert(!(obj.tsid in cache), 'object already exists: ' + obj.tsid);
	obj = persProxy.makeProxy(obj);
	cache[obj.tsid] = obj;
	RC.getContext().setDirty(obj);
	if (typeof obj.onCreate === 'function') {
		obj.onCreate();
	}
	return obj;
}

/**
 * Called by {@link RequestContext#run} after processing a request has
 * finished, writes all resulting game object changes to persistence.
 *
 * @param {object} dlist hash containing the modified game objects
 *        (TSIDs as keys, objects as values)
 * @param {object} ulist hash containing game objects to release from
 *        the live object cache
 * @param {string} logmsg optional information for log messages
 * @param {function} [callback] function to be called after persistence
 *        operations have finished
 */
function postRequestProc(dlist, ulist, logmsg, callback) {
	async.each(Object.keys(dlist),
		function iterate(k, iterCallback) {
			var obj = dlist[k];
			//TODO: stop game object timers/intervals
			/*
			if (obj.deleted || k in ulist) {
				// stop timers/intervals for deleted objects and objects about
				// to be unloaded from cache
			}
			*/
			// perform write or del operation; we're not inside the fiber
			// anymore (due to async), so handle errors carefully here
			var op = obj.deleted ? del : write;
			try {
				op(obj, logmsg, function cb(err, res) {
					// silently ignore errors (we're not interested in them here,
					// but we want to call callback when *all* ops have finished)
					iterCallback(null);
				});
			}
			catch (e) {
				log.error(e, 'failed to %s %s', op.name, obj);
				iterCallback(null);
			}
		},
		function cb() {
			for (var k in ulist) {
				unload(ulist[k]);
			}
			if (callback) callback();
		}
	);
}

/**
 * Writes a game object to persistent storage.
 *
 * @param {GameObject} obj game object to write
 * @param {string} logmsg short additional info for log messages
 * @param {function} callback called when write operation has finished,
 *        or in case of errors
 * @private
 */
function write(obj, logmsg, callback) {
	log.debug('pers.write: %s%s', obj.tsid, logmsg ? ' (' + logmsg + ')' : '');
	pbe.write(orProxy.refify(obj.serialize()), function cb(err, res) {
		if (err) log.error(err, 'could not write: %s', obj.tsid);
		if (callback) return callback(err, res);
	});
}

/**
 * Permanently deletes a game object from persistent storage. Also
 * removes the object from the live object cache.
 *
 * @param {GameObject} obj game object to remove
 * @param {string} logmsg short additional info for log messages
 * @param {function} callback called when delete operation has
 *        finished, or in case of errors
 * @private
 */
function del(obj, logmsg, callback) {
	log.debug('pers.del: %s%s', obj.tsid, logmsg ? ' (' + logmsg + ')' : '');
	delete cache[obj.tsid];
	pbe.del(obj, function db(err, res) {
		if (err) log.error(err, 'could not delete: %s', obj.tsid);
		if (callback) return callback(err, res);
	});
}

/**
 * Removes a game object from the live object cache. This can not check
 * whether there are still references to the object elsewhere (e.g.
 * pending timers), i.e. it cannot guarantee that memory is eventually
 * freed through garbage collection.
 *
 * @param {GameObject} obj game object to unload
 * @param {string} logmsg short additional info for log messages
 * @private
 */
function unload(obj, logmsg) {
	log.debug('pers.unload: %s%s', obj.tsid, logmsg ? ' (' + logmsg + ')' : '');
	if (obj.tsid in cache) {
		//TODO: stop game object timers/intervals
		delete cache[obj.tsid];
	}
}
<|endoftext|>


<commit_before>import Ember from 'ember';
import layout from '../templates/components/g-map-polyline';
import GMapComponent from './g-map';
import compact from '../utils/compact';

const { isEmpty, isPresent, observer, computed, run, assert, typeOf } = Ember;

const allowedPolylineOptions = Ember.A(['strokeColor', 'strokeWeight', 'strokeOpacity', 'zIndex', 'geodesic', 'clickable', 'draggable', 'visible', 'path']);

const GMapPolylineComponent = Ember.Component.extend({
  layout: layout,
  classNames: ['g-map-polyline'],

  map: computed.alias('mapContext.map'),

  init() {
    this._super(arguments);
    this.infowindow = null;
    this.set('coordinates', Ember.A());
    if (isEmpty(this.get('group'))) {
      this.set('group', null);
    }

    const mapContext = this.get('mapContext');
    assert('Must be inside {{#g-map}} component with context set', mapContext instanceof GMapComponent);

    mapContext.registerPolyline(this);
  },

  didInsertElement() {
    this._super();
    if (isEmpty(this.get('polyline'))) {
      const options = compact(this.getProperties(allowedPolylineOptions));
      const polyline = new google.maps.Polyline(options);
      this.set('polyline', polyline);
    }
    this.setMap();
    this.setPath();
    this.updatePolylineOptions();
    this.setOnClick();
    this.setOnDrag();
  },

  willDestroyElement() {
    this.unsetPolylineFromMap();
    this.get('mapContext').unregisterPolyline(this);
  },

  registerCoordinate(coordinate) {
    this.get('coordinates').addObject(coordinate);
  },

  unregisterCoordinate(coordinate) {
    this.get('coordinates').removeObject(coordinate);
    this.setPath();
  },

  unsetPolylineFromMap() {
    const polyline = this.get('polyline');
    if (isPresent(polyline)) {
      polyline.setMap(null);
    }
  },

  mapWasSet: observer('map', function() {
    run.once(this, 'setMap');
  }),

  setMap() {
    const map = this.get('map');
    const polyline = this.get('polyline');

    if (isPresent(polyline) && isPresent(map)) {
      polyline.setMap(map);
    }
  },

  setPath() {
    const polyline = this.get('polyline');
    const coordinates = this.get('coordinates');
    const path = this.get('path');

    if (isPresent(polyline) && isPresent(coordinates)) {
      let coordArray = Ember.A(this.get('coordinates').mapBy('coordinate')).compact();
      polyline.setPath(coordArray);
    }

    if (isPresent(polyline) && isPresent(path)) {
      polyline.setPath(path);
    }
  },

  polylineOptionsChanged: observer(...allowedPolylineOptions, function() {
    run.once(this, 'updatePolylineOptions');
  }),

  updatePolylineOptions() {
    const polyline = this.get('polyline');
    const options = compact(this.getProperties(allowedPolylineOptions));

    if (isPresent(polyline) && isPresent(Object.keys(options))) {
      polyline.setOptions(options);
    }
  },

  setOnClick() {
    const polyline = this.get('polyline');
    if (isPresent(polyline)) {
      polyline.addListener('click', (e) => this.sendOnClick(e));
    }
  },

  sendOnClick(e) {
    const { onClick } = this.attrs;
    const polyline = this.get('polyline');

    if (typeOf(onClick) === 'function') {
      onClick(e, polyline);
    } else {
      this.sendAction('onClick', e, polyline);
    }
  },

  setOnDrag() {
    const polyline = this.get('polyline');
    if (isPresent(polyline)) {
      polyline.addListener('dragend', (e) => this.sendOnDrag(e));
    }
  },

  sendOnDrag(e) {
    const { onDrag } = this.attrs;
    const polyline = this.get('polyline');

    if (typeOf(onDrag) === 'function') {
      onDrag(e, polyline);
    } else {
      this.sendAction('onDrag', e, polyline);
    }
  }
});

GMapPolylineComponent.reopenClass({
  positionalParams: ['mapContext']
});

export default GMapPolylineComponent;
<commit_msg>add icons to allowed options array<commit_after>import Ember from 'ember';
import layout from '../templates/components/g-map-polyline';
import GMapComponent from './g-map';
import compact from '../utils/compact';

const { isEmpty, isPresent, observer, computed, run, assert, typeOf } = Ember;

const allowedPolylineOptions = Ember.A(['strokeColor', 'strokeWeight', 'strokeOpacity', 'zIndex', 'geodesic', 'icons', 'clickable', 'draggable', 'visible', 'path']);

const GMapPolylineComponent = Ember.Component.extend({
  layout: layout,
  classNames: ['g-map-polyline'],

  map: computed.alias('mapContext.map'),

  init() {
    this._super(arguments);
    this.infowindow = null;
    this.set('coordinates', Ember.A());
    if (isEmpty(this.get('group'))) {
      this.set('group', null);
    }

    const mapContext = this.get('mapContext');
    assert('Must be inside {{#g-map}} component with context set', mapContext instanceof GMapComponent);

    mapContext.registerPolyline(this);
  },

  didInsertElement() {
    this._super();
    if (isEmpty(this.get('polyline'))) {
      const options = compact(this.getProperties(allowedPolylineOptions));
      const polyline = new google.maps.Polyline(options);
      this.set('polyline', polyline);
    }
    this.setMap();
    this.setPath();
    this.updatePolylineOptions();
    this.setOnClick();
    this.setOnDrag();
  },

  willDestroyElement() {
    this.unsetPolylineFromMap();
    this.get('mapContext').unregisterPolyline(this);
  },

  registerCoordinate(coordinate) {
    this.get('coordinates').addObject(coordinate);
  },

  unregisterCoordinate(coordinate) {
    this.get('coordinates').removeObject(coordinate);
    this.setPath();
  },

  unsetPolylineFromMap() {
    const polyline = this.get('polyline');
    if (isPresent(polyline)) {
      polyline.setMap(null);
    }
  },

  mapWasSet: observer('map', function() {
    run.once(this, 'setMap');
  }),

  setMap() {
    const map = this.get('map');
    const polyline = this.get('polyline');

    if (isPresent(polyline) && isPresent(map)) {
      polyline.setMap(map);
    }
  },

  setPath() {
    const polyline = this.get('polyline');
    const coordinates = this.get('coordinates');
    const path = this.get('path');

    if (isPresent(polyline) && isPresent(coordinates)) {
      let coordArray = Ember.A(this.get('coordinates').mapBy('coordinate')).compact();
      polyline.setPath(coordArray);
    }

    if (isPresent(polyline) && isPresent(path)) {
      polyline.setPath(path);
    }
  },

  polylineOptionsChanged: observer(...allowedPolylineOptions, function() {
    run.once(this, 'updatePolylineOptions');
  }),

  updatePolylineOptions() {
    const polyline = this.get('polyline');
    const options = compact(this.getProperties(allowedPolylineOptions));

    if (isPresent(polyline) && isPresent(Object.keys(options))) {
      polyline.setOptions(options);
    }
  },

  setOnClick() {
    const polyline = this.get('polyline');
    if (isPresent(polyline)) {
      polyline.addListener('click', (e) => this.sendOnClick(e));
    }
  },

  sendOnClick(e) {
    const { onClick } = this.attrs;
    const polyline = this.get('polyline');

    if (typeOf(onClick) === 'function') {
      onClick(e, polyline);
    } else {
      this.sendAction('onClick', e, polyline);
    }
  },

  setOnDrag() {
    const polyline = this.get('polyline');
    if (isPresent(polyline)) {
      polyline.addListener('dragend', (e) => this.sendOnDrag(e));
    }
  },

  sendOnDrag(e) {
    const { onDrag } = this.attrs;
    const polyline = this.get('polyline');

    if (typeOf(onDrag) === 'function') {
      onDrag(e, polyline);
    } else {
      this.sendAction('onDrag', e, polyline);
    }
  }
});

GMapPolylineComponent.reopenClass({
  positionalParams: ['mapContext']
});

export default GMapPolylineComponent;
<|endoftext|>


<commit_before>const { CollegiateDictionary, WordNotFoundError } = require('mw-dict')
const CONFIG_KEY_DICT = 'merriamWebsterDictKey'

exports.init = async bot => {
  if (bot.config[CONFIG_KEY_DICT]) {
    await initDictClient()
  }
}

const initDictClient = async () => {
  this.dictClient = await new CollegiateDictionary(bot.config[CONFIG_KEY_DICT])
  return true
}

exports.run = async (bot, msg, args) => {
  if (!bot.config[CONFIG_KEY_DICT]) {
    return msg.error(`Merriam-Webster Dictionary key (\`${CONFIG_KEY_DICT}\`) is missing from config.json file!`)
  }

  if (!this.dictClient) {
    await initDictClient()
  }

  if (msg.guild) {
    bot.utils.assertEmbedPermission(msg.channel, msg.member)
  }

  const parsed = bot.utils.parseArgs(args, 'i:')

  if (!parsed.leftover.length) {
    return msg.error('You must specify something to search!')
  }

  const query = parsed.leftover.join(' ')
  const y = 'Merriam-Webster'

  await msg.edit(`${PROGRESS}Searching for \`${query}\` on ${y}\u2026`)

  let resp
  try {
    resp = await this.dictClient.lookup(query)
  } catch (err) {
    if (err instanceof WordNotFoundError) {
      return msg.edit(`${FAILURE}\`${query}\` was not found!`, {
        embed: bot.utils.embed(
          `Suggestions`,
          err.suggestions.join('; '),
          [],
          {
            footer: y,
            color: '#ff0000'
          }
        )
      })
    } else {
      throw new Error(err)
    }
  }

  let index = 0
  if (parsed.options.i) {
    index = parseInt(parsed.options.i)

    if (isNaN(index)) {
      return msg.error('Index must be a number!')
    } else {
      index--
    }
  }

  const selected = resp[index]
  const embed = bot.utils.formatEmbed(
    `${selected.word}${selected.functional_label ? ` (${selected.functional_label})` : ''}`,
    selected.definition.map(d => {
      // All instances of .filter(d => d) used to filter out 'false' from skipping object in line 102
      if (d.meanings) {
        return _beautify(d, selected.word)
      } else if (d.senses) {
        return `**${d.number}:**\n${d.senses.map(s => {
          return `    ${_beautify(s, selected.word)}`
        }).filter(d => d.trim() !== 'false').join('\n')}`
      } else {
        console.log(require('util').inspect(d))
        return '**WARN:** Unexpected behavior for this definition. Check your console\u2026'
      }
    }).filter(d => d).join('\n'),
    [
      ['Link', `**https://www.merriam-webster.com/dictionary/${selected.word.replace(/ /g, '+')}**`],
      ['Match(es)', resp.map((l, i) => `**${i + 1}:** ${l.word}`).join('; ')]
    ],
    {
      footer: y,
      color: '#2d5f7c'
    }
  )

  return msg.edit(
    `Search result of \`${query}\` at index \`${index + 1}/${resp.length}\` on ${y}:`,
    { embed }
  )
}

const _beautify = (m, word) => {
  if (!m.meanings) {
    console.log(require('util').inspect(m))
    console.info('Skipping object for now\u2026')
    return false
  }

  let _temp = m.number ? `**${m.number}**${m.status ? ` *${m.status}*` : ''} : ` : ''

  _temp += m.meanings.map((m, i, a) => {
    if (i === 0 && m.startsWith(':')) {
      m = m.slice(2)
    } else if (i > 0 && !m.startsWith(':')) {
      m = `*${m}*`
    }

    if (i !== a.length - 1 && a[i + 1] !== undefined && !a[i + 1].startsWith(':')) {
      m += '; '
    }

    return m
  }).join('').trim()

  if (m.synonyms) {
    _temp += m.synonyms.map(s => `__${s}__`).join(', ')
  }

  if (m.illustrations) {
    _temp += m.illustrations.map(i => ` \u2022 ${i}`).join('')
  }

  return _temp.replace(new RegExp(`\\b${word}\\b`), `*${word}*`).trim()
}

exports.info = {
  name: 'dictionary',
  usage: 'dictionary [-i] <query>',
  description: 'Looks up a word on Merriam-Webster',
  aliases: ['dict'],
  options: [
    {
      name: '-i',
      usage: '-i <index>',
      description: 'Sets index of which definition to show'
    }
  ]
}
<commit_msg>Tiny improvement to dictionary command's display<commit_after>const { CollegiateDictionary, WordNotFoundError } = require('mw-dict')
const CONFIG_KEY_DICT = 'merriamWebsterDictKey'

exports.init = async bot => {
  if (bot.config[CONFIG_KEY_DICT]) {
    await initDictClient()
  }
}

const initDictClient = async () => {
  this.dictClient = await new CollegiateDictionary(bot.config[CONFIG_KEY_DICT])
  return true
}

exports.run = async (bot, msg, args) => {
  if (!bot.config[CONFIG_KEY_DICT]) {
    return msg.error(`Merriam-Webster Dictionary key (\`${CONFIG_KEY_DICT}\`) is missing from config.json file!`)
  }

  if (!this.dictClient) {
    await initDictClient()
  }

  if (msg.guild) {
    bot.utils.assertEmbedPermission(msg.channel, msg.member)
  }

  const parsed = bot.utils.parseArgs(args, 'i:')

  if (!parsed.leftover.length) {
    return msg.error('You must specify something to search!')
  }

  const query = parsed.leftover.join(' ')
  const y = 'Merriam-Webster'

  await msg.edit(`${PROGRESS}Searching for \`${query}\` on ${y}\u2026`)

  let resp
  try {
    resp = await this.dictClient.lookup(query)
  } catch (err) {
    if (err instanceof WordNotFoundError) {
      return msg.edit(`${FAILURE}\`${query}\` was not found!`, {
        embed: bot.utils.embed(
          `Suggestions`,
          err.suggestions.join('; '),
          [],
          {
            footer: y,
            color: '#ff0000'
          }
        )
      })
    } else {
      throw new Error(err)
    }
  }

  let index = 0
  if (parsed.options.i) {
    index = parseInt(parsed.options.i)

    if (isNaN(index)) {
      return msg.error('Index must be a number!')
    } else {
      index--
    }
  }

  const selected = resp[index]
  const embed = bot.utils.formatEmbed(
    `${selected.word}${selected.functional_label ? ` (${selected.functional_label})` : ''}`,
    selected.definition.map(d => {
      // All instances of .filter(d => d) used to filter out 'false' from skipping object in line 102
      if (d.meanings) {
        return _beautify(d, selected.word)
      } else if (d.senses) {
        return `**${d.number}:**\n${d.senses.map(s => {
          return `    ${_beautify(s, selected.word)}`
        }).filter(d => d.trim() !== 'false').join('\n')}`
      } else {
        console.log(require('util').inspect(d))
        return '**WARN:** Unexpected behavior for this definition. Check your console\u2026'
      }
    }).filter(d => d).join('\n'),
    [
      ['Link', `**https://www.merriam-webster.com/dictionary/${selected.word.replace(/ /g, '+')}**`],
      ['Match(es)', resp.map((l, i) => `**${i + 1}** : ${l.word}`).join('; ')]
    ],
    {
      footer: y,
      color: '#2d5f7c'
    }
  )

  return msg.edit(
    `Search result of \`${query}\` at index \`${index + 1}/${resp.length}\` on ${y}:`,
    { embed }
  )
}

const _beautify = (m, word) => {
  if (!m.meanings) {
    console.log(require('util').inspect(m))
    console.info('Skipping object for now\u2026')
    return false
  }

  let _temp = m.number ? `**${m.number}**${m.status ? ` *${m.status}*` : ''} : ` : ''

  _temp += m.meanings.map((m, i, a) => {
    if (i === 0 && m.startsWith(':')) {
      m = m.slice(2)
    } else if (i > 0 && !m.startsWith(':')) {
      m = `*${m}* `
    }

    if (i !== a.length - 1 && a[i + 1] !== undefined && !a[i + 1].startsWith(':')) {
      m += '; '
    }

    return m
  }).join('').trim()

  if (m.synonyms) {
    _temp += m.synonyms.map(s => `__${s}__`).join(', ')
  }

  if (m.illustrations) {
    _temp += m.illustrations.map(i => ` \u2022 ${i}`).join('')
  }

  return _temp.replace(new RegExp(`\\b${word}\\b`), `*${word}*`).trim()
}

exports.info = {
  name: 'dictionary',
  usage: 'dictionary [-i] <query>',
  description: 'Looks up a word on Merriam-Webster',
  aliases: ['dict'],
  options: [
    {
      name: '-i',
      usage: '-i <index>',
      description: 'Sets index of which definition to show'
    }
  ]
}
<|endoftext|>


<commit_before>/* jslint node: true, esnext: true */

"use strict";

const request = require('request');
const http = require('http');
const url = require('url');

exports.endpointImplementations = {
	"http": {
		"direction": "inout(pull,push)",
		implementation(generator) {
			//console.log(`implementation: ${JSON.stringify(this)} ${this.isIn}`);

			if (this.isIn) {
				const go = generator();

				go.next();

				const o = url.parse(this.target);
				const port = o.port || 80;

				const server = http.createServer(function (req, res) {
					//console.log(`server got: ${JSON.stringify(req.headers)}`);
					go.next({
						info: req.headers,
						stream: req.stream
					});

					res.writeHead(200, {
						'Content-Type': 'text/plain'
					});
					res.end('ok');
				});

				//console.log(`listen ${port}`);
				server.listen(port, '127.0.0.1');
				return go;
			} else
			if (this.isOut) {
				const myGen = function* () {
					let r = yield;
					request.post({
							url: this.target,
							formData: {
								content: r.stream,
							}
						},
						function optionalCallback(err, httpResponse, body) {
							console.log(`http post done: ${err}`);
						});
				};
				const go = myGen();
				go.next();
				return go;
			}
		}
	}
};
<commit_msg>gardening<commit_after>/* jslint node: true, esnext: true */

"use strict";

const request = require('request');
const http = require('http');
const url = require('url');

exports.endpointImplementations = {
	"http": {
		"direction": "inout(pull,push)",
		implementation(generator) {
			//console.log(`implementation: ${JSON.stringify(this)} ${this.isIn}`);

			if (this.isIn) {
				const go = generator();

				go.next();

				const o = url.parse(this.target);
				const port = o.port || 80;

				const server = http.createServer(function (req, res) {
					//console.log(`server got: ${JSON.stringify(req.headers)}`);
					go.next({
						info: req.headers,
						stream: req.stream
					});

					res.writeHead(200, {
						'Content-Type': 'text/plain'
					});
					res.end('ok');
				});

				server.listen(port, '127.0.0.1',function(error) {
					//console.log(`listen ${port} ${error}`);
				});
				return go;
			} else
			if (this.isOut) {
				const myGen = function* () {
					let r = yield;
					request.post({
							url: this.target,
							formData: {
								content: r.stream,
							}
						},
						function optionalCallback(err, httpResponse, body) {
							//console.log(`http post done: ${err}`);
						});
				};
				const go = myGen();
				go.next();
				return go;
			}
		}
	}
};
<|endoftext|>


<commit_before>
$(document).ready(function(){

  var canvas = $('#canvas')[0];
  var ctx = canvas.getContext('2d');
  var raf;

  var paddle = new Paddle()
  paddle.drawPaddle = function() {
    ctx.fillRect(this.x,this.y,this.width,this.height);
    ctx.fillStyle = "red";
  }

  var ball = new Ball()
  ball.drawBall = function(){
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  }

  var move = function(){
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ball.drawBall()
    paddle.drawPaddle()
    ball.x += ball.velocityX;
    ball.y += ball.velocityY;
    raf = window.requestAnimationFrame(move);

    if (ball.y + ball.velocityY > canvas.height || ball.y + ball.velocityY < 0) {
      ball.velocityY = -ball.velocityY;
    }
    if (ball.x + ball.velocityX > canvas.width || ball.x + ball.velocityX < 0) {
      ball.velocityX = -ball.velocityX;
    }

    if (ball.y > canvas.height - 30 ){
      if (ball.x > paddle.surfaceRange()[0] && ball.x < paddle.surfaceRange()[1]){
        console.log('hit');
        ball.velocityY = -ball.velocityY;
      };
    }

  }

  $(document).keydown(function(event){

    if(event.which == 37){ // move paddle left
      if(paddle.leftPaddleBoundary()){
        return paddle.x -= 10;
      } else {
        console.log('Reached left boundary')
      }
    };
    if(event.which == 39){ // move paddle right
      if(paddle.rightPaddleBoundary()){ //if paddle has reached the edge of the game screen, allow no further movement
        paddle.x += 10;
      } else {
        console.log('Reached right boundary')
      }
    };
  });

  raf = window.requestAnimationFrame(move);

});
<commit_msg>Add tick function which allows for smoother horizontal paddle movement<commit_after>
$(document).ready(function(){

  var canvas = $('#canvas')[0];
  var ctx = canvas.getContext('2d');
  var raf;

  var paddle = new Paddle()
  paddle.drawPaddle = function() {
    ctx.fillRect(this.x,this.y,this.width,this.height);
    ctx.fillStyle = "red";
  }

  var ball = new Ball()
  ball.drawBall = function(){
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  }

  var move = function(){
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ball.drawBall()
    paddle.drawPaddle()
    ball.x += ball.velocityX;
    ball.y += ball.velocityY;
    raf = window.requestAnimationFrame(move);

    if (ball.y + ball.velocityY > canvas.height || ball.y + ball.velocityY < 0) {
      ball.velocityY = -ball.velocityY;
    }
    if (ball.x + ball.velocityX > canvas.width || ball.x + ball.velocityX < 0) {
      ball.velocityX = -ball.velocityX;
    }

    if (ball.y > canvas.height - 30 ){
      if (ball.x > paddle.surfaceRange()[0] && ball.x < paddle.surfaceRange()[1]){
        console.log('hit');
        ball.velocityY = -ball.velocityY;
      };
    }
  }

var refreshRate = 10, leftKeyDown = false, rightKeyDown = false

  $(document).keydown(function(event){

    if(event.which == 37){
      leftKeyDown = true
    };
    if(event.which == 39){
      rightKeyDown = true
    };
  });

  $(document).keyup(function(event){

    if(event.which == 37){
      leftKeyDown = false
    };
    if(event.which == 39){
      rightKeyDown = false
    };
  });

  var tick = function(){

    if(leftKeyDown){
      if(paddle.leftPaddleBoundary()){
        paddle.x -= 5;
        console.log(paddle.x);
      } else {
        console.log('Reached left boundary')
      }
    } else if(rightKeyDown){
      if(paddle.rightPaddleBoundary()){
        paddle.x += 5;
        console.log(paddle.x);
      } else {
        console.log('Reached right boundary') //if paddle has reached the edge of the game screen, allow no further movement
      }
    }

    setTimeout(tick, refreshRate);
  }

  tick();

  raf = window.requestAnimationFrame(move);

});
<|endoftext|>


<commit_before>/* global tinymce: true */
import Ember from 'ember';
const {observer, on, run} = Ember;

export default Ember.Component.extend({
  editor: null,
  classNames: ['tinymce-editor'],
  tagName: 'textarea',
  _contentChangedListener: null,
  changeDebounce: 10,
  options: {},

  valueChanged: observer('value', function() {
    let {editor, value} = this.getProperties('editor', 'value');
    if (editor && editor.getContent() !== value) {
      editor.setContent(value || '');
    }
  }),

  onValueChanged(value) {
    this.set('value', value);
  },

  contentChanged(editor) {
    let content = editor.getContent();
    if (editor.isDirty() && content !== this.get('value')) {
      this.onValueChanged(editor.getContent());
      editor.setDirty(true);
    }
  },

  debounceContentChanged(editor, time){
    run.debounce(this, this.contentChanged, editor, time);
  },

  setEvents: observer('editor', function() {
    let {changeDebounce, editor} = this.getProperties('changeDebounce', 'editor');

    if (!editor){
      return;
    }

    editor.on('change keyup keydown keypress mousedown',
              run.bind(this, this.debounceContentChanged, editor, changeDebounce));
  }),

  initTiny: on('didInsertElement', observer('options', function() {
    let {options, editor} = this.getProperties('options', 'editor');

    let initFunction = (editor) => {
      this.set('editor', editor);
      this.get('editor').setContent(this.get('value') || ''); //Set content with default text
    };

    let customOptions = {
      selector: `#${this.get('elementId')}`,
      init_instance_callback: run.bind(this, initFunction)
    };

    if (editor){
      editor.setContent('');
      editor.destroy();
    }

    run.later( () => {
      if (typeof tinymce === 'undefined') { return; }
      tinymce.init(Ember.assign({}, options, customOptions));
    }, 10);
  })),

  cleanUp: on('willDestroyElement', function() {
    let editor = this.get('editor');
    if (editor) {
      editor.off('change keyup keydown keypress mousedown');
      editor.destroy();
    }
  })
});
<commit_msg>Code style<commit_after>/* global tinymce: true */
import Ember from 'ember';
const { observer, on, run } = Ember;

export default Ember.Component.extend({
  editor: null,
  classNames: ['tinymce-editor'],
  tagName: 'textarea',
  _contentChangedListener: null,
  changeDebounce: 10,
  options: {},

  valueChanged: observer('value', function() {
    let {editor, value} = this.getProperties('editor', 'value');
    if (editor && editor.getContent() !== value) {
      editor.setContent(value || '');
    }
  }),

  onValueChanged(value) {
    this.set('value', value);
  },

  contentChanged(editor) {
    let content = editor.getContent();
    if (editor.isDirty() && content !== this.get('value')) {
      this.onValueChanged(editor.getContent());
      editor.setDirty(true);
    }
  },

  debounceContentChanged(editor, time){
    run.debounce(this, this.contentChanged, editor, time);
  },

  setEvents: observer('editor', function() {
    let {changeDebounce, editor} = this.getProperties('changeDebounce', 'editor');

    if (!editor){
      return;
    }

    editor.on('change keyup keydown keypress mousedown',
              run.bind(this, this.debounceContentChanged, editor, changeDebounce));
  }),

  initTiny: on('didInsertElement', observer('options', function() {
    let {options, editor} = this.getProperties('options', 'editor');

    let initFunction = (editor) => {
      this.set('editor', editor);
      this.get('editor').setContent(this.get('value') || ''); //Set content with default text
    };

    let customOptions = {
      selector: `#${this.get('elementId')}`,
      init_instance_callback: run.bind(this, initFunction)
    };

    if (editor){
      editor.setContent('');
      editor.destroy();
    }

    run.later( () => {
      if (typeof tinymce === 'undefined') { return; }
      tinymce.init(Ember.assign({}, options, customOptions));
    }, 10);
  })),

  cleanUp: on('willDestroyElement', function() {
    let editor = this.get('editor');
    if (editor) {
      editor.off('change keyup keydown keypress mousedown');
      editor.destroy();
    }
  })
});
<|endoftext|>


<commit_before>/**
  DateUtil
  --------

  This service provides helper function to create request periods from
  Javascript date objects.

  Typically, you use the getPeriod(dates, granularity) function, which in
  its turn calls an appropriate function, depending on the granularity.

  The dates parameter can be either a single date, which returns a single
  period (e.g. YYYYMMDD), or an array with two Dates (range), which returns
  a period range, (e.g. YYYYMM-YYYYMM)

  To parse a period into a date, use the getDate(period) function. This will
  return a Date with the provided period. Periods without months or days will
  have the missing value set to 1, i.e. getDate("2004") will return a the Date
  2014-01-01.
**/

module.exports = function(ngModule) {
  ngModule.factory('emDateUtil', function() {
    function _zeroPaddedString(number) {
      if (number == null) { return ''; }

      return (number < 10 ? '0' : '') + number;
    }

    function periodFormat(year, month, day) {
      if (month != null) { month ++; }

      return _zeroPaddedString(year) + _zeroPaddedString(month) + _zeroPaddedString(day);
    }

    function periodsFormat(dates, func) {
      if (!angular.isArray(dates)) { return func(dates); }
      return dates.map(func).join('-');
    }

    function getYearPeriod(dates) {
      return periodsFormat(dates, function(date) {
        return periodFormat(date.getFullYear());
      });
    }

    function getMonthPeriod(dates) {
      return periodsFormat(dates, function(date) {
        return periodFormat(date.getFullYear(), date.getMonth());
      });
    }

    function getDayPeriod(dates) {
      return periodsFormat(dates, function(date) {
        return periodFormat(date.getFullYear(), date.getMonth(), date.getDate());
      });
    }

    function getPeriod(dates, granularity) {
      if (granularity === 'month') { return getYearPeriod(dates); }
      if (granularity === 'day') { return getMonthPeriod(dates); }

      return getDayPeriod(dates);
    }

    function getDate(period) {
      if (period.length < 4) { return new Date(); }

      var components = [parseInt(period.substr(0, 4), 10)];
      var i = 0, len = period.length;

      while (4 + (i * 2) < len) {
        components[i + 1] = parseInt(period.substr(4 + (2 * i), 2));
        i++;
      }

      return new Date(components[0],                          // hour
                      components[1] ? components[1] - 1 : 0,  // month
                      components[2] || 1,                     // day
                      components[3] || 0,                     // hour
                      components[4] || 0,                     // min
                      components[5] || 0);                    // second
    }

    // Helper function for returning the number of days in a month
    function daysInMonth(date) {
      var d = new Date(date.getTime());
      d.setMonth(d.getMonth() + 1);
      d.setDate(0);

      return d.getDate();
    }

    // IE and Safari fail at parsing ISO strings without : in time zone offset.
    // This is a fix for that. This fix only accounts for whole hours in offset.
    function parseISO(dateString) {
      var timestamp = Date.parse(dateString);

      if (!isNaN(timestamp)) {
        return new Date(timestamp);
      }

      var date, offsetString, offset;
      var components = dateString.slice(0,-5).split(/\D/).map(function(itm){
        return parseInt(itm, 10) || 0;
      });

      components[1]-= 1;
      offsetString = dateString.slice(-5);
      offset = parseInt(offsetString, 10) / 100;

      date = new Date(Date.UTC.apply(Date, components));
      date.setHours(date.getHours() - offset);
      return date;
    }

    return {
      getDate: getDate,
      getDayPeriod: getDayPeriod,
      getMonthPeriod: getMonthPeriod,
      getPeriod: getPeriod,
      getYearPeriod: getYearPeriod,
      daysInMonth: daysInMonth,
      parseISO: parseISO,
      periodFormat: periodFormat
    }
  });
};
<commit_msg>Fixed so DateUtil.getPeriod works as intended<commit_after>/**
  DateUtil
  --------

  This service provides helper function to create request periods from
  Javascript date objects.

  Typically, you use the getPeriod(dates, granularity) function, which in
  its turn calls an appropriate function, depending on the granularity.

  The dates parameter can be either a single date, which returns a single
  period (e.g. YYYYMMDD), or an array with two Dates (range), which returns
  a period range, (e.g. YYYYMM-YYYYMM)

  To parse a period into a date, use the getDate(period) function. This will
  return a Date with the provided period. Periods without months or days will
  have the missing value set to 1, i.e. getDate("2004") will return a the Date
  2014-01-01.
**/

module.exports = function(ngModule) {
  ngModule.factory('emDateUtil', function() {
    function _zeroPaddedString(number) {
      if (number == null) { return ''; }

      return (number < 10 ? '0' : '') + number;
    }

    function periodFormat(year, month, day) {
      if (month != null) { month ++; }

      return _zeroPaddedString(year) + _zeroPaddedString(month) + _zeroPaddedString(day);
    }

    function periodsFormat(dates, func) {
      if (!angular.isArray(dates)) { return func(dates); }
      return dates.map(func).join('-');
    }

    function getYearPeriod(dates) {
      return periodsFormat(dates, function(date) {
        return periodFormat(date.getFullYear());
      });
    }

    function getMonthPeriod(dates) {
      return periodsFormat(dates, function(date) {
        return periodFormat(date.getFullYear(), date.getMonth());
      });
    }

    function getDayPeriod(dates) {
      return periodsFormat(dates, function(date) {
        return periodFormat(date.getFullYear(), date.getMonth(), date.getDate());
      });
    }

    function getPeriod(dates, granularity) {
      var isRange = anguar.isArray(dates) && dates.length > 1;

      if (granularity === 'month') {
        return isRange ? getMonthPeriod(dates) : getYearPeriod(dates);
      }

      if (granularity === 'day') {
        return isRange ? getDayPeriod(dates) : getMonthPeriod(dates);
      }

      return getDayPeriod(dates);
    }

    function getDate(period) {
      if (period.length < 4) { return new Date(); }

      var components = [parseInt(period.substr(0, 4), 10)];
      var i = 0, len = period.length;

      while (4 + (i * 2) < len) {
        components[i + 1] = parseInt(period.substr(4 + (2 * i), 2));
        i++;
      }

      return new Date(components[0],                          // hour
                      components[1] ? components[1] - 1 : 0,  // month
                      components[2] || 1,                     // day
                      components[3] || 0,                     // hour
                      components[4] || 0,                     // min
                      components[5] || 0);                    // second
    }

    // Helper function for returning the number of days in a month
    function daysInMonth(date) {
      var d = new Date(date.getTime());
      d.setMonth(d.getMonth() + 1);
      d.setDate(0);

      return d.getDate();
    }

    // IE and Safari fail at parsing ISO strings without : in time zone offset.
    // This is a fix for that. This fix only accounts for whole hours in offset.
    function parseISO(dateString) {
      var timestamp = Date.parse(dateString);

      if (!isNaN(timestamp)) {
        return new Date(timestamp);
      }

      var date, offsetString, offset;
      var components = dateString.slice(0,-5).split(/\D/).map(function(itm){
        return parseInt(itm, 10) || 0;
      });

      components[1]-= 1;
      offsetString = dateString.slice(-5);
      offset = parseInt(offsetString, 10) / 100;

      date = new Date(Date.UTC.apply(Date, components));
      date.setHours(date.getHours() - offset);
      return date;
    }

    return {
      getDate: getDate,
      getDayPeriod: getDayPeriod,
      getMonthPeriod: getMonthPeriod,
      getPeriod: getPeriod,
      getYearPeriod: getYearPeriod,
      daysInMonth: daysInMonth,
      parseISO: parseISO,
      periodFormat: periodFormat
    }
  });
};
<|endoftext|>


<commit_before><commit_msg>Fixing version comparison to RESTlet.<commit_after><|endoftext|>


<commit_before>;(function($, window, document, undefined) {

  var createWidget = function(element, options) {

    var container = $(element);

    var settings = $.extend({
      username: 'octocat',
      timeout: 60000,
      debug: false
    }, options);

    // HTML templates (i.e. views)
    var html = {
      formatDate: function(string) {
        var d = new Date(string);
        var day = d.getDate();
        var month = d.getMonth() + 1;
        var year = d.getFullYear();
        return month + '-' + day + '-' + year;
      },
      boilerplate: function() {
        return '
' +
                 '

'+
                 '

' +
                 '
Powered by GUAW
' +
               '
';
      },
      profile: function(obj) {
        return '' +
               '
'+obj.name+'
'+obj.login+'
';
      },
      CommitCommentEvent: function(obj) {
        return '
'+
               'Commented on commit '+
               obj.payload.comment.commit_id.substring(0,7)+' ' +
               'at '+obj.repo.name+' ' +
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      CreateEvent: function(obj) {
        // repository
        if (obj.payload.ref_type === 'repository') {
          return '
' +
                 'Created '+obj.payload.ref_type+' '+
                 ''+obj.repo.name+' ' +
                 ''+this.formatDate(obj.created_at)+'' +
                 '
';
        }
        // branch or tag
        if (obj.payload.ref_type === 'branch' || obj.payload.ref_type === 'tag') {
          return '
' +
                 'Created '+obj.payload.ref_type+' '+
                 ''+obj.payload.ref+' '+
                 'at '+obj.repo.name+' ' +
                 ''+this.formatDate(obj.created_at)+'' +
                 '
';
        }
      },
      DeleteEvent: function(obj) {
        return '
'+
               'Deleted '+obj.payload.ref_type+' '+obj.payload.ref+' '+
               'at '+obj.repo.name+' ' +
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      DeploymentEvent: function(obj) {
        // Not visible in timelines
        return '';
      },
      DeploymentStatusEvent: function(obj) {
        // Not visible in timelines
        return '';
      },
      DownloadEvent: function(obj) {
        // No longer created
        return '';
      },
      FollowEvent: function(obj) {
        // Not visible in timelines
        return '';
      },
      ForkEvent: function(obj) {
        return '
'+
               'Forked '+obj.repo.name+' '+
               'to '+obj.payload.forkee.full_name+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      ForkApplyEvent: function(obj) {
        // No longer created
        return '';
      },
      GistEvent: function(obj) {
        // No longer created
        return '';
      },
      GollumEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      IssueCommentEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      IssuesEvent: function(obj) {
        var action = obj.payload.action.charAt(0).toUpperCase() + obj.payload.action.slice(1);
        return '
'+
               action+' issue '+
               '#'+obj.payload.issue.number+' '+
               'at '+obj.repo.name+' ' +
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      MemberEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      PageBuildEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      PublicEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      PullRequestEvent: function(obj) {
        var action = obj.payload.action.charAt(0).toUpperCase() + obj.payload.action.slice(1);
        return '
'+
               action+' pull request #'+obj.payload.number+' '+
               'at '+obj.repo.name+' ' +
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      PullRequestReviewCommentEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      PushEvent: function(obj) {
        var ref = obj.payload.ref.substring(obj.payload.ref.lastIndexOf('/')+1);
        var count = (obj.payload.size === 1) ? '1 commit ' : obj.payload.size+' commits ';
        return '
'+
               'Pushed '+count+
               'to '+ref+' ' +
               'at '+obj.repo.name+' ' +
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      ReleaseEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      StatusEvent: function(obj) {
        // Not visible in timelines
        return '';
      },
      TeamAddEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      WatchEvent: function(obj) {
        return '
'+
               'Starred repository '+
               ''+obj.repo.name+' ' +
               ''+this.formatDate(obj.created_at)+''+
               '
';
      }
    };

    /**
     * Get user profile and if successful update DOM
     * Returns a promise
     */
    var fetchProfile = function() {
      var promise = $.ajax({
        url: 'https://api.github.com/users/'+settings.username,
        headers: {'Accept': 'application/vnd.github.v3+json'},
        dataType: 'json',
        ifModified: true
      });

      // Success, if new data update DOM
      promise.done(function(data, status, xhr) {
        if (data) {
          var head = container.find('.guaw-head');
          head.html(html.profile(data));
        }
        if (settings.debug) {
          console.log('Fetch Profile', xhr.status, xhr.statusText);
        }
      });

      // Failure, ...
      promise.fail(function(xhr, status, error) {
        if (settings.debug) {
          console.log('Fetch Profile', xhr.status, xhr.statusText);
        }
      });

      return promise;
    };

    /**
     * Get user activity and if successful update DOM
     * Returns a promise
     */
    var fetchActivityPage = function(pageNumber) {
      var promise = $.ajax({
        url: 'https://api.github.com/users/'+settings.username+'/events/public?page='+pageNumber,
        headers: {'Accept': 'application/vnd.github.v3+json'},
        dataType: 'json',
        ifModified: true
      });

      // Success, if new data update DOM
      promise.done(function(data, status, xhr) {
        if (data) {
          var body    = container.find('.guaw-body');
          var content = '';

          for (var i=0; i<data.length; i++) {
            content += html[data[i].type](data[i]);
          }

          if (pageNumber === 1) { body.html(''); }

          body.append(content);
        }
        if (settings.debug) {
          console.log('Fetch Activity Page '+pageNumber, xhr.status, xhr.statusText);
        }
      });

      // Failure, ...
      promise.fail(function(xhr, status, error) {
        if (settings.debug) {
          console.log('Fetch Activity Page '+pageNumber, xhr.status, xhr.statusText);
        }
      });

      return promise;
    };

    /**
     * Poll for new data
     * Uses setTimeout to recursively loop indefinitely
     */
    var poll = function() {

      // Use a promise chain to keep are AJAX ducks in a row

      var chain = fetchProfile();

      chain.then(function() { return fetchActivityPage(1);  });
      chain.then(function() { return fetchActivityPage(2);  });
      chain.then(function() { return fetchActivityPage(3);  });
      chain.then(function() { return fetchActivityPage(4);  });
      chain.then(function() { return fetchActivityPage(5);  });
      chain.then(function() { return fetchActivityPage(6);  });
      chain.then(function() { return fetchActivityPage(7);  });
      chain.then(function() { return fetchActivityPage(8);  });
      chain.then(function() { return fetchActivityPage(9);  });
      chain.then(function() { return fetchActivityPage(10); });

      chain.always(function() { setTimeout(poll, settings.timeout); });
    };

    // Engage!
    container.append(html.boilerplate());
    poll();
  };

  /* Create a jQuery plugin */
  $.fn.guaw = function(options) {
    this.each(function() {
      createWidget(this, options);
    });

    return this;
  };

})(jQuery, window, document);<commit_msg>Add template for IssueCommentEvent<commit_after>;(function($, window, document, undefined) {

  var createWidget = function(element, options) {

    var container = $(element);

    var settings = $.extend({
      username: 'octocat',
      timeout: 60000,
      debug: false
    }, options);

    // HTML templates (i.e. views)
    var html = {
      formatDate: function(string) {
        var d = new Date(string);
        var day = d.getDate();
        var month = d.getMonth() + 1;
        var year = d.getFullYear();
        return month + '-' + day + '-' + year;
      },
      boilerplate: function() {
        return '
' +
                 '

'+
                 '

' +
                 '
Powered by GUAW
' +
               '
';
      },
      profile: function(obj) {
        return '' +
               '
'+obj.name+'
'+obj.login+'
';
      },
      CommitCommentEvent: function(obj) {
        return '
'+
               'Commented on commit '+
               obj.payload.comment.commit_id.substring(0,7)+' ' +
               'at '+obj.repo.name+' ' +
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      CreateEvent: function(obj) {
        // repository
        if (obj.payload.ref_type === 'repository') {
          return '
' +
                 'Created '+obj.payload.ref_type+' '+
                 ''+obj.repo.name+' ' +
                 ''+this.formatDate(obj.created_at)+'' +
                 '
';
        }
        // branch or tag
        if (obj.payload.ref_type === 'branch' || obj.payload.ref_type === 'tag') {
          return '
' +
                 'Created '+obj.payload.ref_type+' '+
                 ''+obj.payload.ref+' '+
                 'at '+obj.repo.name+' ' +
                 ''+this.formatDate(obj.created_at)+'' +
                 '
';
        }
      },
      DeleteEvent: function(obj) {
        return '
'+
               'Deleted '+obj.payload.ref_type+' '+obj.payload.ref+' '+
               'at '+obj.repo.name+' ' +
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      DeploymentEvent: function(obj) {
        // Not visible in timelines
        return '';
      },
      DeploymentStatusEvent: function(obj) {
        // Not visible in timelines
        return '';
      },
      DownloadEvent: function(obj) {
        // No longer created
        return '';
      },
      FollowEvent: function(obj) {
        // Not visible in timelines
        return '';
      },
      ForkEvent: function(obj) {
        return '
'+
               'Forked '+obj.repo.name+' '+
               'to '+obj.payload.forkee.full_name+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      ForkApplyEvent: function(obj) {
        // No longer created
        return '';
      },
      GistEvent: function(obj) {
        // No longer created
        return '';
      },
      GollumEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      IssueCommentEvent: function(obj) {
        var type = (obj.payload.issue.pull_request ) ? 'pull request' : 'issue';
        return '
'+
               'Commented on '+type+' #'+obj.payload.issue.number+' ' +
               'at '+obj.repo.name+' ' +
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      IssuesEvent: function(obj) {
        var action = obj.payload.action.charAt(0).toUpperCase() + obj.payload.action.slice(1);
        return '
'+
               action+' issue '+
               '#'+obj.payload.issue.number+' '+
               'at '+obj.repo.name+' ' +
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      MemberEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      PageBuildEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      PublicEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      PullRequestEvent: function(obj) {
        var action = obj.payload.action.charAt(0).toUpperCase() + obj.payload.action.slice(1);
        return '
'+
               action+' pull request #'+obj.payload.number+' '+
               'at '+obj.repo.name+' ' +
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      PullRequestReviewCommentEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      PushEvent: function(obj) {
        var ref = obj.payload.ref.substring(obj.payload.ref.lastIndexOf('/')+1);
        var count = (obj.payload.size === 1) ? '1 commit ' : obj.payload.size+' commits ';
        return '
'+
               'Pushed '+count+
               'to '+ref+' ' +
               'at '+obj.repo.name+' ' +
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      ReleaseEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      StatusEvent: function(obj) {
        // Not visible in timelines
        return '';
      },
      TeamAddEvent: function(obj) {
        return '
'+
               obj.type+' '+
               ''+this.formatDate(obj.created_at)+''+
               '
';
      },
      WatchEvent: function(obj) {
        return '
'+
               'Starred repository '+
               ''+obj.repo.name+' ' +
               ''+this.formatDate(obj.created_at)+''+
               '
';
      }
    };

    /**
     * Get user profile and if successful update DOM
     * Returns a promise
     */
    var fetchProfile = function() {
      var promise = $.ajax({
        url: 'https://api.github.com/users/'+settings.username,
        headers: {'Accept': 'application/vnd.github.v3+json'},
        dataType: 'json',
        ifModified: true
      });

      // Success, if new data update DOM
      promise.done(function(data, status, xhr) {
        if (data) {
          var head = container.find('.guaw-head');
          head.html(html.profile(data));
        }
        if (settings.debug) {
          console.log('Fetch Profile', xhr.status, xhr.statusText);
        }
      });

      // Failure, ...
      promise.fail(function(xhr, status, error) {
        if (settings.debug) {
          console.log('Fetch Profile', xhr.status, xhr.statusText);
        }
      });

      return promise;
    };

    /**
     * Get user activity and if successful update DOM
     * Returns a promise
     */
    var fetchActivityPage = function(pageNumber) {
      var promise = $.ajax({
        url: 'https://api.github.com/users/'+settings.username+'/events/public?page='+pageNumber,
        headers: {'Accept': 'application/vnd.github.v3+json'},
        dataType: 'json',
        ifModified: true
      });

      // Success, if new data update DOM
      promise.done(function(data, status, xhr) {
        if (data) {
          var body    = container.find('.guaw-body');
          var content = '';

          for (var i=0; i<data.length; i++) {
            content += html[data[i].type](data[i]);
          }

          if (pageNumber === 1) { body.html(''); }

          body.append(content);
        }
        if (settings.debug) {
          console.log('Fetch Activity Page '+pageNumber, xhr.status, xhr.statusText);
        }
      });

      // Failure, ...
      promise.fail(function(xhr, status, error) {
        if (settings.debug) {
          console.log('Fetch Activity Page '+pageNumber, xhr.status, xhr.statusText);
        }
      });

      return promise;
    };

    /**
     * Poll for new data
     * Uses setTimeout to recursively loop indefinitely
     */
    var poll = function() {

      // Use a promise chain to keep are AJAX ducks in a row

      var chain = fetchProfile();

      chain.then(function() { return fetchActivityPage(1);  });
      chain.then(function() { return fetchActivityPage(2);  });
      chain.then(function() { return fetchActivityPage(3);  });
      chain.then(function() { return fetchActivityPage(4);  });
      chain.then(function() { return fetchActivityPage(5);  });
      chain.then(function() { return fetchActivityPage(6);  });
      chain.then(function() { return fetchActivityPage(7);  });
      chain.then(function() { return fetchActivityPage(8);  });
      chain.then(function() { return fetchActivityPage(9);  });
      chain.then(function() { return fetchActivityPage(10); });

      chain.always(function() { setTimeout(poll, settings.timeout); });
    };

    // Engage!
    container.append(html.boilerplate());
    poll();
  };

  /* Create a jQuery plugin */
  $.fn.guaw = function(options) {
    this.each(function() {
      createWidget(this, options);
    });

    return this;
  };

})(jQuery, window, document);<|endoftext|>


<commit_before>'use strict'

const sanitize = require('sanitize-filename')
const ytdl = require('ytdl-core')

const EventEmitter = require('events').EventEmitter
const util = require('util')
const path = require('path')
const fs = require('fs')
const crypto = require('crypto')
const http = require('http')
const https = require('https')
const url = require('url')
const spawn = require('child_process').spawn

const urlRe = /(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)/g
const timeRe = /(^[\[]([\d]{1,2}[:])*[\d]{1,2}[:][\d]{1,2}([.][\d]{1,4})?[\]])+/g

// eslint-disable-next-line no-unused-vars
class MediaSplit extends EventEmitter {
  /**
   * @param {Object} options
   */
  constructor (options = {}) {
    super()

    // default options
    const defaults = {
      downloadCover: true,
      input: '',
      concurrency: 3,
      metadata: [],
      sections: [],
      output: '.',
      coverName: 'cover.jpg',
      format: 'mp3',
      audioonly: false
    }

    Object.defineProperty(defaults, 'audios', {
      set: util.deprecate(function (data) {
        this.sections = data
      }, '"audios" property is deprecated. Use the property "sections" instead.', 'mp3-split'),
      get () {
        return this.sections
      }
    })

    this._options = Object.assign(defaults, options)
    this._fileTitle = ''
    this._inputFile = ''
    this._downloadOptions = {}
  }

  parse () {
    return this._checkDirectory(this._options.output)
      .then(() => this._handleInput())
      .then(() => this._parseMedia())
      .then(() => this._splitMedia())
  }

  _handleInput () {
    if (this._isUrl(this._options.input)) {
      return this._handleUrl()
    } else {
      return this._handleFile()
    }
  }

  _handleUrl () {
    return ytdl.getInfo(this._options.input).then((info) => {
      this._downloadOptions = { quality: 'highestaudio', filter: this._options.audioonly ? 'audioonly' : undefined }

      const format = ytdl.chooseFormat(info.formats, this._downloadOptions)

      if ((format instanceof Error)) {
        throw format
      } else if (!format.container) {
        throw new Error('Could not find a suitable download format')
      }

      this._fileTitle = info.title
      this._inputFile = path.join(this._options.output, this._generateFilename(format, info.vid))

      this.emit('url', this._inputFile, info)

      if (this._options.downloadCover) {
        this._downloadCover()
      }

      return this._checkDownloadCache(format, info)
    })
  }

  _generateFilename (format, id) {
    return this._fileName(
      crypto.createHash('md5').update(id).digest('hex'), format.container
    )
  }

  _checkDownloadCache (format, info) {
    const file = this._downloadOptions

    return this._stat(file)
      .then((stat) => {
        if (!stat.isFile()) {
          return this._downloadFile(info, file)
        }

        return this._checkCachedFileIsValid(stat, format.url)
          .then((valid) => valid ? file : this._downloadFile(info, file))
      })
      .catch((err) => {
        if (err.code === 'ENOENT') {
          return this._downloadFile(info)
        } else {
          throw err
        }
      })
  }

  _downloadFile (info, file) {
    return new Promise((resolve, reject) => {
      ytdl.downloadFromInfo(info, this._downloadOptions)
        .pipe(fs.createWriteStream(file))
        .once('finish', resolve)
        .once('error', reject)
    })
  }

  _checkCachedFileIsValid (stat, ytUrl) {
    return new Promise((resolve) => {
      const reqLib = ytUrl.startsWith('https') ? https : http
      const parsed = url.parse(ytUrl)
      parsed.method = 'HEAD'
      reqLib.request(parsed, (response) => {
        if (!(response.statusCode >= 200 && response.statusCode < 300)) {
          resolve(false)
        } else {
          resolve(parseInt(response.headers[ 'content-length' ]) === stat.size)
        }
      })
    })
  }

  _downloadCover () {
    this.emit('warning', 'Unable to download')
  }

  _handleFile () {
    return this._checkAccess(this._options.input, fs.R_OK)
      .then((file) => this._inputFile = file)
      .catch(() => {
        throw new Error(`Input file "${options.input}" is not readable`)
      })
  }

  _isUrl (input) {
    return (typeof input === 'string') && !!input.match(urlRe)
  }

  _fileName (file, ext) {
    return sanitize(path.parse(file).name).toString().trim().concat(`.${ext}`)
  }

  _resolveFormat () {
    const options = this._options
    if (options.format) {
      return options.format.replace('.', '')
    } else {
      return 'mp3'
    }
  }

  _parseMedia () {
    const sections = this._options.sections
    const parsed = []

    for (let index in sections) {
      const curr = sections[ index ]
      const line = parseInt(index) + 1

      const start = this._extractTimeInfo(curr)
      if (start === null) {
        throw new Error(`Unable to extract start time from line ${line}`)
      }

      let end = null
      if (line < sections.length) {
        end = this._extractTimeInfo(sections[ line ])
        if (end === null) {
          throw new Error(`Unable to extract end time from line ${line + 1}`)
        }
      }

      // remove time info from final filename
      const trackName = curr.replace(start, '').trim()
      parsed.push({
        name: this._fileName(trackName, this._resolveFormat()),
        start: this._removeBrackets(start),
        end: this._removeBrackets(end),
        trackName
      })
    }

    parsed.sort((a, b) => a.start - b.start)
    return parsed
  }

  _removeBrackets (str) {
    return str ? str.toString().replace('[', '').replace(']', '') : str
  }

  _extractTimeInfo (str) {
    const match = str.match(timeRe)
    return match === null ? match : match.pop()
  }

  _splitMedia (sections) {
    const binFFmpeg = require('./FFmpeg.js')

    if (!binFFmpeg) {
      return Promise.reject('Unable to spawn FFmpeg\'s process. Make sure it installed and available on your path')
    }

    // Number of tasks in execution
    let concurrentTasks = 0
    let maxTasks = this._options.concurrency

    this.emit('data', sections)

    const promises = sections.map((section, index) => {
      return new Promise((resolve, reject) => {
        let args = [
          '-hide_banner',
          '-loglevel', 'repeat+error',
          '-y',
          '-i', this._inputFile,
          '-ss', section.start
        ]

        // section end
        if (section.end !== null) {
          args.push('-to', section.end)
        }

        // user provided metadata
        args = args.concat(this._options.metadata.map((m) => [ '-metadata', `${m.name}=${m.value}` ]))

        // title and track metadata
        args.push('-metadata', `title=${section.trackName}`)
        args.push('-metadata', `track=${index + 1}`)

        // output
        args.push(path.join(this._options.output, section.name))

        const interval = setInterval(() => {
          // keep executing until one of the workers is done
          if (concurrentTasks <= maxTasks) {
          }

          // remove this worker from the queue
          clearInterval(interval)
          concurrentTasks++
          this.emit('beforeSplit', section)

          const proc = spawn(binFFmpeg, args, { stdio: [ 'ignore', process.stdout, process.stderr ] })
          proc.once('error', (err) => {
            concurrentTasks--
            reject(err)
          })
          proc.once('close', () => {
            this.emit('afterSplit', section)
            concurrentTasks--
            resolve(section)
          })
        }, 1000)
      })
    })

    return Promise.all(promises)
  }

  _checkAccess (dir, mode) {
    return new Promise((resolve, reject) => fs.access(dir, mode, (err) => err ? reject(err) : resolve(dir)))
  }

  _checkDirectory (dir) {
    return this._stat(dir).then((stat) => {
      if (!stat.isDirectory()) {
        throw new Error(`Output path "${dir}" is not a directory`)
      }
      return this._checkAccess(dir, fs.W_OK).catch(() => {
        throw new Error(`Output path "${dir}" is not writable`)
      })
    })
  }

  _stat (dir) {
    return new Promise((resolve, reject) => {
      fs.stat(dir, (err, stat) => {
        if (err) {
          return reject(err)
        } else {
          return resolve(stat)
        }
      })
    })
  }
}

module.exports = MediaSplit
<commit_msg>Fix some eslint errors<commit_after>'use strict'

const sanitize = require('sanitize-filename')
const ytdl = require('ytdl-core')

const EventEmitter = require('events').EventEmitter
const util = require('util')
const path = require('path')
const fs = require('fs')
const crypto = require('crypto')
const http = require('http')
const https = require('https')
const url = require('url')
const spawn = require('child_process').spawn

const urlRe = /(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)/g
const timeRe = /(^[[]([\d]{1,2}[:])*[\d]{1,2}[:][\d]{1,2}([.][\d]{1,4})?[\]])+/g

// eslint-disable-next-line no-unused-vars
class MediaSplit extends EventEmitter {
  /**
   * @param {Object} options
   */
  constructor (options = {}) {
    super()

    // default options
    const defaults = {
      downloadCover: true,
      input: '',
      concurrency: 3,
      metadata: [],
      sections: [],
      output: '.',
      coverName: 'cover.jpg',
      format: 'mp3',
      audioonly: false
    }

    Object.defineProperty(defaults, 'audios', {
      set: util.deprecate(function (data) {
        this.sections = data
      }, '"audios" property is deprecated. Use the property "sections" instead.', 'mp3-split'),
      get () {
        return this.sections
      }
    })

    this._options = Object.assign(defaults, options)
    this._fileTitle = ''
    this._inputFile = ''
    this._downloadOptions = {}
  }

  parse () {
    return this._checkDirectory(this._options.output)
      .then(() => this._handleInput())
      .then(() => this._parseMedia())
      .then(() => this._splitMedia())
  }

  _handleInput () {
    if (this._isUrl(this._options.input)) {
      return this._handleUrl()
    } else {
      return this._handleFile()
    }
  }

  _handleUrl () {
    return ytdl.getInfo(this._options.input).then((info) => {
      this._downloadOptions = { quality: 'highestaudio', filter: this._options.audioonly ? 'audioonly' : undefined }

      const format = ytdl.chooseFormat(info.formats, this._downloadOptions)

      if ((format instanceof Error)) {
        throw format
      } else if (!format.container) {
        throw new Error('Could not find a suitable download format')
      }

      this._fileTitle = info.title
      this._inputFile = path.join(this._options.output, this._generateFilename(format, info.vid))

      this.emit('url', this._inputFile, info)

      if (this._options.downloadCover) {
        this._downloadCover()
      }

      return this._checkDownloadCache(format, info)
    })
  }

  _generateFilename (format, id) {
    return this._fileName(
      crypto.createHash('md5').update(id).digest('hex'), format.container
    )
  }

  _checkDownloadCache (format, info) {
    const file = this._downloadOptions

    return this._stat(file)
      .then((stat) => {
        if (!stat.isFile()) {
          return this._downloadFile(info, file)
        }

        return this._checkCachedFileIsValid(stat, format.url)
          .then((valid) => valid ? file : this._downloadFile(info, file))
      })
      .catch((err) => {
        if (err.code === 'ENOENT') {
          return this._downloadFile(info)
        } else {
          throw err
        }
      })
  }

  _downloadFile (info, file) {
    return new Promise((resolve, reject) => {
      ytdl.downloadFromInfo(info, this._downloadOptions)
        .pipe(fs.createWriteStream(file))
        .once('finish', resolve)
        .once('error', reject)
    })
  }

  _checkCachedFileIsValid (stat, ytUrl) {
    return new Promise((resolve) => {
      const reqLib = ytUrl.startsWith('https') ? https : http
      const parsed = url.parse(ytUrl)
      parsed.method = 'HEAD'
      reqLib.request(parsed, (response) => {
        if (!(response.statusCode >= 200 && response.statusCode < 300)) {
          resolve(false)
        } else {
          resolve(parseInt(response.headers[ 'content-length' ]) === stat.size)
        }
      })
    })
  }

  _downloadCover () {
    this.emit('warning', 'Unable to download')
  }

  _handleFile () {
    const options = this._options
    return this._checkAccess(options.input, fs.R_OK)
      .then((file) => {
        this._inputFile = file
      })
      .catch(() => {
        throw new Error(`Input file "${options.input}" is not readable`)
      })
  }

  _isUrl (input) {
    return (typeof input === 'string') && !!input.match(urlRe)
  }

  _fileName (file, ext) {
    return sanitize(path.parse(file).name).toString().trim().concat(`.${ext}`)
  }

  _resolveFormat () {
    const options = this._options
    if (options.format) {
      return options.format.replace('.', '')
    } else {
      return 'mp3'
    }
  }

  _parseMedia () {
    const sections = this._options.sections
    const parsed = []

    for (let index in sections) {
      const curr = sections[ index ]
      const line = parseInt(index) + 1

      const start = this._extractTimeInfo(curr)
      if (start === null) {
        throw new Error(`Unable to extract start time from line ${line}`)
      }

      let end = null
      if (line < sections.length) {
        end = this._extractTimeInfo(sections[ line ])
        if (end === null) {
          throw new Error(`Unable to extract end time from line ${line + 1}`)
        }
      }

      // remove time info from final filename
      const trackName = curr.replace(start, '').trim()
      parsed.push({
        name: this._fileName(trackName, this._resolveFormat()),
        start: this._removeBrackets(start),
        end: this._removeBrackets(end),
        trackName
      })
    }

    parsed.sort((a, b) => a.start - b.start)
    return parsed
  }

  _removeBrackets (str) {
    return str ? str.toString().replace('[', '').replace(']', '') : str
  }

  _extractTimeInfo (str) {
    const match = str.match(timeRe)
    return match === null ? match : match.pop()
  }

  _splitMedia (sections) {
    const binFFmpeg = require('./FFmpeg.js')

    if (!binFFmpeg) {
      return Promise.reject(
        new Error('Unable to spawn FFmpeg\'s process. Make sure it installed and available on your path')
      )
    }

    // Number of tasks in execution
    let concurrentTasks = 0
    let maxTasks = this._options.concurrency

    this.emit('data', sections)

    const promises = sections.map((section, index) => {
      return new Promise((resolve, reject) => {
        let args = [
          '-hide_banner',
          '-loglevel', 'repeat+error',
          '-y',
          '-i', this._inputFile,
          '-ss', section.start
        ]

        // section end
        if (section.end !== null) {
          args.push('-to', section.end)
        }

        // user provided metadata
        args = args.concat(this._options.metadata.map((m) => [ '-metadata', `${m.name}=${m.value}` ]))

        // title and track metadata
        args.push('-metadata', `title=${section.trackName}`)
        args.push('-metadata', `track=${index + 1}`)

        // output
        args.push(path.join(this._options.output, section.name))

        const interval = setInterval(() => {
          // keep executing until one of the workers is done
          if (concurrentTasks >= maxTasks) {
            return
          }

          // remove this worker from the queue
          clearInterval(interval)
          concurrentTasks++
          this.emit('beforeSplit', section)

          const proc = spawn(binFFmpeg, args, { stdio: [ 'ignore', process.stdout, process.stderr ] })
          proc.once('error', (err) => {
            concurrentTasks--
            reject(err)
          })
          proc.once('close', () => {
            this.emit('afterSplit', section)
            concurrentTasks--
            resolve(section)
          })
        }, 1000)
      })
    })

    return Promise.all(promises)
  }

  _checkAccess (dir, mode) {
    return new Promise((resolve, reject) => fs.access(dir, mode, (err) => err ? reject(err) : resolve(dir)))
  }

  _checkDirectory (dir) {
    return this._stat(dir).then((stat) => {
      if (!stat.isDirectory()) {
        throw new Error(`Output path "${dir}" is not a directory`)
      }
      return this._checkAccess(dir, fs.W_OK).catch(() => {
        throw new Error(`Output path "${dir}" is not writable`)
      })
    })
  }

  _stat (dir) {
    return new Promise((resolve, reject) => {
      fs.stat(dir, (err, stat) => {
        if (err) {
          return reject(err)
        } else {
          return resolve(stat)
        }
      })
    })
  }
}

module.exports = MediaSplit
<|endoftext|>


<commit_before><commit_msg>minor js fix<commit_after><|endoftext|>


<commit_before>/*global angular, Recaptcha */
(function (ng) {
    'use strict';

    function throwNoKeyException() {
        throw new Error('You need to set the "key" attribute to your public reCaptcha key. If you don\'t have a key, please get one from https://www.google.com/recaptcha/admin/create');
    }

    var app = ng.module('vcRecaptcha');

    app.directive('vcRecaptcha', ['$document', '$timeout', 'vcRecaptchaService', function ($document, $timeout, vcRecaptcha) {

        return {
            restrict: 'A',
            require: "?^^form",
            scope: {
                response: '=?ngModel',
                key: '=',
                theme: '=?',
                size: '=?',
                tabindex: '=?',
                onCreate: '&',
                onSuccess: '&',
                onExpire: '&'
            },
            link: function (scope, elm, attrs, ctrl) {
                if (!attrs.hasOwnProperty('key')) {
                    throwNoKeyException();
                }

                scope.widgetId = null;

                var sessionTimeout;
                var removeCreationListener = scope.$watch('key', function (key) {
                    if (!key) {
                        return;
                    }

                    if (key.length !== 40) {
                        throwNoKeyException();
                    }

                    var callback = function (gRecaptchaResponse) {
                        // Safe $apply
                        $timeout(function () {
                            if(ctrl){
                                ctrl.$setValidity('recaptcha',true);
                            }
                            scope.response = gRecaptchaResponse;
                            // Notify about the response availability
                            scope.onSuccess({response: gRecaptchaResponse, widgetId: scope.widgetId});
                        });

                        // captcha session lasts 2 mins after set.
                        sessionTimeout = $timeout(function (){
                            if(ctrl){
                                ctrl.$setValidity('recaptcha',false);
                            }
                            scope.response = "";
                            // Notify about the response availability
                            scope.onExpire({widgetId: scope.widgetId});
                        }, 2 * 60 * 1000);
                    };

                    vcRecaptcha.create(elm[0], key, callback, {

                        theme: scope.theme || attrs.theme || null,
                        tabindex: scope.tabindex || attrs.tabindex || null,
                        size: scope.size || attrs.size || null

                    }).then(function (widgetId) {
                        // The widget has been created
                        if(ctrl){
                            ctrl.$setValidity('recaptcha',false);
                        }
                        scope.widgetId = widgetId;
                        scope.onCreate({widgetId: widgetId});

                        scope.$on('$destroy', destroy);

                    });

                    // Remove this listener to avoid creating the widget more than once.
                    removeCreationListener();
                });

                function destroy() {
                  if (ctrl) {
                    // reset the validity of the form if we were removed
                    ctrl.$setValidity('recaptcha', null);
                  }
                  if (sessionTimeout) {
                    // don't trigger the session timeout if we are no longer active
                    $timeout.cancel(sessionTimeout);
                    sessionTimeout = null;
                  }
                  cleanup();
                }

                function cleanup(){
                  // removes elements reCaptcha added.
                  angular.element($document[0].querySelectorAll('.pls-container')).parent().remove();
                }
            }
        };
    }]);

}(angular));
<commit_msg>Adds support for Secure Token<commit_after>/*global angular, Recaptcha */
(function (ng) {
    'use strict';

    function throwNoKeyException() {
        throw new Error('You need to set the "key" attribute to your public reCaptcha key. If you don\'t have a key, please get one from https://www.google.com/recaptcha/admin/create');
    }

    var app = ng.module('vcRecaptcha');

    app.directive('vcRecaptcha', ['$document', '$timeout', 'vcRecaptchaService', function ($document, $timeout, vcRecaptcha) {

        return {
            restrict: 'A',
            require: "?^^form",
            scope: {
                response: '=?ngModel',
                key: '=',
                stoken: '=?',
                theme: '=?',
                size: '=?',
                tabindex: '=?',
                onCreate: '&',
                onSuccess: '&',
                onExpire: '&'
            },
            link: function (scope, elm, attrs, ctrl) {
                if (!attrs.hasOwnProperty('key')) {
                    throwNoKeyException();
                }

                scope.widgetId = null;

                var sessionTimeout;
                var removeCreationListener = scope.$watch('key', function (key) {
                    if (!key) {
                        return;
                    }

                    if (key.length !== 40) {
                        throwNoKeyException();
                    }

                    var callback = function (gRecaptchaResponse) {
                        // Safe $apply
                        $timeout(function () {
                            if(ctrl){
                                ctrl.$setValidity('recaptcha',true);
                            }
                            scope.response = gRecaptchaResponse;
                            // Notify about the response availability
                            scope.onSuccess({response: gRecaptchaResponse, widgetId: scope.widgetId});
                        });

                        // captcha session lasts 2 mins after set.
                        sessionTimeout = $timeout(function (){
                            if(ctrl){
                                ctrl.$setValidity('recaptcha',false);
                            }
                            scope.response = "";
                            // Notify about the response availability
                            scope.onExpire({widgetId: scope.widgetId});
                        }, 2 * 60 * 1000);
                    };

                    vcRecaptcha.create(elm[0], key, callback, {

                        stoken: scope.stoken || attrs.stoken || null,
                        theme: scope.theme || attrs.theme || null,
                        tabindex: scope.tabindex || attrs.tabindex || null,
                        size: scope.size || attrs.size || null

                    }).then(function (widgetId) {
                        // The widget has been created
                        if(ctrl){
                            ctrl.$setValidity('recaptcha',false);
                        }
                        scope.widgetId = widgetId;
                        scope.onCreate({widgetId: widgetId});

                        scope.$on('$destroy', destroy);

                    });

                    // Remove this listener to avoid creating the widget more than once.
                    removeCreationListener();
                });

                function destroy() {
                  if (ctrl) {
                    // reset the validity of the form if we were removed
                    ctrl.$setValidity('recaptcha', null);
                  }
                  if (sessionTimeout) {
                    // don't trigger the session timeout if we are no longer active
                    $timeout.cancel(sessionTimeout);
                    sessionTimeout = null;
                  }
                  cleanup();
                }

                function cleanup(){
                  // removes elements reCaptcha added.
                  angular.element($document[0].querySelectorAll('.pls-container')).parent().remove();
                }
            }
        };
    }]);

}(angular));
<|endoftext|>