// ==UserScript== // // @name Batoto MyFollows // @version 15.03.05.1 // @description Filter your follows from comic search; info button and sorting in the old follows page; links between Batoto-MU-Mal and other features. // @namespace https://greasyfork.org/users/168 // @include *://bato.to/* // @include *://www.mangaupdates.com/* // @include *://myanimelist.net/manga/* // @grant GM_xmlhttpRequest // @grant GM_getResourceURL // @grant GM_addStyle // @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 // @downloadURL none // ==/UserScript== //----------------------------------------------------- // Default settings //----------------------------------------------------- var defaultsOpts = { interlinks: { searchEngine: 'google', // 'google' || 'duckduckgo' saveUrls: true, //buttons in 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 autoSort: true, sortMode: 'LastUpdate', // 'LastUpdate' || 'LastRead' || 'Title' 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'), dropList = document.getElementById('new_skin_menucontent'); pageTheme = pageTheme.textContent.toLowerCase().replace(/ .*/, ''); if (pageTheme !== storedTheme) { setStorage('batoto_theme', pageTheme); reload(); return; } dropList.addEventListener('click', function(e) { pageTheme = e.target.textContent.toLowerCase().replace(/ .*/, ''); setStorage('batoto_theme', pageTheme); }, true); }); } 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); reload(); } }); 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>/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(); this.sorting.init(); if (opts.batoto.showTotalFollows) { this.showTotalFollows(); } if (opts.batoto.addInfoBtns) { this.info.addBtns(); } 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); }, sorting: { init: function() { if (opts.batoto.autoSort) { this.run(opts.batoto.sortMode); } var btn, btnClass, btns = ['Title', 'Last Read', 'Last Update'], mainTitle = document.getElementsByClassName('maintitle')[0], handler = function(e) { var mode = this.textContent.replace(' ', ''); batoto.page_oldFollows.sorting.run(mode); }; if (batoto.getTheme() === 'blood') { btnClass = 'ipsButton_secondary bmf_sort_btn'; } else { btnClass = 'ipsButton bmf_sort_btn'; } for (var i = 0; i < 3; i++) { btn = createElem('button', { class: btnClass, href: 'javascript:void(0)' }); btn.textContent = btns[i]; btn.addEventListener('click', handler, false); mainTitle.appendChild(btn); } }, arrays: {}, prev: null, reversed: false, run: function(mode) { if (this.prev === mode) { this.reversed = !this.reversed; } else { this.reversed = false; } this.prev = mode; this.arrays[mode] = this.arrays[mode] || this['getBy' + mode](); this.replaceWith(this.arrays[mode], this.reversed); }, getByLastRead: function() { var array = [], dates = querySelAll('td[style="text-align: right;"]'); for (var i = 0, len = dates.length; i < len; i = i + 2) { array.push({ index: getDateIndex(dates[i].textContent), firstNode: dates[i].parentNode }); } this.sortArray(array, true); return array; }, getByLastUpdate: function() { var array = [], dates = querySelAll('td[style="text-align: right;"]'); for (var i = 1, len = dates.length; i < len; i = i + 2) { array.push({ index: getDateIndex(dates[i].textContent), lastNode: dates[i].parentNode }); } this.sortArray(array, true); return array; }, getByTitle: function() { var array = [], titles = document.getElementsByClassName('ipb_table')[0].getElementsByTagName('strong'); for (var i = 0, len = titles.length; i < len; i++) { array.push({ index: titles[i].textContent.toLowerCase(), firstNode: titles[i].parentNode.parentNode.parentNode }); } this.sortArray(array); return array; }, sortArray: function(array, reverse) { if (reverse) { array.sort(function(a, b) { if (a.index < b.index) return 1; else if (a.index > b.index) return -1; return 0; }); } else { array.sort(function(a, b) { if (a.index > b.index) return 1; else if (a.index < b.index) return -1; return 0; }); } }, replaceWith: function(array, reverse) { var table = querySel('.clearfix > table'), tbody = table.firstElementChild, i = reverse ? array.length - 1 : 0, e = reverse ? -1 : array.length, j = reverse ? -1 : 1, classA = 'row0', classB = 'row1', classC, node1, node2, node3; table.removeChild(tbody); if (array[0].firstNode) { for (; i !== e; i = i + j) { node1 = array[i].firstNode; node2 = node1.nextElementSibling; node3 = node2.nextElementSibling; tbody.appendChild(node1); tbody.appendChild(node2); node1.classList.remove(classB); node2.classList.remove(classB); node1.classList.add(classA); node2.classList.add(classA); if (node2.classList.contains('bmf_info_row')) { tbody.appendChild(node3); node3.classList.remove(classB); node3.classList.add(classA); } classC = classA; classA = classB; classB = classC; } } else if (array[0].lastNode) { for (; i !== e; i = i + j) { node3 = array[i].lastNode; node2 = node3.previousElementSibling; node1 = node2.previousElementSibling; if (node2.classList.contains('bmf_info_row')) { tbody.appendChild(node1); node1.classList.remove(classB); node1.classList.add(classA); } tbody.appendChild(node2); tbody.appendChild(node3); node2.classList.remove(classB); node3.classList.remove(classB); node2.classList.add(classA); node3.classList.add(classA); classC = classA; classA = classB; classB = classC; } } table.appendChild(tbody); } }, info: { addBtns: function() { var that = batoto.page_oldFollows, titles = that.titles, table = querySel('.clearfix > table > tbody'), 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); } table.addEventListener('click', function(e) { if (e.target.className === 'bmf_info_button') { that.info.showBox(e.target); } }, false); }, showBox: function(infoBtn) { // modification of the function used by batoto: // bato.to/js/shortcuts_20131231.js //(prototype.js) var anchor = infoBtn.nextElementSibling, comicId = batoto.getComicId(anchor.getAttribute('href')), div = document.getElementById('cId_' + comicId); if (div === null) { div = this.addBoxContainer(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 = ''; } }, addBoxContainer: 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( '

