Spotify Links On iTunes

Add a Spotify link and embedded player on iTunes album/artist pages

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
				}
			}
		});
	}

})();