// ==UserScript==
// @name Stig's Last.fm Album Linkr
// @namespace dk.rockland.userscript.lastfm.linkr
// @description Adding album links and headers to tracks on Last.Fm's recent plays listings - plus linkifying About Me section on profiles
// @version 2016.10.26.0
// @author Stig Nygaard, http://www.rockland.dk
// @homepageURL http://www.rockland.dk/userscript/lastfm/linkr/
// @supportURL http://www.rockland.dk/userscript/lastfm/linkr/
// @match *://*.last.fm/*
// @match *://*.lastfm.de/*
// @match *://*.lastfm.es/*
// @match *://*.lastfm.fr/*
// @match *://*.lastfm.it/*
// @match *://*.lastfm.ja/*
// @match *://*.lastfm.pl/*
// @match *://*.lastfm.pt/*
// @match *://*.lastfm.ru/*
// @match *://*.lastfm.sv/*
// @match *://*.lastfm.tr/*
// @match *://*.lastfm.zh/*
// @grant none
// @noframes
// ==/UserScript==
var linkr = linkr || {
// CHANGELOG - The most important updates/versions:
changelog: [
{version: '2016.10.26.0', description: 'More intelligent creation of album-header when featured artists on some albumtracks.'},
{version: '2016.10.19.0', description: 'Bonus-feature added: Linkifying urls written in About Me section in Profiles.'},
{version: '2016.07.08.1', description: 'Fine-tuning album-headers with light red background color and more...'},
{version: '2016.07.08.0', description: 'Don\'t put album-header at very top of Recent Tracks if the 1st track is scrobbling now.'},
{version: '2016.07.07.0', description: 'Album "headers" in play listings added.'},
{version: '2016.07.04.0', description: '1st release'}
],
INFO: true,
DEBUG: false,
observed: null,
linking_running: false,
log: function(s, info) {
if ((info && window.console) || (linkr.DEBUG && window.console)) {
window.console.log('*Linkr* '+s);
}
},
insertStyle: function() {
if (!document.getElementById('linkrStyle')) {
var style = document.createElement('style');
style.type = 'text/css';
style.id = 'linkrStyle';
style.innerHTML = 'tr.albumlink-row, tr.albumlink-row > td {background-color:#f1cccc !important; font-style:italic} tr.albumlink-row:hover, tr.albumlink-row:hover > td {background-color:#f9d4d4 !important;}';
document.getElementsByTagName('head')[0].appendChild(style);
linkr.log('linkrStyle has been ADDED');
} else {
linkr.log('linkrStyle was already present');
}
},
linking: function (mutations) {
if(linkr.linking_running) return;
linkr.linking_running = true;
function altvalue(elem) {
if (elem && elem.firstElementChild) {
if (elem.firstElementChild.classList.contains('albumlink-row')) {
return null;
} else if (elem.firstElementChild.firstElementChild && elem.firstElementChild.firstElementChild.firstElementChild && elem.firstElementChild.firstElementChild.firstElementChild.firstElementChild) {
return elem.firstElementChild.firstElementChild.firstElementChild.firstElementChild.alt;
}
}
return null;
}
linkr.log('Running linking()... ', linkr.INFO);
var l = document.querySelectorAll('table.chartlist tr div > img');
for (var i=0; i < l.length; i++) {
linkr.log('iteration '+ i + ' of ' + l.length);
if (l[i].alt && l[i].alt!='') {
l[i].title = l[i].alt;
var tr = parent = l[i].parentNode;
while (tr.tagName.toUpperCase()!=='TR') tr = tr.parentNode;
var a = tr.querySelector('span.chartlist-artists a');
if (a) {
linkr.log('Found img.alt='+l[i].alt);
var albumlink = a.href + '/' + encodeURIComponent(l[i].alt).replace(/%20/g,'+') + '/';
linkr.log('giving albumlink='+albumlink);
var link = document.createElement('a');
link.setAttribute('href', albumlink);
parent.replaceChild(link, l[i]);
link.appendChild(l[i]);
} else {
linkr.log('Artist link not found');
}
}
}
var tlists = document.querySelectorAll('section#recent-tracks-section table.chartlist tbody, section.tracklist-section tbody');
linkr.log('tlists.length='+tlists.length);
for (var j=0; j<tlists.length; j++) {
linkr.log('Loop with tlists['+j+'].');
if (tlists[j] && tlists[j].children && tlists[j].children.length > 2) {
linkr.log('tlists['+j+'] has ' + tlists[j].children.length + ' children');
var loopstart=1;
if (j===0 && tlists[j].children[0].classList.contains('now-scrobbling')) {
loopstart=2; // Don't put album-header at very top of Recent Tracks if the 1st row is a currently scrobbling track
}
for (i = loopstart; i < tlists[j].children.length; i++) {
linkr.log('for-loop. i=' + i);
if (i===1 || !tlists[j].children[i - 2].classList.contains('albumlink-row')) {
linkr.log('for-loop. i=' + i + ' og i-2 er IKKE allerede albumlink-row');
if ( altvalue(tlists[j].children[i])
&& altvalue(tlists[j].children[i - 1])
&& altvalue(tlists[j].children[i]) != ''
&& altvalue(tlists[j].children[i]) === altvalue(tlists[j].children[i - 1])
&& (i===1 || altvalue(tlists[j].children[i]) != altvalue(tlists[j].children[i - 2]))) {
linkr.log('for-loop. i=' + i + ' og vi har fundet en album-gruppes start', linkr.INFO);
// TRY to get albumartist right even when misc. featured artists on album tracks:
var bestindex = i-1;
var artistname = tlists[j].children[bestindex].querySelector('td.chartlist-name span.chartlist-artists > a').textContent;
for (var k=i; k < tlists[j].children.length; k++) {
if (altvalue(tlists[j].children[i-1]) != altvalue(tlists[j].children[k])) break;
if (tlists[j].children[k].querySelector('td.chartlist-name span.chartlist-artists > a').textContent.length < artistname.length) {
bestindex = k;
artistname = tlists[j].children[bestindex].querySelector('td.chartlist-name span.chartlist-artists > a').textContent;
}
// linkr.log('*** k='+k+': altvalue='+altvalue(tlists[j].children[k])+', artist='+ tlists[j].children[k].querySelector('td.chartlist-name span.chartlist-artists > a').textContent, true)
}
var artistlink = tlists[j].children[bestindex].querySelector('td.chartlist-name span.chartlist-artists > a');
var albumtitle = altvalue(tlists[j].children[bestindex]);
var albumcover = tlists[j].children[bestindex].querySelector('td.chartlist-play a > img');
if (albumcover) albumcover=albumcover.src;
if (artistlink) {
artistname = artistname.split(',')[0];
artistlink = artistlink.href.split(',')[0];
albumlink = artistlink + '/' + encodeURIComponent(altvalue(tlists[j].children[bestindex])).replace(/%20/g,'+') + '/';
tr = document.createElement("tr");
tr.classList.add('albumlink-row','js-link-block','js-lazy-buylinks-focus-container');
tr.innerHTML = '<td class="chartlist-play"><div class="chartlist-play-image"><a href="'+albumlink+'"><img title="'+albumtitle+'" src="'+albumcover+'" class="cover-art"></a></div></td><td class="chartlist-loved"><a href="'+albumlink.replace(/\/user\/[^\/]+\/library\//,'/')+'"><img src="http://www.rockland.dk/img/album244c.png" class="cover-art" alt="album" /></a></td><td class="chartlist-name"><span class="chartlist-ellipsis-wrap"><span class="chartlist-artists"><a href="'+artistlink+'" title="'+artistname+'">'+artistname+'</a></span><span class="artist-name-spacer"> — </span><a href="'+albumlink+'" class="link-block-target" title="'+artistname+' — '+albumtitle+'">'+albumtitle+'</a></span></td><td class="chartlist-buylinks"><div class="lazy-buylinks"><button class="disclose-trigger lazy-buylinks-toggle" aria-expanded="false" data-lazy-buylink="" data-lazy-buylink-url="'+albumlink.replace(/\/user\/[^\/]+\/library\//,'/')+'/+partial/buylinks">Buy</button></div></td><td class="chartlist-timestamp"></td><td class="chartlist-delete"></td>';
linkr.log('Now trying to add tr...');
tlists[j].insertBefore(tr, tlists[j].children[i - 1]);
linkr.log('and should be added now!?');
i += 2; // or http://stackoverflow.com/questions/8766910/is-there-a-loop-start-over ?
}
}
}
}
} else {
linkr.log('but not enough children found...');
}
}
// extras here?...
// .about-me-sidebar p
linkr.linkifySidebar();
//var b = document.querySelector('.stationlinks');
//b.insertAdjacentHTML('beforeend','<div style="margin:1em 0;width:300px"><img src="http://www.tapmusic.net/collage.php?user=rockland&type=3month&size=2x8" alt="" style="display:block;margin:0;padding:0;width:300px;height:900px" /><em>Album collage by <a href="http://www.tapmusic.net/lastfm/">www.tapmusic.net/lastfm/</a></em></div>');
linkr.linking_running = false;
},
setupObserver: function () {
linkr.log('Running setupObserver()');
linkr.insertStyle();
linkr.observed = document.querySelector('table.chartlist > tbody');
if (!linkr.observed || !linkr.observed.classList) {
linkr.log('Object to observe NOT found - re-trying later...');
} else if (linkr.observed.classList.contains('hasObserver')) {
linkr.log('Everything is okay! - But checking again later...');
} else {
linkr.linking();
linkr.log('Now adding Observer and starting...', linkr.INFO);
var observer = new MutationObserver(linkr.linking);
var config = {attributes: false, childList: true, subtree: false, characterData: false};
observer.observe(linkr.observed, config);
linkr.observed.classList.add('hasObserver');
linkr.log('Observer added and running...');
}
},
linkifyStr: function (str, attributes) {
var a1 = '<a ' + (attributes ? attributes+' ' : '') + 'href="';
var a2 = '">';
var a3 = '</a>';
var url = /(^|\s|\(|>)([fhtpsr]+:\/\/[^\s]+?)([\.,;\]"]?(\s|$|\))|<)/igm;
// var url2 = /(^|\s|\()([fhtpsr]+:\/\/[^\s]+?)([\.,;\]"]?(\s|$|\)))/igm;
// This looks a bit weird, but we have to do a replace twice to catch URLs
// which immediately follow each other. This is because leading and trailing
// whitespaces are part of the expressions, and if a trailing whitespace of
// a match needs to be a leading whitespace of the next URL to match, it
// won't be caught.
var s = str.replace(url, '$1' + a1 + '$2' + a2 + '$2' + a3 + '$3$4');
return(s.replace(url, '$1' + a1 + '$2' + a2 + '$2' + a3 + '$3$4'));
// var s2 = s.replace(url2,"$1"+a1+"$2"+a2+"$2"+a3+"$3$4"); alert(s2); return(s2);
},
linkifySidebar: function() {
var a = document.querySelectorAll('.about-me-sidebar p');
for(var i=0;i<a.length;i++)
{
// a[i].innerHTML = linkr.linkifyStr(a[i].innerHTML,'target="_blank"');
a[i].innerHTML = linkr.linkifyStr(a[i].innerHTML);
}
},
init: function () {
linkr.log('Running init() on last.fm');
linkr.setupObserver();
setInterval(linkr.setupObserver,2000);
// .about-me-sidebar p
//linkr.linkifySidebar();
// .stationlinks
var b = document.querySelector('.stationlinks');
b.insertAdjacentHTML('beforeend','<div style="margin:1em 0;width:300px"><img src="http://www.tapmusic.net/collage.php?user=rockland&type=3month&size=2x8" alt="" style="display:block;margin:0;padding:0;width:300px;height:900px" /><em>Album collage by <a href="http://www.tapmusic.net/lastfm/">www.tapmusic.net/lastfm/</a></em></div>');
}
};
linkr.init();