// ==UserScript==
// @name JugglingDB, remove spam posts
// @namespace http://www.cs.toronto.edu/~murray/
// @description Applies dodgy heuristics to hide spam on jugglingdb, and allows user to override settings.
// @author Iain Murray, started with andanother's version, code and help from thisone
// @include http://*jugglingdb.com/news/
// @include http://*jugglingdb.com/news/?o*
// @include http://*jugglingdb.com/news/index*
// @version 2.29
// ==/UserScript==
// Changelog:
// v0.6 2010-07-31
// You can get the previous version in case 0.7 broke things for you:
// http://homepages.inf.ed.ac.uk/imurray2/code/user_scripts/jugglingdb_v06.user.js
// v0.7 2010-07-31
// Reorganized things to allow the following new features:
// - table highlighting stripes are now fixed up when posts are hidden
// - If a post has several replies then don't hide it no matter what
// - Filter using post title and author name
// - Fix broken html when there are unescaped '>' or '<' in post titles
// v0.8 2010-09-20
// Fixed a bug introduced in v0.7 that completely broke the script when
// logged in to IJDb. I'm not normally logged in, but the fact no one else
// noticed suggests that this script isn't used very much!
// v0.82 2010-09-29
// Fixed graying of read threads when logged in (bug reported by The Void)
// v1.00 2011-01-05
// Added many more bad words. Excessive repetition of any character now
// counts as bad, as does not using any letters from [a-zA-Z].
// v2.00 2011-04-22
// Added ability to permanently unhide spam for current browser.
// Added ability to permanently mute threads for current browser.
// These features require HTML 5 "localStorage", which may not work for you.
// Version 1 branch still available from:
// http://homepages.inf.ed.ac.uk/imurray2/code/user_scripts/jugglingdb_v1.user.js
// Wrap up the script in case the browser/extension doesn't (Greasemonkey does)
(function(){
var BAD = /(~~~|----|===|http:\/\/|paypal|solutions?.manual|your.home|nike|www|sex|\$\$\$|whole.?sale|cheap.*sale|pharm|simple hack|xxx|nude|nudit|kiss|ac.?tres|exposing|bride|bridal|erotic|nipple|pussy|sutra|teen|girl|lady|ladies|lesbian|bathing|dating|replica|watches|zenith|rolex|jeans|armani|gucci|chanel|coogi|romance|romantic|vuitton|porn|f.ck|soccer|underwear|warez|hackz|jordan|ghd|mlb|jersey|credit|sun.?glass|senilai|free.*(shipping|download|friend)|click.(here|for|this)|quality|bikini|hot.?(hot|vid|phot)|marriage|wedding|sukanya|boob|web.?cam|\$[ 0-9a]*month|scandal|actres|tamil|[bh]ollywood|(max|cheap).*shoe|footwear|ghj|sdf|fds|jkl|lkj|brand.?name|brand\b|sandal|(bad|hot|vid|photo|site|adult).*(bad|hot|vid|photo|site|adult)|illuminati|\bnsa\b|\bfbi\b|\bcia\b|leaked|\bur\b|\bugg|huolongguo|\bearn|sowmya|join.now|mr.?jak|would.you.stop|voucher|(women|woman|partner|friend|free|mom|look).*(watch|look|seek|find|chat)|(watch|look|seek|find|chat).*(look|women|woman|partner|friend|free|mom)|islam|m[ou]hamm?[ae]d|plz|read.?this|lacoste|fashion|gmail|hotmail|yahoo|karvy|penis|cock|dick|sperm|polo.shirt|^buy|^shop|outlet)/i;
var GOOD = /(hl[bg]|ring|club|ball|uni.?s|unicycl|beanbag|silicones|juggl|\[video\]|\[ot\]|\(ot\)|[jw]jf|ija|[ebns]jc|jtv|convention|trick|\(video\)|workshop|fest|multiplex|site.?swap|^[4-9][0-9][0-9]*$|act($|[^a-z])|bbb|\[[0-9abcdef][0-9abcdef]\])/i;
function getThreadRows() {
var tables = document.getElementsByTagName('table');
ti = tables.length;
while (ti--) {
if (tables[ti].className == "threads") {
return tables[ti].getElementsByTagName('tr');
}
}
}
var ROWS = getThreadRows();
//localStorage.removeItem("mutedThreads");
//localStorage.removeItem("unspamThreads");
MUTED_THREADS = localStorage.getItem("mutedThreads");
UNSPAM_THREADS = localStorage.getItem("unspamThreads");
if (UNSPAM_THREADS == null)
UNSPAM_THREADS = ',';
if (MUTED_THREADS == null)
MUTED_THREADS = ',';
//alert('muted: ' + MUTED_THREADS + '\nunspam: ' + UNSPAM_THREADS);
var LINK_COLORS = new Array(ROWS.length);
var CHECKS = new Array(ROWS.length);
var THREAD_IDS = new Array(ROWS.length);
// MUTE_MASKS contains sum of values for each row:
var MUTE_MASKS = new Array(ROWS.length);
SPAM = 1; // Spam filter thinks is spam
MUTE = 2; // Is in user's mute list
UNSPAM = 4; // User has unmuted this spam
var show_spam = false;
TOGGLER = addToggle();
addMuteColumn();
processRows(); // Spam classification happens inside here
toggleSpam();
function addMuteColumn() {
var muteHead = document.createElement("th");
muteHead.style.width = '4em';
muteHead.style.textAlign = 'center';
muteHead.innerHTML = "Hide";
ROWS[0].appendChild(muteHead);
makeClickRow = function(num){return function (){clickRow(num)}};
for (i=1; i < ROWS.length; i++) {
var check = document.createElement("input");
check.type = 'checkbox';
check.checked = false;
check.style.margin = '0';
check.style.padding = '0';
CHECKS[i] = check;
var muteCell = document.createElement("td");
muteCell.style.textAlign = 'center';
muteCell.appendChild(check);
ROWS[i].appendChild(muteCell);
if (check.addEventListener) // Firefox, Safari, Opera
check.addEventListener("click", makeClickRow(i), false);
else if (check.attachEvent) //Internet Explorer, Opera
check.attachEvent('onclick', makeClickRow(i));
}
}
function clickRow(i) {
if (CHECKS[i].checked) {
// Mute this row (if spam, stop unspam-ing it)
UNSPAM_THREADS = UNSPAM_THREADS.replace(RegExp(',' + THREAD_IDS[i] + ',', 'g'), ',');
MUTE_MASKS[i] &= ~UNSPAM;
if (!(MUTE_MASKS[i] & SPAM)) {
MUTED_THREADS += THREAD_IDS[i] + ',';
MUTE_MASKS[i] += MUTE;
}
} else {
// unmute this row (if spam, unspam it)
MUTED_THREADS = MUTED_THREADS.replace(RegExp(',' + THREAD_IDS[i] + ',', 'g'), ',');
MUTE_MASKS[i] &= ~MUTE;
if (MUTE_MASKS[i] & SPAM) {
UNSPAM_THREADS += THREAD_IDS[i] + ',';
MUTE_MASKS[i] += UNSPAM;
}
}
localStorage.setItem("mutedThreads", MUTED_THREADS);
localStorage.setItem("unspamThreads", UNSPAM_THREADS);
//alert('muted: ' + MUTED_THREADS + '\nunspam: ' + UNSPAM_THREADS);
show_spam = !show_spam;
toggleSpam();
}
function processRows() {
numRows = ROWS.length;
for (i=1; i < numRows; i++) {
cells = ROWS[i].getElementsByTagName('td');
// Sanitize HTML inside titles:
cells[0].childNodes[0].innerHTML = cells[0].childNodes[0].innerHTML.replace(/\/g, '>');
// Get data
THREAD_IDS[i] = cells[0].childNodes[0].href.replace(/.*thread=([0-9]*).*/, '$1');
title = cells[0].childNodes[0].childNodes[0].nodeValue;
author = cells[1].childNodes[0].nodeValue;
num_posts = cells[2].childNodes[0].nodeValue.replace(/.*\/ /, '');
LINK_COLORS[i] = cells[0].childNodes[0].style.color;
// Spam filter posts with fewer than 3 responses (spammers don't
// *currently* reply to their own posts very often)
var bad = false;
if ((num_posts < 4) && title && author) {
// Disallow bad words
bad = title.match(BAD);
bad = bad || author.match(BAD);
// Gratuitously repeated characters:
bad = bad || title.match(/(.)\1\1\1\1/);
// Must use a letter in [a-zA-Z]
bad = bad || (! title.match(/[a-zA-Z]/));
// and all-upper case titles
bad = bad || (title.toUpperCase() == title);
// unless some good words were used
bad = bad && (! title.match(GOOD));
}
MUTE_MASKS[i] = bad;
// User Masking
if (UNSPAM_THREADS.match(',' + THREAD_IDS[i] + ','))
MUTE_MASKS[i] += UNSPAM;
if (MUTED_THREADS.match(',' + THREAD_IDS[i] + ','))
MUTE_MASKS[i] += MUTE;
}
}
function toggleSpam() {
if (show_spam)
TOGGLER.innerHTML = 'Hide Hidden';
else
TOGGLER.innerHTML = 'Show Hidden';
var highlight = true;
numRows = ROWS.length;
for (i=1; i < numRows; i++) {
ROWS[i].className = ROWS[i].className.replace(/ highlight/, '');
if (highlight)
ROWS[i].className += " highlight";
highlight = !highlight;
muted = ((MUTE_MASKS[i] & MUTE) || ((MUTE_MASKS[i] & SPAM) && !(MUTE_MASKS[i] & UNSPAM)));
CHECKS[i].checked = muted;
show = show_spam || !muted;
if (!show) {
ROWS[i].style.display = 'none';
highlight = !highlight;
} else {
ROWS[i].style.display = 'table-row';
if (MUTE_MASKS[i] & SPAM) {
ROWS[i].style.color = 'red';
ROWS[i].getElementsByTagName('td')[0].childNodes[0].style.color = 'red';
} else if (MUTE_MASKS[i] & MUTE) {
ROWS[i].style.color = 'darkgreen';
ROWS[i].getElementsByTagName('td')[0].childNodes[0].style.color = 'darkgreen';
} else {
ROWS[i].removeAttribute('style');
// The following didn't restore link color in Google Chrome:
// ROWS[i].getElementsByTagName('td')[0].childNodes[0].removeAttribute('style');
ROWS[i].getElementsByTagName('td')[0].childNodes[0].style.color = LINK_COLORS[i];
}
}
}
show_spam = !show_spam;
}
function addToggle() {
var c = document.getElementsByTagName('div');
var i = c.length;
var toggler = document.createElement("a");
toggler.href = 'javascript:;';
toggler.id = 'toggler';
toggler.innerHTML = "Toggle Spam";
while (i--) {
if (c[i].className == "functions") {
c[i].innerHTML = c[i].innerHTML + ' | Spam filter update, info | ';
c[i].appendChild(toggler);
break;
}
}
if (toggler.addEventListener) // Firefox, Safari, Opera
toggler.addEventListener("click", toggleSpam, false);
else if (toggler.attachEvent) //Internet Explorer, Opera
toggler.attachEvent('onclick', toggleSpam);
return toggler;
}
})();