MB Auto-retry on upload to CAA error

Just autoretry

  1. // ==UserScript==
  2. // @name MB Auto-retry on upload to CAA error
  3. // @namespace https://greasyfork.org/users/321857-anakunda
  4. // @version 1.04
  5. // @match https://musicbrainz.org/release/*/add-cover-art
  6. // @match https://musicbrainz.org/release/*/add-cover-art?*
  7. // @iconURL https://coverartarchive.org/img/big_logo.svg
  8. // @run-at document-end
  9. // @author Anakunda
  10. // @copyright 2023, Anakunda (https://greasyfork.org/users/321857-anakunda)
  11. // @license GPL-3.0-or-later
  12. // @grant GM_notification
  13. // @grant GM_getValue
  14. // @description Just autoretry
  15. // ==/UserScript==
  16.  
  17. {
  18.  
  19. 'use strict';
  20.  
  21. function clearTimers() {
  22. if (timer != undefined) { clearTimeout(timer); timer = undefined; }
  23. if (countdownTimer != undefined) { clearInterval(countdownTimer); countdownTimer = undefined; }
  24. if (controls != null) countdown.textContent = '--';
  25. }
  26.  
  27. const btnSubmit = document.body.querySelector('button#add-cover-art-submit');
  28. if (btnSubmit == null) throw 'Submit button not found';
  29. let haveErrors = false, active = true, timer, controls = null;
  30. let counter, countdownTimer, countdownTime, countdown, countdownWrapper;
  31. let retryCounter = 0, retryDelay = GM_getValue('retry_delay', 5);
  32. const caption = (state = 0) => ['Suspend', 'Resume'][state] + ' autoretry';
  33. new MutationObserver(function(ml, mo) {
  34. for (let mutation of ml) if (!mutation.target.disabled)
  35. if (Array.prototype.some.call(document.querySelectorAll('form#add-cover-art > table > tbody > tr > td > span.msg.error'),
  36. span => span.style.display != 'none' && /^⚠ (?:Server busy|Error)\b/.test(span.textContent.trim()))) {
  37. haveErrors = true;
  38. if (controls == null) {
  39. controls = Object.assign(document.createElement('span'), {
  40. style: `
  41. position: fixed; right: 10px; bottom: 10px; padding: 10px; background-color: white;
  42. border: solid black thin; box-shadow: 1pt 1pt 2pt gray;
  43. display: inline-flex; flex-flow: row; column-gap: 1em; z-index: 999;`,
  44. className: 'autoretry-control',
  45. });
  46. const infoWrapper = Object.assign(document.createElement('div'), {
  47. style: 'padding: 5pt 0;',
  48. });
  49. countdownWrapper = document.createElement('span');
  50. countdownWrapper.append(' in ', countdown = Object.assign(document.createElement('span'), {
  51. style: 'font-weight: bold; display: inline-block; min-width: 2em; text-align: right;',
  52. id: 'autoretry-countdown',
  53. }), ' s');
  54. infoWrapper.append('Retry #', counter = Object.assign(document.createElement('span'), {
  55. style: 'font-weight: bold; color: red;',
  56. id: 'retry-counter',
  57. }), countdownWrapper);
  58. controls.append(Object.assign(document.createElement('button'), {
  59. textContent: caption(),
  60. style: 'padding: 5px 10px; cursor: pointer; border: 1px solid #CCC; border-top: 1px solid #EEE; border-left: 1px solid #EEE;',
  61. id: 'autoretry',
  62. onclick: function(evt) {
  63. if (active) clearTimers();
  64. if ((active = !active) && !btnSubmit.disabled) btnSubmit.click();
  65. evt.currentTarget.textContent = caption(active ? 0 : 1);
  66. countdownWrapper.style.opacity = active ? 1 : 0.3;
  67. return false;
  68. },
  69. }), infoWrapper);
  70. document.body.append(controls);
  71. }
  72. const log10 = Math.log10(retryCounter);
  73. counter.textContent = ++retryCounter;
  74. if (log10 > 0 && log10 % 1 == 0 && retryDelay > 0) retryDelay *= log10 + 1;
  75. if (active) timer = setTimeout(function(elem) {
  76. clearTimers();
  77. if (active && elem instanceof HTMLElement && !elem.disabled) elem.click();
  78. }, retryDelay * 1000 || 0, btnSubmit); else continue;
  79. if ((countdownTime = retryDelay) > 0) {
  80. countdown.textContent = countdownTime;
  81. countdownTimer = setInterval(elem => { elem.textContent = --countdownTime }, 1000, countdown);
  82. }
  83. } else if (haveErrors) {
  84. mo.disconnect();
  85. if (controls != null) controls.remove();
  86. GM_notification({ text: 'Cover art upload successfully completed', title: 'MusicBrainz', highlight: false });
  87. document.location.assign(document.location.origin + document.location.pathname
  88. .replace(/\/(?:add-cover-art)\/?$/i, '/cover-art'));
  89. }
  90. else clearTimers();
  91. }).observe(btnSubmit, { attributes: true, attributeFilter: ['disabled'] });
  92.  
  93. }