You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
6.2 KiB
JavaScript
200 lines
6.2 KiB
JavaScript
import fetch from 'node-fetch';
|
|
|
|
import { playAudioFile } from 'audic';
|
|
import open from 'open';
|
|
import puppeteer from 'puppeteer-extra';
|
|
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
|
import AnonymizeUAPlugin from 'puppeteer-extra-plugin-anonymize-ua';
|
|
import AdblockerPlugin from 'puppeteer-extra-plugin-adblocker';
|
|
import fs from 'fs';
|
|
|
|
const url = 'https://www.bestbuy.com/gateway/graphql';
|
|
const browser_path = 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe';
|
|
|
|
puppeteer.use(StealthPlugin());
|
|
puppeteer.use(AnonymizeUAPlugin());
|
|
puppeteer.use(
|
|
AdblockerPlugin({
|
|
blockTrackers: true,
|
|
}
|
|
));
|
|
|
|
const headers = {
|
|
'Host': 'www.bestbuy.com',
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0',
|
|
'Accept': 'application/json',
|
|
'Accept-Language': 'en-US,en;q=0.5',
|
|
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
'Referer': 'https://www.bestbuy.com/site/nvidia-geforce-rtx-5090-32gb-gddr7-graphics-card-dark-gun-metal/6614151.p?skuId=6614151',
|
|
'X-REQUEST-ID': 'add-on-selector',
|
|
'X-CLIENT-ID': 'ATTACH-accessories-in-variations',
|
|
'Content-Type': 'application/json',
|
|
'mode': 'cors',
|
|
'Origin': 'https://www.bestbuy.com',
|
|
'Connection': 'keep-alive',
|
|
'Sec-Fetch-Dest': 'empty',
|
|
'Sec-Fetch-Mode': 'cors',
|
|
'Sec-Fetch-Site': 'same-origin',
|
|
'Priority': 'u=4',
|
|
'TE': 'trailers'
|
|
};
|
|
|
|
const skuIds = ["6614151","6616096","6617487","6614119","6614120","6616095","6616092","6616096","6614122","6616093","6616090","6616091","6615930","6615929","6615931"]
|
|
|
|
const queries = skuIds.map(skuId => ({
|
|
skuId,
|
|
query: `
|
|
query AOS_fetchButtonStateData($zip: String!, $store: String!) {
|
|
productBySkuId(skuId: "${skuId}") {
|
|
fulfillmentOptions(
|
|
input: {
|
|
buttonState: {context: PDP, destinationZipCode: $zip, storeId: $store},
|
|
shipping: {destinationZipCode: $zip},
|
|
inStorePickup: {storeId: $store}
|
|
}
|
|
) {
|
|
buttonStates {
|
|
buttonState
|
|
planButtonState
|
|
displayText
|
|
skuId
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`
|
|
}));
|
|
|
|
//CHANGE YOUR STORES HERE
|
|
const params = [
|
|
{ zip: "97504", store: "2517" },
|
|
];
|
|
|
|
let lines = 0;
|
|
let found = false;
|
|
let winningResult = null;
|
|
|
|
function getProductUrl(skuId) {
|
|
return `https://www.bestbuy.com/site/${skuId}.p?skuId=${skuId}`;
|
|
}
|
|
|
|
async function addToCart(productUrl) {
|
|
const browser = await puppeteer.launch({
|
|
headless: false,
|
|
defaultViewport: null,
|
|
ignoreDefaultArgs: ["--disable-extensions"],
|
|
dumpio: true,
|
|
args: ["--start-maximized", "--no-sandbox", "--disable-setuid-sandbox"],
|
|
executablePath: browser_path,
|
|
});
|
|
const page = await browser.newPage();
|
|
|
|
// Load cookies
|
|
try{
|
|
const cookies = JSON.parse(fs.readFileSync('./cookies.json', 'utf8'));
|
|
await page.setCookie(...cookies);
|
|
}catch (error){
|
|
console.error('An error occurred:',error)
|
|
}
|
|
|
|
|
|
// Load local storage data
|
|
try{
|
|
const localStorageData = JSON.parse(fs.readFileSync('./localStorage.json', 'utf8'));
|
|
await page.evaluateOnNewDocument((data) => {
|
|
for (const key in data) {
|
|
localStorage.setItem(key, data[key]);
|
|
}
|
|
}, localStorageData);
|
|
}catch (error){
|
|
console.error('An error occurred:',error)
|
|
}
|
|
|
|
await page.goto(productUrl);
|
|
|
|
// Wait for the "Add to Cart" button to be available and click it
|
|
try{
|
|
await page.waitForSelector('.add-to-cart-button',{ timeout: 300000 });
|
|
await page.click('.add-to-cart-button');
|
|
console.log('Item added to cart');
|
|
|
|
await page.waitForSelector('button.btn-primary[data-track="Checkout - Top"]',{ timeout: 300000 });
|
|
await page.click('button.btn-primary[data-track="Checkout - Top"]');
|
|
console.log('Checkout button clicked');
|
|
}catch (error){
|
|
console.error('An error occurred:',error)
|
|
}
|
|
}
|
|
|
|
async function fetchGraphQL(zip, store, query) {
|
|
const body = {
|
|
query,
|
|
variables: { zip, store },
|
|
operationName: "AOS_fetchButtonStateData"
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify(body)
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error: ${response.status}`);
|
|
}
|
|
return await response.json();
|
|
} catch (err) {
|
|
console.error(`Error fetching for zip ${zip}, store ${store} query ${query}:`, err);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function pollStore(zip, store) {
|
|
console.log(`Starting poll for zip ${zip} and store ${store}...`);
|
|
while (!found) {
|
|
for (const { skuId, query } of queries) {
|
|
const data = await fetchGraphQL(zip, store, query);
|
|
if (
|
|
data &&
|
|
data.data &&
|
|
data.data.productBySkuId &&
|
|
data.data.productBySkuId.fulfillmentOptions &&
|
|
data.data.productBySkuId.fulfillmentOptions.buttonStates &&
|
|
data.data.productBySkuId.fulfillmentOptions.buttonStates.length > 0
|
|
) {
|
|
const buttonState = data.data.productBySkuId.fulfillmentOptions.buttonStates[0].buttonState;
|
|
console.log(`skuId: ${skuId} Store: ${store} Ship to: ${zip} -> buttonState: ${buttonState}`);
|
|
if (buttonState !== "SOLD_OUT") {
|
|
found = true;
|
|
const productUrl = getProductUrl(skuId)
|
|
console.log(productUrl)
|
|
winningResult = { skuId, zip, store, buttonState, data };
|
|
const openPromise = open(productUrl);
|
|
const addToCartPromise = addToCart(productUrl);
|
|
await open('./alarm.wav');
|
|
await addToCartPromise
|
|
return winningResult;
|
|
}
|
|
} else {
|
|
console.log(`Incomplete data for zip ${zip}, store ${store}.`);
|
|
}
|
|
lines++
|
|
if(lines > 30) {
|
|
lines = 0;
|
|
process.stdout.write('\x1B[3J\x1B[2J\x1B[H');
|
|
}
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function runAllPollers() {
|
|
const pollers = params.map(({ zip, store }) => pollStore(zip, store));
|
|
const result = await Promise.race(pollers);
|
|
console.log("Found a store with available button state:");
|
|
console.log(JSON.stringify(result, null, 2));
|
|
}
|
|
|
|
runAllPollers();
|