// ==UserScript== // @name MB: CoverArt Uploads Auto Retry on Error // @namespace https://greasyfork.org/users/321857-anakunda // @version 1.02 // @match https://musicbrainz.org/release/*/add-cover-art // @match https://musicbrainz.org/release/*/add-cover-art?* // @run-at document-end // @author Anakunda // @copyright 2023, Anakunda (https://greasyfork.org/users/321857-anakunda) // @license GPL-3.0-or-later // @grant GM_notification // @grant GM_getValue // @description Try to take over the world! // @downloadURL none // ==/UserScript== { 'use strict'; function clearTimers(countdown) { if (timer != undefined) { clearTimeout(timer); timer = undefined; } if (countdownTimer != undefined) { clearInterval(countdownTimer); countdownTimer = undefined; } if (controls == null) return; if ((countdown || (countdown = controls.querySelector('span#countdown'))) != null) countdown.textContent = '--- '; } const btnSubmit = document.body.querySelector('button#add-cover-art-submit'); if (btnSubmit == null) throw 'Submit button not found'; let haveErrors = false, active = true, timer, controls = null, countdownTimer, countdownTime; let retryCounter = 0, retryDelay = GM_getValue('retry_delay', 5); const captions = ['Suspend', 'Resume']; new MutationObserver(function(ml, mo) { clearTimers(); for (let mutation of ml) if (!mutation.target.disabled) if (Array.prototype.some.call(document.querySelectorAll('form#add-cover-art > table > tbody > tr > td > span.msg.error'), span => span.style.display != 'none' && /^⚠ (?:Server busy|Error)\b/.test(span.textContent.trim()))) { haveErrors = true; if (controls == null) { controls = document.createElement('span'); var btnSuspend = Object.assign(document.createElement('button'), { textContent: captions[0], id: 'autoretry', style: 'padding: 5px 10px 5px 7px; margin-left: 1em;', onclick: function(evt) { evt.currentTarget.textContent = captions[(active = !active) ? 0 : 1] + ' autoretry'; clearTimers(); return false; }, }); const countdownWrapper = Object.assign(document.createElement('span'), { style: 'display: inline-block; padding: 5pt 10pt;', }); var retryCounter = Object.assign(document.createElement('span'), { style: 'font-weight: bold; color: red;', id: 'retry-counter', }), countdown = Object.assign(document.createElement('span'), { style: 'font-weight: bold;', id: 'countdown', }); countdownWrapper.append('Retry ', retryCounter, ' in ', countdown, 's'); controls.append(btnSuspend, countdownWrapper); btnSubmit.parentNode.append(controls); } else [retryCounter, countdown] = ['retry-counter', 'countdown'].map(id => controls.querySelector('span#' + id)); if (retryCounter != null) retryCounter.textContent = retryCounter; const e = Math.log10(retryCounter++); if (e > 0 && e == ~~e) retryDelay *= e + 1; if (active) timer = setTimeout(function(elem) { clearTimers(countdown); if (active && elem instanceof HTMLElement && !elem.disabled) elem.click(); }, retryDelay * 1000 || 0, mutation.target); else continue; if (countdown != null && (countdownTime = retryDelay) > 0) { countdown.textContent = countdownTime; countdown = setInterval(elem => { elem.textContent = --countdownTime }, 1000, countdown); } } else if (haveErrors) { mo.disconnect(); if (controls != null) controls.remove(); GM_notification({ text: 'Successfully completed', title: 'MusicBrainz', highlight: true }); } }).observe(btnSubmit, { attributes: true, attributeFilter: ['disabled'] }); }