// ==UserScript==
// @name Tumblr Savior
// @namespace bjornstar
// @description Saves you from ever having to see another post or notes about certain things ever again (heavily modified by Vindicar).
// @version 2.6.0
// @grant unsafeWindow
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @include http://www.tumblr.com/*
// @include https://www.tumblr.com/*
// @exclude http://www.tumblr.com/inbox
// @exclude http://www.tumblr.com/help
// @exclude https://www.tumblr.com/inbox
// @exclude https://www.tumblr.com/help
// ==/UserScript==
(function(){
if ((typeof unsafeWindow.Tumblr == 'undefined') || (typeof unsafeWindow.jQuery == 'undefined'))
return;
function RunInPage(func) {
var s = document.createElement("script");
s.textContent = "(" + func + ")();";
document.body.appendChild(s);
setTimeout(function(){document.body.removeChild(s)}, 0);
}
//If we have no access to Greasemonkey methods, we will need dummy replacements
if (typeof GM_getValue == 'undefined')
GM_getValue = function (target, deflt) { return deflt; };
if (typeof GM_setValue == 'undefined')
GM_setValue = function (target, value) { alert('Can not save value of "'+target+'" - GM_setValue not found.'); };
if (typeof GM_registerMenuCommand == 'undefined')
GM_registerMenuCommand = function () {};
// =======================DEFAULT VALUES=======================
//you can edit these if you are using the script without Greasemonkey
//black list
var blk = GM_getValue('blacklist', /*default goes here >>>*/'');
//gray list
var gry = GM_getValue('graylist', /*default goes here >>>*/'');
//white list
var wht = GM_getValue('whitelist', /*default goes here >>>*/'');
//process notes
var nts = GM_getValue('notes', /*default goes here >>>*/false);
//process HTML code, not text
var html = GM_getValue('inhtml', /*default goes here >>>*/true);
//hide sponsored posts
var spns = GM_getValue('nosponsored', /*default goes here >>>*/false);
//hide posts from recommended blogs
var rcmd = GM_getValue('norecommended', /*default goes here >>>*/false);
// ===================END OF DEFAULT VALUES===================
function parseList(list) {
var lst = list.split(';');
var res = [];
var term;
for (var i=lst.length-1;i>=0;i--) {
term = lst[i];
if (term.trim().length>0)
res.push(term.toLowerCase());
}
res.sort();
return res;
}
function menuList(target, caption, deflt, description) {
if ((typeof description === 'undefined') || (description.length == 0))
description = caption;
GM_registerMenuCommand(caption, function() {
var list = GM_getValue(target, deflt);
list = prompt(description, list);
if (list != null) {
var res = parseList(list);
GM_setValue(target, res.join(';'));
location.reload();
}
});
}
function menuCheckbox(target, caption, deflt, mark) {
if (typeof mark === 'undefined')
mark = ['[ ] ','[X] '];
var value = !!GM_getValue(target,deflt);
GM_registerMenuCommand(mark[value?1:0]+caption, function(){
GM_setValue(target, !GM_getValue(target,deflt));
location.reload();
});
}
menuList('blacklist', 'Edit blacklist', blk, 'Enter blacklisted words(delimiter is ";"):');
menuList('whitelist', 'Edit whitelist', wht, 'Enter whitelisted words(delimiter is ";"):');
menuList('graylist', 'Edit graylist', gry, 'Enter graylisted words(delimiter is ";"):' );
menuCheckbox('notes', 'Apply blacklist to notifications¬es', nts);
menuCheckbox('inhtml', 'Check HTML code instead of text', html);
menuCheckbox('nosponsored', 'Hide sponsored posts', spns);
menuCheckbox('norecommended', 'Hide recommended posts', rcmd);
var rules = {
UNAFFECTED : -1,
WHITELISTED : 0,
GRAYLISTED : 1,
BLACKLISTED : 2,
BLOCKEDOTHER : 3,
notes : nts,
inhtml : html,
nosponsored : spns,
norecommended : rcmd,
blackList : parseList(blk),
grayList : parseList(gry),
whiteList : parseList(wht),
};
unsafeWindow.tumblrsaviour_rules = cloneInto(rules, unsafeWindow);
RunInPage(function(){
var $ = jQuery;
function check(rules, $item) {
if (rules.inhtml) {
var theStr = '';
$item.each(function () {
theStr += $(this).html().toLowerCase();
});
}
else {
var theStr = $item.text().toLowerCase();
}
for (var i=0;i<rules.whiteList.length;i++) {
if(theStr.indexOf(rules.whiteList[i])>=0) {
//console.log('White:'+this.whiteList[i]);
return {status:rules.WHITELISTED, array:[rules.whiteList[i]]};
}
}
for(var i=0;i<rules.blackList.length;i++) {
if(theStr.indexOf(rules.blackList[i])>=0) {
//console.log('Black:'+this.blackList[i]);
return {status:rules.BLACKLISTED, array:[rules.blackList[i]]};
}
}
var graylisted = new Array();
for(var i=0;i<rules.grayList.length;i++) {
if(theStr.indexOf(rules.grayList[i])>=0) {
//console.log('Gray:'+this.grayList[i]);
graylisted.push(rules.grayList[i]);
}
}
if (graylisted.length>0)
return {status:rules.GRAYLISTED, array:graylisted};
else
return {status:rules.UNAFFECTED, array:[]};
}
function spoilerPost($el, reason) {
var id = $el.attr('id').substring(5);
//console.log('covering post',id,'because of',reason);
//content wrapper
var $div_filtered = $('<div style="display:none;"></div>');
//post content
var $post_content = $el.find('.post_content_inner');
// spoiler message
var $div_notice = $('<div class="saviour_placeholder">You have been saved from this post because of: '+reason+'. <i onclick="window.jQuery(this).closest(\'.saviour_placeholder\').hide().next().show();return false;">Click here</i> if you cannot resist the temptation.</div>');
$div_notice.insertBefore($post_content);
$div_filtered.insertBefore($post_content);
$div_filtered.append($post_content);
}
function hidePost($el, reason) {
//console.log('hiding post',$el.attr('id').substring(5),'because of',reason);
//look for the next post
var $next = $el.next('li');
var $prev = $el.prev('li');
if (!$el.children('.post').hasClass('same_user_as_last') && $next.children('.post').hasClass('same_user_as_last')) //if current post is the first one in the chain
// if there is next post and it's of the same author
$next.children('.post').removeClass('same_user_as_last'); //we mark next post as being first in the chain
if (($next.hasClass('post_container') && $prev.hasClass('post_container')) &&
($next.find('.post_avatar').data('blog-url')==$prev.find('.post_avatar').data('blog-url')))
$next.children('.post').addClass('same_user_as_last');
//workaround: removing the element causes conflicts with ImgLikeOpera extension.
$el.hide();
//removing class so tumblr keyboard navigation won't recognize it as post.
$el.removeClass('.post');
}
function processPosts() {
var posts = $('#posts').find('.post.not_mine:not([data-saviour-status])');
posts.each(function(){
var $el = $(this);
if ( (tumblrsaviour_rules.nosponsored && ($el.hasClass('sponsored_post') > 0)) ||
(tumblrsaviour_rules.norecommended && ($el.hasClass('is_recommended') > 0)) ) {
hidePost($el);
$el.attr('data-saviour-status', tumblrsaviour_rules.BLOCKEDOTHER);
}
else {
var $check = $([])
.add($el.find(".post_header"))
.add($el.find(".post_content"))
.add($el.find(".post_tags"));
if (tumblrsaviour_rules.inhtml) {
var $clone = $check.clone();
$clone.find('*[data-tumblelog-popover]').removeAttr('data-tumblelog-popover');
$check = $clone;
}
var savedfrom = check(tumblrsaviour_rules, $check);
$el.attr('data-saviour-status', savedfrom.status);
switch (savedfrom.status) {
case tumblrsaviour_rules.UNAFFECTED: break;
case tumblrsaviour_rules.WHITELISTED: break;
case tumblrsaviour_rules.GRAYLISTED: spoilerPost($el, savedfrom.array.join(';')); break;
case tumblrsaviour_rules.BLACKLISTED: hidePost($el, savedfrom.array.join(';')); break;
}
}
});
//Asking Tumblr keyboard control script to regenerate it's positions.
if (typeof Tumblr.KeyCommands != 'undefined')
Tumblr.KeyCommands.update_post_positions();
}
function processNotifications() {
$('#posts').find('li.notification:not([data-saviour-status])').each(function(){
var $el = $(this);
var savedfrom = check(tumblrsaviour_rules, $el);
$el.attr('data-saviour-status',savedfrom.status);
if (savedfrom.status==tumblrsaviour_rules.BLACKLISTED) {
var $prev = $el.prev('li');
var $next = $el.next('li');
if ($el.hasClass('first_notification') && $next.hasClass('notification')) {
if ($next.hasClass('last_notification'))
$next.addClass('single_notification').removeClass('last_notification');
else
$next.addClass('first_notification');
}
else if ($el.hasClass('last_notification') && $prev.hasClass('notification')) {
if ($prev.hasClass('first_notification'))
$prev.addClass('single_notification').removeClass('first_notification');
else
$prev.addClass('last_notification');
}
$el.hide();
}
});
}
function processNotes(context) {
var notes = $('li.note', context);
notes.each(function(){
var $el = $(this);
var savedfrom = tumblrsaviour_rules.check($el);
if (savedfrom.status==tumblrsaviour_rules.BLACKLISTED) {
$el.hide();
}
});
}
//console.log ('White list: "'+tumblrsaviour_rules.whiteList.join('";"')+'"');
//console.log ('Black list: "'+tumblrsaviour_rules.blackList.join('";"')+'"');
//console.log ('Gray list: "'+tumblrsaviour_rules.grayList.join('";"')+'"');
// if we're going to process notifications, he have to do it first, because post chains are dependent on them
if (tumblrsaviour_rules.notes) {
//filter notifications that are rendered already
processNotifications();
}
//process posts that are loaded already
processPosts();
//and ensure we are notified whenever new portion of posts is loaded
AfterAutoPaginationQueue.push(processPosts);
//also posts are loaded whenever new post is made. we should know about it.
(function () {
var render = Tumblr.PostForms.RenderPost.prototype;
var _render_posts = render.render_posts; //this method is called when new post is ready and upcoming posts should be added to the dashboard
render.render_posts = function (URL, insertPos, htmlAndDomHandler, domHandler) {
return _render_posts.call(this,
URL,
insertPos,
function(){ //this function is called after the posts are added
if (tumblrsaviour_rules.notes)
processNotifications();
processPosts();
if (typeof htmlAndDomHandler == 'function')
return htmlAndDomHandler.apply(this,arguments);
},
domHandler);
};
})();
if (tumblrsaviour_rules.notes) { //we should process notes as well
//installing filter for the notes
var notes = Tumblr.Notes.prototype;
// we have to replace the function called whenever notes are to be loaded with our own
var _load_notes = notes.load_notes;
notes.load_notes = function(post,options,fn){
//the idea is to allow Tumblr engine to load notes...
_load_notes.call(this,post,options,(function(data){
//...and render those notes...
var res = fn(data);
//...but also to filter them immediately ourselves
processNotes(post);
return res;
}) );
};
}
});
})();