Skip to content

Instantly share code, notes, and snippets.

@ehartford
Last active December 25, 2025 20:25
Show Gist options
  • Select an option

  • Save ehartford/acd2529940ec3f5abcd4cdb8d255d9ef to your computer and use it in GitHub Desktop.

Select an option

Save ehartford/acd2529940ec3f5abcd4cdb8d255d9ef to your computer and use it in GitHub Desktop.
ViolentMonkeyMicroCenterStockFinder
// ==UserScript==
// @name Micro Center Stock Finder
// @namespace violentmonkey-microcenter-stock-finder
// @match https://www.microcenter.com/product/*
// @grant GM_xmlhttpRequest
// @connect www.microcenter.com
// @version 1.7
// ==/UserScript==
(function () {
'use strict';
if (document.getElementById('mcFindStockBtn')) return;
/* ------------------------------
* Utilities
* ------------------------------ */
const sleep = ms => new Promise(r => setTimeout(r, ms));
function parseStockFromHTML(html) {
const doc = new DOMParser().parseFromString(html, 'text/html');
const panel = doc.querySelector('#pnlInventory');
if (!panel) return { available: false, reason: 'No inventory panel' };
const text = panel.innerText.toLowerCase();
if (
text.includes('sold out') ||
text.includes('out of stock') ||
text.includes('currently unavailable') ||
text.includes('coming') ||
panel.querySelector('.fa-circle-xmark')
) {
const reason =
panel.innerText.match(/(sold out|out of stock|coming.*)/i)?.[0] ??
'Unavailable';
return { available: false, reason };
}
if (
panel.querySelector('.fa-circle-check') ||
text.includes('in stock') ||
text.includes('limited availability')
) {
return { available: true };
}
return { available: false, reason: 'Unknown status' };
}
/* ------------------------------
* Google Maps URL builder
* ------------------------------ */
function buildGoogleMapsUrl(stores) {
// Convert "CO - Denver" → "Denver, CO"
const locations = stores.map(s => {
const parts = s.name.split('-').map(p => p.trim());
return parts.length === 2
? `${parts[1]}, ${parts[0]}`
: s.name;
});
return (
'https://www.google.com/maps/search/?api=1&query=' +
encodeURIComponent(locations.join(' OR '))
);
}
/* ------------------------------
* UI: Popup
* ------------------------------ */
function showPopup(results) {
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
inset: 0;
background: rgba(0,0,0,.6);
z-index: 99999;
display: flex;
align-items: center;
justify-content: center;
`;
const box = document.createElement('div');
box.style.cssText = `
background: #fff;
padding: 20px;
width: 520px;
max-height: 75vh;
overflow: auto;
border-radius: 8px;
font-family: sans-serif;
`;
const inStock = results.filter(r => r.available);
const outStock = results.filter(r => !r.available);
const mapsUrl =
inStock.length > 0 ? buildGoogleMapsUrl(inStock) : null;
box.innerHTML = `
<h2>In-stock locations (${inStock.length})</h2>
<ul>
${
inStock.length
? inStock
.map(
r =>
`<li>✅ <a href="${r.url}" target="_blank" rel="noopener">${r.name}</a></li>`
)
.join('')
: '<li>None</li>'
}
</ul>
${
mapsUrl
? `<a href="${mapsUrl}" target="_blank" rel="noopener"
style="display:inline-block; margin-top:10px; font-weight:600;">
🗺 Open in Google Maps
</a>`
: ''
}
<details style="margin-top:15px">
<summary>Out of stock (${outStock.length})</summary>
<ul>
${outStock
.map(
r =>
`<li>❌ <a href="${r.url}" target="_blank" rel="noopener">${r.name}</a> — ${r.reason}</li>`
)
.join('')}
</ul>
</details>
<button style="margin-top:15px">Close</button>
`;
box.querySelector('button:last-of-type').onclick = () => overlay.remove();
overlay.appendChild(box);
document.body.appendChild(overlay);
}
/* ------------------------------
* Store Discovery
* ------------------------------ */
function getStores() {
return [...document.querySelectorAll('.dropdown-itemLI a')]
.map(a => ({
name: a.innerText.trim(),
url: new URL(a.getAttribute('href'), location.origin).href
}))
.filter(s => !s.name.includes('Shippable'));
}
function fetchStore(store) {
return new Promise(resolve => {
GM_xmlhttpRequest({
method: 'GET',
url: store.url,
onload: res => {
const status = parseStockFromHTML(res.responseText);
resolve({ ...store, ...status });
},
onerror: () =>
resolve({ ...store, available: false, reason: 'Request failed' })
});
});
}
async function checkStoresParallel(stores, btn) {
const concurrency = 5;
const results = [];
let index = 0;
async function worker() {
while (index < stores.length) {
const current = stores[index++];
btn.textContent = `Checking ${results.length + 1}/${stores.length}…`;
results.push(await fetchStore(current));
await sleep(300);
}
}
await Promise.all(Array.from({ length: concurrency }, worker));
return results;
}
/* ------------------------------
* UI Injection
* ------------------------------ */
function injectButton() {
const btn = document.createElement('button');
btn.id = 'mcFindStockBtn';
btn.textContent = 'Find in-stock locations';
btn.style.cssText = `
margin-top: 10px;
padding: 10px 14px;
font-size: 14px;
background: #0071ce;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
`;
btn.onclick = async () => {
btn.disabled = true;
const stores = getStores();
const results = await checkStoresParallel(stores, btn);
btn.disabled = false;
btn.textContent = 'Find in-stock locations';
showPopup(results);
};
document.querySelector('.changeMyStore')?.appendChild(btn);
}
/* ------------------------------
* Init
* ------------------------------ */
const observer = new MutationObserver(() => {
if (document.querySelector('.dropdown-itemLI')) {
observer.disconnect();
injectButton();
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment