AutoJoin IndieGala Giveaways (improved)

AutoJoin for IndieGala Giveaways!

// ==UserScript==
// @name         AutoJoin IndieGala Giveaways (improved)
// @namespace    http://gdorn.circuitlocution.com
// @version      0.2
// @description  AutoJoin for IndieGala Giveaways!
// @author       George Dorn (@GDorn) and Sergio Susa (http://sergiosusa.com)
// @match        https://www.indiegala.com/giveaways*
// @grant        none
// ==/UserScript==

var reloading = 3 * 60 * 1000; // 3 minutes; delay encountered when out of points
var trying = 10 * 1000; // 10 seconds; delay before loading next page
var min_coins = 25; // number of points to save
var coins_per_page = 10; // number of extra points to save per page, to avoid wasting points on contests hours in the future; cycles to page one after exceeding
var timeBetweenClicks = 2 * 1000; // 2 seconds; delay between clicking on ticket stubs
var max_page = 10; // maximum page number; cycles to page 1 after this
var start_delay = 5 * 1000; // wait this long on page load, for 'match_games_in_steam_library' to finish.
var max_level = 0; // maximum contest level to try to enter; set to higher if you are a higher level.
var max_participants = 200; // skip contests with more participants than this
var skip_already_owned = true; // skip contests for games you already own (according to indiegala)

if (!skip_already_owned){
    start_delay = 50; //if we don't care about winning games we own, we can run a little faster
}

var autoEnter = function() {
    'use strict';

    console.log("Starting auto-entry.");

    if(skip_already_owned){
        removeAlreadyHave();
    }

    var coins = parseInt($(".coins-amount strong").html());
    console.log("Have this many coins: ", coins);    
    if ( coins < min_coins ) {
        console.log("Not enough coins, reloading...")
        reloadPage(randomize(reloading));
        return;
    }

    // add more delay for every stub we need to wait to click on
    var next_page_delay = trying;

    var contests = $('div.tickets-col');

    console.log("Got ", contests.length, " contests: ", contests);

    // iterate through contests on the page, scheduling clicks on stubs if they meet our criteria
    for (var i = 0; i < contests.length; i++){
        // if we spent all our coins, bail
        if (coins < min_coins){
            console.log("Not enough coins to continue");
            break;
        }

        var contest = contests[i];
        console.log("Considering contest:", contest);
        
        var name = $(contest).find('div.ticket-right').find('h2').text().trim();
        var price = parseInt($(contest).find('div.ticket-price')[0].textContent);
        var level = get_level(contest);
        var participants = get_participants(contest);
        console.log("Name: ", name, " Price: ", price, " Level: ", level, " Participants: ", participants);
        
        var stub = contest.getElementsByTagName("aside")[0];
        if (!stub){
            console.log("Skipping ", name, " because can't find stub?  Maybe already entered?");
            continue;
        }
        if (price > coins){
            console.log("Skipping ", name, " because price (", price, ") exceeds coins (", coins, ")");
            continue;
        }

        if (level > max_level){
            console.log("Skipping ", name, " because required level (", level, ") is greater than max_level,", max_level);
            continue;
        }
        
        if (participants > max_participants){
            console.log("Skipping ", name, " because ", participants, " users participating, greater than the allowed ", max_participants);
            continue;
        }

        // if we're here, all checks passed, go ahead and click on it.
        var delay = randomize(timeBetweenClicks);
        setTimeout(function(){ stub.click(); }, delay);
        console.log("Clicking on: ", name, stub);
        next_page_delay += delay; // add the delay to the next page delay so we don't load next page before the click happens
        coins -= price; // deduct cost of contest
    }    
    
    console.log("###### Done with this page, moving on to next. ######");
    
    var next_page = calculateNextPage();

    // get updated coins now in case our coin tracking broke
    coins = parseInt($(".coins-amount strong").html());

    // avoid spending too many coins on later pages, save them for short-running giveaways with fewer entries
    var needed_coins = min_coins + coins_per_page * (next_page -1);
    if ( coins < needed_coins){
        console.log("Going back to page 1; we have ", coins , " but want at least ", needed_coins, " to continue.");
        next_page = 1;
        next_page_delay += trying; //some extra delay before loading first page again
    }

    if (next_page > max_page) {
        next_page = 1;
    }
    
    next_page_delay = randomize(next_page_delay);
    
    console.log("Going to load page ", next_page, " in about ", next_page_delay, 'ms');
    
    setTimeout(function(){

        window.location="https://www.indiegala.com/giveaways/[NUM_PAGE]/expiry/asc".replace("[NUM_PAGE]", next_page);

    }, next_page_delay);

};

$(document).ready(function(){setTimeout(autoEnter, start_delay)});

/***********************************************************
 *  Utility Functions
 **********************************************************/

function randomize(number) {
    // returns a random number somewhere between 3/4 and 5/4 of the given value.
    // maybe helps bust bot detection.
    var base = Math.floor(number * 0.75);
    var max_add = Math.floor(number * 0.5);
    var result = base + Math.floor(Math.random() * max_add);
    return result;
}
    
function removeAlreadyHave() {
    var count = 0;
    var nodes = $('.on-steam-library-corner');
    for (var i = 0; i < nodes.length; i++){
        var node = nodes[i];
        if($(node).is(':visible')){
            $(node).parents('.tickets-col').remove();
            count += 1;
        }
    }
    console.log("Removed ", count, " offers already in steam library.")
}

function get_level(contest){
    // returns the numeric level requirement for a chunk of text extracted from a ticket
    var text = contest.textContent.trim();
    var position = text.indexOf('LEVEL ') + 6;
    return parseInt(text.slice(position));
}

function get_participants(contest){
    var text = $(contest).find('.info-row').text();
    var position = text.indexOf('minutes left') + 12;
    return parseInt(text.slice(position));   
}

function calculateNextPage()
{
    var page = window.location.href.replace("https://www.indiegala.com/giveaways/","").replace("/expiry/asc","");
    return parseInt(page)+1;
}

function reloadPage(miliseconds) {
    console.log("Reloading page in ", miliseconds, " ms");
    setTimeout(function(){
        window.location.reload();
    }, miliseconds);
}

/***********************************************************
 *  Override Functions
 **********************************************************/
window.confirm = function (message, callback, caption) {
    return true;
};