Created
March 12, 2021 08:11
-
-
Save scr4tchy/e6467f77545e81a5578e267df19d4600 to your computer and use it in GitHub Desktop.
ZZZZOTAC "Add to cart"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name ZZZZOTAC "Add to cart" | |
// @namespace http://tampermonkey.net/ | |
// @version 0.0.1 | |
// @description Waits for a Zotac drop.. and adds to cart, from the product page, or from the product listing page (e.g. search, or category) | |
// @author Falcodrin Community - Mentalow#7935 | |
// @match https://www.zotacstore.com/us/* | |
// @run-at document-start | |
// @grant GM.xmlHttpRequest | |
// @connect events.pagerduty.com | |
// ==/UserScript== | |
// Why? | |
// - In case ZZZotac drops overnight, or while you're having dinner with the parents. | |
// - Because Puppeteer & other headless scrapers are unable to go through the Cloudflare page. | |
// | |
// How to? | |
// - Install TamperMonkey in your browser | |
// - Click on the TamperMonkey icon in your browser's extension bar, and then "Create a new script" | |
// - Paste the code and save (CTRL+S does the trick) | |
// - Ensure that the productNames configuration variable fits your needs - it will filter the products based on whether the product name | |
// contains one of the strings specified. | |
// | |
// - If you'd like to be awaken by the script: | |
// - Sign up for for a free PagerDuty account, | |
// - Create a new service (Service -> Service Directory) | |
// - Setup an Integration for that service (Intregations -> Add a new integration -> Use our API directly: API Events v2) | |
// - Paste the integration key in the configuration below | |
// - Install the application on your phone, login, and enable the Push notifications | |
// - Ensure that you have a notification rule to immediately push by going to the notification rules page (My Profile -> Notification Rules) | |
// - Test the notification (especially with your phone in silent mode!) by going to the contact page (My Profile -> Contact Information -> Test) | |
// On certain devices, you may want to add PagerDuty's phones numbers and set a specific tone or DnD bypass (https://support.pagerduty.com/docs/notification-phone-numbers) | |
// - Test the end-to-end flow by adding a product that's in stock to the productNames and navigating to the product's page or to a listing that | |
// contains the product while the script is enabled | |
// | |
// | |
// - Navigate to the product pages you'd like to monitor, to a category, or to a search result's page | |
// If the page contains numerous items, you may increase the number of items shown on the page | |
// - A status bar will appear at the top of the page, confirming that the script is ready | |
// - As soon as the "Add to cart" button is detected, the script will click it, play a sound and you will be redirected to the cart | |
// If PagerDuty is configured, a notification will be sent prior to the redirection, allowing you to finalize the purchase (or try to, if Zzzotac is still up then..) | |
// Version Changelog | |
// 0.0.1 - First release. Credits to Akito for the banner style & original idea. | |
const scriptConfig = { | |
reloadInterval: 60, // Delay in seconds between page refreshes | |
productNames: ["2080", "3060", "3070", "3080", "3090"], // Filters the products that may trigger the "Add to cart" flow | |
pagerduty_key: "", // PagerDuty Integration key (Events v2 API) used to wake the hell outta you | |
}; | |
const audio = new Audio("https://www.soundboard.com/handler/DownLoadTrack.ashx?cliptitle=retard+alert&filename=23/238537-a086a28a-e475-4933-9737-0d0c6b3f2480.mp3"); | |
(async function() { | |
document.addEventListener("DOMContentLoaded", async function() { | |
// Setup banner | |
let {banner, statusInfo} = setupBanner(); | |
document.body.append(banner); | |
if (document.querySelector(".product-sku") !== null) { | |
console.log(document.querySelectorAll(".product-sku")); | |
const productName = document.querySelector("div.product-name > span").innerHTML; | |
statusInfo.innerHTML = `👀 Patiently waiting for the ${productName}.`; | |
await tryCart(document.querySelector("div.main-container"), productName, statusInfo); | |
} else { | |
const products = Array.from(document.querySelector("div.category-products").querySelectorAll("div.product-details")) | |
.filter(e => containsSubstring(e.querySelector(".product-name > a").innerHTML, scriptConfig.productNames)); | |
statusInfo.innerHTML = `👀 Patiently waiting for one of the ${products.length} items on this page to become available.` | |
products.forEach(function(product) { | |
tryCart(product, product.querySelector(".product-name > a").innerHTML, statusInfo); | |
}); | |
} | |
// Get a noice timer going. | |
let timeLeft = scriptConfig.reloadInterval; | |
const statusInfoHTML = statusInfo.innerHTML; | |
while(timeLeft > 0) { | |
timeLeft--; | |
statusInfo.innerHTML = statusInfoHTML + `<br/>⏳ Auto-reloading in ${timeLeft} seconds.`; | |
await sleep(1000); | |
} | |
window.location.reload(true); // skip cache | |
}); | |
}()); | |
async function tryCart(dom, productName, statusInfo) { | |
const addToCartButton = dom.querySelector("button.btn-cart"); | |
if (!addToCartButton) { | |
return; | |
} | |
// FULL SEND --> | |
statusInfo.innerHTML = "💸 IT'S GO TIME!" | |
audio.play() | |
// Notify via PagerDuty! | |
if (scriptConfig.pagerduty_key) { | |
GM.xmlHttpRequest({ | |
method: "POST", | |
url: "https://events.pagerduty.com/v2/enqueue", | |
headers: { "Content-Type": "application/json" }, | |
data: JSON.stringify({ | |
"routing_key": scriptConfig.pagerduty_key, | |
"event_action": "trigger", | |
"payload": { | |
"summary": `[Zotac drop] ${productName}`, | |
"source": window.location.href, | |
"severity": "critical" | |
} | |
}) | |
}); | |
} | |
// Good luck. | |
addToCartButton.click(); | |
} | |
function setupBanner() { | |
// Initialize bottom banner for status + donation info | |
const banner = document.createElement("div"); | |
banner.style.position = "absolute"; banner.style.top = "0px"; banner.style.zIndex = 100; | |
banner.style.width = "100%"; banner.style.padding = "6px"; banner.style.alignItems = "center"; | |
banner.style.background = "#F9B61E"; | |
banner.style.color = "#FFFFFF"; | |
banner.style.fontFamily = "Verdana"; banner.style.fontSize = "16px"; | |
banner.style.display = "flex"; banner.style.flexDirection = "row"; banner.style.justifyContent = "space-between"; | |
// Initialize status info (left side of bottom banner) | |
const statusInfo = document.createElement("div"); | |
statusInfo.style.textAlign = "left"; statusInfo.style.paddingLeft = "10px"; | |
statusInfo.style.order = 0; statusInfo.style.flexBasis = "50%"; | |
statusInfo.innerText = "🤦♂️ Waiting for Cloudflare"; | |
// Initialize donation info (right side of bottom banner) | |
const donationInfo = document.createElement("div"); | |
donationInfo.style.textAlign = "right"; donationInfo.style.paddingRight = "10px"; | |
donationInfo.style.order = 1; donationInfo.style.flexBasis = "50%"; | |
donationInfo.innerHTML = "Thank you! | BTC: bc1qmvx3nmxfp0rrz64l3dthh45a7gh3gpl47umqj5<br/>ETH: 0x722361E88b89Ec87A82F069c28D7AF01e47574e1"; | |
banner.appendChild(statusInfo); | |
banner.appendChild(donationInfo); | |
return {banner, statusInfo}; | |
} | |
function containsSubstring(needle, haystack) { | |
if(Array.isArray(haystack) === false) { | |
return false; | |
} | |
for(const hay of haystack) { | |
if(needle.includes(hay)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
async function sleep(ms) { | |
return new Promise(resolve => setTimeout(resolve, ms)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment