Skip to content

Instantly share code, notes, and snippets.

@strictlymomo
Last active November 12, 2025 17:37
Show Gist options
  • Select an option

  • Save strictlymomo/1a0632db222991e30b53732feb7c884f to your computer and use it in GitHub Desktop.

Select an option

Save strictlymomo/1a0632db222991e30b53732feb7c884f to your computer and use it in GitHub Desktop.
Bandcamp Digging Tool
/**
* BANDCAMP DIGGING TOOL
* Browse any user's collection with ease
*
* How To Use This:
* 1. Open a URL (e.g. https://bandcamp.com/noemsel)
* 2. Run this script in the dev tools console
* 3. Hover over a record to hear it instantaneously
* 4. Keys
* - `z` rewind
* - k` fast-forward
* - `c` equivalent to clicking "Buy Now" button
*/
let nowPlaying = null;
let hoverTimeout = null;
const HOVER_DELAY = 100;
/**
* Injects CSS to make the collection grid full-width for a more immersive digging experience
*/
const transcendGrid = () => {
const style = document.createElement("style");
style.textContent = `
.fan-container .grid { width: 100% !important; }
@media (min-width: 1232px) {
ol.collection-grid { width: 100% !important; }
}
`;
document.head.appendChild(style);
};
/**
* Sets up enhanced functionality to dig through collection items
*/
const initDigger = () => {
const items = document.querySelectorAll(".collection-item-container");
if (items.length === 0) return;
/**
* Handles keyboard shortcuts for seeking (left/right arrows skip 30s)
*/
const handleKeydown = (e) => {
const audio = document.querySelector("audio");
if (!audio || !nowPlaying) return;
const skipAmount = 30;
if (e.key === "ArrowLeft") {
e.preventDefault();
audio.currentTime = Math.max(0, audio.currentTime - skipAmount);
console.log(
`⏪ Skipped back ${skipAmount}s to ${audio.currentTime.toFixed(1)}s`
);
} else if (e.key === "ArrowRight") {
e.preventDefault();
audio.currentTime = Math.min(
audio.duration,
audio.currentTime + skipAmount
);
console.log(
`⏩ Skipped forward ${skipAmount}s to ${audio.currentTime.toFixed(1)}s`
);
}
};
/**
* Stops playback of the previously playing item if it's different from current
*/
const stopPreviousItem = (item) => {
if (nowPlaying && nowPlaying !== item) {
const prevPlayLink = nowPlaying.querySelector(".track_play_auxiliary");
if (prevPlayLink && nowPlaying.classList.contains("playing")) {
prevPlayLink.click();
}
}
};
/**
* Seeks the audio element to the middle of the track
* Returns true if successful, false if audio not ready
*/
const seekToMiddle = () => {
const audio = document.querySelector("audio");
if (audio && audio.readyState >= 2) {
const seekTime = audio.duration / 2;
if (seekTime && !isNaN(seekTime)) {
audio.currentTime = seekTime;
}
return true;
}
return false;
};
/**
* Starts playback of an item and seeks to the middle
*/
const startPlayback = (item) => {
stopPreviousItem(item);
const playLink = item.querySelector(".track_play_auxiliary");
if (!playLink) return;
if (!item.classList.contains("playing")) {
playLink.click();
}
nowPlaying = item;
const checkAudio = setInterval(() => {
if (seekToMiddle()) {
clearInterval(checkAudio);
}
}, 100);
setTimeout(() => clearInterval(checkAudio), 3000);
};
/**
* Initiates playback after a hover delay
*/
const handleMouseEnter = (item) => {
hoverTimeout = setTimeout(() => startPlayback(item), HOVER_DELAY);
};
/**
* Stops playback when mouse leaves and clears any pending hover timeout
*/
const handleMouseLeave = (item) => {
if (hoverTimeout) {
clearTimeout(hoverTimeout);
hoverTimeout = null;
}
if (nowPlaying === item) {
const playLink = item.querySelector(".track_play_auxiliary");
if (playLink && item.classList.contains("playing")) {
playLink.click();
}
nowPlaying = null;
}
};
document.addEventListener("keydown", handleKeydown);
items.forEach((item) => {
const artContainer = item.querySelector(".collection-item-art-container");
if (!artContainer) return;
artContainer.addEventListener("mouseenter", () => handleMouseEnter(item));
artContainer.addEventListener("mouseleave", () => handleMouseLeave(item));
});
console.log(`✓ Hover preview enabled on ${items.length} items`);
};
/**
* Automatically scrolls and clicks "show more" to load all collection items
* Stops after reaching maxItems or when no new items load after 3 attempts
*/
const loadMoreItems = async (maxItems = 500) => {
let previousCount = 0;
let noChangeCount = 0;
console.log("Loading more items...");
while (true) {
const currentCount = document.querySelectorAll(
".collection-item-container"
).length;
if (currentCount >= maxItems) {
console.log(`Reached ${currentCount} items (max: ${maxItems})`);
break;
}
if (currentCount === previousCount) {
noChangeCount++;
if (noChangeCount >= 3) {
console.log(`No more items to load (${currentCount} total)`);
break;
}
} else {
noChangeCount = 0;
}
previousCount = currentCount;
console.log(`Loaded ${currentCount} items so far...`);
window.scrollTo(0, document.body.scrollHeight);
await new Promise((resolve) => setTimeout(resolve, 500));
const showMoreButton = document.querySelector(
".expand-container .show-more"
);
if (showMoreButton && showMoreButton.offsetParent !== null) {
console.log("Clicking show more button...");
showMoreButton.click();
await new Promise((resolve) => setTimeout(resolve, 1000));
}
await new Promise((resolve) => setTimeout(resolve, 1500));
}
window.scrollTo(0, 0);
};
loadMoreItems();
transcendGrid();
initDigger();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment