Greasy Fork is available in English.

MyAnimeList (MAL) Tags Updater

Adds type, genres and other info to entries tags. Can also delete all current tags.

As of 2015-04-26. See the latest version.

// ==UserScript==
// @name         MyAnimeList (MAL) Tags Updater
// @namespace    https://greasyfork.org/users/7517
// @description  Adds type, genres and other info to entries tags. Can also delete all current tags.
// @icon         http://i.imgur.com/b7Fw8oH.png
// @version      2.4.2
// @author       akarin
// @include      /^http:\/\/myanimelist\.net\/(anime|manga)list/
// @include      /^http:\/\/myanimelist\.net\/panel\.php\?go=(add|edit)/
// @include      /^http:\/\/myanimelist\.net\/editlist\.php\?type=(anime|manga&id=)/
// @grant        none
// ==/UserScript==

(function($) {  

var modes = {
	M_NONE: -1, M_LIST: 1, M_POPUP: 2, M_EXTRA: 3
};

var mal = {
	mode: modes.M_NONE,
	page: {},
	tagsUrl: '',
	ajax: { delay: 300, timeout: 5000, type: 'GET', url: 'http://myanimelist.net/' },
	list: { anime: 'anime', manga: 'manga', type: '', status: '' },
	entries: { total: 0, calc: 0, fail: 0 },
	content: { done: $(), fail: $() }
};

mal.main = function() {	
	if ($('#malLogin').length > 0) {
		return;
	}
	
	$.ajaxSetup({ type: mal.ajax.type, timeout: mal.ajax.timeout });
	
	if (document.URL.match(/\/(anime|manga)list\//)) {	
		mal.mode = modes.M_LIST;
		mal.page.list();
	}
	else if (document.URL.match(/\?(type=anime|go=editmanga)$/)) {
		mal.mode = modes.M_EXTRA;
		mal.page.extra();
	}
	else {
		mal.mode = modes.M_POPUP;
		mal.page.popup();
	}
	
	mal.tagsUrl = 'includes/ajax.inc.php?' + (mal.list.type === mal.list.anime ? 't=22&aid=' : 't=30&mid=');
};

mal.page.list = function() {	
	// exit if this is another user's list
	if ($('#mal_cs_otherlinks div:first strong').text().match(/^You are viewing your/) === null) {
		return;
	}
	
	mal.list.type = document.URL.match(/\/animelist\//) ? mal.list.anime : mal.list.manga;
	mal.list.status = $('.status_selected a').prop('href').match(/&status=\d/)[0];
			
	$('<span id="tu_links"></span>')
		.append('<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>')
		.append($('<a href="javascript:void(0);">Update Tags</a>').click(function() { 
			if (true === confirm('Are you sure you want to update all tags?')) {
				mal.updateAllTags(false);
			}
		}))
		.append('&nbsp;&nbsp;')
		.append($('<a href="javascript:void(0);">Clear Tags</a>').click(function() { 
			if (true === confirm('Are you sure you want to clear all tags?')) {
				mal.updateAllTags(true);
			}
		}))
		.appendTo('#mal_cs_otherlinks div:last');
	
	$('#list_surround .animetitle').each(function() {
		var id = $(this).attr('href').match(/\d+/)[0];
		$(this).prev('div[style="float: right;"]').find('small')
			.prepend('<span id="tu_info_' + id + '"></span> - ')
			.prepend(
				$('<a id="tu_link_' + id + '" href="javascript:void(0);">Upd</a>').click(function() { 
					mal.updateTags(id, false);
				})
			);				
	});
};

mal.page.popup = function() {
	mal.list.type = document.URL.match(/(\?go=(add|edit)&|\?type=anime&)/) ? mal.list.anime : mal.list.manga;
	
	var id = $('strong + a:contains(Info)').prop('href').match(/\d+/)[0];
	$('td.borderClass:contains(Tags)').append(
		$('<a href="javascript:void(0)"></a>').click(function() { 
			mal.updateTags(id, false);
		})
		.append('&nbsp;<small>update</small>')
	);
};

mal.page.extra = function() {
	mal.list.type = document.URL.match(/\?type=anime$/) ? mal.list.anime : mal.list.manga;
	mal.content.done = $('<span id="tu_status_done" style="color: green;"></span>');
	mal.content.fail = $('<span id="tu_status_fail" style="color: #c32;"></span>');
	
	$('<span id="tu_links"></span>')
		.append('<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>')
		.append($('<a href="javascript:void(0);">Update Tags</a>').click(function() { 
			if (true === confirm('Are you sure you want to update all tags?')) {
				mal.updateAllTags(false);
			}
		}))
		.append('&nbsp;&nbsp;')
		.append($('<a href="javascript:void(0);">Clear Tags</a>').click(function() { 
			if (true === confirm('Are you sure you want to clear all tags?')) {
				mal.updateAllTags(true);
			}
		}))
		.append('&nbsp;&nbsp;').append(mal.content.done)
		.append('&nbsp;').append(mal.content.fail)
		.appendTo('#content > div:first');
};

mal.updateAllTags = function(clear) {
	var links = $();
	if (mal.mode === modes.M_LIST) {
		$('#list_surround td[style="border-left-width: 0"] span[id^=tu_info_]').empty();
		links = $('#list_surround .animetitle');
	}
	else if (mal.mode === modes.M_EXTRA) {
		links = $('#content strong + a');
		mal.content.done.text('');
		mal.content.fail.text('');
		$('#content span[class^="tu_status_id_"]').empty();
	}
	else {
		return;
	}
	
	mal.entries.total = links.length;
	mal.entries.calc = mal.entries.total;
	mal.entries.fail = 0;
	
	if (links.length === 0) {
		return;
	}
	
	links.each(function(index) {
		var id = $(this).attr('href').match(/\d+/)[0];
		if (mal.mode === modes.M_EXTRA) {
			var span = $(this).parent().find('span[class="tu_status_id_' + id + '"]');
			if (span.length === 0) {
				span = $('<span class="tu_status_id_' + id + '"></span>').appendTo($(this).parent());
			}
		}
		setTimeout(function() { 
			mal.updateTags(id, clear);
		}, mal.ajax.delay * index);
	});
};

mal.updateTags = function(id, clear) {
	if (clear) {
		mal.setTags(id, '');
		if (mal.mode === modes.M_EXTRA) {
			$('#content span[class="tu_status_id_' + id + '"]').html('<small style="color: green;">done</small>');
			mal.content.done.text('done: ' + (mal.entries.total - (--mal.entries.calc)) + '/' + mal.entries.total);
		}
		return;
	}		
	$.ajax(mal.ajax.url + mal.list.type + '/' + id + '/_/pics')
		.done(function(data) {				
			mal.setTags(id, mal.list.type === mal.list.anime ? mal.getAnimeTags(data) : mal.getMangaTags(data));
			if (mal.mode === modes.M_EXTRA) {
				$('#content span[class="tu_status_id_' + id + '"]').html('<small style="color: green;">done</small>');
				mal.content.done.text('done: ' + (mal.entries.total - (--mal.entries.calc)) + '/' + mal.entries.total);
			}
		})
		.fail(function(data) {
			mal.setTags(id, '');
			if (mal.mode === modes.M_EXTRA) {
				$('#content span[class="tu_status_id_' + id + '"]').html('<small style="color: red;">fail</small>');
				mal.content.fail.text('fail: ' + (++mal.entries.fail));
			}
		});
};

mal.setTags = function(id, tags) {
	// normalize commas
	tags = tags.replace(/[,\s]{2,}/g, ',').replace(/(,(?!\s))|(,(\s{2,}))/g, ', ');
	if (mal.mode === modes.M_POPUP) {
		$('#tagtext').prop('value', tags);
		return;
	}
	// mal displays ' as \', so remove all '
	tags = tags.replace(/'/g, '');
	$.get(mal.ajax.url + mal.tagsUrl + id + '&tags=' + encodeURIComponent(tags), function(data) {
		if (mal.mode === modes.M_LIST) {
			if ($('#list_surround .table_header[width="125"]').length > 0) {
				data = data.replace(/&status=\d/g, '').replace(/&tag=/g, mal.list.status + '&tag=');
				$('#list_surround #tagLinks' + id).html(data);
				$('#list_surround #tagRow' + id).text($(data).text());
			}
			$('#list_surround td[style="border-left-width: 0"] span#tu_info_' + id).text('*');
		}
	});
};	

mal.getAnimeTags = function(html) {
	var M = {	
		TYPE: 1, STATUS: 2,	AIRED: 3, PRODUCERS: 4, GENRES: 5, RATING: 6 
	};
	var tags = html.match(/<h2>Information<\/h2>[\s\S]*?>Type:<\/span>([\s\S]*?)<\/div>[\s\S]*?>Status:<\/span>([\s\S]*?)<\/div>[\s\S]*?>Aired:<\/span>([\s\S]*?)<\/div>[\s\S]*?>Producers:<\/span>([\s\S]*?)<\/div>[\s\S]*?>Genres:<\/span>([\s\S]*?)<\/div>[\s\S]*?>Rating:<\/span>([\s\S]*?)( -|None)/);
	
	var date = mal.getDate(tags[M.AIRED]);

	return tags === null ? '' : $.map([
		tags[M.TYPE]
		,$(tags[M.GENRES]
			.replace(/No genres have been added yet\./, '')
		).text()
		,$(tags[M.PRODUCERS]
			.replace(/None found, <a href="\/dbchanges\.php\?(.*)>add some<\/a>\./, '')
			.replace(/<sup>[\s\S]*?<\/sup>/g, '')
			.replace(/,/g, '')
			.replace(/<\/a>[\s]*?<a/g, '</a>, <a')
		).text()
		,date.year
		,date.season
		,date.period
		//,tags[M.RATING] 
		//,tags[M.STATUS].replace('Finished Airing', 'Finished').replace('Currently Airing', 'Airing')		
	], $.trim).join(',');
};

mal.getMangaTags = function(html) {
	var M = {	
		TYPE: 1, STATUS: 2,	PUBLISHED: 3, GENRES: 4, AUTHORS: 5, SERIALIZATION: 6 
	};
	var tags = html.match(/<h2>Information<\/h2>[\s\S]*?>Type:<\/span>([\s\S]*?)<\/div>[\s\S]*?>Status:<\/span>([\s\S]*?)<\/div>[\s\S]*?>Published:<\/span>([\s\S]*?)<\/div>[\s\S]*?>Genres:<\/span>([\s\S]*?)<\/div>[\s\S]*?>Authors:<\/span>([\s\S]*?)<\/div>[\s\S]*?>Serialization:<\/span>([\s\S]*?)(None<\/div>|<\/div>)/);
	
	var date = mal.getDate(tags[M.PUBLISHED]);
	
	return tags === null ? '' : $.map([
		tags[M.TYPE]
		,$(tags[M.GENRES]
			.replace(/No genres have been added yet\./, '')
		).text()
		,$(tags[M.AUTHORS]
			.replace(/,/g, '')
			.replace(/\((Art|Story|Story & Art)\)/g, '')
			.replace(/<\/a>[\s]*?<a/g, '</a>, <a')
		).text()
		,$(tags[M.SERIALIZATION]).text()
		,date.year
		//,date.season
		,date.period
		//,tags[M.STATUS]		
	], $.trim).join(',');
};

mal.getDate = function(str) {
	var date = str.replace(/to(.*)$/, '').trim(),
		m_year = date.match(/[\d]{4}/),
		months = { 'Jan':1,'Feb':2,'Mar':3,'Apr':4,'May':5,'Jun':6,'Jul':7,'Aug':8,'Sep':9,'Oct':10,'Nov':11,'Dec':12 },
		m_month = date.match(/^[a-zA-Z]{3}/),
		month = m_month !== null ? months[m_month[0]] : -1;
	
	var result = {
		year: '', season: '', period: ''
	};
	
	if (m_year !== null) {
		result.year = m_year[0];
	}
	
	if (result.year.length > 0 && month > 0 && result.year >= 2000) {
		if (month >= 1 && month <= 3) result.season = 'Winter';
		if (month >= 4 && month <= 6) result.season = 'Spring';
		if (month >= 7 && month <= 9) result.season = 'Summer';
		if (month >= 10 && month <= 12) result.season = 'Fall';
		result.season += ' ' + result.year;
	}
	
	if (result.year.length > 0) {
		if (result.year < 1950) result.period = '....-1949';
		if (result.year >= 1950 && result.year < 1970) result.period = '1950-1969';
		if (result.year >= 1970 && result.year < 1990) result.period = '1970-1989';
		if (result.year >= 1990 && result.year < 2000) result.period = '1990-1999';
		if (result.year >= 2000 && result.year < 2005) result.period = '2000-2004';
		if (result.year >= 2005 && result.year < 2010) result.period = '2005-2009';
		if (result.year >= 2010 && result.year < 2015) result.period = '2010-2014';
		if (result.year >= 2015) result.period = '2015-....';
	}
	
	if (result.year < 1970) {
		result.year = '';
	}
	
	return result;
};

$('<style type="text/css" />').html('\
	#content span[class^="tu_status_id_"] { float: right; padding: 0 7px; }\
').appendTo('head');

mal.main();

})(jQuery);