Add Spotify to YouTube

Adds a link to Spotify on music videos on YouTube

  1. // ==UserScript==
  2. // @name Add Spotify to YouTube
  3. // @description Adds a link to Spotify on music videos on YouTube
  4. // @include *://youtube.*/*
  5. // @include *://*.youtube.*/*
  6. // @version 0.0.1.20160521101909
  7. // @namespace https://greatest.deepsurf.us/users/3216
  8. // ==/UserScript==
  9.  
  10. var userscript = function()
  11. {
  12.  
  13. // http://stackoverflow.com/questions/2246901/how-can-i-use-jquery-in-greasemonkey-scripts-in-google-chrome
  14. function addJQuery(callback) {
  15. var script = document.createElement("script");
  16. script.setAttribute("src", "//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js");
  17. script.addEventListener('load', function() {
  18. var script = document.createElement("script");
  19. script.textContent = "window.jQ=jQuery.noConflict(true);(" + callback.toString() + ")();";
  20. document.body.appendChild(script);
  21. }, false);
  22. document.body.appendChild(script);
  23. }
  24.  
  25. function main()
  26. {
  27.  
  28. var videoinfo = {url: top.location.href, complete: 0, text: "", timer: null, running: 0};
  29. var DEBUG = 0;
  30. var PLAYVERSION = 0;
  31. var DEBUGLOG;
  32.  
  33. if(DEBUG) DEBUGLOG = console;
  34. else { var TDEBUGLOG = function() { return {log: function() {}}; }; DEBUGLOG = TDEBUGLOG(); }
  35.  
  36. function IsMusic()
  37. {
  38. if(jQ('#eow-title #watch-headline-show-title').length !== 0) return 1;
  39. //if(jQ('#eow-category').text().trim() == "Music") return 1;
  40. var extrasismusic = false;
  41. jQ('.watch-extras-section .watch-meta-item').each(function(index)
  42. {
  43. var item = jQ(this);
  44. var title = item.find('.title').text().trim();
  45. var content = item.find('.content').text().trim();
  46. if(title == "Category" && content == "Music") extrasismusic = true;
  47. if(title == "Music") extrasismusic = true;
  48. });
  49. if(extrasismusic === true) return 1;
  50. var metadata = jQ('.metadata-info-title');
  51. for(var i = 0; i < metadata.length; i++)
  52. {
  53. if(metadata[i].innerText.match(/Buy ".*?" on|Artist/) !== null) return 1;
  54. }
  55.  
  56. if(jQ('#eow-title').text().split('-', 2).length == 2) return 1; // Might be dangerous!
  57. return 0;
  58. }
  59.  
  60. function GetVideoTitle()
  61. {
  62. var artistname;
  63. var trackname;
  64. var fullname;
  65. var artist;
  66. var artist = jQ('#eow-title #watch-headline-show-title');
  67. if(artist.length !== 0 && !artistname)
  68. {
  69. artistname = artist.text();
  70. }
  71. artist = jQ('.metadata-info');
  72. if(artist.length !== 0 && !artistname)
  73. {
  74. for(var i = 0; i < artist.length; i++)
  75. {
  76. var tname = artist[i].innerText.match(/(Artist)[ \n\r]*(.*)/);
  77. if(tname)
  78. {
  79. if(tname[2].trim() != "Various Artists")
  80. {
  81. artistname = tname[2];
  82. break;
  83. }
  84. }
  85. }
  86. }
  87. if(!artistname || !trackname)
  88. {
  89. jQ('.watch-extras-section .watch-meta-item').each(function(index)
  90. {
  91. var item = jQ(this);
  92. var title = item.find('.title').text().trim();
  93. var content = item.find('.content').text().trim();
  94. if(title == "Music")
  95. {
  96. var musicsplit = content.split('by');
  97. var tname = musicsplit[0].match(/"(.*)"/)[1];
  98. var tartist = musicsplit[1].replace(/\([a-z• ]*\)$/i, '').trim();
  99. if(tartist !== "") artistname = tartist;
  100. if(tname !== "") trackname = tname;
  101. }
  102. });
  103. }
  104. if(!artistname)
  105. {
  106. var tname = jQ('#eow-title').text().split('-', 2);
  107. if(tname.length == 2)
  108. {
  109. artistname = tname[0].trim();
  110. }
  111. }
  112. var track = jQ('.metadata-info-title').text().match(/Buy "(.*?)" on/);
  113. if(track !== null && !trackname)
  114. {
  115. trackname = track[1];
  116. }
  117. if(!trackname)
  118. {
  119. //trackname = jQ('#eow-title').text().replace(artistname, "").replace(/-/, '').trim();
  120. var tname = jQ('#eow-title').text().split(/[-]+/, 2);
  121. if(tname.length == 2)
  122. {
  123. trackname = tname[1].trim();
  124. }
  125. else
  126. {
  127. trackname = trackname = jQ('#eow-title').text().replace(/-/, '').trim();
  128. if(artistname) trackname = trackname.replace(artistname, "");
  129. }
  130. }
  131. if(!artistname && trackname)
  132. {
  133. artistname = " ";
  134. }
  135. if(artistname && trackname)
  136. {
  137. trackname = trackname.replace(/\((.*?)\)|\[(.*?)\]| (Re)?mastered| f(ea)?t(uring)?(.)? .*|\"|\'/gi, "").trim();
  138. artistname = artistname.replace(/f(ea)?t(uring)?(.)? .*/gi, "").trim(); // & .*
  139. return {artist: artistname, track: trackname};
  140. }
  141. else
  142. {
  143. if(!artistname) DEBUGLOG.log("failed to get artistname");
  144. if(!trackname) DEBUGLOG.log("failed to get trackname");
  145. }
  146. return 0;
  147. }
  148.  
  149. // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
  150. function escapeRegExp(str) {
  151. return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
  152. }
  153.  
  154. // http://stackoverflow.com/a/21081760
  155. function wordInString(s, word){
  156. return new RegExp( '\\b' + word + '\\b', 'i').test(s);
  157. }
  158.  
  159. function FindOriginal(title, data)
  160. {
  161. var scores = [];
  162. var highest = 0;
  163. var title_artist = title.artist.replace(/[^a-zA-Z ]/g, "").split(' ').filter(Boolean);
  164. var extra = [];
  165. if(!/remix/i.test(title.track))
  166. {
  167. extra.push("remix");
  168. }
  169.  
  170. for(var i = 0; i < Math.min(data.tracks.total, 10); i++)
  171. {
  172. var points = 0;
  173. var track = data.tracks.items[i];
  174. if(new RegExp('^(' + escapeRegExp(title.track) + ')', 'i').test(track.name))
  175. {
  176. points += 1;
  177. }
  178. if(!new RegExp('.*(radio|clean|live|remaster(ed)?|edit|karaoke|' + extra.join('|') + ')', 'gi').test(track.name))
  179. //if(!new RegExp(escapeRegExp(title.track) + ' .*(radio|clean|live|remaster(ed)?|edit|karaoke|' + extra.join('|') + ')', 'gi').test(track.name))
  180. {
  181. points += 1;
  182. }
  183. var artists = track.artists;
  184. for(var x = 0; x < artists.length; x++)
  185. {
  186. if(new RegExp('(karaoke)', 'gi').test(track.name)) points -= 2;
  187. if(title_artist.some(function(str) { return wordInString(this, str); }, artists[x].name)) points += 1;
  188. if(title.artist == artists[x].name) points += 2; // Favour exact matches
  189. }
  190. scores[i] = points;
  191. }
  192. for(var i = 0; i < scores.length; i++) if(scores[i] > scores[highest]) highest = i;
  193. DEBUGLOG.log("scores", scores);
  194. return highest;
  195. }
  196.  
  197. function CheckAddress()
  198. {
  199. if(window.location.href.match(/^(https?\:\/\/(www\.)?youtube.com\/watch)/))
  200. {
  201. return 1;
  202. }
  203. return 0;
  204. }
  205.  
  206. function AddSpotify()
  207. {
  208. if(!CheckAddress()) return 1;
  209.  
  210. videoinfo.complete = 0;
  211. if(IsMusic())
  212. {
  213. var title = GetVideoTitle();
  214. DEBUGLOG.log("title", title);
  215. if(title !== 0)
  216. {
  217. jQ.getJSON('//api.spotify.com/v1/search?q=' + encodeURIComponent(title.artist + " " + title.track) + "&type=track&limit=10", function(data)
  218. {
  219. DEBUGLOG.log("data", data);
  220. DEBUGLOG.log("fullname:", title.artist, title.track);
  221. if(data.tracks.total > 0)
  222. {
  223. var artists = [];
  224. var index = FindOriginal(title, data);
  225. DEBUGLOG.log("Index", index, "selected");
  226. var track = data.tracks.items[index];
  227. jQ.each(track.artists, function(key, val)
  228. {
  229. var artisturl = "";
  230. if(PLAYVERSION === 0) artisturl = val.uri;
  231. else artisturl = val.external_urls.spotify;
  232. artists.push('<a href="' + artisturl + '" ' + (PLAYVERSION ? 'target="_blank"' : '') + '>' + val.name + '</a>');
  233. });
  234. var artistsname = artists.join(", ");
  235. var trackname = track.name;
  236. var urltrack = "";
  237. if(PLAYVERSION === 0) urltrack = track.uri;
  238. else urltrack = track.external_urls.spotify;
  239. var htmloutput = '<div class="youtubespotify yt-uix-menu" style="font-size: 70%"><a href="' + urltrack + '" ' + (PLAYVERSION ? 'target="_blank"' : '') + '>';
  240. htmloutput += '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAACpF6WWAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAQhSURBVDhPhZVtSJ11GMZ/zznHoz56XHbsbFmtpgj5khuZWCO3hDnG+tLqy+hDRRBsxPoSFX4IIigZsQjWgogFoygI5iAsSpdrMqHhXEKzCUujhWe+TI969OiZL6frfp4jKS12w9/n5fz/131d133fj05GwW1iLn2D38eOE5/tYCZ9lZXVFMGAy6a8SkqL9lIVO0Jh7r3Z3RvjP6BLKwucvlJPfGaASC7khiAnAAEHVrVzeRXSy5C8hcCrebaml5xgfva0HxtAryfO8nV/s9hAfo7Agj5gSNdgFnRJoMsrlhwWBD6zCAe3d7K1eE8WRQSyV/5KdPJFXzPFAswTu5BAbBlwWCuknXYNWxItS5anZ9v/5eVm7/xaeExN8tEul6irQ2KYFxZTk677sMnX4VUxzJh0yV5Mw60sW1t2P5mCN5tSnhUe6KneGqZSA0TvgoICGP0TRq7B9LgAtNmkF0SgZAtsrZCX5ahw8nVOBZ0XqGxIKdndbjUv1F/BSS7GM8fOlXJ/DL7/HNpOZDXcIcoqoek52HNQKvQ8nYAJJXm9KU5w30vhd0aTF4iIyTcfQkLs8mTDw49C7ePwSIPud8ADYle4CeaTYib5iZvwazec/kRqxHbHLv99KBDGOXWxLjM+1+cVJ1KkrEsC2OZLtoIEtRzdo8pbn1gHTAmwux3OnIThAY8477WpDlGI5tfhHPvJ1TYzWC9VyckR6NGBod/k7XV5NiNwFS66GR6Un1WPwZNPw5aH/Nbq74HBfmh8xveYjIvT+iMZrx+1jOnLdX5mC0csTfKyirAgiesjdh+82AIN++TnlGzR7zYYVjTng05XqlKeVFeDMdirKqpotU8okSyxSTL5ASWdHIW+8/DzGbh0zgff3givfQSz0/5gOIjpyZ66zFiyj1wdMv+K7/F9HZb88az8gL1XovIa2FblK5iehI/f0rtaST8gA9V6xnJzkTztGmzJdP/RiivfCtUBX7XChW99Fv8XO/fD828okXw2hgsCtL61Xm2saMGZXYhn3v2ulJJCydUEJSRx4m+oVEGMdUjvzAIbgpFh+OUHaFfVLQ4dVeFkU1rzb6CT8vXt/XF/oj7trmF0dsBrKwO2tarKWhvZMk9tBe13G195365BqdkpdcX+qKZlWUxfrUO7NFEGarPf0uZSrBG1/jRmawXyelRhF8uxFiGBW7NbWxnLaSl5/4A/+7Jc2XVz+KkOJjQta5usPby17tm76tmYpSR1Sezs+abOHd7d4eFYeEy9O8XQxFlOdDUT0RDY18ljqvdrbC1st+eK/li15+Tnq02dlMX+/Z5uALUwKz47X8+1sQEKJNH7QEuPJ187V7SM3bwqXRGr5pXdd/jyr4/k4g0uDh9naLxD8q6SXkmpl11KIpWUx/bSUHZEim73Pwr+AWfotNJ4G681AAAAAElFTkSuQmCC">';
  241. htmloutput += ' ' + trackname + '</a> - ' + artistsname + '</div>';
  242. jQ('.youtubespotify').remove();
  243. jQ('#watch8-secondary-actions').append(htmloutput);
  244. }
  245. else
  246. {
  247. jQ('.youtubespotify').remove();
  248. DEBUGLOG.log("No song found");
  249. }
  250. });
  251. } else
  252. {
  253. jQ('.youtubespotify').remove();
  254. DEBUGLOG.log("failed to get title");
  255. }
  256. }
  257. else
  258. {
  259. DEBUGLOG.log("not music!!");
  260. }
  261. videoinfo.complete = 1;
  262. videoinfo.url = top.location.href;
  263. videoinfo.text = jQ('#eow-title').text().trim();
  264. }
  265.  
  266. AddSpotify();
  267.  
  268. function timerfunction()
  269. {
  270. videoinfo.running = 1;
  271. if(videoinfo.complete && videoinfo.url != top.location.href)
  272. {
  273. if(videoinfo.text != jQ('#eow-title').text().trim())
  274. {
  275. AddSpotify();
  276. }
  277. }
  278. }
  279.  
  280. window.addEventListener('focus', function()
  281. {
  282. clearInterval(videoinfo.timer);
  283. if(!videoinfo.running) videoinfo.timer = setInterval(timerfunction, CheckAddress() ? 1000 : 2000);
  284. });
  285.  
  286. window.addEventListener('blur', function()
  287. {
  288. clearInterval(videoinfo.timer);
  289. videoinfo.running = 0;
  290. });
  291.  
  292. }
  293.  
  294. addJQuery(main);
  295.  
  296. };
  297.  
  298. // http://stackoverflow.com/questions/7971930/how-to-call-youtube-flash-api-of-existing-videos-using-greasemonkey
  299. function addJS_Node (text, s_URL, funcToRun, runOnLoad) {
  300. var D = document;
  301. var scriptNode = D.createElement ('script');
  302. if (runOnLoad) {
  303. scriptNode.addEventListener ("load", runOnLoad, false);
  304. }
  305. scriptNode.type = "text/javascript";
  306. if (text) scriptNode.textContent = text;
  307. if (s_URL) scriptNode.src = s_URL;
  308. if (funcToRun) scriptNode.textContent = '(' + funcToRun.toString() + ')()';
  309.  
  310. var targ = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
  311. targ.appendChild (scriptNode);
  312. }
  313.  
  314. addJS_Node(null, null, userscript);