<commit_before>/**
* trials search, selections to combine etc...
* @author Isaak Y Tecle <iyt2@cornell.edu>
*
*/

JSAN.use("Prototype");
JSAN.use('jquery.blockUI');

function getPopIds () {

    var searchedPopsList = jQuery("#searched_trials_table tr").length;

    var tableId;

    if (searchedPopsList) {
	tableId = 'searched_trials_table';
    } else {
	tableId = 'all_trials_table';
    }

    jQuery('#' +tableId + ' tr').filter(':has(:checkbox:checked)')
        .bind('click',  function() {

            jQuery("#done_selecting").val('Done selecting');
            var td =  jQuery(this).html();

            var selectedTrial = '
' + td + '
';

            jQuery("#selected_trials_table tr:last").after(selectedTrial);

            jQuery("#selected_trials_table tr").each( function() {
                jQuery(this).find("input[type=checkbox]")
                    .attr('onclick', 'removeSelectedTrial()')
                    .prop('checked', true);
            });
        });

    jQuery("#selected_trials").show();
    jQuery("#combine_trials_div").show();
    jQuery("#search_again_div").show();

}

jQuery(document).ready(function() {
    jQuery('#done_selecting').on('click', function() {
	hideTrialsList();
    });

});

function hideTrialsList() {
    jQuery("#all_trials_div").empty();
    jQuery("#searched_trials_div").empty();
    jQuery("#done_selecting_div").hide();
    jQuery("#all_trials_search_message").hide();

}

function removeSelectedTrial() {

    jQuery("#selected_trials_table tr").on("change", function() {

        jQuery(this).remove();

        if (jQuery("#selected_trials_table td").length == 0) {
            jQuery("#selected_trials").hide();
            jQuery("#combine_trials_div").hide();
            jQuery("#search_again_div").hide();
            jQuery("#done_selecting").val('Select');

            searchAgain();
        }
    });

}

jQuery(document).ready(function() {
    jQuery('#search_again').on('click', function() {
	searchAgain();
    });

});

function searchAgain () {

    var url = window.location.pathname;

    if (url.match(/solgs\/search\/trials\/trait\//) != null) {
	var traitId = jQuery("input[name='trait_id']").val();
	url = '/solgs/search/result/populations/' + traitId;
    } else {
	url = '/solgs/search/trials/';
    }

    jQuery('#all_trials_div').empty();
    jQuery("#searched_trials_div").empty();
    searchAllTrials(url);
    jQuery("#all_trials_search_message").show();
    jQuery("#done_selecting_div").show();
    jQuery("#done_selecting").val('Select');

}

jQuery(document).ready(function() {
    jQuery('#combine_trait_trials').on('click', function() {
	//combineTraitTrials();
	getCombinedPopsId();
    });

});

function combineTraitTrials () {
    var trId = getTraitId();

    var trialIds = getSelectedTrials();

    var action = "/solgs/combine/populations/trait/" + trId;
    var selectedPops = trId + "=" + trialIds + '&' + 'combine=combine';

    jQuery.blockUI.defaults.applyPlatformOpacityRules = false;
    jQuery.blockUI({message: 'Please wait..'});

    jQuery.ajax({
        type: 'POST',
        dataType: "json",
        url: action,
        data: selectedPops,
        success: function(res) {

            if (res.status) {

                var comboPopsId = res.combo_pops_id;
                var newUrl = '/solgs/model/combined/populations/' + comboPopsId + '/trait/' + trId;

		if (comboPopsId) {
		    window.location.href = newUrl;
		    jQuery.unblockUI();
                } else if (res.redirect_url) {
		    goToSingleTrialPage(res.redirect_url);
		    jQuery.unblockUI();
                }

            } else {

                if (res.not_matching_pops){
                    alert('populations ' + res.not_matching_pops +
                          ' were genotyped using different marker sets. ' +
                          'Please make new selections to combine.' );
                    window.location.href =  '/solgs/search/result/populations/' + trId;
                }

                if (res.redirect_url) {
                    window.location.href = res.redirect_url;
                }
            }
	}
    });

}

jQuery(document).ready(function() {
    jQuery('#combine_trials').on('click', function() {
	getCombinedPopsId();
    });

});

function getCombinedPopsId() {

    var comboPopsList = getSelectedTrials();
    var traitId       = getTraitId();
    var referer       = window.location.href;
    var page;

    if (comboPopsList.length > 1)
    {
	var trialsIds  = comboPopsList.join(",");
	var action     = "/solgs/get/combined/populations/id";

	jQuery.ajax({
            type: 'POST',
            dataType: "json",
            url: action,
            data: {'trials': trialsIds},
            success: function(res) {
		if (res.status) {
    		    var comboPopsId = res.combo_pops_id;

		    if (window.Prototype) {
			delete Array.prototype.toJSON;
		    }

		    var args = {
			'combo_pops_id'   : [ comboPopsId ],
			'combo_pops_list' : comboPopsList,
			'analysis_type'   : 'combine populations',
			'data_set_type'   : 'multiple populations',
			'trait_id'        : traitId,
		    };

		    if (referer.match(/search\/trials\/trait\//)) {
			page = '/solgs/model/combined/trials/' + comboPopsId + '/trait/' + traitId;

		    } else {
			page = '/solgs/populations/combined/' + comboPopsId;
		    }

		    solGS.waitPage(page, args);
		}

            },
            error: function(res) {
    		alert('Error occured getting combined trials unique id');
            }
	});

    } else {
	var popId = comboPopsList[0];

	if (referer.match(/search\/trials\/trait\//)) {
	    page = '/solgs/trait/' + traitId + '/population/' + popId;

	} else {
	    page = '/solgs/population/' + popId;
	}

	var args = {'population_id'   : [ popId ],
		    'analysis_type'   : 'population download',
		    'data_set_type'   : 'single population',
		    'trait_id'        : traitId,
		   };

	solGS.waitPage(page, args);

    }
}

function retrievePopsData(trialsIds) {

    alert('ids ' + trialsIds)
    if (!trialsIds) {
	trialsIds = getSelectedTrials();
    }

    var action = "/solgs/retrieve/populations/data";

    jQuery.blockUI.defaults.applyPlatformOpacityRules = false;
    jQuery.blockUI({message: 'Please wait..'});

    jQuery.ajax({
        type: 'POST',
        dataType: "json",
        url: action,
        data: {'trials': trialsIds},
        success: function(res) {
            if (res.not_matching_pops == null) {

                var combinedPopsId = res.combined_pops_id;

                if (combinedPopsId) {
                    goToCombinedTrialsPage(combinedPopsId);
                    jQuery.unblockUI();
                } else if (res.redirect_url) {
                    goToSingleTrialPage(res.redirect_url);
                    jQuery.unblockUI();
                }

            } else if(res.not_matching_pops )  {

                jQuery.unblockUI();
                alert('populations ' + res.not_matching_pops +
                      ' were genotyped using different marker sets. ' +
                      'Please make new selections to combine.' );

            }
        },
        error: function(res) {
            jQuery.unblockUI();
            alert('An error occured retrieving phenotype' +
                  'and genotype data for trials..');
        }
    });
}

function getSelectedTrials () {

    var trialIds = [];

    if (jQuery("#selected_trials_table").length) {
        jQuery("#selected_trials_table tr").each(function () {
            var trialId = jQuery(this).find("input[type=checkbox]").val();

            if (trialId) {
                trialIds.push(trialId);
            }
        });
    }

    trialIds = trialIds.sort();

    return trialIds;

}

function goToCombinedTrialsPage(combinedPopsId) {

    var action = '/solgs/populations/combined/' + combinedPopsId;

    if (combinedPopsId) {
        window.location.href = action;
    }
}

function goToSingleTrialPage(url) {

    if (url) {
        window.location.href = url;
    }
}

function  getTraitId() {

    var id = jQuery("input[name='trait_id']").val();
    return id;
}

Array.prototype.unique =
    function() {
    var a = [];
    var l = this.length;
    for(var i=0; i<l; i++) {
      for(var j=i+1; j<l; j++) {
        // If this[i] is fo3und later in the array
        if (this[i] === this[j])
          j = ++i;
      }
      a.push(this[i]);
    }
    return a;
  };

<commit_msg>send an array of trial ids instead of comma separated..<commit_after>/**
* trials search, selections to combine etc...
* @author Isaak Y Tecle <iyt2@cornell.edu>
*
*/

JSAN.use("Prototype");
JSAN.use('jquery.blockUI');

function getPopIds () {

    var searchedPopsList = jQuery("#searched_trials_table tr").length;

    var tableId;

    if (searchedPopsList) {
	tableId = 'searched_trials_table';
    } else {
	tableId = 'all_trials_table';
    }

    jQuery('#' +tableId + ' tr').filter(':has(:checkbox:checked)')
        .bind('click',  function() {

            jQuery("#done_selecting").val('Done selecting');
            var td =  jQuery(this).html();

            var selectedTrial = '
' + td + '
';

            jQuery("#selected_trials_table tr:last").after(selectedTrial);

            jQuery("#selected_trials_table tr").each( function() {
                jQuery(this).find("input[type=checkbox]")
                    .attr('onclick', 'removeSelectedTrial()')
                    .prop('checked', true);
            });
        });

    jQuery("#selected_trials").show();
    jQuery("#combine_trials_div").show();
    jQuery("#search_again_div").show();

}

jQuery(document).ready(function() {
    jQuery('#done_selecting').on('click', function() {
	hideTrialsList();
    });

});

function hideTrialsList() {
    jQuery("#all_trials_div").empty();
    jQuery("#searched_trials_div").empty();
    jQuery("#done_selecting_div").hide();
    jQuery("#all_trials_search_message").hide();

}

function removeSelectedTrial() {

    jQuery("#selected_trials_table tr").on("change", function() {

        jQuery(this).remove();

        if (jQuery("#selected_trials_table td").length == 0) {
            jQuery("#selected_trials").hide();
            jQuery("#combine_trials_div").hide();
            jQuery("#search_again_div").hide();
            jQuery("#done_selecting").val('Select');

            searchAgain();
        }
    });

}

jQuery(document).ready(function() {
    jQuery('#search_again').on('click', function() {
	searchAgain();
    });

});

function searchAgain () {

    var url = window.location.pathname;

    if (url.match(/solgs\/search\/trials\/trait\//) != null) {
	var traitId = jQuery("input[name='trait_id']").val();
	url = '/solgs/search/result/populations/' + traitId;
    } else {
	url = '/solgs/search/trials/';
    }

    jQuery('#all_trials_div').empty();
    jQuery("#searched_trials_div").empty();
    searchAllTrials(url);
    jQuery("#all_trials_search_message").show();
    jQuery("#done_selecting_div").show();
    jQuery("#done_selecting").val('Select');

}

jQuery(document).ready(function() {
    jQuery('#combine_trait_trials').on('click', function() {
	//combineTraitTrials();
	getCombinedPopsId();
    });

});

function combineTraitTrials () {
    var trId = getTraitId();

    var trialIds = getSelectedTrials();

    var action = "/solgs/combine/populations/trait/" + trId;
    var selectedPops = trId + "=" + trialIds + '&' + 'combine=combine';

    jQuery.blockUI.defaults.applyPlatformOpacityRules = false;
    jQuery.blockUI({message: 'Please wait..'});

    jQuery.ajax({
        type: 'POST',
        dataType: "json",
        url: action,
        data: selectedPops,
        success: function(res) {

            if (res.status) {

                var comboPopsId = res.combo_pops_id;
                var newUrl = '/solgs/model/combined/populations/' + comboPopsId + '/trait/' + trId;

		if (comboPopsId) {
		    window.location.href = newUrl;
		    jQuery.unblockUI();
                } else if (res.redirect_url) {
		    goToSingleTrialPage(res.redirect_url);
		    jQuery.unblockUI();
                }

            } else {

                if (res.not_matching_pops){
                    alert('populations ' + res.not_matching_pops +
                          ' were genotyped using different marker sets. ' +
                          'Please make new selections to combine.' );
                    window.location.href =  '/solgs/search/result/populations/' + trId;
                }

                if (res.redirect_url) {
                    window.location.href = res.redirect_url;
                }
            }
	}
    });

}

jQuery(document).ready(function() {
    jQuery('#combine_trials').on('click', function() {
	getCombinedPopsId();
    });

});

function getCombinedPopsId(comboPopsList) {

    if (!comboPopsList) {
	comboPopsList = getSelectedTrials();
    }

    var traitId       = getTraitId();
    var referer       = window.location.href;
    var page;

    if (comboPopsList.length > 1)
    {
	//var trialsIds  = comboPopsList.join(",");
	var action     = "/solgs/get/combined/populations/id";

	jQuery.ajax({
            type: 'POST',
            dataType: "json",
            url: action,
            data: {'trials': comboPopsList},
            success: function(res) {
		if (res.status) {
    		    var comboPopsId = res.combo_pops_id;

		    if (window.Prototype) {
			delete Array.prototype.toJSON;
		    }

		    var args = {
			'combo_pops_id'   : [ comboPopsId ],
			'combo_pops_list' : comboPopsList,
			'analysis_type'   : 'combine populations',
			'data_set_type'   : 'multiple populations',
			'trait_id'        : traitId,
		    };

		    if (referer.match(/search\/trials\/trait\//)) {
			page = '/solgs/model/combined/trials/' + comboPopsId + '/trait/' + traitId;

		    } else {
			page = '/solgs/populations/combined/' + comboPopsId;
		    }

		    solGS.waitPage(page, args);
		}

            },
            error: function(res) {
    		alert('Error occured getting combined trials unique id');
            }
	});

    } else {
	var popId = comboPopsList[0];

	if (referer.match(/search\/trials\/trait\//)) {
	    page = '/solgs/trait/' + traitId + '/population/' + popId;

	} else {
	    page = '/solgs/population/' + popId;
	}

	var args = {'population_id'   : [ popId ],
		    'analysis_type'   : 'population download',
		    'data_set_type'   : 'single population',
		    'trait_id'        : traitId,
		   };

	solGS.waitPage(page, args);

    }
}

function retrievePopsData(trialsIds) {

    if (!trialsIds) {
	trialsIds = getSelectedTrials();
    }

    var action = "/solgs/retrieve/populations/data";

    jQuery.blockUI.defaults.applyPlatformOpacityRules = false;
    jQuery.blockUI({message: 'Please wait..'});

    jQuery.ajax({
        type: 'POST',
        dataType: "json",
        url: action,
        data: {'trials': trialsIds},
        success: function(res) {
            if (res.not_matching_pops == null) {

                var combinedPopsId = res.combined_pops_id;

                if (combinedPopsId) {
                    goToCombinedTrialsPage(combinedPopsId);
                    jQuery.unblockUI();
                } else if (res.redirect_url) {
                    goToSingleTrialPage(res.redirect_url);
                    jQuery.unblockUI();
                }

            } else if(res.not_matching_pops )  {

                jQuery.unblockUI();
                alert('populations ' + res.not_matching_pops +
                      ' were genotyped using different marker sets. ' +
                      'Please make new selections to combine.' );

            }
        },
        error: function(res) {
            jQuery.unblockUI();
            alert('An error occured retrieving phenotype' +
                  'and genotype data for trials..');
        }
    });
}

function getSelectedTrials () {

    var trialIds = [];

    if (jQuery("#selected_trials_table").length) {
        jQuery("#selected_trials_table tr").each(function () {
            var trialId = jQuery(this).find("input[type=checkbox]").val();

            if (trialId) {
                trialIds.push(trialId);
            }
        });
    }

    trialIds = trialIds.sort();

    return trialIds;

}

function goToCombinedTrialsPage(combinedPopsId) {

    var action = '/solgs/populations/combined/' + combinedPopsId;

    if (combinedPopsId) {
        window.location.href = action;
    }
}

function goToSingleTrialPage(url) {

    if (url) {
        window.location.href = url;
    }
}

function  getTraitId() {

    var id = jQuery("input[name='trait_id']").val();
    return id;
}

Array.prototype.unique =
    function() {
    var a = [];
    var l = this.length;
    for(var i=0; i<l; i++) {
      for(var j=i+1; j<l; j++) {
        // If this[i] is fo3und later in the array
        if (this[i] === this[j])
          j = ++i;
      }
      a.push(this[i]);
    }
    return a;
  };

<|endoftext|>


<commit_before>const FS   = require('fs');
const Path = require('path');

const Common  = require('./common');
const modules = require('./modules');

let action = process.argv[2];

let root   = Path.resolve(__dirname, '../Surface/source');
let client = Path.resolve(__dirname, '../App.Client');

if (action == 'l' || action == 'link')
{
    link();
}
else if (action == 'u' || action == 'unlink')
{
    unlink();
}
else if (action == 'r' || action == 'relink')
{
    relink();
}

async function link()
{
    for (let $module of modules)
    {
        for (let dependence of Common.objectToDictionary($module.dependencies).filter(x => x.key.startsWith('@surface/')))
        {
            let source = Path.normalize(`${root}\\${dependence.key}`);
            let target = Path.normalize(`${root}\\${$module.name}\\node_modules`);

            Common.makeDir(target + '\\@surface');

            target = Path.normalize(`${target}\\${dependence.key}`);

            if (!FS.existsSync(target))
                await Common.execute(`Linking ${$module.name} dependence[${dependence.key}]:`, `mklink /J ${target} ${source}`);
        }
    }

    if (!FS.existsSync(`${client}\\node_modules\\@surface`))
        await Common.execute(`Linking @surface on client:`, `mklink /J ${client}\\node_modules\\@surface ${root}\\@surface`);

    console.log('Linking done!');
}

async function unlink()
{
    for (let $module of modules)
    {
        for (let dependence of Common.objectToDictionary($module.dependencies).filter(x => x.key.startsWith('@surface/')))
        {
            let targetModule = Path.normalize(`${root}\\${$module.name}\\node_modules\\${dependence.key}`);

            if (FS.existsSync(targetModule))
                await Common.execute(`Unlinking ${$module.name} dependence[${dependence.key}]:`, `rmdir ${targetModule}`);

        }

        let targetFolder = Path.normalize(`${root}\\${$module.name}\\node_modules\\@surface`);

        if (FS.existsSync(targetFolder))
            await Common.execute(`Removing @surface on ${$module.name}:`, `rmdir ${targetFolder}`);
    }

    if (FS.existsSync(`${client}\\node_modules\\@surface`))
        await Common.execute(`Unlinking @surface link on client:`, `rmdir /s /q ${client}\\node_modules\\@surface`);

    console.log('Unlinking done!');
}

async function relink()
{
    await unlink();
    await link();
}<commit_msg>Fix on link task<commit_after>const FS   = require('fs');
const Path = require('path');

const Common  = require('./common');
const modules = require('./modules');

let action = process.argv[2];

let root   = Path.resolve(__dirname, '../Surface/source');
let client = Path.resolve(__dirname, '../App.Client');

if (action == 'l' || action == 'link')
{
    link();
}
else if (action == 'u' || action == 'unlink')
{
    unlink();
}
else if (action == 'r' || action == 'relink')
{
    relink();
}

async function link()
{
    for (let $module of modules)
    {
        for (let dependence of Common.objectToDictionary($module.dependencies).filter(x => x.key.startsWith('@surface/')))
        {
            let source = Path.normalize(`${root}\\${dependence.key}`);
            let target = Path.normalize(`${root}\\${$module.name}\\node_modules`);

            Common.makeDir(target + '\\@surface');

            target = Path.normalize(`${target}\\${dependence.key}`);

            if (!FS.existsSync(target))
                await Common.execute(`Linking ${$module.name} dependence[${dependence.key}]:`, `mklink /J ${target} ${source}`);
        }
    }

    if (!FS.existsSync(`${client}\\node_modules\\@surface`))
        await Common.execute(`Linking @surface on client:`, `mklink /J ${client}\\node_modules\\@surface ${root}\\@surface`);

    console.log('Linking done!');
}

async function unlink()
{
    for (let $module of modules)
    {
        let targetFolder = Path.normalize(`${root}\\${$module.name}\\node_modules\\@surface`);

        if (FS.existsSync(targetFolder))
            await Common.execute(`Removing @surface on ${$module.name}:`, `rmdir /s /q ${targetFolder}`);
    }

    if (FS.existsSync(`${client}\\node_modules\\@surface`))
        await Common.execute(`Unlinking @surface link on client:`, `rmdir /s /q ${client}\\node_modules\\@surface`);

    console.log('Unlinking done!');
}

async function relink()
{
    await unlink();
    await link();
}<|endoftext|>


<commit_before>/*
 * grunt
 * http://gruntjs.com/
 *
 * Copyright (c) 2012 "Cowboy" Ben Alman
 * Licensed under the MIT license.
 * http://benalman.com/about/license/
 */

module.exports = function(grunt) {

  // External libs.
  var jshint = require('jshint').JSHINT;

  // ==========================================================================
  // TASKS
  // ==========================================================================

  grunt.registerMultiTask('lint', 'Validate files with JSHint.', function() {
    // Get flags and globals, allowing target-specific options and globals to
    // override the default options and globals.
    var options, globals, tmp;

    // Check for a jshintrc file.
    if (tmp = grunt.config(['jshint', this.target, 'jshintrc'])) {
      // If a target-specific jshintrc file was specified, read it.
      grunt.verbose.writeln('Using "' + this.target + '" jshintrc file.');
      options = grunt.file.readJSON(tmp);
    } else if (tmp = grunt.config('jshint.jshintrc')) {
      // If a global jshintrc file was specified, read it.
      grunt.verbose.writeln('Using master jshintrc file.');
      options = grunt.file.readJSON(tmp);
    }

    if (options) {
      // A jshintrc file was read.
      if (options.predef) {
        // Temp kluge for https://github.com/jshint/node-jshint/issues/104
        globals = {};
        options.predef.forEach(function(key) {
          globals[key] = true;
        });
        delete options.predef;
      }

    } else {
      // No jshintrc file was read.
      tmp = grunt.config(['jshint', this.target, 'options']);
      if (typeof tmp === 'object') {
        grunt.verbose.writeln('Using "' + this.target + '" JSHint options.');
        options = tmp;
      } else {
        grunt.verbose.writeln('Using master JSHint options.');
        options = grunt.config('jshint.options');
      }

      tmp = grunt.config(['jshint', this.target, 'globals']);
      if (typeof tmp === 'object') {
        grunt.verbose.writeln('Using "' + this.target + '" JSHint globals.');
        globals = tmp;
      } else {
        grunt.verbose.writeln('Using master JSHint globals.');
        globals = grunt.config('jshint.globals');
      }
    }

    grunt.verbose.writeflags(options, 'JSHint options');
    grunt.verbose.writeflags(globals, 'JSHint globals');

    // Lint specified files.
    var files = grunt.file.expandFiles(this.file.src);
    files.forEach(function(filepath) {
      grunt.helper('lint', grunt.file.read(filepath), options, globals, filepath);
    });

    // Fail task if errors were logged.
    if (this.errorCount) { return false; }

    // Otherwise, print a success message.
    grunt.log.ok(files.length + ' files lint free.');
  });

  // ==========================================================================
  // HELPERS
  // ==========================================================================

  // No idea why JSHint treats tabs as options.indent # characters wide, but it
  // does. See issue: https://github.com/jshint/jshint/issues/430
  function getTabStr(options) {
    // Do something that's going to error.
    jshint('\tx', options || {});
    // If an error occurred, figure out what character JSHint reported and
    // subtract one.
    var character = jshint.errors && jshint.errors[0] && jshint.errors[0].character - 1;
    // If character is actually a number, use it. Otherwise use 1.
    var tabsize = isNaN(character) ? 1 : character;
    // If tabsize > 1, return something that should be safe to use as a
    // placeholder. \uFFFF repeated 2+ times.
    return tabsize > 1 && grunt.util.repeat(tabsize, '\uFFFF');
  }

  var tabregex = /\t/g;

  // Lint source code with JSHint.
  grunt.registerHelper('lint', function(src, options, globals, extraMsg) {
    // JSHint sometimes modifies objects you pass in, so clone them.
    options = grunt.util._.clone(options);
    globals = grunt.util._.clone(globals);
    // Enable/disable debugging if option explicitly set.
    if (grunt.option('debug') !== undefined) {
      options.devel = options.debug = grunt.option('debug');
      // Tweak a few things.
      if (grunt.option('debug')) {
        options.maxerr = Infinity;
      }
    }
    var msg = 'Linting' + (extraMsg ? ' ' + extraMsg : '') + '...';
    grunt.verbose.write(msg);
    // Tab size as reported by JSHint.
    var tabstr = getTabStr(options);
    var placeholderregex = new RegExp(tabstr, 'g');
    // Lint.
    var result = jshint(src, options || {}, globals || {});
    var data = jshint.data();
    // Attempt to work around JSHint erroneously reporting bugs.
    // if (!result) {
    //   // Filter out errors that shouldn't be reported.
    //   jshint.errors = jshint.errors.filter(function(o) {
    //     return o && o.something === 'something';
    //   });
    //   // If no errors are left, JSHint actually succeeded.
    //   result = jshint.errors.length === 0;
    // }
    if (result && !data.unused) {
      // Success!
      grunt.verbose.ok();
    } else {
      // Something went wrong.
      grunt.verbose.or.write(msg);
      grunt.log.error();
      // Iterate over all errors.
      jshint.errors.forEach(function(e) {
        // Sometimes there's no error object.
        if (!e) { return; }
        var pos;
        var evidence = e.evidence;
        var character = e.character;
        if (evidence) {
          // Manually increment errorcount since we're not using grunt.log.error().
          grunt.fail.errorcount++;
          // Descriptive code error.
          pos = '['.red + ('L' + e.line).yellow + ':'.red + ('C' + character).yellow + ']'.red;
          grunt.log.writeln(pos + ' ' + e.reason.yellow);
          // If necessary, eplace each tab char with something that can be
          // swapped out later.
          if (tabstr) {
            evidence = evidence.replace(tabregex, tabstr);
          }
          if (character > evidence.length) {
            // End of line.
            evidence = evidence + ' '.inverse.red;
          } else {
            // Middle of line.
            evidence = evidence.slice(0, character - 1) + evidence[character - 1].inverse.red +
              evidence.slice(character);
          }
          // Replace tab placeholder (or tabs) but with a 2-space soft tab.
          evidence = evidence.replace(tabstr ? placeholderregex : tabregex, '  ');
          grunt.log.writeln(evidence);
        } else {
          // Generic "Whoops, too many errors" error.
          grunt.log.error(e.reason);
        }
      });
      // Iterate over all unused variables
      (data.unused || []).forEach(function(u) {
        // Manually increment errorcount since we're not using grunt.log.error().
        grunt.fail.errorcount++;
        var pos = '['.red + ('L' + u.line).yellow + ']'.red;
        grunt.log.writeln(pos + ' unused variable ' + u.name.yellow);
      });
      grunt.log.writeln();
    }
  });

};
<commit_msg>Updating lint task to better report unused vars. Closes #165.<commit_after>/*
 * grunt
 * http://gruntjs.com/
 *
 * Copyright (c) 2012 "Cowboy" Ben Alman
 * Licensed under the MIT license.
 * http://benalman.com/about/license/
 */

module.exports = function(grunt) {

  // External libs.
  var jshint = require('jshint').JSHINT;

  // ==========================================================================
  // TASKS
  // ==========================================================================

  grunt.registerMultiTask('lint', 'Validate files with JSHint.', function() {
    // Get flags and globals, allowing target-specific options and globals to
    // override the default options and globals.
    var options, globals, tmp;

    // Check for a jshintrc file.
    if (tmp = grunt.config(['jshint', this.target, 'jshintrc'])) {
      // If a target-specific jshintrc file was specified, read it.
      grunt.verbose.writeln('Using "' + this.target + '" jshintrc file.');
      options = grunt.file.readJSON(tmp);
    } else if (tmp = grunt.config('jshint.jshintrc')) {
      // If a global jshintrc file was specified, read it.
      grunt.verbose.writeln('Using master jshintrc file.');
      options = grunt.file.readJSON(tmp);
    }

    if (options) {
      // A jshintrc file was read.
      if (options.predef) {
        // Temp kluge for https://github.com/jshint/node-jshint/issues/104
        globals = {};
        options.predef.forEach(function(key) {
          globals[key] = true;
        });
        delete options.predef;
      }

    } else {
      // No jshintrc file was read.
      tmp = grunt.config(['jshint', this.target, 'options']);
      if (typeof tmp === 'object') {
        grunt.verbose.writeln('Using "' + this.target + '" JSHint options.');
        options = tmp;
      } else {
        grunt.verbose.writeln('Using master JSHint options.');
        options = grunt.config('jshint.options');
      }

      tmp = grunt.config(['jshint', this.target, 'globals']);
      if (typeof tmp === 'object') {
        grunt.verbose.writeln('Using "' + this.target + '" JSHint globals.');
        globals = tmp;
      } else {
        grunt.verbose.writeln('Using master JSHint globals.');
        globals = grunt.config('jshint.globals');
      }
    }

    grunt.verbose.writeflags(options, 'JSHint options');
    grunt.verbose.writeflags(globals, 'JSHint globals');

    // Lint specified files.
    var files = grunt.file.expandFiles(this.file.src);
    files.forEach(function(filepath) {
      grunt.helper('lint', grunt.file.read(filepath), options, globals, filepath);
    });

    // Fail task if errors were logged.
    if (this.errorCount) { return false; }

    // Otherwise, print a success message.
    grunt.log.ok(files.length + ' files lint free.');
  });

  // ==========================================================================
  // HELPERS
  // ==========================================================================

  // No idea why JSHint treats tabs as options.indent # characters wide, but it
  // does. See issue: https://github.com/jshint/jshint/issues/430
  function getTabStr(options) {
    // Do something that's going to error.
    jshint('\tx', options || {});
    // If an error occurred, figure out what character JSHint reported and
    // subtract one.
    var character = jshint.errors && jshint.errors[0] && jshint.errors[0].character - 1;
    // If character is actually a number, use it. Otherwise use 1.
    var tabsize = isNaN(character) ? 1 : character;
    // If tabsize > 1, return something that should be safe to use as a
    // placeholder. \uFFFF repeated 2+ times.
    return tabsize > 1 && grunt.util.repeat(tabsize, '\uFFFF');
  }

  var tabregex = /\t/g;

  // Lint source code with JSHint.
  grunt.registerHelper('lint', function(src, options, globals, extraMsg) {
    // JSHint sometimes modifies objects you pass in, so clone them.
    options = grunt.util._.clone(options);
    globals = grunt.util._.clone(globals);
    // Enable/disable debugging if option explicitly set.
    if (grunt.option('debug') !== undefined) {
      options.devel = options.debug = grunt.option('debug');
      // Tweak a few things.
      if (grunt.option('debug')) {
        options.maxerr = Infinity;
      }
    }
    var msg = 'Linting' + (extraMsg ? ' ' + extraMsg : '') + '...';
    grunt.verbose.write(msg);
    // Tab size as reported by JSHint.
    var tabstr = getTabStr(options);
    var placeholderregex = new RegExp(tabstr, 'g');
    // Lint.
    var result = jshint(src, options || {}, globals || {});
    var data = jshint.data();
    // Attempt to work around JSHint erroneously reporting bugs.
    // if (!result) {
    //   // Filter out errors that shouldn't be reported.
    //   jshint.errors = jshint.errors.filter(function(o) {
    //     return o && o.something === 'something';
    //   });
    //   // If no errors are left, JSHint actually succeeded.
    //   result = jshint.errors.length === 0;
    // }
    if (result && !data.unused) {
      // Success!
      grunt.verbose.ok();
      return;
    }
    // Something went wrong.
    grunt.verbose.or.write(msg);
    grunt.log.error();
    // Iterate over all unused variables
    var varsByLine = {};
    if (data.unused) {
      data.unused.forEach(function(unused) {
        var line = unused.line;
        var vars = varsByLine[line] = varsByLine[line] || [];
        vars.push(unused.name);
      });
      Object.keys(varsByLine).forEach(function(line) {
        // Manually increment errorcount since we're not using grunt.log.error().
        grunt.fail.errorcount++;
        var vars = varsByLine[line];
        grunt.log.writeln('['.red + ('L' + line).yellow + ']'.red +
          (' Unused variable' + (vars.length === 1 ? '' : 's') + ': ').yellow +
          grunt.log.wordlist(vars, {color: false, separator: ', '.yellow}));
      });
    }
    // Iterate over all errors.
    jshint.errors.forEach(function(e) {
      // Sometimes there's no error object.
      if (!e) { return; }
      var pos;
      var evidence = e.evidence;
      var character = e.character;
      if (evidence) {
        // Manually increment errorcount since we're not using grunt.log.error().
        grunt.fail.errorcount++;
        // Descriptive code error.
        pos = '['.red + ('L' + e.line).yellow + ':'.red + ('C' + character).yellow + ']'.red;
        grunt.log.writeln(pos + ' ' + e.reason.yellow);
        // If necessary, eplace each tab char with something that can be
        // swapped out later.
        if (tabstr) {
          evidence = evidence.replace(tabregex, tabstr);
        }
        if (character > evidence.length) {
          // End of line.
          evidence = evidence + ' '.inverse.red;
        } else {
          // Middle of line.
          evidence = evidence.slice(0, character - 1) + evidence[character - 1].inverse.red +
            evidence.slice(character);
        }
        // Replace tab placeholder (or tabs) but with a 2-space soft tab.
        evidence = evidence.replace(tabstr ? placeholderregex : tabregex, '  ');
        grunt.log.writeln(evidence);
      } else {
        // Generic "Whoops, too many errors" error.
        grunt.log.error(e.reason);
      }
    });
    grunt.log.writeln();
  });

};
<|endoftext|>


<commit_before>// ==UserScript==
// @name         Neopets Reminders
// @namespace    jvirdo
// @description  Adds daily pulldown with links and countdowns (h/t Jahn Johansen for the original version)
// @include      http://*.neopets.com/*
// @include      http://neopets.com/*
// @match        http://*.neopets.com/*
// @match        http://neopets.com/*
// @require      http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js
// @version      2.0.0
// ==/UserScript==

var bootScript = function(){
    // Contains everything after the domain, e.g. /index.phtml
    var page = location.pathname + location.search;

    // TODO: Add support for specific time events, e.g. Snowager.
    var dailies = {
        "Free, just-click-it dailies": { type: "divider" },
        "Collect Interest": {
            href: "/bank.phtml", type: "daily"
        },
        "Lab Ray": {
            href:"/lab.phtml", type:"daily"
        },
        "Forgotten Shore": {
            href: "/pirates/forgottenshore.phtml", type: "daily"
        },
        "Tombola": {
            href: "/island/tombola.phtml", type: "daily"
        },
        "Fruit Machine": {
            href: "/desert/fruit/index.phtml", type: "daily"
        },
        "Anchor Management": {
            href: "/pirates/anchormanagement.phtml", type: "daily"
        },
        "Deserted Tomb": {
            href: "/worlds/geraptiku/tomb.phtml", type: "daily"
        },
        "TDMBGPOP": {
            href: "/faerieland/tdmbgpop.phtml", type: "daily"
        },
        "Rich Slorg": {
            href: "/shop_of_offers.phtml?slorg_payout=yes", type: "daily"
        },
        "Coltzan's Shrine": {
            href: "/desert/shrine.phtml", type: "interval",
            delay: 60 * 60 * 12
        },
        "Qasalan Expellibox": {
            href: "/mall/shop.phtml?page=giveaway", type: "interval",
            delay: 60 * 60 * 8
        },
        "Healing Springs": {
            href:"/faerieland/springs.phtml", type:"interval",
            delay : 60 * 30
        },
        "Grave Danger": {
            // TODO: Scrape remaining time and update appropriately.
            href: "/halloween/gravedanger/", type: "daily"
        },

        "Item fetching dailies": { type: "divider" },
        "Giant Omelette": {
            href: "/prehistoric/omelette.phtml", type: "daily"
        },
        "Giant Jelly": {
            href: "/jelly/jelly.phtml", type: "daily"
        },
        "Apple Bobbing": {
            href: "/halloween/applebobbing.phtml", type: "daily"
        },
        "Weltrude's Toy Chest": {
            href: "/petpetpark/daily.phtml", type: "daily"
        },
        "Meteor Crash Site 725-XZ": {
            href: "/moon/meteor.phtml", type: "daily"
        },
        "Ye Olde Fishing Vortex": {
            href: "/water/fishing.phtml", type: "daily"
        },
        "Mysterious Symol Hole": {
            href: "/medieval/symolhole.phtml", type: "daily"
        },
        "Obsidian Quarry": {
            href: "/magma/quarry.phtml", type: "daily"
        },
        "Money Tree": {
            href: "/donations.phtml", type: "daily"
        },

        "Dailies that cost NP or items": { type: "divider" },
        "Bargain Stocks": {
            href: "/stockmarket.phtml?type=list&bargain=true", type: "daily"
        },
        "Wheel of Excitement": {
            href: "/faerieland/wheel.phtml", type: "interval",
            delay: 60 * 60 * 2
        },
        "Wheel of Mediocrity": {
            href:"/prehistoric/mediocrity.phtml", type:"interval",
            delay: 60 * 40
        },
        "Wheel of Knowledge": {
            href: "/medieval/knowledge.phtml", type: "daily"
        },
        "Winter Kiosk": {
            href:"/winter/kiosk.phtml", type:"interval",
            delay: 60 * 60 * 6
        },
        "Faerie Caverns": {
            href: "/faerieland/caverns/index.phtml", type: "daily"
        },
        "Random Events Machine": {
            href: "/space/coincidence.phtml", type: "daily"
        },

        "Puzzles or effort required dailies": { type: "divider" },
        "Negg Cave": {
            href: "/shenkuu/neggcave/", type: "daily"
        },
        "Lunar Temple": {
            href: "/shenkuu/lunar/", type: "daily"
        },
        "Faerie Crossword": {
            href: "/games/crossword/index.phtml", type: "daily"
        },
        "Daily Puzzle": {
            href: "/community/", type: "daily"
        },
        "Grumpy King": {
            href: "/medieval/grumpyking.phtml", type: "daily"
        }
    };

    // Good luck finding us, bitches! Temporary variables are temporary. B)
    $ = undefined;
    var jQ = jQuery;
    var $ = jQ;
    jQuery = undefined;

    /*!
     * jQuery Cookie Plugin v1.3
     * https://github.com/carhartl/jquery-cookie
    */
    (function(e,t,n){function i(e){return e}function s(e){return decodeURIComponent(e.replace(r," "))}var r=/\+/g;var o=e.cookie=function(r,u,a){if(u!==n){a=e.extend({},o.defaults,a);if(u===null){a.expires=-1}if(typeof a.expires==="number"){var f=a.expires,l=a.expires=new Date;l.setDate(l.getDate()+f)}u=o.json?JSON.stringify(u):String(u);return t.cookie=[encodeURIComponent(r),"=",o.raw?u:encodeURIComponent(u),a.expires?"; expires="+a.expires.toUTCString():"",a.path?"; path="+a.path:"",a.domain?"; domain="+a.domain:"",a.secure?"; secure":""].join("")}var c=o.raw?i:s;var h=t.cookie.split("; ");for(var p=0,d=h.length;p<d;p++){var v=h[p].split("=");if(c(v.shift())===r){var m=c(v.join("="));return o.json?JSON.parse(m):m}}return null};o.defaults={};e.removeCookie=function(t,n){if(e.cookie(t)!==null){e.cookie(t,null,n);return true}return false}})(jQ,document);

    // If GreaseMonkey's functions are not present, use some fallbacks
    if (typeof(GM_getValue) === "undefined") {
        // First fallback : Opera's scriptStorage object.
        if (typeof(window.opera.scriptStorage) !== "undefined") {
            GM_getValue = function(key, value){
                var result = window.opera.scriptStorage[key];
                return result == undefined ? value : result;
            };
            GM_setValue = function(key, value){
                window.opera.scriptStorage[key] = value;
            };
        // Second fallback : Cookies - made to look like a tracking cookie.
        // Viacom will never know. >:)
        } else {
            var data = $.cookie("track");
            data = data == undefined ? {} : JSON.parse(data);
            GM_getValue = function(key, value){
                var result = data[key];
                return typeof(result) === "undefined" ? value : result;
            };
            GM_setValue = function(key, value){
                data[key] = value;
                $.cookie("track", JSON.stringify(data), { domain : ".neopets.com", expires : 7 });
            };
        }
    }

    function pad_zeros(num, len) {
        var str = num + "";
        while (str.length < len) str = "0" + str;
        return str;
    }

    // Handles NST (equivalent to PST).
    var NST = (function() {
        var tnst = {};
        // Gets current time in NST.
        tnst.now = function() {
            var unix = Math.floor(new Date().getTime() / 1000);
            var nst = unix + (60 * 60 * 16); // XXX: Doesn't handle DST?
            return nst;
        };
        // Formats the given timestamp into text for the pulldown menu.
        tnst.format = function(timestamp) {
            if (timestamp <= 0) return "Open!";
            var hours = pad_zeros(Math.floor(timestamp / 60 / 60), 2),
                min = pad_zeros(Math.floor(timestamp / 60 % 60), 2),
                sec = pad_zeros(timestamp % 60, 2);
            return hours + ":" + min + ":" + sec;
        }
        tnst.day = function(){
            return Math.floor(tnst.now() / (60 * 60 * 24));
        }
        return tnst;
    })();

    // Make sure data persists between page loads.
    var username, getLastTime, setLastTime;

    getLastTime = function(id) {
        var value = GM_getValue(username + "#" + id,
                                "{ \"time\" : -1 , \"day\" : -1 }");
        return JSON.parse(value);
    };

    setLastTime = function(id) {
        var value = JSON.stringify({
            time : NST.now(),
            day : NST.day()
        });
        GM_setValue(username + "#" + id, value);
    };

    var undoneDailies = 0;
    function initializeDailies(user){
        for (var name in dailies) {
            var daily = dailies[name];
            var lastTime = getLastTime(name);

            // Figure out how long to wait from our last visit.
            var wait;
            if (daily.type == "daily") {
                wait = ((lastTime.day + 1) * 24 * 60 * 60) - NST.now();
            } else if (daily.type == "interval") {
                wait = daily.delay - (NST.now() - lastTime.time);
            } else if (daily.type == "divider") {
                continue;
            }
            dailies[name].timeLeft = wait;

            // Figure out if we've already waited that long.
            if (wait <= 0 || lastTime.time == -1) {
                dailies[name].canDo = true;
                undoneDailies++;
            } else {
                dailies[name].canDo = false;
            }

            // Check if we're currently doing this daily.
            if (page == daily.href && dailies[name].canDo) {
                setLastTime(name);
                undoneDailies--;
                dailies[name].canDo = false;
            }
            dailies[name].href = "http://www.neopets.com" + dailies[name].href;
        }
    }

    var GUI = (function(){
        var gui = {};
        gui.header = $("#header table td.user.medText");
        gui.isLoggedIn = function(){
            return gui.header.html().indexOf("userlookup.phtml?user=") != -1;
        };
        gui.headerMod = function(){
            var parts = gui.header.html().split("|"),
                logout = parts[parts.length - 1];
            if(gui.isLoggedIn()){
                username = gui.header.find("a[href^=\"userlookup\"]").text();
                parts.push(" Dailies: "
                           + undoneDailies + " ");
                parts[parts.length - 2] = parts[parts.length - 1];
                parts[parts.length - 1] = logout;
                gui.header.html(parts.join("|"));
                var dailyWindow = $("
"),
                    link = $(".showdailies");

                dailyWindow.css({
                    "background-color": "white",
                    "border": "2px solid black",
                    "border-radius": "8px",
                    "box-shadow": "0 4px 4px #000",
                    "padding": "8px",
                    "position": "absolute",
                    "width": "250px",
                    "left": link.offset().left - 120,
                    "top": link.offset().top + link.height(),
                    "z-index": "9999"
                });
                dailyWindow.hide().appendTo($("body"));
                if (undoneDailies > 0) {
                    link.css("text-shadow", "0px 0px 5px rgba(0, 255, 0, 1)");
                }
                link.html(undoneDailies.length);

                // TODO: Clean this up... Remnant of the original code.
                var firstLine = true;
                for (var name in dailies) {
                    var daily = dailies[name];
                    var dailyEntry = $("
");
                    if (daily.type == "divider") {
                        if (firstLine) {
                            dailyEntry
                                .append($("").css("float", "center").html(name))
                                .append("
");
                            firstLine = false;
                        } else {
                            dailyEntry
                                .append("
")
                                .append($("").css("float", "center").html(name))
                                .append("
");
                        }
                    } else {
                        dailyEntry
                            .append($("").css("float", "left")
                                    .append($("").html(name).attr("href", daily.href)))
                            .append($("").css("float", "right").html(NST.format(daily.timeLeft)))
                            .append("
");
                    }
                    dailyWindow.append(dailyEntry);
                }

                link.click(function(){
                    dailyWindow.slideToggle();
                });
            }
        };
        return gui;
    })();

    // Load!
    $(document).ready(function() {
        initializeDailies();
        GUI.headerMod();
    });
};

if (typeof(window.jQuery) === "undefined") {
    var head = document.getElementsByTagName("head")[0],
        script = document.createElement("script");
    script.type = "text/javascript";
    script.src = "http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js";
    head.appendChild(script);
    window.addEventListener("load", function(e){ bootScript(); });
} else {
    bootScript();
}

<commit_msg>update version<commit_after>// ==UserScript==
// @name         Neopets Reminders
// @namespace    jvirdo
// @description  Adds daily pulldown with links and countdowns (h/t Jahn Johansen for the original version)
// @include      http://*.neopets.com/*
// @include      http://neopets.com/*
// @match        http://*.neopets.com/*
// @match        http://neopets.com/*
// @require      http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js
// @version      2.0.1
// ==/UserScript==

var bootScript = function(){
    // Contains everything after the domain, e.g. /index.phtml
    var page = location.pathname + location.search;

    // TODO: Add support for specific time events, e.g. Snowager.
    var dailies = {
        "Free, just-click-it dailies": { type: "divider" },
        "Collect Interest": {
            href: "/bank.phtml", type: "daily"
        },
        "Lab Ray": {
            href:"/lab.phtml", type:"daily"
        },
        "Forgotten Shore": {
            href: "/pirates/forgottenshore.phtml", type: "daily"
        },
        "Tombola": {
            href: "/island/tombola.phtml", type: "daily"
        },
        "Fruit Machine": {
            href: "/desert/fruit/index.phtml", type: "daily"
        },
        "Anchor Management": {
            href: "/pirates/anchormanagement.phtml", type: "daily"
        },
        "Deserted Tomb": {
            href: "/worlds/geraptiku/tomb.phtml", type: "daily"
        },
        "TDMBGPOP": {
            href: "/faerieland/tdmbgpop.phtml", type: "daily"
        },
        "Rich Slorg": {
            href: "/shop_of_offers.phtml?slorg_payout=yes", type: "daily"
        },
        "Coltzan's Shrine": {
            href: "/desert/shrine.phtml", type: "interval",
            delay: 60 * 60 * 12
        },
        "Qasalan Expellibox": {
            href: "/mall/shop.phtml?page=giveaway", type: "interval",
            delay: 60 * 60 * 8
        },
        "Healing Springs": {
            href:"/faerieland/springs.phtml", type:"interval",
            delay : 60 * 30
        },
        "Grave Danger": {
            // TODO: Scrape remaining time and update appropriately.
            href: "/halloween/gravedanger/", type: "daily"
        },

        "Item fetching dailies": { type: "divider" },
        "Giant Omelette": {
            href: "/prehistoric/omelette.phtml", type: "daily"
        },
        "Giant Jelly": {
            href: "/jelly/jelly.phtml", type: "daily"
        },
        "Apple Bobbing": {
            href: "/halloween/applebobbing.phtml", type: "daily"
        },
        "Weltrude's Toy Chest": {
            href: "/petpetpark/daily.phtml", type: "daily"
        },
        "Meteor Crash Site 725-XZ": {
            href: "/moon/meteor.phtml", type: "daily"
        },
        "Ye Olde Fishing Vortex": {
            href: "/water/fishing.phtml", type: "daily"
        },
        "Mysterious Symol Hole": {
            href: "/medieval/symolhole.phtml", type: "daily"
        },
        "Obsidian Quarry": {
            href: "/magma/quarry.phtml", type: "daily"
        },
        "Money Tree": {
            href: "/donations.phtml", type: "daily"
        },

        "Dailies that cost NP or items": { type: "divider" },
        "Bargain Stocks": {
            href: "/stockmarket.phtml?type=list&bargain=true", type: "daily"
        },
        "Wheel of Excitement": {
            href: "/faerieland/wheel.phtml", type: "interval",
            delay: 60 * 60 * 2
        },
        "Wheel of Mediocrity": {
            href:"/prehistoric/mediocrity.phtml", type:"interval",
            delay: 60 * 40
        },
        "Wheel of Knowledge": {
            href: "/medieval/knowledge.phtml", type: "daily"
        },
        "Winter Kiosk": {
            href:"/winter/kiosk.phtml", type:"interval",
            delay: 60 * 60 * 6
        },
        "Faerie Caverns": {
            href: "/faerieland/caverns/index.phtml", type: "daily"
        },
        "Random Events Machine": {
            href: "/space/coincidence.phtml", type: "daily"
        },

        "Puzzles or effort required dailies": { type: "divider" },
        "Negg Cave": {
            href: "/shenkuu/neggcave/", type: "daily"
        },
        "Lunar Temple": {
            href: "/shenkuu/lunar/", type: "daily"
        },
        "Faerie Crossword": {
            href: "/games/crossword/index.phtml", type: "daily"
        },
        "Daily Puzzle": {
            href: "/community/", type: "daily"
        },
        "Grumpy King": {
            href: "/medieval/grumpyking.phtml", type: "daily"
        }
    };

    // Good luck finding us, bitches! Temporary variables are temporary. B)
    $ = undefined;
    var jQ = jQuery;
    var $ = jQ;
    jQuery = undefined;

    /*!
     * jQuery Cookie Plugin v1.3
     * https://github.com/carhartl/jquery-cookie
    */
    (function(e,t,n){function i(e){return e}function s(e){return decodeURIComponent(e.replace(r," "))}var r=/\+/g;var o=e.cookie=function(r,u,a){if(u!==n){a=e.extend({},o.defaults,a);if(u===null){a.expires=-1}if(typeof a.expires==="number"){var f=a.expires,l=a.expires=new Date;l.setDate(l.getDate()+f)}u=o.json?JSON.stringify(u):String(u);return t.cookie=[encodeURIComponent(r),"=",o.raw?u:encodeURIComponent(u),a.expires?"; expires="+a.expires.toUTCString():"",a.path?"; path="+a.path:"",a.domain?"; domain="+a.domain:"",a.secure?"; secure":""].join("")}var c=o.raw?i:s;var h=t.cookie.split("; ");for(var p=0,d=h.length;p<d;p++){var v=h[p].split("=");if(c(v.shift())===r){var m=c(v.join("="));return o.json?JSON.parse(m):m}}return null};o.defaults={};e.removeCookie=function(t,n){if(e.cookie(t)!==null){e.cookie(t,null,n);return true}return false}})(jQ,document);

    // If GreaseMonkey's functions are not present, use some fallbacks
    if (typeof(GM_getValue) === "undefined") {
        // First fallback : Opera's scriptStorage object.
        if (typeof(window.opera.scriptStorage) !== "undefined") {
            GM_getValue = function(key, value){
                var result = window.opera.scriptStorage[key];
                return result == undefined ? value : result;
            };
            GM_setValue = function(key, value){
                window.opera.scriptStorage[key] = value;
            };
        // Second fallback : Cookies - made to look like a tracking cookie.
        // Viacom will never know. >:)
        } else {
            var data = $.cookie("track");
            data = data == undefined ? {} : JSON.parse(data);
            GM_getValue = function(key, value){
                var result = data[key];
                return typeof(result) === "undefined" ? value : result;
            };
            GM_setValue = function(key, value){
                data[key] = value;
                $.cookie("track", JSON.stringify(data), { domain : ".neopets.com", expires : 7 });
            };
        }
    }

    function pad_zeros(num, len) {
        var str = num + "";
        while (str.length < len) str = "0" + str;
        return str;
    }

    // Handles NST (equivalent to PST).
    var NST = (function() {
        var tnst = {};
        // Gets current time in NST.
        tnst.now = function() {
            var unix = Math.floor(new Date().getTime() / 1000);
            var nst = unix + (60 * 60 * 16); // XXX: Doesn't handle DST?
            return nst;
        };
        // Formats the given timestamp into text for the pulldown menu.
        tnst.format = function(timestamp) {
            if (timestamp <= 0) return "Open!";
            var hours = pad_zeros(Math.floor(timestamp / 60 / 60), 2),
                min = pad_zeros(Math.floor(timestamp / 60 % 60), 2),
                sec = pad_zeros(timestamp % 60, 2);
            return hours + ":" + min + ":" + sec;
        }
        tnst.day = function(){
            return Math.floor(tnst.now() / (60 * 60 * 24));
        }
        return tnst;
    })();

    // Make sure data persists between page loads.
    var username, getLastTime, setLastTime;

    getLastTime = function(id) {
        var value = GM_getValue(username + "#" + id,
                                "{ \"time\" : -1 , \"day\" : -1 }");
        return JSON.parse(value);
    };

    setLastTime = function(id) {
        var value = JSON.stringify({
            time : NST.now(),
            day : NST.day()
        });
        GM_setValue(username + "#" + id, value);
    };

    var undoneDailies = 0;
    function initializeDailies(user){
        for (var name in dailies) {
            var daily = dailies[name];
            var lastTime = getLastTime(name);

            // Figure out how long to wait from our last visit.
            var wait;
            if (daily.type == "daily") {
                wait = ((lastTime.day + 1) * 24 * 60 * 60) - NST.now();
            } else if (daily.type == "interval") {
                wait = daily.delay - (NST.now() - lastTime.time);
            } else if (daily.type == "divider") {
                continue;
            }
            dailies[name].timeLeft = wait;

            // Figure out if we've already waited that long.
            if (wait <= 0 || lastTime.time == -1) {
                dailies[name].canDo = true;
                undoneDailies++;
            } else {
                dailies[name].canDo = false;
            }

            // Check if we're currently doing this daily.
            if (page == daily.href && dailies[name].canDo) {
                setLastTime(name);
                undoneDailies--;
                dailies[name].canDo = false;
            }
            dailies[name].href = "http://www.neopets.com" + dailies[name].href;
        }
    }

    var GUI = (function(){
        var gui = {};
        gui.header = $("#header table td.user.medText");
        gui.isLoggedIn = function(){
            return gui.header.html().indexOf("userlookup.phtml?user=") != -1;
        };
        gui.headerMod = function(){
            var parts = gui.header.html().split("|"),
                logout = parts[parts.length - 1];
            if(gui.isLoggedIn()){
                username = gui.header.find("a[href^=\"userlookup\"]").text();
                parts.push(" Dailies: "
                           + undoneDailies + " ");
                parts[parts.length - 2] = parts[parts.length - 1];
                parts[parts.length - 1] = logout;
                gui.header.html(parts.join("|"));
                var dailyWindow = $("
"),
                    link = $(".showdailies");

                dailyWindow.css({
                    "background-color": "white",
                    "border": "2px solid black",
                    "border-radius": "8px",
                    "box-shadow": "0 4px 4px #000",
                    "padding": "8px",
                    "position": "absolute",
                    "width": "250px",
                    "left": link.offset().left - 120,
                    "top": link.offset().top + link.height(),
                    "z-index": "9999"
                });
                dailyWindow.hide().appendTo($("body"));
                if (undoneDailies > 0) {
                    link.css("text-shadow", "0px 0px 5px rgba(0, 255, 0, 1)");
                }
                link.html(undoneDailies.length);

                // TODO: Clean this up... Remnant of the original code.
                var firstLine = true;
                for (var name in dailies) {
                    var daily = dailies[name];
                    var dailyEntry = $("
");
                    if (daily.type == "divider") {
                        if (firstLine) {
                            dailyEntry
                                .append($("").css("float", "center").html(name))
                                .append("
");
                            firstLine = false;
                        } else {
                            dailyEntry
                                .append("
")
                                .append($("").css("float", "center").html(name))
                                .append("
");
                        }
                    } else {
                        dailyEntry
                            .append($("").css("float", "left")
                                    .append($("").html(name).attr("href", daily.href)))
                            .append($("").css("float", "right").html(NST.format(daily.timeLeft)))
                            .append("
");
                    }
                    dailyWindow.append(dailyEntry);
                }

                link.click(function(){
                    dailyWindow.slideToggle();
                });
            }
        };
        return gui;
    })();

    // Load!
    $(document).ready(function() {
        initializeDailies();
        GUI.headerMod();
    });
};

if (typeof(window.jQuery) === "undefined") {
    var head = document.getElementsByTagName("head")[0],
        script = document.createElement("script");
    script.type = "text/javascript";
    script.src = "http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js";
    head.appendChild(script);
    window.addEventListener("load", function(e){ bootScript(); });
} else {
    bootScript();
}

<|endoftext|>


<commit_before>(function(jQuery) {

  var $ = jQuery;

  var EXAMPLE_URL  = '/germplasm/map';

  // jquery selectors which are used in the tour definition
  var SELECTOR = {
    leafletMap: '#map',
    leafletMarker: 'svg g path.leaflet-clickable:last',
    results: '#results-list',
    accession: '#tour-accession-btn',
    locator: '#tour-coords-btn',
    search: '#search-btn',
    filters: '#current-search-filters',
    reframe: '#reframe-btn',
    addMyData: '#add-my-data-btn',
    baseMap: '#change-base-map-btn',
    coords:  '#enter-coords-btn',
    geolocate: '#geolocate-btn',
    mapHeight: '#map-height-btn',
    tour: '#tour-btn',
    menu: '#menu-btn',
  };

  function _showMenu() {
    if(window.angular) {
      if(! $('#menu').length) {
	angular.element('#menu-btn').trigger('click');
      }
      if($('#search-options:visible')) {
	angular.element('#search-cancel').trigger('click');
      }
    }
  }

  var tour = new Tour({
    name: 'germplasm-map',
    debug: true,
    orphan: true,
    steps: [
      {
        title: 'Welcome',
        content: 'This is a short tour of the Germplasm map viewer. This web \
          app offers searching and visualization of germplasm accessions and \
          trait observations. Use the Next button or → (right arrow key)\
          to advance the tour. Use the Prev button or ← (left arrow key)\
          to step back.',
	onShow: function(tour) {
	  // if the URL is not matching germplasm/map
	  if(document.location.href.indexOf(EXAMPLE_URL) === -1) {
	    console.log('redirecting to: '+ EXAMPLE_URL);
	    document.location.href = EXAMPLE_URL;
	    return (new jQuery.Deferred()).promise();
	  }
	  //return lisTours.waitForSelector(tour, SELECTOR.leafletMap);
	},
        onShown: function (tour) {
          // hide both the search div, and menu div at start of tour.
          if(! $('#menu').length) {
            angular.element('#menu-btn').trigger('click');
          }
          if($('#search-options:visible')) {
            angular.element('#search-cancel').trigger('click');
          }
        }
      },
      {
        title: 'Map Frame',
        content: 'You can drag the map to pan the extent, and use your \
           mouse-wheel to zoom in and out, or use the \
            / \
            zoom buttons \
           (The same as google maps and many other web maps). Try it!',
        element: SELECTOR.leafletMap,
        placement: 'bottom',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.leafletMap);
	}
      },
      {
        title: 'Map Markers',
        content: 'Click on a circular map marker to select this accession and \
get more details. Try it! Map markers are colored by genus, and \
within each genus the hue is darker or lighter for the various \
species.',
        element: SELECTOR.leafletMarker,
        placement: 'bottom',
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.leafletMarker);
	}
      },
      {
        title: 'Results Table',
        content: 'The accessions shown here are automatically synced with the \
search results, and with the map frame.',
        element: SELECTOR.results,
        placement: 'top',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.results);
	}
      },
      {
        title: 'Accession Id Buttons',
        content: 'Click on an accession id to get more details, including \
links out to search other resources. Try it!',
        element: SELECTOR.accession,
        placement: 'top',
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.accession);
	}
      },
      {
        title: 'Locator Buttons',
        content: 'Click on the \
 \
location icon to select, center and reveal this accession on the map.\
Try it!',
        element: SELECTOR.locator,
        placement: 'top',
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.locator);
	}
      },
      {
        title: 'Set Search Parameters',
        content: 'Click on the Search button to reveal search settings. \
The Taxon Search may be plain text or use logical \
AND (&) and OR (|) operators. For example
 \
Arachis hypogaea or
\
sylvestris | burchellii or
\
medicago & brevispina.\
You may click OK to close the search parameters panel.',
        element: SELECTOR.search,
        placement: 'top',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.search);
        },
      },
      {
        title: 'Your Search Filters',
        content: 'Your current search filters are always listed here. You can \
click the   to \
remove any filter. Your map view and results listing are updated \
automatically. Try it!',
        element: SELECTOR.filters,
        placement: 'top',
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.filters);
	}
      },
      {
        title: 'Reframe map',
        content: 'If you want to zoom in on the current list of  \
accessions, use this button.',
        element: SELECTOR.reframe,
        placement: 'bottom',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForContent(
	    tour,
	    function() {
	      _showMenu();
	      return ($(SELECTOR.reframe).length > 0);
	    });
	}
      },
      {
        title: 'Add my data',
        content: 'You can add your own CSV or delimited text data ' +
          'to the map viewer, using this tool.',
        element: SELECTOR.addMyData,
        placement: 'bottom',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForContent(
	    tour,
	    function() {
	      _showMenu();
	      return ($(SELECTOR.addMyData).length > 0);
	    });
	}
      },
      {
        title: 'Change Base Map',
        content: 'You can adjust the base map for a different \
          appearance, if desired. This does not effect your search \
          results!',
        element: SELECTOR.baseMap,
        placement: 'bottom',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForContent(
	    tour,
	    function() {
	      _showMenu();
	      return ($(SELECTOR.baseMap).length > 0);
	    });
	}
      },
      {
        title: 'Geographic Coordinates',
        content: 'Click this button to view the current center of the \
map in latitude and longitude. Or enter new coordinates to go\
there. Remember: the search results are updated automatically.',
        element: SELECTOR.coords,
        placement: 'bottom',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForContent(
	    tour,
	    function() {
	      _showMenu();
	      return ($(SELECTOR.coords).length > 0);
	    });
	}
      },
      {
        title: 'Geolocate',
        content: 'Click this button to go to your current geolocation \
(note: you may be prompted to allow this request by your \
browser). Remember: the search results are updated automatically.',
        element: SELECTOR.geolocate,
        placement: 'left',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForContent(
	    tour,
	    function() {
	      _showMenu();
	      return ($(SELECTOR.geolocate).length > 0);
	    });
	}
      },
      {
        title: 'Map Height',
        content: 'You can adjust the map vertical appearance by using \
this tool.',
        element: SELECTOR.mapHeight,
        placement: 'left',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForContent(
	    tour,
	    function() {
	      _showMenu();
	      return ($(SELECTOR.mapHeight).length > 0);
	    });
	}
      },
      {
        title: 'Revisit this tour',
        content: 'Click this button anytime to re-open this tour of \
the web app!',
        element: SELECTOR.tour,
        placement: 'left',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForContent(
	    tour,
	    function() {
	      _showMenu();
	      return ($(SELECTOR.tour).length > 0);
	    });
	},
	onNext: function(tour) {
	  $(SELECTOR.menu).trigger('click');
	}
      },
      {
        title: 'Menu',
        content: 'Hide/Show the options menu with this button.',
        element: SELECTOR.menu,
        placement: 'left',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.menu);
	}
      }
    ]
  });

  lisTours.register(tour);

}(window.__jquery));
<commit_msg>Use defaultTemplate; correct usage of promise();<commit_after>(function(jQuery) {

  var $ = jQuery;

  var EXAMPLE_URL  = '/germplasm/map';

  // jquery selectors which are used in the tour definition
  var SELECTOR = {
    leafletMap: '#map',
    leafletMarker: 'svg g path.leaflet-clickable:last',
    results: '#results-list',
    accession: '#tour-accession-btn',
    locator: '#tour-coords-btn',
    search: '#search-btn',
    filters: '#current-search-filters',
    reframe: '#reframe-btn',
    addMyData: '#add-my-data-btn',
    baseMap: '#change-base-map-btn',
    coords:  '#enter-coords-btn',
    geolocate: '#geolocate-btn',
    mapHeight: '#map-height-btn',
    tour: '#tour-btn',
    menu: '#menu-btn',
  };

  function _showMenu() {
    if(window.angular) {
      if(! $('#menu').length) {
	angular.element('#menu-btn').trigger('click');
      }
      if($('#search-options:visible')) {
	angular.element('#search-cancel').trigger('click');
      }
    }
  }

  var tour = new Tour({
    name: 'germplasm-map',
    //debug: true,
    template: lisTours.template.defaultTemplate,
    orphan: true,
    steps: [
      {
        title: 'Welcome',
        content: 'This is a short tour of the Germplasm map viewer. This web \
          app offers searching and visualization of germplasm accessions and \
          trait observations. Use the Next button or → (right arrow key)\
          to advance the tour. Use the Prev button or ← (left arrow key)\
          to step back.',
	onShow: function(tour) {
	  // if the URL is not matching germplasm/map
	  if(document.location.href.indexOf(EXAMPLE_URL) === -1) {
	    console.log('redirecting to: '+ EXAMPLE_URL);
	    document.location.href = EXAMPLE_URL;
	    return (new jQuery.Deferred()).promise();
	  }
	  //return lisTours.waitForSelector(tour, SELECTOR.leafletMap);
	},
        onShown: function (tour) {
          // hide both the search div, and menu div at start of tour.
          if(! $('#menu').length) {
            angular.element('#menu-btn').trigger('click');
          }
          if($('#search-options:visible')) {
            angular.element('#search-cancel').trigger('click');
          }
        }
      },
      {
        title: 'Map Frame',
        content: 'You can drag the map to pan the extent, and use your \
           mouse-wheel to zoom in and out, or use the \
            / \
            zoom buttons \
           (The same as google maps and many other web maps). Try it!',
        element: SELECTOR.leafletMap,
        placement: 'bottom',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.leafletMap).promise();
	}
      },
      {
        title: 'Map Markers',
        content: 'Click on a circular map marker to select this accession and \
get more details. Try it! Map markers are colored by genus, and \
within each genus the hue is darker or lighter for the various \
species.',
        element: SELECTOR.leafletMarker,
        placement: 'bottom',
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.leafletMarker).promise();
	}
      },
      {
        title: 'Results Table',
        content: 'The accessions shown here are automatically coordinated with the \
search results, and with the map frame.',
        element: SELECTOR.results,
        placement: 'top',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.results).promise();
	}
      },
      {
        title: 'Accession Id Buttons',
        content: 'Click on an accession id button to get more details, including \
links out to search other resources. Try it!',
        element: SELECTOR.accession,
        placement: 'top',
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.accession).promise();
	}
      },
      {
        title: 'Locator Buttons',
        content: 'Click on the \
 \
location icon to select, center and reveal this accession on the map.\
Try it!',
        element: SELECTOR.locator,
        placement: 'top',
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.locator).promise();
	}
      },
      {
        title: 'Set Search Parameters',
        content: 'Click on the Search button to reveal search settings. \
The Taxon Search may be plain text or use logical \
AND (&) and OR (|) operators. For example
 \
Arachis hypogaea or
\
sylvestris | burchellii or
\
medicago & brevispina.\
You may click OK to close the search parameters panel.',
        element: SELECTOR.search,
        placement: 'top',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.search).promise();
        },
      },
      {
        title: 'Your Search Filters',
        content: 'Your current search filters are always listed here. You can \
click the   to \
remove any filter. Your map view and results listing are updated \
automatically. Try it!',
        element: SELECTOR.filters,
        placement: 'top',
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.filters).promise();
	}
      },
      {
        title: 'Reframe map',
        content: 'If you want to zoom in on the current list of  \
accessions, use this button.',
        element: SELECTOR.reframe,
        placement: 'bottom',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForContent(
	    tour,
	    function() {
	      _showMenu();
	      return ($(SELECTOR.reframe).length > 0);
	    }).promise();
	}
      },
      {
        title: 'Add my data',
        content: 'You can add your own CSV or delimited text data ' +
          'to the map viewer, using this tool.',
        element: SELECTOR.addMyData,
        placement: 'bottom',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForContent(
	    tour,
	    function() {
	      _showMenu();
	      return ($(SELECTOR.addMyData).length > 0);
	    }).promise();
	}
      },
      {
        title: 'Change Base Map',
        content: 'You can adjust the base map for a different \
          appearance, if desired. This does not effect your search \
          results!',
        element: SELECTOR.baseMap,
        placement: 'bottom',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForContent(
	    tour,
	    function() {
	      _showMenu();
	      return ($(SELECTOR.baseMap).length > 0);
	    }).promise();
	}
      },
      {
        title: 'Geographic Coordinates',
        content: 'Click this button to view the current center of the \
map in latitude and longitude. Or enter new coordinates to go\
there. Remember: the search results are updated automatically.',
        element: SELECTOR.coords,
        placement: 'bottom',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForContent(
	    tour,
	    function() {
	      _showMenu();
	      return ($(SELECTOR.coords).length > 0);
	    }).promise();
	}
      },
      {
        title: 'Geolocate',
        content: 'Click this button to go to your current geolocation \
(note: you may be prompted to allow this request by your \
browser). Remember: the search results are updated automatically.',
        element: SELECTOR.geolocate,
        placement: 'left',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForContent(
	    tour,
	    function() {
	      _showMenu();
	      return ($(SELECTOR.geolocate).length > 0);
	    }).promise();
	}
      },
      {
        title: 'Map Height',
        content: 'You can adjust the map vertical appearance by using \
this tool.',
        element: SELECTOR.mapHeight,
        placement: 'left',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForContent(
	    tour,
	    function() {
	      _showMenu();
	      return ($(SELECTOR.mapHeight).length > 0);
	    }).promise();
	}
      },
      {
        title: 'Revisit this tour',
        content: 'Click this button anytime to re-open this tour of \
the web app!',
        element: SELECTOR.tour,
        placement: 'left',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForContent(
	    tour,
	    function() {
	      _showMenu();
	      return ($(SELECTOR.tour).length > 0);
	    }).promise();
	},
	onNext: function(tour) {
	  $(SELECTOR.menu).trigger('click');
	}
      },
      {
        title: 'Menu',
        content: 'Hide/Show the options menu with this button. Congratulations,\
            you have completed the tour!',
        element: SELECTOR.menu,
        placement: 'left',
	reflex: true,
	onShow: function(tour) {
	  return lisTours.waitForSelector(tour, SELECTOR.menu).promise();
	}
      }
    ]
  });

  lisTours.register(tour);

}(window.__jquery));
<|endoftext|>


<commit_before>'use strict';
const r = require('../services/reddit');
const usernoteHelper = require('../services/usernote-helper');
const SUBREDDIT = 'pokemontrades';

const SPAM_COOLDOWN = 60 * 60 * 6; // The number of seconds allowed between threads
const TRADE_TYPE_CSS_CLASSES = new Set(['event', 'shiny', 'comp', 'casual', 'item', 'tradeback', 'dexevo', 'file', 'redeem', 'bank']);
const GIVEAWAY_TYPE_CSS_CLASSES = new Set(['contest', 'giveaway']);

const threadsByUser = new Map();
const handledThreadIds = new Set();

const getSpamReply = (newThread, oldThread) => `Hello /u/${newThread.author.name},

Sorry, but this thread has been removed because it violates [rule 7](/r/pokemontrades/wiki/rules). In the future, please do not post trade threads more often than once per 6 hours. (Your [other thread](${oldThread.url}) was posted more recently than 6 hours ago.)

*This action was performed by a bot. If you think there was some mistake, please click the "report" button next to this comment, and a human will take a look at your post shortly.*`;

module.exports = {
  period: 60,
  onStart: false,
  task ({bot: {nick}}) {
    return r.getSubreddit(SUBREDDIT).getNew()
      .filter(thread => !handledThreadIds.has(thread.id))
      .then(threads => threads.reverse())
      .each(thread => handledThreadIds.add(thread.id))
      .each(thread => {
        if (!threadsByUser.has(thread.author.name)) {
          threadsByUser.set(thread.author.name, []);
        }
      }).mapSeries(thread => {
        const oldThread = threadsByUser.get(thread.author.name).find(oldThread => threadBreaksRules(thread, oldThread));

        if (oldThread && thread.approved_by === null) {
          return thread.remove().reply(getSpamReply(thread, oldThread)).distinguish().return(
            `[Spam notification]: The thread ${thread.url} by /u/${thread.author.name} breaks the spam rule, and has been removed. (Old thread: ${oldThread.url} )`
          ).tap(() => usernoteHelper.addNote({mod: nick, user: thread.author.name, subreddit: thread.subreddit.display_name, note: 'Spam', link: `l,${thread.id}`}));
        } else {
          threadsByUser.set(
            thread.author.name,
            threadsByUser.get(thread.author.name)
              .filter(oldThread => oldThread.created_utc + SPAM_COOLDOWN > Date.now() / 1000)
              .concat({url: thread.url, created_utc: thread.created_utc, link_flair_css_class: thread.link_flair_css_class})
          );
        }
      }).filter(response => response);
  }
};

function threadBreaksRules (thread, oldThread) {
  return oldThread.created_utc + SPAM_COOLDOWN > thread.created_utc && (
    TRADE_TYPE_CSS_CLASSES.has(thread.link_flair_css_class) && TRADE_TYPE_CSS_CLASSES.has(oldThread.link_flair_css_class) ||
    GIVEAWAY_TYPE_CSS_CLASSES.has(thread.link_flair_css_class) && GIVEAWAY_TYPE_CSS_CLASSES.has(oldThread.link_flair_css_class)
  );
}
<commit_msg>Change usernote message<commit_after>'use strict';
const r = require('../services/reddit');
const usernoteHelper = require('../services/usernote-helper');
const SUBREDDIT = 'pokemontrades';

const SPAM_COOLDOWN = 60 * 60 * 6; // The number of seconds allowed between threads
const TRADE_TYPE_CSS_CLASSES = new Set(['event', 'shiny', 'comp', 'casual', 'item', 'tradeback', 'dexevo', 'file', 'redeem', 'bank']);
const GIVEAWAY_TYPE_CSS_CLASSES = new Set(['contest', 'giveaway']);

const threadsByUser = new Map();
const handledThreadIds = new Set();

const getSpamReply = (newThread, oldThread) => `Hello /u/${newThread.author.name},

Sorry, but this thread has been removed because it violates [rule 7](/r/pokemontrades/wiki/rules). In the future, please do not post trade threads more often than once per 6 hours. (Your [other thread](${oldThread.url}) was posted more recently than 6 hours ago.)

*This action was performed by a bot. If you think there was some mistake, please click the "report" button next to this comment, and a human will take a look at your post shortly.*`;

module.exports = {
  period: 60,
  onStart: false,
  task ({bot: {nick}}) {
    return r.getSubreddit(SUBREDDIT).getNew()
      .filter(thread => !handledThreadIds.has(thread.id))
      .then(threads => threads.reverse())
      .each(thread => handledThreadIds.add(thread.id))
      .each(thread => {
        if (!threadsByUser.has(thread.author.name)) {
          threadsByUser.set(thread.author.name, []);
        }
      }).mapSeries(thread => {
        const oldThread = threadsByUser.get(thread.author.name).find(oldThread => threadBreaksRules(thread, oldThread));

        if (oldThread && thread.approved_by === null) {
          return thread.remove().reply(getSpamReply(thread, oldThread)).distinguish().return(
            `[Spam notification]: The thread ${thread.url} by /u/${thread.author.name} breaks the spam rule, and has been removed. (Old thread: ${oldThread.url} )`
          ).tap(() => usernoteHelper.addNote({mod: nick, user: thread.author.name, subreddit: thread.subreddit.display_name, note: '6 hour rule', link: `l,${thread.id}`}));
        } else {
          threadsByUser.set(
            thread.author.name,
            threadsByUser.get(thread.author.name)
              .filter(oldThread => oldThread.created_utc + SPAM_COOLDOWN > Date.now() / 1000)
              .concat({url: thread.url, created_utc: thread.created_utc, link_flair_css_class: thread.link_flair_css_class})
          );
        }
      }).filter(response => response);
  }
};

