// ==UserScript== //
// @name Batoto MyFollows
// @version 14.11.15.2
// @description Filter your follows from comic search, info button for the old follows page, links between Batoto-MU-Mal and other features.
// @namespace https://greasyfork.org/users/168
// @match *://bato.to/*
// @match *://www.mangaupdates.com/*
// @match *://myanimelist.net/manga/*
// @grant GM_xmlhttpRequest
// @grant GM_getResourceURL
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-start
// @noframes
// @resource r_followingIcon https://bato.to/forums/public/style_images/Sylo/star.png
// @resource r_muIcon https://www.mangaupdates.com/favicon.ico
// @resource r_malIcon http://cdn.myanimelist.net/images/faviconv5.ico
// @resource r_batotoIcon https://bato.to/forums/favicon.ico
// @resource r_infoIcon https://bato.to/forums/public/style_images/master/information.png
// ==/UserScript==
//-----------------------------------------------------
// Default settings
//-----------------------------------------------------
var defaultsOpts = {
interlinks: {
searchEngine: 'google', // 'google' || 'duckduckgo'
saveUrls: true,
//comic page
batoto_mu: true,
batoto_mal: true,
mu_mal: true
},
batoto: {
// general
icon: getResourceURL('r_batotoIcon') || 'https://bato.to/forums/favicon.ico',
infoIcon: getResourceURL('r_infoIcon') || 'https://bato.to/forums/public/style_images/master/information.png',
followingIcon: getResourceURL('r_followingIcon') || 'https://bato.to/forums/public/style_images/Sylo/star.png',
hideBloodHeader: true,
comicsPopupDelay: 1000, //ms
defaultToOldFollows: true,
// home page
followingIcon_home: true,
// old follows page
addInfoBtns: true,
showTotalFollows: true,
totalTextColor: '#DDDDDD',
addBoxToNewFollows: true,
categorizeFollows: true,
// comic page
hyperlinkDesc: true,
showMoreRecentBtn: true,
comicsPopup_comic: true,
// search
followingIcon_search: true,
// forums
comicsPopup_forums: true
},
mu: {
//general
icon: getResourceURL('r_muIcon') || 'https://www.mangaupdates.com/favicon.ico'
},
mal: {
//general
icon: getResourceURL('r_malIcon') || 'http://cdn.myanimelist.net/images/faviconv5.ico'
}
};
// * change the icons in the metadata (@resource) and also in defaultsOpts
//-----------------------------------------------------
// Batoto
//-----------------------------------------------------
var batoto = {
init: function() {
if (opts.batoto.defaultToOldFollows) {
this.defaultToOldFollows();
}
ready(function() {
batoto.loadingImg.init();
if (/^\/$/.test(path)) {
batoto.page_home.init();
} else if (/^\/myfollows/.test(path)) {
batoto.page_newFollows.init();
} else if (/^\/follows_comics/.test(path)) {
batoto.page_oldFollows.init();
} else if (/^\/search/.test(path)) {
batoto.page_search.init();
} else if (/^\/comic\/_\/comics\/.+/.test(path)) {
batoto.page_comic.init();
} else if (/^\/forums/.test(path)) {
batoto.page_forums.init();
}
});
},
comicIdRegex: /bato\.to\/comic\/_\/.+\-r(\d+)\/?$/i,
getComicId: function(url) {
if (!url) return null;
var id = url.match(batoto.comicIdRegex);
return id !== null ? id[1] : id;
},
theme: null,
getTheme: function() {
if (this.theme === null) {
var storedTheme = getStorage('batoto_theme');
if (this.isLoggedIn()) {
ready(function() {
var pageTheme = querySel('#new_skin_menucontent > .selected > a');
pageTheme = pageTheme.textContent.toLowerCase().replace(/ .*/, '');
if (pageTheme !== storedTheme) {
setStorage('batoto_theme', pageTheme);
setTimeout(function() {
location.reload();
}, 0);
}
});
} else {
setStorage('batoto_theme', 'subway');
}
this.theme = storedTheme || 'subway';
}
return this.theme;
},
loggedIn: null,
isLoggedIn: function() {
if (this.loggedIn === null) {
var storedLoggedId = getStorage('batoto_logged_in');
ready(function() {
var pageLoggedId = Window.ipb.vars['member_id'] !== 0;
if (pageLoggedId !== storedLoggedId) {
setStorage('batoto_logged_in', pageLoggedId);
setTimeout(function() {
location.reload();
}, 0);
}
});
this.loggedIn = storedLoggedId || false;
}
return this.loggedIn;
},
defaultToOldFollows: function() {
var oldFollows = 'http://bato.to/follows_comics';
if (path === '/myfollows' && location.search === '') {
location.replace(oldFollows);
}
ready(function() {
document.getElementById('nav_menu_4_trigger').href = oldFollows;
if (path === '/') {
querySel('#hook_watched_items > div:last-child > a').href = oldFollows;
}
});
},
setComicPopup: function() {
//uses the same ipb function that previews the profiles
var links = document.getElementsByTagName('a'),
popupConf, anchor, id;
for (var i = 0, len = links.length; i < len; i++) {
anchor = links[i];
id = batoto.getComicId(anchor.getAttribute('href'));
if (id !== null) {
anchor.className += ' _hovertrigger';
anchor.setAttribute('hovercard-ref', 'comicPopup');
anchor.setAttribute('hovercard-id', id);
}
}
popupConf = {
'w': '680px',
'delay': opts.batoto.comicsPopupDelay,
'position': 'auto',
'ajaxUrl': Window.ipb.vars['home_url'] + '/comic_pop?',
'getId': true,
'setIdParam': 'id'
};
if (typeof cloneInto !== 'undefined') {
popupConf = cloneInto(popupConf, unsafeWindow);
}
Window.ipb.hoverCardRegister.initialize('comicPopup', popupConf);
},
followsList: {
updating: false,
update: function(callback) {
if (!this.updating) {
var self = this;
self.updating = true;
batoto.loadingImg.fadeIn();
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status === 200) {
var followsList = [],
regex = /"View topic"><strong>(.*?)<\/strong>/g,
title = regex.exec(request.responseText);
while (title) {
followsList.push(decodeHtmlEntity(title[1]));
title = regex.exec(request.responseText);
}
setStorage('follows_list', toCharObject(followsList));
if (typeof callback === 'function') {
callback();
}
}
batoto.loadingImg.fadeOut();
self.updating = false;
}
};
request.open('GET', '/follows_comics', true);
request.send();
}
}
},
loadingImg: {
init: function() {
var div = document.getElementById('ajax_loading');
if (!div) {
div = createElemHTML(Window.ipb.templates['ajax_loading']);
div.style.display = 'none';
document.getElementById('ipboard_body').appendChild(div);
}
if (typeof cloneInto !== 'undefined') {
this.config = cloneInto(this.config, Window);
}
},
config: {
duration: 0.25
},
queue: 0,
fadeIn: function() {
this.queue++;
if (this.queue === 1) {
if (this.effect) {
this.effect.cancel();
this.effect = null;
}
this.effect = new Window.Effect.Appear('ajax_loading', this.config);
}
},
fadeOut: function() {
this.queue--;
if (this.queue === 0) {
if (this.effect) {
this.effect.cancel();
this.effect = null;
}
this.effect = new Window.Effect.Fade('ajax_loading', this.config);
}
}
}
};
//------------------
// Specific pages
//------------------
batoto.page_home = {
init: function() {
if (opts.batoto.followingIcon_home && batoto.isLoggedIn()) {
this.addFollowsIcons();
}
},
addFollowsIcons: function() {
var followsList = getStorage('follows_list'),
icon = createElem('img', {
title: 'Following',
src: opts.batoto.followingIcon,
class: 'bmf_following_icon'
}),
titles = querySelAll("td > a[style='font-weight:bold;']"),
comic;
if (!followsList) {
batoto.followsList.update(this.addFollowsIcons);
return;
}
for (var i = 1, len = titles.length; i < len; i += 2) {
comic = titles[i];
if (inCharObject(followsList, comic.textContent)) {
comic.parentNode.insertBefore(icon.cloneNode(false), comic.previousSibling);
}
}
}
};
batoto.page_newFollows = {
init: function() {
this.saveFollowsList();
},
saveFollowsList: function() {
var followsList = [],
titles = document.getElementById('categories').parentNode.children;
for (var i = 7, len = titles.length - 7; i < len; i++) {
followsList.push(titles[i].textContent);
}
setStorage('follows_list', toCharObject(followsList));
}
};
batoto.page_oldFollows = {
init: function() {
this.titles = [].slice.call(
document.getElementsByClassName('ipb_table')[0].getElementsByTagName('strong'), 0);
this.saveFollowsList();
if (opts.batoto.showTotalFollows) {
this.showTotalFollows();
}
if (opts.batoto.addInfoBtns) {
this.addInfoBtns();
}
if (opts.batoto.addBoxToNewFollows) {
this.addBoxToNewFollows();
}
if (opts.batoto.categorizeFollows) {
this.categorizeFollows();
}
this.titles = null;
},
saveFollowsList: function() {
var followsList = [],
titles = this.titles;
for (var i = 0, len = titles.length; i < len; i++) {
followsList.push(titles[i].textContent);
}
setStorage('follows_list', toCharObject(followsList));
},
showTotalFollows: function() {
var total = String(this.titles.length),
elem = document.createElement('strong');
elem.id = 'bmf_total_follows';
elem.textContent = 'Total: ' + total + ' comics!';
document.getElementsByClassName('maintitle')[0].appendChild(elem);
},
addInfoBtns: function() {
var titles = this.titles,
showInfo = this.showInfo,
infoBtn = createElem('a', {
href: 'javascript:void(0)',
class: 'bmf_info_button'
}),
title;
for (var i = 0, len = titles.length; i < len; i++) {
title = titles[i].parentNode;
title.parentNode.insertBefore(infoBtn.cloneNode(false), title);
title.previousSibling.addEventListener('click', showInfo, false);
}
},
showInfo: function(event) {
// modification of the function used by batoto:
// bato.to/js/shortcuts_20131231.js
//(prototype.js)
var infoBtn = this,
comicId = batoto.getComicId(infoBtn.nextSibling.href),
div = document.getElementById('cId_' + comicId);
if (div === null) {
div = batoto.page_oldFollows.addInfoContainer(infoBtn, comicId);
}
if (div.children.length === 0) {
batoto.loadingImg.fadeIn();
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState === 4) {
batoto.loadingImg.fadeOut();
if (request.status === 200) {
div.innerHTML = request.responseText;
setTimeout(function() {
div.style.display = '';
}, 30);
}
}
};
request.open('GET', '/comic_pop?id=' + comicId, true);
request.send();
} else if (div.style.display === '') {
div.style.display = 'none';
} else {
div.style.display = '';
}
},
addInfoContainer: function(infoBtn, comicId) {
var nextRow = infoBtn.parentNode.parentNode.nextElementSibling,
div = createElem('div', {
id: 'cId_' + comicId,
style: 'display: none;'
}),
tr = createElem('tr', {
class: nextRow.className.replace('altrow', '') + ' bmf_info_row'
}),
td = createElem('td', {
colspan: '2',
style: 'border-bottom-width:0 !important'
});
td.appendChild(div);
tr.appendChild(td);
querySel('.ipb_table > tbody').insertBefore(tr, nextRow);
infoBtn.parentNode.previousElementSibling.setAttribute('rowspan', '3');
return div;
},
categorizeFollows: function() {
var rows = document.getElementsByTagName('table')[0].rows,
read = 0,
reading = 0,
noReads = 0,
className, row1, row2, link1, link2, viewOptionsBox, views;
for (var i = 0, len = rows.length; i < len; i = i + 2) {
row1 = rows[i];
row2 = rows[i + 1];
if (row1.children[2].textContent !== 'Last Read: No Record') {
link1 = row1.children[2].children[0].getAttribute('href');
link2 = row2.firstElementChild.children[1].getAttribute('href');
if (link1 === link2) {
className = ' bmf_read';
read++;
} else {
className = ' bmf_reading';
reading++;
}
} else {
className = ' bmf_noreads';
noReads++;
}
row1.className = row2.className += className;
}
viewOptionsBox = createElemHTML(
'<div class="general_box clearfix"><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>');
document.getElementById('index_stats').insertBefore(viewOptionsBox, null);
views = function() {
var table = document.getElementsByClassName('ipb_table')[0];
if (this.value === 'hide') {
table.classList.add('bmf_hide_' + this.name);
} else {
table.classList.remove('bmf_hide_' + this.name);
}
};
for (var i = 1; i < 4; i++) {
document.getElementById('show' + i).addEventListener('click', views, false);
document.getElementById('hide' + i).addEventListener('click', views, false);
}
},
addBoxToNewFollows: function() {
var box = document.createElement('div');
box.className = 'general_box alt clearfix';
box.innerHTML = '<h3><img src=" ' + Window.ipb.vars['rate_img_on'] + '" alt="">' +
'\nFollows 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>';
document.getElementById('index_stats').appendChild(box);
}
};
batoto.page_comic = {
init: function() {
if (opts.batoto.comicsPopup_comic) {
batoto.setComicPopup();
}
if (opts.interlinks.batoto_mu) {
this.addInterlinkBtn('mu', 'Search in MangaUpdates');
}
if (opts.interlinks.batoto_mal) {
this.addInterlinkBtn('mal', 'Search in MyAnimeList');
}
if (opts.batoto.hyperlinkDesc) {
this.hyperlinkDesc();
}
if (opts.batoto.showMoreRecentBtn) {
this.addShowMoreRecentBtn();
}
},
addInterlinkBtn: function(target, title) {
var comicTitle = document.getElementsByClassName('ipsType_pagetitle')[0],
comicName = comicTitle.textContent.replace(/\(doujinshi\)/gi, '(Doujin)'),
button = createImgBtn(title, 'bmf_interlink_btn ipsButton_secondary', opts[target].icon);
comicTitle.insertBefore(button, comicTitle.firstChild);
interlinks.getUrl(target, comicName, location.href, function(url) {
button.href = url;
});
button.addEventListener('click', function(event) {
if (!event.ctrlKey || !event.shiftKey) return;
event.preventDefault();
interlinks.saveFromPrompt(location.href, target);
});
},
hyperlinkDesc: function() {
var desc = document.getElementsByTagName('tbody')[0].children[6].children[1],
regex = /(\b(https?):\/\/[\-A-Z0-9+&@#\/%?=~_|!:,.;]*[\-A-Z0-9+&@#\/%=~_|])/gim;
desc.innerHTML = desc.innerHTML.replace(regex, '<a href="$1">$1</a>');
},
addShowMoreRecentBtn: function() {
var btn = createElem('div', {
id: 'bmf_show_recent_btn'
});
var a = createElem('a', {
class: 'input_submit',
href: '/comic/_/comics/?sort_col=record_saved&sort_order=desc'
});
a.textContent = 'Show More';
btn.appendChild(a);
querySelAll('.general_box')[2].appendChild(btn);
}
};
batoto.page_search = {
init: function() {
if (batoto.isLoggedIn()) {
this.runMatch('last');
this.addUpdateFollowsBtn();
this.addExcludeOption();
this.watchForNewResults();
}
},
runMatch: function(table) {
var comics = this.getMatchComics(table),
icon = createElem('img', {
title: 'Following',
src: opts.batoto.followingIcon,
class: 'bmf_following_icon'
}),
matches = comics[0],
mismatches = comics[1],
comic, title;
for (var i = 0, len = matches.length; i < len; i++) {
comic = matches[i];
title = comic.firstChild;
if (opts.batoto.followingIcon_search && title.childNodes.length === 2) {
title.insertBefore(icon.cloneNode(false), title.firstChild.nextSibling);
}
comic.parentNode.parentNode.setAttribute('class', 'bmf_match');
}
for (var i = 0, len = mismatches.length; i < len; i++) {
// if the icon was added but then you unfollowed the comic
// and updated the list, then css will hide the icon
mismatches[i].parentNode.parentNode.setAttribute('class', 'bmf_mismatch');
}
},
getMatchComics: function(table) {
var followsList = getStorage('follows_list'),
matches = [],
mismatches = [],
comics, comic, len, i;
if (table === 'all') {
comics = document.getElementsByTagName('strong');
i = 1;
len = comics.length - 3;
} else if (table === 'last') {
table = document.getElementsByClassName('chapters_list').length - 1;
comics = document.getElementsByClassName('chapters_list')[table].getElementsByTagName('strong');
i = 0;
len = comics.length;
}
for (; i < len; i++) {
comic = comics[i];
if (inCharObject(followsList, comic.textContent.trim())) {
matches.push(comic);
} else {
mismatches.push(comic);
}
}
return [matches, mismatches];
},
matchesVisibility: function(action) {
if (action === 'hide') {
document.getElementById('comic_search_results').className = 'bmf_hide_matches';
// hides the info boxes of matches if shown
var infoBoxes = document.getElementsByClassName('ipsBox'),
infoBox;
for (var i = 0, len = infoBoxes.length; i < len; i++) {
infoBox = infoBoxes[i].parentNode.parentNode;
if (infoBox.previousElementSibling.className === 'bmf_match') {
infoBox.style.display = 'none';
}
}
} else if (action === 'show') {
document.getElementById('comic_search_results').className = '';
}
},
addExcludeOption: function() {
var self = this,
optionsBar = document.getElementById('advanced_options').children[0],
optionInput = document.createElement('tr');
optionInput.innerHTML =
'<tr><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></tr>';
optionsBar.insertBefore(optionInput, optionsBar.children[6]);
document.getElementById('incl_follows').addEventListener('click', function() {
self.matchesHidden = false;
self.matchesVisibility('show');
}, false);
document.getElementById('excl_follows').addEventListener('click', function() {
self.matchesHidden = true;
self.matchesVisibility('hide');
}, false);
},
watchForNewResults: function() {
var self = this,
tablesContainer = document.getElementById('comic_search_results'),
observer = new MutationObserver(function(mutations) {
if (mutations.length === 2 || mutations.length === 4) {
self.runMatch('last');
}
});
observer.observe(tablesContainer, {
childList: true
});
},
addUpdateFollowsBtn: function() {
var self = this,
searchBar = querySel('#comic_search_form > div > div'),
updateBtn = createElem('a', {
id: 'bmf_upd_follows_btn',
class: 'input_submit',
title: 'Update Follows',
href: 'javascript:void(0)'
});
updateBtn.textContent = 'Update Follows';
searchBar.appendChild(updateBtn);
updateBtn.addEventListener('click', function() {
batoto.followsList.update(function() {
self.runMatch('all');
if (self.matchesHidden) {
self.matchesVisibility('hide');
}
});
}, false);
}
};
batoto.page_forums = {
init: function() {
if (opts.batoto.comicsPopup_forums) {
batoto.setComicPopup();
}
}
};
//-----------------------------------------------------
// MangaUpdates
//-----------------------------------------------------
var mu = {
init: function() {
ready(function() {
var url = location.href;
if (/\/series\.html\?(?:.+&|&?)id=[^&]+/.test(url)) {
mu.page_comic.init();
}
});
},
comicIdRegex: /mangaupdates\.com\/series\.html\?(?:.+&|&?)id=([^&]+)/i,
getComicId: function(url) {
if (!url) return null;
var id = url.match(mu.comicIdRegex);
return id !== null ? id[1] : id;
}
};
//------------------
// Specific pages
//------------------
mu.page_comic = {
init: function() {
if (opts.interlinks.mu_mal) {
this.addInterlinkBtn('mal', ' Search in MyAnimeList');
}
if (opts.interlinks.batoto_mu) {
this.addInterlinkBtn('batoto', ' Search in Batoto');
}
},
addInterlinkBtn: function(target, title) {
var comicTitle = document.getElementsByClassName('releasestitle')[0],
comicName = comicTitle.textContent;
if (comicName.indexOf('(Novel)') === -1) {
var button = createImgBtn(title, 'bmf_interlink_btn', opts[target].icon);
comicTitle.parentNode.insertBefore(button, comicTitle);
interlinks.getUrl(target, comicName, location.href, function(url) {
button.href = url;
});
button.addEventListener('click', function(event) {
if (!event.ctrlKey || !event.shiftKey) return;
event.preventDefault();
interlinks.saveFromPrompt(location.href, target);
});
}
}
};
//-----------------------------------------------------
// MyAnimeList
//-----------------------------------------------------
var mal = {
init: function() {
ready(function() {
if (/manga/.test(path)) {
mal.page_comic.init();
}
});
},
comicIdRegex: /myanimelist\.net\/manga\/(\d+)/i,
getComicId: function(url) {
if (!url) return null;
var id = url.match(mal.comicIdRegex);
return id !== null ? id[1] : id;
}
};
mal.page_comic = {
init: function() {
if (opts.interlinks.mu_mal) {
this.addInterlinkBtn('mu', 'Search in MangaUpdates');
}
if (opts.interlinks.batoto_mal) {
this.addInterlinkBtn('batoto', 'Search in Batoto');
}
},
addInterlinkBtn: function(target, title) {
var comicTitle = querySel('#contentWrapper h1 span').previousSibling,
comicName = comicTitle.textContent,
button = createImgBtn(title, 'bmf_interlink_btn', opts[target].icon);
comicTitle.parentNode.insertBefore(button, comicTitle);
interlinks.getUrl(target, comicName, location.href, function(url) {
button.href = url;
});
button.addEventListener('click', function(event) {
if (!event.ctrlKey || !event.shiftKey) return;
event.preventDefault();
interlinks.saveFromPrompt(location.href, target);
});
}
};
//-----------------------------------------------------
// Interlinks between sites
//-----------------------------------------------------
var interlinks = {
getUrl: function(targetName, comicName, sourceUrl, callback) {
var target = this.sites[targetName],
targetId, source, sourceName, sourceId, searchRequest, savedUrl, temporalUrl;
if (sourceUrl) {
for (sourceName in this.sites) {
source = this.sites[sourceName];
if (source.urlRegex.test(sourceUrl)) {
sourceId = source.getId(sourceUrl);
if (sourceId) {
savedUrl = this.getSaved(sourceName, sourceId, targetName);
if (savedUrl) {
callback(savedUrl, 'saved');
return;
}
}
break;
}
}
}
if (!savedUrl) {
if (opts.interlinks.searchEngine === 'google') {
searchRequest = googleRequest;
} else if (opts.interlinks.searchEngine === 'duckduckgo') {
searchRequest = duckRequest;
}
temporalUrl = searchRequest(comicName, target.queryUrl, function(res) {
if (res.success && res.finalUrl.indexOf(target.queryUrl) !== -1) {
callback(res.finalUrl, 'finalUrl');
targetId = target.getId(res.finalUrl);
if (opts.interlinks.saveUrls && sourceId && targetId) {
interlinks.save(sourceName, sourceId, targetName, targetId);
}
} else if (res.finalUrl !== 'not supported') {
callback(res.searchUrl, 'search');
}
});
callback(temporalUrl, 'temporal');
}
},
getSaved: function(sourceName, sourceId, targetName) {
var key = 'interlinks_' + sourceName + '->' + targetName,
stored = getStorage(key),
targetId;
if (!stored) {
return false;
}
targetId = stored[sourceId];
if (targetId) {
return this.sites[targetName].comicUrl.replace('$comicId$', targetId);
}
return false;
},
save: function(sourceName, sourceId, targetName, targetId) {
var key = 'interlinks_' + sourceName + '->' + targetName,
stored = getStorage(key);
if (!stored) {
stored = {};
}
if (targetId === false) {
delete stored[sourceId];
} else {
stored[sourceId] = targetId;
}
setStorage(key, stored);
},
saveFromPrompt: function(sourceUrl, targetName) {
var sourceName, source, sourceId, userUrl, target, targetId;
for (sourceName in this.sites) {
source = this.sites[sourceName];
if (source.urlRegex.test(sourceUrl)) {
sourceId = source.getId(sourceUrl);
break;
}
}
if (!sourceId) {
alert('Sorry. Couldn\'t get the ID for the comic here.');
return;
}
userUrl = prompt('Write the URL for ' + targetName.toUpperCase() +
' (leave empty to remove):');
if (userUrl === null) return;
userUrl = userUrl.trim();
if (userUrl === '') {
this.save(sourceName, sourceId, targetName, false);
alert('Removed successfully.');
return;
}
target = this.sites[targetName];
if (!target.urlRegex.test(userUrl)) {
alert('Doesn\'t look like a valid URL for ' + targetName.toUpperCase() + '.');
return;
}
targetId = this.sites[targetName].getId(userUrl);
if (!targetId) {
alert('Sorry. Couldn\'t get the ID for the comic.');
return;
}
this.save(sourceName, sourceId, targetName, targetId);
alert('Saved successfully.');
},
sites: {
batoto: {
urlRegex: /(bato\.to)|(batoto\.net)/i,
getId: batoto.getComicId,
comicUrl: 'http://bato.to/comic/_/comics/-r$comicId$',
queryUrl: 'bato.to/comic/_/'
},
mu: {
urlRegex: /mangaupdates\.com/i,
getId: mu.getComicId,
comicUrl: 'https://www.mangaupdates.com/series.html?id=$comicId$',
queryUrl: 'mangaupdates.com/series.html?'
},
mal: {
urlRegex: /myanimelist\.net/i,
getId: mal.getComicId,
comicUrl: 'http://myanimelist.net/manga/$comicId$',
queryUrl: 'myanimelist.net/manga/'
}
}
};
//-----------------------------------------------------
// CSS
//-----------------------------------------------------
var CSS = {
load: function(site) {
var css = this[site]();
if (!document.head) {
setTimeout(function() {
GM_addStyle(css);
}, 1);
} else {
GM_addStyle(css);
}
},
batoto: function() {
var css = [
// various
'.bmf_following_icon {',
'vertical-align: top;',
'margin-left: 1px; }',
'.general_box.clearfix img {',
'vertical-align: bottom; }',
'#pu_____hover___comicPopup_inner .ipsBox td {',
'padding: 5px; }',
'#pu_____hover___comicPopup_inner .ipsBox td span ~ span {',
'display: none !important; }',
'#pu_____hover___comicPopup_inner .ipsBox tr:last-child {',
'display: none; }',
// search
'.bmf_hide_matches .bmf_match {',
'display: none; }',
'.bmf_mismatch .bmf_following_icon {',
'display: none; }',
'#bmf_upd_follows_btn {',
'position: absolute;',
'top: 10px;',
'left: 110px;',
'font-weight: normal; }',
// old follows
'.bmf_hide_read .bmf_read, ',
'.bmf_hide_reading .bmf_reading, ',
'.bmf_hide_noreads .bmf_noreads {',
'display: none; }',
'.bmf_read sup {',
'display: none; }',
'.bmf_info_row > td {',
'padding: 0px !important;',
'border: none !important; }',
'.bmf_info_row .ipsBox {',
'background-color: transparent; }',
'.bmf_info_button {',
'padding: 0px 8px;',
'margin-right: 3px;',
'background-repeat: no-repeat;',
'background-position: center;',
'background-image: url(' + opts.batoto.infoIcon + '); }',
'#bmf_total_follows {',
'font-size: 13px;',
'padding-left: 8px;',
'color: ' + opts.batoto.totalTextColor + ';',
'display: inline;',
'vertical-align: middle; }',
// comic page
'.bmf_interlink_btn {',
'display: inline-block !important;',
'width: 28px;',
'height: 28px !important;',
'vertical-align: -2px !important;',
'text-align: center;',
'margin: 0px 8px 0px 0px !important;',
'padding: 0px !important; }',
'.bmf_interlink_btn img {',
'padding: 6px;',
'margin: 0px !important;',
'vertical-align: -2px !important; }',
'#bmf_show_recent_btn {',
'width: 100%;',
'margin-top: 5px;',
'position: absolute;',
'display: flex; }',
'#bmf_show_recent_btn a {',
'margin: 0 auto;',
'font-weight: bold;',
'font-size: 12px; }'
].join('');
if (opts.batoto.hideBloodHeader && batoto.getTheme() === 'blood') {
css += [
'#branding { ',
'position: absolute !important; } '
].join('');
}
return css;
},
mu: function() {
var css = [
// comic page
'.bmf_interlink_btn {',
'background: #e4e4e4;',
'background: linear-gradient(to bottom ,#f6f6f6, #d7d7d7);',
'box-shadow: -1px 1px 0px 0px #dfdcdc inset, 1px -1px 0px 0px #bfbfbf inset;',
'border-radius: 4px;',
'display: inline-block;',
'width: 28px;',
'height: 28px;',
'margin-right: 5px;',
'vertical-align: 3px;',
'text-align: center; }',
'.bmf_interlink_btn img {',
'padding: 5px;',
'vertical-align: -9px;',
'margin-top: 1px; }'
].join('');
return css;
},
mal: function() {
var css = [
// comic page
'.bmf_interlink_btn {',
'background: #e4e4e4;',
'background: linear-gradient(to bottom ,#f6f6f6, #d7d7d7);',
'box-shadow: -1px 1px 0px 0px #dfdcdc inset, 1px -1px 0px 0px #bfbfbf inset;',
'border-radius: 4px;',
'display: inline-block;',
'width: 28px;',
'height: 28px;',
'margin-right: 5px;',
'vertical-align: 3px;',
'text-align: center; }',
'.bmf_interlink_btn img {',
'padding: 5px;',
'vertical-align: -9px;',
'margin-top: 1px; }'
].join('');
return css;
}
};
//-----------------------------------------------------
// Utility functions
//-----------------------------------------------------
function googleRequest(query, queryUrl, callback) {
var encodedQuery = query.toLowerCase().replace(/\||-|~|"|\*|:/g, ' ').replace(/\s+/g, ' '),
encodedQuery = encodeURIComponent(encodedQuery),
encodedQueryUrl = encodeURIComponent(queryUrl),
temporalUrl = 'https://www.google.com/webhp?#btnI=I&q=' + encodedQuery + '&sitesearch=' + encodedQueryUrl,
requestUrl = 'https://www.google.com/search?btnI=I&q=' + encodedQuery + '&sitesearch=' + encodedQueryUrl,
searchUrl = 'https://www.google.com/search?q=' + encodedQuery + '&sitesearch=' + encodedQueryUrl;
GM_xmlhttpRequest({
method: 'HEAD',
url: requestUrl,
headers: {
referer: 'https://www.google.com/'
},
onload: function(response) {
var finalUrl = response.finalUrl;
callback({
'success': (finalUrl && finalUrl.indexOf('https://www.google.com/') !== 0),
'query': query,
'queryUrl': queryUrl,
'finalUrl': finalUrl || 'not supported',
'searchUrl': searchUrl
});
}
});
return temporalUrl;
}
function duckRequest(query, queryUrl, callback) {
var encodedQuery = query.toLowerCase().replace(/\\|"|!|-|:/g, ' ').replace(/\s+/g, ' '),
encodedQuery = encodeURIComponent(encodedQuery),
encodedQueryUrl = encodeURIComponent(queryUrl),
requestUrl = 'https://duckduckgo.com/?kp=-1&q=%5C' + encodedQuery + '+site%3A' + encodedQueryUrl,
searchUrl = 'https://duckduckgo.com/?kp=-1&q=' + encodedQuery + '+site%3A' + encodedQueryUrl;
GM_xmlhttpRequest({
method: 'GET',
url: requestUrl,
onload: function(response) {
var finalUrl = response.responseText.match(/&uddg=(.+?)'/);
finalUrl = decodeURIComponent(finalUrl[1]);
callback({
'success': (finalUrl && finalUrl.indexOf('https://duckduckgo.com/') !== 0),
'query': query,
'queryUrl': queryUrl,
'finalUrl': finalUrl || false,
'searchUrl': searchUrl
});
}
});
// temporalUrl = requestUrl
return requestUrl;
}
function getStorage(key) {
var value = localStorage.getItem('BMF_' + key);
if (value !== undefined && value !== null) {
value = JSON.parse(value);
}
return value;
}
function setStorage(key, value) {
localStorage.setItem('BMF_' + key, JSON.stringify(value));
}
function decodeHtmlEntity(encoded) {
var div = document.createElement('div');
div.innerHTML = encoded;
return div.firstChild.nodeValue;
}
function createImgBtn(title, className, src, href) {
var button = createElem('a', {
title: title || '',
class: className || '',
href: href || 'javascript:void(0)'
});
var img = createElem('img', {
src: src,
alt: ''
});
button.appendChild(img);
return button;
}
function querySel(selector, doc) {
doc = doc || document;
return doc.querySelector(selector);
}
function querySelAll(selector, doc, toArray) {
doc = doc || document;
var nodes = doc.querySelectorAll(selector);
return toArray ? [].slice.call(nodes, 0) : nodes;
}
function createElem(tag, attributes, doc) {
doc = doc || document;
var elem = doc.createElement(tag);
for (var attr in attributes) {
elem.setAttribute(attr, attributes[attr]);
}
return elem;
}
function createElemHTML(html, doc) {
doc = doc || document;
var elem = doc.createElement('div');
elem.innerHTML = html;
return elem.firstElementChild;
}
function toCharObject(stringArray) {
var object = {},
firstChar;
for (var i = 0, len = stringArray.length; i < len; i++) {
firstChar = stringArray[i].charAt(0);
object[firstChar] = object[firstChar] || [];
object[firstChar].push(stringArray[i]);
}
return object;
}
function inCharObject(charObject, string) {
var charArray = charObject[string.charAt(0)] || [];
return (charArray.indexOf(string) !== -1);
}
function ready(callback) {
ready.queue = ready.queue || [];
ready.fired = ready.fired || false;
if (ready.fired) {
setTimeout(callback, 0);
return;
} else if (document.readyState === 'complete') {
ready.fired = true;
setTimeout(callback, 0);
return;
}
if (!ready.whenReady) {
ready.whenReady = function() {
if (!ready.fired) {
ready.fired = true;
for (var i = 0; i < ready.queue.length; i++) {
ready.queue[i].call(window);
}
ready.queue = [];
}
};
document.addEventListener('DOMContentLoaded', ready.whenReady, false);
document.onreadystatechange = function() {
if (document.readyState === 'complete') {
ready.whenReady();
}
};
}
ready.queue.push(callback);
}
function getResourceURL(key) {
if (typeof GM_getResourceURL !== 'undefined') {
return GM_getResourceURL(key);
}
return false;
}
//-----------------------------------------------------
// Run Script
//-----------------------------------------------------
var Window = this.unsafeWindow || unsafeWindow || window,
host = location.host,
path = location.pathname,
opts = defaultsOpts;
if (host === 'bato.to') {
CSS.load('batoto');
batoto.init();
} else if (host === 'www.mangaupdates.com') {
CSS.load('mu');
mu.init();
} else if (host === 'myanimelist.net') {
CSS.load('mal');
mal.init();
}