Yet Another Various Artists Helper
// ==UserScript== // @name What.CD: YAVAH // @namespace hateradio))) // @author hateradio // @version 7.2 // @description Yet Another Various Artists Helper // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjY0NDMyN0UzOUEwQzExRTA4REU5OUY2M0Q5RDZGNTQ1IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjY0NDMyN0U0OUEwQzExRTA4REU5OUY2M0Q5RDZGNTQ1Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NjQ0MzI3RTE5QTBDMTFFMDhERTk5RjYzRDlENkY1NDUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NjQ0MzI3RTI5QTBDMTFFMDhERTk5RjYzRDlENkY1NDUiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz6q1rvzAAAEMElEQVR42uyaP0gcQRTG12CpYBkVUkWto22CYqtiq7FWrNVervdPK5r2oq2orSiptQkE1LRqkSooBNIk+xvyLe/m9la9XDzIvoFh9mZnZme+973vvRE7enp6kjKXF3/aD2n9VbJa4eAdKQPepu2nkhJgAAa8KrEHvHyRlLw4AA6AA+AAOAAOgAPgADgADoAD4AA4AA6AA+AAOAAOgAPgADgADoAD4AA4AA6AA+AAOAAOgAPQmjI7O5scHx8nq6urWd/o6Gjoo/JeZWFhIfTRtqIsLS3VfaNtDOju7s6ep6amcvsnJiZCe3Bw0JJvjoyMhPbi4iL3fedzHPzm5ia0XV1doR0cHEyGh4eTu7u7cHj1Awq/T09Pk9vb21wAsSjsoVxeXiaVSqVmLCzT+729vWztvPXapgGiIwe1DNDGT05Ocg+/tbWVjRGQy8vL2e+1tbWa9zMzM2EeQLcVAH2czVCxPn2Hh4ehv7e3N2MF/QImBo1x5+fnyfj4eLK4uBj6BwYGMvZo/tzcXBjDWMrV1VXDvXU+p+WhIwcBBOhpgRkbGwvPAiUusuz6+npGfw4Zv2e+1mUMoNC2FQBLP0SO37u7u+E39AQYDsBzHgAAhPUp9/f3ud/Qe+mN7WtE/7ZEAYlc3gHpB4SiIsorvMXhUqoPoGLF9fV1exnAoaT4tLK+rCMRtOK3v78f2unp6TAHGqMTCJ2dq7V4D4iTk5Oh2lKkAc8eBWIri9IcQKKF3wKKpS7hTu8pPK+srGRrbWxsZO+tK7FGEav4X+H3aVstaSb87tEMIAbjc/heXppbrVZr4rnSXJv+QmH1S6Di1Jh1rDbgCvTLJeJCf/yOeUVzmnIBCRcb1UE50Pz8fBaeRDUrTIxRgeYaY/vtHMKjzeP1LRsJVOQqsY9LKIt8/8kAIDYSLImM2LC5uZn5n01YFIpsri+/7uvrq0tyAEh3AAACbMZrTgxaf39/Xegr6v9rEZSwAACHZ0P0adMWnO3t7WzjsohYYGO01tOcmBGsf3Z2VgdaUZz/JwwQC6Cwwg1WRn1jS7JpDqqNDQ0N1TFA8VpzcDGxBssrgxPz7JzYvXBDaQtVgBbF/6bD4NHRUc1NzAoPWR4A7ezshD5Zzlpb11KyP/q5sDDHWl+XJcV4zYk1wDIrrzyGAU9OhCRiWMvGVytYsfpay2lTjOUmF98LOLwsayOIAKAyVtqiy48dQyR5KP43zQBtzvqdBKvoEmQB1Fzd3mxmGGdxjXL+Rv6ft7+WMkAfsP5lQ5ilsuK4rsDycaWtEjlZykYDXXdtHsK30RPWka7ENz0JpdyvpQxQLGbDsWDFOX5sbSuE2rRETmujB9b3bdEcGaARAx66NXoq3Gwq7H8WdwD+XwB+lPj8PwHgOK3fSnj4r2n9TB7wPa1v0ko0eF2Sw39J60fY/1uAAQDAo7S7+Gt3nAAAAABJRU5ErkJggg== // @include /https://redacted\.sh/(torrents\.php(\?|\?page=\d+&)id=\d+(&torrentid=\d+)?(#comments)?|upload\.php(\?requestid=\d+)?|requests\.php*)/ // @include /https://orpheus\.network/(torrents\.php(\?|\?page=\d+&)id=\d+(&torrentid=\d+)?(#comments)?|upload\.php(\?requestid=\d+)?|requests\.php*)/ // @include /https://notwhat\.cd/(torrents\.php(\?|\?page=\d+&)id=\d+(&torrentid=\d+)?(#comments)?|upload\.php(\?requestid=\d+)?|requests\.php*)/ // #updated 26 Nov 2024 // #since 18 Jun 2010 // ==/UserScript== (() => { if (!Element.prototype.matches) Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector if (!Element.prototype.closest) { Element.prototype.closest = function (s) { let el = this if (!document.documentElement.contains(el)) return null do { if (el.matches(s)) return el el = el.parentElement || el.parentNode } while (el !== null && el.nodeType === 1) return null } } const _ = { css: text => { if (!this.style) { this.style = document.createElement('style') this.style.type = 'text/css' document.body.appendChild(this.style) } this.style.appendChild(document.createTextNode(`${text}\n`)) }, js: func => { const script = document.createElement('script') script.type = 'application/javascript' script.textContent = `;(${func})();` document.body.appendChild(script) document.body.removeChild(script) }, debounce: (func, wait) => { let timeout return function (...args) { const run = () => { timeout = null func.apply(this, args) } clearTimeout(timeout) timeout = setTimeout(run, wait) } }, on: (element, type, selector, listener) => { element.addEventListener(type, event => { const found = event.target.closest(selector) if (found) listener.call(found, event) }, false) } } class YavaMenu { constructor() { this.sibling = document.querySelector('.box_addartists, #artist_tr') this.setup() YavaMenu.check = document.getElementById('yavah_semi') } get types() { return ['Main', 'Guest', 'Remixer', 'Composer', 'Conductor', 'DJ / Compiler', 'Producer'] } setup() { if (!this.sibling) return const box = document.createElement('div') box.id = 'YAVAH' _.on(box, 'click', 'a', this.toggle) this.boxSetup(box) box.querySelector('a').click() this.box = box } boxSetup(box) { box.className = 'box' box.innerHTML = ` <div class="head"> <strong><abbr title="Yet Another Various Artists Helper">YAVAH</abbr></strong> </div> <div style="padding: 3px 6px 6px"> ${this.generateInputs()} </div>` this.sibling.parentElement.insertBefore(box, this.sibling) } toggle(e) { e.preventDefault() const tog = this.parentElement.nextElementSibling.classList.toggle('hidden') this.innerHTML = `<code>${tog ? '+' : '-'}</code> ${this.dataset.type}` } generateInputs() { // let yavahtog = this.nextElementSibling.classList.toggle('hidden'); // this.firstElementChild.innerHTML = '<code>' + (yavahtog ? '+' : '-') + '</code> ' + this.firstElementChild.dataset.type const boxes = this.types.map(type => { return `<p><a href="#" data-type="${type}"><code>+</code> ${type}</a></p><textarea class="noWhutBB yavahtext hidden"></textarea>` }).join('') return ` <p>Enter artists, one per line.</p> <p> <input type="checkbox" id="yavah_semi"> <label for="yavah_semi">Split semi-colons</label> </p> ${boxes} <p>Review the changes below.</p>` } addEvent(cb) { return this.box && !this.box.addEventListener('input', _.debounce(cb, 250), false) } } class YavaMenuTr extends YavaMenu { boxSetup(box) { box.innerHTML = this.generateInputs() const tr = document.createElement('tr') tr.innerHTML = '<td class="label">YAVAH</td><td></td>' tr.lastElementChild.appendChild(box) this.sibling.parentElement.insertBefore(tr, this.sibling) } } class Yavah { constructor() { this.selector = 'input[name="aliasname[]"]:last-of-type, input[name="artists[]"]:last-of-type' this.add = document.querySelector('.box_addartists a, #artistfields a.brackets') this.inputs = document.querySelector('#AddArtists, #artistfields') if (!this.inputs) return _.css('#YAVAH p a { display:block } .yavahtext{width: 90%; height: 6em}') this.stored = this.inputs.innerHTML this.event = this.event.bind(this) } regex() { if (YavaMenu.check.checked) return /[^\r\n;]+/g return /[^\r\n]+/g } /** * * @param {HTMLTextAreaElement} textarea * @param {number} index Index of HTMLOptionElement within HTMLSelectElement to set (Main, Guest, Composer, etc.) */ fill(textarea, index) { const lines = textarea.value.match(this.REGEX) || [] const unique = new Set(lines.map(_ => _.trim())) unique.delete('') unique.forEach(name => { this.inputs.querySelector(this.selector).value = name this.inputs.querySelector('select:last-of-type').value = index + 1 this.add.click() }) } event() { // Reset the artist box this.inputs.innerHTML = this.stored _.js(() => window.ArtistFieldCount = -1000) const textareas = document.querySelectorAll('#YAVAH textarea') this.REGEX = this.regex() Array.from(textareas).forEach(this.fill, this) } static main() { const yava = new Yavah() const menu = /(?:requests\.php|upload\.php)/.test(document.location.pathname) ? new YavaMenuTr : new YavaMenu menu.addEvent(yava.event) if (document.location.hash === '#yavah-test') { new YavaTest(yava) } } } class YavaTest { constructor(yavah) { this.y = yavah this.indices = '1-1-1-2-2-2-3-3-3-4-4-4-5-5-5-6-6-6-7-7-7-1' this.values = 'A1;A1.1-A2-A3-B1;B1.1-B2-B3-C1;C1.1-C2-C3-D1;D1.1-D2-D3-E1;E1.1-E2-E3-F1;F1.1-F2-F3-G1;G1.1-G2-G3-' this.indicesSemi = '1-1-1-1-2-2-2-2-3-3-3-3-4-4-4-4-5-5-5-5-6-6-6-6-7-7-7-7-1' this.valuesSemi = this.values.replaceAll(';', '-') this.testInit() this.testRegular() this.testSemi() } testInit() { console.log('YAVAH TEST!') // asume if no inputs, then there is nothing to do if (!this.y.inputs) return // fill data const textareas = Array.from(document.querySelectorAll('.yavahtext')) textareas.forEach((t, i) => { const ch = String.fromCharCode(65 + i); t.value = `${ch}1;${ch}1.1\n${ch}2\n${ch}3` }) // go for it! this.y.event() } selects(indices) { // assert dropdowns console.group('YAVAH DROPDOWN TEST') const selects = document.querySelectorAll('#AddArtists select, #artistfields select') if (selects.length === indices.split('-').length) console.debug('[Passed] Valid number of dropdowns') else console.warn('Invalid number of dropdowns') if (Array.from(selects).map(_ => _.value).join('-') === indices) console.debug('[Passed] Valid values for dropdowns') else console.warn('Invalid values for dropdowns') console.groupEnd() } inputs(values) { // assert inputs console.group('YAVAH INPUT TEST') const inputs = document.querySelectorAll('input[name="aliasname[]"], input[name="artists[]"]') if (inputs.length === values.split('-').length) console.debug('[Passed] Valid number of inputs') else console.warn('Invalid number of inputs') if (Array.from(inputs).map(_ => _.value).join('-') === values) console.debug('[Passed] Valid values for inputs') else console.warn('Invalid values for inputs') console.groupEnd() } testRegular() { console.info('Starting Regular Test') this.selects(this.indices) this.inputs(this.values) } testSemi() { console.info('Starting Semicolon Test') YavaMenu.check.checked = true this.y.event() this.selects(this.indicesSemi) this.inputs(this.valuesSemi) } } Yavah.main() })()