forked from albert-sun/bestbuy-queue-automation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
script_product.js
159 lines (135 loc) · 9.03 KB
/
script_product.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// ==UserScript==
// @name Best Buy Automation (Product Details)
// @namespace akito
// @version 2.0.2
// @description Auto-presses drops when button changes to Add
// @author akito#9528 / Albert Sun
// @updateURL https://raw.githubusercontent.com/albert-sun/bestbuy-queue-automation/main/script_product.js
// @downloadURL https://raw.githubusercontent.com/albert-sun/bestbuy-queue-automation/main/script_product.js
// @match https://www.bestbuy.com/*skuId=*
// @match https://www.bestbuy.com/site/combo/*
// @run-at document-start
// @grant none
// ==/UserScript==
// Version Changelog
// 1.7.1 - Moved notification sound retrieval to page load so it would play correctly
// 1.8.0 - Added auto page-reload functionality on sold-out products with the given keywords
// 2.0.0 - Refactored existing code and added auto-reload for sold out / unavailable products
// 2.0.1 - Added a line of code I forgot when porting, sigh
// 2.0.2 - Added update and download URL to metadata, go to Tampermonkey settings -> enable "Check Interval" for auto-updating
const version = "2.0.0";
const scriptDesc = `Best Buy Automation (Product Details) v${version} by akito#9528 / Albert Sun`;
const donationText = "Thank you! | Bitcoin: bc1q6u7kalsxunl5gleqcx3ez4zn6kmahrsnevx2w4 / 1KgcytPHXNwboNbXUN3ZyuASDZWt8Qcf1t | Paypal: akitocodes@gmail.com";
// Quick and dirty script configuration, feel free to edit this!
// - Ignore warnings with the /!\ symbol, just some whitespace linting stuff
// - Note that auto-reload might not work properly if Chrome / Firefox unloads resources or something
const scriptConfig = {
keepPolling: false, // Whether to continue periodically polling for product availability once added to cart | Default: false
initialClick: true, // Whether to auto-click the "Add to Cart" button upon page load (if description includes keyword) | Default: true
soldOutReload: true, // Whether to auto-reload the page after a delay when sold out (if description includes keyword) | Default: true
// ======================================================================================================================================
pollInterval: 0.5, // In seconds, interval between periodic button polls when checking for availability | Default: 0.5
initialDelay: 0.5, // In seconds, delay on page load before auto-clicking the add button (to update existing status) | Default: 0.5
soldOutDelay: 60, // In seconds, delay before refreshing page when product is sold out (to refresh product details) | Default: 60
// ======================================================================================================================================
// Keyword inclusion whitelist for initial clicking and auto-reloading, delete or make empty array for script to ignore.
// Default: ["3060", "3070", "3080", "3090", "6700", "6800", "6900", "5600X", "5800X", "5900X", "5950X", "PS5"]
keywords: ["3060", "3070", "3080", "3090", "6700", "6800", "6900", "5600X", "5800X", "5900X", "5950X", "PS5"]
}
// Quick and dirty function which checks whether the given button / div is yellow (usually RGB around 255, 255, 0).
// Yellow usually means that the item is cartable (though different for the saved items page where it is instead white).
function buttonYellow(button) {
return window.getComputedStyle(button, null).getPropertyValue("background-color").startsWith("rgb(255");
}
// Checks whether the given word (string) contains any of the given keywords (substrings).
// Automatically returns false if the keywords parameter is not an array.
function containsSubstring(word, keywords) {
if(Array.isArray(keywords) === false) {
return false;
}
for(const keyword of keywords) {
if(word.includes(keyword)) {
return true;
}
}
return false;
}
(async function() {
'use strict'; // Necessary? Comes default with Tampermonkey scripts
// Initialize bottom banner for status + donation info
const banner = document.createElement("div");
banner.style.position = "fixed"; banner.style.bottom = "0px"; banner.style.zIndex = 100;
banner.style.width = "100%"; banner.style.padding = "6px"; banner.style.alignItems = "center";
banner.style.backgroundImage = "linear-gradient(to right, coral, crimson, coral, crimson)";
banner.style.fontFamily = "Verdana"; banner.style.fontSize = "12px";
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 = `${scriptDesc} | Initializing script`;
// 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.innerText = donationText;
// Actual bulk of the script including the auto-reloader and interval poller
// Could use document-end instead of document-start and DOM load but I dunno what's better
document.addEventListener("DOMContentLoaded", async function() {
document.body.append(banner);
banner.appendChild(statusInfo);
banner.appendChild(donationInfo);
// Preload audio because doesn't preload when tab not focused
const audio = new Audio("https://proxy.notificationsounds.com/notification-sounds/definite-555/download/file-sounds-1085-definite.mp3");
// Retrieve relevant HTML elements and information from page
const addButton = document.getElementsByClassName("add-to-cart-button")[0];
const productName = document.getElementsByTagName("title")[0].innerText;
const hasKeyword = containsSubstring(productName, scriptConfig.keywords);
// Check current product status (available, sold out / unavailable, queued) and update banner appropriately
// Also process sold out / unavailable triggers at this point because it's convenient
let addAvailable = buttonYellow(addButton);
const disabled = addButton.classList.contains("btn-disabled");
if(disabled === true) { // Button not clickable, either sold out or unavailable
statusInfo.innerHTML = `${scriptDesc} | Product currently sold out or unavailable, `;
if(scriptConfig.soldOutReload === true && hasKeyword === true) {
statusInfo.innerHTML += `auto-reloading in ${scriptConfig.soldOutDelay} seconds.`;
await new Promise(r => setTimeout(r, scriptConfig.soldOutDelay * 1000)); // sleep function
location.reload();
} else {
statusInfo.innerHTML += "auto-reload disabled " + (scriptConfig.soldOutReload === true
? "because not whitelisted"
: "in script config"); // too long to add comments?
return; // exit the script
}
}
statusInfo.innerHTML = `${scriptDesc} | ` + (addAvailable === true
? "Add button clickable, " + (scriptConfig.initialClick === true && scriptConfig.initialClick === true && hasKeyword === true
? `automatically clicking in ${scriptConfig.initialDelay} seconds.`
: "please click to initialize product queue.")
: "Existing product queue detected, waiting for button availability change.");
// Automatically click the button after a set interval if clickable and setting enabled.
if(addAvailable === true && scriptConfig.initialClick === true && hasKeyword === true) {
setTimeout(function() {
addButton.click()
}, scriptConfig.initialDelay * 1000);
}
// Initialize periodic polling for button color swap
let intervalID = setInterval(function() {
let nowAvailable = buttonYellow(addButton);
if(addAvailable === true && nowAvailable === false) { // button clicked to enter queue (yellow => grey)
statusInfo.innerHTML = `${scriptDesc} | Queue entry detected, waiting for button availability change`;
addAvailable = false;
} else if(addAvailable === false && nowAvailable === true) { // queue popped (grey => yellow)
statusInfo.innerHTML = `{scriptDesc} | Availability change detected, button clicked and window opened. Good luck!`;
// Click button, play audio, and open cart window
addButton.click();
window.open("https://www.bestbuy.com/checkout/r/fast-track");
audio.play();
// Cancel periodic polling depending on config
if(scriptConfig.keepPolling === false) {
clearInterval(intervalID);
}
}
}, scriptConfig.pollInterval * 1000);
});
}());