NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name KissCleaner // @namespace juici.github.io // @description Cleans up KissAnime pages. Tested to work with Firefox and Greasemonkey. // @author Juici, crapier // @version 1.5 // @license https://github.com/Juici/KissCleaner/blob/master/LICENSE // @icon https://juici.github.io/KissCleaner/icon.png // @homepage https://github.com/Juici/KissCleaner // @contactURL https://github.com/Juici/KissCleaner/issues // @supportURL https://github.com/Juici/KissCleaner/issues // @contributionURL https://github.com/Juici/KissCleaner#donate // @downloadURL https://juici.github.io/KissCleaner/kisscleaner.user.js // @updateURL https://juici.github.io/KissCleaner/kisscleaner.meta.js // @include /^https?:\/\/kiss(?:anime\.(?:to|ru)|cartoon\.(?:me|se)|asian\.com).*/ // @grant unsafeWindow // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_getResourceText // @resource settings https://juici.github.io/KissCleaner/settings.html?v=1 // @resource css https://juici.github.io/KissCleaner/style.css?v=4 // @resource resizeVideo https://juici.github.io/KissCleaner/resize-video.css?v=1 // @run-at document-start // @noframes // ==/UserScript== // current page url const url = window.location.href; // regex to check against for determining what type page currently on and what to clean const rHome = /https?:\/\/(kiss(?:anime\.ru|cartoon\.se|asian\.com))\/$/; const rAnimeList = /https?:\/\/(kiss(?:anime\.ru|cartoon\.se|asian\.com))\/(AnimeList|Genre|Status|Search|UpcomingAnime|CartoonList|DramaList|Country)/; const rAnimePage = /https?:\/\/(kiss(?:anime\.ru|cartoon\.se|asian\.com))\/(Anime|Cartoon|Drama)\/[^\/]*$/; const rVideoPage = /https?:\/\/(kiss(?:anime\.ru|cartoon\.se|asian\.com))\/(Anime|Cartoon|Drama)\/[^\/]*\/[^\/]*(?:\?id=\d*)?/; // player type constants const PLAYER = { FLASH: 'flash', HTML5: 'html5' }; // site type const SITE = { ANIME: false, CARTOON: false, ASIAN: false }; const kissSite = /:\/\/kiss([^.]+)\./.exec(window.location.href)[1].toUpperCase(); SITE[kissSite] = true; // settings const settings = { // auto pause on video load autoPause: GM_getValue('autoPause', false), // auto advance to next video on playback end autoAdvance: GM_getValue('autoAdvance', true), // auto scroll to video area autoScroll: GM_getValue('autoScroll', true), // resize the video resizeVideo: GM_getValue('resizeVideo', true), // remove login removeLogin: GM_getValue('removeLogin', true), // remove comments area removeComments: GM_getValue('removeComments', true), // video player player: GM_getValue('player', PLAYER.HTML5), // video quality quality: GM_getValue('quality', '1080'), // video volume volume: GM_getValue('volume', 100) }; const _ = { // document query returning array queryAll: function (...selector) { return Array.from(document.querySelectorAll(selector instanceof Array ? selector.join(',') : selector)); }, // remove element matching css selector (first or index) removeAd: function (selector, index) { const ads = _.queryAll(selector); // make sure index is within bounds index = index || 0; if (index < 0 || index > ads.length - 1) { index = 0; } ads[index].remove(); }, // remove all elements matching css selectors removeAds: function (...selectors) { const ads = _.queryAll(selectors); ads.forEach(elt => elt.remove()); }, // clean up the empty space left by ad removal cleanupAdspace: function () { // get the ads frame const adspace = document.getElementById('adsIfrme1'); if (adspace) { // check and remove the clear before the adspace const clearBefore = adspace.parentElement.previousElementSibling; if (clearBefore && clearBefore.matches('.clear')) { clearBefore.remove(); } // check and remove the clear a bit after the adspace const clearAfter = adspace.parentElement.nextElementSibling && adspace.parentElement.nextElementSibling.nextElementSibling && adspace.parentElement.nextElementSibling.nextElementSibling.nextElementSibling ? adspace.parentElement.nextElementSibling.nextElementSibling.nextElementSibling : null; if (clearAfter && clearAfter.matches('.clear')) { clearAfter.remove(); } // remove the adspace's parent (and thus it) adspace.parentElement.remove(); } }, // remove or hide stubborn ads hideAds: function () { let count = 0; const adremover = setInterval(() => { count++; // remove extra elements after #containerRoot in body const rootAds = _.queryAll('#containerRoot ~ *'); rootAds.forEach(elt => elt.remove()); // hide elements after #container in #containerRoot const containerAds = _.queryAll('#container ~ *:not(#kisscleaner-settings-container)'); containerAds.forEach(elt => _.hideElement(elt, true)); // hide elements in the #rightside that aren't content const rightsideAds = _.queryAll('#rightside > div:not(.rightBox):not(.clear):not(.clear2)'); rightsideAds.forEach(elt => _.hideElement(elt, true)); if (count === 50) { clearInterval(adremover); } }, 100); }, // inject javascript into page injectScript: function (js) { // create script to inject const script = document.createElement('script'); script.type = 'text/javascript'; script.innerHTML = (typeof js === 'function' ? `(${js.toString()})();` : js); // inject the script document.head.appendChild(script); }, // hide an element, soft param doesn't use 'display: none;' instead 'visibility: hidden; height: 0; width: 0;' hideElement: function (element, soft) { if (soft === true) { element.style.setProperty('visibility', 'hidden', 'important'); element.style.setProperty('height', '0', 'important'); element.style.setProperty('width', '0', 'important'); } else { element.style.setProperty('display', 'none', 'important'); } }, // navigate to previous video previousVideo: function () { const btnPrevious = document.getElementById('btnPrevious'); if (btnPrevious) { window.location.href = btnPrevious.parentElement.href; } }, // navigate to next video nextVideo: function () { const btnNext = document.getElementById('btnNext'); if (btnNext) { window.location.href = btnNext.parentElement.href; } }, // check if should advance to next video checkAutoAdvance: function (evt) { settings.autoAdvance && _.nextVideo(); } }; // add custom css GM_addStyle(GM_getResourceText('css')); const clean = function () { // pre init checks if (document.querySelector('.cf-browser-verification')) { // cloudflare browser verification console.log('Waiting for CloudFlare browser verification.'); return; } else if (!document.getElementById('containerRoot')) { // some error with page console.log('Something went wrong loading page. Reloading to fix it...'); setTimeout(() => { window.location.href = window.location.href; }, 500); return; } console.log('Initializing KissCleaner...'); //--------------------------------------------------------------------------------------------------------------- // Clean Home page //--------------------------------------------------------------------------------------------------------------- if (rHome.test(url)) { console.log('Cleaning home page...'); // remove sections from the right side of the page const rightsideSearch = [/remove ads/i, /like me please/i]; const rightside = document.getElementById('rightside'); if (rightside) { for (let i = rightside.children.length - 1; i >= 0; i--) { const child = rightside.children[i]; // if child has children if (child.children.length > 0) { // check search patterns let remove = false; for (const pattern of rightsideSearch) { if (child.children[0].textContent.search(pattern) > 0) { remove = true; break; } } // remove because a pattern matched and remove following .clear2 (if exists) if (remove) { const clear2 = child.nextElementSibling; if (clear2 && clear2.matches('.clear2')) { clear2.remove(); } child.remove(); } } } } // remove register link in nav sub bar const navsub = document.querySelector('#navsubbar > p'); if (navsub) { navsub.children[0].remove(); navsub.childNodes[1].remove(); } // remove ads _.removeAds('#divFloatLeft', '#divFloatRight', '#divAds2', '#divAds', '#adsIfrme1'); // remove or hide stubborn ads _.hideAds(); console.log('Finished cleaning home page.'); } //--------------------------------------------------------------------------------------------------------------- // Clean Anime List Pages //--------------------------------------------------------------------------------------------------------------- if (rAnimeList.test(url)) { console.log('Cleaning anime list page...'); // remove large spaces left by empty adspace _.cleanupAdspace(); // remove ads _.removeAds('#divFloatLeft', '#divFloatRight', '#adsIfrme2'); // remove or hide stubborn ads _.hideAds(); console.log('Finished cleaning anime list page.'); } //--------------------------------------------------------------------------------------------------------------- // Clean Episode List Pages //--------------------------------------------------------------------------------------------------------------- if (rAnimePage.test(url)) { console.log('Cleaning episode list page...'); // remove large spaces left by empty adspace _.cleanupAdspace(); // remove ads // remove bookmarks _.removeAds('#divFloatLeft', '#divFloatRight', '#divAds', '#spanBookmark'); // remove fluff from episode list pages const eplist = document.querySelector('div.barContent.episodeList > div:not(.arrow-general)'); if (eplist) { let countdown = document.querySelector('#nextEpisodeCountDown'); if (countdown) { countdown = countdown.parentElement.parentElement; // remove clear before listing const clear = countdown.nextElementSibling; if (clear && clear.matches('.clear')) { clear.remove(); } } // loop remove element if not listing or element containing counting let child; while (eplist.children.length > 0 && !((child = eplist.children[0]).matches('.listing') || (typeof countdown !== 'undefined' && countdown !== null && child === countdown))) { child.remove(); } } // remove comments (different child location for kissasian) settings.removeComments && _.removeAd('div.bigBarContainer', SITE.ASIAN ? 3 : 2); // remove or hide stubborn ads _.hideAds(); console.log('Finished cleaning episode list page.'); } //--------------------------------------------------------------------------------------------------------------- // Clean Video Page //--------------------------------------------------------------------------------------------------------------- if (rVideoPage.test(url)) { console.log('Cleaning video page...'); // override functions so they wont be do anything when called by the pages code _.injectScript(` isBlockAds2 = false; DoDetect2 = function () {}; CheckAdImage = function () {}; xaZlE = function () {}; `); // doesn't appear to be randomly generated function name (leaving this here for now) // get around new anti-adblock on kisscartoon.se //const aab = document.querySelector('#container ~ iframe[src="/ucs.aspx"] ~ script'); //if (aab) { // const aabFn = aab.textContent.replace(/.*function (.*?)\(\).*/, '$1').trim(); // _.injectScript(` // ${aabFn} = function () {}; // `); //} console.log('Overridden anti-adblock detects.'); // remove ad spaces // remove lights off feature (pointless with ads removed) _.removeAds('#divFloatLeft', '#divFloatRight', '#adsIfrme6', '#adsIfrme7', '#adsIfrme8', '#adsIfrme10', '#adsIfrme11', '#adCheck1', '#adCheck2', '#adCheck3', '#divDownload', '#divFileName', '#switch', '#divTextQua', '#videoAd'); _.queryAll(`iframe[src*="${window.location.hostname}/Ads"]:not(#videoAd)`).forEach(e => e.parentElement.remove()); // hide ads _.hideAds(); // remove empty spaces from video pages // remove clears const vid = document.getElementById('centerDivVideo'); const vidParent = vid.parentElement; for (let i = 0; i < vidParent.children.length; i++) { if (vidParent.children[i].matches('.clear, .clear2')) { vidParent.removeChild(vidParent.children[i--]); } } // remove comments area if (settings.removeComments) { // get the comment section on the video pages let comments = document.getElementById('btnShowComments'); comments = (comments ? comments.parentElement : document.getElementById('divComments')); // remove comments and elements just prior if (comments) { let temp; while ((temp = comments.previousElementSibling) && !temp.matches('iframe, script')) { temp.remove(); } while ((temp = comments.nextElementSibling) && !temp.matches('iframe, script')) { temp.remove(); } comments.remove(); } } if (settings.resizeVideo) { GM_addStyle(GM_getResourceText('resizeVideo')); } // hide on page video controls const selectPlayer = document.getElementById('selectPlayer'); if (selectPlayer) { _.hideElement(selectPlayer.parentElement.parentElement.parentElement); } // remove info above video const profileLink = document.querySelector('#adsIfrme a[href="/Profile"]'); if (profileLink) { profileLink.parentElement.parentElement.remove(); } // set player type let useFlash = (settings.player === PLAYER.FLASH); let youtubeFlash = false; // set player cookie and reload with correct player if (useFlash && document.cookie.indexOf('usingFlashV1') < 0) { document.cookie = 'usingFlashV1=true;path=/'; document.cookie = 'usingHTML5V1=; expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/'; window.location.href = window.location.href; } else if (document.cookie.indexOf('usingHTML5V1') < 0) { document.cookie = 'usingFlashV1=; expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/'; document.cookie = 'usingHTML5V1=true;path=/'; window.location.href = window.location.href; } const _jw = (typeof unsafeWindow.jwplayer === 'undefined' || unsafeWindow.jwplayer === null), _mp = (typeof unsafeWindow.myPlayer === 'undefined' || unsafeWindow.myPlayer === null); if ((useFlash && _jw) || (!useFlash && _mp)) { useFlash = !useFlash; youtubeFlash = _jw && _mp; } // start per player settings // flash player if (useFlash) { console.log('Using Flash player.'); if (youtubeFlash) { console.log('Using YouTube player.'); // functions to inject on page for flash video control // fires when youtube player is ready const ytHook = (playerId) => { console.log('Youtube player hooked.'); const video = window.embedVideo; settings.autoPause && video.pauseVideo(); video.addEventListener('onStateChange', _.checkAutoAdvance); // translate option into youtube quality strings values const ytQuality = (function (quality) { switch (quality) { case '360': return 'medium'; case '480': return 'large'; case '720': return 'hd720'; default: return 'hd1080'; } })(settings.quality); // set quality video.setPlaybackQuality(ytQuality); // set volume video.setVolume(settings.volume); // focus on the video (so pressing f will fullscreen) setTimeout(() => { video.focus(); }, 0); // force position to be absolute (compatibility with 'Turn Off the Lights') setTimeout(() => { video.style.position === 'relative' && video.style.removeProperty('position'); }, 100); }; // inject function into page unsafeWindow.onYouTubePlayerReady = exportFunction(ytHook, unsafeWindow); } else { console.log('Using jwplayer.'); // functions to inject on page for flash video control // fires when youtube player is ready const jwHook = () => { // wait till video is loaded into player window.jwplayer().onReady(() => { console.log('jwplayer hooked.'); // change the quality to desired flash option const qualityLevels = window.jwplayer().getQualityLevels(); const desiredQuality = parseInt(settings.quality, 10); let qualitySet = false; // try to find exact quality level for (let i = 0; i < qualityLevels.length; i++) { if (desiredQuality === parseInt(qualityLevels[i].label, 10)) { window.jwplayer().setCurrentQuality(i); qualitySet = true; break; } } // try to find best level alternate if (!qualitySet) { // check if desired level is lower than all available if (desiredQuality < parseInt(qualityLevels[0].label, 10)) { window.jwplayer().setCurrentQuality(0); } // check if desired level is higher than all available else if (desiredQuality > parseInt(qualityLevels[qualityLevels.length - 1].label, 10)) { window.jwplayer().setCurrentQuality(qualityLevels.length - 1); } // else find level that is next smallest else { for (let i = 0; i < qualityLevels.length; i++) { if (desiredQuality < parseInt(qualityLevels[i].label, 10)) { window.jwplayer().setCurrentQuality(i - 1); break; } } } } // pause the video if option is enabled settings.autoPause && window.jwplayer().pause(); // setup callback for end of video checks window.jwplayer().onComplete(_.checkAutoAdvance); }); }; // inject function into page unsafeWindow.jwHook = exportFunction(jwHook, unsafeWindow); // call injected function, hopefully after jwplayer has been setup setTimeout(() => { unsafeWindow.jwHook(); }, 500); } } // html5 player else { console.log('Using HTML5 player.'); // move quality select below player const selectQuality = document.getElementById('selectQuality') || document.getElementById('slcQualix'); const videoArea = document.getElementById('centerDivVideo'); if (selectQuality && videoArea) { const parent = selectQuality.parentElement; videoArea.parentElement.appendChild(document.createElement('div'), videoArea.nextSibling); videoArea.parentElement.appendChild(selectQuality, videoArea.nextSibling); parent.remove(); } // functions to inject on page for html5 video control const html5Hook = () => { console.log('HTML5 player hooked.'); // change the quality to desired flash option const qualityLevels = document.getElementById('selectQuality') || document.getElementById('slcQualix'); const desiredQuality = parseInt(settings.quality, 10); let qualitySet = false; const setQuality = (index) => { qualityLevels.selectedIndex = index; qualityLevels.dispatchEvent(new Event('change')); _.removeAds('.clsTempMSg'); }; // try to find exact quality level for (let i = 0; i < qualityLevels.length; i++) { if (desiredQuality === parseInt(qualityLevels.options[i].innerHTML, 10)) { setQuality(i); qualitySet = true; break; } } // try to find best level alternate if (!qualitySet) { // check if desired level is higher than all available if (desiredQuality < parseInt(qualityLevels.options[qualityLevels.length - 1].innerHTML, 10)) { setQuality(qualityLevels.length - 1); } // check if desired level is lower than all available else if (desiredQuality > parseInt(qualityLevels.options[0].innerHTML, 10)) { setQuality(0); } // else find level that is next smallest else { for (let i = 0; i < qualityLevels.length; i++) { if (desiredQuality > parseInt(qualityLevels.options[i].innerHTML, 10)) { setQuality(i); } } } } const video = document.getElementById('my_video_1_html5_api'); // auto pause if (settings.autoPause) { video.pause(); video.parentElement.setAttribute('autoplay', 'false'); video.autoplay = false; } video.volume = settings.volume / 100; // volume video.addEventListener('ended', _.checkAutoAdvance); // auto advance }; // inject function into page unsafeWindow.html5Hook = exportFunction(html5Hook, unsafeWindow); // call the injected script unsafeWindow.html5Hook(); // remove any ghost vjs-tip (html5 player) setInterval(() => { _.queryAll('#vjs-tip').slice(1).forEach(elt => elt.remove()); }, 500); } // end per player settings // scroll to the container settings.autoScroll && document.getElementById('container') && setTimeout(() => { document.getElementById('container').scrollIntoView(true); }, 0); // add listeners for keydown const windowListener = function (evt) { // prev / next video navigate if (evt.code === 'NumpadMultiply' || evt.code === 'NumpadSubtract') { (evt.code === 'NumpadMultiply') ? _.previousVideo() : _.nextVideo(); evt.preventDefault(); } }; const videoListener = function (evt) { if (!useFlash) { const video = unsafeWindow.document.getElementById('my_video_1_html5_api'); const videoFocused = (unsafeWindow.document.activeElement === video); // speed controls (html5) if (evt.code === 'Minus' || evt.code === 'Equal') { video.playbackRate += ((evt.code === 'Minus' && video.playbackRate > 0.25)? -0.25 : (evt.code === 'Equal' && video.playbackRate < 5) ? 0.25 : 0); evt.preventDefault(); } // large seek (html5) const largeSeek = 20; // TODO add settings option if (evt.ctrlKey && (evt.code === 'ArrowLeft' || evt.code === 'ArrowRight')) { video.currentTime += ((evt.code === 'ArrowLeft') ? -largeSeek : largeSeek); evt.preventDefault(); } } }; const my_video_1 = document.getElementById('my_video_1'); if (my_video_1) { window.addEventListener('keydown', videoListener); } window.addEventListener('keydown', windowListener); console.log('Finished cleaning video page.'); } //--------------------------------------------------------------------------------------------------------------- // Clean All pages //--------------------------------------------------------------------------------------------------------------- console.log('Starting general page cleaning...'); // remove share stuff next to search bar // get the search element near the top of the page const result_box = document.getElementById('result_box'); if (result_box) { const ad = result_box.nextElementSibling; if (ad && (ad.children.length === 0 || !ad.children[0].matches('a[href*="AdvanceSearch"]'))) { ad.remove(); } } // remove login at the top of the page settings.removeLogin && _.removeAd('#topHolderBox'); // remove pointless tabs // remove footer // remove ad hides _.removeAds('#liMobile', '#liReportError', '#liRequest', '#liCommunity', '#liFAQ', '#liReadManga', '#footer', 'div.divCloseBut'); // keys listener for script mnu otions // home key let settingsMenu; let isSettingsMenuOpen = false; const openSettingsMenu = () => { if (!isSettingsMenuOpen) { isSettingsMenuOpen = true; // create settings settingsMenu if it doesn't exist if (typeof settingsMenu === 'undefined' || settingsMenu === null) { const preview = document.implementation.createHTMLDocument('preview'); preview.documentElement.innerHTML = GM_getResourceText('settings'); settingsMenu = preview.getElementById('kisscleaner-settings-container'); } // set settingsMenu option values from current settings const nodes = Array.from(settingsMenu.querySelectorAll('[name]')); nodes.forEach((node) => { if (settings.hasOwnProperty(node.name)) { if (node.type === 'checkbox') { node.checked = settings[node.name]; } else { node.value = settings[node.name]; } } }); // add settings settingsMenu to page document.getElementById('containerRoot').appendChild(settingsMenu); // add save button listener document.getElementById('kisscleaner-settings-save').addEventListener('click', saveSettingsMenu); } else { saveSettingsMenu(); } }; const saveSettingsMenu = () => { // save values const nodes = Array.from(settingsMenu.querySelectorAll('[name]')); let changes = false; nodes.forEach((node) => { if (settings.hasOwnProperty(node.name)) { const value = ((node.type === 'checkbox') ? node.checked : node.value); if (settings[node.name] !== value) { changes = true; } GM_setValue(node.name, (settings[node.name] = value)); } }); // update player cookie if (settings.player === PLAYER.FLASH) { document.cookie = 'usingFlashV1=true;path=/'; document.cookie = 'usingHTML5V1=; expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/'; } else { document.cookie = 'usingFlashV1=; expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/'; document.cookie = 'usingHTML5V1=true;path=/'; } // reload page to make changes if (changes) { window.location.href = window.location.href; } settingsMenu.remove(); isSettingsMenuOpen = false; }; // create global key listener const globalKeyListener = (evt) => { if (evt.code === 'Home') { // prevent default action of scrolling to top of page evt.preventDefault(); openSettingsMenu(); } }; // inject functions into page unsafeWindow.openSettingsMenu = exportFunction(openSettingsMenu, unsafeWindow); unsafeWindow.saveSettingsMenu = exportFunction(saveSettingsMenu, unsafeWindow); // add the listener for keypresses window.addEventListener('keydown', globalKeyListener); // also add as GM menu command GM_registerMenuCommand('KissCleaner Settings', unsafeWindow.openSettingsMenu); // search box navigation const searchResults = document.getElementById('searchResults'); // search results const searchBox = document.getElementById('keyword'); // search form text box // TODO arrow scroll through search results console.log('Finished general page cleaning.'); console.log('Finished initialization.'); }; // work around different greasemonkey and tampermonkey load times window.addEventListener('DOMContentLoaded', clean);