您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Makes it easier to shop for groceries on tesco.com
当前为
// ==UserScript== // @name Tesco Delivery Slots // @author Than // @version 0.02 // @description Makes it easier to shop for groceries on tesco.com // @match https://*.tesco.com/groceries/* // @include https://*.tesco.com/groceries/* // @include https://secure.tesco.com/account/en-GB/login* // @connect tesco.com // @grant GM.xmlHttpRequest // @grant unsafeWindow // @grant GM_addStyle // @grant GM_notification // @grant GM_setClipboard // @run-at document-end // @namespace https://gf.qytechs.cn/users/288098 // ==/UserScript== (function() { // 'use strict'; try { /*-------------------------------------------------------------------------------------------------------------------- ------------------------------------------- General functions -------------------------------------------------- --------------------------------------------------------------------------------------------------------------------*/ // var Email = { send: function (e, o, t, n, a, s, r, c) { var d = Math.floor(1e6 * Math.random() + 1), i = "From=" + e; i += "&to=" + o, i += "&Subject=" + encodeURIComponent(t), i += "&Body=" + encodeURIComponent(n), void 0 == a.token ? (i += "&Host=" + a, i += "&Username=" + s, i += "&Password=" + r, i += "&Action=Send") : (i += "&SecureToken=" + a.token, i += "&Action=SendFromStored", c = a.callback), i += "&cachebuster=" + d, Email.ajaxPost("https://smtpjs.com/v2/smtp.aspx?", i, c) }, sendWithAttachment: function (e, o, t, n, a, s, r, c, d) { var i = Math.floor(1e6 * Math.random() + 1), m = "From=" + e; m += "&to=" + o, m += "&Subject=" + encodeURIComponent(t), m += "&Body=" + encodeURIComponent(n), m += "&Attachment=" + encodeURIComponent(c), void 0 == a.token ? (m += "&Host=" + a, m += "&Username=" + s, m += "&Password=" + r, m += "&Action=Send") : (m += "&SecureToken=" + a.token, m += "&Action=SendFromStored"), m += "&cachebuster=" + i, Email.ajaxPost("https://smtpjs.com/v2/smtp.aspx?", m, d) }, ajaxPost: function (e, o, t) { var n = Email.createCORSRequest("POST", e); n.setRequestHeader("Content-type", "application/x-www-form-urlencoded"), n.onload = function () { var e = n.responseText; void 0 != t && t(e) }, n.send(o) }, ajax: function (e, o) { var t = Email.createCORSRequest("GET", e); t.onload = function () { var e = t.responseText; void 0 != o && o(e) }, t.send() }, createCORSRequest: function (e, o) { var t = new GM.xmlHttpRequest; return "withCredentials" in t ? t.open(e, o, !0) : "undefined" != typeof XDomainRequest ? (t = new XDomainRequest).open(e, o) : t = null, t } }; //Check the DOM for changes and run a callback function on each mutation function observeDOM(callback){ var mutationObserver = new MutationObserver(function(mutations) { //https://davidwalsh.name/mutationobserver-api mutations.forEach(function(mutation) { callback(mutation) // run the user-supplied callback function, }); }); // Keep an eye on the DOM for changes mutationObserver.observe(document.body, { //https://blog.sessionstack.com/how-javascript-works-tracking-changes-in-the-dom-using-mutationobserver-86adc7446401 attributes: true, // characterData: true, childList: true, subtree: true, // attributeOldValue: true, // characterDataOldValue: true, attributeFilter: ["class"] // We're really only interested in stuff that has a className });} function convertKtoM(price){ // converts kg to g, for example return (price / 10).toFixed(2); } function percentColour(percent){ // 100% = red, 0% = green var color = 'rgb(' + (percent *2.56) +',' + ((100 - percent) *2.96) +',0)' return color; } function getRndInteger(min, max) { return Math.floor(Math.random() * (max - min + 1) ) + min; } /*-------------------------------------------------------------------------------------------------------------------- ------------------------------------------- Init functions -------------------------------------------------- --------------------------------------------------------------------------------------------------------------------*/ if (!localStorage.openSlotsTimes){localStorage.openSlotsTimes = JSON.stringify([])} if (!localStorage.openSlots){localStorage.openSlots = JSON.stringify([])} if (!localStorage.errors){localStorage.errors = JSON.stringify([])} function sleep(ms) { // usage: await sleep(4000) return new Promise(resolve => setTimeout(resolve, ms)); } var gmFetch = {} // https://www.vojtechruzicka.com/javascript-async-await/ AND https://gomakethings.com/promise-based-xhr/ gmFetch.get = function (address,headers,anonymous) { return new Promise((resolve, reject) => { if (!headers){headers = ""} anonymous = anonymous ? anonymous : false; GM.xmlHttpRequest({ method: "GET", url: address, headers: headers, anonymous: anonymous, onload: e => resolve(e.response), onerror: reject, ontimeout: reject, }); }); } gmFetch.post = function (address,postData,headers,anonymous,simple=true) { return new Promise((resolve, reject) => { if (!headers){headers = {"Content-Type": "application/x-www-form-urlencoded"}} anonymous = anonymous ? anonymous : false; GM.xmlHttpRequest({ method: "POST", url: address, headers: headers, data: postData, anonymous: anonymous, onload: (simple ? e => resolve(e.response) : e => resolve(e)), onerror: reject, ontimeout: reject, }); }); } async function checkSlotsXhr(){ if (!document.querySelector("a[class*=slot-selector]")){return} // this querySelector signifies that we are on the slot page document.querySelector("a[class*=slot-selector]").scrollIntoView(); // scroll the slots into view just so we can identify whether there are slots var headers = { // headers will be the same for everey request "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:75.0) Gecko/20100101 Firefox/75.0", "Accept": "application/json", "Accept-Language": "en-US,en;q=0.5", "X-Requested-With": "XMLHttpRequest", "Content-Type": "application/json", "ADRUM": "isAjax:true", } var slotTabs = document.querySelectorAll("a[class*=slot-selector]"); // get all the week selection tabs var slotUrls = getSlotUrls(slotTabs); // and the urls from each var slotDates = getSlotDates(slotUrls); while (true){ await visitEachSlotUrl(slotDates) } // var avaiableSlots = await visitEachSlotUrl(slotUrls); // runs the function which checks the responses for slots async function visitEachSlotUrl(slotDates){ for (var i=0,j = slotDates.length;i<j;i++){ // for each url var randomNumber = getRndInteger(22000, 45000) // generates a random millisecond around 6000; // randomNumber =3; console.log(`waiting for ${randomNumber} ms`); await sleep(randomNumber); // first do nothing for a little while var address = "https://www.tesco.com/groceries/en-GB/resources"; var weekBeginning = slotDates[i]; // the current address of this loop var postData = getPostData(weekBeginning); var headers = getHeaders(); console.log(address); console.log(postData); console.log(headers); var response = await gmFetch.post(address,postData,headers); // get the response console.log(response); //redirect-to if (!response.includes("slots")){ console.log(response); GM_notification("Error? What's going on"); } if (response.includes("redirect-to")){ console.log("Error - we got redirected. Slowing things down a little."); console.log(response); await sleep(60000); // give it a minute location.reload(); // reload the whole page return; } if (response.includes("503 Service Temporarily Unavailable")){location.reload();} // reload, to trigger more frequent reloads console.log(response); var json = JSON.parse(response); // parse it console.log(json); var slots = json.slot.data.slots; // get the slot array var slotAvailable = searchForAvailable(slots); // returns true or false depending on whether this group has slots console.log(slotAvailable); if (slotAvailable){ // if it returns true var existingSlotInfo = JSON.parse(localStorage.openSlots); existingSlotInfo.push(json); localStorage.openSlots = JSON.stringify(existingSlotInfo); var existingTimeInfo = JSON.parse(localStorage.openSlotsTimes); existingTimeInfo.push(Date()); localStorage.openSlotsTimes = JSON.stringify(existingTimeInfo); var selectorHref = json.selectedDate + "?slotGroup=1"; // get the css selector we're gonna click on GM_notification("Slots available!"); // alert them 3 times in case they miss it document.querySelector(`[href*='${weekBeginning}']`).click(); // click on the corrosponding week for the user's convenience await sleep(8000); GM_notification("Slots available!"); await sleep(8000); GM_notification("Slots available!"); await sleep(8000); return; // quit this function so we're just sitting on the week with the first available slot } } function searchForAvailable(slots){ for (var i=0,j = slots.length;i<j;i++){ // for each slot in this array if (slots[i].status != "UnAvailable" && slots[i].status != "Unavailable" && slots[i].status != "Booked"){ // if the slot isn't marked "unavailable" or already "Booked" checkIfSpecificDateWanted(slots[i].start); if (checkIfSpecificDateWanted(slots[i].start)){ // but now we want to search a specific date. console.log(slots[i]) // log it return true; // quit this whole function cos we got one } } } return false; // otherwise, if we get through all the loops without an open slot, return false function checkIfSpecificDateWanted(date){ var wantedSlotsArray = [ "2020", "2021", "2022", "2023", "2024", ] for (var i=0,j = wantedSlotsArray.length;i<j;i++){ if (date.includes(wantedSlotsArray[i])){ return true } } } } } function getHeaders(){ var csrfToken = document.querySelector("input[name=_csrf]").value; var headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0", "Accept": "application/json", "Accept-Language": "en-US,en;q=0.5", "X-Requested-With": "XMLHttpRequest", "Content-Type": "application/json", "x-csrf-token": csrfToken, } return headers; } function getPostData(date){ var finalPostData = { "acceptWaitingRoom": false, "resources": [ { "type": "slot", "params": { "query": { "locationId": null, "postcode": "", "slotGroup": 1 }, "shoppingMethod": "delivery", "date": date }, "hash": "8864683814091659" }, { "type": "fulfilmentMetadata", "params": { "shoppingMethod": "delivery" }, "hash": "478896531623469" }, { "type": "trolleyContents", "params": {}, "hash": "8812624788225991" } ], "sharedParams": { "date": date, "shoppingMethod": "delivery", "query": { "locationId": null, "postcode": "", "slotGroup": 1 } } } return JSON.stringify(finalPostData); } function getSlotUrls(slots){ var urlArray = [] for (var i=0,j = slots.length;i<j;i++){ urlArray.push(slots[i].href); } return urlArray; } } checkSlotsXhr(); // checkForSlots(); checkForQueue(); function getSlotDates(slotUrls){ var finalArray = [] for (var i=0,j = slotUrls.length;i<j;i++){ finalArray.push(slotUrls[i].match(/\d\d\d\d-\d\d-\d\d/)[0]) } return finalArray; } function reloadPageAfter(seconds){ setTimeout(function() { location.reload(); }, (seconds * 1000)); } async function checkForQueue(){ var currentHour = Date().split("2020 ")[1].split(":")[0]; var randomNumber = getRndInteger(600, 900) // generates a number around 160 or so by default var pageText = document.body.textContent; if (currentHour == 23){ // it's 11pm randomNumber = getRndInteger(230, 270); // reload more frequently } if (pageText.includes("now in a queue")){ GM_notification("In a queue!"); randomNumber = getRndInteger(65, 79); // reload about once a minute, in case it doesn't happen automatically } if (pageText.includes("Sign in to your account")){ // let's autologin await sleep(16000); randomNumber = 5; document.querySelector(".ui-component__button").click(); // click the login button } if (pageText.includes("503 Service Temporarily Unavailable")){ randomNumber = getRndInteger(20, 31); // reload more frequently to make sure we get back in. } if (pageText.includes("Oops, something went wrong! (504)")){ randomNumber = getRndInteger(20, 31); // reload more frequently to make sure we get back in. } console.log(`Reloading after ${randomNumber} seconds`) reloadPageAfter(randomNumber); } function checkForSlots(){ return; // don't need this right now if (!document.querySelector("a[class*=slot-selector]")){return} // this signifies that we are on the slot page var firstSlot = document.querySelector("a[class*=slot-selector]").textContent var lastSlot = document.querySelectorAll("a[class*=slot-selector]")[2].textContent console.log(firstSlot); if (!firstSlot.includes("Apr 06 - 12")){ GM_notification("New Slots! " + lastSlot); beep(); } reloadPageAfter(30); } function beep() { var snd = new Audio("data:audio/wav;base64,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qJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU="); snd.play(); } } catch(err){console.log(err); var existingErrors = JSON.parse(localStorage.errors); existingErrors.push(err); localStorage.errors = JSON.stringify(existingErrors); } // Your code here... })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址