function threadBreaksRules (thread, oldThread) {
  return oldThread.created_utc + SPAM_COOLDOWN > thread.created_utc && (
    TRADE_TYPE_CSS_CLASSES.has(thread.link_flair_css_class) && TRADE_TYPE_CSS_CLASSES.has(oldThread.link_flair_css_class) ||
    GIVEAWAY_TYPE_CSS_CLASSES.has(thread.link_flair_css_class) && GIVEAWAY_TYPE_CSS_CLASSES.has(oldThread.link_flair_css_class)
  );
}
<|endoftext|>


<commit_before>//App as an object
var todoList = {
  //intial array of Tasks
  todos: [],

  //Adds to tasks
  addTodo: function(todoText){
    this.todos.push({
      todoText: todoText,
      completed: false,
    });
  },

  //Changes task's name
  changeTodo: function(position, todoText){
    this.todos[position].todoText = todoText;
  },

  //Changes completedness of the task
  toggleCompleted: function(position){
    this.todos[position].completed = true;
  },

  //Toggles completedness of all tasks
  //If all complete -> all incompleted
  //Else if some complete or non complete -> all completed
  toggleAll: function(){

    var totalTodos = this.todos.length;
    var completedTodos = 0;

    //Make the counter
    for(var i = 0 ; i < totalTodos;i++){
      if(this.todos[i].completed === true){
        completedTodos++;
      }
    }

    if(completedTodos === totalTodos){
      for(var i = 0; i < totalTodos; i++){
      this.todos[i].completed = false;
      }

    } else {
      for(var i = 0; i < totalTodos; i++){
      this.todos[i].completed = true;
      }
    }
  },

  //Deletes specific task
  deleteTodo: function(position){
    this.todos.splice(position,1);
  },

};

