// ==UserScript== //
// @name Batoto MyFollows
// @description Allows you to filter your follows from comis search, links to MU and MAL from comic page, hides blood header, and other things.
// @namespace https://greasyfork.org/users/168
// @include http://www.batoto.net/*
// @include https://www.batoto.net/*
// @exclude http://www.batoto.net/ads/*
// @exclude https://www.batoto.net/ads/*
// @include http://www.mangaupdates.com/series.html?*
// @include https://www.mangaupdates.com/series.html?*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_getResourceURL
// @run-at document-start
// @version 14.08.25.01
// @resource followingIconR https://www.batoto.net/forums/public/style_images/Sylo/star.png
// @resource muIconR https://www.mangaupdates.com/favicon.ico
// @resource malIconR http://cdn.myanimelist.net/images/faviconv5.ico
// @resource bIconR https://www.batoto.net/forums/favicon.ico
// @resource infoIconR https://www.batoto.net/forums/public/style_images/master/information.png
// ==/UserScript==
var settings = {
addIconsInHomepage: true,
addIconsInSearch: true,
followingIcon: GM_getResourceURL('followingIconR'),
mangaupdatesLinkInComic: true,
muIcon: GM_getResourceURL('muIconR'),
malLinkInComic: true,
malIcon: GM_getResourceURL('malIconR'),
batotoLinkInMU: true,
bIcon: GM_getResourceURL('bIconR'),
infoIcon: 'https://www.batoto.net/forums/public/style_images/master/information.png',
infoButtonOldFollows: true,
durationInfoSlide: 0.0, //0.2 default batoto
showTotalFollows: true,
textColor: '#20B2AA',
redirectToOldFollows: false,
comicsPopupComicPage: true,
comicsPopupForum: true,
popupDelay: 1000,
hideBloodHeader: true,
hyperlinkDescription: true
};
var BMF = {},
utils = {},
layouts = {};
BMF.init = function() {
layouts.setStyles();
if (window.location.host == 'www.batoto.net') {
if (settings.redirectToOldFollows) {
BMF.redirectToOldFollows();
}
window.addEventListener('DOMContentLoaded', BMF.mainBatoto, false);
} else if (window.location.host == 'www.mangaupdates.com') {
window.addEventListener('DOMContentLoaded', BMF.mainMangaUpdates, false);
}
};
BMF.isBlood = function() {
var checkBlood = function() {
var themeImg = document.getElementsByTagName('link')[1].href,
currentIsBlood = themeImg.indexOf('Blood_Images') != -1,
storageIsBlood = utils.getStorage('usingBlood') == true;
if (currentIsBlood != storageIsBlood) {
utils.setStorage('usingBlood', currentIsBlood);
window.location.reload();
}
};
window.addEventListener('DOMContentLoaded', checkBlood, false);
BMF.isBlood = function() {
return BMF.isBlood.value;
};
BMF.isBlood.value = utils.getStorage('usingBlood') || false;
return BMF.isBlood();
};
BMF.redirectToOldFollows = function() {
if (window.location.href == 'http://www.batoto.net/myfollows') {
window.location.replace('/follows_comics');
}
var replaceLinks = function() {
document.getElementById('nav_menu_4_trigger').href = '/follows_comics';
if (window.location.pathname == '/') {
document.getElementById('hook_watched_items').children[2].firstChild.href = '/follows_comics';
}
};
window.addEventListener('DOMContentLoaded', replaceLinks, false);
};
BMF.mainBatoto = function() {
var path = window.location.pathname;
if (path == '/') {
if (settings.addIconsInHomepage) BMF.addFollowIconsHome();
} else if (path == '/myfollows') {
BMF.saveFollowsList('new');
} else if (path == '/follows_comics') {
BMF.saveFollowsList('old');
if (settings.infoButtonOldFollows) BMF.addInfoOldFollows();
var newFollowsLinkBox = layouts.newFollowsLinkBox();
document.getElementById('index_stats').appendChild(newFollowsLinkBox);
BMF.categorizeFollows();
if (settings.showTotalFollows) BMF.addTotalFollows();
} else if (path == '/search') {
BMF.followsInSearch();
} else if (path.indexOf('/comic/_/comics') == 0) {
if (settings.mangaupdatesLinkInComic) BMF.addSearchInMUButton();
if (settings.malLinkInComic) BMF.addSearchInMALButton();
if (settings.comicsPopupComicPage) BMF.setComicPopup();
if (settings.hyperlinkDescription) {
var desc = document.getElementsByTagName('tbody')[0].children[6].children[1],
regExp = /(https?:\/\/([-\w\.]+)+(:\d+)?(\/([-\w\/_\.]*(\?\S+)?)?)?)/ig;
desc.innerHTML = desc.innerHTML.replace(regExp, "<a href='$1'>$1</a>");
}
} else if (path.indeOf('/forums') == 0) {
if (settings.comicsPopupForum) BMF.setComicPopup();
}
};
BMF.addFollowIconsHome = function() {
var followsList = utils.getStorage('followsList'),
icon = utils.newImg('Following', settings.followingIcon, 'following_icon'),
current = document.getElementById('loading_row_1'),
i = 1;
while (current) {
if (followsList.indexOf(current.previousElementSibling.textContent) != -1) {
current.parentNode.insertBefore(icon.cloneNode(false), current.parentNode.childNodes[2]);
}
i = i + 1;
current = document.getElementById('loading_row_' + i.toString());
}
};
BMF.saveFollowsList = function(page) {
var followsList = [],
comics, i, len;
if (page == 'new') {
comics = document.getElementsByClassName("clearfix")[9].children;
i = 8;
} else if (page == 'old') {
comics = document.getElementsByTagName('strong');
i = 1;
}
len = comics.length - 3;
for (undefined; i < len; i++) {
followsList.push(comics[i].textContent);
}
utils.setStorage('followsList', followsList);
};
BMF.addSearchInMUButton = function() {
var title = document.getElementsByClassName('ipsType_pagetitle')[0],
titleText = title.textContent.replace(/\(Doujinshi\)|\(doujinshi\)/g, ''),
button = utils.newAnchorImg('Search in MangaUpdates', settings.muIcon, 'bmf_button');
title.insertBefore(button, title.firstChild);
utils.googleRedirectAnchor(button, 'mangaupdates.com/series.html?id=', titleText);
};
BMF.addSearchInMALButton = function() {
var title = document.getElementsByClassName('ipsType_pagetitle')[0],
titleText = title.textContent.replace(/\(Doujinshi\)|\(doujinshi\)/g, '(Doujin)'),
button = utils.newAnchorImg('Search in MyAnimeList', settings.malIcon, 'bmf_button');
title.insertBefore(button, title.firstChild);
utils.googleRedirectAnchor(button, 'myanimelist.net/manga', titleText);
};
BMF.setComicPopup = function() {
//uses a ipb function (the same that previews the profiles)
var links = [].slice.call(document.getElementsByTagName('a')),
length = links.length,
anchor, href;
for (var i = 0; i < length; i++) {
anchor = links[i];
href = anchor.href;
if (href.indexOf('http://www.batoto.net/comic/_/') == 0 && href.search(/\?|#/) == -1) {
anchor.className += ' _hovertrigger';
anchor.setAttribute('hovercard-ref', 'comicPopup');
anchor.setAttribute('hovercard-id', utils.getComicId(href));
}
}
ipb.hoverCardRegister.initialize('comicPopup', {
'w': '680px',
'delay': settings.popupDelay,
'position': 'auto',
'ajaxUrl': 'http://www.batoto.net/comic_pop?',
'getId': true,
'setIdParam': 'id'
});
};
BMF.addInfoOldFollows = function() {
var titles = [].slice.call(document.getElementsByTagName('strong'), 0),
len = titles.length - 3,
infoButton = document.createElement('a'),
title;
infoButton.href = 'javascript:void(0)';
infoButton.className = 'info_button';
for (var i = 1; i < len; i++) {
title = titles[i].parentNode;
title.parentNode.insertBefore(infoButton.cloneNode(false), title);
title.previousSibling.addEventListener('click', BMF.showInfoOldFollows, false);
}
};
BMF.showInfoOldFollows = function() {
// modification of the function used by batoto
// batoto.net/js/shortcuts_20131231.js
//(prototype.js)
var that = this,
comicid = utils.getComicId(that.nextSibling.href),
divid = 'cId_' + comicid;
if ($(divid) && $(divid).visible()) {
Effect.SlideUp(divid, {
duration: settings.durationInfoSlide
});
} else if ($(divid) && $(divid).innerHTML != '') {
Effect.SlideDown(divid, {
duration: settings.durationInfoSlide
});
} else {
new Ajax.Updater(divid, ipb.vars['home_url'] + '/comic_pop', {
parameters: {
id: comicid
},
method: 'get',
onLoaded: function() {
var tr = document.createElement('tr'),
td = document.createElement('td'),
div = document.createElement('div'),
table = utils.getNode(that, "pa", 3),
nextRow = utils.getNode(that, "pa", 2, "neE", 1);
div.id = divid;
div.style.display = 'none';
tr.className = 'info_row';
if (BMF.isBlood()) {
if (that.parentNode.parentNode.className == 'row1') {
div.className += ' grey_row';
} else {
div.className += ' white_row';
}
}
td.setAttribute('colspan', '2');
td.className = 'info_col';
td.appendChild(div);
tr.className = nextRow.className.replace(/row0|row1|altrow/g, '');
tr.appendChild(td);
table.insertBefore(tr, nextRow);
that.parentNode.previousElementSibling.setAttribute('rowspan', '3');
},
onSuccess: function() {
setTimeout(function() {
new Effect.SlideDown(divid, {
duration: settings.durationInfoSlide
});
}, 50);
}
});
}
};
BMF.updateFollowsList = function() {
//only if there are no request being made currently
var loading = document.getElementById('ajax_loading');
if (!loading || loading.style.display == 'none') {
new Ajax.Request('/follows_comics', {
method: 'get',
onSuccess: function(response) {
var followsArray = [],
regExp = /topic"><strong>(.*?)<\/strong><\/a>/g,
comic = regExp.exec(response.responseText);
while (comic) {
followsArray.push(utils.decodeHtmlEntity(comic[1]));
comic = regExp.exec(response.responseText);
}
utils.setStorage('followsList', followsArray);
//updates all the previous comic results
BMF.matchFollowsSearch('all');
if (settings.addIconsInSearch) BMF.addIconsToMatches();
if (BMF.hideMatches) BMF.changeMatchesDisplay('hide');
}
});
}
};
BMF.categorizeFollows = function() {
var rows = document.getElementsByTagName('table')[0].rows,
len = rows.length,
read = 0,
reading = 0,
noReads = 0,
className, row1, row2, link1, link2;
for (var a = 0; a < len; a = a + 2) {
row1 = rows[a];
row2 = rows[a + 1];
if (row1.children[2].textContent != 'Last Read: No Record') {
link1 = row1.children[2].children[0].href;
link2 = row2.firstElementChild.children[1].href;
if (link1 == link2) {
className = ' bmf_read';
read++;
} else {
className = ' bmf_reading';
reading++;
}
} else {
className = ' bmf_noreads';
noReads++;
}
row1.className = row2.className += className;
}
var viewOptionsBox = layouts.viewOptionsBox(read, reading, noReads);
document.getElementById("index_stats").insertBefore(viewOptionsBox, null);
var views = function() {
var table = document.getElementsByClassName('ipb_table')[0];
if (this.value == 'hide') {
table.classList.add('h_' + this.name);
} else {
table.classList.remove('h_' + this.name);
}
};
for (var e = 1; e < 4; e++) {
document.getElementById('show' + e).addEventListener('click', views, false);
document.getElementById('hide' + e).addEventListener('click', views, false);
}
};
BMF.addTotalFollows = function() {
var total = document.getElementsByTagName('strong').length - 3,
text = document.createElement('strong');
text.className = 'total_follows';
text.textContent = 'You are following ' + total + ' comics!';
document.getElementsByClassName('maintitle')[0].appendChild(text);
};
BMF.followsInSearch = function() {
BMF.matchFollowsSearch();
if (settings.addIconsInSearch) BMF.addIconsToMatches();
//button to update your follows list
var searchBar = document.getElementsByClassName('input_submit')[2].parentNode,
updateButton = document.createElement('a');
updateButton.href = '<a href="javascript:void(0)';
updateButton.title = 'Update Follows';
updateButton.textContent = 'Update Follows';
updateButton.className = 'input_submit update_follows';
searchBar.insertBefore(updateButton, searchBar.childNodes[2]);
updateButton.addEventListener('click', BMF.updateFollowsList, false);
// exclude option in advance search
var optionsBar = document.getElementById('advanced_options').children[0],
optionInput = layouts.radioExcludeFollowsSearch();
optionsBar.insertBefore(optionInput, optionsBar.children[6]);
document.getElementById('incl_follows').addEventListener('click', BMF.changeMatchesDisplay, false);
document.getElementById('excl_follows').addEventListener('click', BMF.changeMatchesDisplay, false);
//watch for when results are added or reseted
var tablesContainer = document.getElementById('comic_search_results'),
observer = new MutationObserver(function(mutations) {
if (mutations.length == 2 || mutations.length == 4) {
BMF.matchFollowsSearch();
if (settings.addIconsInSearch) BMF.addIconsToMatches();
}
});
observer.observe(tablesContainer, {
childList: true
});
};
BMF.matchFollowsSearch = function(tableN) {
this.lastMatches = []; //in case it has to add the icons
var followsList = utils.getStorage('followsList'),
comicRows, len, comicName, i;
if (tableN == 'all') { //if function is called by update follows button then match all again
comicRows = document.getElementsByTagName('strong');
i = 1;
len = comicRows.length - 3;
} else { //just match the new table
tableN = document.getElementsByClassName('chapters_list').length - 1;
comicRows = document.getElementsByClassName('chapters_list')[tableN].getElementsByTagName('strong');
i = 0;
len = comicRows.length;
}
for (undefined; i < len; i++) {
comicName = comicRows[i].textContent.trim();
//classes are added to control the visibility of the rows/icons
if (followsList.indexOf(comicName) != -1) {
comicRows[i].parentNode.parentNode.className = 'bmf_match';
this.lastMatches.push(comicRows[i]);
} else {
//css makes ".bmf_mismatch .following_icon" to be hidden
//in case when updating it shows you unfollowed a comic
comicRows[i].parentNode.parentNode.className = 'bmf_mismatch';
}
}
};
BMF.addIconsToMatches = function() {
//only to lastMatches stored by BMF.matchFollowsSearch();
var icon = utils.newImg('Following', settings.followingIcon, 'following_icon'),
len = this.lastMatches.length,
comic;
for (var i = 0; i < len; i++) {
comic = this.lastMatches[i].firstChild;
if (comic.childNodes.length == 2) { //only if icon wasn't added previously (updating button)
comic.insertBefore(icon.cloneNode(false), comic.firstChild.nextSibling);
}
}
this.lastMatches = [];
};
BMF.changeMatchesDisplay = function(action) {
//in case the function was called by click event in adv search option
action = action.type == 'click' ? this.value : action;
// #comic_search_results is parent of all the tables
// css makes ".bmf_hide .bmf_match" rows to be hidden
if (action == 'hide') {
BMF.hideMatches = true;
document.getElementById('comic_search_results').className = 'bmf_hide';
//hides the info boxes of matches
var infoBoxes = document.getElementsByClassName('ipsBox'),
len = infoBoxes.length,
infoBox;
for (var u = 0; u < len; u++) {
infoBox = infoBoxes[u].parentNode.parentNode;
if (infoBox.previousElementSibling.className == 'bmf_match') {
infoBox.style.display = 'none';
}
}
} else if (action == 'show') {
BMF.hideMatches = false;
document.getElementById('comic_search_results').className = '';
}
};
BMF.mainMangaUpdates = function() {
var href = window.location.href;
if (href.indexOf('https://www.mangaupdates.com/series.html?id=') != -1) {
if (settings.batotoLinkInMU) {
BMF.MU_addSearchInBatotoButton();
}
}
};
BMF.MU_addSearchInBatotoButton = function() {
var title = document.getElementsByClassName('releasestitle')[0];
if (title.textContent.indexOf('(Novel)') == -1) {
var button = utils.newAnchorImg('Search in Batoto', settings.bIcon, 'bmf_button bmf_mu_button');
title.parentNode.insertBefore(button, title);
utils.googleRedirectAnchor(button, 'batoto.net/comic', title.textContent);
}
};
layouts.setStyles = function() {
if (BMF.isBlood() && settings.hideBloodHeader) {
GM_addStyle(
'#branding { ' +
'position: absolute !important; } ');
}
GM_addStyle(
'.following_icon { ' +
'vertical-align: top; ' +
'margin-left: 1px; } ' +
'.bmf_mismatch .following_icon { ' +
'display: none; } ' +
'.bmf_hide .bmf_match { ' +
'display: none; } ' +
'.update_follows { ' +
'position: absolute; ' +
'top: 10px; ' +
'left: 110px; ' +
'font-weight: normal; } ' +
'.info_button { ' +
'padding: 0px 8px; ' +
'margin-right: 2px; ' +
'background-repeat: no-repeat; ' +
'background-image: url(' + GM_getResourceURL('infoIconR') + '); } ' +
'.h_read .bmf_read, .h_reading .bmf_reading, .h_noreads .bmf_noreads { ' +
'display: none; } ' +
'.info_col { ' +
'padding: 0px !important; ' +
'border: none !important; } ' +
'.grey_row .ipsBox { ' +
'background: rgba(0, 0, 0, 0.8); } ' +
'.white_row .ipsBox { ' +
'background: white; }' +
'.total_follows { ' +
'font-size: 70%; ' +
'padding-left: 8px; ' +
'color: ' + settings.textColor + '; ' +
'display: inline; ' +
'vertical-align: middle; } ' +
'.bmf_button { ' +
'color: #595959; ' +
'background: #e4e4e4; ' +
'background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f6f6f6), color-stop(100%,#d7d7d7)); ' +
'box-shadow: -1px 1px 0px 0px #dfdcdc inset, 1px -1px 0px 0px #bfbfbf inset; ' +
'border-radius: 5px; ' +
'display: inline-block; ' +
'width: 28px; ' +
'height: 28px; ' +
'vertical-align: -2px; ' +
'text-align: center; ' +
'margin: 0px 4px; }' +
'.bmf_button:hover { ' +
'box-shadow: 0px 0px 0px 1px #a8a8a8 inset, 1px 1px 3px 0px #bababa; ' +
'color: #424242; } ' +
'.bmf_button:active { ' +
'background: #e6e6e6; ' +
'box-shadow: 0px 1px 3px 0px #808080 inset; } ' +
'.bmf_button img { ' +
'display: inline-block; ' +
'vertical-align: 4px; } ' +
'.bmf_mu_button { ' +
'vertical-align: 0 !important; ' +
'margin-right: 4px !important; }' +
'.bmf_mu_button img { ' +
'vertical-align: 0px !important; ' +
'margin-top: 6px; } ');
};
layouts.radioExcludeFollowsSearch = function() {
var optionRadio = document.createElement('tr');
optionRadio.innerHTML = '<td style=\"text-align: right; font-weight: bold;\">Include MyFollows:</td>' +
'<td style=\"text-align: left; vertical-align: top; padding: 8px 0;\">' +
'<label><input id=\"incl_follows\" type=\"radio\" name=\"follows\" value=\"show\" checked=\"checked\"> Yes</label>' +
'<label><input id=\"excl_follows\" type=\"radio\" name=\"follows\" value=\"hide\" style=\"margin-left: 4px\"> No</label></td>';
return optionRadio;
};
layouts.viewOptionsBox = function(read, reading, noReads) {
var box = document.createElement("div");
box.className = 'general_box clearfix;';
box.innerHTML = '<h3>View Settings</h3><div class="_sbcollapsable">' +
'<a href="javascript:void(0)" onclick=\"$(\'view\').toggle();">Alter Settings</a>' +
'<div id="view" style="display: none;"><table><tbody>' +
'<tr><td>All read (' + read + ')</td><td style="text-align: left; vertical-align: top; padding: 6px 0;">' +
'<label><input id="show1" type="radio" name="read" value="show" checked="checked"> Show</label>' +
'<label><input id="hide1" type="radio" name="read" value="hide" style="margin-left: 6px"> Hide</label></td></tr>' +
'<tr><td>Reading (' + reading + ')</td><td style="text-align: left; vertical-align: top; padding: 6px 0;">' +
'<label><input id="show2" type="radio" name="reading" value="show" checked="checked"> Show</label>' +
'<label><input id="hide2" type="radio" name="reading" value="hide" style="margin-left: 6px"> Hide</label></td></tr>' +
'<tr><td>No reads (' + noReads + ')</td><td style="text-align: left; vertical-align: top; padding: 6px 0;">' +
'<label><input id="show3" type="radio" name="noreads" value="show" checked="checked"> Show</label>' +
'<label><input id="hide3" type="radio" name="noreads" value="hide" style="margin-left: 6px"> Hide</label></td></tr>' +
'</tbody></table></div></div>';
return box;
};
layouts.newFollowsLinkBox = function() {
var box = document.createElement('div');
box.className = 'general_box alt clearfix';
box.innerHTML = '<h3><img src="http://www.batoto.net/forums/public/style_images/master/star.png" alt=""> Follows by Chapters (new follows)</h3>' +
'<div class="recent_activity _sbcollapsable"><div class="tab_toggle_content" style="text-align:center;">' +
'<a href="/myfollows?noRedirect">Right here!</a></div></div>';
return box;
};
utils.setStorage = function(key, value) {
localStorage.setItem('BMF_' + key, JSON.stringify(value));
};
utils.getStorage = function(key) {
return JSON.parse(localStorage.getItem('BMF_' + key));
};
utils.decodeHtmlEntity = function(encoded) {
var div = document.createElement('div');
div.innerHTML = encoded;
return div.firstChild.nodeValue;
};
utils.newAnchorImg = function(title, src, className, href) {
var button = document.createElement('a'),
img = utils.newImg(title, src);
button.className = className;
button.href = href || 'javascript:void(0)';
button.appendChild(img);
return button;
};
utils.newImg = function(title, src, className) {
var icon = document.createElement('img');
icon.alt = '';
icon.title = title;
icon.src = src;
icon.className = className || '';
return icon;
};
utils.getComicId = function(url) {
var temp = url.substring(url.lastIndexOf('r') + 1);
return temp;
};
utils.getNode = function() {
var args = arguments,
node = args[0],
len = args.length,
a, action, times, b;
for (a = 1; a < len; a = a + 2) {
action = args[a];
times = args[a + 1];
b = 0;
if (action == 'pa') { //undefined because of jslint
for (undefined; b < times; b++) {
node = node.parentNode;
}
} else if (action == 'fiE') {
for (undefined; b < times; b++) {
node = node.firstElementChild;
}
} else if (action == 'prE') {
for (undefined; b < times; b++) {
node = node.previousElementSibling;
}
} else if (action == 'neE') {
for (undefined; b < times; b++) {
node = node.nextElementSibling;
}
} else if (action == 'fi') {
for (undefined; b < times; b++) {
node = node.firstChild;
}
} else if (action == 'pr') {
for (undefined; b < times; b++) {
node = node.previousSibling;
}
} else if (action == 'ne') {
for (undefined; b < times; b++) {
node = node.nextSibling;
}
}
}
return node;
};
utils.googleRedirectAnchor = function(button, site, query) {
//removes extra whitespaces, search operators and encode non latin characters
query = encodeURIComponent(query.replace(/-|"|\*|\:/g, ' ').replace(/\s+/g, ' '));
//first url sends you first to google and then redirects you,
//this causes sometimes a half rendering of 'google.com' first.
//second url can give you the direct link to the target site,
//but only through a request with google as referer and response.finalUrl (GM_)
var redirectURL = 'https://www.google.com/webhp?#btnI=I&sitesearch=' + site + '&q=' + query,
requestURL = 'https://www.google.com/search?btnI=I&sitesearch=' + site + '&q=' + query;
button.href = redirectURL;
GM_xmlhttpRequest({
method: 'HEAD',
url: requestURL,
headers: {
Referer: 'https://www.google.com/'
},
onload: function(response) {
if (response.finalUrl.indexOf(site) != -1) {
button.href = response.finalUrl;
} else {
button.href = requestURL;
}
}
});
};
BMF.init();