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); } })();