//Object that includes all methods for buttons
//Separates functions for UI
var handlers = {

  //Triggers todoList.toggleAll(); via a button
  toggleAll: function(){
    todoList.toggleAll();
    view.displayTodos();
  },

//Triggers add.toggleAll(); via a button, adds Todo to list
  addTodo: function(){
    //scoops text from text box
    var newToDo = document.getElementById('addTodoTextInput');
    todoList.addTodo(newToDo.value);
    //clears text input box after given task is added
    newToDo.value = '';
    view.displayTodos();
  },

  //Triggers todoList.changeTodo(); via a button. Inserts new text in give numeric position
  changeTodo: function(){
    var changeTodoPositionInput = document.getElementById('changeTodoPositionInput');
    var changeTodoText = document.getElementById('changeTodoText');
    todoList.changeTodo(changeTodoPositionInput.valueAsNumber,changeTodoText.value);
    changeTodoPositionInput.value = '';
    changeTodoText.value = '';
    view.displayTodos();
  },

   //Triggers todoList.toggleAll(); via a button
   deleteTodo: function(){
    var deleteTodoPositionInput = document.getElementById('deleteTodoPositionInput');
    todoList.deleteTodo(deleteTodoPositionInput.valueAsNumber);
     deleteTodoPositionInput.value =  '';
    view.displayTodos();

  },

  //Triggers todoList.toggleCompleted(); via a button. Takes number value to spcify which todo to toggle complete
  toggleCompleted: function(){
    var toggleCompletedPositionInput = document.getElementById('toggleCompletedPositionInput');
    todoList.toggleCompleted(toggleCompletedPositionInput.valueAsNumber);
    toggleCompletedPositionInput = '';
    view.displayTodos();
  }
};

