NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Dominos Pizza Voucher Codes // @description Finds voucher codes for Dominos Pizza Australia & NZ // @match *://*.dominos.com.au/* // @match *://dominos.com.au/* // @match *://*.dominos.co.nz/* // @match *://dominos.co.nz/* // @match *://*.dominospizza.co.nz/* // @match *://dominospizza.co.nz/* // @version 9.3 // @require http://code.jquery.com/jquery-latest.min.js // @require https://raw.githubusercontent.com/flokii/dominos/master/sha1.js // @require http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js // @require http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn2.js // @require http://www-cs-students.stanford.edu/~tjw/jsbn/prng4.js // @require http://www-cs-students.stanford.edu/~tjw/jsbn/rng.js // @require http://www-cs-students.stanford.edu/~tjw/jsbn/rsa.js // @require http://www-cs-students.stanford.edu/~tjw/jsbn/base64.js // @resource turtle https://i.imgur.com/eH8Ci9N.png // @copyright jehan // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_listValues // @grant GM_addStyle // @grant GM_getResourceURL // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_log // @run-at document-start // ==/UserScript== setLogging(); ctrl = tryGen = false; nThreads = 3; max_retry = 25; max_ajax_callback_retry = 2; ajax_timeout = 10000; maxCPM = GM_getValue('maxCPM', 30); country = window.location.hostname.split('.').pop(); var lastCountry = GM_getValue('country', null); if(lastCountry && (lastCountry != country)) clearCache(); GM_setValue('country', country); if(country == 'au'){ fileListURL = "goo.gl/gd3hTA"; //"goo.gl/lHScDV"; sendCodesURLs = ['G3uTCz','HlTPWA','LzGvkp','kIIRcO','l1wYpD','OiG75K','R1pwZL','ZlQ8hT','K9RzN2','YZCMdS','JoDR2h','oplNof','rrJZrS','QXbUoa','w5TaHJ','TzqVxT','UWncZJ','N0EykP','Aqf4L6','8su4Cp']; storeListRef = 'goo.gl/tf457k'; } else { fileListURL = 'goo.gl/SsY3E6'; sendCodesURLs = ['4mru6g', 'tiHGQm', 'ekxW0X', 'wf80OK', 'qpX6zi', 'MPA7Oy', 'gr34RZ', 'cTwIkH', '9phyAf', 'XXdukM']; storeListRef = 'https://goo.gl/3zaWBc'; } window.dealList = null; window.priceOrder = {}; invalidDeals = []; updInterval = 1800000; consentVersion = 2; sentCodes = []; dealListCallbacks = {}; window.ajaxReq = ajaxReq, window.updateBasket = updateBasket, window.testCode = testCode, window.getSessionVars = getSessionVars, window.chkCode = chkCode, window.showStatus = showStatus; // GM weirdness // window.getBasket = getBasket, setupGMMenu(); googl = {}; window.b64Map = "-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; // override value in base64.js Need to set b64map=b64Map in verifySig due to script loading issues b64RegExp = new RegExp('[' + window.b64Map + ']+', 'g'); window.nilCallbacks = { complete: nil, error: nil }; debug = false; // debug only window.page = window.location.pathname.split('/').pop(); if(debug){ clearCache(); setLogging(true); //debug GM_setValue('consent_code_sharing', consentVersion); } if(!('fill' in Array.prototype)) // fuck you Chrome Array.prototype.fill = function(val){ for(var i = 0; i < this.length; i++) this[i] = val; return this; }; (function(){ updateCodeData( { complete: function(codes){ Log("updateCodeData() callback()"); Log("updateCodeData() callback() codes = ", codes); createCodeList(codes); return true; }, error: function(){ Log("Error in updateCodeData()"); return null; } }); setTimeout(arguments.callee, updInterval); })(); Log("promise_ 1"); promise_storeUrlList = new Promise_(getStoreUrlList); if(page.indexOf('OrderTime')==0) writeSessionVars(true); // new session promise_sessionVars = new Promise_(getSessionVars); promise_dealList = new Promise_(getDealList); promise_codeList = new Promise_(createCodeList); Log("promise_ 2"); if(!validPage(page)) return; promise_SPCodes = new Promise_(getSPCodes, false); promiseAll([promise_storeUrlList, promise_sessionVars], getSPCodes); promiseAll([promise_sessionVars, promise_codeList, promise_dealList], initialiseCodes); promiseAll([promise_codeList, promise_SPCodes], applySPCodes); if(GM_getValue('consent_code_sharing', 0) != consentVersion){ alert("Privacy Statement:\n'Dominos Pizza Voucher Codes' userscript notifies the script author of new, incorrect and expired codes.\nThis process is completely anonymous.\nThe only information shared is the code, the store and whether it was a delivery or pickup.\nNo other personal details are shared, and it's impossible to even find out your IP address.\nIf you do not wish to participate, please uninstall this script immediately."); GM_setValue('consent_code_sharing', consentVersion); } //GM_addStyle("#voucher_form .input-group label { height: 3em!important; }"); GM_addStyle("#voucher_select option:checked, #voucher_select option:hover { box-shadow: 0 0 10px 100px #9C9D9F inset; }"); GM_addStyle("#voucher_select option { display: block!important; text-align: left;}"); GM_addStyle("#voucher_select { background: url('http://i.imgur.com/eH8Ci9N.png') no-repeat center center; background-size: 100% auto; }"); GM_addStyle("#voucher_select {-webkit-appearance: none; -moz-appearance: none; text-indent: 1px; text-overflow: ''; }"); GM_addStyle("#voucher_select { background-color: transparent!important; width: 2em!important; margin-right: 0.3em; direction: rtl; }"); GM_addStyle("#voucher_select option { direction: ltr; }"); //GM_addStyle("#voucher_form > img { float: right; display: none; } #voucher_form > label { height:1.1em!important; display: inline-block!important; width: auto; max-width: 100%;}"); GM_addStyle(".input-group > .row > * { height:2em!important;}"); GM_addStyle(".input-group > .row * { display: inline-block!important; float:none!important; clear:both!important; vertical-align:middle!important;}"); GM_addStyle(".input-group > .row > .col-4 { width: auto!important; margin-left: -1em; }"); //padding-left: 1em; }"); GM_addStyle(".input-group > .row > .col-4 * { border: none!important; }"); GM_addStyle(".input-group > .row > div * { padding-top:0!important; padding-bottom:0!important; height: 100%; }"); GM_addStyle(".input-group > .row > .col-8 { width: auto!important; margin-right: 0.75em!important; }"); GM_addStyle("#apply_voucher { width: 4em!important; }");//margin-top: -1px!important;}"); GM_addStyle("#voucher_code { width: 7em!important; }"); //GM_addStyle("#loading-indicator { width: auto!important; visibility: visible!important; }"); //GM_addStyle("#loading-indicator.loaded { visibility: hidden!important; }"); //visible;display: none!important;}"); GM_addStyle("#loading-indicator { display: none!important; }"); //GM_addStyle("#voucher-button-container { position: relative; } #voucher-button-container > img { position: absolute; height: 1.6em; z-index: -1; left: 54.3%; top: 0.25em; } #apply_voucher { float: right; }"); //GM_addStyle("#voucher_code { width: 50%!important; } #voucher-button-container > * { display: inline!important; }"); //GM_addStyle("#voucher-button-container select { color:#58595B; width: 13%; height: 1.6em; display: inline-block; direction: rtl; opacity: 0; }"); GM_addStyle(".noValidCodes { text-decoration: line-through!important; color: #999999; }"); GM_addStyle(".validCode { color: #227722; }"); if(window.navigator.userAgent.indexOf('WebKit')>0) GM_addStyle("#voucher_select { direction: ltr!important; }"); window.onkeydown = window.onkeyup = function(e){ if(e.keyCode!=17) return; window.ctrl = e.ctrlKey; } document.addEventListener("DOMContentLoaded", function(){ Log("Event: DOMContentLoaded"); $("a[href$='Menu.Payment']").each( function(){ this.onclick = (function(href){ return function(e){ e.stopPropagation(); e.preventDefault(); sendCodeBatch(function(){ window.location.href = href; }); } })(this.href); this.href = ''; } ); initBasketObserver(); $status = $('#voucher_form .input-group label'); scrollStatus("Tip: Most Traditional Pizza coupons can be used for Chef's Best or Mogul Pizzas"); $("<select id='voucher_select' title='Find vouchers'/>").insertBefore('#apply_voucher').change(function(e){ if(this.value == -1) tryGenericCodes(); else tryCodes(this.value); resetChoice(); }).each(function(){}); }); function tryGenericCodes(){ var i = 0; getGenericDescIds(); tryGen = true; (function(){ Log("tryGenericCodes() i = " + i + " desc_id = " + genericDescIds[i]); while(i < genericDescIds.length) if((genericDescIds[i] in window.priceOrder) && priceOrder[genericDescIds[i]]) return tryCodes(genericDescIds[i], arguments.callee), i++; else i++; return tryGen = false, showStatus("All deals checked"), updateBasket(), true; })(); } function rndStr(){ var s = ''; for(var i = 0, l = Math.random()*5+5; i<l; i++) s += String.fromCharCode(Math.floor(Math.random()*26) + Math.round(Math.random()) * 32 + 65); return s; } function runUnsafeCode(c){ Log("runUnsafeCode():\n" + c); var id = rndStr(); $("<script id='" + id + "'>(function(){var unsafeWindow = window; " + c + "; var s = document.getElementById('" + id + "'); s.parentNode.removeChild(s); })();</script>").appendTo(document.body); } function runUnsafeFunction(fn, args){ if(typeof args == 'undefined') var args = []; else args = args.map(function(val){ return typeof val == 'string' ? '"' + val.split('"').join('\"') + '"' : val; }); var c = '(' + fn.toString() + ')(' + args.join(',') + ');'; runUnsafeCode(c); } function setLogging(l){ if(typeof l == 'undefined') var l = GM_getValue('logging', false) === 'true'; GM_setValue('logging', l ? 'true' : 'false'); Log = l ? function(t, a){ if(typeof a != 'undefined') t += "\n" + JSON.stringify(a); console.log(t); //GM_log(t); return true; } : function(){}; // Need to wrap console.log so Chrome doesn't have a fit } function clearCache(){ Log("clearCache()"); var entries = cloneInto(GM_listValues(), window); for(var i = 0; i < entries.length; i++){ Log("Deleting entry: " + entries[i]); GM_deleteValue(entries[i]); } } function resetChoice(){ nullOption.selected = 'selected'; } function cloneObj(obj){ var n = rndStr(); var cl = unsafeWindow[n] = obj; delete unsafeWindow[n]; return cl; var oldState = history.state; history.replaceState(obj, null); var clonedObj = history.state; history.replaceState(oldState, null); return clonedObj; } function createCodeList(codes){ Log("createCodeList()"); createCodeList_(); return true; function createCodeList_(){ if(typeof codes == 'undefined'){ var codeList = GM_getValueJSON('codeList', null); if(codeList) createCodeList.resolve(codeList); return true; } Log("createCodeList()"); var codeList = { delivery: {}, pickup: {}}; for(var desc_id in codes){ if(!codes.hasOwnProperty(desc_id)) continue; if(desc_id == 'deal_list') continue; if(1 in codes[desc_id]){ codeList.delivery[desc_id] = codes[desc_id][1]; //codeList.delivery[desc_id].order = sortIntKeys(codeList.delivery[desc_id]); codeList.pickup[desc_id] = cloneObj(codes[desc_id][1]); } if(0 in codes[desc_id]){ if(desc_id in codeList.pickup) codeList.pickup[desc_id] = concat(codeList.pickup[desc_id], codes[desc_id][0], 'prices'); else codeList.pickup[desc_id] = codes[desc_id][0]; } //if(desc_id in codeList.pickup) //codeList.pickup[desc_id].order = sortIntKeys(codeList.pickup[desc_id]); } GM_setValueJSON('codeList', codeList); return createCodeList.resolve(codeList); } } function initialiseCodes(arr){ Log("initialiseCodes() arr:", arr); var sessionVars = arr[0], codeList = arr[1], dealList = arr[2]; Log("initialiseCodes()"); if(!('newCodeList' in initialiseCodes)) initialiseCodes.newCodeList = 0; if((!('lock' in initialiseCodes)) || !initialiseCodes.lock) initialiseCodes.lock = true; else return initialiseCodes.newCodeList++, false; Log("initialiseCodes() 2"); var startTime = new Date().getTime(); //Log("initialiseCodes() 3 codesList = ", codeList); window.codes = sessionVars.orderDetails.delivery ? codeList.delivery : codeList.pickup; createMenu(sessionVars, codeList, dealList); initialiseCodes.lock = false; if(initialiseCodes.newCodeList) return initialiseCodes.newCodeList--, arguments.callee(); } function createMenu(sessionVar, codeList, dealList){ Log('createMenu()'); Log('createMenu() validDescIds:', sessionVars.validDescIds); Log('createMenu() invalidDescIds:', sessionVars.invalidDescIds); if(!window.dealList) return false; Log('createMenu() 3'); var voucherSelect = document.getElementById('voucher_select'); if(!voucherSelect){ mkObserver(function(node){ if(node.id != 'voucher_select') return true; return createMenu(), null; }, document); return false; } if(!('extraOptions' in createMenu)){ createMenu.extraOptions = true; nullOption = document.createElement('option'); nullOption.value = ''; voucherSelect.appendChild(nullOption); nullOption.style.display = 'none'; resetChoice(); var o = document.createElement('option'); o.value = '-1'; o.innerHTML = "Try All Generic Vouchers"; voucherSelect.appendChild(o); } //Log('createMenu() 3aa window.codes: ', window.codes); //Log("dealList:", window.dealList); Log("dealList.order.length:" + window.dealList.order.length); for(var i = 0, lastOption = null; i < window.dealList.order.length; i++){ var desc_id = window.dealList.order[i], deal = window.dealList.deals[desc_id]; Log('createMenu() 3a i: ' + i); Log('createMenu() 3b desc_id:' + desc_id + ' deal:' + deal); var option = voucherSelect.querySelector('option[value="' + desc_id + '"]'); if(!(desc_id in window.codes) || !count(window.codes[desc_id])){ if(option){ Log('createMenu() 3c desc_id:' + desc_id + ' deal:' + deal); option.parentNode.removeChild(option); } continue; } if(!option){ Log('createMenu() 3d: desc_id:' + desc_id + ' deal:' + deal); option = document.createElement('option'); option.value = desc_id; //voucherSelect.appendChild(option); voucherSelect.insertBefore(option, lastOption && lastOption.nextElementSibling); lastOption = option; } else Log('createMenu() 3dd:' + option.value); Log('3e window.codes[desc_id]:', window.codes[desc_id]); if(sessionVars.invalidDescIds.indexOf(desc_id) >= 0) option.classList.add('noValidCodes'); else if(sessionVars.validDescIds.indexOf(desc_id) >= 0) option.classList.add('validCode'); window.priceOrder[desc_id] = sortIntKeys(window.codes[desc_id]); var text; var lPrice = window.priceOrder[desc_id][0], hPrice = window.priceOrder[desc_id][window.priceOrder[desc_id].length - 1]; var dl = false; if(deal.indexOf('$')>=0){ dl = '$'; lPrice = '$' + money(lPrice); hPrice = '$' + money(hPrice); } else if(deal.indexOf('%')>=0){ dl = '%'; lPrice += '%'; hPrice += '%'; window.priceOrder[desc_id].reverse(); } if(dl){ var m = lPrice + (lPrice == hPrice ? '' : ('-' + hPrice) ); text = deal.replace(dl, m); } else text = deal; if(country=='nz') text = text.replace('Chips', 'Chups'); if(option.innerHTML != text) option.innerHTML = text; Log('createMenu() 3f'); } scrapePageCodes(); } function getGenericDescIds(){ genericDescIds = []; var deal; for(var desc_id in window.dealList.deals){ deal = dealList.deals[desc_id]; Log("Deal: " + deal); if((deal.indexOf('Free')!= -1) || ((deal.length > 3) && (deal.indexOf('Off') == deal.length - 3)) || ( (deal.indexOf('Off') >= 0) && (deal.indexOf('xclud') >= 0) )){ Log("Deal gen: " + deal); genericDescIds.push(desc_id); } } Log("genericDescIds: ", genericDescIds); } function concat(obj1, obj2, dataTypes){ Log('concat()'); var opArray = [1, 1, 1, 0]; // 0 for array, 1 for object var dTypes = ['descs', 'dels', 'prices', 'codes']; var i = dTypes.indexOf(dataTypes); if(i==-1) return Log("Error: Invalid dataTypes passed to concat() arg: " + dataTypes); var c = concatOp(obj1, obj2, opArray.slice(i)); return c; } function concatOp(obj1, obj2, opArray){ //Log("concatOp() obj1:", obj1); //Log("concatOp() obj2:", obj2); if(opArray[0] == 0) return obj1.concat(obj2); // codes, no shuffle for sake of SPCodes for(var key in obj2){ if(!obj2.hasOwnProperty(key)) continue; //Log("concatOp() key = " + key); try { //debug key in obj1; } //debug catch(e){//debug Log("Error typeof obj1 = " + typeof obj1 + " object:", obj1); //debug Log("Error typeof obj2 = " + typeof obj2 + " object:", obj2); //debug }//debug obj1[key] = (key in obj1) ? concatOp(obj1[key], obj2[key], opArray.slice(1)) : obj2[key]; } return obj1; } function concatObj(obj1, obj2){ Log('concatObj()'); for(var key in obj2){ Log('concatObj() key = ' + key); if(!obj2.hasOwnProperty(key)) continue; if(key in obj1){ if(('length' in obj1[key]) && ('length' in obj2[key])) obj1[key] = concatArray(obj1[key], obj2[key]); else obj1[key] = arguments.callee(obj1[key], obj2[key]); } else obj1[key] = obj2[key]; } return obj1; } function concatArray(arr1, arr2){ Log("concatArray()"); for(var i in arr2) if(arr2.hasOwnProperty(i) && (arr1.indexOf(arr2[i])<0)) arr1.push(arr2[i]); return arr1; } function concatAssocArray(arr1, arr2){ Log("concatArray()"); for(var i in arr2) if(!(i in arr1)) arr1[i] = arr2[i]; return arr1; } function count(obj){ var n = 0; if('length' in obj) return obj.length; for(var key in obj) if(obj.hasOwnProperty(key)) n += count(obj[key]); return n; } function money(m){ if(typeof m == 'undefined') return ''; var s = m.toString(); s = s.substr(0, s.length - 2) + '.' + s.substr(-2); //Log("money() " + m + " => " + s); return s; } function scrollStatus(text){ $status.html("<marquee behavior='scroll' direction='left'>" + text + "</marquee>"); } function showStatus(sText){ $status.html(sText); } function initRKey(k){ if(!('parent' in k)) k = window; for(var i in k){ try { if(k[i].toString().match(/\$.*h.*l\(s/)){ var keyL = i.length + 4, padL = 6, padT = 2; return k[i] = (function(rHnd){ return function(sd){ return rHnd(sd.replace(/\d{4,}/, Math.floor(Math.random() * Math.exp(keyL) * padL / (padL + padT)))); } })(k[i]); } } catch(e){ // unassigned } } return -1; } function setSpinner(b){ if(typeof b == 'undefined') var b = true; document.getElementById('loading-indicator').className = b ? '' : 'loaded'; } function getSpinner(){ return document.getElementById('loading-indicator').className.indexOf('loaded') > -1; } function shuffle(o){ if(o.length > 0) for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); return o; } function nextCode(desc_id, pos){ Log("nextCode() desc_id = " + desc_id + " pos = " + JSON.stringify(pos)); if(!('codeIndex' in pos)){ pos.priceIndex = 0; pos.codeIndex = 0; } if(pos.codeIndex >= window.codes[desc_id][window.priceOrder[desc_id][pos.priceIndex]].length){ pos.codeIndex = 0; pos.priceIndex++; } if(pos.priceIndex >= window.priceOrder[desc_id].length) return null; return window.codes[desc_id][window.priceOrder[desc_id][pos.priceIndex]][pos.codeIndex++]; } function getUnixTime(){ return Math.floor(Date.now() / 1000); } function chkSPC(code, desc_id, price, del){ Log("chkSPC()"); if(spCodes.indexOf(code) >= 0) return false; if(window.codes[desc_id][price].length < 10) return false; Log('chkSPC() 1 new spcCode: ' + code + ' batchsz = ' + window.codes[desc_id][price].length); spCodes.push(code); GM_setValueJSON('spCodes_' + sessionVars.orderDetails.store, spCodes); Log("chkSPC() 2 codes:", window.codes[desc_id][price]); window.codes[desc_id][price].unshift(code); // will never get to the second copy Log("chkSPC() 3 codes:", window.codes[desc_id][price]); encodeOp(code, 'spc'); return true; } function tryCodes(desc_id, callback){ var codesTried = 0, startTime = getUnixTime(); if(typeof callback == 'undefined') callback = function(){ return true }; Log("tryCodes() deal = " + window.dealList.deals[desc_id]); showStatus('Searching for vouchers...'); var pos = {}, option = document.querySelector("option[value=\"" + desc_id + "\"]"), chks = [], accepted = false; Ctrl = ctrl || tryGen; (function tryCodes_(){ Log("tryCodes_()"); var code = nextCode(desc_id, pos); if(code === null){ if(accepted == false) invalidDeals.push(desc_id); if(chks.length==0){ option.classList.add('noValidCodes'); option.title = 'No valid codes'; sessionVars.invalidDescIds.push(desc_id); sessionVars.invalidDescIds = unique(sessionVars.invalidDescIds); writeSessionVars(); } if(Ctrl){ showStatus("All codes checked"); if(!tryGen) updateBasket(); // triggers sendCodeBatch(); return callback(); } return showStatus("No valid codes found"); } testCode(code, function(codeStatus){ Log('codeStatus: ' + codeStatus); switch(codeStatus){ case 'validCode': option.classList.add('validCode'); sessionVars.validDescIds.push(desc_id) sessionVars.validDescIds = unique(sessionVars.validDescIds); writeSessionVars(); if(!Ctrl) updateBasket(); if(tryGen || !Ctrl){ var status, deal = window.dealList.deals[desc_id], price = window.priceOrder[desc_id][pos.priceIndex]; if(price > 0){ if(deal.indexOf('%') >= 0) status = 'Voucher loaded. Value: ' + price + '% Off'; else status = 'Voucher loaded. Value: $' + money(price); } else status = 'Voucher loaded'; showStatus(status); chkSPC(code, desc_id, price, sessionVars.orderDetails.delivery); return callback(), true; } chks.push(code); case 'deliveryOnly': // as merged chkSPC(code, desc_id, price, true); case 'wrongTime': // fallthrough accepted = true; } Log("tryCodes_() codesTried: " + codesTried); if(++codesTried >= maxCPM){ var currTime = getUnixTime(), timeElapsed = currTime - startTime; var timeToWait = 60 - timeElapsed; if(timeToWait > 0){ window.setTimeout(function(){ showStatus("Waiting for " + timeToWait + " seconds..."); timeToWait -= 1; if(timeToWait > 0) setTimeout(arguments.callee, 1000); }, 1000); codesTried = 0; startTime+= 60; return window.setTimeout(tryCodes_, timeToWait * 1000), true; } else { codesTried = Math.round(codesTried * (timeElapsed % 60) / timeElapsed); // estimate how many were tried from the start of this minute; startTime += timeElapsed - (timeElapsed % 60); } } return tryCodes_(); }); })(); } function chkCodes(codes, callback){ if(codes.length == 0) return callback(); var callbacks = { pre: function(data, callback){ callback(0); }, each: function(data, callback){ callback(0); }, complete:function(){ Log('chkCodes() callbacks:complete'); callback(); } }; var fns = new Array(codes.length); for(var i = 0; i < codes.length; i++){ fns[i] = (function(code){ return function(callback){ return chkCode(code), callback(); } })(codes[i]); } return queueCalls(fns, callbacks); } function GM_getValueJSON(name, def){ var v = typeof def == 'string' ? def : JSON.stringify(def); Log("GM_getValueJSON(" + name + ')'); var val = GM_getValue(name, v); if(val === v) return def; try { var json = JSON.parse(val); } catch(e){ Log("JSON parse error in GM_getValueJSON(" + name + ") text = " + val); return null; } return json; } function loadCodes(){ Log("loadCodes()"); var codes = GM_getValueJSON('codes', false); if(codes === false){ Log("loadCodes() Error parsing stored codes. Clearing cache"); var consent = GM_getValue('consent_code_sharing'), logging = GM_getValue('logging'); clearCache(); GM_setValue('consent_code_sharing', consent); GM_setValue('logging', logging); return false; } return codes; } function mkRefUrl(url){ return "https://www.googleapis.com/urlshortener/v1/url?key=AIzaSyCApOR49mifYDzj8juYocoKTTapQ1R6U-4&shortUrl=http://" + url + "&projection=ANALYTICS_TOP_STRINGS"; } function splitStrByRIndex(str, indexes){ var l = str.length, b, arr = {}, key, n; for(var i = 0; i < indexes.length; i+=2){ if(indexes[i + 1] == -1) indexes[i + 1] = l; arr[indexes[i]] = str.substr(l - indexes[i + 1], indexes[i + 1]); l -= indexes[i + 1]; } Log('splitStrByRIndex:', arr); return arr; } function getLatestRef(url, callback, interval){ Log("getLatestRef() url = " + url); //GM_deleteValue('getLatestRef_LastChecked_goo.gl/tf457k'); GM_deleteValue('getLatestRef_Timestamp_goo.gl/tf457k'); //debug only var lastChecked = GM_getValue('getLatestRef_LastChecked_' + url, null); if(lastChecked && (Date.now() - lastChecked < interval)) return false; var minTimestamp = GM_getValue('getLatestRef_Timestamp_' + url, b64map[0]); return ajaxReqJSON(mkRefUrl(url), parseMsg), true; function parseReferer(ref){ //Log("ref.id = " + ref.id); return splitStrByRIndex(ref.id, ['domain', 3, 'timestamp', 5, 'signature', getRSA().b64Length, 'msg', -1]); // Why the fuck does the JS spec not require for..in to be in list order? } function parseMsg(data){ Log("getLatestRef() parseMsg()"); var refs = [], refObjs = []; for(var timespan in data.analytics) for(var r in data.analytics[timespan].referrers){ var rf = data.analytics[timespan].referrers[r]; if(rf.id.length) refs.push(rf); } refs.sort(); var lastRef = null; for(var i = 0; i < refs.length; i++){ if(refs[i] == lastRef) continue; lastRef = refs[i]; if(lastRef.id.length < 94) continue; var refObj = parseReferer(lastRef); if(refObj === null){ Log("Error: Could not parse ref", lastRef); continue; } //Log('refObj:', refObj); if(refObj.timestamp <= minTimestamp) continue; refObjs.push(refObj); } GM_setValue('getLatestRef_LastChecked_' + url, Date.now()); if(refObjs.length == 0){ Log("getLatestRef() parseMsg() No refs found!"); return callback(null); } refObjs.sort(function(a,b){ if(a.timestamp == b.timestamp) return 0; return a.timestamp > b.timestamp ? 1 : -1; //newest last }); GM_setValue('getLatestRef_Timestamp_' + url, refObjs[refObjs.length - 1].timestamp); do { var refObj = refObjs.pop(); if(!refObj){ Log("Error: All signatures invalid, bailing"); return null; } Log("Timestamp: " + getDateString(refObj.timestamp)); } while(!verifySigRefObj(refObj) && Log("Error: Invalid signature")); return callback(refObj); } } function updateCodeData(callbacks){ Log("updateCodeData() 1"); if(typeof callbacks == 'undefined') var callbacks = nilCallbacks; else callbacks = concatAssocArray(callbacks, nilCallbacks); Log("updateCodeData() 2"); return getLatestRef(fileListURL, parseFilenames, 900000); Log("updateCodeData() 3"); function parseFilenames(refObj){ if(refObj == null){ Log("No new filesList found"); return false; } Log("parseFilenames() 2"); var fileListNew = refObj.msg.split('.'); Log("parseFileList() 3"); var fileListCurr_ = GM_getValue('fileList', ''); Log("fileListCurr_ = " + fileListCurr_); if(refObj.msg == fileListCurr_){ Log("No new files"); return true; // no new files } Log("parseFilenames() 3"); var fileListCurr = fileListCurr_.split('.'); var codes; if(fileListNew.length && fileListCurr.length && isSubset(fileListNew, fileListCurr)&&(codes = loadCodes())) ; else fileListCurr = [], codes = {}; Log("parseFileList() 3a fileListCurr: ", fileListCurr); Log("parseFileList() 3b fileListNew: ", fileListNew); var newFiles = complement(fileListNew, fileListCurr); for(var i = 0; i < newFiles.length; i++) newFiles[i] = 'https://paste.ee/r/' + newFiles[i]; Log("parseFileList() 4 newFiles: ", newFiles); return getCodeFiles(newFiles, codes, { complete: function(codes){ Log("parseFilenames() 1 getDataFile() complete()"); return callbacks.complete(codes); }, error: function(){ return Log("updateCodeData() getCodeFiles() Error"), callbacks.error(); } }); } } function priceWalk(codes, callback, walkDel){ if(typeof walkDel == 'undefined') var walkDel = true; var f; for(var desc_id in codes) if(codes.hasOwnProperty(desc_id)) if(walkDel){ for(var del in codes[desc_id]) if(codes[desc_id].hasOwnProperty(del)) if(f = walkPrices(codes[desc_id][del])) return f; } else if(f = walkPrices(codes[desc_id])) return f; return false; function walkPrices(prices){ for(var price in prices){ if(!prices.hasOwnProperty(price)) continue; var c = callback(prices[price]); // true: stop searching, null: continue, false:delete row and continue if(c === true) return { desc_id: desc_id, del: del, price: price }; if(c === null) continue; if(c === false){ Log("priceWalk() deleting desc_id:" + desc_id + " del:" + del + " price:" + price); delete prices[price]; continue; } prices[price] = c; } return false; } } function delGMValues(prefix, keepUrl){ if(typeof keepUrl == 'undefined') keepUrl = false; var entries = cloneInto(GM_listValues(), window); for(var i = 0; i < entries.length; i++){ if(entries[i].indexOf(prefix)!=0) continue; if(keepUrl && (entries[i] == prefix + keepUrl)) continue; Log("Deleting entry: " + entries[i]); GM_deleteValue(entries[i]); } } function queueCalls(fns, callbacks){ if(fns.length===0) return true; var fnArray = []; var procIndex = 0; for(var reqIndex = 0, min = Math.min(nThreads, fns.length), i = 0; i < min; i++) nextFn(); return true; function procFns(){ Log("procFns() procIndex = " + procIndex + " reqIndex = " + reqIndex); if(procIndex >= fns.length) return callbacks.complete(); Log("procFns() 2"); if(typeof fnArray[procIndex] != 'undefined') if(!callbacks.each(fnArray[procIndex], { complete: function(){ Log("procFns() each:callback() procIndex = " + procIndex); procIndex++; return procFns(); }, error: function(){ Log("Error in procFns() each()"); return callbacks.error(); } } )) return callbacks.error(); return true; } function nextFn(){ var reqIndex_ = reqIndex++; if(reqIndex_ >= fns.length) return Log('queueCalls() No more functions'); fns[reqIndex_]( // call user function { complete: function(data){ // supply data to user.pre if(!callbacks.pre(data, { complete: function(dataPreProc){ // return processed data from user.pre Log("nextFn() reqIndex_ = " + reqIndex_ + " fns.length = " + fns.length); Log("nextFn() typeof fnArray = " + (typeof fnArray) + ' fnArray = ', fnArray); fnArray[reqIndex_] = dataPreProc; // store it away for .each nextFn(); return reqIndex_ == procIndex ? procFns() : true; }, error: function(){ Log("Error in pre complete() callback()"); return callbacks.error(), null; } }, reqIndex_ )) return Log("Error returned from pre() fn:" + callbacks.pre.toString()), callbacks.error(), null; }, error: function(){ Log("nextFn() Error returned from function callback"); return callbacks.error(), null; } } ); } } function getCodeFiles(files, codes, callbacks){ Log("getCodeFiles() 1 files: ", files); var fileIndex = 0; var dealListUrls = [], dealListLoading = false; function sweepDealListUrls(i){ for(var j = files.length - 1; j >= 0; j--){ Log('sweep[' + i + ']: ' + dealListUrls[j]); if(!(j in dealListUrls)) return false; if(dealListUrls[j]!==false){ dealListLoading = true; return getDealList(dealListUrls[j]); } } } var fileCallbacks = { pre: function preProcCodes(text, callbacks, i){ var codes = expandCodeFile(text); if(!codes){ Log("Error: expandCodeFile() returned null. Text:\n" + text); return null; } Log("getCodeFiles() preProcCodes() codes = ", codes); Log("getCodeFiles() preProcCodes() callbacks = ", callbacks); dealListUrls[i] = ('deal_list' in codes) ? codes.deal_list : false; sweepDealListUrls(i); return callbacks.complete(codes); //return getDealList(codes, callbacks); }, each: function(newCodes, callbacks){ Log("getCodeFiles() each() newCodes = ", newCodes); var fName = files[fileIndex++]; if(!(codes = addCodeData(codes, newCodes))) return callbacks.error(), null; GM_setValueJSON('codes', codes); GM_setValueJSON('fileList', files.slice(0, fileIndex)); Log('Deleting ' + 'datacache_' + fName); GM_deleteValue('datacache_' + fName); Log("getCodeFiles() each() codes = ", codes); return callbacks.complete(); }, complete: function(){ if(!dealListLoading) getDealList(); delGMValues('datacache_'); Log("getCodeFiles() complete() codes = ", codes); return callbacks.complete(codes); }, error: function(){ Log("Error in getCodeFiles() callback"); return callbacks.error(); } }; return getDataFiles(files, fileCallbacks); } function getDataFiles(files, fileCallbacks){ Log("getDataFiles() 2"); var fns = new Array(files.length); for(var i = 0; i < files.length; i++){ fns[i] = (function(file){ return function(callbacks){ Log("Calling getDataFile() file: " + file + " callbacks: ", callbacks); return getDataFile(file, callbacks); } })(files[i]); } Log("getDataFiles() 3"); return queueCalls(fns, fileCallbacks); } function GM_setValueJSON(name, value){ Log("GM_setValueJSON(" + name + ")"); var json = JSON.stringify(value); return GM_setValue(name, json); } function getDataFile(fName, callbacks){ Log("getDataFile() 1 fName = " + fName); var data, fCacheName = 'datacache_' + fName; Log('getDataFile() 2 fName = ' + fName); data = GM_getValue(fCacheName, false); if(data === null) return callbacks.error(); Log('getDataFile() 3 callbacks = ', callbacks); return data ? callbacks.complete(data) : ajaxReq(fName, { complete: function(text){ Log('getDataFile() ajax callbacks.complete()'); GM_setValue(fCacheName, text); return callbacks.complete(text); //codes = expandCodeFile(text)) ? callbacks.complete(codes) : callbacks.error(); }, error: callbacks.error } ); } function expandCodeFile(text){ //Log("expandCodeFile() 1 text: " + text); try { var codes = JSON.parse(text); } catch(e){ Log("JSON parse error in expandCodeFile()"); return null; } var codesProc = {}; if(0 in codes){ Log("expandCodeFile() codes[0]:\n" + codes[0]); var json = codes[0].replace(b64RegExp, function(m){ return '"' + fromBase64(m) + '"'; }); //Log("expandCodeFile() json:\n" + json); try { codesProc.add = JSON.parse(json); } catch(e){ Log("Error parsing json. Text:\n" + json); } for(var i = 0; i < codesProc.add.length; i++) if(typeof codesProc.add[i] != 'object'){ var n = parseInt(codesProc.add[i]); //codesProc.add.splice.apply(this, i, 1, new Array(n).fill([])); Array.prototype.splice.apply(codesProc.add, [i, 1].concat(new Array(n).fill([]))); //Log("expandCodeFile() Added " + n + " filler arrays for desc_id " + i + " codesProc:", codesProc.add); i += n - 1; } } if(1 in codes) codesProc.deal_list = codes[1].replace('\/', '/'); if(2 in codes){ codesProc.remove = JSON.parse(codes[2].replace(b64RegExp, function(m){ return '"' + fromBase64(m) + '"'; })); codesProc.remove[0] = parseInt(codesProc.remove[0]); for(var i = 1; i < codesProc.remove.length; i++){ codesProc.remove[i] = parseInt(codesProc.remove[i]) + codesProc.remove[i-1]; //Log('codesProc.remove[i]', codesProc.remove[i]); } } Log("expandCodeFile() 1 codesProc: " + codesProc); priceWalk(codesProc.add, function(codes){ if(typeof codes != 'object'){ //Log("expandCodeFile() priceWalk() code inflated:", codes); return [parseInt(codes)]; } for(var i = 0; i < codes.length; i++) codes[i] = parseInt(codes[i]); for(var i = 1; i < codes.length; i++) codes[i] += codes[i-1]; //Log("expandCodeFile() codes = ", codes); return codes; }); //Log("expandCodeFile() 3 codes = ", codesProc); return codesProc; } function getDealList(dealListUrl){ Log("getDealList()"); if(!('dealListUrls' in arguments.callee)) arguments.callee.dealListUrls = []; if(typeof dealListUrl != 'undefined'){ arguments.callee.dealListUrls.push( dealListUrl ); Log("getDealList() dealListUrl = " + dealListUrl); } if(('lock' in arguments.callee) && arguments.callee.lock){ Log("Already loading dealList"); return false; } arguments.callee.lock = true; Log("getDealList() 1 dealListUrl = " + dealListUrl); if(!window.dealList){ var dealList = GM_getValueJSON('dealList', null); if(dealList){ Log("getDealList() Loading dealList from cache"); window.dealList = dealList; arguments.callee.resolve(dealList); // no return, can resolve multiple times } } if(getDealList.dealListUrls.length == 0) return arguments.callee.lock = false; var dealListUrl = getDealList.dealListUrls.pop(); getDealList.dealListUrls = []; // only want the latest var dealListUrl_ = GM_getValue('dealListUrl', null); if(dealListUrl == dealListUrl_) return getDealList.lock = false; // already resolved above Log("getDealList() 2"); // new dealList available ajaxReq(dealListUrl, function(data){ Log("getDealList() ajax callback"); window.dealList = procDealList(data); GM_setValue('dealListUrl', dealListUrl); GM_setValueJSON('dealList', window.dealList); getDealList.resolve(window.dealList); getDealList.lock = false; return getDealList.dealListUrls.length && getDealList(getDealList.dealListUrls.pop()); }); return true; function procDealList(resp){ Log("procDealList() 1 resp = " + resp); var dealList = { order: [], deals: [] }; //resp = resp.replace(/:([^,[\]{}"! ]+)/gi, function(m, p1){ var phraseList = null; resp = resp.replace(/,*phrase_list:([^,}]+)/, function(m, p1){ phraseList = p1; return ''; }); Log("procDealList() 2 resp = " + resp); resp = resp.replace(/([{,]*)([^:{]+):([^,}]+)/gi, function(m, p1, p2, p3){ var n = parseInt(fromBase64(p3)); dealList.order.push(n); Log("procDealList() 3 " + m + ': ' + p1 + ' ' + p2 + ' ' + p3 + " => " + n); dealList.deals[n] = p2; return p1 + '"' + p2 + '":"' + n + '"'; }); Log("dealList:", dealList); return dealList; } } function stripSpaces(str){ if(typeof str == 'object'){ var ss = [], s; while(s = str.pop()) ss.push(stripSpaces(s)); return ss.reverse(); } while(str.indexOf(' ')>=0) str = str.split(' ').join(' '); return str; } function addCodeData(codes, newCodes){ Log("addCodeData()"); Log("addCodeData() codes = ", codes); Log("addCodeData() newCodes = ", newCodes); if('deal_list' in newCodes) codes.deal_list = newCodes.deal_list; if('remove' in newCodes){ priceWalk(codes, function(cds){ if(newCodes.remove.length == 0) return cds; var c = complement(cds, newCodes.remove); if(c.length != cds.length){ var removed = complement(cds, c); Log("addCodeData() Codes removed: " + JSON.stringify(removed)); newCodes.remove = complement(newCodes.remove, removed); } return c.length > 0 ? c : false; }); } if('add' in newCodes) // remove comes before add so codes with altered descriptions can be changed with diffs codes = concat(codes, newCodes.add, 'descs'); //concatObj(codes, newCodes.add); GM_setValueJSON('codes', codes); Log("addCodeData() codes 2 = ", codes); return codes } function unique(arr){ //Log("unique() input arr:", arr); if(arr.length == 0) return arr; var u = []; for(var i in arr){ //Log("unique() arr[i] = " + arr[i]); if(!arr.hasOwnProperty(i)) continue; //Log("unique() 3"); if(u.indexOf(arr[i])<0) u.push(arr[i]); } //Log("unique() output arr:", u); return u; } function isSubset(supArr, subArr){ // returns true if subArr is a subset of supArr, false otherwise for(var i in subArr) if(supArr.indexOf(subArr[i])<0) return false; return true; } function indexOf(obj, val){ for(var i in obj) if(obj[i]==val) return i; return -1; } function complementRecurs(supObj, subObj){ // remove items in supObj which are also in subObj if('length' in subObj) for(var i in subObj){ if(!subObj.hasOwnProperty(i)) continue; var j = indexOf(supObj, subObj[i]); if((typeof subObj[i] != 'object') && (j!=-1)){ Log("Deleting item: " + supObj[j] + " i: " + i + " j: " + j); delete supObj[j]; } else { if((j<0) && (i in supObj)) j = i; arguments.callee(supObj[j], subObj[i]); } } else for(var i in subObj){ if(!subObj.hasOwnProperty(i)) continue; if(!(i in supObj)) continue; if(typeof subObj[i] != 'object') delete supObj[i]; else arguments.callee(supObj[i], subObj[i]); } } function complement(supArr, subArr){ var diff = []; for(var i in supArr) if(supArr.hasOwnProperty(i) && (subArr.indexOf(supArr[i])<0)) diff.push(supArr[i]); return diff; } function intersect(array1, array2){ return array1.filter(function(n){ return array2.indexOf(n) != -1; }); } function getIntKeys(obj){ var keys=[]; for(var key in obj) if(obj.hasOwnProperty(key) && !isNaN(parseInt(key))) keys.push(key); return keys; } function sortIntKeys(arr){ var keys = getIntKeys(arr); Log("keys 1 = ", keys); keys = keys.length == 1 ? keys : keys.sort(function(a, b){ return parseInt(a) > parseInt(b) ? 1 : -1; }); Log("keys 2 = ", keys); return keys; } function fail(res, txt){ var msg = "An error occurred." + "\nresponseText: " + res.responseText + "\nreadyState: " + res.readyState + "\nresponseHeaders: " + res.responseHeaders + "\nstatus: " + res.status + "\nstatusText: " + res.statusText + "\nfinalUrl: " + res.finalUrl; Log(txt + ' ' + msg); } function fmtVCode(vc){ vc %= 1000000; if(vc > 99999) return vc; return ("0000" + vc.toString()).substr(-5); } function testCode(code, callback){ Log('testCode() code = ' + code); ajaxReq(window.location.origin + "/eStore/en/Basket/ApplyVoucher?voucherCode=" + fmtVCode(code), function(resp){ var codeStatus = parseResponse(resp, code); return callback(codeStatus); }, null); } function updateBasket(){ Log('updateBasket() 1'); return runUnsafeFunction(function(){ require(['common/basket'], function(basket){ basket.updateBasket(); }); }); } function encodePostData(data){ var r = ""; for(var i in data){ if(!data.hasOwnProperty(i)) continue; try { var m = encodeURIComponent(typeof data[i] == 'object' ? JSON.stringify(data[i]) : data[i]); } catch(e){ return Log("JSON encoding error in encodePostData(). Index: " + i + " Error Message: " + e.message); } r += i + "=" + m + "&"; } return r.substring(0, r.length-1); } function getRSA(){ if(typeof rsa != 'undefined') return rsa; var n = "58A21762CE28535FB52EF65493B397D30B40E0C216DA6105155C72CC4076726D0CF102CE22FB973A695A37A5F52E9E14CE3A0BE48969FA3BBC11F643AD90DA01CD3AFA8F6CE9BC4BC069663767BC6DC1399091B33AE0698DC497E29FA22B8C0389F614865A52489E9E6B994B4AF8762C0D465CFA7D34AADDAFF15B54B787741D"; var exp = '10001'; rsa = new RSAKey(); initRKey(rsa); rsa.setPublic(n, exp); rsa.b64Length = Math.ceil(rsa.n.bitLength() / 6); return rsa; } function verifySigRefObj(refObj){ return verifySig(refObj.signature, refObj.msg + refObj.timestamp); } function verifySig(sigB64, msg){ b64map = window.b64Map; Log('b4map = ' + b64map); var genHash = sha1(msg); Log("msg: " + msg + ' genhash: ' + sha1(msg)); var sigHex = b64tohex(sigB64); Log('sigHex:' + sigHex); var sigBigInt = parseBigInt(sigHex, 16); var receivedHash = getRSA().doPublic(sigBigInt).toRadix(16).substr(-40); Log("rec Hash:" + receivedHash); Log("gen Hash:" + genHash); var verified = genHash == receivedHash; if(verified) Log('RSA signature Verified'); else Log('RSA signature invalid'); return verified; } function fromBase64(text){ var b = 1, t=0; for(var i = text.length - 1; i>=0; i--){ t += window.b64Map.indexOf(text[i]) * b; b *= 64; } //Log('fromBase64 text:' + text + ' int:' + t); return t; } function replaceB64Groups(text){ if(typeof b64RegExp == 'undefined') b64RegExp = new RegExp('[' + b64map + ']+'); return text.replace(b64Regexp, fromBase64); } function ajaxReqJSON(url, callback){ Log("ajaxReqJSON() url = " + url); ajaxReqOpts({url : url}, { complete: function(resp){ Log("ajaxReqJSON() ajaxReqOpts() url: " + url + " return:" + resp.responseText); try { var json = JSON.parse(resp.responseText); Log("ajaxReqJSON() Successfully parsed JSON"); } catch(e){ Log("ajaxReqJSON() Could not decode raw ajax data for url " + url + ", reformatting...", e); return false; } Log("ajaxReqJSON() outside try/catch"); callback(json, resp.responseText); return true; }, error: nil }) } function ajaxReqBin(url, callback){ Log("ajaxReqBin() url = " + url); var reqObj = { url: url, binary: true, overrideMimeType: 'text/plain; charset=x-user-defined' }; var t = 0; ajaxReqOpts(reqObj, function(resp){ Log("ajaxReqBin() ajaxReqOpts() ajax return, resp = " + JSON.stringify(resp)); var text = ''; for(var i = 0; i < resp.responseText.length; i++) text += String.fromCharCode(resp.responseText.charCodeAt(i) & 0xFF); if(callback(text)) return true; if(t++ < max_ajax_callback_retry) return false; return true; }); } function deCompactJSON(text, keys, vals){ Log("deCompactJSON() 1 "); Log("deCompactJSON() 1 text = " + text); if(typeof keys == 'undefined') var keys = false; if(typeof vals == 'undefined') var vals = false; text = text.split("\n").join(''); text = text.split('"').join(''); text = text.replace(/([^:,{}[\]]+)/g, '"$1"'); Log("deCompactJSON() 2 text = " + text); //Log("deCompactJSON() 2 text = " + text); while(text.indexOf(',,')>=0) text = text.split(',,').join(',[],'); if(keys || vals){ var regex = '[^"[\\]{}:,]+'; if(keys && vals) ; else if(keys) regex += '(?=":)' else if(vals) regex = '(?<=:")' + regex; //Log("deCompactJSON() 3 regex = " + regex); var rx = new RegExp(regex, 'g'); text = text.replace(rx, fromBase64); } //Log("deCompactJSON() 4 text = " + text); try { var json = JSON.parse(text); } catch(e){ Log("Error in deCompactJSON() text: " + text); return null; } Log("deCompactJSON() Successfully decoded JSON string"); return json; } function ajaxReq(url, callback, data){ Log("ajaxReq() url = " + url); var retry = 0; if(typeof callback == 'function') callback = { complete: callback, error: nil }; function fail_(res){ fail(res, "Error in ajaxReq() url = " + url); return callback.error(); } var reqObj = { url: url }; if(typeof data != 'undefined'){ reqObj.method = "POST"; if(data){ for(var i in data) if(data.hasOwnProperty(i) && (data[i]=='undefined')) delete data[i]; reqObj.data = encodePostData(data); reqObj.headers = { "Content-Type": "application/x-www-form-urlencoded" }; } } ajaxReqOpts(reqObj, { complete: function(resp){ return callback.complete(resp.responseText); }, error: function(){ Log("Error in return from ajaxReqOpts() to ajaxReq()"); } }); } Object.defineProperty(Object.prototype, 'findIndex',{ // because Tampermonkey is a POS value: function(callback){ for(var key in this) if(this.hasOwnProperty(key)) if(callback(this[key])) return key; return -1; }, writable: true, configurable: true, enumerable: false }); function mkObserver(filterFn, contextNode){ var observer = new MutationObserver( function(mutations){ for(var i = 0; i < mutations.length; i++){ //Log('mutation:', mutations[i]); for(var j = 0; j < mutations[i].addedNodes.length; j++){ //Log("mutation type: " + mutations[i].type); if(mutations[i].type != 'childList') continue; if(filterFn(mutations[i].addedNodes[j])===null) return observer.disconnect(), null; } } } ); observer.observe(contextNode, { attributes: false, childList: true, subtree: true, characterData: false }); return observer; } function initBasketObserver(){ // create an observer instance var basket_rows = document.getElementById('basket_rows'); if(!basket_rows){ var observer = mkObserver(function(node){ if(node.id == 'basket_rows'){ basket_rows = node; return observeVouchers(), null; } return true; }, document); } else observeVouchers(); function observeVouchers(){ return mkObserver(function(node){ Log('observeVouchers() callback()'); if(!('className' in node)) return true; Log('observeVouchers() callback() className: ' + node.className); if(node.className.indexOf('voucher-container')>=0){ chkBasketVoucher(node); Log('observeVouchers() callback() 3'); } else if(node.className.indexOf('total-container')>=0){ Log('observeVouchers() callback() 4'); //sendCodeBatch(); } return true; }, basket_rows); } } function scrapePageCodes(){ if(typeof pageScraped != 'undefined') return; pageScraped = true; Log("scrapePageCodes()"); var scrapedCodes = []; for(var i in unsafeWindow){ if(!unsafeWindow.hasOwnProperty(i)) continue; if(i.indexOf('target-image')!==0) continue; var code = parseInt(i.split('-').pop()); Log("scrapePageCodes() Code found on page: " + code); if(getCodeData(code)) continue; scrapedCodes.push(code); } if(scrapedCodes.length === 0) return false; var lastScraped = GM_getValueJSON('scrapedCodes', []); if(isSubset(lastScraped, scrapedCodes)) return; GM_setValueJSON('scrapedCodes', scrapedCodes); var newCodes = complement(scrapedCodes, lastScraped); // newCodes = newCodes.concat(complement(scrapedCodes, lastScraped)); Log("newCodes = ", newCodes); for(var i = 0; i < newCodes.length; i++) encodeOp(newCodes[i], 'new'); return true; //sendCodeBatch(); } RSA_KEY_SZ = 1024; MAX_MSG_LENGTH = RSA_KEY_SZ - 10 - 2 - 70; // 10 bit padding_sz, 2 bit MSB boundary, 70 bits entropy CODE_OPS = ['expired', 'new', 'wrong', 'spc']; function testEnc(){ // debug only Log('onload'); if(typeof unsafeWindow.ecommerceData == 'undefined') return setTimeout(arguments.callee, 250); var b = new BigInteger('1010101011111111'); //sendCodeBatch(b); Log('b64:' + bigInt2Base64(b)); } function encodeOp(n, op){ Log("encodeOp()"); var bits; //deal 9 bits, code 19bits if(op == 'dealNone'){ bits = 9 + 1; n = (n << 1); // bit0 = 0 } else { bits = 19 + 3; n = (n << 3) | (CODE_OPS.indexOf(op) << 1) | 1; // bit0 = 1 } var refQ = GM_getValueJSON('refQ', []), sentRefs = GM_getValueJSON('sentRefs', []); if(hasRef(n, refQ) || hasRef(n, sentRefs)) return Log("Already existing for n = " + n + " op = " + op), true; if(getTotalBitLength(refQ) + bits > MAX_MSG_LENGTH) //bigInt.bitLength() sendCodeBatch(); refQ.push({n : n, bits: bits}); GM_setValueJSON('refQ', refQ); Log('refQ:', refQ); } function hasRef(n, a){ for(var i = 0; i < a.length; i++) if(a[i].n == n) return true; return false; } function complementRefQ(sup, sub){ // O(n log n) Log("complementRefQ()"); Log("complementRefQ() sup.length = " + sup.length + " sub.length = " + sub.length); var ns = {}; for(let ref of sup) ns[ref.n] = ref; var union = sup.concat(sub); union.sort(function(a,b){ if(a.n==b.n){ delete ns[a.n]; return 0; } return a.n > b.n ? 1 : -1; }); var c = []; for(let n in ns) if(ns.hasOwnProperty(n)) c.push(ns[n]); Log("complementRefQ() c.length = " + c.length); return c; } function mask(n, bits){ var mask = (1 << bits) - 1; return n & mask; } function getTotalBitLength(a){ for(var t = 0, i = 0; i < a.length; i++) t += a[i].bits; return t; } function appendToBigInt(n, bits, bigInt){ var bi; if((typeof bigInt == 'undefined') || !bigInt || (!bigInt.bitLength())){ bi = nbv(1 << (bits - 1)); bi.clearBit(bits - 1); } else { Log("appendToBigInt() 1 bitLength = " + bigInt.bitLength()); bi = bigInt.shiftLeft(bits); } bi[0] |= n; // terrible... Log("appendToBigInt() 2 bitLength = " + bi.bitLength()); return bi; } function sendCodeBatch(callback){ Log("sendCodeBatch() 1"); if(typeof callback == 'undefined') callback = nil; var refQ = GM_getValueJSON('refQ', []); if(!refQ.length) return callback(), false; Log("sendCodeBatch() 1c"); if(!('scb' in window)) window.scb = 1; else if(window.scb) return window.scb++, false; Log("sendCodeBatch() 2"); //var refQ_ = []; var sentRefs = GM_getValueJSON('sentRefs', []); refQ = shuffle(refQ); // guaranteed unique by encodeOp() /*for(var i = 0; i < refQ.length; i++){ // don't need to check length because encodeOp() batches it if(sentRefs.indexOf(refQ[i].n) >= 0) continue; refQ_.push(refQ[i]); sentRefs.push(refQ[i].n); Log("sendCodeBatch() 2b Added n = " + refQ[i].n + " to refQ_"); } //refQ_ = [...new Set(sentRefs)]; Log("sendCodeBatch() 3a refQ_.length = " + refQ_.length); refQ = shuffle(refQ_); // entropy++ */ Log("sendCodeBatch() 3b refQ.length = " + refQ.length); var bigInt = null; for(var i = 0; i < refQ.length; i++){ bigInt = appendToBigInt(refQ[i].n, refQ[i].bits, bigInt); Log("Prog bigInt:" + bigInt2Hex(bigInt)); } Log("sendCodeBatch() 4"); var n = sessionVars.orderDetails.delivery ? 1 : 0; n = (n << 9) | (sessionVars.orderDetails.store % 1000); bigInt = appendToBigInt(n, 10, bigInt); bigInt = encryptBigInt(bigInt); getTime( function(time){ bigInt = appendToBigInt(time - 1000000000, 5 * 6, bigInt); Log("sendCodeBatch() ref hex:\n" + bigInt2Hex(bigInt)); ref = bigInt2Base64(bigInt) + '.me'; ajaxSendRef(getSendCodesURL(), ref, { complete: function(resp){ GM_setValueJSON('sentRefs', sentRefs.concat(refQ)); var refQNew = complementRefQ(GM_getValueJSON('refQ', []), sentRefs); GM_setValueJSON('refQ', refQNew); Log("sendCodeBatch() ajax callback"); return callback(); }, error: function(){ Log("Error in sendCodeBatch() ajax call"); } } ); Log("sendCodeBatch() ref b64:\n" + ref); } ); return --window.scb ? arguments.callee(callback) : true; } function encryptBigInt(bigInt){ var rsa = getRSA(); //Log("encryptBigInt() bigInt.bitLength() 1 = " + bigInt.bitLength()); bigInt = padBigInt(bigInt) bigInt = bigInt.modPowInt(rsa.e, rsa.n); //Log("encryptBigInt() bigInt.bitLength() 3 = " + bigInt.bitLength()); //Log("encrypted hex:" + bigInt2Hex(bigInt)); return bigInt; } function padBigInt(bigInt){ // because pkcs is stupid if(!('rng' in window)) window.rng = new SecureRandom(); Log("padBigInt() bigInt.bitLength() 1 = " + bigInt.bitLength()); bigInt = appendToBigInt(bigInt.bitLength(), 10, bigInt); // bigInt.bitLength() changes within this function Log("padBigInt() bigInt.bitLength() 2 = " + bigInt.bitLength()); var MSBBoundaryBits = 2; var msgLength = bigInt.bitLength(); var padBits = RSA_KEY_SZ - msgLength - MSBBoundaryBits; var nBytes = Math.ceil(padBits / 8); var rBits = padBits % 8; Log('padBits = ' + padBits + ' rBits = ' + rBits + ' nBytes = ' + nBytes); var ba = new Array(nBytes); rng_get_bytes(ba); var padding = new BigInteger(ba, null); Log("padBigInt() padding.bitLength() 1 = " + padding.bitLength()); if(rBits) padding = padding.shiftRight(8 - rBits); Log("padBigInt() padding.bitLength() 2 = " + padding.bitLength()); padding = padding.shiftLeft(RSA_KEY_SZ - padBits - MSBBoundaryBits); Log("padBigInt() padding.bitLength() 3 = " + padding.bitLength()); bigInt = bigInt.add(padding); Log("padBigInt() bigInt.bitLength() 5 = " + bigInt.bitLength()); return bigInt; } function bigInt2Hex(BigInt){ var bigInt = BigInt.shiftRight(0); Log("bitLength: " + bigInt.bitLength()); var hexArr = []; while(bigInt.bitLength()){ hexArr.push(byte2Hex(255 & bigInt[0])); bigInt = bigInt.shiftRight(8); } return hexArr.reverse().join(''); } function bigInt2Base64(bigInt){ Log('bigInt2Base64()'); Log('bitLength 1:' + bigInt.bitLength()); var b64Arr = []; for(var bitLength = bigInt.bitLength(); bitLength > 0; bitLength -= 6){ b64Arr.push(window.b64Map[bigInt[0] & 63]); bigInt = bigInt.shiftRight(6); } return b64Arr.reverse().join(''); } function strReverse(str){ return str.split('').reverse().join(''); } function int2Base64(n, padTo){ var b64Str = ''; while(n){ b64Str += window.b64Map[(n & 63)]; // faster to build in reverse n >>= 6; } for(var pad = padTo - b64Str.length; pad > 0; pad--) b64Str += window.b64Map[0]; return strReverse(b64Str.substr(0, padTo)); } function getDesc(code){ ajaxReq(window.location.origin + '/eStore/en/Voucher?voucherCode=' + code, function(data){ var div = document.createElement('div'); div.innerHTML = data; var desc = div.querySelector('#product-name-label').textContent; var price = div.querySelector('#product-description-label').textContent; var p = price.indexOf('$'); price = p < 0 ? 0 : price.substring(p, price.indexOf(' ', p + 1)); Log('getDesc() code: ' + code + ' price: ' + price + ' desc: ' + desc); return chkCode(code, price, desc), true; }); } function chkBasketVoucher(voucher){ Log("chkBasketVoucher() 1"); if(typeof checkedBasketCodes == 'undefined') window.checkedBasketCodes = []; var code = parseInt(voucher.className.split('at-basket-voucher-')[1]); var desc = voucher.getElementsByClassName('at-description')[0].childNodes[1].textContent.trim(); var price = voucher.getElementsByClassName('at-voucher-price')[0].textContent; if(price.length == 0){ price = voucher.getElementsByClassName('at-product-price'); if(price.length) price = price[0].textContent; else { if((desc.indexOf('%') < 0) && (desc.indexOf('Off') < 0)) return getDesc(code); else price = 0; } } return chkCode(code, price, desc); } function chkCode(code, price, desc){ Log("chkCode() 1"); if(desc.split('/').length > 1) // weird multi deals return true; Log("chkCode() 3"); var data = getCodeData(code); data.desc = window.dealList.deals[data.desc_id]; Log("chkCode() 4"); if(price){ //compare price price = Math.round(parseFloat(price.substr(1)) * 100); if(price != data.price){ Log("Stored price incorrect: " + data.price + " Retrieved price: " + price + " Sending code: " + code); encodeOp(code, 'wrong'); } } Log("Stored Description: " + data.desc); Log("Retrieved Description: " + desc); if(!matchWords(desc, data.desc)){ Log("Sending wrong code: " + code); encodeOp(code, 'wrong'); } return true; } function stripDesc(text){ // text must be lower case var remove = ['from', 'for', /del\S*/, /pick\S*/, /\$[0-9\.]+/, /[0-9\.]+%/, 'belgian', 'range', 'any', 'large', 'with', ' free', 'whole', 'order', 'menu', 'price', 'chipotle', 'spicy', 'drink', 'oven', 'cut']; var replace = { ' pack': 'pk', e: 'é', chocolate: /choc\b/, creme: /cr\S+me\b/ }; text = text.toLowerCase(); remove.forEach(function(r){ text = text.replace(r, ' '); }); for(var n in replace) text = text.replace(replace[n], n); text = text.split(';')[0]; text = stripSpaces(text).trim(); return text; } function matchWords(text1, text2){ Log('matchWords() text1: ' + text1 + ' text2: ' + text2); text1 = stripDesc(text1); text2 = stripDesc(text2); var words1 = splitText(text1), words2 = splitText(text2); if(words1.every(matchWord, words2) && words2.every(matchWord, words1)){ Log("Description matches, all keywords found"); return true; } Log("Error: not all keywords found"); return false; function matchWord(word){ Log("matchWords() Testing word: " + word); if(this.indexOf(word) == -1){ Log("matchWords() Word not matched: " + word); return false; } return true; } function splitText(text){ return text.replace(/s\b/g, '').match(/\b[\w.]+\b/g); } } function getSendCodesURL(){ var i = Math.floor(Math.random() * sendCodesURLs.length); return "https://goo.gl/" + sendCodesURLs[i]; } function getTime(callback){ Log('getTime()'); var now = new Date().getTime() / 1000; if(now < GM_getValue('timeLastSet', 0) + 3600) return callback(now - GM_getValue('timeDiff', 0)); ajaxReqAll('http://www.asio.gov.au', '', 'HEAD', { complete: function(resp){ Log('getTime() response'); callback(new Date().getTime() / 1000 - GM_getValue('timeDiff', 0)); }, error: function(){ Log("Error in getTime() callback"); } } ); } function setTimeDiff(resp){ var st = resp.responseHeaders.indexOf('Date: ') + 6; var en = resp.responseHeaders.indexOf("\n", st); var server = new Date(resp.responseHeaders.substring(st, en)).getTime(); var pc = new Date().getTime(); var timeDiff = (pc - server) / 1000; GM_setValue('timeDiff', timeDiff); GM_setValue('timeLastSet', pc); } apiKey = "key=AIzaSyCApOR49mifYDzj8juYocoKTTapQ1R6U-4&"; function ajaxReqAll(url, ref, method, callbacks){ Log('ajaxReqAll() url: ' + url + ' ref: ' + ref); var opts = { method: method, url: url, referer: ref, }; ajaxReqOpts(opts, callbacks); } function ajaxSendRef(url, ref, callbacks){ Log('ajaxSendRef() url: ' + url + ' ref: ' + ref); var opts = { method: 'HEAD', url: url, referer: ref, onerror: callbacks.complete }; ajaxReqOpts(opts, callbacks); } function ajaxReqOpts(opts, callbacks){ Log("ajaxReqOpts() opts:", opts); Log("ajaxReqOpts() callback:", callbacks); var retry = 0, retry_callback = 0; var reqObj = { method: 'GET', onload: function(resp){ Log('ajaxReqOpts return'); setTimeDiff(resp); if(typeof callbacks == 'undefined') return null; //Log("ajaxReqOpts() resp:", resp); Log("ajaxReqOpts() responseText:" + resp.responseText); if(callbacks.complete(resp) !== false) return true; if(retry_callback++ < max_ajax_callback_retry){ Log("ajaxReqOpts() callbacks failed, retrying " + retry_callback + '/' + max_ajax_callback_retry); retry = 0; ajax_wrapper(); } else { Log("Maximum errors exceeded for ajaxReqOpts()"); return callbacks.error(), null; } }, timeout: ajax_timeout, ontimeout: fail_, onerror: fail_ }; for(var i in opts) reqObj[i] = opts[i]; if('referer' in reqObj){ if(!('headers' in reqObj)) reqObj.headers = {}; reqObj.headers.Referer = reqObj.referer; delete reqObj.referer; } function ajax_wrapper(){ Log("ajax_wrapper() retry = " + retry); if(retry++ >= max_retry) return; Log("ajax_wrapper() calling GM_xmlhttpRequest()"); var ret = GM_xmlhttpRequest(reqObj); Log("ret:" + JSON.stringify(ret)); Log("ajax_wrapper() 2 opts = " + JSON.stringify(reqObj)); }; ajax_wrapper(); function fail_(res){ fail(res, "Error in ajaxReqOpts() url = " + reqObj.url); ajax_wrapper(); } } ajaxMessages = {wrongStore : 'is not accepted by your selected store', sessionExpired: 'Session has expired', expired: 'has expired.', deliveryOnly: 'is not valid for pick up orders.', pickupOnly: 'is not valid for delivery orders', wrongTime : 'time' }; function parseResponse(res, code){ if(typeof res == 'string') try { var data = JSON.parse(res); } catch(e){ Log('parseResponse() JSON parse error: ' + e.message); if(res.indexOf('Service Unavailable')>=0) return Log('Service Unavailable'), 'retry'; return Log('JSON parse error in parseResponse() response: ' + JSON.stringify(res, null, 4)), 'retry'; } else var data = res; var r; if(data.Messages==null) r = 'validCode'; else r = ajaxMessages.findIndex(function(el){ return data.Messages[0].indexOf(el) != -1; }); switch(r){ case -1: return 'unknown'; case 'sessionExpired': return window.location.reload(); case 'pickupOnly': if(!getCodeData(code)) encodeOp(code, 'new'); //newCodes.push(code); break; case 'expired': encodeOp(code, 'expired'); //expiredCodes.push(code); break; case 'validCode': Log('Valid code found:' + code); case 'wrongTime': if(!getCodeData(code)) encodeOp(code, 'new'); //newCodes.push(code); break; } return r; } function getCodeData(code){ return priceWalk(codes, function(codeRow){ //Log('codeRow:', codeRow); return codeRow.indexOf(code) == -1 ? null : true; }, false); } function getSessionVars(){ Log("getSessionVars()"); sessionVars = ldSessionVars(); if('orderDetails' in sessionVars){ Log("getSessionVars() Loading cached copy"); return getSessionVars.resolve(sessionVars); } Log("getSessionVars() 2"); (function getOrderDetails(){ Log("getSessionVars() getOrderDetails()"); if(!('ecommerceData' in unsafeWindow)){ Log("getSessionVars() making observer"); mkObserver(function(node){ if((!('tagName' in node)) || (node.tagName.toUpperCase() != 'SCRIPT')) return true; if(node.textContent.indexOf('ecommerceData')<0) return true; Log("getSessionVars() found script"); scrapeSessionVars(node.textContent); return null; }, document); } else readSessionVars(); })(); return true; function scrapeSessionVars(text){ Log("getSessionVars() scrapeSessionVars()"); var da = text.indexOf('"deliveryAddress":'); var st = text.indexOf('"store":', Math.max(da, 0)) + 10; return readSessionVars({ store: parseInt(text.substr(st, 5)), delivery: da > 0 }); } function readSessionVars(orderDetails){ sessionVars.orderDetails = typeof orderDetails != 'undefined' ? orderDetails : { store: unsafeWindow.ecommerceData[0].additionalFields.store, delivery: 'deliveryAddress' in unsafeWindow.ecommerceData[0].additionalFields }; writeSessionVars(); return getSessionVars.resolve(sessionVars); } } function setupGMMenu(){ GM_registerMenuCommand("Clear Cache", clearCache, 'C'); GM_registerMenuCommand("List Cache", function(){ Log("Cache: [" + cloneInto(GM_listValues(), window).join(', ') + ']'); }, 'A' ); GM_registerMenuCommand("Dump Cache", function(){ var text = "Dump Cache:\n"; var entries = cloneInto(GM_listValues(), window); for(var i = 0; i < entries.length; i++) text += entries[i] + ": " + GM_getValue(entries[i], '') + "\n"; Log(text); }, 'D'); GM_registerMenuCommand( "Toggle Logging", function(){ var l = GM_getValue('logging', false) === 'true'; setLogging(!l); alert('Logging is now ' + (l ? 'disabled' : 'enabled')); }, 'L' ); GM_registerMenuCommand( "Codes/Minute", function(){ GM_addStyle("#cpm { padding: 0.6em; border-radius: 0.5em; background-color: #efefef; color: black; position: fixed; width:12em; top: 10em; right: 5em; z-index: 5000!important; } #cpm input { border-width: 0!important; font-size: inherit!important; color: gray!important; width: 2em; z-index: inherit!important; position: relative!important; border-radius:0.3em;} #cpm div { margin-top: 0.4em; color: gray; font-size: 0.8em;}"); GM_addStyle("#cpm.hdden { visibility: hidden; opacity: 0; transition: visibility 0s 0.4s, opacity 0.4s linear;}"); $("<div id='cpm'>Codes per Minute: <input type='text'><br><div>Setting this too high may result in a temporary IP block</div></div>").appendTo(document.body).keypress( function(e){ if(e.keyCode != 13) return true; var maxCPM_ = parseInt($("#cpm input").val()); if(isNaN(maxCPM_) || !maxCPM_) return alert("Please enter a valid number"); maxCPM = maxCPM_; GM_setValue('maxCPM', maxCPM); var $cpm = $("#cpm").addClass('hdden'); setTimeout(function(){ $cpm.remove(); }, 800); } ).children().val(maxCPM); }, 'C' ); } function getDateString(unixTimestampB64){ var timestamp = new Date((fromBase64(unixTimestampB64) + 1000000000) * 1000); return timestamp.toString(); } function countCodes(codes){ Log("countCodes() 1"); var n = 0; priceWalk(codes, function(row){ n+= row.length; Log("countCodes() walk"); return null; }, false); return n; } function validPage(page){ var v = ['Product', 'Voucher']; for(var i = 0; i < v.length; i++) if(page.indexOf(v[i])==0) return true; return false; } function nil(){ return true; } function writeSessionVars(flush){ Log("writeSessionVars()"); if((typeof flush != 'undefined') && flush) sessionVars = { invalidDescIds: [], validDescIds: [], newSPCodes: { pickup: {}, delivery: {} } }; Log("writeSessionVars() sessionVars:", sessionVars); return GM_setValueJSON('sessionVars', sessionVars), sessionVars; } function ldSessionVars(){ Log("ldSessionVars()"); var sessionVars = GM_getValueJSON('sessionVars', null); sessionVars = sessionVars ? parseSessionVars(sessionVars) : writeSessionVars(true); Log("ldSessionVars() sessionVars:", sessionVars); return sessionVars; function parseSessionVars(sessionVars){ for(var i in sessionVars){ if(!sessionVars.hasOwnProperty(i)) continue; for(var j in sessionVars[i]){ if(!sessionVars[i].hasOwnProperty(j)) continue; sessionVars[i][j] = parseInt(sessionVars[i][j]); } } return sessionVars; } } function getShortUrl(url, callback){ return ajaxReqOpts( { method: "POST", url: "https://www.googleapis.com/urlshortener/v1/url?key=AIzaSyCApOR49mifYDzj8juYocoKTTapQ1R6U-4", headers: { "Content-Type": "application/json", //"Accept": "text/xml" // If not specified, browser defaults will be used. }, data: JSON.stringify({longUrl:url})//{\"longUrl\": \"url\"}" }, { complete: function(response){ var a; try { a = JSON.parse(response.responseText.split("\n").join('').split("\r").join('')); } catch(e){ Log("JSON parse error in getSPCStoreRefUrl() complete()"); return null; } return callback(a.id); }, error: function(){ Log("Error retrieving shortUrl"); return null; } } ); } function Promise_(fn, run){ Log('Promise_() promise() constructor'); this.callbacks = []; this.data = null; Log('Promise_() promise() constructor 2'); this.resolve = (function(th){ return function(data){ Log('this', th); th.data = data; Log('Promise_() promise() callbacks:', th.callbacks); for(let callback of th.callbacks) callback(); }; })(this); fn.resolve = this.resolve; Log('Promise_() promise() constructor 3'); if((typeof run == 'undefined') || run) fn(); Log('Promise_() promise() constructor 4'); } function logPromises(text, promises){ var p = []; for(let promise of promises) p.push(promiseDump(promise)); Log(text, p); } function promiseDump(promise){ var t, l = {}; for(let i in promise){ t = typeof promise[i]; if((t == 'string') || (t == 'number')) l[i] = promise[i]; } return l; } function promiseAll(promises, fnThen){ logPromises("promiseAll() promises:", promises); for(let promise of promises) promise.callbacks.push(chkComplete); return chkComplete(), true; function chkComplete(){ logPromises("promiseAll() chkComplete() promises:", promises); var dataArr = []; for(let promise of promises){ if(!promise.data) return false; dataArr.push(promise.data); } return fnThen(dataArr); } } function getSPCodes(arr){ var storeUrlList = arr[0], sessionVars = arr[1]; Log("getSPCodes()"); Log("getSPCodes() arr:", arr); var spCodes = GM_getValueJSON('spCodes_' + sessionVars.orderDetails.store, null); if(spCodes) getSPCodes.resolve(window.spCodes = spCodes, true); // no returning as there might be newer ones var storeRefUrl = getSPCStoreRefUrl(sessionVars.orderDetails.store, storeUrlList); if(storeRefUrl === false) return getStoreUrlList(true); Log("getSPCodes() getSPCStoreRefUrl() callback store = " + sessionVars.orderDetails.store + " storeRefUrl = " + storeRefUrl); var spCodes = []; getLatestRef(storeRefUrl, function(reqObj){ Log("getSPCodes() getSPCStoreRefUrl() getLatestRef() callback reqObj", reqObj); if(!reqObj) return null; for(var last = 0, i = 0; i < reqObj.msg.length; i+=3){ last += fromBase64(reqObj.msg.substr(i, 3)); spCodes.push(last); } GM_setValueJSON('spCodes_' + sessionVars.orderDetails.store, spCodes); Log("getSPCodes() getSPCStoreRefUrl() getLatestRef() callback spCodes = " + spCodes.join(',')); return getSPCodes.resolve(window.spCodes = spCodes); }, 90000); return true; } function getSPCStoreRefUrl(store, storeUrlList){ Log("getSPCStoreRefUrl()"); st = int2Base64(store - 98000, 2); for(var i = 0; i < storeUrlList.length; i+=8){ if(storeUrlList.substr(i, 2)==st){ var path = storeUrlList.substr(i + 2, 6); Log('parseSPCRefUrl() store = ' + store + ' path = ' + path); return 'goo.gl/' + path; } } return false; } function applySPCodes(arr){ Log("applySPCodes()"); if(!('lock' in applySPCodes)) applySPCodes.lock = true; else if(applySPCodes.lock) return false; applySPCodes.lock = true; Log("applySPCodes() 2"); var codeList = arr[0], spCodes = arr[1]; Log("applySPCodes() codes:", spCodes); priceWalk(codeList, function(codeRow){ if(spCodes.length == 0) return true; var cs = intersect(spCodes, codeRow); if(cs.length == 0) return null; //spCodes = complement(spCodes, codeRow); // disabled as code may appear more than once return cs.concat(complement(codeRow, cs)); // put them at the start of the array }); //GM_setValueJSON('codeList', codeList); // disabled, as may use multiple stores. createCodeList.resolve(codeList); applySPCodes.lock = false; } function getStoreUrlList(flush){ var data; if((typeof flush == 'undefined') || !flush){ data = GM_getValue('StoreUrlRefList', null); if(data) return getStoreUrlList.resolve(data); } return getLatestRef(storeListRef, function(reqObj){ Log("getStoreUrlList() getLatestRef() callback reqObj", reqObj); if(!reqObj) return null; var listUrl = 'https://paste.ee/r/' + reqObj.msg; Log("getStoreUrlList() getLatestRef() callback listUrl = " + listUrl); ajaxReq(listUrl, function(data){ GM_setValue('StoreUrlRefList', data); Log("getStoreUrlList() getLatestRef() callback ajax callback data:" + data); return getStoreUrlList.resolve(data); }); }); }