<commit_before>process.env.NODE_ENV = 'test_in_memory';
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
const gatewayd = require(__dirname+'/../../');
const OutgoingPayment = require(__dirname+'/../../lib/core/outgoing_payment.js');
var fixtures = require(__dirname+'/../fixtures/ripple_rest_integration.js');
var RippleTransactions = gatewayd.models.rippleTransactions;

describe('Outgoing Payment', function() {
  chai.use(chaiAsPromised);

  beforeEach(function(done) {
    gatewayd.database.sync({force: true}).then(function() {
      done();
    });
  });

  describe('Ripple REST error handler', function(){

    it('should handle insufficient fund error and mark record failed', function(done) {
      RippleTransactions
        .create(fixtures.outgoing_record)
        .then(function (rippleTransaction) {
          var outgoingPayment = new OutgoingPayment(rippleTransaction);
          return outgoingPayment._rippleRestResponseHandler(fixtures.errors.insufficient_funds)
            .then(function(handledError){
              chai.assert.strictEqual(handledError.record.state, 'failed');
              chai.assert.strictEqual(handledError.record.transaction_state, 'terINSUF_FEE_B');
              done();
            })
        })
        .error(done);
    });

    it('should handle transaction not found -- retry', function(done) {
      RippleTransactions
        .create(fixtures.outgoing_record)
        .then(function (rippleTransaction) {
          var outgoingPayment = new OutgoingPayment(rippleTransaction);
          return outgoingPayment._rippleRestResponseHandler(fixtures.errors.invalid_requests.transaction_not_found)
            .then(function(handledError){

              chai.assert.strictEqual(handledError.record.state, 'outgoing');
              chai.assert.strictEqual(handledError.handled, 'retry');
              done();
            })
        })
        .error(done);
    });

    it('should handle no path found -- failed', function(done) {

      RippleTransactions
        .create(fixtures.outgoing_record)
        .then(function (rippleTransaction) {

          var outgoingPayment = new OutgoingPayment(rippleTransaction);

          outgoingPayment._rippleRestResponseHandler(fixtures.errors.invalid_requests.no_paths_found)
            .then(function(handledError){
              chai.assert.strictEqual(handledError.record.transaction_state, 'tecPATH_DRY');
              chai.assert.strictEqual(handledError.record.state, 'failed');
              done();
            });
        });
    });

  });

  it('should handle no rippled connection error -- retry', function(done) {

    RippleTransactions
      .create(fixtures.outgoing_record)
      .then(function (rippleTransaction) {
        var outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._rippleRestResponseHandler(fixtures.errors.connection.no_rippled_connection)
          .then(function(handledError){
            chai.assert.strictEqual(handledError.handled, 'retry');
            done();
          })
      })
      .error(done);
  });

  it('should handle rippled busy error -- retry', function(done) {

    RippleTransactions
      .create(fixtures.outgoing_record)
      .then(function (rippleTransaction) {
        var outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._rippleRestResponseHandler(fixtures.errors.connection.rippled_busy)
          .then(function(handledError){
            chai.assert.strictEqual(handledError.handled, 'retry');
            done();
          })
      })
      .error(done);
  });

  it('should handle socket hang up connection error -- retry', function(done) {
    var error = new Error('socket hang up');
    error.code = 'ECONNRESET';

    RippleTransactions
      .create(fixtures.outgoing_record)
      .then(function (rippleTransaction) {
        var outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._rippleRestResponseHandler(error)
          .then(function(error){
            chai.assert.strictEqual(error.handled, 'retry');
            done();
          })
      })
      .error(done);
  });

});

describe('Sending a queued payment to ripple', function() {
  beforeEach(function(done) {
    gatewayd.database.sync({force: true}).then(function() {
      done();
    });
  });

  it('should record the rejection of a payment from the Ripple Ledger', function(done) {

    RippleTransactions
      .create(fixtures.outgoing_record)
      .then(function (rippleTransaction) {
        var outgoingPayment = new OutgoingPayment(rippleTransaction);
        var confirmationResponse = new Object(fixtures.successful_responses.validated_payment);
        confirmationResponse.result = 'temPATH_DRY';

        return outgoingPayment._recordAcceptanceOrRejectionStatus(confirmationResponse)
          .then(function(status) {
            chai.assert.strictEqual(outgoingPayment.record.state, 'failed');
            done();
          })
      })
      .error(done);
  });

  it.skip('should send a ripple payment, response must have a status url', function(done){
    this.timeout(10000);
    RippleTransactions
      .create(fixtures.outgoing_record)
      .then(function (rippleTransaction) {
        var outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._sendPayment(fixtures.requests.payment)
          .then(function(payment){
            chai.assert(payment.success);
            chai.assert(payment.status_url);
            done();
          })
      })
      .error(done);
  });

  it('should mark record pending while a payment is being confirmed', function(done){
    this.timeout(10000);
    RippleTransactions
      .create(fixtures.outgoing_record)
      .then(function (rippleTransaction) {
        var outgoingPayment = new OutgoingPayment(rippleTransaction);
        outgoingPayment._confirmPayment(fixtures.requests.pending_payment)
          .error(function(payment){
            chai.assert(outgoingPayment.record.state, 'pending');
            done();
          });
      })
      .error(done);
  });

  it.skip('should successfully submit outgoing payments and must have transaction_hash and transaction_state', function(done){
    this.timeout(10000);
    var outgoingPayment;
    RippleTransactions
      .create(fixtures.outgoing_record)
      .then(function(rippleTransaction){
        outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._sendPayment(fixtures.requests.payment)
      })
      .then(function(pendingPayment){
        return outgoingPayment._confirmPayment(pendingPayment)
      })
      .then(function(payment){
        return outgoingPayment._recordAcceptanceOrRejectionStatus(payment)
      })
      .then(function(payment){
        chai.assert.strictEqual(payment.transaction_state, 'tesSUCCESS');
        chai.assert(payment.transaction_hash);
        done();
     })
    .error(done);
  });

  it.skip('should successfully submit outgoing payments with invoice and memos fields', function(done){
    this.timeout(10000);
    var outgoingPayment;
    RippleTransactions
      .create(fixtures.outgoing_record_invoice_id_memos)
      .then(function(rippleTransaction){
        outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._sendPayment(fixtures.requests.payment)
      })
      .then(function(pendingPayment){
        return outgoingPayment._confirmPayment(pendingPayment)
      })
      .then(function(confirmedPayment){
        chai.assert(confirmedPayment.memos);
        chai.assert.isArray(confirmedPayment.memos);
        chai.assert.equal(confirmedPayment.invoice_id, fixtures.outgoing_record_invoice_id_memos.invoice_id);
        done();
      })
      .error(done);
  });

  it('should update the outgoing payment with the source balance changes', function(done) {

    var validatedPayment = fixtures.successful_responses.validated_payment;

    validatedPayment.source_balance_changes = [
      { value: '-1.012', currency: 'XRP', issuer: '' },
      { value: '1',      currency: 'BTC', issuer: 'rJMtFJ7hKzvcGyzKp9rN9PrqNPReSsdFv5' },
      { value: '0.45',   currency: 'BTC', issuer: 'rwjYEBN9DSMnxLzGVEfbqmabDd2sr2kzcz' }
    ];

    validatedPayment.destination_balance_changes = [
      { value: '0.5', currency: 'XAU', issuer: 'rJMtFJ7hKzvcGyzKp9rN9PrqNPReSsdFv5' },
      { value: '0.2', currency: 'XAU', issuer: 'rwjYEBN9DSMnxLzGVEfbqmabDd2sr2kzcz' }
    ];

    RippleTransactions
      .create(fixtures.outgoing_record_invoice_id_memos)
      .then(function(rippleTransaction){
        outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._recordAcceptanceOrRejectionStatus(validatedPayment)
      })
      .then(function(rippleTransaction) {
        chai.assert.strictEqual(rippleTransaction.to_currency, 'XAU');
        chai.assert.strictEqual(rippleTransaction.to_amount, 0.7);
        chai.assert.strictEqual(rippleTransaction.from_currency, 'BTC');
        chai.assert.strictEqual(rippleTransaction.from_amount, 1.45);
        done();
      })
      .error(done);
  });

  it('should update the outgoing payment with the source balance changes', function(done) {

    var validatedPayment = fixtures.successful_responses.validated_payment;

    validatedPayment.source_balance_changes = [
      { value: '-1.012', currency: 'XRP', issuer: '' },
      { value: '1',      currency: 'BTC', issuer: 'rJMtFJ7hKzvcGyzKp9rN9PrqNPReSsdFv5' },
      { value: '0.45',   currency: 'BTC', issuer: 'rwjYEBN9DSMnxLzGVEfbqmabDd2sr2kzcz' }
    ];

    validatedPayment.destination_balance_changes = [
      { value: '0.5', currency: 'XAU', issuer: 'rJMtFJ7hKzvcGyzKp9rN9PrqNPReSsdFv5' },
      { value: '0.2', currency: 'XAU', issuer: 'rwjYEBN9DSMnxLzGVEfbqmabDd2sr2kzcz' }
    ];

    RippleTransactions
      .create(fixtures.outgoing_record_invoice_id_memos)
      .then(function(rippleTransaction){
        outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._recordAcceptanceOrRejectionStatus(validatedPayment)
      })
      .then(function(rippleTransaction) {
        chai.assert.strictEqual(rippleTransaction.to_currency, 'XAU');
        chai.assert.strictEqual(rippleTransaction.to_amount, 0.7);
        chai.assert.strictEqual(rippleTransaction.from_currency, 'BTC');
        chai.assert.strictEqual(rippleTransaction.from_amount, 1.45);
        done();
      })
  });

});
<commit_msg>[TASK] Add new tests to package.json<commit_after>process.env.NODE_ENV = 'test_in_memory';
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
const gatewayd = require(__dirname+'/../../');
const OutgoingPayment = require(__dirname+'/../../lib/core/outgoing_payment.js');
var fixtures = require(__dirname+'/../fixtures/ripple_rest_integration.js');
var RippleTransactions = gatewayd.models.rippleTransactions;

describe('Outgoing Payment', function() {
  chai.use(chaiAsPromised);

  beforeEach(function(done) {
    gatewayd.database.sync({force: true}).then(function() {
      done();
    });
  });

  describe('Ripple REST error handler', function(){

    it('should handle insufficient fund error and mark record failed', function(done) {
      RippleTransactions
        .create(fixtures.outgoing_record)
        .then(function (rippleTransaction) {
          var outgoingPayment = new OutgoingPayment(rippleTransaction);
          return outgoingPayment._rippleRestResponseHandler(fixtures.errors.insufficient_funds)
            .then(function(handledError){
              chai.assert.strictEqual(handledError.record.state, 'failed');
              chai.assert.strictEqual(handledError.record.transaction_state, 'terINSUF_FEE_B');
              done();
            })
        })
        .error(done);
    });

    it('should handle transaction not found -- retry', function(done) {
      RippleTransactions
        .create(fixtures.outgoing_record)
        .then(function (rippleTransaction) {
          var outgoingPayment = new OutgoingPayment(rippleTransaction);
          return outgoingPayment._rippleRestResponseHandler(fixtures.errors.invalid_requests.transaction_not_found)
            .then(function(handledError){

              chai.assert.strictEqual(handledError.record.state, 'outgoing');
              chai.assert.strictEqual(handledError.handled, 'retry');
              done();
            })
        })
        .error(done);
    });

    it('should handle no path found -- failed', function(done) {

      RippleTransactions
        .create(fixtures.outgoing_record)
        .then(function (rippleTransaction) {

          var outgoingPayment = new OutgoingPayment(rippleTransaction);

          outgoingPayment._rippleRestResponseHandler(fixtures.errors.invalid_requests.no_paths_found)
            .then(function(handledError){
              chai.assert.strictEqual(handledError.record.transaction_state, 'tecPATH_DRY');
              chai.assert.strictEqual(handledError.record.state, 'failed');
              done();
            });
        });
    });

  });

  it('should handle no rippled connection error -- retry', function(done) {

    RippleTransactions
      .create(fixtures.outgoing_record)
      .then(function (rippleTransaction) {
        var outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._rippleRestResponseHandler(fixtures.errors.connection.no_rippled_connection)
          .then(function(handledError){
            chai.assert.strictEqual(handledError.handled, 'retry');
            done();
          })
      })
      .error(done);
  });

  it('should handle rippled busy error -- retry', function(done) {

    RippleTransactions
      .create(fixtures.outgoing_record)
      .then(function (rippleTransaction) {
        var outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._rippleRestResponseHandler(fixtures.errors.connection.rippled_busy)
          .then(function(handledError){
            chai.assert.strictEqual(handledError.handled, 'retry');
            done();
          })
      })
      .error(done);
  });

  it('should handle socket hang up connection error -- retry', function(done) {
    var error = new Error('socket hang up');
    error.code = 'ECONNRESET';

    RippleTransactions
      .create(fixtures.outgoing_record)
      .then(function (rippleTransaction) {
        var outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._rippleRestResponseHandler(error)
          .then(function(error){
            chai.assert.strictEqual(error.handled, 'retry');
            done();
          })
      })
      .error(done);
  });

});

describe('Sending a queued payment to ripple', function() {
  beforeEach(function(done) {
    gatewayd.database.sync({force: true}).then(function() {
      done();
    });
  });

  it('should record the rejection of a payment from the Ripple Ledger', function(done) {

    RippleTransactions
      .create(fixtures.outgoing_record)
      .then(function (rippleTransaction) {
        var outgoingPayment = new OutgoingPayment(rippleTransaction);
        var confirmationResponse = new Object(fixtures.successful_responses.validated_payment);
        confirmationResponse.result = 'temPATH_DRY';

        return outgoingPayment._recordAcceptanceOrRejectionStatus(confirmationResponse)
          .then(function(status) {
            chai.assert.strictEqual(outgoingPayment.record.state, 'failed');
            done();
          })
      })
      .error(done);
  });

  it.skip('should send a ripple payment, response must have a status url', function(done){
    this.timeout(10000);
    RippleTransactions
      .create(fixtures.outgoing_record)
      .then(function (rippleTransaction) {
        var outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._sendPayment(fixtures.requests.payment)
          .then(function(payment){
            chai.assert(payment.success);
            chai.assert(payment.status_url);
            done();
          })
      })
      .error(done);
  });

  it('should mark record pending while a payment is being confirmed', function(done){
    this.timeout(10000);
    RippleTransactions
      .create(fixtures.outgoing_record)
      .then(function (rippleTransaction) {
        var outgoingPayment = new OutgoingPayment(rippleTransaction);
        outgoingPayment._confirmPayment(fixtures.requests.pending_payment)
          .error(function(payment){
            chai.assert(outgoingPayment.record.state, 'pending');
            done();
          });
      })
      .error(done);
  });

  it.skip('should successfully submit outgoing payments and must have transaction_hash and transaction_state', function(done){
    this.timeout(10000);
    var outgoingPayment;
    RippleTransactions
      .create(fixtures.outgoing_record)
      .then(function(rippleTransaction){
        outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._sendPayment(fixtures.requests.payment)
      })
      .then(function(pendingPayment){
        return outgoingPayment._confirmPayment(pendingPayment)
      })
      .then(function(payment){
        return outgoingPayment._recordAcceptanceOrRejectionStatus(payment)
      })
      .then(function(payment){
        chai.assert.strictEqual(payment.transaction_state, 'tesSUCCESS');
        chai.assert(payment.transaction_hash);
        done();
     })
    .error(done);
  });

  it.skip('should successfully submit outgoing payments with invoice and memos fields', function(done){
    this.timeout(10000);
    var outgoingPayment;
    RippleTransactions
      .create(fixtures.outgoing_record_invoice_id_memos)
      .then(function(rippleTransaction){
        outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._sendPayment(fixtures.requests.payment)
      })
      .then(function(pendingPayment){
        return outgoingPayment._confirmPayment(pendingPayment)
      })
      .then(function(confirmedPayment){
        chai.assert(confirmedPayment.memos);
        chai.assert.isArray(confirmedPayment.memos);
        chai.assert.equal(confirmedPayment.invoice_id, fixtures.outgoing_record_invoice_id_memos.invoice_id);
        done();
      })
      .error(done);
  });

  it('should update the outgoing payment with the source balance changes', function(done) {

    var validatedPayment = fixtures.successful_responses.validated_payment;

    validatedPayment.source_balance_changes = [
      { value: '-1.012', currency: 'XRP', issuer: '' },
      { value: '1',      currency: 'BTC', issuer: 'rJMtFJ7hKzvcGyzKp9rN9PrqNPReSsdFv5' },
      { value: '0.45',   currency: 'BTC', issuer: 'rwjYEBN9DSMnxLzGVEfbqmabDd2sr2kzcz' }
    ];

    validatedPayment.destination_balance_changes = [
      { value: '0.5', currency: 'XAU', issuer: 'rJMtFJ7hKzvcGyzKp9rN9PrqNPReSsdFv5' },
      { value: '0.2', currency: 'XAU', issuer: 'rwjYEBN9DSMnxLzGVEfbqmabDd2sr2kzcz' }
    ];

    RippleTransactions
      .create(fixtures.outgoing_record_invoice_id_memos)
      .then(function(rippleTransaction){
        outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._recordAcceptanceOrRejectionStatus(validatedPayment)
      })
      .then(function(rippleTransaction) {
        chai.assert.strictEqual(rippleTransaction.to_currency, 'XAU');
        chai.assert.strictEqual(rippleTransaction.to_amount, 0.7);
        chai.assert.strictEqual(rippleTransaction.from_currency, 'BTC');
        chai.assert.strictEqual(rippleTransaction.from_amount, 1.45);
        done();
      })
      .error(done);
  });

  it('should update the outgoing payment with the source balance changes', function(done) {

    var validatedPayment = fixtures.successful_responses.validated_payment;

    validatedPayment.source_balance_changes = [
      { value: '-1.012', currency: 'XRP', issuer: '' },
      { value: '1',      currency: 'BTC', issuer: 'rJMtFJ7hKzvcGyzKp9rN9PrqNPReSsdFv5' },
      { value: '0.45',   currency: 'BTC', issuer: 'rwjYEBN9DSMnxLzGVEfbqmabDd2sr2kzcz' }
    ];

    validatedPayment.destination_balance_changes = [
      { value: '0.5', currency: 'XAU', issuer: 'rJMtFJ7hKzvcGyzKp9rN9PrqNPReSsdFv5' },
      { value: '0.2', currency: 'XAU', issuer: 'rwjYEBN9DSMnxLzGVEfbqmabDd2sr2kzcz' }
    ];

    RippleTransactions
      .create(fixtures.outgoing_record_invoice_id_memos)
      .then(function(rippleTransaction){
        outgoingPayment = new OutgoingPayment(rippleTransaction);
        return outgoingPayment._recordAcceptanceOrRejectionStatus(validatedPayment)
      })
      .then(function(rippleTransaction) {
        chai.assert.strictEqual(rippleTransaction.to_currency, 'XAU');
        chai.assert.strictEqual(rippleTransaction.to_amount, 0.7);
        chai.assert.strictEqual(rippleTransaction.from_currency, 'BTC');
        chai.assert.strictEqual(rippleTransaction.from_amount, 1.45);
        done();
      })
      .error(function(error) {
        done(new Error(error);
      });
  });

});
<|endoftext|>


<commit_before>/*
 * @name: taskDirective
 */
define(function(require){

	var _ = require('underscore');

	directive.$inject = ['taskActivate','managerCanvas','$document', '$window', '$rootScope'];
	function directive(activateTask, canvas, $document, $window, $rootScope){
		return {
			scope:{
				task: '=piTask'
			},
			link: function($scope, $element){
				var task = $scope.task;
				var canvasOff, oldTitle;
				var def;
				var promise;
				var proceedObject;
				var settings = $scope.$parent.settings || {};
				var script = task.$script || {};
				var taskName = task.name || script.name;

				if (!task){
					return;
				}

				/**
				 * listen for skip events
				 */
				if (settings.skip){
					$document.on('keydown',proceedListener);
					$scope.$on('$destroy', function(){
						$document.off('keydown', proceedListener);
						$document.off('keydown', skipListener);
					});
				}

				/**
				 * Setup current object
				 */
				$rootScope.current = $window.piGlobal.current = script.current || {};
				if (taskName){
					// extend current script with the piGlobal object
					_.extend($rootScope.current, $window.piGlobal[taskName] || {});
					// set the current object back into the global
					$window.piGlobal[taskName] = script.current;
				}

				/**
				 * Activate task
				 */
				def = activateTask(task, $element, $scope.$new());
				promise = def.promise;

				/**
				 * Add and remove canvas
				 */
				canvasOff = canvas(task.canvas); // apply canvas settings
				promise['finally'](canvasOff); // remove canvas settings

				/**
				 * Add and remove title
				 * if task.title is set, set the title and remove at the end...
				 */
				if (task.title){
					oldTitle = $document[0].title;
					$document[0].title = task.title;
					promise['finally'](function(){$document[0].title = oldTitle;});
				}

				promise['finally'](function(){
					$scope.$emit('task:done', proceedObject);
				});

				/**
				 * Listen for skip events
				 * F5 = refresh
				 * esc = set up arrows for prev/next
				 * @param  {Event} e
				 */
				function proceedListener(e){
					var which = e.which || e.keyCode;
					var key = (e.key || '').toUpperCase();

					// ctrl r ==> refresh
					if ((which == 82 || key == 'R') && e.ctrlKey) {
						proceed(e, 'current');
						e.preventDefault();
					}

					// esc ==> listen for skip direction (once)
					if(which == 27) {
						$document.one('keydown', skipListener);
			        }
				}

				// skip on left/right key codes
				function skipListener(e){
					var which = e.which || e.keyCode;
					if (which == 37) {proceed(e, 'prev');}
					if (which == 39) {proceed(e, 'next');}
				}

				// end task and proceed where needed
				function proceed(e, target){
					e.preventDefault();
					proceedObject = {type:target, bustCache:true}; // when the deferred is resolved this object is used to tell piqPage where to proceed.
					def.resolve();
				}

			}
		};
	}

	return directive;
});<commit_msg>feat(manager): warn for repeating task names<commit_after>/*
 * @name: taskDirective
 */
define(function(require){

	var _ = require('underscore');

	directive.$inject = ['taskActivate','managerCanvas','$document', '$window', '$rootScope', 'piConsole'];
	function directive(activateTask, canvas, $document, $window, $rootScope, piConsole){
		return {
			scope:{
				task: '=piTask'
			},
			link: function($scope, $element){
				var task = $scope.task;
				var canvasOff, oldTitle;
				var def;
				var promise;
				var proceedObject;
				var settings = $scope.$parent.settings || {};
				var script = task.$script || {};
				var taskName = task.name || script.name;
				var piGlobal = window.piGlobal;

				if (!task){
					return;
				}

				/**
				 * listen for skip events
				 */
				if (settings.skip){
					$document.on('keydown',proceedListener);
					$scope.$on('$destroy', function(){
						$document.off('keydown', proceedListener);
						$document.off('keydown', skipListener);
					});
				}

				/**
				 * Setup current object
				 */
				$rootScope.current = piGlobal.current = script.current || {};
				if (taskName){
					if (piGlobal[taskName]){
						piConsole(['task']).warn('This taskName has already been in use: "' + taskName + '"');
					}
					// extend current script with the piGlobal object
					_.extend($rootScope.current, piGlobal[taskName] || {});
					// set the current object back into the global
					$window.piGlobal[taskName] = script.current;
				}

				/**
				 * Activate task
				 */
				def = activateTask(task, $element, $scope.$new());
				promise = def.promise;

				/**
				 * Add and remove canvas
				 */
				canvasOff = canvas(task.canvas); // apply canvas settings
				promise['finally'](canvasOff); // remove canvas settings

				/**
				 * Add and remove title
				 * if task.title is set, set the title and remove at the end...
				 */
				if (task.title){
					oldTitle = $document[0].title;
					$document[0].title = task.title;
					promise['finally'](function(){$document[0].title = oldTitle;});
				}

				promise['finally'](function(){
					$scope.$emit('task:done', proceedObject);
				});

				/**
				 * Listen for skip events
				 * F5 = refresh
				 * esc = set up arrows for prev/next
				 * @param  {Event} e
				 */
				function proceedListener(e){
					var which = e.which || e.keyCode;
					var key = (e.key || '').toUpperCase();

					// ctrl r ==> refresh
					if ((which == 82 || key == 'R') && e.ctrlKey) {
						proceed(e, 'current');
						e.preventDefault();
					}

					// esc ==> listen for skip direction (once)
					if(which == 27) {
						$document.one('keydown', skipListener);
			        }
				}

				// skip on left/right key codes
				function skipListener(e){
					var which = e.which || e.keyCode;
					if (which == 37) {proceed(e, 'prev');}
					if (which == 39) {proceed(e, 'next');}
				}

				// end task and proceed where needed
				function proceed(e, target){
					e.preventDefault();
					proceedObject = {type:target, bustCache:true}; // when the deferred is resolved this object is used to tell piqPage where to proceed.
					def.resolve();
				}

			}
		};
	}

	return directive;
});<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

import React from 'react';
import { _t, _tJsx } from '../../../languageHandler';
import sdk from '../../../index';
import Login from '../../../Login';

// For validating phone numbers without country codes
const PHONE_NUMBER_REGEX = /^[0-9\(\)\-\s]*$/;

/**
 * A wire component which glues together login UI components and Login logic
 */
module.exports = React.createClass({
    displayName: 'Login',

    propTypes: {
        onLoggedIn: React.PropTypes.func.isRequired,

        enableGuest: React.PropTypes.bool,

        customHsUrl: React.PropTypes.string,
        customIsUrl: React.PropTypes.string,
        defaultHsUrl: React.PropTypes.string,
        defaultIsUrl: React.PropTypes.string,
        // Secondary HS which we try to log into if the user is using
        // the default HS but login fails. Useful for migrating to a
        // different home server without confusing users.
        fallbackHsUrl: React.PropTypes.string,

        defaultDeviceDisplayName: React.PropTypes.string,

        // login shouldn't know or care how registration is done.
        onRegisterClick: React.PropTypes.func.isRequired,

        // login shouldn't care how password recovery is done.
        onForgotPasswordClick: React.PropTypes.func,
        onCancelClick: React.PropTypes.func,
    },

    getInitialState: function() {
        return {
            busy: false,
            errorText: null,
            loginIncorrect: false,
            enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl,
            enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl,

            // used for preserving form values when changing homeserver
            username: "",
            phoneCountry: null,
            phoneNumber: "",
            currentFlow: "m.login.password",
        };
    },

    componentWillMount: function() {
        this._initLoginLogic();
    },

    onPasswordLogin: function(username, phoneCountry, phoneNumber, password) {
        this.setState({
            busy: true,
            errorText: null,
            loginIncorrect: false,
        });

        this._loginLogic.loginViaPassword(
            username, phoneCountry, phoneNumber, password,
        ).then((data) => {
            this.props.onLoggedIn(data);
        }, (error) => {
            this._setStateFromError(error, true);
        }).finally(() => {
            this.setState({
                busy: false
            });
        }).done();
    },

    onCasLogin: function() {
      this._loginLogic.redirectToCas();
    },

    _onLoginAsGuestClick: function() {
        var self = this;
        self.setState({
            busy: true,
            errorText: null,
            loginIncorrect: false,
        });

        this._loginLogic.loginAsGuest().then(function(data) {
            self.props.onLoggedIn(data);
        }, function(error) {
            self._setStateFromError(error, true);
        }).finally(function() {
            self.setState({
                busy: false
            });
        }).done();
    },

    onUsernameChanged: function(username) {
        this.setState({ username: username });
    },

    onPhoneCountryChanged: function(phoneCountry) {
        this.setState({ phoneCountry: phoneCountry });
    },

    onPhoneNumberChanged: function(phoneNumber) {
        // Validate the phone number entered
        if (!PHONE_NUMBER_REGEX.test(phoneNumber)) {
            this.setState({ errorText: _t('The phone number entered looks invalid') });
            return;
        }

        this.setState({
            phoneNumber: phoneNumber,
            errorText: null,
        });
    },

    onServerConfigChange: function(config) {
        var self = this;
        let newState = {
            errorText: null, // reset err messages
        };
        if (config.hsUrl !== undefined) {
            newState.enteredHomeserverUrl = config.hsUrl;
        }
        if (config.isUrl !== undefined) {
            newState.enteredIdentityServerUrl = config.isUrl;
        }
        this.setState(newState, function() {
            self._initLoginLogic(config.hsUrl || null, config.isUrl);
        });
    },

    _initLoginLogic: function(hsUrl, isUrl) {
        var self = this;
        hsUrl = hsUrl || this.state.enteredHomeserverUrl;
        isUrl = isUrl || this.state.enteredIdentityServerUrl;

        var fallbackHsUrl = hsUrl == this.props.defaultHsUrl ? this.props.fallbackHsUrl : null;

        var loginLogic = new Login(hsUrl, isUrl, fallbackHsUrl, {
            defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
        });
        this._loginLogic = loginLogic;

        this.setState({
            enteredHomeserverUrl: hsUrl,
            enteredIdentityServerUrl: isUrl,
            busy: true,
            loginIncorrect: false,
        });

        loginLogic.getFlows().then(function(flows) {
            // old behaviour was to always use the first flow without presenting
            // options. This works in most cases (we don't have a UI for multiple
            // logins so let's skip that for now).
            loginLogic.chooseFlow(0);
            self.setState({
                currentFlow: self._getCurrentFlowStep(),
            });
        }, function(err) {
            self._setStateFromError(err, false);
        }).finally(function() {
            self.setState({
                busy: false,
            });
        });
    },

    _getCurrentFlowStep: function() {
        return this._loginLogic ? this._loginLogic.getCurrentFlowStep() : null;
    },

    _setStateFromError: function(err, isLoginAttempt) {
        this.setState({
            errorText: this._errorTextFromError(err),
            // https://matrix.org/jira/browse/SYN-744
            loginIncorrect: isLoginAttempt && (err.httpStatus == 401 || err.httpStatus == 403)
        });
    },

    _errorTextFromError(err) {
        if (err.friendlyText) {
            return err.friendlyText;
        }

        let errCode = err.errcode;
        if (!errCode && err.httpStatus) {
            errCode = "HTTP " + err.httpStatus;
        }

        let errorText = _t("Error: Problem communicating with the given homeserver.") +
                (errCode ? " (" + errCode + ")" : "");

        if (err.cors === 'rejected') {
            if (window.location.protocol === 'https:' &&
                (this.state.enteredHomeserverUrl.startsWith("http:") ||
                 !this.state.enteredHomeserverUrl.startsWith("http")))
            {
                errorText =
                    { _tJsx("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " +
                            "Either use HTTPS or enable unsafe scripts.",
                      /(.*?)<\/a>/,
                      (sub) => { return { sub }; }
                    )}
                ;
            }
            else {
                errorText =
                    { _tJsx("Can't connect to homeserver - please check your connectivity and ensure your homeserver's SSL certificate is trusted.",
                      /(.*?)<\/a>/,
                      (sub) => { return { sub }; }
                    )}
                ;
            }
        }

        return errorText;
    },

    componentForStep: function(step) {
        switch (step) {
            case 'm.login.password':
                const PasswordLogin = sdk.getComponent('login.PasswordLogin');
                return (
                    <PasswordLogin
                        onSubmit={this.onPasswordLogin}
                        initialUsername={this.state.username}
                        initialPhoneCountry={this.state.phoneCountry}
                        initialPhoneNumber={this.state.phoneNumber}
                        onUsernameChanged={this.onUsernameChanged}
                        onPhoneCountryChanged={this.onPhoneCountryChanged}
                        onPhoneNumberChanged={this.onPhoneNumberChanged}
                        onForgotPasswordClick={this.props.onForgotPasswordClick}
                        loginIncorrect={this.state.loginIncorrect}
                    />
                );
            case 'm.login.cas':
                const CasLogin = sdk.getComponent('login.CasLogin');
                return (
                    <CasLogin onSubmit={this.onCasLogin} />
                );
            default:
                if (!step) {
                    return;
                }
                return (

                    { _t('Sorry, this homeserver is using a login which is not recognised ')}({step})

                );
        }
    },

    render: function() {
        const Loader = sdk.getComponent("elements.Spinner");
        const LoginHeader = sdk.getComponent("login.LoginHeader");
        const LoginFooter = sdk.getComponent("login.LoginFooter");
        const ServerConfig = sdk.getComponent("login.ServerConfig");
        const loader = this.state.busy ?
<Loader />
 : null;

        var loginAsGuestJsx;
        if (this.props.enableGuest) {
            loginAsGuestJsx =

                    { _t('Login as guest')}
                ;
        }

        var returnToAppJsx;
        if (this.props.onCancelClick) {
            returnToAppJsx =

                    { _t('Return to app')}
                ;
        }

        return (

                    <LoginHeader />

{ _t('Sign in')}
                            { loader }

                        { this.componentForStep(this.state.currentFlow) }
                        <ServerConfig ref="serverConfig"
                            withToggleButton={true}
                            customHsUrl={this.props.customHsUrl}
                            customIsUrl={this.props.customIsUrl}
                            defaultHsUrl={this.props.defaultHsUrl}
                            defaultIsUrl={this.props.defaultIsUrl}
                            onServerConfigChange={this.onServerConfigChange}
                            delayTimeMs={1000}/>

                                { this.state.errorText }

                            { _t('Create an account')}

                        { loginAsGuestJsx }
                        { returnToAppJsx }
                        <LoginFooter />

        );
    }
});
<commit_msg>Formatting<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

import React from 'react';
import { _t, _tJsx } from '../../../languageHandler';
import sdk from '../../../index';
import Login from '../../../Login';

// For validating phone numbers without country codes
const PHONE_NUMBER_REGEX = /^[0-9\(\)\-\s]*$/;

/**
 * A wire component which glues together login UI components and Login logic
 */
module.exports = React.createClass({
    displayName: 'Login',

    propTypes: {
        onLoggedIn: React.PropTypes.func.isRequired,

        enableGuest: React.PropTypes.bool,

        customHsUrl: React.PropTypes.string,
        customIsUrl: React.PropTypes.string,
        defaultHsUrl: React.PropTypes.string,
        defaultIsUrl: React.PropTypes.string,
        // Secondary HS which we try to log into if the user is using
        // the default HS but login fails. Useful for migrating to a
        // different home server without confusing users.
        fallbackHsUrl: React.PropTypes.string,

        defaultDeviceDisplayName: React.PropTypes.string,

        // login shouldn't know or care how registration is done.
        onRegisterClick: React.PropTypes.func.isRequired,

        // login shouldn't care how password recovery is done.
        onForgotPasswordClick: React.PropTypes.func,
        onCancelClick: React.PropTypes.func,
    },

    getInitialState: function() {
        return {
            busy: false,
            errorText: null,
            loginIncorrect: false,
            enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl,
            enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl,

            // used for preserving form values when changing homeserver
            username: "",
            phoneCountry: null,
            phoneNumber: "",
            currentFlow: "m.login.password",
        };
    },

    componentWillMount: function() {
        this._initLoginLogic();
    },

    onPasswordLogin: function(username, phoneCountry, phoneNumber, password) {
        this.setState({
            busy: true,
            errorText: null,
            loginIncorrect: false,
        });

        this._loginLogic.loginViaPassword(
            username, phoneCountry, phoneNumber, password,
        ).then((data) => {
            this.props.onLoggedIn(data);
        }, (error) => {
            this._setStateFromError(error, true);
        }).finally(() => {
            this.setState({
                busy: false
            });
        }).done();
    },

    onCasLogin: function() {
      this._loginLogic.redirectToCas();
    },

    _onLoginAsGuestClick: function() {
        var self = this;
        self.setState({
            busy: true,
            errorText: null,
            loginIncorrect: false,
        });

        this._loginLogic.loginAsGuest().then(function(data) {
            self.props.onLoggedIn(data);
        }, function(error) {
            self._setStateFromError(error, true);
        }).finally(function() {
            self.setState({
                busy: false
            });
        }).done();
    },

    onUsernameChanged: function(username) {
        this.setState({ username: username });
    },

    onPhoneCountryChanged: function(phoneCountry) {
        this.setState({ phoneCountry: phoneCountry });
    },

    onPhoneNumberChanged: function(phoneNumber) {
        // Validate the phone number entered
        if (!PHONE_NUMBER_REGEX.test(phoneNumber)) {
            this.setState({ errorText: _t('The phone number entered looks invalid') });
            return;
        }

        this.setState({
            phoneNumber: phoneNumber,
            errorText: null,
        });
    },

    onServerConfigChange: function(config) {
        var self = this;
        let newState = {
            errorText: null, // reset err messages
        };
        if (config.hsUrl !== undefined) {
            newState.enteredHomeserverUrl = config.hsUrl;
        }
        if (config.isUrl !== undefined) {
            newState.enteredIdentityServerUrl = config.isUrl;
        }
        this.setState(newState, function() {
            self._initLoginLogic(config.hsUrl || null, config.isUrl);
        });
    },

    _initLoginLogic: function(hsUrl, isUrl) {
        var self = this;
        hsUrl = hsUrl || this.state.enteredHomeserverUrl;
        isUrl = isUrl || this.state.enteredIdentityServerUrl;

        var fallbackHsUrl = hsUrl == this.props.defaultHsUrl ? this.props.fallbackHsUrl : null;

        var loginLogic = new Login(hsUrl, isUrl, fallbackHsUrl, {
            defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
        });
        this._loginLogic = loginLogic;

        this.setState({
            enteredHomeserverUrl: hsUrl,
            enteredIdentityServerUrl: isUrl,
            busy: true,
            loginIncorrect: false,
        });

        loginLogic.getFlows().then(function(flows) {
            // old behaviour was to always use the first flow without presenting
            // options. This works in most cases (we don't have a UI for multiple
            // logins so let's skip that for now).
            loginLogic.chooseFlow(0);
            self.setState({
                currentFlow: self._getCurrentFlowStep(),
            });
        }, function(err) {
            self._setStateFromError(err, false);
        }).finally(function() {
            self.setState({
                busy: false,
            });
        });
    },

    _getCurrentFlowStep: function() {
        return this._loginLogic ? this._loginLogic.getCurrentFlowStep() : null;
    },

    _setStateFromError: function(err, isLoginAttempt) {
        this.setState({
            errorText: this._errorTextFromError(err),
            // https://matrix.org/jira/browse/SYN-744
            loginIncorrect: isLoginAttempt && (err.httpStatus == 401 || err.httpStatus == 403)
        });
    },

    _errorTextFromError(err) {
        if (err.friendlyText) {
            return err.friendlyText;
        }

        let errCode = err.errcode;
        if (!errCode && err.httpStatus) {
            errCode = "HTTP " + err.httpStatus;
        }

        let errorText = _t("Error: Problem communicating with the given homeserver.") +
                (errCode ? " (" + errCode + ")" : "");

        if (err.cors === 'rejected') {
            if (window.location.protocol === 'https:' &&
                (this.state.enteredHomeserverUrl.startsWith("http:") ||
                 !this.state.enteredHomeserverUrl.startsWith("http"))
            ) {
                errorText =
                    { _tJsx("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " +
                            "Either use HTTPS or enable unsafe scripts.",
                      /(.*?)<\/a>/,
                      (sub) => { return { sub }; }
                    )}
                ;
            } else {
                errorText =
                    { _tJsx("Can't connect to homeserver - please check your connectivity and ensure your homeserver's SSL certificate is trusted.",
                      /(.*?)<\/a>/,
                      (sub) => { return { sub }; }
                    )}
                ;
            }
        }

        return errorText;
    },

    componentForStep: function(step) {
        switch (step) {
            case 'm.login.password':
                const PasswordLogin = sdk.getComponent('login.PasswordLogin');
                return (
                    <PasswordLogin
                        onSubmit={this.onPasswordLogin}
                        initialUsername={this.state.username}
                        initialPhoneCountry={this.state.phoneCountry}
                        initialPhoneNumber={this.state.phoneNumber}
                        onUsernameChanged={this.onUsernameChanged}
                        onPhoneCountryChanged={this.onPhoneCountryChanged}
                        onPhoneNumberChanged={this.onPhoneNumberChanged}
                        onForgotPasswordClick={this.props.onForgotPasswordClick}
                        loginIncorrect={this.state.loginIncorrect}
                    />
                );
            case 'm.login.cas':
                const CasLogin = sdk.getComponent('login.CasLogin');
                return (
                    <CasLogin onSubmit={this.onCasLogin} />
                );
            default:
                if (!step) {
                    return;
                }
                return (

                    { _t('Sorry, this homeserver is using a login which is not recognised ')}({step})

                );
        }
    },

    render: function() {
        const Loader = sdk.getComponent("elements.Spinner");
        const LoginHeader = sdk.getComponent("login.LoginHeader");
        const LoginFooter = sdk.getComponent("login.LoginFooter");
        const ServerConfig = sdk.getComponent("login.ServerConfig");
        const loader = this.state.busy ?
<Loader />
 : null;

        var loginAsGuestJsx;
        if (this.props.enableGuest) {
            loginAsGuestJsx =

                    { _t('Login as guest')}
                ;
        }

        var returnToAppJsx;
        if (this.props.onCancelClick) {
            returnToAppJsx =

                    { _t('Return to app')}
                ;
        }

        return (

                    <LoginHeader />

{ _t('Sign in')}
                            { loader }

                        { this.componentForStep(this.state.currentFlow) }
                        <ServerConfig ref="serverConfig"
                            withToggleButton={true}
                            customHsUrl={this.props.customHsUrl}
                            customIsUrl={this.props.customIsUrl}
                            defaultHsUrl={this.props.defaultHsUrl}
                            defaultIsUrl={this.props.defaultIsUrl}
                            onServerConfigChange={this.onServerConfigChange}
                            delayTimeMs={1000}/>

                                { this.state.errorText }

                            { _t('Create an account')}

                        { loginAsGuestJsx }
                        { returnToAppJsx }
                        <LoginFooter />

        );
    }
});
<|endoftext|>


<commit_before>/**
 * /config/express.js
 *
 * This file contains all custom middlewares for Taskboard application.
 *
 * @type {*}
 */

var passport = require("passport");
var moment = require("moment-timezone");

module.exports.express = {
    customMiddleware: function (app) {
        // Add passport middleware
        app.use(passport.initialize());
        app.use(passport.session());

        // Add some basic data for all views
        app.use(function(req, res, next) {
            if (req.user && req.user.language) {
                moment.lang(req.user.language);
            }

            res.locals.currentUser = req.user;
            res.locals.moment = moment;

            next();
        });
    }
};<commit_msg>Added numeral.js to response locals, so that can be used in every view. Also implemented numeral.js localization change.<commit_after>/**
 * /config/express.js
 *
 * This file contains all custom middlewares for Taskboard application.
 *
 * @type {*}
 */

var passport = require("passport");
var moment = require("moment-timezone");
var numeral = require("numeral");

module.exports.express = {
    customMiddleware: function(app) {
        // Add passport middleware and initialize it
        app.use(passport.initialize());
        app.use(passport.session());

        // Add some basic data for all views
        app.use(function(req, res, next) {
            if (req.user && req.user.language) {
                // Change moment language
                moment.lang(req.user.language);

                // Change numeral language
                numeral.language(req.user.language, require("../node_modules/numeral/languages/" + req.user.language + ".js"));
                numeral.language(req.user.language);
            }

            // Set data to response locals, so those are accessible by any view
            res.locals.currentUser = req.user;
            res.locals.moment = moment;
            res.locals.numeral = numeral;

            next();
        });
    }
};<|endoftext|>


<commit_before>/* @flow */
import type {
  GlobalState,
  SessionState,
  DraftState,
  FetchingState,
  FlagsState,
  LoadingState,
  MessageState,
  MuteState,
  NavigationState,
  TopicsState,
  PresenceState,
  RealmEmojiState,
  RealmState,
  SettingsState,
  StreamUnreadItem,
  TypingState,
  UnreadHuddlesState,
  UnreadMentionsState,
  UnreadPmsState,
  Account,
  Debug,
  Subscription,
  Stream,
  Outbox,
  User,
} from './types';

export const getAccounts = (state: GlobalState): Account[] => state.accounts;

export const getSession = (state: GlobalState): SessionState => state.session;

export const getIsActive = (state: GlobalState): boolean => state.session.isActive;
export const getIsOnline = (state: GlobalState): boolean => state.session.isOnline;
export const getDebug = (state: GlobalState): Debug => state.session.debug;
export const getIsHydrated = (state: GlobalState): boolean => state.session.isHydrated;

export const getCanCreateStreams = (state: GlobalState): boolean => state.realm.canCreateStreams;

export const getDrafts = (state: GlobalState): DraftState => state.drafts;

export const getLoading = (state: GlobalState): LoadingState => state.loading;

export const getMute = (state: GlobalState): MuteState => state.mute;

export const getTyping = (state: GlobalState): TypingState => state.typing;

export const getTopics = (state: GlobalState): TopicsState => state.topics;

export const getUsers = (state: GlobalState): User[] => state.users;

export const getFetching = (state: GlobalState): FetchingState => state.fetching;

export const getFlags = (state: GlobalState): FlagsState => state.flags;

export const getReadFlags = (state: GlobalState): Object => state.flags.read;

export const getAllMessages = (state: GlobalState): MessageState => state.messages;

export const getNav = (state: GlobalState): NavigationState => state.nav;

export const getSettings = (state: GlobalState): SettingsState => state.settings;

export const getSubscriptions = (state: GlobalState): Subscription[] => state.subscriptions;

export const getStreams = (state: GlobalState): Stream[] => state.streams;

export const getPresence = (state: GlobalState): PresenceState => state.presence;

export const getOutbox = (state: GlobalState): Outbox[] => state.outbox;

export const getUnreadStreams = (state: GlobalState): StreamUnreadItem[] => state.unread.streams;

export const getUnreadPms = (state: GlobalState): UnreadPmsState => state.unread.pms;

export const getUnreadHuddles = (state: GlobalState): UnreadHuddlesState => state.unread.huddles;

export const getUnreadMentions = (state: GlobalState): UnreadMentionsState => state.unread.mentions;

export const getRealm = (state: GlobalState): RealmState => state.realm;

export const getRealmEmoji = (state: GlobalState): RealmEmojiState => state.realm.emoji;

export const getNavigationRoutes = (state: GlobalState): Object[] => state.nav.routes;

export const getNavigationIndex = (state: GlobalState): number => state.nav.index;
<commit_msg>selector: Create directSelector to get isAdmin.<commit_after>/* @flow */
import type {
  GlobalState,
  SessionState,
  DraftState,
  FetchingState,
  FlagsState,
  LoadingState,
  MessageState,
  MuteState,
  NavigationState,
  TopicsState,
  PresenceState,
  RealmEmojiState,
  RealmState,
  SettingsState,
  StreamUnreadItem,
  TypingState,
  UnreadHuddlesState,
  UnreadMentionsState,
  UnreadPmsState,
  Account,
  Debug,
  Subscription,
  Stream,
  Outbox,
  User,
} from './types';

export const getAccounts = (state: GlobalState): Account[] => state.accounts;

export const getSession = (state: GlobalState): SessionState => state.session;

export const getIsActive = (state: GlobalState): boolean => state.session.isActive;
export const getIsOnline = (state: GlobalState): boolean => state.session.isOnline;
export const getDebug = (state: GlobalState): Debug => state.session.debug;
export const getIsHydrated = (state: GlobalState): boolean => state.session.isHydrated;

export const getCanCreateStreams = (state: GlobalState): boolean => state.realm.canCreateStreams;

export const getDrafts = (state: GlobalState): DraftState => state.drafts;

export const getLoading = (state: GlobalState): LoadingState => state.loading;

export const getMute = (state: GlobalState): MuteState => state.mute;

export const getTyping = (state: GlobalState): TypingState => state.typing;

export const getTopics = (state: GlobalState): TopicsState => state.topics;

export const getUsers = (state: GlobalState): User[] => state.users;

export const getFetching = (state: GlobalState): FetchingState => state.fetching;

export const getFlags = (state: GlobalState): FlagsState => state.flags;

export const getReadFlags = (state: GlobalState): Object => state.flags.read;

export const getAllMessages = (state: GlobalState): MessageState => state.messages;

export const getNav = (state: GlobalState): NavigationState => state.nav;

export const getSettings = (state: GlobalState): SettingsState => state.settings;

export const getSubscriptions = (state: GlobalState): Subscription[] => state.subscriptions;

export const getStreams = (state: GlobalState): Stream[] => state.streams;

export const getPresence = (state: GlobalState): PresenceState => state.presence;

export const getOutbox = (state: GlobalState): Outbox[] => state.outbox;

export const getUnreadStreams = (state: GlobalState): StreamUnreadItem[] => state.unread.streams;

export const getUnreadPms = (state: GlobalState): UnreadPmsState => state.unread.pms;

export const getUnreadHuddles = (state: GlobalState): UnreadHuddlesState => state.unread.huddles;

export const getUnreadMentions = (state: GlobalState): UnreadMentionsState => state.unread.mentions;

export const getRealm = (state: GlobalState): RealmState => state.realm;

export const getRealmEmoji = (state: GlobalState): RealmEmojiState => state.realm.emoji;

export const getNavigationRoutes = (state: GlobalState): Object[] => state.nav.routes;

export const getNavigationIndex = (state: GlobalState): number => state.nav.index;

export const getIsAdmin = (state: GlobalState): boolean => state.realm.isAdmin;
<|endoftext|>


<commit_before>var mongoose = require('mongoose'),
    encryption = require('../utilities/encryption'),
    ShoppingList = mongoose.model('ShoppingList'),
    ShoppingListSchema = mongoose.model('ShoppingList').schema,
    Product = mongoose.model('Product'),
    ProductSchema = mongoose.model('Product').schema,
    Category = mongoose.model('Category');

var userSchema = mongoose.Schema({
    username: { type: String, required: true, unique: true},
    firstName: { type: String, required: true, unique: true},
    lastName: { type: String, required: true, unique: true},
    email: { type: String, required: true, unique: true },
    address: String,
    salt: String,
    hashPass: String,
    roles: [String],
    availableProducts: [ProductSchema],
    shoppingListsHistory: [ShoppingListSchema],
    productsHistory: [ProductSchema]
});

userSchema.method({
    authenticate: function (password) {
        if (encryption.generateHashedPassword(this.salt, password) === this.hashPass) {
            return true;
        }
        else {
            return false;
        }
    }
});

var User = mongoose.model('User', userSchema);

module.exports.seedInitialUsers = function () {
    User.find({}).exec(function (err, collection) {
        if (err) {
            console.log('Cannot find users ' + err);
        }

        if (collection.length === 0) {
            var salt,
                hashedPwd;

            salt = encryption.generateSalt();
            hashedPwd = encryption.generateHashedPassword(salt, 'yasen');
            User.create({username: 'yasen', firstName: 'Yasen', lastName: 'Mihaylov', salt: salt, hashPass: hashedPwd, roles: ['admin']});

            salt = encryption.generateSalt();
            hashedPwd = encryption.generateHashedPassword(salt, 'zdravko');
            User.create({username: 'zdravko', firstName: 'Zdravko', lastName: 'Georgiev', salt: salt, hashPass: hashedPwd, roles: ['admin']});

            salt = encryption.generateSalt();
            hashedPwd = encryption.generateHashedPassword(salt, 'stefan');
            User.create({username: 'stefan', firstName: 'Stefan', lastName: 'Dimov', salt: salt, hashPass: hashedPwd, roles: ['admin']});

            salt = encryption.generateSalt();
            hashedPwd = encryption.generateHashedPassword(salt, 'niki');
            User.create({username: 'niki', firstName: 'Nikolay', lastName: 'Radkov', salt: salt, hashPass: hashedPwd, roles: ['admin']});

            salt = encryption.generateSalt();
            hashedPwd = encryption.generateHashedPassword(salt, 'flextry');
            User.create({username: 'flextry', firstName: 'Martin', lastName: 'Nikolov', salt: salt, hashPass: hashedPwd, roles: ['admin']});
            console.log('users added to database');

            salt = encryption.generateSalt();
            hashedPwd = encryption.generateHashedPassword(salt, 'user');

            User.create({
                username: 'user',
                firstName: 'User',
                lastName: 'Userov',
                email: 'user@user.com',
                salt: salt,
                hashPass: hashedPwd,
                roles: ['admin'],
                availableProducts: [
                    new Product({
                        name: 'Banana',
                        category: new Category({name: 'Fruits'}),
                        quantity: 5,
                        expirationDate: new Date(),
                        purchaseDate: new Date()
                    }),
                    new Product({
                        name: 'Apple',
                        category: new Category({name: 'Fruits'}),
                        quantity: 7,
                        expirationDate: new Date(),
                        purchaseDate: new Date()
                    }),
                    new Product({
                        name: 'Passion Fruit',
                        category: new Category({name: 'Fruits'}),
                        quantity: 2,
                        expirationDate: new Date(),
                        purchaseDate: new Date()
                    }),
                    new Product({
                        name: 'Potato',
                        category: new Category({name: 'Vegetables'}),
                        quantity: 2,
                        expirationDate: new Date(),
                        purchaseDate: new Date()
                    })
                ],
                shoppingListsHistory: [],
                productsHistory: []
            });
        }
    });
};

<commit_msg>fixed user schema to hold collections of ObjectId's<commit_after>var mongoose = require('mongoose'),
    encryption = require('../utilities/encryption'),
    ShoppingList = mongoose.model('ShoppingList'),
    ShoppingListSchema = mongoose.model('ShoppingList').schema,
    Product = mongoose.model('Product'),
    ProductSchema = mongoose.model('Product').schema,
    Category = mongoose.model('Category');

var userSchema = mongoose.Schema({
    username: { type: String, required: true, unique: true},
    firstName: { type: String, required: true, unique: true},
    lastName: { type: String, required: true, unique: true},
    email: { type: String, required: true, unique: true },
    address: String,
    salt: String,
    hashPass: String,
    roles: [String],
    availableProducts: [mongoose.Schema.Types.ObjectId],
    shoppingListsHistory: [mongoose.Schema.Types.ObjectId],
    productsHistory: [mongoose.Schema.Types.ObjectId]
});

userSchema.method({
    authenticate: function (password) {
        if (encryption.generateHashedPassword(this.salt, password) === this.hashPass) {
            return true;
        }
        else {
            return false;
        }
    }
});

var User = mongoose.model('User', userSchema);

module.exports.seedInitialUsers = function () {
    User.find({}).exec(function (err, collection) {
        if (err) {
            console.log('Cannot find users ' + err);
        }

        if (collection.length === 0) {
            var salt,
                hashedPwd;

            salt = encryption.generateSalt();
            hashedPwd = encryption.generateHashedPassword(salt, 'yasen');
            User.create({username: 'yasen', firstName: 'Yasen', lastName: 'Mihaylov', salt: salt, hashPass: hashedPwd, roles: ['admin']});

            salt = encryption.generateSalt();
            hashedPwd = encryption.generateHashedPassword(salt, 'zdravko');
            User.create({username: 'zdravko', firstName: 'Zdravko', lastName: 'Georgiev', salt: salt, hashPass: hashedPwd, roles: ['admin']});

            salt = encryption.generateSalt();
            hashedPwd = encryption.generateHashedPassword(salt, 'stefan');
            User.create({username: 'stefan', firstName: 'Stefan', lastName: 'Dimov', salt: salt, hashPass: hashedPwd, roles: ['admin']});

            salt = encryption.generateSalt();
            hashedPwd = encryption.generateHashedPassword(salt, 'niki');
            User.create({username: 'niki', firstName: 'Nikolay', lastName: 'Radkov', salt: salt, hashPass: hashedPwd, roles: ['admin']});

            salt = encryption.generateSalt();
            hashedPwd = encryption.generateHashedPassword(salt, 'flextry');
            User.create({username: 'flextry', firstName: 'Martin', lastName: 'Nikolov', salt: salt, hashPass: hashedPwd, roles: ['admin']});
            console.log('users added to database');

            salt = encryption.generateSalt();
            hashedPwd = encryption.generateHashedPassword(salt, 'user');

            User.create({
                username: 'user',
                firstName: 'User',
                lastName: 'Userov',
                email: 'user@user.com',
                salt: salt,
                hashPass: hashedPwd,
                roles: ['admin'],
                availableProducts: [],
                shoppingListsHistory: [],
                productsHistory: []
            });
        }
    });
};

<|endoftext|>


<commit_before>// ==UserScript==
// @name         Booru Tag Parser
// @namespace    http://above.average.website
// @version      1.0.5
// @description  Copy current post tags and rating on boorus and illustration2vec in to the clipboard for easy import in to a program or another booru.
// @author       William Moodhe
// @downloadURL  https://github.com/JetBoom/boorutagparser/raw/master/boorutagparser.user.js
// @updateURL    https://github.com/JetBoom/boorutagparser/raw/master/boorutagparser.user.js

// Illustration2Vec
// @include      *demo.illustration2vec.net*

// Catch-all for boorus
// @include      *booru*/post*
// @include      *booru*/*?page=post*
// @include      *booru*/?page=post

// Boorus with weird names
// @include      *rule34.xxx/index.php?page=post*
// @include      *chan.sankakucomplex.com/post/show/*
// @include      *chan.sankakucomplex.com/?tags=*
// @include      *e621.net/post/*
// @include      *konachan.com/post/*
// @include      *shimmie.katawa-shoujo.com/post/*
// @include      *rule34.paheal.net/post/*
// @include      *rule34hentai.net/post/*
// @include      *tbib.org/index.php?page=post*
// @include      *yande.re/post/*

// @run-at       document-end
// @grant        GM_setClipboard
// @grant        GM_setValue
// @grant        GM_getValue

// ==/UserScript==
/* jshint -W097 */
'use strict';

///////

var copy_key_code = GM_getValue('copy_key_code', 221); // ] key
var copy_sound = GM_getValue('copy_sound', 'http://heavy.noxiousnet.com/boorucopy.ogg');
var iv2_confidence_rating = GM_getValue('iv2_confidence_rating', 20.0);

///////

var tags_selector = 'h5, b';
var button_style = 'font:11px monospace;font-weight:0;border:1px solid black;background:#eee;color:#000;display:block;width:90%;margin:2px auto;z-index:9999;';
var div_style = 'position:fixed;width:160px;right:2px;top:2px;text-align:center;font:11px monospace;font-weight:0;border:1px solid black;background:rgba(255,255,255,0.8);z-index:9999;';

function replaceAll(str, originalstr, newstr)
{
    return str.split(originalstr).join(newstr);
}

function insertTags(tags, selector, prefix)
{
    var elements = document.querySelectorAll(selector);

    for (var i=0; i < elements.length; i++)
    {
        var element = elements[i];
        var text = element.innerHTML;
        if (text.length > 1) // Don't copy - + or ?
        {
            text = replaceAll(text, '_', ' ');
            text = replaceAll(text, '>', '>');
            text = replaceAll(text, '<', '<');

            tags[tags.length] = prefix+text;
        }
    }
}

function insertRating(tags, selector)
{
    var elements = document.querySelectorAll(selector);

    for (var i=0; i < elements.length; i++)
    {
        var element = elements[i];
        var text = element.innerHTML;
        text = replaceAll(text.toLowerCase().trim(), ' ', '');

        if (text.indexOf('rating:explicit') >= 0)
        {
            tags[tags.length] = 'rating:explicit';
            break;
        }
        else if (text.indexOf('rating:questionable') >= 0)
        {
            tags[tags.length] = 'rating:questionable';
            break;
        }
        else if (text.indexOf('rating:safe') >= 0)
        {
            tags[tags.length] = 'rating:safe';
            break;
        }
    }
}

function copyBooruTags(tags, noRating)
{
    // Instead of having a list of boorus and their tags and tag structures I just make a big catch-all.

    // danbooru-like
    insertTags(tags, '#tag-list li.category-3 > a.search-tag', 'series:');
    insertTags(tags, '#tag-list li.category-1 > a.search-tag', 'creator:');
    insertTags(tags, '#tag-list li.category-4 > a.search-tag', 'character:');
    insertTags(tags, '#tag-list li.category-0 > a.search-tag', '');

    // lolibooru-like
    insertTags(tags, 'li.tag-type-copyright > a', 'series:');
    insertTags(tags, 'li.tag-type-author > a', 'creator:');
    insertTags(tags, 'li.tag-type-artist > a', 'creator:');
    insertTags(tags, 'li.tag-type-character > a', 'character:');
    insertTags(tags, 'li.tag-type-general > a', '');
    insertTags(tags, 'li.tag-type-studio > a', 'studio:');
    insertTags(tags, 'li.tag-type-circle > a', 'studio:');
    insertTags(tags, 'li.tag-type-medium > a', 'medium:');
    insertTags(tags, 'li.tag-type-style > a', 'medium:');
    insertTags(tags, 'li.tag-type-meta > a', 'meta:');
    insertTags(tags, 'li.tag-type-species > a', 'species:');
    insertTags(tags, 'li.tag-type-faults > a', 'fault:');

    // booru.org-like
    insertTags(tags, '#tag_list li a', '');

    // paheal-like
    insertTags(tags, 'a.tag_name', '');

    if (!noRating)
    {
        // danbooru-like
        insertRating(tags, '#post-information > ul li');

        // lolibooru-like
        insertRating(tags, '#stats > ul li');

        // booru.org-like
        insertRating(tags, '#tag_list ul');
    }

    /*var elements = document.querySelectorAll(tags_selector);
    for (var i=0; i < elements.length; i++)
    {
        var element = elements[i];
        if (element.innerHTML == 'Tags')
        {
            element.innerHTML = 'Tags (Copied)';
            element.style.color = 'lime';
            break;
        }
    }*/
}

function insertI2VTags(tags, selector, prefix, confidenceRequired)
{
    var elements = document.querySelectorAll(selector);
    for (var i=1; i < elements.length; i++)
    {
        var element = elements[i];

        if (confidenceRequired > 0)
        {
            var confidence = element.children[3].children[0].innerHTML;
            confidence = confidence.substr(0, confidence.length - 1);
            confidence = Number(confidence);

            if (confidence < confidenceRequired)
                continue;
        }

        var tag = element.children[1].innerHTML;

        tag = replaceAll(tag, '_', ' ');

        if (prefix)
            tag = prefix + tag;

        tags[tags.length] = tag;

        if (prefix == 'rating') // only add one rating
            break;
    }
}

function copyI2VTags(tags, confidenceRequired, noGeneral, noRating)
{
    insertI2VTags(tags, 'table#copyright_root tr', 'series:', confidenceRequired);
    insertI2VTags(tags, 'table#character_root tr', 'character:', confidenceRequired);

    if (!noGeneral)
        insertI2VTags(tags, 'table#general_root tr', '', confidenceRequired);

    if (!noRating)
        insertI2VTags(tags, 'table#rating_root tr', 'rating:', confidenceRequired);
}

function doCopyAll()
{
    var tags = [];

    copyBooruTags(tags);
    copyI2VTags(tags, iv2_confidence_rating, false);

    GM_setClipboard(tags.join('\n'));

    var buttontext = '';

    if (tags.length > 0)
    {
        playCopySound();
        buttontext = 'copied ' +  tags.length +' tag(s)'
    }
    else
        buttontext = 'nothing to copy!';

    var button = document.querySelector('button#copytagsbutton');
    if (button)
    {
        button.innerHTML = buttontext;
        setTimeout(function(){ button.innerHTML = 'copy tags'; }, 3000);
    }
}

function doOptions()
{
    optionsArea.style.display = optionsArea.style.display == 'none' ? 'block' : 'none';
}

function doc_keyUp(e)
{
    if (e.keyCode == copy_key_code)
    {
        doCopyAll();
    }
}
document.addEventListener('keyup', doc_keyUp, false);

var elements = document.querySelectorAll(tags_selector);
for (var i=0; i < elements.length; i++)
{
    var element = elements[i];
    if (element.innerHTML == 'Tags')
    {
        element.onclick = copyBooruTags;
        break;
    }
}

if (copy_sound.length > 0)
{
    var audio = document.createElement("audio");
    audio.src = copy_sound;
    audio.preload = false;
}

function playCopySound()
{
    if (audio && copy_sound.length > 0)
        audio.play();
}

function doChangeConfidence(e)
{
    iv2_confidence_rating = Number(optionForConfidence.value);
    captionForConfidence.innerHTML = 'iv2 min confidence: ' + iv2_confidence_rating + '%';
    GM_setValue('iv2_confidence_rating', iv2_confidence_rating);
}

function doChangeCopySound(e)
{
    copy_sound = String(optionForCopySound.value);
    GM_setValue('copy_sound', copy_sound);

    if (audio)
        audio.src = copy_sound;
}

var control = document.createElement('div');
control.id = 'boorutagparser';
control.setAttribute('style', div_style);
document.body.appendChild(control);

var copyButton = document.createElement('button');
copyButton.innerHTML = 'copy tags';
copyButton.id = 'copytagsbutton';
copyButton.setAttribute('style', button_style);
copyButton.onclick = doCopyAll;
control.appendChild(copyButton);

var optionsButton = document.createElement('button');
optionsButton.innerHTML = 'options';
optionsButton.id = 'optionsbutton';
optionsButton.setAttribute('style', button_style);
optionsButton.onclick = doOptions;
control.appendChild(optionsButton);

var optionsArea = document.createElement('div');
optionsArea.id = 'optionsarea';
optionsArea.setAttribute('style', 'display:none;');
control.appendChild(optionsArea);

var captionForConfidence = document.createElement('span');
captionForConfidence.id = 'captionconfidence';
captionForConfidence.innerHTML = 'iv2 min confidence: ' + iv2_confidence_rating + '%';
optionsArea.appendChild(captionForConfidence);

var optionForConfidence = document.createElement('input');
optionForConfidence.id = 'optionsconfidence';
optionForConfidence.setAttribute('type', 'range');
optionForConfidence.setAttribute('value', iv2_confidence_rating);
optionForConfidence.setAttribute('min', 0);
optionForConfidence.setAttribute('max', 100);
optionForConfidence.onchange = doChangeConfidence;
optionsArea.appendChild(optionForConfidence);

var captionForCopySound = document.createElement('span');
captionForCopySound.id = 'captioncopysound';
captionForCopySound.innerHTML = 'copy sound url';
optionsArea.appendChild(captionForCopySound);

var optionForCopySound = document.createElement('input');
optionForCopySound.id = 'optionssound';
optionForCopySound.setAttribute('value', copy_sound);
optionForCopySound.setAttribute('style', 'font-size:10px;width:90%;margin:1px auto;');
optionForCopySound.onchange = doChangeCopySound;
optionsArea.appendChild(optionForCopySound);

var version = document.createElement('div');
version.innerHTML = GM_info.script.version;
version.setAttribute('style', 'font-size:8px;');
optionsArea.appendChild(version);
<commit_msg>1.0.6<commit_after>// ==UserScript==
// @name         Booru Tag Parser
// @namespace    http://above.average.website
// @version      1.0.6
// @description  Copy current post tags and rating on boorus and illustration2vec in to the clipboard for easy import in to a program or another booru.
// @author       William Moodhe
// @downloadURL  https://github.com/JetBoom/boorutagparser/raw/master/boorutagparser.user.js
// @updateURL    https://github.com/JetBoom/boorutagparser/raw/master/boorutagparser.user.js

// Illustration2Vec
// @include      *demo.illustration2vec.net*

// Catch-all for boorus
// @include      *booru*/post*
// @include      *booru*/*?page=post*
// @include      *booru*/?page=post

// Boorus with weird names
// @include      *rule34.xxx/index.php?page=post*
// @include      *chan.sankakucomplex.com/post/show/*
// @include      *chan.sankakucomplex.com/?tags=*
// @include      *e621.net/post/*
// @include      *konachan.com/post/*
// @include      *shimmie.katawa-shoujo.com/post/*
// @include      *rule34.paheal.net/post/*
// @include      *rule34hentai.net/post/*
// @include      *tbib.org/index.php?page=post*
// @include      *yande.re/post/*
// @include      *nhentai.net/g/*

// @run-at       document-end
// @grant        GM_setClipboard
// @grant        GM_setValue
// @grant        GM_getValue

// ==/UserScript==
/* jshint -W097 */
'use strict';

///////

var copy_key_code = GM_getValue('copy_key_code', 221); // ] key
var copy_sound = GM_getValue('copy_sound', 'http://heavy.noxiousnet.com/boorucopy.ogg');
var iv2_confidence_rating = GM_getValue('iv2_confidence_rating', 20.0);
var attach_explicit = GM_getValue('attach_explicit', true);
var attach_gid = GM_getValue('attach_gid', true);
var div_top = GM_getValue('div_top', false);

///////

var tags_selector = 'h5, b';
var button_style = 'font:11px monospace;font-weight:0;border:1px solid black;background:#eee;color:#000;display:block;width:90%;margin:2px auto;z-index:9999;';
var div_style = 'opacity:0.2;position:fixed;width:240px;right:2px;top:2px;text-align:center;font:11px monospace;font-weight:0;border:1px solid black;background:rgba(255,255,255,0.8);z-index:9999;';

function replaceAll(str, originalstr, newstr)
{
    return str.split(originalstr).join(newstr);
}

function insertTags(tags, selector, prefix)
{
    var elements = document.querySelectorAll(selector);

    for (var i=0; i < elements.length; i++)
    {
        var element = elements[i];
        var text = element.innerHTML;
        if (text.length > 1) // Don't copy - + or ?
        {
            text = replaceAll(text, '_', ' ');
            text = replaceAll(text, '>', '>');
            text = replaceAll(text, '<', '<');

            tags[tags.length] = prefix+text;
        }
    }
}

function insertRating(tags, selector)
{
    var elements = document.querySelectorAll(selector);

    for (var i=0; i < elements.length; i++)
    {
        var element = elements[i];
        var text = element.innerHTML;
        text = replaceAll(text.toLowerCase().trim(), ' ', '');

        if (text.indexOf('rating:explicit') >= 0)
        {
            tags[tags.length] = 'rating:explicit';
            break;
        }
        else if (text.indexOf('rating:questionable') >= 0)
        {
            tags[tags.length] = 'rating:questionable';
            break;
        }
        else if (text.indexOf('rating:safe') >= 0)
        {
            tags[tags.length] = 'rating:safe';
            break;
        }
    }
}

function copyNHentaiTags(noRating)
{
    // nhentai has a json output we can use.
    // Which is nice because the tags are available even if viewing an individual file.

    var id = window.location.href.match('/g/(\\d+)/*')[1];
    if (!id)
        return;

    id = Number(id);

    fetch('http://nhentai.net/g/' + id + '/json').then(function(response) {
        return response.json();
    }).then(function(json) {
        var tags = [];

        if (json.tags)
        {
            for (var i=0; i < json.tags.length; i++)
            {
                var tagtype = json.tags[i][1];
                var tag = json.tags[i][2];

                // maintain schema consistency
                if (tagtype == 'group')
                    tagtype = 'studio';
                else if (tagtype == 'parody')
                    tagtype = 'series';
                else if (tagtype == 'artist')
                    tagtype = 'creator';

                if (tagtype != 'tag')
                    tag = tagtype + ':' + tag;

                tags[tags.length] = tag;
            }
        }

        if (attach_explicit)
            tags[tags.length] = 'rating:explicit';

        if (attach_gid && json.id)
            tags[tags.length] = 'gallery:' + json.id;

        copyTagsToClipboard(tags);
    }).catch(function(err) {
        console.log('couldn\'t get json!');
    });
}

function copyBooruTags(noRating)
{
    var tags = [];
    // Instead of having a list of boorus and their tags and tag structures I just make a big catch-all.

    // danbooru-like
    insertTags(tags, '#tag-list li.category-3 > a.search-tag', 'series:');
    insertTags(tags, '#tag-list li.category-1 > a.search-tag', 'creator:');
    insertTags(tags, '#tag-list li.category-4 > a.search-tag', 'character:');
    insertTags(tags, '#tag-list li.category-0 > a.search-tag', '');

    // lolibooru-like
    insertTags(tags, 'li.tag-type-copyright > a', 'series:');
    insertTags(tags, 'li.tag-type-author > a', 'creator:');
    insertTags(tags, 'li.tag-type-artist > a', 'creator:');
    insertTags(tags, 'li.tag-type-character > a', 'character:');
    insertTags(tags, 'li.tag-type-general > a', '');
    insertTags(tags, 'li.tag-type-studio > a', 'studio:');
    insertTags(tags, 'li.tag-type-circle > a', 'studio:');
    insertTags(tags, 'li.tag-type-medium > a', 'medium:');
    insertTags(tags, 'li.tag-type-style > a', 'medium:');
    insertTags(tags, 'li.tag-type-meta > a', 'meta:');
    insertTags(tags, 'li.tag-type-species > a', 'species:');
    insertTags(tags, 'li.tag-type-faults > a', 'fault:');

    // booru.org-like
    insertTags(tags, '#tag_list li a', '');

    // paheal-like
    insertTags(tags, 'a.tag_name', '');

    if (!noRating)
    {
        // danbooru-like
        insertRating(tags, '#post-information > ul li');

        // lolibooru-like
        insertRating(tags, '#stats > ul li');

        // booru.org-like
        insertRating(tags, '#tag_list ul');
    }

    copyTagsToClipboard(tags);
}

function insertI2VTags(tags, selector, prefix, confidenceRequired)
{
    var elements = document.querySelectorAll(selector);
    for (var i=1; i < elements.length; i++)
    {
        var element = elements[i];

        if (confidenceRequired > 0)
        {
            var confidence = element.children[3].children[0].innerHTML;
            confidence = confidence.substr(0, confidence.length - 1);
            confidence = Number(confidence);

            if (confidence < confidenceRequired)
                continue;
        }

        var tag = element.children[1].innerHTML;

        tag = replaceAll(tag, '_', ' ');

        if (prefix)
            tag = prefix + tag;

        tags[tags.length] = tag;

        if (prefix == 'rating') // only add one rating
            break;
    }
}

function copyI2VTags(confidenceRequired, noGeneral, noRating)
{
    var tags = [];

    insertI2VTags(tags, 'table#copyright_root tr', 'series:', confidenceRequired);
    insertI2VTags(tags, 'table#character_root tr', 'character:', confidenceRequired);

    if (!noGeneral)
        insertI2VTags(tags, 'table#general_root tr', '', confidenceRequired);

    if (!noRating)
        insertI2VTags(tags, 'table#rating_root tr', 'rating:', confidenceRequired);

    copyTagsToClipboard(tags);
}

function doCopyAll()
{
    control.style.opacity = '1';

    if (window.location.href.indexOf('nhentai.net') >= 0)
        copyNHentaiTags();
    else if (window.location.href.indexOf('illustration2vec.net') >= 0)
        copyI2VTags(iv2_confidence_rating, false);
    else
        copyBooruTags();
}

function copyTagsToClipboard(tags)
{
    GM_setClipboard(tags.join('\n'));

    var buttontext = '';

    if (tags.length > 0)
    {
        playCopySound();
        buttontext = 'copied ' +  tags.length +' tag(s)';
    }
    else
        buttontext = 'nothing to copy!';

    var button = document.querySelector('button#copytagsbutton');
    if (button)
    {
        button.innerHTML = buttontext;
        setTimeout(function(){ button.innerHTML = 'copy tags'; }, 3000);
    }
}

function doOptions()
{
    optionsArea.style.display = optionsArea.style.display == 'none' ? 'block' : 'none';
}

function doc_keyUp(e)
{
    if (e.keyCode == copy_key_code)
    {
        doCopyAll();
    }
}
document.addEventListener('keyup', doc_keyUp, false);

var elements = document.querySelectorAll(tags_selector);
for (var i=0; i < elements.length; i++)
{
    var element = elements[i];
    if (element.innerHTML == 'Tags')
    {
        element.onclick = copyBooruTags;
        break;
    }
}

if (copy_sound.length > 0)
{
    var audio = document.createElement("audio");
    audio.src = copy_sound;
    audio.preload = false;
}

function playCopySound()
{
    if (audio && copy_sound.length > 0)
        audio.play();
}

function doChangeConfidence(e)
{
    iv2_confidence_rating = Number(optionForConfidence.value);
    captionForConfidence.innerHTML = 'iv2 min confidence: ' + iv2_confidence_rating + '%';
    GM_setValue('iv2_confidence_rating', iv2_confidence_rating);
}

function doChangeCopySound(e)
{
    copy_sound = String(optionForCopySound.value);
    GM_setValue('copy_sound', copy_sound);

    if (audio)
        audio.src = copy_sound;
}

function doChangeAttachExplicit(e)
{
    attach_explicit = optionForAttachExplicit.checked;
    GM_setValue('attach_explicit', attach_explicit);
}

function doChangeAttachGID(e)
{
    attach_gid = optionForAttachGID.checked;
    GM_setValue('attach_gid', attach_gid);
}

function doChangeDivTop(e)
{
    div_top = optionForDivTop.checked;
    GM_setValue('div_top', div_top);

    if (div_top)
    {
        control.style.top = '2px';
        control.style.bottom = '';
    }
    else
    {
        control.style.top = '';
        control.style.bottom = '2px';
    }
}

var control = document.createElement('div');
control.id = 'boorutagparser';
control.setAttribute('style', div_style);
control.onmouseenter = function(e) { this.style.opacity = 1; };
control.onmouseleave = function(e) { this.style.opacity = 0.2; };
document.body.appendChild(control);

var copyButton = document.createElement('button');
copyButton.innerHTML = 'copy tags';
copyButton.id = 'copytagsbutton';
copyButton.setAttribute('style', button_style);
copyButton.onclick = doCopyAll;
control.appendChild(copyButton);

var optionsButton = document.createElement('button');
optionsButton.innerHTML = 'options';
optionsButton.id = 'optionsbutton';
optionsButton.setAttribute('style', button_style);
optionsButton.onclick = doOptions;
control.appendChild(optionsButton);

var optionsArea = document.createElement('div');
optionsArea.id = 'optionsarea';
optionsArea.setAttribute('style', 'display:none;');
control.appendChild(optionsArea);

if (window.location.href.indexOf('illustration2vec.net') >= 0)
{
    var captionForConfidence = document.createElement('span');
    captionForConfidence.id = 'captionconfidence';
    captionForConfidence.innerHTML = 'iv2 min confidence: ' + iv2_confidence_rating + '%';
    optionsArea.appendChild(captionForConfidence);

    var optionForConfidence = document.createElement('input');
    optionForConfidence.id = 'optionsconfidence';
    optionForConfidence.setAttribute('type', 'range');
    optionForConfidence.setAttribute('value', iv2_confidence_rating);
    optionForConfidence.setAttribute('min', 0);
    optionForConfidence.setAttribute('max', 100);
    optionForConfidence.onchange = doChangeConfidence;
    optionsArea.appendChild(optionForConfidence);
}

if (window.location.href.indexOf('nhentai.net/g/') >= 0)
{
    var optionForAttachExplicit = document.createElement('input');
    optionForAttachExplicit.setAttribute('type', 'checkbox');
    optionForAttachExplicit.checked = attach_explicit;
    optionForAttachExplicit.onchange = doChangeAttachExplicit;
    optionsArea.appendChild(optionForAttachExplicit);

    var captionForAttachExplicit = document.createElement('span');
    captionForAttachExplicit.innerHTML = 'add rating:explicit<br>';
    optionsArea.appendChild(captionForAttachExplicit);

    var optionForAttachGID = document.createElement('input');
    optionForAttachGID.setAttribute('type', 'checkbox');
    optionForAttachGID.checked = attach_gid;
    optionForAttachGID.onchange = doChangeAttachGID;
    optionsArea.appendChild(optionForAttachGID);

    var captionForAttachGID = document.createElement('span');
    captionForAttachGID.innerHTML = 'add gallery:id#<br>';
    optionsArea.appendChild(captionForAttachGID);
}

var captionForCopySound = document.createElement('span');
captionForCopySound.id = 'captioncopysound';
captionForCopySound.innerHTML = 'copy sound url';
optionsArea.appendChild(captionForCopySound);

var optionForCopySound = document.createElement('input');
optionForCopySound.id = 'optionssound';
optionForCopySound.setAttribute('value', copy_sound);
optionForCopySound.setAttribute('style', 'display:block;font-size:10px;width:90%;margin:1px auto;');
optionForCopySound.onchange = doChangeCopySound;
optionsArea.appendChild(optionForCopySound);

var optionForDivTop = document.createElement('input');
optionForDivTop.setAttribute('type', 'checkbox');
optionForDivTop.checked = div_top;
optionForDivTop.onchange = doChangeDivTop;
optionsArea.appendChild(optionForDivTop);

var captionForDivTop = document.createElement('span');
captionForDivTop.innerHTML = 'attach to top of page<br>';
optionsArea.appendChild(captionForDivTop);

var version = document.createElement('div');
version.innerHTML = GM_info.script.version;
version.setAttribute('style', 'font-size:8px;');
optionsArea.appendChild(version);

doChangeDivTop();
<|endoftext|>