//Responsible for screen output functions
var view = {

  displayTodos: function(){
    var todoUL = document.querySelector('ul');
    todoUL.innerHTML = '';
    for(var i = 0; i < todoList.todos.length; i++){
      var todoLi = document.createElement('li');
      var todoTextWithCompletion = '';

      if(todoList.todos[i].completed === true){
        todoTextWithCompletion = '(x) ';
      } else {todoTextWithCompletion = '( ) '};

      todoLi.textContent = todoTextWithCompletion + todoList.todos[i].todoText;
      todoUL.appendChild(todoLi);
    };
  },
}

<commit_msg>quick<commit_after>//App as an object
var todoList = {
  //intial array of Tasks
  todos: [],

  //Adds to tasks
  addTodo: function(todoText){
    this.todos.push({
      todoText: todoText,
      completed: false,
    });
  },

  //Changes task's name
  changeTodo: function(position, todoText){
    this.todos[position].todoText = todoText;
  },

  //Changes completedness of the task
  toggleCompleted: function(position){
    this.todos[position].completed = true;
  },

  //Toggles completedness of all tasks
  //If all complete -> all incompleted
  //Else if some complete or non complete -> all completed
  toggleAll: function(){

    var totalTodos = this.todos.length;
    var completedTodos = 0;

    //Make the counter
    for(var i = 0 ; i < totalTodos;i++){
      if(this.todos[i].completed === true){
        completedTodos++;
      }
    }

    if(completedTodos === totalTodos){
      for(var i = 0; i < totalTodos; i++){
      this.todos[i].completed = false;
      }

    } else {
      for(var i = 0; i < totalTodos; i++){
      this.todos[i].completed = true;
      }
    }
  },

  //Deletes specific task
  deleteTodo: function(position){
    this.todos.splice(position,1);
  },

};

//Object that includes all methods for buttons
//Separates functions for UI
var handlers = {

  //Triggers todoList.toggleAll(); via a button
  toggleAll: function(){
    todoList.toggleAll();
    view.displayTodos();
  },

//Triggers add.toggleAll(); via a button, adds Todo to list
  addTodo: function(){
    //scoops text from text box
    var newToDo = document.getElementById('addTodoTextInput');
    todoList.addTodo(newToDo.value);
    //clears text input box after given task is added
    newToDo.value = '';
    view.displayTodos();
  },

  //Triggers todoList.changeTodo(); via a button. Inserts new text in give numeric position
  changeTodo: function(){
    var changeTodoPositionInput = document.getElementById('changeTodoPositionInput');
    var changeTodoText = document.getElementById('changeTodoText');
    todoList.changeTodo(changeTodoPositionInput.valueAsNumber,changeTodoText.value);
    changeTodoPositionInput.value = '';
    changeTodoText.value = '';
    view.displayTodos();
  },

   //Triggers todoList.toggleAll(); via a button
   deleteTodo: function(){
    var deleteTodoPositionInput = document.getElementById('deleteTodoPositionInput');
    todoList.deleteTodo(deleteTodoPositionInput.valueAsNumber);
     deleteTodoPositionInput.value =  '';
    view.displayTodos();

  },

  //Triggers todoList.toggleCompleted(); via a button. Takes number value to spcify which todo to toggle complete
  toggleCompleted: function(){
    var toggleCompletedPositionInput = document.getElementById('toggleCompletedPositionInput');
    todoList.toggleCompleted(toggleCompletedPositionInput.valueAsNumber);
    toggleCompletedPositionInput = '';
    view.displayTodos();
  }
};

//Responsible for screen output functions
var view = {

  displayTodos: function(){
    var todoUL = document.querySelector('ul');
    todoUL.innerHTML = '';
    for(var i = 0; i < todoList.todos.length; i++){
      var todoLi = document.createElement('li');
      var todoTextWithCompletion = '';

      if(todoList.todos[i].completed === true){
        todoTextWithCompletion = '(x) ';
      } else {todoTextWithCompletion = '( ) '};

      todoLi.textContent = todoTextWithCompletion + todoList.todos[i].todoText;
      todoUL.appendChild(todoLi);
    };
  },
}

<|endoftext|>


<commit_before>var childProcess = require('child_process'),
    fs = require('fs');

module.exports = function(grunt) {
  grunt.registerTask('test:bin', function() {
    var done = this.async();

    childProcess.exec('./bin/handlebars -a spec/artifacts/empty.handlebars', function(err, stdout) {
      if (err) {
        throw err;
      }

      var expected = fs.readFileSync('./spec/expected/empty.amd.js');
      if (stdout.toString() !== expected.toString()) {
        throw new Error('Expected binary output differed:\n\n"' + stdout + '"\n\n"' + expected + '"');
      }

      done();
    });
  });
  grunt.registerTask('test:mocha', function() {
    var done = this.async();

    var runner = childProcess.fork('./spec/env/runner', [], {stdio: 'inherit'});
    runner.on('close', function(code) {
      if (code != 0) {
        grunt.fatal(code + ' tests failed');
      }
      done();
    });
  });
  grunt.registerTask('test:cov', function() {
    var done = this.async();

    var runner = childProcess.fork('node_modules/.bin/istanbul', ['cover', '--', './spec/env/runner.js'], {stdio: 'inherit'});
    runner.on('close', function(code) {
      if (code != 0) {
        grunt.fatal(code + ' tests failed');
      }
      done();
    });
  });

  grunt.registerTask('test:check-cov', function() {
    var done = this.async();

    var runner = childProcess.fork('node_modules/.bin/istanbul', ['check-coverage', '--statements', '100', '--functions', '100', '--branches', '100', '--lines 100'], {stdio: 'inherit'});
    runner.on('close', function(code) {
      if (code != 0) {
        grunt.fatal('Coverage check failed: ' + code);
      }
      done();
    });
  });
  grunt.registerTask('test', ['test:bin', 'test:cov', 'test:check-cov']);
};
<commit_msg>Add source map flag for istanbul fork<commit_after>var childProcess = require('child_process'),
    fs = require('fs');

module.exports = function(grunt) {
  grunt.registerTask('test:bin', function() {
    var done = this.async();

    childProcess.exec('./bin/handlebars -a spec/artifacts/empty.handlebars', function(err, stdout) {
      if (err) {
        throw err;
      }

      var expected = fs.readFileSync('./spec/expected/empty.amd.js');
      if (stdout.toString() !== expected.toString()) {
        throw new Error('Expected binary output differed:\n\n"' + stdout + '"\n\n"' + expected + '"');
      }

      done();
    });
  });
  grunt.registerTask('test:mocha', function() {
    var done = this.async();

    var runner = childProcess.fork('./spec/env/runner', [], {stdio: 'inherit'});
    runner.on('close', function(code) {
      if (code != 0) {
        grunt.fatal(code + ' tests failed');
      }
      done();
    });
  });
  grunt.registerTask('test:cov', function() {
    var done = this.async();

    var runner = childProcess.fork('node_modules/.bin/istanbul', ['cover', '--source-map', '--', './spec/env/runner.js'], {stdio: 'inherit'});
    runner.on('close', function(code) {
      if (code != 0) {
        grunt.fatal(code + ' tests failed');
      }
      done();
    });
  });

  grunt.registerTask('test:check-cov', function() {
    var done = this.async();

    var runner = childProcess.fork('node_modules/.bin/istanbul', ['check-coverage', '--statements', '100', '--functions', '100', '--branches', '100', '--lines 100'], {stdio: 'inherit'});
    runner.on('close', function(code) {
      if (code != 0) {
        grunt.fatal('Coverage check failed: ' + code);
      }
      done();
    });
  });
  grunt.registerTask('test', ['test:bin', 'test:cov', 'test:check-cov']);
};
<|endoftext|>


<commit_before>var _ = require('lodash');
var async = require('async');

var joy = require('./joy/joy.js');

var schemas = {
  'basal-rate-segment': require('./basal'),
  bolus: require('./bolus'),
  cbg: require('./bg'),
  common: require('./common'),
  deviceMeta: joy(),
  message: require('./message'),
  settings: require('./settings'),
  smbg: require('./bg'),
  wizard: require('./wizard')
};

module.exports = {
  validateOne: function(datum, cb) {
    var handler = schemas[datum.type];
    if (handler == null) {
      datum.errorMessage = util.format('Unknown data.type[%s]', datum.type);
      cb(new Error(datum.errorMessage), datum);
    } else {
      try {
        handler(datum);
      } catch (e) {
        console.log('Oh noes! This is wrong:\n', datum);
        console.log(util.format('Error Message: %s%s', datum.type, e.message));
        datum.errorMessage = e.message;
        result.invalid.push(datum);
        return cb(e, datum);
      }
      cb(null, datum);
    }
  },
  validateAll: function(data, cb) {
    console.time('Pure');
    async.map(data, this.validateOne.bind(this), function(err, results) {
      console.timeEnd('Pure');
      cb(err, results);
    });
  }
};
<commit_msg>Fix "util not defined" error from merge conflict<commit_after>var _ = require('lodash');
var async = require('async');
var util = require('util');

var joy = require('./joy/joy.js');

var schemas = {
  'basal-rate-segment': require('./basal'),
  bolus: require('./bolus'),
  cbg: require('./bg'),
  common: require('./common'),
  deviceMeta: joy(),
  message: require('./message'),
  settings: require('./settings'),
  smbg: require('./bg'),
  wizard: require('./wizard')
};

module.exports = {
  validateOne: function(datum, cb) {
    var handler = schemas[datum.type];
    if (handler == null) {
      datum.errorMessage = util.format('Unknown data.type[%s]', datum.type);
      cb(new Error(datum.errorMessage), datum);
    } else {
      try {
        handler(datum);
      } catch (e) {
        console.log('Oh noes! This is wrong:\n', datum);
        console.log(util.format('Error Message: %s%s', datum.type, e.message));
        datum.errorMessage = e.message;
        result.invalid.push(datum);
        return cb(e, datum);
      }
      cb(null, datum);
    }
  },
  validateAll: function(data, cb) {
    console.time('Pure');
    async.map(data, this.validateOne.bind(this), function(err, results) {
      console.timeEnd('Pure');
      cb(err, results);
    });
  }
};
<|endoftext|>