<commit_before>var request = require('request')
var cache = require('memory-cache')
var async = require('async')

function makeSureWeHaveArtists(callback){
  var eventInfo = {
    url: 'http://ws.audioscrobbler.com/2.0/',
    qs: {
      method: 'event.getinfo',
      event: process.env.LASTFM_EVENT_ID,
      api_key: process.env.LASTFM_KEY,
      format: 'json'
    }
  }

  request(eventInfo, function(error, response, body) {
    if (!error && response.statusCode == 200) {
      var event = JSON.parse(body);
      var artists = event.event.artists.artist || [];
      cache.put('artists', artists, 1000 * 60 * 60 * 24);
      callback();
    } else {
      console.error(error);
      callback(error);
    }
  });
}

function generateParallelArtistCalls(callback, username, funs, playcounts){
        var artists = cache.get('artists');
        artists.forEach(function(artist){

          funs.push(
            //artistfunction
            function (cb) {
              var opts = {
                url: 'http://ws.audioscrobbler.com/2.0/',
                qs: {
                  method: 'artist.getinfo',
                  artist: artist,
                  api_key: process.env.LASTFM_KEY,
                  format: 'json',
                  username: username
                }
              }

              request(opts, function (error, response, body) {
                if (!error && response.statusCode == 200) {
                  var info = JSON.parse(body);
                  var playcount = parseInt(info.artist.stats.userplaycount) || 0;

                  playcounts.push({ name: artist, playcount: playcount });
                } else {
                  console.error(error);
                }
                cb();
              });
            }
            //artistfunction
          );
        });//foreach
        callback();
}

function userArtists(username, _callback) {
  var user_artists = cache.get(username);
  if (user_artists) {
    _callback(user_artists);
  } else {

    //fetch artists from cache
    var playcounts = [];
    var funs = [];
    var artists = cache.get('artists') || [];

    var artistSuggestions = [];
    async.series(
      [
        // make sure we have artists
        function(callback){
          makeSureWeHaveArtists(callback);
        },

        function(callback){
          generateParallelArtistCalls(callback, username, funs, playcounts);
        },

        //get data of all artists
        function(callback) {
          //loopArtists(artists, username, cb, callback)
          async.parallel(funs, function() { callback() });
        },

        //second in series: process data of all artists
        function(callback) {
          async.sortBy(playcounts, function(x, cb) {
            cb(null, -1 * x.playcount)
          }, function(err, result) {
            artistSuggestions = result;
            callback();
          });

        }
      ],

      //series done, send data to user ?
      function(err, results) {
        cache.put(username, artistSuggestions);
        _callback(artistSuggestions);
    });
  }
}

module.exports = {
  topArtists: function(req,res) {
    userArtists(req.param('username'), function(artists) {
      res.type('application/json; charset=utf-8').end(JSON.stringify(artists.slice(0,5)));
    });
  }
}
<commit_msg>no env variable crashing issue fix<commit_after>var request = require('request')
var cache = require('memory-cache')
var async = require('async')

function makeSureWeHaveArtists(callback){
  var eventInfo = {
    url: 'http://ws.audioscrobbler.com/2.0/',
    qs: {
      method: 'event.getinfo',
      event: process.env.LASTFM_EVENT_ID,
      api_key: process.env.LASTFM_KEY,
      format: 'json'
    }
  }

  request(eventInfo, function(error, response, body) {
    if (!error && response.statusCode == 200) {
      var artists, event = JSON.parse(body);
      if(event.error != 10)
        artists = event.event.artists.artist
      else
        artists = [];
      cache.put('artists', artists, 1000 * 60 * 60 * 24);
      callback();
    } else {
      console.error(error);
      callback(error);
    }
  });
}

function generateParallelArtistCalls(callback, username, funs, playcounts){
        var artists = cache.get('artists');
        artists.forEach(function(artist){

          funs.push(
            //artistfunction
            function (cb) {
              var opts = {
                url: 'http://ws.audioscrobbler.com/2.0/',
                qs: {
                  method: 'artist.getinfo',
                  artist: artist,
                  api_key: process.env.LASTFM_KEY,
                  format: 'json',
                  username: username
                }
              }

              request(opts, function (error, response, body) {
                if (!error && response.statusCode == 200) {
                  var info = JSON.parse(body);
                  var playcount = parseInt(info.artist.stats.userplaycount) || 0;

                  playcounts.push({ name: artist, playcount: playcount });
                } else {
                  console.error(error);
                }
                cb();
              });
            }
            //artistfunction
          );
        });//foreach
        callback();
}

function userArtists(username, _callback) {
  var user_artists = cache.get(username);
  if (user_artists) {
    _callback(user_artists);
  } else {
    //fetch artists from cache
    var playcounts = [];
    var funs = [];
    var artists = cache.get('artists') || [];
    var artistSuggestions = [];

    async.series(
      [
        // make sure we have artists
        function(callback){
          makeSureWeHaveArtists(callback);
        },

        function(callback){
          generateParallelArtistCalls(callback, username, funs, playcounts);
        },

        //get data of all artists
        function(callback) {
          //loopArtists(artists, username, cb, callback)
          async.parallel(funs, function() { callback() });
        },

        //second in series: process data of all artists
        function(callback) {
          async.sortBy(playcounts, function(x, cb) {
            cb(null, -1 * x.playcount)
          }, function(err, result) {
            artistSuggestions = result;
            callback();
          });
        }
      ],

      //series done, send data to user ?
      function(err, results) {
        cache.put(username, artistSuggestions);
        _callback(artistSuggestions);
    });
  }
}

module.exports = {
  topArtists: function(req,res) {
    userArtists(req.param('username'), function(artists) {
      res.type('application/json; charset=utf-8').end(JSON.stringify(artists.slice(0,5)));
    });
  }
}
<|endoftext|>


<commit_before>/* jslint node: true, esnext: true */
"use strict";

const Interceptor = require('kronos-interceptor').Interceptor;
const parserFactory = require('./line-tokenizer');

/**
 * This interceptor cares about the handling of the messages.
 * It will add the hops and copies the messages
 */
class LineHeaderInterceptor extends Interceptor {

	constructor(endpoint, config) {
		super(endpoint, config);

		this.streamFilter = parserFactory(config, true);
	}

	static get type() {
		return "line-tokenizer-csv";
	}

	get type() {
		return "line-tokenizer-csv";
	}

	receive(request, oldRequest) {
		if (request.payload) {
			const stream = request.payload;
			request.payload = stream.pipe(this.streamFilter);
		}
		return this.connected.receive(request, oldRequest);
	}
}
exports.Interceptor = LineHeaderInterceptor;
<commit_msg>refactor(class name): Now get the type name from the static type name<commit_after>/* jslint node: true, esnext: true */
"use strict";

const Interceptor = require('kronos-interceptor').Interceptor;
const parserFactory = require('./line-tokenizer');

/**
 * This interceptor cares about the handling of the messages.
 * It will add the hops and copies the messages
 */
class LineTokenizerInterceptor extends Interceptor {

	constructor(endpoint, config) {
		super(endpoint, config);

		this.streamFilter = parserFactory(config, true);
	}

	static get type() {
		return "line-tokenizer-csv";
	}

	get type() {
		return LineTokenizerInterceptor.type;
	}

	receive(request, oldRequest) {
		if (request.payload) {
			const stream = request.payload;
			request.payload = stream.pipe(this.streamFilter);
		}
		return this.connected.receive(request, oldRequest);
	}
}
exports.Interceptor = LineTokenizerInterceptor;
<|endoftext|>


<commit_before>import {
  useWith, curry, curryN, toPairs, equals, over, lensIndex, replace, pipe, map, path,
  either, complement,
} from 'ramda';

// type :: Monad a => a -> String
export const type = either(path(['@@type']), path(['constructor', '@@type']));

// typeEquals :: Monad a => String -> a -> Boolean
export const typeEquals = curry((typeIdent, monad) => type(monad) === typeIdent);

// isSameType :: (Monad a, Monad b) => a -> b -> Boolean
export const isSameType = curryN(2, useWith(equals, [type, type]));

// isNotSameType :: (Monad a, Monad b) => a -> b -> Boolean
export const isNotSameType = curryN(2, complement(isSameType));

// aliases :: Prototype -> NewPrototypePairs
//     Prototype = Object
//     NewPrototypePairs = Array
export const aliases = pipe(
  toPairs,
  map(over(lensIndex(0), replace('fantasy-land/', '')))
);
<commit_msg>refactor(fantasy-land): remove curry around complement on isSameType util<commit_after>import {
  useWith, curry, curryN, toPairs, equals, over, lensIndex, replace, pipe, map, path,
  either, complement,
} from 'ramda';

// type :: Monad a => a -> String
export const type = either(path(['@@type']), path(['constructor', '@@type']));

// typeEquals :: Monad a => String -> a -> Boolean
export const typeEquals = curry((typeIdent, monad) => type(monad) === typeIdent);

// isSameType :: (Monad a, Monad b) => a -> b -> Boolean
export const isSameType = curryN(2, useWith(equals, [type, type]));

// isNotSameType :: (Monad a, Monad b) => a -> b -> Boolean
export const isNotSameType = complement(isSameType);

// aliases :: Prototype -> NewPrototypePairs
//     Prototype = Object
//     NewPrototypePairs = Array
export const aliases = pipe(
  toPairs,
  map(over(lensIndex(0), replace('fantasy-land/', '')))
);
<|endoftext|>


<commit_before>// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * Footer shown when the Files.app is opened as a file/folder selecting dialog.
 * @param {DialogType} dialogType Dialog type.
 * @param {!Element} container Container of the dialog footer.
 * @param {!Element} filenameInput Filename input element.
 * @constructor
 */
function DialogFooter(dialogType, container, filenameInput) {
  /**
   * Root element of the footer.
   * @type {!Element}
   * @const
   */
  this.element = container;

  /**
   * Dialog type.
   * @type {DialogType}
   * @const
   * @private
   */
  this.dialogType_ = dialogType;

  /**
   * OK button in the footer.
   * @const
   * @type {!HTMLButtonElement}
   */
  this.okButton = /** @type {!HTMLButtonElement} */
      (container.querySelector('.ok'));

  /**
   * Cancel button in the footer.
   * @const
   * @type {!HTMLButtonElement}
   */
  this.cancelButton = /** @type {!HTMLButtonElement} */
      (container.querySelector('.cancel'));

  /**
   * File type selector in the footer.
   * @const
   * @type {!HTMLSelectElement}
   */
  this.fileTypeSelector = /** @type {!HTMLSelectElement} */
      (container.querySelector('.file-type'));

  /**
   * @const
   * @type {!Element}
   */
  this.filenameInput = filenameInput;

  // Initialize the element styles.
  container.classList.add('button-panel');

  // Set initial label for OK button. The label can be updated dynamically
  // depending on dialog types.
  this.okButton.textContent = DialogFooter.getOKButtonLabel_(dialogType);

  // Register event handlers.
  this.filenameInput.addEventListener(
      'keydown', this.onFilenameInputKeyDown_.bind(this));
  this.filenameInput.addEventListener(
      'focus', this.onFilenameInputFocus_.bind(this));
}

DialogFooter.prototype = {
  /**
   * @return {number} Selected filter index.
   */
  get selectedFilterIndex() {
    return ~~this.fileTypeSelector.value;
  }
};

/**
 * Finds the dialog footer element for the dialog type.
 * @param {DialogType} dialogType Dialog type.
 * @param {!Document} document Document.
 * @return {!DialogFooter} Dialog footer created with the found element.
 */
DialogFooter.findDialogFooter = function(dialogType, document) {
  // If the footer panel exists, the buttons are placed there. Otherwise,
  // the buttons are on the preview panel.
  var hasFooterPanel = dialogType == DialogType.SELECT_SAVEAS_FILE;
  return new DialogFooter(
      dialogType,
      queryRequiredElement(
          document, hasFooterPanel ? '.dialog-footer' : '.preview-panel'),
      queryRequiredElement(document, '#filename-input-box input'));
};

/**
 * Obtains the label of OK button for the dialog type.
 * @param {DialogType} dialogType Dialog type.
 * @return {string} OK button label.
 * @private
 */
DialogFooter.getOKButtonLabel_ = function(dialogType) {
  switch (dialogType) {
    case DialogType.SELECT_UPLOAD_FOLDER:
      return str('UPLOAD_LABEL');

    case DialogType.SELECT_SAVEAS_FILE:
      return str('SAVE_LABEL');

    case DialogType.SELECT_FOLDER:
    case DialogType.SELECT_OPEN_FILE:
    case DialogType.SELECT_OPEN_MULTI_FILE:
    case DialogType.FULL_PAGE:
      return str('OPEN_LABEL');

    default:
      throw new Error('Unknown dialog type: ' + dialogType);
  }
};

/**
 * Fills the file type list or hides it.
 * @param {!Array.<{extensions: Array.<string>, description: string}>} fileTypes
 *     List of file type.
 * @param {boolean} includeAllFiles Whether the filter includes the 'all files'
 *     item or not.
 */
DialogFooter.prototype.initFileTypeFilter = function(
    fileTypes, includeAllFiles) {
  if (includeAllFiles) {
    var option = document.createElement('option');
    option.innerText = str('ALL_FILES_FILTER');
    option.value = 0;
    this.fileTypeSelector.appendChild(option);
  }

  for (var i = 0; i < fileTypes.length; i++) {
    var fileType = fileTypes[i];
    var option = document.createElement('option');
    var description = fileType.description;
    if (!description) {
      // See if all the extensions in the group have the same description.
      for (var j = 0; j !== fileType.extensions.length; j++) {
        var currentDescription = FileType.typeToString(
            FileType.getTypeForName('.' + fileType.extensions[j]));
        if (!description)  {
          // Set the first time.
          description = currentDescription;
        } else if (description != currentDescription) {
          // No single description, fall through to the extension list.
          description = null;
          break;
        }
      }

      if (!description) {
        // Convert ['jpg', 'png'] to '*.jpg, *.png'.
        description = fileType.extensions.map(function(s) {
          return '*.' + s;
        }).join(', ');
      }
    }
    option.innerText = description;
    option.value = i + 1;

    if (fileType.selected)
      option.selected = true;

    this.fileTypeSelector.appendChild(option);
  }

  var options = this.fileTypeSelector.querySelectorAll('option');
  if (options.length >= 2) {
    // There is in fact no choice, show the selector.
    this.fileTypeSelector.hidden = false;
  }
};

/**
 * @param {Event} event Focus event.
 * @private
 */
DialogFooter.prototype.onFilenameInputFocus_ = function(event) {
  // On focus we want to select everything but the extension, but
  // Chrome will select-all after the focus event completes.  We
  // schedule a timeout to alter the focus after that happens.
  setTimeout(function() {
    this.selectTargetNameInFilenameInput();
  }.bind(this), 0);
};

/**
 * @param {Event} event Key event.
 * @private
 */
DialogFooter.prototype.onFilenameInputKeyDown_ = function(event) {
  if ((util.getKeyModifiers(event) + event.keyCode) === '13' /* Enter */)
    this.okButton.click();
};

DialogFooter.prototype.selectTargetNameInFilenameInput = function() {
  this.filenameInput.focus();
  var selectionEnd = this.filenameInput.value.lastIndexOf('.');
  if (selectionEnd == -1) {
    this.filenameInput.select();
  } else {
    this.filenameInput.selectionStart = 0;
    this.filenameInput.selectionEnd = selectionEnd;
  }
};
<commit_msg>Files.app: Move 'any file type' item to the end of filter list.<commit_after>// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * Footer shown when the Files.app is opened as a file/folder selecting dialog.
 * @param {DialogType} dialogType Dialog type.
 * @param {!Element} container Container of the dialog footer.
 * @param {!Element} filenameInput Filename input element.
 * @constructor
 */
function DialogFooter(dialogType, container, filenameInput) {
  /**
   * Root element of the footer.
   * @type {!Element}
   * @const
   */
  this.element = container;

  /**
   * Dialog type.
   * @type {DialogType}
   * @const
   * @private
   */
  this.dialogType_ = dialogType;

  /**
   * OK button in the footer.
   * @const
   * @type {!HTMLButtonElement}
   */
  this.okButton = /** @type {!HTMLButtonElement} */
      (container.querySelector('.ok'));

  /**
   * Cancel button in the footer.
   * @const
   * @type {!HTMLButtonElement}
   */
  this.cancelButton = /** @type {!HTMLButtonElement} */
      (container.querySelector('.cancel'));

  /**
   * File type selector in the footer.
   * @const
   * @type {!HTMLSelectElement}
   */
  this.fileTypeSelector = /** @type {!HTMLSelectElement} */
      (container.querySelector('.file-type'));

  /**
   * @const
   * @type {!Element}
   */
  this.filenameInput = filenameInput;

  // Initialize the element styles.
  container.classList.add('button-panel');

  // Set initial label for OK button. The label can be updated dynamically
  // depending on dialog types.
  this.okButton.textContent = DialogFooter.getOKButtonLabel_(dialogType);

  // Register event handlers.
  this.filenameInput.addEventListener(
      'keydown', this.onFilenameInputKeyDown_.bind(this));
  this.filenameInput.addEventListener(
      'focus', this.onFilenameInputFocus_.bind(this));
}

