Se7en / Sankaku Recommendations loader

// ==UserScript==
// @name           Sankaku Recommendations loader
// @name:ru        Sankaku Recommendations loader
// @description    Loads next page of recommendations to current one without removing the last
// @description:ru Загружает очередную страницу рекомендаций в текущую без удаления последней
// @namespace      https://openuserjs.org/users/Se7en
// @include        *://chan.sankakucomplex.com/*
// @include        *://idol.sankakucomplex.com/*
// @author         Se7en
// @version        1.0.3
// @grant          GM_xmlhttpRequest
// ==/UserScript==

/*
What does it do:
  + loads next page of recommendations to current page without removing the last one
  + loads pack of pages - by default 5 pages are loaded
  + removes adverts
  + removes share buttons

*****************************
Что делает:
  + Загружает очередную страницу рекомендаций в текущую без удаления последней.
  + Загружает сразу несколько страниц - по умолчанию 5 шт.
  + Удаляет рекламу
  + Удаляет кнопку шары

*/
(function(){
	var clog = function(){};
	//clog = console.log;
	var href = window.location.href,
		isPost = href.indexOf('post') != -1;
	removeAdverts();
	removeShare();
	if( !isPost )
		return;
	var recommend = initRecommend(5),
		pageData = initPageData();
	//loadNextPack();
	newCssClasses();
	
	function initRecommend( n )
	{
		var ret = {
			page: 1,// current recommendation page
			pack: n,// number of pages in one pack
			packN: 0,
			pageEnd: -1,
		};
		return ret;
	}
	function initPageData( d )
	{
		var ret = {
			data: {
				recommend: {
					val: null,
					init: function( doc ){
						this.doc = doc || document;
						this.val = this.doc.querySelector('#recommendations');
					},
					fix: function(){
						var recs = this.val.querySelectorAll('#recommendations');
						[].forEach.call( recs, function(item){
							try{
								item.outerHTML = item.innerHTML;
							}catch(e){}
						});
						// remove extra paginators
						var pags = this.val.querySelectorAll('#recommendations-paginator');
						[].forEach.call( pags, function(item, i, arr){
							if( i != (arr.length - 1) ){
								remove(item);
							}
						});
					},
					add: function(s){
						this.val.innerHTML += s;
					}
				},
				paginator: {
					val: null,
					init: function( doc ){
						this.doc = doc || document;
						this.val = last( this.doc.querySelectorAll('#recommendations-paginator') );
					},
					fix: function(){
						var prv = this.get('prev'),
							nxt = this.get('next'),
							pck = this.get('pack');
						prv.innerHTML = '<<';
						nxt.innerHTML = '>>';
						if( !nxt.classList.contains('sce-recommended-next') ){
							prv.classList.add('sce-recommended-prev');
							nxt.classList.add('sce-recommended-next');
							nxt.addEventListener('click', loadNextRecommendedPage, false );
						}
						if( !pck ){
							pck = this.doc.createElement('span');
							pck.setAttribute('class', 'sce-recommended-pack recommended-pack');
							pck = this.val.appendChild( pck );
							pck.innerHTML = '' +
							'(' + (recommend.page+1) + '-' + (recommend.page+recommend.pack+1) + ')';
							pck.addEventListener('click', loadNextRecommendedPack, false );
						}
					},
					get: function( dir ){
						return this.val.querySelector('.recommended-' + dir );
					},
				},
				postID: {
					val: null,
					init: function( doc ){
						this.doc = doc || document;
						this.val = getPostID(this.doc);
						clog("postID: " + this.val);
					}
				},
			},
			get postID(){
				return this.data.postID.val;
			},
			get: function( prop )
			{
				if( this.data[prop] )
					return this.data[prop];
			},
			init: function( prop, doc ){
				if( this.data[prop] )
					this.data[prop].init( doc );
			},
			fix: function( prop ){
				if( this.data[prop] )
					this.data[prop].fix();
			},
			fixAll: function(){
				this.data.recommend.fix();
				this.data.paginator.fix();
			},
			initAll: function( doc ){
				for( var key in this.data )
					this.data[key].init( doc );
			},
		};
		ret.initAll(d);
		ret.fixAll();
		return ret;
	}
	function getPostID( doc )
	{
		doc = doc || document;
		try{
			return doc.querySelector('#hidden_post_id').innerHTML;
		}catch(e){
			return doc.querySelector('meta[content^="Post"]').getAttribute('content').replace('Post ', '');
		}
	}
	function recommendURL( page )
	{
		return '/post/recommend/' + pageData.postID + '?page=' + page;
	}
	function loadNextPack()
	{
		// load next ${recommend.pack} pages of recommendations
		pageData.get('paginator').get('pack').click();
	}
	function loadNextRecommendedPage( event )
	{
		if( recommend.pageEnd > 0 )
			return;
		recommend.packN = 0;
		GM_xmlhttpRequest({
			url: recommendURL( ++recommend.page ),
			method: 'GET',
			onload: addRecommended,
		});
	}
	function loadNextRecommendedPack( event )
	{
		if( recommend.pageEnd > 0 )
			return;
		var recURL = recommendURL('');
		recommend.packN = 1;
		for( var i = 0; i < recommend.pack; ++i )
		{
			GM_xmlhttpRequest({
				url: recURL + (++recommend.page),
				method: 'GET',
				onload: addRecommended,
			});
		}
	}
	function addRecommended( xhr )
	{
		var pageN = this.url.match(/\?page\=(\d+)/)[1],
			respLen = xhr.response.length;
		if( respLen > 0 ){
			pageData.get('recommend').add( xhr.response );
			pageData.fix('recommend');
		}else if( recommend.pageEnd < 0 )
			recommend.pageEnd = pageN;
		else
			recommend.pageEnd = Math.min( recommend.pageEnd, pageN );
		if( recommend.packN === 0 || recommend.packN == recommend.pack )
		{
			pageData.init('paginator');
			pageData.fix('paginator');
			if( recommend.pageEnd > 0 ){
				pageData.get('paginator').get('pack').innerHTML = '(' + recommend.pageEnd + ')';
				pageData.get('paginator').get('next').innerHTML = 'End';
			}
		}else
			++recommend.packN;
	}
	function addCssClass( cssClass )
	{
		var head = document.querySelector('head'),
			style = document.createElement('style');
		style.type = 'text/css';
		if( style.styleSheets )
			style.styleSheets.cssText = cssClass;
		else
			style.appendChild(document.createTextNode(cssClass));
		head.appendChild(style);
	}
	function newCssClasses()
	{
		addCssClass(`
			#recommendations-paginator > span.recommended-prev {
				margin-left: 30%;
			}
			#recommendations-paginator > span.sce-recommended-next,
			#recommendations-paginator > span.sce-recommended-pack {
				color: #ff761c;
				font-weight: bold;
				cursor: pointer;
			}
			#recommendations-paginator > span.recommended-pack {
				font-size: 2.5em;
				float: left;
				margin: 0.1em 0.25em 0.1em 1%;
				padding: 2px 6px;
				border: 1px solid rgb(234, 234, 234);
			}
		`);
	}
		
	function removeAdverts()
	{
		var timerID = setInterval( removeAds, 500 );
		function removeAds(){
			this.count = this.count || 0;
			var scads = document.getElementsByClassName('scad'), i;
			for( i = 0; i < scads.length; ++i )
				remove( scads[i] );
			scads = document.querySelectorAll('[class*="scad"]');
			for( i = 0; i < scads.length; ++i )
				remove( scads[i] );
			var iframes = document.getElementsByTagName('iframe');
			for( i = 0; i < iframes.length; ++i )
				remove( iframes[i] );
			++this.count;
			clog("> removeAds: ", this.count);
			if( this.count > 10 && isPost )
				clearInterval( timerID );
			else if( this.count > 600 )
				clearInterval( timerID );
		}
	}
	function removeShare()
	{
		var share = document.getElementById('share');
		if( share )
			share.innerHTML = '';
	}
	function remove( elm ){
		return elm.parentNode.removeChild( elm );
	}
	function last( arr ){
		return arr[arr.length - 1];
	}
})();