Skip to content

Instantly share code, notes, and snippets.

@mirka
Last active August 3, 2018 11:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mirka/775dee5c8a4a9fe203469a3d49d75876 to your computer and use it in GitHub Desktop.
Save mirka/775dee5c8a4a9fe203469a3d49d75876 to your computer and use it in GitHub Desktop.
// ==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 (result) {
var obj = JSON.parse(result.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/results/" + 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 (result) {
access_token = JSON.parse(result.responseText).access_token;
if (access_token) {
main();
} else {
console.log("Spotify API Authentication error: " + result.responseText);
}
}
});
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment