Add a Spotify link and embedded player on iTunes album/artist pages
// ==UserScript== // @name Spotify Links On iTunes // @description Add a Spotify link and embedded player on iTunes album/artist pages // @author mirka // @include https://itunes.apple.com/*/artist/* // @include https://itunes.apple.com/artist/* // @include https://itunes.apple.com/*/album/* // @include https://itunes.apple.com/album/* // @namespace http://jaguchi.com // @require https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js // @grant GM_addStyle // @grant GM_xmlhttpRequest // @connect jaguchi.com // @connect spotify.com // @version 4.0.3 // ==/UserScript== (function () { /*========================================= User preferences ===========================================*/ // Set this to false if you want the Spotify link to open in the Web Player var open_in_app = true; /*========================================= User preferences (Advanced) ===========================================*/ // To use your own credentials, change `use_own_credentials` to true // and set your Client ID and Secret below. // (Register an application at https://developer.spotify.com/my-applications // to get your own credentials to authenticate directly with the Spotify API) var use_own_credentials = false; var client_id = ""; var client_secret = ""; /*========================================= Shared variables ===========================================*/ var spotify_btn_id = "spotify-btn"; var access_token; /*========================================= Link insertion handlers ===========================================*/ function insertSpotifyBtn(options) { var $target = $(".we-button__app-text").closest("button"); var $btn = $("<a />", { href: options.uri, id: spotify_btn_id, class: "we-button we-button--outlined we-button--external", html: "<span class='we-button__app-text'>Spotify </span>", css: { marginLeft: "6px", textDecoration: "none", }, }); $btn.attr("aria-label", $target.text().replace("Apple Music", "Spotify")); if (options.search) { $btn.text("Search on Spotify "); $btn.attr("title", "Search on Spotify"); $btn.attr("aria-label", "Search on Spotify"); } GM_addStyle("#" + spotify_btn_id + " { color: #29d264; border-color: #29d264; }"); $target.wrap("<div />"); // Work around for flexbox on Artist page $btn.insertAfter($target); } /*========================================= Spotify search handlers ===========================================*/ function getAlbumData() { var data = $('script[name="schema:music-album"]').text(); var parsed_data = $.parseJSON(data); return { title: parsed_data.name, artist: parsed_data.byArtist.name, }; } function getArtist() { var data = $('script[name="schema:music-group"]').text(); return $.parseJSON(data).name; } function searchAlbum(is_alt_query) { var album_data = getAlbumData(); var album = album_data.title; var artist = album_data.artist; var album_excludes = ["live", "remastered", "compilation", "original (motion picture )?soundtrack", "bonus track version", "(deluxe|exclusive|expanded|revised) (version|edition)", "(music|soundtrack) from the (motion picture|film score)", "feat\. .+"]; var character_regex = /[:&,/()]/g; var album_regex; var query; if (is_alt_query) { album_regex = new RegExp("\\((" + album_excludes.join("|") + ")\\)", "ig"); album = album.replace(album_regex, ""); album = album.replace(/\[.+\]/, ""); // remove bracketed fragment album = album.replace(/(- (single|ep))$/i, ""); album = album.replace(/(version|edition)\)/i, ""); album = album.replace(character_regex, " "); if (/,|&/.test(artist)) { artist = artist.match(/^(.+?)(,|&)/)[1]; // extract first fragment } artist = artist.replace(character_regex, ""); query = album + " " + artist; } else { query = "album:\"" + album + "\"" + " artist:\"" + artist + "\""; } searchSpotify(query, "album", is_alt_query); } function searchArtist() { var artist = getArtist(); var query = "artist:\"" + artist + "\""; searchSpotify(query, "artist"); } function searchSpotify(query, type, is_alt_query) { // type = "album" or "artist" var params = { q: query, type: type, limit: 1, }; var apiUrl = "https://jaguchi.com/spotify-links-on-itunes/search"; var headers = {}; if (use_own_credentials) { headers = { "Authorization": "Bearer " + access_token }; apiUrl = "https://api.spotify.com/v1/search"; } GM_xmlhttpRequest({ method: "GET", url: apiUrl + "?" + $.param(params), headers: headers, onload: function (r###lt) { var obj = JSON.parse(r###lt.responseText); var match_count = obj[type + "s"].total; var app_uri, external_uri; var btn_options; if (match_count == 0) { if (type == "album" && !is_alt_query) { searchAlbum(true); // try an alternative query return; } } else { // at least one match app_uri = obj[type + "s"].items[0].uri; external_uri = obj[type + "s"].items[0].external_urls.spotify; } btn_options = prepareBtnOptions(app_uri, external_uri); insertSpotifyBtn(btn_options); } }); // Choose appropriate uri, or build search uri if no matches found function prepareBtnOptions(app_uri, external_uri) { var uri = open_in_app ? app_uri : external_uri; var query_str = encodeURIComponent(query); var is_search = false; if (!uri) { // Build search uri is_search = true; if (open_in_app) { uri = "spotify:search:" + query_str; } else { uri = "https://open.spotify.com/search/r###lts/" + query_str; } } return { uri: uri, search: is_search, }; } } /*========================================= Page type (album/artist) detection ===========================================*/ if (use_own_credentials) { getAccessToken(); } else { main(); } function main() { detectPageAndRun(); setMutationObserver(detectPageAndRun); } function detectPageAndRun() { if ( /\/album\//.test(window.location.pathname) ) { searchAlbum(); } else { searchArtist(); } } function setMutationObserver(callback) { var observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { var el = mutation.target; if (mutation.oldValue && el.tagName == "LINK" && el.rel == "canonical") { $("#" + spotify_btn_id).remove(); callback(); } }); }); var options = { attributes: true, attributeOldValue: true, subtree: true, }; observer.observe(document.querySelector("head"), options); } function getAccessToken() { GM_xmlhttpRequest({ method: "POST", url: "https://accounts.spotify.com/api/token", data: "grant_type=client_credentials", headers: { "Authorization": "Basic " + btoa(client_id + ":" + client_secret), "Content-Type": "application/x-www-form-urlencoded", }, onload: function (r###lt) { access_token = JSON.parse(r###lt.responseText).access_token; if (access_token) { main(); } else { console.log("Spotify API Authentication error: " + r###lt.responseText); } } }); } })();