Bookmarklets and Userscripts for MusicBrainz.org
MIT License
Bookmarklets and Userscripts for MusicBrainz.org
In order to use one of the bookmarklets you have to save the compressed code snippet from the respective section below as a bookmark. Make sure to add the bookmark to a toolbar of your browser which you can easily access while you are editing on MusicBrainz.
While bookmarklets are good for trying things out because they do not require additional software to be installed, userscripts are more convenient if you need a snippet frequently. In case you have installed a userscript manager browser extension you can simply install userscripts from this page by clicking the Install button. Another benefit of them is that you will receive automatic updates if your userscript manager is configured accordingly.
Searches and replaces ASCII punctuation symbols for many input fields by their preferred Unicode counterparts. Provides “Guess punctuation” buttons for titles, names, disambiguation comments, annotations and edit notes on all entity edit and creation pages.
Also available as a bookmarklet with less features:
Supports the same fields as the userscript but without language detection and granular control over the affected fields.
javascript:(()=>{function e(e,t){const a="background-color";$(e).css(a,"").each((e,n)=>{let g=n.value;g&&(g=((e,t)=>(t.forEach(([t,a])=>{e=e.replace(t,a)}),e))(g,t),g!=n.value&&$(n).val(g).trigger("change").css(a,"yellow"))})}const t=[[/(?<=[^\p{L}\d]|^)"(.+?)"(?=[^\p{L}\d]|$)/gu,"\u201c$1\u201d"],[/(?<=\W|^)'(n)'(?=\W|$)/gi,"\u2019$1\u2019"],[/(?<=[^\p{L}\d]|^)'(.+?)'(?=[^\p{L}\d]|$)/gu,"\u2018$1\u2019"],[/(\d+)"/g,"$1\u2033"],[/(\d+)'(\d+)/g,"$1\u2032$2"],[/'/g,"\u2019"],[/(?<!\.)\.{3}(?!\.)/g,"\u2026"],[/ - /g," \u2013 "],[/\d{4}-\d{2}(?:-\d{2})?(?=\W|$)/g,e=>Number.isNaN(Date.parse(e))?e:e.replaceAll("-","\u2010")],[/\d+(-\d+){2,}/g,e=>e.replaceAll("-","\u2012")],[/(\d+)-(\d+)/g,"$1\u2013$2"],[/(?<=\S)-(?=\S)/g,"\u2010"]],a=[[/\[(.+?)(\|.+?)?\]/g,(e,t,a="")=>`[${btoa(t)}${a}]`],[/(?<=\/\/)(\S+)/g,(e,t)=>btoa(t)],[/'''/g,"<b>"],[/''/g,"<i>"],...t,[/<b>/g,"'''"],[/<i>/g,"''"],[/(?<=\/\/)([A-Za-z0-9+/=]+)/g,(e,t)=>atob(t)],[/\[([A-Za-z0-9+/=]+)(\|.+?)?\]/g,(e,t,a="")=>`[${atob(t)}${a}]`]];e("input#name,input#comment,input.track-name,input[id^=medium-title],input[name$=name],input[name$=comment]",t),e("#annotation,#edit-note-text,textarea[name$=text],.edit-note",a)})();
Imports German broadcast releases from the ARD radio drama database.
Parses copyright notices and automates the process of creating release and recording relationships for these.
credits
) and the edit note (edit-note
) via custom query parameters, which are encoded into the hash of the URL (Example: /edit-relationships#credits=(C)+2023+Test&edit-note=Seeding+example
).Parses voice actor credits from text and automates the process of creating release or recording relationships for these. Also imports credits from Discogs.
credits
) and the edit note (edit-note
) via custom query parameters, which are encoded into the hash of the URL (Example: /edit-relationships#credits=Narrator+-+John+Doe&edit-note=Seeding+example
).[entity-type:mbid|label]
links.javascript:((a,e)=>{const t="background-color";$("textarea[name$=text],textarea[name$=description],textarea[name$=biography]").css(t,"").each((a,n)=>{let r=n.value;r&&(r=((a,e)=>(e.forEach(([e,t])=>{a=a.replace(e,t)}),a))(r,e),r!=n.value&&$(n).val(r).trigger("change").css(t,"yellow"))})})(0,[[/\[(.+?)\]\((.+?)\)/g,"[$2|$1]"],[/(?<!\[)(https?:\/\/\S+)/g,"[$1]"],[/\[(.+?)(\|.+?)?\]/g,(a,e,t="")=>`[${btoa(e)}${t}]`],[/(__|\*\*)(?=\S)(.+?)(?<=\S)\1/g,"'''$2'''"],[/(_|\*)(?=\S)(.+?)(?<=\S)\1/g,"''$2''"],[/^\# +(.+?)( +\#*)?$/gm,"= $1 ="],[/^\#{2} +(.+?)( +\#*)?$/gm,"== $1 =="],[/^\#{3} +(.+?)( +\#*)?$/gm,"=== $1 ==="],[/^(\d+)\. +/gm," $1. "],[/^[-+*] +/gm," * "],[/\[([A-Za-z0-9+/=]+)(\|.+?)?\]/g,(a,e,t="")=>`[${atob(e)}${t}]`]]),void $("textarea[name$=text],textarea[name$=description],textarea[name$=biography]").each(async(a,e)=>{e.disabled=!0;let t=await(n=e.value,(async(a,e,t)=>{const $=[];a.replace(e,(a,...e)=>{const t=((a,e,t)=>(async(a,e)=>{if(a.includes("musicbrainz.org")){const t=new URL(a),[$,n,r]=t.pathname.match(/^\/(.+?)\/([0-9a-f-]{36})$/)||[];if($)return e||(e=await(async a=>{a.pathname="/ws/2"+a.pathname,a.search="?fmt=json";let e=await fetch(a);return e=await e.json(),e.name||e.title})(t)),`[${n}:${r}|${e}]`}return((a,e)=>e?`[${a}|${e}]`:`[${a}]`)(a,e)})(e,t))(a,...e);$.push(t)});const n=await Promise.all($);return a.replace(e,()=>n.shift())})(n,/\[(.+?)(?:\|(.+?))?\]/g));var n;t!=e.value&&$(e).val(t),e.disabled=!1});
javascript:(()=>{async function t(t){return(await fetch("/ws/js/entity/"+t)).json()}const e={_lineage:[],_original:null,_status:1,attributes:null,begin_date:null,editsPending:!1,end_date:null,ended:!1,entity0_credit:"",entity1_credit:"",id:null,linkOrder:0,linkTypeID:null};function i({source:t=MB.relationshipEditor.state.entity,target:i,batchSelectionCount:n=null,...a}){const r=((t,e,i=!1)=>t===e?i:t>e)(t.entityType,i.entityType,a.backward??!1);MB.relationshipEditor.dispatch({type:"update-relationship-state",sourceEntity:t,batchSelectionCount:n,creditsToChangeForSource:"",creditsToChangeForTarget:"",newRelationshipState:{...e,entity0:r?i:t,entity1:r?t:i,id:MB.relationshipEditor.getRelationshipStateId(),...a},oldRelationshipState:null})}function n(...t){return MB.tree.fromDistinctAscArray(t.map(t=>{const e=MB.linkedEntities.link_attribute_type[t.type.gid];return{...t,type:e,typeID:e.id}}))}const a=prompt("MBIDs of RGs which should be added as parts of the series:");a&&(async e=>{for(let a of e){const e=await t(a),r=e.name.match(/\d+/)?.[0];i({target:e,linkTypeID:742,attributes:n({type:{gid:"a59c5830-5ec7-38fe-9a21-c7ea54f6650a"},text_value:r??""})})}})(Array.from(a.matchAll(/[0-9a-f-]{36}/gm),t=>t[0]))})();
javascript:(()=>{function e(e,t){$("input.partial-date-"+e).val(t).trigger("change")}const t=prompt("Date for all release events (YYYY-MM-DD):");if(null!==t){const[,a,n,l]=/(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?/.exec(t)||[];((t,a,n)=>{e("year",t),e("month",a),e("day",n)})(a,n,l)}})();
javascript:(()=>{function e(e,t,i=new Event("change")){e.value=t,e.dispatchEvent(i)}((e,t=document)=>t.querySelectorAll(e))("input[id^=medium-title]").forEach(t=>e(t,t.value.replace(/^(Cassette|CD|Dis[ck]|DVD|SACD|Vinyl)\s*\d+/i,"").trim()));const t=document.getElementById("edit-note-text");e(t,"Clear redundant medium titles, see https://musicbrainz.org/doc/Style/Release#Medium_title\n"+t.value)})();
javascript:(()=>{function t(t,e=document){return e.querySelector(t)}function e(t,e=document){return e.querySelectorAll(t)}const n='ul.cover-art-type-checkboxes input[type="checkbox"]';(({additionalTypes:c=[],commentPattern:o}={})=>{const a=e('tbody[data-bind="foreach: files_to_upload"] > tr'),r={};e(n,a[0]).forEach(t=>{t.parentElement.textContent.trim().toLowerCase().split("/").forEach(e=>r[e]=t.value)});const i=RegExp(String.raw`(?<=\W|_|^)(${Object.keys(r).join("|")})(?=\W|_|$)`,"gi");a.forEach(a=>{const l=t('.file-info span[data-bind="text: name"]',a).textContent,s=Array.from(l.matchAll(i),t=>t[0]);if(c&&s.push(...c),s.length&&((t,c=[])=>{e(n,t).forEach(t=>{c.includes(t.value)&&(t.checked=!0,t.dispatchEvent(new Event("click")))})})(a,s.map(t=>r[t.toLowerCase()])),o){const e=l.match(o);e&&((e,n)=>{const c=t("input.comment",e);c.value=n,c.dispatchEvent(new Event("change"))})(a,e[0])}})})({commentPattern:/(?<=\().+?(?=\))/})})();
+
_
+_, Part
renames track 27/143 "Title" to "Title, Part 027"javascript:(()=>{const e=prompt("Numbering prefix, preceded by flags:\n+ append to current titles\n_ pad numbers","Part ");if(null!==e){let[,t,n]=e.match(/^([+_]*)(.*)/);t={append:t.includes("+"),padNumbers:t.includes("_")},((e="",t={})=>{let n=$("input.track-name");const r=n.length.toString().length,a=new Intl.NumberFormat("en",{minimumIntegerDigits:r});n.each((n,r)=>{let l=n+1;t.padNumbers&&(l=a.format(l));let p=e+l;t.append&&(p=(r.value+p).replace(/([.!?]),/,"$1")),$(r).val(p)}).trigger("change")})(n,t)}})();
javascript:void $(".expand-medium").trigger("click");
javascript:(()=>{function t(t){return new Promise(e=>setTimeout(e,t))}(async e=>{const i=document.querySelector("h1 bdi").textContent.match(/(.+?)(?: (\d+))?:/);if(!i)return;const a=MB.relationshipEditor.state.entity;(async({source:e=MB.relationshipEditor.state.entity,target:i,targetType:a,linkTypeId:o,attributes:r,batchSelection:s=!1}={})=>{const n="string"==typeof i;var p,c;if(i&&!n&&(a=i.entityType),MB.relationshipEditor.dispatch({type:"update-dialog-location",location:{source:e,batchSelection:s}}),await(p=()=>!!MB.relationshipEditor.relationshipDialogDispatch,new Promise(async e=>{for(;!1===p();)await t(1);e()})),a&&MB.relationshipEditor.relationshipDialogDispatch({type:"update-target-type",source:e,targetType:a}),o){const i=await(async(e,{retries:i=10,wait:a=0}={})=>{do{const i=await e();if(void 0!==i)return i;a&&await t(a)}while(i--)})(()=>MB.relationshipEditor.relationshipDialogState.linkType.autocomplete.items.find(t=>t.id==o),{wait:10});i&&MB.relationshipEditor.relationshipDialogDispatch({type:"update-link-type",source:e,action:{type:"update-autocomplete",source:e,action:{type:"select-item",item:i}}})}r&&(t=>{MB.relationshipEditor.relationshipDialogDispatch({type:"set-attributes",attributes:t})})(r),i&&((n?[{type:"type-value",value:i},{type:"search-after-timeout",searchTerm:i}]:[{type:"select-item",item:(c=i,{type:"option",id:c.id,name:c.name,entity:c})}]).forEach(t=>{MB.relationshipEditor.relationshipDialogDispatch({type:"update-target-entity",source:e,action:{type:"update-autocomplete",source:e,action:t}})}),n&&((t,e=document)=>e.querySelector(t))("input.relationship-target").focus())})({source:a.releaseGroup??a,target:i[1],targetType:"series",linkTypeId:742,attributes:[{type:{gid:"a59c5830-5ec7-38fe-9a21-c7ea54f6650a"},text_value:i[2]}]})})()})();
javascript:(()=>{const a=location.pathname.match(/release\/([0-9a-f-]{36})/)?.[1];(a=>{a&&open("https://magicisrc.kepstin.ca?mbid="+a)})(a)})();
javascript:$(".remove-release-event:not(:first)").trigger("click"),void $("#country-0").val(240).trigger("change");
javascript:(()=>{async function t(t){return(await fetch("/ws/js/entity/"+t)).json()}const e={_lineage:[],_original:null,_status:1,attributes:null,begin_date:null,editsPending:!1,end_date:null,ended:!1,entity0_credit:"",entity1_credit:"",id:null,linkOrder:0,linkTypeID:null};function i({source:t=MB.relationshipEditor.state.entity,target:i,batchSelectionCount:n=null,...a}){const o=((t,e,i=!1)=>t===e?i:t>e)(t.entityType,i.entityType,a.backward??!1);MB.relationshipEditor.dispatch({type:"update-relationship-state",sourceEntity:t,batchSelectionCount:n,creditsToChangeForSource:"",creditsToChangeForTarget:"",newRelationshipState:{...e,entity0:o?i:t,entity1:o?t:i,id:MB.relationshipEditor.getRelationshipStateId(),...a},oldRelationshipState:null})}const n=prompt("MBIDs of entities which should be related to this entity:");if(n){const e=Array.from(n.matchAll(/[0-9a-f-]{36}/gm),t=>t[0]),a=MB.relationshipEditor.relationshipDialogState;a&&(async(e,n,a=!1)=>{for(let o of e)i({target:await t(o),linkTypeID:n,backward:a})})(e,a.linkType.autocomplete.selectedItem.entity.id,a.backward)}})();
javascript:(()=>{const e=Array.from(document.querySelectorAll("head > link[rel=alternate]")).map(e=>e.hreflang).map(e=>e.split("-")[1]).filter((e,l,r)=>e&&r.indexOf(e)===l);alert(`Available in ${e.length} countries\n${e.sort().join(", ")}`)})();
javascript:(()=>{const s=window.location.href.match(/(artist|label|master|release)\/(\d+)/)?.slice(1);s&&open(((s,e)=>`https://api.discogs.com/${s}s/${e}`)(...s))})();
Running npm run build
compiles all userscripts and all bookmarklets before it generates an updated version of README.md
. Before you can run this command you have to ensure that you have setup Node.js and have installed the dependencies of the build script via npm install
.
Do you want to create your own userscript or bookmarklet project for MusicBrainz without having to rewrite everything from scratch?
Have a look at my userscript bundler tools which are used to build the userscripts, bookmarklets and the README in this repository.
You can also reuse code from this repository by installing it as a dependency of your own Node.js project:
npm install kellnerd/musicbrainz-scripts
The package gives you access to all the MusicBrainz specific modules under src except for the main modules of the bookmarklets and userscripts themselves.
The primary entry point of the @kellnerd/musicbrainz-scripts
package is index.js which provides import shortcuts for stable, potentially useful pieces of code from src/
.
Shortcut usage: import { buildEditNote } from '@kellnerd/musicbrainz-scripts';
Full specifier: import { buildEditNote } from '@kellnerd/musicbrainz-scripts/src/editNote.js';