DialogFooter.prototype = {
  /**
   * @return {number} Selected filter index. The index is 1 based and 0 means
   *     'any file types'. Keep the meaniing consistent with the index passed to
   *     chrome.fileManagerPrivate.selectFile.
   */
  get selectedFilterIndex() {
    return ~~this.fileTypeSelector.value;
  }
};

/**
 * Finds the dialog footer element for the dialog type.
 * @param {DialogType} dialogType Dialog type.
 * @param {!Document} document Document.
 * @return {!DialogFooter} Dialog footer created with the found element.
 */
DialogFooter.findDialogFooter = function(dialogType, document) {
  // If the footer panel exists, the buttons are placed there. Otherwise,
  // the buttons are on the preview panel.
  var hasFooterPanel = dialogType == DialogType.SELECT_SAVEAS_FILE;
  return new DialogFooter(
      dialogType,
      queryRequiredElement(
          document, hasFooterPanel ? '.dialog-footer' : '.preview-panel'),
      queryRequiredElement(document, '#filename-input-box input'));
};

/**
 * Obtains the label of OK button for the dialog type.
 * @param {DialogType} dialogType Dialog type.
 * @return {string} OK button label.
 * @private
 */
DialogFooter.getOKButtonLabel_ = function(dialogType) {
  switch (dialogType) {
    case DialogType.SELECT_UPLOAD_FOLDER:
      return str('UPLOAD_LABEL');

    case DialogType.SELECT_SAVEAS_FILE:
      return str('SAVE_LABEL');

    case DialogType.SELECT_FOLDER:
    case DialogType.SELECT_OPEN_FILE:
    case DialogType.SELECT_OPEN_MULTI_FILE:
    case DialogType.FULL_PAGE:
      return str('OPEN_LABEL');

    default:
      throw new Error('Unknown dialog type: ' + dialogType);
  }
};

/**
 * Fills the file type list or hides it.
 * @param {!Array.<{extensions: Array.<string>, description: string}>} fileTypes
 *     List of file type.
 * @param {boolean} includeAllFiles Whether the filter includes the 'all files'
 *     item or not.
 */
DialogFooter.prototype.initFileTypeFilter = function(
    fileTypes, includeAllFiles) {
  for (var i = 0; i < fileTypes.length; i++) {
    var fileType = fileTypes[i];
    var option = document.createElement('option');
    var description = fileType.description;
    if (!description) {
      // See if all the extensions in the group have the same description.
      for (var j = 0; j !== fileType.extensions.length; j++) {
        var currentDescription = FileType.typeToString(
            FileType.getTypeForName('.' + fileType.extensions[j]));
        if (!description)  {
          // Set the first time.
          description = currentDescription;
        } else if (description != currentDescription) {
          // No single description, fall through to the extension list.
          description = null;
          break;
        }
      }

      if (!description) {
        // Convert ['jpg', 'png'] to '*.jpg, *.png'.
        description = fileType.extensions.map(function(s) {
          return '*.' + s;
        }).join(', ');
      }
    }
    option.innerText = description;
    option.value = i + 1;

    if (fileType.selected)
      option.selected = true;

    this.fileTypeSelector.appendChild(option);
  }

  if (includeAllFiles) {
    var option = document.createElement('option');
    option.innerText = str('ALL_FILES_FILTER');
    option.value = 0;
    this.fileTypeSelector.appendChild(option);
  }

  var options = this.fileTypeSelector.querySelectorAll('option');
  if (options.length >= 2) {
    // There is in fact no choice, show the selector.
    this.fileTypeSelector.hidden = false;
  }
};

/**
 * @param {Event} event Focus event.
 * @private
 */
DialogFooter.prototype.onFilenameInputFocus_ = function(event) {
  // On focus we want to select everything but the extension, but
  // Chrome will select-all after the focus event completes.  We
  // schedule a timeout to alter the focus after that happens.
  setTimeout(function() {
    this.selectTargetNameInFilenameInput();
  }.bind(this), 0);
};

/**
 * @param {Event} event Key event.
 * @private
 */
DialogFooter.prototype.onFilenameInputKeyDown_ = function(event) {
  if ((util.getKeyModifiers(event) + event.keyCode) === '13' /* Enter */)
    this.okButton.click();
};

DialogFooter.prototype.selectTargetNameInFilenameInput = function() {
  this.filenameInput.focus();
  var selectionEnd = this.filenameInput.value.lastIndexOf('.');
  if (selectionEnd == -1) {
    this.filenameInput.select();
  } else {
    this.filenameInput.selectionStart = 0;
    this.filenameInput.selectionEnd = selectionEnd;
  }
};
<|endoftext|>


<commit_before>var Impetus = {
    url: null,

    init: function(currentUrl) {
        this.url = currentUrl;

        this.createYearChanger();

        this.bindFlashNoticeButtons();
        this.bindPlaceholderReplacement();
    },

    bindFlashNoticeButtons: function() {
        // TODO: Perhaps implement an Undo

        $("#flash-notice a.dismiss").click(function(event) {
            event.preventDefault();
            $("#flash-notice-wrapper").remove();
        });
    },

    bindPlaceholderReplacement: function() {
        $('[placeholder]').focus(function() {
            var input = $(this);
            if (input.val() == input.attr('placeholder')) {
                input.val('');
                input.removeClass('placeholder');
            }
        }).blur(function() {
            var input = $(this);
            if (input.val() == '' || input.val() == input.attr('placeholder')) {
                input.addClass('placeholder');
                input.val(input.attr('placeholder'));
            }
        }).blur().parents('form').submit(function(event) {
            event.preventDefault();

            $(this).find('[placeholder]').each(function() {
                var input = $(this);
                if (input.val() == input.attr('placeholder')) {
                    input.val('');
                }
            });
        });
    },

    populateYearOptions: function(responseJson) {
        var years = responseJson.years;
        var current_year = responseJson.current;
        var output = '';

        for (var i in years) {
            var selected = (years[i].year == current_year) ? 'selected="selected"' : '';
            output += '<option ' + selected + '>' + years[i].year + '</option>';
        }

        return output;
    },

    createYearChanger: function() {
        $.ajax({
            url: Routing.generate('_year_list'),
            type: 'GET',
            dataType: 'json',
            error: Impetus.errorAlert,
            success: function(data) {
                $('#academic-year').html(Impetus.populateYearOptions(data));
            }
        });

        $('#academic-year').change(function(event) {
            var year = $(this).val();

            $.ajax({
                url: Routing.generate('_year_change', { 'year': year }),
                type: 'POST',
                error: Impetus.errorAlert,
                success: function(data) {
                    if (data == 'success') {
                        window.location = Impetus.url;
                    }
                }
            });
        });
    },

    errorAlert: function(msg) {
        msg = (typeof a == 'undefined') ? 'no details provided' : msg;

        alert('Error processing request: ' + msg);
    }
}<commit_msg>Fixed Cache Issue with IE<commit_after>var Impetus = {
    url: null,

    init: function(currentUrl) {
        this.url = currentUrl;

        this.ajaxSetup();

        this.createYearChanger();

        this.bindFlashNoticeButtons();
        this.bindPlaceholderReplacement();
    },

    ajaxSetup: function() {
        // Stop aggressive caching (IE)
        $.ajaxSetup({
            cache: false
        });
    },

    bindFlashNoticeButtons: function() {
        // TODO: Perhaps implement an Undo

        $("#flash-notice a.dismiss").click(function(event) {
            event.preventDefault();
            $("#flash-notice-wrapper").remove();
        });
    },

    bindPlaceholderReplacement: function() {
        $('[placeholder]').focus(function() {
            var input = $(this);
            if (input.val() == input.attr('placeholder')) {
                input.val('');
                input.removeClass('placeholder');
            }
        }).blur(function() {
            var input = $(this);
            if (input.val() == '' || input.val() == input.attr('placeholder')) {
                input.addClass('placeholder');
                input.val(input.attr('placeholder'));
            }
        }).blur().parents('form').submit(function(event) {
            event.preventDefault();

            $(this).find('[placeholder]').each(function() {
                var input = $(this);
                if (input.val() == input.attr('placeholder')) {
                    input.val('');
                }
            });
        });
    },

    populateYearOptions: function(responseJson) {
        var years = responseJson.years;
        var current_year = responseJson.current;
        var output = '';

        for (var i in years) {
            var selected = (years[i].year == current_year) ? 'selected="selected"' : '';
            output += '<option ' + selected + '>' + years[i].year + '</option>';
        }

        return output;
    },

    createYearChanger: function() {
        $.ajax({
            url: Routing.generate('_year_list'),
            type: 'GET',
            dataType: 'json',
            error: Impetus.errorAlert,
            success: function(data) {
                $('#academic-year').html(Impetus.populateYearOptions(data));
            }
        });

        $('#academic-year').change(function(event) {
            var year = $(this).val();

            $.ajax({
                url: Routing.generate('_year_change', { 'year': year }),
                type: 'POST',
                error: Impetus.errorAlert,
                success: function(data) {
                    if (data == 'success') {
                        window.location = Impetus.url;
                    }
                }
            });
        });
    },

    errorAlert: function(msg) {
        msg = (typeof a == 'undefined') ? 'no details provided' : msg;

        alert('Error processing request: ' + msg);
    }
}<|endoftext|>