View Settings

' + 'Alter Settings' + '
'); 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 = '

' + '\nFollows by Chapters (new follows)

' + '
' + '
'; 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); }, false); }, 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, '$1'); }, 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 = 'Include MyFollows:' + '' + '' + '' + ''; 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); }, false); } } }; //----------------------------------------------------- // 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); }, false); } }; //----------------------------------------------------- // 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: 4px; }', '#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_sort_btn {', 'float: right;', 'margin : 0 0 0 5px;', 'height: 26px;', 'line-height: 26px !important; }', '.clearfix > h3.maintitle {', 'line-height: 26px;', 'padding-top: 11px;', 'padding-bottom: 11px; }', '.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: 12px;', '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: -6px;', 'text-align: center; }', '.bmf_interlink_btn img {', 'padding: 5px;', 'vertical-align: 0;', '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: -6px;', 'text-align: center; }', '.bmf_interlink_btn img {', 'padding: 5px;', 'vertical-align: 0;', '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 getDateIndex(text) { var match, now, times, year, month, day, hour, minute; match = text.match(/(\d+) (\w{3})\w* (\d+) - (\d+):(\d+) ?(\w+)?/); if (match) { year = match[3]; month = match[2]; if (month === 'Jan') { month = '01'; } else if (month === 'Feb') { month = '02'; } else if (month === 'Mar') { month = '03'; } else if (month === 'Apr') { month = '04'; } else if (month === 'May') { month = '05'; } else if (month === 'Jun') { month = '06'; } else if (month === 'Jul') { month = '07'; } else if (month === 'Aug') { month = '08'; } else if (month === 'Sep') { month = '09'; } else if (month == 'Oct') { month = '10'; } else if (month === 'Nov') { month = '11'; } else if (month === 'Dec') { month = '12'; } day = match[1]; hour = match[4]; if (hour === '12') { hour = '00'; } if (match[6] === 'PM') { hour = hour * 1 + 12; } minute = match[5]; return year + month + day + hour + minute; } match = text.match(/(A|\d+) (week|day|hour|minute)s? ago/); if (match) { now = new Date(); times = match[1] === 'A' ? 1 : match[1] * 1; if (match[2] === 'week') { now.setDate(now.getDate() - times * 7); } else if (match[2] === 'day') { now.setDate(now.getDate() - times); } else if (match[2] === 'hour') { now.setHours(now.getHours() - times); } else if (match[2] === 'minute') { now.setHours(now.getMinutes() - times); } year = zeroPad(now.getFullYear()); month = zeroPad(now.getMonth() + 1); day = zeroPad(now.getDate()); hour = zeroPad(now.getHours()); minute = zeroPad(now.getMinutes()); return year + month + day + hour + minute; } match = text.match(/(Today|Yesterday), (\d+):(\d+) (\w+)/); if (match) { now = new Date(); year = zeroPad(now.getFullYear()); month = zeroPad(now.getMonth() + 1); if (match[1] === 'Yesterday') { now.setDate(now.getDate() - 1); } day = zeroPad(now.getDate()); hour = match[2]; if (hour === '12') { hour = '00'; } if (match[4] === 'PM') { hour = hour * 1 + 12 + ''; } minute = match[3]; return year + month + day + hour + minute; } return '0'; } function zeroPad(number) { return number < 10 ? '0' + number : '' + number; } 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 reload() { Window.stop(); setTimeout(function() { location.reload(); }, 0); } function getResourceURL(key) { try { return GM_getResourceURL(key) || false; } catch (e) { 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(); }