- // ==UserScript==
- // @name MusicBrainz function library
- // @namespace http://www.jens-bertram.net/userscripts/mbz-lib
- // @description Musicbrainz function library.
- // @require https://code.jquery.com/jquery-2.1.1.min.js
- // @version 0.2.0beta
- // @grant none
- // @supportURL https://github.com/JensBee/userscripts
- // @license MIT
- // ==/UserScript==
- window.MBZ = {};
-
- MBZ.baseUrl = 'https://musicbrainz.org/';
- MBZ.iconUrl = MBZ.baseUrl + 'favicon.ico',
-
- MBZ.Html = {
- /**
- * Add CSS entry to pages <head/>.
- * @param style definition to add
- */
- _addGlobalStyle: function(css) {
- if ($('head').length == 0) {
- $('body').append($('<head>'));
- }
- var style = $('head>style');
- if (style.length == 0) {
- style = $('<style>');
- style.attr('type', 'text/css');
- $('head').append(style);
- }
- style.append(css);
- },
-
- _init: function() {
- this._addGlobalStyle(
- 'button.mbzButton {cursor:pointer;' +
- 'text-decoration:none; text-shadow:-1px -1px 0 rgba(255,201,97,0.3); font-weight:bold; color:#000;' +
- 'padding:5px 5px 5px 25px; border-radius:5px;' +
- 'border-top:1px solid #736CAE; border-left:1px solid #736CAE;' +
- 'border-bottom:1px solid #FFC961; border-right:1px solid #FFC961;' +
- 'background:#FFE3B0 url("' + MBZ.iconUrl + '") no-repeat 5px center;}' +
- 'button.mbzButton:hover {' +
- 'border:1px solid #454074; background-color:#FFD88C;}' +
- 'button.mbzButton:disabled {cursor:default;' +
- 'border:1px solid #ccc; background-color:#ccc; color:#5a5a5a;}'
- );
- },
-
- /**
- * Create a MusicBrainz link.
- * @params[type] type to link to (e.g. release)
- * @params[id] mbid to link to (optional)
- * @params[more] stuff to add after mbid + '/' (optional)
- * @return plain link text
- */
- getLink: function (params) {
- return MBZ.baseUrl + params.type + '/' + (params.id ? params.id + '/' : '') + (params.more || '');
- },
-
- /**
- * Create a MusicBrainz link.
- * @params[type] type to link to (e.g. release)
- * @params[id] mbid to link to (optional)
- * @params[more] stuff to add after mbid + '/' (optional)
- * @params[title] link title attribute (optional)
- * @params[text] link text (optional)
- * @params[before] stuff to put before link (optional)
- * @params[after] stuff to put after link (optional)
- * @params[icon] true/false: include MusicBrainz icon (optional, default: true)
- * @return link jQuery object
- */
- getLinkElement: function (params) {
- params.icon = (typeof params.icon !== 'undefined' && params.icon == false ? false : true);
- var retEl = $('<div style="display:inline-block;">');
- if (params.before) {
- retEl.append(params.before);
- }
- var linkEl = $('<a>' + (params.icon ? this.mbzIcon : '') + (params.text || '') + '</a>');
- linkEl.attr('href', this.getLink({
- type: params.type,
- id: params.id,
- more: params.more
- })).attr('target', '_blank');
- if (params.title) {
- linkEl.attr('title', params.title);
- }
- retEl.append(linkEl);
- if (params.after) {
- retEl.append(params.after);
- }
- return retEl;
- },
-
- getMbzButton: function(caption, title) {
- var btn = $('<button type="button" class="mbzButton">' + caption + '</button>');
- if (title) {
- btn.attr('title', title);
- }
- return btn;
- },
-
- mbzIcon: '<img src="' + MBZ.iconUrl + '" />'
- };
- MBZ.Html._init();
-
- MBZ.Util = {
- /**
- * Convert anything to string.
- * @data object
- */
- asString: function (data) {
- if (data == null) {
- return '';
- }
- switch (typeof data) {
- case 'string':
- return data.trim();
- case 'object':
- return data.toString().trim();
- case 'function':
- return 'function';
- case 'undefined':
- return '';
- default:
- data = data + '';
- return data.trim();
- }
- },
-
- /**
- * Creates http + https url from a given https? url.
- * @url http/https url
- * @return array with given url prefixed with http + https or single url, if not https? protocol
- */
- expandProtocol: function(url) {
- var urls;
- if (url.toLowerCase().startsWith('http')) {
- var urlPath = url.replace(/^https?:\/\//,'');
- urls = ['http://' + urlPath, 'https://' + urlPath];
- } else {
- urls = [url];
- }
- return urls;
- },
-
- /**
- * Creates http + https urls from a given array of https? urls.
- * @urls array of http/https urls
- * @return array with given urls prefixed with http + https
- */
- expandProtocols: function(urls) {
- var newUrls = [];
- var self = this;
- $.each(urls, function(idx, val){
- newUrls = newUrls.concat(self.expandProtocol(val));
- });
- return newUrls;
- },
-
- /**
- * Convert HH:MM:SS, MM:SS, SS to seconds.
- * http://stackoverflow.com/a/9640417
- * @str string
- * @return seconds extracted from initial string
- */
- hmsToSeconds: function (str) {
- str = MBZ.Util.asString(str);
- if (str.indexOf(':') > -1) {
- var p = str.split(':'), s = 0, m = 1;
-
- while (p.length > 0) {
- s += m * parseInt(p.pop(), 10);
- m *= 60;
- }
-
- return s;
- } else {
- return str;
- }
- },
-
- /**
- * Remove a trailing slash from a string
- * @str string
- * @return intial string with trailing slash removed
- */
- rmTrSlash: function (str) {
- if(str.substr(-1) == '/') {
- return str.substr(0, str.length - 1);
- }
- return str;
- }
- };
-
- MBZ.CA = {
- baseUrl: 'https://coverartarchive.org/',
- // no https here (bad_cert notice)
- originBaseUrl: 'https://cors-anywhere.herokuapp.com/coverartarchive.org:443/',
-
- /**
- * Create a CoverArtArchive link.
- * @params[type] type to link to (e.g. release)
- * @params[id] mbid to link to (optional)
- * @params[more] stuff to add after mbid (optional)
- */
- getLink: function (params) {
- return this.originBaseUrl + params.type + '/' + (params.id ? params.id + '/' : '') + (params.more || '');
- },
- };
-
- /**
- * MusicBrainz web service v2 interface.
- */
- MBZ.WS = {
- _baseUrl: MBZ.baseUrl + 'ws/2/',
- _queue: [],
- _pollFreq: 1100,
- _pollInterval: null,
-
- /**
- * Add to request queue.
- * @params[cb] callback
- * @params[url] request url
- * @params[args] callback function parameters object
- * @params[scope] scope for calling callback function
- */
- _qAdd: function(params) {
- this._queue.push(params);
- if (!this._pollInterval) {
- if (this._queue.length == 1) {
- this._qPoll();
- }
- this._pollInterval = setInterval(this._qPoll, this._pollFreq);
- }
- },
-
- /**
- * Execute queued requests.
- */
- _qPoll: function() {
- if (MBZ.WS._queue.length > 0) {
- var item = MBZ.WS._queue.pop();
- $.getJSON(item.url, function(data) {
- if (item.args) {
- if (item.scope) {
- item.cb.call(item.scope, data, item.args);
- } else {
- item.cb(data, item.args);
- }
- } else {
- if (item.scope) {
- item.cb.call(item.scope, data);
- } else {
- item.cb(data);
- }
- }
- }).fail(function(jqxhr, textStatus, error) {
- var err = textStatus + ', ' + error;
- console.error("Request (" + item.url + ") failed: " + err);
- if (item.scope) {
- item.cb.call(item.scope);
- } else {
- item.cb();
- }
- });
- } else if (MBZ.WS._queue.length == 0 && MBZ.WS._pollInterval) {
- clearInterval(MBZ.WS._pollInterval);
- }
- },
-
- /**
- * Lookup a musicbrainz url relation
- * @params[cb] callback function
- * @params[res] url to lookup
- * @params[rel] relation type
- * @params[scope] scope for callback function
- */
- getUrlRelation: function (params) {
- this._qAdd({
- cb: params.cb,
- url: this._baseUrl + 'url?resource=' + encodeURIComponent(params.res) + '&inc=' + params.rel + '-rels',
- scope: params.scope
- });
- },
-
- /**
- * Lookup musicbrainz url relations
- * @params[urls] array of urls to lookup
- * @params[rel] relation type
- * @params[cb] callback function for each response
- * @params[cbInc] callback for each item looked up
- * @params[cbDone] callback to call if all items have been looked up
- * @params[scope] scope for callback functions
- */
- getUrlRelations: function(params) {
- var self = this;
- var count = params.urls.length;
- var current = 0;
- function localCb(data) {
- if (params.scope) {
- params.cb.call(params.scope, data);
- } else {
- params.cb(data);
- }
- if (typeof params.cbInc === 'function') {
- if (params.scope) {
- params.cbInc.call(params.scope);
- } else {
- params.cbInc();
- }
- }
- if (++current == count && typeof params.cbDone === 'function') {
- if (params.scope) {
- params.cbDone.call(params.scope);
- } else {
- params.cbDone();
- }
- }
- }
- $.each(params.urls, function(idx, val) {
- self.getUrlRelation({
- cb: localCb,
- res: val,
- rel: params.rel
- });
- });
- }
- };
-
- /**
- * Release related functions.
- */
- MBZ.Release = function() {
- var form = $('<form method="post" id="' + MBZ.Release._form.baseName + '-' + (MBZ.Release._form.count++) +
- '" target="_blank" action="' + MBZ.Release._form.target + '" acceptCharset="UTF-8"></form>');
-
- this.data = {
- annotation: '', // content
- artists: [],
- labels: [],
- mediums: [],
- note: '', // content
- packaging: '', // type
- releases: [],
- title: '', // content
- tracks: [],
- urls: [] // [target, type]
- };
-
- function addField(name, value) {
- name = MBZ.Util.asString(name);
- value = MBZ.Util.asString(value);
- if (name.length > 0 && value.length > 0) {
- form.append($('<input type="hidden" name="' + name + '" value="' + value
- .replace(/&/g, '&')
- .replace(/"/g, '"')
- .replace(/'/g, ''')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- + '"/>'));
- }
- }
-
- function buildForm(dataSet) {
- if (dataSet.annotation != '') {
- addField('annotation', dataSet.annotation);
- }
-
- if (dataSet.artists.length > 0) {
- $.each(dataSet.artists, function(idx, val) {
- var prefix = 'artist_credit.names.' + (val.idx || idx);
- addField(prefix + '.name', val.cred);
- addField(prefix + '.mbid', val.id);
- addField(prefix + '.artist.name', val.name);
- addField(prefix + '.join_phrase', val.join);
- });
- }
-
- if (dataSet.labels.length > 0) {
- $.each(dataSet.labels, function(idx, val) {
- var prefix = 'labels.' + (val.idx || idx);
- addField(prefix + '.mbid', val.id);
- addField(prefix + '.name', val.name);
- addField(prefix + '.catalog_number', val.catNo);
- });
- }
-
- if (dataSet.note != '') {
- addField('edit_note', dataSet.note);
- }
-
- if (dataSet.releases.length > 0) {
- $.each(dataSet.releases, function(idx, val) {
- var prefix = 'events.' + (val.idx || idx);
- addField(prefix + '.date.year', val.y);
- addField(prefix + '.date.month', val.m);
- addField(prefix + '.date.day', val.d);
- addField(prefix + '.country', val.cc);
- });
- }
-
- $.each(dataSet.mediums, function(idx, val) {
- var prefix = 'mediums.' + (val.idx || idx);
- addField(prefix + '.format', val.fmt);
- addField(prefix + '.name', val.name);
- });
-
- if (dataSet.packaging != '') {
- addField('packaging', dataSet.packaging);
- }
-
- if (dataSet.title != '') {
- addField('name', dataSet.title);
- }
-
- $.each(dataSet.tracks, function(idx, val) {
- var prefix = 'mediums.' + val.med + '.track.' + (val.idx || idx);
- addField(prefix + '.name', val.tit);
- addField(prefix + '.number', val.num);
- addField(prefix + '.recording', val.recId);
- addField(prefix + '.length', val.dur);
-
- if (val.artists) {
- $.each(val.artists, function(aIdx, aVal) {
- var aPrefix = prefix + '.artist_credit.names.' + (aVal.idx || aIdx);
- addField(aPrefix + '.name', aVal.cred);
- addField(aPrefix + '.mbid', aVal.id);
- addField(aPrefix + '.artist.name', aVal.name);
- addField(aPrefix + '.join_phrase', aVal.join);
- });
- }
- });
-
- if (dataSet.urls.length > 0) {
- $.each(dataSet.urls, function(idx, val) {
- addField('urls.' + idx + '.url', val[0]);
- addField('urls.' + idx + '.link_type', val[1]);
- });
- }
- }
-
- /**
- * Submit data to musicbrainz.
- */
- this.submitRelease = function() {
- buildForm(this.data);
- $('body').append(form);
- form.submit();
- };
- };
-
- MBZ.Release._relationCb = function(data) {
- if (!data) {
- return {};
- }
- if (data.relations) {
- var rels = {_res: data.resource};
- $.each(data.relations, function(idx, val) {
- var id = val.release.id;
- var type = val.type;
- if (!rels[id]) {
- rels[id] = [];
- }
- if (rels[id].indexOf(type) == -1) {
- rels[id].push(type);
- }
- });
- return rels;
- }
- };
-
- MBZ.Release._form = {
- baseName: 'mbAddReleaseForm',
- count: 0,
- target: MBZ.baseUrl + 'release/add'
- };
-
- /**
- * Lookup a musicbrainz url relation for 'release' type.
- * @params[cb] callback function
- * @params[res] url to lookup
- * @params[scope] scope for callback function
- */
- MBZ.Release.getUrlRelation = function(params) {
- function innerCb(cbData) {
- if (params.scope) {
- params.cb.call(params.scope, MBZ.Release._relationCb(cbData));
- } else {
- params.cb(MBZ.Release._relationCb(cbData));
- }
- }
- MBZ.WS.getUrlRelation({
- cb: innerCb,
- res: params.res,
- rel: 'release',
- scope: params.scope
- });
- };
-
- /**
- * Lookup musicbrainz url relations for 'release' type.
- * @params[urls] array of urls to lookup
- * @params[cb] callback function for each response
- * @params[cbInc] callback for each item looked up
- * @params[cbDone] callback to call if all items have been looked up
- * @params[scope] scope for callback functions
- */
- MBZ.Release.getUrlRelations = function(params) {
- function innerCb(cbData) {
- if (params.scope) {
- params.cb.call(params.scope, MBZ.Release._relationCb(cbData));
- } else {
- params.cb(MBZ.Release._relationCb(cbData));
- }
- }
- MBZ.WS.getUrlRelations({
- urls: params.urls,
- rel: 'release',
- cb: innerCb,
- cbInc: params.cbInc,
- cbDone: params.cbDone,
- scope: params.scope
- });
- };
-
- MBZ.Release.prototype = {
- /**
- * Add an artist entry.
- * @params plain artist name as string or object:
- * params[cred] artist name as credited
- * params[id] artists mbid
- * params[idx] position
- * params[join] phrase to join with next artist
- * params[name] artist name
- */
- addArtist: function(params) {
- if (typeof params === 'string') {
- this.data.artists.push({name: params});
- } else {
- this.data.artists.push(params);
- }
- },
-
- /**
- * Add a label entry.
- * @params plain label name as string or object.
- * params[catNo] catalog number
- * params[id] mbid
- * params[idx] position
- * params[name] label name
- */
- addLabel: function(params) {
- if (typeof params === 'string') {
- this.data.labels.push({name: params});
- } else {
- this.data.labels.push(params);
- }
- },
-
- /**
- * Set format of a medium.
- * @params[idx] position
- * @params[fmt] format type name
- * @params[name] name
- */
- addMedium: function(params) {
- this.data.mediums.push(params)
- },
-
- /**
- * Add a release event.
- * @params[y] YYYY
- * @params[m] MM
- * @params[d] DD
- * @params[cc] country code
- * @params[idx] position
- */
- addRelease: function(params) {
- this.data.releases.push(params);
- },
-
- /**
- * Add a track.
- * @params[med] medium number
- * @params[tit] track name
- * @params[idx] track number
- * @params[num] track number (free-form)
- * @params[dur] length in MM:SS or milliseconds
- * @params[recId] mbid of existing recording to associate
- * @params[artists] array of objects:
- * obj[cred] artist name as credited
- * obj[id] artists mbid
- * obj[idx] position
- * obj[join] phrase to join with next artist
- * obj[name] artist name
- */
- addTrack: function(params) {
- this.data.tracks.push(params);
- },
-
- /**
- * @url target url
- * @type musicbrainz url type
- * @return true if value was added
- */
- addUrl: function(url, type) {
- url = MBZ.Util.asString(url);
- type = MBZ.Util.asString(type);
-
- this.data.urls.push([url, type]);
- return true;
- },
-
- /**
- * Dump current data (best viewed in FireBug).
- */
- dump: function() {
- console.log(this.data);
- },
-
- /**
- * @content annotation content
- * @return old value
- */
- setAnnotation: function(content) {
- var old = this.data.annotation;
- this.data.annotation = MBZ.Util.asString(content);
- return old;
- },
-
- /**
- * @content edeting note content
- * @return old value
- */
- setNote: function(content) {
- var old = this.data.note;
- this.data.note = MBZ.Util.asString(content);
- return old;
- },
-
- /**
- * @content packaging type
- * @return old value
- */
- setPackaging: function(type) {
- var old = this.data.packaging;
- this.data.packaging = MBZ.Util.asString(type);
- return old;
- },
-
- /**
- * @name release title
- * @return old value
- */
- setTitle: function(name) {
- var old = this.data.title;
- this.data.title = MBZ.Util.asString(name);
- return old;
- },
- };