GK Filter

Filter posts & comments on govnokod.ru

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name		GK Filter
// @namespace	GK
// @description	Filter posts & comments on govnokod.ru
// @include	http://govnokod.ru/*
// @include	http://www.govnokod.ru/*
// @version	3.2.0
// @grant	unsafeWindow
// @grant	GM_registerMenuCommand
// @grant	GM_getValue
// @grant	GM_setValue
// ==/UserScript==

(function(){
$ = unsafeWindow.jQuery;
//dirty, DIRTY hack to wait for certain element to appear. -_- 
//But I have no idea how to do it right.
function waitForSelector(selector, context, mustexist, callback) {
	if (($(selector, context).length>0) == mustexist)
		callback();
	else
		setTimeout(function(){waitForSelector(selector, context, mustexist, callback)}, 50);
	}
//short function for adding custom CSS rules. Why use Greasemonkey specific GM_setStyle() just for that?
function addCSS(rule) {
	var styleElement = document.createElement("style");
	styleElement.type = "text/css";
	if (typeof styleElement.styleSheet !== 'undefined')
		styleElement.styleSheet.cssText = rule;
	else
		styleElement.appendChild(document.createTextNode(rule));
	document.getElementsByTagName("head")[0].appendChild(styleElement);
	}

addCSS([
	'.comment-text.transparent * {color: transparent !important;}',
	'.entry-content.transparent pre * {color: transparent !important;}',
	'.description.transparent * {color: transparent !important;}',
	'.hentry.entry-post-hidden .entry-content {display:none !important;}',
	'.hentry.entry-post-hidden .description {display:none !important;}',
	].join('\n'));

addCSS([
	'#GKFSettings {\
		position:fixed; z-index:1000; top:0;\
		height:80%; width:80%; margin:10%;\
		overflow-x: auto; overflow-y: scroll;\
		border:solid 2px brown;\
		background-color:rgb(242, 240, 227);\
		}',
	'#GKFSettings p {margin:0;padding:0;}',
	'#GKFSettings label {font-weight:bold;margin: 0 5px 0 0}',
	'#GKFSettings .GKFSgroupform {margin:20px;}',
	'#GKFSettings #GKFSglobalsform {margin:20px;}',
	'#GKFSettings .GKFSbuttons {text-align:center;}',
	'#GKFSettings input {width:100%;}',
	'#GKFSettings input[type="checkbox"] {width:auto;}',
	'#GKFSettings input.GKFSgrouptitle {width:auto;}',
	'#GKFSettings .GKFSdeletegroup {width:30px;}',
	'#GKFSettings option.GKFSgood {background-color: #CCFFCC;}',
	'#GKFSettings option.GKFSneutral {background-color: #FFFFFF;}',
	'#GKFSettings option.GKFSevil {background-color: #FFCCCC;}',
	].join('\n'));

//============================================================================================================
var Group = function () {
	if (typeof this == 'undefined')
		throw "Use 'new', Luke!";
	Group.prototype.init.call(this);
}
$.extend(Group, {
	ACTION_NONE : 0,
	ACTION_HIDECONTENT : 1,
	ACTION_HIDETHREAD : 2,
	ACTION_WHITESPACE : 3,
	ACTION_SPOILER : 4,
	ACTION_UNSPOILER : 5,
	VOTE_NONE : 0,
	VOTE_UP : 1,
	VOTE_DOWN : -1,
});
Group.prototype = {
	init: function() {
		this.active = true;
		this.title = '';
		this.chapters = [];
		this.users = [];
		this.comment_action = this.ACTION_NONE;
		this.comment_vote = this.VOTE_NONE;
		this.post_action = this.ACTION_NONE;
		this.post_vote = this.VOTE_NONE;
		this.color = '';
		return this;
	},
	serialize: function() {
		return JSON.stringify({
			"active" : this.active,
			"title" : this.title,
			"chapters" : this.chapters,
			"users" : this.users,
			"comment_action": this.comment_action,
			"comment_vote" : this.comment_vote,
			"post_action": this.post_action,
			"post_vote" : this.post_vote,
			"color" : this.color,
		});
	},
	unserialize : function(str) {
		var obj = JSON.parse(str);
		this.active = obj.active;
		this.title = obj.title;
		this.chapters = []
		for (var i=0;i<obj.chapters.length;i++)
			this.chapters.push(obj.chapters[i].toLowerCase());
		this.users = [];
		for (var i=0;i<obj.users.length;i++)
			this.users.push(obj.users[i].toLowerCase());
		this.comment_action = obj.comment_action;
		this.comment_vote = obj.comment_vote;
		this.post_action = obj.post_action;
		this.post_vote = obj.post_vote;
		this.color = obj.color;
		return this;
	},
	toString: function() {
		return this.serialize();
	},
	matchUser: function(user, chapter) {
		return (this.active) && (
			(this.chapters.length == 0) || 
			(chapter.length == 0) || 
			(this.chapters.indexOf(chapter.toLowerCase()) != -1)
			) && (this.users.indexOf(user.toLowerCase()) != -1);
	},
};
Group.unserialize = function(str) {
	var group = new Group()
	group.unserialize(str);
	return group;
};

//============================================================================================================
var Comment = {
	find: function(context) {
		if (typeof context === 'undefined')
			context = document;
		return $(context).find('.hcomment');
	},
	getAuthor: function($comment) {
		return $comment.find('.entry-author:eq(0)').find('a').text().trim();
	},
	getChapter: function($comment) {
		return $comment.closest('.hentry').find('a[rel="chapter"]').text().trim();
	},
	hideContent: function($comment) {
		$comment.find('.entry-comment-wrapper:eq(0)').hide();
	},
	hideThread: function($comment) {
		$comment.hide();
	},
	whitespace: function($comment) {
		$comment.find('.comment-text:eq(0)').addClass('transparent');
	},
	spoiler: function($comment, text='показать всё, что скрыто', hint=false) {
		var $ec = $comment.find('.entry-comment:eq(0)');
		var $lnk;
		if (!$ec.hasClass('entry-comment-hidden')) {
			$lnk = $('<span class="hidden-text"><a class="ajax" href="#">'+text+'</a></span>');
			$ec
				.addClass('entry-comment-hidden')
				.find('.comment-text:eq(0)')
				.before($lnk);
			}
		else {
			$lnk = $ec.find('.hidden-text:eq(0)').find('a.ajax');
			$lnk.text(text);
			}
		if (hint)
			$lnk.attr('title', $comment.find('.comment-text:eq(0)').text());
	},
	unspoiler: function($comment) {
		var $ec = $comment.find('.entry-comment:eq(0)');
		if ($ec.hasClass('entry-comment-hidden'))
			$ec
				.removeClass('entry-comment-hidden')
				.find('.hidden-text:eq(0)')
				.remove();
	},
	voteUp: function($comment, delay=10.0) {
			var id = $comment.find('.entry-comment-wrapper:eq(0)').attr('id');
			var $links = $comment.find('a.comment-vote-on')
				.filter(function(){
					return $(this).parentsUntil('#'+id, '.hcomment').length == 0; //jQuery 1.4 >_<
					});
			if ($links.length == 1) //чтобы не наломать дров в случае чего
				//$links.css('border','solid 1px blue');
				setTimeout(function(){$links.click()}, Math.floor(Math.random()*delay*1000+100) );
	},
	voteDown: function($comment, delay=10.0) {
			var id = $comment.find('.entry-comment-wrapper:eq(0)').attr('id');
			var $links = $comment.find('a.comment-vote-against')
				.filter(function(){
					return $(this).parentsUntil('#'+id, '.hcomment').length == 0; //jQuery 1.4 >_<
					});
			if ($links.length == 1) //чтобы не наломать дров в случае чего
				//$links.css('border','solid 1px blue');
				setTimeout(function(){$links.click()}, Math.floor(Math.random()*delay*1000+100) );
	},
	mark: function($comment, color) {
		$comment.find('.entry-author:eq(0)').find('a').css('color', color);
	},
};
//============================================================================================================
var Post = {
	find: function(context) {
		if (typeof context === 'undefined')
			context = document;
		return $(context).find('.hentry');
	},
	getAuthor: function($post) {
		return $post.find('.author').find('a:not(has(img))').text().trim();
	},
	getChapter: function($post) {
		return $post.find('a[rel="chapter"]').text().trim();
	},
	hideContent: function($post) {
		$post.find('.entry-content:eq(0)').hide();
		$post.find('.description:eq(0)').hide();
	},
	hideThread: function($post) {
		$post.hide();
	},
	whitespace: function($post) {
		$post.find('.entry-content:eq(0)').addClass('transparent');
		$post.find('.description:eq(0)').addClass('transparent');
	},
	spoiler: function($post, text='показать всё, что скрыто', hint=false) {
		if (!$post.hasClass('entry-post-hidden')) {
			$lnk = $('<span class="hidden-text"><a class="ajax" href="#">'+text+'</a></span>');
			if (hint)
				$lnk.attr('title', $post.find('.entry-content:eq(0)').text());
			$lnk.click(function(event){
				$(this).closest('.hentry').removeClass('entry-post-hidden');
				$(this).remove();
				});
			$post
				.addClass('entry-post-hidden')
				.find('.entry-content:eq(0)')
				.before($lnk);
			}
	},
	voteUp: function($post, delay=10.0) {
			var $links = $post.find('a.vote-on:eq(0)');
			if ($links.length == 1) //чтобы не наломать дров в случае чего
				//$links.css('border','solid 1px blue');
				setTimeout(function(){$links.click()}, Math.floor(Math.random()*delay*1000+100) );
	},
	voteDown: function($post, delay=10.0) {
			var $links = $post.find('a.vote-against:eq(0)');
			if ($links.length == 1) //чтобы не наломать дров в случае чего
				//$links.css('border','solid 1px blue');
				setTimeout(function(){$links.click()}, Math.floor(Math.random()*delay*1000+100) );
	},
	mark: function($post, color) {
		$post.find('.author').find('a').css('color', color);
	},
};
//============================================================================================================
function processPosts(groups, $context) {
	Post.find($context).each(function(){
		var $post = $(this);
		var user = Post.getAuthor($post);
		var chapter = Post.getChapter($post);
		for (var i=0;i<groups.length;i++)
			if (groups[i].matchUser(user, chapter)) {
				switch (groups[i].post_action) {
					case Group.ACTION_HIDECONTENT: Post.hideContent($post); break;
					case Group.ACTION_HIDETHREAD: Post.hideThread($post); break;
					case Group.ACTION_WHITESPACE: Post.whitespace($post); break;
					case Group.ACTION_SPOILER: Post.spoiler($post, Options.globals.spoilertext, Options.globals.spoilerhint); break;
					default: ; break;
				};
				switch (groups[i].post_vote) {
					case Group.VOTE_UP: Post.voteUp($post, Options.globals.votedelay); break;
					case Group.VOTE_DOWN: Post.voteDown($post, Options.globals.votedelay); break;
					default: ; break;
				};
				if (!!groups[i].color)
					Post.mark($post, groups[i].color);
			};
	});
}

function processComments(groups, $context) {
	Comment.find($context).each(function(){
		var $comment = $(this);
		var user = Comment.getAuthor($comment);
		var chapter = Comment.getChapter($comment);
		for (var i=0;i<groups.length;i++)
			if (groups[i].matchUser(user, chapter)) {
				switch (groups[i].comment_action) {
					case Group.ACTION_HIDECONTENT: Comment.hideContent($comment); break;
					case Group.ACTION_HIDETHREAD: Comment.hideThread($comment); break;
					case Group.ACTION_WHITESPACE: Comment.whitespace($comment); break;
					case Group.ACTION_SPOILER: Comment.spoiler($comment, Options.globals.spoilertext, Options.globals.spoilerhint); break;
					case Group.ACTION_UNSPOILER: Comment.unspoiler($comment); break;
					default: ; break;
				};
				switch (groups[i].comment_vote) {
					case Group.VOTE_UP: Comment.voteUp($comment, Options.globals.votedelay); break;
					case Group.VOTE_DOWN: Comment.voteDown($comment, Options.globals.votedelay); break;
					default: ; break;
				};
				if (!!groups[i].color)
					Comment.mark($comment, groups[i].color);
				break;
			};
	});
}
//============================================================================================================
function hijackComments(groups) {
	//перехват загрузки комментариев
	var oldLoadComments = unsafeWindow.comments['load'];

	function newLoadComments(aElemTrigger) {
		var $parent = $(aElemTrigger).closest('.entry-comments');
		oldLoadComments.call(this,aElemTrigger);
		waitForSelector('.hcomment', $parent, true, function(){
			processComments(groups, $parent);
			});
		}

	unsafeWindow.comments['load'] = newLoadComments;
	}

//============================================================================================================
var Options = {
	COLORS : {
		"":"[нет]",
		"silver":"silver",
		"gray":"gray",
		"black":"black",
		"maroon":"maroon",
		"red":"red",
		"orange":"orange",
		"yellow":"yellow",
		"olive":"olive",
		"lime":"lime",
		"green":"green",
		"aqua":"aqua",
		"blue":"blue",
		"navy":"navy",
		"teal":"teal",
		"fuchsia":"fuchsia",
		"purple":"purple",
	},
	$wnd : undefined,
	magic : undefined,
	globals : {},
	installOnSaveListener: function() {
		if (typeof Options.magic === 'undefined') {
			window.addEventListener("message", Options.handlerOnSave, false);
			Options.magic = Math.random().toString(36).slice(2);
			}
	},
	handlerOnSave: function(event) {
		var messageJSON;
		try {
			messageJSON = JSON.parse(event.data);
		}
		catch (zError) {
			// Do nothing
		}
		if ( !messageJSON || (typeof messageJSON.GKFILTER === 'undefined') )
			return; //-- Message is not for us.
		else { 
		if (messageJSON.GKFILTER === Options.magic) {
			GM_setValue('groups',JSON.stringify(messageJSON.groups));
			GM_setValue('globals',JSON.stringify(messageJSON.globals));
			}
		}
	},
	load: function() {
		var arr = JSON.parse(GM_getValue('groups','[]'));
		Options.groups = [];
		for (var i=0;i<arr.length;i++)
			Options.groups.push(Group.unserialize(arr[i]));
		var glb = JSON.parse(GM_getValue('globals','{}'));
		Options.globals = {
			spoilertext: (typeof glb.spoilertext === 'undefined') ? 'показать всё, что скрыто' : glb.spoilertext.toString(),
			spoilerhint: (typeof glb.spoilerhint === 'undefined') ? true : Boolean(glb.spoilerhint),
			votedelay: (typeof glb.votedelay === 'undefined') ? 10.0 : parseFloat(glb.votedelay),
			};
	},
	save: function(){
		var grp = [];
		for (var i=0;i<Options.groups.length;i++)
			grp.push(Options.groups[i].serialize());
		var msg = {'GKFILTER':Options.magic,groups:grp,globals:Options.globals};
		window.postMessage(JSON.stringify(msg), "*");
	},
	buildGroupForm: function(group) {
		var $form = $('\
		<fieldset class="GKFSgroupform">\
			<legend>\
				<input type="text" value="" placeholder="Название группы" class="GKFSgrouptitle"/>\
				<button class="GKFSdeletegroup">X</button>\
			</legend>\
			<p><input type="checkbox" value="" class="GKFSgroupactive"/><label>Группа используется</label></p>\
			<p><label>Участники (обязательно):</label><input type="text" value="" placeholder="Введите имена участников, разделенные ;" class="GKFSuserlist"/></p>\
			<p><label>Разделы (по умолчанию во всех):</label><input type="text" value="" placeholder="Введите названия разделов, разделенные ;" class="GKFSchapterlist"/></p>\
			<p><label>Посты:</label><select rows="1" class="GKFSpost GKFSactions">\
				<option value="'+Group.ACTION_NONE+'" class="GKFSneutral">ничего не делать</option>\
				<option value="'+Group.ACTION_HIDECONTENT+'" class="GKFSevil">скрыть содержимое</option>\
				<option value="'+Group.ACTION_HIDETHREAD+'" class="GKFSevil">скрыть целиком</option>\
				<option value="'+Group.ACTION_WHITESPACE+'" class="GKFSevil">закрыть белым</option>\
				<option value="'+Group.ACTION_SPOILER+'" class="GKFSevil">закрыть спойлером</option>\
			</select>, <select rows="1" class="GKFSpost GKFSvotes">\
				<option value="'+Group.VOTE_NONE+'" class="GKFSneutral">не голосовать</option>\
				<option value="'+Group.VOTE_UP+'" class="GKFSgood">плюсануть</option>\
				<option value="'+Group.VOTE_DOWN+'" class="GKFSevil">минусануть</option>\
			</select></p>\
			<p><label>Комменты:</label><select rows="1" class="GKFScomment GKFSactions">\
				<option value="'+Group.ACTION_NONE+'" class="GKFSneutral">ничего не делать</option>\
				<option value="'+Group.ACTION_HIDECONTENT+'" class="GKFSevil">скрыть коммент</option>\
				<option value="'+Group.ACTION_HIDETHREAD+'" class="GKFSevil">скрыть ветку</option>\
				<option value="'+Group.ACTION_WHITESPACE+'" class="GKFSevil">закрыть белым</option>\
				<option value="'+Group.ACTION_SPOILER+'" class="GKFSevil">закрыть спойлером</option>\
				<option value="'+Group.ACTION_UNSPOILER+'" class="GKFSgood">убрать спойлер</option>\
			</select>, <select rows="1" class="GKFScomment GKFSvotes">\
				<option value="'+Group.VOTE_NONE+'" class="GKFSneutral">не голосовать</option>\
				<option value="'+Group.VOTE_UP+'" class="GKFSgood">плюсануть</option>\
				<option value="'+Group.VOTE_DOWN+'" class="GKFSevil">минусануть</option>\
			</select></p>\
			<p><label>Выделение цветом:</label><select rows="1" class="GKFScolors"></select></p>\
		</fieldset>');
		var coloropts = [];
		for (var color in Options.COLORS)
			coloropts.push('<option value="'+color+'" style="color:'+color+'">'+Options.COLORS[color]+'</option>');
		$form.find('.GKFScolors').append(coloropts.join('\n'));
		$form.find('.GKFSgroupactive').attr('checked', group.active ? 'checked' : '');
		$form.find('.GKFSgrouptitle').val(group.title);
		$form.find('.GKFSuserlist').val(group.users.join(';'));
		$form.find('.GKFSchapterlist').val(group.chapters.join(';'));
		$form.find('.GKFSpost.GKFSactions').val(group.post_action);
		$form.find('.GKFSpost.GKFSvotes').val(group.post_vote);
		$form.find('.GKFScomment.GKFSactions').val(group.comment_action);
		$form.find('.GKFScomment.GKFSvotes').val(group.comment_vote);
		$form.find('.GKFScolors').val(group.color);
		return $form;
	},
	parseGroupForm: function($form) {
		var group = new Group();
		group.active = !!$form.find('.GKFSgroupactive')[0].checked;
		group.title = $form.find('.GKFSgrouptitle').val();
		var arr;
		arr = $form.find('.GKFSuserlist').val().split(';');
		for (var i=0;i<arr.length;i++) {
			var s = $.trim(arr[i]).toLowerCase();
			if (s.length > 0)
				group.users.push(s);
		}
		arr = $form.find('.GKFSchapterlist').val().split(';');
		for (var i=0;i<arr.length;i++) {
			var s = $.trim(arr[i]).toLowerCase();
			if (s.length > 0)
				group.chapters.push(s);
		}
		group.post_action = parseInt($form.find('.GKFSpost.GKFSactions').val(),10);
		group.post_vote = parseInt($form.find('.GKFSpost.GKFSvotes').val(),10);
		group.comment_action = parseInt($form.find('.GKFScomment.GKFSactions').val(),10);
		group.comment_vote = parseInt($form.find('.GKFScomment.GKFSvotes').val(),10);
		group.color = $form.find('.GKFScolors').val();
		return group;
	},
	addGroup: function() {
		var $form = Options.buildGroupForm(new Group());
		$('.GKFSdeletegroup', $form).bind('click', function(e){Options.delGroup($(this).closest('.GKFSgroupform'));});
		Options.$wnd.find('p.GKFSgroups').append($form); 
	},
	delGroup: function($group) {
		if (confirm('Удалить группу "'+$group.find('.GKFSgrouptitle').val()+'"?'))
			$group.remove();
	},
	buildGlobalsForm: function() {
		var $form = $('\
		<fieldset id="GKFSglobalsform">\
			<legend>Глобальные настройки</legend>\
			<p><label>Текст спойлера:</label><input id="GKFSspoilertext" type="text" value=""/></p>\
			<p><input id="GKFSspoilerhint" type="checkbox"/><label>Показывать скрытый текст во всплывающей подсказке.</label></p>\
			<p><label>Интервал голосования, сек</label><input id="GKFSvotedelay" type="input" value=""/></p>\
		</fieldset>');
		$form.find('#GKFSspoilertext').val(Options.globals.spoilertext);
		$form.find('#GKFSspoilerhint').attr('checked', Options.globals.spoilerhint ? 'checked' : '');
		$form.find('#GKFSvotedelay').val(Options.globals.votedelay);
		return $form;
	},
	parseGlobalsForm: function($form) {
		var res = {};
		res.spoilertext = $form.find('#GKFSspoilertext').val();
		res.spoilerhint = $form.find('#GKFSspoilerhint')[0].checked;
		res.votedelay = parseFloat($form.find('#GKFSvotedelay').val());
		return res;
	},
	accept: function() {	
		Options.groups = [];
		Options.$wnd.find('.GKFSgroupform').each(function(){
			Options.groups.push(Options.parseGroupForm($(this)));
		});
		Options.globals = Options.parseGlobalsForm(Options.$wnd.find('#GKFSglobalsform'));
		Options.save();
		Options.hide();
	},
	cancel: function() {
		Options.hide();
	},
	show: function() {
		if ($('#GKsettings').length > 0)
			return;
		Options.$wnd = $('<div id="GKFSettings">\
			<p class="GKFSbuttons">\
				<button class="GKFSaccept">Сохранить</button>\
				<button class="GKFScancel">Отменить</button>\
			</p>\
			<hr class="GKFSseparator" />\
			<p class="GKFSgroups"></p>\
			<p class="GKFSbuttons"><button class="GKFScreategroup">Добавить группу</button></p>\
			<hr class="GKFSseparator" />\
			<p class="GKFSbuttons">\
				<button class="GKFSaccept">Сохранить</button>\
				<button class="GKFScancel">Отменить</button>\
			</p>\
			</div>');
		var $place = Options.$wnd.find('p.GKFSgroups');
		for (var i=0;i<Options.groups.length;i++)
			$place.append(Options.buildGroupForm(Options.groups[i]));
		Options.$wnd.find('.GKFSbuttons:eq(1)').after(Options.buildGlobalsForm());
		$('.GKFSaccept', Options.$wnd).bind('click', function(e){Options.accept();});
		$('.GKFScancel', Options.$wnd).bind('click', function(e){Options.cancel();});
		$('.GKFSdeletegroup', Options.$wnd).bind('click', function(e){Options.delGroup($(this).closest('.GKFSgroupform'));});
		$('.GKFScreategroup', Options.$wnd).bind('click', function(e){Options.addGroup();});
		$('body').append(Options.$wnd);
	},
	hide: function() {
		if (typeof Options.$wnd !== 'undefined') {
			Options.$wnd.remove();
			Options.$wnd = undefined;
		};
	},
};
//регистрируем пользовательские команды
GM_registerMenuCommand("GK Filter settings", function(){Options.show();}, 'f');
//============================================================================================================

Options.installOnSaveListener();
Options.load();
if (Options.groups.length > 0)
	//делаем дело
	if (/govnokod\.ru\/\d+/.test(location))
		//мы на странице поста, обрабатываем все комментарии на странице
		processComments(Options.groups, $('body'));
	else {
		//мы на главной или где-то еще
		//исключение: не обрабатываем посты в профиле пользователя, иначе мы можем их так и не увидеть.
		if (!/govnokod\.ru\/user\/\d+\/codes/.test(location))
			processPosts(Options.groups);
		//тем не менее, перехватим загрузку комментариев
		hijackComments(Options.groups);
		}
})();