// ==UserScript==
// @name           Bad Word Alert!
// @namespace      tag:nathantuggy@sti.net,2008-07-07:BadWordAlert
// @description    Removes user-defined bad words, replacing with user-specified symbols (HTML).
// @include        *
// ==/UserScript==

// Copyright 2008-09-16 Nathan Tuggy <mailto:nathan@tuggycomputer.com>.
//This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

var scWordlistPrompt = "Enter the word to block:";
var scReplacePrompt = "What do you want to replace detected bad words with?\n" + 
        "(HTML is fully supported, including entities; '&nbsp;' will simply delete the word.\n" + 
        "Takes effect after page reload.)";

// Make sure word list is available
if (null == GM_getValue("wordlist", null)) {
    GM_setValue("wordlist", "\\bVeryLongTestWordThatWillNeverShowUpAtAllInAnyPage\\b|\\bgosh|dang(?:ed|s|ing)?\\b|darn(?:ed|s|ing)?\\b");
}
var sWordlist = GM_getValue("wordlist");

// Grab replacement string
if (null == GM_getValue("usReplace", null)) {
    GM_setValue("replace", "&mdash;");
}
var usReplace = GM_getValue("replace");

var re = new RegExp(sWordlist, "gim");
var sReplace = USReplacementToS(usReplace);

function USReplacementToS(us) {
    return us;          // TODO: Validate replacement string somehow, or simply allow unsafe HTML
}

function CleanNode(node) {
    // Elements (1) and the document (9) may have child elements
    if ( ( node.nodeType == 1 ||
           node.nodeType == 9 ) && node.hasChildNodes() ) {
        var children = node.childNodes;
        for (var i = 0; i < children.length; i++) {
            CleanNode(children[i]);
        }
    } else if ( node.nodeType == 3 ) {
        // Check for bad words, replacing with del/ins elements
        if (re.test(node.data.toString())) {
            node.parentNode.innerHTML = node.parentNode.innerHTML.replace(re, function (s) {
                return "<del style=\"display:none!important;\">" + s + "</del><ins style=\"text-decoration:none;\">" + sReplace + "</ins>";
            })
        }
    }
}

function CleanDOM() {
    // Timing code
    /* function time(date) {return ((date.getHours() * 60 + date.getMinutes()) * 60 + date.getSeconds()) * 1000 + date.getMilliseconds()}
    var startTime = time(new Date());
    for (var i = 0; (time(new Date()) - startTime) < 10000 && i < 10000; i++) {
        CleanNode(document.body);
    }
    window.alert((time(new Date()) - startTime) / i); */
    
    // Regular cleaning
    CleanNode(document.body);
}

GM_registerMenuCommand("Block bad word...", function() {
    var usNewWordlist, reTest, bREValid = false;
    while (!bREValid) {
        try {
            var usNewWord = window.prompt(scWordlistPrompt);
            if ("" == usNewWord) return;
            if (!usNewWord.match(/[^a-zA-Z]/)) {
                // Add surrounding match stuff to put whole words in
                usNewWord += "(?:ed|s|ing)?" //"\\b[a-zA-Z\\-]*" + usNewWord + "[a-zA-Z\\-]*\\b"
                // Shove in extra special-character matching for central characters
                usNewWord = usNewWord.replace(/(?!^)([a-z])(?=[a-z])/gi, "[$1!@#$$%^&*]");
                usNewWord = "\\b" + usNewWord + "\\b"
            }
            usNewWordlist = sWordlist + "|" + usNewWord;
            reTest = new RegExp(usNewWordlist);
            bREValid = true;
        }
        catch (ex) {
            bREValid = false;
            if ("SyntaxError" != ex.name) throw ex;
        }
    }
    sWordlist = usNewWordlist;          // Valid, apparently
    GM_setValue("wordlist", sWordlist);
    re = new RegExp(sWordlist, "gim");
    CleanDOM();
});
GM_registerMenuCommand("BWA! Options...", function() {
    usReplace = window.prompt(scReplacePrompt, sReplace);
    if (usReplace) {
        sReplace = USReplacementToS(usReplace);
        GM_setValue("replace", sReplace);
    }
})

CleanDOM();