Skip to content

Instantly share code, notes, and snippets.

@dbernheisel
Created September 7, 2025 18:11
Show Gist options
  • Select an option

  • Save dbernheisel/2f48ad7efc8b4d748ab0aa11f65b2d7b to your computer and use it in GitHub Desktop.

Select an option

Save dbernheisel/2f48ad7efc8b4d748ab0aa11f65b2d7b to your computer and use it in GitHub Desktop.
Scrape your owned Nintendo and Playstation games
// Copy and paste when browsing your collection
// https://www.nintendo.com/us/orders/
// https://library.playstation.com/recently-purchased/1
//
// A textarea will be appended to the page that will have a list of your games
// ready for copy/paste into a spreadsheet program; the columns are:
// Title, System, isPhysical, isDigital, isReproduction, isSubscriptionOnly
// isSubscriptionOnly may not be accurate, because you may also own it physically, but it also is availble on Playstation Plus.
// isReproduction is always false
// isPhysical is always false
const gameListEl = {
"nintendo": "main",
"playstation": "[data-qa='collection-game-list-collection']"
}
const gameEl = {
"nintendo": 'section.sc-aicnew-2',
"playstation": "[data-qa='collection-game-list-product#store-link']"
}
let games = []
const skipNames = [
"F-ZERO™ 99",
"Pokémon™ HOME",
"Minecoin Pack",
"Super Mario Bros.™ 35",
"Animal Crossing™: New Horizons Nintendo Switch Online - Member-Exclusive Items",
"Media Player",
"Twitch",
"Crunchyroll",
"Crackle - Free Movies and TV",
"YouTube",
"Plex",
"Hulu",
"Amazon Prime Video",
"Netflix",
"VUDU™ HD Movies",
"Indivisible Prototype",
"HBO Max",
"Disney+",
"Minecraft Preview",
"Horizon Zero Dawn™ Artbook",
"PUBG - Public Test Server",
]
const skipIncludes = [
"nso family membership",
/realms .* subscription/,
/minecoin pack/,
"soundtrack",
"demo",
"soundtrack",
"demo",
"tech test",
"test server",
"arcade game series:",
]
const removeFromTitle = [
"(PS4)",
"(PS5)",
"(English)",
"™",
"™",
"®",
]
const fixTitles = {
"Minecraft Legends": "Minecraft: Legends",
"Crysis3® Remastered": "Crysis 3 Remastered",
"Crysis2® Remastered": "Crysis 2 Remastered",
"LittleBigPlanet™3": "LittleBigPlanet 3",
"Plants vs Zombies GW2": "Plants vs Zombies Garden Warfare 2",
"DIRT5": "DIRT 5",
"WATCH_DOGS™": "Watch Dogs",
"WATCH_DOGS® 2": "Watch Dogs 2"
}
function cleanTitle(title) {
let cleaned = fixTitles[title] || title
removeFromTitle.forEach((t) => cleaned = cleaned.replace(t, ""))
return cleaned.replace(" : ", ": ").trim()
}
function skipGame(title) {
return (
skipNames.some((t) => title == t) ||
skipIncludes.some((t) => {
if (typeof t === 'string') return title.toLowerCase().includes(t.toLowerCase())
if (t instanceof RegExp) return t.test(title.toLowerCase())
})
)
}
function getSystem(g, system){
switch (system) {
case "nintendo":
const s2 = g.querySelector("[data-testid='NintendoSwitch2LogoOnlyIcon']")
if (s2) return "Nintendo Switch 2"
return "Nintendo Switch"
case "playstation":
const tags = g.querySelector("[data-qa='collection-game-list-product#platform-tags']")
if (tags && tags.innerText.includes("PS4")) {
return "PlayStation 4"
} else if (tags && tags.innerText.includes("PS5")) {
return "PlayStation 5"
}
}
}
function isSubscription(g, system) {
switch (system) {
case "nintendo":
return false
case "playstation":
const el = g.querySelector("[data-qa='collection-game-list-product#service-upsell']")
if (el) {
return el.innerText.includes("PS PLUS")
} else {
return false
}
}
}
function getTitle(g, system) {
switch (system) {
case "playstation":
return JSON.parse(g.dataset.telemetryMeta).titleName
case "nintendo":
const el = g.querySelector('div.cGwtLt h1.s954l')
if(el) {
return el.innerText
} else {
return false
}
}
}
function process(system){
const gameList = document.querySelector(gameListEl[system])
for (let g of gameList.querySelectorAll(gameEl[system])) {
let game = {}
let title = getTitle(g, system)
if (!title) {
console.error("Could not process", g)
continue
}
if (skipGame(title)) continue
game.title = cleanTitle(title)
game.system = getSystem(g, system)
game.physical = false
game.digital = true
game.reproduction = false
game.subscription = isSubscription(g, system)
games.push(game)
}
return [...new Set(games)].sort((e) => e.title)
}
function writeTextArea() {
let text = ""
games.forEach((e) => {
text += `"${e.title}","${e.system}",${e.physical},${e.digital},${e.reproduction},${e.subscription}\n`
})
textEl = document.createElement('textarea')
textEl.name = "copyme"
textEl.style = 'background-color: white; width: 100%;'
textEl.rows = 10
textEl.value = text
document.querySelector('main').append(textEl)
textEl.scrollIntoView()
}
function getNintendoPages() {
const digitalBtn = [...document.querySelectorAll("button.buovD")].find((e) => e.innerText.includes("Digital"))
if (digitalBtn) digitalBtn.click()
let loop
return new Promise(resolve => {
loop = setInterval(function() {
const btn = document.querySelector("button.sc-1wh0o51-3")
if (btn) {
console.log("getting page")
btn.click()
} else {
clearInterval(loop)
console.log("Done getting pages")
resolve()
}
}, 1000)
})
}
async function nextPlaystationPage() {
return await new Promise(resolve => {
let loop
const nextBtn = document.querySelector("[data-qa='pagination#next']:not(:disabled)")
if (nextBtn) {
console.log("getting page")
nextBtn.click()
let loop
loop = setInterval(function() {
if (document.querySelector("[data-qa='collection-loading']")) {
console.log("waiting for page...")
} else {
clearInterval(loop)
resolve(true)
}
}, 1500)
} else {
console.log("Done getting pages")
resolve(false)
}
})
}
async function getPage(system) {
switch (system) {
case "nintendo":
return false
case "playstation":
return await nextPlaystationPage()
break;
}
}
async function preprocess(system) {
switch (system) {
case "nintendo":
await getNintendoPages()
break;
case "playstation":
break;
}
}
async function getGames() {
let system
switch (window.location.host) {
case 'library.playstation.com':
system = 'playstation'
break;
case 'www.nintendo.com':
system = 'nintendo'
break;
default:
console.error("Does not support this site")
return
}
console.log("preprocess")
await preprocess(system)
while (true) {
console.log("process")
process(system)
if (await getPage(system) === false) break
}
writeTextArea()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment