您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show local bunnings stock
// ==UserScript== // @name Bunnings Stock Checker // @namespace http://tampermonkey.net/ // @version 0.1.2 // @description Show local bunnings stock // @author You // @match https://*.bunnings.com.au/* // @icon https://www.google.com/s2/favicons?sz=64&domain=bunnings.com.au // @grant none // @license MIT // ==/UserScript== (async function() { 'use strict'; let coords = getCoords() if (!coords) { let coordString = prompt("Please enter your coordinates (used to check local stores)\nopen googles maps, right click on the map and select the first option to copy the coords to your clipboard, then paste here", "11111,22222"); if (coordString === "11111,22222") { } const [lat, lon] = coordString.replaceAll(" ", "").split(",") coords = { lat, lon, } setCoords(coords) } const chevDown = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>` let prevProduct = { productNumber: undefined, stockLevel: undefined } let oldHref = document.location.href; new MutationObserver(mutations => mutations.forEach(() => oldHref !== document.location.href && (oldHref = document.location.href, go()) )) .observe(document.querySelector("body"), { childList: true, subtree: true }); go() async function go() { let productElement = await waitUntilElementReady("p[data-locator=product-item-number]", 10000) let productNumber if (productElement) { productNumber = productElement?.innerText.split(": ")[1] } if (!productNumber || prevProduct.productNumber === productNumber) { return } prevProduct.productNumber = productNumber if (productNumber) { let body = {products:[productNumber]} let token = getTokenFromCookie() if (!token) { console.error("could not get token from cookie") return } let res = await fetch(`https://api.prod.bunnings.com.au/v1/stores/products/stock?latitude=${coords.lat}&longitude=${coords.lon}¤tPage=0&fields=FULL&pageSize=20&isTrCart=false&tradeRestrictedStore=&isPickup=true&radius=100000`, { "headers": { "accept": "application/json, text/plain, */*", "accept-language": "en-AU,en-US;q=0.9,en;q=0.8", "authorization": `Bearer ${token}`, "clientid": "mHPVWnzuBkrW7rmt56XGwKkb5Gp9BJMk", "content-type": "application/json", "correlationid": "c2910260-7c82-11ee-afa8-2b82a7af0d7d", "country": "AU", "currency": "AUD", "locale": "en_AU", "locationcode": "8056", "sec-ch-ua": "\"Chromium\";v=\"118\", \"Google Chrome\";v=\"118\", \"Not=A?Brand\";v=\"99\"", "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "\"Windows\"", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-site", "sessionid": "de5a3600-76bb-11ee-bb40-eb464f6810a4", "stream": "RETAIL", "userid": "anonymous", "x-region": "QLDMetro" }, "referrer": "https://www.bunnings.com.au/", "referrerPolicy": "strict-origin-when-cross-origin", "body": JSON.stringify(body), "method": "POST", "mode": "cors", }); let {statusDetails, data} = await res.json() console.log(data) const storeNameElement = await waitUntilElementReady("a.highlightStoreName") const storeName = storeNameElement ? storeNameElement.innerText.split("\n")[0] : undefined if (data && storeName) { const selectedStoresStock = data.stores.find(s => s.description === storeName || s.displayName === storeName) if (selectedStoresStock) { injectStockNumber(selectedStoresStock, data.stores) window.addEventListener('focus', function(event) { console.log("<< focus >>") let interval = this.setInterval(() => { if (!this.document.getElementById("stockAdded")) { injectStockNumber(selectedStoresStock, data.stores) } }, 250) this.setTimeout(() => { this.clearInterval(interval) }, 5000) }); } else { } } else { console.error("Store name not found") } prevProduct.stockLevel = data.stores[0].products[0].stock.stockLevel } } async function waitUntilElementReady(selector, timeoutMs = 30000) { let el const start = Date.now() while (Date.now() - start < timeoutMs) { el = document.querySelector(selector) if (el) { break } await new Promise(resolve => setTimeout(resolve, 300)) } return el } function getStockColour(stock) { let bg if (stock < 3) { bg = "#d9534f" } else if (stock < 6) { bg = "#f0b90b" } else { bg = "#21a346" } return bg } function injectStockNumber(selectedStoresStock, stores) { stores = stores.filter(s => s.displayName !== selectedStoresStock.displayName) const storeInfoBanner = document.querySelector("div.storeInfoBanner") let outOfStock = false let inStockLabel = storeInfoBanner.querySelector("span[data-locator=message_InStock]") ?? storeInfoBanner.querySelector("span[data-locator=message_LowStock]") if (!inStockLabel) { outOfStock = true inStockLabel = storeInfoBanner.querySelector("div.headingStyle") } const stock = selectedStoresStock.products[0].stock.stockLevel let bg = getStockColour(stock) if (inStockLabel) { let addedStockElement let stockButtonStyle = `background-color:${bg};border-radius:2px;padding-left:12px;display:flex;gap:0.3rem;align-items:center;justify-content: center` if (outOfStock) { let localStock = stores.slice(0, 3).reduce((a, b) => a + b.products[0].stock.stockLevel, 0); addedStockElement = `<div id="stockAdded" style="${stockButtonStyle}"> <b>${localStock}</b> <div>at nearby stores</div> <div style="margin-left:4px;margin-top:6px;padding-right:4px">${chevDown}</div> </div>` } else { addedStockElement = `<div id="stockAdded" style="${stockButtonStyle}"> <b>${stock}</b> <div>${inStockLabel.innerText}</div> <div style="margin-left:4px;margin-top:6px;padding-right:4px">${chevDown}</div> </div>` } inStockLabel.innerHTML = addedStockElement const dropdown = document.createElement('div'); dropdown.style.display = 'none'; dropdown.style.position = 'absolute'; dropdown.style.backgroundColor = 'white'; dropdown.style.border = '1px solid #0d5257'; dropdown.style.padding = '10px'; dropdown.style.zIndex = '1000'; let col1 = '' let col2 = '' stores.forEach(store => { const stockLevel = store.products[0].stock.stockLevel const colour = getStockColour(stockLevel) col1 += `<div style="background-color:${colour + "20"};color:${colour};text-align: right;padding-right:4px">${stockLevel ?? "-"}</div>` col2 += `<div style="background-color:${colour + "20"};display:flex;align-items: center;column-gap: 0.5rem;padding-right:4px"><div style="height:8px;width:8px;background-color:${colour};border-radius: 9999px;"></div>${store.displayName}</div>` }) dropdown.innerHTML = `<div style="display:grid;grid-template-columns: repeat(9, minmax(0, 1fr))"> <div>${col1}</div><div style="grid-column: span 8 / span 8">${col2}</div> </div>` // Add the dropdown to the button inStockLabel.appendChild(dropdown); // Show the dropdown when the button is hovered over inStockLabel.addEventListener('mouseenter', () => { dropdown.style.display = 'block'; }); // Hide the dropdown when the mouse leaves the button or the dropdown inStockLabel.addEventListener('mouseleave', () => { dropdown.style.display = 'none'; }); } } function getCoords() { let c = localStorage.getItem("stock-level-coords") return c ? JSON.parse(c) : undefined } function setCoords(coords) { if (coords) { localStorage.setItem("stock-level-coords", JSON.stringify(coords)) } } function getCookie(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); } function getTokenFromCookie() { let guestToken = getCookie('guest-token-storage') if (guestToken) { let guestTokenObj = JSON.parse(guestToken) return guestTokenObj.token } } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址