Created
June 16, 2025 03:56
-
-
Save ggorlen/c294812b52ee1c5cb87e2d1728499ed3 to your computer and use it in GitHub Desktop.
discogs video adder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// WIP script that auto-adds videos to a discogs release | |
// TODO: "full album" mode | |
// TODO: try multiple approaches (with and without artist/album title) | |
/* | |
x=[...document.querySelectorAll("[data-track-position]")] | |
.map(e => e.querySelector("td:nth-child(3) span").textContent).join("\n") | |
console.log(x) | |
*/ | |
const titles = ` | |
Imperium In Imperio | |
Divine Dictation | |
The Opportunist | |
Relentless Hatred | |
Sacrificial Hire | |
War For Resources | |
Black Banner | |
Hara-Kiri | |
Stale Affairs | |
Regressive Agenda | |
Incarceration State | |
Unit 731 | |
Icaro | |
Husayni / Handschar | |
Pharmacide | |
`.trim().split("\n"); | |
(async () => { | |
function setNativeValue(element, value) { | |
const valueSetter = Object.getOwnPropertyDescriptor(element, "value")?.set; | |
const prototype = Object.getPrototypeOf(element); | |
const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, "value")?.set; | |
if (valueSetter && valueSetter !== prototypeValueSetter) { | |
prototypeValueSetter.call(element, value); | |
} else { | |
valueSetter.call(element, value); | |
} | |
} | |
function findInputByLabelText(labelText) { | |
return [...document.querySelectorAll('label')].reduce((found, label) => { | |
if (found) return found; | |
if (label.textContent.trim() === labelText.trim()) { | |
return label.htmlFor | |
? document.getElementById(label.htmlFor) | |
: label.querySelector('input, textarea, select'); | |
} | |
return null; | |
}, null); | |
} | |
function findButtonByText(text) { | |
return [...document.querySelectorAll("button")].find(btn => btn.textContent.trim() === text); | |
} | |
function delay(ms) { | |
return new Promise(resolve => setTimeout(resolve, ms)); | |
} | |
function levenshtein(a, b) { | |
const matrix = Array.from({ length: b.length + 1 }, (_, i) => | |
Array(a.length + 1).fill(0) | |
); | |
for (let i = 0; i <= b.length; i++) matrix[i][0] = i; | |
for (let j = 0; j <= a.length; j++) matrix[0][j] = j; | |
for (let i = 1; i <= b.length; i++) { | |
for (let j = 1; j <= a.length; j++) { | |
matrix[i][j] = b[i - 1] === a[j - 1] | |
? matrix[i - 1][j - 1] | |
: Math.min( | |
matrix[i - 1][j] + 1, | |
matrix[i][j - 1] + 1, | |
matrix[i - 1][j - 1] + 1 | |
); | |
} | |
} | |
return matrix[b.length][a.length]; | |
} | |
const albumTitle = document.querySelector("[class*=title]").textContent; | |
for (const title of titles) { | |
const input = findInputByLabelText("YouTube search query:"); | |
setNativeValue(input, `${albumTitle} ${title} "provided"`); | |
input.dispatchEvent(new Event("input", { bubbles: true })); | |
findButtonByText("Search")?.click(); | |
await delay(1000); | |
//const videos = await new Promise(resolve => { | |
// (function update() { | |
// const videos = | |
// [...document.querySelectorAll("[class*='results'] li")]; | |
// | |
// if (videos.length) { | |
// return resolve(videos); | |
// } | |
// | |
// requestAnimationFrame(update); | |
// })(); | |
//}); | |
const videos = [...document.querySelectorAll("[class*='results'] li")]; | |
let bestMatch = null; | |
let minDistance = Infinity; | |
for (const video of videos) { | |
const link = video.querySelector("a"); | |
const resultTitle = link.textContent.trim(); | |
const dist = levenshtein(title.toLowerCase(), resultTitle.toLowerCase()); | |
if (dist < minDistance) { | |
minDistance = dist; | |
bestMatch = video; | |
} | |
} | |
if (bestMatch) { | |
findButtonByText("Add")?.click(); | |
} | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment