Advertisement
Guest User

Untitled

a guest
Nov 19th, 2017
1,066
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name        SteamGifts
  3. // @namespace   SteamGifts
  4. // @description Custom Functions
  5. // @include     *.steamgifts.com/
  6. // @include     *.steamgifts.com/giveaway/*
  7. // @include     *.steamgifts.com/giveaways/search?*
  8. // @include     *.steamgifts.com/open
  9. // @include     *.steamgifts.com/new*
  10. // @include     *.steamgifts.com/offline*
  11. // @include     *.steamgifts.com/suspensions
  12. // @include     *store.steampowered.com/agecheck/*
  13. // @include     *store.steampowered.com/*/agecheck
  14. // @version     5.0
  15. // @modified    10/07/2016
  16. // @grant       GM_xmlhttpRequest
  17. // ==/UserScript==
  18.  
  19. //Mini script for doing steam age checks automatically (used when scraping steam sites)
  20. if (/agecheck/i.test (location.pathname) ) {
  21.     console.log('Age Check Required.');
  22.     var ageForm = document.querySelector ("#agegate_box form");
  23.     if (ageForm != null)
  24.     {
  25.         ageForm.querySelector ("[name='ageDay']").value     = 18;
  26.         ageForm.querySelector ("[name='ageMonth']").value   = 'August';
  27.         ageForm.querySelector ("[name='ageYear']").value    = 1987;
  28.         var newFrame = document.createElement('frame');
  29.         newFrame.name = "PostResult"
  30.         document.body.appendChild(newFrame);
  31.         ageForm.target = newFrame.name;
  32.         ageForm.submit();
  33.     }
  34.     else
  35.     {
  36.         var btnContinue = document.querySelector ("#app_agegate .btn_medium:nth-of-type(1)");
  37.         btnContinue.innerHTML = "<span>Continuing...</span>";
  38.         btnContinue.click();
  39.     }
  40.     setTimeout( function(){ window.close(); }, 30000);
  41.     console.log('Performed Age Check.');
  42. }
  43. // If loading a giveaway page, add a listener for messages from our auto-register script.
  44. else if(/\/giveaway\//i.test (location.pathname)) {
  45.     window.addEventListener("message", receiveMessage, false);
  46.     console.log('Waiting for any giveaway entry messages...');
  47.     function receiveMessage(event)
  48.     {
  49.         console.log('Giveaway Message Recieved...');
  50.         if(event.data == "EnterGiveaway"){            
  51.             console.log('Revieved EnterGiveaway Message!');
  52.             if( !document.getElementsByClassName("sidebar__entry-insert")[0].classList.contains('is-hidden'))
  53.             {
  54.                 console.log('Entered giveaway at ' + location.pathname);                
  55.                 registerGiveawayPage(window.document);
  56.                 setTimeout(function () {
  57.                     if(document.getElementsByClassName("sidebar__entry-insert")[0].classList.contains('is-hidden'))
  58.                         window.close();
  59.                 }, 100);
  60.             }
  61.         }
  62.     }
  63. }
  64. else //Otherwise, we're on SteamGifts!
  65. {
  66.     //////////////////////////////////// CONFIGURATION ////////////////////////////////
  67.     var AUTOREGISTER = true;
  68.     var LEAVEOPEN = false;
  69.     var TimeOut = 60000; //1 Minute
  70.     var RefreshTime = 60000; //1 Minute
  71.     var RegistrationTime = 30000; //30 secs
  72.     var pollingInterval = 1000; // 1 second update speet
  73.     var pollingAttempts = 10;
  74.  
  75.     //Decision Making Constants for modified probability tolerance before auto-registering based on current points.
  76.     var BaseThreshold = 4.64876241265283 / 100.0;
  77.     var MinThreshold = 0.00544389516791109 / 100.0;
  78.     var LogFactor = 4.10546665422788;
  79.     var MaxP = 300;
  80.     var MaxPenalty = 1E5; //Max probability penalty applied (anything with the max penalty should not be registered)
  81.  
  82.     // Game 'Quality' Modifiers (Used for favouring "Good" games and penalizing "Bad" games.)
  83.     // These are generally additive with each other.
  84.    
  85.     // Overall Quality Modifier Power - how much these other modifiers matter
  86.     // Set to zero to only enter games based on win probability. This will maximize games won.
  87.     // Increase the value to increase the relative weight given to entering good games.
  88.     var overallQualityModifierPower = 1.0;
  89.    
  90.     var onWishListModifier = 50.0; // Trumps any other 'quality' modifiers (e.g. not additive)
  91.    
  92.     //// Achievements (Bonus differs depending on number of achievements.)
  93.     //// More achievements often indicates a more thorough game, but too many reeks of spam.
  94.     var achieveTiers = [1,10,30,60,120]
  95.     var achieveBonus = [1,2,3,2,1]
  96.    
  97.     //// Tags
  98.     var unwantedTags = ["Match 3","Dating Sim","RPGMaker","Visual Novel","Sexual Content","Romance","Nudity"];
  99.     var unwantedTagPenalty = -2; // Fitness multiplied by this value divided by number of unwanted tags
  100.     var wantedTags = ["Co-op","Open World","Rhythm","Clicker","First-Person","FPS"];
  101.     var wantedTagBonus = 2; // Fitness is multiplied by this value times the number of wanted tags
  102.     //// Other
  103.     var hasCardsBonus = 2; // If the game has steam trading cards
  104.     var reviewScale = [15,10,5,1,0,-1,-2,-5,-10]; // For the 9 review categories from most positive to most negative.
  105.     var bundleBonus = 2; // If there appears to be multiple games in the giveaway (but we don't know how many)
  106.     var packGameValue = 1; // Bonus for each additional game in a steam bundle.
  107.     var dlcPenalty = -3; // If you wanted the DLC, it would be on your wishlist right?
  108.     var soundtrackPenalty = -10; // If the giveaway is being detected as just a soundtrack dlc
  109.        
  110.     // Giveaway 'Quality' Modifiers (used to favour giveaways with an above-average opportunity to win - which is already inherent in its number of entries, but this scales it further.)
  111.     var shortGiveawayModifier = 0.5
  112.     var shortGiveawayTime = (1.5*60*60) // 1.5 hours in seconds
  113.     var groupGiveawayModifier = 1
  114.     var maxContributorModifier = 2;
  115.     var contributorLevelCap = 10;
  116.    
  117.  
  118.     //////////////////////////////////// END CONFIGURATION ////////////////////////////////
  119.    
  120.     var polling;
  121.    
  122.     var arr_Giveaways = [];
  123.     var arr_CantEnter = [];
  124.     var asyncRequests = 0
  125.     var asyncRequestsCompleted = 0;
  126.     var asyncRequestsCancelled = 0;
  127.     var steamRequestsCompleted = 0;
  128.     var ageChecked = 0;
  129.     var registrations = 0;
  130.    
  131.     try
  132.     {
  133.         console.log('Steamgits script loaded for ' + window.location);
  134.         appendCSStoHead();
  135.         var headerInsertionPoint = document.createElement('div');
  136.         headerInsertionPoint.style.cssText =
  137.             'text-align: right; padding: 1em; position:fixed; z-index: 60; right:0; top:30px; max-height:500px;' +
  138.             'margin:1em; background: none repeat scroll 0 0 rgba(132, 193, 10, 0.8); ' +
  139.             'border-color: rgba(132, 193, 10, 0.5); border-radius: 3em 0.5em 0.5em 3em; ' +
  140.             'border-style: solid none solid solid; border-width: medium; box-shadow: 0 1px 3px black; color: white; ';
  141.         document.body.appendChild(headerInsertionPoint);
  142.        
  143.         // Play some sound to prevent firefox from unloading scripts if while this tab is inactive.
  144.         window.AudioContext = window.AudioContext || window.webkitAudioContext;
  145.         context = new AudioContext();
  146.         var o = context.createOscillator();
  147.         var volume = context.createGain();
  148.         o.connect(volume);
  149.         volume.connect(context.destination);
  150.         o.type = 'sine';
  151.         o.frequency.value = 100;
  152.         volume.gain.value = 0.0001;
  153.         o.start(0);
  154.                
  155.         //Since many of the same games appear in giveaways, we can cache the steam info for that which has already been requested.
  156.         var SteamRequestCache = {};
  157.         // Restore session-persisted cache
  158.         if (sessionStorage.getItem("steam_cache")) {
  159.           //console.log(sessionStorage.getItem("steam_cache"));
  160.           SteamRequestCache = JSON.parse(sessionStorage.getItem("steam_cache"));
  161.           //console.log(SteamRequestCache);
  162.           console.log('Restored cache of steam information (' + Object.keys(SteamRequestCache).length + ' games)');
  163.         }
  164.        
  165.         if(window.self !== window.top)
  166.             console.log('In a frame. Skipping script.');
  167.         else if (/offline/i.test (location.pathname) ||
  168.             /Database connection failed/.test(document.body.innerHTML) ||
  169.             document.getElementsByClassName('nav__points').length == 0 )
  170.         {
  171.             console.log('Site doesn\'t look right. Scheduling a refresh.');
  172.             polling = setInterval(refreshWhenReady, pollingInterval);
  173.         }
  174.         else
  175.         {
  176.             /****** Begin Magic. *****/
  177.             var pointsElem = document.getElementsByClassName('nav__points')[0];
  178.             var myP = parseInt(pointsElem.innerHTML);
  179.             var thresholdForP = FitnessThreshold(myP);
  180.            
  181.             // Set the Async task to pull giveaway info
  182.             polling = setInterval(scrapeGiveaways, pollingInterval);
  183.  
  184.             headerInsertionPoint.appendChild(document.createTextNode(
  185.                 'Threshold for ' + myP + ' P = ' + (thresholdForP*100).toFixed(2) + '%'));
  186.             headerInsertionPoint.appendChild(document.createElement('br'));
  187.             /****** This is the end of the initial code execution. The rest will happen in 'setInterval' callbacks. *****/
  188.         }
  189.     }
  190.     catch(err) //Unforseen things keep happening so...
  191.     {
  192.         console.log("Couldn't setup steamgifts script: " + err.message);
  193.         reset();
  194.     }
  195. }
  196.  
  197. /////////////////////////////// FUNCTIONS /////////////////////////////
  198.  
  199. function appendCSStoHead() {
  200.     var css = '.giveaway__columns * { white-space: nowrap; line-height: 20px; } ' +
  201.               '.giveaway__columns > * { padding: 0 5px; margin-right: 2px !important; } ' +
  202.               '.giveaway__row-outer-wrap { padding: 0; } ' +
  203.               '.giveaway__summary { padding: 0 8px; } ' +
  204.               '.sidebar__mpu { display: none !important; } ' +
  205.               '.sidebar__search-input { min-width: 0px; } ' +
  206.               '.widget-container > div:not(:first-child) { padding-left: 10px; margin-left:10px; overflow: hidden; } ' +
  207.               '.page__outer-wrap { padding: 10px } ' +
  208.               '.pinned-giveaways__inner-wrap { padding: 0px 5px } ' +
  209.               '.pinned-giveaways__inner-wrap--minimized { padding-bottom: 0px } ' +
  210.               '.featured__outer-wrap { padding: 5px 0 } ' +
  211.               '.featured__inner-wrap { padding: 0 220px 0 130px } ' +
  212.               '.global__image-outer-wrap img { max-height: 100px; } ' +
  213.               'small { font-size: 0.8em; } ' +
  214.               'span, a { white-space: nowrap; } ' +
  215.               'span, a { font-size: 10px; } ' +
  216.               '.sidebar { min-width: 115px; max-width: 120px; } ' +
  217.               '.pagination + div, .pagination + div *, .pagination + div + div { height: 0px !important; padding: 0px !important; } ' +
  218.               '.pagination + div + div + div, .pagination + div + div + div * { margin-top: 1px !important; padding-top: 0px !important; margin-bottom: 0px !important; padding-bottom: 0px !important; }' +
  219.               '.inlinedEntry { height: 240px; overflow:scroll; margin-bottom: 4px; }' +
  220.               '.inlinedEntry * { overflow: hidden; white-space: nowrap; width: auto; }';
  221.         head = document.head || document.getElementsByTagName('head')[0],
  222.         style = document.createElement('style');
  223.  
  224.     style.type = 'text/css';
  225.     if (style.styleSheet){
  226.       style.styleSheet.cssText = css;
  227.     } else {
  228.       style.appendChild(document.createTextNode(css));
  229.     }
  230.  
  231.     head.appendChild(style);
  232. }
  233.  
  234. function FitnessThreshold(p) {
  235.     return (BaseThreshold-MinThreshold) * Math.pow(((MaxP - p) / MaxP), LogFactor) + MinThreshold;
  236. }
  237.  
  238. function scrapeGiveaways()
  239. {
  240.     var minimumMet = false;
  241.     var giveaways = document.querySelectorAll(".giveaway__summary");
  242.     console.log("Found " + giveaways.length + " giveaways.");
  243.    
  244.     try
  245.     {
  246.         var last_time_remaining = giveaways[giveaways.length-1].getElementsByClassName('fa-clock-o')[0]
  247.                                     .parentNode.getElementsByTagName('span')[0].innerHTML;
  248.         //We're going to wait at least for as long as 'TimeOut' to get all the gifts that meet these conditions: (less than 6 hours remaining)
  249.         if( last_time_remaining.indexOf('day') > -1 || last_time_remaining.indexOf('week') > -1 ||
  250.             (last_time_remaining.indexOf('hours') > -1 && parseInt(last_time_remaining.match(/(\d+) hours/)[1]) > 6) )
  251.             minimumMet = true;
  252.     }
  253.     catch(err)
  254.     {    //Something's buggered up in getting the time of the last loaded giveaway, this gives us a chance to move on.
  255.         console.log("Error scraping giveaways. Proceeding as success. " + err.message);
  256.         minimumMet = true;
  257.     }
  258.    
  259.     try
  260.     {
  261.         var scraperProgress = document.getElementById('gafLoading');
  262.         //If we've loaded every giveaway (or the giveaway add-on isn't installed), move on to the next phase.
  263.         if( scraperProgress != null )
  264.         {
  265.             //Show how long we've been waiting
  266.             getOrCreateCustomElement('pollingRequests', scraperProgress).innerHTML =
  267.                 'Scraping: ' + pollingAttempts * (pollingInterval / 1000) + ' of ' +
  268.                 (TimeOut / 1000) + ' Seconds Waited';
  269.                
  270.             if( scraperProgress.innerHTML.indexOf('the end') > -1 )
  271.                 doneScraping();  
  272.             else
  273.             {
  274.                 scraperProgress.scrollIntoView();    
  275.                 window.scrollBy(0, -100);
  276.             }
  277.         }    
  278.        
  279.         //We've waited long enough, maybe the server is responding slowly? Anyways, move on to the loaded routine.
  280.         /*
  281.         if( (pollingAttempts >= TimeOut / pollingInterval) ||
  282.             (pollingAttempts > 5 && minimumMet) )
  283.             doneScraping();
  284.         else
  285.             pollingAttempts += 1;  //Disabled scraping multiple pages to cut back on requests.
  286.         */
  287.         doneScraping();
  288.        
  289.         updateAsyncRequestsStatus();
  290.         if( scraperProgress != null )
  291.             setTimeout(AddNewGiveaways, pollingInterval/2);    
  292.     }
  293.     catch(err) //Unforseen things keep happening so...
  294.     {
  295.         console.log("Error scraping giveaways. Resetting the page. " + err.message);
  296.         reset();
  297.     }
  298. }
  299.  
  300. function doneScraping()
  301. {
  302.     window.clearInterval(polling);
  303.     window.scrollTo(0, 0);
  304.    
  305.     AddNewGiveaways();
  306.    
  307.     pollingAttempts = 0;
  308.     polling = setInterval(waitOnAsyncRequests, pollingInterval );
  309.     console.log("Done Scraping");
  310. }
  311.  
  312. function reset()
  313. {    
  314.     window.clearInterval(polling);
  315.     polling = setInterval(refreshWhenReady, pollingInterval);
  316. }
  317.  
  318. function AddNewGiveaways()
  319. {  
  320.     try
  321.     {
  322.         var giveaways = collectionToArray(document.querySelectorAll(".giveaway__summary"));
  323.        
  324.         //If there is a block of developer giveaways at the top, we ignore them because they appear as duplicates in the normal list
  325.         if( /(Developer\:|Featured\:)/.test(giveaways[0].innerHTML) )
  326.             giveaways.splice(0,1);
  327.             //giveaways = collectionToArray(giveaways.splice(0,1)[0].querySelectorAll(".post>div")).concat(giveaways);
  328.            
  329.         for( var I = 0; I < giveaways.length; I++ )
  330.         {
  331.             var elem = giveaways[I];
  332.             var timeRemainingElem = elem.getElementsByClassName('fa-clock-o')[0]
  333.                 .parentNode.getElementsByTagName('span')[0].innerHTML;
  334.             var entriesElem = elem.getElementsByClassName('giveaway__links')[0]
  335.                 .getElementsByTagName('span')[0].innerHTML;
  336.        
  337.             if( elem.clientWidth > 0 &&
  338.                 !/hackeried/.test(elem.className) &&
  339.                 !/\(Ended /.test(timeRemainingElem) &&
  340.                 !/Coming Soon/.test(entriesElem) )
  341.             {
  342.                 elem.className += ' hackeried';
  343.                 var title = elem.getElementsByClassName('giveaway__heading__name')[0].innerHTML;
  344.                
  345.                 var costElemIndex = 0;
  346.                 var copies = 1;
  347.                 var titleElements = elem.getElementsByClassName('giveaway__heading__thin');
  348.                 if(titleElements.length > 1) {
  349.                     costElemIndex = 1;
  350.                     var copies =  parseInt(titleElements[0].innerHTML
  351.                                            .match(/([\d,]+) Copies/)[1].replace(/\,/g,''));
  352.                 }
  353.                 var cost = parseInt(titleElements[costElemIndex].innerHTML.match(/\((\d+)P\)/)[1]);
  354.                
  355.                 var time_remaining = parseTimeString(timeRemainingElem);
  356.                 var created = parseTimeString(elem.getElementsByClassName('giveaway__column--width-fill')[0]
  357.                                               .childNodes[0].innerHTML);
  358.                 var entries = parseInt(entriesElem.match(/([\d,]+) entr(ies|y)/)[1].replace(/\,/g,''));
  359.                 var giveawayURL = elem.getElementsByClassName('giveaway__heading__name')[0].href;
  360.                 var isGroup = false; //elem.getElementsByClassName('group_only').length > 0;
  361.                
  362.                 var steamURL = elem.getElementsByClassName('giveaway__icon');
  363.                 if(steamURL != null) steamURL = steamURL[0].href;
  364.                
  365.                 var contribAmount = elem.getElementsByClassName('giveaway__column--contributor-level');
  366.                 contribAmount = contribAmount.length == 0 ? null : contribAmount[0].innerHTML.match(/Level (\d+)\+/);
  367.                 if( contribAmount == null )
  368.                     contribAmount = 0;
  369.                 else
  370.                     contribAmount = parseInt(contribAmount[1]);
  371.                
  372.                 var giveaway = {
  373.                     elem: elem,
  374.                     title: title,
  375.                     cost: cost,
  376.                     copies: copies,
  377.                     remaining: time_remaining,
  378.                     created: created,
  379.                     entries: entries,
  380.                     isGroup: isGroup,
  381.                     contribAmount: contribAmount,
  382.                     url: giveawayURL,
  383.                     canEnter: true,
  384.                     SteamURL: steamURL
  385.                 }
  386.                 giveaway.prob = Math.min(1, 1/entries * copies);
  387.                 giveaway.prob_per_P = giveaway.prob/cost;
  388.                 giveaway.timePenalty = calcTimePenalty(giveaway);
  389.                
  390.                 var requestDetails = {
  391.                     method: 'GET',
  392.                     url: giveawayURL,
  393.                     timeout: TimeOut,
  394.                     ontimeout: SGRequestFailure(giveaway),
  395.                     onerror: SGRequestFailure(giveaway)
  396.                 }
  397.                 requestDetails.onload =
  398.                     (function(giveaway) {
  399.                         return function(req) {
  400.                             try
  401.                             {
  402.                                 giveaway.canEnter = /sidebar__entry-insert"/i.test (req.responseText);
  403.                                 if(giveaway.canEnter)
  404.                                 {
  405.                                     arr_Giveaways.push(giveaway);
  406.                                    
  407.                                     //Default values in case we can't connect to Steam
  408.                                     giveaway.achievements = null;
  409.                                     giveaway.hasCards = null;
  410.                                     giveaway.isDLC = null;
  411.                                     giveaway.isWishlist = null
  412.                                     giveaway.metascore = null;
  413.                                     giveaway.review = null;
  414.                                     giveaway.tags = [];
  415.                                     calcModifierAndFitness(giveaway);
  416.                                    
  417.                                     if(giveaway.SteamURL != null)
  418.                                         requestSteamInformation(giveaway);
  419.                                     else
  420.                                         appendSteamInfoToGiveaway(giveaway);
  421.                                 }
  422.                                 else
  423.                                 {
  424.                                     cannotEnterGiveaway(giveaway);
  425.                                 }
  426.                                 asyncRequests --;
  427.                                 asyncRequestsCompleted ++;
  428.                             }
  429.                             catch(err)
  430.                             {
  431.                                 console.log("Error retrieving request details." + err.message + "\nLine: " + err.lineNumber);
  432.                                 throw err;
  433.                             }
  434.                         }
  435.                     }(giveaway));
  436.                 asyncRequests ++;
  437.                 GM_xmlhttpRequest(requestDetails);
  438.                
  439.                 appendProbabilityInfoToGiveaway(giveaway);
  440.             }
  441.         }
  442.     }
  443.     catch(err)
  444.     {
  445.         console.log("Error in AddNewGiveaways. " + err.message + "\nLine: " + err.lineNumber);
  446.         reset();
  447.     }
  448. }
  449.  
  450. function SGRequestFailure(giveaway)
  451. {
  452.     return  (function(giveaway) {
  453.                 return function(req) {
  454.                     console.log("SGRequestFailure. " + giveaway.title);
  455.                     asyncRequests--;
  456.                     asyncRequestsCancelled++;
  457.                     arr_Giveaways.push(giveaway);
  458.                    
  459.                     //Default steam values
  460.                     giveaway.achievements = null;
  461.                     giveaway.hasCards = null;
  462.                     giveaway.isDLC = null;
  463.                     giveaway.isWishlist = null;
  464.                     giveaway.review = null;
  465.                     giveaway.metascore = null;
  466.                     calcModifierAndFitness(giveaway);
  467.                     appendSteamInfoToGiveaway(giveaway);
  468.                 }
  469.             }(giveaway));
  470. }
  471.  
  472. function cannotEnterGiveaway(giveaway)
  473. {
  474.     console.log("CannotEnterGiveaway. " + giveaway.title + ". AlreadyOwned=" + giveaway.alreadyOwned);
  475.     arr_CantEnter.push(giveaway);
  476.     //Strike-out things that can't be entered
  477.     giveaway.elem.getElementsByClassName('giveaway__heading__name')[0].style.textDecoration = 'line-through';
  478.     // Uncomment to hide from view games that are already owned (rather than just striking them out.)
  479.     //if(!giveaway.alreadyOwned) {
  480.     //    giveaway.elem.className += ' fade';
  481.     //}
  482. }
  483. // If steam reports that a game is already owned, mark it as ignored, in case we go offline some time.
  484. // TODO: re-implement. This doesn't work.
  485. function markIgnored(giveaway)
  486. {
  487.     var children = giveaway.elem.getElementsByClassName('entries')[0].getElementsByTagName('span');
  488.     for(var i = 0; i < children.length; i++)
  489.     {
  490.         if(children[i].getElementsByTagName('a')[0])
  491.             if(children[i].getElementsByTagName('a')[0].name == 'gafbutton' )
  492.                 children[i].getElementsByTagName('a')[0].onclick();
  493.     }
  494. }
  495.          
  496. //Check the steam store page for more information  
  497. function requestSteamInformation(giveaway)
  498. {    
  499.     //Check the cache first
  500.     if( giveaway.SteamURL in SteamRequestCache )
  501.     {
  502.         if( SteamRequestCache[giveaway.SteamURL].ready )
  503.         {
  504.             // If the cached attempt failed, delete the cache entry.
  505.             var lastRequestSucceeded = SteamRequestCache[giveaway.SteamURL];
  506.             if(!lastRequestSucceeded)
  507.             {
  508.                 delete SteamRequestCache[giveaway.SteamURL];
  509.                 console.log("Deleting cache entry, steamRequestSucceeded not set to true. (" + giveaway.title + " - " + (lastRequestSucceeded || "unset") + ")");
  510.             }
  511.             else
  512.                 copySteamInfo(SteamRequestCache[giveaway.SteamURL].first, giveaway);            
  513.         }
  514.         else
  515.             SteamRequestCache[giveaway.SteamURL].waiting.push(giveaway);
  516.     }
  517.     if( !(giveaway.SteamURL in SteamRequestCache) )
  518.     {
  519.         //Create a new request cache entry
  520.         SteamRequestCache[giveaway.SteamURL] = {
  521.             ready: false,
  522.             first: giveaway,
  523.             waiting: []
  524.         }
  525.    
  526.         //Fire up a request to the steam website
  527.         var steamRequest = {
  528.             method: 'GET',
  529.             url: giveaway.RealSteamURL || giveaway.SteamURL,
  530.             timeout: TimeOut,
  531.             ontimeout: SteamRequestFailure(giveaway),
  532.             onerror: SteamRequestFailure(giveaway)
  533.         }
  534.         steamRequest.onload =
  535.             (function(giveaway) {
  536.                 return function(req) {
  537.                     var deferredRequest = false;
  538.                     try
  539.                     {                        
  540.                         giveaway.steamRequestSucceeded = false;            
  541.                         //If we're taken to an age check screen, we must pass through it
  542.                         if( req.finalUrl.indexOf('agecheck') > -1 && ageChecked == 0)
  543.                         {
  544.                             ageChecked = 1; //Prevent dozens of threads each doing the age check.
  545.                             //There'll be a form to submit, so we'll have to actually open this url in a new window. This same
  546.                             //greasemonkey script will submit the age check, and then subsequent requests from mature steam pages
  547.                             //be able to scrape info.
  548.                             var newWindow = window.open(req.finalUrl);
  549.                             // This attempt to load the game in the background has failed, don't cache the attempt.
  550.                             delete SteamRequestCache[giveaway.SteamURL];
  551.                             return;
  552.                         }
  553.                    
  554.                         if(req.finalUrl.indexOf((giveaway.RealSteamURL||giveaway.SteamURL)) == -1)
  555.                             throw ("Got Redirected to: " + req.finalUrl);
  556.                        
  557.                         // Detect if this giveaway is actually a package:
  558.                         if( req.finalUrl.indexOf('sub') > -1)
  559.                         {
  560.                             var steamURLsRegex = /href="(http:\/\/store.steampowered.com\/app\/[^\/]*\/[^\/]*\/)"/g;
  561.                             var urlMatches;
  562.                             giveaway.packGames = [];
  563.                             giveaway.RealSteamURL = null;
  564.                             var lowestGameId = null;
  565.                             while( (urlMatches = steamURLsRegex.exec(req.responseText)) != null) {
  566.                                 giveaway.packGames.push(urlMatches[1]);
  567.                                 var gameId = parseInt(urlMatches[1].match(/http:\/\/store.steampowered.com\/app\/([^\/]*)\/[^\/]*\//)[1]);
  568.                                 if(lowestGameId == null || lowestGameId > gameId)
  569.                                 {
  570.                                     lowestGameId = gameId;
  571.                                     giveaway.RealSteamURL = urlMatches[1];                                        
  572.                                 }
  573.                             }                            
  574.                             if(giveaway.RealSteamURL == null)
  575.                                 throw ("Game is a steam pack, but can't find the real game: " + req.finalUrl);
  576.                             console.log(giveaway.title + " is a pack. Earliest game in pack is: " + giveaway.RealSteamURL );
  577.                             delete SteamRequestCache[giveaway.SteamURL];
  578.                             requestSteamInformation(giveaway);
  579.                             deferredRequest = true;
  580.                             return;
  581.                         }
  582.                        
  583.                         var dlcSection = req.responseText.match(/game_area_dlc_bubble/);
  584.                         giveaway.isDLC = dlcSection == null ? false : true;
  585.                         var achievements = req.responseText.match(/Includes (\d+) Steam Achievements/);
  586.                         giveaway.achievements = achievements == null || giveaway.isDLC ? 0 : parseInt(achievements[1]);
  587.                         var metascore = req.responseText.match(/(\d+).*reviewRating/);
  588.                         giveaway.metascore = metascore == null ? null : parseInt(metascore[1]);
  589.                         var review = req.responseText.match(/game_review_summary[^<>]*>([^<>]*)<\/span>/);
  590.                         giveaway.review = review == null ? null : review[1];
  591.                         var cardsSection = req.responseText.match(/Steam Trading Cards/);
  592.                         giveaway.hasCards = cardsSection == null || giveaway.isDLC ? false : true;
  593.                         if( giveaway.isDLC )
  594.                         {   //We can collect information about the root game this DLC is for
  595.                             var dlcInfo = req.responseText.match(/requires the base game <a href="(.*)">(.*)<\/a> on Steam in order to play./);
  596.                             if( dlcInfo != null )
  597.                             {
  598.                                 giveaway.requiredURL = dlcInfo[1];
  599.                                 giveaway.requiredTitle = dlcInfo[2];
  600.                             }
  601.                         }
  602.                         var alreadyOwned = req.responseText.match(/game_area_already_owned/);
  603.                         // Only prevent entering the giveaway if we already own the game and this isn't just the main game in a bundle.
  604.                         // TODO: But still prevent entering if we own all the games in the bundle
  605.                         giveaway.alreadyOwned = alreadyOwned != null && (giveaway.RealSteamURL == null);
  606.                         //console.log(giveaway.title + " owned? " + givewaway.alreadyOwned)
  607.                         giveaway.canEnter = giveaway.alreadyOwned ? false : giveaway.canEnter;
  608.                        
  609.                         var wishListSection = req.responseText.match(/This product is already on your wishlist./);
  610.                         var wishListRedHerringSection = req.responseText.match(/add_to_wishlist_area_success/);
  611.                         giveaway.isWishlist = wishListSection != null && wishListRedHerringSection == null;
  612.                        
  613.                         var tagsRegex = /class="app_tag"[^<>]*>[\s]*([^<>\s]*)[\s]*<\/a>/g;
  614.                         var matches;
  615.                         while( (matches = tagsRegex.exec(req.responseText)) != null && giveaway.tags.length < 6) {
  616.                             giveaway.tags.push(matches[1]);                            
  617.                         }
  618.                         //console.log(giveaway.title + " done.")
  619.                         giveaway.steamRequestSucceeded = true;
  620.                     }
  621.                     catch(err)
  622.                     {
  623.                         //Something has gone wrong. Maybe this steam page no longer exists? Can't do much, but hack the dlc thing to note the error
  624.                         console.log("Error in giveaway: " + giveaway.title);
  625.                         giveaway.error = err;
  626.                         console.log(err);
  627.                         delete SteamRequestCache[giveaway.SteamURL];
  628.                     }
  629.                     finally {
  630.                         asyncRequests --;
  631.                         if(!deferredRequest) {
  632.                             updateGiveawayAndWaitingCache(giveaway);
  633.                             steamRequestsCompleted ++;
  634.                         }
  635.                     }
  636.                 }
  637.             }(giveaway));            
  638.         asyncRequests ++;
  639.         GM_xmlhttpRequest(steamRequest);
  640.     }
  641. }
  642.  
  643. function SteamRequestFailure(giveaway)
  644. {
  645.     return  (function(giveaway) {
  646.                 return function(req) {
  647.                     updateGiveawayAndWaitingCache(giveaway);
  648.                     asyncRequests--;
  649.                     asyncRequestsCancelled++;
  650.                 }
  651.             }(giveaway));
  652. }
  653.  
  654. function updateGiveawayAndWaitingCache(giveaway)
  655. {    
  656.     // Rarely, we own a game but steamgifts can't pick up on it.
  657.     // TODO: not working. Just keeps ignoring already ignored titles.
  658.     //if( giveaway.alreadyOwned && (' ' + giveaway.elem.className + ' ').indexOf(' fade ') == -1 )
  659.     //    markIgnored(giveaway); // Click the ignore button if this giveaway hasn't already been ignored.
  660.     if( !giveaway.canEnter )
  661.     {
  662.         removeA(arr_CantEnter, giveaway);
  663.         cannotEnterGiveaway(giveaway);
  664.     }        
  665.    
  666.     calcModifierAndFitness(giveaway);
  667.  
  668.     //Fill in every giveaway waiting for this request to complete
  669.     var cacheEntry = SteamRequestCache[giveaway.SteamURL];
  670.     if( cacheEntry != null )
  671.     {
  672.         cacheEntry.ready = true;
  673.         while(cacheEntry.waiting.length > 0)
  674.             copySteamInfo(giveaway, cacheEntry.waiting.pop());
  675.     }
  676.    
  677.     appendSteamInfoToGiveaway(giveaway);
  678. }
  679.  
  680. function copySteamInfo(src, dst)
  681. {
  682.     dst.steamRequestSucceeded = src.steamRequestSucceeded;
  683.     if(src.RealSteamURL != null)
  684.     {
  685.         dst.RealSteamURL = src.RealSteamURL;
  686.         dst.packGames = src.packGames;
  687.     }
  688.     dst.achievements = src.achievements;
  689.     dst.hasCards = src.hasCards;
  690.     dst.isDLC = src.isDLC;
  691.     if(dst.isDLC)
  692.     {
  693.         dst.requiredTitle = src.requiredTitle;
  694.         dst.requiredURL = src.requiredURL;
  695.     }
  696.     dst.isWishlist = src.isWishlist;
  697.     dst.metascore = src.metascore;
  698.     dst.review = src.review;
  699.     dst.tags = src.tags;
  700.     dst.canEnter = src.canEnter
  701.     if( !dst.canEnter )
  702.     {
  703.         removeA(arr_CantEnter, dst);
  704.         cannotEnterGiveaway(dst);
  705.     }  
  706.     dst.error = src.error;
  707.    
  708.     calcModifierAndFitness(dst);
  709.     appendSteamInfoToGiveaway(dst);
  710. }
  711.  
  712. function waitOnAsyncRequests()
  713. {
  714.     updateAsyncRequestsStatus();
  715.     if(asyncRequests == 0 && asyncRequestsCompleted > 0)
  716.     {
  717.         window.clearInterval(polling);
  718.         sortAndDisplay();
  719.     }
  720.     else
  721.     {    
  722.         //Show how long we've been waiting
  723.         getOrCreateCustomElement('SteamPollingTime', headerInsertionPoint).innerHTML =
  724.             pollingAttempts * (pollingInterval / 1000) + ' of ' + (TimeOut / 1000) + ' Seconds Waited';
  725.        
  726.         //Been trying for too long, maybe something is stuck? Just continue with what information we have
  727.         if( pollingAttempts >= TimeOut / pollingInterval )    
  728.         {
  729.             window.clearInterval(polling);
  730.             sortAndDisplay();
  731.         }
  732.         else
  733.             pollingAttempts += 1;
  734.     }
  735. }
  736.  
  737. function updateAsyncRequestsStatus()
  738. {
  739.     //Async Requests Pending
  740.     if(asyncRequests + asyncRequestsCompleted + asyncRequestsCancelled > 0)
  741.         getOrCreateCustomElement('asyncRequests', headerInsertionPoint).innerHTML =
  742.             asyncRequests + ' Requests Remaining';
  743.    
  744.     //Async Requests Completed
  745.     if(asyncRequestsCompleted > 0)
  746.         getOrCreateCustomElement('requestsCompleted', headerInsertionPoint).innerHTML =
  747.             asyncRequestsCompleted + ' Requests Complete (+' + steamRequestsCompleted + ' Steam)';    
  748.        
  749.     //Timed Out Requests
  750.     if(asyncRequestsCancelled > 0)
  751.         getOrCreateCustomElement('requestsCancelled', headerInsertionPoint).innerHTML =
  752.             asyncRequestsCancelled + ' Requests Timed Out';    
  753. }
  754.  
  755. function sortAndDisplay()
  756. {    
  757.     try
  758.     {
  759.         //Order elements by time remaining, then modified probability (for data dump).
  760.         arr_Giveaways.sort(function(a,b){
  761.             if(a.timePenalty < b.timePenalty) return -1;
  762.             if(a.timePenalty > b.timePenalty) return 1;
  763.             if(a.fitness < b.fitness) return 1;
  764.             if(a.fitness > b.fitness) return -1;
  765.             if(a.remaining < b.remaining) return -1;
  766.             if(a.remaining > b.remaining) return 1;
  767.             return 0;
  768.         });
  769.  
  770.         var result = 'Title\tCopies\tFitness\tModified Prob\tModifier\tProb per P\tProb\tCost ('
  771.                    + myP + 'P)\tEntries\tTime Left\tCreated\tTimePenalty\tAchievements\tMetaScore'
  772.                    + '\tReview\tGroup?\tContribAmt\tCan Enter'
  773.                    + '\tisDLC\tBase Game\tSteam URL\tBase Game URL\tGiveaway URL';
  774.         for( var I = 0; I < arr_Giveaways.length; I++ )
  775.         {
  776.             result = result + '\n' + giveawayToString(arr_Giveaways[I])
  777.         }
  778.  
  779.         var textarea = document.createElement('textarea');
  780.         textarea.value = result;
  781.         textarea.style = 'width: 100%; height: 100px; max-height: 100px';
  782.         headerInsertionPoint.appendChild(textarea);
  783.         textarea.select();
  784.  
  785.         //Auto-registers for deals based on rules
  786.         if(AUTOREGISTER) doAutoregister();
  787.         pollingAttempts = 0;
  788.        
  789.         // Persist the cache of information we've retrieved from steam
  790.         sessionStorage.setItem("steam_cache", JSON.stringify(SteamRequestCache));
  791.         //console.log(SteamRequestCache);
  792.     }
  793.     catch(ex){}
  794.     // Start the countdown until the page refreshes
  795.     polling = setInterval( function(){refreshWhenReady()}, pollingInterval);
  796. }
  797.  
  798. function giveawayToString(giveaway)
  799. {
  800.     return giveaway.title + '\t' + giveaway.copies + '\t' +
  801.            giveaway.fitness + '\t' +
  802.            giveaway.modifiedProb + '\t' +
  803.            giveaway.modifier + '\t' +
  804.            giveaway.prob_per_P + '\t' +
  805.            giveaway.prob + '\t' +
  806.            giveaway.cost + '\t' +
  807.            giveaway.entries + '\t' +
  808.            giveaway.remaining + '\t' +
  809.            giveaway.created + '\t' +
  810.            giveaway.timePenalty + '\t' +
  811.            giveaway.achievements + '\t' +
  812.            giveaway.metascore + '\t' +
  813.            giveaway.review + '\t' +
  814.            giveaway.isGroup + '\t' +
  815.            giveaway.contribAmount + '\t' +
  816.            giveaway.canEnter + '\t' +
  817.            giveaway.isDLC + '\t' +  
  818.            (giveaway.isDLC ? giveaway.requiredTitle : 'N/A') + '\t' +  
  819.            giveaway.SteamURL + '\t' +
  820.            (giveaway.isDLC ? giveaway.requiredURL : 'N/A') + '\t' +  
  821.            giveaway.url + '\t' +
  822.            giveaway.tags.join();
  823. }
  824.  
  825. function getOrCreateCustomElement(elemName, insertionPoint)
  826. {
  827.     var myDiv = document.getElementById(elemName);
  828.     if( myDiv == null )
  829.     {
  830.         myDiv = document.createElement('div');
  831.         myDiv.setAttribute('id', elemName);
  832.         insertionPoint.appendChild(myDiv);
  833.     }
  834.     return myDiv;
  835. }
  836.  
  837. function appendProbabilityInfoToGiveaway(giveaway)
  838. {
  839.     var giveawayInfo = giveaway.elem.getElementsByClassName('giveaway__columns')[0];
  840.     var widthFill = giveaway.elem.getElementsByClassName('giveaway__column--width-fill')[0];
  841.    
  842.     giveawayInfo.children[0].innerHTML =
  843.         giveawayInfo.children[0].innerHTML.replace("remaining", "left").replace("minute","min").replace("second","sec");
  844.     giveawayInfo.children[0].style.fontSize = '10px';
  845.     if(giveaway.timePenalty > 1.01)
  846.     {
  847.         var spanTimePenalty = document.createElement('span');
  848.         spanTimePenalty.style.fontSize = '9px'
  849.         spanTimePenalty.appendChild(document.createTextNode(' (Penalty: ' + giveaway.timePenalty.toFixed(0) + ')'));
  850.         //giveawayInfo.insertBefore(spanTimePenalty, widthFill);
  851.         // Shorten some words that are taking up space.
  852.         giveawayInfo.children[0].appendChild(spanTimePenalty);
  853.     }
  854.    
  855.     var spanProbability = document.createElement('span');
  856.     spanProbability.appendChild(document.createTextNode('Prob: ' + (giveaway.prob * 100).toFixed(2) +
  857.                                 '% (' + (giveaway.prob_per_P * 100).toFixed(2) + '% per P)'));
  858.     giveawayInfo.insertBefore(spanProbability, giveawayInfo.childNodes[0]);
  859. }
  860.  
  861. function appendSteamInfoToGiveaway(giveaway)
  862. {
  863.     try{
  864.         var heading = giveaway.elem.getElementsByClassName('giveaway__heading')[0];
  865.         var giveawayLinks = giveaway.elem.getElementsByClassName('giveaway__links')[0];
  866.  
  867.         var span = document.createElement('span');
  868.         span.innerHTML = '&nbsp;<font size="0.5em">&nbsp;'
  869.                        //+ (giveaway.isWishlist ? 'Wishlist! &nbsp;&nbsp;' : '')
  870.                        //+ (giveaway.hasCards ? 'Cards &nbsp;&nbsp;' : '')
  871.                        //+ (giveaway.achievements > 0 ? 'Achievements: ' + giveaway.achievements + '&nbsp;&nbsp;' : '')
  872.                        //+ (giveaway.review != null ? 'Reviews: ' + giveaway.review + '&nbsp;&nbsp;' : '')
  873.                        + (giveaway.isDLC ? ('DLC for ' + giveaway.requiredTitle + '&nbsp;&nbsp;') : '')
  874.                        + (giveaway.metascore != null ? 'Metascore: ' + giveaway.metascore + '&nbsp;&nbsp;' : '')
  875.                        + (giveaway.tags.length > 0 ? ('Tags: [' + giveaway.tags.join(', ') + ']&nbsp;&nbsp;') : '')
  876.                        + (giveaway.error ? ('Error: ' + giveaway.error + '&nbsp;&nbsp;') : '')
  877.                        + '</font>';
  878.         //console.log(giveaway.title + ": " + span.innerHTML);
  879.         heading.appendChild(span);
  880.         var textNode = document.createElement('span');
  881.         textNode.style.fontSize = "10px";
  882.         textNode.style.borderBottom = "none";
  883.         textNode.style.boxShadow = "none";
  884.         textNode.style.color = "inherit";
  885.         if(typeof(giveaway.modifierLog) !== 'undefined')
  886.             textNode.title = giveaway.modifierLog;
  887.         textNode.innerHTML = 'Modifier: ' + Math.round(giveaway.modifier * 100) / 100 +
  888.                              ', ModifiedProb: ' + (giveaway.modifiedProb * 100).toFixed(2) +
  889.                              '%, Fitness: ' + (giveaway.modifiedProb / giveaway.timePenalty * 100).toFixed(3) + '%';
  890.         giveawayLinks.appendChild(textNode);
  891.        
  892.         // Place some additional key features in the second row.
  893.         var giveawayInfo = giveaway.elem.getElementsByClassName('giveaway__columns')[0];
  894.         var widthFill = giveaway.elem.getElementsByClassName('giveaway__column--width-fill')[0];
  895.         if(giveaway.isWishlist === true)
  896.         {        
  897.             var spanWishlist = document.createElement('span');
  898.             spanWishlist.style.color = "DarkGreen";
  899.             spanWishlist.style.fontWeight = "bold";
  900.             spanWishlist.appendChild(document.createTextNode('Wishlist!'));
  901.             giveawayInfo.insertBefore(spanWishlist, widthFill);
  902.         }
  903.         if(giveaway.hasCards === true)
  904.         {        
  905.             var spanCards = document.createElement('span');
  906.             spanCards.appendChild(document.createTextNode('Cards'));
  907.             giveawayInfo.insertBefore(spanCards, widthFill);
  908.         }
  909.         if(giveaway.achievements != null && giveaway.achievements > 0)
  910.         {        
  911.             var spanAchievements = document.createElement('span');
  912.             spanAchievements.appendChild(document.createTextNode(giveaway.achievements + ' Achievements'));
  913.             giveawayInfo.insertBefore(spanAchievements, widthFill);
  914.         }
  915.         if(giveaway.review != null)
  916.         {
  917.             var spanReview = document.createElement('span');
  918.             spanReview.appendChild(document.createTextNode('Rated ' + giveaway.review));
  919.             switch(giveaway.review)
  920.             {
  921.                 case "Overwhelmingly Positive":
  922.                     spanReview.style.fontWeight = "bold";
  923.                 case "Very Positive":
  924.                     spanReview.style.color = "DarkGreen";
  925.                     break;
  926.                 case "Positive": spanReview.style.color = "Green"; break;
  927.                 case "Mostly Positive": spanReview.style.color = "#6f9c6f"; break;
  928.                 case "Mixed": spanReview.style.color = "Grey"; break;
  929.                 case "Mostly Negative":
  930.                 case "Negative":
  931.                      spanReview.style.color = "Maroon";
  932.                     break;
  933.                 case "Very Negative":
  934.                 case "Overwhelmingly Negative":
  935.                     spanReview.style.fontWeight = "bold";
  936.                     spanReview.style.color = "Red";
  937.                     break;
  938.                 default: spanReview = null;
  939.             }
  940.             if(spanReview != null)
  941.                 giveawayInfo.insertBefore(spanReview, widthFill);
  942.         }
  943.        
  944.         //Change the entry background colour based on how good/bad the modified probability is.
  945.         if(giveaway.modifiedProb < thresholdForP)
  946.             giveaway.elem.style.background = ColorLuminance("#FF8080", Math.max(0, Math.pow(giveaway.modifiedProb/thresholdForP, 0.2) - 0.2) );
  947.         else
  948.             giveaway.elem.style.background = ColorLuminance("#66FF66", Math.max(0, 1.0 - giveaway.modifiedProb/(thresholdForP*50)) );
  949.     }
  950.     catch(err)
  951.     {
  952.         console.log("Error appending giveaway info: " + err.message);
  953.         reset();
  954.     }
  955. }
  956.  
  957. function refreshWhenReady()
  958. {
  959.     modifiedRefreshTime = RefreshTime + (registrations > 0 ? RegistrationTime : 0);
  960.     updateAsyncRequestsStatus();
  961.    
  962.     //Async Requests Pending
  963.     if(registrations > 0)
  964.         getOrCreateCustomElement('Registering', headerInsertionPoint).innerHTML =
  965.             registrations + ' New Registration(s).';
  966.        
  967.     //Show how long we've been waiting
  968.     getOrCreateCustomElement('RefreshTime', headerInsertionPoint).innerHTML =
  969.         'Refreshing in T-' + ((modifiedRefreshTime - pollingAttempts * pollingInterval ) / 1000) + ' Seconds.';
  970.    
  971.     //Been trying for too long, maybe something is stuck? Just continue with what information we have
  972.     if( pollingAttempts >= modifiedRefreshTime / pollingInterval )    
  973.     {
  974.         window.clearInterval(polling);
  975.         window.location = 'http://www.steamgifts.com';
  976.        
  977.         //In case we have trouble connecting, retry every 15 seconds:        
  978.         pollingAttempts = 0;
  979.         polling = setInterval( function(){keeptryingToRefresh()}, pollingInterval);
  980.     }
  981.     else
  982.         pollingAttempts += 1;
  983. }
  984.  
  985. function keeptryingToRefresh()
  986. {    
  987.     updateAsyncRequestsStatus();
  988.     pollingAttempts += 1;
  989.     if(pollingAttempts % (RefreshTime / pollingInterval) == 0)
  990.     {
  991.         // try to kick off a fresh request.
  992.         window.location = 'http://www.steamgifts.com';
  993.     }
  994.     //Show how long we've been trying to refresh    
  995.     getOrCreateCustomElement('RefreshTime', headerInsertionPoint).innerHTML =
  996.         'Reloading for ' + ((pollingAttempts * pollingInterval) / 1000) + ' seconds.'
  997. }
  998.  
  999. function doAutoregister()
  1000. {
  1001.     try
  1002.     {
  1003.         console.log("Auto Registration Process Beginning.");
  1004.         AUTOREGISTER = false; //Avoid re-entry if some threading thing happens.
  1005.         //Get myP again in case it's changed.
  1006.         var pointsElem = document.getElementsByClassName('nav__points')[0];
  1007.         myP = parseInt(pointsElem.innerHTML);
  1008.  
  1009.         //Order elements by descending fitness
  1010.         arr_Giveaways.sort(function(a,b){
  1011.             if(a.fitness < b.fitness) return 1;
  1012.             if(a.fitness > b.fitness) return -1;
  1013.             return 0;
  1014.         });
  1015.        
  1016.         //register(arr_Giveaways[arr_Giveaways.length-1]);
  1017.        
  1018.         //First rule, get in on good deals that are about to expire
  1019.         //Register for anything within the fitness tolerance based on our current points
  1020.         console.log("Looking at " + arr_Giveaways.length + " giveaways.");
  1021.         for( var I = 0; I < arr_Giveaways.length; I++ )
  1022.         {
  1023.             //console.log("Considering " + arr_Giveaways[I].title +
  1024.             //            ". Fitness is " + parseInt(arr_Giveaways[I].fitness * 100000) / 1000 +
  1025.             //            "% (threshold is " + parseInt(FitnessThreshold(myP) * 100000) / 1000 + "%)");
  1026.             if( arr_Giveaways[I].fitness >= FitnessThreshold(myP) ) {
  1027.                 if( meetsBareMinimumRequirementsForRegistration(arr_Giveaways[I]) )
  1028.                 {
  1029.                     console.log(arr_Giveaways[I].title + " fitness and minimum requirements met.");
  1030.                     register(arr_Giveaways[I]);
  1031.                     myP -= arr_Giveaways[I].cost;
  1032.                 }
  1033.                 else {
  1034.                     break;
  1035.                 }
  1036.             }
  1037.             else {
  1038.                 console.log("Fitness requirements not met. All other giveaways have a lower fitness.");
  1039.                 break;
  1040.             }
  1041.         }
  1042.        
  1043.         //Cap is at 300 points, so might as well spend them or lose them:
  1044.         if( myP >= (MaxP - 20) )
  1045.         {    
  1046.             console.log("Points are almost at maximum, looking at giveaways to enter regardless of time penalty.");
  1047.             //Second rule:
  1048.             //Reduce the timepenalty by one for certain giveaways that we predict to do well,
  1049.             //in case nothing actually below the max time penalty is fit enough to register and we're near our cap.
  1050.             for( var I = 0; I < arr_Giveaways.length; I++ )
  1051.             {
  1052.                 //Group giveaways and high contrib amount giveaways usually end up having good odds,
  1053.                 //so if we have points to blow and nothing good is ending soon, we can allow long term hunches.
  1054.                 if( arr_Giveaways[I].timePenalty == MaxPenalty &&
  1055.                     ( arr_Giveaways[I].isGroup ||
  1056.                       arr_Giveaways[I].ContribAmount >= 10 ) )
  1057.                     arr_Giveaways[I].timePenalty --;
  1058.             }
  1059.             //Sort by some random metric of modified prob and time penalty
  1060.             arr_Giveaways.sort(function(a,b){
  1061.                 if((a.modifiedProb / (a.timePenalty/10000)) < (b.modifiedProb / (b.timePenalty/10000))) return -1;
  1062.                 return 1;
  1063.                 //if(a.timePenalty > b.timePenalty) return 1;
  1064.                 //if(a.fitness < b.fitness) return 1;
  1065.                 //if(a.fitness > b.fitness) return -1;
  1066.                 //return 0;
  1067.             });        
  1068.             //Register for the first item in this new ordered list that meets min requirements and whose modified prob exceeds our threshold.
  1069.             for( var I = 0; I < arr_Giveaways.length; I++ )
  1070.             {
  1071.                 if( arr_Giveaways[I].modifiedProb >= FitnessThreshold(myP) &&
  1072.                     meetsBareMinimumRequirementsForRegistration(arr_Giveaways[I]) )
  1073.                 {
  1074.                     register(arr_Giveaways[I]);
  1075.                     return;
  1076.                 }
  1077.             }
  1078.            
  1079.             //Third rule:
  1080.             if( myP >= (MaxP - 5) )
  1081.             {
  1082.                 console.log("Points at maximum, entering the fittest giveaway.");
  1083.                 //If all else fails, register for the one with the lowest cost and highest fitness.
  1084.                 arr_Giveaways.sort(function(a,b){
  1085.                     if(a.cost < b.cost) return -1;
  1086.                     if(a.cost > b.cost) return 1;
  1087.                     if(a.fitness < b.fitness) return 1;
  1088.                     if(a.fitness > b.fitness) return -1;
  1089.                     return 0;
  1090.                 });
  1091.                 register(arr_Giveaways[0]);
  1092.             }
  1093.         }
  1094.     }
  1095.     catch(err)
  1096.     {
  1097.         console.log("Error during auto-register." + err.message + "\nLine: " + err.lineNumber);
  1098.         throw err;
  1099.     }
  1100. }
  1101.  
  1102. //We penalize the probability of games that have a long time left, since there's always a flood of entries in the last legs of a race.
  1103. //Expect "MaxPenalty" times more entries if the entry has more than "LifeLimit" time left.
  1104. function calcTimePenalty(g)
  1105. {    
  1106.     //Lower Limit, Upper Limit, and Exponential Factors for Interpolation of Penalties
  1107.     var TLeftLower = 6*60,  TLeftUpper = 6*60*60, TLeftExponent = 1.9; //For penalizing having a lot of time left
  1108.     var AliveLower = 20*60, AliveUpper = 1*60*60, AliveExponent = 8.0; //For penalizing not having been alive for long
  1109.     var ElapsLower = 0.05,  ElapsUpper = 0.75,    ElapsExponent = 3.0; //Longevaty penalty alternative, using percentage of lifetime elapsed
  1110.  
  1111.     if(g.remaining <= TLeftLower) return 1;
  1112.    
  1113.     var lifeElapsed  = g.remaining / (g.remaining + g.created);
  1114.     var TLeftPenalty = Math.pow(Math.min(1,     Math.max(0, g.remaining - TLeftLower) / (TLeftUpper-TLeftLower) ), TLeftExponent);
  1115.     var AlivePenalty = Math.pow(Math.max(0, 1 - Math.max(0, g.created   - AliveLower) / (AliveUpper-AliveLower) ), AliveExponent);
  1116.     var ElapsPenalty = Math.pow(Math.min(1,     Math.max(0, lifeElapsed - ElapsLower) / (ElapsUpper-ElapsLower) ), ElapsExponent);
  1117.     var CumulPenalty = 1 + (MaxPenalty-1) * Math.min(AlivePenalty + ElapsPenalty + TLeftPenalty, 1);
  1118.  
  1119.     return CumulPenalty;
  1120. }
  1121.  
  1122. function meetsBareMinimumRequirementsForRegistration(giveaway)
  1123. {
  1124.     return giveaway.canEnter &&
  1125.            giveaway.timePenalty < MaxPenalty &&     //The timing for entry isn't so bad as having incurred the max penalty
  1126.            giveaway.remaining < (12 * 60 * 60) &&   //The giveaway has less than 12 hours remaining.
  1127.            giveaway.created > (10 * 60) &&          //The giveaway has been alive for more than 10 minutes.
  1128.            !(giveaway.entries <= 5 && giveaway.remaining > 15*60 ); //If the giveaway has less than 5 entries it has less than 15 minutes remaining.
  1129.             // && giveaway.elem.clientWidth > 0;           //The giveaway hasn't been hidden (filtered) on the main page. This may prevent race conditions with the filtering script.
  1130. }
  1131.  
  1132. function calcModifierAndFitness(giveaway)
  1133. {
  1134.     var baseChance = 100*giveaway.copies/giveaway.entries;
  1135.     giveaway.modifierLog = "Pure win chance is " + giveaway.copies + " / " + giveaway.entries + " (copies / current entries) = " + (baseChance).toPrecision(3)/1 + "%\n";
  1136.     var chancePerP = baseChance / giveaway.cost;
  1137.     giveaway.modifierLog += "Divided by entry cost (" + giveaway.cost + "P) = " + chancePerP.toPrecision(3)/1 + "% chance per unit cost.\n\n";
  1138.    
  1139.     giveaway.modifier = favourabilityModifier(giveaway);
  1140.     giveaway.modifiedProb = giveaway.prob_per_P * giveaway.modifier;
  1141.     giveaway.modifierLog += "\nFitness = " + (100*giveaway.prob_per_P).toPrecision(3)/1 + "% * " + giveaway.modifier.toPrecision(3)/1 + " = " + (100*giveaway.modifiedProb).toPrecision(3)/1 + "%";
  1142.     giveaway.fitness = giveaway.modifiedProb / giveaway.timePenalty;
  1143.     if(giveaway.timePenalty > 1)
  1144.         giveaway.modifierLog += "\n\nPenalty of " + giveaway.timePenalty.toFixed(0)/1 + " applied since there's time for odds to change before giveaway closes." +
  1145.             "\nSo current session fitness is: " + (100*giveaway.fitness).toPrecision(3)/1 + "%";
  1146.    
  1147.     var currentThreshold = FitnessThreshold(myP);
  1148.     giveaway.modifierLog += "\n\nThreshold for entry at current balance of " + myP + "P is " + (currentThreshold*100).toPrecision(3)/1 + "%.\n";
  1149.     if(currentThreshold <= giveaway.fitness)
  1150.         giveaway.modifierLog += "Entering Giveaway!";
  1151.     else if(currentThreshold <= giveaway.modifiedProb)
  1152.         giveaway.modifierLog += "Not entering giveaway now, but likely entering it before it closes.";
  1153.     else
  1154.         giveaway.modifierLog += "Not entering giveaway now, probably not ever.";
  1155. }
  1156.  
  1157. //Detects preferred items (such as game bundles or those with steam achievements) and lowers the threshold for preferred items
  1158. function favourabilityModifier(giveaway)
  1159. {
  1160.     var modifier = 1.0;
  1161.     // Note using .toPrecision(3)/1 helps display non-integer values as a string without crazy precision, like 0.2 becoming 0.19999999999999
  1162.     giveaway.modifierLog += "Base multiplier is " + modifier.toPrecision(3)/1 + "\n";
  1163.    
  1164.     // Giveaway 'Quality' Modifiers used to favour giveaways with an above-average opportunity to win -
  1165.     // which is already inherent in its number of entries, but this scales it further.)
  1166.     //Reward games where the giveaway period is small, since less people have had
  1167.     //time to enter, it's probably a relatively good deal for that particular game.
  1168.     if( giveaway.created + giveaway.remaining <= shortGiveawayTime ){
  1169.         modifier += shortGiveawayModifier;
  1170.         giveaway.modifierLog += "+" + shortGiveawayModifier + " (Short giveaway - less competition) = " + modifier.toPrecision(3)/1 + "\n";
  1171.     }
  1172.     //Same for games that are group giveaways
  1173.     if( giveaway.isGroup ){
  1174.         modifier += groupGiveawayModifier;
  1175.         giveaway.modifierLog += "+" + groupGiveawayModifier + " (Group giveaway - less competition) = " + modifier.toPrecision(3)/1 + "\n";
  1176.     }
  1177.     //Same for games that have a high contributor value
  1178.     if( giveaway.contribAmount > 0 ){  
  1179.         var contribBonus = giveaway.contribAmount >= contributorLevelCap ? maxContributorModifier :
  1180.             maxContributorModifier * giveaway.contribAmount / contributorLevelCap;
  1181.         modifier += contribBonus;
  1182.         giveaway.modifierLog += "+" + contribBonus + " (Contributor level " + giveaway.contribAmount + " required - less competition) = " + modifier.toPrecision(3)/1 + "\n";
  1183.     }
  1184.    
  1185.    
  1186.     // Game 'Quality' Modifiers (Used for favouring "Good" games and penalizing "Bad" games.)
  1187.     // Games on my wish-list are the best! Big bonus
  1188.     if(giveaway.isWishlist === true) {
  1189.         modifier = onWishListModifier;
  1190.         giveaway.modifierLog += "+" + onWishListModifier + " (Wishlisted) = " + modifier.toPrecision(3)/1 + "\n";
  1191.     }
  1192.     // Otherwise, try to guess how much I'd want the game based on these things:
  1193.     else
  1194.     {        
  1195.         // Reward the game having a certain number of achievements
  1196.         if(giveaway.achievements != null) {
  1197.             for(var i = achieveTiers.length; i > 0; i--) {
  1198.                 if(giveaway.achievements >= achieveTiers[i]) {
  1199.                     modifier += achieveBonus[i];
  1200.                     giveaway.modifierLog += "+" + achieveBonus[i] + " (More than " + achieveTiers[i] + " achievements) = " + modifier.toPrecision(3)/1 + "\n";
  1201.                     break;
  1202.                 }
  1203.             }
  1204.         }
  1205.        
  1206.         // Games that produce cards can have the cards sold for profit!
  1207.         if(giveaway.hasCards === true) {
  1208.             modifier += hasCardsBonus;
  1209.             giveaway.modifierLog += "+" + hasCardsBonus + " (Has Cards) = " + modifier.toPrecision(3)/1 + "\n";
  1210.         }
  1211.            
  1212.         // Reward good metascore, penalize bad score    (cumulative)
  1213.         // Note: Probably obsolete, steam pages don't tend to list metascores anymore
  1214.         if(giveaway.metascore != null) {
  1215.             modifier += (giveaway.metascore - 70) / 50;
  1216.             giveaway.modifierLog += "+" + ((giveaway.metascore - 70) / 50) + " (Metascore) = " + modifier.toPrecision(3)/1 + "\n";
  1217.         }
  1218.         if(giveaway.review != null) {
  1219.             var reviewScore = 0;
  1220.             switch(giveaway.review)
  1221.             {
  1222.                 case "Overwhelmingly Positive": reviewScore += reviewScale[0]; break;
  1223.                 case "Very Positive": reviewScore += reviewScale[1]; break;
  1224.                 case "Positive": reviewScore += reviewScale[2]; break;
  1225.                 case "Mostly Positive": reviewScore += reviewScale[3]; break;
  1226.                 case "Mixed": reviewScore += reviewScale[4]; break;
  1227.                 case "Mostly Negative": reviewScore += reviewScale[5]; break;
  1228.                 case "Negative": reviewScore += reviewScale[6]; break;
  1229.                 case "Very Negative": reviewScore += reviewScale[7]; break;
  1230.                 case "Overwhelmingly Negative": reviewScore += reviewScale[8]; break;
  1231.             }
  1232.             if(reviewScore != 0){
  1233.                 modifier += reviewScore;
  1234.                 giveaway.modifierLog += "+" + reviewScore + " (Reviews are " + giveaway.review + ") = " + modifier.toPrecision(3)/1 + "\n";
  1235.             }
  1236.         }
  1237.            
  1238.         // Reward Game Bundles (multiple games)
  1239.         if(giveaway.title.indexOf('Gala') > -1 || giveaway.title.indexOf('Collection') > -1 ||
  1240.            giveaway.title.indexOf('Pack') > -1 || giveaway.title.indexOf('Bundle') > -1) {
  1241.             modifier += bundleBonus;
  1242.             giveaway.modifierLog += "+" + bundleBonus + " (Includes multiple games) = " + modifier.toPrecision(3)/1 + "\n";
  1243.         }
  1244.         // Steam Game Packs get a bonus for each game in the pack.
  1245.         if(giveaway.packGames != null) {
  1246.             var numPackGames = giveaway.packGames.length -1;
  1247.             modifier += numPackGames * packGameValue;
  1248.             giveaway.modifierLog += "+" + (numPackGames * packGameValue) + " (Bundle includes " + numPackGames + " additional games/dlc) = " + modifier.toPrecision(3)/1 + "\n";
  1249.         }
  1250.        
  1251.         // If DLC isn't on my wish list I probably don't want it.
  1252.         // This used to be cut down far more when there was no detection for when you didn't own the base game for the DLC.
  1253.         if(giveaway.isDLC){
  1254.             modifier += dlcPenalty;
  1255.             giveaway.modifierLog += "+" + dlcPenalty + " (Is DLC) = " + modifier.toPrecision(3)/1 + "\n";
  1256.         }
  1257.        
  1258.         // For every unwanted tag, apply a penalty.
  1259.         var unwantedTagCount = 0;
  1260.         for(tag of unwantedTags) {
  1261.             if(giveaway.tags.includes(tag))
  1262.                 unwantedTagCount++;
  1263.         }
  1264.         if(unwantedTagCount > 0){
  1265.             modifier += unwantedTagCount * unwantedTagPenalty;
  1266.             giveaway.modifierLog += "+" + (unwantedTagCount * unwantedTagPenalty) + " (Has " + unwantedTagCount + " unwanted tags) = " + modifier.toPrecision(3)/1 + "\n";
  1267.         }
  1268.            
  1269.         // For every desireable tag, apply a bonus.
  1270.         var wantedTagCount = 0;
  1271.         for(tag of wantedTags) {
  1272.             if(giveaway.tags.includes(tag))
  1273.                 wantedTagCount++;
  1274.         }
  1275.         if(wantedTagCount > 0){
  1276.             modifier += wantedTagCount * wantedTagBonus;
  1277.             giveaway.modifierLog += "+" + (wantedTagCount * wantedTagBonus) + " (Has " + wantedTagCount + " wanted tags) = " + modifier.toPrecision(3)/1 + "\n";
  1278.         }
  1279.        
  1280.         // Don't want Soundtracks (but game and soundtrack bundle is ok)
  1281.         if( !/and Soundtrack/i.test(giveaway.title) && !/\+ Soundtrack/i.test(giveaway.title) )
  1282.             if( / Soundtrack/i.test(giveaway.title) || / OST/.test(giveaway.title) ){
  1283.                 modifier += soundtrackPenalty;
  1284.                 giveaway.modifierLog += "+" + soundtrackPenalty + " (Is a Soundtrack) = " + modifier.toPrecision(3)/1 + "\n";
  1285.             }
  1286.            
  1287.         // If after all the modifiers are compiled, the net result is negative, convert it into
  1288.         // a number between 0 and 1 such that the returned multiplier will reduce the fitness but remain positive.
  1289.         if( modifier < 1 ){
  1290.             var punitiveMultiplier = 1 / Math.pow(10, 1-modifier);
  1291.             giveaway.modifierLog += "Negative modifier of " + modifier.toPrecision(3)/1 + " converted into a punative multiplier of " + punitiveMultiplier.toPrecision(3)/1 + "\n";
  1292.             modifier = punitiveMultiplier;
  1293.         }
  1294.     }
  1295.  
  1296.     var qualityModifierAdjusted = 1 + overallQualityModifierPower * (modifier-1);
  1297.     if(overallQualityModifierPower != 1) {
  1298.         giveaway.modifierLog += "Quality modifier scaling factor of " + overallQualityModifierPower + " is applied.";
  1299.     }
  1300.     giveaway.modifierLog += "Final multiplier is: " + qualityModifierAdjusted.toPrecision(3)/1 + "\n";
  1301.     return qualityModifierAdjusted;
  1302. }
  1303.  
  1304. //Returns the time (in seconds) based on the formatted time string.
  1305. function parseTimeString(timeString)
  1306. {
  1307.     var toSecondsMult = 1;    
  1308.     var match = timeString.match(/(\d+) second/);
  1309.     if( match == null ) {
  1310.         toSecondsMult *= 60;
  1311.         match = timeString.match(/(\d+) minute/);
  1312.     } if( match == null ) {
  1313.         toSecondsMult *= 60;
  1314.         match = timeString.match(/(\d+) hour/);
  1315.     } if( match == null ) {
  1316.         toSecondsMult *= 24;
  1317.         match = timeString.match(/(\d+) day/);
  1318.     } if( match == null ) {
  1319.         toSecondsMult *= 7;
  1320.         match = timeString.match(/(\d+) week/);
  1321.     }
  1322.     return match == null ? null : parseInt(match[1]) * toSecondsMult;
  1323. }
  1324.  
  1325. function registerGiveawayPage(document)
  1326. {
  1327.     try
  1328.     {
  1329.         //Check to see this giveaway is still enterable (in case of a multiple instances conflict)
  1330.         var enterButton = document.querySelector(".sidebar__entry-insert");
  1331.         if( !enterButton.classList.contains('is-hidden') )
  1332.         {
  1333.             // try to submit a comment after registering
  1334.             /* Apparently people don't like comments anymore.
  1335.                 setTimeout( function()
  1336.                 {                
  1337.                     var commentForm = w.document.querySelector (".comment__description form");
  1338.                     var responses = ["Thanks!", "ty", "thanks", "Thanks", "thanks!", "cheers", "ty :)",
  1339.                                      "Awesome, thanks!", "ty~", ":D", "thanks <3"];
  1340.                     commentForm.querySelector ("textarea").value = responses[Math.floor(Math.random()*responses.length)];
  1341.                     commentForm.querySelector (".comment__submit-button").click();
  1342.                     console.log('Commented on ' + giveaway.title);
  1343.                     w.scrollTo(0, 0);
  1344.                 }, RegistrationTime / 2.0 );
  1345.                 */
  1346.  
  1347.             enterButton.click();
  1348.         }
  1349.         else
  1350.         {
  1351.             console.log('Was already registered for ' + document.location);
  1352.         }
  1353.     }
  1354.     catch(err)
  1355.     {
  1356.         console.log('Registration error: ' + err.message + "\nLine: " + err.lineNumber);
  1357.     }
  1358. }
  1359.  
  1360. function register(giveaway)
  1361. {
  1362.     asyncRequests ++;
  1363.     var success = false;
  1364.    
  1365.     var toRunOnLoad = function(e){
  1366.         console.log('Registration frame loaded for ' + giveaway.title);
  1367.         registerGiveawayPage(e);
  1368.         asyncRequests --;
  1369.         registrations ++;
  1370.         console.log('Registered for ' + giveaway.title);
  1371.     };
  1372.    
  1373.     console.log('Registering for ' + giveaway.title + '...');
  1374.     try {
  1375.         ////////// NEW WINDOW METHOD /////////////////////
  1376.         var popup = window.open(giveaway.url, "_blank");
  1377.         var autoEnterInterval = setInterval( function(){
  1378.             try {
  1379.                 console.log('Attempting to send retister message for ' + giveaway.title);
  1380.                 popup.postMessage("EnterGiveaway", "*");
  1381.             }
  1382.             catch(ex) {
  1383.                 console.log("Window closed. Assuming we've registered for " + giveaway.title);
  1384.                 clearInterval(autoEnterInterval);
  1385.                 asyncRequests --;
  1386.                 registrations ++;
  1387.             }
  1388.         }, pollingInterval);
  1389.        
  1390.         /////////// IFRAME METHOD ////////////////////////
  1391.         /*
  1392.         var myIFrame = document.createElement('iframe');
  1393.         myIFrame.setAttribute('id', 'Entry' + asyncRequests);
  1394.         document.body.insertBefore(myIFrame, document.getElementsByClassName('page__outer-wrap')[0]);
  1395.         myIFrame.width = '100%'
  1396.         myIFrame.src = giveaway.url;
  1397.         myIFrame.contentWindow.onload = function() { toRunOnLoad(myIFrame.contentWindow.document) };
  1398.         */
  1399.        
  1400.         /////////// EMBEDDED DIV METHOD ////////////////
  1401.         /*
  1402.         var entryDiv = document.createElement('div');
  1403.         entryDiv.className = "inlinedEntry";
  1404.         giveaway.elem.appendChild(entryDiv);
  1405.         // Requires JQUERY selector
  1406.         //entryDiv.load(giveaway.url, function() { toRunOnLoad(entryDiv); });
  1407.         var request = new XMLHttpRequest();
  1408.         request.open('GET', giveaway.url, true);
  1409.         request.onload = function() {
  1410.           if (request.status >= 200 && request.status < 400) {
  1411.             var resp = request.responseText;
  1412.             resp = resp.replace(/style/g, "style-disabled");
  1413.             entryDiv.innerHTML = resp;
  1414.             toRunOnLoad(entryDiv);
  1415.           }
  1416.         };
  1417.         request.send();
  1418.         */
  1419.        
  1420.         console.log('Waiting for load...');
  1421.     }
  1422.     catch(err)
  1423.     {
  1424.         console.log('Registration error: ' + err.message + "\nLine: " + err.lineNumber);
  1425.     }    
  1426. }
  1427.  
  1428. function collectionToArray(collection)  
  1429. {  
  1430.     var ary = [];  
  1431.     for(var i=0, len = collection.length; i < len; i++)  
  1432.     {  
  1433.         ary.push(collection[i]);  
  1434.     }  
  1435.     return ary;  
  1436. }
  1437.  
  1438. function ColorLuminance(hex, lum) {  
  1439.     // validate hex string  
  1440.     hex = String(hex).replace(/[^0-9a-f]/gi, '');  
  1441.     if (hex.length < 6) {  
  1442.         hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];  
  1443.     }  
  1444.     lum = lum || 0;  
  1445.     // convert to decimal and change luminosity  
  1446.     var rgb = "#", c, i;  
  1447.     for (i = 0; i < 3; i++) {  
  1448.         c = parseInt(hex.substr(i*2,2), 16);  
  1449.         c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);  
  1450.         rgb += ("00"+c).substr(c.length);  
  1451.     }  
  1452.     return rgb;  
  1453. }  
  1454.  
  1455. //Remove an element from an array by value
  1456. function removeA(arr) {
  1457.     var what, a = arguments, L = a.length, ax;
  1458.     while (L > 1 && arr.length) {
  1459.         what = a[--L];
  1460.         while ((ax= arr.indexOf(what)) !== -1) {
  1461.             arr.splice(ax, 1);
  1462.         }
  1463.     }
  1464.     return arr;
  1465. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement