// ==UserScript==
// @name MyAnimeList (MAL) Alternative History
// @namespace https://greasyfork.org/users/7517
// @description Displays anime/manga series history based on RSS feed, instead of default episode/chapter history.
// @icon http://i.imgur.com/b7Fw8oH.png
// @version 3.3.5
// @author akarin
// @include /^http:\/\/myanimelist\.net\/history\//
// @grant none
// @noframes
// ==/UserScript==
(function($) {
var mal = {
debug: false,
ajax: {
//delay: 10,
timeout: 5000
},
covers: {
naLarge: 'na_series.gif',
naSmall: 'qm_50.gif',
url: 'http://cdn.myanimelist.net/images/'
},
cache: {
version: '3.3',
timeExp: 1000*60*60*24*14,
timeDlt: 1000*60*60*6,
check: function() {
if (mal.debug || (localStorage.getItem('version') || '') !== mal.cache.version) {
localStorage.clear();
localStorage.setItem('version', mal.cache.version);
}
}
}
};
function main() {
$.ajaxSetup({ timeout: mal.ajax.timeout });
mal.cache.check();
mal.date = Date.now();
var entries = [],
nickname = document.URL.match(/^http:\/\/myanimelist\.net\/history\/([^\/]+)/)[1],
feedsCount = 0,
feedsTotal = document.URL.match(/^http:\/\/myanimelist\.net\/history\/(?!.*\/.)/) ? 2 : 1,
content = $('<div id="history_content" style="padding: 0 15px 10px 15px;">Loading history...</div>')
.insertBefore($('#horiznav_nav + div').hide());
if (feedsTotal > 1 || document.URL.match(/^http:\/\/myanimelist\.net\/history\/.+?\/anime/)) {
load('/rss.php?type=rw&u=' + nickname, 'anime');
}
if (feedsTotal > 1 || document.URL.match(/^http:\/\/myanimelist\.net\/history\/.+?\/manga/)) {
load('/rss.php?type=rm&u=' + nickname, 'manga');
}
function load(url, feed) {
$.ajax(url).done(function(data) {
process(data, feed);
}).fail(function() {
process('', feed);
});
}
function process(data, feed) {
$('rss > channel > item', data).each(function() {
entries.push({
gtype: feed,
title: $('title', this).text().match(/^(.+)( - (?!.* - ))/)[1],
type: $('title', this).text().match(/( - (?!.* - ))(.+)$/)[2],
link: $('link', this).text().replace(/^http:\/\/127\.0\.0\.1:9001/, ''),
id: $('link', this).text().match(/\/(\d+)\//)[1],
status: $('description', this).text().match(/^(.+) - /)[1]
.replace(/(Watching)/, feed === 'manga' ? 'Reading' : '$1')
.replace(/(Plan to Watch)/, feed === 'manga' ? 'Plan to Read' : '$1'),
progress: $('description', this).text().match(/ - (.+)$/)[1]
.replace(/ (chapters|episodes)/, '')
.replace(/ of /, '/')
.replace(/^0\//, '-/')
.replace(/\?/, '-'),
date: new Date($('pubDate', this).text())
});
});
if ((++feedsCount) < feedsTotal) {
return;
}
entries.sort(function(a, b) {
return b.date - a.date;
});
var table = $('<table id="history_table" width="100%" border="0" cellpadding="0" cellspacing="0"></table>')
.appendTo(content.empty());
$('<tr></tr>')
.append('<td class="normal_header" colspan="2">Title</td>')
.append('<td class="normal_header" width="70" align="center">Type</td>')
.append('<td class="normal_header" width="90" align="center">Status</td>')
.append('<td class="normal_header" width="70" align="center">Progress</td>')
.append('<td class="normal_header" width="125" align="center">Date</td>')
.appendTo(table);
$.each(entries, function(index, entry) {
var dateLast = (index < entries.length - 1) && (entry.date.getDate() != entries[index + 1].date.getDate()),
dateFormat = entry.date.toLocaleTimeString('en-US', {
year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', hour12: false, minute: '2-digit'
});
$('<tr' + (dateLast ? ' class="date_last"' : '') + '></tr>')
.append($('<td width="50" valign="top"></td>')
// Cover
.append($('<div class="picSurround"></div>')
.append($('<a href="' + entry.link + '"></a>')
.append('<img id="cover_' + entry.gtype + '_' + entry.id + '" border="0" ' +
'src="data:image/gif;base64,R0lGODlhAQABAAAAACwAAAAAAQABAAA=" alt="" />')
)
)
)
.append($('<td valign="top"></td>')
// Title
.append($('<a href="' + entry.link + '"></a>').append('<strong>' + entry.title + '</strong>'))
// Additional Info
.append('<div id="info_' + entry.gtype + '_' + entry.id + '"></div>')
)
.append('<td width="70" align="center">' + entry.type + '</td>')
.append('<td width="90" align="center">' + entry.status + '</td>')
.append('<td width="70" align="center">' + entry.progress + '</td>')
.append('<td width="125" align="center">' + dateFormat + '</td>')
.appendTo(table);
//setTimeout(function() {
loadData(entry.id, entry.gtype);
//}, mal.ajax.delay * index);
});
}
function loadData(id, type) {
if (!getInfo(id, type) || !getCover(id, type, true) ) {
$.get('/' + type + '/' + id + '/_/pics', function(data) {
setInfo(id, type, data);
setCover(id, type, data, true);
});
}
}
function getInfo(id, type) {
var infoData = localStorage.getItem('info_' + id);
if (infoData !== null) {
var info = infoData.split(';');
if (parseInt(info[1]) + mal.cache.timeExp > mal.date) {
applyInfo(id, type, info[0]);
return true;
}
}
return false;
}
function setInfo(id, type, data) {
var infoRegExp = (type === 'anime'
?
/<h2>Information<\/h2>[\s\S]*?(<div><span class="dark_text">Producers:<\/span>[\s\S]*?>Genres:<\/span>[\s\S]*?<\/div>)[\s\S]*?<h2>Statistics<\/h2>[\s\S]*?(<div><span class="dark_text">Score:<\/span>[\s\S]*?>Popularity:<\/span>[\s\S]*?<\/div>)/
:
/<h2>Information<\/h2>[\s\S]*?(<div class="spaceit"><span class="dark_text">Genres:<\/span>[\s\S]*?>Authors:<\/span>[\s\S]*?<\/div>)[\s\S]*?<h2>Statistics<\/h2>[\s\S]*?(<div><span class="dark_text">Score:<\/span>[\s\S]*?>Popularity:<\/span>[\s\S]*?<\/div>)/
);
var infoData = data.match(infoRegExp),
info = (infoData === null ? '' :
infoData[1]
.replace(/<sup>[\s\S]*?<\/sup>/g, '')
.replace(/ class="spaceit"/g, '')
.replace(/(<div><span class="dark_text">Producers:[\s\S]*?<\/div>)(<div><span class="dark_text">Genres:[\s\S]*?<\/div>)/, '$2$1') +
'<div>' +
infoData[2]
.replace(/<sup>[\s\S]*?<\/sup>/g, '')
.replace(/<[\/]*?div[\s\S]*?>/g, '') +
'</div>'
);
localStorage.setItem('info_' + id, info + ';' + (mal.date - mal.cache.timeExp + mal.cache.timeDlt));
applyInfo(id, type, info);
}
function getCover(id, type, thumbnail) {
var coverData = localStorage.getItem('cover_' + id);
if (coverData !== null) {
var cover = coverData.split(';');
if (parseInt(cover[1]) + mal.cache.timeExp > mal.date) {
if (cover[0] !== mal.covers.naLarge) {
cover = mal.covers.url + type + '/' + cover[0];
if (thumbnail) {
cover = cover.replace(/\.(\w+)$/, 't.$1');
}
} else {
cover = mal.covers.url + (thumbnail ? mal.covers.naSmall : mal.covers.naLarge);
}
applyCover(id, type, cover);
return true;
}
}
return false;
}
function setCover(id, type, data, thumbnail) {
var cover = $('#content > table td.borderClass img', data).prop('src');
if (cover.length > 0 && cover.indexOf(mal.covers.naLarge) < 0) {
var coverPrefix = mal.covers.url + type + '/';
cover = cover.split(coverPrefix)[1];
localStorage.setItem('cover_' + id, cover + ';' + mal.date);
if (thumbnail) {
cover = cover.replace(/\.(\w+)$/, 't.$1');
}
applyCover(id, type, coverPrefix + cover);
}
else {
cover = thumbnail ? mal.covers.naSmall : mal.covers.naLarge;
localStorage.setItem('cover_' + id, mal.covers.naLarge + ';' + (mal.date - mal.cache.timeExp + mal.cache.timeDlt));
applyCover(id, type, mal.covers.url + cover);
}
}
function applyInfo(id, type, data) {
$('div[id="info_' + type + '_' + id + '"]', content).html(data);
}
function applyCover(id, type, data) {
$('img[id="cover_' + type + '_' + id + '"]', content).attr('src', data);
}
}
main();
$('<style type="text/css" />').html(
'#history_table { min-width: 100%; }' +
'#history_table td { padding: 4px; border-bottom: 1px solid #ebebeb; }' +
'#history_table tr.date_last td { border-bottom: 2px solid #dadada; }' +
'#history_table td.normal_header { border-bottom: 1px solid #bebebe; }' +
'#history_table td div[id^="info_"] { padding: 4px 0 2px; font-size: 10px; color: #666; }'
).appendTo('head');
})(jQuery);