10
\$\begingroup\$

Code Review is running rampant with an infection. A zombie infection. Everywhere posts are becoming rapidly mutated into slobbering, green freaks of nature. But the citizens of Code Review are not running. No; they are fighting! These CRitters are shooting the zombies left and right to keep Code Review a safe and healthy community for everyone.

And, here to help you on your mission is The Apocalypse Assistant. With this utility, you can now keep track of how well you are doing in keeping the zombies at bay.

Back-story: Call of Duty - We're on a mission

For each UTC day, you get 40 votes to cast on questions and answers of your liking and an unlimited amount of answers. This UserScript keeps track of how much of your ammo (votes) you have used and how many shots (answers) you have fired at zombies.

But first, what is a zombie?

In a (successful) attempt to motivate the community in reducing the number of unanswered questions, a Call of Duty was made in which every unanswered question (no upvoted answers) is to be regarded as a Zombie. Every new question is to be regarded as an incoming Zombie.


This can be found on GitHub:

// ==UserScript==
// @name         The Apocalypse Assistant
// @namespace    https://github.com/SirPython/TheApocalypseAssistant
// @version      0.1
// @description  A tool for helping you know how well you are holding back the zombies
// @author       SirPython
// @match        *://codereview.stackexchange.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_notification
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

var STORAGE_NAME = "TheApocalypseAssistant";
var VOTES_PER_DAY = 40;

var ups = document.getElementsByClassName("vote-up-off");
var downs = document.getElementsByClassName("vote-down-off");
var submitAnswer = document.getElementById("submit-button");

var data = GM_getValue(STORAGE_NAME);

/* If there are upvote buttons, there are downvote buttons and a submitAnswer button */
if(ups.length !== 0) {

    /* If it is a new day or if there is no pre-existing data. */
    if(!data || data.date !== getDate()) {
        data = new ApocalypseData();
    } else {
        /* The method .display is not stored in data. */
        data = new ApocalypseData(data.votes, data.answers);
    }

    /* Set a GM menu button for viewing the data. */
    GM_registerMenuCommand("See your progress in the apocalypse", data.display);

    /* Place the vote listener on all the vote buttons on the page. */
    for(var i = 0; i < ups.length; i++) {
        ups[i].addEventListener("click", logVote);
        downs[i].addEventListener("click", logVote);
    }

    /* Place the answer listener on all answer buttons on the page. */
    submitAnswer.addEventListener("click", logAnswer);
}

/**
 * Records that the user has placed a vote and stores
 * it in an ApocalypseData.
 *
 * @param {Event} e - The event information.
 */
function logVote(e) {
    /* If the user is retracting a vote. */
    data.votes += e.srcElement.className.split(" ").length === 2 ? 1 : -1;

    GM_setValue(STORAGE_NAME, data);
    data.display("Ammo spent!");
}

/**
 * Records that the user has answerd and stores it in an
 * ApocalypseData.
 */
function logAnswer() {
    data.answers++;

    GM_setValue(STORAGE_NAME, data);
    data.display("Nice shot!");
}

/**
 * Data that is to be stored via GM_setValue

 * Contains information about the total amount of votes
 * and the current date.
 *
 * @param {number} votes - The amount of votes left.
 *                          This is 40 by default.
 */
function ApocalypseData(votes, answers) {
    this.votes = votes || VOTES_PER_DAY;
    this.answers = answers || 0;
    this.date = getDate();

    /**
     * Displays this data in a notification.
     *
     * @param {string} title - The title of the notification.
     *                          This is "Progress report:" by
     *                          default.
     */
    this.display = function(title) {
        GM_notification({
            title: title || "Progress report:",
            text: "So far, you've shot " + (data.answers) + " (incoming) zombies today!\n" +
                  "You have " + (data.votes) + " ammos left!"
        });
    };
}

/**
 * Get the date in UTC.
 *
 * @return {string} - The UTC date. "DD-MM-YYYY".
 */
function getDate() {
    var d = new Date();
    return d.getUTCDate() + "-" + d.getUTCMonth() + "-" + d.getUTCFullYear();
}

For keeping track of votes, this code will collect all the up-vote and down-vote buttons on the page and attach a listener to them that is run whenever they are clicked. When they are clicked, amount of votes left known is lowered. However, if the button has already been pressed (the user already cast a vote), then the known vote count is raised.

For keeping track of answers, whenever the "Submit Answer", your answer count is increased.

Each day, the vote count is reset back to 40 and the answer count is reset back to 0.


Using

Every time you cast a vote, you will get a notification that looks like this:

Ammo spent!

You have __ ammos left!

Every time you answer a question, you will get a notification that looks like this:

Nice shot!

You have shot __ (incoming) zombies today!

(You may say that just putting "(incoming)" in there is a cheap excuse to not check if a post is actually a zombie. Well, every post is a potential zombie even if it already has answers because the post may need more answers, and every unanswered post has the potential to be a zombie. Basically, every answer is contributing to The Mission).

Then, there's more: when you load this UserScript using GreaseMonkey or TamperMonkey and you open up the icon in your browser when the script is activated, you will see a little button that looks like this:

See your progress in the apocalypse

Upon clicking this, a notification will pop up and show you your stats for the day, just like the notifications that show when you either vote or answer.


Questions

  1. Is my code understandable? I tried to include as much information via comments and JSDoc as I could without cluttering the code too much.

  2. Does my design make sense? Basically, are my functions properly grouped? Or, should I follow a different paradigm, like an OOP one?

  3. Am I following good UserScript practices? I tested this on Chrome with TamperMonkey and everything worked fine for me.

  4. Am I properly using the GreaseMonkey API? Am I misusing any functions, not using any functions, or not using a function to its full potential?

Anything other things that you may notice are also welcome.

\$\endgroup\$
7
\$\begingroup\$

There's a few things you can improve.


UserScripts should follow the *.user.js format, so that TamperMonkey, GreaseMonkey, GoogleChrome and the other installers can pick up on the fact that it's a UserScript, the GitHub file does not.


    GM_notification({
        title: title || "Progress report:",
        text: "So far, you've shot " + (data.answers) + " (incoming) zombies today!\n" +
              "You have " + (data.votes) + " ammos left!"
    });

The grammar in the sentence 'You have x ammos left!' is incorrect. Consider 'You have x rounds/bullets left' instead.

On a related note, after using this script for a little bit, I thought it may be less 'annoying' if it reminded you every 10 shots about your ammo, and then finally at 5. Just a thought.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.