<commit_before>import Discord from 'discord.js';
import { isArray, isFunction, isString } from 'lodash/lang';
import ArgumentParser from '../parsers/ArgumentParser';
import CommandParser from '../parsers/CommandParser';
import CommandRegistry from '../command/CommandRegistry';
import apply from '../core/apply';
import create from '../core/create';

/**
 * Receives message update events and dispatches commands found in the messages.
 * @param {Message} message - A Discord.js `Message` object.
 * @param {Message} [newMessage] - A Discord.js `Message` object, given if the
 *   event is a message update.
 * @returns {Promise.<(boolean|Message|*), TypeError>} A Promise resolving to
 *   a Discord.js `Message` representing the response that was dispatched if the
 *   command was handled successfully, or `false` if the command could not be
 *   dispatched, or whatever value is returned by the command handler's indicator,
 *   if it is a function.
 *   The Promise rejects with a `TypeError` if the command handler's returned
 *   indicator is not of a valid type.
 */
async function dispatch(message, newMessage) {
  if (newMessage && message.content === newMessage.content) {
    return false;
  }

  const commandMessage = newMessage || message;
  const parsedCommand = CommandParser.parse(commandMessage);
  const command = this.registry.get(parsedCommand.identifier);

  if (!command) {
    return false;
  }

  const initialContext = {
    message: commandMessage,
    args: ArgumentParser.parse(command.parameters, parsedCommand.arguments),
  };

  const indicator = await command.handler(initialContext);

  if (isString(indicator)) {
    return commandMessage.channel.sendMessage(indicator);
  } else if (isArray(indicator)) {
    const choice = indicator[Math.floor(Math.random() * indicator.length)];

    if (!isString(choice)) {
      throw new TypeError('Expected message response to be a string.');
    }

    return commandMessage.channel.sendMessage(choice);
  } else if (isFunction(indicator)) {
    return indicator();
  }

  throw new TypeError('Returned value from command handler is not a string, array or function.');
}

/**
 * @classdesc The Ghastly client.
 * @extends Client
 */
export default class Ghastly extends Discord.Client {
  /**
   * Constructor.
   * @param {Object} [options={}] - Options to configure the client.
   */
  constructor(options = {}) {
    super(options);

    /**
     * The command registry for the client.
     * @type {CommandRegistry}
     * @private
     */
    this.registry = new CommandRegistry();

    // register our events
    const dispatcher = dispatch.bind(this);
    this.on('message', dispatcher);
    this.on('messageUpdate', dispatcher);
  }

  /**
   * Adds the given commands to the registry.
   * @param {...Function} commands - The command generators.
   * @returns {Ghastly} The instance this method was called on.
   */
  load(...commands) {
    const generatorApi = { apply, create, config: {} };
    const generatedCommands = commands.map(command => command(generatorApi));
    this.registry.load(...generatedCommands);

    return this;
  }

  /**
   * Removes the given commands from the registry.
   * @param {...string} names - The identifiers of commands to unload.
   * @returns {Ghastly} The instance this method was called on.
   */
  unload(...names) {
    this.registry.unload(...names);

    return this;
  }
}
<commit_msg>Have to check if arguments match parameters.<commit_after>import Discord from 'discord.js';
import { isArray, isFunction, isString } from 'lodash/lang';
import ArgumentParser from '../parsers/ArgumentParser';
import CommandParser from '../parsers/CommandParser';
import CommandRegistry from '../command/CommandRegistry';
import apply from '../core/apply';
import create from '../core/create';

/**
 * Receives message update events and dispatches commands found in the messages.
 * @param {Message} message - A Discord.js `Message` object.
 * @param {Message} [newMessage] - A Discord.js `Message` object, given if the
 *   event is a message update.
 * @returns {Promise.<(boolean|Message|*), TypeError>} A Promise resolving to
 *   a Discord.js `Message` representing the response that was dispatched if the
 *   command was handled successfully, or `false` if the command could not be
 *   dispatched, or whatever value is returned by the command handler's indicator,
 *   if it is a function.
 *   The Promise rejects with a `TypeError` if the command handler's returned
 *   indicator is not of a valid type.
 * @this Ghastly
 */
async function dispatch(message, newMessage) {
  if (newMessage && message.content === newMessage.content) {
    return false;
  }

  const commandMessage = newMessage || message;
  const parsedCommand = CommandParser.parse(commandMessage);
  const command = this.registry.get(parsedCommand.identifier);

  if (!command) {
    return false;
  }

  let initialContext;

  try {
    initialContext = {
      message: commandMessage,
      args: ArgumentParser.parse(command.parameters, parsedCommand.arguments),
    };
  } catch (error) {
    return false;
  }

  const indicator = await command.handler(initialContext);

  if (isString(indicator)) {
    return commandMessage.channel.sendMessage(indicator);
  } else if (isArray(indicator)) {
    const choice = indicator[Math.floor(Math.random() * indicator.length)];

    if (!isString(choice)) {
      throw new TypeError('Expected message response to be a string.');
    }

    return commandMessage.channel.sendMessage(choice);
  } else if (isFunction(indicator)) {
    return indicator();
  }

  throw new TypeError('Returned value from command handler is not a string, array or function.');
}

/**
 * @classdesc The Ghastly client.
 * @extends Client
 */
export default class Ghastly extends Discord.Client {
  /**
   * Constructor.
   * @param {Object} [options={}] - Options to configure the client.
   */
  constructor(options = {}) {
    super(options);

    /**
     * The command registry for the client.
     * @type {CommandRegistry}
     * @private
     */
    this.registry = new CommandRegistry();

    // register our events
    const dispatcher = dispatch.bind(this);
    this.on('message', dispatcher);
    this.on('messageUpdate', dispatcher);
  }

  /**
   * Adds the given commands to the registry.
   * @param {...Function} commands - The command generators.
   * @returns {Ghastly} The instance this method was called on.
   */
  load(...commands) {
    const generatorApi = { apply, create, config: {} };
    const generatedCommands = commands.map(command => command(generatorApi));
    this.registry.load(...generatedCommands);

    return this;
  }

  /**
   * Removes the given commands from the registry.
   * @param {...string} names - The identifiers of commands to unload.
   * @returns {Ghastly} The instance this method was called on.
   */
  unload(...names) {
    this.registry.unload(...names);

    return this;
  }
}
<|endoftext|>


<commit_before>function Project($resource) {
  var Project = $resource('http://localhost:3000/api/v1/projects/:id.json', {id: '@id'}, {
    update: {method: 'PUT'}
  });
  return Project;
};

angular
  .module('app')
  .factory('Project', Project);<commit_msg>Preventing minification by manually injecting dependencies<commit_after>function Project($resource) {
  var Project = $resource('http://localhost:3000/api/v1/projects/:id.json', {id: '@id'}, {
    update: {method: 'PUT'}
  });
  return Project;
};

Project.$inject = ['$resource'];

angular
  .module('app')
  .factory('Project', Project);<|endoftext|>


<commit_before>describe('jquery.textarea-highlighter', function() {
  var $segment;
  beforeEach(function() {
    $segment = setFixtures(
      '
' +
        '<textarea id="target-fixture" class="target"></textarea>' +
        '

' +
      '
'
    );
  });

  it('should add divs on initialize', function() {
    var $target = $segment.find('#target-fixture');

    $target.textareaHighlighter();

    expect( $segment.find('.background-div').length ).toBe(1);
    expect( $segment.find('.autosize').length ).toBe(1);

    // Check is textarea's classes were added to the extra divs
    expect( $segment.find('.background-div').hasClass('target') ).toBe(true);
    expect( $segment.find('.autosize').parent().hasClass('target') ).toBe(true);
  });

  it('should remove extra divs on destroy', function() {
    var $target = $segment.find('#target-fixture');

    $target.textareaHighlighter();
    $target.textareaHighlighter('destroy');

    expect( $segment.find('.background-div').length ).toBe(0);
    expect( $segment.find('.autosize').length ).toBe(0);
  });

  it('should update matches', function() {
    var $target = $segment.find('#target-fixture');

    $target
      .val('This is a stupid test to :)')
      .textareaHighlighter();

    // There should be no highlights
    expect( $segment.find('.background-div').html() ).toBe('This is a stupid test to :)');

    // Update match list
    $target.textareaHighlighter('updateMatches', [{ 'matchClass': 'test', 'match': ['test'] }]);

    expect( $segment.find('.background-div').html() ).toBe('This is a stupid test to :)');
  });

  describe('test highlighting', function() {

    it('should highlight content correctly', function() {
      var $target = $segment.find('#target-fixture');

      $target
        .val('Hi [[[test1]]] this is a {0} test ハゲ THiss [[[{0}]]] and 日本 is some　アホ more [[[tast]]],[[[test2]]] tests. Some more [[[aaa]] and[[[tast]]][[[alex]]] aa')
        .textareaHighlighter({
          matches: [
            { 'matchClass': 'brackets',       'match': ['[[[test1]]]', '[[[tast]]]', '[[[test2]]]', '[[[{0}]]]', '[[[alex]]]'] },
            { 'matchClass': 'brackets error', 'match': ['[[[aaa]]'] },
            { 'matchClass': 'tags',           'match': ['{0}'] },
            { 'matchClass': 'misspelling',    'match': ['THiss', 'est', 'a'] }
          ]
        });

      var html = '';
      html += 'Hi ';
      html += '[[[test1]]]';
      html += ' this is ';
      html += 'a';
      html += ' ';
      html += '{0}';
      html += ' test ハゲ ';
      html += 'THiss';
      html += ' ';
      html += '[[[{0}]]]';
      html += ' and 日本 is some　アホ more ';
      html += '[[[tast]]]';
      html += ',';
      html += '[[[test2]]]';
      html += ' tests. Some more ';
      html += '[[[aaa]]';
      html += ' and';
      html += '[[[tast]]]';
      html += '[[[alex]]]';
      html += ' aa';

      expect( $segment.find('.background-div').html() ).toBe(html);
    });

    it('should escape & highlight HTML content in bacground-div', function() {
      var $target = $segment.find('#target-fixture');
      var text = 'This is a stupid test to
 :)';

      $target
        .val(text)
        .textareaHighlighter({
          matches: [{ 'matchClass': 'test', 'match': ['test', '
'] }]
        });

      expect( $segment.find('.background-div').html() ).toBe( 'This is a <a href="#">stupid</a> test to <br/> :)' );
    });
  });

  describe('test max length', function() {
    var $target;
    var $maxChar;
    beforeEach(function() {
      $target = $segment.find('#target-fixture');
      $maxChar = $segment.find('.max-char');

      $target
        .textareaHighlighter({
          matches: [{ 'matchClass': 'test', 'match': ['test'] }],
          maxlength        : 30,
          maxlengthWarning : 'error',
          maxlengthElement : $segment.find('.max-char')
        });
    });

    it('should not highlight when not over max char limitation', function(done) {
      $target.val('This is a stupid test to').trigger('input');

      var html = '';
      html += 'This is a stupid ';
      html += 'test';
      html += ' to';

      setTimeout(function() {
        expect( $segment.find('.background-div').html() ).toBe(html);
        expect( $maxChar.hasClass('error') ).toBe(false);
        expect( $maxChar.html() ).toBe('6');

        done();
      }, 200);
    });

    it('should highlight when over max char limitation', function(done) {
      $target.val('This is a stupid test to max char limitation...').trigger('input');

      var html = '';
      html += 'This is a stupid ';
      html += 'test';
      html += ' to max c';
      html += 'har limitation...';

      setTimeout(function() {
        expect( $segment.find('.background-div').html() ).toBe(html);
        expect( $maxChar.hasClass('error') ).toBe(true);
        expect( $maxChar.html() ).toBe('-17');

        done();
      }, 200);
    });
  });

});
<commit_msg>udpate test<commit_after>describe('jquery.textarea-highlighter', function() {
  var $segment;
  beforeEach(function() {
    $segment = setFixtures(
      '
' +
        '<textarea id="target-fixture" class="target"></textarea>' +
        '

' +
      '
'
    );
  });

  it('should add divs on initialize', function() {
    var $target = $segment.find('#target-fixture');

    $target.textareaHighlighter();

    expect( $segment.find('.background-div').length ).toBe(1);
    expect( $segment.find('.autosize').length ).toBe(1);

    // Check is textarea's classes were added to the extra divs
    expect( $segment.find('.background-div').hasClass('target') ).toBe(true);
    expect( $segment.find('.autosize').parent().hasClass('target') ).toBe(true);
  });

  it('should remove extra divs on destroy', function() {
    var $target = $segment.find('#target-fixture');

    $target.textareaHighlighter();
    $target.textareaHighlighter('destroy');

    expect( $segment.find('.background-div').length ).toBe(0);
    expect( $segment.find('.autosize').length ).toBe(0);
  });

  it('should update matches', function() {
    var $target = $segment.find('#target-fixture');

    $target
      .val('This is a stupid test to :)')
      .textareaHighlighter();

    // There should be no highlights
    expect( $segment.find('.background-div').html() ).toBe('This is a stupid test to :)');

    // Update match list
    $target.textareaHighlighter('updateMatches', [{ 'matchClass': 'test', 'match': ['test'] }]);

    expect( $segment.find('.background-div').html() ).toBe('This is a stupid test to :)');
  });

  describe('test highlighting', function() {

    it('should highlight content correctly', function() {
      var $target = $segment.find('#target-fixture');

      $target
        .val('Hi [[[test1]]] this is a {0} test ハゲ THiss [[[{0}]]] and 日本 is some　アホ more [[[tast]]],[[[test2]]] tests. Some more [[[aaa]] and[[[tast]]][[[alex]]] aa')
        .textareaHighlighter({
          matches: [
            { 'matchClass': 'brackets',       'match': ['[[[test1]]]', '[[[tast]]]', '[[[test2]]]', '[[[{0}]]]', '[[[alex]]]'] },
            { 'matchClass': 'brackets error', 'match': ['[[[aaa]]'] },
            { 'matchClass': 'tags',           'match': ['{0}'] },
            { 'matchClass': 'misspelling',    'match': ['THiss', 'est', 'a'] }
          ]
        });

      var html = '';
      html += 'Hi ';
      html += '[[[test1]]]';
      html += ' this is ';
      html += 'a';
      html += ' ';
      html += '{0}';
      html += ' test ハゲ ';
      html += 'THiss';
      html += ' ';
      html += '[[[{0}]]]';
      html += ' and 日本 is some　アホ more ';
      html += '[[[tast]]]';
      html += ',';
      html += '[[[test2]]]';
      html += ' tests. Some more ';
      html += '[[[aaa]]';
      html += ' and';
      html += '[[[tast]]]';
      html += '[[[alex]]]';
      html += ' aa';

      expect( $segment.find('.background-div').html() ).toBe(html);
    });

    it('should escape & highlight HTML content in bacground-div', function() {
      var $target = $segment.find('#target-fixture');
      var text = 'This is a stupid test to
 :)';

      $target
        .val(text)
        .textareaHighlighter({
          matches: [{ 'matchClass': 'test', 'match': ['test', '
'] }]
        });

      expect( $segment.find('.background-div').html() ).toBe( 'This is a <a href="#">stupid</a> test to <br/> :)' );
    });
  });

  describe('test max length', function() {
    var $target;
    var $maxChar;
    beforeEach(function() {
      $target = $segment.find('#target-fixture');
      $maxChar = $segment.find('.max-char');

      $target
        .textareaHighlighter({
          matches: [{ 'matchClass': 'test', 'match': ['test'] }],
          maxlength        : 30,
          maxlengthWarning : 'error',
          maxlengthElement : $segment.find('.max-char')
        });
    });

    it('should not highlight when not over max char limitation', function(done) {
      $target.val('This is a stupid test to').trigger('input');

      var html = '';
      html += 'This is a stupid ';
      html += 'test';
      html += ' to';

      setTimeout(function() {
        expect( $segment.find('.background-div').html() ).toBe(html);
        expect( $maxChar.hasClass('error') ).toBe(false);
        expect( $maxChar.html() ).toBe('6');

        done();
      }, 200);
    });

    it('should highlight when over max char limitation', function(done) {
      $target.val('This is a stupid test to max char limitation...').trigger('input');

      var html = '';
      html += 'This is a stupid ';
      html += 'test';
      html += ' to max c';
      html += 'har limitation...';

      setTimeout(function() {
        expect( $segment.find('.background-div').html() ).toBe(html);
        expect( $maxChar.hasClass('error') ).toBe(true);
        expect( $maxChar.html() ).toBe('-17');

        $target.val('This').trigger('input');
        setTimeout(function() {
          expect( $maxChar.hasClass('error') ).toBe(false);
          expect( $maxChar.html() ).toBe('26');

          done();
        }, 200);
      }, 200);
    });
  });

});
<|endoftext|>


<commit_before>import Component from 'ember-component';
import get from 'ember-metal/get';
import set from 'ember-metal/set';
import { getProperties } from 'ember-metal/get';
import { debounce, later, scheduleOnce } from 'ember-runloop';
import c3 from 'c3';

export default Component.extend({
  tagName: 'div',
  classNames: ['c3-chart-component'],

  // triggered when data is updated by didUpdateAttrs
  _reload() {
    const chart = get(this, 'c3chart');

    // if data should not be appended
    // e.g. when using a pie or donut chart
    if ( get(this, 'unloadDataBeforeChange') ) {
      chart.unload();
      // default animation is 350ms
      // t/f data must by loaded after unload animation (400)
      // or chart will not properly render
      later(this, function() {
        chart.load(
          // data, axis, color are only mutable elements
          get(this, 'data'),
          get(this, 'axis'),
          get(this, 'color')
        );
      }, 400);
    } else {
      chart.load(
        get(this, 'data'),
        get(this, 'axis'),
        get(this, 'color')
      );
    }
  },

  // triggered when component added by didInsertElement
  _setupc3() {
    // get all base c3 properties
    const chartConfig = getProperties(this,
      ['data','axis','regions','bar','pie','donut','gauge',
      'grid','legend','tooltip','subchart','zoom','point',
      'line','area','size','padding','color','transition']);

    // bind c3 chart to component's DOM element
    chartConfig.bindto = get(this, 'element');

    // emit events to controller
    callbacks.call(this);
    function callbacks() {
      const that = this;
      const c3events = [
        'oninit',
        'onrendered',
        'onmouseover',
        'onmouseout',
        'onresize',
        'onresized'
      ];
      c3events.forEach((event) => {
        chartConfig[event] = function() {
          that.sendAction(event, that);
        };
      });
    }

    // render the initial chart
    set(this, 'c3chart', c3.generate(chartConfig));
  },

  /***
   * Component lifecycle hooks to control rendering actions
   ***/

  didReceiveAttrs() {
    // if DOM is not ready when component is inserted,
    // rendering issues can occur
    // t/f use 'afterRender' property to ensure
    // state readiness
    try {
      scheduleOnce('afterRender', this, this._setupc3);
    } catch(err) {
      console.log(err);
    }
  },

  didUpdateAttrs() {
    // if data proprety is dependent on async relationships,
    // animations can cause buggy renders, therefore debounce
    // component update to ensure proper visualization
    debounce(this, this._reload, 360);
  },

  willDestroyElement() {
    // execute teardown method
    this._super();
    get(this, 'c3chart').destroy();
  }
});
<commit_msg>Fix reload<commit_after>import Component from 'ember-component';
import get from 'ember-metal/get';
import set from 'ember-metal/set';
import { getProperties } from 'ember-metal/get';
import { debounce, later, scheduleOnce } from 'ember-runloop';
import c3 from 'c3';

export default Component.extend({
  tagName: 'div',
  classNames: ['c3-chart-component'],

  // triggered when data is updated by didUpdateAttrs
  _reload() {
    const chart = get(this, 'c3chart');

    // if data should not be appended
    // e.g. when using a pie or donut chart
    if ( get(this, 'unloadDataBeforeChange') ) {
      chart.unload();
      // default animation is 350ms
      // t/f data must by loaded after unload animation (400)
      // or chart will not properly render
      later(this, function() {
        chart.load(
          // data, axis, color are only mutable elements
          get(this, 'data'),
          get(this, 'axis'),
          get(this, 'color')
        );
      }, 400);
    } else {
      chart.load(
        get(this, 'data'),
        get(this, 'axis'),
        get(this, 'color')
      );
    }
  },

  // triggered when component added by didInsertElement
  _setupc3() {
    // get all base c3 properties
    const chartConfig = getProperties(this,
      ['data','axis','regions','bar','pie','donut','gauge',
      'grid','legend','tooltip','subchart','zoom','point',
      'line','area','size','padding','color','transition']);

    // bind c3 chart to component's DOM element
    chartConfig.bindto = get(this, 'element');

    // emit events to controller
    callbacks.call(this);
    function callbacks() {
      const that = this;
      const c3events = [
        'oninit',
        'onrendered',
        'onmouseover',
        'onmouseout',
        'onresize',
        'onresized'
      ];
      c3events.forEach((event) => {
        chartConfig[event] = function() {
          that.sendAction(event, that);
        };
      });
    }

    // render the initial chart
    set(this, 'c3chart', c3.generate(chartConfig));
  },

  /***
   * Component lifecycle hooks to control rendering actions
   ***/

  didInsertElement() {
    // if DOM is not ready when component is inserted,
    // rendering issues can occur
    // t/f use 'afterRender' property to ensure
    // state readiness
    try {
      scheduleOnce('afterRender', this, this._setupc3);
    } catch(err) {
      console.log(err);
    }
  },

  didUpdateAttrs() {
    // if data proprety is dependent on async relationships,
    // animations can cause buggy renders, therefore debounce
    // component update to ensure proper visualization
    debounce(this, this._reload, 360);
  },

  willDestroyElement() {
    // execute teardown method
    this._super();
    get(this, 'c3chart').destroy();
  }
});
<|endoftext|>


<commit_before>var irc    = require('irc')
, winston  = require('winston')
, fs       = require('fs')
, yaml     = require('js-yaml')
, coin     = require('node-dogecoin')
, webadmin = require('../lib/webadmin/app');

// check if the config file exists
if(!fs.existsSync('./config/config.yml')) {
  winston.error('Configuration file doesn\'t exist! Please read the README.md file first.');
  process.exit(1);
}

// handle sigint
process.on('exit', function() {
  winston.info('Exiting...');
  if(client != null) {
    client.disconnect('My master ordered me to leave.');
  }
});

// load settings
var settings = yaml.load(fs.readFileSync('./config/config.yml', 'utf-8'));

// load winston's cli defaults
winston.cli();

// write logs to file
if(settings.log.file) {
  winston.add(winston.transports.File, {
    filename: settings.log.file
  , level: 'info'});
}

// connect to coin json-rpc
winston.info('Connecting to coind...');

var coin = coin({
  host: settings.rpc.host
, port: settings.rpc.port
, user: settings.rpc.user
, pass: settings.rpc.pass
});

coin.getBalance(function(err, balance) {
  if(err) {
    winston.error('Could not connect to %s RPC API! ', settings.coin.full_name, err);
    process.exit(1);
    return;
  }

  var balance = typeof(balance) == 'object' ? balance.result : balance;
  winston.info('Connected to JSON RPC API. Current total balance is %d' + settings.coin.short_name, balance);
})

// run webadmin
if(settings.webadmin.enabled)
{
  winston.info('Running webadmin on port %d', settings.webadmin.port);
  webadmin.app(settings.webadmin.port, coin, settings.webadmin, winston);
}

// connect to the server
winston.info('Connecting to the server...');

var client = new irc.Client(settings.connection.host, settings.login.nickname, {
  port:   settings.connection.port
, secure: settings.connection.secure

, channels: settings.channels
, userName: settings.login.username
, realName: settings.login.realname

, debug: settings.connection.debug
});

// gets user's login status
irc.Client.prototype.getLoginStatus = function(nickname, callback) {
  // request login status
  this.say('NickServ', settings.connection.status_command + ' ' + nickname);

  // wait for response
  var listener = function(from, to, message) {
   // proceed only on NickServ's ACC response
    var regexp = new RegExp('^(\\S+) ' + settings.connection.status_command + ' (\\d)');
    if(from != undefined && from.toLowerCase() == 'nickserv' && regexp.test(message)) {
      var match = message.match(regexp);
      var user  = match[1];
      var level = match[2];

      // if the right response, call the callback and remove this listener
      if(user.toLowerCase() == nickname.toLowerCase()) {
        callback(Number(level));
        this.removeListener('notice', listener);
      }
    }
  }

  this.addListener('notice', listener);
}

// gets a empty coin address
irc.Client.prototype.getAddress = function(nickname, callback) {
  winston.debug('Requesting address for %s', nickname);
  coin.send('getaccountaddress', nickname.toLowerCase(), function(err, address) {
    if(err) {
      winston.error('Something went wrong while getting address. ' + err);
      callback(err);

      return false;
    }

    callback(false, address);
  });
}

String.prototype.expand = function(values) {
  var global = {
    nick: client.nick
  }
  return this.replace(/%([a-zA-Z_]+)%/g, function(str, variable) {
    return typeof(values[variable]) == 'undefined' ?
      (typeof(settings.coin[variable]) == 'undefined' ?
        (typeof(global[variable]) == 'undefined' ?
          str : global[variable]) : settings.coin[variable]) : values[variable];
  });
}

// basic handlers
client.addListener('registered', function(message) {
  winston.info('Connected to %s.', message.server);

  client.say('NickServ', 'IDENTIFY ' + settings.login.nickserv_password);
});

client.addListener('error', function(message) {
  winston.error('Received an error from IRC network: ', message);
});

client.addListener('message', function(from, channel, message) {
  var match = message.match(/^(!?)(\S+)/);
  if(match == null) return;
  var prefix  = match[1];
  var command = match[2];

  if(settings.commands[command]) {
    if(channel == client.nick && settings.commands[command].pm === false) return;
    if(channel != client.nick && (settings.commands[command].channel === false || prefix != '!')) return;
  } else {
    return;
  }

  // if pms, make sure to respond to pms instead to itself
  if(channel == client.nick) channel = from;

  // comands that don't require identifying
  if(command == 'help' || command == 'help')
  {
    for(var i = 0; i < settings.messages[command].length; i++) {
      var message = settings.messages[command][i];
      client.say(channel, message.expand({}));
    }

    return;
  }

  // if not that, message will be undefined for some reason
  // todo: find a fix for that
  var msg = message;
  client.getLoginStatus(from, function(status) {
    var message = msg;
    // check if the sending user is logged in (identified) with nickserv
    if(status != 3) {
      winston.info('%s tried to use command `%s`, but is not identified.', from, message);
      client.say(channel, settings.messages.not_identified.expand({name: from}));
      return;
    }

    switch(command) {
      case 'tip':
        var match = message.match(/^.?tip (\S+) ([\d\.]+)/);
        if(match == null || match < 3) {
          client.say(channel, 'Usage: !tip <nickname> <amount>')
          return;
        }
        var to     = match[1];
        var amount = Number(match[2]);

        if(amount == NaN) {
          client.say(channel, settings.messages.invalid_amount.expand({name: from, amount: match[2]}));
          return;
        }

        if(to.toLowerCase() == from.toLowerCase()) {
          client.say(channel, settings.messages.tip_self.expand({name: from}));
          return;
        }

        if(amount < settings.coin.min_tip) {
          client.say(channel, settings.messages.tip_too_small.expand({from: from, to: to, amount: amount}));
          return;
        }
        // check balance with min. 5 confirmations
        coin.getBalance(from, settings.coin.min_confirmations, function(err, balance) {
          if(err) {
            winston.error('Error in !tip command.', err);
            client.say(channel, settings.messages.error.expand({name: from}));
            return;
          }
          var balance = typeof(balance) == 'object' ? balance.result : balance;

          if(balance >= amount) {
            client.getAddress(to, function(err, to_address) { // get the address to actually create a new one
              if(err) {
                winston.error('Error in !tip command', err);
                client.say(channel, settings.messages.error.expand({name: from}));
                return;
              }

              coin.send('move', from.toLowerCase(), to.toLowerCase(), amount, function(err, reply) {
                if(err || !reply) {
                  winston.error('Error in !tip command', err);
                  client.say(channel, settings.messages.error.expand({name: from}));
                  return;
                }

                winston.info('%s tipped %s %d%s', from, to, amount, settings.coin.short_name)
                client.say(channel, settings.messages.tipped.expand({from: from, to: to, amount: amount}));
              });
            });
          } else {
            winston.info('%s tried to tip %s %d, but has only %d', from, to, amount, balance);
            client.say(channel, settings.messages.no_funds.expand({name: from, balance: balance, short: amount - balance, amount: amount}));
          }
        });
        break;
      case 'address':
        var user = from.toLowerCase();
        client.getAddress(user, function(err, address) {
          if(err) {
            winston.error('Error in !address command', err);
            client.say(channel, settings.messages.error.expand({name: from}));
            return;
          }

          client.say(channel, settings.messages.deposit_address.expand({name: user, address: address}));
        });
        break;
      case 'balance':
        var user = from.toLowerCase();
        coin.getBalance(user, settings.coin.min_confirmations, function(err, balance) {
          if(err) {
            winston.error('Error in !balance command', err);
            client.say(channel, settings.messages.error.expand({name: from}));
            return;
          }

          var balance = typeof(balance) == 'object' ? balance.result : balance;

          coin.getBalance(user, 0, function(err, unconfirmed_balance) {
          if(err) {
              winston.error('Error in !balance command', err);
              client.say(channel, settings.messages.balance.expand({balance: balance, name: user}));
              return;
            }

            var unconfirmed_balance = typeof(unconfirmed_balance) == 'object' ? unconfirmed_balance.result : unconfirmed_balance;

            client.say(channel, settings.messages.balance_unconfirmed.expand({balance: balance, name: user, unconfirmed: unconfirmed_balance - balance}));
          })
        });
        break;
      case 'withdraw':
        var match = message.match(/^.?withdraw (\S+)$/);
        if(match == null) {
          client.say(channel, 'Usage: !withdraw <' + settings.coin.full_name + ' address>');
          return;
        }
        var address = match[1];

        coin.validateAddress(address, function(err, reply) {
          if(err) {
            winston.error('Error in !withdraw command', err);
            client.say(channel, settings.messages.error.expand({name: from}));
            return;
          }

          if(reply.isvalid) {
            coin.getBalance(from.toLowerCase(), settings.coin.min_confirmations, function(err, balance) {
              if(err) {
                winston.error('Error in !withdraw command', err);
                client.say(channel, settings.messages.error.expand({name: from}));
                return;
              }
              var balance = typeof(balance) == 'object' ? balance.result : balance;

              if(balance < settings.coin.min_withdraw) {
                winston.warn('%s tried to withdraw %d, but min is set to %d', from, balance, settings.coin.min_withdraw);
                client.say(channel, settings.messages.withdraw_too_small.expand({name: from, balance: balance}));
                return;
              }

              coin.sendFrom(from.toLowerCase(), address, balance - settings.coin.withdrawal_fee, function(err, reply) {
                if(err) {
                  winston.error('Error in !withdraw command', err);
                  client.say(channel, settings.messages.error.expand({name: from}));
                  return;
                }

                var values = {name: from, address: address, balance: balance, amount: balance - settings.coin.withdrawal_fee, transaction: reply}
                for(var i = 0; i < settings.messages.withdraw_success.length; i++) {
                  var msg = settings.messages.withdraw_success[i];
                  client.say(channel, msg.expand(values));
                };

                // transfer the rest (usually withdrawal fee - txfee) to bots wallet
                coin.getBalance(from.toLowerCase(), function(err, balance) {
                  if(err) {
                    winston.error('Something went wrong while transferring fees', err);
                    return;
                  }

                  var balance = typeof(balance) == 'object' ? balance.result : balance;
                  coin.move(from.toLowerCase(), settings.login.nickname, balance, function(err) {
                    if(err) {
                      winston.error('Something went wrong while transferring fees', err);
                      return;
                    }
                  });
                });
              });
            });
          } else {
            winston.warn('%s tried to withdraw to an invalid address', from);
            client.say(channel, settings.messages.invalid_address.expand({address: address, name: from}));
          }
        });
        break;
    }
  });
});
<commit_msg>ah my js skills<commit_after>var irc    = require('irc')
, winston  = require('winston')
, fs       = require('fs')
, yaml     = require('js-yaml')
, coin     = require('node-dogecoin')
, webadmin = require('../lib/webadmin/app');

// check if the config file exists
if(!fs.existsSync('./config/config.yml')) {
  winston.error('Configuration file doesn\'t exist! Please read the README.md file first.');
  process.exit(1);
}

// handle sigint
process.on('exit', function() {
  winston.info('Exiting...');
  if(client != null) {
    client.disconnect('My master ordered me to leave.');
  }
});

// load settings
var settings = yaml.load(fs.readFileSync('./config/config.yml', 'utf-8'));

// load winston's cli defaults
winston.cli();

// write logs to file
if(settings.log.file) {
  winston.add(winston.transports.File, {
    filename: settings.log.file
  , level: 'info'});
}

// connect to coin json-rpc
winston.info('Connecting to coind...');

var coin = coin({
  host: settings.rpc.host
, port: settings.rpc.port
, user: settings.rpc.user
, pass: settings.rpc.pass
});

coin.getBalance(function(err, balance) {
  if(err) {
    winston.error('Could not connect to %s RPC API! ', settings.coin.full_name, err);
    process.exit(1);
    return;
  }

  var balance = typeof(balance) == 'object' ? balance.result : balance;
  winston.info('Connected to JSON RPC API. Current total balance is %d' + settings.coin.short_name, balance);
})

// run webadmin
if(settings.webadmin.enabled)
{
  winston.info('Running webadmin on port %d', settings.webadmin.port);
  webadmin.app(settings.webadmin.port, coin, settings.webadmin, winston);
}

// connect to the server
winston.info('Connecting to the server...');

var client = new irc.Client(settings.connection.host, settings.login.nickname, {
  port:   settings.connection.port
, secure: settings.connection.secure

, channels: settings.channels
, userName: settings.login.username
, realName: settings.login.realname

, debug: settings.connection.debug
});

// gets user's login status
irc.Client.prototype.getLoginStatus = function(nickname, callback) {
  // request login status
  this.say('NickServ', settings.connection.status_command + ' ' + nickname);

  // wait for response
  var listener = function(from, to, message) {
   // proceed only on NickServ's ACC response
    var regexp = new RegExp('^(\\S+) ' + settings.connection.status_command + ' (\\d)');
    if(from != undefined && from.toLowerCase() == 'nickserv' && regexp.test(message)) {
      var match = message.match(regexp);
      var user  = match[1];
      var level = match[2];

      // if the right response, call the callback and remove this listener
      if(user.toLowerCase() == nickname.toLowerCase()) {
        callback(Number(level));
        this.removeListener('notice', listener);
      }
    }
  }

  this.addListener('notice', listener);
}

// gets a empty coin address
irc.Client.prototype.getAddress = function(nickname, callback) {
  winston.debug('Requesting address for %s', nickname);
  coin.send('getaccountaddress', nickname.toLowerCase(), function(err, address) {
    if(err) {
      winston.error('Something went wrong while getting address. ' + err);
      callback(err);

      return false;
    }

    callback(false, address);
  });
}

String.prototype.expand = function(values) {
  var global = {
    nick: client.nick
  }
  return this.replace(/%([a-zA-Z_]+)%/g, function(str, variable) {
    return typeof(values[variable]) == 'undefined' ?
      (typeof(settings.coin[variable]) == 'undefined' ?
        (typeof(global[variable]) == 'undefined' ?
          str : global[variable]) : settings.coin[variable]) : values[variable];
  });
}

// basic handlers
client.addListener('registered', function(message) {
  winston.info('Connected to %s.', message.server);

  client.say('NickServ', 'IDENTIFY ' + settings.login.nickserv_password);
});

client.addListener('error', function(message) {
  winston.error('Received an error from IRC network: ', message);
});

client.addListener('message', function(from, channel, message) {
  var match = message.match(/^(!?)(\S+)/);
  if(match == null) return;
  var prefix  = match[1];
  var command = match[2];

  if(settings.commands[command]) {
    if(channel == client.nick && settings.commands[command].pm === false) return;
    if(channel != client.nick && (settings.commands[command].channel === false || prefix != '!')) return;
  } else {
    return;
  }

  // if pms, make sure to respond to pms instead to itself
  if(channel == client.nick) channel = from;

  // comands that don't require identifying
  if(command == 'help' || command == 'help')
  {
    for(var i = 0; i < settings.messages[command].length; i++) {
      var message = settings.messages[command][i];
      client.say(channel, message.expand({}));
    }

    return;
  }

  // if not that, message will be undefined for some reason
  // todo: find a fix for that
  var msg = message;
  client.getLoginStatus(from, function(status) {
    var message = msg;
    // check if the sending user is logged in (identified) with nickserv
    if(status != 3) {
      winston.info('%s tried to use command `%s`, but is not identified.', from, message);
      client.say(channel, settings.messages.not_identified.expand({name: from}));
      return;
    }

    switch(command) {
      case 'tip':
        var match = message.match(/^.?tip (\S+) ([\d\.]+)/);
        if(match == null || match < 3) {
          client.say(channel, 'Usage: !tip <nickname> <amount>')
          return;
        }
        var to     = match[1];
        var amount = Number(match[2]);

        if(isNaN(amount)) {
          client.say(channel, settings.messages.invalid_amount.expand({name: from, amount: match[2]}));
          return;
        }

        if(to.toLowerCase() == from.toLowerCase()) {
          client.say(channel, settings.messages.tip_self.expand({name: from}));
          return;
        }

        if(amount < settings.coin.min_tip) {
          client.say(channel, settings.messages.tip_too_small.expand({from: from, to: to, amount: amount}));
          return;
        }
        // check balance with min. 5 confirmations
        coin.getBalance(from, settings.coin.min_confirmations, function(err, balance) {
          if(err) {
            winston.error('Error in !tip command.', err);
            client.say(channel, settings.messages.error.expand({name: from}));
            return;
          }
          var balance = typeof(balance) == 'object' ? balance.result : balance;

          if(balance >= amount) {
            client.getAddress(to, function(err, to_address) { // get the address to actually create a new one
              if(err) {
                winston.error('Error in !tip command', err);
                client.say(channel, settings.messages.error.expand({name: from}));
                return;
              }

              coin.send('move', from.toLowerCase(), to.toLowerCase(), amount, function(err, reply) {
                if(err || !reply) {
                  winston.error('Error in !tip command', err);
                  client.say(channel, settings.messages.error.expand({name: from}));
                  return;
                }

                winston.info('%s tipped %s %d%s', from, to, amount, settings.coin.short_name)
                client.say(channel, settings.messages.tipped.expand({from: from, to: to, amount: amount}));
              });
            });
          } else {
            winston.info('%s tried to tip %s %d, but has only %d', from, to, amount, balance);
            client.say(channel, settings.messages.no_funds.expand({name: from, balance: balance, short: amount - balance, amount: amount}));
          }
        });
        break;
      case 'address':
        var user = from.toLowerCase();
        client.getAddress(user, function(err, address) {
          if(err) {
            winston.error('Error in !address command', err);
            client.say(channel, settings.messages.error.expand({name: from}));
            return;
          }

          client.say(channel, settings.messages.deposit_address.expand({name: user, address: address}));
        });
        break;
      case 'balance':
        var user = from.toLowerCase();
        coin.getBalance(user, settings.coin.min_confirmations, function(err, balance) {
          if(err) {
            winston.error('Error in !balance command', err);
            client.say(channel, settings.messages.error.expand({name: from}));
            return;
          }

          var balance = typeof(balance) == 'object' ? balance.result : balance;

          coin.getBalance(user, 0, function(err, unconfirmed_balance) {
          if(err) {
              winston.error('Error in !balance command', err);
              client.say(channel, settings.messages.balance.expand({balance: balance, name: user}));
              return;
            }

            var unconfirmed_balance = typeof(unconfirmed_balance) == 'object' ? unconfirmed_balance.result : unconfirmed_balance;

            client.say(channel, settings.messages.balance_unconfirmed.expand({balance: balance, name: user, unconfirmed: unconfirmed_balance - balance}));
          })
        });
        break;
      case 'withdraw':
        var match = message.match(/^.?withdraw (\S+)$/);
        if(match == null) {
          client.say(channel, 'Usage: !withdraw <' + settings.coin.full_name + ' address>');
          return;
        }
        var address = match[1];

        coin.validateAddress(address, function(err, reply) {
          if(err) {
            winston.error('Error in !withdraw command', err);
            client.say(channel, settings.messages.error.expand({name: from}));
            return;
          }

          if(reply.isvalid) {
            coin.getBalance(from.toLowerCase(), settings.coin.min_confirmations, function(err, balance) {
              if(err) {
                winston.error('Error in !withdraw command', err);
                client.say(channel, settings.messages.error.expand({name: from}));
                return;
              }
              var balance = typeof(balance) == 'object' ? balance.result : balance;

              if(balance < settings.coin.min_withdraw) {
                winston.warn('%s tried to withdraw %d, but min is set to %d', from, balance, settings.coin.min_withdraw);
                client.say(channel, settings.messages.withdraw_too_small.expand({name: from, balance: balance}));
                return;
              }

              coin.sendFrom(from.toLowerCase(), address, balance - settings.coin.withdrawal_fee, function(err, reply) {
                if(err) {
                  winston.error('Error in !withdraw command', err);
                  client.say(channel, settings.messages.error.expand({name: from}));
                  return;
                }

                var values = {name: from, address: address, balance: balance, amount: balance - settings.coin.withdrawal_fee, transaction: reply}
                for(var i = 0; i < settings.messages.withdraw_success.length; i++) {
                  var msg = settings.messages.withdraw_success[i];
                  client.say(channel, msg.expand(values));
                };

                // transfer the rest (usually withdrawal fee - txfee) to bots wallet
                coin.getBalance(from.toLowerCase(), function(err, balance) {
                  if(err) {
                    winston.error('Something went wrong while transferring fees', err);
                    return;
                  }

                  var balance = typeof(balance) == 'object' ? balance.result : balance;
                  coin.move(from.toLowerCase(), settings.login.nickname, balance, function(err) {
                    if(err) {
                      winston.error('Something went wrong while transferring fees', err);
                      return;
                    }
                  });
                });
              });
            });
          } else {
            winston.warn('%s tried to withdraw to an invalid address', from);
            client.say(channel, settings.messages.invalid_address.expand({address: address, name: from}));
          }
        });
        break;
    }
  });
});
<|endoftext|>